mirror of
https://git.mirrors.martin98.com/https://github.com/slic3r/Slic3r.git
synced 2025-08-18 08:35:53 +08:00
Merge branch 'adaptive-slicing' into adaptive-slicing-spline
This commit is contained in:
commit
66cc47500b
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
@ -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.
|
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.)
|
* 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
|
* Operating system type + version
|
||||||
* Steps to reproduce the issue, including:
|
* Steps to reproduce the issue, including:
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@ MANIFEST.bak
|
|||||||
xs/MANIFEST.bak
|
xs/MANIFEST.bak
|
||||||
xs/assertlib*
|
xs/assertlib*
|
||||||
.init_bundle.ini
|
.init_bundle.ini
|
||||||
|
local-lib
|
||||||
|
@ -20,3 +20,4 @@ addons:
|
|||||||
- libboost-thread1.55-dev
|
- libboost-thread1.55-dev
|
||||||
- libboost-system1.55-dev
|
- libboost-system1.55-dev
|
||||||
- libboost-filesystem1.55-dev
|
- libboost-filesystem1.55-dev
|
||||||
|
- liblocal-lib-perl
|
||||||
|
17
Build.PL
17
Build.PL
@ -15,7 +15,6 @@ my %prereqs = qw(
|
|||||||
File::Basename 0
|
File::Basename 0
|
||||||
File::Spec 0
|
File::Spec 0
|
||||||
Getopt::Long 0
|
Getopt::Long 0
|
||||||
Math::PlanePath 53
|
|
||||||
Module::Build::WithXSpp 0.14
|
Module::Build::WithXSpp 0.14
|
||||||
Moo 1.003001
|
Moo 1.003001
|
||||||
POSIX 0
|
POSIX 0
|
||||||
@ -108,15 +107,19 @@ EOF
|
|||||||
if !$cpanm;
|
if !$cpanm;
|
||||||
my @cpanm_args = ();
|
my @cpanm_args = ();
|
||||||
push @cpanm_args, "--sudo" if $sudo;
|
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)
|
# make sure our cpanm is updated (old ones don't support the ~ syntax)
|
||||||
system $cpanm, @cpanm_args, 'App::cpanminus';
|
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);
|
my %modules = (%prereqs, %recommends);
|
||||||
foreach my $module (sort keys %modules) {
|
foreach my $module (sort keys %modules) {
|
||||||
my $version = $modules{$module};
|
my $version = $modules{$module};
|
||||||
|
15
README.md
15
README.md
@ -2,9 +2,11 @@ _Q: Oh cool, a new RepRap slicer?_
|
|||||||
|
|
||||||
A: Yes.
|
A: Yes.
|
||||||
|
|
||||||
Slic3r [](https://travis-ci.org/alexrj/Slic3r) [](https://ci.appveyor.com/project/lordofhyphens/slic3r)
|
Slic3r [](https://travis-ci.org/alexrj/Slic3r) [](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
|
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,
|
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++,
|
The core geometric algorithms and data structures are written in C++,
|
||||||
and Perl is used for high-level flow abstraction, GUI and testing.
|
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 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
|
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.
|
* 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.
|
* 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.
|
* 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.
|
* 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
|
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)
|
(default: 100,100)
|
||||||
--z-offset Additional height in mm to add to vertical coordinates
|
--z-offset Additional height in mm to add to vertical coordinates
|
||||||
(+/-, default: 0)
|
(+/-, 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)
|
default: reprap)
|
||||||
--use-relative-e-distances Enable this to get relative E values (default: no)
|
--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)
|
--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 Number of items with auto-arrange (1+, default: 1)
|
||||||
--duplicate-grid Number of items with grid arrangement (default: 1,1)
|
--duplicate-grid Number of items with grid arrangement (default: 1,1)
|
||||||
--duplicate-distance Distance in mm between copies (default: 6)
|
--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
|
--xy-size-compensation
|
||||||
Grow/shrink objects by the configured absolute distance (mm, default: 0)
|
Grow/shrink objects by the configured absolute distance (mm, default: 0)
|
||||||
|
|
||||||
|
273
doc/How_to_build_Slic3r.txt
Normal file
273
doc/How_to_build_Slic3r.txt
Normal 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.
|
@ -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;
|
package Slic3r;
|
||||||
|
|
||||||
# Copyright holder: Alessandro Ranellucci
|
# Copyright holder: Alessandro Ranellucci
|
||||||
@ -17,6 +20,7 @@ sub debugf {
|
|||||||
# load threads before Moo as required by it
|
# load threads before Moo as required by it
|
||||||
our $have_threads;
|
our $have_threads;
|
||||||
BEGIN {
|
BEGIN {
|
||||||
|
# Test, whether the perl was compiled with ithreads support and ithreads actually work.
|
||||||
use Config;
|
use Config;
|
||||||
$have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1";
|
$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;
|
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
|
### temporarily disable threads if using the broken Moo version
|
||||||
use Moo;
|
use Moo;
|
||||||
$have_threads = 0 if $Moo::VERSION == 1.003000;
|
$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;
|
if $^V == v5.16;
|
||||||
|
|
||||||
use FindBin;
|
use FindBin;
|
||||||
|
# Path to the images.
|
||||||
our $var = sub { decode_path($FindBin::Bin) . "/var/" . $_[0] };
|
our $var = sub { decode_path($FindBin::Bin) . "/var/" . $_[0] };
|
||||||
|
|
||||||
use Moo 1.003001;
|
use Moo 1.003001;
|
||||||
@ -40,13 +52,11 @@ use Slic3r::Config;
|
|||||||
use Slic3r::ExPolygon;
|
use Slic3r::ExPolygon;
|
||||||
use Slic3r::ExtrusionLoop;
|
use Slic3r::ExtrusionLoop;
|
||||||
use Slic3r::ExtrusionPath;
|
use Slic3r::ExtrusionPath;
|
||||||
use Slic3r::Fill;
|
|
||||||
use Slic3r::Flow;
|
use Slic3r::Flow;
|
||||||
use Slic3r::Format::AMF;
|
use Slic3r::Format::AMF;
|
||||||
use Slic3r::Format::OBJ;
|
use Slic3r::Format::OBJ;
|
||||||
use Slic3r::Format::STL;
|
use Slic3r::Format::STL;
|
||||||
use Slic3r::GCode::ArcFitting;
|
use Slic3r::GCode::ArcFitting;
|
||||||
use Slic3r::GCode::CoolingBuffer;
|
|
||||||
use Slic3r::GCode::MotionPlanner;
|
use Slic3r::GCode::MotionPlanner;
|
||||||
use Slic3r::GCode::PressureRegulator;
|
use Slic3r::GCode::PressureRegulator;
|
||||||
use Slic3r::GCode::Reader;
|
use Slic3r::GCode::Reader;
|
||||||
@ -72,13 +82,16 @@ use Encode::Locale 1.05;
|
|||||||
use Encode;
|
use Encode;
|
||||||
use Unicode::Normalize;
|
use Unicode::Normalize;
|
||||||
|
|
||||||
|
# Scaling between the float and integer coordinates.
|
||||||
|
# Floats are in mm.
|
||||||
use constant SCALING_FACTOR => 0.000001;
|
use constant SCALING_FACTOR => 0.000001;
|
||||||
use constant RESOLUTION => 0.0125;
|
# 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 SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR;
|
# use constant RESOLUTION => 0.0125;
|
||||||
|
# use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR;
|
||||||
use constant LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER => 0.15;
|
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 @my_threads = ();
|
||||||
my @threads : shared = ();
|
my @threads : shared = ();
|
||||||
my $pause_sema = Thread::Semaphore->new;
|
my $pause_sema = Thread::Semaphore->new;
|
||||||
@ -114,6 +127,12 @@ sub spawn_thread {
|
|||||||
return $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 {
|
sub parallelize {
|
||||||
my %params = @_;
|
my %params = @_;
|
||||||
|
|
||||||
@ -177,7 +196,7 @@ sub thread_cleanup {
|
|||||||
warn "Calling thread_cleanup() from main thread\n";
|
warn "Calling thread_cleanup() from main thread\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
# prevent destruction of shared objects
|
# prevent destruction of shared objects
|
||||||
no warnings 'redefine';
|
no warnings 'redefine';
|
||||||
*Slic3r::BridgeDetector::DESTROY = sub {};
|
*Slic3r::BridgeDetector::DESTROY = sub {};
|
||||||
@ -194,6 +213,7 @@ sub thread_cleanup {
|
|||||||
*Slic3r::ExtrusionLoop::DESTROY = sub {};
|
*Slic3r::ExtrusionLoop::DESTROY = sub {};
|
||||||
*Slic3r::ExtrusionPath::DESTROY = sub {};
|
*Slic3r::ExtrusionPath::DESTROY = sub {};
|
||||||
*Slic3r::ExtrusionPath::Collection::DESTROY = sub {};
|
*Slic3r::ExtrusionPath::Collection::DESTROY = sub {};
|
||||||
|
*Slic3r::Filler::DESTROY = sub {};
|
||||||
*Slic3r::Flow::DESTROY = sub {};
|
*Slic3r::Flow::DESTROY = sub {};
|
||||||
*Slic3r::GCode::DESTROY = sub {};
|
*Slic3r::GCode::DESTROY = sub {};
|
||||||
*Slic3r::GCode::AvoidCrossingPerimeters::DESTROY = sub {};
|
*Slic3r::GCode::AvoidCrossingPerimeters::DESTROY = sub {};
|
||||||
@ -219,6 +239,7 @@ sub thread_cleanup {
|
|||||||
*Slic3r::Print::DESTROY = sub {};
|
*Slic3r::Print::DESTROY = sub {};
|
||||||
*Slic3r::Print::Object::DESTROY = sub {};
|
*Slic3r::Print::Object::DESTROY = sub {};
|
||||||
*Slic3r::Print::Region::DESTROY = sub {};
|
*Slic3r::Print::Region::DESTROY = sub {};
|
||||||
|
*Slic3r::SLAPrint::DESTROY = sub {};
|
||||||
*Slic3r::Surface::DESTROY = sub {};
|
*Slic3r::Surface::DESTROY = sub {};
|
||||||
*Slic3r::Surface::Collection::DESTROY = sub {};
|
*Slic3r::Surface::Collection::DESTROY = sub {};
|
||||||
*Slic3r::TriangleMesh::DESTROY = sub {};
|
*Slic3r::TriangleMesh::DESTROY = sub {};
|
||||||
@ -267,6 +288,12 @@ sub resume_all_threads {
|
|||||||
$pause_sema->up;
|
$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 {
|
sub encode_path {
|
||||||
my ($path) = @_;
|
my ($path) = @_;
|
||||||
|
|
||||||
@ -276,6 +303,7 @@ sub encode_path {
|
|||||||
return $path;
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Convert a path coded by a file system locale to Unicode.
|
||||||
sub decode_path {
|
sub decode_path {
|
||||||
my ($path) = @_;
|
my ($path) = @_;
|
||||||
|
|
||||||
@ -291,6 +319,7 @@ sub decode_path {
|
|||||||
return $path;
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Open a file by converting $filename to local file system locales.
|
||||||
sub open {
|
sub open {
|
||||||
my ($fh, $mode, $filename) = @_;
|
my ($fh, $mode, $filename) = @_;
|
||||||
return CORE::open $$fh, $mode, encode_path($filename);
|
return CORE::open $$fh, $mode, encode_path($filename);
|
||||||
|
@ -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;
|
package Slic3r::Config;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
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
|
rotate scale duplicate_grid start_perimeters_at_concave_points start_perimeters_at_non_overhang
|
||||||
randomize_start seal_position bed_size print_center g0);
|
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();
|
our $Options = print_config_def();
|
||||||
|
|
||||||
# overwrite the hard-coded readonly value (this information is not available in XS)
|
# 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 {
|
sub new_from_defaults {
|
||||||
my $class = shift;
|
my $class = shift;
|
||||||
my (@opt_keys) = @_;
|
my (@opt_keys) = @_;
|
||||||
@ -39,12 +46,16 @@ sub new_from_defaults {
|
|||||||
return $self;
|
return $self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# From command line parameters
|
||||||
sub new_from_cli {
|
sub new_from_cli {
|
||||||
my $class = shift;
|
my $class = shift;
|
||||||
my %args = @_;
|
my %args = @_;
|
||||||
|
|
||||||
|
# Delete hash keys with undefined value.
|
||||||
delete $args{$_} for grep !defined $args{$_}, keys %args;
|
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)) {
|
for (qw(start end layer toolchange)) {
|
||||||
my $opt_key = "${_}_gcode";
|
my $opt_key = "${_}_gcode";
|
||||||
if ($args{$opt_key}) {
|
if ($args{$opt_key}) {
|
||||||
@ -57,7 +68,7 @@ sub new_from_cli {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
my $self = $class->new;
|
my $self = $class->new;
|
||||||
foreach my $opt_key (keys %args) {
|
foreach my $opt_key (keys %args) {
|
||||||
my $opt_def = $Options->{$opt_key};
|
my $opt_def = $Options->{$opt_key};
|
||||||
@ -83,6 +94,8 @@ sub merge {
|
|||||||
return $config;
|
return $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Load a flat ini file without a category into the underlying C++ Slic3r::DynamicConfig class,
|
||||||
|
# convert legacy configuration names.
|
||||||
sub load {
|
sub load {
|
||||||
my $class = shift;
|
my $class = shift;
|
||||||
my ($file) = @_;
|
my ($file) = @_;
|
||||||
@ -100,6 +113,8 @@ sub save {
|
|||||||
return $self->_save(Slic3r::encode_path($file));
|
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 {
|
sub load_ini_hash {
|
||||||
my $class = shift;
|
my $class = shift;
|
||||||
my ($ini_hash) = @_;
|
my ($ini_hash) = @_;
|
||||||
@ -184,6 +199,8 @@ sub _handle_legacy {
|
|||||||
return ($opt_key, $value);
|
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 {
|
sub as_ini {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
|
|
||||||
@ -308,7 +325,7 @@ sub validate {
|
|||||||
my $max_nozzle_diameter = max(@{ $self->nozzle_diameter });
|
my $max_nozzle_diameter = max(@{ $self->nozzle_diameter });
|
||||||
die "Invalid extrusion width (too large)\n"
|
die "Invalid extrusion width (too large)\n"
|
||||||
if defined first { $_ > 10 * $max_nozzle_diameter }
|
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);
|
qw(perimeter infill solid_infill top_infill support_material first_layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,6 +362,7 @@ sub validate {
|
|||||||
|
|
||||||
# CLASS METHODS:
|
# CLASS METHODS:
|
||||||
|
|
||||||
|
# Write a "Windows" style ini file with categories enclosed in squre brackets.
|
||||||
sub write_ini {
|
sub write_ini {
|
||||||
my $class = shift;
|
my $class = shift;
|
||||||
my ($file, $ini) = @_;
|
my ($file, $ini) = @_;
|
||||||
@ -363,6 +381,10 @@ sub write_ini {
|
|||||||
close $fh;
|
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 {
|
sub read_ini {
|
||||||
my $class = shift;
|
my $class = shift;
|
||||||
my ($file) = @_;
|
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::PrintObject::new { Slic3r::Config::Static::new_PrintObjectConfig }
|
||||||
sub Slic3r::Config::PrintRegion::new { Slic3r::Config::Static::new_PrintRegionConfig }
|
sub Slic3r::Config::PrintRegion::new { Slic3r::Config::Static::new_PrintRegionConfig }
|
||||||
sub Slic3r::Config::Full::new { Slic3r::Config::Static::new_FullPrintConfig }
|
sub Slic3r::Config::Full::new { Slic3r::Config::Static::new_FullPrintConfig }
|
||||||
|
sub Slic3r::Config::SLAPrint::new { Slic3r::Config::Static::new_SLAPrintConfig }
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -21,7 +21,7 @@ sub read_file {
|
|||||||
|
|
||||||
my $mesh = Slic3r::TriangleMesh->new;
|
my $mesh = Slic3r::TriangleMesh->new;
|
||||||
$mesh->ReadFromPerl($vertices, $facets);
|
$mesh->ReadFromPerl($vertices, $facets);
|
||||||
$mesh->repair;
|
$mesh->check_topology;
|
||||||
|
|
||||||
my $model = Slic3r::Model->new;
|
my $model = Slic3r::Model->new;
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ sub read_file {
|
|||||||
|
|
||||||
my $mesh = Slic3r::TriangleMesh->new;
|
my $mesh = Slic3r::TriangleMesh->new;
|
||||||
$mesh->ReadSTLFile($path);
|
$mesh->ReadSTLFile($path);
|
||||||
$mesh->repair;
|
$mesh->check_topology;
|
||||||
|
|
||||||
die "This STL file couldn't be read because it's empty.\n"
|
die "This STL file couldn't be read because it's empty.\n"
|
||||||
if $mesh->facets_count == 0;
|
if $mesh->facets_count == 0;
|
||||||
|
@ -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;
|
|
@ -1,3 +1,5 @@
|
|||||||
|
# A pure perl (no C++ implementation) G-code filter, to control the pressure inside the nozzle.
|
||||||
|
|
||||||
package Slic3r::GCode::PressureRegulator;
|
package Slic3r::GCode::PressureRegulator;
|
||||||
use Moo;
|
use Moo;
|
||||||
|
|
||||||
@ -36,8 +38,8 @@ sub process {
|
|||||||
} elsif ($info->{extruding} && $info->{dist_XY} > 0) {
|
} elsif ($info->{extruding} && $info->{dist_XY} > 0) {
|
||||||
# This is a print move.
|
# This is a print move.
|
||||||
my $F = $args->{F} // $reader->F;
|
my $F = $args->{F} // $reader->F;
|
||||||
if ($F != $self->_last_print_F) {
|
if ($F != $self->_last_print_F || ($F == $self->_last_print_F && $self->_advance == 0)) {
|
||||||
# We are setting a (potentially) new speed, so we calculate the new advance amount.
|
# 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)
|
# First calculate relative flow rate (mm of filament over mm of travel)
|
||||||
my $rel_flow_rate = $info->{dist_E} / $info->{dist_XY};
|
my $rel_flow_rate = $info->{dist_E} / $info->{dist_XY};
|
||||||
@ -54,6 +56,7 @@ sub process {
|
|||||||
$self->_extrusion_axis, $new_E, $self->_unretract_speed;
|
$self->_extrusion_axis, $new_E, $self->_unretract_speed;
|
||||||
$new_gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $reader->E
|
$new_gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $reader->E
|
||||||
if !$self->config->use_relative_e_distances;
|
if !$self->config->use_relative_e_distances;
|
||||||
|
$new_gcode .= sprintf "G1 F%.3f ; restore F\n", $F;
|
||||||
$self->_advance($new_advance);
|
$self->_advance($new_advance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +64,7 @@ sub process {
|
|||||||
}
|
}
|
||||||
} elsif (($info->{retracting} || $cmd eq 'G10') && $self->_advance != 0) {
|
} elsif (($info->{retracting} || $cmd eq 'G10') && $self->_advance != 0) {
|
||||||
# We need to bring pressure to zero when retracting.
|
# 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";
|
$new_gcode .= "$info->{raw}\n";
|
||||||
@ -75,13 +78,14 @@ sub process {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub _discharge {
|
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 $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",
|
my $gcode = sprintf "G1 %s%.5f F%.3f ; pressure discharge\n",
|
||||||
$self->_extrusion_axis, $new_E, $F // $self->_unretract_speed;
|
$self->_extrusion_axis, $new_E, $F // $self->_unretract_speed;
|
||||||
$gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $self->reader->E
|
$gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $self->reader->E
|
||||||
if !$self->config->use_relative_e_distances;
|
if !$self->config->use_relative_e_distances;
|
||||||
|
$gcode .= sprintf "G1 F%.3f ; restore F\n", $oldSpeed;
|
||||||
$self->_advance(0);
|
$self->_advance(0);
|
||||||
|
|
||||||
return $gcode;
|
return $gcode;
|
||||||
|
@ -22,7 +22,7 @@ sub apply_print_config {
|
|||||||
sub clone {
|
sub clone {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
return (ref $self)->new(
|
return (ref $self)->new(
|
||||||
map { $_ => $self->$_ } (@AXES, 'F', '_extrusion_axis'),
|
map { $_ => $self->$_ } (@AXES, 'F', '_extrusion_axis', 'config'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ use Slic3r::GUI::Plater::ObjectPartsPanel;
|
|||||||
use Slic3r::GUI::Plater::ObjectCutDialog;
|
use Slic3r::GUI::Plater::ObjectCutDialog;
|
||||||
use Slic3r::GUI::Plater::ObjectLayersDialog;
|
use Slic3r::GUI::Plater::ObjectLayersDialog;
|
||||||
use Slic3r::GUI::Plater::ObjectSettingsDialog;
|
use Slic3r::GUI::Plater::ObjectSettingsDialog;
|
||||||
|
use Slic3r::GUI::Plater::LambdaObjectDialog;
|
||||||
use Slic3r::GUI::Plater::OverrideSettingsPanel;
|
use Slic3r::GUI::Plater::OverrideSettingsPanel;
|
||||||
use Slic3r::GUI::Plater::SplineControl;
|
use Slic3r::GUI::Plater::SplineControl;
|
||||||
use Slic3r::GUI::Preferences;
|
use Slic3r::GUI::Preferences;
|
||||||
@ -33,6 +34,7 @@ use Slic3r::GUI::Projector;
|
|||||||
use Slic3r::GUI::OptionsGroup;
|
use Slic3r::GUI::OptionsGroup;
|
||||||
use Slic3r::GUI::OptionsGroup::Field;
|
use Slic3r::GUI::OptionsGroup::Field;
|
||||||
use Slic3r::GUI::SimpleTab;
|
use Slic3r::GUI::SimpleTab;
|
||||||
|
use Slic3r::GUI::SLAPrintOptions;
|
||||||
use Slic3r::GUI::Tab;
|
use Slic3r::GUI::Tab;
|
||||||
|
|
||||||
our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1";
|
our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1";
|
||||||
@ -67,7 +69,7 @@ our $Settings = {
|
|||||||
mode => 'simple',
|
mode => 'simple',
|
||||||
version_check => 1,
|
version_check => 1,
|
||||||
autocenter => 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.
|
# 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.
|
# By default, Prusa has the controller hidden.
|
||||||
no_controller => 1,
|
no_controller => 1,
|
||||||
@ -97,6 +99,9 @@ sub OnInit {
|
|||||||
$self->{notifier} = Slic3r::GUI::Notifier->new;
|
$self->{notifier} = Slic3r::GUI::Notifier->new;
|
||||||
|
|
||||||
# locate or create data directory
|
# 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);
|
$datadir ||= Slic3r::decode_path(Wx::StandardPaths::Get->GetUserDataDir);
|
||||||
my $enc_datadir = Slic3r::encode_path($datadir);
|
my $enc_datadir = Slic3r::encode_path($datadir);
|
||||||
Slic3r::debugf "Data directory: %s\n", $datadir;
|
Slic3r::debugf "Data directory: %s\n", $datadir;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# Bed shape dialog
|
||||||
|
|
||||||
package Slic3r::GUI::2DBed;
|
package Slic3r::GUI::2DBed;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package Slic3r::GUI::3DScene::Base;
|
package Slic3r::GUI::3DScene::Base;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS);
|
use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS);
|
||||||
# must load OpenGL *before* Wx::GLCanvas
|
# must load OpenGL *before* Wx::GLCanvas
|
||||||
|
|
||||||
use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants);
|
use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants);
|
||||||
use base qw(Wx::GLCanvas Class::Accessor);
|
use base qw(Wx::GLCanvas Class::Accessor);
|
||||||
use Math::Trig qw(asin);
|
use Math::Trig qw(asin);
|
||||||
@ -23,6 +23,7 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init
|
|||||||
on_move
|
on_move
|
||||||
volumes
|
volumes
|
||||||
_sphi _stheta
|
_sphi _stheta
|
||||||
|
cutting_plane_axis
|
||||||
cutting_plane_z
|
cutting_plane_z
|
||||||
cut_lines_vertices
|
cut_lines_vertices
|
||||||
bed_shape
|
bed_shape
|
||||||
@ -43,9 +44,26 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init
|
|||||||
use constant TRACKBALLSIZE => 0.8;
|
use constant TRACKBALLSIZE => 0.8;
|
||||||
use constant TURNTABLE_MODE => 1;
|
use constant TURNTABLE_MODE => 1;
|
||||||
use constant GROUND_Z => -0.02;
|
use constant GROUND_Z => -0.02;
|
||||||
use constant DEFAULT_COLOR => [1,1,0];
|
use constant SELECTED_COLOR => [0,1,0];
|
||||||
use constant SELECTED_COLOR => [0,1,0,1];
|
use constant HOVER_COLOR => [0.4,0.9,0];
|
||||||
use constant HOVER_COLOR => [0.4,0.9,0,1];
|
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
|
# make OpenGL::Array thread-safe
|
||||||
{
|
{
|
||||||
@ -56,6 +74,7 @@ use constant HOVER_COLOR => [0.4,0.9,0,1];
|
|||||||
sub new {
|
sub new {
|
||||||
my ($class, $parent) = @_;
|
my ($class, $parent) = @_;
|
||||||
|
|
||||||
|
|
||||||
# We can only enable multi sample anti aliasing wih wxWidgets 3.0.3 and with a hacked Wx::GLCanvas,
|
# 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.
|
# which exports some new WX_GL_XXX constants, namely WX_GL_SAMPLE_BUFFERS and WX_GL_SAMPLES.
|
||||||
my $can_multisample =
|
my $can_multisample =
|
||||||
@ -74,6 +93,7 @@ sub new {
|
|||||||
# we request a depth buffer explicitely because it looks like it's not created by
|
# we request a depth buffer explicitely because it looks like it's not created by
|
||||||
# default on Linux, causing transparency issues
|
# default on Linux, causing transparency issues
|
||||||
my $self = $class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "", $attrib);
|
my $self = $class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "", $attrib);
|
||||||
|
|
||||||
if (Wx::wxVERSION >= 3.000003) {
|
if (Wx::wxVERSION >= 3.000003) {
|
||||||
# Wx 3.0.3 contains an ugly hack to support some advanced OpenGL attributes through the attribute list.
|
# 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.
|
# 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;
|
$self->Refresh;
|
||||||
});
|
});
|
||||||
EVT_MOUSE_EVENTS($self, \&mouse_event);
|
EVT_MOUSE_EVENTS($self, \&mouse_event);
|
||||||
|
|
||||||
|
|
||||||
return $self;
|
return $self;
|
||||||
}
|
}
|
||||||
@ -147,7 +168,15 @@ sub mouse_event {
|
|||||||
} elsif ($e->LeftDClick) {
|
} elsif ($e->LeftDClick) {
|
||||||
$self->on_double_click->()
|
$self->on_double_click->()
|
||||||
if $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
|
# If user pressed left or right button we first check whether this happened
|
||||||
# on a volume or not.
|
# on a volume or not.
|
||||||
my $volume_idx = $self->_hover_volume_idx // -1;
|
my $volume_idx = $self->_hover_volume_idx // -1;
|
||||||
@ -209,14 +238,23 @@ sub mouse_event {
|
|||||||
$self->_dragged(1);
|
$self->_dragged(1);
|
||||||
$self->Refresh;
|
$self->Refresh;
|
||||||
} elsif ($e->Dragging) {
|
} 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 dragging over blank area with left button, rotate
|
||||||
if (defined $self->_drag_start_pos) {
|
if (defined $self->_drag_start_pos) {
|
||||||
my $orig = $self->_drag_start_pos;
|
my $orig = $self->_drag_start_pos;
|
||||||
if (TURNTABLE_MODE) {
|
if (TURNTABLE_MODE) {
|
||||||
$self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE);
|
$self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE);
|
||||||
$self->_stheta($self->_stheta - ($pos->y - $orig->y) * 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;
|
$self->_stheta(0) if $self->_stheta < 0;
|
||||||
} else {
|
} else {
|
||||||
my $size = $self->GetClientSize;
|
my $size = $self->GetClientSize;
|
||||||
@ -266,7 +304,7 @@ sub mouse_event {
|
|||||||
$self->_dragged(undef);
|
$self->_dragged(undef);
|
||||||
} elsif ($e->Moving) {
|
} elsif ($e->Moving) {
|
||||||
$self->_mouse_pos($pos);
|
$self->_mouse_pos($pos);
|
||||||
$self->Refresh;
|
$self->Refresh if $self->enable_picking;
|
||||||
} else {
|
} else {
|
||||||
$e->Skip();
|
$e->Skip();
|
||||||
}
|
}
|
||||||
@ -290,19 +328,63 @@ sub set_viewport_from_scene {
|
|||||||
$self->_dirty(1);
|
$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 {
|
sub zoom_to_bounding_box {
|
||||||
my ($self, $bb) = @_;
|
my ($self, $bb) = @_;
|
||||||
|
|
||||||
# calculate the zoom factor needed to adjust viewport to
|
# calculate the zoom factor needed to adjust viewport to
|
||||||
# bounding box
|
# bounding box
|
||||||
my $max_size = max(@{$bb->size}) * 2;
|
my $max_size = max(@{$bb->size}) * 1.05;
|
||||||
my $min_viewport_size = min($self->GetSizeWH);
|
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
|
# center view around bounding box center
|
||||||
$self->_camera_target($bb->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 {
|
sub zoom_to_bed {
|
||||||
@ -423,19 +505,35 @@ sub select_volume {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub SetCuttingPlane {
|
sub SetCuttingPlane {
|
||||||
my ($self, $z, $expolygons) = @_;
|
my ($self, $axis, $z, $expolygons) = @_;
|
||||||
|
|
||||||
|
$self->cutting_plane_axis($axis);
|
||||||
$self->cutting_plane_z($z);
|
$self->cutting_plane_z($z);
|
||||||
|
|
||||||
# grow slices in order to display them better
|
# grow slices in order to display them better
|
||||||
$expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1);
|
$expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1);
|
||||||
|
|
||||||
|
my $bb = $self->volumes_bounding_box;
|
||||||
|
|
||||||
my @verts = ();
|
my @verts = ();
|
||||||
foreach my $line (map @{$_->lines}, map @$_, @$expolygons) {
|
foreach my $line (map @{$_->lines}, map @$_, @$expolygons) {
|
||||||
push @verts, (
|
if ($axis == X) {
|
||||||
unscale($line->a->x), unscale($line->a->y), $z, #))
|
push @verts, (
|
||||||
unscale($line->b->x), unscale($line->b->y), $z, #))
|
$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));
|
$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);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
glEnableClientState(GL_VERTEX_ARRAY);
|
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);
|
glColor4f(0.8, 0.6, 0.5, 0.4);
|
||||||
glNormal3d(0,0,1);
|
glNormal3d(0,0,1);
|
||||||
glVertexPointer_p(3, $self->bed_triangles);
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
|
glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
|
||||||
glDisableClientState(GL_VERTEX_ARRAY);
|
glDisableClientState(GL_VERTEX_ARRAY);
|
||||||
|
|
||||||
@ -768,13 +876,29 @@ sub Render {
|
|||||||
|
|
||||||
# draw grid
|
# draw grid
|
||||||
glLineWidth(3);
|
glLineWidth(3);
|
||||||
glColor4f(0.2, 0.2, 0.2, 0.4);
|
|
||||||
glEnableClientState(GL_VERTEX_ARRAY);
|
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);
|
glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
|
||||||
glDisableClientState(GL_VERTEX_ARRAY);
|
glDisableClientState(GL_VERTEX_ARRAY);
|
||||||
|
|
||||||
glDisable(GL_BLEND);
|
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;
|
my $volumes_bb = $self->volumes_bounding_box;
|
||||||
@ -785,8 +909,8 @@ sub Render {
|
|||||||
glDisable(GL_DEPTH_TEST);
|
glDisable(GL_DEPTH_TEST);
|
||||||
my $origin = $self->origin;
|
my $origin = $self->origin;
|
||||||
my $axis_len = max(
|
my $axis_len = max(
|
||||||
0.3 * max(@{ $self->bed_bounding_box->size }),
|
max(@{ $self->bed_bounding_box->size }),
|
||||||
2 * max(@{ $volumes_bb->size }),
|
1.2 * max(@{ $volumes_bb->size }),
|
||||||
);
|
);
|
||||||
glLineWidth(2);
|
glLineWidth(2);
|
||||||
glBegin(GL_LINES);
|
glBegin(GL_LINES);
|
||||||
@ -824,18 +948,68 @@ sub Render {
|
|||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
glBegin(GL_QUADS);
|
glBegin(GL_QUADS);
|
||||||
glColor4f(0.8, 0.8, 0.8, 0.5);
|
glColor4f(0.8, 0.8, 0.8, 0.5);
|
||||||
glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z);
|
if ($self->cutting_plane_axis == X) {
|
||||||
glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z);
|
glVertex3f($bb->x_min+$plane_z, $bb->y_min-20, $bb->z_min-20);
|
||||||
glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z);
|
glVertex3f($bb->x_min+$plane_z, $bb->y_max+20, $bb->z_min-20);
|
||||||
glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z);
|
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();
|
glEnd();
|
||||||
glEnable(GL_CULL_FACE);
|
glEnable(GL_CULL_FACE);
|
||||||
glDisable(GL_BLEND);
|
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();
|
glFlush();
|
||||||
|
|
||||||
$self->SwapBuffers();
|
$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 {
|
sub draw_volumes {
|
||||||
@ -858,9 +1032,9 @@ sub draw_volumes {
|
|||||||
my $b = ($volume_idx & 0x00FF0000) >> 16;
|
my $b = ($volume_idx & 0x00FF0000) >> 16;
|
||||||
glColor4f($r/255.0, $g/255.0, $b/255.0, 1);
|
glColor4f($r/255.0, $g/255.0, $b/255.0, 1);
|
||||||
} elsif ($volume->selected) {
|
} elsif ($volume->selected) {
|
||||||
glColor4f(@{ &SELECTED_COLOR });
|
glColor4f(@{ &SELECTED_COLOR }, $volume->color->[3]);
|
||||||
} elsif ($volume->hover) {
|
} elsif ($volume->hover) {
|
||||||
glColor4f(@{ &HOVER_COLOR });
|
glColor4f(@{ &HOVER_COLOR }, $volume->color->[3]);
|
||||||
} else {
|
} else {
|
||||||
glColor4f(@{ $volume->color });
|
glColor4f(@{ $volume->color });
|
||||||
}
|
}
|
||||||
@ -914,10 +1088,26 @@ sub draw_volumes {
|
|||||||
glDisable(GL_BLEND);
|
glDisable(GL_BLEND);
|
||||||
|
|
||||||
if (defined $self->cutting_plane_z) {
|
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);
|
glLineWidth(2);
|
||||||
glColor3f(0, 0, 0);
|
glColor3f(0, 0, 0);
|
||||||
glVertexPointer_p(3, $self->cut_lines_vertices);
|
|
||||||
glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3);
|
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);
|
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_SKIRT);
|
||||||
return if !$print->step_done(STEP_BRIM);
|
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 $qverts = Slic3r::GUI::_3DScene::GLVertexArray->new;
|
||||||
my $tverts = 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 }
|
my @layers = sort { $a->print_z <=> $b->print_z }
|
||||||
@{$object->layers}, @{$object->support_layers};
|
@{$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) {
|
foreach my $layer (@layers) {
|
||||||
my $top_z = $layer->print_z;
|
my $top_z = $layer->print_z;
|
||||||
|
|
||||||
@ -1171,9 +1378,13 @@ sub load_print_object_toolpaths {
|
|||||||
$perim_offsets{$top_z} = [
|
$perim_offsets{$top_z} = [
|
||||||
$perim_qverts->size, $perim_tverts->size,
|
$perim_qverts->size, $perim_tverts->size,
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
if (!exists $infill_offsets{$top_z}) {
|
||||||
$infill_offsets{$top_z} = [
|
$infill_offsets{$top_z} = [
|
||||||
$infill_qverts->size, $infill_tverts->size,
|
$infill_qverts->size, $infill_tverts->size,
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
if (!exists $support_offsets{$top_z}) {
|
||||||
$support_offsets{$top_z} = [
|
$support_offsets{$top_z} = [
|
||||||
$support_qverts->size, $support_tverts->size,
|
$support_qverts->size, $support_tverts->size,
|
||||||
];
|
];
|
||||||
@ -1200,40 +1411,79 @@ sub load_print_object_toolpaths {
|
|||||||
$support_qverts, $support_tverts);
|
$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;
|
if ($infill_qverts->size() > 0 || $infill_tverts->size() > 0) {
|
||||||
my $bb = Slic3r::Geometry::BoundingBoxf3->new;
|
push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new(
|
||||||
foreach my $copy (@{ $object->_shifted_copies }) {
|
bounding_box => $bb,
|
||||||
my $cbb = $obb->clone;
|
color => COLORS->[1],
|
||||||
$cbb->translate(@$copy);
|
qverts => $infill_qverts,
|
||||||
$bb->merge_point(Slic3r::Pointf3->new_unscale(@{$cbb->min_point}, 0));
|
tverts => $infill_tverts,
|
||||||
$bb->merge_point(Slic3r::Pointf3->new_unscale(@{$cbb->max_point}, $object->size->z));
|
offsets => { %infill_offsets },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new(
|
if ($support_qverts->size() > 0 || $support_tverts->size() > 0) {
|
||||||
bounding_box => $bb,
|
push @{$self->volumes}, Slic3r::GUI::3DScene::Volume->new(
|
||||||
color => COLORS->[0],
|
bounding_box => $bb,
|
||||||
qverts => $perim_qverts,
|
color => COLORS->[2],
|
||||||
tverts => $perim_tverts,
|
qverts => $support_qverts,
|
||||||
offsets => { %perim_offsets },
|
tverts => $support_tverts,
|
||||||
);
|
offsets => { %support_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 },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub set_toolpaths_range {
|
sub set_toolpaths_range {
|
||||||
@ -1272,6 +1522,8 @@ sub _expolygons_to_verts {
|
|||||||
gluDeleteTess($tess);
|
gluDeleteTess($tess);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Fill in the $qverts and $tverts with quads and triangles
|
||||||
|
# for the extrusion $entity.
|
||||||
sub _extrusionentity_to_verts {
|
sub _extrusionentity_to_verts {
|
||||||
my ($self, $entity, $top_z, $copy, $qverts, $tverts) = @_;
|
my ($self, $entity, $top_z, $copy, $qverts, $tverts) = @_;
|
||||||
|
|
||||||
@ -1303,8 +1555,18 @@ sub _extrusionentity_to_verts {
|
|||||||
push @$heights, map $path->height, 0..$#$path_lines;
|
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,
|
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 {
|
sub object_idx {
|
||||||
|
@ -51,7 +51,7 @@ sub new {
|
|||||||
'<a href="http://slic3r.org/">Slic3r</a> is licensed under the ' .
|
'<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>.' .
|
'<a href="http://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License, version 3</a>.' .
|
||||||
'<br /><br /><br />' .
|
'<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 />' .
|
'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. ' .
|
'Slic3r logo designed by Corey Daniels, <a href="http://www.famfamfam.com/lab/icons/silk/">Silk Icon Set</a> designed by Mark James. ' .
|
||||||
'</font>' .
|
'</font>' .
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
# The bed shape dialog.
|
||||||
|
# The dialog opens from Print Settins tab -> Bed Shape: Set...
|
||||||
|
|
||||||
package Slic3r::GUI::BedShapeDialog;
|
package Slic3r::GUI::BedShapeDialog;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
@ -209,6 +212,7 @@ sub _update_shape {
|
|||||||
my $rect_origin = $self->{optgroups}[SHAPE_RECTANGULAR]->get_value('rect_origin');
|
my $rect_origin = $self->{optgroups}[SHAPE_RECTANGULAR]->get_value('rect_origin');
|
||||||
my ($x, $y) = @$rect_size;
|
my ($x, $y) = @$rect_size;
|
||||||
return if !looks_like_number($x) || !looks_like_number($y); # empty strings or '-' or other things
|
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 ($x0, $y0) = (0,0);
|
||||||
my ($x1, $y1) = ($x,$y);
|
my ($x1, $y1) = ($x,$y);
|
||||||
{
|
{
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# A tiny dialog to select an OctoPrint device to print to.
|
||||||
|
|
||||||
package Slic3r::GUI::BonjourBrowser;
|
package Slic3r::GUI::BonjourBrowser;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
@ -9,14 +11,10 @@ use base 'Wx::Dialog';
|
|||||||
|
|
||||||
sub new {
|
sub new {
|
||||||
my $class = shift;
|
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);
|
my $self = $class->SUPER::new($parent, -1, "Device Browser", wxDefaultPosition, [350,700], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||||
|
|
||||||
# look for devices
|
$self->{devices} = $devices;
|
||||||
eval "use Net::Bonjour; 1";
|
|
||||||
my $res = Net::Bonjour->new('http');
|
|
||||||
$res->discover;
|
|
||||||
$self->{devices} = [ $res->entries ];
|
|
||||||
|
|
||||||
# label
|
# label
|
||||||
my $text = Wx::StaticText->new($self, -1, "Choose an OctoPrint device in your network:", wxDefaultPosition, wxDefaultSize);
|
my $text = Wx::StaticText->new($self, -1, "Choose an OctoPrint device in your network:", wxDefaultPosition, wxDefaultSize);
|
||||||
|
@ -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;
|
package Slic3r::GUI::ConfigWizard;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
@ -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;
|
package Slic3r::GUI::Controller;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# A printer "Controller" -> "ManualControlDialog" subtab, opened per 3D printer connected?
|
||||||
|
|
||||||
package Slic3r::GUI::Controller::ManualControlDialog;
|
package Slic3r::GUI::Controller::ManualControlDialog;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# The main frame, the parent of all.
|
||||||
|
|
||||||
package Slic3r::GUI::MainFrame;
|
package Slic3r::GUI::MainFrame;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
@ -19,7 +21,11 @@ sub new {
|
|||||||
my ($class, %params) = @_;
|
my ($class, %params) = @_;
|
||||||
|
|
||||||
my $self = $class->SUPER::new(undef, -1, 'Slic3r', wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE);
|
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
|
# store input params
|
||||||
$self->{mode} = $params{mode};
|
$self->{mode} = $params{mode};
|
||||||
@ -33,6 +39,11 @@ sub new {
|
|||||||
$self->_init_tabpanel;
|
$self->_init_tabpanel;
|
||||||
$self->_init_menubar;
|
$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
|
# initialize status bar
|
||||||
$self->{statusbar} = Slic3r::GUI::ProgressStatusBar->new($self, -1);
|
$self->{statusbar} = Slic3r::GUI::ProgressStatusBar->new($self, -1);
|
||||||
$self->{statusbar}->SetStatusText("Version $Slic3r::VERSION - Remember to check for updates at http://slic3r.org/");
|
$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}) {
|
if (!$self->{no_plater}) {
|
||||||
$panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), "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} = {};
|
$self->{options_tabs} = {};
|
||||||
|
|
||||||
@ -169,6 +180,13 @@ sub _init_menubar {
|
|||||||
# File menu
|
# File menu
|
||||||
my $fileMenu = Wx::Menu->new;
|
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->_append_menu_item($fileMenu, "&Load Config…\tCtrl+L", 'Load exported configuration file', sub {
|
||||||
$self->load_config_file;
|
$self->load_config_file;
|
||||||
}, undef, 'plugin_add.png');
|
}, undef, 'plugin_add.png');
|
||||||
@ -235,11 +253,9 @@ sub _init_menubar {
|
|||||||
$plater->export_amf;
|
$plater->export_amf;
|
||||||
}, undef, 'brick_go.png');
|
}, undef, 'brick_go.png');
|
||||||
$self->_append_menu_item($self->{plater_menu}, "Open DLP Projector…\tCtrl+L", 'Open projector window for DLP printing', sub {
|
$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);
|
$plater->pause_background_process;
|
||||||
|
Slic3r::GUI::SLAPrintOptions->new($self)->ShowModal;
|
||||||
# this double invocation is needed for properly hiding the MainFrame
|
$plater->resume_background_process;
|
||||||
$projector->Show;
|
|
||||||
$projector->ShowModal;
|
|
||||||
}, undef, 'film.png');
|
}, undef, 'film.png');
|
||||||
|
|
||||||
$self->{object_menu} = $self->{plater}->object_menu;
|
$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->_append_menu_item($windowMenu, "Select &Plater Tab\tCtrl+1", 'Show the plater', sub {
|
||||||
$self->select_tab(0);
|
$self->select_tab(0);
|
||||||
}, undef, 'application_view_tile.png');
|
}, undef, 'application_view_tile.png');
|
||||||
if (!$self->{no_controller}) {
|
$tab_offset += 1;
|
||||||
$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;
|
|
||||||
}
|
}
|
||||||
|
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->_append_menu_item($windowMenu, "Select P&rint Settings Tab\tCtrl+2", 'Show the print settings', sub {
|
||||||
$self->select_tab($tab_offset+0);
|
$self->select_tab($tab_offset+0);
|
||||||
}, undef, 'cog.png');
|
}, undef, 'cog.png');
|
||||||
@ -272,6 +292,18 @@ sub _init_menubar {
|
|||||||
$self->select_tab($tab_offset+2);
|
$self->select_tab($tab_offset+2);
|
||||||
}, undef, 'printer_empty.png');
|
}, 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
|
# Help menu
|
||||||
my $helpMenu = Wx::Menu->new;
|
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->{plater_menu}, "&Plater") if $self->{plater_menu};
|
||||||
$menubar->Append($self->{object_menu}, "&Object") if $self->{object_menu};
|
$menubar->Append($self->{object_menu}, "&Object") if $self->{object_menu};
|
||||||
$menubar->Append($windowMenu, "&Window");
|
$menubar->Append($windowMenu, "&Window");
|
||||||
|
$menubar->Append($self->{viewMenu}, "&View") if $self->{viewMenu};
|
||||||
$menubar->Append($helpMenu, "&Help");
|
$menubar->Append($helpMenu, "&Help");
|
||||||
$self->SetMenuBar($menubar);
|
$self->SetMenuBar($menubar);
|
||||||
}
|
}
|
||||||
@ -393,7 +426,7 @@ sub quick_slice {
|
|||||||
if ($params{reslice}) {
|
if ($params{reslice}) {
|
||||||
$output_file = $qs_last_output_file if defined $qs_last_output_file;
|
$output_file = $qs_last_output_file if defined $qs_last_output_file;
|
||||||
} elsif ($params{save_as}) {
|
} 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};
|
$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:',
|
my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:',
|
||||||
wxTheApp->output_path(dirname($output_file)),
|
wxTheApp->output_path(dirname($output_file)),
|
||||||
@ -773,6 +806,14 @@ sub select_tab {
|
|||||||
$self->{tabpanel}->SetSelection($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 {
|
sub _append_menu_item {
|
||||||
my ($self, $menu, $string, $description, $cb, $id, $icon) = @_;
|
my ($self, $menu, $string, $description, $cb, $id, $icon) = @_;
|
||||||
|
|
||||||
|
@ -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;
|
package Slic3r::GUI::Notifier;
|
||||||
use Moo;
|
use Moo;
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# A dialog group object. Used by the Tab, SimpleTab, Preferences dialog, ManualControlDialog etc.
|
||||||
|
|
||||||
package Slic3r::GUI::OptionsGroup;
|
package Slic3r::GUI::OptionsGroup;
|
||||||
use Moo;
|
use Moo;
|
||||||
|
|
||||||
|
@ -592,4 +592,10 @@ sub disable {
|
|||||||
$self->textctrl->SetEditable(0);
|
$self->textctrl->SetEditable(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub set_range {
|
||||||
|
my ($self, $min, $max) = @_;
|
||||||
|
|
||||||
|
$self->slider->SetRange($min * $self->scale, $max * $self->scale);
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# The "Plater" tab. It contains the "3D", "2D", "Preview" and "Layers" subtabs.
|
||||||
|
|
||||||
package Slic3r::GUI::Plater;
|
package Slic3r::GUI::Plater;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
@ -5,7 +7,8 @@ use utf8;
|
|||||||
|
|
||||||
use File::Basename qw(basename dirname);
|
use File::Basename qw(basename dirname);
|
||||||
use List::Util qw(sum first max);
|
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 threads::shared qw(shared_clone);
|
||||||
use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc
|
use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc
|
||||||
:panel :sizer :toolbar :window wxTheApp :notebook :combobox);
|
:panel :sizer :toolbar :window wxTheApp :notebook :combobox);
|
||||||
@ -52,6 +55,8 @@ sub new {
|
|||||||
));
|
));
|
||||||
$self->{model} = Slic3r::Model->new;
|
$self->{model} = Slic3r::Model->new;
|
||||||
$self->{print} = Slic3r::Print->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->{objects} = [];
|
||||||
|
|
||||||
$self->{print}->set_status_cb(sub {
|
$self->{print}->set_status_cb(sub {
|
||||||
@ -111,6 +116,7 @@ sub new {
|
|||||||
$self->{canvas}->on_instances_moved($on_instances_moved);
|
$self->{canvas}->on_instances_moved($on_instances_moved);
|
||||||
|
|
||||||
# Initialize 3D toolpaths preview
|
# Initialize 3D toolpaths preview
|
||||||
|
$self->{preview3D_page_idx} = -1;
|
||||||
if ($Slic3r::GUI::have_OpenGL) {
|
if ($Slic3r::GUI::have_OpenGL) {
|
||||||
$self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print});
|
$self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print});
|
||||||
$self->{preview3D}->canvas->on_viewport_changed(sub {
|
$self->{preview3D}->canvas->on_viewport_changed(sub {
|
||||||
@ -121,15 +127,30 @@ sub new {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Initialize toolpaths preview
|
# Initialize toolpaths preview
|
||||||
|
$self->{toolpaths2D_page_idx} = -1;
|
||||||
if ($Slic3r::GUI::have_OpenGL) {
|
if ($Slic3r::GUI::have_OpenGL) {
|
||||||
$self->{toolpaths2D} = Slic3r::GUI::Plater::2DToolpaths->new($self->{preview_notebook}, $self->{print});
|
$self->{toolpaths2D} = Slic3r::GUI::Plater::2DToolpaths->new($self->{preview_notebook}, $self->{print});
|
||||||
$self->{preview_notebook}->AddPage($self->{toolpaths2D}, 'Layers');
|
$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 {
|
EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{preview_notebook}, sub {
|
||||||
if ($self->{preview_notebook}->GetSelection == $self->{preview3D_page_idx}) {
|
wxTheApp->CallAfter(sub {
|
||||||
$self->{preview3D}->load_print;
|
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
|
# toolbar for object manipulation
|
||||||
@ -236,7 +257,33 @@ sub new {
|
|||||||
$self->{print_file} = $self->export_gcode(Wx::StandardPaths::Get->GetTempDir());
|
$self->{print_file} = $self->export_gcode(Wx::StandardPaths::Get->GetTempDir());
|
||||||
});
|
});
|
||||||
EVT_BUTTON($self, $self->{btn_send_gcode}, sub {
|
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);
|
EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl);
|
||||||
|
|
||||||
@ -513,6 +560,36 @@ sub add {
|
|||||||
$self->load_file($_) for @input_files;
|
$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 {
|
sub load_file {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($input_file) = @_;
|
my ($input_file) = @_;
|
||||||
@ -528,6 +605,7 @@ sub load_file {
|
|||||||
my $model = eval { Slic3r::Model->read_from_file($input_file) };
|
my $model = eval { Slic3r::Model->read_from_file($input_file) };
|
||||||
Slic3r::GUI::show_error($self, $@) if $@;
|
Slic3r::GUI::show_error($self, $@) if $@;
|
||||||
|
|
||||||
|
my @obj_idx = ();
|
||||||
if (defined $model) {
|
if (defined $model) {
|
||||||
if ($model->looks_like_multipart_object) {
|
if ($model->looks_like_multipart_object) {
|
||||||
my $dialog = Wx::MessageDialog->new($self,
|
my $dialog = Wx::MessageDialog->new($self,
|
||||||
@ -539,11 +617,13 @@ sub load_file {
|
|||||||
$model->convert_multipart_object;
|
$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));
|
$self->statusbar->SetStatusText("Loaded " . basename($input_file));
|
||||||
}
|
}
|
||||||
|
|
||||||
$process_dialog->Destroy;
|
$process_dialog->Destroy;
|
||||||
|
|
||||||
|
return @obj_idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub load_model_objects {
|
sub load_model_objects {
|
||||||
@ -558,9 +638,10 @@ sub load_model_objects {
|
|||||||
my @obj_idx = ();
|
my @obj_idx = ();
|
||||||
foreach my $model_object (@model_objects) {
|
foreach my $model_object (@model_objects) {
|
||||||
my $o = $self->{model}->add_object($model_object);
|
my $o = $self->{model}->add_object($model_object);
|
||||||
|
$o->repair;
|
||||||
|
|
||||||
push @{ $self->{objects} }, Slic3r::GUI::Plater::Object->new(
|
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} };
|
push @obj_idx, $#{ $self->{objects} };
|
||||||
|
|
||||||
@ -624,6 +705,8 @@ sub load_model_objects {
|
|||||||
$self->object_list_changed;
|
$self->object_list_changed;
|
||||||
|
|
||||||
$self->schedule_background_process;
|
$self->schedule_background_process;
|
||||||
|
|
||||||
|
return @obj_idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub bed_centerf {
|
sub bed_centerf {
|
||||||
@ -772,24 +855,23 @@ sub rotate {
|
|||||||
|
|
||||||
if (!defined $angle) {
|
if (!defined $angle) {
|
||||||
my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z';
|
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);
|
my $default = $axis == Z ? rad2deg($model_instance->rotation) : 0;
|
||||||
return if !$angle || $angle == -1;
|
# Wx::GetNumberFromUser() does not support decimal numbers
|
||||||
$angle = 0 - $angle; # rotate clockwise (be consistent with button icon)
|
$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;
|
$self->stop_background_process;
|
||||||
|
|
||||||
if ($axis == Z) {
|
if ($axis == Z) {
|
||||||
my $new_angle = $model_instance->rotation + deg2rad($angle);
|
my $new_angle = deg2rad($angle);
|
||||||
$_->set_rotation($new_angle) for @{ $model_object->instances };
|
$_->set_rotation($_->rotation + $new_angle) for @{ $model_object->instances };
|
||||||
$object->transform_thumbnail($self->{model}, $obj_idx);
|
$object->transform_thumbnail($self->{model}, $obj_idx);
|
||||||
} else {
|
} else {
|
||||||
# rotation around X and Y needs to be performed on mesh
|
# rotation around X and Y needs to be performed on mesh
|
||||||
# so we first apply any Z rotation
|
# so we first apply any Z rotation
|
||||||
if ($model_instance->rotation != 0) {
|
$model_object->transform_by_instance($model_instance, 1);
|
||||||
$model_object->rotate($model_instance->rotation, Z);
|
|
||||||
$_->set_rotation(0) for @{ $model_object->instances };
|
|
||||||
}
|
|
||||||
$model_object->rotate(deg2rad($angle), $axis);
|
$model_object->rotate(deg2rad($angle), $axis);
|
||||||
|
|
||||||
# realign object to Z = 0
|
# realign object to Z = 0
|
||||||
@ -816,10 +898,7 @@ sub mirror {
|
|||||||
my $model_instance = $model_object->instances->[0];
|
my $model_instance = $model_object->instances->[0];
|
||||||
|
|
||||||
# apply Z rotation before mirroring
|
# apply Z rotation before mirroring
|
||||||
if ($model_instance->rotation != 0) {
|
$model_object->transform_by_instance($model_instance, 1);
|
||||||
$model_object->rotate($model_instance->rotation, Z);
|
|
||||||
$_->set_rotation(0) for @{ $model_object->instances };
|
|
||||||
}
|
|
||||||
|
|
||||||
$model_object->mirror($axis);
|
$model_object->mirror($axis);
|
||||||
$model_object->update_bounding_box;
|
$model_object->update_bounding_box;
|
||||||
@ -857,21 +936,23 @@ sub changescale {
|
|||||||
my $scale;
|
my $scale;
|
||||||
if ($tosize) {
|
if ($tosize) {
|
||||||
my $cursize = $object_size->[$axis];
|
my $cursize = $object_size->[$axis];
|
||||||
my $newsize = Wx::GetNumberFromUser("", "Enter the new size for the selected object:", "Scale along $axis_name",
|
# Wx::GetNumberFromUser() does not support decimal numbers
|
||||||
$cursize, 0, $bed_size->[$axis], $self);
|
my $newsize = Wx::GetTextFromUser(
|
||||||
return if !$newsize || $newsize < 0;
|
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;
|
$scale = $newsize / $cursize * 100;
|
||||||
} else {
|
} else {
|
||||||
$scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale along $axis_name",
|
# Wx::GetNumberFromUser() does not support decimal numbers
|
||||||
100, 0, 100000, $self);
|
$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
|
# apply Z rotation before scaling
|
||||||
if ($model_instance->rotation != 0) {
|
$model_object->transform_by_instance($model_instance, 1);
|
||||||
$model_object->rotate($model_instance->rotation, Z);
|
|
||||||
$_->set_rotation(0) for @{ $model_object->instances };
|
|
||||||
}
|
|
||||||
|
|
||||||
my $versor = [1,1,1];
|
my $versor = [1,1,1];
|
||||||
$versor->[$axis] = $scale/100;
|
$versor->[$axis] = $scale/100;
|
||||||
@ -882,16 +963,18 @@ sub changescale {
|
|||||||
my $scale;
|
my $scale;
|
||||||
if ($tosize) {
|
if ($tosize) {
|
||||||
my $cursize = max(@$object_size);
|
my $cursize = max(@$object_size);
|
||||||
my $newsize = Wx::GetNumberFromUser("", "Enter the new max size for the selected object:", "Scale",
|
# Wx::GetNumberFromUser() does not support decimal numbers
|
||||||
$cursize, 0, max(@$bed_size), $self);
|
my $newsize = Wx::GetTextFromUser("Enter the new max size for the selected object:",
|
||||||
return if !$newsize || $newsize < 0;
|
"Scale", $cursize, $self);
|
||||||
|
return if !$newsize || $newsize !~ /^\d*(?:\.\d*)?$/ || $newsize < 0;
|
||||||
$scale = $newsize / $cursize * 100;
|
$scale = $newsize / $cursize * 100;
|
||||||
} else {
|
} else {
|
||||||
# max scale factor should be above 2540 to allow importing files exported in inches
|
# 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',
|
# Wx::GetNumberFromUser() does not support decimal numbers
|
||||||
$model_instance->scaling_factor*100, 0, 100000, $self);
|
$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%");
|
$self->{list}->SetItem($obj_idx, 2, "$scale%");
|
||||||
$scale /= 100; # turn percent into factor
|
$scale /= 100; # turn percent into factor
|
||||||
@ -921,9 +1004,7 @@ sub arrange {
|
|||||||
$self->pause_background_process;
|
$self->pause_background_process;
|
||||||
|
|
||||||
my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape);
|
my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape);
|
||||||
eval {
|
my $success = $self->{model}->arrange_objects($self->GetFrame->config->min_object_distance, $bb);
|
||||||
$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
|
# 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
|
# when parts don't fit in print bed
|
||||||
|
|
||||||
@ -977,11 +1058,22 @@ sub split_object {
|
|||||||
sub schedule_background_process {
|
sub schedule_background_process {
|
||||||
my ($self) = @_;
|
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}) {
|
if (defined $self->{apply_config_timer}) {
|
||||||
$self->{apply_config_timer}->Start(PROCESS_DELAY, 1); # 1 = one shot
|
$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 {
|
sub async_apply_config {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
|
|
||||||
@ -1062,6 +1154,7 @@ sub stop_background_process {
|
|||||||
$self->statusbar->SetCancelCallback(undef);
|
$self->statusbar->SetCancelCallback(undef);
|
||||||
$self->statusbar->StopBusy;
|
$self->statusbar->StopBusy;
|
||||||
$self->statusbar->SetStatusText("");
|
$self->statusbar->SetStatusText("");
|
||||||
|
|
||||||
$self->{toolpaths2D}->reload_print if $self->{toolpaths2D};
|
$self->{toolpaths2D}->reload_print if $self->{toolpaths2D};
|
||||||
$self->{preview3D}->reload_print if $self->{preview3D};
|
$self->{preview3D}->reload_print if $self->{preview3D};
|
||||||
$self->{ObjectLayersDialog}->reload_preview if $self->{ObjectLayersDialog};
|
$self->{ObjectLayersDialog}->reload_preview if $self->{ObjectLayersDialog};
|
||||||
@ -1138,9 +1231,9 @@ sub export_gcode {
|
|||||||
|
|
||||||
# select output file
|
# select output file
|
||||||
if ($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 {
|
} 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)),
|
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);
|
basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||||
if ($dlg->ShowModal != wxID_OK) {
|
if ($dlg->ShowModal != wxID_OK) {
|
||||||
@ -1196,6 +1289,7 @@ sub on_process_completed {
|
|||||||
Slic3r::debugf "Background processing completed.\n";
|
Slic3r::debugf "Background processing completed.\n";
|
||||||
$self->{process_thread}->detach if $self->{process_thread};
|
$self->{process_thread}->detach if $self->{process_thread};
|
||||||
$self->{process_thread} = undef;
|
$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 we're supposed to perform an explicit export let's display the error in a dialog
|
||||||
if ($error && $self->{export_gcode_output_file}) {
|
if ($error && $self->{export_gcode_output_file}) {
|
||||||
@ -1314,6 +1408,7 @@ sub send_gcode {
|
|||||||
# OctoPrint doesn't like Windows paths so we use basename()
|
# OctoPrint doesn't like Windows paths so we use basename()
|
||||||
# Also, since we need to read from filesystem we process it through encode_path()
|
# Also, since we need to read from filesystem we process it through encode_path()
|
||||||
file => [ $path, basename($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");
|
$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 {
|
sub export_object_stl {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
||||||
@ -1369,7 +1498,7 @@ sub _get_export_file {
|
|||||||
|
|
||||||
my $output_file = $main::opt{output};
|
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;
|
$output_file =~ s/\.gcode$/$suffix/i;
|
||||||
my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file),
|
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);
|
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;
|
return unless $dlg->ShowModal == wxID_OK;
|
||||||
|
|
||||||
if (my @new_objects = $dlg->NewModelObjects) {
|
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->remove($obj_idx);
|
||||||
$self->load_model_objects(grep defined($_), @new_objects);
|
$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;
|
$self->object_settings_dialog;
|
||||||
}, undef, 'cog.png');
|
}, undef, 'cog.png');
|
||||||
$menu->AppendSeparator();
|
$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 {
|
$frame->_append_menu_item($menu, "Export object as STL…", 'Export this single object as STL file', sub {
|
||||||
$self->export_object_stl;
|
$self->export_object_stl;
|
||||||
}, undef, 'brick_go.png');
|
}, undef, 'brick_go.png');
|
||||||
@ -1862,6 +1999,20 @@ sub object_menu {
|
|||||||
return $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;
|
package Slic3r::GUI::Plater::DropTarget;
|
||||||
use Wx::DND;
|
use Wx::DND;
|
||||||
use base 'Wx::FileDropTarget';
|
use base 'Wx::FileDropTarget';
|
||||||
@ -1888,6 +2039,7 @@ sub OnDropFiles {
|
|||||||
$self->{window}->load_file($_) for @$filenames;
|
$self->{window}->load_file($_) for @$filenames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 2D preview of an object. Each object is previewed by its convex hull.
|
||||||
package Slic3r::GUI::Plater::Object;
|
package Slic3r::GUI::Plater::Object;
|
||||||
use Moo;
|
use Moo;
|
||||||
|
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
# 2D preview on the platter.
|
||||||
|
# 3D objects are visualized by their convex hulls.
|
||||||
|
|
||||||
package Slic3r::GUI::Plater::2D;
|
package Slic3r::GUI::Plater::2D;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
@ -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;
|
package Slic3r::GUI::Plater::2DToolpaths;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
@ -126,7 +126,6 @@ sub load_print {
|
|||||||
#my @volume_ids = $self->canvas->load_object($object->model_object);
|
#my @volume_ids = $self->canvas->load_object($object->model_object);
|
||||||
#$self->canvas->volumes->[$_]->color->[3] = 0.2 for @volume_ids;
|
#$self->canvas->volumes->[$_]->color->[3] = 0.2 for @volume_ids;
|
||||||
}
|
}
|
||||||
$self->canvas->zoom_to_volumes;
|
|
||||||
$self->_loaded(1);
|
$self->_loaded(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
214
lib/Slic3r/GUI/Plater/LambdaObjectDialog.pm
Normal file
214
lib/Slic3r/GUI/Plater/LambdaObjectDialog.pm
Normal 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;
|
@ -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;
|
package Slic3r::GUI::Plater::ObjectCutDialog;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
use utf8;
|
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 qw(wxTheApp :dialog :id :misc :sizer wxTAB_TRAVERSAL);
|
||||||
use Wx::Event qw(EVT_CLOSE EVT_BUTTON);
|
use Wx::Event qw(EVT_CLOSE EVT_BUTTON);
|
||||||
use base 'Wx::Dialog';
|
use base 'Wx::Dialog';
|
||||||
@ -21,12 +26,16 @@ sub new {
|
|||||||
# Note whether the window was already closed, so a pending update is not executed.
|
# Note whether the window was already closed, so a pending update is not executed.
|
||||||
$self->{already_closed} = 0;
|
$self->{already_closed} = 0;
|
||||||
|
|
||||||
|
$self->{model_object}->transform_by_instance($self->{model_object}->get_instance(0), 1);
|
||||||
|
|
||||||
# cut options
|
# cut options
|
||||||
|
my $size_z = $self->{model_object}->instance_bounding_box(0)->size->z;
|
||||||
$self->{cut_options} = {
|
$self->{cut_options} = {
|
||||||
z => 0,
|
axis => Z,
|
||||||
keep_upper => 1,
|
z => $size_z/2,
|
||||||
|
keep_upper => 0,
|
||||||
keep_lower => 1,
|
keep_lower => 1,
|
||||||
rotate_lower => 1,
|
rotate_lower => 0,
|
||||||
preview => 1,
|
preview => 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -51,13 +60,21 @@ sub new {
|
|||||||
},
|
},
|
||||||
label_width => 120,
|
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(
|
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||||
opt_id => 'z',
|
opt_id => 'z',
|
||||||
type => 'slider',
|
type => 'slider',
|
||||||
label => 'Z',
|
label => 'Z',
|
||||||
default => $self->{cut_options}{z},
|
default => $self->{cut_options}{z},
|
||||||
min => 0,
|
min => 0,
|
||||||
max => $self->{model_object}->bounding_box->size->z,
|
max => $size_z,
|
||||||
full_width => 1,
|
full_width => 1,
|
||||||
));
|
));
|
||||||
{
|
{
|
||||||
@ -94,8 +111,14 @@ sub new {
|
|||||||
));
|
));
|
||||||
{
|
{
|
||||||
my $cut_button_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
my $cut_button_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||||
|
|
||||||
$self->{btn_cut} = Wx::Button->new($self, -1, "Perform cut", wxDefaultPosition, wxDefaultSize);
|
$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);
|
$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(
|
$optgroup->append_line(Slic3r::GUI::OptionsGroup::Line->new(
|
||||||
sizer => $cut_button_sizer,
|
sizer => $cut_button_sizer,
|
||||||
));
|
));
|
||||||
@ -129,14 +152,14 @@ sub new {
|
|||||||
$self->_perform_cut() unless $self->{mesh_cut_valid};
|
$self->_perform_cut() unless $self->{mesh_cut_valid};
|
||||||
|
|
||||||
# Adjust position / orientation of the split object halves.
|
# Adjust position / orientation of the split object halves.
|
||||||
if ($self->{new_model_objects}{lower}) {
|
if (my $lower = $self->{new_model_objects}[0]) {
|
||||||
if ($self->{cut_options}{rotate_lower}) {
|
if ($self->{cut_options}{rotate_lower} && $self->{cut_options}{axis} == Z) {
|
||||||
$self->{new_model_objects}{lower}->rotate(PI, X);
|
$lower->rotate(PI, X);
|
||||||
$self->{new_model_objects}{lower}->center_around_origin; # align to Z = 0
|
|
||||||
}
|
}
|
||||||
|
$lower->center_around_origin; # align to Z = 0
|
||||||
}
|
}
|
||||||
if ($self->{new_model_objects}{upper}) {
|
if (my $upper = $self->{new_model_objects}[1]) {
|
||||||
$self->{new_model_objects}{upper}->center_around_origin; # align to Z = 0
|
$upper->center_around_origin; # align to Z = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Note that the window was already closed, so a pending update will not be executed.
|
# 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->EndModal(wxID_OK);
|
||||||
$self->Destroy();
|
$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 {
|
EVT_CLOSE($self, sub {
|
||||||
# Note that the window was already closed, so a pending update will not be executed.
|
# 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
|
sub _mesh_slice_z_pos
|
||||||
{
|
{
|
||||||
my ($self) = @_;
|
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.
|
# 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};
|
return if $self->{mesh_cut_valid};
|
||||||
|
|
||||||
my $z = $self->_mesh_slice_z_pos();
|
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};
|
my ($upper_object, $lower_object) = @{$new_model->objects};
|
||||||
$self->{new_model} = $new_model;
|
$self->{new_model} = $new_model;
|
||||||
$self->{new_model_objects} = {};
|
$self->{new_model_objects} = [];
|
||||||
if ($self->{cut_options}{keep_upper} && $upper_object->volumes_count > 0) {
|
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) {
|
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;
|
$self->{mesh_cut_valid} = 1;
|
||||||
@ -207,41 +281,53 @@ sub _update {
|
|||||||
# Only recalculate the cut, if the live cut preview is active.
|
# Only recalculate the cut, if the live cut preview is active.
|
||||||
my $life_preview_active = $self->_life_preview_active();
|
my $life_preview_active = $self->_life_preview_active();
|
||||||
$self->_perform_cut() if $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
|
# 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
|
# and cut dialog but ModelObject::cut() needs Z without any instance transformation
|
||||||
my $z = $self->_mesh_slice_z_pos();
|
my $z = $self->_mesh_slice_z_pos();
|
||||||
|
|
||||||
|
|
||||||
# update canvas
|
# update canvas
|
||||||
if ($self->{canvas}) {
|
if ($self->{canvas}) {
|
||||||
# get volumes to render
|
# get volumes to render
|
||||||
my @objects = ();
|
my @objects = ();
|
||||||
if ($life_preview_active) {
|
if ($life_preview_active) {
|
||||||
push @objects, values %{$self->{new_model_objects}};
|
push @objects, grep defined, @{$self->{new_model_objects}};
|
||||||
} else {
|
} else {
|
||||||
push @objects, $self->{model_object};
|
push @objects, $self->{model_object};
|
||||||
}
|
}
|
||||||
|
|
||||||
# get section contour
|
# get section contour
|
||||||
my @expolygons = ();
|
my @expolygons = ();
|
||||||
foreach my $volume (@{$self->{model_object}->volumes}) {
|
foreach my $volume (@{$self->{model_object}->volumes}) {
|
||||||
next if !$volume->mesh;
|
next if !$volume->mesh;
|
||||||
next if $volume->modifier;
|
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;
|
push @expolygons, @$expp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
my $offset = $self->{model_object}->instances->[0]->offset;
|
||||||
foreach my $expolygon (@expolygons) {
|
foreach my $expolygon (@expolygons) {
|
||||||
$self->{model_object}->instances->[0]->transform_polygon($_)
|
$self->{model_object}->instances->[0]->transform_polygon($_)
|
||||||
for @$expolygon;
|
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}->reset_objects;
|
||||||
$self->{canvas}->load_object($_, undef, [0]) for @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->{canvas}->SetCuttingPlane(
|
||||||
$self->{cut_options}{z},
|
$self->{cut_options}{axis},
|
||||||
|
$plane_z,
|
||||||
[@expolygons],
|
[@expolygons],
|
||||||
);
|
);
|
||||||
$self->{canvas}->Render;
|
$self->{canvas}->Render;
|
||||||
@ -252,9 +338,16 @@ sub _update {
|
|||||||
{
|
{
|
||||||
my $z = $self->{cut_options}{z};
|
my $z = $self->{cut_options}{z};
|
||||||
my $optgroup = $self->{optgroup};
|
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_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('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});
|
$optgroup->get_field('preview')->toggle($self->{cut_options}{keep_upper} != $self->{cut_options}{keep_lower});
|
||||||
|
|
||||||
# update cut button
|
# update cut button
|
||||||
@ -269,7 +362,7 @@ sub _update {
|
|||||||
|
|
||||||
sub NewModelObjects {
|
sub NewModelObjects {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
return values %{ $self->{new_model_objects} };
|
return grep defined, @{ $self->{new_model_objects} };
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
|
# Configuration of mesh modifiers and their parameters.
|
||||||
|
# This panel is inserted into ObjectSettingsDialog.
|
||||||
|
|
||||||
package Slic3r::GUI::Plater::ObjectPartsPanel;
|
package Slic3r::GUI::Plater::ObjectPartsPanel;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
use utf8;
|
use utf8;
|
||||||
|
|
||||||
use File::Basename qw(basename);
|
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);
|
wxTheApp);
|
||||||
use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED);
|
use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED);
|
||||||
use base 'Wx::Panel';
|
use base 'Wx::Panel';
|
||||||
@ -38,10 +41,12 @@ sub new {
|
|||||||
# buttons
|
# buttons
|
||||||
$self->{btn_load_part} = Wx::Button->new($self, -1, "Load part…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
$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_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);
|
$self->{btn_delete} = Wx::Button->new($self, -1, "Delete part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||||
if ($Slic3r::GUI::have_button_icons) {
|
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_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_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));
|
$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);
|
my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||||
$buttons_sizer->Add($self->{btn_load_part}, 0);
|
$buttons_sizer->Add($self->{btn_load_part}, 0);
|
||||||
$buttons_sizer->Add($self->{btn_load_modifier}, 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);
|
$buttons_sizer->Add($self->{btn_delete}, 0);
|
||||||
$self->{btn_load_part}->SetFont($Slic3r::GUI::small_font);
|
$self->{btn_load_part}->SetFont($Slic3r::GUI::small_font);
|
||||||
$self->{btn_load_modifier}->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);
|
$self->{btn_delete}->SetFont($Slic3r::GUI::small_font);
|
||||||
|
|
||||||
# part settings panel
|
# part settings panel
|
||||||
$self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->{part_settings_changed} = 1; });
|
$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);
|
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);
|
$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
|
# 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($tree, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
|
||||||
$left_sizer->Add($buttons_sizer, 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($settings_sizer, 1, wxEXPAND | wxALL, 0);
|
||||||
|
$left_sizer->Add($optgroup_movers->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||||
|
|
||||||
# right pane with preview canvas
|
# right pane with preview canvas
|
||||||
my $canvas;
|
my $canvas;
|
||||||
@ -81,7 +135,7 @@ sub new {
|
|||||||
|
|
||||||
$canvas->load_object($self->{model_object}, undef, [0]);
|
$canvas->load_object($self->{model_object}, undef, [0]);
|
||||||
$canvas->set_auto_bed_shape;
|
$canvas->set_auto_bed_shape;
|
||||||
$canvas->SetSize([500,500]);
|
$canvas->SetSize([500,700]);
|
||||||
$canvas->zoom_to_volumes;
|
$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_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_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);
|
EVT_BUTTON($self, $self->{btn_delete}, \&on_btn_delete);
|
||||||
|
|
||||||
$self->reload_tree;
|
$self->reload_tree;
|
||||||
@ -176,6 +231,12 @@ sub selection_changed {
|
|||||||
$self->{btn_delete}->Disable;
|
$self->{btn_delete}->Disable;
|
||||||
$self->{settings_panel}->disable;
|
$self->{settings_panel}->disable;
|
||||||
$self->{settings_panel}->set_config(undef);
|
$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) {
|
if (my $itemData = $self->get_selection) {
|
||||||
my ($config, @opt_keys);
|
my ($config, @opt_keys);
|
||||||
@ -188,6 +249,24 @@ sub selection_changed {
|
|||||||
|
|
||||||
# attach volume config to settings panel
|
# attach volume config to settings panel
|
||||||
my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ];
|
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;
|
$config = $volume->config;
|
||||||
$self->{staticbox}->SetLabel('Part Settings');
|
$self->{staticbox}->SetLabel('Part Settings');
|
||||||
|
|
||||||
@ -197,6 +276,7 @@ sub selection_changed {
|
|||||||
# select nothing in 3D preview
|
# select nothing in 3D preview
|
||||||
|
|
||||||
# attach object config to settings panel
|
# attach object config to settings panel
|
||||||
|
$self->{left_sizer}->Hide($self->{optgroup_movers}->sizer);
|
||||||
$self->{staticbox}->SetLabel('Object Settings');
|
$self->{staticbox}->SetLabel('Object Settings');
|
||||||
@opt_keys = (map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new);
|
@opt_keys = (map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new);
|
||||||
$config = $self->{model_object}->config;
|
$config = $self->{model_object}->config;
|
||||||
@ -249,19 +329,65 @@ sub on_btn_load {
|
|||||||
$self->_parts_changed;
|
$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 {
|
sub on_btn_delete {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
|
|
||||||
my $itemData = $self->get_selection;
|
my $itemData = $self->get_selection;
|
||||||
if ($itemData && $itemData->{type} eq 'volume') {
|
if ($itemData && $itemData->{type} eq 'volume') {
|
||||||
my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
|
my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
|
||||||
|
|
||||||
# if user is deleting the last solid part, throw error
|
# if user is deleting the last solid part, throw error
|
||||||
if (!$volume->modifier && scalar(grep !$_->modifier, @{$self->{model_object}->volumes}) == 1) {
|
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.");
|
Slic3r::GUI::show_error($self, "You can't delete the last solid part from this object.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$self->{model_object}->delete_volume($itemData->{volume_id});
|
$self->{model_object}->delete_volume($itemData->{volume_id});
|
||||||
$self->{parts_changed} = 1;
|
$self->{parts_changed} = 1;
|
||||||
}
|
}
|
||||||
@ -270,9 +396,9 @@ sub on_btn_delete {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub _parts_changed {
|
sub _parts_changed {
|
||||||
my ($self) = @_;
|
my ($self, $selected_volume_idx) = @_;
|
||||||
|
|
||||||
$self->reload_tree;
|
$self->reload_tree($selected_volume_idx);
|
||||||
if ($self->{canvas}) {
|
if ($self->{canvas}) {
|
||||||
$self->{canvas}->reset_objects;
|
$self->{canvas}->reset_objects;
|
||||||
$self->{canvas}->load_object($self->{model_object});
|
$self->{canvas}->load_object($self->{model_object});
|
||||||
@ -307,4 +433,21 @@ sub PartSettingsChanged {
|
|||||||
return $self->{part_settings_changed};
|
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;
|
1;
|
||||||
|
@ -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;
|
package Slic3r::GUI::Plater::ObjectSettingsDialog;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
@ -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;
|
package Slic3r::GUI::Plater::OverrideSettingsPanel;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# Preferences dialog, opens from Menu: File->Preferences
|
||||||
|
|
||||||
package Slic3r::GUI::Preferences;
|
package Slic3r::GUI::Preferences;
|
||||||
use Wx qw(:dialog :id :misc :sizer :systemsettings wxTheApp);
|
use Wx qw(:dialog :id :misc :sizer :systemsettings wxTheApp);
|
||||||
use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER);
|
use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# Status bar at the bottom of the main screen.
|
||||||
|
|
||||||
package Slic3r::GUI::ProgressStatusBar;
|
package Slic3r::GUI::ProgressStatusBar;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
|
# DLP Projector screen for the SLA (stereolitography) print process
|
||||||
|
|
||||||
package Slic3r::GUI::Projector;
|
package Slic3r::GUI::Projector;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
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 Wx::Event qw(EVT_BUTTON EVT_CLOSE EVT_TEXT_ENTER EVT_SPINCTRL EVT_SLIDER);
|
||||||
use base qw(Wx::Dialog Class::Accessor);
|
use base qw(Wx::Dialog Class::Accessor);
|
||||||
use utf8;
|
use utf8;
|
||||||
@ -376,10 +379,23 @@ sub new {
|
|||||||
|
|
||||||
{
|
{
|
||||||
# should be wxCLOSE but it crashes on Linux, maybe it's a Wx bug
|
# should be wxCLOSE but it crashes on Linux, maybe it's a Wx bug
|
||||||
my $buttons = $self->CreateStdDialogButtonSizer(wxOK);
|
my $buttons = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||||
EVT_BUTTON($self, wxID_OK, sub {
|
{
|
||||||
$self->_close;
|
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);
|
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||||
}
|
}
|
||||||
EVT_CLOSE($self, sub {
|
EVT_CLOSE($self, sub {
|
||||||
@ -415,7 +431,7 @@ sub new {
|
|||||||
|
|
||||||
my $duration = $self->controller->remaining_print_time;
|
my $duration = $self->controller->remaining_print_time;
|
||||||
$self->_set_status(sprintf "Printing layer %d/%d (z = %.2f); %d minutes and %d seconds left",
|
$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,
|
$self->controller->current_layer_height,
|
||||||
int($duration/60), ($duration - int($duration/60)*60)); # % truncates to integer
|
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_spinctrl}->SetRange(0, $max);
|
||||||
$self->{layers_slider}->SetRange(0, $max);
|
$self->{layers_slider}->SetRange(0, $max);
|
||||||
}
|
}
|
||||||
@ -455,6 +471,27 @@ sub _update_buttons {
|
|||||||
$self->Layout;
|
$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 {
|
sub _set_status {
|
||||||
my ($self, $status) = @_;
|
my ($self, $status) = @_;
|
||||||
$self->{status_text}->SetLabel($status // '');
|
$self->{status_text}->SetLabel($status // '');
|
||||||
@ -468,8 +505,9 @@ sub show_print_time {
|
|||||||
|
|
||||||
|
|
||||||
my $duration = $self->controller->print_time;
|
my $duration = $self->controller->print_time;
|
||||||
$self->_set_status(sprintf "Estimated print time: %d minutes and %d seconds",
|
$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
|
int($duration/60), ($duration - int($duration/60)*60), # % truncates to integer
|
||||||
|
$self->controller->total_resin);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub _close {
|
sub _close {
|
||||||
@ -505,6 +543,7 @@ package Slic3r::GUI::Projector::Controller;
|
|||||||
use Moo;
|
use Moo;
|
||||||
use Wx qw(wxTheApp :id :timer);
|
use Wx qw(wxTheApp :id :timer);
|
||||||
use Wx::Event qw(EVT_TIMER);
|
use Wx::Event qw(EVT_TIMER);
|
||||||
|
use Slic3r::Geometry qw(unscale);
|
||||||
use Slic3r::Print::State ':steps';
|
use Slic3r::Print::State ':steps';
|
||||||
use Time::HiRes qw(gettimeofday tv_interval);
|
use Time::HiRes qw(gettimeofday tv_interval);
|
||||||
|
|
||||||
@ -517,7 +556,6 @@ has 'sender' => (is => 'rw');
|
|||||||
has 'timer' => (is => 'rw');
|
has 'timer' => (is => 'rw');
|
||||||
has 'is_printing' => (is => 'rw', default => sub { 0 });
|
has 'is_printing' => (is => 'rw', default => sub { 0 });
|
||||||
has '_print' => (is => 'rw');
|
has '_print' => (is => 'rw');
|
||||||
has '_layers' => (is => 'rw');
|
|
||||||
has '_heights' => (is => 'rw');
|
has '_heights' => (is => 'rw');
|
||||||
has '_layer_num' => (is => 'rw');
|
has '_layer_num' => (is => 'rw');
|
||||||
has '_timer_cb' => (is => 'rw');
|
has '_timer_cb' => (is => 'rw');
|
||||||
@ -527,7 +565,23 @@ sub BUILD {
|
|||||||
|
|
||||||
Slic3r::GUI::disable_screensaver();
|
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
|
# projection timer
|
||||||
my $timer_id = &Wx::NewId();
|
my $timer_id = &Wx::NewId();
|
||||||
@ -546,40 +600,6 @@ sub delay {
|
|||||||
$self->timer->Start($wait * 1000, wxTIMER_ONE_SHOT);
|
$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 {
|
sub current_layer_height {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
|
|
||||||
@ -613,7 +633,7 @@ sub start_print {
|
|||||||
# start with black
|
# start with black
|
||||||
Slic3r::debugf "starting black projection\n";
|
Slic3r::debugf "starting black projection\n";
|
||||||
$self->_layer_num(-1);
|
$self->_layer_num(-1);
|
||||||
$self->screen->project_layers(undef);
|
$self->screen->project_layer(undef);
|
||||||
$self->delay($self->config2->{settle_time}, sub {
|
$self->delay($self->config2->{settle_time}, sub {
|
||||||
$self->project_next_layer;
|
$self->project_next_layer;
|
||||||
});
|
});
|
||||||
@ -630,7 +650,7 @@ sub stop_print {
|
|||||||
$self->is_printing(0);
|
$self->is_printing(0);
|
||||||
$self->timer->Stop;
|
$self->timer->Stop;
|
||||||
$self->_timer_cb(undef);
|
$self->_timer_cb(undef);
|
||||||
$self->screen->project_layers(undef);
|
$self->screen->project_layer(undef);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub print_completed {
|
sub print_completed {
|
||||||
@ -652,19 +672,18 @@ sub print_completed {
|
|||||||
sub is_projecting {
|
sub is_projecting {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
|
|
||||||
return defined $self->screen->layers;
|
return defined $self->screen->layer_num;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub project_layer {
|
sub project_layer {
|
||||||
my ($self, $layer_num) = @_;
|
my ($self, $layer_num) = @_;
|
||||||
|
|
||||||
if (!defined $layer_num || $layer_num >= $self->layer_count) {
|
if (!defined $layer_num || $layer_num >= $self->_print->layer_count) {
|
||||||
$self->screen->project_layers(undef);
|
$self->screen->project_layer(undef);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my @layers = @{ $self->_layers->{ $self->_heights->[$layer_num] } };
|
$self->screen->project_layer($layer_num);
|
||||||
$self->screen->project_layers([ @layers ]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub project_next_layer {
|
sub project_next_layer {
|
||||||
@ -672,7 +691,7 @@ sub project_next_layer {
|
|||||||
|
|
||||||
$self->_layer_num($self->_layer_num + 1);
|
$self->_layer_num($self->_layer_num + 1);
|
||||||
Slic3r::debugf "projecting layer %d\n", $self->_layer_num;
|
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;
|
$self->print_completed;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -699,7 +718,7 @@ sub project_next_layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$self->delay($time, sub {
|
$self->delay($time, sub {
|
||||||
$self->screen->project_layers(undef);
|
$self->screen->project_layer(undef);
|
||||||
$self->project_next_layer;
|
$self->project_next_layer;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -722,8 +741,21 @@ sub print_time {
|
|||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
|
|
||||||
return $self->config2->{bottom_layers} * $self->config2->{bottom_exposure_time}
|
return $self->config2->{bottom_layers} * $self->config2->{bottom_exposure_time}
|
||||||
+ (@{$self->_heights} - $self->config2->{bottom_layers}) * $self->config2->{exposure_time}
|
+ ($self->_print->layer_count - $self->config2->{bottom_layers}) * $self->config2->{exposure_time}
|
||||||
+ @{$self->_heights} * $self->config2->{settle_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 {
|
sub DESTROY {
|
||||||
@ -741,9 +773,9 @@ use base qw(Wx::Dialog Class::Accessor);
|
|||||||
|
|
||||||
use List::Util qw(min);
|
use List::Util qw(min);
|
||||||
use Slic3r::Geometry qw(X Y unscale scale);
|
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 {
|
sub new {
|
||||||
my ($class, $parent, $config, $config2) = @_;
|
my ($class, $parent, $config, $config2) = @_;
|
||||||
@ -803,10 +835,10 @@ sub _resize {
|
|||||||
$self->Refresh;
|
$self->Refresh;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub project_layers {
|
sub project_layer {
|
||||||
my ($self, $layers) = @_;
|
my ($self, $layer_num) = @_;
|
||||||
|
|
||||||
$self->layers($layers);
|
$self->layer_num($layer_num);
|
||||||
$self->Refresh;
|
$self->Refresh;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -865,32 +897,67 @@ sub _repaint {
|
|||||||
|
|
||||||
$dc->SetTextForeground(wxWHITE);
|
$dc->SetTextForeground(wxWHITE);
|
||||||
$dc->SetFont(Wx::Font->new(20, wxDEFAULT, wxNORMAL, wxNORMAL));
|
$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
|
# get layers at this height
|
||||||
# draw layers
|
# draw layers
|
||||||
$dc->SetPen(Wx::Pen->new(wxWHITE, 1, wxSOLID));
|
$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 };
|
return if !$self->print || !defined $self->layer_num;
|
||||||
foreach my $copy (@{$layer->object->_shifted_copies}) {
|
|
||||||
foreach my $polygon (@polygons) {
|
if ($self->print->layer_solid($self->layer_num)) {
|
||||||
$polygon = $polygon->clone;
|
$self->_paint_expolygon($_, $dc) for @{$self->print->layer_slices($self->layer_num)};
|
||||||
$polygon->translate(@$copy);
|
} else {
|
||||||
|
# perimeters first, because their "hole" is painted black
|
||||||
if ($polygon->is_counter_clockwise) {
|
$self->_paint_expolygon($_, $dc) for
|
||||||
$dc->SetBrush(Wx::Brush->new(wxWHITE, wxSOLID));
|
@{$self->print->layer_perimeters($self->layer_num)},
|
||||||
} else {
|
@{$self->print->layer_solid_infill($self->layer_num)};
|
||||||
$dc->SetBrush(Wx::Brush->new(wxBLACK, wxSOLID));
|
|
||||||
}
|
$self->_paint_expolygon($_, $dc)
|
||||||
$dc->DrawPolygon($self->scaled_points_to_pixel($polygon->pp), 0, 0);
|
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
|
# convert a model coordinate into a pixel coordinate
|
||||||
|
118
lib/Slic3r/GUI/SLAPrintOptions.pm
Normal file
118
lib/Slic3r/GUI/SLAPrintOptions.pm
Normal 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;
|
@ -1,3 +1,6 @@
|
|||||||
|
# The "Simple" Print Settings tab.
|
||||||
|
# The "Simple" mode is enabled by File->Preferences dialog.
|
||||||
|
|
||||||
package Slic3r::GUI::SimpleTab;
|
package Slic3r::GUI::SimpleTab;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
@ -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;
|
package Slic3r::GUI::Tab;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
@ -106,7 +109,10 @@ sub new {
|
|||||||
$self->_on_presets_changed;
|
$self->_on_presets_changed;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
# C++ instance DynamicPrintConfig
|
||||||
$self->{config} = Slic3r::Config->new;
|
$self->{config} = Slic3r::Config->new;
|
||||||
|
# Initialize the DynamicPrintConfig by default keys/values.
|
||||||
|
# Possible %params keys: no_controller
|
||||||
$self->build(%params);
|
$self->build(%params);
|
||||||
$self->update_tree;
|
$self->update_tree;
|
||||||
$self->_update;
|
$self->_update;
|
||||||
@ -474,7 +480,7 @@ sub build {
|
|||||||
perimeter_acceleration infill_acceleration bridge_acceleration
|
perimeter_acceleration infill_acceleration bridge_acceleration
|
||||||
first_layer_acceleration default_acceleration
|
first_layer_acceleration default_acceleration
|
||||||
skirts skirt_distance skirt_height min_skirt_length
|
skirts skirt_distance skirt_height min_skirt_length
|
||||||
brim_width
|
brim_connections_width brim_width
|
||||||
support_material support_material_threshold support_material_enforce_layers
|
support_material support_material_threshold support_material_enforce_layers
|
||||||
raft_layers
|
raft_layers
|
||||||
support_material_pattern support_material_spacing support_material_angle
|
support_material_pattern support_material_spacing support_material_angle
|
||||||
@ -569,6 +575,7 @@ sub build {
|
|||||||
{
|
{
|
||||||
my $optgroup = $page->new_optgroup('Brim');
|
my $optgroup = $page->new_optgroup('Brim');
|
||||||
$optgroup->append_single_option_line('brim_width');
|
$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);
|
$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
|
if ($config->fill_density == 100
|
||||||
&& !first { $_ eq $config->fill_pattern } @{$Slic3r::Config::Options->{external_fill_pattern}{values}}) {
|
&& !first { $_ eq $config->fill_pattern } @{$Slic3r::Config::Options->{external_fill_pattern}{values}}) {
|
||||||
@ -775,7 +811,7 @@ sub _update {
|
|||||||
|
|
||||||
my $new_conf = Slic3r::Config->new;
|
my $new_conf = Slic3r::Config->new;
|
||||||
if ($dialog->ShowModal() == wxID_YES) {
|
if ($dialog->ShowModal() == wxID_YES) {
|
||||||
$new_conf->set("fill_pattern", 1);
|
$new_conf->set("fill_pattern", 'rectilinear');
|
||||||
} else {
|
} else {
|
||||||
$new_conf->set("fill_density", 40);
|
$new_conf->set("fill_density", 40);
|
||||||
}
|
}
|
||||||
@ -823,7 +859,7 @@ sub _update {
|
|||||||
$self->get_field($_)->toggle($have_skirt)
|
$self->get_field($_)->toggle($have_skirt)
|
||||||
for qw(skirt_distance skirt_height);
|
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()
|
# perimeter_extruder uses the same logic as in Print::extruders()
|
||||||
$self->get_field('perimeter_extruder')->toggle($have_perimeters || $have_brim);
|
$self->get_field('perimeter_extruder')->toggle($have_perimeters || $have_brim);
|
||||||
|
|
||||||
@ -863,7 +899,7 @@ sub build {
|
|||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
||||||
$self->init_config_options(qw(
|
$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
|
temperature first_layer_temperature bed_temperature first_layer_bed_temperature
|
||||||
fan_always_on cooling
|
fan_always_on cooling
|
||||||
min_fan_speed max_fan_speed bridge_fan_speed disable_fan_first_layers
|
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');
|
$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 {
|
sub _update {
|
||||||
@ -1000,7 +1057,7 @@ sub build {
|
|||||||
my (%params) = @_;
|
my (%params) = @_;
|
||||||
|
|
||||||
$self->init_config_options(qw(
|
$self->init_config_options(qw(
|
||||||
bed_shape z_offset
|
bed_shape z_offset has_heatbed
|
||||||
gcode_flavor use_relative_e_distances
|
gcode_flavor use_relative_e_distances
|
||||||
serial_port serial_speed
|
serial_port serial_speed
|
||||||
octoprint_host octoprint_apikey
|
octoprint_host octoprint_apikey
|
||||||
@ -1067,6 +1124,7 @@ sub build {
|
|||||||
);
|
);
|
||||||
$optgroup->append_single_option_line($option);
|
$optgroup->append_single_option_line($option);
|
||||||
}
|
}
|
||||||
|
$optgroup->append_single_option_line('has_heatbed');
|
||||||
$optgroup->on_change(sub {
|
$optgroup->on_change(sub {
|
||||||
my ($opt_id) = @_;
|
my ($opt_id) = @_;
|
||||||
if ($opt_id eq 'extruders_count') {
|
if ($opt_id eq 'extruders_count') {
|
||||||
@ -1142,13 +1200,24 @@ sub build {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EVT_BUTTON($self, $btn, sub {
|
EVT_BUTTON($self, $btn, sub {
|
||||||
my $dlg = Slic3r::GUI::BonjourBrowser->new($self);
|
# look for devices
|
||||||
if ($dlg->ShowModal == wxID_OK) {
|
my $entries;
|
||||||
my $value = $dlg->GetValue . ":" . $dlg->GetPort;
|
{
|
||||||
$self->{config}->set('octoprint_host', $value);
|
my $res = Net::Bonjour->new('http');
|
||||||
$self->update_dirty;
|
$res->discover;
|
||||||
$self->_on_value_change('octoprint_host', $value);
|
$entries = [ $res->entries ];
|
||||||
$self->reload_config;
|
}
|
||||||
|
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
|
# some options only apply when not using firmware retraction
|
||||||
$self->get_field($_, $i)->toggle($retraction && !$config->use_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)) {
|
if ($config->use_firmware_retraction && $config->get_at('wipe', $i)) {
|
||||||
my $dialog = Wx::MessageDialog->new($self,
|
my $dialog = Wx::MessageDialog->new($self,
|
||||||
"The Wipe option is not available when using the Firmware Retraction mode.\n"
|
"The Wipe option is not available when using the Firmware Retraction mode.\n"
|
||||||
|
@ -5,9 +5,9 @@ use warnings;
|
|||||||
require Exporter;
|
require Exporter;
|
||||||
our @ISA = qw(Exporter);
|
our @ISA = qw(Exporter);
|
||||||
our @EXPORT_OK = qw(offset offset_ex
|
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
|
JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex
|
||||||
intersection intersection_pl diff_pl union CLIPPER_OFFSET_SCALE
|
intersection intersection_pl diff_pl union CLIPPER_OFFSET_SCALE
|
||||||
union_pt_chained diff_ppl intersection_ppl);
|
union_pt_chained intersection_ppl);
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# Extends the C++ class Slic3r::Layer.
|
||||||
|
|
||||||
package Slic3r::Layer;
|
package Slic3r::Layer;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
@ -29,15 +31,6 @@ sub regions {
|
|||||||
return [ map $self->get_region($_), 0..($self->region_count-1) ];
|
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;
|
package Slic3r::Layer::Support;
|
||||||
our @ISA = qw(Slic3r::Layer);
|
our @ISA = qw(Slic3r::Layer);
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# extends C++ class Slic3r::Model
|
||||||
package Slic3r::Model;
|
package Slic3r::Model;
|
||||||
|
|
||||||
use List::Util qw(first max any);
|
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);
|
$self->delete_object($_) for reverse 0..($self->objects_count-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Extends C++ class Slic3r::ModelMaterial
|
||||||
package Slic3r::Model::Material;
|
package Slic3r::Model::Material;
|
||||||
|
|
||||||
sub apply {
|
sub apply {
|
||||||
@ -101,6 +103,7 @@ sub apply {
|
|||||||
$self->set_attribute($_, $attributes{$_}) for keys %$attributes;
|
$self->set_attribute($_, $attributes{$_}) for keys %$attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Extends C++ class Slic3r::ModelObject
|
||||||
package Slic3r::Model::Object;
|
package Slic3r::Model::Object;
|
||||||
|
|
||||||
use File::Basename qw(basename);
|
use File::Basename qw(basename);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# The slicing work horse.
|
||||||
|
# Extends C++ class Slic3r::Print
|
||||||
package Slic3r::Print;
|
package Slic3r::Print;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
@ -10,8 +12,9 @@ use Slic3r::ExtrusionPath ':roles';
|
|||||||
use Slic3r::Flow ':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 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
|
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::Print::State ':steps';
|
||||||
|
use Slic3r::Surface qw(S_TYPE_BOTTOM);
|
||||||
|
|
||||||
our $status_cb;
|
our $status_cb;
|
||||||
|
|
||||||
@ -39,8 +42,12 @@ sub size {
|
|||||||
sub process {
|
sub process {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
|
|
||||||
|
$self->status_cb->(20, "Generating perimeters");
|
||||||
$_->make_perimeters for @{$self->objects};
|
$_->make_perimeters for @{$self->objects};
|
||||||
|
|
||||||
|
$self->status_cb->(70, "Infilling layers");
|
||||||
$_->infill for @{$self->objects};
|
$_->infill for @{$self->objects};
|
||||||
|
|
||||||
$_->generate_support_material for @{$self->objects};
|
$_->generate_support_material for @{$self->objects};
|
||||||
$self->make_skirt;
|
$self->make_skirt;
|
||||||
$self->make_brim; # must come after make_skirt
|
$self->make_brim; # must come after make_skirt
|
||||||
@ -70,9 +77,32 @@ sub export_gcode {
|
|||||||
$self->process;
|
$self->process;
|
||||||
|
|
||||||
# output everything to a G-code file
|
# 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->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
|
# run post-processing scripts
|
||||||
if (@{$self->config->post_process}) {
|
if (@{$self->config->post_process}) {
|
||||||
@ -89,6 +119,7 @@ sub export_gcode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Export SVG slices for the offline SLA printing.
|
||||||
sub export_svg {
|
sub export_svg {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my %params = @_;
|
my %params = @_;
|
||||||
@ -97,7 +128,7 @@ sub export_svg {
|
|||||||
|
|
||||||
my $fh = $params{output_fh};
|
my $fh = $params{output_fh};
|
||||||
if (!$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;
|
$output_file =~ s/\.gcode$/.svg/i;
|
||||||
Slic3r::open(\$fh, ">", $output_file) or die "Failed to open $output_file for writing\n";
|
Slic3r::open(\$fh, ">", $output_file) or die "Failed to open $output_file for writing\n";
|
||||||
print "Exporting to $output_file..." unless $params{quiet};
|
print "Exporting to $output_file..." unless $params{quiet};
|
||||||
@ -319,132 +350,17 @@ sub make_brim {
|
|||||||
$_->generate_support_material for @{$self->objects};
|
$_->generate_support_material for @{$self->objects};
|
||||||
$self->make_skirt;
|
$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");
|
$self->status_cb->(88, "Generating brim");
|
||||||
|
$self->_make_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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 $self = shift;
|
||||||
my ($file) = @_;
|
my $err = $self->_validate;
|
||||||
|
die $err . "\n" if (defined($err) && $err ne '');
|
||||||
# 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -98,10 +98,7 @@ sub BUILD {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new(
|
$self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new($self->_gcodegen));
|
||||||
config => $self->config,
|
|
||||||
gcodegen => $self->_gcodegen,
|
|
||||||
));
|
|
||||||
|
|
||||||
$self->_spiral_vase(Slic3r::GCode::SpiralVase->new(config => $self->config))
|
$self->_spiral_vase(Slic3r::GCode::SpiralVase->new(config => $self->config))
|
||||||
if $self->config->spiral_vase;
|
if $self->config->spiral_vase;
|
||||||
@ -162,7 +159,7 @@ sub export {
|
|||||||
if $self->config->cooling && $self->config->disable_fan_first_layers;
|
if $self->config->cooling && $self->config->disable_fan_first_layers;
|
||||||
|
|
||||||
# set bed temperature
|
# 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);
|
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
|
# is triggered, so machine has more time to reach such temperatures
|
||||||
if ($layer->id == 0 && $finished_objects > 0) {
|
if ($layer->id == 0 && $finished_objects > 0) {
|
||||||
printf $fh $gcodegen->writer->set_bed_temperature($self->config->first_layer_bed_temperature),
|
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->_print_first_layer_temperature(0);
|
||||||
}
|
}
|
||||||
$self->process_layer($layer, [$copy]);
|
$self->process_layer($layer, [$copy]);
|
||||||
@ -357,7 +354,7 @@ sub process_layer {
|
|||||||
# check whether we're going to apply spiralvase logic
|
# check whether we're going to apply spiralvase logic
|
||||||
if (defined $self->_spiral_vase) {
|
if (defined $self->_spiral_vase) {
|
||||||
$self->_spiral_vase->enable(
|
$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)
|
&& ($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 { $_->region->config->bottom_solid_layers > $layer->id } @{$layer->regions})
|
||||||
&& !defined(first { $_->perimeters->items_count > 1 } @{$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);
|
if $temperature && $temperature != $self->config->get_at('first_layer_temperature', $extruder->id);
|
||||||
}
|
}
|
||||||
$gcode .= $self->_gcodegen->writer->set_bed_temperature($self->print->config->bed_temperature)
|
$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);
|
$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);
|
$gcode .= $self->_gcodegen->set_extruder($self->print->regions->[0]->config->perimeter_extruder-1);
|
||||||
$self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0));
|
$self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0));
|
||||||
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(1);
|
$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};
|
for @{$self->print->brim};
|
||||||
$self->_brim_done(1);
|
$self->_brim_done(1);
|
||||||
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(0);
|
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(0);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
package Slic3r::Print::Object;
|
package Slic3r::Print::Object;
|
||||||
|
# extends c++ class Slic3r::PrintObject (Print.xsp)
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
@ -32,6 +33,14 @@ sub support_layers {
|
|||||||
return [ map $self->get_support_layer($_), 0..($self->support_layer_count - 1) ];
|
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
|
# this should be idempotent
|
||||||
sub slice {
|
sub slice {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
@ -40,6 +49,13 @@ sub slice {
|
|||||||
$self->set_step_started(STEP_SLICE);
|
$self->set_step_started(STEP_SLICE);
|
||||||
$self->print->status_cb->(10, "Processing triangulated mesh");
|
$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
|
# init layers
|
||||||
{
|
{
|
||||||
$self->clear_layers;
|
$self->clear_layers;
|
||||||
@ -64,8 +80,7 @@ sub slice {
|
|||||||
{
|
{
|
||||||
my @nozzle_diameters = (
|
my @nozzle_diameters = (
|
||||||
map $self->print->config->get_at('nozzle_diameter', $_),
|
map $self->print->config->get_at('nozzle_diameter', $_),
|
||||||
$self->config->support_material_extruder-1,
|
@{$self->support_material_extruders},
|
||||||
$self->config->support_material_interface_extruder-1,
|
|
||||||
);
|
);
|
||||||
$support_material_layer_height = 0.75 * min(@nozzle_diameters);
|
$support_material_layer_height = 0.75 * min(@nozzle_diameters);
|
||||||
}
|
}
|
||||||
@ -428,146 +443,13 @@ sub slice {
|
|||||||
$self->set_step_done(STEP_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 {
|
sub make_perimeters {
|
||||||
my $self = shift;
|
my ($self) = @_;
|
||||||
|
|
||||||
# prerequisites
|
# prerequisites
|
||||||
$self->slice;
|
$self->slice;
|
||||||
|
|
||||||
return if $self->step_done(STEP_PERIMETERS);
|
$self->_make_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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub prepare_infill {
|
sub prepare_infill {
|
||||||
@ -614,30 +496,7 @@ sub infill {
|
|||||||
# prerequisites
|
# prerequisites
|
||||||
$self->prepare_infill;
|
$self->prepare_infill;
|
||||||
|
|
||||||
return if $self->step_done(STEP_INFILL);
|
$self->_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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub generate_support_material {
|
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
|
# 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.
|
# fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries.
|
||||||
sub clip_fill_surfaces {
|
sub clip_fill_surfaces {
|
||||||
@ -975,19 +693,27 @@ sub discover_horizontal_shells {
|
|||||||
|
|
||||||
# find intersection between neighbor and current layer's surfaces
|
# find intersection between neighbor and current layer's surfaces
|
||||||
# intersections have contours and holes
|
# intersections have contours and holes
|
||||||
# we update $solid so that we limit the next neighbor layer to the areas that were
|
my $new_internal_solid = intersection(
|
||||||
# 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(
|
|
||||||
$solid,
|
$solid,
|
||||||
[ map $_->p, grep { ($_->surface_type == S_TYPE_INTERNAL) || ($_->surface_type == S_TYPE_INTERNALSOLID) } @neighbor_fill_surfaces ],
|
[ map $_->p, grep { ($_->surface_type == S_TYPE_INTERNAL) || ($_->surface_type == S_TYPE_INTERNALSOLID) } @neighbor_fill_surfaces ],
|
||||||
1,
|
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 ($layerm->region->config->fill_density == 0) {
|
||||||
# if we're printing a hollow object we discard any solid shell thinner
|
# 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 {
|
sub _simplify_slices {
|
||||||
my ($self, $distance) = @_;
|
my ($self, $distance) = @_;
|
||||||
|
|
||||||
|
@ -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;
|
package Slic3r::Print::Simple;
|
||||||
use Moo;
|
use Moo;
|
||||||
|
|
||||||
@ -6,7 +13,7 @@ use Slic3r::Geometry qw(X Y);
|
|||||||
has '_print' => (
|
has '_print' => (
|
||||||
is => 'ro',
|
is => 'ro',
|
||||||
default => sub { Slic3r::Print->new },
|
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
|
total_used_filament total_extruded_volume
|
||||||
placeholder_parser process)],
|
placeholder_parser process)],
|
||||||
);
|
);
|
||||||
@ -41,18 +48,24 @@ has 'print_center' => (
|
|||||||
default => sub { Slic3r::Pointf->new(100,100) },
|
default => sub { Slic3r::Pointf->new(100,100) },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
has 'dont_arrange' => (
|
||||||
|
is => 'rw',
|
||||||
|
default => sub { 0 },
|
||||||
|
);
|
||||||
|
|
||||||
has 'output_file' => (
|
has 'output_file' => (
|
||||||
is => 'rw',
|
is => 'rw',
|
||||||
);
|
);
|
||||||
|
|
||||||
sub set_model {
|
sub set_model {
|
||||||
|
# $model is of type Slic3r::Model
|
||||||
my ($self, $model) = @_;
|
my ($self, $model) = @_;
|
||||||
|
|
||||||
# make method idempotent so that the object is reusable
|
# make method idempotent so that the object is reusable
|
||||||
$self->_print->clear_objects;
|
$self->_print->clear_objects;
|
||||||
|
|
||||||
# make sure all objects have at least one defined instance
|
# 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
|
# apply scaling and rotation supplied from command line if any
|
||||||
foreach my $instance (map @{$_->instances}, @{$model->objects}) {
|
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) {
|
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) {
|
} elsif ($need_arrange) {
|
||||||
$model->duplicate_objects($self->duplicate, $self->_print->config->min_object_distance);
|
$model->duplicate_objects($self->duplicate, $self->_print->config->min_object_distance);
|
||||||
} elsif ($self->duplicate > 1) {
|
} elsif ($self->duplicate > 1) {
|
||||||
@ -69,7 +82,7 @@ sub set_model {
|
|||||||
$model->duplicate($self->duplicate, $self->_print->config->min_object_distance);
|
$model->duplicate($self->duplicate, $self->_print->config->min_object_distance);
|
||||||
}
|
}
|
||||||
$_->translate(0,0,-$_->bounding_box->z_min) for @{$model->objects};
|
$_->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}) {
|
foreach my $model_object (@{$model->objects}) {
|
||||||
$self->_print->auto_assign_extruders($model_object);
|
$self->_print->auto_assign_extruders($model_object);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
# Wraps C++ enums Slic3r::PrintStep and Slic3r::PrintObjectStep
|
||||||
package Slic3r::Print::State;
|
package Slic3r::Print::State;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
@ -558,11 +558,6 @@ sub generate_toolpaths {
|
|||||||
$pattern = 'honeycomb';
|
$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_angle = $self->object_config->support_material_angle + 90;
|
||||||
my $interface_spacing = $self->object_config->support_material_interface_spacing + $interface_flow->spacing;
|
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;
|
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);
|
$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
|
# interface and contact infill
|
||||||
if (@$interface || @$contact_infill) {
|
if (@$interface || @$contact_infill) {
|
||||||
$fillers{interface}->angle($interface_angle);
|
$fillers{interface}->set_angle($interface_angle);
|
||||||
$fillers{interface}->spacing($_interface_flow->spacing);
|
$fillers{interface}->set_min_spacing($_interface_flow->spacing);
|
||||||
|
|
||||||
# find centerline of the external loop
|
# find centerline of the external loop
|
||||||
$interface = offset2($interface, +scaled_epsilon, -(scaled_epsilon + $_interface_flow->scaled_width/2));
|
$interface = offset2($interface, +scaled_epsilon, -(scaled_epsilon + $_interface_flow->scaled_width/2));
|
||||||
@ -702,7 +707,7 @@ sub generate_toolpaths {
|
|||||||
|
|
||||||
my @paths = ();
|
my @paths = ();
|
||||||
foreach my $expolygon (@{union_ex($interface)}) {
|
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),
|
Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL),
|
||||||
density => $interface_density,
|
density => $interface_density,
|
||||||
layer_height => $layer->height,
|
layer_height => $layer->height,
|
||||||
@ -716,7 +721,7 @@ sub generate_toolpaths {
|
|||||||
mm3_per_mm => $mm3_per_mm,
|
mm3_per_mm => $mm3_per_mm,
|
||||||
width => $_interface_flow->width,
|
width => $_interface_flow->width,
|
||||||
height => $layer->height,
|
height => $layer->height,
|
||||||
), @p;
|
), @$p;
|
||||||
}
|
}
|
||||||
|
|
||||||
$layer->support_interface_fills->append(@paths);
|
$layer->support_interface_fills->append(@paths);
|
||||||
@ -725,11 +730,11 @@ sub generate_toolpaths {
|
|||||||
# support or flange
|
# support or flange
|
||||||
if (@$base) {
|
if (@$base) {
|
||||||
my $filler = $fillers{support};
|
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
|
# We don't use $base_flow->spacing because we need a constant spacing
|
||||||
# value that guarantees that all layers are correctly aligned.
|
# value that guarantees that all layers are correctly aligned.
|
||||||
$filler->spacing($flow->spacing);
|
$filler->set_min_spacing($flow->spacing);
|
||||||
|
|
||||||
my $density = $support_density;
|
my $density = $support_density;
|
||||||
my $base_flow = $_flow;
|
my $base_flow = $_flow;
|
||||||
@ -742,13 +747,13 @@ sub generate_toolpaths {
|
|||||||
# base flange
|
# base flange
|
||||||
if ($layer_id == 0) {
|
if ($layer_id == 0) {
|
||||||
$filler = $fillers{interface};
|
$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;
|
$density = 0.5;
|
||||||
$base_flow = $self->first_layer_flow;
|
$base_flow = $self->first_layer_flow;
|
||||||
|
|
||||||
# use the proper spacing for first layer as we don't need to align
|
# use the proper spacing for first layer as we don't need to align
|
||||||
# its pattern to the other layers
|
# its pattern to the other layers
|
||||||
$filler->spacing($base_flow->spacing);
|
$filler->set_min_spacing($base_flow->spacing);
|
||||||
} else {
|
} else {
|
||||||
# draw a perimeter all around support infill
|
# draw a perimeter all around support infill
|
||||||
# TODO: use brim ordering algorithm
|
# TODO: use brim ordering algorithm
|
||||||
@ -767,7 +772,7 @@ sub generate_toolpaths {
|
|||||||
|
|
||||||
my $mm3_per_mm = $base_flow->mm3_per_mm;
|
my $mm3_per_mm = $base_flow->mm3_per_mm;
|
||||||
foreach my $expolygon (@$to_infill) {
|
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),
|
Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL),
|
||||||
density => $density,
|
density => $density,
|
||||||
layer_height => $layer->height,
|
layer_height => $layer->height,
|
||||||
@ -780,7 +785,7 @@ sub generate_toolpaths {
|
|||||||
mm3_per_mm => $mm3_per_mm,
|
mm3_per_mm => $mm3_per_mm,
|
||||||
width => $base_flow->width,
|
width => $base_flow->width,
|
||||||
height => $layer->height,
|
height => $layer->height,
|
||||||
), @p;
|
), @$p;
|
||||||
}
|
}
|
||||||
|
|
||||||
$layer->support_fills->append(@paths);
|
$layer->support_fills->append(@paths);
|
||||||
|
@ -27,6 +27,14 @@ sub mesh {
|
|||||||
$facets = [
|
$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],
|
[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') {
|
} elsif ($name eq 'cube_with_hole') {
|
||||||
$vertices = [
|
$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]
|
[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]
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
|
# 2D cut in the XZ plane through the toolpaths.
|
||||||
|
# For debugging purposes.
|
||||||
|
|
||||||
package Slic3r::Test::SectionCut;
|
package Slic3r::Test::SectionCut;
|
||||||
use Moo;
|
use Moo;
|
||||||
|
|
||||||
use List::Util qw(first min max);
|
use List::Util qw(any min max);
|
||||||
use Slic3r::Geometry qw(unscale);
|
use Slic3r::Geometry qw(unscale);
|
||||||
use Slic3r::Geometry::Clipper qw(intersection_pl);
|
use Slic3r::Geometry::Clipper qw(intersection_pl);
|
||||||
use SVG;
|
use SVG;
|
||||||
@ -10,20 +13,27 @@ use Slic3r::SVG;
|
|||||||
has 'print' => (is => 'ro', required => 1);
|
has 'print' => (is => 'ro', required => 1);
|
||||||
has 'scale' => (is => 'ro', default => sub { 30 });
|
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 '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 '_height' => (is => 'rw');
|
||||||
has '_svg' => (is => 'rw');
|
has '_svg' => (is => 'rw');
|
||||||
has '_svg_style' => (is => 'rw', default => sub { {} });
|
has '_svg_style' => (is => 'rw', default => sub { {} });
|
||||||
|
has '_bb' => (is => 'lazy');
|
||||||
|
|
||||||
sub BUILD {
|
sub _build__line {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
||||||
# calculate the Y coordinate of the section line
|
# 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;
|
my $y = ($bb->y_min + $bb->y_max) * $self->y_percent;
|
||||||
|
|
||||||
# store our section line
|
# 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 {
|
sub export_svg {
|
||||||
@ -32,8 +42,7 @@ sub export_svg {
|
|||||||
|
|
||||||
# get bounding box of print and its height
|
# get bounding box of print and its height
|
||||||
# (Print should return a BoundingBox3 object instead)
|
# (Print should return a BoundingBox3 object instead)
|
||||||
my $bb = $self->print->bounding_box;
|
my $print_size = $self->_bb->size;
|
||||||
my $print_size = $bb->size;
|
|
||||||
$self->_height(max(map $_->print_z, map @{$_->layers}, @{$self->print->objects}));
|
$self->_height(max(map $_->print_z, map @{$_->layers}, @{$self->print->objects}));
|
||||||
|
|
||||||
# initialize the SVG canvas
|
# initialize the SVG canvas
|
||||||
@ -72,98 +81,118 @@ sub _plot_group {
|
|||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($filter) = @_;
|
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 $object (@{$self->print->objects}) {
|
||||||
foreach my $copy (@{$object->_shifted_copies}) {
|
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
|
||||||
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
|
my @paths = map $_->clone, map @{$_->flatten}, grep defined $_, $filter->($layer);
|
||||||
# get all ExtrusionPath objects
|
|
||||||
my @paths = map $_->clone,
|
my $name = sprintf "%s %d (z = %f)",
|
||||||
map { ($_->isa('Slic3r::ExtrusionLoop') || $_->isa('Slic3r::ExtrusionPath::Collection')) ? @$_ : $_ }
|
($layer->isa('Slic3r::Layer::Support') ? 'Support Layer' : 'Layer'),
|
||||||
grep defined $_,
|
$layer->id,
|
||||||
$filter->($layer);
|
$layer->print_z;
|
||||||
|
|
||||||
# move paths to location of copy
|
my $g = $self->_svg->getElementByID($name)
|
||||||
$_->polyline->translate(@$copy) for @paths;
|
|| $self->_svg->group(id => $name, style => { %{$self->_svg_style} });
|
||||||
|
|
||||||
|
foreach my $copy (@{$object->_shifted_copies}) {
|
||||||
if (0) {
|
if (0) {
|
||||||
# export plan with section line and exit
|
# export plan with section line and exit
|
||||||
|
my @grown = map @{$_->grow}, @paths;
|
||||||
|
$_->translate(@$copy) for @paths;
|
||||||
|
|
||||||
require "Slic3r/SVG.pm";
|
require "Slic3r/SVG.pm";
|
||||||
Slic3r::SVG::output(
|
Slic3r::SVG::output(
|
||||||
"line.svg",
|
"line.svg",
|
||||||
no_arrows => 1,
|
no_arrows => 1,
|
||||||
lines => [ $self->line ],
|
polygons => \@grown,
|
||||||
red_polylines => [ map $_->polyline, @paths ],
|
red_lines => [ $self->_line ],
|
||||||
);
|
);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach my $path (@paths) {
|
$self->_plot_path($_, $g, $copy, $layer) for @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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
sub _y {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($y) = @_;
|
my ($y) = @_;
|
||||||
|
62
slic3r.pl
62
slic3r.pl
@ -6,13 +6,15 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/lib";
|
use lib "$FindBin::Bin/lib";
|
||||||
|
use local::lib "$FindBin::Bin/local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use File::Basename qw(basename);
|
use File::Basename qw(basename);
|
||||||
use Getopt::Long qw(:config no_auto_abbrev);
|
use Getopt::Long qw(:config no_auto_abbrev);
|
||||||
use List::Util qw(first);
|
use List::Util qw(first);
|
||||||
use POSIX qw(setlocale LC_NUMERIC);
|
use POSIX qw(setlocale LC_NUMERIC ceil);
|
||||||
use Slic3r;
|
use Slic3r;
|
||||||
|
use Slic3r::Geometry qw(epsilon X Y Z deg2rad);
|
||||||
use Time::HiRes qw(gettimeofday tv_interval);
|
use Time::HiRes qw(gettimeofday tv_interval);
|
||||||
$|++;
|
$|++;
|
||||||
binmode STDOUT, ':utf8';
|
binmode STDOUT, ':utf8';
|
||||||
@ -40,14 +42,16 @@ my %cli_options = ();
|
|||||||
'merge|m' => \$opt{merge},
|
'merge|m' => \$opt{merge},
|
||||||
'repair' => \$opt{repair},
|
'repair' => \$opt{repair},
|
||||||
'cut=f' => \$opt{cut},
|
'cut=f' => \$opt{cut},
|
||||||
|
'cut-grid=s' => \$opt{cut_grid},
|
||||||
'split' => \$opt{split},
|
'split' => \$opt{split},
|
||||||
'info' => \$opt{info},
|
'info' => \$opt{info},
|
||||||
|
|
||||||
'scale=f' => \$opt{scale},
|
'scale=f' => \$opt{scale},
|
||||||
'rotate=i' => \$opt{rotate},
|
'rotate=f' => \$opt{rotate},
|
||||||
'duplicate=i' => \$opt{duplicate},
|
'duplicate=i' => \$opt{duplicate},
|
||||||
'duplicate-grid=s' => \$opt{duplicate_grid},
|
'duplicate-grid=s' => \$opt{duplicate_grid},
|
||||||
'print-center=s' => \$opt{print_center},
|
'print-center=s' => \$opt{print_center},
|
||||||
|
'dont-arrange' => \$opt{dont_arrange},
|
||||||
);
|
);
|
||||||
foreach my $opt_key (keys %{$Slic3r::Config::Options}) {
|
foreach my $opt_key (keys %{$Slic3r::Config::Options}) {
|
||||||
my $cli = $Slic3r::Config::Options->{$opt_key}->{cli} or next;
|
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);
|
$mesh->translate(0, 0, -$mesh->bounding_box->z_min);
|
||||||
my $upper = Slic3r::TriangleMesh->new;
|
my $upper = Slic3r::TriangleMesh->new;
|
||||||
my $lower = Slic3r::TriangleMesh->new;
|
my $lower = Slic3r::TriangleMesh->new;
|
||||||
$mesh->cut($opt{cut}, $upper, $lower);
|
$mesh->cut(Z, $opt{cut}, $upper, $lower);
|
||||||
$upper->repair;
|
$upper->repair;
|
||||||
$lower->repair;
|
$lower->repair;
|
||||||
$upper->write_ascii("${file}_upper.stl")
|
$upper->write_ascii("${file}_upper.stl")
|
||||||
@ -158,6 +162,49 @@ if (@ARGV) { # slicing from command line
|
|||||||
exit;
|
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}) {
|
if ($opt{split}) {
|
||||||
foreach my $file (@ARGV) {
|
foreach my $file (@ARGV) {
|
||||||
$file = Slic3r::decode_path($file);
|
$file = Slic3r::decode_path($file);
|
||||||
@ -185,6 +232,7 @@ if (@ARGV) { # slicing from command line
|
|||||||
} else {
|
} else {
|
||||||
$model = Slic3r::Model->read_from_file($input_file);
|
$model = Slic3r::Model->read_from_file($input_file);
|
||||||
}
|
}
|
||||||
|
$model->repair;
|
||||||
|
|
||||||
if ($opt{info}) {
|
if ($opt{info}) {
|
||||||
$model->print_info;
|
$model->print_info;
|
||||||
@ -200,10 +248,11 @@ if (@ARGV) { # slicing from command line
|
|||||||
|
|
||||||
my $sprint = Slic3r::Print::Simple->new(
|
my $sprint = Slic3r::Print::Simple->new(
|
||||||
scale => $opt{scale} // 1,
|
scale => $opt{scale} // 1,
|
||||||
rotate => $opt{rotate} // 0,
|
rotate => deg2rad($opt{rotate} // 0),
|
||||||
duplicate => $opt{duplicate} // 1,
|
duplicate => $opt{duplicate} // 1,
|
||||||
duplicate_grid => $opt{duplicate_grid} // [1,1],
|
duplicate_grid => $opt{duplicate_grid} // [1,1],
|
||||||
print_center => $opt{print_center} // Slic3r::Pointf->new(100,100),
|
print_center => $opt{print_center} // Slic3r::Pointf->new(100,100),
|
||||||
|
dont_arrange => $opt{dont_arrange} // 0,
|
||||||
status_cb => sub {
|
status_cb => sub {
|
||||||
my ($percent, $message) = @_;
|
my ($percent, $message) = @_;
|
||||||
printf "=> %s\n", $message;
|
printf "=> %s\n", $message;
|
||||||
@ -296,7 +345,7 @@ $j
|
|||||||
(default: 100,100)
|
(default: 100,100)
|
||||||
--z-offset Additional height in mm to add to vertical coordinates
|
--z-offset Additional height in mm to add to vertical coordinates
|
||||||
(+/-, default: $config->{z_offset})
|
(+/-, 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})
|
default: $config->{gcode_flavor})
|
||||||
--use-relative-e-distances Enable this to get relative E values (default: no)
|
--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)
|
--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 Number of items with auto-arrange (1+, default: 1)
|
||||||
--duplicate-grid Number of items with grid arrangement (default: 1,1)
|
--duplicate-grid Number of items with grid arrangement (default: 1,1)
|
||||||
--duplicate-distance Distance in mm between copies (default: $config->{duplicate_distance})
|
--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
|
--xy-size-compensation
|
||||||
Grow/shrink objects by the configured absolute distance (mm, default: $config->{xy_size_compensation})
|
Grow/shrink objects by the configured absolute distance (mm, default: $config->{xy_size_compensation})
|
||||||
|
|
||||||
|
@ -2,16 +2,15 @@ cmake_minimum_required (VERSION 2.8)
|
|||||||
project (slic3r)
|
project (slic3r)
|
||||||
|
|
||||||
# only on newer GCCs: -ftemplate-backtrace-limit=0
|
# 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_GREATER 4.7.0)
|
||||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.7.3)
|
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_LESS 4.7.3)
|
||||||
endif(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.7.0)
|
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)
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
|
|
||||||
IF(CMAKE_HOST_APPLE)
|
IF(CMAKE_HOST_APPLE)
|
||||||
@ -45,14 +44,22 @@ add_library(libslic3r STATIC
|
|||||||
${LIBDIR}/libslic3r/Extruder.cpp
|
${LIBDIR}/libslic3r/Extruder.cpp
|
||||||
${LIBDIR}/libslic3r/ExtrusionEntity.cpp
|
${LIBDIR}/libslic3r/ExtrusionEntity.cpp
|
||||||
${LIBDIR}/libslic3r/ExtrusionEntityCollection.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/Flow.cpp
|
||||||
${LIBDIR}/libslic3r/GCode.cpp
|
${LIBDIR}/libslic3r/GCode.cpp
|
||||||
|
${LIBDIR}/libslic3r/GCode/CoolingBuffer.cpp
|
||||||
${LIBDIR}/libslic3r/GCodeSender.cpp
|
${LIBDIR}/libslic3r/GCodeSender.cpp
|
||||||
${LIBDIR}/libslic3r/GCodeWriter.cpp
|
${LIBDIR}/libslic3r/GCodeWriter.cpp
|
||||||
${LIBDIR}/libslic3r/Geometry.cpp
|
${LIBDIR}/libslic3r/Geometry.cpp
|
||||||
${LIBDIR}/libslic3r/IO.cpp
|
${LIBDIR}/libslic3r/IO.cpp
|
||||||
${LIBDIR}/libslic3r/Layer.cpp
|
${LIBDIR}/libslic3r/Layer.cpp
|
||||||
${LIBDIR}/libslic3r/LayerRegion.cpp
|
${LIBDIR}/libslic3r/LayerRegion.cpp
|
||||||
|
${LIBDIR}/libslic3r/LayerRegionFill.cpp
|
||||||
${LIBDIR}/libslic3r/Line.cpp
|
${LIBDIR}/libslic3r/Line.cpp
|
||||||
${LIBDIR}/libslic3r/Model.cpp
|
${LIBDIR}/libslic3r/Model.cpp
|
||||||
${LIBDIR}/libslic3r/MotionPlanner.cpp
|
${LIBDIR}/libslic3r/MotionPlanner.cpp
|
||||||
@ -67,10 +74,10 @@ add_library(libslic3r STATIC
|
|||||||
${LIBDIR}/libslic3r/PrintConfig.cpp
|
${LIBDIR}/libslic3r/PrintConfig.cpp
|
||||||
${LIBDIR}/libslic3r/PrintObject.cpp
|
${LIBDIR}/libslic3r/PrintObject.cpp
|
||||||
${LIBDIR}/libslic3r/PrintRegion.cpp
|
${LIBDIR}/libslic3r/PrintRegion.cpp
|
||||||
|
${LIBDIR}/libslic3r/SLAPrint.cpp
|
||||||
${LIBDIR}/libslic3r/Surface.cpp
|
${LIBDIR}/libslic3r/Surface.cpp
|
||||||
${LIBDIR}/libslic3r/SurfaceCollection.cpp
|
${LIBDIR}/libslic3r/SurfaceCollection.cpp
|
||||||
${LIBDIR}/libslic3r/SVG.cpp
|
${LIBDIR}/libslic3r/SVG.cpp
|
||||||
${LIBDIR}/libslic3r/SVGExport.cpp
|
|
||||||
${LIBDIR}/libslic3r/TriangleMesh.cpp
|
${LIBDIR}/libslic3r/TriangleMesh.cpp
|
||||||
)
|
)
|
||||||
add_library(admesh STATIC
|
add_library(admesh STATIC
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
#include "Config.hpp"
|
#include "Config.hpp"
|
||||||
#include "Model.hpp"
|
#include "Geometry.hpp"
|
||||||
#include "IO.hpp"
|
#include "IO.hpp"
|
||||||
|
#include "Model.hpp"
|
||||||
|
#include "SLAPrint.hpp"
|
||||||
#include "TriangleMesh.hpp"
|
#include "TriangleMesh.hpp"
|
||||||
#include "SVGExport.hpp"
|
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <math.h>
|
||||||
|
#include "boost/filesystem.hpp"
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
|
|
||||||
@ -33,8 +36,18 @@ main(const int argc, const char **argv)
|
|||||||
// load config files supplied via --load
|
// load config files supplied via --load
|
||||||
for (std::vector<std::string>::const_iterator file = cli_config.load.values.begin();
|
for (std::vector<std::string>::const_iterator file = cli_config.load.values.begin();
|
||||||
file != cli_config.load.values.end(); ++file) {
|
file != cli_config.load.values.end(); ++file) {
|
||||||
|
if (!boost::filesystem::exists(*file)) {
|
||||||
|
std::cout << "No such file: " << *file << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
DynamicPrintConfig c;
|
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();
|
c.normalize();
|
||||||
print_config.apply(c);
|
print_config.apply(c);
|
||||||
}
|
}
|
||||||
@ -50,9 +63,19 @@ main(const int argc, const char **argv)
|
|||||||
// read input file(s) if any
|
// read input file(s) if any
|
||||||
std::vector<Model> models;
|
std::vector<Model> models;
|
||||||
for (t_config_option_keys::const_iterator it = input_files.begin(); it != input_files.end(); ++it) {
|
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;
|
Model model;
|
||||||
// TODO: read other file formats with Model::read_from_file()
|
// 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()) {
|
if (model.objects.empty()) {
|
||||||
printf("Error: file is empty: %s\n", it->c_str());
|
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())
|
if (cli_config.scale_to_fit.is_positive_volume())
|
||||||
(*o)->scale_to_fit(cli_config.scale_to_fit.value);
|
(*o)->scale_to_fit(cli_config.scale_to_fit.value);
|
||||||
|
|
||||||
|
// TODO: honor option order?
|
||||||
(*o)->scale(cli_config.scale.value);
|
(*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
|
// TODO: handle --merge
|
||||||
@ -84,7 +110,7 @@ main(const int argc, const char **argv)
|
|||||||
|
|
||||||
TriangleMesh mesh = model->mesh();
|
TriangleMesh mesh = model->mesh();
|
||||||
mesh.repair();
|
mesh.repair();
|
||||||
Slic3r::IO::OBJ::write(mesh, outfile);
|
IO::OBJ::write(mesh, outfile);
|
||||||
printf("File exported to %s\n", outfile.c_str());
|
printf("File exported to %s\n", outfile.c_str());
|
||||||
} else if (cli_config.export_pov) {
|
} else if (cli_config.export_pov) {
|
||||||
std::string outfile = cli_config.output.value;
|
std::string outfile = cli_config.output.value;
|
||||||
@ -92,19 +118,57 @@ main(const int argc, const char **argv)
|
|||||||
|
|
||||||
TriangleMesh mesh = model->mesh();
|
TriangleMesh mesh = model->mesh();
|
||||||
mesh.repair();
|
mesh.repair();
|
||||||
Slic3r::IO::POV::write(mesh, outfile);
|
IO::POV::write(mesh, outfile);
|
||||||
printf("File exported to %s\n", outfile.c_str());
|
printf("File exported to %s\n", outfile.c_str());
|
||||||
} else if (cli_config.export_svg) {
|
} else if (cli_config.export_svg) {
|
||||||
std::string outfile = cli_config.output.value;
|
std::string outfile = cli_config.output.value;
|
||||||
if (outfile.empty()) outfile = model->objects.front()->input_file + ".svg";
|
if (outfile.empty()) outfile = model->objects.front()->input_file + ".svg";
|
||||||
|
|
||||||
SVGExport svg_export(model->mesh());
|
SLAPrint print(&*model);
|
||||||
svg_export.mesh.repair();
|
print.config.apply(print_config, true);
|
||||||
svg_export.config.apply(print_config, true);
|
print.slice();
|
||||||
svg_export.writeSVG(outfile);
|
print.write_svg(outfile);
|
||||||
printf("SVG file exported to %s\n", outfile.c_str());
|
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 {
|
} 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;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
for (t_config_option_keys::const_iterator it = input_files.begin(); it != input_files.end(); ++it) {
|
||||||
TriangleMesh mesh;
|
TriangleMesh mesh;
|
||||||
Slic3r::IO::STL::read(*it, &mesh);
|
Slic3r::IO::STL::read(*it, &mesh);
|
||||||
calculate_normals(&mesh.stl);
|
mesh.extrude_tin(config.option("offset", true)->getFloat());
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
std::string outfile = config.option("output", true)->getString();
|
std::string outfile = config.option("output", true)->getString();
|
||||||
if (outfile.empty()) outfile = *it + "_extruded.stl";
|
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());
|
printf("Extruded mesh written to %s\n", outfile.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,9 @@ plan tests => 34;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use Slic3r;
|
use Slic3r;
|
||||||
|
1
t/arcs.t
1
t/arcs.t
@ -7,6 +7,7 @@ plan tests => 24;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use Slic3r;
|
use Slic3r;
|
||||||
|
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first sum);
|
use List::Util qw(first sum);
|
||||||
|
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first sum);
|
use List::Util qw(first sum);
|
||||||
|
@ -7,6 +7,7 @@ plan tests => 6;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use Slic3r;
|
use Slic3r;
|
||||||
|
@ -7,6 +7,7 @@ plan tests => 6;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(sum);
|
use List::Util qw(sum);
|
||||||
|
@ -7,6 +7,7 @@ plan tests => 11;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use Slic3r;
|
use Slic3r;
|
||||||
|
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first);
|
use List::Util qw(first);
|
||||||
|
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use Slic3r;
|
use Slic3r;
|
||||||
|
49
t/cooling.t
49
t/cooling.t
@ -2,30 +2,29 @@ use Test::More;
|
|||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
plan tests => 11;
|
plan tests => 12;
|
||||||
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use List::Util qw(first);
|
||||||
use Slic3r;
|
use Slic3r;
|
||||||
use Slic3r::Test;
|
use Slic3r::Test;
|
||||||
|
|
||||||
|
my $gcodegen;
|
||||||
sub buffer {
|
sub buffer {
|
||||||
my $config = shift || Slic3r::Config->new;
|
my $config = shift || Slic3r::Config->new;
|
||||||
|
|
||||||
my $print_config = Slic3r::Config::Print->new;
|
my $print_config = Slic3r::Config::Print->new;
|
||||||
$print_config->apply_dynamic($config);
|
$print_config->apply_dynamic($config);
|
||||||
|
|
||||||
my $gcodegen = Slic3r::GCode->new;
|
$gcodegen = Slic3r::GCode->new;
|
||||||
$gcodegen->apply_print_config($print_config);
|
$gcodegen->apply_print_config($print_config);
|
||||||
$gcodegen->set_layer_count(10);
|
$gcodegen->set_layer_count(10);
|
||||||
my $buffer = Slic3r::GCode::CoolingBuffer->new(
|
return Slic3r::GCode::CoolingBuffer->new($gcodegen);
|
||||||
config => $print_config,
|
|
||||||
gcodegen => $gcodegen,
|
|
||||||
);
|
|
||||||
return $buffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
my $config = Slic3r::Config->new_from_defaults;
|
my $config = Slic3r::Config->new_from_defaults;
|
||||||
@ -33,14 +32,14 @@ $config->set('disable_fan_first_layers', 0);
|
|||||||
|
|
||||||
{
|
{
|
||||||
my $buffer = buffer($config);
|
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;
|
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';
|
like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold';
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
my $buffer = buffer($config);
|
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(
|
my $gcode = $buffer->append(
|
||||||
"G1 X50 F2500\n" .
|
"G1 X50 F2500\n" .
|
||||||
"G1 F3000;_EXTRUDE_SET_SPEED\n" .
|
"G1 F3000;_EXTRUDE_SET_SPEED\n" .
|
||||||
@ -55,7 +54,7 @@ $config->set('disable_fan_first_layers', 0);
|
|||||||
|
|
||||||
{
|
{
|
||||||
my $buffer = buffer($config);
|
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;
|
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';
|
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 = "";
|
my $gcode = "";
|
||||||
for my $obj_id (0 .. 1) {
|
for my $obj_id (0 .. 1) {
|
||||||
# use an elapsed time which is < the slowdown threshold but greater than it when summed twice
|
# 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->append("G1 X100 E1 F3000\n", $obj_id, 0, 0.4);
|
||||||
}
|
}
|
||||||
$gcode .= $buffer->flush;
|
$gcode .= $buffer->flush;
|
||||||
@ -78,7 +77,7 @@ $config->set('disable_fan_first_layers', 0);
|
|||||||
for my $layer_id (0 .. 1) {
|
for my $layer_id (0 .. 1) {
|
||||||
for my $obj_id (0 .. 1) {
|
for my $obj_id (0 .. 1) {
|
||||||
# use an elapsed time which is < the threshold but greater than it when summed twice
|
# 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
|
$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 $layer_id (0 .. 1) {
|
||||||
for my $obj_id (0 .. 1) {
|
for my $obj_id (0 .. 1) {
|
||||||
# use an elapsed time which is < the threshold even when summed twice
|
# 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
|
$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';
|
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__
|
__END__
|
||||||
|
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first);
|
use List::Util qw(first);
|
||||||
@ -59,7 +60,7 @@ use Slic3r::Test;
|
|||||||
$config->set('start_gcode', "TRAVEL:[travel_speed] HEIGHT:[layer_height]\n");
|
$config->set('start_gcode', "TRAVEL:[travel_speed] HEIGHT:[layer_height]\n");
|
||||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
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);
|
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 =~ /ts_${t}_/, 'print config options are replaced in output filename';
|
||||||
ok $output_file =~ /lh_$h\./, 'region config options are replaced in output filename';
|
ok $output_file =~ /lh_$h\./, 'region config options are replaced in output filename';
|
||||||
|
@ -8,6 +8,7 @@ plan tests => 20;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first);
|
use List::Util qw(first);
|
||||||
|
114
t/fill.t
114
t/fill.t
@ -2,17 +2,18 @@ use Test::More;
|
|||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
plan tests => 43;
|
plan tests => 92;
|
||||||
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first sum);
|
use List::Util qw(first sum);
|
||||||
use Slic3r;
|
use Slic3r;
|
||||||
use Slic3r::Geometry qw(X Y scale unscale convex_hull);
|
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);
|
use Slic3r::Geometry::Clipper qw(union diff diff_ex offset offset2_ex diff_pl);
|
||||||
use Slic3r::Surface qw(:types);
|
use Slic3r::Surface qw(:types);
|
||||||
use Slic3r::Test;
|
use Slic3r::Test;
|
||||||
|
|
||||||
@ -20,25 +21,78 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
|
|||||||
|
|
||||||
{
|
{
|
||||||
my $print = Slic3r::Print->new;
|
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 $surface_width = 250;
|
||||||
my $distance = $filler->adjust_solid_spacing(
|
my $distance = Slic3r::Filler::adjust_solid_spacing($surface_width, 47);
|
||||||
width => $surface_width,
|
is $distance, 50, 'adjusted solid distance';
|
||||||
distance => 100,
|
|
||||||
);
|
|
||||||
is $distance, 125, 'adjusted solid distance';
|
|
||||||
is $surface_width % $distance, 0, '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 $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,50], [0,50] ]);
|
||||||
my $filler = Slic3r::Fill::Rectilinear->new(
|
my $filler = Slic3r::Filler->new_from_type('rectilinear');
|
||||||
bounding_box => $expolygon->bounding_box,
|
$filler->set_bounding_box($expolygon->bounding_box);
|
||||||
angle => 0,
|
$filler->set_angle(0);
|
||||||
);
|
|
||||||
my $surface = Slic3r::Surface->new(
|
my $surface = Slic3r::Surface->new(
|
||||||
surface_type => S_TYPE_TOP,
|
surface_type => S_TYPE_TOP,
|
||||||
expolygon => $expolygon,
|
expolygon => $expolygon,
|
||||||
@ -48,11 +102,12 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
|
|||||||
height => 0.4,
|
height => 0.4,
|
||||||
nozzle_diameter => 0.50,
|
nozzle_diameter => 0.50,
|
||||||
);
|
);
|
||||||
$filler->spacing($flow->spacing);
|
$filler->set_min_spacing($flow->spacing);
|
||||||
|
$filler->set_density(1);
|
||||||
foreach my $angle (0, 45) {
|
foreach my $angle (0, 45) {
|
||||||
$surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]);
|
$surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]);
|
||||||
my @paths = $filler->fill_surface($surface, layer_height => 0.4, density => 0.4);
|
my $paths = $filler->fill_surface($surface, layer_height => 0.4, density => 0.4);
|
||||||
is scalar @paths, 1, 'one continuous path';
|
is scalar @$paths, 1, 'one continuous path';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,10 +115,10 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
|
|||||||
my $test = sub {
|
my $test = sub {
|
||||||
my ($expolygon, $flow_spacing, $angle, $density) = @_;
|
my ($expolygon, $flow_spacing, $angle, $density) = @_;
|
||||||
|
|
||||||
my $filler = Slic3r::Fill::Rectilinear->new(
|
my $filler = Slic3r::Filler->new_from_type('rectilinear');
|
||||||
bounding_box => $expolygon->bounding_box,
|
$filler->set_bounding_box($expolygon->bounding_box);
|
||||||
angle => $angle // 0,
|
$filler->set_angle($angle // 0);
|
||||||
);
|
$filler->set_dont_adjust(0);
|
||||||
my $surface = Slic3r::Surface->new(
|
my $surface = Slic3r::Surface->new(
|
||||||
surface_type => S_TYPE_BOTTOM,
|
surface_type => S_TYPE_BOTTOM,
|
||||||
expolygon => $expolygon,
|
expolygon => $expolygon,
|
||||||
@ -73,15 +128,15 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
|
|||||||
height => 0.4,
|
height => 0.4,
|
||||||
nozzle_diameter => $flow_spacing,
|
nozzle_diameter => $flow_spacing,
|
||||||
);
|
);
|
||||||
$filler->spacing($flow->spacing);
|
$filler->set_min_spacing($flow->spacing);
|
||||||
my @paths = $filler->fill_surface(
|
my $paths = $filler->fill_surface(
|
||||||
$surface,
|
$surface,
|
||||||
layer_height => $flow->height,
|
layer_height => $flow->height,
|
||||||
density => $density // 1,
|
density => $density // 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
# check whether any part was left uncovered
|
# 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);
|
my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ], 1);
|
||||||
|
|
||||||
# ignore very small dots
|
# ignore very small dots
|
||||||
@ -93,8 +148,9 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
|
|||||||
require "Slic3r/SVG.pm";
|
require "Slic3r/SVG.pm";
|
||||||
Slic3r::SVG::output(
|
Slic3r::SVG::output(
|
||||||
"uncovered.svg",
|
"uncovered.svg",
|
||||||
expolygons => [$expolygon],
|
expolygons => [$expolygon],
|
||||||
red_expolygons => $uncovered,
|
red_expolygons => $uncovered,
|
||||||
|
polylines => $paths,
|
||||||
);
|
);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
@ -116,7 +172,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
|
|||||||
$expolygon = Slic3r::ExPolygon->new(
|
$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]]
|
[[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] ]);
|
$expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [98,0], [98,10], [0,10] ]);
|
||||||
$test->($expolygon, 0.5, 45, 0.99); # non-solid infill
|
$test->($expolygon, 0.5, 45, 0.99); # non-solid infill
|
||||||
|
1
t/flow.t
1
t/flow.t
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first sum);
|
use List::Util qw(first sum);
|
||||||
|
1
t/gaps.t
1
t/gaps.t
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first);
|
use List::Util qw(first);
|
||||||
|
73
t/gcode.t
73
t/gcode.t
@ -1,10 +1,11 @@
|
|||||||
use Test::More tests => 23;
|
use Test::More tests => 27;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first);
|
use List::Util qw(first);
|
||||||
@ -215,4 +216,74 @@ use Slic3r::Test;
|
|||||||
ok !$spiral, 'spiral vase is correctly disabled on layers with multiple loops';
|
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__
|
__END__
|
||||||
|
@ -7,6 +7,7 @@ plan tests => 42;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use Slic3r;
|
use Slic3r;
|
||||||
|
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first);
|
use List::Util qw(first);
|
||||||
|
@ -8,6 +8,7 @@ plan tests => 4;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use Slic3r;
|
use Slic3r;
|
||||||
|
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first);
|
use List::Util qw(first);
|
||||||
|
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use Slic3r::ExtrusionLoop ':roles';
|
use Slic3r::ExtrusionLoop ':roles';
|
||||||
|
@ -7,6 +7,7 @@ plan tests => 18;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use Slic3r;
|
use Slic3r;
|
||||||
|
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw();
|
use List::Util qw();
|
||||||
|
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first);
|
use List::Util qw(first);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use Test::More tests => 22;
|
use Test::More tests => 26;
|
||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(any);
|
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 = (1); # ignore the first travel move from home to first point
|
||||||
my @retracted_length = (0);
|
my @retracted_length = (0);
|
||||||
my $lifted = 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 $changed_tool = 0;
|
||||||
my $wait_for_toolchange = 0;
|
my $wait_for_toolchange = 0;
|
||||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
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 'only lifting while retracted' if !$retracted[$tool];
|
||||||
fail 'double lift' if $lifted;
|
fail 'double lift' if $lifted;
|
||||||
$lifted = 1;
|
$lifted = 1;
|
||||||
|
$lift_dist = $info->{dist_Z};
|
||||||
}
|
}
|
||||||
if ($info->{dist_Z} < 0) {
|
if ($info->{dist_Z} < 0) {
|
||||||
fail 'going down only after lifting' if !$lifted;
|
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'
|
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))
|
if !_eq($info->{dist_Z}, -$lift_dist)
|
||||||
&& !_eq($info->{dist_Z}, -$print->print->config->get_at('retract_lift', $tool) + $conf->layer_height);
|
&& !_eq($info->{dist_Z}, -lift_dist + $conf->layer_height);
|
||||||
|
$lift_dist = 0;
|
||||||
$lifted = 0;
|
$lifted = 0;
|
||||||
}
|
}
|
||||||
fail 'move Z at travel speed' if ($args->{F} // $self->F) != $conf->travel_speed * 60;
|
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]);
|
$conf->set('retract_restart_extra', [-1]);
|
||||||
ok $test->($conf), "negative restart extra length$descr";
|
ok $test->($conf), "negative restart extra length$descr";
|
||||||
|
|
||||||
$conf->set('retract_lift', [1]);
|
$conf->set('retract_lift', [1, 2]);
|
||||||
ok $test->($conf), "lift$descr";
|
ok $test->($conf), "lift$descr";
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -204,7 +208,7 @@ use Slic3r::Test qw(_eq);
|
|||||||
{
|
{
|
||||||
my $config = Slic3r::Config->new_from_defaults;
|
my $config = Slic3r::Config->new_from_defaults;
|
||||||
$config->set('start_gcode', '');
|
$config->set('start_gcode', '');
|
||||||
$config->set('retract_lift', [3]);
|
$config->set('retract_lift', [3, 4]);
|
||||||
|
|
||||||
my @lifted_at = ();
|
my @lifted_at = ();
|
||||||
my $test = sub {
|
my $test = sub {
|
||||||
@ -219,19 +223,36 @@ use Slic3r::Test qw(_eq);
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$config->set('retract_lift_above', [0]);
|
$config->set('retract_lift_above', [0, 0]);
|
||||||
$config->set('retract_lift_below', [0]);
|
$config->set('retract_lift_below', [0, 0]);
|
||||||
$test->();
|
$test->();
|
||||||
ok !!@lifted_at, 'lift takes place when above/below == 0';
|
ok !!@lifted_at, 'lift takes place when above/below == 0';
|
||||||
|
|
||||||
$config->set('retract_lift_above', [5]);
|
$config->set('retract_lift_above', [5, 6]);
|
||||||
$config->set('retract_lift_below', [15]);
|
$config->set('retract_lift_below', [15, 13]);
|
||||||
$test->();
|
$test->();
|
||||||
ok !!@lifted_at, 'lift takes place when above/below != 0';
|
ok !!@lifted_at, 'lift takes place when above/below != 0';
|
||||||
ok !(any { $_ < $config->get_at('retract_lift_above', 0) } @lifted_at),
|
ok !(any { $_ < $config->get_at('retract_lift_above', 0) } @lifted_at),
|
||||||
'Z is not lifted below the configured value';
|
'Z is not lifted below the configured value';
|
||||||
ok !(any { $_ > $config->get_at('retract_lift_below', 0) } @lifted_at),
|
ok !(any { $_ > $config->get_at('retract_lift_below', 0) } @lifted_at),
|
||||||
'Z is not lifted above the configured value';
|
'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__
|
__END__
|
||||||
|
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first sum);
|
use List::Util qw(first sum);
|
||||||
|
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first);
|
use List::Util qw(first);
|
||||||
|
@ -8,6 +8,7 @@ plan tests => 16;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
# temporarily disable compilation errors due to constant not being exported anymore
|
# temporarily disable compilation errors due to constant not being exported anymore
|
||||||
|
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first);
|
use List::Util qw(first);
|
||||||
|
1
t/svg.t
1
t/svg.t
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use Slic3r;
|
use Slic3r;
|
||||||
|
1
t/thin.t
1
t/thin.t
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use Slic3r;
|
use Slic3r;
|
||||||
|
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use List::Util qw(first);
|
use List::Util qw(first);
|
||||||
|
@ -5,6 +5,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use Slic3r;
|
use Slic3r;
|
||||||
|
@ -7,6 +7,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use File::Basename qw(basename);
|
use File::Basename qw(basename);
|
||||||
|
@ -1 +1 @@
|
|||||||
@perl5.22.2.exe slic3r.pl %*
|
@perl5.24.0.exe slic3r.pl %*
|
||||||
|
@ -9,6 +9,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use Getopt::Long qw(:config no_auto_abbrev);
|
use Getopt::Long qw(:config no_auto_abbrev);
|
||||||
|
@ -8,6 +8,7 @@ use warnings;
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
use FindBin;
|
use FindBin;
|
||||||
use lib "$FindBin::Bin/../lib";
|
use lib "$FindBin::Bin/../lib";
|
||||||
|
use local::lib "$FindBin::Bin/../local-lib";
|
||||||
}
|
}
|
||||||
|
|
||||||
use Slic3r;
|
use Slic3r;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user