diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 004ad75e2..601c44db4 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -2,7 +2,9 @@ Did you encounter an issue with using Slic3r? Fear not! This guide will help you There is a good chance that the issue, you have encountered, is already reported. Please check the [list of reported issues](https://github.com/alexrj/Slic3r/issues) before creating a new issue report. If you find an existing issue report, feel free to add further information to that report. -If possible, please include the following information when [reporting an issue](https://github.com/alexrj/Slic3r/issues/new): +If you are reporting an issue relating to a release version of Slic3r, it would help a lot if you could also confirm that the behavior is still present in the newest build [(windows)](https://bintray.com/lordofhyphens/Slic3r/slic3r_dev/). Otherwise your issue will be closed as soon as someone else isn't able to reproduce it on current master. + +When possible, please include the following information when [reporting an issue](https://github.com/alexrj/Slic3r/issues/new): * Slic3r version (See the about dialog for the version number. If running from git, please include the git commit ID from `git rev-parse HEAD` also.) * Operating system type + version * Steps to reproduce the issue, including: diff --git a/.gitignore b/.gitignore index 95428a002..089e0a2c0 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ MANIFEST.bak xs/MANIFEST.bak xs/assertlib* .init_bundle.ini +local-lib diff --git a/.travis.yml b/.travis.yml index f25d6bcbd..8e9616ab5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,3 +20,4 @@ addons: - libboost-thread1.55-dev - libboost-system1.55-dev - libboost-filesystem1.55-dev + - liblocal-lib-perl diff --git a/Build.PL b/Build.PL index 06261f77a..93cc7b1e6 100644 --- a/Build.PL +++ b/Build.PL @@ -15,7 +15,6 @@ my %prereqs = qw( File::Basename 0 File::Spec 0 Getopt::Long 0 - Math::PlanePath 53 Module::Build::WithXSpp 0.14 Moo 1.003001 POSIX 0 @@ -108,15 +107,19 @@ EOF if !$cpanm; my @cpanm_args = (); push @cpanm_args, "--sudo" if $sudo; - + + # install local::lib without --local-lib otherwise it's not usable afterwards + if (!eval "use local::lib qw(local-lib); 1") { + my $res = system $cpanm, @cpanm_args, 'local::lib'; + warn "Warning: local::lib is required. You might need to run the `cpanm --sudo local::lib` command in order to install it.\n" + if $res != 0; + } + + push @cpanm_args, ('--local-lib', 'local-lib'); + # make sure our cpanm is updated (old ones don't support the ~ syntax) system $cpanm, @cpanm_args, 'App::cpanminus'; - # install the Windows-compatible Math::Libm - if ($^O eq 'MSWin32' && !eval "use Math::Libm; 1") { - system $cpanm, @cpanm_args, 'https://github.com/alexrj/Math-Libm/tarball/master'; - } - my %modules = (%prereqs, %recommends); foreach my $module (sort keys %modules) { my $version = $modules{$module}; diff --git a/README.md b/README.md index 44a6f53bc..d9f3cff52 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ _Q: Oh cool, a new RepRap slicer?_ A: Yes. -Slic3r [![Build Status](https://travis-ci.org/alexrj/Slic3r.png?branch=master)](https://travis-ci.org/alexrj/Slic3r) [![Build status](https://ci.appveyor.com/api/projects/status/8iqmeat6cj158vo6?svg=true)](https://ci.appveyor.com/project/lordofhyphens/slic3r) +Slic3r [![Build Status](https://travis-ci.org/alexrj/Slic3r.svg?branch=master)](https://travis-ci.org/alexrj/Slic3r) [![Build status](https://ci.appveyor.com/api/projects/status/8iqmeat6cj158vo6?svg=true)](https://ci.appveyor.com/project/lordofhyphens/slic3r) ====== -Prebuilt Win32 builds https://bintray.com/lordofhyphens/Slic3r/slic3r_dev/view +Prebuilt Win32 builds: +* https://bintray.com/lordofhyphens/Slic3r/slic3r_dev/view (from build server) +* https://bintray.com/lordofhyphens/Slic3r/slic3r_dev/1.3.0-dev (manually packaged) Slic3r takes 3D models (STL, OBJ, AMF) and converts them into G-code instructions for 3D printers. It's compatible with any modern printer based on the RepRap toolchain, @@ -18,7 +20,7 @@ See the [project homepage](http://slic3r.org/) at slic3r.org and the The core geometric algorithms and data structures are written in C++, and Perl is used for high-level flow abstraction, GUI and testing. -If you're wondering why Perl, see http://xkcd.com/224/ +If you're wondering why Perl, see https://xkcd.com/224/ The C++ API is public and its use in other projects is encouraged. The goal is to make Slic3r fully modular so that any part of its logic @@ -69,7 +71,7 @@ Sure! You can do the following to find things that are available to help with: * Items in the [TODO](https://github.com/alexrj/Slic3r/wiki/TODO) wiki page. * Please comment in the related github issue that you are working on it so that other people know. * Drop me a line at aar@cpan.org. -* You can also find me (rarely) in #reprap and in #slic3r on [FreeNode](http://webchat.freenode.net) with the nickname _Sound_. Another contributor, _LoH_, is also in both channels. +* You can also find me (rarely) in #reprap and in #slic3r on [FreeNode](https://webchat.freenode.net) with the nickname _Sound_. Another contributor, _LoH_, is also in both channels. * Add an [issue](https://github.com/alexrj/Slic3r/issues) to the github tracker if it isn't already present. Before sending patches and pull requests contact me (preferably through opening a github issue or commenting on an existing, related, issue) to discuss your proposed @@ -134,7 +136,7 @@ The author of the Silk icon set is Mark James. (default: 100,100) --z-offset Additional height in mm to add to vertical coordinates (+/-, default: 0) - --gcode-flavor The type of G-code to generate (reprap/teacup/makerware/sailfish/mach3/machinekit/smoothie/no-extrusion, + --gcode-flavor The type of G-code to generate (reprap/teacup/repetier/makerware/sailfish/mach3/machinekit/smoothie/no-extrusion, default: reprap) --use-relative-e-distances Enable this to get relative E values (default: no) --use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no) @@ -324,6 +326,9 @@ The author of the Silk icon set is Mark James. --duplicate Number of items with auto-arrange (1+, default: 1) --duplicate-grid Number of items with grid arrangement (default: 1,1) --duplicate-distance Distance in mm between copies (default: 6) + --dont-arrange Don't arrange the objects on the build plate. The model coordinates + define the absolute positions on the build plate. + The option --print-center will be ignored. --xy-size-compensation Grow/shrink objects by the configured absolute distance (mm, default: 0) diff --git a/doc/How_to_build_Slic3r.txt b/doc/How_to_build_Slic3r.txt new file mode 100644 index 000000000..0c5c709b0 --- /dev/null +++ b/doc/How_to_build_Slic3r.txt @@ -0,0 +1,273 @@ +How to build Slic3r on Mac OS X 10.7 Lion 64bit +--------------------------------------------- +Vojtech Bubnik, 2016-04-26 + + +1) Install Mac OS X 10.7 Lion 64 bit with X Code +------------------------------------------------ + +One has to build the OSX Slic3r on a real Mac, either directly on the system, or on a virtualized OSX. On Mac, two commercial solutions are available to legally virtualize MacOS on MacOS: +http://www.parallels.com/eu/products/desktop/ +http://www.vmware.com/products/workstation/ + +Installation of a X Code on an OS X 10.7 Lion needs a bit of work. The latest X Code supported by the Lion on a Virtual Box is 4.21. The trouble is, the certificates of the X Code 4.21 installation package expired. One way to work around the certificate is to flatten the installation package by unpacking and repacking it: +pkgutil --expand Foobar.pkg foobar +pkgutil --flatten foobar barfoo.pkg + +The flattened package is available here: +\\rs.prusa\Development\Slic3r-Prusa\installxcode_421_lion_fixed.pkg + +This installer does not install the X Code directly. Instead, it installs another installer with a set of 47 pkg files. These files have their certificates expired as well. You will find the packages on your MacOS here: +/Applications/Install Xcode.app/Contents/Resources/Packages/ + +It is best to flatten them in a loop: +cd /Applications/Install\ Xcode.app/Contents/Resources/Packages/ +for f in *.pkg; do + pkgutil --expand $f /tmp/$f + rm -f $f + pkgutil --flatten /tmp/$f $f +done + +After that, you may finish the installation of Xcode by running +/Applications/Install\ Xcode.app + + +1b) Installing the Xcode on a newer system +------------------------------------------- +You will need to register as an Apple developer on +https://developer.apple.com/ +log in and download and install Xcode +https://developer.apple.com/downloads/ +You will likely need to download and install Xcode Command Line Tools, though the Xcode 4.1 came with the command line tools installed. + + +2) Prepare the development environment +-------------------------------------- + +Install the brew package manager: +http://brew.sh/ +The brew package manager requires the git command line tool. Normally the git tool is installed as part of the Xcode command line tools. +Copy and execute a command line from the top of http://brew.sh/ . It is possible, that the invocation of git fails because of some parameters the old git does not recognize. If so, invoke the git call manually. + +Compile the boost library using brew. Following line compiles a 64bit boost with both static and shared libraries. +brew install boost --universal + +Install dylibbundler tool. The dylibbundler tool serves to collect dependent dynamic libraries and fix their linkage. Execute +brew install dylibbundler + +3) Install perl +--------------- + +We don't want to distribute perl pre-installed on the Mac OS box. First, the system perl installation is not correct on some Mac OS versions, second it is not rellocatable. To compile a 64bit rellocatable perl, we use the perlbrew distribution. The perlbrew distribution installs into a user home directory and it allows switching between multiple versions of perl. +http://perlbrew.pl/ + +First install perlbrew +\curl -L http://install.perlbrew.pl | bash +Then compile the newest perl with the rellocatable @INC path and with multithreading enabled, execute following line: +perlbrew install --threads -Duserelocatableinc --switch perl-5.22.0 +The --switch parameter switches the active perl to the currently compiled one. +Available perl versions could be listed by calling +perlbrew available + +Initialize CPAN, install PAR and PAR::Packer modules +execute cpan command, from the cpan prompt, run +install App::cpanminus +install PAR +install PAR::Packer +quit + +4) Download and install Slic3r +------------------------------ + +git clone git://github.com/alexrj/Slic3r +cd Slic3r +perl Build.PL + +Now Slic3r shall be compiled. You may try to execute +perl slic3r.pl +to get a help screen, or +perl slic3r.pl some_model.stl +to have the model sliced. + +5) Download and compile the GUI libraries needed to execute Slic3r in GUI mode +------------------------------------------------------------------------------ + +For the current Slic3r 1.2.30 code base, set the environment variable SLIC3R_STATIC to link a static version of the boost library: +export SLIC3R_STATIC=1 + +then execute +perl Build.PL --gui +and keep your fingers crossed. The Build.PL script downloads and compiles the WxWidgets 3.0 through a Alien::Wx PERL package. The WxWidget shared libraries will land into +~/perl5/perlbrew/perls/perl-5.22.1/lib/site_perl/5.22.1/darwin-thread-multi-2level/Alien/wxWidgets/ + +On Maverics, we experienced following issue compiling WxPerl: +http://wiki.bolay.net/doku.php?id=acdsn:acdsn-a:mac + +Now you could run the GUI version of slic3r by calling +perl slic3r.pl --gui +If some dependency is missing, the MacOS system will let you know. + +6) Packing the Slic3r +--------------------- + +Perl is an operating system on its own. Many modules are shared among multiple applications and it is difficult to extract a stand-alone application from a perl installation manually. Fortunately, tools are available, which automate the process to some extent. One of the tools is the PAR::Packer. The PAR::Packer tool (pp executable) is able to create a standalone executable for a perl script. The standalone executable contains a PAR archive (a zip file) bundled with a perl interpreter. When executed, the bundled executable will decompress most of the PAR archive into a temp folder. Because of that, we will use the PAR::Packer to resolve and collect the dependencies, but we will create an installer manually. + +The PAR::Packer could analyze the dependencies by a statical analysis, or at a runtime. The statical analysis does not resolve the dynamically loaded modules. On the other side, the statical analysis is pessimistic, therefore it often collects unneeded packages. The dynamic analysis may miss some package, if not all branches of a code are executed. We will try to solely depend on the dynamic analysis to keep the installation size minimal. We may need to develop a protocol or an automatic UI tool to exercise as much as possible from the Slic3r GUI to pack the GUI version reliably. Once a reliable list of dependencies is collected, we may not need the PAR::Packer anymore. + +To create a PAR archive of a command line slic3r, execute +pp -e -p -x slic3r.pl --xargs cube.stl -o slic3r.par +and let the slic3r slice a cube.stl to load the dynamic modules. + +To create a PAR archive of a GUI slic3r, execute +pp -e -p -x slic3r.pl --xargs --gui -o slic3r.par +and exercise the slic3r gui to load all modules. + +Rename the slic3r.par file to slic3r.zip and decompress. Most of the code needed to execute Slic3r is there, only the perl executable is missing for the command line slic3r, and the WxWidgets shared libraries and the liblzma shared library are missing for the GUI version. + +7) Collecting the dependent shared libraries, making the link paths relative +---------------------------------------------------------------------------- + +We have linked Slic3r against a static boost library, therefore the command line slic3r is not dependent on any non-system shared library. The situation is different for the GUI slic3r, which links dynamically against WxWidgets and liblzma. + +The trick developed by Apple to allow rellocable shared libraries is to addres a shared library relatively to the path of the executable by encoding a special token @executable_path at the start of the path. Unfortunately the libraries requried by Slic3r are compiled with absolute paths. + +Once the slic3r.par archive is unpacked, one may list the native shared libraries by +find ./ -name '*.bundle' +and one may list the dependencies by running +otool -L somefile.bundle +Most of the dependencies point to system directores and these dependences are always fulfilled. Dependencies pointing to the WxWidget libraries need to be fixed. These have a form +~/perlbrew/perls/perl-5.22.1/lib/site_perl/5.22.1/darwin-thread-multi-2level/Alien/wxWidgets/osx_cocoa_3_0_2_uni/lib/libwx_*.dylib +and we need to replace them with +@executable_path/../Frameworks/libwx_*.dylib +Another dependency, which needs our attention is +/usr/local/Cellar/xz/5.2.2/lib/liblzma.5.dylib + +Fortunately, a tool dylibbundler was developed to address this problem. +First install dylibbundler by calling +brew dylibbundler + +For some installations, the dylibbundler tool sufficiently fixes all dependencies. Unfortunately, the WxWidgets installation is inconsistent in the versioning, therefore a certain clean-up is required. Namely, the WxWidgets libraries are compiled with the full build number in their file name. For example, the base library is built as libwx_baseu-3.0.0.2.0.dylib and a symlink is created libwx_baseu-3.0.dylib pointing to the full name. Then some of the Wx libraries link against the full name and some against the symlink, leading the dylibbundler to pack both. We solved the problem by whipping up a following script: +\\rs.prusa\Development\Slic3r-Prusa\How_to_build_on_MacOSX_Lion\fix_dependencies.sh + +call +slic3r_dependencies.sh --fix +to collect the shared libraries into Content/Frameworks and to fix their linkage. + +call +slic3r_dependencies.sh --show +to list dependent libraries in a sorted order. All the non-system dependencies shall start with @executable_path after the fix. + + + +8) Packing Slic3r into a dmg image using a bunch of scripts +----------------------------------------------------------- + +Instead of relying on the PAR::Packer to collect the dependencies, we have used PAR::Packer to extract the dependencies, we manually cleaned them up and created an installer script. +\\rs.prusa\Development\Slic3r-Prusa\How_to_build_on_MacOSX_Lion\Slic3r-Build-MacOS +First compile Slic3r, then call build_dmg.sh with a path to the Slic3r source tree as a parameter. +The script will collect all dependencies into Slic3r.app and it will create Slic3r.dmg. +If SLIC3R_GUI variable is defined, a GUI variant of Slic3r will be packed. + + + + +How to build on Windows +----------------------- + +The prefered perl distribution on MS Windows is the Strawberry Perl 5.22.1.3 (32bit) +http://strawberryperl.com/ + +Let it install into c:\strawberry +You may make a copy of the distribution. We recommend to make following copies: +For a release command line only build: c:\strawberry-minimal +For a release GUI build: c:\strawberry-minimal-gui +For a development build with debugging information: c:\strawberry-debug +and to make one of them active by making a directory junction: +mklink /d c:\Strawberry c:\Strawberry-debug + +Building boost: +Slic3r seems to have a trouble with the latest boost 1.60.0 on Windows. Please use 1.59. +Decompress it to +c:\dev\ +otherwise it will not be found by the Build.PL on Windows. You may consider to hack xs\Build.PL with your prefered boost path. +run +bootstrap.bat mingw +b2 toolset=gcc + +Install git command line +https://git-scm.com/ + +Download Slic3r source code +git clone git://github.com/alexrj/Slic3r.git + +Run compilation +cd Slic3r +perl Build.PL +perl Build.PL --gui + +With a bit of luck, you will end up with a working Slic3r including GUI. + + + + +Packing on Windows +------------------ + +Life is easy on Windows. PAR::Packer will help again to collect the dependencies. The basic procedure is the same as for MacOS: + +To create a PAR archive of a command line slic3r, execute +pp -e -p -x slic3r.pl --xargs cube.stl -o slic3r.par +and let the slic3r slice a cube.stl to load the dynamic modules. + +To create a PAR archive of a GUI slic3r, execute +pp -e -p -x slic3r.pl --xargs --gui -o slic3r.par +and exercise the slic3r gui to load all modules. + +The standalone installation is then created from the PAR archive by renaming it into a zip and adding following binaries from c:\strawberry to the root of the extracted zip: +perl5.22.1.exe +perl522.dll +libgcc_s_sjlj-1.dll +libstdc++-6.dll +libwinpthread-1.dll + +The GUI build requires following DLLs in addition: +libglut-0_.dll +wxbase30u_gcc_custom.dll +wxmsw30u_adv_gcc_custom.dll +wxmsw30u_core_gcc_custom.dll +wxmsw30u_gl_gcc_custom.dll +wxmsw30u_html_gcc_custom.dll + +and the var directory with the icons needs to be copied to the destination directory. + +To run the slic3r, move the script\slic3r.pl one level up and create a following tiny windows batch in the root of the unpacked zip: +@perl5.22.1.exe slic3r.pl %* +A windows shortcut may be created for the GUI version. Instead of the perl.exe, it is better to use the wperl.exe to start the GUI Slic3r, because it does not open a text console. + +The strawberry perl is already rellocatable, which means that the perl interpreter will find the perl modules in the lib directory, +and Windows look up the missing DLLs in the directory of the executable, therefore no further rellocation effort is necessary. + + +Packing on Windows, a single EXE solution +----------------------------------------- + +One may try to create a PAR executable for command line slic3r: +pp -M Encode::Locale -M Moo -M Thread::Semaphore -M Slic3r::XS -M Unicode::Normalize -o slic3r.exe slic3r.pl + +One may as well create a PAR executable for Windows GUI: +pp -M Encode::Locale -M Moo -M Thread::Semaphore -M OpenGL -M Slic3r::XS -M Unicode::Normalize -M Wx -M Class::Accessor -M Wx::DND -M Wx::Grid -M Wx::Print -M Wx::Html -M Wx::GLCanvas -M Math::Trig -M threads -M threads::shared -M Thread::Queue -l C:\strawberry\perl\site\lib\auto\Wx\Wx.xs.dll -o -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxbase30u_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_core_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_gl_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_adv_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_html_gcc_custom.dll -o slic3r.exe slic3r.pl + +Remember, that these executables will unpack into a temporary directory. The directory may be declared by setting an environment variable PAR_GLOBAL_TEMP. Otherwise the temporaries are unpacked into +C:\Users\xxx\AppData\Local\Temp\par-xxxxxx + + +Debugging the perl, debugging on Windows +---------------------------------------- + +It is possible to debug perl using the integrated debugger. The EPIC plugin for Eclipse works very well with an older eclipse-SDK-3.6.2. There is a catch though: The Perl debugger does not work correctly with multiple threads running under the Perl interpreter. If that happens, the EPIC plugin gets confused and the debugger stops working. The same happens with the Komodo IDE. + +Debugging the C++ code works fine using the latest Eclipse for C++ and the gdb of MinGW. The gdb packed with the Strawberry distribution does not contain the Python support, so pretty printing of the stl containers only works if another gdb build is used. The one of the QT installation works well. + +It is yet a bit more complicated. The Strawberry MINGW is compiled for a different C++ exception passing model (SJLJ) than the other MINGWs, so one cannot simply combine MINGWs on Windows. For an unknown reason the nice debugger of the QT Creator hangs when debugging the C++ compiled by the Strawberry MINGW. Mabe it is because of the different exception passing models. + +And to disable optimization of the C/C++ code, one has to manually modify Config_heavy.pl in the Perl central installation. The SLIC3R_DEBUG environment variable did not override all the -O2 and -O3 flags that the perl build adds the gcc execution line. diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 8ae73bd18..ecba7ba8a 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -1,3 +1,6 @@ +# This package loads all the non-GUI Slic3r perl packages. +# In addition, it implements utility functions for file handling and threading. + package Slic3r; # Copyright holder: Alessandro Ranellucci @@ -17,6 +20,7 @@ sub debugf { # load threads before Moo as required by it our $have_threads; BEGIN { + # Test, whether the perl was compiled with ithreads support and ithreads actually work. use Config; $have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1"; warn "threads.pm >= 1.96 is required, please update\n" if $have_threads && $threads::VERSION < 1.96; @@ -24,12 +28,20 @@ BEGIN { ### temporarily disable threads if using the broken Moo version use Moo; $have_threads = 0 if $Moo::VERSION == 1.003000; + + # Disable multi threading completely by an environment value. + # This is useful for debugging as the Perl debugger does not work + # in multi-threaded context at all. + # A good interactive perl debugger is the ActiveState Komodo IDE + # or the EPIC http://www.epic-ide.org/ + $have_threads = 0 if (defined($ENV{'SLIC3R_SINGLETHREADED'}) && $ENV{'SLIC3R_SINGLETHREADED'} == 1) } -warn "Running Slic3r under Perl 5.16 is not supported nor recommended\n" +warn "Running Slic3r under Perl 5.16 is neither supported nor recommended\n" if $^V == v5.16; use FindBin; +# Path to the images. our $var = sub { decode_path($FindBin::Bin) . "/var/" . $_[0] }; use Moo 1.003001; @@ -40,13 +52,11 @@ use Slic3r::Config; use Slic3r::ExPolygon; use Slic3r::ExtrusionLoop; use Slic3r::ExtrusionPath; -use Slic3r::Fill; use Slic3r::Flow; use Slic3r::Format::AMF; use Slic3r::Format::OBJ; use Slic3r::Format::STL; use Slic3r::GCode::ArcFitting; -use Slic3r::GCode::CoolingBuffer; use Slic3r::GCode::MotionPlanner; use Slic3r::GCode::PressureRegulator; use Slic3r::GCode::Reader; @@ -72,13 +82,16 @@ use Encode::Locale 1.05; use Encode; use Unicode::Normalize; +# Scaling between the float and integer coordinates. +# Floats are in mm. use constant SCALING_FACTOR => 0.000001; -use constant RESOLUTION => 0.0125; -use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR; +# Resolution to simplify perimeters to. These constants are now used in C++ code only. Better to publish them to Perl from the C++ code. +# use constant RESOLUTION => 0.0125; +# use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR; use constant LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER => 0.15; -use constant INFILL_OVERLAP_OVER_SPACING => 0.3; +# use constant INFILL_OVERLAP_OVER_SPACING => 0.3; -# keep track of threads we created +# Keep track of threads we created. Each thread keeps its own list of threads it spwaned. my @my_threads = (); my @threads : shared = (); my $pause_sema = Thread::Semaphore->new; @@ -114,6 +127,12 @@ sub spawn_thread { return $thread; } +# If the threading is enabled, spawn a set of threads. +# Otherwise run the task on the current thread. +# Used for +# Slic3r::Print::Object->layers->make_perimeters +# Slic3r::Print::Object->layers->make_fill +# Slic3r::Print::SupportMaterial::generate_toolpaths sub parallelize { my %params = @_; @@ -177,7 +196,7 @@ sub thread_cleanup { warn "Calling thread_cleanup() from main thread\n"; return; } - + # prevent destruction of shared objects no warnings 'redefine'; *Slic3r::BridgeDetector::DESTROY = sub {}; @@ -194,6 +213,7 @@ sub thread_cleanup { *Slic3r::ExtrusionLoop::DESTROY = sub {}; *Slic3r::ExtrusionPath::DESTROY = sub {}; *Slic3r::ExtrusionPath::Collection::DESTROY = sub {}; + *Slic3r::Filler::DESTROY = sub {}; *Slic3r::Flow::DESTROY = sub {}; *Slic3r::GCode::DESTROY = sub {}; *Slic3r::GCode::AvoidCrossingPerimeters::DESTROY = sub {}; @@ -219,6 +239,7 @@ sub thread_cleanup { *Slic3r::Print::DESTROY = sub {}; *Slic3r::Print::Object::DESTROY = sub {}; *Slic3r::Print::Region::DESTROY = sub {}; + *Slic3r::SLAPrint::DESTROY = sub {}; *Slic3r::Surface::DESTROY = sub {}; *Slic3r::Surface::Collection::DESTROY = sub {}; *Slic3r::TriangleMesh::DESTROY = sub {}; @@ -267,6 +288,12 @@ sub resume_all_threads { $pause_sema->up; } +# Convert a Unicode path to a file system locale. +# The encoding is (from Encode::Locale POD): +# Alias | Windows | Mac OS X | POSIX +# locale_fs | ANSI | UTF-8 | nl_langinfo +# where nl_langinfo is en-US.UTF-8 on a modern Linux as well. +# So this conversion seems to make the most sense on Windows. sub encode_path { my ($path) = @_; @@ -276,6 +303,7 @@ sub encode_path { return $path; } +# Convert a path coded by a file system locale to Unicode. sub decode_path { my ($path) = @_; @@ -291,6 +319,7 @@ sub decode_path { return $path; } +# Open a file by converting $filename to local file system locales. sub open { my ($fh, $mode, $filename) = @_; return CORE::open $$fh, $mode, encode_path($filename); diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 366c96f2c..a399a8333 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -1,3 +1,6 @@ +# Extends C++ class Slic3r::DynamicPrintConfig +# This perl class does not keep any perl class variables, +# all the storage is handled by the underlying C++ code. package Slic3r::Config; use strict; use warnings; @@ -11,6 +14,8 @@ our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_ rotate scale duplicate_grid start_perimeters_at_concave_points start_perimeters_at_non_overhang randomize_start seal_position bed_size print_center g0); +# C++ Slic3r::PrintConfigDef exported as a Perl hash of hashes. +# The C++ counterpart is a constant singleton. our $Options = print_config_def(); # overwrite the hard-coded readonly value (this information is not available in XS) @@ -24,6 +29,8 @@ $Options->{threads}{readonly} = !$Slic3r::have_threads; } } +# Fill in the underlying C++ Slic3r::DynamicPrintConfig with the content of the defaults +# provided by the C++ class Slic3r::FullPrintConfig. sub new_from_defaults { my $class = shift; my (@opt_keys) = @_; @@ -39,12 +46,16 @@ sub new_from_defaults { return $self; } +# From command line parameters sub new_from_cli { my $class = shift; my %args = @_; + # Delete hash keys with undefined value. delete $args{$_} for grep !defined $args{$_}, keys %args; + # Replace the start_gcode, end_gcode ... hash values + # with the content of the files they reference. for (qw(start end layer toolchange)) { my $opt_key = "${_}_gcode"; if ($args{$opt_key}) { @@ -57,7 +68,7 @@ sub new_from_cli { } } } - + my $self = $class->new; foreach my $opt_key (keys %args) { my $opt_def = $Options->{$opt_key}; @@ -83,6 +94,8 @@ sub merge { return $config; } +# Load a flat ini file without a category into the underlying C++ Slic3r::DynamicConfig class, +# convert legacy configuration names. sub load { my $class = shift; my ($file) = @_; @@ -100,6 +113,8 @@ sub save { return $self->_save(Slic3r::encode_path($file)); } +# Deserialize a perl hash into the underlying C++ Slic3r::DynamicConfig class, +# convert legacy configuration names. sub load_ini_hash { my $class = shift; my ($ini_hash) = @_; @@ -184,6 +199,8 @@ sub _handle_legacy { return ($opt_key, $value); } +# Create a hash of hashes from the underlying C++ Slic3r::DynamicPrintConfig. +# The first hash key is '_' meaning no category. sub as_ini { my ($self) = @_; @@ -308,7 +325,7 @@ sub validate { my $max_nozzle_diameter = max(@{ $self->nozzle_diameter }); die "Invalid extrusion width (too large)\n" if defined first { $_ > 10 * $max_nozzle_diameter } - map $self->get_abs_value_over("${_}_extrusion_width", $self->layer_height), + map $self->get_abs_value_over("${_}_extrusion_width", $max_nozzle_diameter), qw(perimeter infill solid_infill top_infill support_material first_layer); } @@ -345,6 +362,7 @@ sub validate { # CLASS METHODS: +# Write a "Windows" style ini file with categories enclosed in squre brackets. sub write_ini { my $class = shift; my ($file, $ini) = @_; @@ -363,6 +381,10 @@ sub write_ini { close $fh; } +# Parse a "Windows" style ini file with categories enclosed in squre brackets. +# Returns a hash of hashes over strings. +# {category}{name}=value +# Non-categorized entries are stored under a category '_'. sub read_ini { my $class = shift; my ($file) = @_; @@ -399,5 +421,6 @@ sub Slic3r::Config::Print::new { Slic3r::Config::Static::new_PrintConfig } sub Slic3r::Config::PrintObject::new { Slic3r::Config::Static::new_PrintObjectConfig } sub Slic3r::Config::PrintRegion::new { Slic3r::Config::Static::new_PrintRegionConfig } sub Slic3r::Config::Full::new { Slic3r::Config::Static::new_FullPrintConfig } +sub Slic3r::Config::SLAPrint::new { Slic3r::Config::Static::new_SLAPrintConfig } 1; diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm deleted file mode 100644 index 8c63fde59..000000000 --- a/lib/Slic3r/Fill.pm +++ /dev/null @@ -1,294 +0,0 @@ -package Slic3r::Fill; -use Moo; - -use List::Util qw(max); -use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Fill::3DHoneycomb; -use Slic3r::Fill::Base; -use Slic3r::Fill::Concentric; -use Slic3r::Fill::Honeycomb; -use Slic3r::Fill::PlanePath; -use Slic3r::Fill::Rectilinear; -use Slic3r::Flow ':roles'; -use Slic3r::Geometry qw(X Y PI scale chained_path deg2rad); -use Slic3r::Geometry::Clipper qw(union union_ex diff diff_ex intersection_ex offset offset2); -use Slic3r::Surface ':types'; - - -has 'bounding_box' => (is => 'ro', required => 0); -has 'fillers' => (is => 'rw', default => sub { {} }); - -our %FillTypes = ( - archimedeanchords => 'Slic3r::Fill::ArchimedeanChords', - rectilinear => 'Slic3r::Fill::Rectilinear', - grid => 'Slic3r::Fill::Grid', - flowsnake => 'Slic3r::Fill::Flowsnake', - octagramspiral => 'Slic3r::Fill::OctagramSpiral', - hilbertcurve => 'Slic3r::Fill::HilbertCurve', - line => 'Slic3r::Fill::Line', - concentric => 'Slic3r::Fill::Concentric', - honeycomb => 'Slic3r::Fill::Honeycomb', - '3dhoneycomb' => 'Slic3r::Fill::3DHoneycomb', -); - -sub filler { - my $self = shift; - my ($filler) = @_; - - if (!ref $self) { - return $FillTypes{$filler}->new; - } - - $self->fillers->{$filler} ||= $FillTypes{$filler}->new( - bounding_box => $self->bounding_box, - ); - return $self->fillers->{$filler}; -} - -sub make_fill { - my $self = shift; - my ($layerm) = @_; - - Slic3r::debugf "Filling layer %d:\n", $layerm->layer->id; - - my $fill_density = $layerm->region->config->fill_density; - my $infill_flow = $layerm->flow(FLOW_ROLE_INFILL); - my $solid_infill_flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL); - my $top_solid_infill_flow = $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL); - - my @surfaces = (); - - # merge adjacent surfaces - # in case of bridge surfaces, the ones with defined angle will be attached to the ones - # without any angle (shouldn't this logic be moved to process_external_surfaces()?) - { - my @surfaces_with_bridge_angle = grep { $_->bridge_angle >= 0 } @{$layerm->fill_surfaces}; - - # group surfaces by distinct properties - my @groups = @{$layerm->fill_surfaces->group}; - - # merge compatible groups (we can generate continuous infill for them) - { - # cache flow widths and patterns used for all solid groups - # (we'll use them for comparing compatible groups) - my @is_solid = my @fw = my @pattern = (); - for (my $i = 0; $i <= $#groups; $i++) { - # we can only merge solid non-bridge surfaces, so discard - # non-solid surfaces - if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->layer->id == 0)) { - $is_solid[$i] = 1; - $fw[$i] = ($groups[$i][0]->surface_type == S_TYPE_TOP) - ? $top_solid_infill_flow->width - : $solid_infill_flow->width; - $pattern[$i] = $groups[$i][0]->is_external - ? $layerm->region->config->external_fill_pattern - : 'rectilinear'; - } else { - $is_solid[$i] = 0; - $fw[$i] = 0; - $pattern[$i] = 'none'; - } - } - - # loop through solid groups - for (my $i = 0; $i <= $#groups; $i++) { - next if !$is_solid[$i]; - - # find compatible groups and append them to this one - for (my $j = $i+1; $j <= $#groups; $j++) { - next if !$is_solid[$j]; - - if ($fw[$i] == $fw[$j] && $pattern[$i] eq $pattern[$j]) { - # groups are compatible, merge them - push @{$groups[$i]}, @{$groups[$j]}; - splice @groups, $j, 1; - splice @is_solid, $j, 1; - splice @fw, $j, 1; - splice @pattern, $j, 1; - } - } - } - } - - # give priority to bridges - @groups = sort { ($a->[0]->bridge_angle >= 0) ? -1 : 0 } @groups; - - foreach my $group (@groups) { - my $union_p = union([ map $_->p, @$group ], 1); - - # subtract surfaces having a defined bridge_angle from any other - if (@surfaces_with_bridge_angle && $group->[0]->bridge_angle < 0) { - $union_p = diff( - $union_p, - [ map $_->p, @surfaces_with_bridge_angle ], - 1, - ); - } - - # subtract any other surface already processed - my $union = diff_ex( - $union_p, - [ map $_->p, @surfaces ], - 1, - ); - - push @surfaces, map $group->[0]->clone(expolygon => $_), @$union; - } - } - - # we need to detect any narrow surfaces that might collapse - # when adding spacing below - # such narrow surfaces are often generated in sloping walls - # by bridge_over_infill() and combine_infill() as a result of the - # subtraction of the combinable area from the layer infill area, - # which leaves small areas near the perimeters - # we are going to grow such regions by overlapping them with the void (if any) - # TODO: detect and investigate whether there could be narrow regions without - # any void neighbors - { - my $distance_between_surfaces = max( - $infill_flow->scaled_spacing, - $solid_infill_flow->scaled_spacing, - $top_solid_infill_flow->scaled_spacing, - ); - my $collapsed = diff( - [ map @{$_->expolygon}, @surfaces ], - offset2([ map @{$_->expolygon}, @surfaces ], -$distance_between_surfaces/2, +$distance_between_surfaces/2), - 1, - ); - push @surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => S_TYPE_INTERNALSOLID, - ), @{intersection_ex( - offset($collapsed, $distance_between_surfaces), - [ - (map @{$_->expolygon}, grep $_->surface_type == S_TYPE_INTERNALVOID, @surfaces), - (@$collapsed), - ], - 1, - )}; - } - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("fill_" . $layerm->print_z . ".svg", - expolygons => [ map $_->expolygon, grep !$_->is_solid, @surfaces ], - red_expolygons => [ map $_->expolygon, grep $_->is_solid, @surfaces ], - ); - } - - my @fills = (); - SURFACE: foreach my $surface (@surfaces) { - next if $surface->surface_type == S_TYPE_INTERNALVOID; - my $filler = $layerm->region->config->fill_pattern; - my $density = $fill_density; - my $role = ($surface->surface_type == S_TYPE_TOP) ? FLOW_ROLE_TOP_SOLID_INFILL - : $surface->is_solid ? FLOW_ROLE_SOLID_INFILL - : FLOW_ROLE_INFILL; - my $is_bridge = $layerm->layer->id > 0 && $surface->is_bridge; - my $is_solid = $surface->is_solid; - - if ($surface->is_solid) { - $density = 100; - $filler = 'rectilinear'; - if ($surface->is_external && !$is_bridge) { - $filler = $layerm->region->config->external_fill_pattern; - } - } else { - next SURFACE unless $density > 0; - } - - # get filler object - my $f = $self->filler($filler); - - # calculate the actual flow we'll be using for this infill - my $h = $surface->thickness == -1 ? $layerm->layer->height : $surface->thickness; - my $flow = $layerm->region->flow( - $role, - $h, - $is_bridge || $f->use_bridge_flow, - $layerm->layer->id == 0, - -1, - $layerm->layer->object, - ); - - # calculate flow spacing for infill pattern generation - my $using_internal_flow = 0; - if (!$is_solid && !$is_bridge) { - # it's internal infill, so we can calculate a generic flow spacing - # for all layers, for avoiding the ugly effect of - # misaligned infill on first layer because of different extrusion width and - # layer height - my $internal_flow = $layerm->region->flow( - FLOW_ROLE_INFILL, - $layerm->layer->object->config->layer_height, # TODO: handle infill_every_layers? - 0, # no bridge - 0, # no first layer - -1, # auto width - $layerm->layer->object, - ); - $f->spacing($internal_flow->spacing); - $using_internal_flow = 1; - } else { - $f->spacing($flow->spacing); - } - - $f->layer_id($layerm->layer->id); - $f->z($layerm->layer->print_z); - $f->angle(deg2rad($layerm->region->config->fill_angle)); - $f->loop_clipping(scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); - - # apply half spacing using this flow's own spacing and generate infill - my @polylines = map $f->fill_surface( - $_, - density => $density/100, - layer_height => $h, - ), @{ $surface->offset(-scale($f->spacing)/2) }; - - next unless @polylines; - - # calculate actual flow from spacing (which might have been adjusted by the infill - # pattern generator) - if ($using_internal_flow) { - # if we used the internal flow we're not doing a solid infill - # so we can safely ignore the slight variation that might have - # been applied to $f->flow_spacing - } else { - $flow = Slic3r::Flow->new_from_spacing( - spacing => $f->spacing, - nozzle_diameter => $flow->nozzle_diameter, - layer_height => $h, - bridge => $is_bridge || $f->use_bridge_flow, - ); - } - my $mm3_per_mm = $flow->mm3_per_mm; - - # save into layer - { - my $role = $is_bridge ? EXTR_ROLE_BRIDGE - : $is_solid ? (($surface->surface_type == S_TYPE_TOP) ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL) - : EXTR_ROLE_FILL; - - push @fills, my $collection = Slic3r::ExtrusionPath::Collection->new; - $collection->no_sort($f->no_sort); - $collection->append( - map Slic3r::ExtrusionPath->new( - polyline => $_, - role => $role, - mm3_per_mm => $mm3_per_mm, - width => $flow->width, - height => $flow->height, - ), @polylines, - ); - } - } - - # add thin fill regions - foreach my $thin_fill (@{$layerm->thin_fills}) { - push @fills, Slic3r::ExtrusionPath::Collection->new($thin_fill); - } - - return @fills; -} - -1; diff --git a/lib/Slic3r/Fill/3DHoneycomb.pm b/lib/Slic3r/Fill/3DHoneycomb.pm deleted file mode 100644 index 3bf7e547f..000000000 --- a/lib/Slic3r/Fill/3DHoneycomb.pm +++ /dev/null @@ -1,230 +0,0 @@ -package Slic3r::Fill::3DHoneycomb; -use Moo; - -extends 'Slic3r::Fill::Base'; - -use POSIX qw(ceil fmod); -use Slic3r::Geometry qw(scale scaled_epsilon); -use Slic3r::Geometry::Clipper qw(intersection_pl); - -# require bridge flow since most of this pattern hangs in air -sub use_bridge_flow { 1 } - -sub fill_surface { - my ($self, $surface, %params) = @_; - - my $expolygon = $surface->expolygon; - my $bb = $expolygon->bounding_box; - my $size = $bb->size; - - my $distance = scale($self->spacing) / $params{density}; - - # align bounding box to a multiple of our honeycomb grid module - # (a module is 2*$distance since one $distance half-module is - # growing while the other $distance half-module is shrinking) - { - my $min = $bb->min_point; - $min->translate( - -($bb->x_min % (2*$distance)), - -($bb->y_min % (2*$distance)), - ); - $bb->merge_point($min); - } - - # generate pattern - my @polylines = map Slic3r::Polyline->new(@$_), - makeGrid( - scale($self->z), - $distance, - ceil($size->x / $distance) + 1, - ceil($size->y / $distance) + 1, #// - (($self->layer_id / $surface->thickness_layers) % 2) + 1, - ); - - # move pattern in place - $_->translate($bb->x_min, $bb->y_min) for @polylines; - - # clip pattern to boundaries - @polylines = @{intersection_pl(\@polylines, \@$expolygon)}; - - # connect lines - unless ($params{dont_connect} || !@polylines) { # prevent calling leftmost_point() on empty collections - my ($expolygon_off) = @{$expolygon->offset_ex(scaled_epsilon)}; - my $collection = Slic3r::Polyline::Collection->new(@polylines); - @polylines = (); - foreach my $polyline (@{$collection->chained_path_from($collection->leftmost_point, 0)}) { - # try to append this polyline to previous one if any - if (@polylines) { - my $line = Slic3r::Line->new($polylines[-1]->last_point, $polyline->first_point); - if ($line->length <= 1.5*$distance && $expolygon_off->contains_line($line)) { - $polylines[-1]->append_polyline($polyline); - next; - } - } - - # make a clone before $collection goes out of scope - push @polylines, $polyline->clone; - } - } - - # TODO: return ExtrusionLoop objects to get better chained paths - return @polylines; -} - - -=head1 DESCRIPTION - -Creates a contiguous sequence of points at a specified height that make -up a horizontal slice of the edges of a space filling truncated -octahedron tesselation. The octahedrons are oriented so that the -square faces are in the horizontal plane with edges parallel to the X -and Y axes. - -Credits: David Eccles (gringer). - -=head2 makeGrid(z, gridSize, gridWidth, gridHeight, curveType) - -Generate a set of curves (array of array of 2d points) that describe a -horizontal slice of a truncated regular octahedron with a specified -grid square size. - -=cut - -sub makeGrid { - my ($z, $gridSize, $gridWidth, $gridHeight, $curveType) = @_; - my $scaleFactor = $gridSize; - my $normalisedZ = $z / $scaleFactor; - my @points = makeNormalisedGrid($normalisedZ, $gridWidth, $gridHeight, $curveType); - foreach my $lineRef (@points) { - foreach my $pointRef (@$lineRef) { - $pointRef->[0] *= $scaleFactor; - $pointRef->[1] *= $scaleFactor; - } - } - return @points; -} - -=head1 FUNCTIONS -=cut - -=head2 colinearPoints(offset, gridLength) - -Generate an array of points that are in the same direction as the -basic printing line (i.e. Y points for columns, X points for rows) - -Note: a negative offset only causes a change in the perpendicular -direction - -=cut - -sub colinearPoints { - my ($offset, $baseLocation, $gridLength) = @_; - - my @points = (); - push @points, $baseLocation - abs($offset/2); - for (my $i = 0; $i < $gridLength; $i++) { - push @points, $baseLocation + $i + abs($offset/2); - push @points, $baseLocation + ($i+1) - abs($offset/2); - } - push @points, $baseLocation + $gridLength + abs($offset/2); - return @points; -} - -=head2 colinearPoints(offset, baseLocation, gridLength) - -Generate an array of points for the dimension that is perpendicular to -the basic printing line (i.e. X points for columns, Y points for rows) - -=cut - -sub perpendPoints { - my ($offset, $baseLocation, $gridLength) = @_; - - my @points = (); - my $side = 2*(($baseLocation) % 2) - 1; - push @points, $baseLocation - $offset/2 * $side; - for (my $i = 0; $i < $gridLength; $i++) { - $side = 2*(($i+$baseLocation) % 2) - 1; - push @points, $baseLocation + $offset/2 * $side; - push @points, $baseLocation + $offset/2 * $side; - } - push @points, $baseLocation - $offset/2 * $side; - - return @points; -} - -=head2 trim(pointArrayRef, minX, minY, maxX, maxY) - -Trims an array of points to specified rectangular limits. Point -components that are outside these limits are set to the limits. - -=cut - -sub trim { - my ($pointArrayRef, $minX, $minY, $maxX, $maxY) = @_; - - foreach (@$pointArrayRef) { - $_->[0] = ($_->[0] < $minX) ? $minX : (($_->[0] > $maxX) ? $maxX : $_->[0]); - $_->[1] = ($_->[1] < $minY) ? $minY : (($_->[1] > $maxY) ? $maxY : $_->[1]); - } -} - -=head2 makeNormalisedGrid(z, gridWidth, gridHeight, curveType) - -Generate a set of curves (array of array of 2d points) that describe a -horizontal slice of a truncated regular octahedron with edge length 1. - -curveType specifies which lines to print, 1 for vertical lines -(columns), 2 for horizontal lines (rows), and 3 for both. - -=cut - -sub makeNormalisedGrid { - my ($z, $gridWidth, $gridHeight, $curveType) = @_; - - ## offset required to create a regular octagram - my $octagramGap = 0.5; - - # sawtooth wave function for range f($z) = [-$octagramGap .. $octagramGap] - my $a = sqrt(2); # period - my $wave = abs(fmod($z, $a) - $a/2)/$a*4 - 1; - my $offset = $wave * $octagramGap; - - my @points = (); - if (($curveType & 1) != 0) { - for (my $x = 0; $x <= $gridWidth; $x++) { - my @xPoints = perpendPoints($offset, $x, $gridHeight); - my @yPoints = colinearPoints($offset, 0, $gridHeight); - # This is essentially @newPoints = zip(@xPoints, @yPoints) - my @newPoints = map [ $xPoints[$_], $yPoints[$_] ], 0..$#xPoints; - - # trim points to grid edges - #trim(\@newPoints, 0, 0, $gridWidth, $gridHeight); - - if ($x % 2 == 0){ - push @points, [ @newPoints ]; - } else { - push @points, [ reverse @newPoints ]; - } - } - } - if (($curveType & 2) != 0) { - for (my $y = 0; $y <= $gridHeight; $y++) { - my @xPoints = colinearPoints($offset, 0, $gridWidth); - my @yPoints = perpendPoints($offset, $y, $gridWidth); - my @newPoints = map [ $xPoints[$_], $yPoints[$_] ], 0..$#xPoints; - - # trim points to grid edges - #trim(\@newPoints, 0, 0, $gridWidth, $gridHeight); - - if ($y % 2 == 0) { - push @points, [ @newPoints ]; - } else { - push @points, [ reverse @newPoints ]; - } - } - } - return @points; -} - -1; diff --git a/lib/Slic3r/Fill/Base.pm b/lib/Slic3r/Fill/Base.pm deleted file mode 100644 index 75c8e03e6..000000000 --- a/lib/Slic3r/Fill/Base.pm +++ /dev/null @@ -1,91 +0,0 @@ -package Slic3r::Fill::Base; -use Moo; - -has 'layer_id' => (is => 'rw'); -has 'z' => (is => 'rw'); # in unscaled coordinates -has 'angle' => (is => 'rw'); # in radians, ccw, 0 = East -has 'spacing' => (is => 'rw'); # in unscaled coordinates -has 'loop_clipping' => (is => 'rw', default => sub { 0 }); # in scaled coordinates -has 'bounding_box' => (is => 'ro', required => 0); # Slic3r::Geometry::BoundingBox object - -sub adjust_solid_spacing { - my $self = shift; - my %params = @_; - - my $number_of_lines = int($params{width} / $params{distance}) + 1; - return $params{distance} if $number_of_lines <= 1; - - my $extra_space = $params{width} % $params{distance}; - return $params{distance} + $extra_space / ($number_of_lines - 1); -} - -sub no_sort { 0 } -sub use_bridge_flow { 0 } - - -package Slic3r::Fill::WithDirection; -use Moo::Role; - -use Slic3r::Geometry qw(PI rad2deg); - -sub angles () { [0, PI/2] } - -sub infill_direction { - my $self = shift; - my ($surface) = @_; - - if (!defined $self->angle) { - warn "Using undefined infill angle"; - $self->angle(0); - } - - # set infill angle - my (@rotate); - $rotate[0] = $self->angle; - $rotate[1] = $self->bounding_box - ? $self->bounding_box->center - : $surface->expolygon->bounding_box->center; - my $shift = $rotate[1]->clone; - - if (defined $self->layer_id) { - # alternate fill direction - my $layer_num = $self->layer_id / $surface->thickness_layers; - my $angle = $self->angles->[$layer_num % @{$self->angles}]; - $rotate[0] = $self->angle + $angle if $angle; - } - - # use bridge angle - if ($surface->bridge_angle >= 0) { - Slic3r::debugf "Filling bridge with angle %d\n", rad2deg($surface->bridge_angle); - $rotate[0] = $surface->bridge_angle; - } - - $rotate[0] += PI/2; - $shift->rotate(@rotate); - return [\@rotate, $shift]; -} - -# this method accepts any object that implements rotate() and translate() -sub rotate_points { - my $self = shift; - my ($expolygon, $rotate_vector) = @_; - - # rotate points - my ($rotate, $shift) = @$rotate_vector; - $rotate = [ -$rotate->[0], $rotate->[1] ]; - $expolygon->rotate(@$rotate); - $expolygon->translate(@$shift); -} - -sub rotate_points_back { - my $self = shift; - my ($paths, $rotate_vector) = @_; - - my ($rotate, $shift) = @$rotate_vector; - $shift = [ map -$_, @$shift ]; - - $_->translate(@$shift) for @$paths; - $_->rotate(@$rotate) for @$paths; -} - -1; diff --git a/lib/Slic3r/Fill/Concentric.pm b/lib/Slic3r/Fill/Concentric.pm deleted file mode 100644 index ca1837c4e..000000000 --- a/lib/Slic3r/Fill/Concentric.pm +++ /dev/null @@ -1,57 +0,0 @@ -package Slic3r::Fill::Concentric; -use Moo; - -extends 'Slic3r::Fill::Base'; - -use Slic3r::Geometry qw(scale unscale X); -use Slic3r::Geometry::Clipper qw(offset offset2 union_pt_chained); - -sub no_sort { 1 } - -sub fill_surface { - my $self = shift; - my ($surface, %params) = @_; - - # no rotation is supported for this infill pattern - - my $expolygon = $surface->expolygon; - my $bounding_box = $expolygon->bounding_box; - - my $min_spacing = scale($self->spacing); - my $distance = $min_spacing / $params{density}; - - if ($params{density} == 1 && !$params{dont_adjust}) { - $distance = $self->adjust_solid_spacing( - width => $bounding_box->size->[X], - distance => $distance, - ); - $self->spacing(unscale $distance); - } - - my @loops = my @last = map $_->clone, @$expolygon; - while (@last) { - push @loops, @last = @{offset2(\@last, -($distance + 0.5*$min_spacing), +0.5*$min_spacing)}; - } - - # generate paths from the outermost to the innermost, to avoid - # adhesion problems of the first central tiny loops - @loops = map Slic3r::Polygon->new(@$_), - reverse @{union_pt_chained(\@loops)}; - - # split paths using a nearest neighbor search - my @paths = (); - my $last_pos = Slic3r::Point->new(0,0); - foreach my $loop (@loops) { - push @paths, $loop->split_at_index($last_pos->nearest_point_index(\@$loop)); - $last_pos = $paths[-1]->last_point; - } - - # clip the paths to prevent the extruder from getting exactly on the first point of the loop - $_->clip_end($self->loop_clipping) for @paths; - @paths = grep $_->is_valid, @paths; # remove empty paths (too short, thus eaten by clipping) - - # TODO: return ExtrusionLoop objects to get better chained paths - return @paths; -} - -1; diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm deleted file mode 100644 index b0fbd65ff..000000000 --- a/lib/Slic3r/Fill/Honeycomb.pm +++ /dev/null @@ -1,129 +0,0 @@ -package Slic3r::Fill::Honeycomb; -use Moo; - -extends 'Slic3r::Fill::Base'; -with qw(Slic3r::Fill::WithDirection); - -has 'cache' => (is => 'rw', default => sub {{}}); - -use Slic3r::Geometry qw(PI X Y MIN MAX scale scaled_epsilon); -use Slic3r::Geometry::Clipper qw(intersection intersection_pl); - -sub angles () { [0, PI/3, PI/3*2] } - -sub fill_surface { - my $self = shift; - my ($surface, %params) = @_; - - my $rotate_vector = $self->infill_direction($surface); - - # cache hexagons math - my $cache_id = sprintf "d%s_s%s", $params{density}, $self->spacing; - my $m; - if (!($m = $self->cache->{$cache_id})) { - $m = $self->cache->{$cache_id} = {}; - my $min_spacing = scale($self->spacing); - $m->{distance} = $min_spacing / $params{density}; - $m->{hex_side} = $m->{distance} / (sqrt(3)/2); - $m->{hex_width} = $m->{distance} * 2; # $m->{hex_width} == $m->{hex_side} * sqrt(3); - my $hex_height = $m->{hex_side} * 2; - $m->{pattern_height} = $hex_height + $m->{hex_side}; - $m->{y_short} = $m->{distance} * sqrt(3)/3; - $m->{x_offset} = $min_spacing / 2; - $m->{y_offset} = $m->{x_offset} * sqrt(3)/3; - $m->{hex_center} = Slic3r::Point->new($m->{hex_width}/2, $m->{hex_side}); - } - - my @polygons = (); - { - # adjust actual bounding box to the nearest multiple of our hex pattern - # and align it so that it matches across layers - - my $bounding_box = $surface->expolygon->bounding_box; - { - # rotate bounding box according to infill direction - my $bb_polygon = $bounding_box->polygon; - $bb_polygon->rotate($rotate_vector->[0][0], $m->{hex_center}); - $bounding_box = $bb_polygon->bounding_box; - - # extend bounding box so that our pattern will be aligned with other layers - # $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one - $bounding_box->merge_point(Slic3r::Point->new( - $bounding_box->x_min - ($bounding_box->x_min % $m->{hex_width}), - $bounding_box->y_min - ($bounding_box->y_min % $m->{pattern_height}), - )); - } - - my $x = $bounding_box->x_min; - while ($x <= $bounding_box->x_max) { - my $p = []; - - my @x = ($x + $m->{x_offset}, $x + $m->{distance} - $m->{x_offset}); - for (1..2) { - @$p = reverse @$p; # turn first half upside down - my @p = (); - for (my $y = $bounding_box->y_min; $y <= $bounding_box->y_max; $y += $m->{y_short} + $m->{hex_side} + $m->{y_short} + $m->{hex_side}) { - push @$p, - [ $x[1], $y + $m->{y_offset} ], - [ $x[0], $y + $m->{y_short} - $m->{y_offset} ], - [ $x[0], $y + $m->{y_short} + $m->{hex_side} + $m->{y_offset} ], - [ $x[1], $y + $m->{y_short} + $m->{hex_side} + $m->{y_short} - $m->{y_offset} ], - [ $x[1], $y + $m->{y_short} + $m->{hex_side} + $m->{y_short} + $m->{hex_side} + $m->{y_offset} ]; - } - @x = map $_ + $m->{distance}, reverse @x; # draw symmetrical pattern - $x += $m->{distance}; - } - - push @polygons, Slic3r::Polygon->new(@$p); - } - - $_->rotate(-$rotate_vector->[0][0], $m->{hex_center}) for @polygons; - } - - my @paths; - if ($params{complete} || 1) { - # we were requested to complete each loop; - # in this case we don't try to make more continuous paths - @paths = map $_->split_at_first_point, - @{intersection([ $surface->p ], \@polygons)}; - - } else { - # consider polygons as polylines without re-appending the initial point: - # this cuts the last segment on purpose, so that the jump to the next - # path is more straight - @paths = @{intersection_pl( - [ map Slic3r::Polyline->new(@$_), @polygons ], - [ @{$surface->expolygon} ], - )}; - - # connect paths - if (@paths) { # prevent calling leftmost_point() on empty collections - my $collection = Slic3r::Polyline::Collection->new(@paths); - @paths = (); - foreach my $path (@{$collection->chained_path_from($collection->leftmost_point, 0)}) { - if (@paths) { - # distance between first point of this path and last point of last path - my $distance = $paths[-1]->last_point->distance_to($path->first_point); - - if ($distance <= $m->{hex_width}) { - $paths[-1]->append_polyline($path); - next; - } - } - - # make a clone before $collection goes out of scope - push @paths, $path->clone; - } - } - - # clip paths again to prevent connection segments from crossing the expolygon boundaries - @paths = @{intersection_pl( - \@paths, - [ map @$_, @{$surface->expolygon->offset_ex(scaled_epsilon)} ], - )}; - } - - return @paths; -} - -1; diff --git a/lib/Slic3r/Fill/PlanePath.pm b/lib/Slic3r/Fill/PlanePath.pm deleted file mode 100644 index 556835ec4..000000000 --- a/lib/Slic3r/Fill/PlanePath.pm +++ /dev/null @@ -1,118 +0,0 @@ -package Slic3r::Fill::PlanePath; -use Moo; - -extends 'Slic3r::Fill::Base'; -with qw(Slic3r::Fill::WithDirection); - -use Slic3r::Geometry qw(scale X1 Y1 X2 Y2); -use Slic3r::Geometry::Clipper qw(intersection_pl); - -sub angles () { [0] } -sub multiplier () { 1 } - -sub process_polyline {} - -sub fill_surface { - my $self = shift; - my ($surface, %params) = @_; - - # rotate polygons - my $expolygon = $surface->expolygon->clone; - my $rotate_vector = $self->infill_direction($surface); - $self->rotate_points($expolygon, $rotate_vector); - - my $distance_between_lines = scale($self->spacing) / $params{density} * $self->multiplier; - - # align infill across layers using the object's bounding box - my $bb_polygon = $self->bounding_box->polygon; - $self->rotate_points($bb_polygon, $rotate_vector); - my $bounding_box = $bb_polygon->bounding_box; - - (ref $self) =~ /::([^:]+)$/; - my $path = "Math::PlanePath::$1"->new; - - my $translate = Slic3r::Point->new(0,0); # vector - if ($path->x_negative || $path->y_negative) { - # if the curve extends on both positive and negative coordinate space, - # center our expolygon around origin - $translate = $bounding_box->center->negative; - } else { - # if the curve does not extend in negative coordinate space, - # move expolygon entirely in positive coordinate space - $translate = $bounding_box->min_point->negative; - } - $expolygon->translate(@$translate); - $bounding_box->translate(@$translate); - - my ($n_lo, $n_hi) = $path->rect_to_n_range( - map { $_ / $distance_between_lines } - @{$bounding_box->min_point}, - @{$bounding_box->max_point}, - ); - - my $polyline = Slic3r::Polyline->new( - map [ map { $_ * $distance_between_lines } $path->n_to_xy($_) ], ($n_lo..$n_hi) - ); - return {} if @$polyline <= 1; - - $self->process_polyline($polyline, $bounding_box); - - my @paths = @{intersection_pl([$polyline], \@$expolygon)}; - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("fill.svg", - no_arrows => 1, - polygons => \@$expolygon, - green_polygons => [ $bounding_box->polygon ], - polylines => [ $polyline ], - red_polylines => \@paths, - ); - } - - # paths must be repositioned and rotated back - $_->translate(@{$translate->negative}) for @paths; - $self->rotate_points_back(\@paths, $rotate_vector); - - return @paths; -} - - -package Slic3r::Fill::ArchimedeanChords; -use Moo; -extends 'Slic3r::Fill::PlanePath'; -use Math::PlanePath::ArchimedeanChords; - - -package Slic3r::Fill::Flowsnake; -use Moo; -extends 'Slic3r::Fill::PlanePath'; -use Math::PlanePath::Flowsnake; -use Slic3r::Geometry qw(X); - -# Sorry, this fill is currently broken. - -sub process_polyline { - my $self = shift; - my ($polyline, $bounding_box) = @_; - - $_->[X] += $bounding_box->center->[X] for @$polyline; -} - - -package Slic3r::Fill::HilbertCurve; -use Moo; -extends 'Slic3r::Fill::PlanePath'; -use Math::PlanePath::HilbertCurve; - - -package Slic3r::Fill::OctagramSpiral; -use Moo; -extends 'Slic3r::Fill::PlanePath'; -use Math::PlanePath::OctagramSpiral; - -sub multiplier () { sqrt(2) } - - - -1; diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm deleted file mode 100644 index 0922ff771..000000000 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ /dev/null @@ -1,168 +0,0 @@ -package Slic3r::Fill::Rectilinear; -use Moo; - -extends 'Slic3r::Fill::Base'; -with qw(Slic3r::Fill::WithDirection); - -has '_min_spacing' => (is => 'rw'); -has '_line_spacing' => (is => 'rw'); -has '_diagonal_distance' => (is => 'rw'); -has '_line_oscillation' => (is => 'rw'); - -use Slic3r::Geometry qw(scale unscale scaled_epsilon); -use Slic3r::Geometry::Clipper qw(intersection_pl); - -sub horizontal_lines { 0 } - -sub fill_surface { - my $self = shift; - my ($surface, %params) = @_; - - # rotate polygons so that we can work with vertical lines here - my $expolygon = $surface->expolygon->clone; - my $rotate_vector = $self->infill_direction($surface); - $self->rotate_points($expolygon, $rotate_vector); - - $self->_min_spacing(scale $self->spacing); - $self->_line_spacing($self->_min_spacing / $params{density}); - $self->_diagonal_distance($self->_line_spacing * 2); - $self->_line_oscillation($self->_line_spacing - $self->_min_spacing); # only for Line infill - my $bounding_box = $expolygon->bounding_box; - - # define flow spacing according to requested density - if ($params{density} == 1 && !$params{dont_adjust}) { - $self->_line_spacing($self->adjust_solid_spacing( - width => $bounding_box->size->x, - distance => $self->_line_spacing, - )); - $self->spacing(unscale $self->_line_spacing); - } else { - # extend bounding box so that our pattern will be aligned with other layers - $bounding_box->merge_point(Slic3r::Point->new( - $bounding_box->x_min - ($bounding_box->x_min % $self->_line_spacing), - $bounding_box->y_min - ($bounding_box->y_min % $self->_line_spacing), - )); - } - - # generate the basic pattern - my $x_max = $bounding_box->x_max + scaled_epsilon; - my @lines = (); - for (my $x = $bounding_box->x_min; $x <= $x_max; $x += $self->_line_spacing) { - push @lines, $self->_line($#lines, $x, $bounding_box->y_min, $bounding_box->y_max); - } - if ($self->horizontal_lines) { - my $y_max = $bounding_box->y_max + scaled_epsilon; - for (my $y = $bounding_box->y_min; $y <= $y_max; $y += $self->_line_spacing) { - push @lines, Slic3r::Polyline->new( - [$bounding_box->x_min, $y], - [$bounding_box->x_max, $y], - ); - } - } - - # clip paths against a slightly larger expolygon, so that the first and last paths - # are kept even if the expolygon has vertical sides - # the minimum offset for preventing edge lines from being clipped is scaled_epsilon; - # however we use a larger offset to support expolygons with slightly skewed sides and - # not perfectly straight - my @polylines = @{intersection_pl(\@lines, $expolygon->offset(+scale 0.02))}; - - my $extra = $self->_min_spacing * &Slic3r::INFILL_OVERLAP_OVER_SPACING; - foreach my $polyline (@polylines) { - my ($first_point, $last_point) = @$polyline[0,-1]; - if ($first_point->y > $last_point->y) { #> - ($first_point, $last_point) = ($last_point, $first_point); - } - $first_point->set_y($first_point->y - $extra); #-- - $last_point->set_y($last_point->y + $extra); #++ - } - - # connect lines - unless ($params{dont_connect} || !@polylines) { # prevent calling leftmost_point() on empty collections - # offset the expolygon by max(min_spacing/2, extra) - my ($expolygon_off) = @{$expolygon->offset_ex($self->_min_spacing/2)}; - my $collection = Slic3r::Polyline::Collection->new(@polylines); - @polylines = (); - - foreach my $polyline (@{$collection->chained_path_from($collection->leftmost_point, 0)}) { - if (@polylines) { - my $first_point = $polyline->first_point; - my $last_point = $polylines[-1]->last_point; - my @distance = map abs($first_point->$_ - $last_point->$_), qw(x y); - - # TODO: we should also check that both points are on a fill_boundary to avoid - # connecting paths on the boundaries of internal regions - if ($self->_can_connect(@distance) && $expolygon_off->contains_line(Slic3r::Line->new($last_point, $first_point))) { - $polylines[-1]->append_polyline($polyline); - next; - } - } - - # make a clone before $collection goes out of scope - push @polylines, $polyline->clone; - } - } - - # paths must be rotated back - $self->rotate_points_back(\@polylines, $rotate_vector); - - return @polylines; -} - -sub _line { - my ($self, $i, $x, $y_min, $y_max) = @_; - - return Slic3r::Polyline->new( - [$x, $y_min], - [$x, $y_max], - ); -} - -sub _can_connect { - my ($self, $dist_X, $dist_Y) = @_; - - return $dist_X <= $self->_diagonal_distance - && $dist_Y <= $self->_diagonal_distance; -} - - -package Slic3r::Fill::Line; -use Moo; -extends 'Slic3r::Fill::Rectilinear'; - -use Slic3r::Geometry qw(scaled_epsilon); - -sub _line { - my ($self, $i, $x, $y_min, $y_max) = @_; - - if ($i % 2) { - return Slic3r::Polyline->new( - [$x - $self->_line_oscillation, $y_min], - [$x + $self->_line_oscillation, $y_max], - ); - } else { - return Slic3r::Polyline->new( - [$x, $y_min], - [$x, $y_max], - ); - } -} - -sub _can_connect { - my ($self, $dist_X, $dist_Y) = @_; - - my $TOLERANCE = 10 * scaled_epsilon; - return ($dist_X >= ($self->_line_spacing - $self->_line_oscillation) - $TOLERANCE) - && ($dist_X <= ($self->_line_spacing + $self->_line_oscillation) + $TOLERANCE) - && $dist_Y <= $self->_diagonal_distance; -} - - -package Slic3r::Fill::Grid; -use Moo; -extends 'Slic3r::Fill::Rectilinear'; - -sub angles () { [0] } -sub horizontal_lines { 1 } - -1; diff --git a/lib/Slic3r/Format/OBJ.pm b/lib/Slic3r/Format/OBJ.pm index 616b38cdc..7a92fec82 100644 --- a/lib/Slic3r/Format/OBJ.pm +++ b/lib/Slic3r/Format/OBJ.pm @@ -21,7 +21,7 @@ sub read_file { my $mesh = Slic3r::TriangleMesh->new; $mesh->ReadFromPerl($vertices, $facets); - $mesh->repair; + $mesh->check_topology; my $model = Slic3r::Model->new; diff --git a/lib/Slic3r/Format/STL.pm b/lib/Slic3r/Format/STL.pm index 11928d8b2..a3b859104 100644 --- a/lib/Slic3r/Format/STL.pm +++ b/lib/Slic3r/Format/STL.pm @@ -12,7 +12,7 @@ sub read_file { my $mesh = Slic3r::TriangleMesh->new; $mesh->ReadSTLFile($path); - $mesh->repair; + $mesh->check_topology; die "This STL file couldn't be read because it's empty.\n" if $mesh->facets_count == 0; diff --git a/lib/Slic3r/GCode/CoolingBuffer.pm b/lib/Slic3r/GCode/CoolingBuffer.pm deleted file mode 100644 index 19fb8d7a8..000000000 --- a/lib/Slic3r/GCode/CoolingBuffer.pm +++ /dev/null @@ -1,81 +0,0 @@ -package Slic3r::GCode::CoolingBuffer; -use Moo; - -has 'config' => (is => 'ro', required => 1); # Slic3r::Config::Print -has 'gcodegen' => (is => 'ro', required => 1); -has 'gcode' => (is => 'rw', default => sub {""}); -has 'elapsed_time' => (is => 'rw', default => sub {0}); -has 'layer_id' => (is => 'rw'); -has 'last_z' => (is => 'rw', default => sub { {} }); # obj_id => z (basically a 'last seen' table) -has 'min_print_speed' => (is => 'lazy'); - -sub _build_min_print_speed { - my $self = shift; - return 60 * $self->config->min_print_speed; -} - -sub append { - my $self = shift; - my ($gcode, $obj_id, $layer_id, $print_z) = @_; - - my $return = ""; - if (exists $self->last_z->{$obj_id} && $self->last_z->{$obj_id} != $print_z) { - $return = $self->flush; - } - - $self->layer_id($layer_id); - $self->last_z->{$obj_id} = $print_z; - $self->gcode($self->gcode . $gcode); - $self->elapsed_time($self->elapsed_time + $self->gcodegen->elapsed_time); - $self->gcodegen->set_elapsed_time(0); - - return $return; -} - -sub flush { - my $self = shift; - - my $gcode = $self->gcode; - my $elapsed = $self->elapsed_time; - $self->gcode(""); - $self->elapsed_time(0); - $self->last_z({}); # reset the whole table otherwise we would compute overlapping times - - my $fan_speed = $self->config->fan_always_on ? $self->config->min_fan_speed : 0; - my $speed_factor = 1; - if ($self->config->cooling) { - Slic3r::debugf "Layer %d estimated printing time: %d seconds\n", $self->layer_id, $elapsed; - if ($elapsed < $self->config->slowdown_below_layer_time) { - $fan_speed = $self->config->max_fan_speed; - $speed_factor = $elapsed / $self->config->slowdown_below_layer_time; - } elsif ($elapsed < $self->config->fan_below_layer_time) { - $fan_speed = $self->config->max_fan_speed - ($self->config->max_fan_speed - $self->config->min_fan_speed) - * ($elapsed - $self->config->slowdown_below_layer_time) - / ($self->config->fan_below_layer_time - $self->config->slowdown_below_layer_time); #/ - } - Slic3r::debugf " fan = %d%%, speed = %d%%\n", $fan_speed, $speed_factor * 100; - - if ($speed_factor < 1) { - $gcode =~ s/^(?=.*?;_EXTRUDE_SET_SPEED)(?!.*?;_WIPE)(?min_print_speed ? $self->min_print_speed : $new_speed) - /gexm; - } - } - $fan_speed = 0 if $self->layer_id < $self->config->disable_fan_first_layers; - $gcode = $self->gcodegen->writer->set_fan($fan_speed) . $gcode; - - # bridge fan speed - if (!$self->config->cooling || $self->config->bridge_fan_speed == 0 || $self->layer_id < $self->config->disable_fan_first_layers) { - $gcode =~ s/^;_BRIDGE_FAN_(?:START|END)\n//gm; - } else { - $gcode =~ s/^;_BRIDGE_FAN_START\n/ $self->gcodegen->writer->set_fan($self->config->bridge_fan_speed, 1) /gmex; - $gcode =~ s/^;_BRIDGE_FAN_END\n/ $self->gcodegen->writer->set_fan($fan_speed, 1) /gmex; - } - $gcode =~ s/;_WIPE//g; - $gcode =~ s/;_EXTRUDE_SET_SPEED//g; - - return $gcode; -} - -1; diff --git a/lib/Slic3r/GCode/PressureRegulator.pm b/lib/Slic3r/GCode/PressureRegulator.pm index 6074b9486..a5bf6f61e 100644 --- a/lib/Slic3r/GCode/PressureRegulator.pm +++ b/lib/Slic3r/GCode/PressureRegulator.pm @@ -1,3 +1,5 @@ +# A pure perl (no C++ implementation) G-code filter, to control the pressure inside the nozzle. + package Slic3r::GCode::PressureRegulator; use Moo; @@ -36,8 +38,8 @@ sub process { } elsif ($info->{extruding} && $info->{dist_XY} > 0) { # This is a print move. my $F = $args->{F} // $reader->F; - if ($F != $self->_last_print_F) { - # We are setting a (potentially) new speed, so we calculate the new advance amount. + if ($F != $self->_last_print_F || ($F == $self->_last_print_F && $self->_advance == 0)) { + # We are setting a (potentially) new speed or a discharge event happend since the last speed change, so we calculate the new advance amount. # First calculate relative flow rate (mm of filament over mm of travel) my $rel_flow_rate = $info->{dist_E} / $info->{dist_XY}; @@ -54,6 +56,7 @@ sub process { $self->_extrusion_axis, $new_E, $self->_unretract_speed; $new_gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $reader->E if !$self->config->use_relative_e_distances; + $new_gcode .= sprintf "G1 F%.3f ; restore F\n", $F; $self->_advance($new_advance); } @@ -61,7 +64,7 @@ sub process { } } elsif (($info->{retracting} || $cmd eq 'G10') && $self->_advance != 0) { # We need to bring pressure to zero when retracting. - $new_gcode .= $self->_discharge($args->{F}); + $new_gcode .= $self->_discharge($args->{F}, $args->{F} // $reader->F); } $new_gcode .= "$info->{raw}\n"; @@ -75,13 +78,14 @@ sub process { } sub _discharge { - my ($self, $F) = @_; + my ($self, $F, $oldSpeed) = @_; my $new_E = ($self->config->use_relative_e_distances ? 0 : $self->reader->E) - $self->_advance; my $gcode = sprintf "G1 %s%.5f F%.3f ; pressure discharge\n", $self->_extrusion_axis, $new_E, $F // $self->_unretract_speed; $gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $self->reader->E if !$self->config->use_relative_e_distances; + $gcode .= sprintf "G1 F%.3f ; restore F\n", $oldSpeed; $self->_advance(0); return $gcode; diff --git a/lib/Slic3r/GCode/Reader.pm b/lib/Slic3r/GCode/Reader.pm index 6743e6f83..5763b3848 100644 --- a/lib/Slic3r/GCode/Reader.pm +++ b/lib/Slic3r/GCode/Reader.pm @@ -22,7 +22,7 @@ sub apply_print_config { sub clone { my $self = shift; return (ref $self)->new( - map { $_ => $self->$_ } (@AXES, 'F', '_extrusion_axis'), + map { $_ => $self->$_ } (@AXES, 'F', '_extrusion_axis', 'config'), ); } diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index eb359181a..1bdefebd0 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -25,6 +25,7 @@ use Slic3r::GUI::Plater::ObjectPartsPanel; use Slic3r::GUI::Plater::ObjectCutDialog; use Slic3r::GUI::Plater::ObjectLayersDialog; use Slic3r::GUI::Plater::ObjectSettingsDialog; +use Slic3r::GUI::Plater::LambdaObjectDialog; use Slic3r::GUI::Plater::OverrideSettingsPanel; use Slic3r::GUI::Plater::SplineControl; use Slic3r::GUI::Preferences; @@ -33,6 +34,7 @@ use Slic3r::GUI::Projector; use Slic3r::GUI::OptionsGroup; use Slic3r::GUI::OptionsGroup::Field; use Slic3r::GUI::SimpleTab; +use Slic3r::GUI::SLAPrintOptions; use Slic3r::GUI::Tab; our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1"; @@ -67,7 +69,7 @@ our $Settings = { mode => 'simple', version_check => 1, autocenter => 1, - background_processing => 1, + background_processing => 0, # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. # By default, Prusa has the controller hidden. no_controller => 1, @@ -97,6 +99,9 @@ sub OnInit { $self->{notifier} = Slic3r::GUI::Notifier->new; # locate or create data directory + # Unix: ~/.Slic3r + # Windows: "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r" + # Mac: "~/Library/Application Support/Slic3r" $datadir ||= Slic3r::decode_path(Wx::StandardPaths::Get->GetUserDataDir); my $enc_datadir = Slic3r::encode_path($datadir); Slic3r::debugf "Data directory: %s\n", $datadir; diff --git a/lib/Slic3r/GUI/2DBed.pm b/lib/Slic3r/GUI/2DBed.pm index c8896a795..afa31f951 100644 --- a/lib/Slic3r/GUI/2DBed.pm +++ b/lib/Slic3r/GUI/2DBed.pm @@ -1,3 +1,5 @@ +# Bed shape dialog + package Slic3r::GUI::2DBed; use strict; use warnings; diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index a36d2263c..f63a82146 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -1,9 +1,9 @@ package Slic3r::GUI::3DScene::Base; use strict; use warnings; - use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS); # must load OpenGL *before* Wx::GLCanvas + use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants); use base qw(Wx::GLCanvas Class::Accessor); use Math::Trig qw(asin); @@ -23,6 +23,7 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init on_move volumes _sphi _stheta + cutting_plane_axis cutting_plane_z cut_lines_vertices bed_shape @@ -43,9 +44,26 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init use constant TRACKBALLSIZE => 0.8; use constant TURNTABLE_MODE => 1; use constant GROUND_Z => -0.02; -use constant DEFAULT_COLOR => [1,1,0]; -use constant SELECTED_COLOR => [0,1,0,1]; -use constant HOVER_COLOR => [0.4,0.9,0,1]; +use constant SELECTED_COLOR => [0,1,0]; +use constant HOVER_COLOR => [0.4,0.9,0]; +use constant PI => 3.1415927; + +# Constant to determine if Vertex Buffer objects are used to draw +# bed grid and the cut plane for object separation. +# Old Perl (5.10.x) should set to 0. +use constant HAS_VBO => 1; + + +# phi / theta angles to orient the camera. +use constant VIEW_ISO => [45.0,45.0]; +use constant VIEW_LEFT => [90.0,90.0]; +use constant VIEW_RIGHT => [-90.0,90.0]; +use constant VIEW_TOP => [0.0,0.0]; +use constant VIEW_BOTTOM => [0.0,180.0]; +use constant VIEW_FRONT => [0.0,90.0]; +use constant VIEW_REAR => [180.0,90.0]; + +use constant GIMBAL_LOCK_THETA_MAX => 170; # make OpenGL::Array thread-safe { @@ -56,6 +74,7 @@ use constant HOVER_COLOR => [0.4,0.9,0,1]; sub new { my ($class, $parent) = @_; + # We can only enable multi sample anti aliasing wih wxWidgets 3.0.3 and with a hacked Wx::GLCanvas, # which exports some new WX_GL_XXX constants, namely WX_GL_SAMPLE_BUFFERS and WX_GL_SAMPLES. my $can_multisample = @@ -74,6 +93,7 @@ sub new { # we request a depth buffer explicitely because it looks like it's not created by # default on Linux, causing transparency issues my $self = $class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "", $attrib); + if (Wx::wxVERSION >= 3.000003) { # Wx 3.0.3 contains an ugly hack to support some advanced OpenGL attributes through the attribute list. # The attribute list is transferred between the wxGLCanvas and wxGLContext constructors using a single static array s_wglContextAttribs. @@ -133,6 +153,7 @@ sub new { $self->Refresh; }); EVT_MOUSE_EVENTS($self, \&mouse_event); + return $self; } @@ -147,7 +168,15 @@ sub mouse_event { } elsif ($e->LeftDClick) { $self->on_double_click->() if $self->on_double_click; - } elsif ($e->LeftDown || $e->RightDown) { + } elsif ($e->MiddleDClick) { + if (@{$self->volumes}) { + $self->zoom_to_volumes; + } else { + $self->zoom_to_bed; + } + $self->_dirty(1); + $self->Refresh; + } elsif (($e->LeftDown || $e->RightDown) && not $e->ShiftDown) { # If user pressed left or right button we first check whether this happened # on a volume or not. my $volume_idx = $self->_hover_volume_idx // -1; @@ -209,14 +238,23 @@ sub mouse_event { $self->_dragged(1); $self->Refresh; } elsif ($e->Dragging) { - if ($e->LeftIsDown) { + if ($e->AltDown) { + # Move the camera center on the Z axis based on mouse Y axis movement + if (defined $self->_drag_start_pos) { + my $orig = $self->_drag_start_pos; + $self->_camera_target->translate(0, 0, $pos->y - $orig->y); + $self->on_viewport_changed->() if $self->on_viewport_changed; + $self->Refresh; + } + $self->_drag_start_pos($pos); + } elsif ($e->LeftIsDown) { # if dragging over blank area with left button, rotate if (defined $self->_drag_start_pos) { my $orig = $self->_drag_start_pos; if (TURNTABLE_MODE) { $self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE); $self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE); #- - $self->_stheta(150) if $self->_stheta > 150; + $self->_stheta(GIMBAL_LOCK_THETA_MAX) if $self->_stheta > GIMBAL_LOCK_THETA_MAX; $self->_stheta(0) if $self->_stheta < 0; } else { my $size = $self->GetClientSize; @@ -266,7 +304,7 @@ sub mouse_event { $self->_dragged(undef); } elsif ($e->Moving) { $self->_mouse_pos($pos); - $self->Refresh; + $self->Refresh if $self->enable_picking; } else { $e->Skip(); } @@ -290,19 +328,63 @@ sub set_viewport_from_scene { $self->_dirty(1); } +# Set the camera to a default orientation, +# zoom to volumes. +sub select_view { + my ($self, $direction) = @_; + my $dirvec; + if (ref($direction)) { + $dirvec = $direction; + } else { + if ($direction eq 'iso') { + $dirvec = VIEW_ISO; + } elsif ($direction eq 'left') { + $dirvec = VIEW_LEFT; + } elsif ($direction eq 'right') { + $dirvec = VIEW_RIGHT; + } elsif ($direction eq 'top') { + $dirvec = VIEW_TOP; + } elsif ($direction eq 'bottom') { + $dirvec = VIEW_BOTTOM; + } elsif ($direction eq 'front') { + $dirvec = VIEW_FRONT; + } elsif ($direction eq 'rear') { + $dirvec = VIEW_REAR; + } + } + $self->_sphi($dirvec->[0]); + $self->_stheta($dirvec->[1]); + + # Avoid gimbal lock. + $self->_stheta(GIMBAL_LOCK_THETA_MAX) if $self->_stheta > GIMBAL_LOCK_THETA_MAX; + $self->_stheta(0) if $self->_stheta < 0; + + # View everything. + $self->volumes_bounding_box->defined + ? $self->zoom_to_volumes + : $self->zoom_to_bed; + + $self->on_viewport_changed->() if $self->on_viewport_changed; + $self->_dirty(1); + $self->Refresh; +} + sub zoom_to_bounding_box { my ($self, $bb) = @_; # calculate the zoom factor needed to adjust viewport to # bounding box - my $max_size = max(@{$bb->size}) * 2; + my $max_size = max(@{$bb->size}) * 1.05; my $min_viewport_size = min($self->GetSizeWH); - $self->_zoom($min_viewport_size / $max_size); + if ($max_size != 0) { + # only re-zoom if we have a valid bounding box, avoid a divide by 0 error. + $self->_zoom($min_viewport_size / $max_size); - # center view around bounding box center - $self->_camera_target($bb->center); + # center view around bounding box center + $self->_camera_target($bb->center); - $self->on_viewport_changed->() if $self->on_viewport_changed; + $self->on_viewport_changed->() if $self->on_viewport_changed; + } } sub zoom_to_bed { @@ -423,19 +505,35 @@ sub select_volume { } sub SetCuttingPlane { - my ($self, $z, $expolygons) = @_; + my ($self, $axis, $z, $expolygons) = @_; + $self->cutting_plane_axis($axis); $self->cutting_plane_z($z); # grow slices in order to display them better $expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1); + my $bb = $self->volumes_bounding_box; + my @verts = (); foreach my $line (map @{$_->lines}, map @$_, @$expolygons) { - push @verts, ( - unscale($line->a->x), unscale($line->a->y), $z, #)) - unscale($line->b->x), unscale($line->b->y), $z, #)) - ); + if ($axis == X) { + push @verts, ( + $bb->x_min + $z, unscale($line->a->x), unscale($line->a->y), #)) + $bb->x_min + $z, unscale($line->b->x), unscale($line->b->y), #)) + ); + } elsif ($axis == Y) { + push @verts, ( + unscale($line->a->y), $bb->y_min + $z, unscale($line->a->x), #)) + unscale($line->b->y), $bb->y_min + $z, unscale($line->b->x), #)) + ); + } else { + push @verts, ( + unscale($line->a->x), unscale($line->a->y), $z, #)) + unscale($line->b->x), unscale($line->b->y), $z, #)) + ); + } + } $self->cut_lines_vertices(OpenGL::Array->new_list(GL_FLOAT, @verts)); } @@ -756,9 +854,19 @@ sub Render { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnableClientState(GL_VERTEX_ARRAY); + if (HAS_VBO) { + my ($triangle_vertex); + ($triangle_vertex) = + glGenBuffersARB_p(1); + $self->bed_triangles->bind($triangle_vertex); + glBufferDataARB_p(GL_ARRAY_BUFFER_ARB, $self->bed_triangles, GL_STATIC_DRAW_ARB); + glVertexPointer_c(3, GL_FLOAT, 0, 0); + } else { + # fall back on old behavior + glVertexPointer_p(3, $self->bed_triangles); + } glColor4f(0.8, 0.6, 0.5, 0.4); glNormal3d(0,0,1); - glVertexPointer_p(3, $self->bed_triangles); glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3); glDisableClientState(GL_VERTEX_ARRAY); @@ -768,13 +876,29 @@ sub Render { # draw grid glLineWidth(3); - glColor4f(0.2, 0.2, 0.2, 0.4); glEnableClientState(GL_VERTEX_ARRAY); - glVertexPointer_p(3, $self->bed_grid_lines); + if (HAS_VBO) { + my ($grid_vertex); + ($grid_vertex) = + glGenBuffersARB_p(1); + $self->bed_grid_lines->bind($grid_vertex); + glBufferDataARB_p(GL_ARRAY_BUFFER_ARB, $self->bed_grid_lines, GL_STATIC_DRAW_ARB); + glVertexPointer_c(3, GL_FLOAT, 0, 0); + } else { + # fall back on old behavior + glVertexPointer_p(3, $self->bed_grid_lines); + } + glColor4f(0.2, 0.2, 0.2, 0.4); + glNormal3d(0,0,1); glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3); glDisableClientState(GL_VERTEX_ARRAY); glDisable(GL_BLEND); + if (HAS_VBO) { + # Turn off buffer objects to let the rest of the draw code work. + glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); + } } my $volumes_bb = $self->volumes_bounding_box; @@ -785,8 +909,8 @@ sub Render { glDisable(GL_DEPTH_TEST); my $origin = $self->origin; my $axis_len = max( - 0.3 * max(@{ $self->bed_bounding_box->size }), - 2 * max(@{ $volumes_bb->size }), + max(@{ $self->bed_bounding_box->size }), + 1.2 * max(@{ $volumes_bb->size }), ); glLineWidth(2); glBegin(GL_LINES); @@ -824,18 +948,68 @@ sub Render { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBegin(GL_QUADS); glColor4f(0.8, 0.8, 0.8, 0.5); - glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z); - glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z); - glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z); - glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z); + if ($self->cutting_plane_axis == X) { + glVertex3f($bb->x_min+$plane_z, $bb->y_min-20, $bb->z_min-20); + glVertex3f($bb->x_min+$plane_z, $bb->y_max+20, $bb->z_min-20); + glVertex3f($bb->x_min+$plane_z, $bb->y_max+20, $bb->z_max+20); + glVertex3f($bb->x_min+$plane_z, $bb->y_min-20, $bb->z_max+20); + } elsif ($self->cutting_plane_axis == Y) { + glVertex3f($bb->x_min-20, $bb->y_min+$plane_z, $bb->z_min-20); + glVertex3f($bb->x_max+20, $bb->y_min+$plane_z, $bb->z_min-20); + glVertex3f($bb->x_max+20, $bb->y_min+$plane_z, $bb->z_max+20); + glVertex3f($bb->x_min-20, $bb->y_min+$plane_z, $bb->z_max+20); + } elsif ($self->cutting_plane_axis == Z) { + glVertex3f($bb->x_min-20, $bb->y_min-20, $bb->z_min+$plane_z); + glVertex3f($bb->x_max+20, $bb->y_min-20, $bb->z_min+$plane_z); + glVertex3f($bb->x_max+20, $bb->y_max+20, $bb->z_min+$plane_z); + glVertex3f($bb->x_min-20, $bb->y_max+20, $bb->z_min+$plane_z); + } glEnd(); glEnable(GL_CULL_FACE); glDisable(GL_BLEND); } + if (defined $self->_drag_start_pos || defined $self->_drag_start_xy) { + $self->draw_center_of_rotation($self->_camera_target->x, $self->_camera_target->y, $self->_camera_target->z); + } + glFlush(); $self->SwapBuffers(); + + # Calling glFinish has a performance penalty, but it seems to fix some OpenGL driver hang-up with extremely large scenes. + glFinish(); +} + +sub draw_axes { + my ($self, $x, $y, $z, $length, $width, $allways_visible) = @_; + if ($allways_visible) { + glDisable(GL_DEPTH_TEST); + } else { + glEnable(GL_DEPTH_TEST); + } + glLineWidth($width); + glBegin(GL_LINES); + # draw line for x axis + glColor3f(1, 0, 0); + glVertex3f($x, $y, $z); + glVertex3f($x + $length, $y, $z); + # draw line for y axis + glColor3f(0, 1, 0); + glVertex3f($x, $y, $z); + glVertex3f($x, $y + $length, $z); + # draw line for Z axis + glColor3f(0, 0, 1); + glVertex3f($x, $y, $z); + glVertex3f($x, $y, $z + $length); + glEnd(); +} + +sub draw_center_of_rotation { + my ($self, $x, $y, $z) = @_; + + $self->draw_axes($x, $y, $z, 10, 1, 1); + $self->draw_axes($x, $y, $z, 10, 4, 0); } sub draw_volumes { @@ -858,9 +1032,9 @@ sub draw_volumes { my $b = ($volume_idx & 0x00FF0000) >> 16; glColor4f($r/255.0, $g/255.0, $b/255.0, 1); } elsif ($volume->selected) { - glColor4f(@{ &SELECTED_COLOR }); + glColor4f(@{ &SELECTED_COLOR }, $volume->color->[3]); } elsif ($volume->hover) { - glColor4f(@{ &HOVER_COLOR }); + glColor4f(@{ &HOVER_COLOR }, $volume->color->[3]); } else { glColor4f(@{ $volume->color }); } @@ -914,10 +1088,26 @@ sub draw_volumes { glDisable(GL_BLEND); if (defined $self->cutting_plane_z) { + if (HAS_VBO) { + # Use Vertex Buffer Object for cutting plane (previous method crashes on modern POGL). + my ($cut_vertex) = glGenBuffersARB_p(1); + $self->cut_lines_vertices->bind($cut_vertex); + glBufferDataARB_p(GL_ARRAY_BUFFER_ARB, $self->cut_lines_vertices, GL_STATIC_DRAW_ARB); + glVertexPointer_c(3, GL_FLOAT, 0, 0); + } else { + # Use legacy method. + glVertexPointer_p(3, $self->cut_lines_vertices); + } glLineWidth(2); glColor3f(0, 0, 0); - glVertexPointer_p(3, $self->cut_lines_vertices); glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3); + + if (HAS_VBO) { + # Turn off buffer objects to let the rest of the draw code work. + glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); + } + } glDisableClientState(GL_VERTEX_ARRAY); } @@ -1098,7 +1288,9 @@ sub load_print_toolpaths { return if !$print->step_done(STEP_SKIRT); return if !$print->step_done(STEP_BRIM); - return if !$print->has_skirt && $print->config->brim_width == 0; + return if !$print->has_skirt + && $print->config->brim_width == 0 + && $print->config->brim_connections_width == 0; my $qverts = Slic3r::GUI::_3DScene::GLVertexArray->new; my $tverts = Slic3r::GUI::_3DScene::GLVertexArray->new; @@ -1164,6 +1356,21 @@ sub load_print_object_toolpaths { my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers}; + # Bounding box of the object and its copies. + my $bb = Slic3r::Geometry::BoundingBoxf3->new; + { + my $obb = $object->bounding_box; + foreach my $copy (@{ $object->_shifted_copies }) { + my $cbb = $obb->clone; + $cbb->translate(@$copy); + $bb->merge_point(Slic3r::Pointf3->new_unscale(@{$cbb->min_point}, 0)); + $bb->merge_point(Slic3r::Pointf3->new_unscale(@{$cbb->max_point}, $object->size->z)); + } + } + + # Maximum size of an allocation block: 32MB / sizeof(float) + my $alloc_size_max = 32 * 1048576 / 4; + foreach my $layer (@layers) { my $top_z = $layer->print_z; @@ -1171,9 +1378,13 @@ sub load_print_object_toolpaths { $perim_offsets{$top_z} = [ $perim_qverts->size, $perim_tverts->size, ]; + } + if (!exists $infill_offsets{$top_z}) { $infill_offsets{$top_z} = [ $infill_qverts->size, $infill_tverts->size, ]; + } + if (!exists $support_offsets{$top_z}) { $support_offsets{$top_z} = [ $support_qverts->size, $support_tverts->size, ]; @@ -1200,40 +1411,79 @@ sub load_print_object_toolpaths { $support_qverts, $support_tverts); } } + + if ($perim_qverts->size() > $alloc_size_max || $perim_tverts->size() > $alloc_size_max) { + # Store the vertex arrays and restart their containers. + push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new( + bounding_box => $bb, + color => COLORS->[0], + qverts => $perim_qverts, + tverts => $perim_tverts, + offsets => { %perim_offsets }, + ); + $perim_qverts = Slic3r::GUI::_3DScene::GLVertexArray->new; + $perim_tverts = Slic3r::GUI::_3DScene::GLVertexArray->new; + %perim_offsets = (); + } + + if ($infill_qverts->size() > $alloc_size_max || $infill_tverts->size() > $alloc_size_max) { + # Store the vertex arrays and restart their containers. + push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new( + bounding_box => $bb, + color => COLORS->[1], + qverts => $infill_qverts, + tverts => $infill_tverts, + offsets => { %infill_offsets }, + ); + $infill_qverts = Slic3r::GUI::_3DScene::GLVertexArray->new; + $infill_tverts = Slic3r::GUI::_3DScene::GLVertexArray->new; + %infill_offsets = (); + } + + if ($support_qverts->size() > $alloc_size_max || $support_tverts->size() > $alloc_size_max) { + # Store the vertex arrays and restart their containers. + push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new( + bounding_box => $bb, + color => COLORS->[2], + qverts => $support_qverts, + tverts => $support_tverts, + offsets => { %support_offsets }, + ); + $support_qverts = Slic3r::GUI::_3DScene::GLVertexArray->new; + $support_tverts = Slic3r::GUI::_3DScene::GLVertexArray->new; + %support_offsets = (); + } + } + + if ($perim_qverts->size() > 0 || $perim_tverts->size() > 0) { + push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new( + bounding_box => $bb, + color => COLORS->[0], + qverts => $perim_qverts, + tverts => $perim_tverts, + offsets => { %perim_offsets }, + ); } - my $obb = $object->bounding_box; - my $bb = Slic3r::Geometry::BoundingBoxf3->new; - foreach my $copy (@{ $object->_shifted_copies }) { - my $cbb = $obb->clone; - $cbb->translate(@$copy); - $bb->merge_point(Slic3r::Pointf3->new_unscale(@{$cbb->min_point}, 0)); - $bb->merge_point(Slic3r::Pointf3->new_unscale(@{$cbb->max_point}, $object->size->z)); + if ($infill_qverts->size() > 0 || $infill_tverts->size() > 0) { + push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new( + bounding_box => $bb, + color => COLORS->[1], + qverts => $infill_qverts, + tverts => $infill_tverts, + offsets => { %infill_offsets }, + ); } - push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new( - bounding_box => $bb, - color => COLORS->[0], - qverts => $perim_qverts, - tverts => $perim_tverts, - offsets => { %perim_offsets }, - ); - - push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new( - bounding_box => $bb, - color => COLORS->[1], - qverts => $infill_qverts, - tverts => $infill_tverts, - offsets => { %infill_offsets }, - ); - - push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new( - bounding_box => $bb, - color => COLORS->[2], - qverts => $support_qverts, - tverts => $support_tverts, - offsets => { %support_offsets }, - ); + if ($support_qverts->size() > 0 || $support_tverts->size() > 0) { + push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new( + bounding_box => $bb, + color => COLORS->[2], + qverts => $support_qverts, + tverts => $support_tverts, + offsets => { %support_offsets }, + ); + } } sub set_toolpaths_range { @@ -1272,6 +1522,8 @@ sub _expolygons_to_verts { gluDeleteTess($tess); } +# Fill in the $qverts and $tverts with quads and triangles +# for the extrusion $entity. sub _extrusionentity_to_verts { my ($self, $entity, $top_z, $copy, $qverts, $tverts) = @_; @@ -1303,8 +1555,18 @@ sub _extrusionentity_to_verts { push @$heights, map $path->height, 0..$#$path_lines; } } + + # Calling the C++ implementation Slic3r::_3DScene::_extrusionentity_to_verts_do() + # This adds new vertices to the $qverts and $tverts. Slic3r::GUI::_3DScene::_extrusionentity_to_verts_do($lines, $widths, $heights, - $closed, $top_z, $copy, $qverts, $tverts); + $closed, + # Top height of the extrusion. + $top_z, + # $copy is not used here. + $copy, + # GLVertexArray object: C++ class maintaining an std::vector for coords and normals. + $qverts, + $tverts); } sub object_idx { diff --git a/lib/Slic3r/GUI/AboutDialog.pm b/lib/Slic3r/GUI/AboutDialog.pm index 7b9625976..831377a1f 100644 --- a/lib/Slic3r/GUI/AboutDialog.pm +++ b/lib/Slic3r/GUI/AboutDialog.pm @@ -51,7 +51,7 @@ sub new { 'Slic3r is licensed under the ' . 'GNU Affero General Public License, version 3.' . '


' . - 'Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Y. Sapir, Mike Sheldrake and numerous others. ' . + 'Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others. ' . 'Manual by Gary Hodgson. Inspired by the RepRap community.
' . 'Slic3r logo designed by Corey Daniels, Silk Icon Set designed by Mark James. ' . '' . diff --git a/lib/Slic3r/GUI/BedShapeDialog.pm b/lib/Slic3r/GUI/BedShapeDialog.pm index 49c8b9e07..9da717ab8 100644 --- a/lib/Slic3r/GUI/BedShapeDialog.pm +++ b/lib/Slic3r/GUI/BedShapeDialog.pm @@ -1,3 +1,6 @@ +# The bed shape dialog. +# The dialog opens from Print Settins tab -> Bed Shape: Set... + package Slic3r::GUI::BedShapeDialog; use strict; use warnings; @@ -209,6 +212,7 @@ sub _update_shape { my $rect_origin = $self->{optgroups}[SHAPE_RECTANGULAR]->get_value('rect_origin'); my ($x, $y) = @$rect_size; return if !looks_like_number($x) || !looks_like_number($y); # empty strings or '-' or other things + return if !$x || !$y; my ($x0, $y0) = (0,0); my ($x1, $y1) = ($x,$y); { diff --git a/lib/Slic3r/GUI/BonjourBrowser.pm b/lib/Slic3r/GUI/BonjourBrowser.pm index 21966d6e8..c7513165f 100644 --- a/lib/Slic3r/GUI/BonjourBrowser.pm +++ b/lib/Slic3r/GUI/BonjourBrowser.pm @@ -1,3 +1,5 @@ +# A tiny dialog to select an OctoPrint device to print to. + package Slic3r::GUI::BonjourBrowser; use strict; use warnings; @@ -9,14 +11,10 @@ use base 'Wx::Dialog'; sub new { my $class = shift; - my ($parent) = @_; + my ($parent, $devices) = @_; my $self = $class->SUPER::new($parent, -1, "Device Browser", wxDefaultPosition, [350,700], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); - # look for devices - eval "use Net::Bonjour; 1"; - my $res = Net::Bonjour->new('http'); - $res->discover; - $self->{devices} = [ $res->entries ]; + $self->{devices} = $devices; # label my $text = Wx::StaticText->new($self, -1, "Choose an OctoPrint device in your network:", wxDefaultPosition, wxDefaultSize); diff --git a/lib/Slic3r/GUI/ConfigWizard.pm b/lib/Slic3r/GUI/ConfigWizard.pm index 008b76f25..b90837937 100644 --- a/lib/Slic3r/GUI/ConfigWizard.pm +++ b/lib/Slic3r/GUI/ConfigWizard.pm @@ -1,3 +1,6 @@ +# The config wizard is executed when the Slic3r is first started. +# The wizard helps the user to specify the 3D printer properties. + package Slic3r::GUI::ConfigWizard; use strict; use warnings; diff --git a/lib/Slic3r/GUI/Controller.pm b/lib/Slic3r/GUI/Controller.pm index 6d2d16b9e..c6a568f82 100644 --- a/lib/Slic3r/GUI/Controller.pm +++ b/lib/Slic3r/GUI/Controller.pm @@ -1,3 +1,7 @@ +# The "Controller" tab to control the printer using serial / USB. +# This feature is rarely used. Much more often, the firmware reads the G-codes from a SD card. +# May there be multiple subtabs per each printer connected? + package Slic3r::GUI::Controller; use strict; use warnings; diff --git a/lib/Slic3r/GUI/Controller/ManualControlDialog.pm b/lib/Slic3r/GUI/Controller/ManualControlDialog.pm index ebd0031a9..ac997730d 100644 --- a/lib/Slic3r/GUI/Controller/ManualControlDialog.pm +++ b/lib/Slic3r/GUI/Controller/ManualControlDialog.pm @@ -1,3 +1,5 @@ +# A printer "Controller" -> "ManualControlDialog" subtab, opened per 3D printer connected? + package Slic3r::GUI::Controller::ManualControlDialog; use strict; use warnings; diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index d97ad22ef..c1df8b30e 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -1,3 +1,5 @@ +# The main frame, the parent of all. + package Slic3r::GUI::MainFrame; use strict; use warnings; @@ -19,7 +21,11 @@ sub new { my ($class, %params) = @_; my $self = $class->SUPER::new(undef, -1, 'Slic3r', wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE); - $self->SetIcon(Wx::Icon->new($Slic3r::var->("Slic3r_128px.png"), wxBITMAP_TYPE_PNG) ); + if ($^O eq 'MSWin32') { + $self->SetIcon(Wx::Icon->new($Slic3r::var->("Slic3r.ico"), wxBITMAP_TYPE_ICO)); + } else { + $self->SetIcon(Wx::Icon->new($Slic3r::var->("Slic3r_128px.png"), wxBITMAP_TYPE_PNG)); + } # store input params $self->{mode} = $params{mode}; @@ -33,6 +39,11 @@ sub new { $self->_init_tabpanel; $self->_init_menubar; + # set default tooltip timer in msec + # SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values + # (SetAutoPop is not available on GTK.) + eval { Wx::ToolTip::SetAutoPop(32767) }; + # initialize status bar $self->{statusbar} = Slic3r::GUI::ProgressStatusBar->new($self, -1); $self->{statusbar}->SetStatusText("Version $Slic3r::VERSION - Remember to check for updates at http://slic3r.org/"); @@ -98,9 +109,9 @@ sub _init_tabpanel { if (!$self->{no_plater}) { $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), "Plater"); - if (!$self->{no_controller}) { - $panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), "Controller"); - } + } + if (!$self->{no_controller}) { + $panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), "Controller"); } $self->{options_tabs} = {}; @@ -169,6 +180,13 @@ sub _init_menubar { # File menu my $fileMenu = Wx::Menu->new; { + $self->_append_menu_item($fileMenu, "Open STL/OBJ/AMF…\tCtrl+O", 'Open a model', sub { + $self->{plater}->add if $self->{plater}; + }, undef, 'brick_add.png'); + $self->_append_menu_item($fileMenu, "Open 2.5D TIN mesh…", 'Import a 2.5D TIN mesh', sub { + $self->{plater}->add_tin if $self->{plater}; + }, undef, 'map_add.png'); + $fileMenu->AppendSeparator(); $self->_append_menu_item($fileMenu, "&Load Config…\tCtrl+L", 'Load exported configuration file', sub { $self->load_config_file; }, undef, 'plugin_add.png'); @@ -235,11 +253,9 @@ sub _init_menubar { $plater->export_amf; }, undef, 'brick_go.png'); $self->_append_menu_item($self->{plater_menu}, "Open DLP Projector…\tCtrl+L", 'Open projector window for DLP printing', sub { - my $projector = Slic3r::GUI::Projector->new($self); - - # this double invocation is needed for properly hiding the MainFrame - $projector->Show; - $projector->ShowModal; + $plater->pause_background_process; + Slic3r::GUI::SLAPrintOptions->new($self)->ShowModal; + $plater->resume_background_process; }, undef, 'film.png'); $self->{object_menu} = $self->{plater}->object_menu; @@ -254,14 +270,18 @@ sub _init_menubar { $self->_append_menu_item($windowMenu, "Select &Plater Tab\tCtrl+1", 'Show the plater', sub { $self->select_tab(0); }, undef, 'application_view_tile.png'); - if (!$self->{no_controller}) { - $self->_append_menu_item($windowMenu, "Select &Controller Tab\tCtrl+T", 'Show the printer controller', sub { - $self->select_tab(1); - }, undef, 'printer_empty.png'); - } - $windowMenu->AppendSeparator(); - $tab_offset += 2; + $tab_offset += 1; } + if (!$self->{no_controller}) { + $self->_append_menu_item($windowMenu, "Select &Controller Tab\tCtrl+T", 'Show the printer controller', sub { + $self->select_tab(1); + }, undef, 'printer_empty.png'); + $tab_offset += 1; + } + if ($tab_offset > 0) { + $windowMenu->AppendSeparator(); + } + $self->_append_menu_item($windowMenu, "Select P&rint Settings Tab\tCtrl+2", 'Show the print settings', sub { $self->select_tab($tab_offset+0); }, undef, 'cog.png'); @@ -272,6 +292,18 @@ sub _init_menubar { $self->select_tab($tab_offset+2); }, undef, 'printer_empty.png'); } + + # View menu + if (!$self->{no_plater}) { + $self->{viewMenu} = Wx::Menu->new; + $self->_append_menu_item($self->{viewMenu}, "Iso" , 'Iso View' , sub { $self->select_view('iso' ); }); + $self->_append_menu_item($self->{viewMenu}, "Top" , 'Top View' , sub { $self->select_view('top' ); }); + $self->_append_menu_item($self->{viewMenu}, "Bottom" , 'Bottom View' , sub { $self->select_view('bottom' ); }); + $self->_append_menu_item($self->{viewMenu}, "Front" , 'Front View' , sub { $self->select_view('front' ); }); + $self->_append_menu_item($self->{viewMenu}, "Rear" , 'Rear View' , sub { $self->select_view('rear' ); }); + $self->_append_menu_item($self->{viewMenu}, "Left" , 'Left View' , sub { $self->select_view('left' ); }); + $self->_append_menu_item($self->{viewMenu}, "Right" , 'Right View' , sub { $self->select_view('right' ); }); + } # Help menu my $helpMenu = Wx::Menu->new; @@ -305,6 +337,7 @@ sub _init_menubar { $menubar->Append($self->{plater_menu}, "&Plater") if $self->{plater_menu}; $menubar->Append($self->{object_menu}, "&Object") if $self->{object_menu}; $menubar->Append($windowMenu, "&Window"); + $menubar->Append($self->{viewMenu}, "&View") if $self->{viewMenu}; $menubar->Append($helpMenu, "&Help"); $self->SetMenuBar($menubar); } @@ -393,7 +426,7 @@ sub quick_slice { if ($params{reslice}) { $output_file = $qs_last_output_file if defined $qs_last_output_file; } elsif ($params{save_as}) { - $output_file = $sprint->expanded_output_filepath; + $output_file = $sprint->output_filepath; $output_file =~ s/\.gcode$/.svg/i if $params{export_svg}; my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', wxTheApp->output_path(dirname($output_file)), @@ -773,6 +806,14 @@ sub select_tab { $self->{tabpanel}->SetSelection($tab); } +# Set a camera direction, zoom to all objects. +sub select_view { + my ($self, $direction) = @_; + if (! $self->{no_plater}) { + $self->{plater}->select_view($direction); + } +} + sub _append_menu_item { my ($self, $menu, $string, $description, $cb, $id, $icon) = @_; diff --git a/lib/Slic3r/GUI/Notifier.pm b/lib/Slic3r/GUI/Notifier.pm index a2242bcdf..eb548d014 100644 --- a/lib/Slic3r/GUI/Notifier.pm +++ b/lib/Slic3r/GUI/Notifier.pm @@ -1,3 +1,6 @@ +# Notify about the end of slicing. +# The notifications are sent out using the Growl protocol if installed, and using DBus XWindow protocol. + package Slic3r::GUI::Notifier; use Moo; diff --git a/lib/Slic3r/GUI/OptionsGroup.pm b/lib/Slic3r/GUI/OptionsGroup.pm index b64fc8cb7..382dbc744 100644 --- a/lib/Slic3r/GUI/OptionsGroup.pm +++ b/lib/Slic3r/GUI/OptionsGroup.pm @@ -1,3 +1,5 @@ +# A dialog group object. Used by the Tab, SimpleTab, Preferences dialog, ManualControlDialog etc. + package Slic3r::GUI::OptionsGroup; use Moo; diff --git a/lib/Slic3r/GUI/OptionsGroup/Field.pm b/lib/Slic3r/GUI/OptionsGroup/Field.pm index fc17a65e7..d2cf76ce6 100644 --- a/lib/Slic3r/GUI/OptionsGroup/Field.pm +++ b/lib/Slic3r/GUI/OptionsGroup/Field.pm @@ -592,4 +592,10 @@ sub disable { $self->textctrl->SetEditable(0); } +sub set_range { + my ($self, $min, $max) = @_; + + $self->slider->SetRange($min * $self->scale, $max * $self->scale); +} + 1; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index b28304bab..7e271bfb6 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1,3 +1,5 @@ +# The "Plater" tab. It contains the "3D", "2D", "Preview" and "Layers" subtabs. + package Slic3r::GUI::Plater; use strict; use warnings; @@ -5,7 +7,8 @@ use utf8; use File::Basename qw(basename dirname); use List::Util qw(sum first max); -use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad); +use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad rad2deg); +use LWP::UserAgent; use threads::shared qw(shared_clone); use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc :panel :sizer :toolbar :window wxTheApp :notebook :combobox); @@ -52,6 +55,8 @@ sub new { )); $self->{model} = Slic3r::Model->new; $self->{print} = Slic3r::Print->new; + $self->{processed} = 0; + # List of Perl objects Slic3r::GUI::Plater::Object, representing a 2D preview of the platter. $self->{objects} = []; $self->{print}->set_status_cb(sub { @@ -111,6 +116,7 @@ sub new { $self->{canvas}->on_instances_moved($on_instances_moved); # Initialize 3D toolpaths preview + $self->{preview3D_page_idx} = -1; if ($Slic3r::GUI::have_OpenGL) { $self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print}); $self->{preview3D}->canvas->on_viewport_changed(sub { @@ -121,15 +127,30 @@ sub new { } # Initialize toolpaths preview + $self->{toolpaths2D_page_idx} = -1; if ($Slic3r::GUI::have_OpenGL) { $self->{toolpaths2D} = Slic3r::GUI::Plater::2DToolpaths->new($self->{preview_notebook}, $self->{print}); $self->{preview_notebook}->AddPage($self->{toolpaths2D}, 'Layers'); + $self->{toolpaths2D_page_idx} = $self->{preview_notebook}->GetPageCount-1; } EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{preview_notebook}, sub { - if ($self->{preview_notebook}->GetSelection == $self->{preview3D_page_idx}) { - $self->{preview3D}->load_print; - } + wxTheApp->CallAfter(sub { + my $sel = $self->{preview_notebook}->GetSelection; + if ($sel == $self->{preview3D_page_idx} || $sel == $self->{toolpaths2D_page_idx}) { + if (!$Slic3r::GUI::Settings->{_}{background_processing} && !$self->{processed}) { + $self->statusbar->SetCancelCallback(sub { + $self->stop_background_process; + $self->statusbar->SetStatusText("Slicing cancelled"); + $self->{preview_notebook}->SetSelection(0); + }); + $self->start_background_process; + } else { + $self->{preview3D}->load_print + if $sel == $self->{preview3D_page_idx}; + } + } + }); }); # toolbar for object manipulation @@ -236,7 +257,33 @@ sub new { $self->{print_file} = $self->export_gcode(Wx::StandardPaths::Get->GetTempDir()); }); EVT_BUTTON($self, $self->{btn_send_gcode}, sub { - $self->{send_gcode_file} = $self->export_gcode(Wx::StandardPaths::Get->GetTempDir()); + my $filename = basename($self->{print}->output_filepath($main::opt{output})); + $filename = Wx::GetTextFromUser("Save to printer with the following name:", + "OctoPrint", $filename, $self); + + my $process_dialog = Wx::ProgressDialog->new('Querying OctoPrint…', "Checking whether file already exists…", 100, $self, 0); + $process_dialog->Pulse; + + my $ua = LWP::UserAgent->new; + $ua->timeout(5); + my $res = $ua->get("http://" . $self->{config}->octoprint_host . "/api/files/local"); + $process_dialog->Destroy; + if ($res->is_success) { + if ($res->decoded_content =~ /"name":\s*"\Q$filename\E"/) { + my $dialog = Wx::MessageDialog->new($self, + "It looks like a file with the same name already exists in the server. " + . "Shall I overwrite it?", + 'OctoPrint', wxICON_WARNING | wxYES | wxNO); + return if $dialog->ShowModal() == wxID_NO; + } + } + + my $dialog = Wx::MessageDialog->new($self, + "Shall I start the print after uploading the file?", + 'OctoPrint', wxICON_QUESTION | wxYES | wxNO); + $self->{send_gcode_file_print} = ($dialog->ShowModal() == wxID_YES); + + $self->{send_gcode_file} = $self->export_gcode(Wx::StandardPaths::Get->GetTempDir() . "/$filename"); }); EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl); @@ -513,6 +560,36 @@ sub add { $self->load_file($_) for @input_files; } +sub add_tin { + my $self = shift; + + my @input_files = wxTheApp->open_model($self); + return if !@input_files; + + my $offset = Wx::GetNumberFromUser("", "Enter the minimum thickness in mm (i.e. the offset from the lowest point):", "2.5D TIN", + 5, 0, 1000000, $self); + return if $offset < 0; + + foreach my $input_file (@input_files) { + my $model = eval { Slic3r::Model->read_from_file($input_file) }; + Slic3r::GUI::show_error($self, $@) if $@; + next if !$model; + + if ($model->looks_like_multipart_object) { + Slic3r::GUI::show_error($self, "Multi-part models cannot be opened as 2.5D TIN files. Please load a single continuous mesh."); + next; + } + + my $model_object = $model->get_object(0); + eval { + $model_object->get_volume(0)->extrude_tin($offset); + }; + Slic3r::GUI::show_error($self, $@) if $@; + + $self->load_model_objects($model_object); + } +} + sub load_file { my $self = shift; my ($input_file) = @_; @@ -528,6 +605,7 @@ sub load_file { my $model = eval { Slic3r::Model->read_from_file($input_file) }; Slic3r::GUI::show_error($self, $@) if $@; + my @obj_idx = (); if (defined $model) { if ($model->looks_like_multipart_object) { my $dialog = Wx::MessageDialog->new($self, @@ -539,11 +617,13 @@ sub load_file { $model->convert_multipart_object; } } - $self->load_model_objects(@{$model->objects}); + @obj_idx = $self->load_model_objects(@{$model->objects}); $self->statusbar->SetStatusText("Loaded " . basename($input_file)); } $process_dialog->Destroy; + + return @obj_idx; } sub load_model_objects { @@ -558,9 +638,10 @@ sub load_model_objects { my @obj_idx = (); foreach my $model_object (@model_objects) { my $o = $self->{model}->add_object($model_object); + $o->repair; push @{ $self->{objects} }, Slic3r::GUI::Plater::Object->new( - name => basename($model_object->input_file), + name => $model_object->name || basename($model_object->input_file), ); push @obj_idx, $#{ $self->{objects} }; @@ -624,6 +705,8 @@ sub load_model_objects { $self->object_list_changed; $self->schedule_background_process; + + return @obj_idx; } sub bed_centerf { @@ -772,24 +855,23 @@ sub rotate { if (!defined $angle) { my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z'; - $angle = Wx::GetNumberFromUser("", "Enter the rotation angle:", "Rotate around $axis_name axis", $model_instance->rotation, -364, 364, $self); - return if !$angle || $angle == -1; - $angle = 0 - $angle; # rotate clockwise (be consistent with button icon) + my $default = $axis == Z ? rad2deg($model_instance->rotation) : 0; + # Wx::GetNumberFromUser() does not support decimal numbers + $angle = Wx::GetTextFromUser("Enter the rotation angle:", "Rotate around $axis_name axis", + $default, $self); + return if !$angle || $angle !~ /^-?\d*(?:\.\d*)?$/ || $angle == -1; } $self->stop_background_process; if ($axis == Z) { - my $new_angle = $model_instance->rotation + deg2rad($angle); - $_->set_rotation($new_angle) for @{ $model_object->instances }; + my $new_angle = deg2rad($angle); + $_->set_rotation($_->rotation + $new_angle) for @{ $model_object->instances }; $object->transform_thumbnail($self->{model}, $obj_idx); } else { # rotation around X and Y needs to be performed on mesh # so we first apply any Z rotation - if ($model_instance->rotation != 0) { - $model_object->rotate($model_instance->rotation, Z); - $_->set_rotation(0) for @{ $model_object->instances }; - } + $model_object->transform_by_instance($model_instance, 1); $model_object->rotate(deg2rad($angle), $axis); # realign object to Z = 0 @@ -816,10 +898,7 @@ sub mirror { my $model_instance = $model_object->instances->[0]; # apply Z rotation before mirroring - if ($model_instance->rotation != 0) { - $model_object->rotate($model_instance->rotation, Z); - $_->set_rotation(0) for @{ $model_object->instances }; - } + $model_object->transform_by_instance($model_instance, 1); $model_object->mirror($axis); $model_object->update_bounding_box; @@ -857,21 +936,23 @@ sub changescale { my $scale; if ($tosize) { my $cursize = $object_size->[$axis]; - my $newsize = Wx::GetNumberFromUser("", "Enter the new size for the selected object:", "Scale along $axis_name", - $cursize, 0, $bed_size->[$axis], $self); - return if !$newsize || $newsize < 0; + # Wx::GetNumberFromUser() does not support decimal numbers + my $newsize = Wx::GetTextFromUser( + sprintf("Enter the new size for the selected object (print bed: %smm):", $bed_size->[$axis]), + "Scale along $axis_name", + $cursize, $self); + return if !$newsize || $newsize !~ /^\d*(?:\.\d*)?$/ || $newsize < 0; $scale = $newsize / $cursize * 100; } else { - $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale along $axis_name", - 100, 0, 100000, $self); + # Wx::GetNumberFromUser() does not support decimal numbers + $scale = Wx::GetTextFromUser("Enter the scale % for the selected object:", + "Scale along $axis_name", 100, $self); + $scale =~ s/%$//; + return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0; } - return if !$scale || $scale < 0; # apply Z rotation before scaling - if ($model_instance->rotation != 0) { - $model_object->rotate($model_instance->rotation, Z); - $_->set_rotation(0) for @{ $model_object->instances }; - } + $model_object->transform_by_instance($model_instance, 1); my $versor = [1,1,1]; $versor->[$axis] = $scale/100; @@ -882,16 +963,18 @@ sub changescale { my $scale; if ($tosize) { my $cursize = max(@$object_size); - my $newsize = Wx::GetNumberFromUser("", "Enter the new max size for the selected object:", "Scale", - $cursize, 0, max(@$bed_size), $self); - return if !$newsize || $newsize < 0; + # Wx::GetNumberFromUser() does not support decimal numbers + my $newsize = Wx::GetTextFromUser("Enter the new max size for the selected object:", + "Scale", $cursize, $self); + return if !$newsize || $newsize !~ /^\d*(?:\.\d*)?$/ || $newsize < 0; $scale = $newsize / $cursize * 100; } else { # max scale factor should be above 2540 to allow importing files exported in inches - $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", 'Scale', - $model_instance->scaling_factor*100, 0, 100000, $self); + # Wx::GetNumberFromUser() does not support decimal numbers + $scale = Wx::GetTextFromUser("Enter the scale % for the selected object:", 'Scale', + $model_instance->scaling_factor*100, $self); + return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0; } - return if !$scale || $scale < 0; $self->{list}->SetItem($obj_idx, 2, "$scale%"); $scale /= 100; # turn percent into factor @@ -921,9 +1004,7 @@ sub arrange { $self->pause_background_process; my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape); - eval { - $self->{model}->arrange_objects($self->GetFrame->config->min_object_distance, $bb); - }; + my $success = $self->{model}->arrange_objects($self->GetFrame->config->min_object_distance, $bb); # ignore arrange failures on purpose: user has visual feedback and we don't need to warn him # when parts don't fit in print bed @@ -977,11 +1058,22 @@ sub split_object { sub schedule_background_process { my ($self) = @_; + $self->{processed} = 0; + + if (!$Slic3r::GUI::Settings->{_}{background_processing}) { + my $sel = $self->{preview_notebook}->GetSelection; + if ($sel == $self->{preview3D_page_idx} || $sel == $self->{toolpaths2D_page_idx}) { + $self->{preview_notebook}->SetSelection(0); + } + } + if (defined $self->{apply_config_timer}) { $self->{apply_config_timer}->Start(PROCESS_DELAY, 1); # 1 = one shot } } +# Executed asynchronously by a timer every PROCESS_DELAY (0.5 second). +# The timer is started by schedule_background_process(), sub async_apply_config { my ($self) = @_; @@ -1062,6 +1154,7 @@ sub stop_background_process { $self->statusbar->SetCancelCallback(undef); $self->statusbar->StopBusy; $self->statusbar->SetStatusText(""); + $self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; $self->{preview3D}->reload_print if $self->{preview3D}; $self->{ObjectLayersDialog}->reload_preview if $self->{ObjectLayersDialog}; @@ -1138,9 +1231,9 @@ sub export_gcode { # select output file if ($output_file) { - $self->{export_gcode_output_file} = $self->{print}->expanded_output_filepath($output_file); + $self->{export_gcode_output_file} = $self->{print}->output_filepath($output_file); } else { - my $default_output_file = $self->{print}->expanded_output_filepath($main::opt{output}); + my $default_output_file = $self->{print}->output_filepath($main::opt{output}); my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', wxTheApp->output_path(dirname($default_output_file)), basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { @@ -1196,6 +1289,7 @@ sub on_process_completed { Slic3r::debugf "Background processing completed.\n"; $self->{process_thread}->detach if $self->{process_thread}; $self->{process_thread} = undef; + $self->{processed} = 1; # if we're supposed to perform an explicit export let's display the error in a dialog if ($error && $self->{export_gcode_output_file}) { @@ -1314,6 +1408,7 @@ sub send_gcode { # OctoPrint doesn't like Windows paths so we use basename() # Also, since we need to read from filesystem we process it through encode_path() file => [ $path, basename($path) ], + print => $self->{send_gcode_file_print} ? 1 : 0, ], ); @@ -1338,6 +1433,40 @@ sub export_stl { $self->statusbar->SetStatusText("STL file exported to $output_file"); } +sub reload_from_disk { + my ($self) = @_; + + my ($obj_idx, $object) = $self->selected_object; + return if !defined $obj_idx; + + my $model_object = $self->{model}->objects->[$obj_idx]; + return if !$model_object->input_file + || !-e $model_object->input_file; + + my @new_obj_idx = $self->load_file($model_object->input_file); + return if !@new_obj_idx; + + foreach my $new_obj_idx (@new_obj_idx) { + my $o = $self->{model}->objects->[$new_obj_idx]; + $o->clear_instances; + $o->add_instance($_) for @{$model_object->instances}; + + if ($o->volumes_count == $model_object->volumes_count) { + for my $i (0..($o->volumes_count-1)) { + $o->get_volume($i)->config->apply($model_object->get_volume($i)->config); + } + } + } + + $self->remove($obj_idx); + + # Trigger thumbnail generation again, because the remove() method altered + # object indexes before background thumbnail generation called its completion + # event, so the on_thumbnail_made callback is called with the wrong $obj_idx. + # When porting to C++ we'll probably have cleaner ways to do this. + $self->make_thumbnail($_-1) for @new_obj_idx; +} + sub export_object_stl { my $self = shift; @@ -1369,7 +1498,7 @@ sub _get_export_file { my $output_file = $main::opt{output}; { - $output_file = $self->{print}->expanded_output_filepath($output_file); + $output_file = $self->{print}->output_filepath($output_file); $output_file =~ s/\.gcode$/$suffix/i; my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file), basename($output_file), &Slic3r::GUI::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); @@ -1563,9 +1692,14 @@ sub object_cut_dialog { return unless $dlg->ShowModal == wxID_OK; if (my @new_objects = $dlg->NewModelObjects) { + my $process_dialog = Wx::ProgressDialog->new('Loading…', "Loading new objects…", 100, $self, 0); + $process_dialog->Pulse; + $self->remove($obj_idx); $self->load_model_objects(grep defined($_), @new_objects); - $self->arrange; + $self->arrange if @new_objects <= 2; # don't arrange for grid cuts + + $process_dialog->Destroy; } } @@ -1855,6 +1989,9 @@ sub object_menu { $self->object_settings_dialog; }, undef, 'cog.png'); $menu->AppendSeparator(); + $frame->_append_menu_item($menu, "Reload from Disk", 'Reload the selected file from Disk', sub { + $self->reload_from_disk; + }, undef, 'arrow_refresh.png'); $frame->_append_menu_item($menu, "Export object as STL…", 'Export this single object as STL file', sub { $self->export_object_stl; }, undef, 'brick_go.png'); @@ -1862,6 +1999,20 @@ sub object_menu { return $menu; } +# Set a camera direction, zoom to all objects. +sub select_view { + my ($self, $direction) = @_; + my $idx_page = $self->{preview_notebook}->GetSelection; + my $page = ($idx_page == &Wx::wxNOT_FOUND) ? '3D' : $self->{preview_notebook}->GetPageText($idx_page); + if ($page eq 'Preview') { + $self->{preview3D}->canvas->select_view($direction); + $self->{canvas3D}->set_viewport_from_scene($self->{preview3D}->canvas); + } else { + $self->{canvas3D}->select_view($direction); + $self->{preview3D}->canvas->set_viewport_from_scene($self->{canvas3D}); + } +} + package Slic3r::GUI::Plater::DropTarget; use Wx::DND; use base 'Wx::FileDropTarget'; @@ -1888,6 +2039,7 @@ sub OnDropFiles { $self->{window}->load_file($_) for @$filenames; } +# 2D preview of an object. Each object is previewed by its convex hull. package Slic3r::GUI::Plater::Object; use Moo; diff --git a/lib/Slic3r/GUI/Plater/2D.pm b/lib/Slic3r/GUI/Plater/2D.pm index 24bf80135..8fb8908e1 100644 --- a/lib/Slic3r/GUI/Plater/2D.pm +++ b/lib/Slic3r/GUI/Plater/2D.pm @@ -1,3 +1,6 @@ +# 2D preview on the platter. +# 3D objects are visualized by their convex hulls. + package Slic3r::GUI::Plater::2D; use strict; use warnings; diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm index ee59c2980..bc3d4e605 100644 --- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -1,3 +1,7 @@ +# 2D preview of the tool paths of a single layer, using a thin line. +# OpenGL is used to render the paths. +# Vojtech also added a 2D simulation of under/over extrusion in a single layer. + package Slic3r::GUI::Plater::2DToolpaths; use strict; use warnings; diff --git a/lib/Slic3r/GUI/Plater/3DPreview.pm b/lib/Slic3r/GUI/Plater/3DPreview.pm index fa71fcb1d..c1d1cf57e 100644 --- a/lib/Slic3r/GUI/Plater/3DPreview.pm +++ b/lib/Slic3r/GUI/Plater/3DPreview.pm @@ -126,7 +126,6 @@ sub load_print { #my @volume_ids = $self->canvas->load_object($object->model_object); #$self->canvas->volumes->[$_]->color->[3] = 0.2 for @volume_ids; } - $self->canvas->zoom_to_volumes; $self->_loaded(1); } diff --git a/lib/Slic3r/GUI/Plater/LambdaObjectDialog.pm b/lib/Slic3r/GUI/Plater/LambdaObjectDialog.pm new file mode 100644 index 000000000..e812ddaa0 --- /dev/null +++ b/lib/Slic3r/GUI/Plater/LambdaObjectDialog.pm @@ -0,0 +1,214 @@ +# Generate an anonymous or "lambda" 3D object. This gets used with the Create Modifier option in Settings. +# + +package Slic3r::GUI::Plater::LambdaObjectDialog; +use strict; +use warnings; +use utf8; + +use Slic3r::Geometry qw(PI X); +use Wx qw(wxTheApp :dialog :id :misc :sizer wxTAB_TRAVERSAL wxCB_READONLY wxTE_PROCESS_TAB); +use Wx::Event qw(EVT_CLOSE EVT_BUTTON EVT_COMBOBOX EVT_TEXT); +use Scalar::Util qw(looks_like_number); +use base 'Wx::Dialog'; + +sub new { + my $class = shift; + my ($parent, %params) = @_; + my $self = $class->SUPER::new($parent, -1, "Create Modifier", wxDefaultPosition, [500,500], + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); + + # Note whether the window was already closed, so a pending update is not executed. + $self->{already_closed} = 0; + $self->{object_parameters} = { + type => 'slab', + dim => [1, 1, 1], + cyl_r => 1, + cyl_h => 1, + sph_rho => 1.0, + slab_h => 1.0, + }; + + $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL); + my $buttons = $self->CreateStdDialogButtonSizer(wxOK | wxCANCEL); + EVT_BUTTON($self, wxID_OK, sub { + $self->EndModal(wxID_OK); + $self->Destroy; + }); + EVT_BUTTON($self, wxID_CANCEL, sub { + $self->EndModal(wxID_CANCEL); + $self->Destroy; + }); + + $self->{type} = Wx::ComboBox->new($self, 1, $self->{object_parameters}{type}, + wxDefaultPosition, wxDefaultSize, + [qw(slab box cylinder sphere)], wxCB_READONLY); + + my $optgroup_box; + $optgroup_box = $self->{optgroup_box} = Slic3r::GUI::OptionsGroup->new( + parent => $self, + title => 'Add Cube...', + on_change => sub { + # Do validation + my ($opt_id) = @_; + if ($opt_id == 0 || $opt_id == 1 || $opt_id == 2) { + if (!looks_like_number($optgroup_box->get_value($opt_id))) { + return 0; + } + } + $self->{object_parameters}->{dim}[$opt_id] = $optgroup_box->get_value($opt_id); + }, + label_width => 100, + ); + + $optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 0, + label => 'L (x)', + type => 'f', + default => $self->{object_parameters}{dim}[0], + sidetext => 'mm', + )); + $optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 1, + label => 'W (y)', + type => 'f', + default => $self->{object_parameters}{dim}[1], + sidetext => 'mm', + )); + $optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 2, + label => 'H (z)', + type => 'f', + default => $self->{object_parameters}{dim}[2], + sidetext => 'mm', + )); + + my $optgroup_cylinder; + $optgroup_cylinder = $self->{optgroup_cylinder} = Slic3r::GUI::OptionsGroup->new( + parent => $self, + title => 'Add Cylinder...', + on_change => sub { + # Do validation + my ($opt_id) = @_; + if ($opt_id eq 'cyl_r' || $opt_id eq 'cyl_h') { + if (!looks_like_number($optgroup_cylinder->get_value($opt_id))) { + return 0; + } + } + $self->{object_parameters}->{$opt_id} = $optgroup_cylinder->get_value($opt_id); + }, + label_width => 100, + ); + + $optgroup_cylinder->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => "cyl_r", + label => 'Radius', + type => 'f', + default => $self->{object_parameters}{cyl_r}, + sidetext => 'mm', + )); + $optgroup_cylinder->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => "cyl_h", + label => 'Height', + type => 'f', + default => $self->{object_parameters}{cyl_h}, + sidetext => 'mm', + )); + + my $optgroup_sphere; + $optgroup_sphere = $self->{optgroup_sphere} = Slic3r::GUI::OptionsGroup->new( + parent => $self, + title => 'Add Sphere...', + on_change => sub { + # Do validation + my ($opt_id) = @_; + if ($opt_id eq 'sph_rho') { + if (!looks_like_number($optgroup_sphere->get_value($opt_id))) { + return 0; + } + } + $self->{object_parameters}->{$opt_id} = $optgroup_sphere->get_value($opt_id); + }, + label_width => 100, + ); + + $optgroup_sphere->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => "sph_rho", + label => 'Radius', + type => 'f', + default => $self->{object_parameters}{sph_rho}, + sidetext => 'mm', + )); + + my $optgroup_slab; + $optgroup_slab = $self->{optgroup_slab} = Slic3r::GUI::OptionsGroup->new( + parent => $self, + title => 'Add Slab...', + on_change => sub { + # Do validation + my ($opt_id) = @_; + if ($opt_id eq 'slab_h') { + if (!looks_like_number($optgroup_slab->get_value($opt_id))) { + return 0; + } + } + $self->{object_parameters}->{$opt_id} = $optgroup_slab->get_value($opt_id); + }, + label_width => 100, + ); + $optgroup_slab->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => "slab_h", + label => 'Thickness', + type => 'f', + default => $self->{object_parameters}{slab_h}, + sidetext => 'mm', + )); + + + EVT_COMBOBOX($self, 1, sub{ + $self->{object_parameters}->{type} = $self->{type}->GetValue(); + $self->_update_ui; + }); + + + $self->{sizer}->Add($self->{type}, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); + $self->{sizer}->Add($optgroup_box->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); + $self->{sizer}->Add($optgroup_cylinder->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); + $self->{sizer}->Add($optgroup_sphere->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); + $self->{sizer}->Add($optgroup_slab->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); + $self->{sizer}->Add($buttons,0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); + $self->_update_ui; + + $self->SetSizer($self->{sizer}); + $self->{sizer}->Fit($self); + $self->{sizer}->SetSizeHints($self); + + + return $self; +} + +sub ObjectParameter { + my ($self) = @_; + return $self->{object_parameters}; +} + +sub _update_ui { + my ($self) = @_; + $self->{sizer}->Hide($self->{optgroup_cylinder}->sizer); + $self->{sizer}->Hide($self->{optgroup_slab}->sizer); + $self->{sizer}->Hide($self->{optgroup_box}->sizer); + $self->{sizer}->Hide($self->{optgroup_sphere}->sizer); + if ($self->{type}->GetValue eq "box") { + $self->{sizer}->Show($self->{optgroup_box}->sizer); + } elsif ($self->{type}->GetValue eq "cylinder") { + $self->{sizer}->Show($self->{optgroup_cylinder}->sizer); + } elsif ($self->{type}->GetValue eq "slab") { + $self->{sizer}->Show($self->{optgroup_slab}->sizer); + } elsif ($self->{type}->GetValue eq "sphere") { + $self->{sizer}->Show($self->{optgroup_sphere}->sizer); + } + $self->{sizer}->Fit($self); + $self->{sizer}->SetSizeHints($self); + +} +1; diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm index a523fb085..cf4f3d99d 100644 --- a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm @@ -1,9 +1,14 @@ +# Cut an object at a Z position, keep either the top or the bottom of the object. +# This dialog gets opened with the "Cut..." button above the platter. + package Slic3r::GUI::Plater::ObjectCutDialog; use strict; use warnings; use utf8; -use Slic3r::Geometry qw(PI X); +use POSIX qw(ceil); +use Scalar::Util qw(looks_like_number); +use Slic3r::Geometry qw(PI X Y Z); use Wx qw(wxTheApp :dialog :id :misc :sizer wxTAB_TRAVERSAL); use Wx::Event qw(EVT_CLOSE EVT_BUTTON); use base 'Wx::Dialog'; @@ -21,12 +26,16 @@ sub new { # Note whether the window was already closed, so a pending update is not executed. $self->{already_closed} = 0; + $self->{model_object}->transform_by_instance($self->{model_object}->get_instance(0), 1); + # cut options + my $size_z = $self->{model_object}->instance_bounding_box(0)->size->z; $self->{cut_options} = { - z => 0, - keep_upper => 1, + axis => Z, + z => $size_z/2, + keep_upper => 0, keep_lower => 1, - rotate_lower => 1, + rotate_lower => 0, preview => 1, }; @@ -51,13 +60,21 @@ sub new { }, label_width => 120, ); + $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 'axis', + type => 'select', + label => 'Axis', + labels => ['X','Y','Z'], + values => [X,Y,Z], + default => $self->{cut_options}{axis}, + )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'z', type => 'slider', label => 'Z', default => $self->{cut_options}{z}, min => 0, - max => $self->{model_object}->bounding_box->size->z, + max => $size_z, full_width => 1, )); { @@ -94,8 +111,14 @@ sub new { )); { my $cut_button_sizer = Wx::BoxSizer->new(wxVERTICAL); + $self->{btn_cut} = Wx::Button->new($self, -1, "Perform cut", wxDefaultPosition, wxDefaultSize); + $self->{btn_cut}->SetDefault; $cut_button_sizer->Add($self->{btn_cut}, 0, wxALIGN_RIGHT | wxALL, 10); + + $self->{btn_cut_grid} = Wx::Button->new($self, -1, "Cut by grid…", wxDefaultPosition, wxDefaultSize); + $cut_button_sizer->Add($self->{btn_cut_grid}, 0, wxALIGN_RIGHT | wxALL, 10); + $optgroup->append_line(Slic3r::GUI::OptionsGroup::Line->new( sizer => $cut_button_sizer, )); @@ -129,14 +152,14 @@ sub new { $self->_perform_cut() unless $self->{mesh_cut_valid}; # Adjust position / orientation of the split object halves. - if ($self->{new_model_objects}{lower}) { - if ($self->{cut_options}{rotate_lower}) { - $self->{new_model_objects}{lower}->rotate(PI, X); - $self->{new_model_objects}{lower}->center_around_origin; # align to Z = 0 + if (my $lower = $self->{new_model_objects}[0]) { + if ($self->{cut_options}{rotate_lower} && $self->{cut_options}{axis} == Z) { + $lower->rotate(PI, X); } + $lower->center_around_origin; # align to Z = 0 } - if ($self->{new_model_objects}{upper}) { - $self->{new_model_objects}{upper}->center_around_origin; # align to Z = 0 + if (my $upper = $self->{new_model_objects}[1]) { + $upper->center_around_origin; # align to Z = 0 } # Note that the window was already closed, so a pending update will not be executed. @@ -144,6 +167,49 @@ sub new { $self->EndModal(wxID_OK); $self->Destroy(); }); + + EVT_BUTTON($self, $self->{btn_cut_grid}, sub { + my $grid_x = Wx::GetTextFromUser("Enter the width of the desired tiles along the X axis:", + "Cut by Grid", 100, $self); + return if !looks_like_number($grid_x) || $grid_x <= 0; + + my $grid_y = Wx::GetTextFromUser("Enter the width of the desired tiles along the Y axis:", + "Cut by Grid", 100, $self); + return if !looks_like_number($grid_y) || $grid_y <= 0; + + my $process_dialog = Wx::ProgressDialog->new('Cutting…', "Cutting model by grid…", 100, $self, 0); + $process_dialog->Pulse; + + my $meshes = $self->{model_object}->mesh->cut_by_grid(Slic3r::Pointf->new($grid_x, $grid_y)); + $self->{new_model_objects} = []; + + my $bb = $self->{model_object}->bounding_box; + $self->{new_model} = my $model = Slic3r::Model->new; + for my $i (0..$#$meshes) { + push @{$self->{new_model_objects}}, my $o = $model->add_object( + name => sprintf('%s (%d)', $self->{model_object}->name, $i+1), + ); + my $v = $o->add_volume( + mesh => $meshes->[$i], + name => $o->name, + ); + $o->center_around_origin; + my $i = $o->add_instance( + offset => Slic3r::Pointf->new(@{$o->origin_translation->negative}[X,Y]), + ); + $i->offset->translate( + 5 * ceil(($i->offset->x - $bb->center->x) / $grid_x), + 5 * ceil(($i->offset->y - $bb->center->y) / $grid_y), + ); + } + + $process_dialog->Destroy; + + # Note that the window was already closed, so a pending update will not be executed. + $self->{already_closed} = 1; + $self->EndModal(wxID_OK); + $self->Destroy(); + }); EVT_CLOSE($self, sub { # Note that the window was already closed, so a pending update will not be executed. @@ -162,7 +228,15 @@ sub new { sub _mesh_slice_z_pos { my ($self) = @_; - return $self->{cut_options}{z} / $self->{model_object}->instances->[0]->scaling_factor; + + my $bb = $self->{model_object}->instance_bounding_box(0); + my $z = $self->{cut_options}{axis} == X ? $bb->x_min + : $self->{cut_options}{axis} == Y ? $bb->y_min + : $bb->z_min; + + $z += $self->{cut_options}{z} / $self->{model_object}->instances->[0]->scaling_factor; + + return $z; } # Only perform live preview if just a single part of the object shall survive. @@ -181,16 +255,16 @@ sub _perform_cut return if $self->{mesh_cut_valid}; my $z = $self->_mesh_slice_z_pos(); - - my ($new_model) = $self->{model_object}->cut($z); + + my ($new_model) = $self->{model_object}->cut($self->{cut_options}{axis}, $z); my ($upper_object, $lower_object) = @{$new_model->objects}; $self->{new_model} = $new_model; - $self->{new_model_objects} = {}; + $self->{new_model_objects} = []; if ($self->{cut_options}{keep_upper} && $upper_object->volumes_count > 0) { - $self->{new_model_objects}{upper} = $upper_object; + $self->{new_model_objects}[1] = $upper_object; } if ($self->{cut_options}{keep_lower} && $lower_object->volumes_count > 0) { - $self->{new_model_objects}{lower} = $lower_object; + $self->{new_model_objects}[0] = $lower_object; } $self->{mesh_cut_valid} = 1; @@ -207,41 +281,53 @@ sub _update { # Only recalculate the cut, if the live cut preview is active. my $life_preview_active = $self->_life_preview_active(); $self->_perform_cut() if $life_preview_active; - + { # scale Z down to original size since we're using the transformed mesh for 3D preview # and cut dialog but ModelObject::cut() needs Z without any instance transformation my $z = $self->_mesh_slice_z_pos(); - # update canvas if ($self->{canvas}) { # get volumes to render my @objects = (); if ($life_preview_active) { - push @objects, values %{$self->{new_model_objects}}; + push @objects, grep defined, @{$self->{new_model_objects}}; } else { push @objects, $self->{model_object}; } - + # get section contour my @expolygons = (); foreach my $volume (@{$self->{model_object}->volumes}) { next if !$volume->mesh; next if $volume->modifier; - my $expp = $volume->mesh->slice([ $z + $volume->mesh->bounding_box->z_min ])->[0]; + my $expp = $volume->mesh->slice_at($self->{cut_options}{axis}, $z); push @expolygons, @$expp; } + + my $offset = $self->{model_object}->instances->[0]->offset; foreach my $expolygon (@expolygons) { $self->{model_object}->instances->[0]->transform_polygon($_) for @$expolygon; - $expolygon->translate(map Slic3r::Geometry::scale($_), @{ $self->{model_object}->instances->[0]->offset }); + + if ($self->{cut_options}{axis} != X) { + $expolygon->translate(0, Slic3r::Geometry::scale($offset->y)); #) + } + if ($self->{cut_options}{axis} != Y) { + $expolygon->translate(Slic3r::Geometry::scale($offset->x), 0); + } } $self->{canvas}->reset_objects; $self->{canvas}->load_object($_, undef, [0]) for @objects; + + my $plane_z = $self->{cut_options}{z}; + $plane_z += 0.02 if !$self->{cut_options}{keep_upper}; + $plane_z -= 0.02 if !$self->{cut_options}{keep_lower}; $self->{canvas}->SetCuttingPlane( - $self->{cut_options}{z}, + $self->{cut_options}{axis}, + $plane_z, [@expolygons], ); $self->{canvas}->Render; @@ -252,9 +338,16 @@ sub _update { { my $z = $self->{cut_options}{z}; my $optgroup = $self->{optgroup}; + { + my $bb = $self->{model_object}->instance_bounding_box(0); + my $max = $self->{cut_options}{axis} == X ? $bb->size->x + : $self->{cut_options}{axis} == Y ? $bb->size->y ### + : $bb->size->z; + $optgroup->get_field('z')->set_range(0, $max); + } $optgroup->get_field('keep_upper')->toggle(my $have_upper = abs($z - $optgroup->get_option('z')->max) > 0.1); $optgroup->get_field('keep_lower')->toggle(my $have_lower = $z > 0.1); - $optgroup->get_field('rotate_lower')->toggle($z > 0 && $self->{cut_options}{keep_lower}); + $optgroup->get_field('rotate_lower')->toggle($z > 0 && $self->{cut_options}{keep_lower} && $self->{cut_options}{axis} == Z); $optgroup->get_field('preview')->toggle($self->{cut_options}{keep_upper} != $self->{cut_options}{keep_lower}); # update cut button @@ -269,7 +362,7 @@ sub _update { sub NewModelObjects { my ($self) = @_; - return values %{ $self->{new_model_objects} }; + return grep defined, @{ $self->{new_model_objects} }; } 1; diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index a75be5902..1898ac6a0 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -1,10 +1,13 @@ +# Configuration of mesh modifiers and their parameters. +# This panel is inserted into ObjectSettingsDialog. + package Slic3r::GUI::Plater::ObjectPartsPanel; use strict; use warnings; use utf8; use File::Basename qw(basename); -use Wx qw(:misc :sizer :treectrl :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG +use Wx qw(:misc :sizer :treectrl :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxID_CANCEL wxTheApp); use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED); use base 'Wx::Panel'; @@ -38,10 +41,12 @@ sub new { # buttons $self->{btn_load_part} = Wx::Button->new($self, -1, "Load part…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $self->{btn_load_modifier} = Wx::Button->new($self, -1, "Load modifier…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + $self->{btn_load_lambda_modifier} = Wx::Button->new($self, -1, "Create modifier…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $self->{btn_delete} = Wx::Button->new($self, -1, "Delete part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); if ($Slic3r::GUI::have_button_icons) { $self->{btn_load_part}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG)); $self->{btn_load_modifier}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG)); + $self->{btn_load_lambda_modifier}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG)); $self->{btn_delete}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_delete.png"), wxBITMAP_TYPE_PNG)); } @@ -49,21 +54,70 @@ sub new { my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL); $buttons_sizer->Add($self->{btn_load_part}, 0); $buttons_sizer->Add($self->{btn_load_modifier}, 0); + $buttons_sizer->Add($self->{btn_load_lambda_modifier}, 0); $buttons_sizer->Add($self->{btn_delete}, 0); $self->{btn_load_part}->SetFont($Slic3r::GUI::small_font); $self->{btn_load_modifier}->SetFont($Slic3r::GUI::small_font); + $self->{btn_load_lambda_modifier}->SetFont($Slic3r::GUI::small_font); $self->{btn_delete}->SetFont($Slic3r::GUI::small_font); # part settings panel $self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->{part_settings_changed} = 1; }); my $settings_sizer = Wx::StaticBoxSizer->new($self->{staticbox} = Wx::StaticBox->new($self, -1, "Part Settings"), wxVERTICAL); $settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0); - + + my $optgroup_movers; + # initialize the movement target before it's used. + # on Windows this causes a segfault due to calling distance_to() + # on the object. + $self->{move_target} = Slic3r::Pointf3->new; + $optgroup_movers = $self->{optgroup_movers} = Slic3r::GUI::OptionsGroup->new( + parent => $self, + title => 'Move', + on_change => sub { + my ($opt_id) = @_; + # There seems to be an issue with wxWidgets 3.0.2/3.0.3, where the slider + # genates tens of events for a single value change. + # Only trigger the recalculation if the value changes + # or a live preview was activated and the mesh cut is not valid yet. + my $new = Slic3r::Pointf3->new(map $optgroup_movers->get_value($_), qw(x y z)); + if ($self->{move_target}->distance_to($new) > 0) { + $self->{move_target} = $new; + wxTheApp->CallAfter(sub { + $self->_update; + }); + } + }, + label_width => 20, + ); + $optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 'x', + type => 'slider', + label => 'X', + default => 0, + full_width => 1, + )); + $optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 'y', + type => 'slider', + label => 'Y', + default => 0, + full_width => 1, + )); + $optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 'z', + type => 'slider', + label => 'Z', + default => 0, + full_width => 1, + )); + # left pane with tree - my $left_sizer = Wx::BoxSizer->new(wxVERTICAL); + my $left_sizer = $self->{left_sizer} = Wx::BoxSizer->new(wxVERTICAL); $left_sizer->Add($tree, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10); $left_sizer->Add($buttons_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10); $left_sizer->Add($settings_sizer, 1, wxEXPAND | wxALL, 0); + $left_sizer->Add($optgroup_movers->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); # right pane with preview canvas my $canvas; @@ -81,7 +135,7 @@ sub new { $canvas->load_object($self->{model_object}, undef, [0]); $canvas->set_auto_bed_shape; - $canvas->SetSize([500,500]); + $canvas->SetSize([500,700]); $canvas->zoom_to_volumes; } @@ -104,6 +158,7 @@ sub new { }); EVT_BUTTON($self, $self->{btn_load_part}, sub { $self->on_btn_load(0) }); EVT_BUTTON($self, $self->{btn_load_modifier}, sub { $self->on_btn_load(1) }); + EVT_BUTTON($self, $self->{btn_load_lambda_modifier}, sub { $self->on_btn_lambda(1) }); EVT_BUTTON($self, $self->{btn_delete}, \&on_btn_delete); $self->reload_tree; @@ -176,6 +231,12 @@ sub selection_changed { $self->{btn_delete}->Disable; $self->{settings_panel}->disable; $self->{settings_panel}->set_config(undef); + + # reset move sliders + $self->{optgroup_movers}->set_value("x", 0); + $self->{optgroup_movers}->set_value("y", 0); + $self->{optgroup_movers}->set_value("z", 0); + $self->{move_target} = Slic3r::Pointf3->new; if (my $itemData = $self->get_selection) { my ($config, @opt_keys); @@ -188,6 +249,24 @@ sub selection_changed { # attach volume config to settings panel my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ]; + + if ($volume->modifier) { + my $movers = $self->{optgroup_movers}; + + my $obj_bb = $self->{model_object}->raw_bounding_box; + my $vol_bb = $volume->mesh->bounding_box; + my $vol_size = $vol_bb->size; + $movers->get_field('x')->set_range($obj_bb->x_min - $vol_size->x, $obj_bb->x_max); + $movers->get_field('y')->set_range($obj_bb->y_min - $vol_size->y, $obj_bb->y_max); #,, + $movers->get_field('z')->set_range($obj_bb->z_min - $vol_size->z, $obj_bb->z_max); + $movers->get_field('x')->set_value($vol_bb->x_min); + $movers->get_field('y')->set_value($vol_bb->y_min); + $movers->get_field('z')->set_value($vol_bb->z_min); + + $self->{left_sizer}->Show($movers->sizer); + } else { + $self->{left_sizer}->Hide($self->{optgroup_movers}->sizer); + } $config = $volume->config; $self->{staticbox}->SetLabel('Part Settings'); @@ -197,6 +276,7 @@ sub selection_changed { # select nothing in 3D preview # attach object config to settings panel + $self->{left_sizer}->Hide($self->{optgroup_movers}->sizer); $self->{staticbox}->SetLabel('Object Settings'); @opt_keys = (map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new); $config = $self->{model_object}->config; @@ -249,19 +329,65 @@ sub on_btn_load { $self->_parts_changed; } +sub on_btn_lambda { + my ($self, $is_modifier) = @_; + + my $dlg = Slic3r::GUI::Plater::LambdaObjectDialog->new($self); + if ($dlg->ShowModal() == wxID_CANCEL) { + return; + } + my $params = $dlg->ObjectParameter; + my $type = "".$params->{"type"}; + my $name = "lambda-".$params->{"type"}; + my $mesh; + + if ($type eq "box") { + $mesh = Slic3r::TriangleMesh::make_cube(@{$params->{"dim"}}); + } elsif ($type eq "cylinder") { + $mesh = Slic3r::TriangleMesh::make_cylinder($params->{"cyl_r"}, $params->{"cyl_h"}); + } elsif ($type eq "sphere") { + $mesh = Slic3r::TriangleMesh::make_sphere($params->{"sph_rho"}); + } elsif ($type eq "slab") { + my $size = $self->{model_object}->bounding_box->size; + $mesh = Slic3r::TriangleMesh::make_cube( + $size->x*1.5, + $size->y*1.5, #** + $params->{"slab_h"}, + ); + # box sets the base coordinate at 0,0, move to center of plate + $mesh->translate( + -$size->x*1.5/2.0, + -$size->y*1.5/2.0, #** + 0, + ); + } else { + return; + } + $mesh->repair; + my $new_volume = $self->{model_object}->add_volume(mesh => $mesh); + $new_volume->set_modifier($is_modifier); + $new_volume->set_name($name); + + # set a default extruder value, since user can't add it manually + $new_volume->config->set_ifndef('extruder', 0); + + $self->{parts_changed} = 1; + $self->_parts_changed($self->{model_object}->volumes_count-1); +} + sub on_btn_delete { my ($self) = @_; - + my $itemData = $self->get_selection; if ($itemData && $itemData->{type} eq 'volume') { my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}]; - + # if user is deleting the last solid part, throw error if (!$volume->modifier && scalar(grep !$_->modifier, @{$self->{model_object}->volumes}) == 1) { Slic3r::GUI::show_error($self, "You can't delete the last solid part from this object."); return; } - + $self->{model_object}->delete_volume($itemData->{volume_id}); $self->{parts_changed} = 1; } @@ -270,9 +396,9 @@ sub on_btn_delete { } sub _parts_changed { - my ($self) = @_; + my ($self, $selected_volume_idx) = @_; - $self->reload_tree; + $self->reload_tree($selected_volume_idx); if ($self->{canvas}) { $self->{canvas}->reset_objects; $self->{canvas}->load_object($self->{model_object}); @@ -307,4 +433,21 @@ sub PartSettingsChanged { return $self->{part_settings_changed}; } +sub _update { + my ($self) = @_; + + my $itemData = $self->get_selection; + if ($itemData && $itemData->{type} eq 'volume') { + my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}]; + $volume->mesh->translate(@{ $volume->mesh->bounding_box->min_point->vector_to($self->{move_target}) }); + } + + $self->{parts_changed} = 1; + my @objects = (); + push @objects, $self->{model_object}; + $self->{canvas}->reset_objects; + $self->{canvas}->load_object($_, undef, [0]) for @objects; + $self->{canvas}->Render; +} + 1; diff --git a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm index c920b796a..d7909816b 100644 --- a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm @@ -1,3 +1,7 @@ +# This dialog opens up when double clicked on an object line in the list at the right side of the platter. +# One may load additional STLs and additional modifier STLs, +# one may change the properties of the print per each modifier mesh or a Z-span. + package Slic3r::GUI::Plater::ObjectSettingsDialog; use strict; use warnings; diff --git a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm index 1b976c10e..e848b7638 100644 --- a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm +++ b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm @@ -1,3 +1,6 @@ +# Included in ObjectSettingsDialog -> ObjectPartsPanel. +# Maintains, displays, adds and removes overrides of slicing parameters for an object and its modifier mesh. + package Slic3r::GUI::Plater::OverrideSettingsPanel; use strict; use warnings; diff --git a/lib/Slic3r/GUI/Preferences.pm b/lib/Slic3r/GUI/Preferences.pm index ed210d229..431f642d6 100644 --- a/lib/Slic3r/GUI/Preferences.pm +++ b/lib/Slic3r/GUI/Preferences.pm @@ -1,3 +1,5 @@ +# Preferences dialog, opens from Menu: File->Preferences + package Slic3r::GUI::Preferences; use Wx qw(:dialog :id :misc :sizer :systemsettings wxTheApp); use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER); diff --git a/lib/Slic3r/GUI/ProgressStatusBar.pm b/lib/Slic3r/GUI/ProgressStatusBar.pm index b901740a9..32fd52680 100644 --- a/lib/Slic3r/GUI/ProgressStatusBar.pm +++ b/lib/Slic3r/GUI/ProgressStatusBar.pm @@ -1,3 +1,5 @@ +# Status bar at the bottom of the main screen. + package Slic3r::GUI::ProgressStatusBar; use strict; use warnings; diff --git a/lib/Slic3r/GUI/Projector.pm b/lib/Slic3r/GUI/Projector.pm index a783ecf55..b6d1cb5a6 100644 --- a/lib/Slic3r/GUI/Projector.pm +++ b/lib/Slic3r/GUI/Projector.pm @@ -1,7 +1,10 @@ +# DLP Projector screen for the SLA (stereolitography) print process + package Slic3r::GUI::Projector; use strict; use warnings; -use Wx qw(:dialog :id :misc :sizer :systemsettings :bitmap :button :icon wxTheApp); +use File::Basename qw(basename dirname); +use Wx qw(:dialog :id :misc :sizer :systemsettings :bitmap :button :icon :filedialog wxTheApp); use Wx::Event qw(EVT_BUTTON EVT_CLOSE EVT_TEXT_ENTER EVT_SPINCTRL EVT_SLIDER); use base qw(Wx::Dialog Class::Accessor); use utf8; @@ -376,10 +379,23 @@ sub new { { # should be wxCLOSE but it crashes on Linux, maybe it's a Wx bug - my $buttons = $self->CreateStdDialogButtonSizer(wxOK); - EVT_BUTTON($self, wxID_OK, sub { - $self->_close; - }); + my $buttons = Wx::BoxSizer->new(wxHORIZONTAL); + { + my $btn = Wx::Button->new($self, -1, "Export SVG…"); + EVT_BUTTON($self, $btn, sub { + $self->_export_svg; + }); + $buttons->Add($btn, 0); + } + $buttons->AddStretchSpacer(1); + { + my $btn = Wx::Button->new($self, -1, "Close"); + $btn->SetDefault; + EVT_BUTTON($self, $btn, sub { + $self->_close; + }); + $buttons->Add($btn, 0); + } $sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); } EVT_CLOSE($self, sub { @@ -415,7 +431,7 @@ sub new { my $duration = $self->controller->remaining_print_time; $self->_set_status(sprintf "Printing layer %d/%d (z = %.2f); %d minutes and %d seconds left", - $layer_num, $self->controller->layer_count, + $layer_num, $self->controller->_print->layer_count, $self->controller->current_layer_height, int($duration/60), ($duration - int($duration/60)*60)); # % truncates to integer }, @@ -426,7 +442,7 @@ sub new { }, )); { - my $max = $self->controller->layer_count-1; + my $max = $self->controller->_print->layer_count-1; $self->{layers_spinctrl}->SetRange(0, $max); $self->{layers_slider}->SetRange(0, $max); } @@ -455,6 +471,27 @@ sub _update_buttons { $self->Layout; } +sub _export_svg { + my ($self) = @_; + + my $output_file = 'print.svg'; + my $dlg = Wx::FileDialog->new( + $self, + 'Save SVG file as:', + wxTheApp->output_path(dirname($output_file)), + basename($output_file), + &Slic3r::GUI::FILE_WILDCARDS->{svg}, + wxFD_SAVE | wxFD_OVERWRITE_PROMPT, + ); + if ($dlg->ShowModal != wxID_OK) { + $dlg->Destroy; + return; + } + $output_file = Slic3r::decode_path($dlg->GetPath); + + $self->controller->_print->write_svg($output_file); +} + sub _set_status { my ($self, $status) = @_; $self->{status_text}->SetLabel($status // ''); @@ -468,8 +505,9 @@ sub show_print_time { my $duration = $self->controller->print_time; - $self->_set_status(sprintf "Estimated print time: %d minutes and %d seconds", - int($duration/60), ($duration - int($duration/60)*60)); # % truncates to integer + $self->_set_status(sprintf "Estimated print time: %d minutes and %d seconds - %.2f liters", + int($duration/60), ($duration - int($duration/60)*60), # % truncates to integer + $self->controller->total_resin); } sub _close { @@ -505,6 +543,7 @@ package Slic3r::GUI::Projector::Controller; use Moo; use Wx qw(wxTheApp :id :timer); use Wx::Event qw(EVT_TIMER); +use Slic3r::Geometry qw(unscale); use Slic3r::Print::State ':steps'; use Time::HiRes qw(gettimeofday tv_interval); @@ -517,7 +556,6 @@ has 'sender' => (is => 'rw'); has 'timer' => (is => 'rw'); has 'is_printing' => (is => 'rw', default => sub { 0 }); has '_print' => (is => 'rw'); -has '_layers' => (is => 'rw'); has '_heights' => (is => 'rw'); has '_layer_num' => (is => 'rw'); has '_timer_cb' => (is => 'rw'); @@ -527,7 +565,23 @@ sub BUILD { Slic3r::GUI::disable_screensaver(); - $self->set_print(wxTheApp->{mainframe}->{plater}->{print}); + # init print + { + my $print = Slic3r::SLAPrint->new(wxTheApp->{mainframe}->{plater}->{model}); + $print->apply_config(wxTheApp->{mainframe}->config); + $self->_print($print); + $self->screen->print($print); + + # make sure layers were sliced + { + my $progress_dialog = Wx::ProgressDialog->new('Slicing…', "Processing layers…", 100, undef, 0); + $progress_dialog->Pulse; + $print->slice; + $progress_dialog->Destroy; + } + + $self->_heights($print->heights); + } # projection timer my $timer_id = &Wx::NewId(); @@ -546,40 +600,6 @@ sub delay { $self->timer->Start($wait * 1000, wxTIMER_ONE_SHOT); } -sub set_print { - my ($self, $print) = @_; - - # make sure layers were sliced - { - my $progress_dialog; - foreach my $object (@{$print->objects}) { - next if $object->step_done(STEP_SLICE); - $progress_dialog //= Wx::ProgressDialog->new('Slicing…', "Processing layers…", 100, undef, 0); - $progress_dialog->Pulse; - $object->slice; - } - $progress_dialog->Destroy if $progress_dialog; - } - - $self->_print($print); - - # sort layers by Z - my %layers = (); - foreach my $layer (map { @{$_->layers}, @{$_->support_layers} } @{$print->objects}) { - my $height = $layer->print_z; - $layers{$height} //= []; - push @{$layers{$height}}, $layer; - } - $self->_layers({ %layers }); - $self->_heights([ sort { $a <=> $b } keys %layers ]); -} - -sub layer_count { - my ($self) = @_; - - return scalar @{$self->_heights}; -} - sub current_layer_height { my ($self) = @_; @@ -613,7 +633,7 @@ sub start_print { # start with black Slic3r::debugf "starting black projection\n"; $self->_layer_num(-1); - $self->screen->project_layers(undef); + $self->screen->project_layer(undef); $self->delay($self->config2->{settle_time}, sub { $self->project_next_layer; }); @@ -630,7 +650,7 @@ sub stop_print { $self->is_printing(0); $self->timer->Stop; $self->_timer_cb(undef); - $self->screen->project_layers(undef); + $self->screen->project_layer(undef); } sub print_completed { @@ -652,19 +672,18 @@ sub print_completed { sub is_projecting { my ($self) = @_; - return defined $self->screen->layers; + return defined $self->screen->layer_num; } sub project_layer { my ($self, $layer_num) = @_; - if (!defined $layer_num || $layer_num >= $self->layer_count) { - $self->screen->project_layers(undef); + if (!defined $layer_num || $layer_num >= $self->_print->layer_count) { + $self->screen->project_layer(undef); return; } - my @layers = @{ $self->_layers->{ $self->_heights->[$layer_num] } }; - $self->screen->project_layers([ @layers ]); + $self->screen->project_layer($layer_num); } sub project_next_layer { @@ -672,7 +691,7 @@ sub project_next_layer { $self->_layer_num($self->_layer_num + 1); Slic3r::debugf "projecting layer %d\n", $self->_layer_num; - if ($self->_layer_num >= $self->layer_count) { + if ($self->_layer_num >= $self->_print->layer_count) { $self->print_completed; return; } @@ -699,7 +718,7 @@ sub project_next_layer { } $self->delay($time, sub { - $self->screen->project_layers(undef); + $self->screen->project_layer(undef); $self->project_next_layer; }); }); @@ -722,8 +741,21 @@ sub print_time { my ($self) = @_; return $self->config2->{bottom_layers} * $self->config2->{bottom_exposure_time} - + (@{$self->_heights} - $self->config2->{bottom_layers}) * $self->config2->{exposure_time} - + @{$self->_heights} * $self->config2->{settle_time}; + + ($self->_print->layer_count - $self->config2->{bottom_layers}) * $self->config2->{exposure_time} + + $self->_print->layer_count * $self->config2->{settle_time}; +} + +sub total_resin { + my ($self) = @_; + + my $vol = 0; # mm^3 + + for my $i (0..($self->_print->layer_count-1)) { + my $lh = $self->_heights->[$i] - ($i == 0 ? 0 : $self->_heights->[$i-1]); + $vol += unscale(unscale($_->area)) * $lh for @{ $self->_print->layer_slices($i) }; + } + + return $vol/1000/1000; # liters } sub DESTROY { @@ -741,9 +773,9 @@ use base qw(Wx::Dialog Class::Accessor); use List::Util qw(min); use Slic3r::Geometry qw(X Y unscale scale); -use Slic3r::Geometry::Clipper qw(intersection_pl); +use Slic3r::Geometry::Clipper qw(intersection_pl union_ex); -__PACKAGE__->mk_accessors(qw(config config2 scaling_factor bed_origin layers)); +__PACKAGE__->mk_accessors(qw(config config2 scaling_factor bed_origin print layer_num)); sub new { my ($class, $parent, $config, $config2) = @_; @@ -803,10 +835,10 @@ sub _resize { $self->Refresh; } -sub project_layers { - my ($self, $layers) = @_; +sub project_layer { + my ($self, $layer_num) = @_; - $self->layers($layers); + $self->layer_num($layer_num); $self->Refresh; } @@ -865,32 +897,67 @@ sub _repaint { $dc->SetTextForeground(wxWHITE); $dc->SetFont(Wx::Font->new(20, wxDEFAULT, wxNORMAL, wxNORMAL)); - $dc->DrawText("X", @{$self->unscaled_point_to_pixel([10, -2])}); - $dc->DrawText("Y", @{$self->unscaled_point_to_pixel([-2, 10])}); + + my $p = $self->unscaled_point_to_pixel([10, 0]); + $dc->DrawText("X", $p->[X], $p->[Y]); + $p = $self->unscaled_point_to_pixel([0, 10]); + $dc->DrawText("Y", $p->[X]-20, $p->[Y]-10); } } - return if !defined $self->layers; - # get layers at this height # draw layers $dc->SetPen(Wx::Pen->new(wxWHITE, 1, wxSOLID)); - foreach my $layer (@{$self->layers}) { - my @polygons = sort { $a->contains_point($b->first_point) ? -1 : 1 } map @$_, @{ $layer->slices }; - foreach my $copy (@{$layer->object->_shifted_copies}) { - foreach my $polygon (@polygons) { - $polygon = $polygon->clone; - $polygon->translate(@$copy); - - if ($polygon->is_counter_clockwise) { - $dc->SetBrush(Wx::Brush->new(wxWHITE, wxSOLID)); - } else { - $dc->SetBrush(Wx::Brush->new(wxBLACK, wxSOLID)); - } - $dc->DrawPolygon($self->scaled_points_to_pixel($polygon->pp), 0, 0); - } - } + + return if !$self->print || !defined $self->layer_num; + + if ($self->print->layer_solid($self->layer_num)) { + $self->_paint_expolygon($_, $dc) for @{$self->print->layer_slices($self->layer_num)}; + } else { + # perimeters first, because their "hole" is painted black + $self->_paint_expolygon($_, $dc) for + @{$self->print->layer_perimeters($self->layer_num)}, + @{$self->print->layer_solid_infill($self->layer_num)}; + + $self->_paint_expolygon($_, $dc) + for @{union_ex($self->print->layer_infill($self->layer_num)->grow)}; } + + # draw support material + my $sm_radius = $self->print->config->get_abs_value_over('support_material_extrusion_width', $self->print->config->layer_height)/2; + $dc->SetBrush(Wx::Brush->new(wxWHITE, wxSOLID)); + foreach my $pillar (@{$self->print->sm_pillars}) { + next unless $pillar->{top_layer} >= $self->layer_num + && $pillar->{bottom_layer} <= $self->layer_num; + + my $radius = min( + $sm_radius, + ($pillar->{top_layer} - $self->layer_num + 1) * $self->print->config->layer_height, + ); + + $dc->DrawCircle( + @{$self->scaled_points_to_pixel([$pillar->{point}])->[0]}, + $radius * $self->scaling_factor, + ); + } +} + +sub _paint_expolygon { + my ($self, $expolygon, $dc) = @_; + + my @polygons = sort { $a->contains_point($b->first_point) ? -1 : 1 } @$expolygon; + $self->_paint_polygon($_, $dc) for @polygons; +} + +sub _paint_polygon { + my ($self, $polygon, $dc) = @_; + + if ($polygon->is_counter_clockwise) { + $dc->SetBrush(Wx::Brush->new(wxWHITE, wxSOLID)); + } else { + $dc->SetBrush(Wx::Brush->new(wxBLACK, wxSOLID)); + } + $dc->DrawPolygon($self->scaled_points_to_pixel($polygon->pp), 0, 0); } # convert a model coordinate into a pixel coordinate diff --git a/lib/Slic3r/GUI/SLAPrintOptions.pm b/lib/Slic3r/GUI/SLAPrintOptions.pm new file mode 100644 index 000000000..7b3348823 --- /dev/null +++ b/lib/Slic3r/GUI/SLAPrintOptions.pm @@ -0,0 +1,118 @@ +package Slic3r::GUI::SLAPrintOptions; +use Wx qw(:dialog :id :misc :sizer :systemsettings wxTheApp); +use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER); +use base qw(Wx::Dialog Class::Accessor); + +__PACKAGE__->mk_accessors(qw(config)); + +sub new { + my ($class, $parent) = @_; + my $self = $class->SUPER::new($parent, -1, "SLA/DLP Print", wxDefaultPosition, wxDefaultSize); + + $self->config(Slic3r::Config::SLAPrint->new); + $self->config->apply_dynamic(wxTheApp->{mainframe}->config); + + my $sizer = Wx::BoxSizer->new(wxVERTICAL); + my $new_optgroup = sub { + my ($title) = @_; + + my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( + parent => $self, + title => $title, + config => $self->config, + label_width => 200, + ); + $sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); + return $optgroup; + }; + { + my $optgroup = $new_optgroup->('Layers'); + $optgroup->append_single_option_line('layer_height'); + $optgroup->append_single_option_line('first_layer_height'); + } + { + my $optgroup = $new_optgroup->('Infill'); + $optgroup->append_single_option_line('fill_density'); + $optgroup->append_single_option_line('fill_pattern'); + { + my $line = $optgroup->create_single_option_line('perimeter_extrusion_width'); + $line->label('Shell thickness'); + my $opt = $line->get_options->[0]; + $opt->sidetext('mm'); + $opt->tooltip('Thickness of the external shell (both horizontal and vertical).'); + $optgroup->append_line($line); + } + { + my $line = $optgroup->create_single_option_line('infill_extrusion_width'); + $line->label('Infill thickness'); + my $opt = $line->get_options->[0]; + $opt->sidetext('mm'); + $opt->tooltip('Thickness of the infill lines.'); + $optgroup->append_line($line); + } + $optgroup->append_single_option_line('fill_angle'); + } + { + my $optgroup = $new_optgroup->('Raft'); + $optgroup->append_single_option_line('raft_layers'); + $optgroup->append_single_option_line('raft_offset'); + } + { + my $optgroup = $new_optgroup->('Support Material'); + $optgroup->append_single_option_line('support_material'); + { + my $line = $optgroup->create_single_option_line('support_material_spacing'); + $line->label('Pillars spacing'); + my $opt = $line->get_options->[0]; + $opt->tooltip('Max spacing between support material pillars.'); + $optgroup->append_line($line); + } + { + my $line = $optgroup->create_single_option_line('support_material_extrusion_width'); + $line->label('Pillars diameter'); + my $opt = $line->get_options->[0]; + $opt->sidetext('mm'); + $opt->tooltip('Diameter of the cylindrical support pillars.'); + $optgroup->append_line($line); + } + } + + + my $buttons = $self->CreateStdDialogButtonSizer(wxOK | wxCANCEL); + EVT_BUTTON($self, wxID_OK, sub { $self->_accept }); + $sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); + + $self->SetSizer($sizer); + $sizer->SetSizeHints($self); + + return $self; +} + +sub _accept { + my $self = shift; + + # validate config + eval { + die "Invalid shell thickness (must be greater than 0).\n" + if $self->config->fill_density < 100 && $self->config->perimeter_extrusion_width == 0; + die "Invalid infill thickness (must be greater than 0).\n" + if $self->config->fill_density < 100 && $self->config->infill_extrusion_width == 0; + }; + if ($@) { + Slic3r::GUI::show_error($self, $@); + return; + } + + wxTheApp->{mainframe}->load_config($self->config->dynamic); + + $self->EndModal(wxID_OK); + $self->Close; # needed on Linux + + my $projector = Slic3r::GUI::Projector->new($self->GetParent); + + # this double invocation is needed for properly hiding the MainFrame + $projector->Show; + $projector->ShowModal; +} + +1; diff --git a/lib/Slic3r/GUI/SimpleTab.pm b/lib/Slic3r/GUI/SimpleTab.pm index 888ff0e9b..13c6efc88 100644 --- a/lib/Slic3r/GUI/SimpleTab.pm +++ b/lib/Slic3r/GUI/SimpleTab.pm @@ -1,3 +1,6 @@ +# The "Simple" Print Settings tab. +# The "Simple" mode is enabled by File->Preferences dialog. + package Slic3r::GUI::SimpleTab; use strict; use warnings; diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index e64e48d40..558b8c11a 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -1,3 +1,6 @@ +# The "Expert" tab at the right of the main tabbed window. +# The "Expert" is enabled by File->Preferences dialog. + package Slic3r::GUI::Tab; use strict; use warnings; @@ -106,7 +109,10 @@ sub new { $self->_on_presets_changed; }); + # C++ instance DynamicPrintConfig $self->{config} = Slic3r::Config->new; + # Initialize the DynamicPrintConfig by default keys/values. + # Possible %params keys: no_controller $self->build(%params); $self->update_tree; $self->_update; @@ -474,7 +480,7 @@ sub build { perimeter_acceleration infill_acceleration bridge_acceleration first_layer_acceleration default_acceleration skirts skirt_distance skirt_height min_skirt_length - brim_width + brim_connections_width brim_width support_material support_material_threshold support_material_enforce_layers raft_layers support_material_pattern support_material_spacing support_material_angle @@ -569,6 +575,7 @@ sub build { { my $optgroup = $page->new_optgroup('Brim'); $optgroup->append_single_option_line('brim_width'); + $optgroup->append_single_option_line('brim_connections_width'); } } @@ -765,6 +772,35 @@ sub _update { $self->load_config($new_conf); } } + + if ($config->support_material) { + # Ask only once. + if (! $self->{support_material_overhangs_queried}) { + $self->{support_material_overhangs_queried} = 1; + if ($config->overhangs != 1) { + my $dialog = Wx::MessageDialog->new($self, + "Supports work better, if the following feature is enabled:\n" + . "- Detect bridging perimeters\n" + . "\nShall I adjust those settings for supports?", + 'Support Generator', wxICON_WARNING | wxYES | wxNO | wxCANCEL); + my $answer = $dialog->ShowModal(); + my $new_conf = Slic3r::Config->new; + if ($answer == wxID_YES) { + # Enable "detect bridging perimeters". + $new_conf->set("overhangs", 1); + } elsif ($answer == wxID_NO) { + # Do nothing, leave supports on and "detect bridging perimeters" off. + } elsif ($answer == wxID_CANCEL) { + # Disable supports. + $new_conf->set("support_material", 0); + $self->{support_material_overhangs_queried} = 0; + } + $self->load_config($new_conf); + } + } + } else { + $self->{support_material_overhangs_queried} = 0; + } if ($config->fill_density == 100 && !first { $_ eq $config->fill_pattern } @{$Slic3r::Config::Options->{external_fill_pattern}{values}}) { @@ -775,7 +811,7 @@ sub _update { my $new_conf = Slic3r::Config->new; if ($dialog->ShowModal() == wxID_YES) { - $new_conf->set("fill_pattern", 1); + $new_conf->set("fill_pattern", 'rectilinear'); } else { $new_conf->set("fill_density", 40); } @@ -823,7 +859,7 @@ sub _update { $self->get_field($_)->toggle($have_skirt) for qw(skirt_distance skirt_height); - my $have_brim = $config->brim_width > 0; + my $have_brim = $config->brim_width > 0 || $config->brim_connections_width; # perimeter_extruder uses the same logic as in Print::extruders() $self->get_field('perimeter_extruder')->toggle($have_perimeters || $have_brim); @@ -863,7 +899,7 @@ sub build { my $self = shift; $self->init_config_options(qw( - filament_colour filament_diameter extrusion_multiplier + filament_colour filament_diameter filament_notes filament_max_volumetric_speed extrusion_multiplier temperature first_layer_temperature bed_temperature first_layer_bed_temperature fan_always_on cooling min_fan_speed max_fan_speed bridge_fan_speed disable_fan_first_layers @@ -944,6 +980,27 @@ sub build { $optgroup->append_single_option_line('min_print_speed'); } } + + { + my $page = $self->add_options_page('Advanced', 'wrench.png'); + { + my $optgroup = $page->new_optgroup('Print speed override'); + $optgroup->append_single_option_line('filament_max_volumetric_speed', 0); + } + } + + { + my $page = $self->add_options_page('Notes', 'note.png'); + { + my $optgroup = $page->new_optgroup('Notes', + label_width => 0, + ); + my $option = $optgroup->get_option('filament_notes', 0); + $option->full_width(1); + $option->height(250); + $optgroup->append_single_option_line($option); + } + } } sub _update { @@ -1000,7 +1057,7 @@ sub build { my (%params) = @_; $self->init_config_options(qw( - bed_shape z_offset + bed_shape z_offset has_heatbed gcode_flavor use_relative_e_distances serial_port serial_speed octoprint_host octoprint_apikey @@ -1067,6 +1124,7 @@ sub build { ); $optgroup->append_single_option_line($option); } + $optgroup->append_single_option_line('has_heatbed'); $optgroup->on_change(sub { my ($opt_id) = @_; if ($opt_id eq 'extruders_count') { @@ -1142,13 +1200,24 @@ sub build { } EVT_BUTTON($self, $btn, sub { - my $dlg = Slic3r::GUI::BonjourBrowser->new($self); - if ($dlg->ShowModal == wxID_OK) { - my $value = $dlg->GetValue . ":" . $dlg->GetPort; - $self->{config}->set('octoprint_host', $value); - $self->update_dirty; - $self->_on_value_change('octoprint_host', $value); - $self->reload_config; + # look for devices + my $entries; + { + my $res = Net::Bonjour->new('http'); + $res->discover; + $entries = [ $res->entries ]; + } + if (@{$entries}) { + my $dlg = Slic3r::GUI::BonjourBrowser->new($self, $entries); + if ($dlg->ShowModal == wxID_OK) { + my $value = $dlg->GetValue . ":" . $dlg->GetPort; + $self->{config}->set('octoprint_host', $value); + $self->update_dirty; + $self->_on_value_change('octoprint_host', $value); + $self->reload_config; + } + } else { + Wx::MessageDialog->new($self, 'No Bonjour device found', 'Device Browser', wxOK | wxICON_INFORMATION)->ShowModal; } }); @@ -1400,7 +1469,12 @@ sub _update { # some options only apply when not using firmware retraction $self->get_field($_, $i)->toggle($retraction && !$config->use_firmware_retraction) - for qw(retract_speed retract_restart_extra wipe); + for qw(retract_restart_extra wipe); + + # retraction speed is also used by auto-speed pressure regulator, even when + # user enabled firmware retraction + $self->get_field('retract_speed', $i)->toggle($retraction); + if ($config->use_firmware_retraction && $config->get_at('wipe', $i)) { my $dialog = Wx::MessageDialog->new($self, "The Wipe option is not available when using the Firmware Retraction mode.\n" diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index 652abb8db..a3239c2fa 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -5,9 +5,9 @@ use warnings; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(offset offset_ex - diff_ex diff union_ex intersection_ex xor_ex JT_ROUND JT_MITER + diff_ex diff union_ex intersection_ex JT_ROUND JT_MITER JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex intersection intersection_pl diff_pl union CLIPPER_OFFSET_SCALE - union_pt_chained diff_ppl intersection_ppl); + union_pt_chained intersection_ppl); 1; diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index 32a6e62fc..4bff9b61f 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -1,3 +1,5 @@ +# Extends the C++ class Slic3r::Layer. + package Slic3r::Layer; use strict; use warnings; @@ -29,15 +31,6 @@ sub regions { return [ map $self->get_region($_), 0..($self->region_count-1) ]; } -sub make_fill { - my ($self) = @_; - - foreach my $layerm (@{$self->regions}) { - $layerm->fills->clear; - $layerm->fills->append($_) for $self->object->fill_maker->make_fill($layerm); - } -} - package Slic3r::Layer::Support; our @ISA = qw(Slic3r::Layer); diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 01797316b..e852a4ab3 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -1,3 +1,4 @@ +# extends C++ class Slic3r::Model package Slic3r::Model; use List::Util qw(first max any); @@ -94,6 +95,7 @@ sub convert_multipart_object { $self->delete_object($_) for reverse 0..($self->objects_count-2); } +# Extends C++ class Slic3r::ModelMaterial package Slic3r::Model::Material; sub apply { @@ -101,6 +103,7 @@ sub apply { $self->set_attribute($_, $attributes{$_}) for keys %$attributes; } +# Extends C++ class Slic3r::ModelObject package Slic3r::Model::Object; use File::Basename qw(basename); diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 053fcdef9..6be0b058b 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -1,3 +1,5 @@ +# The slicing work horse. +# Extends C++ class Slic3r::Print package Slic3r::Print; use strict; use warnings; @@ -10,8 +12,9 @@ use Slic3r::ExtrusionPath ':roles'; use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale convex_hull); use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex intersection offset - offset2 union union_pt_chained JT_ROUND JT_SQUARE); + offset2 union union_pt_chained JT_ROUND JT_SQUARE diff_pl); use Slic3r::Print::State ':steps'; +use Slic3r::Surface qw(S_TYPE_BOTTOM); our $status_cb; @@ -39,8 +42,12 @@ sub size { sub process { my ($self) = @_; + $self->status_cb->(20, "Generating perimeters"); $_->make_perimeters for @{$self->objects}; + + $self->status_cb->(70, "Infilling layers"); $_->infill for @{$self->objects}; + $_->generate_support_material for @{$self->objects}; $self->make_skirt; $self->make_brim; # must come after make_skirt @@ -70,9 +77,32 @@ sub export_gcode { $self->process; # output everything to a G-code file - my $output_file = $self->expanded_output_filepath($params{output_file}); + my $output_file = $self->output_filepath($params{output_file} // ''); $self->status_cb->(90, "Exporting G-code" . ($output_file ? " to $output_file" : "")); - $self->write_gcode($params{output_fh} || $output_file); + + { + # open output gcode file if we weren't supplied a file-handle + my ($fh, $tempfile); + if ($params{output_fh}) { + $fh = $params{output_fh}; + } else { + $tempfile = "$output_file.tmp"; + Slic3r::open(\$fh, ">", $tempfile) + or die "Failed to open $tempfile for writing\n"; + + # enable UTF-8 output since user might have entered Unicode characters in fields like notes + binmode $fh, ':utf8'; + } + + Slic3r::Print::GCode->new( + print => $self, + fh => $fh, + )->export; + + # close our gcode file + close $fh; + rename $tempfile, $output_file if $tempfile; + } # run post-processing scripts if (@{$self->config->post_process}) { @@ -89,6 +119,7 @@ sub export_gcode { } } +# Export SVG slices for the offline SLA printing. sub export_svg { my $self = shift; my %params = @_; @@ -97,7 +128,7 @@ sub export_svg { my $fh = $params{output_fh}; if (!$fh) { - my $output_file = $self->expanded_output_filepath($params{output_file}); + my $output_file = $self->output_filepath($params{output_file}); $output_file =~ s/\.gcode$/.svg/i; Slic3r::open(\$fh, ">", $output_file) or die "Failed to open $output_file for writing\n"; print "Exporting to $output_file..." unless $params{quiet}; @@ -319,132 +350,17 @@ sub make_brim { $_->generate_support_material for @{$self->objects}; $self->make_skirt; - return if $self->step_done(STEP_BRIM); - $self->set_step_started(STEP_BRIM); - - # since this method must be idempotent, we clear brim paths *before* - # checking whether we need to generate them - $self->brim->clear; - - if ($self->config->brim_width == 0) { - $self->set_step_done(STEP_BRIM); - return; - } $self->status_cb->(88, "Generating brim"); - - # brim is only printed on first layer and uses perimeter extruder - my $first_layer_height = $self->skirt_first_layer_height; - my $flow = $self->brim_flow; - my $mm3_per_mm = $flow->mm3_per_mm; - - my $grow_distance = $flow->scaled_width / 2; - my @islands = (); # array of polygons - foreach my $obj_idx (0 .. ($self->object_count - 1)) { - my $object = $self->objects->[$obj_idx]; - my $layer0 = $object->get_layer(0); - my @object_islands = ( - (map $_->contour, @{$layer0->slices}), - ); - if (@{ $object->support_layers }) { - my $support_layer0 = $object->support_layers->[0]; - push @object_islands, - (map @{$_->polyline->grow($grow_distance)}, @{$support_layer0->support_fills}) - if $support_layer0->support_fills; - push @object_islands, - (map @{$_->polyline->grow($grow_distance)}, @{$support_layer0->support_interface_fills}) - if $support_layer0->support_interface_fills; - } - foreach my $copy (@{$object->_shifted_copies}) { - push @islands, map { $_->translate(@$copy); $_ } map $_->clone, @object_islands; - } - } - - my @loops = (); - my $num_loops = sprintf "%.0f", $self->config->brim_width / $flow->width; - for my $i (reverse 1 .. $num_loops) { - # JT_SQUARE ensures no vertex is outside the given offset distance - # -0.5 because islands are not represented by their centerlines - # (first offset more, then step back - reverse order than the one used for - # perimeters because here we're offsetting outwards) - push @loops, @{offset2(\@islands, ($i + 0.5) * $flow->scaled_spacing, -1.0 * $flow->scaled_spacing, 100000, JT_SQUARE)}; - } - - $self->brim->append(map Slic3r::ExtrusionLoop->new_from_paths( - Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polygon->new(@$_)->split_at_first_point, - role => EXTR_ROLE_SKIRT, - mm3_per_mm => $mm3_per_mm, - width => $flow->width, - height => $first_layer_height, - ), - ), reverse @{union_pt_chained(\@loops)}); - - $self->set_step_done(STEP_BRIM); + $self->_make_brim; } -sub write_gcode { +# Wrapper around the C++ Slic3r::Print::validate() +# to produce a Perl exception without a hang-up on some Strawberry perls. +sub validate +{ my $self = shift; - my ($file) = @_; - - # open output gcode file if we weren't supplied a file-handle - my $fh; - if (ref $file eq 'IO::Scalar') { - $fh = $file; - } else { - Slic3r::open(\$fh, ">", $file) - or die "Failed to open $file for writing\n"; - - # enable UTF-8 output since user might have entered Unicode characters in fields like notes - binmode $fh, ':utf8'; - } - - my $exporter = Slic3r::Print::GCode->new( - print => $self, - fh => $fh, - ); - $exporter->export; - - # close our gcode file - close $fh; -} - -# this method will return the supplied input file path after expanding its -# format variables with their values -sub expanded_output_filepath { - my $self = shift; - my ($path) = @_; - - return undef if !@{$self->objects}; - my $input_file = first { defined $_ } map $_->model_object->input_file, @{$self->objects}; - return undef if !defined $input_file; - - my $filename = my $filename_base = basename($input_file); - $filename_base =~ s/\.[^.]+$//; # without suffix - - # set filename in placeholder parser so that it's available also in custom G-code - $self->placeholder_parser->set(input_filename => $filename); - $self->placeholder_parser->set(input_filename_base => $filename_base); - - # set other variables from model object - $self->placeholder_parser->set_multiple( - scale => [ map $_->model_object->instances->[0]->scaling_factor * 100 . "%", @{$self->objects} ], - ); - - if ($path && -d $path) { - # if output path is an existing directory, we take that and append - # the specified filename format - $path = File::Spec->join($path, $self->config->output_filename_format); - } elsif (!$path) { - # if no explicit output file was defined, we take the input - # file directory and append the specified filename format - $path = (fileparse($input_file))[1] . $self->config->output_filename_format; - } else { - # path is a full path to a file so we use it as it is - } - - # make sure we use an up-to-date timestamp - $self->placeholder_parser->update_timestamp; - return $self->placeholder_parser->process($path); + my $err = $self->_validate; + die $err . "\n" if (defined($err) && $err ne ''); } 1; diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index 3c32875ed..9e36322de 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -98,10 +98,7 @@ sub BUILD { } } - $self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new( - config => $self->config, - gcodegen => $self->_gcodegen, - )); + $self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new($self->_gcodegen)); $self->_spiral_vase(Slic3r::GCode::SpiralVase->new(config => $self->config)) if $self->config->spiral_vase; @@ -162,7 +159,7 @@ sub export { if $self->config->cooling && $self->config->disable_fan_first_layers; # set bed temperature - if ((my $temp = $self->config->first_layer_bed_temperature) && $self->config->start_gcode !~ /M(?:190|140)/i) { + if ($self->config->has_heatbed && (my $temp = $self->config->first_layer_bed_temperature) && $self->config->start_gcode !~ /M(?:190|140)/i) { printf $fh $gcodegen->writer->set_bed_temperature($temp, 1); } @@ -264,7 +261,7 @@ sub export { # is triggered, so machine has more time to reach such temperatures if ($layer->id == 0 && $finished_objects > 0) { printf $fh $gcodegen->writer->set_bed_temperature($self->config->first_layer_bed_temperature), - if $self->config->first_layer_bed_temperature; + if $self->config->first_layer_bed_temperature && $self->config->has_heatbed; $self->_print_first_layer_temperature(0); } $self->process_layer($layer, [$copy]); @@ -357,7 +354,7 @@ sub process_layer { # check whether we're going to apply spiralvase logic if (defined $self->_spiral_vase) { $self->_spiral_vase->enable( - ($layer->id > 0 || $self->print->config->brim_width == 0) + ($layer->id > 0 || $self->print->config->brim_width == 0 || $self->print->config->brim_connections_width == 0) && ($layer->id >= $self->print->config->skirt_height && !$self->print->has_infinite_skirt) && !defined(first { $_->region->config->bottom_solid_layers > $layer->id } @{$layer->regions}) && !defined(first { $_->perimeters->items_count > 1 } @{$layer->regions}) @@ -375,7 +372,7 @@ sub process_layer { if $temperature && $temperature != $self->config->get_at('first_layer_temperature', $extruder->id); } $gcode .= $self->_gcodegen->writer->set_bed_temperature($self->print->config->bed_temperature) - if $self->print->config->bed_temperature && $self->print->config->bed_temperature != $self->print->config->first_layer_bed_temperature; + if $self->config->has_heatbed && $self->print->config->first_layer_bed_temperature && $self->print->config->bed_temperature != $self->print->config->first_layer_bed_temperature; $self->_second_layer_things_done(1); } @@ -447,7 +444,7 @@ sub process_layer { $gcode .= $self->_gcodegen->set_extruder($self->print->regions->[0]->config->perimeter_extruder-1); $self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0)); $self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(1); - $gcode .= $self->_gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed) + $gcode .= $self->_gcodegen->extrude($_, 'brim', $object->config->support_material_speed) for @{$self->print->brim}; $self->_brim_done(1); $self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(0); diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 837fe9006..0a1a709e6 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -1,4 +1,5 @@ package Slic3r::Print::Object; +# extends c++ class Slic3r::PrintObject (Print.xsp) use strict; use warnings; @@ -32,6 +33,14 @@ sub support_layers { return [ map $self->get_support_layer($_), 0..($self->support_layer_count - 1) ]; } +# 1) Decides Z positions of the layers, +# 2) Initializes layers and their regions +# 3) Slices the object meshes +# 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes +# 5) Applies size compensation (offsets the slices in XY plane) +# 6) Replaces bad slices by the slices reconstructed from the upper/lower layer +# Resulting expolygons of layer regions are marked as Internal. +# # this should be idempotent sub slice { my $self = shift; @@ -40,6 +49,13 @@ sub slice { $self->set_step_started(STEP_SLICE); $self->print->status_cb->(10, "Processing triangulated mesh"); + { + my @nozzle_diameters = map $self->print->config->get_at('nozzle_diameter', $_), + @{$self->print->object_extruders}; + + $self->config->set('layer_height', min(@nozzle_diameters, $self->config->layer_height)); + } + # init layers { $self->clear_layers; @@ -64,8 +80,7 @@ sub slice { { my @nozzle_diameters = ( map $self->print->config->get_at('nozzle_diameter', $_), - $self->config->support_material_extruder-1, - $self->config->support_material_interface_extruder-1, + @{$self->support_material_extruders}, ); $support_material_layer_height = 0.75 * min(@nozzle_diameters); } @@ -428,146 +443,13 @@ sub slice { $self->set_step_done(STEP_SLICE); } -sub _slice_region { - my ($self, $region_id, $z, $modifier) = @_; - - return [] if !@{$self->get_region_volumes($region_id)}; - - # compose mesh - my $mesh; - foreach my $volume_id (@{ $self->get_region_volumes($region_id) }) { - my $volume = $self->model_object->volumes->[$volume_id]; - next if $volume->modifier && !$modifier; - next if !$volume->modifier && $modifier; - - if (defined $mesh) { - $mesh->merge($volume->mesh); - } else { - $mesh = $volume->mesh->clone; - } - } - return if !defined $mesh; - - # transform mesh - # we ignore the per-instance transformations currently and only - # consider the first one - $self->model_object->instances->[0]->transform_mesh($mesh, 1); - - # align mesh to Z = 0 (it should be already aligned actually) and apply XY shift - $mesh->translate((map unscale(-$_), @{$self->_copies_shift}), -$self->model_object->bounding_box->z_min); - - # perform actual slicing - return $mesh->slice($z); -} - sub make_perimeters { - my $self = shift; + my ($self) = @_; # prerequisites $self->slice; - return if $self->step_done(STEP_PERIMETERS); - $self->set_step_started(STEP_PERIMETERS); - $self->print->status_cb->(20, "Generating perimeters"); - - # merge slices if they were split into types - if ($self->typed_slices) { - $_->merge_slices for @{$self->layers}; - $self->set_typed_slices(0); - $self->invalidate_step(STEP_PREPARE_INFILL); - } - - # compare each layer to the one below, and mark those slices needing - # one additional inner perimeter, like the top of domed objects- - - # this algorithm makes sure that at least one perimeter is overlapping - # but we don't generate any extra perimeter if fill density is zero, as they would be floating - # inside the object - infill_only_where_needed should be the method of choice for printing - # hollow objects - for my $region_id (0 .. ($self->print->region_count-1)) { - my $region = $self->print->regions->[$region_id]; - my $region_perimeters = $region->config->perimeters; - - next if !$region->config->extra_perimeters; - next if $region_perimeters == 0; - next if $region->config->fill_density == 0; - - for my $i (0 .. ($self->layer_count - 2)) { - my $layerm = $self->get_layer($i)->get_region($region_id); - my $upper_layerm = $self->get_layer($i+1)->get_region($region_id); - my $upper_layerm_polygons = [ map $_->p, @{$upper_layerm->slices} ]; - # Filter upper layer polygons in intersection_ppl by their bounding boxes? - # my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ]; - my $total_loop_length = sum(map $_->length, @$upper_layerm_polygons) // 0; - - my $perimeter_spacing = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_spacing; - my $ext_perimeter_flow = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER); - my $ext_perimeter_width = $ext_perimeter_flow->scaled_width; - my $ext_perimeter_spacing = $ext_perimeter_flow->scaled_spacing; - - foreach my $slice (@{$layerm->slices}) { - while (1) { - # compute the total thickness of perimeters - my $perimeters_thickness = $ext_perimeter_width/2 + $ext_perimeter_spacing/2 - + ($region_perimeters-1 + $slice->extra_perimeters) * $perimeter_spacing; - - # define a critical area where we don't want the upper slice to fall into - # (it should either lay over our perimeters or outside this area) - my $critical_area_depth = $perimeter_spacing*1.5; - my $critical_area = diff( - offset($slice->expolygon->arrayref, -$perimeters_thickness), - offset($slice->expolygon->arrayref, -($perimeters_thickness + $critical_area_depth)), - ); - - # check whether a portion of the upper slices falls inside the critical area - my $intersection = intersection_ppl( - $upper_layerm_polygons, - $critical_area, - ); - - # only add an additional loop if at least 30% of the slice loop would benefit from it - my $total_intersection_length = sum(map $_->length, @$intersection) // 0; - last unless $total_intersection_length > $total_loop_length*0.3; - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "extra.svg", - no_arrows => 1, - expolygons => union_ex($critical_area), - polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ], - ); - } - - $slice->extra_perimeters($slice->extra_perimeters + 1); - } - Slic3r::debugf " adding %d more perimeter(s) at layer %d\n", - $slice->extra_perimeters, $layerm->layer->id - if $slice->extra_perimeters > 0; - } - } - } - - Slic3r::parallelize( - threads => $self->print->config->threads, - items => sub { 0 .. ($self->layer_count - 1) }, - thread_cb => sub { - my $q = shift; - while (defined (my $i = $q->dequeue)) { - $self->get_layer($i)->make_perimeters; - } - }, - no_threads_cb => sub { - $_->make_perimeters for @{$self->layers}; - }, - ); - - # simplify slices (both layer and region slices), - # we only need the max resolution for perimeters - ### This makes this method not-idempotent, so we keep it disabled for now. - ###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION); - - $self->set_step_done(STEP_PERIMETERS); + $self->_make_perimeters; } sub prepare_infill { @@ -614,30 +496,7 @@ sub infill { # prerequisites $self->prepare_infill; - return if $self->step_done(STEP_INFILL); - $self->set_step_started(STEP_INFILL); - $self->print->status_cb->(70, "Infilling layers"); - - Slic3r::parallelize( - threads => $self->print->config->threads, - items => sub { 0..$#{$self->layers} }, - thread_cb => sub { - my $q = shift; - while (defined (my $i = $q->dequeue)) { - $self->get_layer($i)->make_fill; - } - }, - no_threads_cb => sub { - foreach my $layer (@{$self->layers}) { - $layer->make_fill; - } - }, - ); - - ### we could free memory now, but this would make this step not idempotent - ### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers}; - - $self->set_step_done(STEP_INFILL); + $self->_infill; } sub generate_support_material { @@ -683,147 +542,6 @@ sub _support_material { ); } -sub detect_surfaces_type { - my $self = shift; - Slic3r::debugf "Detecting solid surfaces...\n"; - - for my $region_id (0 .. ($self->print->region_count-1)) { - for my $i (0 .. ($self->layer_count - 1)) { - my $layerm = $self->get_layer($i)->regions->[$region_id]; - - # prepare a reusable subroutine to make surface differences - my $difference = sub { - my ($subject, $clip, $result_type) = @_; - my $diff = diff( - [ map @$_, @$subject ], - [ map @$_, @$clip ], - 1, - ); - - # collapse very narrow parts (using the safety offset in the diff is not enough) - my $offset = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER)->scaled_width / 10; - return map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type), - @{ offset2_ex($diff, -$offset, +$offset) }; - }; - - # comparison happens against the *full* slices (considering all regions) - # unless internal shells are requested - my $upper_layer = $i < $self->layer_count - 1 ? $self->get_layer($i+1) : undef; - my $lower_layer = $i > 0 ? $self->get_layer($i-1) : undef; - - # find top surfaces (difference between current surfaces - # of current layer and upper one) - my @top = (); - if ($upper_layer) { - my $upper_slices = $self->config->interface_shells - ? [ map $_->expolygon, @{$upper_layer->regions->[$region_id]->slices} ] - : $upper_layer->slices; - - @top = $difference->( - [ map $_->expolygon, @{$layerm->slices} ], - $upper_slices, - S_TYPE_TOP, - ); - } else { - # if no upper layer, all surfaces of this one are solid - # we clone surfaces because we're going to clear the slices collection - @top = map $_->clone, @{$layerm->slices}; - $_->surface_type(S_TYPE_TOP) for @top; - } - - # find bottom surfaces (difference between current surfaces - # of current layer and lower one) - my @bottom = (); - if ($lower_layer) { - # any surface lying on the void is a true bottom bridge - push @bottom, $difference->( - [ map $_->expolygon, @{$layerm->slices} ], - $lower_layer->slices, - S_TYPE_BOTTOMBRIDGE, - ); - - # if we have soluble support material, don't bridge - if ($self->config->support_material && $self->config->support_material_contact_distance == 0) { - $_->surface_type(S_TYPE_BOTTOM) for @bottom; - } - - # if user requested internal shells, we need to identify surfaces - # lying on other slices not belonging to this region - if ($self->config->interface_shells) { - # non-bridging bottom surfaces: any part of this layer lying - # on something else, excluding those lying on our own region - my $supported = intersection_ex( - [ map @{$_->expolygon}, @{$layerm->slices} ], - [ map @$_, @{$lower_layer->slices} ], - ); - push @bottom, $difference->( - $supported, - [ map $_->expolygon, @{$lower_layer->regions->[$region_id]->slices} ], - S_TYPE_BOTTOM, - ); - } - } else { - # if no lower layer, all surfaces of this one are solid - # we clone surfaces because we're going to clear the slices collection - @bottom = map $_->clone, @{$layerm->slices}; - - # if we have raft layers, consider bottom layer as a bridge - # just like any other bottom surface lying on the void - if ($self->config->raft_layers > 0 && $self->config->support_material_contact_distance > 0) { - $_->surface_type(S_TYPE_BOTTOMBRIDGE) for @bottom; - } else { - $_->surface_type(S_TYPE_BOTTOM) for @bottom; - } - } - - # now, if the object contained a thin membrane, we could have overlapping bottom - # and top surfaces; let's do an intersection to discover them and consider them - # as bottom surfaces (to allow for bridge detection) - if (@top && @bottom) { - my $overlapping = intersection_ex([ map $_->p, @top ], [ map $_->p, @bottom ]); - Slic3r::debugf " layer %d contains %d membrane(s)\n", $layerm->layer->id, scalar(@$overlapping) - if $Slic3r::debug; - @top = $difference->([map $_->expolygon, @top], $overlapping, S_TYPE_TOP); - } - - # find internal surfaces (difference between top/bottom surfaces and others) - my @internal = $difference->( - [ map $_->expolygon, @{$layerm->slices} ], - [ map $_->expolygon, @top, @bottom ], - S_TYPE_INTERNAL, - ); - - # save surfaces to layer - $layerm->slices->clear; - $layerm->slices->append($_) for (@bottom, @top, @internal); - - Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n", - $layerm->layer->id, scalar(@bottom), scalar(@top), scalar(@internal) if $Slic3r::debug; - } - - # clip surfaces to the fill boundaries - foreach my $layer (@{$self->layers}) { - my $layerm = $layer->regions->[$region_id]; - - # Note: this method should be idempotent, but fill_surfaces gets modified - # in place. However we're now only using its boundaries (which are invariant) - # so we're safe. This guarantees idempotence of prepare_infill() also in case - # that combine_infill() turns some fill_surface into VOID surfaces. - my $fill_boundaries = [ map $_->clone->p, @{$layerm->fill_surfaces} ]; - $layerm->fill_surfaces->clear; - foreach my $surface (@{$layerm->slices}) { - my $intersection = intersection_ex( - [ $surface->p ], - $fill_boundaries, - ); - $layerm->fill_surfaces->append($_) - for map Slic3r::Surface->new(expolygon => $_, surface_type => $surface->surface_type), - @$intersection; - } - } - } -} - # Idempotence of this method is guaranteed by the fact that we don't remove things from # fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries. sub clip_fill_surfaces { @@ -975,19 +693,27 @@ sub discover_horizontal_shells { # find intersection between neighbor and current layer's surfaces # intersections have contours and holes - # we update $solid so that we limit the next neighbor layer to the areas that were - # found on this one - in other words, solid shells on one layer (for a given external surface) - # are always a subset of the shells found on the previous shell layer - # this approach allows for DWIM in hollow sloping vases, where we want bottom - # shells to be generated in the base but not in the walls (where there are many - # narrow bottom surfaces): reassigning $solid will consider the 'shadow' of the - # upper perimeter as an obstacle and shell will not be propagated to more upper layers - my $new_internal_solid = $solid = intersection( + my $new_internal_solid = intersection( $solid, [ map $_->p, grep { ($_->surface_type == S_TYPE_INTERNAL) || ($_->surface_type == S_TYPE_INTERNALSOLID) } @neighbor_fill_surfaces ], 1, ); - next EXTERNAL if !@$new_internal_solid; + if (!@$new_internal_solid) { + # No internal solid needed on this layer. In order to decide whether to continue + # searching on the next neighbor (thus enforcing the configured number of solid + # layers, use different strategies according to configured infill density: + if ($layerm->region->config->fill_density == 0) { + # If user expects the object to be void (for example a hollow sloping vase), + # don't continue the search. In this case, we only generate the external solid + # shell if the object would otherwise show a hole (gap between perimeters of + # the two layers), and internal solid shells are a subset of the shells found + # on each previous layer. + next EXTERNAL; + } else { + # If we have internal infill, we can generate internal solid shells freely. + next NEIGHBOR; + } + } if ($layerm->region->config->fill_density == 0) { # if we're printing a hollow object we discard any solid shell thinner @@ -1214,6 +940,9 @@ sub combine_infill { } } +# Simplify the sliced model, if "resolution" configuration parameter > 0. +# The simplification is problematic, because it simplifies the slices independent from each other, +# which makes the simplified discretization visible on the object surface. sub _simplify_slices { my ($self, $distance) = @_; diff --git a/lib/Slic3r/Print/Simple.pm b/lib/Slic3r/Print/Simple.pm index 5618484fa..4fe3eb820 100644 --- a/lib/Slic3r/Print/Simple.pm +++ b/lib/Slic3r/Print/Simple.pm @@ -1,3 +1,10 @@ +# A simple wrapper to quickly print a single model without a GUI. +# Used by the command line slic3r.pl, by command line utilities pdf-slic3s.pl and view-toolpaths.pl, +# and by the quick slice menu of the Slic3r GUI. +# +# It creates and owns an instance of Slic3r::Print to perform the slicing +# and it accepts an instance of Slic3r::Model from the outside. + package Slic3r::Print::Simple; use Moo; @@ -6,7 +13,7 @@ use Slic3r::Geometry qw(X Y); has '_print' => ( is => 'ro', default => sub { Slic3r::Print->new }, - handles => [qw(apply_config extruders expanded_output_filepath + handles => [qw(apply_config extruders output_filepath total_used_filament total_extruded_volume placeholder_parser process)], ); @@ -41,18 +48,24 @@ has 'print_center' => ( default => sub { Slic3r::Pointf->new(100,100) }, ); +has 'dont_arrange' => ( + is => 'rw', + default => sub { 0 }, +); + has 'output_file' => ( is => 'rw', ); sub set_model { + # $model is of type Slic3r::Model my ($self, $model) = @_; # make method idempotent so that the object is reusable $self->_print->clear_objects; # make sure all objects have at least one defined instance - my $need_arrange = $model->add_default_instances; + my $need_arrange = $model->add_default_instances && ! $self->dont_arrange; # apply scaling and rotation supplied from command line if any foreach my $instance (map @{$_->instances}, @{$model->objects}) { @@ -61,7 +74,7 @@ sub set_model { } if ($self->duplicate_grid->[X] > 1 || $self->duplicate_grid->[Y] > 1) { - $model->duplicate_objects_grid($self->duplicate_grid, $self->_print->config->duplicate_distance); + $model->duplicate_objects_grid($self->duplicate_grid->[X], $self->duplicate_grid->[Y], $self->_print->config->duplicate_distance); } elsif ($need_arrange) { $model->duplicate_objects($self->duplicate, $self->_print->config->min_object_distance); } elsif ($self->duplicate > 1) { @@ -69,7 +82,7 @@ sub set_model { $model->duplicate($self->duplicate, $self->_print->config->min_object_distance); } $_->translate(0,0,-$_->bounding_box->z_min) for @{$model->objects}; - $model->center_instances_around_point($self->print_center); + $model->center_instances_around_point($self->print_center) if (! $self->dont_arrange); foreach my $model_object (@{$model->objects}) { $self->_print->auto_assign_extruders($model_object); diff --git a/lib/Slic3r/Print/State.pm b/lib/Slic3r/Print/State.pm index 7220aa818..d242e3760 100644 --- a/lib/Slic3r/Print/State.pm +++ b/lib/Slic3r/Print/State.pm @@ -1,3 +1,4 @@ +# Wraps C++ enums Slic3r::PrintStep and Slic3r::PrintObjectStep package Slic3r::Print::State; use strict; use warnings; diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index eea6397af..34829a2a2 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -558,11 +558,6 @@ sub generate_toolpaths { $pattern = 'honeycomb'; } - my %fillers = ( - interface => $object->fill_maker->filler('rectilinear'), - support => $object->fill_maker->filler($pattern), - ); - my $interface_angle = $self->object_config->support_material_angle + 90; my $interface_spacing = $self->object_config->support_material_interface_spacing + $interface_flow->spacing; my $interface_density = $interface_spacing == 0 ? 1 : $interface_flow->spacing / $interface_spacing; @@ -673,10 +668,20 @@ sub generate_toolpaths { $layer->support_interface_fills->append(@loops); } + # Allocate the fillers exclusively in the worker threads! Don't allocate them at the main thread, + # as Perl copies the C++ pointers by default, so then the C++ objects are shared between threads! + my %fillers = ( + interface => Slic3r::Filler->new_from_type('rectilinear'), + support => Slic3r::Filler->new_from_type($pattern), + ); + my $bounding_box = $object->bounding_box; + $fillers{interface}->set_bounding_box($object->bounding_box); + $fillers{support}->set_bounding_box($object->bounding_box); + # interface and contact infill if (@$interface || @$contact_infill) { - $fillers{interface}->angle($interface_angle); - $fillers{interface}->spacing($_interface_flow->spacing); + $fillers{interface}->set_angle($interface_angle); + $fillers{interface}->set_min_spacing($_interface_flow->spacing); # find centerline of the external loop $interface = offset2($interface, +scaled_epsilon, -(scaled_epsilon + $_interface_flow->scaled_width/2)); @@ -702,7 +707,7 @@ sub generate_toolpaths { my @paths = (); foreach my $expolygon (@{union_ex($interface)}) { - my @p = $fillers{interface}->fill_surface( + my $p = $fillers{interface}->fill_surface( Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL), density => $interface_density, layer_height => $layer->height, @@ -716,7 +721,7 @@ sub generate_toolpaths { mm3_per_mm => $mm3_per_mm, width => $_interface_flow->width, height => $layer->height, - ), @p; + ), @$p; } $layer->support_interface_fills->append(@paths); @@ -725,11 +730,11 @@ sub generate_toolpaths { # support or flange if (@$base) { my $filler = $fillers{support}; - $filler->angle($angles[ ($layer_id) % @angles ]); + $filler->set_angle($angles[ ($layer_id) % @angles ]); # We don't use $base_flow->spacing because we need a constant spacing # value that guarantees that all layers are correctly aligned. - $filler->spacing($flow->spacing); + $filler->set_min_spacing($flow->spacing); my $density = $support_density; my $base_flow = $_flow; @@ -742,13 +747,13 @@ sub generate_toolpaths { # base flange if ($layer_id == 0) { $filler = $fillers{interface}; - $filler->angle($self->object_config->support_material_angle + 90); + $filler->set_angle($self->object_config->support_material_angle + 90); $density = 0.5; $base_flow = $self->first_layer_flow; # use the proper spacing for first layer as we don't need to align # its pattern to the other layers - $filler->spacing($base_flow->spacing); + $filler->set_min_spacing($base_flow->spacing); } else { # draw a perimeter all around support infill # TODO: use brim ordering algorithm @@ -767,7 +772,7 @@ sub generate_toolpaths { my $mm3_per_mm = $base_flow->mm3_per_mm; foreach my $expolygon (@$to_infill) { - my @p = $filler->fill_surface( + my $p = $filler->fill_surface( Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL), density => $density, layer_height => $layer->height, @@ -780,7 +785,7 @@ sub generate_toolpaths { mm3_per_mm => $mm3_per_mm, width => $base_flow->width, height => $layer->height, - ), @p; + ), @$p; } $layer->support_fills->append(@paths); diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index f0bcef1c7..345af7333 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -27,6 +27,14 @@ sub mesh { $facets = [ [0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5], ], + } elsif ($name eq 'box') { + my ($x, $y, $z) = @{ $params{"dim"} }; + $vertices = [ + [$x,$y,0], [$x,0,0], [0,0,0], [0,$y,0], [$x,$y,$z], [0,$y,$z], [0,0,$z], [$x,0,$z], + ]; + $facets = [ + [0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5], + ], } elsif ($name eq 'cube_with_hole') { $vertices = [ [0,0,0],[0,0,10],[0,20,0],[0,20,10],[20,0,0],[20,0,10],[5,5,0],[15,5,0],[5,15,0],[20,20,0],[15,15,0],[20,20,10],[5,5,10],[5,15,10],[15,5,10],[15,15,10] diff --git a/lib/Slic3r/Test/SectionCut.pm b/lib/Slic3r/Test/SectionCut.pm index 467f4e9d4..8b3de5a76 100644 --- a/lib/Slic3r/Test/SectionCut.pm +++ b/lib/Slic3r/Test/SectionCut.pm @@ -1,7 +1,10 @@ +# 2D cut in the XZ plane through the toolpaths. +# For debugging purposes. + package Slic3r::Test::SectionCut; use Moo; -use List::Util qw(first min max); +use List::Util qw(any min max); use Slic3r::Geometry qw(unscale); use Slic3r::Geometry::Clipper qw(intersection_pl); use SVG; @@ -10,20 +13,27 @@ use Slic3r::SVG; has 'print' => (is => 'ro', required => 1); has 'scale' => (is => 'ro', default => sub { 30 }); has 'y_percent' => (is => 'ro', default => sub { 0.5 }); # Y coord of section line expressed as factor -has 'line' => (is => 'rw'); +has '_line' => (is => 'lazy'); has '_height' => (is => 'rw'); has '_svg' => (is => 'rw'); has '_svg_style' => (is => 'rw', default => sub { {} }); +has '_bb' => (is => 'lazy'); -sub BUILD { +sub _build__line { my $self = shift; # calculate the Y coordinate of the section line - my $bb = $self->print->bounding_box; + my $bb = $self->_bb; my $y = ($bb->y_min + $bb->y_max) * $self->y_percent; # store our section line - $self->line(Slic3r::Line->new([ $bb->x_min, $y ], [ $bb->x_max, $y ])); + return Slic3r::Line->new([ $bb->x_min, $y ], [ $bb->x_max, $y ]); +} + +sub _build__bb { + my ($self) = @_; + + return $self->print->bounding_box; } sub export_svg { @@ -32,8 +42,7 @@ sub export_svg { # get bounding box of print and its height # (Print should return a BoundingBox3 object instead) - my $bb = $self->print->bounding_box; - my $print_size = $bb->size; + my $print_size = $self->_bb->size; $self->_height(max(map $_->print_z, map @{$_->layers}, @{$self->print->objects})); # initialize the SVG canvas @@ -72,98 +81,118 @@ sub _plot_group { my $self = shift; my ($filter) = @_; - my $bb = $self->print->bounding_box; - my $g = $self->_svg->group(style => { %{$self->_svg_style} }); - foreach my $object (@{$self->print->objects}) { - foreach my $copy (@{$object->_shifted_copies}) { - foreach my $layer (@{$object->layers}, @{$object->support_layers}) { - # get all ExtrusionPath objects - my @paths = map $_->clone, - map { ($_->isa('Slic3r::ExtrusionLoop') || $_->isa('Slic3r::ExtrusionPath::Collection')) ? @$_ : $_ } - grep defined $_, - $filter->($layer); - - # move paths to location of copy - $_->polyline->translate(@$copy) for @paths; - + foreach my $layer (@{$object->layers}, @{$object->support_layers}) { + my @paths = map $_->clone, map @{$_->flatten}, grep defined $_, $filter->($layer); + + my $name = sprintf "%s %d (z = %f)", + ($layer->isa('Slic3r::Layer::Support') ? 'Support Layer' : 'Layer'), + $layer->id, + $layer->print_z; + + my $g = $self->_svg->getElementByID($name) + || $self->_svg->group(id => $name, style => { %{$self->_svg_style} }); + + foreach my $copy (@{$object->_shifted_copies}) { if (0) { # export plan with section line and exit + my @grown = map @{$_->grow}, @paths; + $_->translate(@$copy) for @paths; + require "Slic3r/SVG.pm"; Slic3r::SVG::output( "line.svg", no_arrows => 1, - lines => [ $self->line ], - red_polylines => [ map $_->polyline, @paths ], + polygons => \@grown, + red_lines => [ $self->_line ], ); exit; } - foreach my $path (@paths) { - foreach my $line (@{$path->lines}) { - my @intersections = @{intersection_pl( - [ $self->line->as_polyline ], - $line->grow(Slic3r::Geometry::scale $path->width/2), - )}; - - die "Intersection has more than two points!\n" - if defined first { @$_ > 2 } @intersections; - - # turn intersections to lines - my @lines = map Slic3r::Line->new(@$_), @intersections; - - # align intersections to canvas - $_->translate(-$bb->x_min, 0) for @lines; - - # we want lines oriented from left to right in order to draw - # rectangles correctly - foreach my $line (@lines) { - $line->reverse if $line->a->x > $line->b->x; - } - - if ($path->is_bridge) { - foreach my $line (@lines) { - my $radius = $path->width / 2; - my $width = unscale abs($line->b->x - $line->a->x); - if ((10 * $radius) < $width) { - # we're cutting the path in the longitudinal direction, so we've got a rectangle - $g->rectangle( - 'x' => $self->scale * unscale($line->a->x), - 'y' => $self->scale * $self->_y($layer->print_z), - 'width' => $self->scale * $width, - 'height' => $self->scale * $radius * 2, - 'rx' => $self->scale * $radius * 0.35, - 'ry' => $self->scale * $radius * 0.35, - ); - } else { - $g->circle( - 'cx' => $self->scale * (unscale($line->a->x) + $radius), - 'cy' => $self->scale * $self->_y($layer->print_z - $radius), - 'r' => $self->scale * $radius, - ); - } - } - } else { - foreach my $line (@lines) { - my $height = $path->height; - $height = $layer->height if $height == -1; - $g->rectangle( - 'x' => $self->scale * unscale($line->a->x), - 'y' => $self->scale * $self->_y($layer->print_z), - 'width' => $self->scale * unscale($line->b->x - $line->a->x), - 'height' => $self->scale * $height, - 'rx' => $self->scale * $height * 0.5, - 'ry' => $self->scale * $height * 0.5, - ); - } - } - } - } + $self->_plot_path($_, $g, $copy, $layer) for @paths; } } } } +sub _plot_path { + my ($self, $path, $g, $copy, $layer) = @_; + + my $grown = $path->grow; + $_->translate(@$copy) for @$grown; + my $intersections = intersection_pl( + [ $self->_line->as_polyline ], + $grown, + ); + + if (0 && @$intersections) { + # export plan with section line and exit + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "intersections.svg", + no_arrows => 1, + polygons => $grown, + red_lines => [ $self->_line ], + ); + exit; + } + + # turn intersections to lines + die "Intersection has more than two points!\n" + if any { @$_ > 2 } @$intersections; + my @lines = map Slic3r::Line->new(@$_), @$intersections; + + my $is_bridge = $path->isa('Slic3r::ExtrusionPath') + ? $path->is_bridge + : any { $_->is_bridge } @$path; + + foreach my $line (@lines) { + my $this_path = $path; + if ($path->isa('Slic3r::ExtrusionLoop')) { + # FIXME: find the actual ExtrusionPath of this intersection + $this_path = $path->[0]; + } + + # align to canvas + $line->translate(-$self->_bb->x_min, 0); + + # we want lines oriented from left to right in order to draw rectangles correctly + $line->reverse if $line->a->x > $line->b->x; + + if ($is_bridge) { + my $radius = $this_path->width / 2; + my $width = unscale abs($line->b->x - $line->a->x); + if ((10 * $radius) < $width) { + # we're cutting the path in the longitudinal direction, so we've got a rectangle + $g->rectangle( + 'x' => $self->scale * unscale($line->a->x), + 'y' => $self->scale * $self->_y($layer->print_z), + 'width' => $self->scale * $width, + 'height' => $self->scale * $radius * 2, + 'rx' => $self->scale * $radius * 0.35, + 'ry' => $self->scale * $radius * 0.35, + ); + } else { + $g->circle( + 'cx' => $self->scale * (unscale($line->a->x) + $radius), + 'cy' => $self->scale * $self->_y($layer->print_z - $radius), + 'r' => $self->scale * $radius, + ); + } + } else { + my $height = $this_path->height != -1 ? $this_path->height : $layer->height; + $g->rectangle( + 'x' => $self->scale * unscale($line->a->x), + 'y' => $self->scale * $self->_y($layer->print_z), + 'width' => $self->scale * unscale($line->b->x - $line->a->x), + 'height' => $self->scale * $height, + 'rx' => $self->scale * $height * 0.5, + 'ry' => $self->scale * $height * 0.5, + ); + } + } +} + sub _y { my $self = shift; my ($y) = @_; diff --git a/slic3r.pl b/slic3r.pl index c634e7272..9f3ca42fe 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -6,13 +6,15 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/lib"; + use local::lib "$FindBin::Bin/local-lib"; } use File::Basename qw(basename); use Getopt::Long qw(:config no_auto_abbrev); use List::Util qw(first); -use POSIX qw(setlocale LC_NUMERIC); +use POSIX qw(setlocale LC_NUMERIC ceil); use Slic3r; +use Slic3r::Geometry qw(epsilon X Y Z deg2rad); use Time::HiRes qw(gettimeofday tv_interval); $|++; binmode STDOUT, ':utf8'; @@ -40,14 +42,16 @@ my %cli_options = (); 'merge|m' => \$opt{merge}, 'repair' => \$opt{repair}, 'cut=f' => \$opt{cut}, + 'cut-grid=s' => \$opt{cut_grid}, 'split' => \$opt{split}, 'info' => \$opt{info}, 'scale=f' => \$opt{scale}, - 'rotate=i' => \$opt{rotate}, + 'rotate=f' => \$opt{rotate}, 'duplicate=i' => \$opt{duplicate}, 'duplicate-grid=s' => \$opt{duplicate_grid}, 'print-center=s' => \$opt{print_center}, + 'dont-arrange' => \$opt{dont_arrange}, ); foreach my $opt_key (keys %{$Slic3r::Config::Options}) { my $cli = $Slic3r::Config::Options->{$opt_key}->{cli} or next; @@ -147,7 +151,7 @@ if (@ARGV) { # slicing from command line $mesh->translate(0, 0, -$mesh->bounding_box->z_min); my $upper = Slic3r::TriangleMesh->new; my $lower = Slic3r::TriangleMesh->new; - $mesh->cut($opt{cut}, $upper, $lower); + $mesh->cut(Z, $opt{cut}, $upper, $lower); $upper->repair; $lower->repair; $upper->write_ascii("${file}_upper.stl") @@ -158,6 +162,49 @@ if (@ARGV) { # slicing from command line exit; } + if ($opt{cut_grid}) { + my ($grid_x, $grid_y) = split /[,x]/, $opt{cut_grid}, 2; + foreach my $file (@ARGV) { + $file = Slic3r::decode_path($file); + my $model = Slic3r::Model->read_from_file($file); + $model->add_default_instances; + my $mesh = $model->mesh; + my $bb = $mesh->bounding_box; + $mesh->translate(0, 0, -$bb->z_min); + + my $x_parts = ceil(($bb->size->x - epsilon)/$grid_x); + my $y_parts = ceil(($bb->size->y - epsilon)/$grid_y); #-- + + for my $i (1..$x_parts) { + my $this = Slic3r::TriangleMesh->new; + if ($i == $x_parts) { + $this = $mesh; + } else { + my $next = Slic3r::TriangleMesh->new; + $mesh->cut(X, $bb->x_min + ($grid_x * $i), $next, $this); + $this->repair; + $next->repair; + $mesh = $next; + } + + for my $j (1..$y_parts) { + my $tile = Slic3r::TriangleMesh->new; + if ($j == $y_parts) { + $tile = $this; + } else { + my $next = Slic3r::TriangleMesh->new; + $this->cut(Y, $bb->y_min + ($grid_y * $j), $next, $tile); + $tile->repair; + $next->repair; + $this = $next; + } + $tile->write_ascii("${file}_${i}_${j}.stl"); + } + } + } + exit; + } + if ($opt{split}) { foreach my $file (@ARGV) { $file = Slic3r::decode_path($file); @@ -185,6 +232,7 @@ if (@ARGV) { # slicing from command line } else { $model = Slic3r::Model->read_from_file($input_file); } + $model->repair; if ($opt{info}) { $model->print_info; @@ -200,10 +248,11 @@ if (@ARGV) { # slicing from command line my $sprint = Slic3r::Print::Simple->new( scale => $opt{scale} // 1, - rotate => $opt{rotate} // 0, + rotate => deg2rad($opt{rotate} // 0), duplicate => $opt{duplicate} // 1, duplicate_grid => $opt{duplicate_grid} // [1,1], print_center => $opt{print_center} // Slic3r::Pointf->new(100,100), + dont_arrange => $opt{dont_arrange} // 0, status_cb => sub { my ($percent, $message) = @_; printf "=> %s\n", $message; @@ -296,7 +345,7 @@ $j (default: 100,100) --z-offset Additional height in mm to add to vertical coordinates (+/-, default: $config->{z_offset}) - --gcode-flavor The type of G-code to generate (reprap/teacup/makerware/sailfish/mach3/machinekit/smoothie/no-extrusion, + --gcode-flavor The type of G-code to generate (reprap/teacup/repetier/makerware/sailfish/mach3/machinekit/smoothie/no-extrusion, default: $config->{gcode_flavor}) --use-relative-e-distances Enable this to get relative E values (default: no) --use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no) @@ -485,6 +534,9 @@ $j --duplicate Number of items with auto-arrange (1+, default: 1) --duplicate-grid Number of items with grid arrangement (default: 1,1) --duplicate-distance Distance in mm between copies (default: $config->{duplicate_distance}) + --dont-arrange Don't arrange the objects on the build plate. The model coordinates + define the absolute positions on the build plate. + The option --print-center will be ignored. --xy-size-compensation Grow/shrink objects by the configured absolute distance (mm, default: $config->{xy_size_compensation}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index efeaa41de..faf23a75c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,16 +2,15 @@ cmake_minimum_required (VERSION 2.8) project (slic3r) # only on newer GCCs: -ftemplate-backtrace-limit=0 -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSLIC3R_DEBUG") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -DM_PI=3.14159265358979323846 -D_GLIBCXX_USE_C99 -DHAS_BOOL -DNOGDI -DBOOST_ASIO_DISABLE_KQUEUE") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -DSLIC3R_DEBUG") -set(workaround "") if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.7.0) if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.7.3) - set(workaround "-fno-inline-small-functions") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-inline-small-functions") endif(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.7.3) endif(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.7.0) -set(CMAKE_CXX_FLAGS "-DM_PI=3.14159265358979323846 -D_GLIBCXX_USE_C99 -DHAS_BOOL -DNOGDI -DBOOST_ASIO_DISABLE_KQUEUE ${workaround}") set(CMAKE_INCLUDE_CURRENT_DIR ON) IF(CMAKE_HOST_APPLE) @@ -45,14 +44,22 @@ add_library(libslic3r STATIC ${LIBDIR}/libslic3r/Extruder.cpp ${LIBDIR}/libslic3r/ExtrusionEntity.cpp ${LIBDIR}/libslic3r/ExtrusionEntityCollection.cpp + ${LIBDIR}/libslic3r/Fill/Fill.cpp + ${LIBDIR}/libslic3r/Fill/Fill3DHoneycomb.cpp + ${LIBDIR}/libslic3r/Fill/FillConcentric.cpp + ${LIBDIR}/libslic3r/Fill/FillHoneycomb.cpp + ${LIBDIR}/libslic3r/Fill/FillPlanePath.cpp + ${LIBDIR}/libslic3r/Fill/FillRectilinear.cpp ${LIBDIR}/libslic3r/Flow.cpp ${LIBDIR}/libslic3r/GCode.cpp + ${LIBDIR}/libslic3r/GCode/CoolingBuffer.cpp ${LIBDIR}/libslic3r/GCodeSender.cpp ${LIBDIR}/libslic3r/GCodeWriter.cpp ${LIBDIR}/libslic3r/Geometry.cpp ${LIBDIR}/libslic3r/IO.cpp ${LIBDIR}/libslic3r/Layer.cpp ${LIBDIR}/libslic3r/LayerRegion.cpp + ${LIBDIR}/libslic3r/LayerRegionFill.cpp ${LIBDIR}/libslic3r/Line.cpp ${LIBDIR}/libslic3r/Model.cpp ${LIBDIR}/libslic3r/MotionPlanner.cpp @@ -67,10 +74,10 @@ add_library(libslic3r STATIC ${LIBDIR}/libslic3r/PrintConfig.cpp ${LIBDIR}/libslic3r/PrintObject.cpp ${LIBDIR}/libslic3r/PrintRegion.cpp + ${LIBDIR}/libslic3r/SLAPrint.cpp ${LIBDIR}/libslic3r/Surface.cpp ${LIBDIR}/libslic3r/SurfaceCollection.cpp ${LIBDIR}/libslic3r/SVG.cpp - ${LIBDIR}/libslic3r/SVGExport.cpp ${LIBDIR}/libslic3r/TriangleMesh.cpp ) add_library(admesh STATIC diff --git a/src/slic3r.cpp b/src/slic3r.cpp index 2ea600f4b..3192fb618 100644 --- a/src/slic3r.cpp +++ b/src/slic3r.cpp @@ -1,13 +1,16 @@ #include "Config.hpp" -#include "Model.hpp" +#include "Geometry.hpp" #include "IO.hpp" +#include "Model.hpp" +#include "SLAPrint.hpp" #include "TriangleMesh.hpp" -#include "SVGExport.hpp" #include "libslic3r.h" #include #include #include #include +#include +#include "boost/filesystem.hpp" using namespace Slic3r; @@ -33,8 +36,18 @@ main(const int argc, const char **argv) // load config files supplied via --load for (std::vector::const_iterator file = cli_config.load.values.begin(); file != cli_config.load.values.end(); ++file) { + if (!boost::filesystem::exists(*file)) { + std::cout << "No such file: " << *file << std::endl; + exit(1); + } + DynamicPrintConfig c; - c.load(*file); + try { + c.load(*file); + } catch (std::exception &e) { + std::cout << "Error while reading config file: " << e.what() << std::endl; + exit(1); + } c.normalize(); print_config.apply(c); } @@ -50,9 +63,19 @@ main(const int argc, const char **argv) // read input file(s) if any std::vector models; for (t_config_option_keys::const_iterator it = input_files.begin(); it != input_files.end(); ++it) { + if (!boost::filesystem::exists(*it)) { + std::cout << "No such file: " << *it << std::endl; + exit(1); + } + Model model; // TODO: read other file formats with Model::read_from_file() - Slic3r::IO::STL::read(*it, &model); + try { + IO::STL::read(*it, &model); + } catch (std::exception &e) { + std::cout << *it << ": " << e.what() << std::endl; + exit(1); + } if (model.objects.empty()) { printf("Error: file is empty: %s\n", it->c_str()); @@ -66,8 +89,11 @@ main(const int argc, const char **argv) if (cli_config.scale_to_fit.is_positive_volume()) (*o)->scale_to_fit(cli_config.scale_to_fit.value); + // TODO: honor option order? (*o)->scale(cli_config.scale.value); - (*o)->rotate(cli_config.rotate.value, Z); + (*o)->rotate(Geometry::deg2rad(cli_config.rotate_x.value), X); + (*o)->rotate(Geometry::deg2rad(cli_config.rotate_y.value), Y); + (*o)->rotate(Geometry::deg2rad(cli_config.rotate.value), Z); } // TODO: handle --merge @@ -84,7 +110,7 @@ main(const int argc, const char **argv) TriangleMesh mesh = model->mesh(); mesh.repair(); - Slic3r::IO::OBJ::write(mesh, outfile); + IO::OBJ::write(mesh, outfile); printf("File exported to %s\n", outfile.c_str()); } else if (cli_config.export_pov) { std::string outfile = cli_config.output.value; @@ -92,19 +118,57 @@ main(const int argc, const char **argv) TriangleMesh mesh = model->mesh(); mesh.repair(); - Slic3r::IO::POV::write(mesh, outfile); + IO::POV::write(mesh, outfile); printf("File exported to %s\n", outfile.c_str()); } else if (cli_config.export_svg) { std::string outfile = cli_config.output.value; if (outfile.empty()) outfile = model->objects.front()->input_file + ".svg"; - - SVGExport svg_export(model->mesh()); - svg_export.mesh.repair(); - svg_export.config.apply(print_config, true); - svg_export.writeSVG(outfile); + + SLAPrint print(&*model); + print.config.apply(print_config, true); + print.slice(); + print.write_svg(outfile); printf("SVG file exported to %s\n", outfile.c_str()); + } else if (cli_config.cut_x > 0 || cli_config.cut_y > 0 || cli_config.cut > 0) { + model->repair(); + model->translate(0, 0, -model->bounding_box().min.z); + + if (!model->objects.empty()) { + // FIXME: cut all objects + Model out; + if (cli_config.cut_x > 0) { + model->objects.front()->cut(X, cli_config.cut_x, &out); + } else if (cli_config.cut_y > 0) { + model->objects.front()->cut(Y, cli_config.cut_y, &out); + } else { + model->objects.front()->cut(Z, cli_config.cut, &out); + } + + ModelObject &upper = *out.objects[0]; + ModelObject &lower = *out.objects[1]; + + if (upper.facets_count() > 0) { + TriangleMesh m = upper.mesh(); + IO::STL::write(m, upper.input_file + "_upper.stl"); + } + if (lower.facets_count() > 0) { + TriangleMesh m = lower.mesh(); + IO::STL::write(m, lower.input_file + "_lower.stl"); + } + } + } else if (cli_config.cut_grid.value.x > 0 && cli_config.cut_grid.value.y > 0) { + TriangleMesh mesh = model->mesh(); + mesh.repair(); + + TriangleMeshPtrs meshes = mesh.cut_by_grid(cli_config.cut_grid.value); + for (TriangleMeshPtrs::iterator m = meshes.begin(); m != meshes.end(); ++m) { + std::ostringstream ss; + ss << model->objects.front()->input_file << "_" << (m - meshes.begin()) << ".stl"; + IO::STL::write(**m, ss.str()); + delete *m; + } } else { - std::cerr << "error: only --export-svg and --export-obj are currently supported" << std::endl; + std::cerr << "error: command not supported" << std::endl; return 1; } } diff --git a/src/utils/extrude-tin.cpp b/src/utils/extrude-tin.cpp index d2b9b49e6..0043b42e8 100644 --- a/src/utils/extrude-tin.cpp +++ b/src/utils/extrude-tin.cpp @@ -34,59 +34,12 @@ main(const int argc, const char **argv) for (t_config_option_keys::const_iterator it = input_files.begin(); it != input_files.end(); ++it) { TriangleMesh mesh; Slic3r::IO::STL::read(*it, &mesh); - calculate_normals(&mesh.stl); - - if (mesh.facets_count() == 0) { - printf("Error: file is empty: %s\n", it->c_str()); - continue; - } - - float z = mesh.stl.stats.min.z - config.option("offset", true)->getFloat(); - printf("min.z = %f, z = %f\n", mesh.stl.stats.min.z, z); - TriangleMesh mesh2 = mesh; - - for (int i = 0; i < mesh.stl.stats.number_of_facets; ++i) { - const stl_facet &facet = mesh.stl.facet_start[i]; - - if (facet.normal.z < 0) { - printf("Invalid 2.5D mesh / TIN (one facet points downwards = %f)\n", facet.normal.z); - exit(1); - } - - for (int j = 0; j < 3; ++j) { - if (mesh.stl.neighbors_start[i].neighbor[j] == -1) { - stl_facet new_facet; - float normal[3]; - - // first triangle - new_facet.vertex[0] = new_facet.vertex[2] = facet.vertex[(j+1)%3]; - new_facet.vertex[1] = facet.vertex[j]; - new_facet.vertex[2].z = z; - stl_calculate_normal(normal, &new_facet); - stl_normalize_vector(normal); - new_facet.normal.x = normal[0]; - new_facet.normal.y = normal[1]; - new_facet.normal.z = normal[2]; - stl_add_facet(&mesh2.stl, &new_facet); - - // second triangle - new_facet.vertex[0] = new_facet.vertex[1] = facet.vertex[j]; - new_facet.vertex[2] = facet.vertex[(j+1)%3]; - new_facet.vertex[1].z = new_facet.vertex[2].z = z; - new_facet.normal.x = normal[0]; - new_facet.normal.y = normal[1]; - new_facet.normal.z = normal[2]; - stl_add_facet(&mesh2.stl, &new_facet); - } - } - } - - mesh2.repair(); + mesh.extrude_tin(config.option("offset", true)->getFloat()); std::string outfile = config.option("output", true)->getString(); if (outfile.empty()) outfile = *it + "_extruded.stl"; - Slic3r::IO::STL::write(mesh2, outfile); + Slic3r::IO::STL::write(mesh, outfile); printf("Extruded mesh written to %s\n", outfile.c_str()); } diff --git a/t/angles.t b/t/angles.t index 1e1a6c9c5..2ae00b4e8 100644 --- a/t/angles.t +++ b/t/angles.t @@ -7,6 +7,9 @@ plan tests => 34; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use lib "$FindBin::Bin/../lib"; + use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; diff --git a/t/arcs.t b/t/arcs.t index 65b83de43..ec85dfbc2 100644 --- a/t/arcs.t +++ b/t/arcs.t @@ -7,6 +7,7 @@ plan tests => 24; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; diff --git a/t/avoid_crossing_perimeters.t b/t/avoid_crossing_perimeters.t index dd6c3e7b6..7cd04e50e 100644 --- a/t/avoid_crossing_perimeters.t +++ b/t/avoid_crossing_perimeters.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first sum); diff --git a/t/bridges.t b/t/bridges.t index 432d5e7a9..51dda5c64 100644 --- a/t/bridges.t +++ b/t/bridges.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first sum); diff --git a/t/clean_polylines.t b/t/clean_polylines.t index 4526bd60c..50c6f5bbd 100644 --- a/t/clean_polylines.t +++ b/t/clean_polylines.t @@ -7,6 +7,7 @@ plan tests => 6; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; diff --git a/t/clipper.t b/t/clipper.t index a898a53f6..3c9838143 100644 --- a/t/clipper.t +++ b/t/clipper.t @@ -7,6 +7,7 @@ plan tests => 6; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(sum); diff --git a/t/collinear.t b/t/collinear.t index 9dee77700..b28a3602c 100644 --- a/t/collinear.t +++ b/t/collinear.t @@ -7,6 +7,7 @@ plan tests => 11; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; diff --git a/t/combineinfill.t b/t/combineinfill.t index 6661cdd6c..66e461d45 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); diff --git a/t/config.t b/t/config.t index 7f85dae84..829ef5f39 100644 --- a/t/config.t +++ b/t/config.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; diff --git a/t/cooling.t b/t/cooling.t index 9958037f1..a413a2b33 100644 --- a/t/cooling.t +++ b/t/cooling.t @@ -2,30 +2,29 @@ use Test::More; use strict; use warnings; -plan tests => 11; +plan tests => 12; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } +use List::Util qw(first); use Slic3r; use Slic3r::Test; +my $gcodegen; sub buffer { my $config = shift || Slic3r::Config->new; my $print_config = Slic3r::Config::Print->new; $print_config->apply_dynamic($config); - my $gcodegen = Slic3r::GCode->new; + $gcodegen = Slic3r::GCode->new; $gcodegen->apply_print_config($print_config); $gcodegen->set_layer_count(10); - my $buffer = Slic3r::GCode::CoolingBuffer->new( - config => $print_config, - gcodegen => $gcodegen, - ); - return $buffer; + return Slic3r::GCode::CoolingBuffer->new($gcodegen); } my $config = Slic3r::Config->new_from_defaults; @@ -33,14 +32,14 @@ $config->set('disable_fan_first_layers', 0); { my $buffer = buffer($config); - $buffer->gcodegen->set_elapsed_time($buffer->config->slowdown_below_layer_time + 1); + $buffer->gcodegen->set_elapsed_time($buffer->gcodegen->config->slowdown_below_layer_time + 1); my $gcode = $buffer->append('G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1', 0, 0, 0.4) . $buffer->flush; like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold'; } { my $buffer = buffer($config); - $buffer->gcodegen->set_elapsed_time($buffer->config->slowdown_below_layer_time - 1); + $buffer->gcodegen->set_elapsed_time($buffer->gcodegen->config->slowdown_below_layer_time - 1); my $gcode = $buffer->append( "G1 X50 F2500\n" . "G1 F3000;_EXTRUDE_SET_SPEED\n" . @@ -55,7 +54,7 @@ $config->set('disable_fan_first_layers', 0); { my $buffer = buffer($config); - $buffer->gcodegen->set_elapsed_time($buffer->config->fan_below_layer_time + 1); + $buffer->gcodegen->set_elapsed_time($buffer->gcodegen->config->fan_below_layer_time + 1); my $gcode = $buffer->append('G1 X100 E1 F3000', 0, 0, 0.4) . $buffer->flush; unlike $gcode, qr/M106/, 'fan is not activated when elapsed time is greater than fan threshold'; } @@ -65,7 +64,7 @@ $config->set('disable_fan_first_layers', 0); my $gcode = ""; for my $obj_id (0 .. 1) { # use an elapsed time which is < the slowdown threshold but greater than it when summed twice - $buffer->gcodegen->set_elapsed_time($buffer->config->slowdown_below_layer_time - 1); + $buffer->gcodegen->set_elapsed_time($buffer->gcodegen->config->slowdown_below_layer_time - 1); $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, 0, 0.4); } $gcode .= $buffer->flush; @@ -78,7 +77,7 @@ $config->set('disable_fan_first_layers', 0); for my $layer_id (0 .. 1) { for my $obj_id (0 .. 1) { # use an elapsed time which is < the threshold but greater than it when summed twice - $buffer->gcodegen->set_elapsed_time($buffer->config->fan_below_layer_time - 1); + $buffer->gcodegen->set_elapsed_time($buffer->gcodegen->config->fan_below_layer_time - 1); $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights } } @@ -92,7 +91,7 @@ $config->set('disable_fan_first_layers', 0); for my $layer_id (0 .. 1) { for my $obj_id (0 .. 1) { # use an elapsed time which is < the threshold even when summed twice - $buffer->gcodegen->set_elapsed_time($buffer->config->fan_below_layer_time/2 - 1); + $buffer->gcodegen->set_elapsed_time($buffer->gcodegen->config->fan_below_layer_time/2 - 1); $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights } } @@ -135,4 +134,28 @@ $config->set('disable_fan_first_layers', 0); ok !$bridge_with_no_fan, 'bridge fan is turned on for all bridges'; } +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('cooling', 1); + $config->set('fan_below_layer_time', 0); + $config->set('slowdown_below_layer_time', 10); + $config->set('min_print_speed', 0); + $config->set('start_gcode', ''); + + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my @layer_times = (0); # in seconds + Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd eq 'G1') { + if ($info->{dist_Z}) { + push @layer_times, 0; + } + $layer_times[-1] += abs($info->{dist_XY} || $info->{dist_E} || $info->{dist_Z} || 0) / ($args->{F} // $self->F) * 60; + } + }); + my $all_below = !defined first { $_ > 0 && $_ < $config->slowdown_below_layer_time } @layer_times; + ok $all_below, 'slowdown_below_layer_time is honored'; +} + __END__ diff --git a/t/custom_gcode.t b/t/custom_gcode.t index 9d68d1eed..653bb26ae 100644 --- a/t/custom_gcode.t +++ b/t/custom_gcode.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); @@ -59,7 +60,7 @@ use Slic3r::Test; $config->set('start_gcode', "TRAVEL:[travel_speed] HEIGHT:[layer_height]\n"); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $output_file = $print->print->expanded_output_filepath; + my $output_file = $print->print->output_filepath; my ($t, $h) = map $config->$_, qw(travel_speed layer_height); ok $output_file =~ /ts_${t}_/, 'print config options are replaced in output filename'; ok $output_file =~ /lh_$h\./, 'region config options are replaced in output filename'; diff --git a/t/dynamic.t b/t/dynamic.t index beae06ad0..5d4d3ceb4 100644 --- a/t/dynamic.t +++ b/t/dynamic.t @@ -8,6 +8,7 @@ plan tests => 20; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); diff --git a/t/fill.t b/t/fill.t index 6fb05196e..a9e505c16 100644 --- a/t/fill.t +++ b/t/fill.t @@ -2,17 +2,18 @@ use Test::More; use strict; use warnings; -plan tests => 43; +plan tests => 92; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first sum); use Slic3r; -use Slic3r::Geometry qw(X Y scale unscale convex_hull); -use Slic3r::Geometry::Clipper qw(union diff diff_ex offset offset2_ex); +use Slic3r::Geometry qw(PI X Y scaled_epsilon scale unscale convex_hull); +use Slic3r::Geometry::Clipper qw(union diff diff_ex offset offset2_ex diff_pl); use Slic3r::Surface qw(:types); use Slic3r::Test; @@ -20,25 +21,78 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $print = Slic3r::Print->new; - my $filler = Slic3r::Fill::Rectilinear->new( - print => $print, - bounding_box => Slic3r::Geometry::BoundingBox->new_from_points([ Slic3r::Point->new(0, 0), Slic3r::Point->new(10, 10) ]), - ); my $surface_width = 250; - my $distance = $filler->adjust_solid_spacing( - width => $surface_width, - distance => 100, - ); - is $distance, 125, 'adjusted solid distance'; + my $distance = Slic3r::Filler::adjust_solid_spacing($surface_width, 47); + is $distance, 50, 'adjusted solid distance'; is $surface_width % $distance, 0, 'adjusted solid distance'; } +{ + my $filler = Slic3r::Filler->new_from_type('rectilinear'); + $filler->set_angle(-(PI)/2); + $filler->set_min_spacing(5); + $filler->set_dont_adjust(1); + $filler->set_endpoints_overlap(0); + + my $test = sub { + my ($expolygon) = @_; + my $surface = Slic3r::Surface->new( + surface_type => S_TYPE_TOP, + expolygon => $expolygon, + ); + return $filler->fill_surface($surface); + }; + + # square + $filler->set_density($filler->min_spacing / 50); + for my $i (0..3) { + # check that it works regardless of the points order + my @points = ([0,0], [100,0], [100,100], [0,100]); + @points = (@points[$i..$#points], @points[0..($i-1)]); + my $paths = $test->(my $e = Slic3r::ExPolygon->new([ scale_points @points ])); + + is(scalar @$paths, 1, 'one continuous path') or done_testing, exit; + ok abs($paths->[0]->length - scale(3*100 + 2*50)) - scaled_epsilon, 'path has expected length'; + } + + # diamond with endpoints on grid + { + my $paths = $test->(my $e = Slic3r::ExPolygon->new([ scale_points [0,0], [100,0], [150,50], [100,100], [0,100], [-50,50] ])); + is(scalar @$paths, 1, 'one continuous path') or done_testing, exit; + } + + # square with hole + for my $angle (-(PI/2), -(PI/4), -(PI), PI/2, PI) { + for my $spacing (25, 5, 7.5, 8.5) { + $filler->set_density($filler->min_spacing / $spacing); + $filler->set_angle($angle); + my $paths = $test->(my $e = Slic3r::ExPolygon->new( + [ scale_points [0,0], [100,0], [100,100], [0,100] ], + [ scale_points reverse [25,25], [75,25], [75,75], [25,75] ], + )); + + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "fill.svg", + no_arrows => 1, + expolygons => [$e], + polylines => $paths, + ); + } + + ok(@$paths >= 2 && @$paths <= 3, '2 or 3 continuous paths') or done_testing, exit; + ok(!@{diff_pl($paths->arrayref, offset(\@$e, +scaled_epsilon*10))}, + 'paths don\'t cross hole') or done_testing, exit; + } + } +} + { my $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,50], [0,50] ]); - my $filler = Slic3r::Fill::Rectilinear->new( - bounding_box => $expolygon->bounding_box, - angle => 0, - ); + my $filler = Slic3r::Filler->new_from_type('rectilinear'); + $filler->set_bounding_box($expolygon->bounding_box); + $filler->set_angle(0); my $surface = Slic3r::Surface->new( surface_type => S_TYPE_TOP, expolygon => $expolygon, @@ -48,11 +102,12 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } height => 0.4, nozzle_diameter => 0.50, ); - $filler->spacing($flow->spacing); + $filler->set_min_spacing($flow->spacing); + $filler->set_density(1); foreach my $angle (0, 45) { $surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]); - my @paths = $filler->fill_surface($surface, layer_height => 0.4, density => 0.4); - is scalar @paths, 1, 'one continuous path'; + my $paths = $filler->fill_surface($surface, layer_height => 0.4, density => 0.4); + is scalar @$paths, 1, 'one continuous path'; } } @@ -60,10 +115,10 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } my $test = sub { my ($expolygon, $flow_spacing, $angle, $density) = @_; - my $filler = Slic3r::Fill::Rectilinear->new( - bounding_box => $expolygon->bounding_box, - angle => $angle // 0, - ); + my $filler = Slic3r::Filler->new_from_type('rectilinear'); + $filler->set_bounding_box($expolygon->bounding_box); + $filler->set_angle($angle // 0); + $filler->set_dont_adjust(0); my $surface = Slic3r::Surface->new( surface_type => S_TYPE_BOTTOM, expolygon => $expolygon, @@ -73,15 +128,15 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } height => 0.4, nozzle_diameter => $flow_spacing, ); - $filler->spacing($flow->spacing); - my @paths = $filler->fill_surface( + $filler->set_min_spacing($flow->spacing); + my $paths = $filler->fill_surface( $surface, layer_height => $flow->height, density => $density // 1, ); # check whether any part was left uncovered - my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $filler->spacing/2)}, @paths; + my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $filler->spacing/2)}, @$paths; my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ], 1); # ignore very small dots @@ -93,8 +148,9 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } require "Slic3r/SVG.pm"; Slic3r::SVG::output( "uncovered.svg", - expolygons => [$expolygon], - red_expolygons => $uncovered, + expolygons => [$expolygon], + red_expolygons => $uncovered, + polylines => $paths, ); exit; } @@ -116,7 +172,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } $expolygon = Slic3r::ExPolygon->new( [[59515297,5422499],[59531249,5578697],[59695801,6123186],[59965713,6630228],[60328214,7070685],[60773285,7434379],[61274561,7702115],[61819378,7866770],[62390306,7924789],[62958700,7866744],[63503012,7702244],[64007365,7434357],[64449960,7070398],[64809327,6634999],[65082143,6123325],[65245005,5584454],[65266967,5422499],[66267307,5422499],[66269190,8310081],[66275379,17810072],[66277259,20697500],[65267237,20697500],[65245004,20533538],[65082082,19994444],[64811462,19488579],[64450624,19048208],[64012101,18686514],[63503122,18415781],[62959151,18251378],[62453416,18198442],[62390147,18197355],[62200087,18200576],[61813519,18252990],[61274433,18415918],[60768598,18686517],[60327567,19047892],[59963609,19493297],[59695865,19994587],[59531222,20539379],[59515153,20697500],[58502480,20697500],[58502480,5422499]] ); - $test->($expolygon, 0.524341649025257); + $test->($expolygon, 0.524341649025257, PI/2); $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [98,0], [98,10], [0,10] ]); $test->($expolygon, 0.5, 45, 0.99); # non-solid infill diff --git a/t/flow.t b/t/flow.t index 071d290c8..37e9deafa 100644 --- a/t/flow.t +++ b/t/flow.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first sum); diff --git a/t/gaps.t b/t/gaps.t index 16baa0363..01ec1b4e3 100644 --- a/t/gaps.t +++ b/t/gaps.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); diff --git a/t/gcode.t b/t/gcode.t index ef2209a80..ec9b7bb2a 100644 --- a/t/gcode.t +++ b/t/gcode.t @@ -1,10 +1,11 @@ -use Test::More tests => 23; +use Test::More tests => 27; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); @@ -215,4 +216,74 @@ use Slic3r::Test; ok !$spiral, 'spiral vase is correctly disabled on layers with multiple loops'; } +{ + # Tests that the Repetier flavor produces M201 Xnnn Ynnn for resetting + # acceleration, also that M204 Snnn syntax is not generated. + my $config = Slic3r::Config->new_from_defaults; + $config->set('gcode_flavor', 'repetier'); + $config->set('default_acceleration', 1337); + my $print = Slic3r::Test::init_print('cube_with_hole', config => $config); + + my $has_accel = 0; + my $has_m204 = 0; + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd eq 'M201' && exists $args->{X} && exists $args->{Y}) { + if ($args->{X} == 1337 && $args->{Y} == 1337) { + $has_accel = 1; + } + } + if ($cmd eq 'M204' && exists $args->{S}) { + $has_m204 = 1; + } + }); + ok $has_accel, 'M201 is generated for repetier firmware.'; + ok !$has_m204, 'M204 is not generated for repetier firmware'; +} + +{ + # Test verifies that if has_heatbed is false, M190/M140 gcodes are not + # generated by default even if a bed temperature is set. + my $config = Slic3r::Config->new_from_defaults; + $config->set('has_heatbed', 0); + $config->set('first_layer_bed_temperature', 100); + $config->set('bed_temperature', 60); + + my $had_gcode = 0; + my $print = Slic3r::Test::init_print('cube_with_hole', config => $config); + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if (($cmd eq 'M140' && exists $args->{S}) || + ($cmd eq 'M190' && exists $args->{S})) { + $had_gcode = 1; + } + }); + + ok !$had_gcode, 'M190/M140 codes are not generated if has_heatbed = 0'; +} + +{ + # Test verifies that if has_heatbed is true, M190/M140 gcodes are + # generated by default if a bed temperature is set. + my $config = Slic3r::Config->new_from_defaults; + $config->set('has_heatbed', 1); + $config->set('first_layer_bed_temperature', 100); + $config->set('bed_temperature', 60); + + my $had_gcode = 0; + my $print = Slic3r::Test::init_print('cube_with_hole', config => $config); + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if (($cmd eq 'M140' && exists $args->{S}) || + ($cmd eq 'M190' && exists $args->{S})) { + $had_gcode = 1; + } + }); + + ok $had_gcode, 'M190/M140 codes are generated if has_heatbed = 1'; +} + __END__ diff --git a/t/geometry.t b/t/geometry.t index 4529e6b1c..0f37c0aa3 100644 --- a/t/geometry.t +++ b/t/geometry.t @@ -7,6 +7,7 @@ plan tests => 42; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; diff --git a/t/layers.t b/t/layers.t index 57fd25760..a85998077 100644 --- a/t/layers.t +++ b/t/layers.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); diff --git a/t/loops.t b/t/loops.t index 6293a4be7..e662469ca 100644 --- a/t/loops.t +++ b/t/loops.t @@ -8,6 +8,7 @@ plan tests => 4; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; diff --git a/t/multi.t b/t/multi.t index 362f1f7b4..6ce862818 100644 --- a/t/multi.t +++ b/t/multi.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); diff --git a/t/perimeters.t b/t/perimeters.t index 47b844887..828504311 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r::ExtrusionLoop ':roles'; diff --git a/t/polyclip.t b/t/polyclip.t index 1292d3216..0808c7be9 100644 --- a/t/polyclip.t +++ b/t/polyclip.t @@ -7,6 +7,7 @@ plan tests => 18; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; diff --git a/t/pressure.t b/t/pressure.t index 6bbb81d84..dde236954 100644 --- a/t/pressure.t +++ b/t/pressure.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(); diff --git a/t/print.t b/t/print.t index 28cf1ee42..b78739e49 100644 --- a/t/print.t +++ b/t/print.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); diff --git a/t/retraction.t b/t/retraction.t index a7076c025..571e9755d 100644 --- a/t/retraction.t +++ b/t/retraction.t @@ -1,10 +1,11 @@ -use Test::More tests => 22; +use Test::More tests => 26; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(any); @@ -26,6 +27,7 @@ use Slic3r::Test qw(_eq); my @retracted = (1); # ignore the first travel move from home to first point my @retracted_length = (0); my $lifted = 0; + my $lift_dist = 0; # track lifted distance for toolchanges and extruders with different retract_lift values my $changed_tool = 0; my $wait_for_toolchange = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { @@ -48,12 +50,14 @@ use Slic3r::Test qw(_eq); fail 'only lifting while retracted' if !$retracted[$tool]; fail 'double lift' if $lifted; $lifted = 1; + $lift_dist = $info->{dist_Z}; } if ($info->{dist_Z} < 0) { fail 'going down only after lifting' if !$lifted; fail 'going down by the same amount of the lift or by the amount needed to get to next layer' - if !_eq($info->{dist_Z}, -$print->print->config->get_at('retract_lift', $tool)) - && !_eq($info->{dist_Z}, -$print->print->config->get_at('retract_lift', $tool) + $conf->layer_height); + if !_eq($info->{dist_Z}, -$lift_dist) + && !_eq($info->{dist_Z}, -lift_dist + $conf->layer_height); + $lift_dist = 0; $lifted = 0; } fail 'move Z at travel speed' if ($args->{F} // $self->F) != $conf->travel_speed * 60; @@ -110,7 +114,7 @@ use Slic3r::Test qw(_eq); $conf->set('retract_restart_extra', [-1]); ok $test->($conf), "negative restart extra length$descr"; - $conf->set('retract_lift', [1]); + $conf->set('retract_lift', [1, 2]); ok $test->($conf), "lift$descr"; }; @@ -204,7 +208,7 @@ use Slic3r::Test qw(_eq); { my $config = Slic3r::Config->new_from_defaults; $config->set('start_gcode', ''); - $config->set('retract_lift', [3]); + $config->set('retract_lift', [3, 4]); my @lifted_at = (); my $test = sub { @@ -219,19 +223,36 @@ use Slic3r::Test qw(_eq); }); }; - $config->set('retract_lift_above', [0]); - $config->set('retract_lift_below', [0]); + $config->set('retract_lift_above', [0, 0]); + $config->set('retract_lift_below', [0, 0]); $test->(); ok !!@lifted_at, 'lift takes place when above/below == 0'; - $config->set('retract_lift_above', [5]); - $config->set('retract_lift_below', [15]); + $config->set('retract_lift_above', [5, 6]); + $config->set('retract_lift_below', [15, 13]); $test->(); ok !!@lifted_at, 'lift takes place when above/below != 0'; ok !(any { $_ < $config->get_at('retract_lift_above', 0) } @lifted_at), 'Z is not lifted below the configured value'; ok !(any { $_ > $config->get_at('retract_lift_below', 0) } @lifted_at), 'Z is not lifted above the configured value'; + + # check lifting with different values for 2. extruder + $config->set('perimeter_extruder', 2); + $config->set('infill_extruder', 2); + $config->set('retract_lift_above', [0, 0]); + $config->set('retract_lift_below', [0, 0]); + $test->(); + ok !!@lifted_at, 'lift takes place when above/below == 0 for 2. extruder'; + + $config->set('retract_lift_above', [5, 6]); + $config->set('retract_lift_below', [15, 13]); + $test->(); + ok !!@lifted_at, 'lift takes place when above/below != 0 for 2. extruder'; + ok !(any { $_ < $config->get_at('retract_lift_above', 1) } @lifted_at), + 'Z is not lifted below the configured value for 2. extruder'; + ok !(any { $_ > $config->get_at('retract_lift_below', 1) } @lifted_at), + 'Z is not lifted above the configured value for 2. extruder'; } __END__ diff --git a/t/shells.t b/t/shells.t index e7eb5da13..e6eb65a9d 100644 --- a/t/shells.t +++ b/t/shells.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first sum); diff --git a/t/skirt_brim.t b/t/skirt_brim.t index 3353149cc..9b2a0a126 100644 --- a/t/skirt_brim.t +++ b/t/skirt_brim.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); diff --git a/t/slice.t b/t/slice.t index 301ae164e..51b52163d 100644 --- a/t/slice.t +++ b/t/slice.t @@ -8,6 +8,7 @@ plan tests => 16; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } # temporarily disable compilation errors due to constant not being exported anymore diff --git a/t/support.t b/t/support.t index 3eba6e64b..33e5d9cb3 100644 --- a/t/support.t +++ b/t/support.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); diff --git a/t/svg.t b/t/svg.t index 9299eeefa..6092efe24 100644 --- a/t/svg.t +++ b/t/svg.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; diff --git a/t/thin.t b/t/thin.t index 514614802..2d256d286 100644 --- a/t/thin.t +++ b/t/thin.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; diff --git a/t/threads.t b/t/threads.t index 106a68af5..7fcd86f0e 100644 --- a/t/threads.t +++ b/t/threads.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); diff --git a/t/vibrationlimit.t b/t/vibrationlimit.t index 7bfa27acb..1a5488a6e 100644 --- a/t/vibrationlimit.t +++ b/t/vibrationlimit.t @@ -5,6 +5,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; diff --git a/utils/amf-to-stl.pl b/utils/amf-to-stl.pl index b421dd33a..f9599704f 100755 --- a/utils/amf-to-stl.pl +++ b/utils/amf-to-stl.pl @@ -7,6 +7,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use File::Basename qw(basename); diff --git a/utils/autorun.bat b/utils/autorun.bat index b952b87e5..be2c7b3f0 100644 --- a/utils/autorun.bat +++ b/utils/autorun.bat @@ -1 +1 @@ -@perl5.22.2.exe slic3r.pl %* +@perl5.24.0.exe slic3r.pl %* diff --git a/utils/config-bundle-to-config.pl b/utils/config-bundle-to-config.pl index beecd666f..e1d7f6143 100755 --- a/utils/config-bundle-to-config.pl +++ b/utils/config-bundle-to-config.pl @@ -9,6 +9,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Getopt::Long qw(:config no_auto_abbrev); diff --git a/utils/dump-stl.pl b/utils/dump-stl.pl index 08b4d750a..0c7f609de 100644 --- a/utils/dump-stl.pl +++ b/utils/dump-stl.pl @@ -8,6 +8,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; diff --git a/utils/gcode_sectioncut.pl b/utils/gcode_sectioncut.pl index 4d9619e4c..b94a2fc4b 100644 --- a/utils/gcode_sectioncut.pl +++ b/utils/gcode_sectioncut.pl @@ -7,6 +7,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Getopt::Long qw(:config no_auto_abbrev); diff --git a/utils/package_win32.ps1 b/utils/package_win32.ps1 index 0acbed6a2..1311c4a48 100644 --- a/utils/package_win32.ps1 +++ b/utils/package_win32.ps1 @@ -1,17 +1,33 @@ # Written by Joseph Lenox # Licensed under the same license as the rest of Slic3r. # ------------------------ -# You need to have Strawberry Perl 5.22 installed for this to work, +# You need to have Strawberry Perl 5.24.0.1 installed for this to work, +param ( + [switch]$exe = $false +) echo "Make this is run from the perl command window." echo "Requires PAR." New-Variable -Name "current_branch" -Value "" +New-Variable -Name "current_date" -Value "$(Get-Date -UFormat '%Y.%m.%d')" +New-Variable -Name "output_file" -Value "" git branch | foreach { - if ($_ -match "` (.*)"){ - $current_branch += $matches[1] + if ($env:APPVEYOR) { + if ($_ -match "` (.*)") { + $current_branch += $matches[1] + } + } else { + if ($_ -match "\*` (.*)"){ + $current_branch += $matches[1] + } } } +if ($exe) { + $output_file = "slic3r.exe" +} else { + $output_file = "slic3r.par" +} # Change this to where you have Strawberry Perl installed. New-Variable -Name "STRAWBERRY_PATH" -Value "C:\Strawberry" @@ -22,21 +38,15 @@ pp ` -a "../utils;utils" ` -a "autorun.bat;slic3r.bat" ` -a "../var;var" ` --a "${STRAWBERRY_PATH}\perl\bin\perl5.22.2.exe;perl5.22.2.exe" ` --a "${STRAWBERRY_PATH}\perl\bin\perl522.dll;perl522.dll" ` +-a "${STRAWBERRY_PATH}\perl\bin\perl5.24.0.exe;perl5.24.0.exe" ` +-a "${STRAWBERRY_PATH}\perl\bin\perl524.dll;perl524.dll" ` -a "${STRAWBERRY_PATH}\perl\bin\libgcc_s_sjlj-1.dll;libgcc_s_sjlj-1.dll" ` -a "${STRAWBERRY_PATH}\perl\bin\libstdc++-6.dll;libstdc++-6.dll" ` -a "${STRAWBERRY_PATH}\perl\bin\libwinpthread-1.dll;libwinpthread-1.dll" ` -a "${STRAWBERRY_PATH}\perl\bin\freeglut.dll;freeglut.dll" ` -a "${STRAWBERRY_PATH}\c\bin\libglut-0_.dll;libglut-0_.dll" ` --a "${STRAWBERRY_PATH}\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxbase30u_gcc_custom.dll;wxbase30u_gcc_custom.dll" ` --a "${STRAWBERRY_PATH}\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_adv_gcc_custom.dll;wxmsw30u_adv_gcc_custom.dll" ` --a "${STRAWBERRY_PATH}\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_gl_gcc_custom.dll;wxmsw30u_gl_gcc_custom.dll" ` --a "${STRAWBERRY_PATH}\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_core_gcc_custom.dll;wxmsw30u_core_gcc_custom.dll" ` --a "${STRAWBERRY_PATH}\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_html_gcc_custom.dll;wxmsw30u_html_gcc_custom.dll" ` --a "${STRAWBERRY_PATH}\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxbase30u_xml_gcc_custom.dll;wxbase30u_xml_gcc_custom.dll" ` --a "${STRAWBERRY_PATH}\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxbase30u_net_gcc_custom.dll;wxbase30u_net_gcc_custom.dll" ` -a "../lib;lib" ` +-a "../local-lib;local-lib" ` -a "../slic3r.pl;slic3r.pl" ` -M AutoLoader ` -M B ` @@ -47,20 +57,12 @@ pp ` -M Config ` -M Crypt::CBC ` -M Cwd ` --M Data::UUID ` -M Devel::GlobalDestruction ` -M Digest ` -M Digest::MD5 ` -M Digest::SHA ` -M Digest::base ` -M DynaLoader ` --M Encode ` --M Encode::Alias ` --M Encode::Byte ` --M Encode::Config ` --M Encode::Encoding ` --M Encode::Locale ` --M Encode::MIME::Name ` -M Errno ` -M Exporter ` -M Exporter::Heavy ` @@ -71,8 +73,6 @@ pp ` -M File::Spec::Unix ` -M File::Spec::Win32 ` -M FindBin ` --M Getopt::Long ` --M Growl::GNTP ` -M HTTP::Config ` -M HTTP::Date ` -M HTTP::Headers ` @@ -85,11 +85,6 @@ pp ` -M IO ` -M IO::Handle ` -M IO::Select ` --M IO::Socket ` --M IO::Socket::INET ` --M IO::Socket::INET6 ` --M IO::Socket::IP ` --M IO::Socket::UNIX ` -M LWP ` -M LWP::MediaTypes ` -M LWP::MemberMixin ` @@ -97,56 +92,16 @@ pp ` -M LWP::Protocol::http ` -M LWP::UserAgent ` -M List::Util ` --M Math::Libm ` --M Math::PlanePath ` --M Math::PlanePath::ArchimedeanChords ` --M Math::PlanePath::Base::Digits ` --M Math::PlanePath::Base::Generic ` --M Math::PlanePath::Base::NSEW ` --M Math::PlanePath::Flowsnake ` --M Math::PlanePath::FlowsnakeCentres ` --M Math::PlanePath::HilbertCurve ` --M Math::PlanePath::OctagramSpiral ` --M Math::PlanePath::SacksSpiral ` -M Math::Trig ` -M Method::Generate::Accessor ` -M Method::Generate::BuildAll ` -M Method::Generate::Constructor ` -M Module::Runtime ` --M Moo ` --M Moo::HandleMoose ` --M Moo::Object ` --M Moo::Role ` --M Moo::sification ` --M Net::Bonjour ` --M Net::Bonjour::Entry ` --M Net::DNS ` --M Net::DNS::Domain ` --M Net::DNS::DomainName ` --M Net::DNS::Header ` --M Net::DNS::Packet ` --M Net::DNS::Parameters ` --M Net::DNS::Question ` --M Net::DNS::RR ` --M Net::DNS::RR::OPT ` --M Net::DNS::RR::PTR ` --M Net::DNS::Resolver ` --M Net::DNS::Resolver ` --M Net::DNS::Resolver::Base ` --M Net::DNS::Resolver::MSWin32 ` --M Net::DNS::Update ` --M Net::HTTP ` --M Net::HTTP::Methods ` --M OpenGL ` -M POSIX ` -M Pod::Escapes ` -M Pod::Text ` -M Pod::Usage ` --M Role::Tiny ` --M Scalar::Util ` -M SelectSaver ` --M Slic3r::* ` --M Slic3r::XS ` -M Socket ` -M Socket6 ` -M Storable ` @@ -154,7 +109,6 @@ pp ` -M Sub::Exporter ` -M Sub::Exporter::Progressive ` -M Sub::Name ` --M Sub::Quote ` -M Sub::Util ` -M Symbol ` -M Term::Cap ` @@ -165,59 +119,35 @@ pp ` -M Tie::Handle ` -M Tie::Hash ` -M Tie::StdHandle ` --M Time::HiRes ` -M Time::Local ` -M URI ` -M URI::Escape ` -M URI::http ` -M Unicode::Normalize ` --M Win32 ` -M Win32::API ` --M Win32::API::Struct ` --M Win32::API::Type ` --M Win32::IPHelper ` -M Win32::TieRegistry ` -M Win32::WinError ` -M Win32API::Registry ` --M Wx ` --M Wx::App ` --M Wx::DND ` --M Wx::DropSource ` --M Wx::Event ` --M Wx::GLCanvas ` --M Wx::Grid ` --M Wx::Html ` --M Wx::Locale ` --M Wx::Menu ` --M Wx::Mini ` --M Wx::Print ` --M Wx::RadioBox ` --M Wx::Timer ` -M XSLoader ` --M attributes ` --M base ` --M bytes ` --M constant ` --M constant ` --M constant::defer ` --M enum ` --M feature ` --M integer ` --M locale ` +-B ` -M lib ` --M mro ` --M overload ` --M overload::numbers ` --M overloading ` --M parent ` --M re ` --M strict ` --M threads ` --M threads::shared ` --M utf8 ` --M vars ` --M warnings ` --M warnings::register ` --e -p ..\slic3r.pl -o ..\slic3r.par +-p ..\slic3r.pl -o ..\${output_file} -copy ..\slic3r.par "..\slic3r-${current_branch}-${APPVEYOR_BUILD_NUMBER}-$(git rev-parse --short HEAD).zip" +# switch renaming based on whether or not using packaged exe or zip +if ($exe) { + if ($env:APPVEYOR) { + copy ..\slic3r.exe "..\slic3r-${current_branch}.${current_date}.${env:APPVEYOR_BUILD_NUMBER}.$(git rev-parse --short HEAD).exe" + del ..\slic3r.exe + } else { + copy ..\slic3r.exe "..\slic3r-${current_branch}.${current_date}.$(git rev-parse --short HEAD).exe" + del ..\slic3r.exe + } +} else { +# make this more useful for not being on the appveyor server + if ($env:APPVEYOR) { + copy ..\slic3r.par "..\slic3r-${current_branch}.${current_date}.${env:APPVEYOR_BUILD_NUMBER}.$(git rev-parse --short HEAD).zip" + } else { + copy ..\slic3r.par "..\slic3r-${current_branch}.${current_date}.$(git rev-parse --short HEAD).zip" + del ../slic3r.par + } +} diff --git a/utils/pdf-slices.pl b/utils/pdf-slices.pl index fa7612bb1..30c41ae45 100755 --- a/utils/pdf-slices.pl +++ b/utils/pdf-slices.pl @@ -7,6 +7,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Getopt::Long qw(:config no_auto_abbrev); diff --git a/utils/send-gcode.pl b/utils/send-gcode.pl index 989b17872..0b803baa6 100644 --- a/utils/send-gcode.pl +++ b/utils/send-gcode.pl @@ -6,6 +6,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; diff --git a/utils/split_stl.pl b/utils/split_stl.pl index 8e7d957a4..e9990b8fc 100755 --- a/utils/split_stl.pl +++ b/utils/split_stl.pl @@ -7,6 +7,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use File::Basename qw(basename); diff --git a/utils/stl-to-amf.pl b/utils/stl-to-amf.pl index e1adec7ce..f4e133ce1 100755 --- a/utils/stl-to-amf.pl +++ b/utils/stl-to-amf.pl @@ -7,6 +7,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use File::Basename qw(basename); diff --git a/utils/view-mesh.pl b/utils/view-mesh.pl index 687a56224..91cd20a29 100644 --- a/utils/view-mesh.pl +++ b/utils/view-mesh.pl @@ -7,10 +7,12 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Getopt::Long qw(:config no_auto_abbrev); use Slic3r; +use Slic3r::Geometry qw(Z); use Slic3r::GUI; use Slic3r::GUI::3DScene; $|++; @@ -39,7 +41,7 @@ my %opt = (); $app->{canvas}->load_object($model, 0); $app->{canvas}->set_auto_bed_shape; $app->{canvas}->zoom_to_volumes; - $app->{canvas}->SetCuttingPlane($opt{cut}) if defined $opt{cut}; + $app->{canvas}->SetCuttingPlane(Z, $opt{cut}) if defined $opt{cut}; $app->MainLoop; } diff --git a/utils/view-toolpaths.pl b/utils/view-toolpaths.pl index 4bbbaab4c..d4c47f072 100755 --- a/utils/view-toolpaths.pl +++ b/utils/view-toolpaths.pl @@ -7,6 +7,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Getopt::Long qw(:config no_auto_abbrev); diff --git a/utils/wireframe.pl b/utils/wireframe.pl index 053581dec..f49b66e56 100644 --- a/utils/wireframe.pl +++ b/utils/wireframe.pl @@ -8,6 +8,7 @@ use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; } use Getopt::Long qw(:config no_auto_abbrev); diff --git a/utils/zsh/functions/_slic3r b/utils/zsh/functions/_slic3r index df1cecb03..a78da948a 100644 --- a/utils/zsh/functions/_slic3r +++ b/utils/zsh/functions/_slic3r @@ -22,7 +22,7 @@ _arguments -S \ '*--nozzle-diameter[specify nozzle diameter]:nozzle diameter in mm' \ '--print-center[specify print center coordinates]:print center coordinates in mm,mm' \ '--z-offset[specify Z-axis offset]:Z-axis offset in mm' \ - '--gcode-flavor[specify the type of G-code to generate]:G-code flavor:(reprap teacup makerware sailfish mach3 machinekit no-extrusion)' \ + '--gcode-flavor[specify the type of G-code to generate]:G-code flavor:(reprap teacup repetier makerware sailfish mach3 machinekit no-extrusion)' \ '(--use-relative-e-distances --no-use-relative-e-distances)'--{no-,}use-relative-e-distances'[disable/enable relative E values]' \ '--extrusion-axis[specify letter associated with the extrusion axis]:extrusion axis letter' \ '(--gcode-arcs --no-gcode-arcs)'--{no-,}gcode-arcs'[disable/enable G2/G3 commands for native arcs]' \ diff --git a/var/arrow_refresh.png b/var/arrow_refresh.png new file mode 100755 index 000000000..0de26566d Binary files /dev/null and b/var/arrow_refresh.png differ diff --git a/var/map_add.png b/var/map_add.png new file mode 100755 index 000000000..2b72da06a Binary files /dev/null and b/var/map_add.png differ diff --git a/xs/Build.PL b/xs/Build.PL index 8a111c1a4..7d2556da8 100644 --- a/xs/Build.PL +++ b/xs/Build.PL @@ -3,10 +3,14 @@ use strict; use warnings; +use Config; use Devel::CheckLib; use ExtUtils::CppGuess; use Module::Build::WithXSpp; +my $cpp_guess = ExtUtils::CppGuess->new; +my $mswin = $^O eq 'MSWin32'; + # _GLIBCXX_USE_C99 : to get the long long type for g++ # HAS_BOOL : stops Perl/lib/CORE/handy.h from doing "# define bool char" for MSVC # NOGDI : prevents inclusion of wingdi.h which defines functions Polygon() and Polyline() in global namespace @@ -16,32 +20,67 @@ my @ldflags = (); if ($^O eq 'darwin') { push @ldflags, qw(-framework IOKit -framework CoreFoundation); } +if ($mswin) { + # In case windows.h is included, we don't want the min / max macros to be active. + # If is included, we want the #defines to be active (M_PI etc.) + push @cflags, qw(-DNOMINMAX -D_USE_MATH_DEFINES); +} my @INC = qw(-Isrc/libslic3r); -my @LIBS = qw(-Lsrc/libslic3r); +my @LIBS = $cpp_guess->is_msvc ? qw(-LIBPATH:src/libslic3r) : qw(-Lsrc/libslic3r); # search for Boost in a number of places -my @boost_include = my @boost_libs = (); -if (defined $ENV{BOOST_DIR}) { - if (-d "$ENV{BOOST_DIR}/include") { - push @boost_include, $ENV{BOOST_DIR} . '/include'; +my @boost_include = (); +if (defined $ENV{BOOST_INCLUDEDIR}) { + push @boost_include, $ENV{BOOST_INCLUDEDIR} +} elsif (defined $ENV{BOOST_DIR}) { + my $subdir = $ENV{BOOST_DIR} . (($mswin == 1) ? '\include' : '/include'); + if (-d $subdir) { + push @boost_include, $subdir; } else { push @boost_include, $ENV{BOOST_DIR}; } - push @boost_libs, $ENV{BOOST_DIR}; } else { - push @boost_include, grep { -d $_ } - qw(/opt/local/include /usr/local/include /opt/include), - qw(/usr/include C:\Boost\include); - push @boost_libs, grep { -d $_ } - qw(/opt/local/lib /usr/local/lib /opt/lib /usr/lib), - qw(C:\Boost\lib /lib); - - if ($^O eq 'MSWin32') { - for my $path (glob('C:\dev\boost*'), glob ('C:\boost*')) { + # Boost library was not defined by the environment. + # Try to guess at some default paths. + if ($mswin) { + for my $path (glob('C:\dev\boost*\include'), glob ('C:\boost*\include')) { push @boost_include, $path; - push @boost_libs, $path . "/stage/lib"; } + if (! @boost_include) { + # No boost\include. Try to include the boost root. + for my $path (glob('C:\dev\boost*'), glob ('C:\boost*')) { + push @boost_include, $path; + } + } + } else { + push @boost_include, grep { -d $_ } + qw(/opt/local/include /usr/local/include /opt/include /usr/include); + } +} + +my @boost_libs = (); +if (defined $ENV{BOOST_LIBRARYDIR}) { + push @boost_libs, $ENV{BOOST_LIBRARYDIR} +} elsif (defined $ENV{BOOST_DIR}) { + my $subdir = $ENV{BOOST_DIR} . ($mswin ? '\stage\lib' : '/stage/lib'); + if (-d $subdir) { + push @boost_libs, $subdir; + } else { + push @boost_libs, $ENV{BOOST_DIR}; + } +} else { + # Boost library was not defined by the environment. + # Try to guess at some default paths. + if ($mswin) { + for my $path ( + glob('C:\dev\boost*\lib'), glob ('C:\boost*\lib'), + glob('C:\dev\boost*\stage\lib'), glob ('C:\boost*\stage\lib')) { + push @boost_libs, $path; + } + } else { + push @boost_libs, grep { -d $_ } + qw(/opt/local/lib /usr/local/lib /opt/lib /usr/lib /lib); } } @@ -50,29 +89,44 @@ my $have_boost = 0; my @boost_libraries = qw(system thread filesystem); # we need these # check without explicit lib path (works on Linux) -$have_boost = 1 - if check_lib( - lib => [ map "boost_${_}", @boost_libraries ], - ); +if (! $mswin) { + $have_boost = 1 + if check_lib( + lib => [ map "boost_${_}", @boost_libraries ], + ); +} if (!$ENV{SLIC3R_STATIC} && $have_boost) { + # The boost library was detected by check_lib on Linux. push @LIBS, map "-lboost_${_}", @boost_libraries; } else { - foreach my $path (@boost_libs) { - my @files = glob "$path/libboost_system*"; + # Either static linking, or check_lib could not be used to find the boost libraries. + my $lib_prefix = 'libboost_'; + my $lib_ext = $Config{lib_ext}; + PATH: foreach my $path (@boost_libs) { + # Try to find the boost system library. + my @files = glob "$path/${lib_prefix}system*$lib_ext"; next if !@files; - if ($files[0] =~ /libboost_system([^.]+)/) { + if ($files[0] =~ /${lib_prefix}system([^.]+)$lib_ext$/) { + # Suffix contains the version number, the build type etc. my $suffix = $1; - check_lib( - lib => [ map "boost_${_}${suffix}", @boost_libraries ], - INC => join(' ', map "-I$_", @INC, @boost_include), - LIBS => "-L$path", - ) or next; - + # Verify existence of all required boost libraries at $path. + for my $lib (map "${lib_prefix}${_}${suffix}${lib_ext}", @boost_libraries) { + # If the library file does not exist, try next library path. + -f "$path/$lib" or next PATH; + } + if (! $cpp_guess->is_msvc) { + # Test the correctness of boost libraries by linking them to a minimal C program. + check_lib( + lib => [ map "boost_${_}${suffix}", @boost_libraries ], + INC => join(' ', map "-I$_", @INC, @boost_include), + LIBS => "-L$path", + ) or next; + } push @INC, (map " -I$_", @boost_include); # TODO: only use the one related to the chosen lib path - if ($ENV{SLIC3R_STATIC}) { - push @LIBS, map "$path/libboost_$_$suffix.a", @boost_libraries; + if ($ENV{SLIC3R_STATIC} || $cpp_guess->is_msvc) { + push @LIBS, map "${path}/${lib_prefix}$_${suffix}${lib_ext}", @boost_libraries; } else { push @LIBS, " -L$path", (map " -lboost_$_$suffix", @boost_libraries); } @@ -91,13 +145,20 @@ path through the BOOST_DIR environment variable: BOOST_DIR=/path/to/boost perl Build.PL +Or you may specify BOOST_INCLUDEPATH and BOOST_LIBRARYPATH separatly, which +is handy, if you have built Boost libraries with mutliple settings. + EOF if ($ENV{SLIC3R_DEBUG}) { # only on newer GCCs: -ftemplate-backtrace-limit=0 - push @cflags, qw(-DSLIC3R_DEBUG -g); + push @cflags, '-DSLIC3R_DEBUG'; + push @cflags, $cpp_guess->is_msvc ? '-Gd' : '-g'; +} else { + # Disable asserts in the release builds. + push @cflags, '-DNDEBUG'; } -if (ExtUtils::CppGuess->new->is_gcc) { +if ($cpp_guess->is_gcc) { # check whether we're dealing with a buggy GCC version # see https://github.com/alexrj/Slic3r/issues/1965 if (`cc --version` =~ / 4\.7\.[012]/) { diff --git a/xs/MANIFEST b/xs/MANIFEST index fcc323bb9..a56a8a1d2 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -28,10 +28,24 @@ src/libslic3r/ExtrusionEntity.cpp src/libslic3r/ExtrusionEntity.hpp src/libslic3r/ExtrusionEntityCollection.cpp src/libslic3r/ExtrusionEntityCollection.hpp +src/libslic3r/Fill/Fill.cpp +src/libslic3r/Fill/Fill.hpp +src/libslic3r/Fill/FillConcentric.cpp +src/libslic3r/Fill/FillConcentric.hpp +src/libslic3r/Fill/FillHoneycomb.cpp +src/libslic3r/Fill/FillHoneycomb.hpp +src/libslic3r/Fill/Fill3DHoneycomb.cpp +src/libslic3r/Fill/Fill3DHoneycomb.hpp +src/libslic3r/Fill/FillPlanePath.cpp +src/libslic3r/Fill/FillPlanePath.hpp +src/libslic3r/Fill/FillRectilinear.cpp +src/libslic3r/Fill/FillRectilinear.hpp src/libslic3r/Flow.cpp src/libslic3r/Flow.hpp src/libslic3r/GCode.cpp src/libslic3r/GCode.hpp +src/libslic3r/GCode/CoolingBuffer.cpp +src/libslic3r/GCode/CoolingBuffer.hpp src/libslic3r/GCodeSender.cpp src/libslic3r/GCodeSender.hpp src/libslic3r/GCodeWriter.cpp @@ -43,6 +57,7 @@ src/libslic3r/IO.hpp src/libslic3r/Layer.cpp src/libslic3r/Layer.hpp src/libslic3r/LayerRegion.cpp +src/libslic3r/LayerRegionFill.cpp src/libslic3r/LayerHeightSpline.hpp src/libslic3r/LayerHeightSpline.cpp src/libslic3r/libslic3r.h @@ -72,6 +87,8 @@ src/libslic3r/PrintConfig.cpp src/libslic3r/PrintConfig.hpp src/libslic3r/PrintObject.cpp src/libslic3r/PrintRegion.cpp +src/libslic3r/SLAPrint.cpp +src/libslic3r/SLAPrint.hpp src/libslic3r/SupportMaterial.hpp src/libslic3r/Surface.cpp src/libslic3r/Surface.hpp @@ -123,6 +140,9 @@ t/18_motionplanner.t t/19_model.t t/20_print.t t/21_gcode.t +t/22_exception.t +t/23_config.t +t/inc/22_config_bad_config_options.ini xsp/BoundingBox.xsp xsp/BridgeDetector.xsp xsp/Clipper.xsp @@ -133,6 +153,7 @@ xsp/Extruder.xsp xsp/ExtrusionEntityCollection.xsp xsp/ExtrusionLoop.xsp xsp/ExtrusionPath.xsp +xsp/Filler.xsp xsp/Flow.xsp xsp/GCode.xsp xsp/GCodeSender.xsp @@ -154,6 +175,7 @@ xsp/Polygon.xsp xsp/Polyline.xsp xsp/PolylineCollection.xsp xsp/Print.xsp +xsp/SLAPrint.xsp xsp/SupportMaterial.xsp xsp/Surface.xsp xsp/SurfaceCollection.xsp diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index e0c0268a9..be24b2de7 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -123,6 +123,17 @@ sub clone { ); } +package Slic3r::Filler; + +sub fill_surface { + my ($self, $surface, %args) = @_; + $self->set_density($args{density}) if defined($args{density}); + $self->set_dont_connect($args{dont_connect}) if defined($args{dont_connect}); + $self->set_dont_adjust($args{dont_adjust}) if defined($args{dont_adjust}); + $self->set_complete($args{complete}) if defined($args{complete}); + return $self->_fill_surface($surface); +} + package Slic3r::Flow; sub new { @@ -215,6 +226,7 @@ for my $class (qw( Slic3r::ExtrusionLoop Slic3r::ExtrusionPath Slic3r::ExtrusionPath::Collection + Slic3r::Filler Slic3r::Flow Slic3r::GCode Slic3r::GCode::AvoidCrossingPerimeters diff --git a/xs/src/admesh/connect.c b/xs/src/admesh/connect.c index 6ce775f9f..56ebfa144 100644 --- a/xs/src/admesh/connect.c +++ b/xs/src/admesh/connect.c @@ -106,53 +106,42 @@ stl_check_facets_exact(stl_file *stl) { } } stl_free_edges(stl); + +#if 0 + printf("Number of faces: %d, number of manifold edges: %d, number of connected edges: %d, number of unconnected edges: %d\r\n", + stl->stats.number_of_facets, stl->stats.number_of_facets * 3, + stl->stats.connected_edges, stl->stats.number_of_facets * 3 - stl->stats.connected_edges); +#endif } static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, stl_vertex *a, stl_vertex *b) { - float diff_x; - float diff_y; - float diff_z; - float max_diff; - if (stl->error) return; - diff_x = ABS(a->x - b->x); - diff_y = ABS(a->y - b->y); - diff_z = ABS(a->z - b->z); - max_diff = STL_MAX(diff_x, diff_y); - max_diff = STL_MAX(diff_z, max_diff); - stl->stats.shortest_edge = STL_MIN(max_diff, stl->stats.shortest_edge); + { + float diff_x = ABS(a->x - b->x); + float diff_y = ABS(a->y - b->y); + float diff_z = ABS(a->z - b->z); + float max_diff = STL_MAX(diff_x, diff_y); + max_diff = STL_MAX(diff_z, max_diff); + stl->stats.shortest_edge = STL_MIN(max_diff, stl->stats.shortest_edge); + } - if(diff_x == max_diff) { - if(a->x > b->x) { - memcpy(&edge->key[0], a, sizeof(stl_vertex)); - memcpy(&edge->key[3], b, sizeof(stl_vertex)); - } else { - memcpy(&edge->key[0], b, sizeof(stl_vertex)); - memcpy(&edge->key[3], a, sizeof(stl_vertex)); - edge->which_edge += 3; /* this edge is loaded backwards */ - } - } else if(diff_y == max_diff) { - if(a->y > b->y) { - memcpy(&edge->key[0], a, sizeof(stl_vertex)); - memcpy(&edge->key[3], b, sizeof(stl_vertex)); - } else { - memcpy(&edge->key[0], b, sizeof(stl_vertex)); - memcpy(&edge->key[3], a, sizeof(stl_vertex)); - edge->which_edge += 3; /* this edge is loaded backwards */ - } + // Ensure identical vertex ordering of equal edges. + // This method is numerically robust. + if ((a->x != b->x) ? + (a->x < b->x) : + ((a->y != b->y) ? + (a->y < b->y) : + (a->z < b->z))) { + memcpy(&edge->key[0], a, sizeof(stl_vertex)); + memcpy(&edge->key[3], b, sizeof(stl_vertex)); } else { - if(a->z > b->z) { - memcpy(&edge->key[0], a, sizeof(stl_vertex)); - memcpy(&edge->key[3], b, sizeof(stl_vertex)); - } else { - memcpy(&edge->key[0], b, sizeof(stl_vertex)); - memcpy(&edge->key[3], a, sizeof(stl_vertex)); - edge->which_edge += 3; /* this edge is loaded backwards */ - } + memcpy(&edge->key[0], b, sizeof(stl_vertex)); + memcpy(&edge->key[3], a, sizeof(stl_vertex)); + edge->which_edge += 3; /* this edge is loaded backwards */ } } @@ -309,26 +298,17 @@ stl_check_facets_nearby(stl_file *stl, float tolerance) { static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, stl_vertex *a, stl_vertex *b, float tolerance) { - float diff_x; - float diff_y; - float diff_z; - float max_diff; - unsigned vertex1[3]; - unsigned vertex2[3]; - - - diff_x = ABS(a->x - b->x); - diff_y = ABS(a->y - b->y); - diff_z = ABS(a->z - b->z); - max_diff = STL_MAX(diff_x, diff_y); - max_diff = STL_MAX(diff_z, max_diff); - - vertex1[0] = (unsigned)((a->x - stl->stats.min.x) / tolerance); - vertex1[1] = (unsigned)((a->y - stl->stats.min.y) / tolerance); - vertex1[2] = (unsigned)((a->z - stl->stats.min.z) / tolerance); - vertex2[0] = (unsigned)((b->x - stl->stats.min.x) / tolerance); - vertex2[1] = (unsigned)((b->y - stl->stats.min.y) / tolerance); - vertex2[2] = (unsigned)((b->z - stl->stats.min.z) / tolerance); + // Index of a grid cell spaced by tolerance. + uint32_t vertex1[3] = { + (uint32_t)((a->x - stl->stats.min.x) / tolerance), + (uint32_t)((a->y - stl->stats.min.y) / tolerance), + (uint32_t)((a->z - stl->stats.min.z) / tolerance) + }; + uint32_t vertex2[3] = { + (uint32_t)((b->x - stl->stats.min.x) / tolerance), + (uint32_t)((b->y - stl->stats.min.y) / tolerance), + (uint32_t)((b->z - stl->stats.min.z) / tolerance) + }; if( (vertex1[0] == vertex2[0]) && (vertex1[1] == vertex2[1]) @@ -337,33 +317,19 @@ stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, return 0; } - if(diff_x == max_diff) { - if(a->x > b->x) { - memcpy(&edge->key[0], vertex1, sizeof(stl_vertex)); - memcpy(&edge->key[3], vertex2, sizeof(stl_vertex)); - } else { - memcpy(&edge->key[0], vertex2, sizeof(stl_vertex)); - memcpy(&edge->key[3], vertex1, sizeof(stl_vertex)); - edge->which_edge += 3; /* this edge is loaded backwards */ - } - } else if(diff_y == max_diff) { - if(a->y > b->y) { - memcpy(&edge->key[0], vertex1, sizeof(stl_vertex)); - memcpy(&edge->key[3], vertex2, sizeof(stl_vertex)); - } else { - memcpy(&edge->key[0], vertex2, sizeof(stl_vertex)); - memcpy(&edge->key[3], vertex1, sizeof(stl_vertex)); - edge->which_edge += 3; /* this edge is loaded backwards */ - } + // Ensure identical vertex ordering of edges, which vertices land into equal grid cells. + // This method is numerically robust. + if ((vertex1[0] != vertex2[0]) ? + (vertex1[0] < vertex2[0]) : + ((vertex1[1] != vertex2[1]) ? + (vertex1[1] < vertex2[1]) : + (vertex1[2] < vertex2[2]))) { + memcpy(&edge->key[0], vertex1, sizeof(stl_vertex)); + memcpy(&edge->key[3], vertex2, sizeof(stl_vertex)); } else { - if(a->z > b->z) { - memcpy(&edge->key[0], vertex1, sizeof(stl_vertex)); - memcpy(&edge->key[3], vertex2, sizeof(stl_vertex)); - } else { - memcpy(&edge->key[0], vertex2, sizeof(stl_vertex)); - memcpy(&edge->key[3], vertex1, sizeof(stl_vertex)); - edge->which_edge += 3; /* this edge is loaded backwards */ - } + memcpy(&edge->key[0], vertex2, sizeof(stl_vertex)); + memcpy(&edge->key[3], vertex1, sizeof(stl_vertex)); + edge->which_edge += 3; /* this edge is loaded backwards */ } return 1; } @@ -564,6 +530,33 @@ stl_change_vertices(stl_file *stl, int facet_num, int vnot, next_edge = pivot_vertex; } } +#if 0 + if (stl->facet_start[facet_num].vertex[pivot_vertex].x == new_vertex.x && + stl->facet_start[facet_num].vertex[pivot_vertex].y == new_vertex.y && + stl->facet_start[facet_num].vertex[pivot_vertex].z == new_vertex.z) + printf("Changing vertex %f,%f,%f: Same !!!\r\n", + new_vertex.x, new_vertex.y, new_vertex.z); + else { + if (stl->facet_start[facet_num].vertex[pivot_vertex].x != new_vertex.x) + printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", + stl->facet_start[facet_num].vertex[pivot_vertex].x, + *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex].x), + new_vertex.x, + *reinterpret_cast(&new_vertex.x)); + if (stl->facet_start[facet_num].vertex[pivot_vertex].y != new_vertex.y) + printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", + stl->facet_start[facet_num].vertex[pivot_vertex].y, + *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex].y), + new_vertex.y, + *reinterpret_cast(&new_vertex.y)); + if (stl->facet_start[facet_num].vertex[pivot_vertex].z != new_vertex.z) + printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", + stl->facet_start[facet_num].vertex[pivot_vertex].z, + *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex].z), + new_vertex.z, + *reinterpret_cast(&new_vertex.z)); + } +#endif stl->facet_start[facet_num].vertex[pivot_vertex] = new_vertex; vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge]; facet_num = stl->neighbors_start[facet_num].neighbor[next_edge]; diff --git a/xs/src/admesh/stl.h b/xs/src/admesh/stl.h index b2be5830e..7871c0351 100644 --- a/xs/src/admesh/stl.h +++ b/xs/src/admesh/stl.h @@ -24,6 +24,13 @@ #define __admesh_stl__ #include +#include +#include +#include + +#ifndef BOOST_LITTLE_ENDIAN +#error "admesh works correctly on little endian machines only!" +#endif #ifdef __cplusplus extern "C" { @@ -33,11 +40,15 @@ extern "C" { #define STL_MIN(A,B) ((A)<(B)? (A):(B)) #define ABS(X) ((X) < 0 ? -(X) : (X)) +// Size of the binary STL header, free form. #define LABEL_SIZE 80 +// Binary STL, length of the "number of faces" counter. #define NUM_FACET_SIZE 4 +// Binary STL, sizeof header + number of faces. #define HEADER_SIZE 84 #define STL_MIN_FILE_SIZE 284 #define ASCII_LINES_PER_FACET 7 +// Comparing an edge by memcmp, 2x3x4 bytes = 24 #define SIZEOF_EDGE_SORT 24 typedef struct { @@ -46,11 +57,11 @@ typedef struct { float z; } stl_vertex; -typedef struct { - float x; - float y; - float z; -} stl_normal; +#ifdef static_assert +static_assert(sizeof(stl_vertex) == 12, "size of stl_vertex incorrect"); +#endif + +typedef stl_vertex stl_normal; typedef char stl_extra[2]; @@ -61,6 +72,13 @@ typedef struct { } stl_facet; #define SIZEOF_STL_FACET 50 +#ifdef static_assert +static_assert(offsetof(stl_facet, normal) == 0, "stl_facet.normal has correct offset"); +static_assert(offsetof(stl_facet, vertex) == 12, "stl_facet.vertex has correct offset"); +static_assert(offsetof(stl_facet, extra ) == 48, "stl_facet.extra has correct offset"); +static_assert(sizeof(stl_facet) >= SIZEOF_STL_FACET, "size of stl_facet incorrect"); +#endif + typedef enum {binary, ascii, inmemory} stl_type; typedef struct { @@ -70,14 +88,24 @@ typedef struct { } stl_edge; typedef struct stl_hash_edge { - unsigned key[6]; + // Key of a hash edge: 2x binary copy of a floating point vertex. + uint32_t key[6]; + // Index of a facet owning this edge. int facet_number; + // Index of this edge inside the facet with an index of facet_number. + // If this edge is stored backwards, which_edge is increased by 3. int which_edge; struct stl_hash_edge *next; } stl_hash_edge; +#ifdef static_assert +static_assert(offsetof(stl_hash_edge, facet_number) == SIZEOF_EDGE_SORT, "size of stl_hash_edge.key incorrect"); +#endif + typedef struct { + // Index of a neighbor facet. int neighbor[3]; + // Index of an opposite vertex at the neighbor face. char which_vertex_not[3]; } stl_neighbors; diff --git a/xs/src/admesh/stl_io.c b/xs/src/admesh/stl_io.c index e015cee99..7d8e4eab8 100644 --- a/xs/src/admesh/stl_io.c +++ b/xs/src/admesh/stl_io.c @@ -203,63 +203,6 @@ stl_print_neighbors(stl_file *stl, char *file) { fclose(fp); } -void -stl_put_little_int(FILE *fp, int value_in) { - int new_value; - union { - int int_value; - char char_value[4]; - } value; - - value.int_value = value_in; - - new_value = value.char_value[0] & 0xFF; - new_value |= (value.char_value[1] & 0xFF) << 0x08; - new_value |= (value.char_value[2] & 0xFF) << 0x10; - new_value |= (value.char_value[3] & 0xFF) << 0x18; - fwrite(&new_value, sizeof(int), 1, fp); -} - -void -stl_put_little_float(FILE *fp, float value_in) { - int new_value; - union { - float float_value; - char char_value[4]; - } value; - - value.float_value = value_in; - - new_value = value.char_value[0] & 0xFF; - new_value |= (value.char_value[1] & 0xFF) << 0x08; - new_value |= (value.char_value[2] & 0xFF) << 0x10; - new_value |= (value.char_value[3] & 0xFF) << 0x18; - fwrite(&new_value, sizeof(int), 1, fp); -} - -void -stl_write_binary_block(stl_file *stl, FILE *fp) -{ - int i; - for(i = 0; i < stl->stats.number_of_facets; i++) - { - stl_put_little_float(fp, stl->facet_start[i].normal.x); - stl_put_little_float(fp, stl->facet_start[i].normal.y); - stl_put_little_float(fp, stl->facet_start[i].normal.z); - stl_put_little_float(fp, stl->facet_start[i].vertex[0].x); - stl_put_little_float(fp, stl->facet_start[i].vertex[0].y); - stl_put_little_float(fp, stl->facet_start[i].vertex[0].z); - stl_put_little_float(fp, stl->facet_start[i].vertex[1].x); - stl_put_little_float(fp, stl->facet_start[i].vertex[1].y); - stl_put_little_float(fp, stl->facet_start[i].vertex[1].z); - stl_put_little_float(fp, stl->facet_start[i].vertex[2].x); - stl_put_little_float(fp, stl->facet_start[i].vertex[2].y); - stl_put_little_float(fp, stl->facet_start[i].vertex[2].z); - fputc(stl->facet_start[i].extra[0], fp); - fputc(stl->facet_start[i].extra[1], fp); - } -} - void stl_write_binary(stl_file *stl, const char *file, const char *label) { FILE *fp; @@ -285,11 +228,9 @@ stl_write_binary(stl_file *stl, const char *file, const char *label) { for(i = strlen(label); i < LABEL_SIZE; i++) putc(0, fp); fseek(fp, LABEL_SIZE, SEEK_SET); - - stl_put_little_int(fp, stl->stats.number_of_facets); - - stl_write_binary_block(stl, fp); - + fwrite(&stl->stats.number_of_facets, 4, 1, fp); + for(i = 0; i < stl->stats.number_of_facets; i++) + fwrite(stl->facet_start + i, SIZEOF_STL_FACET, 1, fp); fclose(fp); } diff --git a/xs/src/admesh/stlinit.c b/xs/src/admesh/stlinit.c index 58f6947c7..d7a436665 100644 --- a/xs/src/admesh/stlinit.c +++ b/xs/src/admesh/stlinit.c @@ -20,6 +20,7 @@ * https://github.com/admesh/admesh/issues */ +#include #include #include #include @@ -27,10 +28,8 @@ #include "stl.h" -#if !defined(SEEK_SET) -#define SEEK_SET 0 -#define SEEK_CUR 1 -#define SEEK_END 2 +#ifndef SEEK_SET +#error "SEEK_SET not defined" #endif void @@ -55,6 +54,8 @@ stl_initialize(stl_file *stl) { stl->stats.number_of_parts = 0; stl->stats.original_num_facets = 0; stl->stats.number_of_facets = 0; + stl->stats.bounding_diameter = 0; + stl->stats.shortest_edge = FLT_MAX; stl->stats.facets_malloced = 0; stl->stats.volume = -1.0; @@ -277,10 +278,7 @@ stl_read(stl_file *stl, int first_facet, int first) { /* Read a single facet from a binary .STL file */ { /* we assume little-endian architecture! */ - if (fread(&facet.normal, sizeof(stl_normal), 1, stl->fp) \ - + fread(&facet.vertex, sizeof(stl_vertex), 3, stl->fp) \ - + fread(&facet.extra, sizeof(char), 2, stl->fp) != 6) { - perror("Cannot read facet"); + if (fread(&facet, 1, SIZEOF_STL_FACET, stl->fp) != SIZEOF_STL_FACET) { stl->error = 1; return; } @@ -304,9 +302,47 @@ stl_read(stl_file *stl, int first_facet, int first) { return; } } - /* Write the facet into memory. */ - stl->facet_start[i] = facet; +#if 0 + // Report close to zero vertex coordinates. Due to the nature of the floating point numbers, + // close to zero values may be represented with singificantly higher precision than the rest of the vertices. + // It may be worth to round these numbers to zero during loading to reduce the number of errors reported + // during the STL import. + for (size_t j = 0; j < 3; ++ j) { + if (facet.vertex[j].x > -1e-12f && facet.vertex[j].x < 1e-12f) + printf("stl_read: facet %d.x = %e\r\n", j, facet.vertex[j].x); + if (facet.vertex[j].y > -1e-12f && facet.vertex[j].y < 1e-12f) + printf("stl_read: facet %d.y = %e\r\n", j, facet.vertex[j].y); + if (facet.vertex[j].z > -1e-12f && facet.vertex[j].z < 1e-12f) + printf("stl_read: facet %d.z = %e\r\n", j, facet.vertex[j].z); + } +#endif + +#if 1 + { + // Positive and negative zeros are possible in the floats, which are considered equal by the FP unit. + // When using a memcmp on raw floats, those numbers report to be different. + // Unify all +0 and -0 to +0 to make the floats equal under memcmp. + uint32_t *f = (uint32_t*)&facet; + int j; + for (j = 0; j < 12; ++ j, ++ f) // 3x vertex + normal: 4x3 = 12 floats + if (*f == 0x80000000) + // Negative zero, switch to positive zero. + *f = 0; + } +#else + { + // Due to the nature of the floating point numbers, close to zero values may be represented with singificantly higher precision + // than the rest of the vertices. Round them to zero. + float *f = (float*)&facet; + for (int j = 0; j < 12; ++ j, ++ f) // 3x vertex + normal: 4x3 = 12 floats + if (*f > -1e-12f && *f < 1e-12f) + // Negative zero, switch to positive zero. + *f = 0; + } +#endif + /* Write the facet into memory. */ + memcpy(stl->facet_start+i, &facet, SIZEOF_STL_FACET); stl_facet_stats(stl, facet, first); first = 0; } diff --git a/xs/src/admesh/util.c b/xs/src/admesh/util.c index 15f405468..0ee6b209c 100644 --- a/xs/src/admesh/util.c +++ b/xs/src/admesh/util.c @@ -27,7 +27,7 @@ #include "stl.h" -static void stl_rotate(float *x, float *y, float angle); +static void stl_rotate(float *x, float *y, const double c, const double s); static float get_area(stl_facet *facet); static float get_volume(stl_file *stl); @@ -189,13 +189,16 @@ void stl_rotate_x(stl_file *stl, float angle) { int i; int j; + double radian_angle = (angle / 180.0) * M_PI; + double c = cos(radian_angle); + double s = sin(radian_angle); if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl_rotate(&stl->facet_start[i].vertex[j].y, - &stl->facet_start[i].vertex[j].z, angle); + &stl->facet_start[i].vertex[j].z, c, s); } } stl_get_size(stl); @@ -206,13 +209,16 @@ void stl_rotate_y(stl_file *stl, float angle) { int i; int j; + double radian_angle = (angle / 180.0) * M_PI; + double c = cos(radian_angle); + double s = sin(radian_angle); if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl_rotate(&stl->facet_start[i].vertex[j].z, - &stl->facet_start[i].vertex[j].x, angle); + &stl->facet_start[i].vertex[j].x, c, s); } } stl_get_size(stl); @@ -223,13 +229,16 @@ void stl_rotate_z(stl_file *stl, float angle) { int i; int j; + double radian_angle = (angle / 180.0) * M_PI; + double c = cos(radian_angle); + double s = sin(radian_angle); if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl_rotate(&stl->facet_start[i].vertex[j].x, - &stl->facet_start[i].vertex[j].y, angle); + &stl->facet_start[i].vertex[j].y, c, s); } } stl_get_size(stl); @@ -239,17 +248,11 @@ stl_rotate_z(stl_file *stl, float angle) { static void -stl_rotate(float *x, float *y, float angle) { - double r; - double theta; - double radian_angle; - - radian_angle = (angle / 180.0) * M_PI; - - r = sqrt((*x **x) + (*y **y)); - theta = atan2(*y, *x); - *x = r * cos(theta + radian_angle); - *y = r * sin(theta + radian_angle); +stl_rotate(float *x, float *y, const double c, const double s) { + double xold = *x; + double yold = *y; + *x = c * xold - s * yold; + *y = s * xold + c * yold; } extern void diff --git a/xs/src/clipper.cpp b/xs/src/clipper.cpp old mode 100644 new mode 100755 index bfdcd5d98..a5b8829e1 --- a/xs/src/clipper.cpp +++ b/xs/src/clipper.cpp @@ -1,10 +1,10 @@ /******************************************************************************* * * * Author : Angus Johnson * -* Version : 6.2.9 * -* Date : 16 February 2015 * +* Version : 6.4.1 * +* Date : 5 December 2016 * * Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2015 * +* Copyright : Angus Johnson 2010-2016 * * * * License: * * Use, modification & distribution is subject to Boost Software License Ver 1. * @@ -65,12 +65,11 @@ static int const Skip = -2; //edge that would otherwise close a path struct TEdge { IntPoint Bot; - IntPoint Curr; + IntPoint Curr; //current (updated for every new scanbeam) IntPoint Top; - IntPoint Delta; double Dx; PolyType PolyTyp; - EdgeSide Side; + EdgeSide Side; //side only refers to current side of solution poly int WindDelta; //1 or -1 depending on winding direction int WindCnt; int WindCnt2; //winding count of the opposite polytype @@ -98,6 +97,8 @@ struct LocalMinimum { struct OutPt; +//OutRec: contains a path in the clipping solution. Edges in the AEL will +//carry a pointer to an OutRec when they are part of the clipping solution. struct OutRec { int Idx; bool IsHole; @@ -178,7 +179,7 @@ int PolyTree::Total() const // PolyNode methods ... //------------------------------------------------------------------------------ -PolyNode::PolyNode(): Childs(), Parent(0), Index(0), m_IsOpen(false) +PolyNode::PolyNode(): Parent(0), Index(0), m_IsOpen(false) { } //------------------------------------------------------------------------------ @@ -402,19 +403,25 @@ double Area(const Path &poly) } //------------------------------------------------------------------------------ -double Area(const OutRec &outRec) +double Area(const OutPt *op) { - OutPt *op = outRec.Pts; + const OutPt *startOp = op; if (!op) return 0; double a = 0; do { a += (double)(op->Prev->Pt.X + op->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); op = op->Next; - } while (op != outRec.Pts); + } while (op != startOp); return a * 0.5; } //------------------------------------------------------------------------------ +double Area(const OutRec &outRec) +{ + return Area(outRec.Pts); +} +//------------------------------------------------------------------------------ + bool PointIsVertex(const IntPoint &Pt, OutPt *pp) { OutPt *pp2 = pp; @@ -535,15 +542,17 @@ bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) { #ifndef use_int32 if (UseFullInt64Range) - return Int128Mul(e1.Delta.Y, e2.Delta.X) == Int128Mul(e1.Delta.X, e2.Delta.Y); + return Int128Mul(e1.Top.Y - e1.Bot.Y, e2.Top.X - e2.Bot.X) == + Int128Mul(e1.Top.X - e1.Bot.X, e2.Top.Y - e2.Bot.Y); else #endif - return e1.Delta.Y * e2.Delta.X == e1.Delta.X * e2.Delta.Y; + return (e1.Top.Y - e1.Bot.Y) * (e2.Top.X - e2.Bot.X) == + (e1.Top.X - e1.Bot.X) * (e2.Top.Y - e2.Bot.Y); } //------------------------------------------------------------------------------ -bool SlopesEqual(const IntPoint &pt1, const IntPoint &pt2, - const IntPoint &pt3, bool UseFullInt64Range) +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, bool UseFullInt64Range) { #ifndef use_int32 if (UseFullInt64Range) @@ -554,8 +563,8 @@ bool SlopesEqual(const IntPoint &pt1, const IntPoint &pt2, } //------------------------------------------------------------------------------ -bool SlopesEqual(const IntPoint &pt1, const IntPoint &pt2, - const IntPoint &pt3, const IntPoint &pt4, bool UseFullInt64Range) +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3, const IntPoint pt4, bool UseFullInt64Range) { #ifndef use_int32 if (UseFullInt64Range) @@ -568,11 +577,11 @@ bool SlopesEqual(const IntPoint &pt1, const IntPoint &pt2, inline bool IsHorizontal(TEdge &e) { - return e.Delta.Y == 0; + return e.Dx == HORIZONTAL; } //------------------------------------------------------------------------------ -inline double GetDx(const IntPoint &pt1, const IntPoint &pt2) +inline double GetDx(const IntPoint pt1, const IntPoint pt2) { return (pt1.Y == pt2.Y) ? HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); @@ -581,11 +590,9 @@ inline double GetDx(const IntPoint &pt1, const IntPoint &pt2) inline void SetDx(TEdge &e) { - e.Delta.X = (e.Top.X - e.Bot.X); - e.Delta.Y = (e.Top.Y - e.Bot.Y); - - if (e.Delta.Y == 0) e.Dx = HORIZONTAL; - else e.Dx = (double)(e.Delta.X) / e.Delta.Y; + cInt dy = (e.Top.Y - e.Bot.Y); + if (dy == 0) e.Dx = HORIZONTAL; + else e.Dx = (double)(e.Top.X - e.Bot.X) / dy; } //--------------------------------------------------------------------------- @@ -625,7 +632,7 @@ void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) ip.X = TopX(Edge1, ip.Y); return; } - else if (Edge1.Delta.X == 0) + else if (Edge1.Dx == 0) { ip.X = Edge1.Bot.X; if (IsHorizontal(Edge2)) @@ -636,7 +643,7 @@ void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) ip.Y = Round(ip.X / Edge2.Dx + b2); } } - else if (Edge2.Delta.X == 0) + else if (Edge2.Dx == 0) { ip.X = Edge2.Bot.X; if (IsHorizontal(Edge1)) @@ -803,7 +810,12 @@ bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) p = btmPt2->Next; while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next; double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt)); - return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); + + if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) && + std::min(dx1p, dx1n) == std::min(dx2p, dx2n)) + return Area(btmPt1) > 0; //if otherwise identical use orientation + else + return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); } //------------------------------------------------------------------------------ @@ -845,8 +857,8 @@ OutPt* GetBottomPt(OutPt *pp) } //------------------------------------------------------------------------------ -bool Pt2IsBetweenPt1AndPt3(const IntPoint &pt1, - const IntPoint &pt2, const IntPoint &pt3) +bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, + const IntPoint pt2, const IntPoint pt3) { if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) return false; @@ -886,7 +898,7 @@ void RangeTest(const IntPoint& Pt, bool& useFullRange) if (useFullRange) { if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) - throw "Coordinate outside allowed range"; + throw clipperException("Coordinate outside allowed range"); } else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) { @@ -1185,8 +1197,6 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) locMin.RightBound = E->Prev; leftBoundIsForward = true; //Q.nextInLML = Q.next } - locMin.LeftBound->Side = esLeft; - locMin.RightBound->Side = esRight; if (!Closed) locMin.LeftBound->WindDelta = 0; else if (locMin.LeftBound->Next == locMin.RightBound) @@ -1225,8 +1235,6 @@ void ClipperBase::Clear() DisposeLocalMinimaList(); for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) { - //for each edge array in turn, find the first used edge and - //check for and remove any hiddenPts in each edge in the array. TEdge* edges = m_edges[i]; delete [] edges; } @@ -1242,9 +1250,11 @@ void ClipperBase::Reset() if (m_CurrentLM == m_MinimaList.end()) return; //ie nothing to process std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter()); + m_Scanbeam = ScanbeamList(); //clears/resets priority_queue //reset all edges ... for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm) { + InsertScanbeam(lm->Y); TEdge* e = lm->LeftBound; if (e) { @@ -1261,6 +1271,8 @@ void ClipperBase::Reset() e->OutIdx = Unassigned; } } + m_ActiveEdges = 0; + m_CurrentLM = m_MinimaList.begin(); } //------------------------------------------------------------------------------ @@ -1271,10 +1283,12 @@ void ClipperBase::DisposeLocalMinimaList() } //------------------------------------------------------------------------------ -void ClipperBase::PopLocalMinima() +bool ClipperBase::PopLocalMinima(cInt Y, const LocalMinimum *&locMin) { - if (m_CurrentLM == m_MinimaList.end()) return; + if (m_CurrentLM == m_MinimaList.end() || (*m_CurrentLM).Y != Y) return false; + locMin = &(*m_CurrentLM); ++m_CurrentLM; + return true; } //------------------------------------------------------------------------------ @@ -1293,6 +1307,7 @@ IntRect ClipperBase::GetBounds() result.bottom = lm->LeftBound->Bot.Y; while (lm != m_MinimaList.end()) { + //todo - needs fixing for open paths result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y); TEdge* e = lm->LeftBound; for (;;) { @@ -1315,6 +1330,142 @@ IntRect ClipperBase::GetBounds() } return result; } +//------------------------------------------------------------------------------ + +void ClipperBase::InsertScanbeam(const cInt Y) +{ + m_Scanbeam.push(Y); +} +//------------------------------------------------------------------------------ + +bool ClipperBase::PopScanbeam(cInt &Y) +{ + if (m_Scanbeam.empty()) return false; + Y = m_Scanbeam.top(); + m_Scanbeam.pop(); + while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates. + return true; +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeAllOutRecs(){ + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + DisposeOutRec(i); + m_PolyOuts.clear(); +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeOutRec(PolyOutList::size_type index) +{ + OutRec *outRec = m_PolyOuts[index]; + if (outRec->Pts) DisposeOutPts(outRec->Pts); + delete outRec; + m_PolyOuts[index] = 0; +} +//------------------------------------------------------------------------------ + +void ClipperBase::DeleteFromAEL(TEdge *e) +{ + TEdge* AelPrev = e->PrevInAEL; + TEdge* AelNext = e->NextInAEL; + if (!AelPrev && !AelNext && (e != m_ActiveEdges)) return; //already deleted + if (AelPrev) AelPrev->NextInAEL = AelNext; + else m_ActiveEdges = AelNext; + if (AelNext) AelNext->PrevInAEL = AelPrev; + e->NextInAEL = 0; + e->PrevInAEL = 0; +} +//------------------------------------------------------------------------------ + +OutRec* ClipperBase::CreateOutRec() +{ + OutRec* result = new OutRec; + result->IsHole = false; + result->IsOpen = false; + result->FirstLeft = 0; + result->Pts = 0; + result->BottomPt = 0; + result->PolyNd = 0; + m_PolyOuts.push_back(result); + result->Idx = (int)m_PolyOuts.size() - 1; + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) +{ + //check that one or other edge hasn't already been removed from AEL ... + if (Edge1->NextInAEL == Edge1->PrevInAEL || + Edge2->NextInAEL == Edge2->PrevInAEL) return; + + if (Edge1->NextInAEL == Edge2) + { + TEdge* Next = Edge2->NextInAEL; + if (Next) Next->PrevInAEL = Edge1; + TEdge* Prev = Edge1->PrevInAEL; + if (Prev) Prev->NextInAEL = Edge2; + Edge2->PrevInAEL = Prev; + Edge2->NextInAEL = Edge1; + Edge1->PrevInAEL = Edge2; + Edge1->NextInAEL = Next; + } + else if (Edge2->NextInAEL == Edge1) + { + TEdge* Next = Edge1->NextInAEL; + if (Next) Next->PrevInAEL = Edge2; + TEdge* Prev = Edge2->PrevInAEL; + if (Prev) Prev->NextInAEL = Edge1; + Edge1->PrevInAEL = Prev; + Edge1->NextInAEL = Edge2; + Edge2->PrevInAEL = Edge1; + Edge2->NextInAEL = Next; + } + else + { + TEdge* Next = Edge1->NextInAEL; + TEdge* Prev = Edge1->PrevInAEL; + Edge1->NextInAEL = Edge2->NextInAEL; + if (Edge1->NextInAEL) Edge1->NextInAEL->PrevInAEL = Edge1; + Edge1->PrevInAEL = Edge2->PrevInAEL; + if (Edge1->PrevInAEL) Edge1->PrevInAEL->NextInAEL = Edge1; + Edge2->NextInAEL = Next; + if (Edge2->NextInAEL) Edge2->NextInAEL->PrevInAEL = Edge2; + Edge2->PrevInAEL = Prev; + if (Edge2->PrevInAEL) Edge2->PrevInAEL->NextInAEL = Edge2; + } + + if (!Edge1->PrevInAEL) m_ActiveEdges = Edge1; + else if (!Edge2->PrevInAEL) m_ActiveEdges = Edge2; +} +//------------------------------------------------------------------------------ + +void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e) +{ + if (!e->NextInLML) + throw clipperException("UpdateEdgeIntoAEL: invalid call"); + + e->NextInLML->OutIdx = e->OutIdx; + TEdge* AelPrev = e->PrevInAEL; + TEdge* AelNext = e->NextInAEL; + if (AelPrev) AelPrev->NextInAEL = e->NextInLML; + else m_ActiveEdges = e->NextInLML; + if (AelNext) AelNext->PrevInAEL = e->NextInLML; + e->NextInLML->Side = e->Side; + e->NextInLML->WindDelta = e->WindDelta; + e->NextInLML->WindCnt = e->WindCnt; + e->NextInLML->WindCnt2 = e->WindCnt2; + e = e->NextInLML; + e->Curr = e->Bot; + e->PrevInAEL = AelPrev; + e->NextInAEL = AelNext; + if (!IsHorizontal(*e)) InsertScanbeam(e->Top.Y); +} +//------------------------------------------------------------------------------ + +bool ClipperBase::LocalMinimaPending() +{ + return (m_CurrentLM != m_MinimaList.end()); +} //------------------------------------------------------------------------------ // TClipper methods ... @@ -1322,8 +1473,6 @@ IntRect ClipperBase::GetBounds() Clipper::Clipper(int initOptions) : ClipperBase() //constructor { - m_ActiveEdges = 0; - m_SortedEdges = 0; m_ExecuteLocked = false; m_UseFullRange = false; m_ReverseOutput = ((initOptions & ioReverseSolution) != 0); @@ -1336,12 +1485,6 @@ Clipper::Clipper(int initOptions) : ClipperBase() //constructor } //------------------------------------------------------------------------------ -Clipper::~Clipper() //destructor -{ - Clear(); -} -//------------------------------------------------------------------------------ - #ifdef use_xyz void Clipper::ZFillFunction(ZFillCallback zFillFunc) { @@ -1350,18 +1493,6 @@ void Clipper::ZFillFunction(ZFillCallback zFillFunc) //------------------------------------------------------------------------------ #endif -void Clipper::Reset() -{ - ClipperBase::Reset(); - m_Scanbeam = ScanbeamList(); - m_Maxima = MaximaList(); - m_ActiveEdges = 0; - m_SortedEdges = 0; - for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); ++lm) - InsertScanbeam(lm->Y); -} -//------------------------------------------------------------------------------ - bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType fillType) { return Execute(clipType, solution, fillType, fillType); @@ -1431,19 +1562,26 @@ bool Clipper::ExecuteInternal() bool succeeded = true; try { Reset(); - if (m_CurrentLM == m_MinimaList.end()) return true; - cInt botY = PopScanbeam(); - do { - InsertLocalMinimaIntoAEL(botY); + m_Maxima = MaximaList(); + m_SortedEdges = 0; + + succeeded = true; + cInt botY, topY; + if (!PopScanbeam(botY)) return false; + InsertLocalMinimaIntoAEL(botY); + while (PopScanbeam(topY) || LocalMinimaPending()) + { ProcessHorizontals(); - ClearGhostJoins(); - if (m_Scanbeam.empty()) break; - cInt topY = PopScanbeam(); - succeeded = ProcessIntersections(topY); - if (!succeeded) break; + ClearGhostJoins(); + if (!ProcessIntersections(topY)) + { + succeeded = false; + break; + } ProcessEdgesAtTopOfScanbeam(topY); botY = topY; - } while (!m_Scanbeam.empty() || m_CurrentLM != m_MinimaList.end()); + InsertLocalMinimaIntoAEL(botY); + } } catch(...) { @@ -1483,45 +1621,20 @@ bool Clipper::ExecuteInternal() } //------------------------------------------------------------------------------ -void Clipper::InsertScanbeam(const cInt Y) -{ - m_Scanbeam.push(Y); -} -//------------------------------------------------------------------------------ - -cInt Clipper::PopScanbeam() -{ - const cInt Y = m_Scanbeam.top(); - m_Scanbeam.pop(); - while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { m_Scanbeam.pop(); } // Pop duplicates. - return Y; -} -//------------------------------------------------------------------------------ - -void Clipper::DisposeAllOutRecs(){ - for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) - DisposeOutRec(i); - m_PolyOuts.clear(); -} -//------------------------------------------------------------------------------ - -void Clipper::DisposeOutRec(PolyOutList::size_type index) -{ - OutRec *outRec = m_PolyOuts[index]; - if (outRec->Pts) DisposeOutPts(outRec->Pts); - delete outRec; - m_PolyOuts[index] = 0; -} -//------------------------------------------------------------------------------ - -void Clipper::SetWindingCount(TEdge &edge) const +void Clipper::SetWindingCount(TEdge &edge) { TEdge *e = edge.PrevInAEL; //find the edge of the same polytype that immediately preceeds 'edge' in AEL while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) e = e->PrevInAEL; if (!e) { - edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + if (edge.WindDelta == 0) + { + PolyFillType pft = (edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType); + edge.WindCnt = (pft == pftNegative ? -1 : 1); + } + else + edge.WindCnt = edge.WindDelta; edge.WindCnt2 = 0; e = m_ActiveEdges; //ie get ready to calc WindCnt2 } @@ -1753,13 +1866,16 @@ OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) prevE = e->PrevInAEL; } - if (prevE && prevE->OutIdx >= 0 && - (TopX(*prevE, Pt.Y) == TopX(*e, Pt.Y)) && - SlopesEqual(*e, *prevE, m_UseFullRange) && - (e->WindDelta != 0) && (prevE->WindDelta != 0)) + if (prevE && prevE->OutIdx >= 0) { - OutPt* outPt = AddOutPt(prevE, Pt); - AddJoin(result, outPt, e->Top); + cInt xPrev = TopX(*prevE, Pt.Y); + cInt xE = TopX(*e, Pt.Y); + if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) && + SlopesEqual(IntPoint(xPrev, Pt.Y), prevE->Top, IntPoint(xE, Pt.Y), e->Top, m_UseFullRange)) + { + OutPt* outPt = AddOutPt(prevE, Pt); + AddJoin(result, outPt, e->Top); + } } return result; } @@ -1801,6 +1917,15 @@ void Clipper::AddEdgeToSEL(TEdge *edge) } //------------------------------------------------------------------------------ +bool Clipper::PopEdgeFromSEL(TEdge *&edge) +{ + if (!m_SortedEdges) return false; + edge = m_SortedEdges; + DeleteFromSEL(m_SortedEdges); + return true; +} +//------------------------------------------------------------------------------ + void Clipper::CopyAELToSEL() { TEdge* e = m_ActiveEdges; @@ -1814,7 +1939,7 @@ void Clipper::CopyAELToSEL() } //------------------------------------------------------------------------------ -void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint &OffPt) +void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) { Join* j = new Join; j->OutPt1 = op1; @@ -1840,7 +1965,7 @@ void Clipper::ClearGhostJoins() } //------------------------------------------------------------------------------ -void Clipper::AddGhostJoin(OutPt *op, const IntPoint &OffPt) +void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) { Join* j = new Join; j->OutPt1 = op; @@ -1852,11 +1977,12 @@ void Clipper::AddGhostJoin(OutPt *op, const IntPoint &OffPt) void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) { - while (m_CurrentLM != m_MinimaList.end() && (m_CurrentLM->Y == botY)) + const LocalMinimum *lm; + while (PopLocalMinima(botY, lm)) { - TEdge* lb = m_CurrentLM->LeftBound; - TEdge* rb = m_CurrentLM->RightBound; - PopLocalMinima(); + TEdge* lb = lm->LeftBound; + TEdge* rb = lm->RightBound; + OutPt *Op1 = 0; if (!lb) { @@ -1888,8 +2014,13 @@ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) if (rb) { - if(IsHorizontal(*rb)) AddEdgeToSEL(rb); - else InsertScanbeam( rb->Top.Y ); + if (IsHorizontal(*rb)) + { + AddEdgeToSEL(rb); + if (rb->NextInLML) + InsertScanbeam(rb->NextInLML->Top.Y); + } + else InsertScanbeam( rb->Top.Y ); } if (!lb || !rb) continue; @@ -1911,7 +2042,7 @@ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) if (lb->OutIdx >= 0 && lb->PrevInAEL && lb->PrevInAEL->Curr.X == lb->Bot.X && lb->PrevInAEL->OutIdx >= 0 && - SlopesEqual(*lb->PrevInAEL, *lb, m_UseFullRange) && + SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top, m_UseFullRange) && (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) { OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot); @@ -1922,7 +2053,7 @@ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) { if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 && - SlopesEqual(*rb->PrevInAEL, *rb, m_UseFullRange) && + SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr, rb->Top, m_UseFullRange) && (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) { OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot); @@ -1946,19 +2077,6 @@ void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) } //------------------------------------------------------------------------------ -void Clipper::DeleteFromAEL(TEdge *e) -{ - TEdge* AelPrev = e->PrevInAEL; - TEdge* AelNext = e->NextInAEL; - if( !AelPrev && !AelNext && (e != m_ActiveEdges) ) return; //already deleted - if( AelPrev ) AelPrev->NextInAEL = AelNext; - else m_ActiveEdges = AelNext; - if( AelNext ) AelNext->PrevInAEL = AelPrev; - e->NextInAEL = 0; - e->PrevInAEL = 0; -} -//------------------------------------------------------------------------------ - void Clipper::DeleteFromSEL(TEdge *e) { TEdge* SelPrev = e->PrevInSEL; @@ -2180,21 +2298,29 @@ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) } //------------------------------------------------------------------------------ -void Clipper::SetHoleState(TEdge *e, OutRec *outrec) const +void Clipper::SetHoleState(TEdge *e, OutRec *outrec) { - bool IsHole = false; TEdge *e2 = e->PrevInAEL; + TEdge *eTmp = 0; while (e2) { if (e2->OutIdx >= 0 && e2->WindDelta != 0) { - IsHole = !IsHole; - if (! outrec->FirstLeft) - outrec->FirstLeft = m_PolyOuts[e2->OutIdx]; + if (!eTmp) eTmp = e2; + else if (eTmp->OutIdx == e2->OutIdx) eTmp = 0; } e2 = e2->PrevInAEL; } - if (IsHole) outrec->IsHole = true; + if (!eTmp) + { + outrec->FirstLeft = 0; + outrec->IsHole = false; + } + else + { + outrec->FirstLeft = m_PolyOuts[eTmp->OutIdx]; + outrec->IsHole = !outrec->FirstLeft->IsHole; + } } //------------------------------------------------------------------------------ @@ -2218,7 +2344,7 @@ OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) } //------------------------------------------------------------------------------ -bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2) +bool OutRec1RightOfOutRec2(OutRec* outRec1, OutRec* outRec2) { do { @@ -2238,16 +2364,16 @@ OutRec* Clipper::GetOutRec(int Idx) } //------------------------------------------------------------------------------ -void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) const +void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) { //get the start and ends of both output polygons ... OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; OutRec *holeStateRec; - if (Param1RightOfParam2(outRec1, outRec2)) + if (OutRec1RightOfOutRec2(outRec1, outRec2)) holeStateRec = outRec2; - else if (Param1RightOfParam2(outRec2, outRec1)) + else if (OutRec1RightOfOutRec2(outRec2, outRec1)) holeStateRec = outRec1; else holeStateRec = GetLowermostRec(outRec1, outRec2); @@ -2260,7 +2386,6 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) const OutPt* p2_lft = outRec2->Pts; OutPt* p2_rt = p2_lft->Prev; - EdgeSide Side; //join e2 poly onto e1 poly and delete pointers to e2 ... if( e1->Side == esLeft ) { @@ -2282,7 +2407,6 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) const p1_rt->Next = p2_lft; outRec1->Pts = p2_lft; } - Side = esLeft; } else { if( e2->Side == esRight ) @@ -2301,7 +2425,6 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) const p1_lft->Prev = p2_rt; p2_rt->Next = p1_lft; } - Side = esRight; } outRec1->BottomPt = 0; @@ -2327,7 +2450,7 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) const if( e->OutIdx == ObsoleteIdx ) { e->OutIdx = OKIdx; - e->Side = Side; + e->Side = e1->Side; break; } e = e->NextInAEL; @@ -2337,21 +2460,6 @@ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) const } //------------------------------------------------------------------------------ -OutRec* Clipper::CreateOutRec() -{ - OutRec* result = new OutRec; - result->IsHole = false; - result->IsOpen = false; - result->FirstLeft = 0; - result->Pts = 0; - result->BottomPt = 0; - result->PolyNd = 0; - m_PolyOuts.push_back(result); - result->Idx = (int)m_PolyOuts.size()-1; - return result; -} -//------------------------------------------------------------------------------ - OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) { if( e->OutIdx < 0 ) @@ -2403,13 +2511,9 @@ OutPt* Clipper::GetLastOutPt(TEdge *e) void Clipper::ProcessHorizontals() { - TEdge* horzEdge = m_SortedEdges; - while(horzEdge) - { - DeleteFromSEL(horzEdge); + TEdge* horzEdge; + while (PopEdgeFromSEL(horzEdge)) ProcessHorizontal(horzEdge); - horzEdge = m_SortedEdges; - } } //------------------------------------------------------------------------------ @@ -2433,64 +2537,21 @@ inline bool IsIntermediate(TEdge *e, const cInt Y) TEdge *GetMaximaPair(TEdge *e) { - TEdge* result = 0; if ((e->Next->Top == e->Top) && !e->Next->NextInLML) - result = e->Next; + return e->Next; else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML) - result = e->Prev; - - if (result && (result->OutIdx == Skip || - //result is false if both NextInAEL & PrevInAEL are nil & not horizontal ... - (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) - return 0; - return result; + return e->Prev; + else return 0; } //------------------------------------------------------------------------------ -void Clipper::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) +TEdge *GetMaximaPairEx(TEdge *e) { - //check that one or other edge hasn't already been removed from AEL ... - if (Edge1->NextInAEL == Edge1->PrevInAEL || - Edge2->NextInAEL == Edge2->PrevInAEL) return; - - if( Edge1->NextInAEL == Edge2 ) - { - TEdge* Next = Edge2->NextInAEL; - if( Next ) Next->PrevInAEL = Edge1; - TEdge* Prev = Edge1->PrevInAEL; - if( Prev ) Prev->NextInAEL = Edge2; - Edge2->PrevInAEL = Prev; - Edge2->NextInAEL = Edge1; - Edge1->PrevInAEL = Edge2; - Edge1->NextInAEL = Next; - } - else if( Edge2->NextInAEL == Edge1 ) - { - TEdge* Next = Edge1->NextInAEL; - if( Next ) Next->PrevInAEL = Edge2; - TEdge* Prev = Edge2->PrevInAEL; - if( Prev ) Prev->NextInAEL = Edge1; - Edge1->PrevInAEL = Prev; - Edge1->NextInAEL = Edge2; - Edge2->PrevInAEL = Edge1; - Edge2->NextInAEL = Next; - } - else - { - TEdge* Next = Edge1->NextInAEL; - TEdge* Prev = Edge1->PrevInAEL; - Edge1->NextInAEL = Edge2->NextInAEL; - if( Edge1->NextInAEL ) Edge1->NextInAEL->PrevInAEL = Edge1; - Edge1->PrevInAEL = Edge2->PrevInAEL; - if( Edge1->PrevInAEL ) Edge1->PrevInAEL->NextInAEL = Edge1; - Edge2->NextInAEL = Next; - if( Edge2->NextInAEL ) Edge2->NextInAEL->PrevInAEL = Edge2; - Edge2->PrevInAEL = Prev; - if( Edge2->PrevInAEL ) Edge2->PrevInAEL->NextInAEL = Edge2; - } - - if( !Edge1->PrevInAEL ) m_ActiveEdges = Edge1; - else if( !Edge2->PrevInAEL ) m_ActiveEdges = Edge2; + //as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's horizontal) + TEdge* result = GetMaximaPair(e); + if (result && (result->OutIdx == Skip || + (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) return 0; + return result; } //------------------------------------------------------------------------------ @@ -2576,7 +2637,7 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) { Direction dir; cInt horzLeft, horzRight; - bool IsOpen = (horzEdge->OutIdx >= 0 && m_PolyOuts[horzEdge->OutIdx]->IsOpen); + bool IsOpen = (horzEdge->WindDelta == 0); GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); @@ -2588,20 +2649,20 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) MaximaList::const_iterator maxIt; MaximaList::const_reverse_iterator maxRit; - if (!m_Maxima.empty()) + if (m_Maxima.size() > 0) { //get the first maxima in range (X) ... if (dir == dLeftToRight) { maxIt = m_Maxima.begin(); - while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) ++maxIt; + while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) maxIt++; if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X) maxIt = m_Maxima.end(); } else { maxRit = m_Maxima.rbegin(); - while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) ++maxRit; + while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) maxRit++; if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X) maxRit = m_Maxima.rend(); } @@ -2620,7 +2681,7 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) //this code block inserts extra coords into horizontal edges (in output //polygons) whereever maxima touch these horizontal edges. This helps //'simplifying' polygons (ie if the Simplify property is set). - if (!m_Maxima.empty()) + if (m_Maxima.size() > 0) { if (dir == dLeftToRight) { @@ -2628,7 +2689,7 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) { if (horzEdge->OutIdx >= 0 && !IsOpen) AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y)); - ++maxIt; + maxIt++; } } else @@ -2637,7 +2698,7 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) { if (horzEdge->OutIdx >= 0 && !IsOpen) AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y)); - ++maxRit; + maxRit++; } } }; @@ -2759,29 +2820,6 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) } //------------------------------------------------------------------------------ -void Clipper::UpdateEdgeIntoAEL(TEdge *&e) -{ - if( !e->NextInLML ) throw - clipperException("UpdateEdgeIntoAEL: invalid call"); - - e->NextInLML->OutIdx = e->OutIdx; - TEdge* AelPrev = e->PrevInAEL; - TEdge* AelNext = e->NextInAEL; - if (AelPrev) AelPrev->NextInAEL = e->NextInLML; - else m_ActiveEdges = e->NextInLML; - if (AelNext) AelNext->PrevInAEL = e->NextInLML; - e->NextInLML->Side = e->Side; - e->NextInLML->WindDelta = e->WindDelta; - e->NextInLML->WindCnt = e->WindCnt; - e->NextInLML->WindCnt2 = e->WindCnt2; - e = e->NextInLML; - e->Curr = e->Bot; - e->PrevInAEL = AelPrev; - e->NextInAEL = AelNext; - if (!IsHorizontal(*e)) InsertScanbeam(e->Top.Y); -} -//------------------------------------------------------------------------------ - bool Clipper::ProcessIntersections(const cInt topY) { if( !m_ActiveEdges ) return true; @@ -2839,6 +2877,7 @@ void Clipper::BuildIntersectList(const cInt topY) if(e->Curr.X > eNext->Curr.X) { IntersectPoint(*e, *eNext, Pt); + if (Pt.Y < topY) Pt = IntPoint(TopX(*e, topY), topY); IntersectNode * newNode = new IntersectNode; newNode->Edge1 = e; newNode->Edge2 = eNext; @@ -2913,7 +2952,7 @@ bool Clipper::FixupIntersectionOrder() void Clipper::DoMaxima(TEdge *e) { - TEdge* eMaxPair = GetMaximaPair(e); + TEdge* eMaxPair = GetMaximaPairEx(e); if (!eMaxPair) { if (e->OutIdx >= 0) @@ -2974,7 +3013,7 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) if(IsMaximaEdge) { - TEdge* eMaxPair = GetMaximaPair(e); + TEdge* eMaxPair = GetMaximaPairEx(e); IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair)); } @@ -3046,7 +3085,7 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) if (ePrev && ePrev->Curr.X == e->Bot.X && ePrev->Curr.Y == e->Bot.Y && op && ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && - SlopesEqual(*e, *ePrev, m_UseFullRange) && + SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top, m_UseFullRange) && (e->WindDelta != 0) && (ePrev->WindDelta != 0)) { OutPt* op2 = AddOutPt(ePrev, e->Bot); @@ -3055,7 +3094,7 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) else if (eNext && eNext->Curr.X == e->Bot.X && eNext->Curr.Y == e->Bot.Y && op && eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && - SlopesEqual(*e, *eNext, m_UseFullRange) && + SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top, m_UseFullRange) && (e->WindDelta != 0) && (eNext->WindDelta != 0)) { OutPt* op2 = AddOutPt(eNext, e->Bot); @@ -3323,7 +3362,7 @@ OutPt* DupOutPt(OutPt* outPt, bool InsertAfter) //------------------------------------------------------------------------------ bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, - const IntPoint &Pt, bool DiscardLeft) + const IntPoint Pt, bool DiscardLeft) { Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight); Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight); @@ -3576,15 +3615,14 @@ static OutRec* ParseFirstLeft(OutRec* FirstLeft) } //------------------------------------------------------------------------------ -void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const +void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) { //tests if NewOutRec contains the polygon before reassigning FirstLeft for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { OutRec* outRec = m_PolyOuts[i]; - if (!outRec->Pts || !outRec->FirstLeft) continue; OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); - if (firstLeft == OldOutRec) + if (outRec->Pts && firstLeft == OldOutRec) { if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) outRec->FirstLeft = NewOutRec; @@ -3593,13 +3631,40 @@ void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const } //---------------------------------------------------------------------- -void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) const -{ +void Clipper::FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec) +{ + //A polygon has split into two such that one is now the inner of the other. + //It's possible that these polygons now wrap around other polygons, so check + //every polygon that's also contained by OuterOutRec's FirstLeft container + //(including 0) to see if they've become inner to the new inner polygon ... + OutRec* orfl = OuterOutRec->FirstLeft; + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + { + OutRec* outRec = m_PolyOuts[i]; + + if (!outRec->Pts || outRec == OuterOutRec || outRec == InnerOutRec) + continue; + OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); + if (firstLeft != orfl && firstLeft != InnerOutRec && firstLeft != OuterOutRec) + continue; + if (Poly2ContainsPoly1(outRec->Pts, InnerOutRec->Pts)) + outRec->FirstLeft = InnerOutRec; + else if (Poly2ContainsPoly1(outRec->Pts, OuterOutRec->Pts)) + outRec->FirstLeft = OuterOutRec; + else if (outRec->FirstLeft == InnerOutRec || outRec->FirstLeft == OuterOutRec) + outRec->FirstLeft = orfl; + } +} +//---------------------------------------------------------------------- +void Clipper::FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec) +{ //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { OutRec* outRec = m_PolyOuts[i]; - if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec; + OutRec* firstLeft = ParseFirstLeft(outRec->FirstLeft); + if (outRec->Pts && firstLeft == OldOutRec) + outRec->FirstLeft = NewOutRec; } } //---------------------------------------------------------------------- @@ -3620,8 +3685,8 @@ void Clipper::JoinCommonEdges() //before calling JoinPoints() ... OutRec *holeStateRec; if (outRec1 == outRec2) holeStateRec = outRec1; - else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; - else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; + else if (OutRec1RightOfOutRec2(outRec1, outRec2)) holeStateRec = outRec2; + else if (OutRec1RightOfOutRec2(outRec2, outRec1)) holeStateRec = outRec1; else holeStateRec = GetLowermostRec(outRec1, outRec2); if (!JoinPoints(join, outRec1, outRec2)) continue; @@ -3638,25 +3703,12 @@ void Clipper::JoinCommonEdges() //update all OutRec2.Pts Idx's ... UpdateOutPtIdxs(*outRec2); - //We now need to check every OutRec.FirstLeft pointer. If it points - //to OutRec1 it may need to point to OutRec2 instead ... - if (m_UsingPolyTree) - for (PolyOutList::size_type j = 0; j < m_PolyOuts.size() - 1; j++) - { - OutRec* oRec = m_PolyOuts[j]; - if (!oRec->Pts || ParseFirstLeft(oRec->FirstLeft) != outRec1 || - oRec->IsHole == outRec1->IsHole) continue; - if (Poly2ContainsPoly1(oRec->Pts, join->OutPt2)) - oRec->FirstLeft = outRec2; - } - if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) { - //outRec2 is contained by outRec1 ... + //outRec1 contains outRec2 ... outRec2->IsHole = !outRec1->IsHole; outRec2->FirstLeft = outRec1; - //fixup FirstLeft pointers that may need reassigning to OutRec1 if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) @@ -3664,13 +3716,12 @@ void Clipper::JoinCommonEdges() } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) { - //outRec1 is contained by outRec2 ... + //outRec2 contains outRec1 ... outRec2->IsHole = outRec1->IsHole; outRec1->IsHole = !outRec2->IsHole; outRec2->FirstLeft = outRec1->FirstLeft; outRec1->FirstLeft = outRec2; - //fixup FirstLeft pointers that may need reassigning to OutRec1 if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0)) @@ -3699,8 +3750,7 @@ void Clipper::JoinCommonEdges() outRec1->FirstLeft = outRec2->FirstLeft; outRec2->FirstLeft = outRec1; - //fixup FirstLeft pointers that may need reassigning to OutRec1 - if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); + if (m_UsingPolyTree) FixupFirstLefts3(outRec2, outRec1); } } } @@ -4398,6 +4448,7 @@ void CleanPolygon(Path& poly, double distance) void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance) { + out_polys.resize(in_polys.size()); for (Paths::size_type i = 0; i < in_polys.size(); ++i) CleanPolygon(in_polys[i], out_polys[i], distance); } @@ -4462,7 +4513,7 @@ void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool p } //------------------------------------------------------------------------------ -void TranslatePath(const Path& input, Path& output, const IntPoint& delta) +void TranslatePath(const Path& input, Path& output, const IntPoint delta) { //precondition: input != output output.resize(input.size()); diff --git a/xs/src/clipper.hpp b/xs/src/clipper.hpp old mode 100644 new mode 100755 index c8009e64e..e60873bcc --- a/xs/src/clipper.hpp +++ b/xs/src/clipper.hpp @@ -1,8 +1,8 @@ /******************************************************************************* * * * Author : Angus Johnson * -* Version : 6.2.9 * -* Date : 16 February 2015 * +* Version : 6.4.1 * +* Date : 5 December 2016 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2015 * * * @@ -34,7 +34,7 @@ #ifndef clipper_hpp #define clipper_hpp -#define CLIPPER_VERSION "6.2.6" +#define CLIPPER_VERSION "6.4.1" //use_int32: When enabled 32bit ints are used instead of 64bit ints. This //improve performance but coordinate values are limited to the range +/- 46340 @@ -146,6 +146,7 @@ public: bool IsOpen() const; int ChildCount() const; private: + //PolyNode& operator =(PolyNode& other); unsigned Index; //node index in Parent.Childs bool m_IsOpen; JoinType m_jointype; @@ -159,12 +160,13 @@ private: class PolyTree: public PolyNode { public: - ~PolyTree(){Clear();}; + ~PolyTree(){ Clear(); }; PolyNode* GetFirst() const; void Clear(); int Total() const; private: - PolyNodes AllNodes; + //PolyTree& operator =(PolyTree& other); + PolyNodes AllNodes; friend class Clipper; //to access AllNodes }; @@ -220,20 +222,27 @@ class ClipperBase public: ClipperBase(); virtual ~ClipperBase(); - bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); + virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); virtual void Clear(); IntRect GetBounds(); - bool PreserveCollinear() const {return m_PreserveCollinear;}; + bool PreserveCollinear() {return m_PreserveCollinear;}; void PreserveCollinear(bool value) {m_PreserveCollinear = value;}; protected: void DisposeLocalMinimaList(); TEdge* AddBoundsToLML(TEdge *e, bool IsClosed); - void PopLocalMinima(); virtual void Reset(); TEdge* ProcessBound(TEdge* E, bool IsClockwise); - TEdge* DescendToMin(TEdge *&E); - void AscendToMax(TEdge *&E, bool Appending, bool IsClosed); + void InsertScanbeam(const cInt Y); + bool PopScanbeam(cInt &Y); + bool LocalMinimaPending(); + bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin); + OutRec* CreateOutRec(); + void DisposeAllOutRecs(); + void DisposeOutRec(PolyOutList::size_type index); + void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); + void DeleteFromAEL(TEdge *e); + void UpdateEdgeIntoAEL(TEdge *&e); typedef std::vector MinimaList; MinimaList::iterator m_CurrentLM; @@ -241,8 +250,13 @@ protected: bool m_UseFullRange; EdgeList m_edges; - bool m_PreserveCollinear; - bool m_HasOpenPaths; + bool m_PreserveCollinear; + bool m_HasOpenPaths; + PolyOutList m_PolyOuts; + TEdge *m_ActiveEdges; + + typedef std::priority_queue ScanbeamList; + ScanbeamList m_Scanbeam; }; //------------------------------------------------------------------------------ @@ -250,7 +264,6 @@ class Clipper : public virtual ClipperBase { public: Clipper(int initOptions = 0); - ~Clipper(); bool Execute(ClipType clipType, Paths &solution, PolyFillType fillType = pftEvenOdd); @@ -265,28 +278,23 @@ public: PolyTree &polytree, PolyFillType subjFillType, PolyFillType clipFillType); - bool ReverseSolution() const { return m_ReverseOutput; }; + bool ReverseSolution() { return m_ReverseOutput; }; void ReverseSolution(bool value) {m_ReverseOutput = value;}; - bool StrictlySimple() const {return m_StrictSimple;}; + bool StrictlySimple() {return m_StrictSimple;}; void StrictlySimple(bool value) {m_StrictSimple = value;}; //set the callback function for z value filling on intersections (otherwise Z is 0) #ifdef use_xyz void ZFillFunction(ZFillCallback zFillFunc); #endif protected: - void Reset(); virtual bool ExecuteInternal(); private: - PolyOutList m_PolyOuts; JoinList m_Joins; JoinList m_GhostJoins; IntersectList m_IntersectList; ClipType m_ClipType; - typedef std::priority_queue ScanbeamList; - ScanbeamList m_Scanbeam; typedef std::list MaximaList; MaximaList m_Maxima; - TEdge *m_ActiveEdges; TEdge *m_SortedEdges; bool m_ExecuteLocked; PolyFillType m_ClipFillType; @@ -297,42 +305,35 @@ private: #ifdef use_xyz ZFillCallback m_ZFill; //custom callback #endif - void SetWindingCount(TEdge& edge) const; + void SetWindingCount(TEdge& edge); bool IsEvenOddFillType(const TEdge& edge) const; bool IsEvenOddAltFillType(const TEdge& edge) const; - void InsertScanbeam(const cInt Y); - cInt PopScanbeam(); void InsertLocalMinimaIntoAEL(const cInt botY); void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge); void AddEdgeToSEL(TEdge *edge); + bool PopEdgeFromSEL(TEdge *&edge); void CopyAELToSEL(); void DeleteFromSEL(TEdge *e); - void DeleteFromAEL(TEdge *e); - void UpdateEdgeIntoAEL(TEdge *&e); void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); bool IsContributing(const TEdge& edge) const; bool IsTopHorz(const cInt XPos); - void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); void DoMaxima(TEdge *e); void ProcessHorizontals(); void ProcessHorizontal(TEdge *horzEdge); void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); OutRec* GetOutRec(int idx); - void AppendPolygon(TEdge *e1, TEdge *e2) const; + void AppendPolygon(TEdge *e1, TEdge *e2); void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); - OutRec* CreateOutRec(); OutPt* AddOutPt(TEdge *e, const IntPoint &pt); OutPt* GetLastOutPt(TEdge *e); - void DisposeAllOutRecs(); - void DisposeOutRec(PolyOutList::size_type index); bool ProcessIntersections(const cInt topY); void BuildIntersectList(const cInt topY); void ProcessIntersectList(); void ProcessEdgesAtTopOfScanbeam(const cInt topY); void BuildResult(Paths& polys); void BuildResult2(PolyTree& polytree); - void SetHoleState(TEdge *e, OutRec *outrec) const; + void SetHoleState(TEdge *e, OutRec *outrec); void DisposeIntersectNodes(); bool FixupIntersectionOrder(); void FixupOutPolygon(OutRec &outrec); @@ -340,15 +341,16 @@ private: bool IsHole(TEdge *e); bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); void FixHoleLinkage(OutRec &outrec); - void AddJoin(OutPt *op1, OutPt *op2, const IntPoint &offPt); + void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt); void ClearJoins(); void ClearGhostJoins(); - void AddGhostJoin(OutPt *op, const IntPoint &offPt); + void AddGhostJoin(OutPt *op, const IntPoint offPt); bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2); void JoinCommonEdges(); void DoSimplePolygons(); - void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const; - void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) const; + void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec); + void FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec); + void FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec); #ifdef use_xyz void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2); #endif diff --git a/xs/src/libslic3r/BoundingBox.cpp b/xs/src/libslic3r/BoundingBox.cpp index 3ec2258a9..809f8925c 100644 --- a/xs/src/libslic3r/BoundingBox.cpp +++ b/xs/src/libslic3r/BoundingBox.cpp @@ -68,6 +68,26 @@ BoundingBox::polygon() const return p; } +BoundingBox BoundingBox::rotated(double angle) const +{ + BoundingBox out; + out.merge(this->min.rotated(angle)); + out.merge(this->max.rotated(angle)); + out.merge(Point(this->min.x, this->max.y).rotated(angle)); + out.merge(Point(this->max.x, this->min.y).rotated(angle)); + return out; +} + +BoundingBox BoundingBox::rotated(double angle, const Point ¢er) const +{ + BoundingBox out; + out.merge(this->min.rotated(angle, center)); + out.merge(this->max.rotated(angle, center)); + out.merge(Point(this->min.x, this->max.y).rotated(angle, center)); + out.merge(Point(this->max.x, this->min.y).rotated(angle, center)); + return out; +} + template void BoundingBoxBase::scale(double factor) { diff --git a/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp index 3fd6008a5..2b5120110 100644 --- a/xs/src/libslic3r/BoundingBox.hpp +++ b/xs/src/libslic3r/BoundingBox.hpp @@ -21,6 +21,8 @@ class BoundingBoxBase bool defined; BoundingBoxBase() : defined(false) {}; + BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) : + min(pmin), max(pmax), defined(pmin.x < pmax.x && pmin.y < pmax.y) {} BoundingBoxBase(const std::vector &points); void merge(const PointClass &point); void merge(const std::vector &points); @@ -38,6 +40,9 @@ class BoundingBox3Base : public BoundingBoxBase { public: BoundingBox3Base() : BoundingBoxBase() {}; + BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) : + BoundingBoxBase(pmin, pmax) + { if (pmin.z >= pmax.z) BoundingBoxBase::defined = false; } BoundingBox3Base(const std::vector &points); void merge(const PointClass &point); void merge(const std::vector &points); @@ -53,8 +58,17 @@ class BoundingBox : public BoundingBoxBase public: void polygon(Polygon* polygon) const; Polygon polygon() const; + BoundingBox rotated(double angle) const; + BoundingBox rotated(double angle, const Point ¢er) const; + void rotate(double angle) { + *this = this->rotated(angle); + } + void rotate(double angle, const Point ¢er) { + *this = this->rotated(angle, center); + } BoundingBox() : BoundingBoxBase() {}; + BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase(pmin, pmax) {}; BoundingBox(const Points &points) : BoundingBoxBase(points) {}; BoundingBox(const Lines &lines); }; @@ -66,15 +80,29 @@ class BoundingBox3 : public BoundingBox3Base {}; class BoundingBoxf : public BoundingBoxBase { public: BoundingBoxf() : BoundingBoxBase() {}; + BoundingBoxf(const Pointf &pmin, const Pointf &pmax) : BoundingBoxBase(pmin, pmax) {}; BoundingBoxf(const std::vector &points) : BoundingBoxBase(points) {}; }; class BoundingBoxf3 : public BoundingBox3Base { public: BoundingBoxf3() : BoundingBox3Base() {}; + BoundingBoxf3(const Pointf3 &pmin, const Pointf3 &pmax) : BoundingBox3Base(pmin, pmax) {}; BoundingBoxf3(const std::vector &points) : BoundingBox3Base(points) {}; }; +template +inline bool operator==(const BoundingBoxBase &bb1, const BoundingBoxBase &bb2) +{ + return bb1.min == bb2.min && bb1.max == bb2.max; +} + +template +inline bool operator!=(const BoundingBoxBase &bb1, const BoundingBoxBase &bb2) +{ + return !(bb1 == bb2); +} + } #endif diff --git a/xs/src/libslic3r/BridgeDetector.cpp b/xs/src/libslic3r/BridgeDetector.cpp index 46d6ff1f3..f9061e405 100644 --- a/xs/src/libslic3r/BridgeDetector.cpp +++ b/xs/src/libslic3r/BridgeDetector.cpp @@ -30,12 +30,11 @@ BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonColle { /* outset our bridge by an arbitrary amout; we'll use this outer margin for detecting anchors */ - Polygons grown; - offset((Polygons)this->expolygon, &grown, this->extrusion_width); + Polygons grown = offset(this->expolygon, this->extrusion_width); // detect what edges lie on lower slices by turning bridge contour and holes // into polylines and then clipping them with each lower slice's contour - intersection(grown, this->lower_slices.contours(), &this->_edges); + this->_edges = intersection_pl(grown, this->lower_slices.contours()); #ifdef SLIC3R_DEBUG printf(" bridge has %zu support(s)\n", this->_edges.size()); @@ -43,7 +42,7 @@ BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonColle // detect anchors as intersection between our bridge expolygon and the lower slices // safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some edges - intersection(grown, this->lower_slices, &this->_anchors, true); + this->_anchors = intersection_ex(grown, this->lower_slices, true); /* if (0) { @@ -65,8 +64,7 @@ BridgeDetector::detect_angle() /* Outset the bridge expolygon by half the amount we used for detecting anchors; we'll use this one to clip our test lines and be sure that their endpoints are inside the anchors and not on their contours leading to false negatives. */ - Polygons clip_area; - offset(this->expolygon, &clip_area, +this->extrusion_width/2); + Polygons clip_area = offset(this->expolygon, +this->extrusion_width/2); /* we'll now try several directions using a rudimentary visibility check: bridge in several directions and then sum the length of lines having both @@ -131,8 +129,7 @@ BridgeDetector::detect_angle() for (coord_t y = bb.min.y; y <= bb.max.y; y += line_increment) lines.push_back(Line(Point(bb.min.x, y), Point(bb.max.x, y))); - Lines clipped_lines; - intersection(lines, my_clip_area, &clipped_lines); + Lines clipped_lines = intersection_ln(lines, my_clip_area); // remove any line not having both endpoints within anchors for (size_t i = 0; i < clipped_lines.size(); ++i) { @@ -194,11 +191,11 @@ BridgeDetector::detect_angle() return true; } -void -BridgeDetector::coverage(double angle, Polygons* coverage) const +Polygons +BridgeDetector::coverage(double angle) const { if (angle == -1) angle = this->angle; - if (angle == -1) return; + if (angle == -1) return Polygons(); // Clone our expolygon and rotate it so that we work with vertical lines. ExPolygon expolygon = this->expolygon; @@ -207,8 +204,7 @@ BridgeDetector::coverage(double angle, Polygons* coverage) const /* Outset the bridge expolygon by half the amount we used for detecting anchors; we'll use this one to generate our trapezoids and be sure that their vertices are inside the anchors and not on their contours leading to false negatives. */ - ExPolygons grown; - offset(expolygon, &grown, this->extrusion_width/2.0); + ExPolygons grown = offset_ex(expolygon, this->extrusion_width/2.0); // Compute trapezoids according to a vertical orientation Polygons trapezoids; @@ -226,9 +222,7 @@ BridgeDetector::coverage(double angle, Polygons* coverage) const Polygons covered; for (Polygons::const_iterator trapezoid = trapezoids.begin(); trapezoid != trapezoids.end(); ++trapezoid) { - Lines lines = trapezoid->lines(); - Lines supported; - intersection(lines, anchors, &supported); + Lines supported = intersection_ln(trapezoid->lines(), anchors); // not nice, we need a more robust non-numeric check for (size_t i = 0; i < supported.size(); ++i) { @@ -242,14 +236,13 @@ BridgeDetector::coverage(double angle, Polygons* coverage) const } // merge trapezoids and rotate them back - Polygons _coverage; - union_(covered, &_coverage); + Polygons _coverage = union_(covered); for (Polygons::iterator p = _coverage.begin(); p != _coverage.end(); ++p) p->rotate(-(PI/2.0 - angle), Point(0,0)); // intersect trapezoids with actual bridge area to remove extra margins // and append it to result - intersection(_coverage, this->expolygon, coverage); + return intersection(_coverage, this->expolygon); /* if (0) { @@ -268,22 +261,14 @@ BridgeDetector::coverage(double angle, Polygons* coverage) const */ } -Polygons -BridgeDetector::coverage(double angle) const -{ - Polygons pp; - this->coverage(angle, &pp); - return pp; -} - /* This method returns the bridge edges (as polylines) that are not supported but would allow the entire bridge area to be bridged with detected angle if supported too */ -void -BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const +Polylines +BridgeDetector::unsupported_edges(double angle) const { if (angle == -1) angle = this->angle; - if (angle == -1) return; + if (angle == -1) return Polylines(); // get bridge edges (both contour and holes) Polylines bridge_edges; @@ -293,10 +278,10 @@ BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const } // get unsupported edges - Polygons grown_lower; - offset(this->lower_slices, &grown_lower, +this->extrusion_width); - Polylines _unsupported; - diff(bridge_edges, grown_lower, &_unsupported); + Polylines _unsupported = diff_pl( + bridge_edges, + offset(this->lower_slices, +this->extrusion_width) + ); /* Split into individual segments and filter out edges parallel to the bridging angle TODO: angle tolerance should probably be based on segment length and flow width, @@ -304,13 +289,15 @@ BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const extrusions would be anchored within such length (i.e. a slightly non-parallel bridging direction might still benefit from anchors if long enough) double angle_tolerance = PI / 180.0 * 5.0; */ + Polylines unsupported; for (Polylines::const_iterator polyline = _unsupported.begin(); polyline != _unsupported.end(); ++polyline) { Lines lines = polyline->lines(); for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { if (!Slic3r::Geometry::directions_parallel(line->direction(), angle)) - unsupported->push_back(*line); + unsupported.push_back(*line); } } + return unsupported; /* if (0) { @@ -328,12 +315,4 @@ BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const */ } -Polylines -BridgeDetector::unsupported_edges(double angle) const -{ - Polylines pp; - this->unsupported_edges(angle, &pp); - return pp; -} - } diff --git a/xs/src/libslic3r/BridgeDetector.hpp b/xs/src/libslic3r/BridgeDetector.hpp index 5b1566b27..bbbd26df9 100644 --- a/xs/src/libslic3r/BridgeDetector.hpp +++ b/xs/src/libslic3r/BridgeDetector.hpp @@ -9,22 +9,27 @@ namespace Slic3r { class BridgeDetector { - public: +public: + // The non-grown hole. ExPolygon expolygon; + // Lower slices, all regions. ExPolygonCollection lower_slices; - double extrusion_width; // scaled + // Scaled extrusion width of the infill. + double extrusion_width; + // Angle resolution for the brute force search of the best bridging angle. double resolution; + // The final optimal angle. double angle; BridgeDetector(const ExPolygon &_expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width); bool detect_angle(); - void coverage(double angle, Polygons* coverage) const; Polygons coverage(double angle = -1) const; - void unsupported_edges(double angle, Polylines* unsupported) const; Polylines unsupported_edges(double angle = -1) const; - private: - Polylines _edges; // representing the supporting edges +private: + // Open lines representing the supporting edges. + Polylines _edges; + // Closed polygons representing the supporting areas. ExPolygons _anchors; }; diff --git a/xs/src/libslic3r/ClipperUtils.cpp b/xs/src/libslic3r/ClipperUtils.cpp index 69c8643f3..98ca0e272 100644 --- a/xs/src/libslic3r/ClipperUtils.cpp +++ b/xs/src/libslic3r/ClipperUtils.cpp @@ -5,54 +5,53 @@ namespace Slic3r { //----------------------------------------------------------- // legacy code from Clipper documentation -void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, Slic3r::ExPolygons* expolygons) +void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, ExPolygons* expolygons) { size_t cnt = expolygons->size(); expolygons->resize(cnt + 1); - ClipperPath_to_Slic3rMultiPoint(polynode.Contour, &(*expolygons)[cnt].contour); + (*expolygons)[cnt].contour = ClipperPath_to_Slic3rMultiPoint(polynode.Contour); (*expolygons)[cnt].holes.resize(polynode.ChildCount()); for (int i = 0; i < polynode.ChildCount(); ++i) { - ClipperPath_to_Slic3rMultiPoint(polynode.Childs[i]->Contour, &(*expolygons)[cnt].holes[i]); + (*expolygons)[cnt].holes[i] = ClipperPath_to_Slic3rMultiPoint(polynode.Childs[i]->Contour); //Add outer polygons contained by (nested within) holes ... for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++j) AddOuterPolyNodeToExPolygons(*polynode.Childs[i]->Childs[j], expolygons); } } -void PolyTreeToExPolygons(ClipperLib::PolyTree& polytree, Slic3r::ExPolygons* expolygons) +ExPolygons +PolyTreeToExPolygons(ClipperLib::PolyTree& polytree) { - expolygons->clear(); - for (int i = 0; i < polytree.ChildCount(); ++i) - AddOuterPolyNodeToExPolygons(*polytree.Childs[i], expolygons); + ExPolygons retval; + for (int i = 0; i < polytree.ChildCount(); ++i) + AddOuterPolyNodeToExPolygons(*polytree.Childs[i], &retval); + return retval; } //----------------------------------------------------------- template -void -ClipperPath_to_Slic3rMultiPoint(const ClipperLib::Path &input, T* output) +T +ClipperPath_to_Slic3rMultiPoint(const ClipperLib::Path &input) { - output->points.clear(); - for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit) { - output->points.push_back(Slic3r::Point( (*pit).X, (*pit).Y )); - } + T retval; + for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit) + retval.points.push_back(Point( (*pit).X, (*pit).Y )); + return retval; } -template void ClipperPath_to_Slic3rMultiPoint(const ClipperLib::Path &input, Slic3r::Polygon* output); template -void -ClipperPaths_to_Slic3rMultiPoints(const ClipperLib::Paths &input, T* output) +T +ClipperPaths_to_Slic3rMultiPoints(const ClipperLib::Paths &input) { - output->clear(); - for (ClipperLib::Paths::const_iterator it = input.begin(); it != input.end(); ++it) { - typename T::value_type p; - ClipperPath_to_Slic3rMultiPoint(*it, &p); - output->push_back(p); - } + T retval; + for (ClipperLib::Paths::const_iterator it = input.begin(); it != input.end(); ++it) + retval.push_back(ClipperPath_to_Slic3rMultiPoint(*it)); + return retval; } -void -ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, Slic3r::ExPolygons* output) +ExPolygons +ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input) { // init Clipper ClipperLib::Clipper clipper; @@ -64,29 +63,26 @@ ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, Slic3r::ExPolyg clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); // offset results work with both EvenOdd and NonZero // write to ExPolygons object - output->clear(); - PolyTreeToExPolygons(polytree, output); + return PolyTreeToExPolygons(polytree); } -void -Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input, ClipperLib::Path* output) +ClipperLib::Path +Slic3rMultiPoint_to_ClipperPath(const MultiPoint &input) { - output->clear(); - for (Slic3r::Points::const_iterator pit = input.points.begin(); pit != input.points.end(); ++pit) { - output->push_back(ClipperLib::IntPoint( (*pit).x, (*pit).y )); - } + ClipperLib::Path retval; + for (Points::const_iterator pit = input.points.begin(); pit != input.points.end(); ++pit) + retval.push_back(ClipperLib::IntPoint( (*pit).x, (*pit).y )); + return retval; } template -void -Slic3rMultiPoints_to_ClipperPaths(const T &input, ClipperLib::Paths* output) +ClipperLib::Paths +Slic3rMultiPoints_to_ClipperPaths(const T &input) { - output->clear(); - for (typename T::const_iterator it = input.begin(); it != input.end(); ++it) { - ClipperLib::Path p; - Slic3rMultiPoint_to_ClipperPath(*it, &p); - output->push_back(p); - } + ClipperLib::Paths retval; + for (typename T::const_iterator it = input.begin(); it != input.end(); ++it) + retval.push_back(Slic3rMultiPoint_to_ClipperPath(*it)); + return retval; } void @@ -100,13 +96,12 @@ scaleClipperPolygons(ClipperLib::Paths &polygons, const double scale) } } -void -offset(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta, +ClipperLib::Paths +_offset(const Polygons &polygons, const float delta, double scale, ClipperLib::JoinType joinType, double miterLimit) { // read input - ClipperLib::Paths input; - Slic3rMultiPoints_to_ClipperPaths(polygons, &input); + ClipperLib::Paths input = Slic3rMultiPoints_to_ClipperPaths(polygons); // scale input scaleClipperPolygons(input, scale); @@ -119,40 +114,20 @@ offset(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float co.MiterLimit = miterLimit; } co.AddPaths(input, joinType, ClipperLib::etClosedPolygon); - co.Execute(*retval, (delta*scale)); + ClipperLib::Paths retval; + co.Execute(retval, (delta*scale)); // unscale output - scaleClipperPolygons(*retval, 1/scale); + scaleClipperPolygons(retval, 1/scale); + return retval; } -void -offset(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) -{ - // perform offset - ClipperLib::Paths output; - offset(polygons, &output, delta, scale, joinType, miterLimit); - - // convert into ExPolygons - ClipperPaths_to_Slic3rMultiPoints(output, retval); -} - -Slic3r::Polygons -offset(const Slic3r::Polygons &polygons, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) -{ - Slic3r::Polygons pp; - offset(polygons, &pp, delta, scale, joinType, miterLimit); - return pp; -} - -void -offset(const Slic3r::Polylines &polylines, ClipperLib::Paths* retval, const float delta, +ClipperLib::Paths +_offset(const Polylines &polylines, const float delta, double scale, ClipperLib::JoinType joinType, double miterLimit) { // read input - ClipperLib::Paths input; - Slic3rMultiPoints_to_ClipperPaths(polylines, &input); + ClipperLib::Paths input = Slic3rMultiPoints_to_ClipperPaths(polylines); // scale input scaleClipperPolygons(input, scale); @@ -165,82 +140,78 @@ offset(const Slic3r::Polylines &polylines, ClipperLib::Paths* retval, const floa co.MiterLimit = miterLimit; } co.AddPaths(input, joinType, ClipperLib::etOpenButt); - co.Execute(*retval, (delta*scale)); + ClipperLib::Paths retval; + co.Execute(retval, (delta*scale)); // unscale output - scaleClipperPolygons(*retval, 1/scale); + scaleClipperPolygons(retval, 1/scale); + return retval; } -void -offset(const Slic3r::Polylines &polylines, Slic3r::Polygons* retval, const float delta, +Polygons +offset(const Polygons &polygons, const float delta, double scale, ClipperLib::JoinType joinType, double miterLimit) { // perform offset - ClipperLib::Paths output; - offset(polylines, &output, delta, scale, joinType, miterLimit); + ClipperLib::Paths output = _offset(polygons, delta, scale, joinType, miterLimit); - // convert into ExPolygons - ClipperPaths_to_Slic3rMultiPoints(output, retval); + // convert into Polygons + return ClipperPaths_to_Slic3rMultiPoints(output); } -void -offset(const Slic3r::Surface &surface, Slic3r::Surfaces* retval, const float delta, +Polygons +offset(const Polylines &polylines, const float delta, double scale, ClipperLib::JoinType joinType, double miterLimit) { // perform offset - Slic3r::ExPolygons expp; - offset(surface.expolygon, &expp, delta, scale, joinType, miterLimit); + ClipperLib::Paths output = _offset(polylines, delta, scale, joinType, miterLimit); + + // convert into Polygons + return ClipperPaths_to_Slic3rMultiPoints(output); +} + +Surfaces +offset(const Surface &surface, const float delta, + double scale, ClipperLib::JoinType joinType, double miterLimit) +{ + // perform offset + ExPolygons expp = offset_ex(surface.expolygon, delta, scale, joinType, miterLimit); // clone the input surface for each expolygon we got - retval->clear(); - retval->reserve(expp.size()); + Surfaces retval; + retval.reserve(expp.size()); for (ExPolygons::iterator it = expp.begin(); it != expp.end(); ++it) { Surface s = surface; // clone s.expolygon = *it; - retval->push_back(s); + retval.push_back(s); } + return retval; } -void -offset(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta, +ExPolygons +offset_ex(const Polygons &polygons, const float delta, double scale, ClipperLib::JoinType joinType, double miterLimit) { // perform offset - ClipperLib::Paths output; - offset(polygons, &output, delta, scale, joinType, miterLimit); + ClipperLib::Paths output = _offset(polygons, delta, scale, joinType, miterLimit); // convert into ExPolygons - ClipperPaths_to_Slic3rExPolygons(output, retval); + return ClipperPaths_to_Slic3rExPolygons(output); } -Slic3r::ExPolygons -offset_ex(const Slic3r::Polygons &polygons, const float delta, +ExPolygons +offset_ex(const ExPolygons &expolygons, const float delta, double scale, ClipperLib::JoinType joinType, double miterLimit) { - Slic3r::ExPolygons expp; - offset(polygons, &expp, delta, scale, joinType, miterLimit); - return expp; + return offset_ex(to_polygons(expolygons), delta, scale, joinType, miterLimit); } -Slic3r::ExPolygons -offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) -{ - Slic3r::Polygons pp; - for (Slic3r::ExPolygons::const_iterator ex = expolygons.begin(); ex != expolygons.end(); ++ex) { - Slic3r::Polygons pp2 = *ex; - pp.insert(pp.end(), pp2.begin(), pp2.end()); - } - return offset_ex(pp, delta, scale, joinType, miterLimit); -} - -void -offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta1, - const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) +ClipperLib::Paths +_offset2(const Polygons &polygons, const float delta1, const float delta2, + const double scale, const ClipperLib::JoinType joinType, const double miterLimit) { // read input - ClipperLib::Paths input; - Slic3rMultiPoints_to_ClipperPaths(polygons, &input); + ClipperLib::Paths input = Slic3rMultiPoints_to_ClipperPaths(polygons); // scale input scaleClipperPolygons(input, scale); @@ -261,62 +232,44 @@ offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float // perform second offset co.Clear(); co.AddPaths(output1, joinType, ClipperLib::etClosedPolygon); - co.Execute(*retval, (delta2*scale)); + ClipperLib::Paths retval; + co.Execute(retval, (delta2*scale)); // unscale output - scaleClipperPolygons(*retval, 1/scale); + scaleClipperPolygons(retval, 1/scale); + return retval; } -void -offset2(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta1, - const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) +Polygons +offset2(const Polygons &polygons, const float delta1, const float delta2, + const double scale, const ClipperLib::JoinType joinType, const double miterLimit) { // perform offset - ClipperLib::Paths output; - offset2(polygons, &output, delta1, delta2, scale, joinType, miterLimit); + ClipperLib::Paths output = _offset2(polygons, delta1, delta2, scale, joinType, miterLimit); // convert into ExPolygons - ClipperPaths_to_Slic3rMultiPoints(output, retval); + return ClipperPaths_to_Slic3rMultiPoints(output); } -Slic3r::Polygons -offset2(const Slic3r::Polygons &polygons, const float delta1, - const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) -{ - Slic3r::Polygons pp; - offset2(polygons, &pp, delta1, delta2, scale, joinType, miterLimit); - return pp; -} - -void -offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta1, - const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) +ExPolygons +offset2_ex(const Polygons &polygons, const float delta1, const float delta2, + const double scale, const ClipperLib::JoinType joinType, const double miterLimit) { // perform offset - ClipperLib::Paths output; - offset2(polygons, &output, delta1, delta2, scale, joinType, miterLimit); + ClipperLib::Paths output = _offset2(polygons, delta1, delta2, scale, joinType, miterLimit); // convert into ExPolygons - ClipperPaths_to_Slic3rExPolygons(output, retval); -} - -Slic3r::ExPolygons -offset2_ex(const Slic3r::Polygons &polygons, const float delta1, - const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) -{ - Slic3r::ExPolygons expp; - offset2(polygons, &expp, delta1, delta2, scale, joinType, miterLimit); - return expp; + return ClipperPaths_to_Slic3rExPolygons(output); } template -void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, - const Slic3r::Polygons &clip, T* retval, const ClipperLib::PolyFillType fillType, const bool safety_offset_) +T +_clipper_do(const ClipperLib::ClipType clipType, const Polygons &subject, + const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_) { // read input - ClipperLib::Paths input_subject, input_clip; - Slic3rMultiPoints_to_ClipperPaths(subject, &input_subject); - Slic3rMultiPoints_to_ClipperPaths(clip, &input_clip); + ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); + ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip); // perform safety offset if (safety_offset_) { @@ -333,20 +286,22 @@ void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polygons &su // add polygons clipper.AddPaths(input_subject, ClipperLib::ptSubject, true); - clipper.AddPaths(input_clip, ClipperLib::ptClip, true); + clipper.AddPaths(input_clip, ClipperLib::ptClip, true); // perform operation - clipper.Execute(clipType, *retval, fillType, fillType); + T retval; + clipper.Execute(clipType, retval, fillType, fillType); + return retval; } -void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, - const Slic3r::Polygons &clip, ClipperLib::PolyTree* retval, const ClipperLib::PolyFillType fillType, +ClipperLib::PolyTree +_clipper_do(const ClipperLib::ClipType clipType, const Polylines &subject, + const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_) { // read input - ClipperLib::Paths input_subject, input_clip; - Slic3rMultiPoints_to_ClipperPaths(subject, &input_subject); - Slic3rMultiPoints_to_ClipperPaths(clip, &input_clip); + ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); + ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip); // perform safety offset if (safety_offset_) safety_offset(&input_clip); @@ -360,285 +315,134 @@ void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polylines &s clipper.AddPaths(input_clip, ClipperLib::ptClip, true); // perform operation - clipper.Execute(clipType, *retval, fillType, fillType); + ClipperLib::PolyTree retval; + clipper.Execute(clipType, retval, fillType, fillType); + return retval; } -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, - const Slic3r::Polygons &clip, Slic3r::Polygons* retval, bool safety_offset_) +Polygons +_clipper(ClipperLib::ClipType clipType, const Polygons &subject, + const Polygons &clip, bool safety_offset_) { // perform operation - ClipperLib::Paths output; - _clipper_do(clipType, subject, clip, &output, ClipperLib::pftNonZero, safety_offset_); + ClipperLib::Paths output = _clipper_do(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_); // convert into Polygons - ClipperPaths_to_Slic3rMultiPoints(output, retval); + return ClipperPaths_to_Slic3rMultiPoints(output); } -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, - const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_) +ExPolygons +_clipper_ex(ClipperLib::ClipType clipType, const Polygons &subject, + const Polygons &clip, bool safety_offset_) { // perform operation - ClipperLib::PolyTree polytree; - _clipper_do(clipType, subject, clip, &polytree, ClipperLib::pftNonZero, safety_offset_); + ClipperLib::PolyTree polytree = _clipper_do(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_); // convert into ExPolygons - PolyTreeToExPolygons(polytree, retval); + return PolyTreeToExPolygons(polytree); } -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, - const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_) +Polylines +_clipper_pl(ClipperLib::ClipType clipType, const Polylines &subject, + const Polygons &clip, bool safety_offset_) { // perform operation - ClipperLib::PolyTree polytree; - _clipper_do(clipType, subject, clip, &polytree, ClipperLib::pftNonZero, safety_offset_); + ClipperLib::PolyTree polytree = _clipper_do(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_); // convert into Polylines ClipperLib::Paths output; ClipperLib::PolyTreeToPaths(polytree, output); - ClipperPaths_to_Slic3rMultiPoints(output, retval); + return ClipperPaths_to_Slic3rMultiPoints(output); } -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, - const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_) -{ - // convert Lines to Polylines - Slic3r::Polylines polylines; - polylines.reserve(subject.size()); - for (Slic3r::Lines::const_iterator line = subject.begin(); line != subject.end(); ++line) - polylines.push_back(*line); - - // perform operation - _clipper(clipType, polylines, clip, &polylines, safety_offset_); - - // convert Polylines to Lines - for (Slic3r::Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) - retval->push_back(*polyline); -} - -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, - const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_) +Polylines +_clipper_pl(ClipperLib::ClipType clipType, const Polygons &subject, + const Polygons &clip, bool safety_offset_) { // transform input polygons into polylines - Slic3r::Polylines polylines; + Polylines polylines; polylines.reserve(subject.size()); - for (Slic3r::Polygons::const_iterator polygon = subject.begin(); polygon != subject.end(); ++polygon) + for (Polygons::const_iterator polygon = subject.begin(); polygon != subject.end(); ++polygon) polylines.push_back(*polygon); // implicit call to split_at_first_point() // perform clipping - _clipper(clipType, polylines, clip, retval, safety_offset_); + Polylines retval = _clipper_pl(clipType, polylines, clip, safety_offset_); /* If the split_at_first_point() call above happens to split the polygon inside the clipping area we would get two consecutive polylines instead of a single one, so we go through them in order to recombine continuous polylines. */ - for (size_t i = 0; i < retval->size(); ++i) { - for (size_t j = i+1; j < retval->size(); ++j) { - if ((*retval)[i].points.back().coincides_with((*retval)[j].points.front())) { + for (size_t i = 0; i < retval.size(); ++i) { + for (size_t j = i+1; j < retval.size(); ++j) { + if (retval[i].points.back().coincides_with(retval[j].points.front())) { /* If last point of i coincides with first point of j, append points of j to i and delete j */ - (*retval)[i].points.insert((*retval)[i].points.end(), (*retval)[j].points.begin()+1, (*retval)[j].points.end()); - retval->erase(retval->begin() + j); + retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end()); + retval.erase(retval.begin() + j); --j; - } else if ((*retval)[i].points.front().coincides_with((*retval)[j].points.back())) { + } else if (retval[i].points.front().coincides_with(retval[j].points.back())) { /* If first point of i coincides with last point of j, prepend points of j to i and delete j */ - (*retval)[i].points.insert((*retval)[i].points.begin(), (*retval)[j].points.begin(), (*retval)[j].points.end()-1); - retval->erase(retval->begin() + j); + retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1); + retval.erase(retval.begin() + j); --j; - } else if ((*retval)[i].points.front().coincides_with((*retval)[j].points.front())) { + } else if (retval[i].points.front().coincides_with(retval[j].points.front())) { /* Since Clipper does not preserve orientation of polylines, also check the case when first point of i coincides with first point of j. */ - (*retval)[j].reverse(); - (*retval)[i].points.insert((*retval)[i].points.begin(), (*retval)[j].points.begin(), (*retval)[j].points.end()-1); - retval->erase(retval->begin() + j); + retval[j].reverse(); + retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1); + retval.erase(retval.begin() + j); --j; - } else if ((*retval)[i].points.back().coincides_with((*retval)[j].points.back())) { + } else if (retval[i].points.back().coincides_with(retval[j].points.back())) { /* Since Clipper does not preserve orientation of polylines, also check the case when last point of i coincides with last point of j. */ - (*retval)[j].reverse(); - (*retval)[i].points.insert((*retval)[i].points.end(), (*retval)[j].points.begin()+1, (*retval)[j].points.end()); - retval->erase(retval->begin() + j); + retval[j].reverse(); + retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end()); + retval.erase(retval.begin() + j); --j; } } - } -} - -template -void diff(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_) -{ - _clipper(ClipperLib::ctDifference, subject, clip, retval, safety_offset_); -} -template void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); -template void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons* retval, bool safety_offset_); -template void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); -template void diff(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); -template void diff(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_); - -template -void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_) -{ - Slic3r::Polygons pp; - for (Slic3r::ExPolygons::const_iterator ex = clip.begin(); ex != clip.end(); ++ex) { - Slic3r::Polygons ppp = *ex; - pp.insert(pp.end(), ppp.begin(), ppp.end()); } - diff(subject, pp, retval, safety_offset_); -} -template void diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); - -template -void diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_) -{ - Slic3r::Polygons pp; - for (Slic3r::ExPolygons::const_iterator ex = subject.begin(); ex != subject.end(); ++ex) { - Slic3r::Polygons ppp = *ex; - pp.insert(pp.end(), ppp.begin(), ppp.end()); - } - diff(pp, clip, retval, safety_offset_); -} - -Slic3r::Polygons -diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_) -{ - Slic3r::Polygons pp; - diff(subject, clip, &pp, safety_offset_); - return pp; -} - -template -Slic3r::ExPolygons -diff_ex(const SubjectType &subject, const ClipType &clip, bool safety_offset_) -{ - Slic3r::ExPolygons expp; - diff(subject, clip, &expp, safety_offset_); - return expp; -} -template Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_); -template Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_); -template Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_); - -template -void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_) -{ - _clipper(ClipperLib::ctIntersection, subject, clip, retval, safety_offset_); -} -template void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); -template void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons* retval, bool safety_offset_); -template void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); -template void intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); -template void intersection(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_); - -template -SubjectType intersection(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_) -{ - SubjectType pp; - intersection(subject, clip, &pp, safety_offset_); - return pp; -} - -template Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_); -template Slic3r::Polylines intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_); -template Slic3r::Lines intersection(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_); - -Slic3r::ExPolygons -intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_) -{ - Slic3r::ExPolygons expp; - intersection(subject, clip, &expp, safety_offset_); - return expp; -} - -template -bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_) -{ - SubjectType retval; - intersection(subject, clip, &retval, safety_offset_); - return !retval.empty(); -} -template bool intersects(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_); -template bool intersects(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_); -template bool intersects(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_); - -void xor_(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, - bool safety_offset_) -{ - _clipper(ClipperLib::ctXor, subject, clip, retval, safety_offset_); -} - -template -void union_(const Slic3r::Polygons &subject, T* retval, bool safety_offset_) -{ - Slic3r::Polygons p; - _clipper(ClipperLib::ctUnion, subject, p, retval, safety_offset_); -} -template void union_(const Slic3r::Polygons &subject, Slic3r::ExPolygons* retval, bool safety_offset_); -template void union_(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool safety_offset_); - -Slic3r::Polygons -union_(const Slic3r::Polygons &subject, bool safety_offset) -{ - Polygons pp; - union_(subject, &pp, safety_offset); - return pp; -} - -Slic3r::ExPolygons -union_ex(const Slic3r::Polygons &subject, bool safety_offset) -{ - ExPolygons expp; - union_(subject, &expp, safety_offset); - return expp; -} - -Slic3r::ExPolygons -union_ex(const Slic3r::Surfaces &subject, bool safety_offset) -{ - Polygons pp; - for (Slic3r::Surfaces::const_iterator s = subject.begin(); s != subject.end(); ++s) { - Polygons spp = *s; - pp.insert(pp.end(), spp.begin(), spp.end()); - } - return union_ex(pp, safety_offset); -} - -void union_(const Slic3r::Polygons &subject1, const Slic3r::Polygons &subject2, Slic3r::Polygons* retval, bool safety_offset) -{ - Polygons pp = subject1; - pp.insert(pp.end(), subject2.begin(), subject2.end()); - union_(pp, retval, safety_offset); -} - -Slic3r::Polygons -union_(const Slic3r::ExPolygons &subject1, const Slic3r::ExPolygons &subject2, bool safety_offset) -{ - Polygons pp; - for (Slic3r::ExPolygons::const_iterator it = subject1.begin(); it != subject1.end(); ++it) { - Polygons spp = *it; - pp.insert(pp.end(), spp.begin(), spp.end()); - } - for (Slic3r::ExPolygons::const_iterator it = subject2.begin(); it != subject2.end(); ++it) { - Polygons spp = *it; - pp.insert(pp.end(), spp.begin(), spp.end()); - } - Polygons retval; - union_(pp, &retval, safety_offset); return retval; } -void union_pt(const Slic3r::Polygons &subject, ClipperLib::PolyTree* retval, bool safety_offset_) +Lines +_clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons &clip, + bool safety_offset_) { - Slic3r::Polygons clip; - _clipper_do(ClipperLib::ctUnion, subject, clip, retval, ClipperLib::pftEvenOdd, safety_offset_); + // convert Lines to Polylines + Polylines polylines; + polylines.reserve(subject.size()); + for (Lines::const_iterator line = subject.begin(); line != subject.end(); ++line) + polylines.push_back(*line); + + // perform operation + polylines = _clipper_pl(clipType, polylines, clip, safety_offset_); + + // convert Polylines to Lines + Lines retval; + for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) + retval.push_back(*polyline); + return retval; } -void union_pt_chained(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool safety_offset_) +ClipperLib::PolyTree +union_pt(const Polygons &subject, bool safety_offset_) { - ClipperLib::PolyTree pt; - union_pt(subject, &pt, safety_offset_); - traverse_pt(pt.Childs, retval); + return _clipper_do(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_); } -static void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons* retval) +Polygons +union_pt_chained(const Polygons &subject, bool safety_offset_) +{ + ClipperLib::PolyTree polytree = union_pt(subject, safety_offset_); + + Polygons retval; + traverse_pt(polytree.Childs, &retval); + return retval; +} + +void +traverse_pt(ClipperLib::PolyNodes &nodes, Polygons* retval) { /* use a nearest neighbor search to order these children TODO: supply start_near to chained_path() too? */ @@ -660,19 +464,19 @@ static void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons* retval) // traverse the next depth traverse_pt((*it)->Childs, retval); - Polygon p; - ClipperPath_to_Slic3rMultiPoint((*it)->Contour, &p); + Polygon p = ClipperPath_to_Slic3rMultiPoint((*it)->Contour); retval->push_back(p); if ((*it)->IsHole()) retval->back().reverse(); // ccw } } -void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool preserve_collinear) +Polygons +simplify_polygons(const Polygons &subject, bool preserve_collinear) { // convert into Clipper polygons - ClipperLib::Paths input_subject, output; - Slic3rMultiPoints_to_ClipperPaths(subject, &input_subject); + ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); + ClipperLib::Paths output; if (preserve_collinear) { ClipperLib::Clipper c; c.PreserveCollinear(true); @@ -684,21 +488,18 @@ void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::Polygons* retval } // convert into Slic3r polygons - ClipperPaths_to_Slic3rMultiPoints(output, retval); + return ClipperPaths_to_Slic3rMultiPoints(output); } -void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::ExPolygons* retval, bool preserve_collinear) +ExPolygons +simplify_polygons_ex(const Polygons &subject, bool preserve_collinear) { if (!preserve_collinear) { - Polygons polygons; - simplify_polygons(subject, &polygons, preserve_collinear); - union_(polygons, retval); - return; + return union_ex(simplify_polygons(subject, preserve_collinear)); } // convert into Clipper polygons - ClipperLib::Paths input_subject; - Slic3rMultiPoints_to_ClipperPaths(subject, &input_subject); + ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); ClipperLib::PolyTree polytree; @@ -709,7 +510,7 @@ void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::ExPolygons* retv c.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero); // convert into ExPolygons - PolyTreeToExPolygons(polytree, retval); + return PolyTreeToExPolygons(polytree); } void safety_offset(ClipperLib::Paths* paths) diff --git a/xs/src/libslic3r/ClipperUtils.hpp b/xs/src/libslic3r/ClipperUtils.hpp index 08697652c..9e4e5e89a 100644 --- a/xs/src/libslic3r/ClipperUtils.hpp +++ b/xs/src/libslic3r/ClipperUtils.hpp @@ -14,6 +14,11 @@ using ClipperLib::jtSquare; namespace Slic3r { +// Factor to convert from coord_t (which is int32) to an int64 type used by the Clipper library. +//FIXME Vojtech: Better to use a power of 2 coefficient and to use bit shifts for scaling. +// How about 2^17=131072? +// By the way, is the scalling needed at all? Cura runs all the computation with a fixed point precision of 1um, while Slic3r scales to 1nm, +// further scaling by 10e5 brings us to #define CLIPPER_OFFSET_SCALE 100000.0 //----------------------------------------------------------- @@ -22,121 +27,196 @@ void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, Slic3r::ExPoly void PolyTreeToExPolygons(ClipperLib::PolyTree& polytree, Slic3r::ExPolygons& expolygons); //----------------------------------------------------------- -void Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input, ClipperLib::Path* output); +ClipperLib::Path Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input); template -void Slic3rMultiPoints_to_ClipperPaths(const T &input, ClipperLib::Paths* output); +ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const T &input); template -void ClipperPath_to_Slic3rMultiPoint(const ClipperLib::Path &input, T* output); +T ClipperPath_to_Slic3rMultiPoint(const ClipperLib::Path &input); template -void ClipperPaths_to_Slic3rMultiPoints(const ClipperLib::Paths &input, T* output); -void ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, Slic3r::ExPolygons* output); +T ClipperPaths_to_Slic3rMultiPoints(const ClipperLib::Paths &input); +Slic3r::ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input); void scaleClipperPolygons(ClipperLib::Paths &polygons, const double scale); // offset Polygons -void offset(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta, - double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); -void offset(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta, - double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, +ClipperLib::Paths _offset(const Slic3r::Polygons &polygons, const float delta, + double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, - double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, + double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); // offset Polylines -void offset(const Slic3r::Polylines &polylines, ClipperLib::Paths* retval, const float delta, - double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtSquare, +ClipperLib::Paths _offset(const Slic3r::Polylines &polylines, const float delta, + double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); -void offset(const Slic3r::Polylines &polylines, Slic3r::Polygons* retval, const float delta, - double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtSquare, +Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, + double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); -void offset(const Slic3r::Surface &surface, Slic3r::Surfaces* retval, const float delta, - double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtSquare, +Slic3r::Surfaces offset(const Slic3r::Surface &surface, const float delta, + double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3); -void offset(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta, - double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, - double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, + double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, - double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, + double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -void offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta1, - const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); -void offset2(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta1, - const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, +ClipperLib::Paths _offset2(const Slic3r::Polygons &polygons, const float delta1, + const float delta2, double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1, - const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); -void offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta1, - const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, + const float delta2, double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1, - const float delta2, double scale = 100000, ClipperLib::JoinType joinType = ClipperLib::jtMiter, + const float delta2, double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); template -void _clipper_do(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, - const Slic3r::Polygons &clip, T* retval, bool safety_offset_); -void _clipper_do(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, - const Slic3r::Polygons &clip, ClipperLib::Paths* retval, bool safety_offset_); -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, - const Slic3r::Polygons &clip, Slic3r::Polygons* retval, bool safety_offset_); -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, - const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, - const Slic3r::Polygons &clip, Slic3r::Polylines* retval); -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, - const Slic3r::Polygons &clip, Slic3r::Lines* retval); +T _clipper_do(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, + const Slic3r::Polygons &clip, const ClipperLib::PolyFillType fillType, bool safety_offset_ = false); -template -void diff(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_ = false); +ClipperLib::PolyTree _clipper_do(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, + const Slic3r::Polygons &clip, const ClipperLib::PolyFillType fillType, bool safety_offset_ = false); -template -void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_ = false); +Slic3r::Polygons _clipper(ClipperLib::ClipType clipType, + const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +Slic3r::ExPolygons _clipper_ex(ClipperLib::ClipType clipType, + const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +Slic3r::Polylines _clipper_pl(ClipperLib::ClipType clipType, + const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +Slic3r::Polylines _clipper_pl(ClipperLib::ClipType clipType, + const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, + const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); -Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +// diff +inline Slic3r::Polygons +diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper(ClipperLib::ctDifference, subject, clip, safety_offset_); +} -template -Slic3r::ExPolygons diff_ex(const SubjectType &subject, const ClipType &clip, bool safety_offset_ = false); +inline Slic3r::ExPolygons +diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_ex(ClipperLib::ctDifference, subject, clip, safety_offset_); +} -template -void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_ = false); +inline Slic3r::ExPolygons +diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) +{ + return _clipper_ex(ClipperLib::ctDifference, to_polygons(subject), to_polygons(clip), safety_offset_); +} -template -SubjectType intersection(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +inline Slic3r::Polygons +diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) +{ + return _clipper(ClipperLib::ctDifference, to_polygons(subject), to_polygons(clip), safety_offset_); +} -Slic3r::ExPolygons -intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +inline Slic3r::Polylines +diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_pl(ClipperLib::ctDifference, subject, clip, safety_offset_); +} -template -bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +inline Slic3r::Polylines +diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_pl(ClipperLib::ctDifference, subject, clip, safety_offset_); +} -void xor_(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, - bool safety_offset_ = false); +inline Slic3r::Lines +diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_ln(ClipperLib::ctDifference, subject, clip, safety_offset_); +} -template -void union_(const Slic3r::Polygons &subject, T* retval, bool safety_offset_ = false); +// intersection +inline Slic3r::Polygons +intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper(ClipperLib::ctIntersection, subject, clip, safety_offset_); +} -Slic3r::Polygons union_(const Slic3r::Polygons &subject, bool safety_offset = false); -Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, bool safety_offset = false); -Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject, bool safety_offset = false); +inline Slic3r::ExPolygons +intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_ex(ClipperLib::ctIntersection, subject, clip, safety_offset_); +} -void union_(const Slic3r::Polygons &subject1, const Slic3r::Polygons &subject2, Slic3r::Polygons* retval, bool safety_offset = false); -Slic3r::Polygons union_(const Slic3r::ExPolygons &subject1, const Slic3r::ExPolygons &subject2, bool safety_offset = false); +inline Slic3r::ExPolygons +intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) +{ + return _clipper_ex(ClipperLib::ctIntersection, to_polygons(subject), to_polygons(clip), safety_offset_); +} -void union_pt(const Slic3r::Polygons &subject, ClipperLib::PolyTree* retval, bool safety_offset_ = false); -void union_pt_chained(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool safety_offset_ = false); -static void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons* retval); +inline Slic3r::Polygons +intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) +{ + return _clipper(ClipperLib::ctIntersection, to_polygons(subject), to_polygons(clip), safety_offset_); +} -void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool preserve_collinear = false); -void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::ExPolygons* retval, bool preserve_collinear = false); +inline Slic3r::Polylines +intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_pl(ClipperLib::ctIntersection, subject, clip, safety_offset_); +} + +inline Slic3r::Polylines +intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_pl(ClipperLib::ctIntersection, subject, clip, safety_offset_); +} + +inline Slic3r::Lines +intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_ln(ClipperLib::ctIntersection, subject, clip, safety_offset_); +} + +// union +inline Slic3r::Polygons +union_(const Slic3r::Polygons &subject, bool safety_offset_ = false) +{ + return _clipper(ClipperLib::ctUnion, subject, Slic3r::Polygons(), safety_offset_); +} + +inline Slic3r::Polygons +union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2, bool safety_offset_ = false) +{ + return _clipper(ClipperLib::ctUnion, subject, subject2, safety_offset_); +} + +inline Slic3r::ExPolygons +union_ex(const Slic3r::Polygons &subject, bool safety_offset_ = false) +{ + return _clipper_ex(ClipperLib::ctUnion, subject, Slic3r::Polygons(), safety_offset_); +} + +inline Slic3r::ExPolygons +union_ex(const Slic3r::ExPolygons &subject, bool safety_offset_ = false) +{ + return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_); +} + +inline Slic3r::ExPolygons +union_ex(const Slic3r::Surfaces &subject, bool safety_offset_ = false) +{ + return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_); +} + + +ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject, bool safety_offset_ = false); +Slic3r::Polygons union_pt_chained(const Slic3r::Polygons &subject, bool safety_offset_ = false); +void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons* retval); + +/* OTHER */ +Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false); +Slic3r::ExPolygons simplify_polygons_ex(const Slic3r::Polygons &subject, bool preserve_collinear = false); void safety_offset(ClipperLib::Paths* paths); diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index 59b0401c6..ed8ffc468 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -5,13 +5,15 @@ #include #include #include // std::runtime_error +#include +#include #include #include #include +#include #include #include -#include -#include +#include #if defined(_WIN32) && !defined(setenv) && defined(_putenv_s) #define setenv(k, v, o) _putenv_s(k, v) @@ -19,6 +21,159 @@ namespace Slic3r { +std::string escape_string_cstyle(const std::string &str) +{ + // Allocate a buffer twice the input string length, + // so the output will fit even if all input characters get escaped. + std::vector out(str.size() * 2, 0); + char *outptr = out.data(); + for (size_t i = 0; i < str.size(); ++ i) { + char c = str[i]; + if (c == '\n' || c == '\r') { + (*outptr ++) = '\\'; + (*outptr ++) = 'n'; + } else + (*outptr ++) = c; + } + return std::string(out.data(), outptr - out.data()); +} + +std::string escape_strings_cstyle(const std::vector &strs) +{ + // 1) Estimate the output buffer size to avoid buffer reallocation. + size_t outbuflen = 0; + for (size_t i = 0; i < strs.size(); ++ i) + // Reserve space for every character escaped + quotes + semicolon. + outbuflen += strs[i].size() * 2 + 3; + // 2) Fill in the buffer. + std::vector out(outbuflen, 0); + char *outptr = out.data(); + for (size_t j = 0; j < strs.size(); ++ j) { + if (j > 0) + // Separate the strings. + (*outptr ++) = ';'; + const std::string &str = strs[j]; + // Is the string simple or complex? Complex string contains spaces, tabs, new lines and other + // escapable characters. Empty string shall be quoted as well, if it is the only string in strs. + bool should_quote = strs.size() == 1 && str.empty(); + for (size_t i = 0; i < str.size(); ++ i) { + char c = str[i]; + if (c == ' ' || c == '\t' || c == '\\' || c == '"' || c == '\r' || c == '\n') { + should_quote = true; + break; + } + } + if (should_quote) { + (*outptr ++) = '"'; + for (size_t i = 0; i < str.size(); ++ i) { + char c = str[i]; + if (c == '\\' || c == '"') { + (*outptr ++) = '\\'; + (*outptr ++) = c; + } else if (c == '\n' || c == '\r') { + (*outptr ++) = '\\'; + (*outptr ++) = 'n'; + } else + (*outptr ++) = c; + } + (*outptr ++) = '"'; + } else { + memcpy(outptr, str.data(), str.size()); + outptr += str.size(); + } + } + return std::string(out.data(), outptr - out.data()); +} + +bool unescape_string_cstyle(const std::string &str, std::string &str_out) +{ + std::vector out(str.size(), 0); + char *outptr = out.data(); + for (size_t i = 0; i < str.size(); ++ i) { + char c = str[i]; + if (c == '\\') { + if (++ i == str.size()) + return false; + c = str[i]; + if (c == 'n') + (*outptr ++) = '\n'; + } else + (*outptr ++) = c; + } + str_out.assign(out.data(), outptr - out.data()); + return true; +} + +bool unescape_strings_cstyle(const std::string &str, std::vector &out) +{ + out.clear(); + if (str.empty()) + return true; + + size_t i = 0; + for (;;) { + // Skip white spaces. + char c = str[i]; + while (c == ' ' || c == '\t') { + if (++ i == str.size()) + return true; + c = str[i]; + } + // Start of a word. + std::vector buf; + buf.reserve(16); + // Is it enclosed in quotes? + c = str[i]; + if (c == '"') { + // Complex case, string is enclosed in quotes. + for (++ i; i < str.size(); ++ i) { + c = str[i]; + if (c == '"') { + // End of string. + break; + } + if (c == '\\') { + if (++ i == str.size()) + return false; + c = str[i]; + if (c == 'n') + c = '\n'; + } + buf.push_back(c); + } + if (i == str.size()) + return false; + ++ i; + } else { + for (; i < str.size(); ++ i) { + c = str[i]; + if (c == ';') + break; + buf.push_back(c); + } + } + // Store the string into the output vector. + out.push_back(std::string(buf.data(), buf.size())); + if (i == str.size()) + return true; + // Skip white spaces. + c = str[i]; + while (c == ' ' || c == '\t') { + if (++ i == str.size()) + // End of string. This is correct. + return true; + c = str[i]; + } + if (c != ';') + return false; + if (++ i == str.size()) { + // Emit one additional empty string. + out.push_back(std::string()); + return true; + } + } +} + bool operator== (const ConfigOption &a, const ConfigOption &b) { @@ -140,27 +295,31 @@ ConfigBase::set_deserialize(const t_config_option_key &opt_key, std::string str, return opt->deserialize(str, append); } +// Return an absolute value of a possibly relative config variable. +// For example, return absolute infill extrusion width, either from an absolute value, or relative to the layer height. double -ConfigBase::get_abs_value(const t_config_option_key &opt_key) { - ConfigOption* opt = this->option(opt_key, false); - if (ConfigOptionFloatOrPercent* optv = dynamic_cast(opt)) { +ConfigBase::get_abs_value(const t_config_option_key &opt_key) const { + const ConfigOption* opt = this->option(opt_key); + if (const ConfigOptionFloatOrPercent* optv = dynamic_cast(opt)) { // get option definition const ConfigOptionDef* def = this->def->get(opt_key); assert(def != NULL); // compute absolute value over the absolute value of the base option return optv->get_abs_value(this->get_abs_value(def->ratio_over)); - } else if (ConfigOptionFloat* optv = dynamic_cast(opt)) { + } else if (const ConfigOptionFloat* optv = dynamic_cast(opt)) { return optv->value; } else { throw "Not a valid option type for get_abs_value()"; } } +// Return an absolute value of a possibly relative config variable. +// For example, return absolute infill extrusion width, either from an absolute value, or relative to a provided value. double -ConfigBase::get_abs_value(const t_config_option_key &opt_key, double ratio_over) { +ConfigBase::get_abs_value(const t_config_option_key &opt_key, double ratio_over) const { // get stored option value - ConfigOptionFloatOrPercent* opt = dynamic_cast(this->option(opt_key)); + const ConfigOptionFloatOrPercent* opt = dynamic_cast(this->option(opt_key)); assert(opt != NULL); // compute absolute value @@ -342,8 +501,8 @@ DynamicConfig::read_cli(const int argc, const char** argv, t_config_option_keys* if (token == "--") { // stop parsing tokens as options parse_options = false; - } else if (parse_options && boost::starts_with(token, "--")) { - boost::algorithm::erase_head(token, 2); + } else if (parse_options && boost::starts_with(token, "-")) { + boost::algorithm::trim_left_if(token, boost::algorithm::is_any_of("-")); // TODO: handle --key=value // look for the option def @@ -355,7 +514,9 @@ DynamicConfig::read_cli(const int argc, const char** argv, t_config_option_keys* if (optdef->cli == token || optdef->cli == token + '!' - || boost::starts_with(optdef->cli, token + "=")) { + || boost::starts_with(optdef->cli, token + "=") + || boost::starts_with(optdef->cli, token + "|") + || (token.length() == 1 && boost::contains(optdef->cli, "|" + token))) { opt_key = oit->first; break; } @@ -392,7 +553,6 @@ StaticConfig::set_defaults() t_config_option_keys keys = this->keys(); for (t_config_option_keys::const_iterator it = keys.begin(); it != keys.end(); ++it) { const ConfigOptionDef* def = this->def->get(*it); - if (def->default_value != NULL) this->option(*it)->set(*def->default_value); } diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index c293b4ef8..ad01f1644 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -15,9 +15,16 @@ namespace Slic3r { +// Name of the configuration option. typedef std::string t_config_option_key; typedef std::vector t_config_option_keys; +extern std::string escape_string_cstyle(const std::string &str); +extern std::string escape_strings_cstyle(const std::vector &strs); +extern bool unescape_string_cstyle(const std::string &str, std::string &out); +extern bool unescape_strings_cstyle(const std::string &str, std::vector &out); + +// A generic value of a configuration option. class ConfigOption { public: virtual ~ConfigOption() {}; @@ -34,6 +41,7 @@ class ConfigOption { friend bool operator!= (const ConfigOption &a, const ConfigOption &b); }; +// Value of a single valued option (bool, int, float, string, point, enum) template class ConfigOptionSingle : public ConfigOption { public: @@ -47,12 +55,14 @@ class ConfigOptionSingle : public ConfigOption { }; }; +// Value of a vector valued option (bools, ints, floats, strings, points) class ConfigOptionVectorBase : public ConfigOption { public: virtual ~ConfigOptionVectorBase() {}; virtual std::vector vserialize() const = 0; }; +// Value of a vector valued option (bools, ints, floats, strings, points), template template class ConfigOptionVector : public ConfigOptionVectorBase { @@ -116,6 +126,7 @@ class ConfigOptionFloats : public ConfigOptionVector std::vector vserialize() const { std::vector vv; + vv.reserve(this->values.size()); for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { std::ostringstream ss; ss << *it; @@ -179,6 +190,7 @@ class ConfigOptionInts : public ConfigOptionVector std::vector vserialize() const { std::vector vv; + vv.reserve(this->values.size()); for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { std::ostringstream ss; ss << *it; @@ -210,29 +222,12 @@ class ConfigOptionString : public ConfigOptionSingle std::string getString() const { return this->value; }; - std::string serialize() const { - std::string str = this->value; - - // s/\R/\\n/g - size_t pos = 0; - while ((pos = str.find("\n", pos)) != std::string::npos || (pos = str.find("\r", pos)) != std::string::npos) { - str.replace(pos, 1, "\\n"); - pos += 2; // length of "\\n" - } - - return str; + std::string serialize() const { + return escape_string_cstyle(this->value); }; - + bool deserialize(std::string str, bool append = false) { - // s/\\n/\n/g - size_t pos = 0; - while ((pos = str.find("\\n", pos)) != std::string::npos) { - str.replace(pos, 2, "\n"); - pos += 1; // length of "\n" - } - - this->value = str; - return true; + return unescape_string_cstyle(str, this->value); }; }; @@ -245,12 +240,7 @@ class ConfigOptionStrings : public ConfigOptionVector ConfigOptionStrings* clone() const { return new ConfigOptionStrings(this->values); }; std::string serialize() const { - std::ostringstream ss; - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - if (it - this->values.begin() != 0) ss << ";"; - ss << *it; - } - return ss.str(); + return escape_strings_cstyle(this->values); }; std::vector vserialize() const { @@ -258,13 +248,7 @@ class ConfigOptionStrings : public ConfigOptionVector }; bool deserialize(std::string str, bool append = false) { - if (!append) this->values.clear(); - std::istringstream is(str); - std::string item_str; - while (std::getline(is, item_str, ';')) { - this->values.push_back(item_str); - } - return true; + return unescape_strings_cstyle(str, this->values); }; }; @@ -482,6 +466,7 @@ class ConfigOptionBools : public ConfigOptionVector }; }; +// Map from an enum name to an enum integer value. typedef std::map t_config_enum_values; template @@ -508,11 +493,14 @@ class ConfigOptionEnum : public ConfigOptionSingle return true; }; + // Map from an enum name to an enum integer value. + //FIXME The map is called often, it shall be initialized statically. static t_config_enum_values get_enum_values(); }; -/* We use this one in DynamicConfig objects, otherwise it's better to use - the specialized ConfigOptionEnum containers. */ +// Generic enum configuration value. +// We use this one in DynamicConfig objects when creating a config value object for ConfigOptionType == coEnum. +// In the StaticConfig, it is better to use the specialized ConfigOptionEnum containers. class ConfigOptionEnumGeneric : public ConfigOptionInt { public: @@ -532,51 +520,105 @@ class ConfigOptionEnumGeneric : public ConfigOptionInt }; }; +// Type of a configuration value. enum ConfigOptionType { coNone, + // single float coFloat, + // vector of floats coFloats, + // single int coInt, + // vector of ints coInts, + // single string coString, + // vector of strings coStrings, + // percent value. Currently only used for infill. coPercent, + // a fraction or an absolute value coFloatOrPercent, + // single 2d point. Currently not used. coPoint, - coPoint3, + // vector of 2d points. Currently used for the definition of the print bed and for the extruder offsets. coPoints, + coPoint3, + // single boolean value coBool, + // vector of boolean values coBools, + // a generic enum coEnum, }; +// Definition of a configuration value for the purpose of GUI presentation, editing, value mapping and config file handling. class ConfigOptionDef { public: + // What type? bool, int, string etc. ConfigOptionType type; + // Default value of this option. The default value object is owned by ConfigDef, it is released in its destructor. ConfigOption* default_value; + + // Usually empty. + // Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection, + // "select_open" - to open a selection dialog (currently only a serial port selection). std::string gui_type; + // Usually empty. Otherwise "serialized" or "show_value" + // The flags may be combined. + // "serialized" - vector valued option is entered in a single edit field. Values are separated by a semicolon. + // "show_value" - even if enum_values / enum_labels are set, still display the value, not the enum label. std::string gui_flags; + // Label of the GUI input field. + // In case the GUI input fields are grouped in some views, the label defines a short label of a grouped value, + // while full_label contains a label of a stand-alone field. + // The full label is shown, when adding an override parameter for an object or a modified object. std::string label; std::string full_label; + // Category of a configuration field, from the GUI perspective. + // One of: "Layers and Perimeters", "Infill", "Support material", "Speed", "Extruders", "Advanced", "Extrusion Width" std::string category; + // A tooltip text shown in the GUI. std::string tooltip; + // Text right from the input field, usually a unit of measurement. std::string sidetext; + // Format of this parameter on a command line. std::string cli; + // Set for type == coFloatOrPercent. + // It provides a link to a configuration value, of which this option provides a ratio. + // For example, + // For example external_perimeter_speed may be defined as a fraction of perimeter_speed. t_config_option_key ratio_over; + // True for multiline strings. bool multiline; + // For text input: If true, the GUI text box spans the complete page width. bool full_width; + // Not editable. Currently only used for the display of the number of threads. bool readonly; + // Height of a multiline GUI text box. int height; + // Optional width of an input field. int width; + // limit of a numeric input. + // If not set, the is set to + // By setting min=0, only nonnegative input is allowed. int min; int max; + // Legacy names for this configuration option. + // Used when parsing legacy configuration file. std::vector aliases; + // Sometimes a single value may well define multiple values in a "beginner" mode. + // Currently used for aliasing "solid_layers" to "top_solid_layers", "bottom_solid_layers". std::vector shortcut; + // Definition of values / labels for a combo box. + // Mostly used for enums (when type == coEnum), but may be used for ints resp. floats, if gui_type is set to "i_enum_open" resp. "f_enum_open". std::vector enum_values; std::vector enum_labels; + // For enums (when type == coEnum). Maps enum_values to enums. + // Initialized by ConfigOptionEnum::get_enum_values() t_config_enum_values enum_keys_map; - + ConfigOptionDef() : type(coNone), default_value(NULL), multiline(false), full_width(false), readonly(false), height(-1), width(-1), min(INT_MIN), max(INT_MAX) {}; @@ -587,8 +629,14 @@ class ConfigOptionDef ConfigOptionDef& operator= (ConfigOptionDef other); }; +// Map from a config option name to its definition. +// The definition does not carry an actual value of the config option, only its constant default value. +// t_config_option_key is std::string typedef std::map t_optiondef_map; +// Definition of configuration values for the purpose of GUI presentation, editing, value mapping and config file handling. +// The configuration definition is static: It does not carry the actual configuration values, +// but it carries the defaults of the configuration values. class ConfigDef { public: @@ -598,9 +646,14 @@ class ConfigDef void merge(const ConfigDef &other); }; +// An abstract configuration store. class ConfigBase { public: + // Definition of configuration values for the purpose of GUI presentation, editing, value mapping and config file handling. + // The configuration definition is static: It does not carry the actual configuration values, + // but it carries the defaults of the configuration values. + // ConfigBase does not own ConfigDef, it only references it. const ConfigDef* def; ConfigBase() : def(NULL) {}; @@ -616,13 +669,15 @@ class ConfigBase t_config_option_keys diff(ConfigBase &other); std::string serialize(const t_config_option_key &opt_key) const; bool set_deserialize(const t_config_option_key &opt_key, std::string str, bool append = false); - double get_abs_value(const t_config_option_key &opt_key); - double get_abs_value(const t_config_option_key &opt_key, double ratio_over); + double get_abs_value(const t_config_option_key &opt_key) const; + double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; void setenv_(); void load(const std::string &file); void save(const std::string &file) const; }; +// Configuration store with dynamic number of configuration values. +// In Slic3r, the dynamic config is mostly used at the user interface layer. class DynamicConfig : public virtual ConfigBase { public: @@ -643,13 +698,20 @@ class DynamicConfig : public virtual ConfigBase t_options_map options; }; +// Configuration store with a static definition of configuration values. +// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons, +// because the configuration values could be accessed directly. class StaticConfig : public virtual ConfigBase { public: StaticConfig() : ConfigBase() {}; + // Gets list of config option names for each config option of this->def, which has a static counter-part defined by the derived object + // and which could be resolved by this->optptr(key) call. t_config_option_keys keys() const; - //virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) = 0; + // Set all statically defined config options to their defaults defined by this->def. void set_defaults(); + // The derived class has to implement optptr to resolve a static configuration value. + // virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) = 0; }; class UnknownOptionException : public std::exception {}; diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index 3640d3918..19c3184f3 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -52,6 +52,15 @@ ExPolygon::translate(double x, double y) } } +void +ExPolygon::rotate(double angle) +{ + contour.rotate(angle); + for (Polygons::iterator it = holes.begin(); it != holes.end(); ++it) { + (*it).rotate(angle); + } +} + void ExPolygon::rotate(double angle, const Point ¢er) { @@ -90,9 +99,7 @@ ExPolygon::contains(const Line &line) const bool ExPolygon::contains(const Polyline &polyline) const { - Polylines pl_out; - diff((Polylines)polyline, *this, &pl_out); - return pl_out.empty(); + return diff_pl((Polylines)polyline, *this).empty(); } bool @@ -152,8 +159,7 @@ ExPolygon::simplify_p(double tolerance) const p.points.pop_back(); pp.push_back(p); } - simplify_polygons(pp, &pp); - return pp; + return simplify_polygons(pp); } ExPolygons @@ -346,8 +352,7 @@ ExPolygon::get_trapezoids2(Polygons* polygons) const poly[3].y = bb.max.y; // intersect with this expolygon - Polygons trapezoids; - intersection(poly, *this, &trapezoids); + Polygons trapezoids = intersection(poly, *this); // append results to return value polygons->insert(polygons->end(), trapezoids.begin(), trapezoids.end()); @@ -384,10 +389,7 @@ ExPolygon::triangulate_pp(Polygons* polygons) const // convert polygons std::list input; - Polygons pp = *this; - simplify_polygons(pp, &pp, true); - ExPolygons expp; - union_(pp, &expp); + ExPolygons expp = simplify_polygons_ex(*this, true); for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) { // contour @@ -440,8 +442,7 @@ ExPolygon::triangulate_pp(Polygons* polygons) const void ExPolygon::triangulate_p2t(Polygons* polygons) const { - ExPolygons expp; - simplify_polygons(*this, &expp, true); + ExPolygons expp = simplify_polygons_ex(*this, true); for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) { // TODO: prevent duplicate points diff --git a/xs/src/libslic3r/ExPolygon.hpp b/xs/src/libslic3r/ExPolygon.hpp index 69fe9fb0e..4289eb390 100644 --- a/xs/src/libslic3r/ExPolygon.hpp +++ b/xs/src/libslic3r/ExPolygon.hpp @@ -20,6 +20,7 @@ class ExPolygon operator Polygons() const; void scale(double factor); void translate(double x, double y); + void rotate(double angle); void rotate(double angle, const Point ¢er); double area() const; bool is_valid() const; @@ -45,6 +46,21 @@ class ExPolygon std::string dump_perl() const; }; +inline Polygons +to_polygons(const ExPolygons &expolygons) +{ + Polygons pp; + for (ExPolygons::const_iterator ex = expolygons.begin(); ex != expolygons.end(); ++ex) + append_to(pp, (Polygons)*ex); + return pp; +} + +inline ExPolygons +operator+(ExPolygons src1, const ExPolygons &src2) { + append_to(src1, src2); + return src1; +}; + } // start Boost diff --git a/xs/src/libslic3r/ExPolygonCollection.hpp b/xs/src/libslic3r/ExPolygonCollection.hpp index ec3cb9522..e63dd9142 100644 --- a/xs/src/libslic3r/ExPolygonCollection.hpp +++ b/xs/src/libslic3r/ExPolygonCollection.hpp @@ -34,6 +34,12 @@ class ExPolygonCollection void append(const ExPolygons &expolygons); }; +inline ExPolygonCollection& +operator <<(ExPolygonCollection &coll, const ExPolygons &expolygons) { + coll.append(expolygons); + return coll; +}; + } #endif diff --git a/xs/src/libslic3r/ExtrusionEntity.cpp b/xs/src/libslic3r/ExtrusionEntity.cpp index 5d0e5502a..bd4323650 100644 --- a/xs/src/libslic3r/ExtrusionEntity.cpp +++ b/xs/src/libslic3r/ExtrusionEntity.cpp @@ -3,41 +3,18 @@ #include "ExPolygonCollection.hpp" #include "ClipperUtils.hpp" #include "Extruder.hpp" +#include #include +#include #include namespace Slic3r { - -ExtrusionPath* -ExtrusionPath::clone() const -{ - return new ExtrusionPath (*this); -} -void -ExtrusionPath::reverse() -{ - this->polyline.reverse(); -} - -Point -ExtrusionPath::first_point() const -{ - return this->polyline.points.front(); -} - -Point -ExtrusionPath::last_point() const -{ - return this->polyline.points.back(); -} - void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { // perform clipping - Polylines clipped; - intersection(this->polyline, collection, &clipped); + Polylines clipped = intersection_pl(this->polyline, collection); return this->_inflate_collection(clipped, retval); } @@ -45,8 +22,7 @@ void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { // perform clipping - Polylines clipped; - diff(this->polyline, collection, &clipped); + Polylines clipped = diff_pl(this->polyline, collection); return this->_inflate_collection(clipped, retval); } @@ -68,38 +44,6 @@ ExtrusionPath::length() const return this->polyline.length(); } -bool -ExtrusionPath::is_perimeter() const -{ - return this->role == erPerimeter - || this->role == erExternalPerimeter - || this->role == erOverhangPerimeter; -} - -bool -ExtrusionPath::is_infill() const -{ - return this->role == erBridgeInfill - || this->role == erInternalInfill - || this->role == erSolidInfill - || this->role == erTopSolidInfill; -} - -bool -ExtrusionPath::is_solid_infill() const -{ - return this->role == erBridgeInfill - || this->role == erSolidInfill - || this->role == erTopSolidInfill; -} - -bool -ExtrusionPath::is_bridge() const -{ - return this->role == erBridgeInfill - || this->role == erOverhangPerimeter; -} - void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const { @@ -113,15 +57,7 @@ ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCo Polygons ExtrusionPath::grow() const { - Polygons pp; - offset(this->polyline, &pp, +scale_(this->width/2)); - return pp; -} - -ExtrusionLoop* -ExtrusionLoop::clone() const -{ - return new ExtrusionLoop (*this); + return offset(this->polyline, +scale_(this->width/2)); } bool @@ -148,18 +84,6 @@ ExtrusionLoop::reverse() std::reverse(this->paths.begin(), this->paths.end()); } -Point -ExtrusionLoop::first_point() const -{ - return this->paths.front().polyline.points.front(); -} - -Point -ExtrusionLoop::last_point() const -{ - return this->paths.back().polyline.points.back(); // which coincides with first_point(), by the way -} - Polygon ExtrusionLoop::polygon() const { @@ -193,6 +117,7 @@ ExtrusionLoop::split_at_vertex(const Point &point) } else { // new paths list starts with the second half of current path ExtrusionPaths new_paths; + new_paths.reserve(this->paths.size() + 1); { ExtrusionPath p = *path; p.polyline.points.erase(p.polyline.points.begin(), p.polyline.points.begin() + idx); @@ -212,7 +137,7 @@ ExtrusionLoop::split_at_vertex(const Point &point) if (p.polyline.is_valid()) new_paths.push_back(p); } // we can now override the old path list with the new one and stop looping - this->paths = new_paths; + std::swap(this->paths, new_paths); } return true; } @@ -240,14 +165,26 @@ ExtrusionLoop::split_at(const Point &point) } // now split path_idx in two parts - ExtrusionPath p1 = this->paths[path_idx]; - ExtrusionPath p2 = p1; - this->paths[path_idx].polyline.split_at(p, &p1.polyline, &p2.polyline); + const ExtrusionPath &path = this->paths[path_idx]; + ExtrusionPath p1(path.role, path.mm3_per_mm, path.width, path.height); + ExtrusionPath p2(path.role, path.mm3_per_mm, path.width, path.height); + path.polyline.split_at(p, &p1.polyline, &p2.polyline); - // install the two paths - this->paths.erase(this->paths.begin() + path_idx); - if (p2.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p2); - if (p1.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p1); + if (this->paths.size() == 1) { + if (! p1.polyline.is_valid()) + std::swap(this->paths.front().polyline.points, p2.polyline.points); + else if (! p2.polyline.is_valid()) + std::swap(this->paths.front().polyline.points, p1.polyline.points); + else { + p2.polyline.points.insert(p2.polyline.points.end(), p1.polyline.points.begin() + 1, p1.polyline.points.end()); + std::swap(this->paths.front().polyline.points, p2.polyline.points); + } + } else { + // install the two paths + this->paths.erase(this->paths.begin() + path_idx); + if (p2.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p2); + if (p1.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p1); + } // split at the new vertex this->split_at_vertex(p); @@ -285,53 +222,41 @@ ExtrusionLoop::has_overhang_point(const Point &point) const return false; } -bool -ExtrusionLoop::is_perimeter() const -{ - return this->paths.front().role == erPerimeter - || this->paths.front().role == erExternalPerimeter - || this->paths.front().role == erOverhangPerimeter; -} - -bool -ExtrusionLoop::is_infill() const -{ - return this->paths.front().role == erBridgeInfill - || this->paths.front().role == erInternalInfill - || this->paths.front().role == erSolidInfill - || this->paths.front().role == erTopSolidInfill; -} - -bool -ExtrusionLoop::is_solid_infill() const -{ - return this->paths.front().role == erBridgeInfill - || this->paths.front().role == erSolidInfill - || this->paths.front().role == erTopSolidInfill; -} - Polygons ExtrusionLoop::grow() const { - Polygons pp; - for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) { - Polygons path_pp = path->grow(); - pp.insert(pp.end(), path_pp.begin(), path_pp.end()); + if (this->paths.empty()) return Polygons(); + + // collect all the path widths + std::vector widths; + for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) + widths.push_back(path->width); + + // grow this polygon with the minimum common width + // (this ensures vertices are grown correctly, which doesn't happen if we just + // union the paths grown individually) + const float min_width = *std::min_element(widths.begin(), widths.end()); + const Polygon p = this->polygon(); + Polygons pp = diff( + offset(p, +scale_(min_width/2)), + offset(p, -scale_(min_width/2)) + ); + + // if we have thicker segments, grow them + if (min_width != *std::max_element(widths.begin(), widths.end())) { + for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) + append_to(pp, path->grow()); } - return pp; + + return union_(pp); } double ExtrusionLoop::min_mm3_per_mm() const { - double min_mm3_per_mm = 0; - for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) { - if (min_mm3_per_mm == 0) { - min_mm3_per_mm = path->mm3_per_mm; - } else { - min_mm3_per_mm = fmin(min_mm3_per_mm, path->mm3_per_mm); - } - } + double min_mm3_per_mm = std::numeric_limits::max(); + for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) + min_mm3_per_mm = std::min(min_mm3_per_mm, path->mm3_per_mm); return min_mm3_per_mm; } diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index 7b4e796e4..1d7ef9a99 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -36,22 +36,18 @@ enum ExtrusionLoopRole { class ExtrusionEntity { - public: - virtual bool is_collection() const { - return false; - }; - virtual bool is_loop() const { - return false; - }; - virtual bool can_reverse() const { - return true; - }; +public: + virtual bool is_collection() const { return false; } + virtual bool is_loop() const { return false; } + virtual bool can_reverse() const { return true; } virtual ExtrusionEntity* clone() const = 0; virtual ~ExtrusionEntity() {}; virtual void reverse() = 0; virtual Point first_point() const = 0; virtual Point last_point() const = 0; + // Produce a list of 2D polygons covered by the extruded path. virtual Polygons grow() const = 0; + // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. virtual double min_mm3_per_mm() const = 0; virtual Polyline as_polyline() const = 0; virtual double length() const { return 0; }; @@ -61,34 +57,57 @@ typedef std::vector ExtrusionEntitiesPtr; class ExtrusionPath : public ExtrusionEntity { - public: +public: Polyline polyline; ExtrusionRole role; - double mm3_per_mm; // mm^3 of plastic per mm of linear head motion + // Volumetric velocity. mm^3 of plastic per mm of linear head motion + double mm3_per_mm; + // Width of the extrusion. float width; + // Height of the extrusion. float height; ExtrusionPath(ExtrusionRole role) : role(role), mm3_per_mm(-1), width(-1), height(-1) {}; - ExtrusionPath* clone() const; - void reverse(); - Point first_point() const; - Point last_point() const; + ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : role(role), mm3_per_mm(mm3_per_mm), width(width), height(height) {}; +// ExtrusionPath(ExtrusionRole role, const Flow &flow) : role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height) {}; + ExtrusionPath* clone() const { return new ExtrusionPath (*this); } + void reverse() { this->polyline.reverse(); } + Point first_point() const { return this->polyline.points.front(); } + Point last_point() const { return this->polyline.points.back(); } + // Produce a list of extrusion paths into retval by clipping this path by ExPolygonCollection. + // Currently not used. void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const; + // Produce a list of extrusion paths into retval by removing parts of this path by ExPolygonCollection. + // Currently not used. void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const; void clip_end(double distance); void simplify(double tolerance); virtual double length() const; - bool is_perimeter() const; - bool is_infill() const; - bool is_solid_infill() const; - bool is_bridge() const; + bool is_perimeter() const { + return this->role == erPerimeter + || this->role == erExternalPerimeter + || this->role == erOverhangPerimeter; + }; + bool is_infill() const { + return this->role == erBridgeInfill + || this->role == erInternalInfill + || this->role == erSolidInfill + || this->role == erTopSolidInfill; + }; + bool is_solid_infill() const { + return this->role == erBridgeInfill + || this->role == erSolidInfill + || this->role == erTopSolidInfill; + }; + bool is_bridge() const { + return this->role == erBridgeInfill + || this->role == erOverhangPerimeter; + }; + // Produce a list of 2D polygons covered by the extruded path. Polygons grow() const; - double min_mm3_per_mm() const { - return this->mm3_per_mm; - }; - Polyline as_polyline() const { - return this->polyline; - }; + // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. + double min_mm3_per_mm() const { return this->mm3_per_mm; } + Polyline as_polyline() const { return this->polyline; } private: void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; @@ -109,31 +128,45 @@ class ExtrusionLoop : public ExtrusionEntity : role(role) { this->paths.push_back(path); }; - bool is_loop() const { - return true; - }; - bool can_reverse() const { - return false; - }; - ExtrusionLoop* clone() const; + bool is_loop() const { return true; } + bool can_reverse() const { return false; } + ExtrusionLoop* clone() const { return new ExtrusionLoop (*this); } bool make_clockwise(); bool make_counter_clockwise(); void reverse(); - Point first_point() const; - Point last_point() const; + Point first_point() const { return this->paths.front().polyline.points.front(); } + Point last_point() const { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); } Polygon polygon() const; virtual double length() const; bool split_at_vertex(const Point &point); void split_at(const Point &point); void clip_end(double distance, ExtrusionPaths* paths) const; + // Test, whether the point is extruded by a bridging flow. + // This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead. bool has_overhang_point(const Point &point) const; - bool is_perimeter() const; - bool is_infill() const; - bool is_solid_infill() const; + bool is_perimeter() const { + return this->paths.front().role == erPerimeter + || this->paths.front().role == erExternalPerimeter + || this->paths.front().role == erOverhangPerimeter; + }; + bool is_infill() const { + return this->paths.front().role == erBridgeInfill + || this->paths.front().role == erInternalInfill + || this->paths.front().role == erSolidInfill + || this->paths.front().role == erTopSolidInfill; + }; + bool is_solid_infill() const { + return this->paths.front().role == erBridgeInfill + || this->paths.front().role == erSolidInfill + || this->paths.front().role == erTopSolidInfill; + } + // Produce a list of 2D polygons covered by the extruded path. Polygons grow() const; + // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. double min_mm3_per_mm() const; - Polyline as_polyline() const { - return this->polygon().split_at_first_point(); + Polyline as_polyline() const { return this->polygon().split_at_first_point(); } + void append(const ExtrusionPath &path) { + this->paths.push_back(path); }; }; diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.cpp b/xs/src/libslic3r/ExtrusionEntityCollection.cpp index 31aa3d4c8..091cd3ff0 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.cpp @@ -33,9 +33,16 @@ ExtrusionEntityCollection::swap (ExtrusionEntityCollection &c) } ExtrusionEntityCollection::~ExtrusionEntityCollection() +{ + this->clear(); +} + +void +ExtrusionEntityCollection::clear() { for (ExtrusionEntitiesPtr::iterator it = this->entities.begin(); it != this->entities.end(); ++it) delete *it; + this->entities.clear(); } ExtrusionEntityCollection::operator ExtrusionPaths() const @@ -101,6 +108,17 @@ ExtrusionEntityCollection::append(const ExtrusionPaths &paths) this->append(*path); } +void +ExtrusionEntityCollection::append(const Polylines &polylines, const ExtrusionPath &templ) +{ + this->entities.reserve(this->entities.size() + polylines.size()); + for (Polylines::const_iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) { + ExtrusionPath *path = templ.clone(); + path->polyline = *it_polyline; + this->entities.push_back(path); + } +} + void ExtrusionEntityCollection::replace(size_t i, const ExtrusionEntity &entity) { @@ -181,10 +199,8 @@ Polygons ExtrusionEntityCollection::grow() const { Polygons pp; - for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { - Polygons entity_pp = (*it)->grow(); - pp.insert(pp.end(), entity_pp.begin(), entity_pp.end()); - } + for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) + append_to(pp, (*it)->grow()); return pp; } diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.hpp b/xs/src/libslic3r/ExtrusionEntityCollection.hpp index 7e44ccd18..c3a26df82 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.hpp @@ -3,6 +3,7 @@ #include "libslic3r.h" #include "ExtrusionEntity.hpp" +#include "Polyline.hpp" namespace Slic3r { @@ -29,13 +30,12 @@ class ExtrusionEntityCollection : public ExtrusionEntity bool empty() const { return this->entities.empty(); }; - void clear() { - this->entities.clear(); - }; + void clear(); void swap (ExtrusionEntityCollection &c); void append(const ExtrusionEntity &entity); void append(const ExtrusionEntitiesPtr &entities); void append(const ExtrusionPaths &paths); + void append(const Polylines &polylines, const ExtrusionPath &templ); void replace(size_t i, const ExtrusionEntity &entity); void remove(size_t i); ExtrusionEntityCollection chained_path(bool no_reverse = false, std::vector* orig_indices = NULL) const; diff --git a/xs/src/libslic3r/Fill/Fill.cpp b/xs/src/libslic3r/Fill/Fill.cpp new file mode 100644 index 000000000..c88a32514 --- /dev/null +++ b/xs/src/libslic3r/Fill/Fill.cpp @@ -0,0 +1,141 @@ +#include +#include + +#include "../ClipperUtils.hpp" +#include "../Surface.hpp" +#include "../PrintConfig.hpp" + +#include "Fill.hpp" +#include "FillConcentric.hpp" +#include "FillHoneycomb.hpp" +#include "Fill3DHoneycomb.hpp" +#include "FillPlanePath.hpp" +#include "FillRectilinear.hpp" + +namespace Slic3r { + +Fill* +Fill::new_from_type(const InfillPattern type) +{ + switch (type) { + case ipConcentric: return new FillConcentric(); + case ipHoneycomb: return new FillHoneycomb(); + case ip3DHoneycomb: return new Fill3DHoneycomb(); + + case ipRectilinear: return new FillRectilinear(); + case ipAlignedRectilinear: return new FillAlignedRectilinear(); + case ipGrid: return new FillGrid(); + + case ipTriangles: return new FillTriangles(); + case ipStars: return new FillStars(); + case ipCubic: return new FillCubic(); + + case ipArchimedeanChords: return new FillArchimedeanChords(); + case ipHilbertCurve: return new FillHilbertCurve(); + case ipOctagramSpiral: return new FillOctagramSpiral(); + + default: CONFESS("unknown type"); return NULL; + } +} + +Fill* +Fill::new_from_type(const std::string &type) +{ + static t_config_enum_values enum_keys_map = ConfigOptionEnum::get_enum_values(); + t_config_enum_values::const_iterator it = enum_keys_map.find(type); + return (it == enum_keys_map.end()) ? NULL : new_from_type(InfillPattern(it->second)); +} + +Polylines +Fill::fill_surface(const Surface &surface) +{ + if (this->density == 0) return Polylines(); + + // Perform offset. + ExPolygons expp = offset_ex(surface.expolygon, -scale_(this->min_spacing)/2); + + // Implementations can change this if they adjust the flow. + this->_spacing = this->min_spacing; + + // Create the infills for each of the regions. + Polylines polylines_out; + for (size_t i = 0; i < expp.size(); ++i) + this->_fill_surface_single( + surface.thickness_layers, + this->_infill_direction(surface), + expp[i], + &polylines_out + ); + return polylines_out; +} + +// Calculate a new spacing to fill width with possibly integer number of lines, +// the first and last line being centered at the interval ends. +// This function possibly increases the spacing, never decreases, +// and for a narrow width the increase in spacing may become severe, +// therefore the adjustment is limited to 20% increase. +coord_t +Fill::adjust_solid_spacing(const coord_t width, const coord_t distance) +{ + assert(width >= 0); + assert(distance > 0); + const int number_of_intervals = floor(width / distance); + if (number_of_intervals == 0) return distance; + + coord_t distance_new = (width / number_of_intervals); + + const coordf_t factor = coordf_t(distance_new) / coordf_t(distance); + assert(factor > 1. - 1e-5); + + // How much could the extrusion width be increased? By 20%. + // Because of this limit, this method is not idempotent: each run + // will increment distance by 20%. + const coordf_t factor_max = 1.2; + if (factor > factor_max) + distance_new = floor((double)distance * factor_max + 0.5); + + assert((distance_new * number_of_intervals) <= width); + + return distance_new; +} + +// Returns orientation of the infill and the reference point of the infill pattern. +// For a normal print, the reference point is the center of a bounding box of the STL. +Fill::direction_t +Fill::_infill_direction(const Surface &surface) const +{ + // set infill angle + float out_angle = this->angle; + + // Bounding box is the bounding box of a Slic3r::PrintObject + // The bounding box is only undefined in unit tests. + Point out_shift = this->bounding_box.defined + ? this->bounding_box.center() + : surface.expolygon.contour.bounding_box().center(); + + #if 0 + if (!this->bounding_box.defined) { + printf("Fill::_infill_direction: empty bounding box!"); + } else { + printf("Fill::_infill_direction: reference point %d, %d\n", out_shift.x, out_shift.y); + } + #endif + + if (surface.bridge_angle >= 0) { + // use bridge angle + //FIXME Vojtech: Add a debugf? + // Slic3r::debugf "Filling bridge with angle %d\n", rad2deg($surface->bridge_angle); + #ifdef SLIC3R_DEBUG + printf("Filling bridge with angle %f\n", surface.bridge_angle); + #endif + out_angle = surface.bridge_angle; + } else if (this->layer_id != size_t(-1)) { + // alternate fill direction + out_angle += this->_layer_angle(this->layer_id / surface.thickness_layers); + } + + out_angle += float(M_PI/2.); + return direction_t(out_angle, out_shift); +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/Fill.hpp b/xs/src/libslic3r/Fill/Fill.hpp new file mode 100644 index 000000000..abb9e1c65 --- /dev/null +++ b/xs/src/libslic3r/Fill/Fill.hpp @@ -0,0 +1,124 @@ +#ifndef slic3r_Fill_hpp_ +#define slic3r_Fill_hpp_ + +#include +#include +#include +#include + +#include "../libslic3r.h" +#include "../BoundingBox.hpp" +#include "../ExPolygon.hpp" +#include "../Polyline.hpp" +#include "../PrintConfig.hpp" + +namespace Slic3r { + +class Surface; + +// Abstract base class for the infill generators. +class Fill +{ +public: + // Index of the layer. + size_t layer_id; + + // Z coordinate of the top print surface, in unscaled coordinates + coordf_t z; + + // in unscaled coordinates + coordf_t min_spacing; + + // overlap over spacing for extrusion endpoints + float endpoints_overlap; + + // in radians, ccw, 0 = East + float angle; + + // In scaled coordinates. Maximum lenght of a perimeter segment connecting two infill lines. + // Used by the FillRectilinear2, FillGrid2, FillTriangles, FillStars and FillCubic. + // If left to zero, the links will not be limited. + coord_t link_max_length; + + // In scaled coordinates. Used by the concentric infill pattern to clip the loops to create extrusion paths. + coord_t loop_clipping; + + // In scaled coordinates. Bounding box of the 2D projection of the object. + // If not defined, the bounding box of each single expolygon is used. + BoundingBox bounding_box; + + // Fill density, fraction in <0, 1> + float density; + + // Don't connect the fill lines around the inner perimeter. + bool dont_connect; + + // Don't adjust spacing to fill the space evenly. + bool dont_adjust; + + // For Honeycomb. + // we were requested to complete each loop; + // in this case we don't try to make more continuous paths + bool complete; + +public: + static Fill* new_from_type(const InfillPattern type); + static Fill* new_from_type(const std::string &type); + static coord_t adjust_solid_spacing(const coord_t width, const coord_t distance); + virtual Fill* clone() const = 0; + virtual ~Fill() {}; + + // Implementations can override the following virtual methods: + // Use bridge flow for the fill? + virtual bool use_bridge_flow() const { return false; }; + + // Do not sort the fill lines to optimize the print head path? + virtual bool no_sort() const { return false; }; + + // Can this pattern be used for solid infill? + virtual bool can_solid() const { return false; }; + + // Perform the fill. + virtual Polylines fill_surface(const Surface &surface); + + coordf_t spacing() const { return this->_spacing; }; + +protected: + // the actual one in unscaled coordinates, we fill this while generating paths + coordf_t _spacing; + + Fill() : + layer_id(size_t(-1)), + z(0.f), + min_spacing(0.f), + endpoints_overlap(0.3f), + angle(0), + link_max_length(0), + loop_clipping(0), + density(0), + dont_connect(false), + dont_adjust(false), + complete(false), + _spacing(0.f) + {}; + + typedef std::pair direction_t; + + // The expolygon may be modified by the method to avoid a copy. + virtual void _fill_surface_single( + unsigned int thickness_layers, + const direction_t &direction, + ExPolygon &expolygon, + Polylines* polylines_out) {}; + + // Implementations can override the following virtual method: + virtual float _layer_angle(size_t idx) const { + return (idx % 2) == 0 ? (M_PI/2.) : 0; + }; + + direction_t _infill_direction(const Surface &surface) const; +}; + +} // namespace Slic3r + +#endif // slic3r_Fill_hpp_ diff --git a/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp new file mode 100644 index 000000000..4464cc877 --- /dev/null +++ b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -0,0 +1,226 @@ +#include "../ClipperUtils.hpp" +#include "../PolylineCollection.hpp" +#include "../Surface.hpp" + +#include "Fill3DHoneycomb.hpp" + +namespace Slic3r { + +/* +Creates a contiguous sequence of points at a specified height that make +up a horizontal slice of the edges of a space filling truncated +octahedron tesselation. The octahedrons are oriented so that the +square faces are in the horizontal plane with edges parallel to the X +and Y axes. + +Credits: David Eccles (gringer). +*/ + +// Generate an array of points that are in the same direction as the +// basic printing line (i.e. Y points for columns, X points for rows) +// Note: a negative offset only causes a change in the perpendicular +// direction +static std::vector +colinearPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength) +{ + const coordf_t offset2 = std::abs(offset / coordf_t(2.)); + std::vector points; + points.push_back(baseLocation - offset2); + for (size_t i = 0; i < gridLength; ++i) { + points.push_back(baseLocation + i + offset2); + points.push_back(baseLocation + i + 1 - offset2); + } + points.push_back(baseLocation + gridLength + offset2); + return points; +} + +// Generate an array of points for the dimension that is perpendicular to +// the basic printing line (i.e. X points for columns, Y points for rows) +static std::vector +perpendPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength) +{ + const coordf_t offset2 = offset / coordf_t(2.); + coord_t side = 2 * (baseLocation & 1) - 1; + std::vector points; + points.push_back(baseLocation - offset2 * side); + for (size_t i = 0; i < gridLength; ++i) { + side = 2*((i+baseLocation) & 1) - 1; + points.push_back(baseLocation + offset2 * side); + points.push_back(baseLocation + offset2 * side); + } + points.push_back(baseLocation - offset2 * side); + return points; +} + +template +static inline T +clamp(T low, T high, T x) +{ + return std::max(low, std::min(high, x)); +} + +// Trims an array of points to specified rectangular limits. Point +// components that are outside these limits are set to the limits. +static inline void +trim(Pointfs &pts, coordf_t minX, coordf_t minY, coordf_t maxX, coordf_t maxY) +{ + for (Pointfs::iterator it = pts.begin(); it != pts.end(); ++ it) { + it->x = clamp(minX, maxX, it->x); + it->y = clamp(minY, maxY, it->y); + } +} + +static inline Pointfs +zip(const std::vector &x, const std::vector &y) +{ + assert(x.size() == y.size()); + Pointfs out; + out.reserve(x.size()); + for (size_t i = 0; i < x.size(); ++ i) + out.push_back(Pointf(x[i], y[i])); + return out; +} + +// Generate a set of curves (array of array of 2d points) that describe a +// horizontal slice of a truncated regular octahedron with edge length 1. +// curveType specifies which lines to print, 1 for vertical lines +// (columns), 2 for horizontal lines (rows), and 3 for both. +static std::vector +makeNormalisedGrid(coordf_t z, size_t gridWidth, size_t gridHeight, size_t curveType) +{ + // offset required to create a regular octagram + coordf_t octagramGap = coordf_t(0.5); + + // sawtooth wave function for range f($z) = [-$octagramGap .. $octagramGap] + coordf_t a = std::sqrt(coordf_t(2.)); // period + coordf_t wave = fabs(fmod(z, a) - a/2.)/a*4. - 1.; + coordf_t offset = wave * octagramGap; + + std::vector points; + if ((curveType & 1) != 0) { + for (size_t x = 0; x <= gridWidth; ++x) { + points.push_back(Pointfs()); + Pointfs &newPoints = points.back(); + newPoints = zip( + perpendPoints(offset, x, gridHeight), + colinearPoints(offset, 0, gridHeight)); + // trim points to grid edges + trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight)); + if (x & 1) + std::reverse(newPoints.begin(), newPoints.end()); + } + } + if ((curveType & 2) != 0) { + for (size_t y = 0; y <= gridHeight; ++y) { + points.push_back(Pointfs()); + Pointfs &newPoints = points.back(); + newPoints = zip( + colinearPoints(offset, 0, gridWidth), + perpendPoints(offset, y, gridWidth) + ); + // trim points to grid edges + trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight)); + if (y & 1) + std::reverse(newPoints.begin(), newPoints.end()); + } + } + return points; +} + +// Generate a set of curves (array of array of 2d points) that describe a +// horizontal slice of a truncated regular octahedron with a specified +// grid square size. +static Polylines +makeGrid(coord_t z, coord_t gridSize, size_t gridWidth, size_t gridHeight, size_t curveType) +{ + coord_t scaleFactor = gridSize; + coordf_t normalisedZ = coordf_t(z) / coordf_t(scaleFactor); + std::vector polylines = makeNormalisedGrid(normalisedZ, gridWidth, gridHeight, curveType); + Polylines result; + result.reserve(polylines.size()); + for (std::vector::const_iterator it_polylines = polylines.begin(); it_polylines != polylines.end(); ++ it_polylines) { + result.push_back(Polyline()); + Polyline &polyline = result.back(); + for (Pointfs::const_iterator it = it_polylines->begin(); it != it_polylines->end(); ++ it) + polyline.points.push_back(Point(coord_t(it->x * scaleFactor), coord_t(it->y * scaleFactor))); + } + return result; +} + +void +Fill3DHoneycomb::_fill_surface_single( + unsigned int thickness_layers, + const direction_t &direction, + ExPolygon &expolygon, + Polylines* polylines_out) +{ + // no rotation is supported for this infill pattern + BoundingBox bb = expolygon.contour.bounding_box(); + const coord_t distance = coord_t(scale_(this->min_spacing) / this->density); + + // align bounding box to a multiple of our honeycomb grid module + // (a module is 2*$distance since one $distance half-module is + // growing while the other $distance half-module is shrinking) + bb.min.align_to_grid(Point(2*distance, 2*distance)); + + // generate pattern + Polylines polylines = makeGrid( + scale_(this->z), + distance, + ceil(bb.size().x / distance) + 1, + ceil(bb.size().y / distance) + 1, + ((this->layer_id/thickness_layers) % 2) + 1 + ); + + // move pattern in place + for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it) + it->translate(bb.min.x, bb.min.y); + + // clip pattern to boundaries + polylines = intersection_pl(polylines, (Polygons)expolygon); + + // connect lines + if (!this->dont_connect && !polylines.empty()) { // prevent calling leftmost_point() on empty collections + ExPolygon expolygon_off; + { + ExPolygons expolygons_off = offset_ex(expolygon, SCALED_EPSILON); + if (!expolygons_off.empty()) { + // When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island. + assert(expolygons_off.size() == 1); + std::swap(expolygon_off, expolygons_off.front()); + } + } + Polylines chained = PolylineCollection::chained_path_from( + STDMOVE(polylines), + PolylineCollection::leftmost_point(polylines), + false // reverse allowed + ); + bool first = true; + for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { + if (!first) { + // Try to connect the lines. + Points &pts_end = polylines_out->back().points; + const Point &first_point = it_polyline->points.front(); + const Point &last_point = pts_end.back(); + // TODO: we should also check that both points are on a fill_boundary to avoid + // connecting paths on the boundaries of internal regions + if (first_point.distance_to(last_point) <= 1.5 * distance && + expolygon_off.contains(Line(last_point, first_point))) { + // Append the polyline. + pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end()); + continue; + } + } + // The lines cannot be connected. + #if SLIC3R_CPPVER >= 11 + polylines_out->push_back(std::move(*it_polyline)); + #else + polylines_out->push_back(Polyline()); + std::swap(polylines_out->back(), *it_polyline); + #endif + first = false; + } + } +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/Fill3DHoneycomb.hpp b/xs/src/libslic3r/Fill/Fill3DHoneycomb.hpp new file mode 100644 index 000000000..43d43ab83 --- /dev/null +++ b/xs/src/libslic3r/Fill/Fill3DHoneycomb.hpp @@ -0,0 +1,31 @@ +#ifndef slic3r_Fill3DHoneycomb_hpp_ +#define slic3r_Fill3DHoneycomb_hpp_ + +#include + +#include "../libslic3r.h" + +#include "Fill.hpp" + +namespace Slic3r { + +class Fill3DHoneycomb : public Fill +{ +public: + virtual Fill* clone() const { return new Fill3DHoneycomb(*this); }; + virtual ~Fill3DHoneycomb() {} + + // require bridge flow since most of this pattern hangs in air + virtual bool use_bridge_flow() const { return true; } + +protected: + virtual void _fill_surface_single( + unsigned int thickness_layers, + const direction_t &direction, + ExPolygon &expolygon, + Polylines* polylines_out); +}; + +} // namespace Slic3r + +#endif // slic3r_Fill3DHoneycomb_hpp_ diff --git a/xs/src/libslic3r/Fill/FillConcentric.cpp b/xs/src/libslic3r/Fill/FillConcentric.cpp new file mode 100644 index 000000000..24d8f318a --- /dev/null +++ b/xs/src/libslic3r/Fill/FillConcentric.cpp @@ -0,0 +1,62 @@ +#include "../ClipperUtils.hpp" +#include "../ExPolygon.hpp" +#include "../Surface.hpp" + +#include "FillConcentric.hpp" + +namespace Slic3r { + +void +FillConcentric::_fill_surface_single( + unsigned int thickness_layers, + const direction_t &direction, + ExPolygon &expolygon, + Polylines* polylines_out) +{ + // no rotation is supported for this infill pattern + + const coord_t min_spacing = scale_(this->min_spacing); + coord_t distance = coord_t(min_spacing / this->density); + + if (this->density > 0.9999f && !this->dont_adjust) { + BoundingBox bounding_box = expolygon.contour.bounding_box(); + distance = this->adjust_solid_spacing(bounding_box.size().x, distance); + this->_spacing = unscale(distance); + } + + Polygons loops = (Polygons)expolygon; + Polygons last = loops; + while (!last.empty()) { + last = offset2(last, -(distance + min_spacing/2), +min_spacing/2); + append_to(loops, last); + } + + // generate paths from the outermost to the innermost, to avoid + // adhesion problems of the first central tiny loops + loops = union_pt_chained(loops, false); + + // split paths using a nearest neighbor search + const size_t iPathFirst = polylines_out->size(); + Point last_pos(0, 0); + for (Polygons::const_iterator it_loop = loops.begin(); it_loop != loops.end(); ++ it_loop) { + polylines_out->push_back(it_loop->split_at_index(last_pos.nearest_point_index(*it_loop))); + last_pos = polylines_out->back().last_point(); + } + + // clip the paths to prevent the extruder from getting exactly on the first point of the loop + // Keep valid paths only. + size_t j = iPathFirst; + for (size_t i = iPathFirst; i < polylines_out->size(); ++i) { + (*polylines_out)[i].clip_end(this->loop_clipping); + if ((*polylines_out)[i].is_valid()) { + if (j < i) + std::swap((*polylines_out)[j], (*polylines_out)[i]); + ++j; + } + } + if (j < polylines_out->size()) + polylines_out->erase(polylines_out->begin() + j, polylines_out->end()); + // TODO: return ExtrusionLoop objects to get better chained paths +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillConcentric.hpp b/xs/src/libslic3r/Fill/FillConcentric.hpp new file mode 100644 index 000000000..bc973b757 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillConcentric.hpp @@ -0,0 +1,27 @@ +#ifndef slic3r_FillConcentric_hpp_ +#define slic3r_FillConcentric_hpp_ + +#include "Fill.hpp" + +namespace Slic3r { + +class FillConcentric : public Fill +{ +public: + virtual ~FillConcentric() {} + +protected: + virtual Fill* clone() const { return new FillConcentric(*this); }; + virtual void _fill_surface_single( + unsigned int thickness_layers, + const direction_t &direction, + ExPolygon &expolygon, + Polylines* polylines_out); + + virtual bool no_sort() const { return true; } + virtual bool can_solid() const { return true; }; +}; + +} // namespace Slic3r + +#endif // slic3r_FillConcentric_hpp_ diff --git a/xs/src/libslic3r/Fill/FillHoneycomb.cpp b/xs/src/libslic3r/Fill/FillHoneycomb.cpp new file mode 100644 index 000000000..02198bcd7 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillHoneycomb.cpp @@ -0,0 +1,125 @@ +#include "FillHoneycomb.hpp" +#include "../ClipperUtils.hpp" +#include "../PolylineCollection.hpp" +#include "../Surface.hpp" + + +namespace Slic3r { + +void +FillHoneycomb::_fill_surface_single( + unsigned int thickness_layers, + const direction_t &direction, + ExPolygon &expolygon, + Polylines* polylines_out) +{ + // cache hexagons math + CacheID cache_id = std::make_pair(this->density, this->min_spacing); + Cache::iterator it_m = this->cache.find(cache_id); + if (it_m == this->cache.end()) { + it_m = this->cache.insert(it_m, std::pair(cache_id, CacheData())); + CacheData &m = it_m->second; + coord_t min_spacing = scale_(this->min_spacing); + m.distance = min_spacing / this->density; + m.hex_side = m.distance / (sqrt(3)/2); + m.hex_width = m.distance * 2; // $m->{hex_width} == $m->{hex_side} * sqrt(3); + coord_t hex_height = m.hex_side * 2; + m.pattern_height = hex_height + m.hex_side; + m.y_short = m.distance * sqrt(3)/3; + m.x_offset = min_spacing / 2; + m.y_offset = m.x_offset * sqrt(3)/3; + m.hex_center = Point(m.hex_width/2, m.hex_side); + } + CacheData &m = it_m->second; + + Polygons polygons; + { + // adjust actual bounding box to the nearest multiple of our hex pattern + // and align it so that it matches across layers + + BoundingBox bounding_box = expolygon.contour.bounding_box(); + { + // rotate bounding box according to infill direction + Polygon bb_polygon = bounding_box.polygon(); + bb_polygon.rotate(direction.first, m.hex_center); + bounding_box = bb_polygon.bounding_box(); + + // extend bounding box so that our pattern will be aligned with other layers + // $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one + // The infill is not aligned to the object bounding box, but to a world coordinate system. Supposedly good enough. + bounding_box.min.align_to_grid(Point(m.hex_width, m.pattern_height)); + } + + for (coord_t x = bounding_box.min.x; x <= bounding_box.max.x; ) { + Polygon p; + coord_t ax[2] = { x + m.x_offset, x + m.distance - m.x_offset }; + for (size_t i = 0; i < 2; ++ i) { + std::reverse(p.points.begin(), p.points.end()); // turn first half upside down + for (coord_t y = bounding_box.min.y; y <= bounding_box.max.y; y += m.y_short + m.hex_side + m.y_short + m.hex_side) { + p.points.push_back(Point(ax[1], y + m.y_offset)); + p.points.push_back(Point(ax[0], y + m.y_short - m.y_offset)); + p.points.push_back(Point(ax[0], y + m.y_short + m.hex_side + m.y_offset)); + p.points.push_back(Point(ax[1], y + m.y_short + m.hex_side + m.y_short - m.y_offset)); + p.points.push_back(Point(ax[1], y + m.y_short + m.hex_side + m.y_short + m.hex_side + m.y_offset)); + } + ax[0] = ax[0] + m.distance; + ax[1] = ax[1] + m.distance; + std::swap(ax[0], ax[1]); // draw symmetrical pattern + x += m.distance; + } + p.rotate(-direction.first, m.hex_center); + polygons.push_back(p); + } + } + + if (true || this->complete) { + // we were requested to complete each loop; + // in this case we don't try to make more continuous paths + Polygons polygons_trimmed = intersection((Polygons)expolygon, polygons); + for (Polygons::iterator it = polygons_trimmed.begin(); it != polygons_trimmed.end(); ++ it) + polylines_out->push_back(it->split_at_first_point()); + } else { + // consider polygons as polylines without re-appending the initial point: + // this cuts the last segment on purpose, so that the jump to the next + // path is more straight + Polylines paths = intersection_pl( + to_polylines(polygons), + (Polygons)expolygon + ); + + // connect paths + if (!paths.empty()) { // prevent calling leftmost_point() on empty collections + Polylines chained = PolylineCollection::chained_path_from( + STDMOVE(paths), + PolylineCollection::leftmost_point(paths), + false + ); + assert(paths.empty()); + paths.clear(); + + for (Polylines::iterator it_path = chained.begin(); it_path != chained.end(); ++ it_path) { + if (!paths.empty()) { + // distance between first point of this path and last point of last path + double distance = paths.back().last_point().distance_to(it_path->first_point()); + if (distance <= m.hex_width) { + paths.back().points.insert(paths.back().points.end(), it_path->points.begin(), it_path->points.end()); + continue; + } + } + // Don't connect the paths. + paths.push_back(*it_path); + } + } + + // clip paths again to prevent connection segments from crossing the expolygon boundaries + paths = intersection_pl(paths, to_polygons(offset_ex(expolygon, SCALED_EPSILON))); + + // Move the polylines to the output, avoid a deep copy. + size_t j = polylines_out->size(); + polylines_out->resize(j + paths.size(), Polyline()); + for (size_t i = 0; i < paths.size(); ++ i) + std::swap((*polylines_out)[j++], paths[i]); + } +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillHoneycomb.hpp b/xs/src/libslic3r/Fill/FillHoneycomb.hpp new file mode 100644 index 000000000..b048a3b2f --- /dev/null +++ b/xs/src/libslic3r/Fill/FillHoneycomb.hpp @@ -0,0 +1,47 @@ +#ifndef slic3r_FillHoneycomb_hpp_ +#define slic3r_FillHoneycomb_hpp_ + +#include + +#include "../libslic3r.h" + +#include "Fill.hpp" + +namespace Slic3r { + +class FillHoneycomb : public Fill +{ +public: + virtual ~FillHoneycomb() {} + +protected: + virtual Fill* clone() const { return new FillHoneycomb(*this); }; + virtual void _fill_surface_single( + unsigned int thickness_layers, + const direction_t &direction, + ExPolygon &expolygon, + Polylines* polylines_out + ); + + // Cache the hexagon math. + struct CacheData + { + coord_t distance; + coord_t hex_side; + coord_t hex_width; + coord_t pattern_height; + coord_t y_short; + coord_t x_offset; + coord_t y_offset; + Point hex_center; + }; + typedef std::pair CacheID; // density, spacing + typedef std::map Cache; + Cache cache; + + virtual float _layer_angle(size_t idx) const { return float(M_PI/3.) * (idx % 3); } +}; + +} // namespace Slic3r + +#endif // slic3r_FillHoneycomb_hpp_ diff --git a/xs/src/libslic3r/Fill/FillPlanePath.cpp b/xs/src/libslic3r/Fill/FillPlanePath.cpp new file mode 100644 index 000000000..2b78f24e1 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillPlanePath.cpp @@ -0,0 +1,207 @@ +#include "../ClipperUtils.hpp" +#include "../PolylineCollection.hpp" +#include "../Surface.hpp" + +#include "FillPlanePath.hpp" + +namespace Slic3r { + +void FillPlanePath::_fill_surface_single( + unsigned int thickness_layers, + const direction_t &direction, + ExPolygon &expolygon, + Polylines* polylines_out) +{ + expolygon.rotate(-direction.first); + + const coord_t distance_between_lines = scale_(this->min_spacing) / this->density; + + // align infill across layers using the object's bounding box (if available) + BoundingBox bounding_box = this->bounding_box.defined + ? this->bounding_box + : expolygon.contour.bounding_box(); + bounding_box = bounding_box.rotated(-direction.first); + + const Point shift = this->_centered() + ? bounding_box.center() + : bounding_box.min; + expolygon.translate(-shift.x, -shift.y); + bounding_box.translate(-shift.x, -shift.y); + + const Pointfs pts = this->_generate( + coord_t(ceil(coordf_t(bounding_box.min.x) / distance_between_lines)), + coord_t(ceil(coordf_t(bounding_box.min.y) / distance_between_lines)), + coord_t(ceil(coordf_t(bounding_box.max.x) / distance_between_lines)), + coord_t(ceil(coordf_t(bounding_box.max.y) / distance_between_lines)) + ); + + Polylines polylines; + if (pts.size() >= 2) { + // Convert points to a polyline, upscale. + polylines.push_back(Polyline()); + Polyline &polyline = polylines.back(); + polyline.points.reserve(pts.size()); + for (Pointfs::const_iterator it = pts.begin(); it != pts.end(); ++ it) { + polyline.points.push_back(Point( + coord_t(floor(it->x * distance_between_lines + 0.5)), + coord_t(floor(it->y * distance_between_lines + 0.5)) + )); + } +// polylines = intersection_pl(polylines_src, offset((Polygons)expolygon, scale_(0.02))); + polylines = intersection_pl(polylines, (Polygons)expolygon); + +/* + if (1) { + require "Slic3r/SVG.pm"; + print "Writing fill.svg\n"; + Slic3r::SVG::output("fill.svg", + no_arrows => 1, + polygons => \@$expolygon, + green_polygons => [ $bounding_box->polygon ], + polylines => [ $polyline ], + red_polylines => \@paths, + ); + } +*/ + + // paths must be repositioned and rotated back + for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it) { + it->translate(shift.x, shift.y); + it->rotate(direction.first); + } + } + + // Move the polylines to the output, avoid a deep copy. + size_t j = polylines_out->size(); + polylines_out->resize(j + polylines.size(), Polyline()); + for (size_t i = 0; i < polylines.size(); ++ i) + std::swap((*polylines_out)[j++], polylines[i]); +} + +// Follow an Archimedean spiral, in polar coordinates: r=a+b\theta +Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) +{ + // Radius to achieve. + const coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5; + // Now unwind the spiral. + const coordf_t a = 1.; + const coordf_t b = 1./(2.*M_PI); + coordf_t theta = 0.; + coordf_t r = 1; + Pointfs out; + //FIXME Vojtech: If used as a solid infill, there is a gap left at the center. + out.push_back(Pointf(0, 0)); + out.push_back(Pointf(1, 0)); + while (r < rmax) { + theta += 1. / r; + r = a + b * theta; + out.push_back(Pointf(r * cos(theta), r * sin(theta))); + } + return out; +} + +// Adapted from +// http://cpansearch.perl.org/src/KRYDE/Math-PlanePath-122/lib/Math/PlanePath/HilbertCurve.pm +// +// state=0 3--2 plain +// | +// 0--1 +// +// state=4 1--2 transpose +// | | +// 0 3 +// +// state=8 +// +// state=12 3 0 rot180 + transpose +// | | +// 2--1 +// +static inline Point hilbert_n_to_xy(const size_t n) +{ + static const int next_state[16] = { 4,0,0,12, 0,4,4,8, 12,8,8,4, 8,12,12,0 }; + static const int digit_to_x[16] = { 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0 }; + static const int digit_to_y[16] = { 0,0,1,1, 0,1,1,0, 1,1,0,0, 1,0,0,1 }; + + // Number of 2 bit digits. + size_t ndigits = 0; + { + size_t nc = n; + while(nc > 0) { + nc >>= 2; + ++ ndigits; + } + } + int state = (ndigits & 1) ? 4 : 0; + int dirstate = (ndigits & 1) ? 0 : 4; + coord_t x = 0; + coord_t y = 0; + for (int i = (int)ndigits - 1; i >= 0; -- i) { + int digit = (n >> (i * 2)) & 3; + state += digit; + if (digit != 3) + dirstate = state; // lowest non-3 digit + x |= digit_to_x[state] << i; + y |= digit_to_y[state] << i; + state = next_state[state]; + } + return Point(x, y); +} + +Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) +{ + // Minimum power of two square to fit the domain. + size_t sz = 2; + size_t pw = 1; + { + size_t sz0 = std::max(max_x + 1 - min_x, max_y + 1 - min_y); + while (sz < sz0) { + sz = sz << 1; + ++pw; + } + } + + const size_t sz2 = sz * sz; + Pointfs line; + line.reserve(sz2); + for (size_t i = 0; i < sz2; ++ i) { + Point p = hilbert_n_to_xy(i); + line.push_back(Pointf(p.x + min_x, p.y + min_y)); + } + return line; +} + +Pointfs FillOctagramSpiral::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) +{ + // Radius to achieve. + const coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5; + // Now unwind the spiral. + coordf_t r = 0; + const coordf_t r_inc = sqrt(2.); + Pointfs out; + out.push_back(Pointf(0, 0)); + while (r < rmax) { + r += r_inc; + coordf_t rx = r / sqrt(2.); + coordf_t r2 = r + rx; + out.push_back(Pointf( r, 0.)); + out.push_back(Pointf( r2, rx)); + out.push_back(Pointf( rx, rx)); + out.push_back(Pointf( rx, r2)); + out.push_back(Pointf(0., r)); + out.push_back(Pointf(-rx, r2)); + out.push_back(Pointf(-rx, rx)); + out.push_back(Pointf(-r2, rx)); + out.push_back(Pointf(-r, 0.)); + out.push_back(Pointf(-r2, -rx)); + out.push_back(Pointf(-rx, -rx)); + out.push_back(Pointf(-rx, -r2)); + out.push_back(Pointf(0., -r)); + out.push_back(Pointf( rx, -r2)); + out.push_back(Pointf( rx, -rx)); + out.push_back(Pointf( r2+r_inc, -rx)); + } + return out; +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillPlanePath.hpp b/xs/src/libslic3r/Fill/FillPlanePath.hpp new file mode 100644 index 000000000..7e308aac5 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillPlanePath.hpp @@ -0,0 +1,68 @@ +#ifndef slic3r_FillPlanePath_hpp_ +#define slic3r_FillPlanePath_hpp_ + +#include + +#include "../libslic3r.h" + +#include "Fill.hpp" + +namespace Slic3r { + +// The original Perl code used path generators from Math::PlanePath library: +// http://user42.tuxfamily.org/math-planepath/ +// http://user42.tuxfamily.org/math-planepath/gallery.html + +class FillPlanePath : public Fill +{ +public: + virtual ~FillPlanePath() {} + +protected: + virtual void _fill_surface_single( + unsigned int thickness_layers, + const direction_t &direction, + ExPolygon &expolygon, + Polylines* polylines_out); + + virtual float _layer_angle(size_t idx) const { return 0.f; } + virtual bool _centered() const = 0; + virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) = 0; +}; + +class FillArchimedeanChords : public FillPlanePath +{ +public: + virtual Fill* clone() const { return new FillArchimedeanChords(*this); }; + virtual ~FillArchimedeanChords() {} + +protected: + virtual bool _centered() const { return true; } + virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y); +}; + +class FillHilbertCurve : public FillPlanePath +{ +public: + virtual Fill* clone() const { return new FillHilbertCurve(*this); }; + virtual ~FillHilbertCurve() {} + +protected: + virtual bool _centered() const { return false; } + virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y); +}; + +class FillOctagramSpiral : public FillPlanePath +{ +public: + virtual Fill* clone() const { return new FillOctagramSpiral(*this); }; + virtual ~FillOctagramSpiral() {} + +protected: + virtual bool _centered() const { return true; } + virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y); +}; + +} // namespace Slic3r + +#endif // slic3r_FillPlanePath_hpp_ diff --git a/xs/src/libslic3r/Fill/FillRectilinear.cpp b/xs/src/libslic3r/Fill/FillRectilinear.cpp new file mode 100644 index 000000000..69991db74 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillRectilinear.cpp @@ -0,0 +1,481 @@ +#include "../ClipperUtils.hpp" +#include "../ExPolygon.hpp" +#include "../PolylineCollection.hpp" +#include "../Surface.hpp" +#include +#include + +#include "FillRectilinear.hpp" + +//#define DEBUG_RECTILINEAR +#ifdef DEBUG_RECTILINEAR + #include "../SVG.hpp" +#endif + +namespace Slic3r { + +void +FillRectilinear::_fill_single_direction(ExPolygon expolygon, + const direction_t &direction, coord_t x_shift, Polylines* out) +{ + // rotate polygons so that we can work with vertical lines here + expolygon.rotate(-direction.first); + + assert(this->density > 0.0001f && this->density <= 1.f); + const coord_t min_spacing = scale_(this->min_spacing); + coord_t line_spacing = (double) min_spacing / this->density; + + // We ignore this->bounding_box because it doesn't matter; we're doing align_to_grid below. + BoundingBox bounding_box = expolygon.contour.bounding_box(); + + // Due to integer rounding, rotated polygons might not preserve verticality + // (i.e. when rotating by PI/2 two points having the same x coordinate + // they might get different y coordinates), thus the first line will be skipped. + bounding_box.offset(-1); + + // define flow spacing according to requested density + if (this->density > 0.9999f && !this->dont_adjust) { + line_spacing = this->adjust_solid_spacing(bounding_box.size().x, line_spacing); + this->_spacing = unscale(line_spacing); + } else { + // extend bounding box so that our pattern will be aligned with other layers + // Transform the reference point to the rotated coordinate system. + Point p = direction.second.rotated(-direction.first); + p.x -= x_shift >= 0 ? x_shift : (x_shift + line_spacing); + bounding_box.min.align_to_grid( + Point(line_spacing, line_spacing), + p + ); + } + + // Find all the polygons points intersecting the rectilinear vertical lines and store + // them in an std::map<> (grid) which orders them automatically by x and y. + // For each intersection point we store its position (upper/lower): upper means it's + // the upper endpoint of an intersection line, and vice versa. + // Whenever between two intersection points we find vertices of the original polygon, + // store them in the 'skipped' member of the latter point. + + grid_t grid; + { + const Polygons polygons = expolygon; + for (Polygons::const_iterator polygon = polygons.begin(); polygon != polygons.end(); ++polygon) { + const Points &points = polygon->points; + + // This vector holds the original polygon vertices found after the last intersection + // point. We'll flush it as soon as we find the next intersection point. + Points skipped_points; + + // This vector holds the coordinates of the intersection points found while + // looping through the polygon. + Points ips; + + for (Points::const_iterator p = points.begin(); p != points.end(); ++p) { + const Point &prev = p == points.begin() ? *(points.end()-1) : *(p-1); + const Point &next = p == points.end()-1 ? *points.begin() : *(p+1); + + // Does the p-next line belong to an intersection line? + if (p->x == next.x && ((p->x - bounding_box.min.x) % line_spacing) == 0) { + if (p->y == next.y) continue; // skip coinciding points + vertical_t &v = grid[p->x]; + + // Detect line direction. + IntersectionPoint::ipType p_type = IntersectionPoint::ipTypeLower; + IntersectionPoint::ipType n_type = IntersectionPoint::ipTypeUpper; + if (p->y > next.y) std::swap(p_type, n_type); // line goes downwards + + // Do we already have 'p' in our grid? + vertical_t::iterator pit = v.find(p->y); + if (pit != v.end()) { + // Yes, we have it. If its not of the same type, it means it's + // an intermediate point of a longer line. We store this information + // for now and we'll remove it later. + if (pit->second.type != p_type) + pit->second.type = IntersectionPoint::ipTypeMiddle; + } else { + // Store the point. + IntersectionPoint ip(p->x, p->y, p_type); + v[p->y] = ip; + ips.push_back(ip); + } + + // Do we already have 'next' in our grid? + pit = v.find(next.y); + if (pit != v.end()) { + // Yes, we have it. If its not of the same type, it means it's + // an intermediate point of a longer line. We store this information + // for now and we'll remove it later. + if (pit->second.type != n_type) + pit->second.type = IntersectionPoint::ipTypeMiddle; + } else { + // Store the point. + IntersectionPoint ip(next.x, next.y, n_type); + v[next.y] = ip; + ips.push_back(ip); + } + continue; + } + + // We're going to look for intersection points within this line. + // First, let's sort its x coordinates regardless of the original line direction. + const coord_t min_x = std::min(p->x, next.x); + const coord_t max_x = std::max(p->x, next.x); + + // Now find the leftmost intersection point belonging to the line. + const coord_t min_x2 = bounding_box.min.x + ceil((double) (min_x - bounding_box.min.x) / (double)line_spacing) * (double)line_spacing; + assert(min_x2 >= min_x); + + // In case this coordinate does not belong to this line, we have no intersection points. + if (min_x2 > max_x) { + // Store the two skipped points and move on. + skipped_points.push_back(*p); + skipped_points.push_back(next); + continue; + } + + // Find the rightmost intersection point belonging to the line. + const coord_t max_x2 = bounding_box.min.x + floor((double) (max_x - bounding_box.min.x) / (double) line_spacing) * (double)line_spacing; + assert(max_x2 <= max_x); + + // We're now going past the first point, so save it. + const bool line_goes_right = next.x > p->x; + if (line_goes_right ? (p->x < min_x2) : (p->x > max_x2)) + skipped_points.push_back(*p); + + // Now loop through those intersection points according the original direction + // of the line (because we need to store them in this order). + for (coord_t x = line_goes_right ? min_x2 : max_x2; + x >= min_x && x <= max_x; + x += line_goes_right ? +line_spacing : -line_spacing) { + + // Is this intersection an endpoint of the original line *and* is the + // intersection just a tangent point? If so, just skip it. + if (x == p->x && ((prev.x > x && next.x > x) || (prev.x < x && next.x < x))) { + skipped_points.push_back(*p); + continue; + } + if (x == next.x) { + const Point &next2 = p == (points.end()-2) ? *points.begin() + : p == (points.end()-1) ? *(points.begin()+1) : *(p+2); + if ((p->x > x && next2.x > x) || (p->x < x && next2.x < x)) { + skipped_points.push_back(next); + continue; + } + } + + // Calculate the y coordinate of this intersection. + IntersectionPoint ip( + x, + p->y + double(next.y - p->y) * double(x - p->x) / double(next.x - p->x), + line_goes_right ? IntersectionPoint::ipTypeLower : IntersectionPoint::ipTypeUpper + ); + vertical_t &v = grid[ip.x]; + + // Did we already find this point? + // (We might have found it as the endpoint of a vertical line.) + { + vertical_t::iterator pit = v.find(ip.y); + if (pit != v.end()) { + // Yes, we have it. If its not of the same type, it means it's + // an intermediate point of a longer line. We store this information + // for now and we'll remove it later. + if (pit->second.type != ip.type) + pit->second.type = IntersectionPoint::ipTypeMiddle; + continue; + } + } + + // Store the skipped polygon vertices along with this point. + ip.skipped = skipped_points; + skipped_points.clear(); + + #ifdef DEBUG_RECTILINEAR + printf("NEW POINT at %f,%f\n", unscale(ip.x), unscale(ip.y)); + for (Points::const_iterator it = ip.skipped.begin(); it != ip.skipped.end(); ++it) + printf(" skipped: %f,%f\n", unscale(it->x), unscale(it->y)); + #endif + + // Store the point. + v[ip.y] = ip; + ips.push_back(ip); + } + + // We're now going past the final point, so save it. + if (line_goes_right ? (next.x > max_x2) : (next.x < min_x2)) + skipped_points.push_back(next); + } + + if (!this->dont_connect) { + // We'll now build connections between the vertical intersection lines. + // Each intersection point will be connected to the first intersection point + // found along the original polygon having a greater x coordinate (or the same + // x coordinate: think about two vertical intersection lines having the same x + // separated by a hole polygon: we'll connect them with the hole portion). + // We will sweep only from left to right, so we only need to build connections + // in this direction. + for (Points::const_iterator it = ips.begin(); it != ips.end(); ++it) { + IntersectionPoint &ip = grid[it->x][it->y]; + IntersectionPoint &next = it == ips.end()-1 ? grid[ips.begin()->x][ips.begin()->y] : grid[(it+1)->x][(it+1)->y]; + + #ifdef DEBUG_RECTILINEAR + printf("CONNECTING %f,%f to %f,%f\n", + unscale(ip.x), unscale(ip.y), + unscale(next.x), unscale(next.y) + ); + #endif + + // We didn't flush the skipped_points vector after completing the loop above: + // it now contains the polygon vertices between the last and the first + // intersection points. + if (it == ips.begin()) + ip.skipped.insert(ip.skipped.begin(), skipped_points.begin(), skipped_points.end()); + + if (ip.x <= next.x) { + // Link 'ip' to 'next' ---> + if (ip.next.empty()) { + ip.next = next.skipped; + ip.next.push_back(next); + } + } else if (next.x < ip.x) { + // Link 'next' to 'ip' ---> + if (next.next.empty()) { + next.next = next.skipped; + std::reverse(next.next.begin(), next.next.end()); + next.next.push_back(ip); + } + } + } + } + + // Do some cleanup: remove the 'skipped' points we used for building + // connections and also remove the middle intersection points. + for (Points::const_iterator it = ips.begin(); it != ips.end(); ++it) { + vertical_t &v = grid[it->x]; + IntersectionPoint &ip = v[it->y]; + ip.skipped.clear(); + if (ip.type == IntersectionPoint::ipTypeMiddle) + v.erase(it->y); + } + } + } + + #ifdef DEBUG_RECTILINEAR + SVG svg("grid.svg"); + svg.draw(expolygon); + + printf("GRID:\n"); + for (grid_t::const_iterator it = grid.begin(); it != grid.end(); ++it) { + printf("x = %f:\n", unscale(it->first)); + for (vertical_t::const_iterator v = it->second.begin(); v != it->second.end(); ++v) { + const IntersectionPoint &ip = v->second; + printf(" y = %f (%s, next = %f,%f, extra = %zu)\n", unscale(v->first), + ip.type == IntersectionPoint::ipTypeLower ? "lower" + : ip.type == IntersectionPoint::ipTypeMiddle ? "middle" : "upper", + (ip.next.empty() ? -1 : unscale(ip.next.back().x)), + (ip.next.empty() ? -1 : unscale(ip.next.back().y)), + (ip.next.empty() ? 0 : ip.next.size()-1) + ); + svg.draw(ip, ip.type == IntersectionPoint::ipTypeLower ? "blue" + : ip.type == IntersectionPoint::ipTypeMiddle ? "yellow" : "red"); + } + } + printf("\n"); + + svg.Close(); + #endif + + // Store the number of polygons already existing in the output container. + const size_t n_polylines_out_old = out->size(); + + // Loop until we have no more vertical lines available. + while (!grid.empty()) { + // Get the first x coordinate. + vertical_t &v = grid.begin()->second; + + // If this x coordinate does not have any y coordinate, remove it. + if (v.empty()) { + grid.erase(grid.begin()); + continue; + } + + // We expect every x coordinate to contain an even number of y coordinates + // because they are the endpoints of vertical intersection lines: + // lower/upper, lower/upper etc. + assert(v.size() % 2 == 0); + + // Get the first lower point. + vertical_t::iterator it = v.begin(); // minimum x,y + IntersectionPoint p = it->second; + assert(p.type == IntersectionPoint::ipTypeLower); + + // Start our polyline. + Polyline polyline; + polyline.append(p); + polyline.points.back().y -= this->endpoints_overlap; + + while (true) { + // Complete the vertical line by finding the corresponding upper or lower point. + if (p.type == IntersectionPoint::ipTypeUpper) { + // find first point along c.x with y < c.y + assert(it != grid[p.x].begin()); + --it; + } else { + // find first point along c.x with y > c.y + ++it; + assert(it != grid[p.x].end()); + } + + // Append the point to our polyline. + IntersectionPoint b = it->second; + assert(b.type != p.type); + polyline.append(b); + polyline.points.back().y += this->endpoints_overlap * (b.type == IntersectionPoint::ipTypeUpper ? 1 : -1); + + // Remove the two endpoints of this vertical line from the grid. + { + vertical_t &v = grid[p.x]; + v.erase(p.y); + v.erase(it); + if (v.empty()) grid.erase(p.x); + } + // Do we have a connection starting from here? + // If not, stop the polyline. + if (b.next.empty()) + break; + + // If we have a connection, append it to the polyline. + // We apply the y extension to the whole connection line. This works well when + // the connection is straight and horizontal, but doesn't work well when the + // connection is articulated and also has vertical parts. + { + // TODO: here's where we should check for overextrusion. We should only add + // connection points while they are not generating vertical lines within the + // extrusion thickness of the main vertical lines. We should also check whether + // a previous run of this method occupied this polygon portion (derived infill + // patterns doing multiple runs at different angles generate overlapping connections). + // In both cases, we should just stop the connection and break the polyline here. + const size_t n = polyline.points.size(); + polyline.append(b.next); + for (Points::iterator pit = polyline.points.begin()+n; pit != polyline.points.end(); ++pit) + pit->y += this->endpoints_overlap * (b.type == IntersectionPoint::ipTypeUpper ? 1 : -1); + } + + // Is the final point still available? + if (grid.count(b.next.back().x) == 0 + || grid[b.next.back().x].count(b.next.back().y) == 0) + // We already used this point or we might have removed this + // point while building the grid because it's collinear (middle); in either + // cases the connection line from the previous one is legit and worth having. + break; + + // Retrieve the intersection point. The next loop will find the correspondent + // endpoint of the vertical line. + it = grid[ b.next.back().x ].find(b.next.back().y); + p = it->second; + + // If the connection brought us to another x coordinate, we expect the point + // type to be the same. + assert((p.type == b.type && p.x > b.x) + || (p.type != b.type && p.x == b.x)); + } + + // Yay, we have a polyline! + if (polyline.is_valid()) + out->push_back(polyline); + } + + // paths must be rotated back + for (Polylines::iterator it = out->begin() + n_polylines_out_old; + it != out->end(); ++it) + it->rotate(direction.first); +} + +void FillRectilinear::_fill_surface_single( + unsigned int thickness_layers, + const direction_t &direction, + ExPolygon &expolygon, + Polylines* out) +{ + this->_fill_single_direction(expolygon, direction, 0, out); +} + +void FillGrid::_fill_surface_single( + unsigned int thickness_layers, + const direction_t &direction, + ExPolygon &expolygon, + Polylines* out) +{ + FillGrid fill2 = *this; + fill2.density /= 2.; + + direction_t direction2 = direction; + direction2.first += PI/2; + fill2._fill_single_direction(expolygon, direction, 0, out); + fill2.dont_connect = true; + fill2._fill_single_direction(expolygon, direction2, 0, out); +} + +void FillTriangles::_fill_surface_single( + unsigned int thickness_layers, + const direction_t &direction, + ExPolygon &expolygon, + Polylines* out) +{ + FillTriangles fill2 = *this; + fill2.density /= 3.; + direction_t direction2 = direction; + + fill2._fill_single_direction(expolygon, direction2, 0, out); + + fill2.dont_connect = true; + direction2.first += PI/3; + fill2._fill_single_direction(expolygon, direction2, 0, out); + + direction2.first += PI/3; + fill2._fill_single_direction(expolygon, direction2, 0, out); +} + +void FillStars::_fill_surface_single( + unsigned int thickness_layers, + const direction_t &direction, + ExPolygon &expolygon, + Polylines* out) +{ + FillStars fill2 = *this; + fill2.density /= 3.; + direction_t direction2 = direction; + + fill2._fill_single_direction(expolygon, direction2, 0, out); + + fill2.dont_connect = true; + direction2.first += PI/3; + fill2._fill_single_direction(expolygon, direction2, 0, out); + + direction2.first += PI/3; + const coord_t x_shift = 0.5 * scale_(fill2.min_spacing) / fill2.density; + fill2._fill_single_direction(expolygon, direction2, x_shift, out); +} + +void FillCubic::_fill_surface_single( + unsigned int thickness_layers, + const direction_t &direction, + ExPolygon &expolygon, + Polylines* out) +{ + FillCubic fill2 = *this; + fill2.density /= 3.; + direction_t direction2 = direction; + + const coord_t range = scale_(this->min_spacing / this->density); + const coord_t x_shift = abs(( (coord_t)(scale_(this->z) + range) % (coord_t)(range * 2)) - range); + + fill2._fill_single_direction(expolygon, direction2, -x_shift, out); + + fill2.dont_connect = true; + direction2.first += PI/3; + fill2._fill_single_direction(expolygon, direction2, +x_shift, out); + + direction2.first += PI/3; + fill2._fill_single_direction(expolygon, direction2, -x_shift, out); +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillRectilinear.hpp b/xs/src/libslic3r/Fill/FillRectilinear.hpp new file mode 100644 index 000000000..d5d5b8ecf --- /dev/null +++ b/xs/src/libslic3r/Fill/FillRectilinear.hpp @@ -0,0 +1,135 @@ +#ifndef slic3r_FillRectilinear_hpp_ +#define slic3r_FillRectilinear_hpp_ + +#include "../libslic3r.h" + +#include "Fill.hpp" + +namespace Slic3r { + +class FillRectilinear : public Fill +{ +public: + virtual Fill* clone() const { return new FillRectilinear(*this); }; + virtual ~FillRectilinear() {} + virtual bool can_solid() const { return true; }; + +protected: + virtual void _fill_surface_single( + unsigned int thickness_layers, + const direction_t &direction, + ExPolygon &expolygon, + Polylines* polylines_out); + + void _fill_single_direction(ExPolygon expolygon, const direction_t &direction, + coord_t x_shift, Polylines* out); + + struct IntersectionPoint : Point { + enum ipType { ipTypeLower, ipTypeUpper, ipTypeMiddle }; + ipType type; + + // skipped contains the polygon points accumulated between the previous intersection + // point and the current one, in the original polygon winding order (does not contain + // either points) + Points skipped; + + // next contains a polygon portion connecting this point to the first intersection + // point found following the polygon in any direction but having: + // x > this->x || (x == this->x && y > this->y) + // (it doesn't contain *this but it contains the target intersection point) + Points next; + + IntersectionPoint() : Point() {}; + IntersectionPoint(coord_t x, coord_t y, ipType _type) : Point(x,y), type(_type) {}; + }; + typedef std::map vertical_t; // + typedef std::map grid_t; // > +}; + +class FillAlignedRectilinear : public FillRectilinear +{ +public: + virtual Fill* clone() const { return new FillAlignedRectilinear(*this); }; + virtual ~FillAlignedRectilinear() {}; + virtual bool can_solid() const { return false; }; + +protected: + // Keep the angle constant in all layers. + virtual float _layer_angle(size_t idx) const { return 0.f; }; +}; + +class FillGrid : public FillRectilinear +{ +public: + virtual Fill* clone() const { return new FillGrid(*this); }; + virtual ~FillGrid() {} + virtual bool can_solid() const { return false; }; + +protected: + // The grid fill will keep the angle constant between the layers,; see the implementation of Slic3r::Fill. + virtual float _layer_angle(size_t idx) const { return 0.f; } + + virtual void _fill_surface_single( + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines* polylines_out); +}; + +class FillTriangles : public FillRectilinear +{ +public: + virtual Fill* clone() const { return new FillTriangles(*this); }; + virtual ~FillTriangles() {} + virtual bool can_solid() const { return false; }; + +protected: + // The grid fill will keep the angle constant between the layers,; see the implementation of Slic3r::Fill. + virtual float _layer_angle(size_t idx) const { return 0.f; } + + virtual void _fill_surface_single( + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines* polylines_out); +}; + +class FillStars : public FillRectilinear +{ +public: + virtual Fill* clone() const { return new FillStars(*this); }; + virtual ~FillStars() {} + virtual bool can_solid() const { return false; }; + +protected: + // The grid fill will keep the angle constant between the layers,; see the implementation of Slic3r::Fill. + virtual float _layer_angle(size_t idx) const { return 0.f; } + + virtual void _fill_surface_single( + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines* polylines_out); +}; + +class FillCubic : public FillRectilinear +{ +public: + virtual Fill* clone() const { return new FillCubic(*this); }; + virtual ~FillCubic() {} + virtual bool can_solid() const { return false; }; + +protected: + // The grid fill will keep the angle constant between the layers,; see the implementation of Slic3r::Fill. + virtual float _layer_angle(size_t idx) const { return 0.f; } + + virtual void _fill_surface_single( + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines* polylines_out); +}; + +}; // namespace Slic3r + +#endif // slic3r_FillRectilinear_hpp_ diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 41a8671a6..b5d44f333 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -98,7 +98,7 @@ OozePrevention::pre_toolchange(GCode &gcodegen) if (gcodegen.config.standby_temperature_delta.value != 0) { // we assume that heating is always slower than cooling, so no need to block gcode += gcodegen.writer.set_temperature - (this->_get_temp(gcodegen) + gcodegen.config.standby_temperature_delta.value, false); + (this->_get_temp(gcodegen) + gcodegen.config.standby_temperature_delta.value, false, gcodegen.writer.extruder()->id); } return gcode; @@ -110,7 +110,7 @@ OozePrevention::post_toolchange(GCode &gcodegen) std::string gcode; if (gcodegen.config.standby_temperature_delta.value != 0) { - gcode += gcodegen.writer.set_temperature(this->_get_temp(gcodegen), true); + gcode += gcodegen.writer.set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer.extruder()->id); } return gcode; @@ -283,11 +283,8 @@ GCode::change_layer(const Layer &layer) this->first_layer = (layer.id() == 0); // avoid computing islands and overhangs if they're not needed - if (this->config.avoid_crossing_perimeters) { - ExPolygons islands; - union_(layer.slices, &islands, true); - this->avoid_crossing_perimeters.init_layer_mp(islands); - } + if (this->config.avoid_crossing_perimeters) + this->avoid_crossing_perimeters.init_layer_mp(union_ex(layer.slices, true)); std::string gcode; if (this->layer_count > 0) { @@ -560,6 +557,13 @@ GCode::_extrude(ExtrusionPath path, std::string description, double speed) this->config.max_volumetric_speed.value / path.mm3_per_mm ); } + if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { + // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) + speed = std::min( + speed, + EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm + ); + } double F = speed * 60; // convert mm/sec to mm/min // extrude arc or line @@ -635,8 +639,13 @@ GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment) for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) gcode += this->writer.travel_to_xy(this->point_to_gcode(line->b), comment); + /* While this makes the estimate more accurate, CoolingBuffer calculates the slowdown + factor on the whole elapsed time but only alters non-travel moves, thus the resulting + time is still shorter than the configured threshold. We could create a new + elapsed_travel_time but we would still need to account for bridges, retractions, wipe etc. if (this->config.cooling) - this->elapsed_time += travel.length() / this->config.get_abs_value("travel_speed"); + this->elapsed_time += unscale(travel.length()) / this->config.get_abs_value("travel_speed"); + */ return gcode; } @@ -663,15 +672,6 @@ GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) /* skip retraction if travel is contained in an internal slice *and* internal infill is enabled (so that stringing is entirely not visible) */ return false; - } else if (this->layer->any_bottom_region_slice_contains(travel) - && this->layer->upper_layer != NULL - && this->layer->upper_layer->slices.contains(travel) - && (this->config.bottom_solid_layers.value >= 2 || this->config.fill_density.value > 0)) { - /* skip retraction if travel is contained in an *infilled* bottom slice - but only if it's also covered by an *infilled* upper layer's slice - so that it's not visible from above (a bottom surface might not have an - upper slice in case of a thin membrane) */ - return false; } } diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index ebeb50e78..8698eb1a6 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -76,12 +76,19 @@ class GCode { Wipe wipe; AvoidCrossingPerimeters avoid_crossing_perimeters; bool enable_loop_clipping; + // If enabled, the G-code generator will put following comments at the ends + // of the G-code lines: _EXTRUDE_SET_SPEED, _WIPE, _BRIDGE_FAN_START, _BRIDGE_FAN_END + // Those comments are received and consumed (removed from the G-code) by the CoolingBuffer.pm Perl module. bool enable_cooling_markers; size_t layer_count; int layer_index; // just a counter const Layer* layer; std::map _seam_position; bool first_layer; // this flag triggers first layer speeds + // Used by the CoolingBuffer.pm Perl module to calculate time spent per layer change. + // This value is not quite precise. First it only accouts for extrusion moves and travel moves, + // it does not account for wipe, retract / unretract moves. + // second it does not account for the velocity profiles of the printer. float elapsed_time; // seconds double volumetric_speed; diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.cpp b/xs/src/libslic3r/GCode/CoolingBuffer.cpp new file mode 100644 index 000000000..e976c9f46 --- /dev/null +++ b/xs/src/libslic3r/GCode/CoolingBuffer.cpp @@ -0,0 +1,132 @@ +#include "CoolingBuffer.hpp" +#include +#include +#include + +namespace Slic3r { + +std::string +CoolingBuffer::append(const std::string &gcode, std::string obj_id, size_t layer_id, float print_z) +{ + std::string out; + if (this->_last_z.find(obj_id) != this->_last_z.end()) { + // A layer was finished, Z of the object's layer changed. Process the layer. + out = this->flush(); + } + + this->_layer_id = layer_id; + this->_last_z[obj_id] = print_z; + this->_gcode += gcode; + // This is a very rough estimate of the print time, + // not taking into account the acceleration curves generated by the printer firmware. + this->_elapsed_time += this->_gcodegen->elapsed_time; + this->_gcodegen->elapsed_time = 0; + + return out; +} + +void +apply_speed_factor(std::string &line, float speed_factor, float min_print_speed) +{ + // find pos of F + size_t pos = line.find_first_of('F'); + size_t last_pos = line.find_first_of(' ', pos+1); + + // extract current speed + float speed; + { + std::istringstream iss(line.substr(pos+1, last_pos)); + iss >> speed; + } + + // change speed + speed *= speed_factor; + speed = std::max(speed, min_print_speed); + + // replace speed in string + { + std::ostringstream oss; + oss << speed; + line.replace(pos+1, (last_pos-pos), oss.str()); + } +} + +std::string +CoolingBuffer::flush() +{ + GCode &gg = *this->_gcodegen; + + std::string gcode = this->_gcode; + float elapsed = this->_elapsed_time; + this->_gcode = ""; + this->_elapsed_time = 0; + this->_last_z.clear(); // reset the whole table otherwise we would compute overlapping times + + int fan_speed = gg.config.fan_always_on ? gg.config.min_fan_speed.value : 0; + + float speed_factor = 1.0; + + if (gg.config.cooling) { + #ifdef SLIC3R_DEBUG + printf("Layer %zu estimated printing time: %f seconds\n", this->_layer_id, elapsed); + #endif + + if (elapsed < (float)gg.config.slowdown_below_layer_time) { + // Layer time very short. Enable the fan to a full throttle and slow down the print + // (stretch the layer print time to slowdown_below_layer_time). + fan_speed = gg.config.max_fan_speed; + speed_factor = elapsed / (float)gg.config.slowdown_below_layer_time; + } else if (elapsed < (float)gg.config.fan_below_layer_time) { + // Layer time quite short. Enable the fan proportionally according to the current layer time. + fan_speed = gg.config.max_fan_speed + - (gg.config.max_fan_speed - gg.config.min_fan_speed) + * (elapsed - (float)gg.config.slowdown_below_layer_time) + / (gg.config.fan_below_layer_time - gg.config.slowdown_below_layer_time); + } + + #ifdef SLIC3R_DEBUG + printf(" fan = %d%%, speed = %f%%\n", fan_speed, speed_factor * 100); + #endif + + if (speed_factor < 1.0) { + // Adjust feed rate of G1 commands marked with an _EXTRUDE_SET_SPEED + // as long as they are not _WIPE moves (they cannot if they are _EXTRUDE_SET_SPEED) + // and they are not preceded directly by _BRIDGE_FAN_START (do not adjust bridging speed). + std::string new_gcode; + std::istringstream ss(gcode); + std::string line; + bool bridge_fan_start = false; + while (std::getline(ss, line)) { + if (boost::starts_with(line, "G1") + && boost::contains(line, ";_EXTRUDE_SET_SPEED") + && !boost::contains(line, ";_WIPE") + && !bridge_fan_start) { + apply_speed_factor(line, speed_factor, this->_min_print_speed); + boost::replace_first(line, ";_EXTRUDE_SET_SPEED", ""); + } + bridge_fan_start = boost::contains(line, ";_BRIDGE_FAN_START"); + new_gcode += line + '\n'; + } + gcode = new_gcode; + } + } + if (this->_layer_id < gg.config.disable_fan_first_layers) + fan_speed = 0; + + gcode = gg.writer.set_fan(fan_speed) + gcode; + + // bridge fan speed + if (!gg.config.cooling || gg.config.bridge_fan_speed == 0 || this->_layer_id < gg.config.disable_fan_first_layers) { + boost::replace_all(gcode, ";_BRIDGE_FAN_START", ""); + boost::replace_all(gcode, ";_BRIDGE_FAN_END", ""); + } else { + boost::replace_all(gcode, ";_BRIDGE_FAN_START", gg.writer.set_fan(gg.config.bridge_fan_speed, true)); + boost::replace_all(gcode, ";_BRIDGE_FAN_END", gg.writer.set_fan(fan_speed, true)); + } + boost::replace_all(gcode, ";_WIPE", ""); + boost::replace_all(gcode, ";_EXTRUDE_SET_SPEED", ""); + + return gcode; +} + +} diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.hpp b/xs/src/libslic3r/GCode/CoolingBuffer.hpp new file mode 100644 index 000000000..01770a58d --- /dev/null +++ b/xs/src/libslic3r/GCode/CoolingBuffer.hpp @@ -0,0 +1,39 @@ +#ifndef slic3r_CoolingBuffer_hpp_ +#define slic3r_CoolingBuffer_hpp_ + +#include "libslic3r.h" +#include "GCode.hpp" +#include +#include + +namespace Slic3r { + +/* +A standalone G-code filter, to control cooling of the print. +The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited +and the print is modified to stretch over a minimum layer time. +*/ + +class CoolingBuffer { + public: + CoolingBuffer(GCode &gcodegen) + : _gcodegen(&gcodegen), _elapsed_time(0.), _layer_id(0) + { + this->_min_print_speed = this->_gcodegen->config.min_print_speed * 60; + }; + std::string append(const std::string &gcode, std::string obj_id, size_t layer_id, float print_z); + std::string flush(); + GCode* gcodegen() { return this->_gcodegen; }; + + private: + GCode* _gcodegen; + std::string _gcode; + float _elapsed_time; + size_t _layer_id; + std::map _last_z; + float _min_print_speed; +}; + +} + +#endif diff --git a/xs/src/libslic3r/GCodeWriter.cpp b/xs/src/libslic3r/GCodeWriter.cpp index d256ab2af..b3909c8ed 100644 --- a/xs/src/libslic3r/GCodeWriter.cpp +++ b/xs/src/libslic3r/GCodeWriter.cpp @@ -54,7 +54,7 @@ GCodeWriter::preamble() gcode << "G21 ; set units to millimeters\n"; gcode << "G90 ; use absolute coordinates\n"; } - if (FLAVOR_IS(gcfRepRap) || FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfSmoothie)) { + if (FLAVOR_IS(gcfRepRap) || FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepetier) || FLAVOR_IS(gcfSmoothie)) { if (this->config.use_relative_e_distances) { gcode << "M83 ; use relative distances for extrusion\n"; } else { @@ -78,11 +78,9 @@ GCodeWriter::postamble() const std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool) const { - if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish))) - return ""; std::string code, comment; - if (wait && FLAVOR_IS_NOT(gcfTeacup)) { + if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfMakerWare) && FLAVOR_IS_NOT(gcfSailfish)) { code = "M109"; comment = "set temperature and wait for it to be reached"; } else { @@ -106,6 +104,9 @@ GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool) cons if (FLAVOR_IS(gcfTeacup) && wait) gcode << "M116 ; wait for temperature to be reached\n"; + if (wait && tool !=-1 && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish))) + gcode << "M6 T" << tool << " ; wait for temperature to be reached\n"; + return gcode.str(); } @@ -185,7 +186,14 @@ GCodeWriter::set_acceleration(unsigned int acceleration) this->_last_acceleration = acceleration; std::ostringstream gcode; - gcode << "M204 S" << acceleration; + if (FLAVOR_IS(gcfRepetier)) { + gcode << "M201 X" << acceleration << " Y" << acceleration; + if (this->config.gcode_comments) gcode << " ; adjust acceleration"; + gcode << "\n"; + gcode << "M202 X" << acceleration << " Y" << acceleration; + } else { + gcode << "M204 S" << acceleration; + } if (this->config.gcode_comments) gcode << " ; adjust acceleration"; gcode << "\n"; @@ -500,10 +508,10 @@ GCodeWriter::lift() // check whether the above/below conditions are met double target_lift = 0; { - double above = this->config.retract_lift_above.get_at(0); - double below = this->config.retract_lift_below.get_at(0); + double above = this->config.retract_lift_above.get_at(this->_extruder->id); + double below = this->config.retract_lift_below.get_at(this->_extruder->id); if (this->_pos.z >= above && (below == 0 || this->_pos.z <= below)) - target_lift = this->config.retract_lift.get_at(0); + target_lift = this->config.retract_lift.get_at(this->_extruder->id); } if (this->_lifted == 0 && target_lift > 0) { this->_lifted = target_lift; diff --git a/xs/src/libslic3r/GCodeWriter.hpp b/xs/src/libslic3r/GCodeWriter.hpp index 72d54215b..51dc0d6d6 100644 --- a/xs/src/libslic3r/GCodeWriter.hpp +++ b/xs/src/libslic3r/GCodeWriter.hpp @@ -10,7 +10,7 @@ namespace Slic3r { class GCodeWriter { - public: +public: GCodeConfig config; std::map extruders; bool multiple_extruders; @@ -48,7 +48,7 @@ class GCodeWriter { std::string unlift(); Pointf3 get_position() const; - private: +private: std::string _extrusion_axis; Extruder* _extruder; unsigned int _last_acceleration; @@ -60,6 +60,6 @@ class GCodeWriter { std::string _retract(double length, double restart_extra, const std::string &comment); }; -} +} /* namespace Slic3r */ -#endif +#endif /* slic3r_GCodeWriter_hpp_ */ diff --git a/xs/src/libslic3r/Geometry.cpp b/xs/src/libslic3r/Geometry.cpp index 6d864f631..0cc9a575c 100644 --- a/xs/src/libslic3r/Geometry.cpp +++ b/xs/src/libslic3r/Geometry.cpp @@ -11,12 +11,186 @@ #include #include #include +#include #include #ifdef SLIC3R_DEBUG #include "SVG.hpp" #endif +#ifdef SLIC3R_DEBUG +namespace boost { namespace polygon { + +// The following code for the visualization of the boost Voronoi diagram is based on: +// +// Boost.Polygon library voronoi_graphic_utils.hpp header file +// Copyright Andrii Sydorchuk 2010-2012. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +template +class voronoi_visual_utils { + public: + // Discretize parabolic Voronoi edge. + // Parabolic Voronoi edges are always formed by one point and one segment + // from the initial input set. + // + // Args: + // point: input point. + // segment: input segment. + // max_dist: maximum discretization distance. + // discretization: point discretization of the given Voronoi edge. + // + // Template arguments: + // InCT: coordinate type of the input geometries (usually integer). + // Point: point type, should model point concept. + // Segment: segment type, should model segment concept. + // + // Important: + // discretization should contain both edge endpoints initially. + template class Point, + template class Segment> + static + typename enable_if< + typename gtl_and< + typename gtl_if< + typename is_point_concept< + typename geometry_concept< Point >::type + >::type + >::type, + typename gtl_if< + typename is_segment_concept< + typename geometry_concept< Segment >::type + >::type + >::type + >::type, + void + >::type discretize( + const Point& point, + const Segment& segment, + const CT max_dist, + std::vector< Point >* discretization) { + // Apply the linear transformation to move start point of the segment to + // the point with coordinates (0, 0) and the direction of the segment to + // coincide the positive direction of the x-axis. + CT segm_vec_x = cast(x(high(segment))) - cast(x(low(segment))); + CT segm_vec_y = cast(y(high(segment))) - cast(y(low(segment))); + CT sqr_segment_length = segm_vec_x * segm_vec_x + segm_vec_y * segm_vec_y; + + // Compute x-coordinates of the endpoints of the edge + // in the transformed space. + CT projection_start = sqr_segment_length * + get_point_projection((*discretization)[0], segment); + CT projection_end = sqr_segment_length * + get_point_projection((*discretization)[1], segment); + + // Compute parabola parameters in the transformed space. + // Parabola has next representation: + // f(x) = ((x-rot_x)^2 + rot_y^2) / (2.0*rot_y). + CT point_vec_x = cast(x(point)) - cast(x(low(segment))); + CT point_vec_y = cast(y(point)) - cast(y(low(segment))); + CT rot_x = segm_vec_x * point_vec_x + segm_vec_y * point_vec_y; + CT rot_y = segm_vec_x * point_vec_y - segm_vec_y * point_vec_x; + + // Save the last point. + Point last_point = (*discretization)[1]; + discretization->pop_back(); + + // Use stack to avoid recursion. + std::stack point_stack; + point_stack.push(projection_end); + CT cur_x = projection_start; + CT cur_y = parabola_y(cur_x, rot_x, rot_y); + + // Adjust max_dist parameter in the transformed space. + const CT max_dist_transformed = max_dist * max_dist * sqr_segment_length; + while (!point_stack.empty()) { + CT new_x = point_stack.top(); + CT new_y = parabola_y(new_x, rot_x, rot_y); + + // Compute coordinates of the point of the parabola that is + // furthest from the current line segment. + CT mid_x = (new_y - cur_y) / (new_x - cur_x) * rot_y + rot_x; + CT mid_y = parabola_y(mid_x, rot_x, rot_y); + + // Compute maximum distance between the given parabolic arc + // and line segment that discretize it. + CT dist = (new_y - cur_y) * (mid_x - cur_x) - + (new_x - cur_x) * (mid_y - cur_y); + dist = dist * dist / ((new_y - cur_y) * (new_y - cur_y) + + (new_x - cur_x) * (new_x - cur_x)); + if (dist <= max_dist_transformed) { + // Distance between parabola and line segment is less than max_dist. + point_stack.pop(); + CT inter_x = (segm_vec_x * new_x - segm_vec_y * new_y) / + sqr_segment_length + cast(x(low(segment))); + CT inter_y = (segm_vec_x * new_y + segm_vec_y * new_x) / + sqr_segment_length + cast(y(low(segment))); + discretization->push_back(Point(inter_x, inter_y)); + cur_x = new_x; + cur_y = new_y; + } else { + point_stack.push(mid_x); + } + } + + // Update last point. + discretization->back() = last_point; + } + + private: + // Compute y(x) = ((x - a) * (x - a) + b * b) / (2 * b). + static CT parabola_y(CT x, CT a, CT b) { + return ((x - a) * (x - a) + b * b) / (b + b); + } + + // Get normalized length of the distance between: + // 1) point projection onto the segment + // 2) start point of the segment + // Return this length divided by the segment length. This is made to avoid + // sqrt computation during transformation from the initial space to the + // transformed one and vice versa. The assumption is made that projection of + // the point lies between the start-point and endpoint of the segment. + template class Point, + template class Segment> + static + typename enable_if< + typename gtl_and< + typename gtl_if< + typename is_point_concept< + typename geometry_concept< Point >::type + >::type + >::type, + typename gtl_if< + typename is_segment_concept< + typename geometry_concept< Segment >::type + >::type + >::type + >::type, + CT + >::type get_point_projection( + const Point& point, const Segment& segment) { + CT segment_vec_x = cast(x(high(segment))) - cast(x(low(segment))); + CT segment_vec_y = cast(y(high(segment))) - cast(y(low(segment))); + CT point_vec_x = x(point) - cast(x(low(segment))); + CT point_vec_y = y(point) - cast(y(low(segment))); + CT sqr_segment_length = + segment_vec_x * segment_vec_x + segment_vec_y * segment_vec_y; + CT vec_dot = segment_vec_x * point_vec_x + segment_vec_y * point_vec_y; + return vec_dot / sqr_segment_length; + } + + template + static CT cast(const InCT& value) { + return static_cast(value); + } +}; + +} } // namespace boost::polygon +#endif + using namespace boost::polygon; // provides also high() and low() namespace Slic3r { namespace Geometry { @@ -149,29 +323,32 @@ deg2rad(double angle) return PI * angle / 180.0; } -void -simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval) -{ - Polygons pp; - for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) { - Polygon p = *it; - p.points.push_back(p.points.front()); - p.points = MultiPoint::_douglas_peucker(p.points, tolerance); - p.points.pop_back(); - pp.push_back(p); - } - Slic3r::simplify_polygons(pp, retval); -} - double linint(double value, double oldmin, double oldmax, double newmin, double newmax) { return (value - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin; } -Pointfs -arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf* bb) +class ArrangeItem { + public: + Pointf pos; + size_t index_x, index_y; + coordf_t dist; +}; +class ArrangeItemIndex { + public: + coordf_t index; + ArrangeItem item; + ArrangeItemIndex(coordf_t _index, ArrangeItem _item) : index(_index), item(_item) {}; +}; + +bool +arrange(size_t total_parts, const Pointf &part_size, coordf_t dist, const BoundingBoxf* bb, Pointfs &positions) { + positions.clear(); + + Pointf part = part_size; + // use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm part.x += dist; part.y += dist; @@ -189,7 +366,7 @@ arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf* bb) size_t cellw = floor((area.x + dist) / part.x); size_t cellh = floor((area.y + dist) / part.y); if (total_parts > (cellw * cellh)) - CONFESS("%zu parts won't fit in your print area!\n", total_parts); + return false; // total space used by cells Pointf cells(cellw * part.x, cellh * part.y); @@ -244,7 +421,7 @@ arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf* bb) } cellsorder.insert(cellsorder.begin() + low, ArrangeItemIndex(index, c)); } - ENDSORT: true; + ENDSORT: ; } } @@ -270,7 +447,6 @@ arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf* bb) } } // now we actually place objects into cells, positioned such that the left and bottom borders are at 0 - Pointfs positions; for (size_t i = 1; i <= total_parts; ++i) { ArrangeItemIndex c = cellsorder.front(); cellsorder.erase(cellsorder.begin()); @@ -287,7 +463,7 @@ arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf* bb) } } - return positions; + return true; } void @@ -298,14 +474,21 @@ MedialAxis::build(ThickPolylines* polylines) /* // DEBUG: dump all Voronoi edges { + SVG svg("voronoi.svg"); + svg.draw(*this->expolygon); for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { if (edge->is_infinite()) continue; ThickPolyline polyline; polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() )); polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() )); + polyline.width.push_back(this->max_width); + polyline.width.push_back(this->max_width); polylines->push_back(polyline); + + svg.draw(polyline); } + svg.Close(); return; } */ diff --git a/xs/src/libslic3r/Geometry.hpp b/xs/src/libslic3r/Geometry.hpp index 14bc3f0ca..55c760f7e 100644 --- a/xs/src/libslic3r/Geometry.hpp +++ b/xs/src/libslic3r/Geometry.hpp @@ -23,22 +23,13 @@ template bool contains(const std::vector &vector, const Point &point double rad2deg(double angle); double rad2deg_dir(double angle); double deg2rad(double angle); -void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval); -class ArrangeItem { - public: - Pointf pos; - size_t index_x, index_y; - coordf_t dist; -}; -class ArrangeItemIndex { - public: - coordf_t index; - ArrangeItem item; - ArrangeItemIndex(coordf_t _index, ArrangeItem _item) : index(_index), item(_item) {}; -}; double linint(double value, double oldmin, double oldmax, double newmin, double newmax); -Pointfs arrange(size_t total_parts, Pointf part, coordf_t dist, const BoundingBoxf* bb); +bool arrange( + // input + size_t num_parts, const Pointf &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box, + // output + Pointfs &positions); class MedialAxis { public: @@ -47,7 +38,7 @@ class MedialAxis { double max_width; double min_width; MedialAxis(double _max_width, double _min_width, const ExPolygon* _expolygon = NULL) - : max_width(_max_width), min_width(_min_width), expolygon(_expolygon) {}; + : expolygon(_expolygon), max_width(_max_width), min_width(_min_width) {}; void build(ThickPolylines* polylines); void build(Polylines* polylines); diff --git a/xs/src/libslic3r/IO.cpp b/xs/src/libslic3r/IO.cpp index 4a44cf23c..9aabe0c45 100644 --- a/xs/src/libslic3r/IO.cpp +++ b/xs/src/libslic3r/IO.cpp @@ -8,8 +8,12 @@ namespace Slic3r { namespace IO { bool STL::read(std::string input_file, TriangleMesh* mesh) { - mesh->ReadSTLFile(input_file); - mesh->check_topology(); + try { + mesh->ReadSTLFile(input_file); + mesh->check_topology(); + } catch (...) { + throw std::runtime_error("Error while reading STL file"); + } return true; } diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp index dc931923c..102654be0 100644 --- a/xs/src/libslic3r/Layer.cpp +++ b/xs/src/libslic3r/Layer.cpp @@ -3,7 +3,6 @@ #include "Geometry.hpp" #include "Print.hpp" - namespace Slic3r { Layer::Layer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, @@ -47,18 +46,6 @@ Layer::set_id(size_t id) this->_id = id; } -PrintObject* -Layer::object() -{ - return this->_object; -} - -const PrintObject* -Layer::object() const -{ - return this->_object; -} - size_t Layer::region_count() const @@ -73,12 +60,6 @@ Layer::clear_regions() this->delete_region(i); } -LayerRegion* -Layer::get_region(int idx) -{ - return this->regions.at(idx); -} - LayerRegion* Layer::add_region(PrintRegion* print_region) { @@ -110,7 +91,7 @@ Layer::make_slices() Polygons region_slices_p = (*layerm)->slices; slices_p.insert(slices_p.end(), region_slices_p.begin(), region_slices_p.end()); } - union_(slices_p, &slices); + slices = union_ex(slices_p); } this->slices.expolygons.clear(); @@ -162,6 +143,10 @@ Layer::any_bottom_region_slice_contains(const T &item) const } template bool Layer::any_bottom_region_slice_contains(const Polyline &item) const; + +// Here the perimeters are created cummulatively for all layer regions sharing the same parameters influencing the perimeters. +// The perimeter paths and the thin fills (ExtrusionEntityCollection) are assigned to the first compatible layer region. +// The resulting fill surface is split back among the originating regions. void Layer::make_perimeters() { @@ -229,8 +214,8 @@ Layer::make_perimeters() if (!fill_surfaces.surfaces.empty()) { for (LayerRegionPtrs::iterator l = layerms.begin(); l != layerms.end(); ++l) { ExPolygons expp = intersection_ex( - fill_surfaces, - (*l)->slices + (Polygons) fill_surfaces, + (Polygons) (*l)->slices ); (*l)->fill_surfaces.surfaces.clear(); @@ -245,16 +230,21 @@ Layer::make_perimeters() } } - -SupportLayer::SupportLayer(size_t id, PrintObject *object, coordf_t height, - coordf_t print_z, coordf_t slice_z) -: Layer(id, object, height, print_z, slice_z) +void +Layer::make_fills() { + #ifdef SLIC3R_DEBUG + printf("Making fills for layer %zu\n", this->id()); + #endif + + FOREACH_LAYERREGION(this, it_layerm) { + (*it_layerm)->make_fill(); + + #ifndef NDEBUG + for (size_t i = 0; i < (*it_layerm)->fills.entities.size(); ++i) + assert(dynamic_cast((*it_layerm)->fills.entities[i]) != NULL); + #endif + } } -SupportLayer::~SupportLayer() -{ -} - - } diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index 4badb8374..c0db224ec 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -25,8 +25,10 @@ class LayerRegion friend class Layer; public: - Layer* layer(); - PrintRegion* region(); + Layer* layer() { return this->_layer; }; + const Layer* layer() const { return this->_layer; }; + PrintRegion* region() { return this->_region; }; + const PrintRegion* region() const { return this->_region; }; // collection of surfaces generated by slicing the original geometry // divided by type top/bottom/internal @@ -57,6 +59,7 @@ class LayerRegion void merge_slices(); void prepare_fill_surfaces(); void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces); + void make_fill(); void process_external_surfaces(const Layer* lower_layer); double infill_area_threshold() const; @@ -64,8 +67,9 @@ class LayerRegion Layer *_layer; PrintRegion *_region; - LayerRegion(Layer *layer, PrintRegion *region); - ~LayerRegion(); + LayerRegion(Layer *layer, PrintRegion *region) + : _layer(layer), _region(region) {}; + ~LayerRegion() {}; }; @@ -77,8 +81,8 @@ class Layer { public: size_t id() const; void set_id(size_t id); - PrintObject* object(); - const PrintObject* object() const; + PrintObject* object() { return this->_object; }; + const PrintObject* object() const { return this->_object; }; Layer *upper_layer; Layer *lower_layer; @@ -94,7 +98,8 @@ class Layer { size_t region_count() const; - LayerRegion* get_region(int idx); + LayerRegion* get_region(size_t idx) { return this->regions.at(idx); }; + const LayerRegion* get_region(size_t idx) const { return this->regions.at(idx); }; LayerRegion* add_region(PrintRegion* print_region); void make_slices(); @@ -102,10 +107,11 @@ class Layer { template bool any_internal_region_slice_contains(const T &item) const; template bool any_bottom_region_slice_contains(const T &item) const; void make_perimeters(); + void make_fills(); protected: size_t _id; // sequential number of layer, 0-based - PrintObject *_object; + PrintObject* _object; Layer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, @@ -126,9 +132,10 @@ class SupportLayer : public Layer { ExtrusionEntityCollection support_interface_fills; protected: - SupportLayer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, - coordf_t slice_z); - virtual ~SupportLayer(); + SupportLayer(size_t id, PrintObject *object, coordf_t height, + coordf_t print_z, coordf_t slice_z) + : Layer(id, object, height, print_z, slice_z) {}; + virtual ~SupportLayer() {}; }; diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index f459c5878..42809d0ae 100644 --- a/xs/src/libslic3r/LayerRegion.cpp +++ b/xs/src/libslic3r/LayerRegion.cpp @@ -7,28 +7,6 @@ namespace Slic3r { -LayerRegion::LayerRegion(Layer *layer, PrintRegion *region) -: _layer(layer), - _region(region) -{ -} - -LayerRegion::~LayerRegion() -{ -} - -Layer* -LayerRegion::layer() -{ - return this->_layer; -} - -PrintRegion* -LayerRegion::region() -{ - return this->_region; -} - Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const { @@ -45,9 +23,8 @@ LayerRegion::flow(FlowRole role, bool bridge, double width) const void LayerRegion::merge_slices() { - ExPolygons expp; // without safety offset, artifacts are generated (GH #2494) - union_(this->slices, &expp, true); + ExPolygons expp = union_ex((Polygons)this->slices, true); this->slices.surfaces.clear(); this->slices.surfaces.reserve(expp.size()); @@ -77,6 +54,7 @@ LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* ); if (this->layer()->lower_layer != NULL) + // Cummulative sum of polygons over all the regions. g.lower_slices = &this->layer()->lower_layer->slices; g.layer_id = this->layer()->id(); @@ -97,30 +75,29 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface) { if (!surface->is_bottom()) continue; - ExPolygons grown = offset_ex(surface->expolygon, +margin); + const ExPolygons grown = offset_ex(surface->expolygon, +margin); /* detect bridge direction before merging grown surfaces otherwise adjacent bridges would get merged into a single one while they need different directions also, supply the original expolygon instead of the grown one, because in case of very thin (but still working) anchors, the grown expolygon would go beyond them */ double angle = -1; - if (lower_layer != NULL) { + if (lower_layer != NULL && surface->is_bridge()) { BridgeDetector bd( surface->expolygon, lower_layer->slices, - this->flow(frInfill, this->layer()->height, true).scaled_width() + this->flow(frInfill, true).scaled_width() ); #ifdef SLIC3R_DEBUG - printf("Processing bridge at layer %zu:\n", this->layer()->id(); + printf("Processing bridge at layer %zu:\n", this->layer()->id()); #endif if (bd.detect_angle()) { angle = bd.angle; if (this->layer()->object()->config.support_material) { - Polygons coverage = bd.coverage(); - this->bridged.insert(this->bridged.end(), coverage.begin(), coverage.end()); + append_to(this->bridged, bd.coverage()); this->unsupported_bridge_edges.append(bd.unsupported_edges()); } } @@ -167,18 +144,16 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) { // merge top and bottom in a single collection SurfaceCollection tb = top; - tb.surfaces.insert(tb.surfaces.end(), bottom.surfaces.begin(), bottom.surfaces.end()); + tb.append(bottom); // group surfaces - std::vector groups; + std::vector groups; tb.group(&groups); - for (std::vector::const_iterator g = groups.begin(); g != groups.end(); ++g) { + for (std::vector::const_iterator g = groups.begin(); g != groups.end(); ++g) { Polygons subject; - for (SurfacesPtr::const_iterator s = g->begin(); s != g->end(); ++s) { - Polygons pp = **s; - subject.insert(subject.end(), pp.begin(), pp.end()); - } + for (SurfacesConstPtr::const_iterator s = g->begin(); s != g->end(); ++s) + append_to(subject, (Polygons)**s); ExPolygons expp = intersection_ex( subject, @@ -203,15 +178,13 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) } // group surfaces - std::vector groups; + std::vector groups; other.group(&groups); - for (std::vector::const_iterator g = groups.begin(); g != groups.end(); ++g) { + for (std::vector::const_iterator g = groups.begin(); g != groups.end(); ++g) { Polygons subject; - for (SurfacesPtr::const_iterator s = g->begin(); s != g->end(); ++s) { - Polygons pp = **s; - subject.insert(subject.end(), pp.begin(), pp.end()); - } + for (SurfacesConstPtr::const_iterator s = g->begin(); s != g->end(); ++s) + append_to(subject, (Polygons)**s); ExPolygons expp = diff_ex( subject, diff --git a/xs/src/libslic3r/LayerRegionFill.cpp b/xs/src/libslic3r/LayerRegionFill.cpp new file mode 100644 index 000000000..0b4276439 --- /dev/null +++ b/xs/src/libslic3r/LayerRegionFill.cpp @@ -0,0 +1,297 @@ +#include "Layer.hpp" +#include "ClipperUtils.hpp" +#include "Fill/Fill.hpp" +#include "Geometry.hpp" +#include "Print.hpp" +#include "PrintConfig.hpp" +#include "Surface.hpp" + +namespace Slic3r { + +struct SurfaceGroupAttrib +{ + SurfaceGroupAttrib() : is_solid(false), fw(0.f), pattern(-1) {} + bool operator==(const SurfaceGroupAttrib &other) const + { return is_solid == other.is_solid && fw == other.fw && pattern == other.pattern; } + bool is_solid; + float fw; + // pattern is of type InfillPattern, -1 for an unset pattern. + int pattern; +}; + +// Generate infills for a LayerRegion. +// The LayerRegion at this point of time may contain +// surfaces of various types (internal/bridge/top/bottom/solid). +// The infills are generated on the groups of surfaces with a compatible type. +// Fills an array of ExtrusionPathCollection objects containing the infills generated now +// and the thin fills generated by generate_perimeters(). +void +LayerRegion::make_fill() +{ + this->fills.clear(); + + const double fill_density = this->region()->config.fill_density; + const Flow infill_flow = this->flow(frInfill); + const Flow solid_infill_flow = this->flow(frSolidInfill); + const Flow top_solid_infill_flow = this->flow(frTopSolidInfill); + const coord_t perimeter_spacing = this->flow(frPerimeter).scaled_spacing(); + + SurfaceCollection surfaces; + + // merge adjacent surfaces + // in case of bridge surfaces, the ones with defined angle will be attached to the ones + // without any angle (shouldn't this logic be moved to process_external_surfaces()?) + { + Polygons polygons_bridged; + polygons_bridged.reserve(this->fill_surfaces.surfaces.size()); + for (Surfaces::const_iterator it = this->fill_surfaces.surfaces.begin(); it != this->fill_surfaces.surfaces.end(); ++it) + if (it->is_bridge() && it->bridge_angle >= 0) + append_to(polygons_bridged, (Polygons)*it); + + // group surfaces by distinct properties (equal surface_type, thickness, thickness_layers, bridge_angle) + // group is of type SurfaceCollection + // FIXME: Use some smart heuristics to merge similar surfaces to eliminate tiny regions. + std::vector groups; + this->fill_surfaces.group(&groups); + + // merge compatible solid groups (we can generate continuous infill for them) + { + // cache flow widths and patterns used for all solid groups + // (we'll use them for comparing compatible groups) + std::vector group_attrib(groups.size()); + for (size_t i = 0; i < groups.size(); ++i) { + const Surface &surface = *groups[i].front(); + // we can only merge solid non-bridge surfaces, so discard + // non-solid or bridge surfaces + if (!surface.is_solid() || surface.is_bridge()) continue; + + group_attrib[i].is_solid = true; + group_attrib[i].fw = (surface.surface_type == stTop) ? top_solid_infill_flow.width : solid_infill_flow.width; + group_attrib[i].pattern = surface.is_external() ? this->region()->config.external_fill_pattern.value : ipRectilinear; + } + // Loop through solid groups, find compatible groups and append them to this one. + for (size_t i = 0; i < groups.size(); ++i) { + if (!group_attrib[i].is_solid) + continue; + for (size_t j = i + 1; j < groups.size();) { + if (group_attrib[i] == group_attrib[j]) { + // groups are compatible, merge them + append_to(groups[i], groups[j]); + groups.erase(groups.begin() + j); + group_attrib.erase(group_attrib.begin() + j); + } else { + ++j; + } + } + } + } + + // Give priority to oriented bridges. Process the bridges in the first round, the rest of the surfaces in the 2nd round. + for (size_t round = 0; round < 2; ++ round) { + for (std::vector::const_iterator it_group = groups.begin(); it_group != groups.end(); ++ it_group) { + const SurfacesConstPtr &group = *it_group; + const bool is_oriented_bridge = group.front()->is_bridge() && group.front()->bridge_angle >= 0; + if (is_oriented_bridge != (round == 0)) + continue; + + // Make a union of polygons defining the infiill regions of a group, use a safety offset. + Polygons union_p = union_(to_polygons(group), true); + + // Subtract surfaces having a defined bridge_angle from any other, use a safety offset. + if (!is_oriented_bridge && !polygons_bridged.empty()) + union_p = diff(union_p, polygons_bridged, true); + + // subtract any other surface already processed + //FIXME Vojtech: Because the bridge surfaces came first, they are subtracted twice! + surfaces.append( + diff_ex(union_p, to_polygons(surfaces), true), + *group.front() // template + ); + } + } + } + + // we need to detect any narrow surfaces that might collapse + // when adding spacing below + // such narrow surfaces are often generated in sloping walls + // by bridge_over_infill() and combine_infill() as a result of the + // subtraction of the combinable area from the layer infill area, + // which leaves small areas near the perimeters + // we are going to grow such regions by overlapping them with the void (if any) + // TODO: detect and investigate whether there could be narrow regions without + // any void neighbors + { + coord_t distance_between_surfaces = std::max( + std::max(infill_flow.scaled_spacing(), solid_infill_flow.scaled_spacing()), + top_solid_infill_flow.scaled_spacing() + ); + + Polygons surfaces_polygons = (Polygons)surfaces; + Polygons collapsed = diff( + surfaces_polygons, + offset2(surfaces_polygons, -distance_between_surfaces/2, +distance_between_surfaces/2), + true + ); + + Polygons to_subtract; + surfaces.filter_by_type(stInternalVoid, &to_subtract); + + append_to(to_subtract, collapsed); + surfaces.append( + intersection_ex( + offset(collapsed, distance_between_surfaces), + to_subtract, + true + ), + stInternalSolid + ); + } + + if (false) { +// require "Slic3r/SVG.pm"; +// Slic3r::SVG::output("fill_" . $layerm->print_z . ".svg", +// expolygons => [ map $_->expolygon, grep !$_->is_solid, @surfaces ], +// red_expolygons => [ map $_->expolygon, grep $_->is_solid, @surfaces ], +// ); + } + + for (Surfaces::const_iterator surface_it = surfaces.surfaces.begin(); + surface_it != surfaces.surfaces.end(); ++surface_it) { + + const Surface &surface = *surface_it; + if (surface.surface_type == stInternalVoid) + continue; + + InfillPattern fill_pattern = this->region()->config.fill_pattern.value; + double density = fill_density; + FlowRole role = (surface.surface_type == stTop) ? frTopSolidInfill + : surface.is_solid() ? frSolidInfill + : frInfill; + const bool is_bridge = this->layer()->id() > 0 && surface.is_bridge(); + + if (surface.is_solid()) { + density = 100.; + fill_pattern = (surface.is_external() && !is_bridge) + ? this->region()->config.external_fill_pattern.value + : ipRectilinear; + } else if (density <= 0) + continue; + + // get filler object + #if SLIC3R_CPPVER >= 11 + std::unique_ptr f = std::unique_ptr(Fill::new_from_type(fill_pattern)); + #else + std::auto_ptr f = std::auto_ptr(Fill::new_from_type(fill_pattern)); + #endif + + // switch to rectilinear if this pattern doesn't support solid infill + if (density > 99 && !f->can_solid()) + #if SLIC3R_CPPVER >= 11 + f = std::unique_ptr(Fill::new_from_type(ipRectilinear)); + #else + f = std::auto_ptr(Fill::new_from_type(ipRectilinear)); + #endif + + f->bounding_box = this->layer()->object()->bounding_box(); + + // calculate the actual flow we'll be using for this infill + coordf_t h = (surface.thickness == -1) ? this->layer()->height : surface.thickness; + Flow flow = this->region()->flow( + role, + h, + is_bridge || f->use_bridge_flow(), // bridge flow? + this->layer()->id() == 0, // first layer? + -1, // auto width + *this->layer()->object() + ); + + // calculate flow spacing for infill pattern generation + bool using_internal_flow = false; + if (!surface.is_solid() && !is_bridge) { + // it's internal infill, so we can calculate a generic flow spacing + // for all layers, for avoiding the ugly effect of + // misaligned infill on first layer because of different extrusion width and + // layer height + Flow internal_flow = this->region()->flow( + frInfill, + this->layer()->object()->config.layer_height.value, // TODO: handle infill_every_layers? + false, // no bridge + false, // no first layer + -1, // auto width + *this->layer()->object() + ); + f->min_spacing = internal_flow.spacing(); + using_internal_flow = true; + } else { + f->min_spacing = flow.spacing(); + } + + f->endpoints_overlap = this->region()->config.get_abs_value("infill_overlap", + (perimeter_spacing + scale_(f->min_spacing))/2); + + f->layer_id = this->layer()->id(); + f->z = this->layer()->print_z; + f->angle = Geometry::deg2rad(this->region()->config.fill_angle.value); + + // Maximum length of the perimeter segment linking two infill lines. + f->link_max_length = (!is_bridge && density > 80) + ? scale_(3 * f->min_spacing) + : 0; + + // Used by the concentric infill pattern to clip the loops to create extrusion paths. + f->loop_clipping = scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER; + + // apply half spacing using this flow's own spacing and generate infill + f->density = density/100; + f->dont_adjust = false; + Polylines polylines = f->fill_surface(surface); + if (polylines.empty()) + continue; + + // calculate actual flow from spacing (which might have been adjusted by the infill + // pattern generator) + if (using_internal_flow) { + // if we used the internal flow we're not doing a solid infill + // so we can safely ignore the slight variation that might have + // been applied to f->spacing() + } else { + flow = Flow::new_from_spacing(f->spacing(), flow.nozzle_diameter, h, is_bridge || f->use_bridge_flow()); + } + + // Save into layer. + ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(); + coll->no_sort = f->no_sort(); + this->fills.entities.push_back(coll); + + { + ExtrusionRole role; + if (is_bridge) { + role = erBridgeInfill; + } else if (surface.is_solid()) { + role = (surface.surface_type == stTop) ? erTopSolidInfill : erSolidInfill; + } else { + role = erInternalInfill; + } + + ExtrusionPath templ(role); + templ.mm3_per_mm = flow.mm3_per_mm(); + templ.width = flow.width; + templ.height = flow.height; + + coll->append(STDMOVE(polylines), templ); + } + } + + // add thin fill regions + // thin_fills are of C++ Slic3r::ExtrusionEntityCollection, perl type Slic3r::ExtrusionPath::Collection + // Unpacks the collection, creates multiple collections per path so that they will + // be individually included in the nearest neighbor search. + // The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection. + for (ExtrusionEntitiesPtr::const_iterator thin_fill = this->thin_fills.entities.begin(); thin_fill != this->thin_fills.entities.end(); ++ thin_fill) { + ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(); + this->fills.entities.push_back(coll); + coll->append(**thin_fill); + } +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Line.hpp b/xs/src/libslic3r/Line.hpp index 42e811449..ff8b086e1 100644 --- a/xs/src/libslic3r/Line.hpp +++ b/xs/src/libslic3r/Line.hpp @@ -53,7 +53,7 @@ class ThickLine : public Line coordf_t a_width, b_width; ThickLine() : a_width(0), b_width(0) {}; - ThickLine(Point _a, Point _b) : a_width(0), b_width(0), Line(_a, _b) {}; + ThickLine(Point _a, Point _b) : Line(_a, _b), a_width(0), b_width(0) {}; }; class Linef diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 9175995bf..0fe072218 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -224,21 +224,22 @@ Model::raw_mesh() const return mesh; } -Pointfs -Model::_arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb) const +bool +Model::_arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb, Pointfs &out) const { // we supply unscaled data to arrange() return Slic3r::Geometry::arrange( sizes.size(), // number of parts BoundingBoxf(sizes).max, // width and height of a single cell dist, // distance between cells - bb // bounding box of the area to fill + bb, // bounding box of the area to fill + out // output positions ); } /* arrange objects preserving their instance count but altering their instance positions */ -void +bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) { // get the (transformed) size of each instance so that we take @@ -250,7 +251,9 @@ Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) } } - Pointfs positions = this->_arrange(instance_sizes, dist, bb); + Pointfs positions; + if (! this->_arrange(instance_sizes, dist, bb, positions)) + return false; for (ModelObjectPtrs::const_iterator o = this->objects.begin(); o != this->objects.end(); ++o) { for (ModelInstancePtrs::const_iterator i = (*o)->instances.begin(); i != (*o)->instances.end(); ++i) { @@ -259,6 +262,7 @@ Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) } (*o)->invalidate_bounding_box(); } + return true; } /* duplicate the entire model preserving instance relative positions */ @@ -266,7 +270,9 @@ void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) { Pointfs model_sizes(copies_num-1, this->bounding_box().size()); - Pointfs positions = this->_arrange(model_sizes, dist, bb); + Pointfs positions; + if (! this->_arrange(model_sizes, dist, bb, positions)) + CONFESS("Cannot duplicate part as the resulting objects would not fit on the print bed.\n"); // note that this will leave the object count unaltered @@ -340,7 +346,7 @@ ModelMaterial::apply(const t_model_material_attributes &attributes) ModelObject::ModelObject(Model *model) - : model(model), _bounding_box_valid(false) + : _bounding_box_valid(false), model(model) {} ModelObject::ModelObject(Model *model, const ModelObject &other, bool copy_volumes) @@ -483,7 +489,16 @@ ModelObject::invalidate_bounding_box() void ModelObject::update_bounding_box() { - this->_bounding_box = this->mesh().bounding_box(); +// this->_bounding_box = this->mesh().bounding_box(); + BoundingBoxf3 raw_bbox; + for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { + if ((*v)->modifier) continue; + raw_bbox.merge((*v)->mesh.bounding_box()); + } + BoundingBoxf3 bb; + for (ModelInstancePtrs::const_iterator i = this->instances.begin(); i != this->instances.end(); ++i) + bb.merge((*i)->transform_bounding_box(raw_bbox)); + this->_bounding_box = bb; this->_bounding_box_valid = true; } @@ -526,12 +541,8 @@ ModelObject::raw_bounding_box() const BoundingBoxf3 bb; for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { if ((*v)->modifier) continue; - TriangleMesh mesh = (*v)->mesh; - if (this->instances.empty()) CONFESS("Can't call raw_bounding_box() with no instances"); - this->instances.front()->transform_mesh(&mesh, true); - - bb.merge(mesh.bounding_box()); + bb.merge(this->instances.front()->transform_mesh_bounding_box(&(*v)->mesh, true)); } return bb; } @@ -540,9 +551,12 @@ ModelObject::raw_bounding_box() const BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx) const { - TriangleMesh mesh = this->raw_mesh(); - this->instances[instance_idx]->transform_mesh(&mesh); - return mesh.bounding_box(); + BoundingBoxf3 bb; + for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { + if ((*v)->modifier) continue; + bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(&(*v)->mesh, true)); + } + return bb; } void @@ -550,7 +564,10 @@ ModelObject::center_around_origin() { // calculate the displacements needed to // center this object around the origin - BoundingBoxf3 bb = this->raw_mesh().bounding_box(); + BoundingBoxf3 bb; + for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) + if (! (*v)->modifier) + bb.merge((*v)->mesh.bounding_box()); // first align to origin on XYZ Vectorf3 vector(-bb.min.x, -bb.min.y, -bb.min.z); @@ -600,6 +617,7 @@ ModelObject::scale(float factor) void ModelObject::scale(const Pointf3 &versor) { + if (versor.x == 1 && versor.y == 1 && versor.z == 1) return; for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { (*v)->mesh.scale(versor); } @@ -626,6 +644,7 @@ ModelObject::scale_to_fit(const Sizef3 &size) void ModelObject::rotate(float angle, const Axis &axis) { + if (angle == 0) return; for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { (*v)->mesh.rotate(angle, axis); } @@ -643,6 +662,24 @@ ModelObject::mirror(const Axis &axis) this->invalidate_bounding_box(); } +void +ModelObject::transform_by_instance(const ModelInstance &instance, bool dont_translate) +{ + this->rotate(instance.rotation, Z); + this->scale(instance.scaling_factor); + if (!dont_translate) + this->translate(instance.offset.x, instance.offset.y, 0); + + for (ModelInstancePtrs::iterator i = this->instances.begin(); i != this->instances.end(); ++i) { + (*i)->rotation -= instance.rotation; + (*i)->scaling_factor /= instance.scaling_factor; + if (!dont_translate) + (*i)->offset.translate(-instance.offset.x, -instance.offset.y); + } + this->origin_translation = Pointf3(0,0,0); + this->invalidate_bounding_box(); +} + size_t ModelObject::materials_count() const { @@ -675,13 +712,15 @@ ModelObject::needed_repair() const } void -ModelObject::cut(coordf_t z, Model* model) const +ModelObject::cut(Axis axis, coordf_t z, Model* model) const { // clone this one to duplicate instances, materials etc. ModelObject* upper = model->add_object(*this); ModelObject* lower = model->add_object(*this); upper->clear_volumes(); lower->clear_volumes(); + upper->input_file = ""; + lower->input_file = ""; for (ModelVolumePtrs::const_iterator v = this->volumes.begin(); v != this->volumes.end(); ++v) { ModelVolume* volume = *v; @@ -690,10 +729,16 @@ ModelObject::cut(coordf_t z, Model* model) const upper->add_volume(*volume); lower->add_volume(*volume); } else { - TriangleMeshSlicer tms(&volume->mesh); TriangleMesh upper_mesh, lower_mesh; - // TODO: shouldn't we use object bounding box instead of per-volume bb? - tms.cut(z + volume->mesh.bounding_box().min.z, &upper_mesh, &lower_mesh); + + if (axis == X) { + TriangleMeshSlicer(&volume->mesh).cut(z, &upper_mesh, &lower_mesh); + } else if (axis == Y) { + TriangleMeshSlicer(&volume->mesh).cut(z, &upper_mesh, &lower_mesh); + } else if (axis == Z) { + TriangleMeshSlicer(&volume->mesh).cut(z, &upper_mesh, &lower_mesh); + } + upper_mesh.repair(); lower_mesh.repair(); upper_mesh.reset_repair_stats(); @@ -731,6 +776,7 @@ ModelObject::split(ModelObjectPtrs* new_objects) (*mesh)->repair(); ModelObject* new_object = this->model->add_object(*this, false); + new_object->input_file = ""; ModelVolume* new_volume = new_object->add_volume(**mesh); new_volume->name = volume->name; new_volume->config = volume->config; @@ -884,6 +930,63 @@ ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const mesh->translate(this->offset.x, this->offset.y, 0); } +BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mesh, bool dont_translate) const +{ + // rotate around mesh origin + double c = cos(this->rotation); + double s = sin(this->rotation); + BoundingBoxf3 bbox; + for (int i = 0; i < mesh->stl.stats.number_of_facets; ++ i) { + const stl_facet &facet = mesh->stl.facet_start[i]; + for (int j = 0; j < 3; ++ j) { + stl_vertex v = facet.vertex[j]; + double xold = v.x; + double yold = v.y; + v.x = float(c * xold - s * yold); + v.y = float(s * xold + c * yold); + v.x *= float(this->scaling_factor); + v.y *= float(this->scaling_factor); + v.z *= float(this->scaling_factor); + if (!dont_translate) { + v.x += this->offset.x; + v.y += this->offset.y; + } + bbox.merge(Pointf3(v.x, v.y, v.z)); + } + } + return bbox; +} + +BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const +{ + // rotate around mesh origin + double c = cos(this->rotation); + double s = sin(this->rotation); + Pointf3 pts[4] = { + bbox.min, + bbox.max, + Pointf3(bbox.min.x, bbox.max.y, bbox.min.z), + Pointf3(bbox.max.x, bbox.min.y, bbox.max.z) + }; + BoundingBoxf3 out; + for (int i = 0; i < 4; ++ i) { + Pointf3 &v = pts[i]; + double xold = v.x; + double yold = v.y; + v.x = float(c * xold - s * yold); + v.y = float(s * xold + c * yold); + v.x *= this->scaling_factor; + v.y *= this->scaling_factor; + v.z *= this->scaling_factor; + if (!dont_translate) { + v.x += this->offset.x; + v.y += this->offset.y; + } + out.merge(v); + } + return out; +} + void ModelInstance::transform_polygon(Polygon* polygon) const { diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index aeb784b0b..16ea81155 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -28,10 +28,18 @@ typedef std::vector ModelObjectPtrs; typedef std::vector ModelVolumePtrs; typedef std::vector ModelInstancePtrs; +// The print bed content. +// Description of a triangular model with multiple materials, multiple instances with various affine transformations +// and with multiple modifier meshes. +// A model groups multiple objects, each object having possibly multiple instances, +// all objects may share mutliple materials. class Model { public: + // Materials are owned by a model and referenced by objects through t_model_material_id. + // Single material may be shared by multiple models. ModelMaterialMap materials; + // Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation). ModelObjectPtrs objects; Model(); @@ -58,42 +66,57 @@ class Model void translate(coordf_t x, coordf_t y, coordf_t z); TriangleMesh mesh() const; TriangleMesh raw_mesh() const; - Pointfs _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb = NULL) const; - void arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); + bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb, Pointfs &out) const; + bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); + // Croaks if the duplicated objects do not fit the print bed. void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); void print_info() const; }; +// Material, which may be shared across multiple ModelObjects of a single Model. class ModelMaterial { friend class Model; public: + // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose. t_model_material_attributes attributes; + // Dynamic configuration storage for the object specific configuration values, overriding the global configuration. DynamicPrintConfig config; Model* get_model() const { return this->model; }; void apply(const t_model_material_attributes &attributes); private: + // Parent, owning this material. Model* model; ModelMaterial(Model *model); ModelMaterial(Model *model, const ModelMaterial &other); }; +// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials), +// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. +// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, +// different rotation and different uniform scaling. class ModelObject { friend class Model; public: std::string name; std::string input_file; + // Instances of this ModelObject. Each instance defines a shift on the print bed, rotation around the Z axis and a uniform scaling. + // Instances are owned by this ModelObject. ModelInstancePtrs instances; + // Printable and modifier volumes, each with its material ID and a set of override parameters. + // ModelVolumes are owned by this ModelObject. ModelVolumePtrs volumes; + // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings. DynamicPrintConfig config; + // Variation of a layer thickness for spans of Z coordinates. t_layer_height_ranges layer_height_ranges; - + /* This vector accumulates the total translation applied to the object by the center_around_origin() method. Callers might want to apply the same translation to new volumes before adding them to this object in order to preserve alignment @@ -133,15 +156,17 @@ class ModelObject void scale_to_fit(const Sizef3 &size); void rotate(float angle, const Axis &axis); void mirror(const Axis &axis); + void transform_by_instance(const ModelInstance &instance, bool dont_translate = false); size_t materials_count() const; size_t facets_count() const; bool needed_repair() const; - void cut(coordf_t z, Model* model) const; + void cut(Axis axis, coordf_t z, Model* model) const; void split(ModelObjectPtrs* new_objects); void update_bounding_box(); // this is a private method but we expose it until we need to expose it via XS void print_info() const; private: + // Parent object, owning this ModelObject. Model* model; ModelObject(Model *model); @@ -151,15 +176,22 @@ class ModelObject ~ModelObject(); }; +// An object STL, or a modifier volume, over which a different set of parameters shall be applied. +// ModelVolume instances are owned by a ModelObject. class ModelVolume { friend class ModelObject; public: std::string name; + // The triangular model. TriangleMesh mesh; + // Configuration parameters specific to an object model geometry or a modifier volume, + // overriding the global Slic3r settings and the ModelObject settings. DynamicPrintConfig config; + // Is it an object to be printed, or a modifier volume? bool modifier; + // A parent object owning this modifier volume. ModelObject* get_object() const { return this->object; }; t_model_material_id material_id() const; void material_id(t_model_material_id material_id); @@ -169,6 +201,7 @@ class ModelVolume ModelMaterial* assign_unique_material(); private: + // Parent object owning this ModelVolume. ModelObject* object; t_model_material_id _material_id; @@ -178,19 +211,29 @@ class ModelVolume void swap(ModelVolume &other); }; +// A single instance of a ModelObject. +// Knows the affine transformation of an object. class ModelInstance { friend class ModelObject; public: - double rotation; // in radians around mesh center point + double rotation; // Rotation around the Z axis, in radians around mesh center point double scaling_factor; Pointf offset; // in unscaled coordinates ModelObject* get_object() const { return this->object; }; + + // To be called on an external mesh void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; + // Calculate a bounding box of a transformed mesh. To be called on an external mesh. + BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh* mesh, bool dont_translate = false) const; + // Transform an external bounding box. + BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const; + // To be called on an external polygon. It does not translate the polygon, only rotates and scales. void transform_polygon(Polygon* polygon) const; private: + // Parent object, owning this instance. ModelObject* object; ModelInstance(ModelObject *object); diff --git a/xs/src/libslic3r/MotionPlanner.cpp b/xs/src/libslic3r/MotionPlanner.cpp index a1df679cf..1f604d5fa 100644 --- a/xs/src/libslic3r/MotionPlanner.cpp +++ b/xs/src/libslic3r/MotionPlanner.cpp @@ -155,12 +155,12 @@ MotionPlanner::shortest_path(const Point &from, const Point &to) if (!grown_env.contains(from)) { // delete second point while the line connecting first to third crosses the // boundaries as many times as the current first to second - while (polyline.points.size() > 2 && intersection((Lines)Line(from, polyline.points[2]), grown_env).size() == 1) { + while (polyline.points.size() > 2 && intersection_ln(Line(from, polyline.points[2]), grown_env).size() == 1) { polyline.points.erase(polyline.points.begin() + 1); } } if (!grown_env.contains(to)) { - while (polyline.points.size() > 2 && intersection((Lines)Line(*(polyline.points.end() - 3), to), grown_env).size() == 1) { + while (polyline.points.size() > 2 && intersection_ln(Line(*(polyline.points.end() - 3), to), grown_env).size() == 1) { polyline.points.erase(polyline.points.end() - 2); } } @@ -294,7 +294,7 @@ MotionPlannerEnv::nearest_env_point(const Point &from, const Point &to) const size_t result = from.nearest_waypoint_index(pp, to); // as we assume 'from' is outside env, any node will require at least one crossing - if (intersection((Lines)Line(from, pp[result]), this->island).size() > 1) { + if (intersection_ln(Line(from, pp[result]), this->island).size() > 1) { // discard result pp.erase(pp.begin() + result); } else { @@ -313,10 +313,10 @@ MotionPlannerEnv::nearest_env_point(const Point &from, const Point &to) const } void -MotionPlannerGraph::add_edge(size_t from, size_t to, double weight) +MotionPlannerGraph::add_edge(node_t from, node_t to, double weight) { // extend adjacency list until this start node - if (this->adjacency_list.size() < from+1) + if (this->adjacency_list.size() < (size_t)from+1) this->adjacency_list.resize(from+1); this->adjacency_list[from].push_back(neighbor(to, weight)); @@ -334,7 +334,7 @@ MotionPlannerGraph::find_node(const Point &point) const } Polyline -MotionPlannerGraph::shortest_path(size_t from, size_t to) +MotionPlannerGraph::shortest_path(node_t from, node_t to) { // this prevents a crash in case for some reason we got here with an empty adjacency list if (this->adjacency_list.empty()) return Polyline(); @@ -345,7 +345,7 @@ MotionPlannerGraph::shortest_path(size_t from, size_t to) std::vector previous; { // number of nodes - size_t n = this->adjacency_list.size(); + const int n = this->adjacency_list.size(); // initialize dist and previous dist.clear(); diff --git a/xs/src/libslic3r/MotionPlanner.hpp b/xs/src/libslic3r/MotionPlanner.hpp index b6251ff21..1fbdb4123 100644 --- a/xs/src/libslic3r/MotionPlanner.hpp +++ b/xs/src/libslic3r/MotionPlanner.hpp @@ -47,9 +47,9 @@ class MotionPlannerGraph public: Points nodes; //std::map, double> edges; - void add_edge(size_t from, size_t to, double weight); + void add_edge(node_t from, node_t to, double weight); size_t find_node(const Point &point) const; - Polyline shortest_path(size_t from, size_t to); + Polyline shortest_path(node_t from, node_t to); }; class MotionPlanner diff --git a/xs/src/libslic3r/MultiPoint.cpp b/xs/src/libslic3r/MultiPoint.cpp index 6857d6393..d8ee1d09d 100644 --- a/xs/src/libslic3r/MultiPoint.cpp +++ b/xs/src/libslic3r/MultiPoint.cpp @@ -30,11 +30,29 @@ MultiPoint::translate(const Point &vector) this->translate(vector.x, vector.y); } +void +MultiPoint::rotate(double angle) +{ + double s = sin(angle); + double c = cos(angle); + for (Points::iterator it = points.begin(); it != points.end(); ++it) { + double cur_x = (double)it->x; + double cur_y = (double)it->y; + it->x = (coord_t)round(c * cur_x - s * cur_y); + it->y = (coord_t)round(c * cur_y + s * cur_x); + } +} + void MultiPoint::rotate(double angle, const Point ¢er) { + double s = sin(angle); + double c = cos(angle); for (Points::iterator it = points.begin(); it != points.end(); ++it) { - (*it).rotate(angle, center); + double dx = double(it->x - center.x); + double dy = double(it->y - center.y); + it->x = (coord_t)round(double(center.x) + c * dx - s * dy); + it->y = (coord_t)round(double(center.y) + c * dy + s * dx); } } @@ -61,12 +79,6 @@ MultiPoint::length() const return len; } -bool -MultiPoint::is_valid() const -{ - return this->points.size() >= 2; -} - int MultiPoint::find_point(const Point &point) const { @@ -89,15 +101,33 @@ MultiPoint::bounding_box() const return BoundingBox(this->points); } -void +bool +MultiPoint::has_duplicate_points() const +{ + for (size_t i = 1; i < points.size(); ++i) + if (points[i-1].coincides_with(points[i])) + return true; + return false; +} + +bool MultiPoint::remove_duplicate_points() { - for (size_t i = 1; i < this->points.size(); ++i) { - if (this->points.at(i).coincides_with(this->points.at(i-1))) { - this->points.erase(this->points.begin() + i); - --i; + size_t j = 0; + for (size_t i = 1; i < points.size(); ++i) { + if (points[j].coincides_with(points[i])) { + // Just increase index i. + } else { + ++ j; + if (j < i) + points[j] = points[i]; } } + if (++ j < points.size()) { + points.erase(points.begin() + j, points.end()); + return true; + } + return false; } void @@ -141,9 +171,12 @@ MultiPoint::dump_perl() const return ret.str(); } +//FIXME This is very inefficient in term of memory use. +// The recursive algorithm shall run in place, not allocating temporary data in each recursion. Points MultiPoint::_douglas_peucker(const Points &points, const double tolerance) { + assert(points.size() >= 2); Points results; double dmax = 0; size_t index = 0; @@ -160,13 +193,15 @@ MultiPoint::_douglas_peucker(const Points &points, const double tolerance) Points dp0; dp0.reserve(index + 1); dp0.insert(dp0.end(), points.begin(), points.begin() + index + 1); + // Recursive call. Points dp1 = MultiPoint::_douglas_peucker(dp0, tolerance); results.reserve(results.size() + dp1.size() - 1); results.insert(results.end(), dp1.begin(), dp1.end() - 1); dp0.clear(); - dp0.reserve(points.size() - index + 1); + dp0.reserve(points.size() - index); dp0.insert(dp0.end(), points.begin() + index, points.end()); + // Recursive call. dp1 = MultiPoint::_douglas_peucker(dp0, tolerance); results.reserve(results.size() + dp1.size()); results.insert(results.end(), dp1.begin(), dp1.end()); diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp index d057b14f1..99bc79281 100644 --- a/xs/src/libslic3r/MultiPoint.hpp +++ b/xs/src/libslic3r/MultiPoint.hpp @@ -22,17 +22,25 @@ class MultiPoint void scale(double factor); void translate(double x, double y); void translate(const Point &vector); + void rotate(double angle); void rotate(double angle, const Point ¢er); void reverse(); Point first_point() const; virtual Point last_point() const = 0; virtual Lines lines() const = 0; double length() const; - bool is_valid() const; + bool is_valid() const { return this->points.size() >= 2; } + int find_point(const Point &point) const; bool has_boundary_point(const Point &point) const; BoundingBox bounding_box() const; - void remove_duplicate_points(); + + // Return true if there are exact duplicates. + bool has_duplicate_points() const; + + // Remove exact duplicates, return true if any duplicate has been removed. + bool remove_duplicate_points(); + void append(const Point &point); void append(const Points &points); void append(const Points::const_iterator &begin, const Points::const_iterator &end); @@ -42,6 +50,6 @@ class MultiPoint static Points _douglas_peucker(const Points &points, const double tolerance); }; -} +} // namespace Slic3r #endif diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index f9e6a8c6e..83056e868 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -54,8 +54,7 @@ PerimeterGenerator::process() for (Surfaces::const_iterator surface = this->slices->surfaces.begin(); surface != this->slices->surfaces.end(); ++surface) { // detect how many perimeters must be generated for this island - signed short loop_number = this->config->perimeters + surface->extra_perimeters; - loop_number--; // 0-indexed loops + const int loop_number = this->config->perimeters + surface->extra_perimeters -1; // 0-indexed loops Polygons gaps; @@ -67,7 +66,7 @@ PerimeterGenerator::process() ThickPolylines thin_walls; // we loop one time more than needed in order to find gaps after the last perimeter was applied - for (signed short i = 0; i <= loop_number+1; ++i) { // outer loop is 0 + for (int i = 0; i <= loop_number+1; ++i) { // outer loop is 0 Polygons offsets; if (i == 0) { // the minimum thickness of a single loop is: @@ -116,15 +115,25 @@ PerimeterGenerator::process() */ } } else { + //FIXME Is this offset correct if the line width of the inner perimeters differs + // from the line width of the infill? coord_t distance = (i == 1) ? ext_pspacing2 : pspacing; if (this->config->thin_walls) { + // This path will ensure, that the perimeters do not overfill, as in + // prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters + // excessively, creating gaps, which then need to be filled in by the not very + // reliable gap fill algorithm. + // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than + // the original. offsets = offset2( last, -(distance + min_spacing/2 - 1), +(min_spacing/2 - 1) ); } else { + // If "detect thin walls" is not enabled, this paths will be entered, which + // leads to overflows, as in prusa3d/Slic3r GH #32 offsets = offset( last, -distance @@ -160,16 +169,16 @@ PerimeterGenerator::process() } // nest loops: holes first - for (signed short d = 0; d <= loop_number; ++d) { + for (int d = 0; d <= loop_number; ++d) { PerimeterGeneratorLoops &holes_d = holes[d]; // loop through all holes having depth == d - for (signed short i = 0; i < holes_d.size(); ++i) { + for (int i = 0; i < (int)holes_d.size(); ++i) { const PerimeterGeneratorLoop &loop = holes_d[i]; // find the hole loop that contains this one, if any - for (signed short t = d+1; t <= loop_number; ++t) { - for (signed short j = 0; j < holes[t].size(); ++j) { + for (int t = d+1; t <= loop_number; ++t) { + for (int j = 0; j < (int)holes[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = holes[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); @@ -181,8 +190,8 @@ PerimeterGenerator::process() } // if no hole contains this hole, find the contour loop that contains it - for (signed short t = loop_number; t >= 0; --t) { - for (signed short j = 0; j < contours[t].size(); ++j) { + for (int t = loop_number; t >= 0; --t) { + for (int j = 0; j < (int)contours[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); @@ -197,16 +206,16 @@ PerimeterGenerator::process() } // nest contour loops - for (signed short d = loop_number; d >= 1; --d) { + for (int d = loop_number; d >= 1; --d) { PerimeterGeneratorLoops &contours_d = contours[d]; // loop through all contours having depth == d - for (signed short i = 0; i < contours_d.size(); ++i) { + for (int i = 0; i < (int)contours_d.size(); ++i) { const PerimeterGeneratorLoop &loop = contours_d[i]; // find the contour loop that contains it - for (signed short t = d-1; t >= 0; --t) { - for (signed short j = 0; j < contours[t].size(); ++j) { + for (int t = d-1; t >= 0; --t) { + for (int j = 0; j < contours[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); @@ -270,6 +279,8 @@ PerimeterGenerator::process() are not subtracted from fill surfaces (they might be too short gaps that medial axis skips but infill might join with other infill regions and use zigzag). */ + //FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing, + // therefore it may cover the area, but no the volume. last = diff(last, gap_fill.grow()); } } @@ -287,10 +298,6 @@ PerimeterGenerator::process() inset += pspacing/2; } - // only apply infill overlap if we actually have one perimeter - if (inset > 0) - inset -= this->config->get_abs_value("infill_overlap", inset + ispacing/2); - { ExPolygons expp = union_ex(last); @@ -343,8 +350,7 @@ PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, && !(this->object_config->support_material && this->object_config->support_material_contact_distance.value == 0)) { // get non-overhang paths by intersecting this loop with the grown lower slices { - Polylines polylines; - intersection((Polygons)loop->polygon, this->_lower_slices_p, &polylines); + Polylines polylines = intersection_pl(loop->polygon, this->_lower_slices_p); for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) { ExtrusionPath path(role); @@ -360,8 +366,7 @@ PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, // outside the grown lower slices (thus where the distance between // the loop centerline and original lower slices is >= half nozzle diameter { - Polylines polylines; - diff((Polygons)loop->polygon, this->_lower_slices_p, &polylines); + Polylines polylines = diff_pl(loop->polygon, this->_lower_slices_p); for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) { ExtrusionPath path(erOverhangPerimeter); @@ -447,7 +452,7 @@ PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRo ExtrusionPath path(role); ThickLines lines = p->thicklines(); - for (size_t i = 0; i < lines.size(); ++i) { + for (int i = 0; i < (int)lines.size(); ++i) { const ThickLine& line = lines[i]; const coordf_t line_len = line.length(); @@ -455,7 +460,7 @@ PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRo double thickness_delta = fabs(line.a_width - line.b_width); if (thickness_delta > tolerance) { - const unsigned short segments = ceil(thickness_delta / tolerance); + const size_t segments = ceil(thickness_delta / tolerance); const coordf_t seg_len = line_len / segments; Points pp; std::vector width; @@ -537,12 +542,6 @@ PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRo return coll; } -bool -PerimeterGeneratorLoop::is_external() const -{ - return this->depth == 0; -} - bool PerimeterGeneratorLoop::is_internal_contour() const { diff --git a/xs/src/libslic3r/PerimeterGenerator.hpp b/xs/src/libslic3r/PerimeterGenerator.hpp index eb3fa0fcb..0e7fbd3e4 100644 --- a/xs/src/libslic3r/PerimeterGenerator.hpp +++ b/xs/src/libslic3r/PerimeterGenerator.hpp @@ -11,25 +11,33 @@ namespace Slic3r { -class PerimeterGeneratorLoop; -typedef std::vector PerimeterGeneratorLoops; - +// Hierarchy of perimeters. class PerimeterGeneratorLoop { - public: +public: + // Polygon of this contour. Polygon polygon; + // Is it a contour or a hole? + // Contours are CCW oriented, holes are CW oriented. bool is_contour; + // Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole. unsigned short depth; + // Children contour, may be both CCW and CW oriented (outer contours or holes). std::vector children; PerimeterGeneratorLoop(Polygon polygon, unsigned short depth) : polygon(polygon), is_contour(false), depth(depth) {}; - bool is_external() const; + // External perimeter. It may be CCW or CW oriented (outer contour or hole contour). + bool is_external() const { return this->depth == 0; } + // An island, which may have holes, but it does not have another internal island. bool is_internal_contour() const; }; +typedef std::vector PerimeterGeneratorLoops; + class PerimeterGenerator { - public: +public: + // Inputs: const SurfaceCollection* slices; const ExPolygonCollection* lower_slices; double layer_height; @@ -41,14 +49,26 @@ class PerimeterGenerator { PrintRegionConfig* config; PrintObjectConfig* object_config; PrintConfig* print_config; + // Outputs: ExtrusionEntityCollection* loops; ExtrusionEntityCollection* gap_fill; SurfaceCollection* fill_surfaces; - PerimeterGenerator(const SurfaceCollection* slices, double layer_height, Flow flow, - PrintRegionConfig* config, PrintObjectConfig* object_config, - PrintConfig* print_config, ExtrusionEntityCollection* loops, - ExtrusionEntityCollection* gap_fill, SurfaceCollection* fill_surfaces) + PerimeterGenerator( + // Input: + const SurfaceCollection* slices, + double layer_height, + Flow flow, + PrintRegionConfig* config, + PrintObjectConfig* object_config, + PrintConfig* print_config, + // Output: + // Loops with the external thin walls + ExtrusionEntityCollection* loops, + // Gaps without the thin walls + ExtrusionEntityCollection* gap_fill, + // Infills without the gap fills + SurfaceCollection* fill_surfaces) : slices(slices), lower_slices(NULL), layer_height(layer_height), layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow), overhang_flow(flow), solid_infill_flow(flow), diff --git a/xs/src/libslic3r/PlaceholderParser.cpp b/xs/src/libslic3r/PlaceholderParser.cpp index cddfa21fa..501349d5c 100644 --- a/xs/src/libslic3r/PlaceholderParser.cpp +++ b/xs/src/libslic3r/PlaceholderParser.cpp @@ -3,14 +3,22 @@ #include #include #include -#include // provides **environ +#ifdef _MSC_VER + #include // provides **_environ +#else + #include // provides **environ +#endif #ifdef __APPLE__ #include #undef environ #define environ (*_NSGetEnviron()) #else - extern char **environ; + #ifdef _MSC_VER + #define environ _environ + #else + extern char **environ; + #endif #endif namespace Slic3r { diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp index 29ce0b153..5269a4c11 100644 --- a/xs/src/libslic3r/Point.cpp +++ b/xs/src/libslic3r/Point.cpp @@ -54,19 +54,28 @@ Point::translate(const Vector &vector) this->translate(vector.x, vector.y); } +void +Point::rotate(double angle) +{ + double cur_x = (double)this->x; + double cur_y = (double)this->y; + double s = sin(angle); + double c = cos(angle); + this->x = (coord_t)round(c * cur_x - s * cur_y); + this->y = (coord_t)round(c * cur_y + s * cur_x); +} + void Point::rotate(double angle, const Point ¢er) { double cur_x = (double)this->x; double cur_y = (double)this->y; - this->x = (coord_t)round( (double)center.x + cos(angle) * (cur_x - (double)center.x) - sin(angle) * (cur_y - (double)center.y) ); - this->y = (coord_t)round( (double)center.y + cos(angle) * (cur_y - (double)center.y) + sin(angle) * (cur_x - (double)center.x) ); -} - -bool -Point::coincides_with(const Point &point) const -{ - return this->x == point.x && this->y == point.y; + double s = sin(angle); + double c = cos(angle); + double dx = cur_x - (double)center.x; + double dy = cur_y - (double)center.y; + this->x = (coord_t)round( (double)center.x + c * dx - s * dy ); + this->y = (coord_t)round( (double)center.y + c * dy + s * dx ); } bool @@ -295,12 +304,40 @@ Point::vector_to(const Point &point) const return Vector(point.x - this->x, point.y - this->y); } +// Align a coordinate to a grid. The coordinate may be negative, +// the aligned value will never be bigger than the original one. +static coord_t +_align_to_grid(const coord_t coord, const coord_t spacing) { + // Current C++ standard defines the result of integer division to be rounded to zero, + // for both positive and negative numbers. Here we want to round down for negative + // numbers as well. + assert(spacing > 0); + coord_t aligned = (coord < 0) ? + ((coord - spacing + 1) / spacing) * spacing : + (coord / spacing) * spacing; + assert(aligned <= coord); + return aligned; +} + +void +Point::align_to_grid(const Point &spacing, const Point &base) +{ + this->x = base.x + _align_to_grid(this->x - base.x, spacing.x); + this->y = base.y + _align_to_grid(this->y - base.y, spacing.y); +} + Point operator+(const Point& point1, const Point& point2) { return Point(point1.x + point2.x, point1.y + point2.y); } +Point +operator-(const Point& point1, const Point& point2) +{ + return Point(point1.x - point2.x, point1.y - point2.y); +} + Point operator*(double scalar, const Point& point2) { @@ -349,13 +386,26 @@ Pointf::translate(const Vectorf &vector) this->translate(vector.x, vector.y); } +void +Pointf::rotate(double angle) +{ + double cur_x = this->x; + double cur_y = this->y; + double s = sin(angle); + double c = cos(angle); + this->x = c * cur_x - s * cur_y; + this->y = c * cur_y + s * cur_x; +} + void Pointf::rotate(double angle, const Pointf ¢er) { double cur_x = this->x; double cur_y = this->y; - this->x = center.x + cos(angle) * (cur_x - center.x) - sin(angle) * (cur_y - center.y); - this->y = center.y + cos(angle) * (cur_y - center.y) + sin(angle) * (cur_x - center.x); + double s = sin(angle); + double c = cos(angle); + this->x = center.x + c * (cur_x - center.x) - s * (cur_y - center.y); + this->y = center.y + c * (cur_y - center.y) + s * (cur_x - center.x); } Pointf diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 6638e0eb3..7fd05ff48 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -2,10 +2,10 @@ #define slic3r_Point_hpp_ #include "libslic3r.h" -#include #include -#include #include +#include +#include namespace Slic3r { @@ -42,8 +42,19 @@ class Point void scale(double factor); void translate(double x, double y); void translate(const Vector &vector); + void rotate(double angle); void rotate(double angle, const Point ¢er); - bool coincides_with(const Point &point) const; + Point rotated(double angle) const { + Point p(*this); + p.rotate(angle); + return p; + } + Point rotated(double angle, const Point ¢er) const { + Point p(*this); + p.rotate(angle, center); + return p; + } + bool coincides_with(const Point &point) const { return this->x == point.x && this->y == point.y; } bool coincides_with_epsilon(const Point &point) const; int nearest_point_index(const Points &points) const; int nearest_point_index(const PointConstPtrs &points) const; @@ -61,11 +72,25 @@ class Point Point projection_onto(const Line &line) const; Point negative() const; Vector vector_to(const Point &point) const; + void align_to_grid(const Point &spacing, const Point &base = Point(0,0)); }; Point operator+(const Point& point1, const Point& point2); +Point operator-(const Point& point1, const Point& point2); Point operator*(double scalar, const Point& point2); +inline Points& +operator+=(Points &dst, const Points &src) { + append_to(dst, src); + return dst; +}; + +inline Points& +operator+=(Points &dst, const Point &p) { + dst.push_back(p); + return dst; +}; + class Point3 : public Point { public: @@ -92,6 +117,7 @@ class Pointf void scale(double factor); void translate(double x, double y); void translate(const Vectorf &vector); + void rotate(double angle); void rotate(double angle, const Pointf ¢er); Pointf negative() const; Vectorf vector_to(const Pointf &point) const; @@ -113,6 +139,16 @@ class Pointf3 : public Pointf Vectorf3 vector_to(const Pointf3 &point) const; }; +template +inline Points +to_points(const std::vector &items) +{ + Points pp; + for (typename std::vector::const_iterator it = items.begin(); it != items.end(); ++it) + append_to(pp, (Points)*it); + return pp; +} + } // start Boost diff --git a/xs/src/libslic3r/Polygon.cpp b/xs/src/libslic3r/Polygon.cpp index 102838809..e36512970 100644 --- a/xs/src/libslic3r/Polygon.cpp +++ b/xs/src/libslic3r/Polygon.cpp @@ -59,6 +59,7 @@ Polygon::split_at_vertex(const Point &point) const return Polyline(); } +// Split a closed polygon into an open polyline, with the split point duplicated at both ends. Polyline Polygon::split_at_index(int index) const { @@ -71,6 +72,7 @@ Polygon::split_at_index(int index) const return polyline; } +// Split a closed polygon into an open polyline, with the split point duplicated at both ends. Polyline Polygon::split_at_first_point() const { @@ -86,17 +88,13 @@ Polygon::equally_spaced_points(double distance) const double Polygon::area() const { - ClipperLib::Path p; - Slic3rMultiPoint_to_ClipperPath(*this, &p); - return ClipperLib::Area(p); + return ClipperLib::Area(Slic3rMultiPoint_to_ClipperPath(*this)); } bool Polygon::is_counter_clockwise() const { - ClipperLib::Path p; - Slic3rMultiPoint_to_ClipperPath(*this, &p); - return ClipperLib::Orientation(p); + return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this)); } bool @@ -131,6 +129,8 @@ Polygon::is_valid() const return this->points.size() >= 3; } +// Does an unoriented polygon contain a point? +// Tested by counting intersections along a horizontal line. bool Polygon::contains(const Point &point) const { @@ -139,6 +139,8 @@ Polygon::contains(const Point &point) const Points::const_iterator i = this->points.begin(); Points::const_iterator j = this->points.end() - 1; for (; i != this->points.end(); j = i++) { + //FIXME this test is not numerically robust. Particularly, it does not handle horizontal segments at y == point.y well. + // Does the ray with y == point.y intersect this line segment? if ( ((i->y > point.y) != (j->y > point.y)) && ((double)point.x < (double)(j->x - i->x) * (double)(point.y - i->y) / (double)(j->y - i->y) + (double)i->x) ) result = !result; @@ -157,10 +159,7 @@ Polygon::simplify(double tolerance) const Polygon p(MultiPoint::_douglas_peucker(points, tolerance)); p.points.pop_back(); - Polygons pp; - pp.push_back(p); - simplify_polygons(pp, &pp); - return pp; + return simplify_polygons(p); } void diff --git a/xs/src/libslic3r/Polygon.hpp b/xs/src/libslic3r/Polygon.hpp index ccde4a740..1b9887218 100644 --- a/xs/src/libslic3r/Polygon.hpp +++ b/xs/src/libslic3r/Polygon.hpp @@ -6,11 +6,11 @@ #include #include "Line.hpp" #include "MultiPoint.hpp" -#include "Polyline.hpp" namespace Slic3r { class Polygon; +class Polyline; typedef std::vector Polygons; class Polygon : public MultiPoint { @@ -25,7 +25,9 @@ class Polygon : public MultiPoint { Point last_point() const; virtual Lines lines() const; Polyline split_at_vertex(const Point &point) const; + // Split a closed polygon into an open polyline, with the split point duplicated at both ends. Polyline split_at_index(int index) const; + // Split a closed polygon into an open polyline, with the split point duplicated at both ends. Polyline split_at_first_point() const; Points equally_spaced_points(double distance) const; double area() const; @@ -34,6 +36,8 @@ class Polygon : public MultiPoint { bool make_counter_clockwise(); bool make_clockwise(); bool is_valid() const; + // Does an unoriented polygon contain a point? + // Tested by counting intersections along a horizontal line. bool contains(const Point &point) const; Polygons simplify(double tolerance) const; void simplify(double tolerance, Polygons &polygons) const; @@ -44,6 +48,18 @@ class Polygon : public MultiPoint { Points convex_points(double angle = PI) const; }; +inline Polygons +operator+(Polygons src1, const Polygons &src2) { + append_to(src1, src2); + return src1; +}; + +inline Polygons& +operator+=(Polygons &dst, const Polygons &src2) { + append_to(dst, src2); + return dst; +}; + } // start Boost diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp index 4d6673f2c..dfd87fef7 100644 --- a/xs/src/libslic3r/Polyline.hpp +++ b/xs/src/libslic3r/Polyline.hpp @@ -4,6 +4,7 @@ #include "libslic3r.h" #include "Line.hpp" #include "MultiPoint.hpp" +#include "Polygon.hpp" #include #include @@ -42,6 +43,24 @@ class ThickPolyline : public Polyline { void reverse(); }; +inline Polylines +to_polylines(const Polygons &polygons) +{ + Polylines pp; + for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) + pp.push_back((Polyline)*it); + return pp; +} + +inline Polylines +to_polylines(const Lines &lines) +{ + Polylines pp; + for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) + pp.push_back((Polyline)*it); + return pp; +} + } #endif diff --git a/xs/src/libslic3r/PolylineCollection.cpp b/xs/src/libslic3r/PolylineCollection.cpp index f3077b0e1..756fa7a82 100644 --- a/xs/src/libslic3r/PolylineCollection.cpp +++ b/xs/src/libslic3r/PolylineCollection.cpp @@ -2,50 +2,133 @@ namespace Slic3r { -void -PolylineCollection::chained_path(PolylineCollection* retval, bool no_reverse) const +struct Chaining { - if (this->polylines.empty()) return; - this->chained_path_from(this->polylines.front().first_point(), retval, no_reverse); -} + Point first; + Point last; + size_t idx; +}; -void -PolylineCollection::chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse) const +#ifndef sqr + #define sqr(x) (x * x); +#endif + +template +inline int nearest_point_index(const std::vector &pairs, const Point &start_near, bool no_reverse) { - Polylines my_paths = this->polylines; - - Points endpoints; - for (Polylines::const_iterator it = my_paths.begin(); it != my_paths.end(); ++it) { - endpoints.push_back(it->first_point()); - if (no_reverse) { - endpoints.push_back(it->first_point()); - } else { - endpoints.push_back(it->last_point()); + T dmin = std::numeric_limits::max(); + int idx = 0; + for (std::vector::const_iterator it = pairs.begin(); it != pairs.end(); ++it) { + T d = sqr(T(start_near.x - it->first.x)); + if (d <= dmin) { + d += sqr(T(start_near.y - it->first.y)); + if (d < dmin) { + idx = (it - pairs.begin()) * 2; + dmin = d; + if (dmin < EPSILON) + break; + } + } + if (! no_reverse) { + d = sqr(T(start_near.x - it->last.x)); + if (d <= dmin) { + d += sqr(T(start_near.y - it->last.y)); + if (d < dmin) { + idx = (it - pairs.begin()) * 2 + 1; + dmin = d; + if (dmin < EPSILON) + break; + } + } } } - - while (!my_paths.empty()) { + return idx; +} + +Polylines PolylineCollection::_chained_path_from( + const Polylines &src, + Point start_near, + bool no_reverse +#if SLIC3R_CPPVER >= 11 + , bool move_from_src +#endif + ) +{ + std::vector endpoints; + endpoints.reserve(src.size()); + for (size_t i = 0; i < src.size(); ++ i) { + Chaining c; + c.first = src[i].first_point(); + if (! no_reverse) + c.last = src[i].last_point(); + c.idx = i; + endpoints.push_back(c); + } + Polylines retval; + while (! endpoints.empty()) { // find nearest point - int start_index = start_near.nearest_point_index(endpoints); - int path_index = start_index/2; - if (start_index % 2 && !no_reverse) { - my_paths.at(path_index).reverse(); + int endpoint_index = nearest_point_index(endpoints, start_near, no_reverse); + assert(endpoint_index >= 0 && endpoint_index < (int)endpoints.size() * 2); +#if SLIC3R_CPPVER > 11 + if (move_from_src) { + retval.push_back(std::move(src[endpoints[endpoint_index/2].idx])); + } else { + retval.push_back(src[endpoints[endpoint_index/2].idx]); } - retval->polylines.push_back(my_paths.at(path_index)); - my_paths.erase(my_paths.begin() + path_index); - endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2); - start_near = retval->polylines.back().last_point(); +#else + retval.push_back(src[endpoints[endpoint_index/2].idx]); +#endif + if (endpoint_index & 1) + retval.back().reverse(); + endpoints.erase(endpoints.begin() + endpoint_index/2); + start_near = retval.back().last_point(); } + return retval; } -Point -PolylineCollection::leftmost_point() const +#if SLIC3R_CPPVER >= 11 +Polylines PolylineCollection::chained_path(Polylines &&src, bool no_reverse) { - if (this->polylines.empty()) CONFESS("leftmost_point() called on empty PolylineCollection"); - Point p = this->polylines.front().leftmost_point(); - for (Polylines::const_iterator it = this->polylines.begin() + 1; it != this->polylines.end(); ++it) { + return (src.empty() || src.front().points.empty()) ? + Polylines() : + _chained_path_from(src, src.front().first_point(), no_reverse, true); +} + +Polylines PolylineCollection::chained_path_from(Polylines &&src, Point start_near, bool no_reverse) +{ + return _chained_path_from(src, start_near, no_reverse, true); +} +#endif + +Polylines PolylineCollection::chained_path(const Polylines &src, bool no_reverse) +{ + return (src.empty() || src.front().points.empty()) ? + Polylines() : + _chained_path_from(src, src.front().first_point(), no_reverse +#if SLIC3R_CPPVER >= 11 + , false +#endif + ); +} + +Polylines PolylineCollection::chained_path_from(const Polylines &src, Point start_near, bool no_reverse) +{ + return _chained_path_from(src, start_near, no_reverse +#if SLIC3R_CPPVER >= 11 + , false +#endif + ); +} + +Point PolylineCollection::leftmost_point(const Polylines &polylines) +{ + if (polylines.empty()) CONFESS("leftmost_point() called on empty PolylineCollection"); + Polylines::const_iterator it = polylines.begin(); + Point p = it->leftmost_point(); + for (++ it; it != polylines.end(); ++it) { Point p2 = it->leftmost_point(); - if (p2.x < p.x) p = p2; + if (p2.x < p.x) + p = p2; } return p; } @@ -56,4 +139,4 @@ PolylineCollection::append(const Polylines &pp) this->polylines.insert(this->polylines.end(), pp.begin(), pp.end()); } -} +} // namespace Slic3r diff --git a/xs/src/libslic3r/PolylineCollection.hpp b/xs/src/libslic3r/PolylineCollection.hpp index d41e8c465..80d609410 100644 --- a/xs/src/libslic3r/PolylineCollection.hpp +++ b/xs/src/libslic3r/PolylineCollection.hpp @@ -8,12 +8,32 @@ namespace Slic3r { class PolylineCollection { - public: + static Polylines _chained_path_from( + const Polylines &src, + Point start_near, + bool no_reverse +#if SLIC3R_CPPVER >= 11 + , bool move_from_src +#endif + ); + +public: Polylines polylines; - void chained_path(PolylineCollection* retval, bool no_reverse = false) const; - void chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse = false) const; - Point leftmost_point() const; + void chained_path(PolylineCollection* retval, bool no_reverse = false) const + { retval->polylines = chained_path(this->polylines, no_reverse); } + void chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse = false) const + { retval->polylines = chained_path_from(this->polylines, start_near, no_reverse); } + Point leftmost_point() const + { return leftmost_point(polylines); } void append(const Polylines &polylines); + + static Point leftmost_point(const Polylines &polylines); +#if SLIC3R_CPPVER >= 11 + static Polylines chained_path(Polylines &&src, bool no_reverse = false); + static Polylines chained_path_from(Polylines &&src, Point start_near, bool no_reverse = false); +#endif + static Polylines chained_path(const Polylines &src, bool no_reverse = false); + static Polylines chained_path_from(const Polylines &src, Point start_near, bool no_reverse = false); }; } diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 8d114f6e2..9dec28e9f 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -1,10 +1,13 @@ #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" +#include "Fill/Fill.hpp" #include "Flow.hpp" #include "Geometry.hpp" #include "SupportMaterial.hpp" #include +#include +#include namespace Slic3r { @@ -133,12 +136,6 @@ Print::clear_regions() this->delete_region(i); } -PrintRegion* -Print::get_region(size_t idx) -{ - return regions.at(idx); -} - PrintRegion* Print::add_region() { @@ -169,7 +166,8 @@ Print::invalidate_state_by_config_options(const std::vector || *opt_key == "min_skirt_length" || *opt_key == "ooze_prevention") { steps.insert(psSkirt); - } else if (*opt_key == "brim_width") { + } else if (*opt_key == "brim_width" + || *opt_key == "brim_connections_width") { steps.insert(psBrim); steps.insert(psSkirt); } else if (*opt_key == "nozzle_diameter" @@ -310,7 +308,7 @@ Print::object_extruders() const FOREACH_REGION(this, region) { // these checks reflect the same logic used in the GUI for enabling/disabling // extruder selection fields - if ((*region)->config.perimeters.value > 0 || this->config.brim_width.value > 0) + if ((*region)->config.perimeters.value > 0 || this->config.brim_width.value > 0 || this->config.brim_connections_width.value > 0) extruders.insert((*region)->config.perimeter_extruder - 1); if ((*region)->config.fill_density.value > 0) @@ -438,6 +436,29 @@ Print::add_model_object(ModelObject* model_object, int idx) // apply config to print object o->config.apply(this->default_object_config); o->config.apply(object_config, true); + + // update placeholders + { + // get the first input file name + std::string input_file; + std::vector v_scale; + FOREACH_OBJECT(this, object) { + const ModelObject &mobj = *(*object)->model_object(); + v_scale.push_back( boost::lexical_cast(mobj.instances[0]->scaling_factor*100) + "%" ); + if (input_file.empty()) + input_file = mobj.input_file; + } + + PlaceholderParser &pp = this->placeholder_parser; + pp.set("scale", v_scale); + if (!input_file.empty()) { + // get basename with and without suffix + const std::string input_basename = boost::filesystem::path(input_file).filename().string(); + pp.set("input_filename", input_basename); + const std::string input_basename_base = input_basename.substr(0, input_basename.find_last_of(".")); + pp.set("input_filename_base", input_basename_base); + } + } } bool @@ -576,7 +597,7 @@ bool Print::has_skirt() const || this->has_infinite_skirt(); } -void +std::string Print::validate() const { if (this->config.complete_objects) { @@ -607,20 +628,16 @@ Print::validate() const object->model_object()->instances.front()->transform_polygon(&convex_hull); // grow convex hull with the clearance margin - { - Polygons grown_hull; - offset(convex_hull, &grown_hull, scale_(this->config.extruder_clearance_radius.value)/2, 1, jtRound, scale_(0.1)); - convex_hull = grown_hull.front(); - } + convex_hull = offset(convex_hull, scale_(this->config.extruder_clearance_radius.value)/2, 1, jtRound, scale_(0.1)).front(); // now we check that no instance of convex_hull intersects any of the previously checked object instances for (Points::const_iterator copy = object->_shifted_copies.begin(); copy != object->_shifted_copies.end(); ++copy) { Polygon p = convex_hull; p.translate(*copy); - if (intersects(a, p)) - throw PrintValidationException("Some objects are too close; your extruder will collide with them."); + if (!intersection(a, p).empty()) + return "Some objects are too close; your extruder will collide with them."; - union_(a, p, &a); + a = union_(a, p); } } } @@ -637,54 +654,23 @@ Print::validate() const // it will be printed as last one so its height doesn't matter object_height.pop_back(); if (!object_height.empty() && object_height.back() > scale_(this->config.extruder_clearance_height.value)) - throw PrintValidationException("Some objects are too tall and cannot be printed without extruder collisions."); + return "Some objects are too tall and cannot be printed without extruder collisions."; } - } + } // end if (this->config.complete_objects) if (this->config.spiral_vase) { size_t total_copies_count = 0; FOREACH_OBJECT(this, i_object) total_copies_count += (*i_object)->copies().size(); if (total_copies_count > 1) - throw PrintValidationException("The Spiral Vase option can only be used when printing a single object."); + return "The Spiral Vase option can only be used when printing a single object."; if (this->regions.size() > 1) - throw PrintValidationException("The Spiral Vase option can only be used when printing single material objects."); + return "The Spiral Vase option can only be used when printing single material objects."; } - { - // find the smallest nozzle diameter - std::set extruders = this->extruders(); - if (extruders.empty()) - throw PrintValidationException("The supplied settings will cause an empty print."); - - std::set nozzle_diameters; - for (std::set::iterator it = extruders.begin(); it != extruders.end(); ++it) - nozzle_diameters.insert(this->config.nozzle_diameter.get_at(*it)); - double min_nozzle_diameter = *std::min_element(nozzle_diameters.begin(), nozzle_diameters.end()); - - FOREACH_OBJECT(this, i_object) { - PrintObject* object = *i_object; - - // validate first_layer_height - double first_layer_height = object->config.get_abs_value("first_layer_height"); - double first_layer_min_nozzle_diameter; - if (object->config.raft_layers > 0) { - // if we have raft layers, only support material extruder is used on first layer - size_t first_layer_extruder = object->config.raft_layers == 1 - ? object->config.support_material_interface_extruder-1 - : object->config.support_material_extruder-1; - first_layer_min_nozzle_diameter = this->config.nozzle_diameter.get_at(first_layer_extruder); - } else { - // if we don't have raft layers, any nozzle diameter is potentially used in first layer - first_layer_min_nozzle_diameter = min_nozzle_diameter; - } - if (first_layer_height > first_layer_min_nozzle_diameter) - throw PrintValidationException("First layer height can't be greater than nozzle diameter"); - - // validate layer_height - if (object->config.layer_height.value > min_nozzle_diameter) - throw PrintValidationException("Layer height can't be greater than nozzle diameter"); - } - } + if (this->extruders().empty()) + return "The supplied settings will cause an empty print."; + + return std::string(); } // the bounding box of objects placed in copies position @@ -794,6 +780,141 @@ Print::skirt_flow() const ); } +void +Print::_make_brim() +{ + if (this->state.is_done(psBrim)) return; + this->state.set_started(psBrim); + + // since this method must be idempotent, we clear brim paths *before* + // checking whether we need to generate them + this->brim.clear(); + + if (this->config.brim_width == 0 && this->config.brim_connections_width == 0) { + this->state.set_done(psBrim); + return; + } + + // brim is only printed on first layer and uses perimeter extruder + const double first_layer_height = this->skirt_first_layer_height(); + const Flow flow = this->brim_flow(); + const double mm3_per_mm = flow.mm3_per_mm(); + + const coord_t grow_distance = flow.scaled_width()/2; + Polygons islands; + + FOREACH_OBJECT(this, object) { + const Layer &layer0 = *(*object)->get_layer(0); + + Polygons object_islands = layer0.slices.contours(); + + if (!(*object)->support_layers.empty()) { + const SupportLayer &support_layer0 = *(*object)->get_support_layer(0); + + for (ExtrusionEntitiesPtr::const_iterator it = support_layer0.support_fills.entities.begin(); + it != support_layer0.support_fills.entities.end(); ++it) + append_to(object_islands, offset((*it)->as_polyline(), grow_distance)); + + for (ExtrusionEntitiesPtr::const_iterator it = support_layer0.support_interface_fills.entities.begin(); + it != support_layer0.support_interface_fills.entities.end(); ++it) + append_to(object_islands, offset((*it)->as_polyline(), grow_distance)); + } + for (Points::const_iterator copy = (*object)->_shifted_copies.begin(); copy != (*object)->_shifted_copies.end(); + ++copy) { + for (Polygons::const_iterator p = object_islands.begin(); p != object_islands.end(); ++p) { + Polygon p2 = *p; + p2.translate(*copy); + islands.push_back(p2); + } + } + } + + Polygons loops; + const int num_loops = floor(this->config.brim_width / flow.width + 0.5); + for (int i = num_loops; i >= 1; --i) { + // JT_SQUARE ensures no vertex is outside the given offset distance + // -0.5 because islands are not represented by their centerlines + // (first offset more, then step back - reverse order than the one used for + // perimeters because here we're offsetting outwards) + append_to(loops, offset2( + islands, + flow.scaled_spacing() * (i + 0.5), + flow.scaled_spacing() * -1.0, + 100000, + ClipperLib::jtSquare + )); + } + + { + Polygons chained = union_pt_chained(loops); + for (Polygons::const_reverse_iterator p = chained.rbegin(); p != chained.rend(); ++p) { + ExtrusionPath path(erSkirt, mm3_per_mm, flow.width, first_layer_height); + path.polyline = p->split_at_first_point(); + this->brim.append(ExtrusionLoop(path)); + } + } + + if (this->config.brim_connections_width > 0) { + // get islands to connects + for (Polygons::iterator p = islands.begin(); p != islands.end(); ++p) + *p = Geometry::convex_hull(p->points); + + islands = offset(islands, flow.scaled_spacing() * (num_loops-0.2), 10000, jtSquare); + + // compute centroid for each island + Points centroids; + centroids.reserve(islands.size()); + for (Polygons::const_iterator p = islands.begin(); p != islands.end(); ++p) + centroids.push_back(p->centroid()); + + // in order to check visibility we need to account for the connections width, + // so let's use grown islands + const double scaled_width = scale_(this->config.brim_connections_width); + const Polygons grown = offset(islands, +scaled_width/2); + + // find pairs of islands having direct visibility + Lines lines; + for (size_t i = 0; i < islands.size(); ++i) { + for (size_t j = (i+1); j < islands.size(); ++j) { + // check visibility + Line line(centroids[i], centroids[j]); + if (diff_pl((Polyline)line, grown).size() != 1) continue; + lines.push_back(line); + } + } + + std::auto_ptr filler(Fill::new_from_type(ipRectilinear)); + filler->min_spacing = flow.spacing(); + filler->dont_adjust = true; + filler->density = 1; + + // subtract already generated connections in order to prevent crossings + // and overextrusion + Polygons other; + + for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { + ExPolygons expp = diff_ex( + offset((Polyline)*line, scaled_width/2), + islands + other + ); + + filler->angle = line->direction(); + for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) { + append_to(other, (Polygons)*ex); + + const Polylines paths = filler->fill_surface(Surface(stBottom, *ex)); + for (Polylines::const_iterator pl = paths.begin(); pl != paths.end(); ++pl) { + ExtrusionPath path(erSkirt, mm3_per_mm, flow.width, first_layer_height); + path.polyline = *pl; + this->brim.append(path); + } + } + } + } + + this->state.set_done(psBrim); +} + PrintRegionConfig Print::_region_config_from_model_volume(const ModelVolume &volume) @@ -834,9 +955,9 @@ Print::auto_assign_extruders(ModelObject* model_object) const // only assign extruders if object has more than one volume if (model_object->volumes.size() < 2) return; - size_t extruders = this->config.nozzle_diameter.values.size(); for (ModelVolumePtrs::const_iterator v = model_object->volumes.begin(); v != model_object->volumes.end(); ++v) { if (!(*v)->material_id().empty()) { + //FIXME Vojtech: This assigns an extruder ID even to a modifier volume, if it has a material assigned. size_t extruder_id = (v - model_object->volumes.begin()) + 1; if (!(*v)->config.has("extruder")) (*v)->config.opt("extruder", true)->value = extruder_id; @@ -844,4 +965,34 @@ Print::auto_assign_extruders(ModelObject* model_object) const } } +std::string +Print::output_filename() +{ + this->placeholder_parser.update_timestamp(); + return this->placeholder_parser.process(this->config.output_filename_format.value); +} + +std::string +Print::output_filepath(const std::string &path) +{ + // if we were supplied no path, generate an automatic one based on our first object's input file + if (path.empty()) { + // get the first input file name + std::string input_file; + FOREACH_OBJECT(this, object) { + input_file = (*object)->model_object()->input_file; + if (!input_file.empty()) break; + } + return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).string(); + } + + // if we were supplied a directory, use it and append our automatically generated filename + boost::filesystem::path p(path); + if (boost::filesystem::is_directory(p)) + return (p / this->output_filename()).string(); + + // if we were supplied a file which is not a directory, use it + return path; +} + } diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 69b927b1b..3bd63e5dd 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -3,8 +3,9 @@ #include "libslic3r.h" #include +#include #include -#include +#include #include "BoundingBox.hpp" #include "Flow.hpp" #include "PrintConfig.hpp" @@ -20,7 +21,7 @@ class Print; class PrintObject; class ModelObject; - +// Print step IDs for keeping track of the print state. enum PrintStep { psSkirt, psBrim, }; @@ -29,11 +30,7 @@ enum PrintObjectStep { posInfill, posSupportMaterial, }; -class PrintValidationException : public std::runtime_error { - public: - PrintValidationException(const std::string &error) : std::runtime_error(error) {}; -}; - +// To be instantiated over PrintStep or PrintObjectStep enums. template class PrintState { @@ -105,7 +102,7 @@ class PrintObject PrintState state; Print* print(); - ModelObject* model_object(); + ModelObject* model_object() { return this->_model_object; }; Points copies() const; bool add_copy(const Pointf &point); @@ -114,6 +111,8 @@ class PrintObject bool set_copies(const Points &points); bool reload_model_instances(); BoundingBox bounding_box() const; + std::set extruders() const; + std::set support_material_extruders() const; // adds region_id, too, if necessary void add_region_volume(int region_id, int volume_id); @@ -122,6 +121,7 @@ class PrintObject size_t layer_count() const; void clear_layers(); Layer* get_layer(int idx); + // print_z: top of the layer; slice_z: center of the layer. Layer* add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z); void delete_layer(int idx); @@ -137,8 +137,12 @@ class PrintObject bool invalidate_all_steps(); bool has_support_material() const; + void detect_surfaces_type(); void process_external_surfaces(); void bridge_over_infill(); + std::vector _slice_region(size_t region_id, std::vector z, bool modifier); + void _make_perimeters(); + void _infill(); private: Print* _print; @@ -154,6 +158,7 @@ class PrintObject typedef std::vector PrintObjectPtrs; typedef std::vector PrintRegionPtrs; +// The complete print tray with possibly multiple objects. class Print { public: @@ -182,7 +187,8 @@ class Print bool reload_model_instances(); // methods for handling regions - PrintRegion* get_region(size_t idx); + PrintRegion* get_region(size_t idx) { return this->regions.at(idx); }; + const PrintRegion* get_region(size_t idx) const { return this->regions.at(idx); }; PrintRegion* add_region(); // methods for handling state @@ -195,12 +201,14 @@ class Print bool apply_config(DynamicPrintConfig config); bool has_infinite_skirt() const; bool has_skirt() const; - void validate() const; + // Returns an empty string if valid, otherwise returns an error message. + std::string validate() const; BoundingBox bounding_box() const; BoundingBox total_bounding_box() const; double skirt_first_layer_height() const; Flow brim_flow() const; Flow skirt_flow() const; + void _make_brim(); std::set object_extruders() const; std::set support_material_extruders() const; @@ -209,6 +217,8 @@ class Print double max_allowed_layer_height() const; bool has_support_material() const; void auto_assign_extruders(ModelObject* model_object) const; + std::string output_filename(); + std::string output_filepath(const std::string &path); private: void clear_regions(); diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 3ed618c00..2c0a4fa61 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1,11 +1,10 @@ #include "PrintConfig.hpp" +#include namespace Slic3r { PrintConfigDef::PrintConfigDef() { - t_optiondef_map &Options = this->options; - ConfigOptionDef* def; def = this->add("adaptive_slicing", coBool); @@ -30,10 +29,15 @@ PrintConfigDef::PrintConfigDef() opt->values.push_back(Pointf(0,200)); def->default_value = opt; } + def = this->add("has_heatbed", coBool); + def->label = "Has heated bed"; + def->tooltip = "Unselecting this will suppress automatic generation of bed heating gcode."; + def->cli = "has_heatbed!"; + def->default_value = new ConfigOptionBool(true); def = this->add("bed_temperature", coInt); def->label = "Other layers"; - def->tooltip = "Bed temperature for layers after the first one. Set this to zero to disable bed temperature control commands in the output."; + def->tooltip = "Bed temperature for layers after the first one."; def->cli = "bed-temperature=i"; def->full_label = "Bed temperature"; def->min = 0; @@ -93,6 +97,14 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloat(60); + def = this->add("brim_connections_width", coFloat); + def->label = "Brim connections width"; + def->tooltip = "If set to a positive value, straight connections will be built on the first layer between adjacent objects."; + def->sidetext = "mm"; + def->cli = "brim-connections-width=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(0); + def = this->add("brim_width", coFloat); def->label = "Brim width"; def->tooltip = "Horizontal width of the brim that will be printed around each object on the first layer."; @@ -171,11 +183,13 @@ PrintConfigDef::PrintConfigDef() def->cli = "external-fill-pattern|solid-fill-pattern=s"; def->enum_keys_map = ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("rectilinear"); + def->enum_values.push_back("alignedrectilinear"); def->enum_values.push_back("concentric"); def->enum_values.push_back("hilbertcurve"); def->enum_values.push_back("archimedeanchords"); def->enum_values.push_back("octagramspiral"); def->enum_labels.push_back("Rectilinear"); + def->enum_labels.push_back("Aligned Rectilinear"); def->enum_labels.push_back("Concentric"); def->enum_labels.push_back("Hilbert Curve"); def->enum_labels.push_back("Archimedean Chords"); @@ -306,6 +320,31 @@ PrintConfigDef::PrintConfigDef() def->default_value = opt; } + def = this->add("filament_notes", coStrings); + def->label = "Filament notes"; + def->tooltip = "You can put your notes regarding the filament here."; + def->cli = "filament-notes=s@"; + def->multiline = true; + def->full_width = true; + def->height = 130; + { + ConfigOptionStrings* opt = new ConfigOptionStrings(); + opt->values.push_back(""); + def->default_value = opt; + } + + def = this->add("filament_max_volumetric_speed", coFloats); + def->label = "Max volumetric speed"; + def->tooltip = "Maximum volumetric speed allowed for this filament. Limits the maximum volumetric speed of a print to the minimum of print and filament volumetric speed. Set to zero for no limit."; + def->sidetext = "mm³/s"; + def->cli = "filament-max-volumetric-speed=f@"; + def->min = 0; + { + ConfigOptionFloats* opt = new ConfigOptionFloats(); + opt->values.push_back(0.f); + def->default_value = opt; + } + def = this->add("filament_diameter", coFloats); def->label = "Diameter"; def->tooltip = "Enter your filament diameter here. Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average."; @@ -378,8 +417,11 @@ PrintConfigDef::PrintConfigDef() def->cli = "fill-pattern=s"; def->enum_keys_map = ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("rectilinear"); + def->enum_values.push_back("alignedrectilinear"); def->enum_values.push_back("grid"); - def->enum_values.push_back("line"); + def->enum_values.push_back("triangles"); + def->enum_values.push_back("stars"); + def->enum_values.push_back("cubic"); def->enum_values.push_back("concentric"); def->enum_values.push_back("honeycomb"); def->enum_values.push_back("3dhoneycomb"); @@ -387,15 +429,18 @@ PrintConfigDef::PrintConfigDef() def->enum_values.push_back("archimedeanchords"); def->enum_values.push_back("octagramspiral"); def->enum_labels.push_back("Rectilinear"); + def->enum_labels.push_back("Aligned Rectilinear"); def->enum_labels.push_back("Grid"); - def->enum_labels.push_back("Line"); + def->enum_labels.push_back("Triangles"); + def->enum_labels.push_back("Stars"); + def->enum_labels.push_back("Cubic"); def->enum_labels.push_back("Concentric"); def->enum_labels.push_back("Honeycomb"); def->enum_labels.push_back("3D Honeycomb"); def->enum_labels.push_back("Hilbert Curve"); def->enum_labels.push_back("Archimedean Chords"); def->enum_labels.push_back("Octagram Spiral"); - def->default_value = new ConfigOptionEnum(ipHoneycomb); + def->default_value = new ConfigOptionEnum(ipStars); def = this->add("first_layer_acceleration", coFloat); def->label = "First layer"; @@ -478,6 +523,7 @@ PrintConfigDef::PrintConfigDef() def->cli = "gcode-flavor=s"; def->enum_keys_map = ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("reprap"); + def->enum_values.push_back("repetier"); def->enum_values.push_back("teacup"); def->enum_values.push_back("makerware"); def->enum_values.push_back("sailfish"); @@ -485,7 +531,8 @@ PrintConfigDef::PrintConfigDef() def->enum_values.push_back("machinekit"); def->enum_values.push_back("smoothie"); def->enum_values.push_back("no-extrusion"); - def->enum_labels.push_back("RepRap (Marlin/Sprinter/Repetier)"); + def->enum_labels.push_back("RepRap (Marlin/Sprinter)"); + def->enum_labels.push_back("Repetier"); def->enum_labels.push_back("Teacup"); def->enum_labels.push_back("MakerWare (MakerBot)"); def->enum_labels.push_back("Sailfish (MakerBot)"); @@ -549,7 +596,7 @@ PrintConfigDef::PrintConfigDef() def->sidetext = "mm or %"; def->cli = "infill-overlap=s"; def->ratio_over = "perimeter_extrusion_width"; - def->default_value = new ConfigOptionFloatOrPercent(15, true); + def->default_value = new ConfigOptionFloatOrPercent(55, true); def = this->add("infill_speed", coFloat); def->label = "Infill"; @@ -923,7 +970,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("retract_speed", coFloats); def->label = "Speed"; def->full_label = "Retraction Speed"; - def->tooltip = "The speed for retractions (it only applies to the extruder motor)."; + def->tooltip = "The speed for retractions (it only applies to the extruder motor). If you use the Firmware Retraction option, please note this value still affects the auto-speed pressure regulator."; def->sidetext = "mm/s"; def->cli = "retract-speed=f@"; { @@ -1250,9 +1297,11 @@ PrintConfigDef::PrintConfigDef() def->cli = "threads|j=i"; def->readonly = true; def->min = 1; - def->max = 16; - def->default_value = new ConfigOptionInt(2); - + { + unsigned int threads = boost::thread::hardware_concurrency(); + def->default_value = new ConfigOptionInt(threads > 0 ? threads : 2); + } + def = this->add("toolchange_gcode", coString); def->label = "Tool change G-code"; def->tooltip = "This custom code is inserted right before every extruder change. Note that you can use placeholder variables for all Slic3r settings as well as [previous_extruder] and [next_extruder]."; @@ -1400,10 +1449,32 @@ PrintConfigBase::min_object_distance() const CLIConfigDef::CLIConfigDef() { - t_optiondef_map &Options = this->options; - ConfigOptionDef* def; + def = this->add("cut", coFloat); + def->label = "Cut"; + def->tooltip = "Cut model at the given Z."; + def->cli = "cut"; + def->default_value = new ConfigOptionFloat(0); + + def = this->add("cut_grid", coFloat); + def->label = "Cut"; + def->tooltip = "Cut model in the XY plane into tiles of the specified max size."; + def->cli = "cut-grid"; + def->default_value = new ConfigOptionPoint(); + + def = this->add("cut_x", coFloat); + def->label = "Cut"; + def->tooltip = "Cut model at the given X."; + def->cli = "cut-x"; + def->default_value = new ConfigOptionFloat(0); + + def = this->add("cut_y", coFloat); + def->label = "Cut"; + def->tooltip = "Cut model at the given Y."; + def->cli = "cut-y"; + def->default_value = new ConfigOptionFloat(0); + def = this->add("export_obj", coBool); def->label = "Export SVG"; def->tooltip = "Export the model as OBJ."; @@ -1446,6 +1517,18 @@ CLIConfigDef::CLIConfigDef() def->cli = "rotate"; def->default_value = new ConfigOptionFloat(0); + def = this->add("rotate_x", coFloat); + def->label = "Rotate around X"; + def->tooltip = "Rotation angle around the X axis in degrees (0-360, default: 0)."; + def->cli = "rotate-x"; + def->default_value = new ConfigOptionFloat(0); + + def = this->add("rotate_y", coFloat); + def->label = "Rotate around Y"; + def->tooltip = "Rotation angle around the Y axis in degrees (0-360, default: 0)."; + def->cli = "rotate-y"; + def->default_value = new ConfigOptionFloat(0); + def = this->add("save", coString); def->label = "Save config file"; def->tooltip = "Save configuration to the specified file."; diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 53c4191f9..1c418173c 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -1,3 +1,20 @@ +// Configuration store of Slic3r. +// +// The configuration store is either static or dynamic. +// DynamicPrintConfig is used mainly at the user interface. while the StaticPrintConfig is used +// during the slicing and the g-code generation. +// +// The classes derived from StaticPrintConfig form a following hierarchy. +// Virtual inheritance is used for some of the parent objects. +// +// FullPrintConfig +// PrintObjectConfig +// PrintRegionConfig +// PrintConfig +// GCodeConfig +// HostConfig +// + #ifndef slic3r_PrintConfig_hpp_ #define slic3r_PrintConfig_hpp_ @@ -9,11 +26,13 @@ namespace Slic3r { enum GCodeFlavor { - gcfRepRap, gcfTeacup, gcfMakerWare, gcfSailfish, gcfMach3, gcfMachinekit, gcfNoExtrusion, gcfSmoothie, + gcfRepRap, gcfTeacup, gcfMakerWare, gcfSailfish, gcfMach3, gcfMachinekit, gcfNoExtrusion, gcfSmoothie, gcfRepetier, }; enum InfillPattern { - ipRectilinear, ipGrid, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, + ipRectilinear, ipGrid, ipAlignedRectilinear, + ipTriangles, ipStars, ipCubic, + ipConcentric, ipHoneycomb, ip3DHoneycomb, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, }; @@ -28,6 +47,7 @@ enum SeamPosition { template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { t_config_enum_values keys_map; keys_map["reprap"] = gcfRepRap; + keys_map["repetier"] = gcfRepetier; keys_map["teacup"] = gcfTeacup; keys_map["makerware"] = gcfMakerWare; keys_map["sailfish"] = gcfSailfish; @@ -41,8 +61,11 @@ template<> inline t_config_enum_values ConfigOptionEnum::get_enum_v template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { t_config_enum_values keys_map; keys_map["rectilinear"] = ipRectilinear; + keys_map["alignedrectilinear"] = ipAlignedRectilinear; keys_map["grid"] = ipGrid; - keys_map["line"] = ipLine; + keys_map["triangles"] = ipTriangles; + keys_map["stars"] = ipStars; + keys_map["cubic"] = ipCubic; keys_map["concentric"] = ipConcentric; keys_map["honeycomb"] = ipHoneycomb; keys_map["3dhoneycomb"] = ip3DHoneycomb; @@ -69,14 +92,19 @@ template<> inline t_config_enum_values ConfigOptionEnum::get_enum_ return keys_map; } +// Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs. +// Does not store the actual values, but defines default values. class PrintConfigDef : public ConfigDef { public: PrintConfigDef(); }; +// The one and only global definition of SLic3r configuration options. +// This definition is constant. extern PrintConfigDef print_config_def; +// Slic3r configuration storage with print_config_def assigned. class PrintConfigBase : public virtual ConfigBase { public: @@ -87,6 +115,12 @@ class PrintConfigBase : public virtual ConfigBase double min_object_distance() const; }; +// Slic3r dynamic configuration, used to override the configuration +// per object, per modification volume or per printing material. +// The dynamic configuration is also used to store user modifications of the print global parameters, +// so the modified configuration values may be diffed against the active configuration +// to invalidate the proper slicing resp. g-code generation processing steps. +// This object is mapped to Perl as Slic3r::Config. class DynamicPrintConfig : public PrintConfigBase, public DynamicConfig { public: @@ -94,12 +128,14 @@ class DynamicPrintConfig : public PrintConfigBase, public DynamicConfig void normalize(); }; + class StaticPrintConfig : public PrintConfigBase, public StaticConfig { public: StaticPrintConfig() : PrintConfigBase(), StaticConfig() {}; }; +// This object is mapped to Perl as Slic3r::Config::PrintObject. class PrintObjectConfig : public virtual StaticPrintConfig { public: @@ -167,6 +203,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig }; }; +// This object is mapped to Perl as Slic3r::Config::PrintRegion. class PrintRegionConfig : public virtual StaticPrintConfig { public: @@ -246,6 +283,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig }; }; +// This object is mapped to Perl as Slic3r::Config::GCode. class GCodeConfig : public virtual StaticPrintConfig { public: @@ -254,6 +292,7 @@ class GCodeConfig : public virtual StaticPrintConfig ConfigOptionString extrusion_axis; ConfigOptionFloats extrusion_multiplier; ConfigOptionFloats filament_diameter; + ConfigOptionFloats filament_max_volumetric_speed; ConfigOptionBool gcode_comments; ConfigOptionEnum gcode_flavor; ConfigOptionString layer_gcode; @@ -286,6 +325,7 @@ class GCodeConfig : public virtual StaticPrintConfig OPT_PTR(extrusion_axis); OPT_PTR(extrusion_multiplier); OPT_PTR(filament_diameter); + OPT_PTR(filament_max_volumetric_speed); OPT_PTR(gcode_comments); OPT_PTR(gcode_flavor); OPT_PTR(layer_gcode); @@ -322,14 +362,17 @@ class GCodeConfig : public virtual StaticPrintConfig }; }; +// This object is mapped to Perl as Slic3r::Config::Print. class PrintConfig : public GCodeConfig { public: ConfigOptionBool avoid_crossing_perimeters; ConfigOptionPoints bed_shape; + ConfigOptionBool has_heatbed; ConfigOptionInt bed_temperature; ConfigOptionFloat bridge_acceleration; ConfigOptionInt bridge_fan_speed; + ConfigOptionFloat brim_connections_width; ConfigOptionFloat brim_width; ConfigOptionBool complete_objects; ConfigOptionBool cooling; @@ -342,6 +385,7 @@ class PrintConfig : public GCodeConfig ConfigOptionBool fan_always_on; ConfigOptionInt fan_below_layer_time; ConfigOptionStrings filament_colour; + ConfigOptionStrings filament_notes; ConfigOptionFloat first_layer_acceleration; ConfigOptionInt first_layer_bed_temperature; ConfigOptionFloatOrPercent first_layer_extrusion_width; @@ -386,9 +430,11 @@ class PrintConfig : public GCodeConfig virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) { OPT_PTR(avoid_crossing_perimeters); OPT_PTR(bed_shape); + OPT_PTR(has_heatbed); OPT_PTR(bed_temperature); OPT_PTR(bridge_acceleration); OPT_PTR(bridge_fan_speed); + OPT_PTR(brim_connections_width); OPT_PTR(brim_width); OPT_PTR(complete_objects); OPT_PTR(cooling); @@ -401,6 +447,7 @@ class PrintConfig : public GCodeConfig OPT_PTR(fan_always_on); OPT_PTR(fan_below_layer_time); OPT_PTR(filament_colour); + OPT_PTR(filament_notes); OPT_PTR(first_layer_acceleration); OPT_PTR(first_layer_bed_temperature); OPT_PTR(first_layer_extrusion_width); @@ -468,6 +515,7 @@ class HostConfig : public virtual StaticPrintConfig }; }; +// This object is mapped to Perl as Slic3r::Config::Full. class FullPrintConfig : public PrintObjectConfig, public PrintRegionConfig, public PrintConfig, public HostConfig { @@ -492,30 +540,50 @@ class FullPrintConfig }; }; -class SVGExportConfig +class SLAPrintConfig : public virtual StaticPrintConfig { public: + ConfigOptionFloat fill_angle; + ConfigOptionPercent fill_density; + ConfigOptionEnum fill_pattern; ConfigOptionFloatOrPercent first_layer_height; + ConfigOptionFloatOrPercent infill_extrusion_width; ConfigOptionFloat layer_height; + ConfigOptionFloatOrPercent perimeter_extrusion_width; ConfigOptionInt raft_layers; ConfigOptionFloat raft_offset; ConfigOptionBool support_material; ConfigOptionFloatOrPercent support_material_extrusion_width; ConfigOptionFloat support_material_spacing; + ConfigOptionInt threads; - SVGExportConfig() : StaticPrintConfig() { + SLAPrintConfig() : StaticPrintConfig() { this->set_defaults(); + + // override some defaults + this->fill_density.value = 100; + this->fill_pattern.value = ipGrid; + this->infill_extrusion_width.value = 0.5; + this->infill_extrusion_width.percent = false; + this->perimeter_extrusion_width.value = 1; + this->perimeter_extrusion_width.percent = false; }; virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) { + OPT_PTR(fill_angle); + OPT_PTR(fill_density); + OPT_PTR(fill_pattern); OPT_PTR(first_layer_height); + OPT_PTR(infill_extrusion_width); OPT_PTR(layer_height); + OPT_PTR(perimeter_extrusion_width); OPT_PTR(raft_layers); OPT_PTR(raft_offset); OPT_PTR(support_material); OPT_PTR(support_material_extrusion_width); OPT_PTR(support_material_spacing); + OPT_PTR(threads); return NULL; }; @@ -533,6 +601,10 @@ class CLIConfig : public virtual ConfigBase, public StaticConfig { public: + ConfigOptionFloat cut; + ConfigOptionPoint cut_grid; + ConfigOptionFloat cut_x; + ConfigOptionFloat cut_y; ConfigOptionBool export_obj; ConfigOptionBool export_pov; ConfigOptionBool export_svg; @@ -540,6 +612,8 @@ class CLIConfig ConfigOptionStrings load; ConfigOptionString output; ConfigOptionFloat rotate; + ConfigOptionFloat rotate_x; + ConfigOptionFloat rotate_y; ConfigOptionString save; ConfigOptionFloat scale; ConfigOptionPoint3 scale_to_fit; @@ -550,6 +624,10 @@ class CLIConfig }; virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) { + OPT_PTR(cut); + OPT_PTR(cut_grid); + OPT_PTR(cut_x); + OPT_PTR(cut_y); OPT_PTR(export_obj); OPT_PTR(export_pov); OPT_PTR(export_svg); @@ -557,6 +635,8 @@ class CLIConfig OPT_PTR(load); OPT_PTR(output); OPT_PTR(rotate); + OPT_PTR(rotate_x); + OPT_PTR(rotate_y); OPT_PTR(save); OPT_PTR(scale); OPT_PTR(scale_to_fit); diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 5fedcabf3..54b16262e 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -2,6 +2,7 @@ #include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" +#include namespace Slic3r { @@ -41,12 +42,6 @@ PrintObject::print() return this->_print; } -ModelObject* -PrintObject::model_object() -{ - return this->_model_object; -} - Points PrintObject::copies() const { @@ -121,6 +116,28 @@ PrintObject::bounding_box() const return BoundingBox(pp); } +// returns 0-based indices of used extruders +std::set +PrintObject::extruders() const +{ + std::set extruders = this->_print->extruders(); + std::set sm_extruders = this->support_material_extruders(); + extruders.insert(sm_extruders.begin(), sm_extruders.end()); + return extruders; +} + +// returns 0-based indices of used extruders +std::set +PrintObject::support_material_extruders() const +{ + std::set extruders; + if (this->has_support_material()) { + extruders.insert(this->config.support_material_extruder - 1); + extruders.insert(this->config.support_material_interface_extruder - 1); + } + return extruders; +} + void PrintObject::add_region_volume(int region_id, int volume_id) { @@ -219,7 +236,6 @@ PrintObject::invalidate_state_by_config_options(const std::vectorconfig.support_material_enforce_layers > 0; } +// This function analyzes slices of a region (SurfaceCollection slices). +// Each region slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface. +// Initially all slices are of type S_TYPE_INTERNAL. +// Slices are compared against the top / bottom slices and regions and classified to the following groups: +// S_TYPE_TOP - Part of a region, which is not covered by any upper layer. This surface will be filled with a top solid infill. +// S_TYPE_BOTTOMBRIDGE - Part of a region, which is not fully supported, but it hangs in the air, or it hangs losely on a support or a raft. +// S_TYPE_BOTTOM - Part of a region, which is not supported by the same region, but it is supported either by another region, or by a soluble interface layer. +// S_TYPE_INTERNAL - Part of a region, which is supported by the same region type. +// If a part of a region is of S_TYPE_BOTTOM and S_TYPE_TOP, the S_TYPE_BOTTOM wins. +void +PrintObject::detect_surfaces_type() +{ + //Slic3r::debugf "Detecting solid surfaces...\n"; + FOREACH_REGION(this->_print, region) { + size_t region_id = region - this->_print->regions.begin(); + + FOREACH_LAYER(this, layer_it) { + size_t layer_idx = layer_it - this->layers.begin(); + Layer &layer = **layer_it; + LayerRegion &layerm = *layer.get_region(region_id); + // comparison happens against the *full* slices (considering all regions) + // unless internal shells are requested + + const Layer* upper_layer = layer_idx < (this->layer_count()-1) ? this->get_layer(layer_idx+1) : NULL; + const Layer* lower_layer = layer_idx > 0 ? this->get_layer(layer_idx-1) : NULL; + + // collapse very narrow parts (using the safety offset in the diff is not enough) + const float offset = layerm.flow(frExternalPerimeter).scaled_width() / 10.f; + + const Polygons layerm_slices_surfaces = layerm.slices; + + // find top surfaces (difference between current surfaces + // of current layer and upper one) + SurfaceCollection top; + if (upper_layer != NULL) { + const Polygons upper_slices = this->config.interface_shells.value + ? (Polygons)upper_layer->get_region(region_id)->slices + : (Polygons)upper_layer->slices; + + top.append( + offset2_ex( + diff(layerm_slices_surfaces, upper_slices, true), + -offset, offset + ), + stTop + ); + } else { + // if no upper layer, all surfaces of this one are solid + // we clone surfaces because we're going to clear the slices collection + top = layerm.slices; + for (Surfaces::iterator it = top.surfaces.begin(); it != top.surfaces.end(); ++ it) + it->surface_type = stTop; + } + + // find bottom surfaces (difference between current surfaces + // of current layer and lower one) + SurfaceCollection bottom; + if (lower_layer != NULL) { + // If we have soluble support material, don't bridge. The overhang will be squished against a soluble layer separating + // the support from the print. + const SurfaceType surface_type_bottom = + (this->config.support_material.value && this->config.support_material_contact_distance.value == 0) + ? stBottom + : stBottomBridge; + + // Any surface lying on the void is a true bottom bridge (an overhang) + bottom.append( + offset2_ex( + diff(layerm_slices_surfaces, lower_layer->slices, true), + -offset, offset + ), + surface_type_bottom + ); + + // if user requested internal shells, we need to identify surfaces + // lying on other slices not belonging to this region + if (this->config.interface_shells) { + // non-bridging bottom surfaces: any part of this layer lying + // on something else, excluding those lying on our own region + bottom.append( + offset2_ex( + diff( + intersection(layerm_slices_surfaces, lower_layer->slices), // supported + lower_layer->get_region(region_id)->slices, + true + ), + -offset, offset + ), + stBottom + ); + } + } else { + // if no lower layer, all surfaces of this one are solid + // we clone surfaces because we're going to clear the slices collection + bottom = layerm.slices; + + // if we have raft layers, consider bottom layer as a bridge + // just like any other bottom surface lying on the void + const SurfaceType surface_type_bottom = + (this->config.raft_layers.value > 0 && this->config.support_material_contact_distance.value > 0) + ? stBottomBridge + : stBottom; + for (Surfaces::iterator it = bottom.surfaces.begin(); it != bottom.surfaces.end(); ++ it) + it->surface_type = surface_type_bottom; + } + + // now, if the object contained a thin membrane, we could have overlapping bottom + // and top surfaces; let's do an intersection to discover them and consider them + // as bottom surfaces (to allow for bridge detection) + if (!top.empty() && !bottom.empty()) { + const Polygons top_polygons = to_polygons(STDMOVE(top)); + top.clear(); + top.append( + offset2_ex(diff(top_polygons, bottom, true), -offset, offset), + stTop + ); + } + + // save surfaces to layer + layerm.slices.clear(); + layerm.slices.append(STDMOVE(top)); + layerm.slices.append(STDMOVE(bottom)); + + // find internal surfaces (difference between top/bottom surfaces and others) + { + Polygons topbottom = top; append_to(topbottom, (Polygons)bottom); + + layerm.slices.append( + offset2_ex( + diff(layerm_slices_surfaces, topbottom, true), + -offset, offset + ), + stInternal + ); + } + + /* + Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n", + $layerm->layer->id, scalar(@bottom), scalar(@top), scalar(@internal) if $Slic3r::debug; + */ + + } // for each layer of a region + + /* Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. + Note: this method should be idempotent, but fill_surfaces gets modified + in place. However we're now only using its boundaries (which are invariant) + so we're safe. This guarantees idempotence of prepare_infill() also in case + that combine_infill() turns some fill_surface into VOID surfaces. */ + FOREACH_LAYER(this, layer_it) { + LayerRegion &layerm = *(*layer_it)->get_region(region_id); + + const Polygons fill_boundaries = layerm.fill_surfaces; + layerm.fill_surfaces.clear(); + for (Surfaces::const_iterator surface = layerm.slices.surfaces.begin(); + surface != layerm.slices.surfaces.end(); ++ surface) { + layerm.fill_surfaces.append( + intersection_ex(*surface, fill_boundaries), + surface->surface_type + ); + } + } + } +} + void PrintObject::process_external_surfaces() { @@ -368,13 +549,13 @@ void PrintObject::bridge_over_infill() { FOREACH_REGION(this->_print, region) { - size_t region_id = region - this->_print->regions.begin(); + const size_t region_id = region - this->_print->regions.begin(); // skip bridging in case there are no voids if ((*region)->config.fill_density.value == 100) continue; // get bridge flow - Flow bridge_flow = (*region)->flow( + const Flow bridge_flow = (*region)->flow( frSolidInfill, -1, // layer height, not relevant for bridge flow true, // bridge @@ -383,6 +564,10 @@ PrintObject::bridge_over_infill() *this ); + // get the average extrusion volume per surface unit + const double mm3_per_mm = bridge_flow.mm3_per_mm(); + const double mm3_per_mm2 = mm3_per_mm / bridge_flow.width; + FOREACH_LAYER(this, layer_it) { // skip first layer if (layer_it == this->layers.begin()) continue; @@ -393,6 +578,41 @@ PrintObject::bridge_over_infill() // extract the stInternalSolid surfaces that might be transformed into bridges Polygons internal_solid; layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid); + if (internal_solid.empty()) continue; + + // check whether we should bridge or not according to density + { + // get the normal solid infill flow we would use if not bridging + const Flow normal_flow = layerm->flow(frSolidInfill, false); + + // Bridging over sparse infill has two purposes: + // 1) cover better the gaps of internal sparse infill, especially when + // printing at very low densities; + // 2) provide a greater flow when printing very thin layers where normal + // solid flow would be very poor. + // So we calculate density threshold as interpolation according to normal flow. + // If normal flow would be equal or greater than the bridge flow, we can keep + // a low threshold like 25% in order to bridge only when printing at very low + // densities, when sparse infill has significant gaps. + // If normal flow would be equal or smaller than half the bridge flow, we + // use a higher threshold like 50% in order to bridge in more cases. + // We still never bridge whenever fill density is greater than 50% because + // we would overstuff. + const float min_threshold = 25.0; + const float max_threshold = 50.0; + const float density_threshold = std::max( + std::min( + min_threshold + + (max_threshold - min_threshold) + * (normal_flow.mm3_per_mm() - mm3_per_mm) + / (mm3_per_mm/2 - mm3_per_mm), + max_threshold + ), + min_threshold + ); + + if ((*region)->config.fill_density.value > density_threshold) continue; + } // check whether the lower area is deep enough for absorbing the extra flow // (for obvious physical reasons but also for preventing the bridge extrudates @@ -401,13 +621,23 @@ PrintObject::bridge_over_infill() { Polygons to_bridge_pp = internal_solid; + // Only bridge where internal infill exists below the solid shell matching + // these two conditions: + // 1) its depth is at least equal to our bridge extrusion diameter; + // 2) its free volume (thus considering infill density) is at least equal + // to the volume needed by our bridge flow. + double excess_mm3_per_mm2 = mm3_per_mm2; + // iterate through lower layers spanned by bridge_flow - double bottom_z = layer->print_z - bridge_flow.height; + const double bottom_z = layer->print_z - bridge_flow.height; for (int i = (layer_it - this->layers.begin()) - 1; i >= 0; --i) { const Layer* lower_layer = this->layers[i]; - // stop iterating if layer is lower than bottom_z - if (lower_layer->print_z < bottom_z) break; + // subtract the void volume of this layer + excess_mm3_per_mm2 -= lower_layer->height * (100 - (*region)->config.fill_density.value)/100; + + // stop iterating if both conditions are matched + if (lower_layer->print_z < bottom_z && excess_mm3_per_mm2 <= 0) break; // iterate through regions and collect internal surfaces Polygons lower_internal; @@ -418,9 +648,12 @@ PrintObject::bridge_over_infill() to_bridge_pp = intersection(to_bridge_pp, lower_internal); } + // don't bridge if the volume condition isn't matched + if (excess_mm3_per_mm2 > 0) continue; + // there's no point in bridging too thin/short regions { - double min_width = bridge_flow.scaled_width() * 3; + const double min_width = bridge_flow.scaled_width() * 3; to_bridge_pp = offset2(to_bridge_pp, -min_width, +min_width); } @@ -435,7 +668,7 @@ PrintObject::bridge_over_infill() #endif // compute the remaning internal solid surfaces as difference - ExPolygons not_to_bridge = diff_ex(internal_solid, to_bridge, true); + const ExPolygons not_to_bridge = diff_ex(internal_solid, to_polygons(to_bridge), true); // build the new collection of fill_surfaces { @@ -491,4 +724,177 @@ PrintObject::bridge_over_infill() } } +// called from slice() +std::vector +PrintObject::_slice_region(size_t region_id, std::vector z, bool modifier) +{ + std::vector layers; + std::vector ®ion_volumes = this->region_volumes[region_id]; + if (region_volumes.empty()) return layers; + + ModelObject &object = *this->model_object(); + + // compose mesh + TriangleMesh mesh; + for (std::vector::const_iterator it = region_volumes.begin(); + it != region_volumes.end(); ++it) { + + const ModelVolume &volume = *object.volumes[*it]; + if (volume.modifier != modifier) continue; + + mesh.merge(volume.mesh); + } + if (mesh.facets_count() == 0) return layers; + + // transform mesh + // we ignore the per-instance transformations currently and only + // consider the first one + object.instances[0]->transform_mesh(&mesh, true); + + // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift + mesh.translate( + -unscale(this->_copies_shift.x), + -unscale(this->_copies_shift.y), + -object.bounding_box().min.z + ); + + // perform actual slicing + TriangleMeshSlicer(&mesh).slice(z, &layers); + return layers; +} + +void +PrintObject::_make_perimeters() +{ + if (this->state.is_done(posPerimeters)) return; + this->state.set_started(posPerimeters); + + // merge slices if they were split into types + if (this->typed_slices) { + FOREACH_LAYER(this, layer_it) + (*layer_it)->merge_slices(); + this->typed_slices = false; + this->state.invalidate(posPrepareInfill); + } + + // compare each layer to the one below, and mark those slices needing + // one additional inner perimeter, like the top of domed objects- + + // this algorithm makes sure that at least one perimeter is overlapping + // but we don't generate any extra perimeter if fill density is zero, as they would be floating + // inside the object - infill_only_where_needed should be the method of choice for printing + // hollow objects + FOREACH_REGION(this->_print, region_it) { + size_t region_id = region_it - this->_print->regions.begin(); + const PrintRegion ®ion = **region_it; + + + if (!region.config.extra_perimeters + || region.config.perimeters == 0 + || region.config.fill_density == 0 + || this->layer_count() < 2) continue; + + for (size_t i = 0; i <= (this->layer_count()-2); ++i) { + LayerRegion &layerm = *this->get_layer(i)->get_region(region_id); + const LayerRegion &upper_layerm = *this->get_layer(i+1)->get_region(region_id); + const Polygons upper_layerm_polygons = upper_layerm.slices; + + // Filter upper layer polygons in intersection_ppl by their bounding boxes? + // my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ]; + double total_loop_length = 0; + for (Polygons::const_iterator it = upper_layerm_polygons.begin(); it != upper_layerm_polygons.end(); ++it) + total_loop_length += it->length(); + + const coord_t perimeter_spacing = layerm.flow(frPerimeter).scaled_spacing(); + const Flow ext_perimeter_flow = layerm.flow(frExternalPerimeter); + const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width(); + const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing(); + + for (Surfaces::iterator slice = layerm.slices.surfaces.begin(); + slice != layerm.slices.surfaces.end(); ++slice) { + while (true) { + // compute the total thickness of perimeters + const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2 + + (region.config.perimeters-1 + slice->extra_perimeters) * perimeter_spacing; + + // define a critical area where we don't want the upper slice to fall into + // (it should either lay over our perimeters or outside this area) + const coord_t critical_area_depth = perimeter_spacing * 1.5; + const Polygons critical_area = diff( + offset(slice->expolygon, -perimeters_thickness), + offset(slice->expolygon, -(perimeters_thickness + critical_area_depth)) + ); + + // check whether a portion of the upper slices falls inside the critical area + const Polylines intersection = intersection_pl( + upper_layerm_polygons, + critical_area + ); + + // only add an additional loop if at least 30% of the slice loop would benefit from it + { + double total_intersection_length = 0; + for (Polylines::const_iterator it = intersection.begin(); it != intersection.end(); ++it) + total_intersection_length += it->length(); + if (total_intersection_length <= total_loop_length*0.3) break; + } + + /* + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "extra.svg", + no_arrows => 1, + expolygons => union_ex($critical_area), + polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ], + ); + } + */ + + slice->extra_perimeters++; + } + + #ifdef DEBUG + if (slice->extra_perimeters > 0) + printf(" adding %d more perimeter(s) at layer %zu\n", slice->extra_perimeters, i); + #endif + } + } + } + + parallelize( + std::queue(std::deque(this->layers.begin(), this->layers.end())), // cast LayerPtrs to std::queue + boost::bind(&Slic3r::Layer::make_perimeters, _1), + this->_print->config.threads.value + ); + + /* + simplify slices (both layer and region slices), + we only need the max resolution for perimeters + ### This makes this method not-idempotent, so we keep it disabled for now. + ###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION); + */ + + this->state.set_done(posPerimeters); +} + +void +PrintObject::_infill() +{ + if (this->state.is_done(posInfill)) return; + this->state.set_started(posInfill); + + parallelize( + std::queue(std::deque(this->layers.begin(), this->layers.end())), // cast LayerPtrs to std::queue + boost::bind(&Slic3r::Layer::make_fills, _1), + this->_print->config.threads.value + ); + + /* we could free memory now, but this would make this step not idempotent + ### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers}; + */ + + this->state.set_done(posInfill); +} + } diff --git a/xs/src/libslic3r/SLAPrint.cpp b/xs/src/libslic3r/SLAPrint.cpp new file mode 100644 index 000000000..826e6184e --- /dev/null +++ b/xs/src/libslic3r/SLAPrint.cpp @@ -0,0 +1,333 @@ +#include "SLAPrint.hpp" +#include "ClipperUtils.hpp" +#include "ExtrusionEntity.hpp" +#include "Fill/Fill.hpp" +#include "Geometry.hpp" +#include "Surface.hpp" +#include +#include +#include + +namespace Slic3r { + +void +SLAPrint::slice() +{ + TriangleMesh mesh = this->model->mesh(); + mesh.repair(); + + // align to origin taking raft into account + this->bb = mesh.bounding_box(); + if (this->config.raft_layers > 0) { + this->bb.min.x -= this->config.raft_offset.value; + this->bb.min.y -= this->config.raft_offset.value; + this->bb.max.x += this->config.raft_offset.value; + this->bb.max.y += this->config.raft_offset.value; + } + mesh.translate(0, 0, -bb.min.z); + this->bb.translate(0, 0, -bb.min.z); + + // if we are generating a raft, first_layer_height will not affect mesh slicing + const float lh = this->config.layer_height.value; + const float first_lh = this->config.first_layer_height.value; + + // generate the list of Z coordinates for mesh slicing + // (we slice each layer at half of its thickness) + this->layers.clear(); + { + const float first_slice_lh = (this->config.raft_layers > 0) ? lh : first_lh; + this->layers.push_back(Layer(first_slice_lh/2, first_slice_lh)); + } + while (this->layers.back().print_z + lh/2 <= mesh.stl.stats.max.z) { + this->layers.push_back(Layer(this->layers.back().print_z + lh/2, this->layers.back().print_z + lh)); + } + + // perform slicing and generate layers + { + std::vector slice_z; + for (size_t i = 0; i < this->layers.size(); ++i) + slice_z.push_back(this->layers[i].slice_z); + + std::vector slices; + TriangleMeshSlicer(&mesh).slice(slice_z, &slices); + + for (size_t i = 0; i < slices.size(); ++i) + this->layers[i].slices.expolygons = slices[i]; + } + + // generate infill + if (this->config.fill_density < 100) { + std::auto_ptr fill(Fill::new_from_type(this->config.fill_pattern.value)); + fill->bounding_box.merge(Point::new_scale(bb.min.x, bb.min.y)); + fill->bounding_box.merge(Point::new_scale(bb.max.x, bb.max.y)); + fill->min_spacing = this->config.get_abs_value("infill_extrusion_width", this->config.layer_height.value); + fill->angle = Geometry::deg2rad(this->config.fill_angle.value); + fill->density = this->config.fill_density.value/100; + + parallelize( + 0, + this->layers.size()-1, + boost::bind(&SLAPrint::_infill_layer, this, _1, fill.get()), + this->config.threads.value + ); + } + + // generate support material + this->sm_pillars.clear(); + ExPolygons overhangs; + if (this->config.support_material) { + // flatten and merge all the overhangs + { + Polygons pp; + for (std::vector::const_iterator it = this->layers.begin()+1; it != this->layers.end(); ++it) + pp += diff(it->slices, (it - 1)->slices); + overhangs = union_ex(pp); + } + + // generate points following the shape of each island + Points pillars_pos; + const coordf_t spacing = scale_(this->config.support_material_spacing); + const coordf_t radius = scale_(this->sm_pillars_radius()); + for (ExPolygons::const_iterator it = overhangs.begin(); it != overhangs.end(); ++it) { + // leave a radius/2 gap between pillars and contour to prevent lateral adhesion + for (float inset = radius * 1.5;; inset += spacing) { + // inset according to the configured spacing + Polygons curr = offset(*it, -inset); + if (curr.empty()) break; + + // generate points along the contours + for (Polygons::const_iterator pg = curr.begin(); pg != curr.end(); ++pg) { + Points pp = pg->equally_spaced_points(spacing); + for (Points::const_iterator p = pp.begin(); p != pp.end(); ++p) + pillars_pos.push_back(*p); + } + } + } + + // for each pillar, check which layers it applies to + for (Points::const_iterator p = pillars_pos.begin(); p != pillars_pos.end(); ++p) { + SupportPillar pillar(*p); + bool object_hit = false; + + // check layers top-down + for (int i = this->layers.size()-1; i >= 0; --i) { + // check whether point is void in this layer + if (!this->layers[i].slices.contains(*p)) { + // no slice contains the point, so it's in the void + if (pillar.top_layer > 0) { + // we have a pillar, so extend it + pillar.bottom_layer = i + this->config.raft_layers; + } else if (object_hit) { + // we don't have a pillar and we're below the object, so create one + pillar.top_layer = i + this->config.raft_layers; + } + } else { + if (pillar.top_layer > 0) { + // we have a pillar which is not needed anymore, so store it and initialize a new potential pillar + this->sm_pillars.push_back(pillar); + pillar = SupportPillar(*p); + } + object_hit = true; + } + } + if (pillar.top_layer > 0) this->sm_pillars.push_back(pillar); + } + } + + // generate a solid raft if requested + // (do this after support material because we take support material shape into account) + if (this->config.raft_layers > 0) { + ExPolygons raft = this->layers.front().slices + overhangs; // take support material into account + raft = offset_ex(raft, scale_(this->config.raft_offset)); + for (int i = this->config.raft_layers; i >= 1; --i) { + this->layers.insert(this->layers.begin(), Layer(0, first_lh + lh * (i-1))); + this->layers.front().slices = raft; + } + + // prepend total raft height to all sliced layers + for (size_t i = this->config.raft_layers; i < this->layers.size(); ++i) + this->layers[i].print_z += first_lh + lh * (this->config.raft_layers-1); + } +} + +void +SLAPrint::_infill_layer(size_t i, const Fill* _fill) +{ + Layer &layer = this->layers[i]; + + const float shell_thickness = this->config.get_abs_value("perimeter_extrusion_width", this->config.layer_height.value); + + // In order to detect what regions of this layer need to be solid, + // perform an intersection with layers within the requested shell thickness. + Polygons internal = layer.slices; + for (size_t j = 0; j < this->layers.size(); ++j) { + const Layer &other = this->layers[j]; + if (std::abs(other.print_z - layer.print_z) > shell_thickness) continue; + + if (j == 0 || j == this->layers.size()-1) { + internal.clear(); + break; + } else if (i != j) { + internal = intersection(internal, other.slices); + if (internal.empty()) break; + } + } + + // If we have no internal infill, just print the whole layer as a solid slice. + if (internal.empty()) return; + layer.solid = false; + + const Polygons infill = offset(layer.slices, -scale_(shell_thickness)); + + // Generate solid infill + layer.solid_infill << diff_ex(infill, internal, true); + + // Generate internal infill + { + std::auto_ptr fill(_fill->clone()); + fill->layer_id = i; + fill->z = layer.print_z; + + ExtrusionPath templ(erInternalInfill); + templ.width = fill->spacing(); + const ExPolygons internal_ex = intersection_ex(infill, internal); + for (ExPolygons::const_iterator it = internal_ex.begin(); it != internal_ex.end(); ++it) { + Polylines polylines = fill->fill_surface(Surface(stInternal, *it)); + layer.infill.append(polylines, templ); + } + } + + // Generate perimeter(s). + layer.perimeters << diff_ex( + layer.slices, + offset(layer.slices, -scale_(shell_thickness)) + ); +} + +void +SLAPrint::write_svg(const std::string &outputfile) const +{ + const Sizef3 size = this->bb.size(); + const double support_material_radius = sm_pillars_radius(); + + FILE* f = fopen(outputfile.c_str(), "w"); + fprintf(f, + "\n" + "\n" + "\n" + "\n" + , size.x, size.y, SLIC3R_VERSION); + + for (size_t i = 0; i < this->layers.size(); ++i) { + const Layer &layer = this->layers[i]; + fprintf(f, + "\t\n", + i, + layer.print_z, + layer.slice_z, + layer.print_z - ((i == 0) ? 0. : this->layers[i-1].print_z) + ); + + if (layer.solid) { + const ExPolygons &slices = layer.slices.expolygons; + for (ExPolygons::const_iterator it = slices.begin(); it != slices.end(); ++it) { + std::string pd = this->_SVG_path_d(*it); + + fprintf(f,"\t\t\n", + pd.c_str(), "white", "black", "0", unscale(unscale(it->area())) + ); + } + } else { + // Perimeters. + for (ExPolygons::const_iterator it = layer.perimeters.expolygons.begin(); + it != layer.perimeters.expolygons.end(); ++it) { + std::string pd = this->_SVG_path_d(*it); + + fprintf(f,"\t\t\n", + pd.c_str(), "white", "black", "0" + ); + } + + // Solid infill. + for (ExPolygons::const_iterator it = layer.solid_infill.expolygons.begin(); + it != layer.solid_infill.expolygons.end(); ++it) { + std::string pd = this->_SVG_path_d(*it); + + fprintf(f,"\t\t\n", + pd.c_str(), "white", "black", "0" + ); + } + + // Internal infill. + for (ExtrusionEntitiesPtr::const_iterator it = layer.infill.entities.begin(); + it != layer.infill.entities.end(); ++it) { + const ExPolygons infill = union_ex((*it)->grow()); + + for (ExPolygons::const_iterator e = infill.begin(); e != infill.end(); ++e) { + std::string pd = this->_SVG_path_d(*e); + + fprintf(f,"\t\t\n", + pd.c_str(), "white", "black", "0" + ); + } + } + } + + // don't print support material in raft layers + if (i >= (size_t)this->config.raft_layers) { + // look for support material pillars belonging to this layer + for (std::vector::const_iterator it = this->sm_pillars.begin(); it != this->sm_pillars.end(); ++it) { + if (!(it->top_layer >= i && it->bottom_layer <= i)) continue; + + // generate a conic tip + float radius = fminf( + support_material_radius, + (it->top_layer - i + 1) * this->config.layer_height.value + ); + + fprintf(f,"\t\t\n", + unscale(it->x) - this->bb.min.x, + size.y - (unscale(it->y) - this->bb.min.y), + radius + ); + } + } + + fprintf(f,"\t\n"); + } + fprintf(f,"\n"); +} + +coordf_t +SLAPrint::sm_pillars_radius() const +{ + coordf_t radius = this->config.support_material_extrusion_width.get_abs_value(this->config.support_material_spacing)/2; + if (radius == 0) radius = this->config.support_material_spacing / 3; // auto + return radius; +} + +std::string +SLAPrint::_SVG_path_d(const Polygon &polygon) const +{ + const Sizef3 size = this->bb.size(); + std::ostringstream d; + d << "M "; + for (Points::const_iterator p = polygon.points.begin(); p != polygon.points.end(); ++p) { + d << unscale(p->x) - this->bb.min.x << " "; + d << size.y - (unscale(p->y) - this->bb.min.y) << " "; // mirror Y coordinates as SVG uses downwards Y + } + d << "z"; + return d.str(); +} + +std::string +SLAPrint::_SVG_path_d(const ExPolygon &expolygon) const +{ + std::string pd; + const Polygons pp = expolygon; + for (Polygons::const_iterator mp = pp.begin(); mp != pp.end(); ++mp) + pd += this->_SVG_path_d(*mp) + " "; + return pd; +} + +} diff --git a/xs/src/libslic3r/SLAPrint.hpp b/xs/src/libslic3r/SLAPrint.hpp new file mode 100644 index 000000000..6c6028abb --- /dev/null +++ b/xs/src/libslic3r/SLAPrint.hpp @@ -0,0 +1,57 @@ +#ifndef slic3r_SLAPrint_hpp_ +#define slic3r_SLAPrint_hpp_ + +#include "libslic3r.h" +#include "ExPolygon.hpp" +#include "ExPolygonCollection.hpp" +#include "Fill/Fill.hpp" +#include "Model.hpp" +#include "Point.hpp" +#include "PrintConfig.hpp" +#include "SVG.hpp" + +namespace Slic3r { + +class SLAPrint +{ + public: + SLAPrintConfig config; + + class Layer { + public: + ExPolygonCollection slices; + ExPolygonCollection perimeters; + ExtrusionEntityCollection infill; + ExPolygonCollection solid_infill; + float slice_z, print_z; + bool solid; + + Layer(float _slice_z, float _print_z) + : slice_z(_slice_z), print_z(_print_z), solid(true) {}; + }; + std::vector layers; + + class SupportPillar : public Point { + public: + size_t top_layer, bottom_layer; + SupportPillar(const Point &p) : Point(p), top_layer(0), bottom_layer(0) {}; + }; + std::vector sm_pillars; + + SLAPrint(Model* _model) : model(_model) {}; + void slice(); + void write_svg(const std::string &outputfile) const; + + private: + Model* model; + BoundingBoxf3 bb; + + void _infill_layer(size_t i, const Fill* fill); + coordf_t sm_pillars_radius() const; + std::string _SVG_path_d(const Polygon &polygon) const; + std::string _SVG_path_d(const ExPolygon &expolygon) const; +}; + +} + +#endif diff --git a/xs/src/libslic3r/SVG.cpp b/xs/src/libslic3r/SVG.cpp index af367ae76..5fb0e7d5f 100644 --- a/xs/src/libslic3r/SVG.cpp +++ b/xs/src/libslic3r/SVG.cpp @@ -19,18 +19,54 @@ SVG::SVG(const char* filename) ); } +SVG::SVG(const char* filename, const BoundingBox &bbox) + : arrows(false), fill("grey"), stroke("black"), origin(bbox.min), filename(filename) +{ + this->f = fopen(filename, "w"); + float w = COORD(bbox.max.x - bbox.min.x); + float h = COORD(bbox.max.y - bbox.min.y); + fprintf(this->f, + "\n" + "\n" + "\n" + " \n" + " \n" + " \n", + h, w); +} + void -SVG::draw(const Line &line, std::string stroke) +SVG::draw(const Line &line, std::string stroke, coord_t stroke_width) { fprintf(this->f, - " arrows) fprintf(this->f, " marker-end=\"url(#endArrow)\""); fprintf(this->f, "/>\n"); } +void SVG::draw(const ThickLine &line, const std::string &fill, const std::string &stroke, coord_t stroke_width) +{ + Pointf dir(line.b.x-line.a.x, line.b.y-line.a.y); + Pointf perp(-dir.y, dir.x); + coordf_t len = sqrt(perp.x*perp.x + perp.y*perp.y); + coordf_t da = coordf_t(0.5)*line.a_width/len; + coordf_t db = coordf_t(0.5)*line.b_width/len; + fprintf(this->f, + " \n", + COORD(line.a.x-da*perp.x-origin.x), + COORD(line.a.y-da*perp.y-origin.y), + COORD(line.b.x-db*perp.x-origin.x), + COORD(line.b.y-db*perp.y-origin.y), + COORD(line.b.x+db*perp.x-origin.x), + COORD(line.b.y+db*perp.y-origin.y), + COORD(line.a.x+da*perp.x-origin.x), + COORD(line.a.y+da*perp.y-origin.y), + fill.c_str(), stroke.c_str(), + (stroke_width == 0) ? 1.f : COORD(stroke_width)); +} + void SVG::draw(const Lines &lines, std::string stroke) { @@ -80,31 +116,45 @@ SVG::draw(const Polygons &polygons, std::string fill) } void -SVG::draw(const Polyline &polyline, std::string stroke) +SVG::draw(const Polyline &polyline, std::string stroke, coord_t stroke_width) { this->stroke = stroke; - this->path(this->get_path_d(polyline, false), false); + this->path(this->get_path_d(polyline, false), false, stroke_width); } void -SVG::draw(const Polylines &polylines, std::string stroke) +SVG::draw(const Polylines &polylines, std::string stroke, coord_t stroke_width) { for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) - this->draw(*it, stroke); + this->draw(*it, fill, stroke_width); +} + +void SVG::draw(const ThickLines &thicklines, const std::string &fill, const std::string &stroke, coord_t stroke_width) +{ + for (ThickLines::const_iterator it = thicklines.begin(); it != thicklines.end(); ++it) + this->draw(*it, fill, stroke, stroke_width); } void -SVG::draw(const ThickPolylines &polylines, std::string stroke) +SVG::draw(const ThickPolylines &polylines, const std::string &stroke, coord_t stroke_width) { for (ThickPolylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) - this->draw((Polyline)*it, stroke); + this->draw((Polyline)*it, stroke, stroke_width); +} + +void +SVG::draw(const ThickPolylines &thickpolylines, const std::string &fill, const std::string &stroke, coord_t stroke_width) +{ + for (ThickPolylines::const_iterator it = thickpolylines.begin(); it != thickpolylines.end(); ++ it) + draw(it->thicklines(), fill, stroke, stroke_width); } void -SVG::draw(const Point &point, std::string fill, unsigned int radius) +SVG::draw(const Point &point, std::string fill, coord_t iradius) { + float radius = (iradius == 0) ? 3.f : COORD(iradius); std::ostringstream svg; - svg << " "; @@ -112,22 +162,26 @@ SVG::draw(const Point &point, std::string fill, unsigned int radius) } void -SVG::draw(const Points &points, std::string fill, unsigned int radius) +SVG::draw(const Points &points, std::string fill, coord_t radius) { for (Points::const_iterator it = points.begin(); it != points.end(); ++it) this->draw(*it, fill, radius); } void -SVG::path(const std::string &d, bool fill) +SVG::path(const std::string &d, bool fill, coord_t stroke_width) { + float lineWidth = 0.f; + if (! fill) + lineWidth = (stroke_width == 0) ? 2.f : COORD(stroke_width); + fprintf( this->f, - " \n", + " \n", d.c_str(), fill ? this->fill.c_str() : "none", this->stroke.c_str(), - fill ? "0" : "2", + lineWidth, (this->arrows && !fill) ? " marker-end=\"url(#endArrow)\"" : "" ); } @@ -138,8 +192,8 @@ SVG::get_path_d(const MultiPoint &mp, bool closed) const std::ostringstream d; d << "M "; for (Points::const_iterator p = mp.points.begin(); p != mp.points.end(); ++p) { - d << COORD(p->x) << " "; - d << COORD(p->y) << " "; + d << COORD(p->x - origin.x) << " "; + d << COORD(p->y - origin.y) << " "; } if (closed) d << "z"; return d.str(); diff --git a/xs/src/libslic3r/SVG.hpp b/xs/src/libslic3r/SVG.hpp index f0ae06d04..94cfc27dd 100644 --- a/xs/src/libslic3r/SVG.hpp +++ b/xs/src/libslic3r/SVG.hpp @@ -13,27 +13,32 @@ class SVG public: bool arrows; std::string fill, stroke; - + Point origin; + SVG(const char* filename); - void draw(const Line &line, std::string stroke = "black"); + SVG(const char* filename, const BoundingBox &bbox); + void draw(const Line &line, std::string stroke = "black", coord_t stroke_width = 0); + void draw(const ThickLine &line, const std::string &fill, const std::string &stroke, coord_t stroke_width = 0); void draw(const Lines &lines, std::string stroke = "black"); void draw(const IntersectionLines &lines, std::string stroke = "black"); void draw(const ExPolygon &expolygon, std::string fill = "grey"); void draw(const ExPolygons &expolygons, std::string fill = "grey"); void draw(const Polygon &polygon, std::string fill = "grey"); void draw(const Polygons &polygons, std::string fill = "grey"); - void draw(const Polyline &polyline, std::string stroke = "black"); - void draw(const Polylines &polylines, std::string stroke = "black"); - void draw(const ThickPolylines &polylines, std::string stroke = "black"); - void draw(const Point &point, std::string fill = "black", unsigned int radius = 3); - void draw(const Points &points, std::string fill = "black", unsigned int radius = 3); + void draw(const Polyline &polyline, std::string stroke = "black", coord_t stroke_width = 0); + void draw(const Polylines &polylines, std::string stroke = "black", coord_t stroke_width = 0); + void draw(const ThickLines &thicklines, const std::string &fill = "lime", const std::string &stroke = "black", coord_t stroke_width = 0); + void draw(const ThickPolylines &polylines, const std::string &stroke = "black", coord_t stroke_width = 0); + void draw(const ThickPolylines &thickpolylines, const std::string &fill, const std::string &stroke, coord_t stroke_width); + void draw(const Point &point, std::string fill = "black", coord_t radius = 0); + void draw(const Points &points, std::string fill = "black", coord_t radius = 0); void Close(); private: std::string filename; FILE* f; - void path(const std::string &d, bool fill); + void path(const std::string &d, bool fill, coord_t stroke_width = 0); std::string get_path_d(const MultiPoint &mp, bool closed = false) const; }; diff --git a/xs/src/libslic3r/SVGExport.cpp b/xs/src/libslic3r/SVGExport.cpp deleted file mode 100644 index a209977cf..000000000 --- a/xs/src/libslic3r/SVGExport.cpp +++ /dev/null @@ -1,124 +0,0 @@ -#include "SVGExport.hpp" -#include "ClipperUtils.hpp" -#include -#include - -namespace Slic3r { - -void -SVGExport::writeSVG(const std::string &outputfile) -{ - // align to origin taking raft into account - BoundingBoxf3 bb = this->mesh.bounding_box(); - if (this->config.raft_layers > 0) { - bb.min.x -= this->config.raft_offset.value; - bb.min.y -= this->config.raft_offset.value; - bb.max.x += this->config.raft_offset.value; - bb.max.y += this->config.raft_offset.value; - } - this->mesh.translate(-bb.min.x, -bb.min.y, -bb.min.z); // align to origin - bb.translate(-bb.min.x, -bb.min.y, -bb.min.z); // align to origin - const Sizef3 size = bb.size(); - - // if we are generating a raft, first_layer_height will not affect mesh slicing - const float lh = this->config.layer_height.value; - const float first_lh = this->config.first_layer_height.value; - - // generate the list of Z coordinates for mesh slicing - // (we slice each layer at half of its thickness) - std::vector slice_z, layer_z; - { - const float first_slice_lh = (this->config.raft_layers > 0) ? lh : first_lh; - slice_z.push_back(first_slice_lh/2); - layer_z.push_back(first_slice_lh); - } - while (layer_z.back() + lh/2 <= this->mesh.stl.stats.max.z) { - slice_z.push_back(layer_z.back() + lh/2); - layer_z.push_back(layer_z.back() + lh); - } - - // perform the slicing - std::vector layers; - TriangleMeshSlicer(&this->mesh).slice(slice_z, &layers); - - // generate a solid raft if requested - if (this->config.raft_layers > 0) { - ExPolygons raft = offset_ex(layers.front(), scale_(this->config.raft_offset)); - for (int i = this->config.raft_layers; i >= 1; --i) { - layer_z.insert(layer_z.begin(), first_lh + lh * (i-1)); - layers.insert(layers.begin(), raft); - } - - // prepend total raft height to all sliced layers - for (int i = this->config.raft_layers; i < layer_z.size(); ++i) - layer_z[i] += first_lh + lh * (this->config.raft_layers-1); - } - - // generate support material - std::vector support_material(layers.size()); - if (this->config.support_material) { - // generate a grid of points according to the configured spacing, - // covering the entire object bounding box - Points support_material_points; - for (coordf_t x = bb.min.x; x <= bb.max.x; x += this->config.support_material_spacing) { - for (coordf_t y = bb.min.y; y <= bb.max.y; y += this->config.support_material_spacing) { - support_material_points.push_back(Point(scale_(x), scale_(y))); - } - } - - // check overhangs, starting from the upper layer, and detect which points apply - // to each layer - ExPolygons overhangs; - for (int i = layer_z.size()-1; i >= 0; --i) { - overhangs = diff_ex(union_(overhangs, layers[i+1]), layers[i]); - for (Points::const_iterator it = support_material_points.begin(); it != support_material_points.end(); ++it) { - for (ExPolygons::const_iterator e = overhangs.begin(); e != overhangs.end(); ++e) { - if (e->contains(*it)) { - support_material[i].push_back(*it); - break; - } - } - } - } - } - - double support_material_radius = this->config.support_material_extrusion_width.get_abs_value(this->config.layer_height)/2; - - FILE* f = fopen(outputfile.c_str(), "w"); - fprintf(f, - "\n" - "\n" - "\n" - "\n" - , size.x, size.y, SLIC3R_VERSION); - - for (size_t i = 0; i < layer_z.size(); ++i) { - fprintf(f, "\t\n", i, layer_z[i]); - for (ExPolygons::const_iterator it = layers[i].begin(); it != layers[i].end(); ++it) { - std::string pd; - Polygons pp = *it; - for (Polygons::const_iterator mp = pp.begin(); mp != pp.end(); ++mp) { - std::ostringstream d; - d << "M "; - for (Points::const_iterator p = mp->points.begin(); p != mp->points.end(); ++p) { - d << unscale(p->x) << " "; - d << unscale(p->y) << " "; - } - d << "z"; - pd += d.str() + " "; - } - fprintf(f,"\t\t\n", - pd.c_str(), "white", "black", "0", unscale(unscale(it->area())) - ); - } - for (Points::const_iterator it = support_material[i].begin(); it != support_material[i].end(); ++it) { - fprintf(f,"\t\t\n", - unscale(it->x), unscale(it->y), support_material_radius - ); - } - fprintf(f,"\t\n"); - } - fprintf(f,"\n"); -} - -} diff --git a/xs/src/libslic3r/SVGExport.hpp b/xs/src/libslic3r/SVGExport.hpp deleted file mode 100644 index 2075f1c6d..000000000 --- a/xs/src/libslic3r/SVGExport.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef slic3r_SVGExport_hpp_ -#define slic3r_SVGExport_hpp_ - -#include "libslic3r.h" -#include "ExPolygon.hpp" -#include "PrintConfig.hpp" -#include "SVG.hpp" -#include "TriangleMesh.hpp" - -namespace Slic3r { - -class SVGExport -{ - public: - SVGExportConfig config; - TriangleMesh mesh; - - SVGExport(const TriangleMesh &mesh) : mesh(mesh) { - this->mesh.mirror_x(); - }; - void writeSVG(const std::string &outputfile); -}; - -} - -#endif diff --git a/xs/src/libslic3r/Surface.hpp b/xs/src/libslic3r/Surface.hpp index 21395bdc6..895d7e904 100644 --- a/xs/src/libslic3r/Surface.hpp +++ b/xs/src/libslic3r/Surface.hpp @@ -33,6 +33,34 @@ class Surface typedef std::vector Surfaces; typedef std::vector SurfacesPtr; +typedef std::vector SurfacesConstPtr; + +inline Polygons +to_polygons(const Surfaces &surfaces) +{ + Slic3r::Polygons pp; + for (Surfaces::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) + append_to(pp, (Polygons)*s); + return pp; +} + +inline Polygons +to_polygons(const SurfacesPtr &surfaces) +{ + Slic3r::Polygons pp; + for (SurfacesPtr::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) + append_to(pp, (Polygons)**s); + return pp; +} + +inline Polygons +to_polygons(const SurfacesConstPtr &surfaces) +{ + Slic3r::Polygons pp; + for (SurfacesConstPtr::const_iterator s = surfaces.begin(); s != surfaces.end(); ++s) + append_to(pp, (Polygons)**s); + return pp; +} } diff --git a/xs/src/libslic3r/SurfaceCollection.cpp b/xs/src/libslic3r/SurfaceCollection.cpp index 21e6eb0cc..fb92796ca 100644 --- a/xs/src/libslic3r/SurfaceCollection.cpp +++ b/xs/src/libslic3r/SurfaceCollection.cpp @@ -41,13 +41,13 @@ SurfaceCollection::simplify(double tolerance) /* group surfaces by common properties */ void -SurfaceCollection::group(std::vector *retval) +SurfaceCollection::group(std::vector *retval) const { - for (Surfaces::iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++it) { + for (Surfaces::const_iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++it) { // find a group with the same properties - SurfacesPtr* group = NULL; - for (std::vector::iterator git = retval->begin(); git != retval->end(); ++git) { - Surface* gkey = git->front(); + SurfacesConstPtr* group = NULL; + for (std::vector::iterator git = retval->begin(); git != retval->end(); ++git) { + const Surface* gkey = git->front(); if ( gkey->surface_type == it->surface_type && gkey->thickness == it->thickness && gkey->thickness_layers == it->thickness_layers @@ -104,17 +104,48 @@ void SurfaceCollection::filter_by_type(SurfaceType type, Polygons* polygons) { for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { - if (surface->surface_type == type) { - Polygons pp = surface->expolygon; - polygons->insert(polygons->end(), pp.begin(), pp.end()); - } + if (surface->surface_type == type) + append_to(*polygons, (Polygons)surface->expolygon); } } void SurfaceCollection::append(const SurfaceCollection &coll) { - this->surfaces.insert(this->surfaces.end(), coll.surfaces.begin(), coll.surfaces.end()); + this->append(coll.surfaces); +} + +void +SurfaceCollection::append(const Surfaces &surfaces) +{ + append_to(this->surfaces, surfaces); +} + +void +SurfaceCollection::append(const ExPolygons &src, const Surface &templ) +{ + this->surfaces.reserve(this->surfaces.size() + src.size()); + for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++ it) { + this->surfaces.push_back(templ); + this->surfaces.back().expolygon = *it; + } +} + +void +SurfaceCollection::append(const ExPolygons &src, SurfaceType surfaceType) +{ + this->surfaces.reserve(this->surfaces.size() + src.size()); + for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++ it) + this->surfaces.push_back(Surface(surfaceType, *it)); +} + +size_t +SurfaceCollection::polygons_count() const +{ + size_t count = 0; + for (Surfaces::const_iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++ it) + count += 1 + it->expolygon.holes.size(); + return count; } } diff --git a/xs/src/libslic3r/SurfaceCollection.hpp b/xs/src/libslic3r/SurfaceCollection.hpp index a4a3a7e5d..e9d42fc49 100644 --- a/xs/src/libslic3r/SurfaceCollection.hpp +++ b/xs/src/libslic3r/SurfaceCollection.hpp @@ -18,12 +18,18 @@ class SurfaceCollection operator Polygons() const; operator ExPolygons() const; void simplify(double tolerance); - void group(std::vector *retval); + void group(std::vector *retval) const; template bool any_internal_contains(const T &item) const; template bool any_bottom_contains(const T &item) const; SurfacesPtr filter_by_type(SurfaceType type); void filter_by_type(SurfaceType type, Polygons* polygons); void append(const SurfaceCollection &coll); + void append(const Surfaces &surfaces); + void append(const ExPolygons &src, const Surface &templ); + void append(const ExPolygons &src, SurfaceType surfaceType); + size_t polygons_count() const; + bool empty() const { return this->surfaces.empty(); }; + void clear() { this->surfaces.clear(); }; }; } diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 4286d5aa3..2a52c865f 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -2,8 +2,8 @@ #include "ClipperUtils.hpp" #include "Geometry.hpp" #include -#include #include +#include #include #include #include @@ -11,6 +11,7 @@ #include #include #include +#include #ifdef SLIC3R_DEBUG #include "SVG.hpp" @@ -24,6 +25,48 @@ TriangleMesh::TriangleMesh() stl_initialize(&this->stl); } +TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& facets ) + : repaired(false) +{ + stl_initialize(&this->stl); + stl_file &stl = this->stl; + stl.error = 0; + stl.stats.type = inmemory; + + // count facets and allocate memory + stl.stats.number_of_facets = facets.size(); + stl.stats.original_num_facets = stl.stats.number_of_facets; + stl_allocate(&stl); + + for (int i = 0; i < stl.stats.number_of_facets; i++) { + stl_facet facet; + facet.normal.x = 0; + facet.normal.y = 0; + facet.normal.z = 0; + + const Pointf3& ref_f1 = points[facets[i].x]; + facet.vertex[0].x = ref_f1.x; + facet.vertex[0].y = ref_f1.y; + facet.vertex[0].z = ref_f1.z; + + const Pointf3& ref_f2 = points[facets[i].y]; + facet.vertex[1].x = ref_f2.x; + facet.vertex[1].y = ref_f2.y; + facet.vertex[1].z = ref_f2.z; + + const Pointf3& ref_f3 = points[facets[i].z]; + facet.vertex[2].x = ref_f3.x; + facet.vertex[2].y = ref_f3.y; + facet.vertex[2].z = ref_f3.z; + + facet.extra[0] = 0; + facet.extra[1] = 0; + + stl.facet_start[i] = facet; + } + stl_get_size(&stl); +} + TriangleMesh::TriangleMesh(const TriangleMesh &other) : stl(other.stl), repaired(other.repaired) { @@ -50,15 +93,15 @@ TriangleMesh::TriangleMesh(const TriangleMesh &other) TriangleMesh& TriangleMesh::operator= (TriangleMesh other) { - swap(*this, other); + this->swap(other); return *this; } void -TriangleMesh::swap(TriangleMesh &first, TriangleMesh &second) +TriangleMesh::swap(TriangleMesh &other) { - std::swap(first.repaired, second.repaired); - std::swap(first.stl, second.stl); + std::swap(this->stl, other.stl); + std::swap(this->repaired, other.repaired); } TriangleMesh::~TriangleMesh() { @@ -68,6 +111,7 @@ TriangleMesh::~TriangleMesh() { void TriangleMesh::ReadSTLFile(const std::string &input_file) { stl_open(&stl, input_file.c_str()); + if (this->stl.error != 0) throw std::runtime_error("Failed to read STL file"); } void @@ -351,6 +395,47 @@ TriangleMesh::split() const return meshes; } +TriangleMeshPtrs +TriangleMesh::cut_by_grid(const Pointf &grid) const +{ + TriangleMesh mesh = *this; + const BoundingBoxf3 bb = mesh.bounding_box(); + const Sizef3 size = bb.size(); + const size_t x_parts = ceil((size.x - EPSILON)/grid.x); + const size_t y_parts = ceil((size.y - EPSILON)/grid.y); + + TriangleMeshPtrs meshes; + for (size_t i = 1; i <= x_parts; ++i) { + TriangleMesh curr; + if (i == x_parts) { + curr = mesh; + } else { + TriangleMesh next; + TriangleMeshSlicer(&mesh).cut(bb.min.x + (grid.x * i), &next, &curr); + curr.repair(); + next.repair(); + mesh = next; + } + + for (size_t j = 1; j <= y_parts; ++j) { + TriangleMesh* tile; + if (j == y_parts) { + tile = new TriangleMesh(curr); + } else { + TriangleMesh next; + tile = new TriangleMesh; + TriangleMeshSlicer(&curr).cut(bb.min.y + (grid.y * j), &next, tile); + tile->repair(); + next.repair(); + curr = next; + } + + meshes.push_back(tile); + } + } + return meshes; +} + void TriangleMesh::merge(const TriangleMesh &mesh) { @@ -390,10 +475,7 @@ TriangleMesh::horizontal_projection() const } // the offset factor was tuned using groovemount.stl - offset(pp, &pp, 0.01 / SCALING_FACTOR); - ExPolygons retval; - union_(pp, &retval, true); - return retval; + return union_ex(offset(pp, 0.01 / SCALING_FACTOR), true); } Polygon @@ -430,7 +512,205 @@ TriangleMesh::require_shared_vertices() } void -TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) +TriangleMesh::extrude_tin(float offset) +{ + calculate_normals(&this->stl); + + const int number_of_facets = this->stl.stats.number_of_facets; + if (number_of_facets == 0) + throw std::runtime_error("Error: file is empty"); + + const float z = this->stl.stats.min.z - offset; + + for (int i = 0; i < number_of_facets; ++i) { + const stl_facet &facet = this->stl.facet_start[i]; + + if (facet.normal.z < 0) + throw std::runtime_error("Invalid 2.5D mesh: at least one facet points downwards."); + + for (int j = 0; j < 3; ++j) { + if (this->stl.neighbors_start[i].neighbor[j] == -1) { + stl_facet new_facet; + float normal[3]; + + // first triangle + new_facet.vertex[0] = new_facet.vertex[2] = facet.vertex[(j+1)%3]; + new_facet.vertex[1] = facet.vertex[j]; + new_facet.vertex[2].z = z; + stl_calculate_normal(normal, &new_facet); + stl_normalize_vector(normal); + new_facet.normal.x = normal[0]; + new_facet.normal.y = normal[1]; + new_facet.normal.z = normal[2]; + stl_add_facet(&this->stl, &new_facet); + + // second triangle + new_facet.vertex[0] = new_facet.vertex[1] = facet.vertex[j]; + new_facet.vertex[2] = facet.vertex[(j+1)%3]; + new_facet.vertex[1].z = new_facet.vertex[2].z = z; + new_facet.normal.x = normal[0]; + new_facet.normal.y = normal[1]; + new_facet.normal.z = normal[2]; + stl_add_facet(&this->stl, &new_facet); + } + } + } + stl_get_size(&this->stl); + + this->repair(); +} + +// Generate the vertex list for a cube solid of arbitrary size in X/Y/Z. +TriangleMesh +TriangleMesh::make_cube(double x, double y, double z) { + Pointf3 pv[8] = { + Pointf3(x, y, 0), Pointf3(x, 0, 0), Pointf3(0, 0, 0), + Pointf3(0, y, 0), Pointf3(x, y, z), Pointf3(0, y, z), + Pointf3(0, 0, z), Pointf3(x, 0, z) + }; + Point3 fv[12] = { + Point3(0, 1, 2), Point3(0, 2, 3), Point3(4, 5, 6), + Point3(4, 6, 7), Point3(0, 4, 7), Point3(0, 7, 1), + Point3(1, 7, 6), Point3(1, 6, 2), Point3(2, 6, 5), + Point3(2, 5, 3), Point3(4, 0, 3), Point3(4, 3, 5) + }; + + std::vector facets(&fv[0], &fv[0]+12); + Pointf3s vertices(&pv[0], &pv[0]+8); + + TriangleMesh mesh(vertices ,facets); + return mesh; +} + +// Generate the mesh for a cylinder and return it, using +// the generated angle to calculate the top mesh triangles. +// Default is 360 sides, angle fa is in radians. +TriangleMesh +TriangleMesh::make_cylinder(double r, double h, double fa) { + Pointf3s vertices; + std::vector facets; + + // 2 special vertices, top and bottom center, rest are relative to this + vertices.push_back(Pointf3(0.0, 0.0, 0.0)); + vertices.push_back(Pointf3(0.0, 0.0, h)); + + // adjust via rounding to get an even multiple for any provided angle. + double angle = (2*PI / floor(2*PI / fa)); + + // for each line along the polygon approximating the top/bottom of the + // circle, generate four points and four facets (2 for the wall, 2 for the + // top and bottom. + // Special case: Last line shares 2 vertices with the first line. + unsigned id = vertices.size() - 1; + vertices.push_back(Pointf3(sin(0) * r , cos(0) * r, 0)); + vertices.push_back(Pointf3(sin(0) * r , cos(0) * r, h)); + for (double i = 0; i < 2*PI; i+=angle) { + Pointf3 b(0, r, 0); + Pointf3 t(0, r, h); + b.rotate(i, Pointf3(0,0,0)); + t.rotate(i, Pointf3(0,0,h)); + vertices.push_back(b); + vertices.push_back(t); + id = vertices.size() - 1; + facets.push_back(Point3( 0, id - 1, id - 3)); // top + facets.push_back(Point3(id, 1, id - 2)); // bottom + facets.push_back(Point3(id, id - 2, id - 3)); // upper-right of side + facets.push_back(Point3(id, id - 3, id - 1)); // bottom-left of side + } + // Connect the last set of vertices with the first. + facets.push_back(Point3( 2, 0, id - 1)); + facets.push_back(Point3( 1, 3, id)); + facets.push_back(Point3(id, 3, 2)); + facets.push_back(Point3(id, 2, id - 1)); + + TriangleMesh mesh(vertices, facets); + return mesh; +} + +// Generates mesh for a sphere centered about the origin, using the generated angle +// to determine the granularity. +// Default angle is 1 degree. +TriangleMesh +TriangleMesh::make_sphere(double rho, double fa) { + Pointf3s vertices; + std::vector facets; + + // Algorithm: + // Add points one-by-one to the sphere grid and form facets using relative coordinates. + // Sphere is composed effectively of a mesh of stacked circles. + + // adjust via rounding to get an even multiple for any provided angle. + double angle = (2*PI / floor(2*PI / fa)); + + // Ring to be scaled to generate the steps of the sphere + std::vector ring; + for (double i = 0; i < 2*PI; i+=angle) { + ring.push_back(i); + } + const size_t steps = ring.size(); + const double increment = (double)(1.0 / (double)steps); + + // special case: first ring connects to 0,0,0 + // insert and form facets. + vertices.push_back(Pointf3(0.0, 0.0, -rho)); + size_t id = vertices.size(); + for (size_t i = 0; i < ring.size(); i++) { + // Fixed scaling + const double z = -rho + increment*rho*2.0; + // radius of the circle for this step. + const double r = sqrt(abs(rho*rho - z*z)); + Pointf3 b(0, r, z); + b.rotate(ring[i], Pointf3(0,0,z)); + vertices.push_back(b); + if (i == 0) { + facets.push_back(Point3(1, 0, ring.size())); + } else { + facets.push_back(Point3(id, 0, id - 1)); + } + id++; + } + + // General case: insert and form facets for each step, joining it to the ring below it. + for (size_t s = 2; s < steps - 1; s++) { + const double z = -rho + increment*(double)s*2.0*rho; + const double r = sqrt(abs(rho*rho - z*z)); + + for (size_t i = 0; i < ring.size(); i++) { + Pointf3 b(0, r, z); + b.rotate(ring[i], Pointf3(0,0,z)); + vertices.push_back(b); + if (i == 0) { + // wrap around + facets.push_back(Point3(id + ring.size() - 1 , id, id - 1)); + facets.push_back(Point3(id, id - ring.size(), id - 1)); + } else { + facets.push_back(Point3(id , id - ring.size(), (id - 1) - ring.size())); + facets.push_back(Point3(id, id - 1 - ring.size() , id - 1)); + } + id++; + } + } + + + // special case: last ring connects to 0,0,rho*2.0 + // only form facets. + vertices.push_back(Pointf3(0.0, 0.0, rho)); + for (size_t i = 0; i < ring.size(); i++) { + if (i == 0) { + // third vertex is on the other side of the ring. + facets.push_back(Point3(id, id - ring.size(), id - 1)); + } else { + facets.push_back(Point3(id, id - ring.size() + i, id - ring.size() + (i - 1))); + } + } + id++; + TriangleMesh mesh(vertices, facets); + return mesh; +} + +template +void +TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const { /* This method gets called with a list of unscaled Z coordinates and outputs @@ -454,58 +734,68 @@ TriangleMeshSlicer::slice(const std::vector &z, std::vector* la At the end, we free the tables generated by analyze() as we don't need them anymore. - FUTURE: parallelize slice_facet() and make_loops() NOTE: this method accepts a vector of floats because the mesh coordinate type is float. */ std::vector lines(z.size()); - - for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; facet_idx++) { - stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; - - // find facet extents - float min_z = fminf(facet->vertex[0].z, fminf(facet->vertex[1].z, facet->vertex[2].z)); - float max_z = fmaxf(facet->vertex[0].z, fmaxf(facet->vertex[1].z, facet->vertex[2].z)); - - #ifdef SLIC3R_DEBUG - printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, - facet->vertex[0].x, facet->vertex[0].y, facet->vertex[0].z, - facet->vertex[1].x, facet->vertex[1].y, facet->vertex[1].z, - facet->vertex[2].x, facet->vertex[2].y, facet->vertex[2].z); - printf("z: min = %.2f, max = %.2f\n", min_z, max_z); - #endif - - // find layer extents - std::vector::const_iterator min_layer, max_layer; - min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z - max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z - #ifdef SLIC3R_DEBUG - printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); - #endif - - for (std::vector::const_iterator it = min_layer; it != max_layer + 1; ++it) { - std::vector::size_type layer_idx = it - z.begin(); - this->slice_facet(*it / SCALING_FACTOR, *facet, facet_idx, min_z, max_z, &lines[layer_idx]); - } + { + boost::mutex lines_mutex; + parallelize( + 0, + this->mesh->stl.stats.number_of_facets-1, + boost::bind(&TriangleMeshSlicer::_slice_do, this, _1, &lines, &lines_mutex, z) + ); } // v_scaled_shared could be freed here // build loops layers->resize(z.size()); - for (std::vector::iterator it = lines.begin(); it != lines.end(); ++it) { - size_t layer_idx = it - lines.begin(); - #ifdef SLIC3R_DEBUG - printf("Layer %zu:\n", layer_idx); - #endif - this->make_loops(*it, &(*layers)[layer_idx]); + parallelize( + 0, + lines.size()-1, + boost::bind(&TriangleMeshSlicer::_make_loops_do, this, _1, &lines, layers) + ); +} + +template +void +TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector* lines, boost::mutex* lines_mutex, + const std::vector &z) const +{ + const stl_facet &facet = this->mesh->stl.facet_start[facet_idx]; + + // find facet extents + const float min_z = fminf(_z(facet.vertex[0]), fminf(_z(facet.vertex[1]), _z(facet.vertex[2]))); + const float max_z = fmaxf(_z(facet.vertex[0]), fmaxf(_z(facet.vertex[1]), _z(facet.vertex[2]))); + + #ifdef SLIC3R_DEBUG + printf("\n==> FACET %zu (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, + _x(facet.vertex[0]), _y(facet.vertex[0]), _z(facet.vertex[0]), + _x(facet.vertex[1]), _y(facet.vertex[1]), _z(facet.vertex[1]), + _x(facet.vertex[2]), _y(facet.vertex[2]), _z(facet.vertex[2])); + printf("z: min = %.2f, max = %.2f\n", min_z, max_z); + #endif + + // find layer extents + std::vector::const_iterator min_layer, max_layer; + min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z + max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z + #ifdef SLIC3R_DEBUG + printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); + #endif + + for (std::vector::const_iterator it = min_layer; it != max_layer + 1; ++it) { + std::vector::size_type layer_idx = it - z.begin(); + this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &(*lines)[layer_idx], lines_mutex); } } +template void -TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) +TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const { std::vector layers_p; this->slice(z, &layers_p); @@ -521,8 +811,22 @@ TriangleMeshSlicer::slice(const std::vector &z, std::vector* } } +template void -TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int &facet_idx, const float &min_z, const float &max_z, std::vector* lines) const +TriangleMeshSlicer::slice(float z, ExPolygons* slices) const +{ + std::vector zz; + zz.push_back(z); + std::vector layers; + this->slice(zz, &layers); + append_to(*slices, layers.front()); +} + +template +void +TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int &facet_idx, + const float &min_z, const float &max_z, std::vector* lines, + boost::mutex* lines_mutex) const { std::vector points; std::vector< std::vector::size_type > points_on_layer; @@ -532,10 +836,10 @@ TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int this is needed to get all intersection lines in a consistent order (external on the right of the line) */ int i = 0; - if (facet.vertex[1].z == min_z) { + if (_z(facet.vertex[1]) == min_z) { // vertex 1 has lowest Z i = 1; - } else if (facet.vertex[2].z == min_z) { + } else if (_z(facet.vertex[2]) == min_z) { // vertex 2 has lowest Z i = 2; } @@ -546,7 +850,7 @@ TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int stl_vertex* a = &this->v_scaled_shared[a_id]; stl_vertex* b = &this->v_scaled_shared[b_id]; - if (a->z == b->z && a->z == slice_z) { + if (_z(*a) == _z(*b) && _z(*a) == slice_z) { // edge is horizontal and belongs to the current layer stl_vertex &v0 = this->v_scaled_shared[ this->mesh->stl.v_indices[facet_idx].vertex[0] ]; @@ -555,26 +859,31 @@ TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int IntersectionLine line; if (min_z == max_z) { line.edge_type = feHorizontal; - if (this->mesh->stl.facet_start[facet_idx].normal.z < 0) { + if (_z(this->mesh->stl.facet_start[facet_idx].normal) < 0) { /* if normal points downwards this is a bottom horizontal facet so we reverse its point order */ std::swap(a, b); std::swap(a_id, b_id); } - } else if (v0.z < slice_z || v1.z < slice_z || v2.z < slice_z) { + } else if (_z(v0) < slice_z || _z(v1) < slice_z || _z(v2) < slice_z) { line.edge_type = feTop; std::swap(a, b); std::swap(a_id, b_id); } else { line.edge_type = feBottom; } - line.a.x = a->x; - line.a.y = a->y; - line.b.x = b->x; - line.b.y = b->y; + line.a.x = _x(*a); + line.a.y = _y(*a); + line.b.x = _x(*b); + line.b.y = _y(*b); line.a_id = a_id; line.b_id = b_id; - lines->push_back(line); + if (lines_mutex != NULL) { + boost::lock_guard l(*lines_mutex); + lines->push_back(line); + } else { + lines->push_back(line); + } found_horizontal_edge = true; @@ -582,26 +891,26 @@ TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int // because we won't find anything interesting if (line.edge_type != feHorizontal) return; - } else if (a->z == slice_z) { + } else if (_z(*a) == slice_z) { IntersectionPoint point; - point.x = a->x; - point.y = a->y; + point.x = _x(*a); + point.y = _y(*a); point.point_id = a_id; points.push_back(point); points_on_layer.push_back(points.size()-1); - } else if (b->z == slice_z) { + } else if (_z(*b) == slice_z) { IntersectionPoint point; - point.x = b->x; - point.y = b->y; + point.x = _x(*b); + point.y = _y(*b); point.point_id = b_id; points.push_back(point); points_on_layer.push_back(points.size()-1); - } else if ((a->z < slice_z && b->z > slice_z) || (b->z < slice_z && a->z > slice_z)) { + } else if ((_z(*a) < slice_z && _z(*b) > slice_z) || (_z(*b) < slice_z && _z(*a) > slice_z)) { // edge intersects the current layer; calculate intersection IntersectionPoint point; - point.x = b->x + (a->x - b->x) * (slice_z - b->z) / (a->z - b->z); - point.y = b->y + (a->y - b->y) * (slice_z - b->z) / (a->z - b->z); + point.x = _x(*b) + (_x(*a) - _x(*b)) * (slice_z - _z(*b)) / (_z(*a) - _z(*b)); + point.y = _y(*b) + (_y(*a) - _y(*b)) * (slice_z - _z(*b)) / (_z(*a) - _z(*b)); point.edge_id = edge_id; points.push_back(point); } @@ -627,13 +936,26 @@ TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int line.b_id = points[0].point_id; line.edge_a_id = points[1].edge_id; line.edge_b_id = points[0].edge_id; - lines->push_back(line); + if (lines_mutex != NULL) { + boost::lock_guard l(*lines_mutex); + lines->push_back(line); + } else { + lines->push_back(line); + } return; } } +template void -TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) +TriangleMeshSlicer::_make_loops_do(size_t i, std::vector* lines, std::vector* layers) const +{ + this->make_loops((*lines)[i], &(*layers)[i]); +} + +template +void +TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const { /* SVG svg("lines.svg"); @@ -734,6 +1056,7 @@ TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* l for (IntersectionLinePtrs::const_iterator lineptr = loop.begin(); lineptr != loop.end(); ++lineptr) { p.points.push_back((*lineptr)->a); } + loops->push_back(p); #ifdef SLIC3R_DEBUG @@ -772,8 +1095,9 @@ class _area_comp { std::vector* abs_area; }; +template void -TriangleMeshSlicer::make_expolygons_simple(std::vector &lines, ExPolygons* slices) +TriangleMeshSlicer::make_expolygons_simple(std::vector &lines, ExPolygons* slices) const { Polygons loops; this->make_loops(lines, &loops); @@ -806,8 +1130,9 @@ TriangleMeshSlicer::make_expolygons_simple(std::vector &lines, } } +template void -TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slices) +TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slices) const { /* Input loops are not suitable for evenodd nor nonzero fill types, as we might get @@ -848,14 +1173,13 @@ TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slices) if (area[*loop_idx] > +EPSILON) { p_slices.push_back(*loop); } else if (area[*loop_idx] < -EPSILON) { - diff(p_slices, *loop, &p_slices); + p_slices = diff(p_slices, *loop); } } // perform a safety offset to merge very close facets (TODO: find test case for this) double safety_offset = scale_(0.0499); - ExPolygons ex_slices; - offset2(p_slices, &ex_slices, +safety_offset, -safety_offset); + ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset); #ifdef SLIC3R_DEBUG size_t holes_count = 0; @@ -870,26 +1194,28 @@ TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slices) slices->insert(slices->end(), ex_slices.begin(), ex_slices.end()); } +template void -TriangleMeshSlicer::make_expolygons(std::vector &lines, ExPolygons* slices) +TriangleMeshSlicer::make_expolygons(std::vector &lines, ExPolygons* slices) const { Polygons pp; this->make_loops(lines, &pp); this->make_expolygons(pp, slices); } +template void -TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) +TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) const { IntersectionLines upper_lines, lower_lines; - float scaled_z = scale_(z); + const float scaled_z = scale_(z); for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; facet_idx++) { stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; // find facet extents - float min_z = fminf(facet->vertex[0].z, fminf(facet->vertex[1].z, facet->vertex[2].z)); - float max_z = fmaxf(facet->vertex[0].z, fmaxf(facet->vertex[1].z, facet->vertex[2].z)); + float min_z = fminf(_z(facet->vertex[0]), fminf(_z(facet->vertex[1]), _z(facet->vertex[2]))); + float max_z = fmaxf(_z(facet->vertex[0]), fmaxf(_z(facet->vertex[1]), _z(facet->vertex[2]))); // intersect facet with cutting plane IntersectionLines lines; @@ -918,9 +1244,9 @@ TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) // look for the vertex on whose side of the slicing plane there are no other vertices int isolated_vertex; - if ( (facet->vertex[0].z > z) == (facet->vertex[1].z > z) ) { + if ( (_z(facet->vertex[0]) > z) == (_z(facet->vertex[1]) > z) ) { isolated_vertex = 2; - } else if ( (facet->vertex[1].z > z) == (facet->vertex[2].z > z) ) { + } else if ( (_z(facet->vertex[1]) > z) == (_z(facet->vertex[2]) > z) ) { isolated_vertex = 0; } else { isolated_vertex = 1; @@ -933,12 +1259,12 @@ TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) // intersect v0-v1 and v2-v0 with cutting plane and make new vertices stl_vertex v0v1, v2v0; - v0v1.x = v1->x + (v0->x - v1->x) * (z - v1->z) / (v0->z - v1->z); - v0v1.y = v1->y + (v0->y - v1->y) * (z - v1->z) / (v0->z - v1->z); - v0v1.z = z; - v2v0.x = v2->x + (v0->x - v2->x) * (z - v2->z) / (v0->z - v2->z); - v2v0.y = v2->y + (v0->y - v2->y) * (z - v2->z) / (v0->z - v2->z); - v2v0.z = z; + _x(v0v1) = _x(*v1) + (_x(*v0) - _x(*v1)) * (z - _z(*v1)) / (_z(*v0) - _z(*v1)); + _y(v0v1) = _y(*v1) + (_y(*v0) - _y(*v1)) * (z - _z(*v1)) / (_z(*v0) - _z(*v1)); + _z(v0v1) = z; + _x(v2v0) = _x(*v2) + (_x(*v0) - _x(*v2)) * (z - _z(*v2)) / (_z(*v0) - _z(*v2)); + _y(v2v0) = _y(*v2) + (_y(*v0) - _y(*v2)) * (z - _z(*v2)) / (_z(*v0) - _z(*v2)); + _z(v2v0) = z; // build the triangular facet stl_facet triangle; @@ -958,7 +1284,7 @@ TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) quadrilateral[1].vertex[1] = v2v0; quadrilateral[1].vertex[2] = v0v1; - if (v0->z > z) { + if (_z(*v0) > z) { if (upper != NULL) stl_add_facet(&upper->stl, &triangle); if (lower != NULL) { stl_add_facet(&lower->stl, &quadrilateral[0]); @@ -990,13 +1316,13 @@ TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) Polygon p = *polygon; p.reverse(); stl_facet facet; - facet.normal.x = 0; - facet.normal.y = 0; - facet.normal.z = -1; + _x(facet.normal) = 0; + _y(facet.normal) = 0; + _z(facet.normal) = -1; for (size_t i = 0; i <= 2; ++i) { - facet.vertex[i].x = unscale(p.points[i].x); - facet.vertex[i].y = unscale(p.points[i].y); - facet.vertex[i].z = z; + _x(facet.vertex[i]) = unscale(p.points[i].x); + _y(facet.vertex[i]) = unscale(p.points[i].y); + _z(facet.vertex[i]) = z; } stl_add_facet(&upper->stl, &facet); } @@ -1016,13 +1342,13 @@ TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) // convert triangles to facets and append them to mesh for (Polygons::const_iterator polygon = triangles.begin(); polygon != triangles.end(); ++polygon) { stl_facet facet; - facet.normal.x = 0; - facet.normal.y = 0; - facet.normal.z = 1; + _x(facet.normal) = 0; + _y(facet.normal) = 0; + _z(facet.normal) = 1; for (size_t i = 0; i <= 2; ++i) { - facet.vertex[i].x = unscale(polygon->points[i].x); - facet.vertex[i].y = unscale(polygon->points[i].y); - facet.vertex[i].z = z; + _x(facet.vertex[i]) = unscale(polygon->points[i].x); + _y(facet.vertex[i]) = unscale(polygon->points[i].y); + _z(facet.vertex[i]) = z; } stl_add_facet(&lower->stl, &facet); } @@ -1031,10 +1357,10 @@ TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) stl_get_size(&(upper->stl)); stl_get_size(&(lower->stl)); - } -TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : mesh(_mesh), v_scaled_shared(NULL) +template +TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : mesh(_mesh), v_scaled_shared(NULL) { // build a table to map a facet_idx to its three edge indices this->mesh->require_shared_vertices(); @@ -1093,9 +1419,14 @@ TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : mesh(_mesh), v_sca } } -TriangleMeshSlicer::~TriangleMeshSlicer() +template +TriangleMeshSlicer::~TriangleMeshSlicer() { if (this->v_scaled_shared != NULL) free(this->v_scaled_shared); } +template class TriangleMeshSlicer; +template class TriangleMeshSlicer; +template class TriangleMeshSlicer; + } diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index 196ef17c0..cba8ba43e 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -4,6 +4,7 @@ #include "libslic3r.h" #include #include +#include #include "BoundingBox.hpp" #include "Line.hpp" #include "Point.hpp" @@ -13,16 +14,17 @@ namespace Slic3r { class TriangleMesh; -class TriangleMeshSlicer; +template class TriangleMeshSlicer; typedef std::vector TriangleMeshPtrs; class TriangleMesh { public: TriangleMesh(); + TriangleMesh(const Pointf3s &points, const std::vector &facets); TriangleMesh(const TriangleMesh &other); TriangleMesh& operator= (TriangleMesh other); - void swap(TriangleMesh &first, TriangleMesh &second); + void swap(TriangleMesh &other); ~TriangleMesh(); void ReadSTLFile(const std::string &input_file); void write_ascii(const std::string &output_file); @@ -47,6 +49,7 @@ class TriangleMesh void center_around_origin(); void rotate(double angle, Point* center); TriangleMeshPtrs split() const; + TriangleMeshPtrs cut_by_grid(const Pointf &grid) const; void merge(const TriangleMesh &mesh); ExPolygons horizontal_projection() const; Polygon convex_hull(); @@ -54,12 +57,20 @@ class TriangleMesh void reset_repair_stats(); bool needed_repair() const; size_t facets_count() const; + void extrude_tin(float offset); + + static TriangleMesh make_cube(double x, double y, double z); + static TriangleMesh make_cylinder(double r, double h, double fa=(2*PI/360)); + static TriangleMesh make_sphere(double rho, double fa=(2*PI/360)); + stl_file stl; bool repaired; private: void require_shared_vertices(); - friend class TriangleMeshSlicer; + friend class TriangleMeshSlicer; + friend class TriangleMeshSlicer; + friend class TriangleMeshSlicer; }; enum FacetEdgeType { feNone, feTop, feBottom, feHorizontal }; @@ -86,27 +97,62 @@ class IntersectionLine : public Line typedef std::vector IntersectionLines; typedef std::vector IntersectionLinePtrs; +template class TriangleMeshSlicer { public: TriangleMesh* mesh; TriangleMeshSlicer(TriangleMesh* _mesh); ~TriangleMeshSlicer(); - void slice(const std::vector &z, std::vector* layers); - void slice(const std::vector &z, std::vector* layers); - void slice_facet(float slice_z, const stl_facet &facet, const int &facet_idx, const float &min_z, const float &max_z, std::vector* lines) const; - void cut(float z, TriangleMesh* upper, TriangleMesh* lower); + void slice(const std::vector &z, std::vector* layers) const; + void slice(const std::vector &z, std::vector* layers) const; + void slice(float z, ExPolygons* slices) const; + void slice_facet(float slice_z, const stl_facet &facet, const int &facet_idx, + const float &min_z, const float &max_z, std::vector* lines, + boost::mutex* lines_mutex = NULL) const; + + void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; private: typedef std::vector< std::vector > t_facets_edges; t_facets_edges facets_edges; stl_vertex* v_scaled_shared; - void make_loops(std::vector &lines, Polygons* loops); - void make_expolygons(const Polygons &loops, ExPolygons* slices); - void make_expolygons_simple(std::vector &lines, ExPolygons* slices); - void make_expolygons(std::vector &lines, ExPolygons* slices); + void _slice_do(size_t facet_idx, std::vector* lines, boost::mutex* lines_mutex, const std::vector &z) const; + void _make_loops_do(size_t i, std::vector* lines, std::vector* layers) const; + void make_loops(std::vector &lines, Polygons* loops) const; + void make_expolygons(const Polygons &loops, ExPolygons* slices) const; + void make_expolygons_simple(std::vector &lines, ExPolygons* slices) const; + void make_expolygons(std::vector &lines, ExPolygons* slices) const; + + float& _x(stl_vertex &vertex) const; + float& _y(stl_vertex &vertex) const; + float& _z(stl_vertex &vertex) const; + const float& _x(stl_vertex const &vertex) const; + const float& _y(stl_vertex const &vertex) const; + const float& _z(stl_vertex const &vertex) const; }; +template<> inline float& TriangleMeshSlicer::_x(stl_vertex &vertex) const { return vertex.y; } +template<> inline float& TriangleMeshSlicer::_y(stl_vertex &vertex) const { return vertex.z; } +template<> inline float& TriangleMeshSlicer::_z(stl_vertex &vertex) const { return vertex.x; } +template<> inline float const& TriangleMeshSlicer::_x(stl_vertex const &vertex) const { return vertex.y; } +template<> inline float const& TriangleMeshSlicer::_y(stl_vertex const &vertex) const { return vertex.z; } +template<> inline float const& TriangleMeshSlicer::_z(stl_vertex const &vertex) const { return vertex.x; } + +template<> inline float& TriangleMeshSlicer::_x(stl_vertex &vertex) const { return vertex.z; } +template<> inline float& TriangleMeshSlicer::_y(stl_vertex &vertex) const { return vertex.x; } +template<> inline float& TriangleMeshSlicer::_z(stl_vertex &vertex) const { return vertex.y; } +template<> inline float const& TriangleMeshSlicer::_x(stl_vertex const &vertex) const { return vertex.z; } +template<> inline float const& TriangleMeshSlicer::_y(stl_vertex const &vertex) const { return vertex.x; } +template<> inline float const& TriangleMeshSlicer::_z(stl_vertex const &vertex) const { return vertex.y; } + +template<> inline float& TriangleMeshSlicer::_x(stl_vertex &vertex) const { return vertex.x; } +template<> inline float& TriangleMeshSlicer::_y(stl_vertex &vertex) const { return vertex.y; } +template<> inline float& TriangleMeshSlicer::_z(stl_vertex &vertex) const { return vertex.z; } +template<> inline float const& TriangleMeshSlicer::_x(stl_vertex const &vertex) const { return vertex.x; } +template<> inline float const& TriangleMeshSlicer::_y(stl_vertex const &vertex) const { return vertex.y; } +template<> inline float const& TriangleMeshSlicer::_z(stl_vertex const &vertex) const { return vertex.z; } + } #endif diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index eb3fb8f03..6789d58ff 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -4,16 +4,30 @@ // this needs to be included early for MSVC (listing it in Build.PL is not enough) #include #include +#include +#include #include +#include +#include #define SLIC3R_VERSION "1.3.0-dev" +//FIXME This epsilon value is used for many non-related purposes: +// For a threshold of a squared Euclidean distance, +// for a trheshold in a difference of radians, +// for a threshold of a cross product of two non-normalized vectors etc. #define EPSILON 1e-4 +// Scaling factor for a conversion from coord_t to coordf_t: 10e-6 +// This scaling generates a following fixed point representation with for a 32bit integer: +// 0..4294mm with 1nm resolution #define SCALING_FACTOR 0.000001 +// RESOLUTION, SCALED_RESOLUTION: Used as an error threshold for a Douglas-Peucker polyline simplification algorithm. #define RESOLUTION 0.0125 #define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR) #define PI 3.141592653589793238 +// When extruding a closed loop, the loop is interrupted and shortened a bit to reduce the seam. #define LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER 0.15 +// Maximum perimeter length for the loop to apply the small perimeter speed. #define SMALL_PERIMETER_LENGTH (6.5 / SCALING_FACTOR) * 2 * PI #define INSET_OVERLAP_TOLERANCE 0.4 #define EXTERNAL_INFILL_MARGIN 3 @@ -23,17 +37,79 @@ typedef long coord_t; typedef double coordf_t; -namespace Slic3r { - -// TODO: make sure X = 0 -enum Axis { X, Y, Z }; - -} -using namespace Slic3r; - /* Implementation of CONFESS("foo"): */ -#define CONFESS(...) confess_at(__FILE__, __LINE__, __func__, __VA_ARGS__) +#ifdef _MSC_VER + #define CONFESS(...) confess_at(__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#else + #define CONFESS(...) confess_at(__FILE__, __LINE__, __func__, __VA_ARGS__) +#endif void confess_at(const char *file, int line, const char *func, const char *pat, ...); /* End implementation of CONFESS("foo"): */ +// Which C++ version is supported? +// For example, could optimized functions with move semantics be used? +#if __cplusplus==201402L + #define SLIC3R_CPPVER 14 + #define STDMOVE(WHAT) std::move(WHAT) +#elif __cplusplus==201103L + #define SLIC3R_CPPVER 11 + #define STDMOVE(WHAT) std::move(WHAT) +#else + #define SLIC3R_CPPVER 0 + #define STDMOVE(WHAT) (WHAT) +#endif + +namespace Slic3r { + +enum Axis { X=0, Y, Z }; + +template +inline void append_to(std::vector &dst, const std::vector &src) +{ + dst.insert(dst.end(), src.begin(), src.end()); +} + +template void +_parallelize_do(std::queue* queue, boost::mutex* queue_mutex, boost::function func) +{ + //std::cout << "THREAD STARTED: " << boost::this_thread::get_id() << std::endl; + while (true) { + T i; + { + boost::lock_guard l(*queue_mutex); + if (queue->empty()) return; + i = queue->front(); + queue->pop(); + } + //std::cout << " Thread " << boost::this_thread::get_id() << " processing item " << i << std::endl; + func(i); + boost::this_thread::interruption_point(); + } +} + +template void +parallelize(std::queue queue, boost::function func, + int threads_count = boost::thread::hardware_concurrency()) +{ + if (threads_count == 0) threads_count = 2; + boost::mutex queue_mutex; + boost::thread_group workers; + for (int i = 0; i < std::min(threads_count, (int)queue.size()); i++) + workers.add_thread(new boost::thread(&_parallelize_do, &queue, &queue_mutex, func)); + workers.join_all(); +} + +template void +parallelize(T start, T end, boost::function func, + int threads_count = boost::thread::hardware_concurrency()) +{ + std::queue queue; + for (T i = start; i <= end; ++i) queue.push(i); + parallelize(queue, func, threads_count); +} + +} // namespace Slic3r + +using namespace Slic3r; + #endif diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 5cdd45688..3b121e979 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -11,7 +11,9 @@ REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop"); // there is no ExtrusionLoop::Collection or ExtrusionEntity::Collection REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection"); REGISTER_CLASS(Flow, "Flow"); +REGISTER_CLASS(Filler, "Filler"); REGISTER_CLASS(AvoidCrossingPerimeters, "GCode::AvoidCrossingPerimeters"); +REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer"); REGISTER_CLASS(OozePrevention, "GCode::OozePrevention"); REGISTER_CLASS(Wipe, "GCode::Wipe"); REGISTER_CLASS(GCode, "GCode"); @@ -52,6 +54,7 @@ REGISTER_CLASS(PrintRegionConfig, "Config::PrintRegion"); REGISTER_CLASS(GCodeConfig, "Config::GCode"); REGISTER_CLASS(PrintConfig, "Config::Print"); REGISTER_CLASS(FullPrintConfig, "Config::Full"); +REGISTER_CLASS(SLAPrint, "SLAPrint"); REGISTER_CLASS(Surface, "Surface"); REGISTER_CLASS(SurfaceCollection, "Surface::Collection"); REGISTER_CLASS(TriangleMesh, "TriangleMesh"); @@ -532,8 +535,7 @@ SV* polynode2perl(const ClipperLib::PolyNode& node) { HV* hv = newHV(); - Slic3r::Polygon p; - ClipperPath_to_Slic3rMultiPoint(node.Contour, &p); + Polygon p = ClipperPath_to_Slic3rMultiPoint(node.Contour); if (node.IsHole()) { (void)hv_stores( hv, "hole", Slic3r::perl_to_SV_clone_ref(p) ); } else { diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index e398a1c37..519f96aa9 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -8,6 +8,8 @@ _3DScene::_extrusionentity_to_verts_do(const Lines &lines, const std::vector &heights, bool closed, double top_z, const Point ©, GLVertexArray* qverts, GLVertexArray* tverts) { + if (lines.empty()) return; + /* It looks like it's faster without reserving capacity... // each segment has 4 quads, thus 16 vertices; + 2 caps qverts->reserve_more(3 * 4 * (4 * lines.size() + 2)); diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index 69ac337f7..a5a3949d6 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -31,6 +31,7 @@ #include #include #include +#include #ifdef SLIC3RXS extern "C" { @@ -40,13 +41,20 @@ extern "C" { #include "ppport.h" #undef do_open #undef do_close +#undef bind +#undef seed +#ifdef _MSC_VER + // Undef some of the macros set by Perl , which cause compilation errors on Win32 + #undef send + #undef connect +#endif /* _MSC_VER */ } #endif -#include #include #include #include +#include #include #include #include @@ -145,6 +153,19 @@ SV* to_SV(TriangleMesh* THIS); SV* polynode_children_2_perl(const ClipperLib::PolyNode& node); SV* polynode2perl(const ClipperLib::PolyNode& node); +class Filler +{ + public: + Filler() : fill(NULL) {}; + ~Filler() { + if (fill != NULL) { + delete fill; + fill = NULL; + } + }; + Fill* fill; +}; + } #endif diff --git a/xs/t/01_trianglemesh.t b/xs/t/01_trianglemesh.t index fd57cf805..377d4a022 100644 --- a/xs/t/01_trianglemesh.t +++ b/xs/t/01_trianglemesh.t @@ -6,6 +6,8 @@ use warnings; use Slic3r::XS; use Test::More tests => 49; +use constant Z => 2; + is Slic3r::TriangleMesh::hello_world(), 'Hello world!', 'hello world'; @@ -117,7 +119,7 @@ my $cube = { { my $upper = Slic3r::TriangleMesh->new; my $lower = Slic3r::TriangleMesh->new; - $m->cut(0, $upper, $lower); + $m->cut(Z, 0, $upper, $lower); $upper->repair; $lower->repair; is $upper->facets_count, 12, 'upper mesh has all facets except those belonging to the slicing plane'; is $lower->facets_count, 0, 'lower mesh has no facets'; @@ -125,7 +127,7 @@ my $cube = { { my $upper = Slic3r::TriangleMesh->new; my $lower = Slic3r::TriangleMesh->new; - $m->cut(10, $upper, $lower); + $m->cut(Z, 10, $upper, $lower); #$upper->repair; $lower->repair; # we expect: # 2 facets on external horizontal surfaces diff --git a/xs/t/15_config.t b/xs/t/15_config.t index 838ce18b0..3a7ad0de6 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -4,7 +4,8 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 110; +use Test::More tests => 146; +use Data::Dumper; foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintConfig) { $config->set('layer_height', 0.3); @@ -24,7 +25,48 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo is $config->serialize('notes'), 'foo\nbar', 'serialize string with newline'; $config->set_deserialize('notes', 'bar\nbaz'); is $config->get('notes'), "bar\nbaz", 'deserialize string with newline'; - + + foreach my $test_data ( + { + name => 'empty', + values => [], + serialized => '' + }, + { + name => 'single empty', + values => [''], + serialized => '""' + }, + { + name => 'single noempty, simple', + values => ['RGB'], + serialized => 'RGB' + }, + { + name => 'multiple noempty, simple', + values => ['ABC', 'DEF', '09182745@!#$*(&'], + serialized => 'ABC;DEF;09182745@!#$*(&' + }, + { + name => 'multiple, simple, some empty', + values => ['ABC', 'DEF', '', '09182745@!#$*(&', ''], + serialized => 'ABC;DEF;;09182745@!#$*(&;' + }, + { + name => 'complex', + values => ['some "quoted" notes', "yet\n some notes", "whatever \n notes", ''], + serialized => '"some \"quoted\" notes";"yet\n some notes";"whatever \n notes";' + } + ) + { + $config->set('filament_notes', $test_data->{values}); + is $config->serialize('filament_notes'), $test_data->{serialized}, 'serialize multi-string value ' . $test_data->{name}; + $config->set_deserialize('filament_notes', ''); + is_deeply $config->get('filament_notes'), [], 'deserialize multi-string value - empty ' . $test_data->{name}; + $config->set_deserialize('filament_notes', $test_data->{serialized}); + is_deeply $config->get('filament_notes'), $test_data->{values}, 'deserialize complex multi-string value ' . $test_data->{name}; + } + $config->set('first_layer_height', 0.3); ok abs($config->get('first_layer_height') - 0.3) < 1e-4, 'set/get absolute floatOrPercent'; is $config->serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent'; @@ -55,8 +97,8 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo $config->set_deserialize('gcode_flavor', 'machinekit'); is $config->get('gcode_flavor'), 'machinekit', 'deserialize enum (gcode_flavor)'; - $config->set_deserialize('fill_pattern', 'line'); - is $config->get('fill_pattern'), 'line', 'deserialize enum (fill_pattern)'; + $config->set_deserialize('fill_pattern', 'stars'); + is $config->get('fill_pattern'), 'stars', 'deserialize enum (fill_pattern)'; $config->set_deserialize('support_material_pattern', 'pillars'); is $config->get('support_material_pattern'), 'pillars', 'deserialize enum (support_material_pattern)'; @@ -157,12 +199,12 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo { my $config = Slic3r::Config->new; - $config->set('fill_pattern', 'line'); + $config->set('fill_pattern', 'stars'); my $config2 = Slic3r::Config->new; $config2->set('fill_pattern', 'hilbertcurve'); - is $config->get('fill_pattern'), 'line', 'no interferences between DynamicConfig objects'; + is $config->get('fill_pattern'), 'stars', 'no interferences between DynamicConfig objects'; } { diff --git a/xs/t/22_exception.t b/xs/t/22_exception.t new file mode 100644 index 000000000..ca2ffea89 --- /dev/null +++ b/xs/t/22_exception.t @@ -0,0 +1,16 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Slic3r::XS; +use Test::More tests => 1; + +{ + eval { + Slic3r::xspp_test_croak_hangs_on_strawberry(); + }; + is $@, "xspp_test_croak_hangs_on_strawberry: exception catched\n", 'croak from inside a C++ exception delivered'; +} + +__END__ diff --git a/xs/t/22_config.t b/xs/t/23_config.t similarity index 100% rename from xs/t/22_config.t rename to xs/t/23_config.t diff --git a/xs/xsp/BoundingBox.xsp b/xs/xsp/BoundingBox.xsp index 9da9173aa..53af895bf 100644 --- a/xs/xsp/BoundingBox.xsp +++ b/xs/xsp/BoundingBox.xsp @@ -26,7 +26,8 @@ long x_max() %code{% RETVAL = THIS->max.x; %}; long y_min() %code{% RETVAL = THIS->min.y; %}; long y_max() %code{% RETVAL = THIS->max.y; %}; - + bool defined() %code{% RETVAL = THIS->defined; %}; + %{ BoundingBox* @@ -63,6 +64,7 @@ new_from_points(CLASS, points) void set_x_max(double val) %code{% THIS->max.x = val; %}; void set_y_min(double val) %code{% THIS->min.y = val; %}; void set_y_max(double val) %code{% THIS->max.y = val; %}; + bool defined() %code{% RETVAL = THIS->defined; %}; %{ @@ -97,4 +99,5 @@ new_from_points(CLASS, points) double y_max() %code{% RETVAL = THIS->max.y; %}; double z_min() %code{% RETVAL = THIS->min.z; %}; double z_max() %code{% RETVAL = THIS->max.z; %}; + bool defined() %code{% RETVAL = THIS->defined; %}; }; diff --git a/xs/xsp/Clipper.xsp b/xs/xsp/Clipper.xsp index 7a33ea0c4..7bbbdb8d6 100644 --- a/xs/xsp/Clipper.xsp +++ b/xs/xsp/Clipper.xsp @@ -8,6 +8,40 @@ %package{Slic3r::Geometry::Clipper}; +Polygons offset(Polygons polygons, float delta, double scale = CLIPPER_OFFSET_SCALE, + ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); + +ExPolygons offset_ex(Polygons polygons, float delta, double scale = CLIPPER_OFFSET_SCALE, + ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); + +Polygons offset2(Polygons polygons, float delta1, float delta2, double scale = CLIPPER_OFFSET_SCALE, + ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); + +ExPolygons offset2_ex(Polygons polygons, float delta1, float delta2, double scale = CLIPPER_OFFSET_SCALE, + ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); + +Polygons diff(Polygons subject, Polygons clip, bool safety_offset = false); + +ExPolygons diff_ex(Polygons subject, Polygons clip, bool safety_offset = false); + +Polylines diff_pl(Polylines subject, Polygons clip); + +Polygons intersection(Polygons subject, Polygons clip, bool safety_offset = false); + +ExPolygons intersection_ex(Polygons subject, Polygons clip, bool safety_offset = false); + +Polylines intersection_pl(Polylines subject, Polygons clip); + +%name{intersection_ppl} Polylines intersection_pl(Polygons subject, Polygons clip); + +%name{union} Polygons union_(Polygons subject, bool safety_offset = false); + +ExPolygons union_ex(Polygons subject, bool safety_offset = false); + +Polygons union_pt_chained(Polygons subject, bool safety_offset = false); + +Polygons simplify_polygons(Polygons subject); + %{ IV @@ -21,188 +55,15 @@ _constant() RETVAL = ix; OUTPUT: RETVAL -Polygons -offset(polygons, delta, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtMiter, miterLimit = 3) - Polygons polygons - const float delta - double scale - ClipperLib::JoinType joinType - double miterLimit - CODE: - offset(polygons, &RETVAL, delta, scale, joinType, miterLimit); - OUTPUT: - RETVAL - -ExPolygons -offset_ex(polygons, delta, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtMiter, miterLimit = 3) - Polygons polygons - const float delta - double scale - ClipperLib::JoinType joinType - double miterLimit - CODE: - offset(polygons, &RETVAL, delta, scale, joinType, miterLimit); - OUTPUT: - RETVAL - -Polygons -offset2(polygons, delta1, delta2, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtMiter, miterLimit = 3) - Polygons polygons - const float delta1 - const float delta2 - double scale - ClipperLib::JoinType joinType - double miterLimit - CODE: - offset2(polygons, &RETVAL, delta1, delta2, scale, joinType, miterLimit); - OUTPUT: - RETVAL - -ExPolygons -offset2_ex(polygons, delta1, delta2, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtMiter, miterLimit = 3) - Polygons polygons - const float delta1 - const float delta2 - double scale - ClipperLib::JoinType joinType - double miterLimit - CODE: - offset2(polygons, &RETVAL, delta1, delta2, scale, joinType, miterLimit); - OUTPUT: - RETVAL - -Polygons -diff(subject, clip, safety_offset = false) - Polygons subject - Polygons clip - bool safety_offset - CODE: - diff(subject, clip, &RETVAL, safety_offset); - OUTPUT: - RETVAL - -ExPolygons -diff_ex(subject, clip, safety_offset = false) - Polygons subject - Polygons clip - bool safety_offset - CODE: - diff(subject, clip, &RETVAL, safety_offset); - OUTPUT: - RETVAL - -Polylines -diff_pl(subject, clip) - Polylines subject - Polygons clip - CODE: - diff(subject, clip, &RETVAL); - OUTPUT: - RETVAL - -Polylines -diff_ppl(subject, clip) - Polygons subject - Polygons clip - CODE: - diff(subject, clip, &RETVAL); - OUTPUT: - RETVAL - -Polygons -intersection(subject, clip, safety_offset = false) - Polygons subject - Polygons clip - bool safety_offset - CODE: - intersection(subject, clip, &RETVAL, safety_offset); - OUTPUT: - RETVAL - -ExPolygons -intersection_ex(subject, clip, safety_offset = false) - Polygons subject - Polygons clip - bool safety_offset - CODE: - intersection(subject, clip, &RETVAL, safety_offset); - OUTPUT: - RETVAL - -Polylines -intersection_pl(subject, clip) - Polylines subject - Polygons clip - CODE: - intersection(subject, clip, &RETVAL); - OUTPUT: - RETVAL - -Polylines -intersection_ppl(subject, clip) - Polygons subject - Polygons clip - CODE: - intersection(subject, clip, &RETVAL); - OUTPUT: - RETVAL - -ExPolygons -xor_ex(subject, clip, safety_offset = false) - Polygons subject - Polygons clip - bool safety_offset - CODE: - xor_(subject, clip, &RETVAL, safety_offset); - OUTPUT: - RETVAL - -Polygons -union(subject, safety_offset = false) - Polygons subject - bool safety_offset - CODE: - union_(subject, &RETVAL, safety_offset); - OUTPUT: - RETVAL - -ExPolygons -union_ex(subject, safety_offset = false) - Polygons subject - bool safety_offset - CODE: - union_(subject, &RETVAL, safety_offset); - OUTPUT: - RETVAL - SV* union_pt(subject, safety_offset = false) Polygons subject bool safety_offset CODE: // perform operation - ClipperLib::PolyTree polytree; - union_pt(subject, &polytree, safety_offset); - + ClipperLib::PolyTree polytree = union_pt(subject, safety_offset); RETVAL = polynode_children_2_perl(polytree); OUTPUT: RETVAL -Polygons -union_pt_chained(subject, safety_offset = false) - Polygons subject - bool safety_offset - CODE: - union_pt_chained(subject, &RETVAL, safety_offset); - OUTPUT: - RETVAL - -Polygons -simplify_polygons(subject) - Polygons subject - CODE: - simplify_polygons(subject, &RETVAL); - OUTPUT: - RETVAL - %} diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 056f1e1fd..96c4830e0 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -53,6 +53,8 @@ %code{% RETVAL = new PrintRegionConfig (); %}; static StaticPrintConfig* new_FullPrintConfig() %code{% RETVAL = new FullPrintConfig (); %}; + static StaticPrintConfig* new_SLAPrintConfig() + %code{% RETVAL = new SLAPrintConfig (); %}; ~StaticPrintConfig(); bool has(t_config_option_key opt_key); SV* as_hash() @@ -88,6 +90,8 @@ double min_object_distance(); %name{_load} void load(std::string file); %name{_save} void save(std::string file); + DynamicPrintConfig* dynamic() + %code{% RETVAL = new DynamicPrintConfig (); RETVAL->apply(*THIS, true); %}; }; %package{Slic3r::Config}; @@ -133,7 +137,7 @@ print_config_def() (void)hv_stores( hv, "label", newSVpvn_utf8(optdef->label.c_str(), optdef->label.length(), true) ); if (!optdef->full_label.empty()) (void)hv_stores( hv, "full_label", newSVpvn_utf8(optdef->full_label.c_str(), optdef->full_label.length(), true) ); - (void)hv_stores( hv, "category", newSVpvn(optdef->category.c_str(), optdef->category.length()) ); + (void)hv_stores( hv, "category", newSVpvn_utf8(optdef->category.c_str(), optdef->category.length(), true) ); (void)hv_stores( hv, "tooltip", newSVpvn_utf8(optdef->tooltip.c_str(), optdef->tooltip.length(), true) ); (void)hv_stores( hv, "sidetext", newSVpvn_utf8(optdef->sidetext.c_str(), optdef->sidetext.length(), true) ); (void)hv_stores( hv, "cli", newSVpvn(optdef->cli.c_str(), optdef->cli.length()) ); diff --git a/xs/xsp/Filler.xsp b/xs/xsp/Filler.xsp new file mode 100644 index 000000000..0c5c08b6c --- /dev/null +++ b/xs/xsp/Filler.xsp @@ -0,0 +1,85 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "libslic3r/Fill/Fill.hpp" +#include "libslic3r/PolylineCollection.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" +%} + +%name{Slic3r::Filler} class Filler { + ~Filler(); + + void set_bounding_box(BoundingBox *bbox) + %code{% THIS->fill->bounding_box = *bbox; %}; + + void set_min_spacing(coordf_t spacing) + %code{% THIS->fill->min_spacing = spacing; %}; + coordf_t min_spacing() + %code{% RETVAL = THIS->fill->min_spacing; %}; + + coordf_t spacing() + %code{% RETVAL = THIS->fill->spacing(); %}; + + void set_endpoints_overlap(float overlap) + %code{% THIS->fill->endpoints_overlap = overlap; %}; + float endpoints_overlap() + %code{% RETVAL = THIS->fill->endpoints_overlap; %}; + + void set_layer_id(size_t layer_id) + %code{% THIS->fill->layer_id = layer_id; %}; + void set_z(coordf_t z) + %code{% THIS->fill->z = z; %}; + void set_angle(float angle) + %code{% THIS->fill->angle = angle; %}; + void set_link_max_length(coordf_t len) + %code{% THIS->fill->link_max_length = len; %}; + void set_loop_clipping(coordf_t clipping) + %code{% THIS->fill->loop_clipping = clipping; %}; + + bool use_bridge_flow() + %code{% RETVAL = THIS->fill->use_bridge_flow(); %}; + bool no_sort() + %code{% RETVAL = THIS->fill->no_sort(); %}; + + void set_density(float density) + %code{% THIS->fill->density = density; %}; + void set_dont_connect(bool dont_connect) + %code{% THIS->fill->dont_connect = dont_connect; %}; + void set_dont_adjust(bool dont_adjust) + %code{% THIS->fill->dont_adjust = dont_adjust; %}; + void set_complete(bool complete) + %code{% THIS->fill->complete = complete; %}; + + PolylineCollection* _fill_surface(Surface *surface) + %code{% + PolylineCollection *pc = NULL; + if (THIS->fill != NULL) { + pc = new PolylineCollection(); + pc->polylines = THIS->fill->fill_surface(*surface); + } + RETVAL = pc; + %}; + +%{ + +Filler* +new_from_type(CLASS, type) + char* CLASS; + std::string type; + CODE: + Filler *filler = new Filler(); + filler->fill = Fill::new_from_type(type); + RETVAL = filler; + OUTPUT: + RETVAL + +%} + +}; + +%package{Slic3r::Filler}; + +coord_t adjust_solid_spacing(coord_t width, coord_t distance) + %code{% RETVAL = Fill::adjust_solid_spacing(width, distance); %}; diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 7f17a3a12..579951c73 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -3,6 +3,7 @@ %{ #include #include "libslic3r/GCode.hpp" +#include "libslic3r/GCode/CoolingBuffer.hpp" %} %name{Slic3r::GCode::AvoidCrossingPerimeters} class AvoidCrossingPerimeters { @@ -70,6 +71,16 @@ %code{% THIS->path = *value; %}; }; +%name{Slic3r::GCode::CoolingBuffer} class CoolingBuffer { + CoolingBuffer(GCode* gcode) + %code{% RETVAL = new CoolingBuffer(*gcode); %}; + ~CoolingBuffer(); + Ref gcodegen(); + + std::string append(std::string gcode, std::string obj_id, size_t layer_id, float print_z); + std::string flush(); +}; + %name{Slic3r::GCode} class GCode { GCode(); ~GCode(); diff --git a/xs/xsp/Geometry.xsp b/xs/xsp/Geometry.xsp index 718ce5bdf..37dad4aee 100644 --- a/xs/xsp/Geometry.xsp +++ b/xs/xsp/Geometry.xsp @@ -9,7 +9,12 @@ %package{Slic3r::Geometry}; Pointfs arrange(size_t total_parts, Pointf* part, coordf_t dist, BoundingBoxf* bb = NULL) - %code{% RETVAL = Slic3r::Geometry::arrange(total_parts, *part, dist, bb); %}; + %code{% + Pointfs points; + if (! Slic3r::Geometry::arrange(total_parts, *part, dist, bb, points)) + CONFESS("%zu parts won't fit in your print area!\n", total_parts); + RETVAL = points; + %}; %{ @@ -81,15 +86,6 @@ deg2rad(angle) OUTPUT: RETVAL -Polygons -simplify_polygons(polygons, tolerance) - Polygons polygons - double tolerance - CODE: - Slic3r::Geometry::simplify_polygons(polygons, tolerance, &RETVAL); - OUTPUT: - RETVAL - IV _constant() diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index e5697ce89..4be9feccf 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -86,6 +86,7 @@ bool any_bottom_region_slice_contains_polyline(Polyline* polyline) %code%{ RETVAL = THIS->any_bottom_region_slice_contains(*polyline); %}; void make_perimeters(); + void make_fills(); }; %name{Slic3r::Layer::Support} class SupportLayer { diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 8bfd10123..0825867be 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -66,12 +66,12 @@ ModelObjectPtrs* objects() %code%{ RETVAL = &THIS->objects; %}; - Pointfs _arrange(Pointfs sizes, double dist, BoundingBoxf* bb = NULL); - void arrange_objects(double dist, BoundingBoxf* bb = NULL); + bool arrange_objects(double dist, BoundingBoxf* bb = NULL); void duplicate(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL); void duplicate_objects(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL); void duplicate_objects_grid(unsigned int x, unsigned int y, double dist); void print_info(); + void repair(); }; @@ -143,6 +143,8 @@ ModelMaterial::attributes() void clear_volumes(); int volumes_count() %code%{ RETVAL = THIS->volumes.size(); %}; + Ref get_volume(int idx) + %code%{ RETVAL = THIS->volumes.at(idx); %}; %name{_add_instance} Ref add_instance(); Ref _add_instance_clone(ModelInstance* other) @@ -151,6 +153,8 @@ ModelMaterial::attributes() void clear_instances(); int instances_count() %code%{ RETVAL = THIS->instances.size(); %}; + Ref get_instance(int idx) + %code%{ RETVAL = THIS->instances.at(idx); %}; std::string name() %code%{ RETVAL = THIS->name; %}; @@ -185,11 +189,13 @@ ModelMaterial::attributes() %code{% THIS->scale(*versor); %}; void rotate(float angle, Axis axis); void mirror(Axis axis); + void transform_by_instance(ModelInstance* instance, bool dont_translate = false) + %code{% THIS->transform_by_instance(*instance, dont_translate); %}; - Model* cut(double z) + Model* cut(Axis axis, double z) %code%{ RETVAL = new Model(); - THIS->cut(z, RETVAL); + THIS->cut(axis, z, RETVAL); %}; ModelObjectPtrs* split_object() @@ -199,6 +205,7 @@ ModelMaterial::attributes() %}; void print_info(); + void repair(); }; @@ -226,6 +233,15 @@ ModelMaterial::attributes() %code%{ THIS->modifier = modifier; %}; ModelMaterial* assign_unique_material(); + + void extrude_tin(float offset) + %code%{ + try { + THIS->mesh.extrude_tin(offset); + } catch (std::exception& e) { + croak("%s", e.what()); + } + %}; }; diff --git a/xs/xsp/Polyline.xsp b/xs/xsp/Polyline.xsp index 31bd4045e..41cf4f359 100644 --- a/xs/xsp/Polyline.xsp +++ b/xs/xsp/Polyline.xsp @@ -86,7 +86,7 @@ Polyline::grow(delta, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtSqu ClipperLib::JoinType joinType double miterLimit CODE: - offset(*THIS, &RETVAL, delta, scale, joinType, miterLimit); + RETVAL = offset(*THIS, delta, scale, joinType, miterLimit); OUTPUT: RETVAL diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index a5369cf54..9e57b2570 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -65,6 +65,22 @@ _constant() Clone bounding_box(); Ref _copies_shift() %code%{ RETVAL = &THIS->_copies_shift; %}; + std::vector support_material_extruders() + %code%{ + std::set extruders = THIS->support_material_extruders(); + RETVAL.reserve(extruders.size()); + for (std::set::const_iterator e = extruders.begin(); e != extruders.end(); ++e) { + RETVAL.push_back(*e); + } + %}; + std::vector extruders() + %code%{ + std::set extruders = THIS->extruders(); + RETVAL.reserve(extruders.size()); + for (std::set::const_iterator e = extruders.begin(); e != extruders.end(); ++e) { + RETVAL.push_back(*e); + } + %}; bool typed_slices() %code%{ RETVAL = THIS->typed_slices; %}; @@ -109,8 +125,30 @@ _constant() void set_step_started(PrintObjectStep step) %code%{ THIS->state.set_started(step); %}; + void detect_surfaces_type(); void process_external_surfaces(); void bridge_over_infill(); + SV* _slice_region(size_t region_id, std::vector z, bool modifier) + %code%{ + std::vector z_f(z.begin(), z.end()); + std::vector layers = THIS->_slice_region(region_id, z_f, modifier); + AV* layers_av = newAV(); + size_t len = layers.size(); + if (len > 0) av_extend(layers_av, len-1); + for (unsigned int i = 0; i < layers.size(); i++) { + AV* expolygons_av = newAV(); + len = layers[i].size(); + if (len > 0) av_extend(expolygons_av, len-1); + unsigned int j = 0; + for (ExPolygons::iterator it = layers[i].begin(); it != layers[i].end(); ++it) { + av_store(expolygons_av, j++, perl_to_SV_clone_ref(*it)); + } + av_store(layers_av, i, newRV_noinc((SV*)expolygons_av)); + } + RETVAL = (SV*)newRV_noinc((SV*)layers_av); + %}; + void _make_perimeters(); + void _infill(); int ptr() %code%{ RETVAL = (int)(intptr_t)THIS; %}; @@ -214,25 +252,22 @@ _constant() double max_allowed_layer_height() const; bool has_support_material() const; void auto_assign_extruders(ModelObject* model_object); + std::string output_filename(); + std::string output_filepath(std::string path = ""); void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig* config) %code%{ RETVAL = THIS->apply_config(*config); %}; bool has_infinite_skirt(); bool has_skirt(); - void validate() - %code%{ - try { - THIS->validate(); - } catch (PrintValidationException &e) { - croak("%s\n", e.what()); - } - %}; + std::string _validate() + %code%{ RETVAL = THIS->validate(); %}; Clone bounding_box(); Clone total_bounding_box(); double skirt_first_layer_height(); Clone brim_flow(); Clone skirt_flow(); + void _make_brim(); %{ double diff --git a/xs/xsp/SLAPrint.xsp b/xs/xsp/SLAPrint.xsp new file mode 100644 index 000000000..6526f713c --- /dev/null +++ b/xs/xsp/SLAPrint.xsp @@ -0,0 +1,65 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "libslic3r/SLAPrint.hpp" +%} + +%name{Slic3r::SLAPrint} class SLAPrint { + SLAPrint(Model* model); + ~SLAPrint(); + + void apply_config(DynamicPrintConfig* config) + %code%{ THIS->config.apply(*config, true); %}; + void slice(); + size_t layer_count() + %code{% RETVAL = THIS->layers.size(); %}; + + DynamicPrintConfig* config() + %code%{ RETVAL = new DynamicPrintConfig; RETVAL->apply(THIS->config); %}; + + ExPolygons layer_slices(size_t i) + %code%{ RETVAL = THIS->layers[i].slices; %}; + ExPolygons layer_solid_infill(size_t i) + %code%{ RETVAL = THIS->layers[i].solid_infill; %}; + ExPolygons layer_perimeters(size_t i) + %code%{ RETVAL = THIS->layers[i].perimeters; %}; + Ref layer_infill(size_t i) + %code%{ RETVAL = &THIS->layers[i].infill; %}; + bool layer_solid(size_t i) + %code%{ RETVAL = THIS->layers[i].solid; %}; + void write_svg(std::string file); + +%{ + +std::vector +SLAPrint::heights() + CODE: + for (std::vector::const_iterator it = THIS->layers.begin(); + it != THIS->layers.end(); + ++it) + RETVAL.push_back(it->print_z); + OUTPUT: + RETVAL + +SV* +SLAPrint::sm_pillars() + CODE: + AV* av = newAV(); + for (std::vector::const_iterator it = THIS->sm_pillars.begin(); + it != THIS->sm_pillars.end(); + ++it) + { + HV* hv = newHV(); + (void)hv_stores( hv, "top_layer", newSViv(it->top_layer) ); + (void)hv_stores( hv, "bottom_layer", newSViv(it->bottom_layer) ); + (void)hv_stores( hv, "point", perl_to_SV_clone_ref((Point)*it) ); + av_push(av, newRV_noinc((SV*)hv)); + } + RETVAL = newRV_noinc((SV*)av); + OUTPUT: + RETVAL + +%} + +}; diff --git a/xs/xsp/Surface.xsp b/xs/xsp/Surface.xsp index ebacd1795..021a8b7e4 100644 --- a/xs/xsp/Surface.xsp +++ b/xs/xsp/Surface.xsp @@ -89,7 +89,7 @@ Surface::offset(delta, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtMi ClipperLib::JoinType joinType double miterLimit CODE: - offset(*THIS, &RETVAL, delta, scale, joinType, miterLimit); + RETVAL = offset(*THIS, delta, scale, joinType, miterLimit); OUTPUT: RETVAL diff --git a/xs/xsp/SurfaceCollection.xsp b/xs/xsp/SurfaceCollection.xsp index 19cf3f828..a4fade4e5 100644 --- a/xs/xsp/SurfaceCollection.xsp +++ b/xs/xsp/SurfaceCollection.xsp @@ -60,18 +60,18 @@ SV* SurfaceCollection::group() CODE: // perform grouping - std::vector groups; + std::vector groups; THIS->group(&groups); // build return arrayref AV* av = newAV(); av_fill(av, groups.size()-1); size_t i = 0; - for (std::vector::iterator it = groups.begin(); it != groups.end(); ++it) { + for (std::vector::const_iterator it = groups.begin(); it != groups.end(); ++it) { AV* innerav = newAV(); av_fill(innerav, it->size()-1); size_t j = 0; - for (SurfacesPtr::iterator it_s = it->begin(); it_s != it->end(); ++it_s) { + for (SurfacesConstPtr::const_iterator it_s = it->begin(); it_s != it->end(); ++it_s) { av_store(innerav, j++, perl_to_SV_clone_ref(**it_s)); } av_store(av, i++, newRV_noinc((SV*)innerav)); diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index 3b8f1022c..373cba12e 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -30,6 +30,8 @@ void align_to_origin(); void rotate(double angle, Point* center); TriangleMeshPtrs split(); + TriangleMeshPtrs cut_by_grid(Pointf* grid) + %code{% RETVAL = THIS->cut_by_grid(*grid); %}; void merge(TriangleMesh* mesh) %code{% THIS->merge(*mesh); %}; ExPolygons horizontal_projection(); @@ -39,6 +41,7 @@ %code{% RETVAL = THIS->bounding_box().center(); %}; int facets_count(); void reset_repair_stats(); + %{ void @@ -185,7 +188,7 @@ TriangleMesh::slice(z) std::vector z_f(z.begin(), z.end()); std::vector layers; - TriangleMeshSlicer mslicer(THIS); + TriangleMeshSlicer mslicer(THIS); mslicer.slice(z_f, &layers); AV* layers_av = newAV(); @@ -205,14 +208,35 @@ TriangleMesh::slice(z) OUTPUT: RETVAL +ExPolygons +TriangleMesh::slice_at(axis, z) + Axis axis + double z + CODE: + if (axis == X) { + TriangleMeshSlicer(THIS).slice(z, &RETVAL); + } else if (axis == Y) { + TriangleMeshSlicer(THIS).slice(z, &RETVAL); + } else if (axis == Z) { + TriangleMeshSlicer(THIS).slice(z, &RETVAL); + } + OUTPUT: + RETVAL + void -TriangleMesh::cut(z, upper, lower) +TriangleMesh::cut(axis, z, upper, lower) + Axis axis float z; TriangleMesh* upper; TriangleMesh* lower; CODE: - TriangleMeshSlicer mslicer(THIS); - mslicer.cut(z, upper, lower); + if (axis == X) { + TriangleMeshSlicer(THIS).cut(z, upper, lower); + } else if (axis == Y) { + TriangleMeshSlicer(THIS).cut(z, upper, lower); + } else { + TriangleMeshSlicer(THIS).cut(z, upper, lower); + } std::vector TriangleMesh::bb3() @@ -231,6 +255,13 @@ TriangleMesh::bb3() %package{Slic3r::TriangleMesh}; +Clone make_cube(double x, double y, double z) + %code{% RETVAL = TriangleMesh::make_cube(x, y, z); %}; +Clone make_cylinder(double r, double h) + %code{% RETVAL = TriangleMesh::make_cylinder(r, h); %}; +Clone make_sphere(double rho) + %code{% RETVAL = TriangleMesh::make_sphere(rho); %}; + %{ PROTOTYPES: DISABLE diff --git a/xs/xsp/XS.xsp b/xs/xsp/XS.xsp index c54e06be5..1f6c8b610 100644 --- a/xs/xsp/XS.xsp +++ b/xs/xsp/XS.xsp @@ -16,4 +16,12 @@ VERSION() RETVAL = newSVpv(SLIC3R_VERSION, 0); OUTPUT: RETVAL -%} \ No newline at end of file +void +xspp_test_croak_hangs_on_strawberry() + CODE: + try { + throw 1; + } catch (...) { + croak("xspp_test_croak_hangs_on_strawberry: exception catched\n"); + } +%} diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 7590ec90e..69c1831cc 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -1,3 +1,4 @@ +coord_t T_IV coordf_t T_NV std::string T_STD_STRING @@ -56,6 +57,10 @@ TriangleMesh* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +SLAPrint* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + Point* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T @@ -112,6 +117,10 @@ ExtrusionLoop* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +Filler* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + Flow* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T @@ -188,6 +197,10 @@ OozePrevention* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +CoolingBuffer* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + GCode* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 8eb09db3b..fbc2d03f5 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -1,5 +1,6 @@ %typemap{bool}{simple}; %typemap{size_t}{simple}; +%typemap{coord_t}{simple}; %typemap{coordf_t}{simple}; %typemap{std::string}; %typemap{t_config_option_key}; @@ -60,6 +61,9 @@ %typemap{Flow*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; +%typemap{Filler*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; %typemap{Line*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; @@ -151,6 +155,10 @@ %typemap{Ref}{simple}; %typemap{Clone}{simple}; +%typemap{CoolingBuffer*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; + %typemap{GCode*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; @@ -195,13 +203,17 @@ %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{GLVertexArray*}; +%typemap{SLAPrint*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; %typemap{PrintRegionPtrs*}; %typemap{PrintObjectPtrs*}; %typemap{LayerPtrs*}; %typemap{SupportLayerPtrs*}; - +%typemap{ClipperLib::JoinType}{simple}; +%typemap{ClipperLib::PolyFillType}{simple}; %typemap{Axis}{parsed}{ %cpp_type{Axis}; %precall_code{%