Merge branch 'adaptive-slicing' into adaptive-slicing-spline

This commit is contained in:
Florens Wasserfall 2017-01-26 21:22:57 +01:00
commit 66cc47500b
220 changed files with 9565 additions and 4467 deletions

View File

@ -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:

1
.gitignore vendored
View File

@ -11,3 +11,4 @@ MANIFEST.bak
xs/MANIFEST.bak
xs/assertlib*
.init_bundle.ini
local-lib

View File

@ -20,3 +20,4 @@ addons:
- libboost-thread1.55-dev
- libboost-system1.55-dev
- libboost-filesystem1.55-dev
- liblocal-lib-perl

View File

@ -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};

View File

@ -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)

273
doc/How_to_build_Slic3r.txt Normal file
View File

@ -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.

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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)(?<!;_BRIDGE_FAN_START\n)(G1\sF)(\d+(?:\.\d+)?)/
my $new_speed = $2 * $speed_factor;
$1 . sprintf("%.3f", $new_speed < $self->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;

View File

@ -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;

View File

@ -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'),
);
}

View File

@ -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;

View File

@ -1,3 +1,5 @@
# Bed shape dialog
package Slic3r::GUI::2DBed;
use strict;
use warnings;

View File

@ -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<float> for coords and normals.
$qverts,
$tverts);
}
sub object_idx {

View File

@ -51,7 +51,7 @@ sub new {
'<a href="http://slic3r.org/">Slic3r</a> is licensed under the ' .
'<a href="http://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License, version 3</a>.' .
'<br /><br /><br />' .
'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. <br />' .
'Slic3r logo designed by Corey Daniels, <a href="http://www.famfamfam.com/lab/icons/silk/">Silk Icon Set</a> designed by Mark James. ' .
'</font>' .

View File

@ -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);
{

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -1,3 +1,5 @@
# A printer "Controller" -> "ManualControlDialog" subtab, opened per 3D printer connected?
package Slic3r::GUI::Controller::ManualControlDialog;
use strict;
use warnings;

View File

@ -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) = @_;

View File

@ -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;

View File

@ -1,3 +1,5 @@
# A dialog group object. Used by the Tab, SimpleTab, Preferences dialog, ManualControlDialog etc.
package Slic3r::GUI::OptionsGroup;
use Moo;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -1,3 +1,5 @@
# Status bar at the bottom of the main screen.
package Slic3r::GUI::ProgressStatusBar;
use strict;
use warnings;

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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"

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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) = @_;

View File

@ -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);

View File

@ -1,3 +1,4 @@
# Wraps C++ enums Slic3r::PrintStep and Slic3r::PrintObjectStep
package Slic3r::Print::State;
use strict;
use warnings;

View File

@ -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);

View File

@ -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]

View File

@ -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) = @_;

View File

@ -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})

View File

@ -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

View File

@ -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 <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <math.h>
#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<std::string>::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<Model> 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;
}
}

View File

@ -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());
}

View File

@ -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;

View File

@ -7,6 +7,7 @@ plan tests => 24;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
use local::lib "$FindBin::Bin/../local-lib";
}
use Slic3r;

View File

@ -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);

View File

@ -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);

View File

@ -7,6 +7,7 @@ plan tests => 6;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
use local::lib "$FindBin::Bin/../local-lib";
}
use Slic3r;

View File

@ -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);

View File

@ -7,6 +7,7 @@ plan tests => 11;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
use local::lib "$FindBin::Bin/../local-lib";
}
use Slic3r;

View File

@ -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);

View File

@ -5,6 +5,7 @@ use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
use local::lib "$FindBin::Bin/../local-lib";
}
use Slic3r;

View File

@ -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__

View File

@ -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';

View File

@ -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);

114
t/fill.t
View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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__

View File

@ -7,6 +7,7 @@ plan tests => 42;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
use local::lib "$FindBin::Bin/../local-lib";
}
use Slic3r;

View File

@ -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);

View File

@ -8,6 +8,7 @@ plan tests => 4;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
use local::lib "$FindBin::Bin/../local-lib";
}
use Slic3r;

View File

@ -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);

View File

@ -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';

View File

@ -7,6 +7,7 @@ plan tests => 18;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
use local::lib "$FindBin::Bin/../local-lib";
}
use Slic3r;

View File

@ -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();

View File

@ -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);

View File

@ -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__

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -5,6 +5,7 @@ use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
use local::lib "$FindBin::Bin/../local-lib";
}
use Slic3r;

View File

@ -5,6 +5,7 @@ use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
use local::lib "$FindBin::Bin/../local-lib";
}
use Slic3r;

View File

@ -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);

View File

@ -5,6 +5,7 @@ use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
use local::lib "$FindBin::Bin/../local-lib";
}
use Slic3r;

View File

@ -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);

View File

@ -1 +1 @@
@perl5.22.2.exe slic3r.pl %*
@perl5.24.0.exe slic3r.pl %*

View File

@ -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);

View File

@ -8,6 +8,7 @@ use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
use local::lib "$FindBin::Bin/../local-lib";
}
use Slic3r;

Some files were not shown because too many files have changed in this diff Show More