Merge pull request #4491 from lordofhyphens/cppgui

Cppgui - Intermediate merge
This commit is contained in:
Joseph Lenox 2018-07-27 21:47:22 -05:00 committed by GitHub
commit 44d145d986
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
90 changed files with 8719 additions and 507 deletions

3
.gitignore vendored
View File

@ -23,3 +23,6 @@ core
CMakeCache.txt
*.cmake
*.exe
src/test/test_options.hpp
src/slic3r
build/*

View File

@ -4,12 +4,12 @@ before_install:
script:
- bash package/linux/travis-setup.sh
- if [[ "$BUILD_PL" == false ]]; then mkdir build && cd build; fi
- if [[ "$BUILD_PL" == false ]]; then cmake -DBOOST_ROOT=$BOOST_DIR -DSLIC3R_STATIC=ON ../src; fi
- if [[ "$BUILD_PL" == false ]]; then cmake -DBOOST_ROOT=$BOOST_DIR -DSLIC3R_STATIC=ON -DCMAKE_BUILD_TYPE=Release ../src; fi
- if [[ "$BUILD_PL" == false ]]; then cmake --build .; fi
- if [[ "$BUILD_PL" == false ]]; then ctest . -R TestSlic3r; fi
- if [[ "$BUILD_PL" == false ]]; then ./slic3r_test -s; fi
- if [[ "$BUILD_PL" == true ]]; then perlbrew switch slic3r-perl; fi
- if [[ "$BUILD_PL" == true ]]; then perl ./Build.PL; fi
# - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ctest . -R TestGUI; fi
# - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ./gui_test -s; fi
branches:
only:
- master
@ -62,6 +62,7 @@ matrix:
- package/deploy/sftp-symlink.sh linux ~/slic3r-upload.rsa tar.bz2 *.bz2
- os: linux
env:
- BUILD_PL=false
- WXVERSION=pkg
- CC=gcc-7
- CXX=g++-7
@ -79,11 +80,13 @@ matrix:
- package/deploy/sftp-symlink.sh linux ~/slic3r-upload.rsa AppImage Slic3r*.AppImage
- package/deploy/sftp-symlink.sh linux ~/slic3r-upload.rsa tar.bz2 *.bz2
- os: osx
osx_image: xcode9.4
before_install:
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ( sudo Xvfb :99 -ac -screen 0 1024x768x8; echo ok )& fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update -v ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install ccache; fi
env:
- BUILD_PL=false
- WXVERSION=pkg
- BOOST_DIR=$HOME/boost_1_63_0
- DISPLAY=:99.0

View File

@ -40,7 +40,7 @@ See the [project homepage](http://slic3r.org/) at slic3r.org for more informatio
### What language is it written in?
The core parts of Slic3r are written in C++11, with multithreading. The graphical interface is in the process of being ported to C++11.
The core parts of Slic3r are written in C++11, with multithreading. The graphical interface is in the process of being ported to C++14.
### How to install?
@ -57,23 +57,26 @@ Sure! You can do the following to find things that are available to help with:
* Development
* [Low Effort tasks](https://github.com/alexrj/Slic3r/labels/Low%20Effort): pick one of them!
* [More available tasks](https://github.com/alexrj/Slic3r/milestone/31): let's discuss together before you start working on them
* [Help Wanted tasks](https://github.com/alexrj/Slic3r/labels/help%20wanted): pick one of them!
* [More available tasks](https://github.com/alexrj/Slic3r/milestone/32): let's discuss together before you start working on them
* Please comment in the related GitHub issue that you are working on it so that other people know.
* Contribute to the [Manual](http://manual.slic3r.org/)! (see its [GitHub repository](https://github.com/alexrj/Slic3r-Manual))
* You can also find us in #slic3r on [FreeNode](https://webchat.freenode.net): talk to _Sound_, _LoH_ or the other members of the Slic3r community.
* Add an [issue](https://github.com/alexrj/Slic3r/issues) to the GitHub tracker if it isn't already present.
* Drop Alessandro a line at aar@cpan.org.
* Drop Joseph a line at lenox.joseph@gmail.com
### Directory structure
* `package/`: the scripts used for packaging the executables
* `src/`: the C++ source of the `slic3r` executable the and CMake definition file for compiling it
* `src/GUI`: The C++ GUI.
* `t/`: the test suite
* `src/test`: New test suite for libslic3r and the GUI. Implemented with [Catch2](https://github.com/catchorg/Catch2)
* `t/`: the test suite (deprecated)
* `utils/`: various useful scripts
* `xs/src/libslic3r/`: C++ sources for libslic3r
* `xs/t/`: test suite for libslic3r
* `xs/xsp/`: bindings for calling libslic3r from Perl (XS)
* `xs/t/`: test suite for libslic3r (deprecated)
* `xs/xsp/`: bindings for calling libslic3r from Perl (XS) (deprecated)
### Acknowledgements

View File

@ -1,273 +0,0 @@
How to build Slic3r on Mac OS X 10.7 Lion 64bit
---------------------------------------------
Vojtech Bubnik, 2016-04-26
1) Install Mac OS X 10.7 Lion 64 bit with X Code
------------------------------------------------
One has to build the OSX Slic3r on a real Mac, either directly on the system, or on a virtualized OSX. On Mac, two commercial solutions are available to legally virtualize MacOS on MacOS:
http://www.parallels.com/eu/products/desktop/
http://www.vmware.com/products/workstation/
Installation of a X Code on an OS X 10.7 Lion needs a bit of work. The latest X Code supported by the Lion on a Virtual Box is 4.21. The trouble is, the certificates of the X Code 4.21 installation package expired. One way to work around the certificate is to flatten the installation package by unpacking and repacking it:
pkgutil --expand Foobar.pkg foobar
pkgutil --flatten foobar barfoo.pkg
The flattened package is available here:
\\rs.prusa\Development\Slic3r-Prusa\installxcode_421_lion_fixed.pkg
This installer does not install the X Code directly. Instead, it installs another installer with a set of 47 pkg files. These files have their certificates expired as well. You will find the packages on your MacOS here:
/Applications/Install Xcode.app/Contents/Resources/Packages/
It is best to flatten them in a loop:
cd /Applications/Install\ Xcode.app/Contents/Resources/Packages/
for f in *.pkg; do
pkgutil --expand $f /tmp/$f
rm -f $f
pkgutil --flatten /tmp/$f $f
done
After that, you may finish the installation of Xcode by running
/Applications/Install\ Xcode.app
1b) Installing the Xcode on a newer system
-------------------------------------------
You will need to register as an Apple developer on
https://developer.apple.com/
log in and download and install Xcode
https://developer.apple.com/downloads/
You will likely need to download and install Xcode Command Line Tools, though the Xcode 4.1 came with the command line tools installed.
2) Prepare the development environment
--------------------------------------
Install the brew package manager:
http://brew.sh/
The brew package manager requires the git command line tool. Normally the git tool is installed as part of the Xcode command line tools.
Copy and execute a command line from the top of http://brew.sh/ . It is possible, that the invocation of git fails because of some parameters the old git does not recognize. If so, invoke the git call manually.
Compile the boost library using brew. Following line compiles a 64bit boost with both static and shared libraries.
brew install boost --universal
Install dylibbundler tool. The dylibbundler tool serves to collect dependent dynamic libraries and fix their linkage. Execute
brew install dylibbundler
3) Install perl
---------------
We don't want to distribute perl pre-installed on the Mac OS box. First, the system perl installation is not correct on some Mac OS versions, second it is not rellocatable. To compile a 64bit rellocatable perl, we use the perlbrew distribution. The perlbrew distribution installs into a user home directory and it allows switching between multiple versions of perl.
http://perlbrew.pl/
First install perlbrew
\curl -L http://install.perlbrew.pl | bash
Then compile the newest perl with the rellocatable @INC path and with multithreading enabled, execute following line:
perlbrew install --threads -Duserelocatableinc --switch perl-5.22.0
The --switch parameter switches the active perl to the currently compiled one.
Available perl versions could be listed by calling
perlbrew available
Initialize CPAN, install PAR and PAR::Packer modules
execute cpan command, from the cpan prompt, run
install App::cpanminus
install PAR
install PAR::Packer
quit
4) Download and install Slic3r
------------------------------
git clone git://github.com/alexrj/Slic3r
cd Slic3r
perl Build.PL
Now Slic3r shall be compiled. You may try to execute
perl slic3r.pl
to get a help screen, or
perl slic3r.pl some_model.stl
to have the model sliced.
5) Download and compile the GUI libraries needed to execute Slic3r in GUI mode
------------------------------------------------------------------------------
For the current Slic3r 1.2.30 code base, set the environment variable SLIC3R_STATIC to link a static version of the boost library:
export SLIC3R_STATIC=1
then execute
perl Build.PL --gui
and keep your fingers crossed. The Build.PL script downloads and compiles the WxWidgets 3.0 through a Alien::Wx PERL package. The WxWidget shared libraries will land into
~/perl5/perlbrew/perls/perl-5.22.1/lib/site_perl/5.22.1/darwin-thread-multi-2level/Alien/wxWidgets/
On Maverics, we experienced following issue compiling WxPerl:
http://wiki.bolay.net/doku.php?id=acdsn:acdsn-a:mac
Now you could run the GUI version of slic3r by calling
perl slic3r.pl --gui
If some dependency is missing, the MacOS system will let you know.
6) Packing the Slic3r
---------------------
Perl is an operating system on its own. Many modules are shared among multiple applications and it is difficult to extract a stand-alone application from a perl installation manually. Fortunately, tools are available, which automate the process to some extent. One of the tools is the PAR::Packer. The PAR::Packer tool (pp executable) is able to create a standalone executable for a perl script. The standalone executable contains a PAR archive (a zip file) bundled with a perl interpreter. When executed, the bundled executable will decompress most of the PAR archive into a temp folder. Because of that, we will use the PAR::Packer to resolve and collect the dependencies, but we will create an installer manually.
The PAR::Packer could analyze the dependencies by a statical analysis, or at a runtime. The statical analysis does not resolve the dynamically loaded modules. On the other side, the statical analysis is pessimistic, therefore it often collects unneeded packages. The dynamic analysis may miss some package, if not all branches of a code are executed. We will try to solely depend on the dynamic analysis to keep the installation size minimal. We may need to develop a protocol or an automatic UI tool to exercise as much as possible from the Slic3r GUI to pack the GUI version reliably. Once a reliable list of dependencies is collected, we may not need the PAR::Packer anymore.
To create a PAR archive of a command line slic3r, execute
pp -e -p -x slic3r.pl --xargs cube.stl -o slic3r.par
and let the slic3r slice a cube.stl to load the dynamic modules.
To create a PAR archive of a GUI slic3r, execute
pp -e -p -x slic3r.pl --xargs --gui -o slic3r.par
and exercise the slic3r gui to load all modules.
Rename the slic3r.par file to slic3r.zip and decompress. Most of the code needed to execute Slic3r is there, only the perl executable is missing for the command line slic3r, and the WxWidgets shared libraries and the liblzma shared library are missing for the GUI version.
7) Collecting the dependent shared libraries, making the link paths relative
----------------------------------------------------------------------------
We have linked Slic3r against a static boost library, therefore the command line slic3r is not dependent on any non-system shared library. The situation is different for the GUI slic3r, which links dynamically against WxWidgets and liblzma.
The trick developed by Apple to allow rellocable shared libraries is to addres a shared library relatively to the path of the executable by encoding a special token @executable_path at the start of the path. Unfortunately the libraries requried by Slic3r are compiled with absolute paths.
Once the slic3r.par archive is unpacked, one may list the native shared libraries by
find ./ -name '*.bundle'
and one may list the dependencies by running
otool -L somefile.bundle
Most of the dependencies point to system directores and these dependences are always fulfilled. Dependencies pointing to the WxWidget libraries need to be fixed. These have a form
~/perlbrew/perls/perl-5.22.1/lib/site_perl/5.22.1/darwin-thread-multi-2level/Alien/wxWidgets/osx_cocoa_3_0_2_uni/lib/libwx_*.dylib
and we need to replace them with
@executable_path/../Frameworks/libwx_*.dylib
Another dependency, which needs our attention is
/usr/local/Cellar/xz/5.2.2/lib/liblzma.5.dylib
Fortunately, a tool dylibbundler was developed to address this problem.
First install dylibbundler by calling
brew dylibbundler
For some installations, the dylibbundler tool sufficiently fixes all dependencies. Unfortunately, the WxWidgets installation is inconsistent in the versioning, therefore a certain clean-up is required. Namely, the WxWidgets libraries are compiled with the full build number in their file name. For example, the base library is built as libwx_baseu-3.0.0.2.0.dylib and a symlink is created libwx_baseu-3.0.dylib pointing to the full name. Then some of the Wx libraries link against the full name and some against the symlink, leading the dylibbundler to pack both. We solved the problem by whipping up a following script:
\\rs.prusa\Development\Slic3r-Prusa\How_to_build_on_MacOSX_Lion\fix_dependencies.sh
call
slic3r_dependencies.sh --fix
to collect the shared libraries into Content/Frameworks and to fix their linkage.
call
slic3r_dependencies.sh --show
to list dependent libraries in a sorted order. All the non-system dependencies shall start with @executable_path after the fix.
8) Packing Slic3r into a dmg image using a bunch of scripts
-----------------------------------------------------------
Instead of relying on the PAR::Packer to collect the dependencies, we have used PAR::Packer to extract the dependencies, we manually cleaned them up and created an installer script.
\\rs.prusa\Development\Slic3r-Prusa\How_to_build_on_MacOSX_Lion\Slic3r-Build-MacOS
First compile Slic3r, then call build_dmg.sh with a path to the Slic3r source tree as a parameter.
The script will collect all dependencies into Slic3r.app and it will create Slic3r.dmg.
If SLIC3R_GUI variable is defined, a GUI variant of Slic3r will be packed.
How to build on Windows
-----------------------
The prefered perl distribution on MS Windows is the Strawberry Perl 5.22.1.3 (32bit)
http://strawberryperl.com/
Let it install into c:\strawberry
You may make a copy of the distribution. We recommend to make following copies:
For a release command line only build: c:\strawberry-minimal
For a release GUI build: c:\strawberry-minimal-gui
For a development build with debugging information: c:\strawberry-debug
and to make one of them active by making a directory junction:
mklink /d c:\Strawberry c:\Strawberry-debug
Building boost:
Slic3r seems to have a trouble with the latest boost 1.60.0 on Windows. Please use 1.59.
Decompress it to
c:\dev\
otherwise it will not be found by the Build.PL on Windows. You may consider to hack xs\Build.PL with your prefered boost path.
run
bootstrap.bat mingw
b2 toolset=gcc
Install git command line
https://git-scm.com/
Download Slic3r source code
git clone git://github.com/alexrj/Slic3r.git
Run compilation
cd Slic3r
perl Build.PL
perl Build.PL --gui
With a bit of luck, you will end up with a working Slic3r including GUI.
Packing on Windows
------------------
Life is easy on Windows. PAR::Packer will help again to collect the dependencies. The basic procedure is the same as for MacOS:
To create a PAR archive of a command line slic3r, execute
pp -e -p -x slic3r.pl --xargs cube.stl -o slic3r.par
and let the slic3r slice a cube.stl to load the dynamic modules.
To create a PAR archive of a GUI slic3r, execute
pp -e -p -x slic3r.pl --xargs --gui -o slic3r.par
and exercise the slic3r gui to load all modules.
The standalone installation is then created from the PAR archive by renaming it into a zip and adding following binaries from c:\strawberry to the root of the extracted zip:
perl5.22.1.exe
perl522.dll
libgcc_s_sjlj-1.dll
libstdc++-6.dll
libwinpthread-1.dll
The GUI build requires following DLLs in addition:
libglut-0_.dll
wxbase30u_gcc_custom.dll
wxmsw30u_adv_gcc_custom.dll
wxmsw30u_core_gcc_custom.dll
wxmsw30u_gl_gcc_custom.dll
wxmsw30u_html_gcc_custom.dll
and the var directory with the icons needs to be copied to the destination directory.
To run the slic3r, move the script\slic3r.pl one level up and create a following tiny windows batch in the root of the unpacked zip:
@perl5.22.1.exe slic3r.pl %*
A windows shortcut may be created for the GUI version. Instead of the perl.exe, it is better to use the wperl.exe to start the GUI Slic3r, because it does not open a text console.
The strawberry perl is already rellocatable, which means that the perl interpreter will find the perl modules in the lib directory,
and Windows look up the missing DLLs in the directory of the executable, therefore no further rellocation effort is necessary.
Packing on Windows, a single EXE solution
-----------------------------------------
One may try to create a PAR executable for command line slic3r:
pp -M Encode::Locale -M Moo -M Thread::Semaphore -M Slic3r::XS -M Unicode::Normalize -o slic3r.exe slic3r.pl
One may as well create a PAR executable for Windows GUI:
pp -M Encode::Locale -M Moo -M Thread::Semaphore -M OpenGL -M Slic3r::XS -M Unicode::Normalize -M Wx -M Class::Accessor -M Wx::DND -M Wx::Grid -M Wx::Print -M Wx::Html -M Wx::GLCanvas -M Math::Trig -M threads -M threads::shared -M Thread::Queue -l C:\strawberry\perl\site\lib\auto\Wx\Wx.xs.dll -o -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxbase30u_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_core_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_gl_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_adv_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_html_gcc_custom.dll -o slic3r.exe slic3r.pl
Remember, that these executables will unpack into a temporary directory. The directory may be declared by setting an environment variable PAR_GLOBAL_TEMP. Otherwise the temporaries are unpacked into
C:\Users\xxx\AppData\Local\Temp\par-xxxxxx
Debugging the perl, debugging on Windows
----------------------------------------
It is possible to debug perl using the integrated debugger. The EPIC plugin for Eclipse works very well with an older eclipse-SDK-3.6.2. There is a catch though: The Perl debugger does not work correctly with multiple threads running under the Perl interpreter. If that happens, the EPIC plugin gets confused and the debugger stops working. The same happens with the Komodo IDE.
Debugging the C++ code works fine using the latest Eclipse for C++ and the gdb of MinGW. The gdb packed with the Strawberry distribution does not contain the Python support, so pretty printing of the stl containers only works if another gdb build is used. The one of the QT installation works well.
It is yet a bit more complicated. The Strawberry MINGW is compiled for a different C++ exception passing model (SJLJ) than the other MINGWs, so one cannot simply combine MINGWs on Windows. For an unknown reason the nice debugger of the QT Creator hangs when debugging the C++ compiled by the Strawberry MINGW. Mabe it is because of the different exception passing models.
And to disable optimization of the C/C++ code, one has to manually modify Config_heavy.pl in the Perl central installation. The SLIC3R_DEBUG environment variable did not override all the -O2 and -O3 flags that the perl build adds the gcc execution line.

View File

@ -60,7 +60,7 @@ sub generate {
$self->clip_with_shape($interface, $shape) if @$shape;
# Propagate contact layers and interface layers downwards to generate
# the main support layers.
# the main support layers.
my ($base) = $self->generate_base_layers($support_z, $contact, $interface, $top);
$self->clip_with_object($base, $support_z, $object);
$self->clip_with_shape($base, $shape) if @$shape;
@ -108,7 +108,7 @@ sub contact_area {
# determine contact areas
my %contact = (); # contact_z => [ polygons ]
my %overhang = (); # contact_z => [ polygons ] - this stores the actual overhang supported by each contact layer
my %overhang = (); # contact_z => [ polygons ] - this stores the actual overhang supported by each contact layer
for my $layer_id (0 .. $#{$object->layers}) {
# note $layer_id might != $layer->id when raft_layers > 0
# so $layer_id == 0 means first object layer
@ -224,7 +224,7 @@ sub contact_area {
# Get all perimeters as polylines.
# TODO: split_at_first_point() (called by as_polyline() for ExtrusionLoops)
# could split a bridge mid-way
# could split a bridge mid-way
my @overhang_perimeters = map $_->as_polyline, @{$layerm->perimeters->flatten};
# Only consider the overhang parts of such perimeters,
@ -374,7 +374,7 @@ sub object_top {
# we considered)
my $min_top = min(keys %top) // max(keys %$contact);
# use <= instead of just < because otherwise we'd ignore any contact regions
# having the same Z of top layers
# having the same Z of top layers
push @$projection, map @{$contact->{$_}}, grep { $_ > $layer->print_z && $_ <= $min_top } keys %$contact;
# now find whether any projection falls onto this top surface
@ -511,7 +511,7 @@ sub generate_bottom_interface_layers {
my $interface_layers = 0;
# loop through support layers until we find the one(s) right above the top
# surface
# surface
foreach my $layer_id (0 .. $#$support_z) {
my $z = $support_z->[$layer_id];
next unless $z > $top_z;
@ -581,7 +581,7 @@ sub generate_base_layers {
# This method removes object silhouette from support material
# (it's used with interface and base only). It removes a bit more,
# leaving a thin gap between object and support in the XY plane.
# leaving a thin gap between object and support in the XY plane.
sub clip_with_object {
my ($self, $support, $support_z, $object) = @_;
@ -595,7 +595,7 @@ sub clip_with_object {
# $layer->slices contains the full shape of layer, thus including
# perimeter's width. $support contains the full shape of support
# material, thus including the width of its foremost extrusion.
# material, thus including the width of its foremost extrusion.
# We leave a gap equal to a full extrusion width.
$support->{$i} = diff(
$support->{$i},
@ -825,7 +825,7 @@ sub generate_toolpaths {
$base_flow = $self->first_layer_flow;
# use the proper spacing for first layer as we don't need to align
# its pattern to the other layers
# its pattern to the other layers
$filler->set_min_spacing($base_flow->spacing);
# subtract brim so that it goes around the object fully (and support gets its own brim)

View File

@ -32,7 +32,7 @@ cd $WD/${APP}.AppDir
mkdir -p $WD/${APP}.AppDir/usr/bin
# Copy primary Slic3r script here and perl-local, as well as var
for i in {var,slic3r.pl,perl-local}; do
for i in {var,Slic3r}; do
cp -R $srcfolder/$i $WD/${APP}.AppDir/usr/bin/
done
@ -48,8 +48,6 @@ for i in $(cat $WD/libpaths.appimage.txt | grep -v "^#" | awk -F# '{print $1}');
done
cp -R $srcfolder/local-lib ${WD}/${APP}.AppDir/usr/lib/local-lib
cat > $WD/${APP}.AppDir/AppRun << 'EOF'
#!/usr/bin/env bash
# some magic to find out the real location of this script dealing with symlinks

View File

@ -1,8 +1,3 @@
/home/travis/builds/alexrj/Slic3r/local-lib/lib/perl5/x86_64-linux-thread-multi/Alien/wxWidgets/gtk_3_0_2_uni/lib/libwx_baseu-3.0.so.0
/home/travis/builds/alexrj/Slic3r/local-lib/lib/perl5/x86_64-linux-thread-multi/Alien/wxWidgets/gtk_3_0_2_uni/lib/libwx_gtk2u_adv-3.0.so.0
/home/travis/builds/alexrj/Slic3r/local-lib/lib/perl5/x86_64-linux-thread-multi/Alien/wxWidgets/gtk_3_0_2_uni/lib/libwx_gtk2u_core-3.0.so.0
/home/travis/builds/alexrj/Slic3r/local-lib/lib/perl5/x86_64-linux-thread-multi/Alien/wxWidgets/gtk_3_0_2_uni/lib/libwx_gtk2u_gl-3.0.so.0
/home/travis/builds/alexrj/Slic3r/local-lib/lib/perl5/x86_64-linux-thread-multi/Alien/wxWidgets/gtk_3_0_2_uni/lib/libwx_gtk2u_html-3.0.so.0
/lib/x86_64-linux-gnu/liblzma.so.5
/lib/x86_64-linux-gnu/libpng12.so.0
/usr/lib/x86_64-linux-gnu/libjpeg.so.8

View File

@ -10,7 +10,6 @@ if [ "$#" -ne 1 ]; then
echo "Usage: $(basename $0) arch_name"
exit 1;
fi
libdirs=$(find ./local-lib -iname *.so -exec dirname {} \; | sort -u | paste -sd ";" -)
WD=./$(dirname $0)
source $(dirname $0)/../common/util.sh
# Determine if this is a tagged (release) commit.
@ -22,7 +21,6 @@ set_build_id
set_branch
set_app_name
set_pr_id
install_par
# If we're on a branch, add the branch name to the app name.
if [ "$current_branch" == "master" ]; then
@ -35,8 +33,7 @@ else
dmgfile=slic3r-${SLIC3R_BUILD_ID}-${1}-${current_branch}.tar.bz2
fi
rm -rf $WD/_tmp
mkdir -p $WD/_tmp
mkdir -p $WD
# Set the application folder infomation.
appfolder="$WD/${appname}"
@ -46,8 +43,6 @@ resourcefolder=$appfolder
echo "Appfolder: $appfolder, archivefolder: $archivefolder"
# Our slic3r dir and location of perl
PERL_BIN=$(which perl)
PP_BIN=$(which pp)
SLIC3R_DIR="./"
if [[ -d "${appfolder}" ]]; then
@ -67,14 +62,12 @@ echo "Copying resources..."
cp -rf $SLIC3R_DIR/var $resourcefolder/
echo "Copying Slic3r..."
cp $SLIC3R_DIR/slic3r.pl $archivefolder/slic3r.pl
cp -fRP $SLIC3R_DIR/local-lib $archivefolder/local-lib
cp -fRP $SLIC3R_DIR/lib/* $archivefolder/local-lib/lib/perl5/
cp $SLIC3R_DIR/slic3r $archivefolder/Slic3r
mkdir $archivefolder/bin
echo "Installing libraries to $archivefolder/bin ..."
if [ -z ${WXDIR+x} ]; then
for bundle in $(find $archivefolder/local-lib/lib/perl5 -name '*.so' | grep "Wx") $(find $archivefolder/local-lib/lib/perl5 -name '*.so' -type f | grep "wxWidgets"); do
for bundle in $archivefolder/Slic3r; do
echo "$(LD_LIBRARY_PATH=$libdirs ldd $bundle | grep .so | grep local-lib | awk '{print $3}')"
for dylib in $(LD_LIBRARY_PATH=$libdirs ldd $bundle | grep .so | grep local-lib | awk '{print $3}'); do
install -v $dylib $archivefolder/bin
@ -91,37 +84,8 @@ echo "Copying startup script..."
cp -f $WD/startup_script.sh $archivefolder/$appname
chmod +x $archivefolder/$appname
echo "Copying perl from $PERL_BIN"
# Edit package/common/coreperl to add/remove core Perl modules added to this package, one per line.
cp -f $PERL_BIN $archivefolder/perl-local
${PP_BIN} wxextension .0 \
-M $(grep -v "^#" ${WD}/../common/coreperl | xargs | awk 'BEGIN { OFS=" -M "}; {$1=$1; print $0}') \
-B -p -e "print 123" -o $WD/_tmp/test.par
unzip -qq -o $WD/_tmp/test.par -d $WD/_tmp/
cp -rf $WD/_tmp/lib/* $archivefolder/local-lib/lib/perl5/
cp -rf $WD/_tmp/shlib $archivefolder/
rm -rf $WD/_tmp
for i in $(cat $WD/libpaths.txt | grep -v "^#" | awk -F# '{print $1}'); do
install -v $i $archivefolder/bin
done
echo "Cleaning local-lib"
rm -rf $archivefolder/local-lib/bin
rm -rf $archivefolder/local-lib/man
rm -f $archivefolder/local-lib/lib/perl5/Algorithm/*.pl
rm -rf $archivefolder/local-lib/lib/perl5/unicore
rm -rf $archivefolder/local-lib/lib/perl5/App
rm -rf $archivefolder/local-lib/lib/perl5/Devel/CheckLib.pm
rm -rf $archivefolder/local-lib/lib/perl5/ExtUtils
rm -rf $archivefolder/local-lib/lib/perl5/Module/Build*
rm -rf $(pwd)$archivefolder/local-lib/lib/perl5/TAP
rm -rf $(pwd)/$archivefolder/local-lib/lib/perl5/Test*
find $(pwd)/$archivefolder/local-lib -type d -path '*/Wx/*' \( -name WebView \
-or -name DocView -or -name STC -or -name IPC \
-or -name Calendar -or -name DataView \
-or -name DateTime -or -name Media -or -name PerlTest \
-or -name Ribbon \) -exec rm -rf "{}" \;
rm -rf $archivefolder/local-lib/lib/perl5/*/Alien/wxWidgets/*/include
find $archivefolder/local-lib -depth -type d -empty -exec rmdir "{}" \;
tar -C$(pwd)/$(dirname $appfolder) -cjf $(pwd)/$dmgfile "$appname"

View File

@ -3,4 +3,4 @@
BIN=$(readlink "$0")
DIR=$(dirname "$BIN")
export LD_LIBRARY_PATH="$DIR/bin"
exec "$DIR/perl-local" -I"$DIR/local-lib/lib/perl5" "$DIR/slic3r.pl" $@
exec "$DIR/Slic3r"

View File

@ -7,7 +7,7 @@ if [[ "$BUILD_PL" == true ]]; then
WXVERSION=302
if [ ! -e $CACHE/slic3r-perlbrew-5.24.tar.bz2 ]; then
echo "Downloading http://www.siusgs.com/slic3r/buildserver/slic3r-perl.524.travis.tar.bz2 => $CACHE/slic3r-perlbrew-5.24.tar.bz2"
curl -L "http://www.siusgs.com/slic3r/buildserver/slic3r-perl.524.travis.tar.bz2" -o $CACHE/slic3r-perlbrew-5.24.tar.bz2;
curl -L "http://www.siusgs.com/slic3r/buildserver/slic3r-perl.524.gcc49.travis.tar.bz2" -o $CACHE/slic3r-perlbrew-5.24.tar.bz2;
fi
if [ ! -e $CACHE/boost-compiled.tar.bz2 ]; then
@ -16,7 +16,7 @@ if [[ "$BUILD_PL" == true ]]; then
fi
if [ ! -e $CACHE/local-lib-wx${WXVERSION}.tar.bz2 ]; then
echo "Downloading http://www.siusgs.com/slic3r/buildserver/slic3r-dependencies.travis-wx${WXVERSION}.tar.bz2 => $CACHE/local-lib-wx${WXVERSION}.tar.bz2"
echo "Downloading http://www.siusgs.com/slic3r/buildserver/slic3r-dependencies.gcc49.travis-wx${WXVERSION}.tar.bz2 => $CACHE/local-lib-wx${WXVERSION}.tar.bz2"
curl -L "http://www.siusgs.com/slic3r/buildserver/slic3r-dependencies.travis-wx${WXVERSION}.tar.bz2" -o $CACHE/local-lib-wx${WXVERSION}.tar.bz2
fi

View File

@ -8,7 +8,11 @@ option(SLIC3R_STATIC "Build and link Slic3r statically." ON)
option(BUILD_EXTRUDE_TIN "Build and link the extrude-tin application." OFF)
# only on newer GCCs: -ftemplate-backtrace-limit=0
set(CMAKE_CXX_FLAGS "-g ${CMAKE_CXX_FLAGS} -Wall -DM_PI=3.14159265358979323846 -D_GLIBCXX_USE_C99 -DHAS_BOOL -DNOGDI -DBOOST_ASIO_DISABLE_KQUEUE -DNO_PERL")
set(CMAKE_CXX_FLAGS_1 "${CMAKE_CXX_FLAGS} -Wall -DM_PI=3.14159265358979323846 -D_GLIBCXX_USE_C99 -DHAS_BOOL -DNOGDI -DBOOST_ASIO_DISABLE_KQUEUE -DNO_PERL")
# Flags for debug and release tuning
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0 ${CMAKE_CXX_FLAGS_1}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 ${CMAKE_CXX_FLAGS_1}")
if(DEFINED ENV{SLIC3R_VAR_REL})
set(CMAKE_CXX_FLAGS "-DVAR_REL=$ENV{SLIC3R_VAR_REL}")
@ -35,13 +39,7 @@ endif()
execute_process(COMMAND git rev-parse --short HEAD OUTPUT_VARIABLE GIT_VERSION ERROR_QUIET)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -DSLIC3R_DEBUG")
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.7.0)
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.7.3)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-inline-small-functions")
endif(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.7.3)
endif(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.7.0)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
@ -67,9 +65,14 @@ find_package(Boost REQUIRED COMPONENTS system thread filesystem)
set(LIBDIR ${CMAKE_CURRENT_SOURCE_DIR}/../xs/src/)
set(GUI_LIBDIR ${CMAKE_CURRENT_SOURCE_DIR}/GUI/)
set(TESTDIR ${CMAKE_CURRENT_SOURCE_DIR}/test)
set(GUI_TESTDIR ${CMAKE_CURRENT_SOURCE_DIR}/test/GUI/)
# directory that contains the dependent non-source files, like models and configurations
set(TESTFILE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/test/inputs/)
include_directories(${LIBDIR})
include_directories(${LIBDIR}/libslic3r)
include_directories(${LIBDIR}/slic3r/GUI/)
@ -84,7 +87,7 @@ include_directories(${LIBDIR}/poly2tri/common)
add_library(ZipArchive STATIC
${LIBDIR}/Zip/ZipArchive.cpp
)
target_compile_features(ZipArchive PUBLIC cxx_std_11)
add_library(libslic3r STATIC
${LIBDIR}/libslic3r/BoundingBox.cpp
@ -92,6 +95,7 @@ add_library(libslic3r STATIC
${LIBDIR}/libslic3r/ClipperUtils.cpp
${LIBDIR}/libslic3r/ConfigBase.cpp
${LIBDIR}/libslic3r/Config.cpp
${LIBDIR}/libslic3r/ConditionalGCode.cpp
${LIBDIR}/libslic3r/ExPolygon.cpp
${LIBDIR}/libslic3r/ExPolygonCollection.cpp
${LIBDIR}/libslic3r/Extruder.cpp
@ -106,6 +110,7 @@ add_library(libslic3r STATIC
${LIBDIR}/libslic3r/Fill/FillGyroid.cpp
${LIBDIR}/libslic3r/Flow.cpp
${LIBDIR}/libslic3r/GCode.cpp
${LIBDIR}/libslic3r/PrintGCode.cpp
${LIBDIR}/libslic3r/GCode/CoolingBuffer.cpp
${LIBDIR}/libslic3r/GCode/SpiralVase.cpp
${LIBDIR}/libslic3r/GCodeReader.cpp
@ -140,12 +145,11 @@ add_library(libslic3r STATIC
${LIBDIR}/libslic3r/SurfaceCollection.cpp
${LIBDIR}/libslic3r/SVG.cpp
${LIBDIR}/libslic3r/TriangleMesh.cpp
${LIBDIR}/libslic3r/SupportMaterial.cpp
${LIBDIR}/libslic3r/utils.cpp
)
target_compile_features(libslic3r PUBLIC cxx_std_11)
add_library(BSpline STATIC
${LIBDIR}/BSpline/BSpline.cpp
)
@ -161,11 +165,14 @@ add_library(admesh STATIC
set_property(TARGET admesh PROPERTY C_STANDARD 99)
add_library(clipper STATIC ${LIBDIR}/clipper.cpp)
add_library(expat STATIC
${LIBDIR}/expat/xmlparse.c
${LIBDIR}/expat/xmlrole.c
${LIBDIR}/expat/xmltok.c
)
target_compile_features(clipper PUBLIC cxx_std_11)
add_library(polypartition STATIC ${LIBDIR}/polypartition.cpp)
add_library(poly2tri STATIC
${LIBDIR}/poly2tri/common/shapes.cc
@ -175,8 +182,6 @@ add_library(poly2tri STATIC
${LIBDIR}/poly2tri/sweep/sweep.cc
)
set(UI_TEST_SOURCES
${GUI_TESTDIR}/testableframe.cpp
${GUI_TESTDIR}/test_harness_gui.cpp
@ -187,33 +192,68 @@ set(UI_TEST_SOURCES
${GUI_TESTDIR}/test_field_numchoice.cpp
${GUI_TESTDIR}/test_field_point.cpp
${GUI_TESTDIR}/test_field_point3.cpp
${GUI_TESTDIR}/test_field_colorpicker.cpp
${GUI_TESTDIR}/test_field_slider.cpp
${GUI_TESTDIR}/test_optionsgroup.cpp
${GUI_TESTDIR}/test_misc_ui.cpp
)
set(SLIC3R_TEST_SOURCES
${TESTDIR}/test_harness.cpp
${TESTDIR}/test_data.cpp
${TESTDIR}/libslic3r/test_trianglemesh.cpp
${TESTDIR}/libslic3r/test_config.cpp
${TESTDIR}/libslic3r/test_support_material.cpp
${TESTDIR}/libslic3r/test_flow.cpp
${TESTDIR}/libslic3r/test_model.cpp
${TESTDIR}/libslic3r/test_printgcode.cpp
${TESTDIR}/libslic3r/test_print.cpp
${TESTDIR}/libslic3r/test_skirt_brim.cpp
${TESTDIR}/libslic3r/test_test_data.cpp
${TESTDIR}/libslic3r/test_geometry.cpp
)
add_executable(slic3r slic3r.cpp)
target_compile_features(slic3r PUBLIC cxx_std_14)
#set_target_properties(slic3r PROPERTIES LINK_SEARCH_START_STATIC 1)
#set_target_properties(slic3r PROPERTIES LINK_SEARCH_END_STATIC 1)
if (${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_GREATER 3.10)
cmake_policy(SET CMP0072 NEW)
endif (${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_GREATER 3.10)
set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_STATIC_RUNTIME OFF)
find_package(OpenGL)
if(SLIC3R_STATIC)
set(Boost_USE_STATIC_LIBS ON)
set(Boost_USE_STATIC_RUNTIME ON)
else(SLIC3R_STATIC)
set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_STATIC_RUNTIME OFF)
endif(SLIC3R_STATIC)
find_library(bsystem_l boost_system log)
add_library(bsystem SHARED IMPORTED)
if(SLIC3R_STATIC)
add_library(bsystem STATIC IMPORTED)
else(SLIC3R_STATIC)
add_library(bsystem SHARED IMPORTED)
endif(SLIC3R_STATIC)
set_target_properties(bsystem PROPERTIES IMPORTED_LOCATION ${bsystem_l})
find_library(bthread_l boost_thread)
if(SLIC3R_STATIC)
add_library(bthread STATIC IMPORTED)
else(SLIC3R_STATIC)
add_library(bthread SHARED IMPORTED)
endif(SLIC3R_STATIC)
set_target_properties(bthread PROPERTIES IMPORTED_LOCATION ${bthread_l})
include_directories(${Boost_INCLUDE_DIRS})
if(${SLIC3R_STATIC})
if(SLIC3R_STATIC)
set(wxWidgets_USE_STATIC ON)
else(${SLIC3R_STATIC})
else(SLIC3R_STATIC)
set(wxWidgets_USE_STATIC OFF)
endif(${SLIC3R_STATIC})
endif(SLIC3R_STATIC)
set(wxWidgets_USE_UNICODE ON)
@ -258,10 +298,14 @@ IF(wxWidgets_FOUND)
${GUI_LIBDIR}/Dialogs/PrintEditor.cpp
${GUI_LIBDIR}/Dialogs/PrinterEditor.cpp
${GUI_LIBDIR}/Dialogs/MaterialEditor.cpp
${GUI_LIBDIR}/Dialogs/ObjectCutDialog.cpp
${GUI_LIBDIR}/GUI.cpp
${GUI_LIBDIR}/MainFrame.cpp
${GUI_LIBDIR}/Plater.cpp
${GUI_LIBDIR}/Scene3D.cpp
${GUI_LIBDIR}/Plater/Plate2D.cpp
${GUI_LIBDIR}/Plater/Plate3D.cpp
${GUI_LIBDIR}/Plater/Preview3D.cpp
${GUI_LIBDIR}/Plater/PlaterObject.cpp
${GUI_LIBDIR}/ProgressStatusBar.cpp
${GUI_LIBDIR}/Settings.cpp
@ -270,10 +314,13 @@ IF(wxWidgets_FOUND)
${GUI_LIBDIR}/OptionsGroup/UI_Choice.cpp
${GUI_LIBDIR}/OptionsGroup/UI_Point.cpp
${GUI_LIBDIR}/OptionsGroup/UI_Point3.cpp
${GUI_LIBDIR}/OptionsGroup/UI_Color.cpp
${GUI_LIBDIR}/OptionsGroup/UI_Slider.cpp
${LIBDIR}/slic3r/GUI/3DScene.cpp
)
target_compile_features(slic3r_gui PUBLIC cxx_std_14)
#only build GUI lib if building with wx
target_link_libraries (slic3r slic3r_gui ${wxWidgets_LIBRARIES})
target_link_libraries (slic3r slic3r_gui ${wxWidgets_LIBRARIES} ${OPENGL_LIBRARIES})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSE_WX")
if (GUI_BUILD_TESTS)
@ -309,7 +356,7 @@ IF(wxWidgets_FOUND)
endif()
add_executable(gui_test ${UI_TEST_SOURCES})
add_test(NAME TestGUI COMMAND gui_test)
target_link_libraries(gui_test PUBLIC libslic3r slic3r_gui Catch ${wxWidgets_LIBRARIES} ${LIBSLIC3R_DEPENDS})
target_link_libraries(gui_test PUBLIC libslic3r slic3r_gui Catch ${wxWidgets_LIBRARIES} ${LIBSLIC3R_DEPENDS} ${OPENGL_LIBRARIES})
endif()
ELSE(wxWidgets_FOUND)
# For convenience. When we cannot continue, inform the user
@ -375,8 +422,12 @@ if (SLIC3R_BUILD_TESTS)
target_include_directories(Catch INTERFACE ${CMAKE_BINARY_DIR}/external/Catch/include)
target_compile_definitions(Catch INTERFACE $<$<CXX_COMPILER_ID:MSVC>:_SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING>)
endif()
include_directories(${TESTDIR})
configure_file("${TESTDIR}/test_options.hpp.in" "${TESTDIR}/test_options.hpp")
add_executable(slic3r_test ${SLIC3R_TEST_SOURCES})
add_test(NAME TestSlic3r COMMAND slic3r_test)
target_compile_features(slic3r_test PUBLIC cxx_std_14)
target_link_libraries(slic3r_test PUBLIC libslic3r Catch ${LIBSLIC3R_DEPENDS})
endif()

View File

@ -7,7 +7,7 @@ class DefaultColor : public ColorScheme {
public:
const std::string name() const { return "Default"; }
const bool SOLID_BACKGROUNDCOLOR() const { return false; };
const wxColour SELECTED_COLOR() const { return wxColour(0, 1, 0); };
const wxColour SELECTED_COLOR() const { return wxColour(0, 255, 0); };
const wxColour HOVER_COLOR() const { return wxColour(255*0.4, 255*0.9, 0); }; //<Hover over Model
const wxColour TOP_COLOR() const { return wxColour(10,98,144); }; //<TOP Backgroud color
const wxColour BOTTOM_COLOR() const { return wxColour(0,0,0); }; //<BOTTOM Backgroud color
@ -15,10 +15,10 @@ public:
const wxColour GRID_COLOR() const { return wxColour(255*0.2, 255*0.2, 255*0.2, 255*0.4); }; //<Grid color
const wxColour GROUND_COLOR() const { return wxColour(255*0.8, 255*0.6, 255*0.5, 255*0.4); }; //<Ground or Plate color
const wxColour COLOR_CUTPLANE() const { return wxColour(255*0.8, 255*0.8, 255*0.8, 255*0.5); };
const wxColour COLOR_PARTS() const { return wxColour(1, 255*0.95, 255*0.2, 1); }; //<Perimeter color
const wxColour COLOR_INFILL() const { return wxColour(1, 255*0.45, 255*0.45, 1); };
const wxColour COLOR_SUPPORT() const { return wxColour(255*0.5, 1, 255*0.5, 1); };
const wxColour COLOR_UNKNOWN() const { return wxColour(255*0.5, 255*0.5, 1, 1); };
const wxColour COLOR_PARTS() const { return wxColour(255, 255*0.95, 255*0.2); }; //<Perimeter color
const wxColour COLOR_INFILL() const { return wxColour(255, 255*0.45, 255*0.45); };
const wxColour COLOR_SUPPORT() const { return wxColour(255*0.5, 1, 255*0.5); };
const wxColour COLOR_UNKNOWN() const { return wxColour(255*0.5, 255*0.5, 1); };
const wxColour BED_COLOR() const { return wxColour(255, 255, 255); };
const wxColour BED_GRID() const { return wxColour(230, 230, 230); };
const wxColour BED_SELECTED() const { return wxColour(255, 166, 128); };

View File

@ -0,0 +1,351 @@
#include "ObjectCutDialog.hpp"
// 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.
namespace Slic3r { namespace GUI {
ObjectCutDialog::ObjectCutDialog(wxWindow* parent, ModelObject* _model_object/*, ...*/): wxDialog(parent, -1, _(_model_object->name), wxDefaultPosition, wxSize(500, 500), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), model_object(_model_object) {
/*
$self->{model_object}->transform_by_instance($self->{model_object}->get_instance(0), 1);
// cut options
my $size_z = $self->{model_object}->instance_bounding_box(0)->size->z;
$self->{cut_options} = {
axis => Z,
z => $size_z/2,
keep_upper => 0,
keep_lower => 1,
rotate_lower => 0,
preview => 1,
};
my $optgroup;
$optgroup = $self->{optgroup} = Slic3r::GUI::OptionsGroup->new(
parent => $self,
title => 'Cut',
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.
if ($self->{cut_options}{$opt_id} != $optgroup->get_value($opt_id) ||
! $self->{mesh_cut_valid} && $self->_life_preview_active()) {
$self->{cut_options}{$opt_id} = $optgroup->get_value($opt_id);
$self->{mesh_cut_valid} = 0;
wxTheApp->CallAfter(sub {
$self->_update;
});
}
},
label_width => 120,
);
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'axis',
type => 'select',
label => 'Axis',
labels => ['X','Y','Z'],
values => [X,Y,Z],
default => $self->{cut_options}{axis},
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'z',
type => 'slider',
label => 'Z',
default => $self->{cut_options}{z},
min => 0,
max => $size_z,
full_width => 1,
));
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Keep',
);
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'keep_upper',
type => 'bool',
label => 'Upper part',
default => $self->{cut_options}{keep_upper},
));
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'keep_lower',
type => 'bool',
label => 'Lower part',
default => $self->{cut_options}{keep_lower},
));
$optgroup->append_line($line);
}
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'rotate_lower',
label => 'Rotate lower part upwards',
type => 'bool',
tooltip => 'If enabled, the lower part will be rotated by 180° so that the flat cut surface lies on the print bed.',
default => $self->{cut_options}{rotate_lower},
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'preview',
label => 'Show preview',
type => 'bool',
tooltip => 'If enabled, object will be cut in real time.',
default => $self->{cut_options}{preview},
));
{
my $cut_button_sizer = Wx::BoxSizer->new(wxVERTICAL);
$self->{btn_cut} = Wx::Button->new($self, -1, "Perform cut", wxDefaultPosition, wxDefaultSize);
$self->{btn_cut}->SetDefault;
$cut_button_sizer->Add($self->{btn_cut}, 0, wxALIGN_RIGHT | wxALL, 10);
$self->{btn_cut_grid} = Wx::Button->new($self, -1, "Cut by grid…", wxDefaultPosition, wxDefaultSize);
$cut_button_sizer->Add($self->{btn_cut_grid}, 0, wxALIGN_RIGHT | wxALL, 10);
$optgroup->append_line(Slic3r::GUI::OptionsGroup::Line->new(
sizer => $cut_button_sizer,
));
}
# left pane with tree
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
$left_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
# right pane with preview canvas
my $canvas;
if ($Slic3r::GUI::have_OpenGL) {
$canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self);
$canvas->load_object($self->{model_object}, undef, [0]);
$canvas->set_auto_bed_shape;
$canvas->SetSize([500,500]);
$canvas->SetMinSize($canvas->GetSize);
$canvas->zoom_to_volumes;
}
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
$self->{sizer}->Add($left_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
$self->{sizer}->Add($canvas, 1, wxEXPAND | wxALL, 0) if $canvas;
$self->SetSizer($self->{sizer});
$self->SetMinSize($self->GetSize);
$self->{sizer}->SetSizeHints($self);
EVT_BUTTON($self, $self->{btn_cut}, sub {
// Recalculate the cut if the preview was not active.
$self->_perform_cut() unless $self->{mesh_cut_valid};
// Adjust position / orientation of the split object halves.
if (my $lower = $self->{new_model_objects}[0]) {
if ($self->{cut_options}{rotate_lower} && $self->{cut_options}{axis} == Z) {
$lower->rotate(PI, X);
}
$lower->center_around_origin; # align to Z = 0
}
if (my $upper = $self->{new_model_objects}[1]) {
$upper->center_around_origin; # align to Z = 0
}
// Note that the window was already closed, so a pending update will not be executed.
already_closed = true;
EndModal(wxID_OK);
Destroy();
});
EVT_BUTTON($self, $self->{btn_cut_grid}, sub {
my $grid_x = Wx::GetTextFromUser("Enter the width of the desired tiles along the X axis:",
"Cut by Grid", 100, $self);
return if !looks_like_number($grid_x) || $grid_x <= 0;
my $grid_y = Wx::GetTextFromUser("Enter the width of the desired tiles along the Y axis:",
"Cut by Grid", 100, $self);
return if !looks_like_number($grid_y) || $grid_y <= 0;
my $process_dialog = Wx::ProgressDialog->new('Cutting', "Cutting model by grid…", 100, $self, 0);
$process_dialog->Pulse;
my $meshes = $self->{model_object}->mesh->cut_by_grid(Slic3r::Pointf->new($grid_x, $grid_y));
$self->{new_model_objects} = [];
my $bb = $self->{model_object}->bounding_box;
$self->{new_model} = my $model = Slic3r::Model->new;
for my $i (0..$#$meshes) {
push @{$self->{new_model_objects}}, my $o = $model->add_object(
name => sprintf('%s (%d)', $self->{model_object}->name, $i+1),
);
my $v = $o->add_volume(
mesh => $meshes->[$i],
name => $o->name,
);
$o->center_around_origin;
my $i = $o->add_instance(
offset => Slic3r::Pointf->new(@{$o->origin_translation->negative}[X,Y]),
);
$i->offset->translate(
5 * ceil(($i->offset->x - $bb->center->x) / $grid_x),
5 * ceil(($i->offset->y - $bb->center->y) / $grid_y),
);
}
$process_dialog->Destroy;
# Note that the window was already closed, so a pending update will not be executed.
$self->{already_closed} = 1;
$self->EndModal(wxID_OK);
$self->Destroy();
});
EVT_CLOSE($self, sub {
# Note that the window was already closed, so a pending update will not be executed.
already_closed = true;
EndModal(wxID_CANCEL);
Destroy();
});
*/
_update();
}
// 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
void ObjectCutDialog::_mesh_slice_z_pos(){
/*
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.
void ObjectCutDialog::_life_preview_active() {
/*
return $self->{cut_options}{preview} && ($self->{cut_options}{keep_upper} != $self->{cut_options}{keep_lower});
*/
}
// Slice the mesh, keep the top / bottom part.
void ObjectCutDialog::_perform_cut(){
// Early exit. If the cut is valid, don't recalculate it.
if (mesh_cut_valid) return;
/*
my $z = $self->_mesh_slice_z_pos();
my ($new_model) = $self->{model_object}->cut($self->{cut_options}{axis}, $z);
my ($upper_object, $lower_object) = @{$new_model->objects};
$self->{new_model} = $new_model;
$self->{new_model_objects} = [];
if ($self->{cut_options}{keep_upper} && $upper_object->volumes_count > 0) {
$self->{new_model_objects}[1] = $upper_object;
}
if ($self->{cut_options}{keep_lower} && $lower_object->volumes_count > 0) {
$self->{new_model_objects}[0] = $lower_object;
}
*/
mesh_cut_valid = true;
}
void ObjectCutDialog::_update() {
// Don't update if the window was already closed.
// We are not sure whether the action planned by wxTheApp->CallAfter() may be triggered after the window is closed.
// Probably not, but better be safe than sorry, which is espetially true on multiple platforms.
if (already_closed) return;
/*
# Only recalculate the cut, if the live cut preview is active.
my $life_preview_active = $self->_life_preview_active();
$self->_perform_cut() if $life_preview_active;
{
# scale Z down to original size since we're using the transformed mesh for 3D preview
# and cut dialog but ModelObject::cut() needs Z without any instance transformation
my $z = $self->_mesh_slice_z_pos();
# update canvas
if ($self->{canvas}) {
# get volumes to render
my @objects = ();
if ($life_preview_active) {
push @objects, grep defined, @{$self->{new_model_objects}};
} else {
push @objects, $self->{model_object};
}
# get section contour
my @expolygons = ();
foreach my $volume (@{$self->{model_object}->volumes}) {
next if !$volume->mesh;
next if $volume->modifier;
my $expp = $volume->mesh->slice_at($self->{cut_options}{axis}, $z);
push @expolygons, @$expp;
}
my $offset = $self->{model_object}->instances->[0]->offset;
foreach my $expolygon (@expolygons) {
$self->{model_object}->instances->[0]->transform_polygon($_)
for @$expolygon;
if ($self->{cut_options}{axis} != X) {
$expolygon->translate(0, Slic3r::Geometry::scale($offset->y)); #)
}
if ($self->{cut_options}{axis} != Y) {
$expolygon->translate(Slic3r::Geometry::scale($offset->x), 0);
}
}
$self->{canvas}->reset_objects;
$self->{canvas}->load_object($_, undef, [0]) for @objects;
my $plane_z = $self->{cut_options}{z};
$plane_z += 0.02 if !$self->{cut_options}{keep_upper};
$plane_z -= 0.02 if !$self->{cut_options}{keep_lower};
$self->{canvas}->SetCuttingPlane(
$self->{cut_options}{axis},
$plane_z,
[@expolygons],
);
$self->{canvas}->Render;
}
}
# update controls
{
my $z = $self->{cut_options}{z};
my $optgroup = $self->{optgroup};
{
my $bb = $self->{model_object}->instance_bounding_box(0);
my $max = $self->{cut_options}{axis} == X ? $bb->size->x
: $self->{cut_options}{axis} == Y ? $bb->size->y ###
: $bb->size->z;
$optgroup->get_field('z')->set_range(0, $max);
}
$optgroup->get_field('keep_upper')->toggle(my $have_upper = abs($z - $optgroup->get_option('z')->max) > 0.1);
$optgroup->get_field('keep_lower')->toggle(my $have_lower = $z > 0.1);
$optgroup->get_field('rotate_lower')->toggle($z > 0 && $self->{cut_options}{keep_lower} && $self->{cut_options}{axis} == Z);
$optgroup->get_field('preview')->toggle($self->{cut_options}{keep_upper} != $self->{cut_options}{keep_lower});
# update cut button
if (($self->{cut_options}{keep_upper} && $have_upper)
|| ($self->{cut_options}{keep_lower} && $have_lower)) {
$self->{btn_cut}->Enable;
} else {
$self->{btn_cut}->Disable;
}
}
*/
}
void ObjectCutDialog::NewModelObjects() {
/*
my ($self) = @_;
return grep defined, @{ $self->{new_model_objects} };
*/
}
}} // namespace Slic3r::GUI

View File

@ -0,0 +1,51 @@
#ifndef OBJECTCUTDIALOG_HPP
#define OBJECTCUTDIALOG_HPP
#include <wx/dialog.h>
#include "Scene3D.hpp"
#include "Model.hpp"
namespace Slic3r { namespace GUI {
class ObjectCutCanvas : public Scene3D {
// Not sure yet
protected:
// Draws the cutting plane
void after_render();
};
struct CutOptions {
float z;
enum {X,Y,Z} axis;
bool keep_upper, keep_lower, rotate_lower;
bool preview;
};
class ObjectCutDialog : public wxDialog {
public:
ObjectCutDialog(wxWindow* parent, ModelObject* _model);
private:
// Mark whether the mesh cut is valid.
// If not, it needs to be recalculated by _update() on wxTheApp->CallAfter() or on exit of the dialog.
bool mesh_cut_valid = false;
// Note whether the window was already closed, so a pending update is not executed.
bool already_closed = false;
// ObjectCutCanvas canvas;
ModelObject* model_object;
//std::shared_ptr<Slic3r::Model> model;
void _mesh_slice_z_pos();
void _life_preview_active();
void _perform_cut();
void _update();
void NewModelObjects();
};
}} // namespace Slic3r::GUI
#endif // OBJECTCUTDIALOG_HPP

View File

@ -17,7 +17,7 @@ namespace Slic3r { namespace GUI {
class App: public wxApp
{
public:
virtual bool OnInit();
virtual bool OnInit() override;
App() : wxApp() {}
/// Save position, size, and maximize state for a TopLevelWindow (includes Frames) by name in Settings.

70
src/GUI/OptionsGroup.hpp Normal file
View File

@ -0,0 +1,70 @@
#include "ConfigBase.hpp"
#include "Config.hpp"
#include "OptionsGroup/Field.hpp"
#include <string>
#include <map>
#include <memory>
namespace Slic3r { namespace GUI {
class Option;
class Line;
class OptionsGroup;
class ConfigOptionsGroup;
/// Class to
class Option {
public:
/// TODO: Constructor needs to include a default value
/// Reference to the configuration store.
ConfigOptionDef& desc;
Option(const ConfigOptionType& _type, const ConfigOption& _def) : desc(this->_desc) {
desc.type = _type;
// set _default to whatever it needs to be based on _type
// set desc default value to our stored one.
desc.default_value = _default;
}
// move constructor, owns the Config item.
Option(ConfigOptionDef&& remote) : desc(this->_desc), _desc(std::move(remote)) { }
/// Destructor to take care of the owned default value.
~Option() {
if (_default != nullptr) delete _default;
_default = nullptr;
}
/// TODO: Represent a sidebar widget
void* side_widget {nullptr};
private:
/// Actual configuration store.
/// Holds important information related to this configuration item.
ConfigOptionDef _desc;
/// default value, passed into _desc on construction.
/// Owns the data.
ConfigOption* _default {nullptr};
};
class OptionsGroup {
public:
OptionsGroup() {}
ConfigOption* get_field(const t_config_option_key& opt_id) { return nullptr; }
protected:
std::map<t_config_option_key, std::unique_ptr<Option>> _options;
std::map<t_config_option_key, std::unique_ptr<UI_Field>> _fields;
};
class ConfigOptionsGroup : public OptionsGroup {
public:
std::shared_ptr<Slic3r::Config> config;
protected:
};
}} // namespace Slic3r::GUI

View File

@ -18,6 +18,9 @@
#include "wx/arrstr.h"
#include "wx/stattext.h"
#include "wx/sizer.h"
#include <wx/colour.h>
#include <wx/clrpicker.h>
#include <wx/slider.h>
namespace Slic3r { namespace GUI {
@ -127,7 +130,7 @@ public:
protected:
virtual std::string LogChannel() override { return "UI_Checkbox"s; }
void _on_change(std::string opt_id) {
void _on_change(std::string opt_id) override {
if (!this->disable_change_event && this->window->IsEnabled() && this->on_change != nullptr) {
this->on_change(opt_id, this->get_bool());
}
@ -241,7 +244,7 @@ public:
protected:
virtual std::string LogChannel() override { return "UI_Choice"s; }
void _on_change(std::string opt_id) {
void _on_change(std::string opt_id) override {
if (!this->disable_change_event && this->window->IsEnabled() && this->on_change != nullptr) {
this->on_change(opt_id, this->get_string());
}
@ -283,7 +286,7 @@ protected:
void _set_value(double value, bool show_value = false);
void _set_value(std::string value);
void _on_change(std::string opt_id) {
void _on_change(std::string opt_id) override {
if (!this->disable_change_event && this->window->IsEnabled() && this->on_change != nullptr) {
this->on_change(opt_id, this->get_string());
}
@ -298,7 +301,7 @@ public:
UI_Point(wxWindow* _parent, Slic3r::ConfigOptionDef _opt);
~UI_Point() { _lbl_x->Destroy(); _lbl_y->Destroy(); _ctrl_x->Destroy(); _ctrl_y->Destroy(); }
std::string get_string();
std::string get_string() override;
void set_value(boost::any value) override; //< Implements set_value
@ -347,7 +350,7 @@ class UI_Point3 : public UI_Sizer {
public:
UI_Point3(wxWindow* _parent, Slic3r::ConfigOptionDef _opt);
~UI_Point3() { _lbl_x->Destroy(); _lbl_y->Destroy(); _ctrl_x->Destroy(); _ctrl_y->Destroy(); _lbl_z->Destroy(); _ctrl_z->Destroy(); }
std::string get_string();
std::string get_string() override;
void set_value(boost::any value) override; //< Implements set_value
@ -395,6 +398,68 @@ private:
};
class UI_Color : public UI_Window {
public:
UI_Color(wxWindow* parent, Slic3r::ConfigOptionDef _opt );
~UI_Color() { _picker->Destroy(); }
wxColourPickerCtrl* picker() { return this->_picker; }
void set_value(boost::any value) override;
std::string get_string() override;
std::function<void (const std::string&, const std::string&)> on_change {nullptr};
protected:
virtual std::string LogChannel() override { return "UI_Color"s; }
void _on_change(std::string opt_id) override {
if (!this->disable_change_event && this->_picker->IsEnabled() && this->on_change != nullptr) {
this->on_change(opt_id, _picker->GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString());
}
}
private:
wxColour _string_to_color(const std::string& _color);
wxColourPickerCtrl* _picker {nullptr};
};
class UI_Slider : public UI_Sizer {
public:
UI_Slider(wxWindow* parent, Slic3r::ConfigOptionDef _opt, size_t scale = 10);
~UI_Slider();
void set_value(boost::any value) override;
std::string get_string() override;
double get_double() override;
int get_int() override;
void enable() override;
void disable() override;
/// change the min/max of the built-in slider
template <typename T> void set_range(T min, T max);
/// Change the scale of the slider bar. Return value from get_X functions does not change.
void set_scale(size_t new_scale);
/// Returns pointer to owned wxSlider.
wxSlider* slider() { return _slider;}
/// Returns pointer to owned wxTextCtrl.
wxTextCtrl* textctrl() { return _textctrl;}
/// Registered on_change callback.
std::function<void (const std::string&, const double&)> on_change {nullptr};
protected:
virtual std::string LogChannel() override { return "UI_Slider"s; }
private:
void _on_change(std::string opt_id) override {
if (!this->disable_change_event && this->_slider->IsEnabled() && this->on_change != nullptr) {
this->on_change(opt_id, _slider->GetValue() / _scale);
}
}
void _update_textctrl();
wxTextCtrl* _textctrl {nullptr};
wxSlider* _slider {nullptr};
size_t _scale {10};
};
} } // Namespace Slic3r::GUI
#endif // SLIC3R_FIELD_HPP

View File

@ -0,0 +1,44 @@
#include "OptionsGroup/Field.hpp"
#include "misc_ui.hpp"
namespace Slic3r { namespace GUI {
UI_Color::UI_Color(wxWindow* parent, Slic3r::ConfigOptionDef _opt ) : UI_Window(parent, _opt) {
wxColour default_color(255,255,255,255);
if (_opt.default_value != nullptr) {
default_color = _string_to_color(_opt.default_value->getString());
}
this->_picker = new wxColourPickerCtrl(parent, wxID_ANY, default_color, wxDefaultPosition, this->_default_size());
this->window = dynamic_cast<wxWindow*>(this->_picker);
this->_picker->Bind(wxEVT_COLOURPICKER_CHANGED, [this](wxColourPickerEvent& e) { this->_on_change(""); e.Skip(); });
}
void UI_Color::set_value(boost::any value) {
if (value.type() == typeid(wxColour)) {
_picker->SetColour(boost::any_cast<wxColour>(value));
} else if (value.type() == typeid(std::string)) {
_picker->SetColour(wxString(boost::any_cast<std::string>(value)));
} else if (value.type() == typeid(const char*)) {
_picker->SetColour(wxString(boost::any_cast<const char*>(value)));
} else if (value.type() == typeid(wxString)) {
_picker->SetColour(boost::any_cast<wxString>(value));
} else {
Slic3r::Log::warn(this->LogChannel(), LOG_WSTRING("Type " << value.type().name() << " is not handled in set_value."));
}
}
std::string UI_Color::get_string() {
return _picker->GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
}
wxColour UI_Color::_string_to_color(const std::string& color) {
// if invalid color string sent, use the default
wxColour col(255,255,255,255);
if (col.Set(wxString(color)))
return col;
return wxColor();
}
} } // Namespace Slic3r::GUI

View File

@ -0,0 +1,114 @@
#include "OptionsGroup/Field.hpp"
#include "misc_ui.hpp"
#include "utils.hpp"
namespace Slic3r { namespace GUI {
UI_Slider::UI_Slider(wxWindow* parent, Slic3r::ConfigOptionDef _opt, size_t scale) : UI_Sizer(parent, _opt), _scale(scale) {
double default_value {0.0};
if (_opt.default_value != nullptr) { default_value = _opt.default_value->getFloat(); }
sizer = new wxBoxSizer(wxHORIZONTAL);
_slider = new wxSlider(parent, wxID_ANY,
(default_value < _opt.min ? _opt.min : default_value) * this->_scale,
(_opt.min > _opt.max || _opt.min == INT_MIN ? 0 : _opt.min) * this->_scale,
(_opt.max <= _opt.min || _opt.max == INT_MAX ? 100 : _opt.max) * this->_scale,
wxDefaultPosition,
wxSize(_opt.width, _opt.height));
_textctrl = new wxTextCtrl(parent, wxID_ANY,
trim_zeroes(std::to_string(static_cast<double>(_slider->GetValue()) / this->_scale)),
wxDefaultPosition,
wxSize(50, -1),
wxTE_PROCESS_ENTER);
sizer->Add(_slider, 1, wxALIGN_CENTER_VERTICAL, 0);
sizer->Add(_textctrl, 0, wxALIGN_CENTER_VERTICAL, 0);
_textctrl->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent& e) { this->_on_change(""); e.Skip(); });
_textctrl->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { if (this->on_kill_focus != nullptr) {this->on_kill_focus(""); this->_on_change("");} e.Skip(); });
_slider->Bind(wxEVT_SLIDER, [this](wxCommandEvent& e) { this->_on_change(""); e.Skip(); });
}
UI_Slider::~UI_Slider() { _slider->Destroy(); _textctrl->Destroy(); }
void UI_Slider::set_value(boost::any value) {
this->disable_change_event = true;
if (value.type() == typeid(int)) {
this->_slider->SetValue(boost::any_cast<int>(value) * this->_scale);
} else if (value.type() == typeid(double)) {
this->_slider->SetValue(boost::any_cast<double>(value) * this->_scale);
} else if (value.type() == typeid(float)) {
this->_slider->SetValue(boost::any_cast<float>(value) * this->_scale);
} else if (value.type() == typeid(std::string)) {
std::string _val = boost::any_cast<std::string>(value);
try {
this->_slider->SetValue(std::stod(_val) * this->_scale);
} catch (std::invalid_argument &e) {
Slic3r::Log::error(this->LogChannel()) << "Conversion to numeric from string failed.\n";
}
} else if (value.type() == typeid(wxString)) {
std::string _val = boost::any_cast<wxString>(value).ToStdString();
try {
this->_slider->SetValue(std::stod(_val) * this->_scale);
} catch (std::invalid_argument &e) {
Slic3r::Log::error(this->LogChannel()) << "Conversion to numeric from string failed.\n";
}
} else {
Slic3r::Log::warn(this->LogChannel(), LOG_WSTRING("Type " << value.type().name() << " is not handled in set_value."));
}
this->_update_textctrl();
this->disable_change_event = false;
}
double UI_Slider::get_double() {
return static_cast<double>(this->_slider->GetValue()) / this->_scale;
}
int UI_Slider::get_int() {
return static_cast<int>(this->_slider->GetValue()) / this->_scale;
}
std::string UI_Slider::get_string() {
return _trim_zeroes(std::to_string(static_cast<double>(this->_slider->GetValue()) / this->_scale));
}
void UI_Slider::set_scale(size_t new_scale) {
this->disable_change_event = true;
auto current_value {this->get_double()};
this->_slider->SetRange(
this->_slider->GetMin() / this->_scale * new_scale,
this->_slider->GetMax() / this->_scale * new_scale);
this->_scale = new_scale;
this->set_value(current_value);
this->disable_change_event = false;
}
void UI_Slider::_update_textctrl() {
this->_textctrl->ChangeValue(this->get_string());
this->_textctrl->SetInsertionPointEnd();
}
void UI_Slider::disable() {
this->_slider->Disable();
this->_textctrl->Disable();
this->_textctrl->SetEditable(false);
}
void UI_Slider::enable() {
this->_slider->Enable();
this->_textctrl->Enable();
this->_textctrl->SetEditable(true);
}
template <typename T>
void UI_Slider::set_range(T min, T max) {
this->_slider->SetRange(static_cast<int>(min * static_cast<int>(this->_scale)), static_cast<int>(max * static_cast<int>(this->_scale)));
}
} } // Namespace Slic3r::GUI

View File

@ -13,6 +13,7 @@
#include "BoundingBox.hpp"
#include "Geometry.hpp"
#include "Dialogs/AnglePicker.hpp"
#include "Dialogs/ObjectCutDialog.hpp"
namespace Slic3r { namespace GUI {
@ -88,7 +89,10 @@ Plater::Plater(wxWindow* parent, const wxString& title) :
canvas3D = new Plate3D(preview_notebook, wxDefaultSize, objects, model, config);
preview_notebook->AddPage(canvas3D, _("3D"));
preview3D = new Preview3D(preview_notebook, wxDefaultSize, objects, model, config);
canvas3D->on_select_object = std::function<void (ObjIdx obj_idx)>(on_select_object);
canvas3D->on_instances_moved = std::function<void ()>(on_instances_moved);
preview3D = new Preview3D(preview_notebook, wxDefaultSize, print, objects, model, config);
preview_notebook->AddPage(preview3D, _("Preview"));
preview2D = new Preview2D(preview_notebook, wxDefaultSize, objects, model, config);
@ -483,7 +487,7 @@ void Plater::arrange() {
GetFrame()->statusbar->SetStatusText(_("Nothing to arrange."));
return;
}
bool success {this->model->arrange_objects(this->config->min_object_distance(), &bb)};
bool success {this->model->arrange_objects(this->config->config().min_object_distance(), &bb)};
if (success) {
GetFrame()->statusbar->SetStatusText(_("Objects were arranged."));
@ -494,13 +498,15 @@ void Plater::arrange() {
}
void Plater::on_model_change(bool force_autocenter) {
Log::info(LogChannel, L"Called on_modal_change");
// reload the select submenu (if already initialized)
{
auto* menu = this->GetFrame()->plater_select_menu;
if (menu != nullptr) {
for (auto* item : menu->GetMenuItems() ) { menu->Delete(item); }
auto list = menu->GetMenuItems();
for (auto it = list.begin();it!=list.end(); it++) { menu->Delete(*it); }
for (const auto& obj : this->objects) {
const auto idx {obj.identifier};
auto name {wxString(obj.name)};
@ -561,6 +567,7 @@ void Plater::select_object() {
void Plater::selection_changed() {
// Remove selection in 2D plater
this->canvas2D->set_selected(-1, -1);
this->canvas3D->selection_changed();
auto obj = this->selected_object();
bool have_sel {obj != this->objects.end()};
@ -882,6 +889,13 @@ void Plater::changescale() {
void Plater::object_cut_dialog() {
//TODO
ObjRef obj {this->selected_object()};
if (obj == this->objects.end()) return;
auto* model_object {this->model->objects.at(obj->identifier)};
auto cut_dialog = new ObjectCutDialog(nullptr, model_object);
cut_dialog->ShowModal();
cut_dialog->Destroy();
}
void Plater::object_layers_dialog() {

171
src/GUI/Plater/3DPreview.pm Normal file
View File

@ -0,0 +1,171 @@
package Slic3r::GUI::Plater::3DPreview;
use strict;
use warnings;
use utf8;
use Slic3r::Print::State ':steps';
use Wx qw(:misc :sizer :slider :statictext);
use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN);
use base qw(Wx::Panel Class::Accessor);
__PACKAGE__->mk_accessors(qw(print enabled _loaded canvas slider));
sub new {
my $class = shift;
my ($parent, $print) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition);
# init GUI elements
my $canvas = Slic3r::GUI::3DScene->new($self);
$self->canvas($canvas);
my $slider = Wx::Slider->new(
$self, -1,
0, # default
0, # min
# we set max to a bogus non-zero value because the MSW implementation of wxSlider
# will skip drawing the slider if max <= min:
1, # max
wxDefaultPosition,
wxDefaultSize,
wxVERTICAL | wxSL_INVERSE,
);
$self->slider($slider);
my $z_label = $self->{z_label} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
[40,-1], wxALIGN_CENTRE_HORIZONTAL);
$z_label->SetFont($Slic3r::GUI::small_font);
my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
$vsizer->Add($slider, 1, wxALL | wxEXPAND | wxALIGN_CENTER, 3);
$vsizer->Add($z_label, 0, wxALL | wxEXPAND | wxALIGN_CENTER, 3);
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($canvas, 1, wxALL | wxEXPAND, 0);
$sizer->Add($vsizer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5);
EVT_SLIDER($self, $slider, sub {
$self->set_z($self->{layers_z}[$slider->GetValue])
if $self->enabled;
});
EVT_KEY_DOWN($canvas, sub {
my ($s, $event) = @_;
my $key = $event->GetKeyCode;
if ($key == 85 || $key == 315) {
$slider->SetValue($slider->GetValue + 1);
$self->set_z($self->{layers_z}[$slider->GetValue]);
} elsif ($key == 68 || $key == 317) {
$slider->SetValue($slider->GetValue - 1);
$self->set_z($self->{layers_z}[$slider->GetValue]);
} else {
$event->Skip;
}
});
$self->SetSizer($sizer);
$self->SetMinSize($self->GetSize);
$sizer->SetSizeHints($self);
# init canvas
$self->print($print);
$self->reload_print;
return $self;
}
sub reload_print {
my ($self, $obj_idx) = @_;
$self->canvas->reset_objects;
$self->_loaded(0);
$self->load_print($obj_idx);
}
sub load_print {
my ($self, $obj_idx) = @_;
return if $self->_loaded;
# we require that there's at least one object and the posSlice step
# is performed on all of them (this ensures that _shifted_copies was
# populated and we know the number of layers)
if (!$self->print->object_step_done(STEP_SLICE)) {
$self->enabled(0);
$self->slider->Hide;
$self->canvas->Refresh; # clears canvas
return;
}
my $z_idx;
{
my %z = (); # z => 1
if(defined $obj_idx) { # Load only given object
foreach my $layer (@{$self->{print}->get_object($obj_idx)->layers}) {
$z{$layer->print_z} = 1;
}
}else{ # Load all objects on the plater + support material
foreach my $object (@{$self->{print}->objects}) {
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
$z{$layer->print_z} = 1;
}
}
}
$self->enabled(1);
$self->{layers_z} = [ sort { $a <=> $b } keys %z ];
$self->slider->SetRange(0, scalar(@{$self->{layers_z}})-1);
if (($z_idx = $self->slider->GetValue) <= $#{$self->{layers_z}} && $self->slider->GetValue != 0) {
# use $z_idx
} else {
$self->slider->SetValue(scalar(@{$self->{layers_z}})-1);
$z_idx = @{$self->{layers_z}} ? -1 : undef;
}
$self->slider->Show;
$self->Layout;
}
if ($self->IsShown) {
# set colors
$self->canvas->color_toolpaths_by($Slic3r::GUI::Settings->{_}{color_toolpaths_by});
if ($self->canvas->color_toolpaths_by eq 'extruder') {
my @filament_colors = map { s/^#//; [ map $_/255, (unpack 'C*', pack 'H*', $_), 255 ] }
@{$self->print->config->filament_colour};
$self->canvas->colors->[$_] = $filament_colors[$_] for 0..$#filament_colors;
} else {
$self->canvas->colors([ $self->canvas->default_colors ]);
}
if(defined $obj_idx) { # Load only one object
$self->canvas->load_print_object_toolpaths($self->{print}->get_object($obj_idx));
}else{ # load all objects
# load skirt and brim
$self->canvas->load_print_toolpaths($self->print);
foreach my $object (@{$self->print->objects}) {
$self->canvas->load_print_object_toolpaths($object);
#my @volume_ids = $self->canvas->load_object($object->model_object);
#$self->canvas->volumes->[$_]->color->[3] = 0.2 for @volume_ids;
}
}
$self->_loaded(1);
}
$self->set_z($self->{layers_z}[$z_idx]);
}
sub set_z {
my ($self, $z) = @_;
return if !$self->enabled;
$self->{z_label}->SetLabel(sprintf '%.2f', $z);
$self->canvas->set_toolpaths_range(0, $z);
$self->canvas->Refresh if $self->IsShown;
}
sub set_bed_shape {
my ($self, $bed_shape) = @_;
$self->canvas->set_bed_shape($bed_shape);
}
1;

View File

@ -386,7 +386,7 @@ void Plate2D::update_bed_size() {
const auto& canvas_h {canvas_size.GetHeight()};
if (canvas_w == 0) return; // Abort early if we haven't drawn canvas yet.
this->bed_polygon = Slic3r::Polygon(scale(dynamic_cast<ConfigOptionPoints*>(config->optptr("bed_shape"))->values));
this->bed_polygon = Slic3r::Polygon(scale(config->get<ConfigOptionPoints>("bed_shape").values));
const auto& polygon = bed_polygon;

176
src/GUI/Plater/Plate3D.cpp Normal file
View File

@ -0,0 +1,176 @@
#include "Plater/Plate3D.hpp"
#include "misc_ui.hpp"
namespace Slic3r { namespace GUI {
Plate3D::Plate3D(wxWindow* parent, const wxSize& size, std::vector<PlaterObject>& _objects, std::shared_ptr<Model> _model, std::shared_ptr<Config> _config) :
Scene3D(parent, size), objects(_objects), model(_model), config(_config)
{
// Bind the extra mouse events
this->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { this->mouse_down(e); });
this->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent &e) { this->mouse_down(e); });
}
void Plate3D::mouse_down(wxMouseEvent &e){
if(hover){
on_select_object(hover_object);
moving = true;
moving_volume = hover_volume;
move_start = Point(e.GetX(),e.GetY());
}else{
on_select_object(-1);
}
hover = false;
}
void Plate3D::mouse_up(wxMouseEvent &e){
if(moving){
//translate object
moving = false;
uint i = 0;
for(const PlaterObject &object: objects){
const auto &modelobj = model->objects.at(object.identifier);
for(ModelInstance *instance: modelobj->instances){
uint size = modelobj->volumes.size();
if(i <= moving_volume && moving_volume < i+size){
instance->offset.translate(volumes.at(i).origin);
modelobj->update_bounding_box();
on_instances_moved();
Refresh();
return;
}else{
i+=size;
}
}
}
}
Scene3D::mouse_up(e);
}
void Plate3D::mouse_move(wxMouseEvent &e){
if(!e.Dragging()){
pos = Point(e.GetX(),e.GetY());
mouse = true;
Refresh();
} else if(moving){
const auto p = Point(e.GetX(),e.GetY());
const auto current = mouse_ray(p).intersect_plane(0);
const auto old = mouse_ray(move_start).intersect_plane(0);
move_start = p;
uint i = 0;
for(const PlaterObject &object: objects){
const auto &modelobj = model->objects.at(object.identifier);
for(ModelInstance *instance: modelobj->instances){
uint size = modelobj->volumes.size();
if(i <= moving_volume && moving_volume < i+size){
for(ModelVolume* volume: modelobj->volumes){
volumes.at(i).origin.translate(old.vector_to(current));
i++;
}
Refresh();
return;
}else{
i+=size;
}
}
}
} else {
Scene3D::mouse_move(e);
}
}
void Plate3D::update(){
volumes.clear();
for(const PlaterObject &object: objects){
const auto &modelobj = model->objects.at(object.identifier);
for(ModelInstance *instance: modelobj->instances){
for(ModelVolume* volume: modelobj->volumes){
TriangleMesh copy = volume->mesh;
instance->transform_mesh(&copy);
GLVertexArray model;
model.load_mesh(copy);
volumes.push_back(Volume{ wxColor(200,200,200), Pointf3(0,0,0), model, copy.bounding_box()});
}
}
}
color_volumes();
Refresh();
}
void Plate3D::color_volumes(){
uint i = 0;
for(const PlaterObject &object: objects){
const auto &modelobj = model->objects.at(object.identifier);
bool hover_object = hover && i <= hover_volume && hover_volume < i+modelobj->instances.size()*modelobj->volumes.size();
for(ModelInstance *instance: modelobj->instances){
for(ModelVolume* volume: modelobj->volumes){
auto& rendervolume = volumes.at(i);
if(object.selected){
rendervolume.color = ui_settings->color->SELECTED_COLOR();
}else if(hover_object){
rendervolume.color = ui_settings->color->HOVER_COLOR();
} else {
rendervolume.color = ui_settings->color->COLOR_PARTS();
}
i++;
}
}
}
}
void Plate3D::before_render(){
if (!mouse){
color_volumes();
return;
}
// Color each volume a different color, render and test which color is beneath the mouse.
//glDisable(GL_MULTISAMPLE) if ($self->{can_multisample});
glDisable(GL_LIGHTING);
uint i = 1;
for(Volume &volume : volumes){
volume.color = wxColor((i>>16)&0xFF,(i>>8)&0xFF,i&0xFF);
i++;
}
draw_volumes();
glFlush();
glFinish();
GLubyte color[4] = {0,0,0,0};
glReadPixels(pos.x, GetSize().GetHeight()- pos.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, color);
// Handle the hovered volume
uint index = (color[0]<<16) + (color[1]<<8) + color[2];
hover = false;
///*$self->_hover_volume_idx(undef);
//$_->hover(0) for @{$self->volumes};
if (index != 0 && index <= volumes.size()) {
hover = true;
hover_volume = index - 1;
uint k = 0;
for(const PlaterObject &object: objects){
const auto &modelobj = model->objects.at(object.identifier);
if(k <= hover_volume && hover_volume < k+modelobj->instances.size()*modelobj->volumes.size()){
hover_object = k;
break;
}
k++;
}
/*
$self->volumes->[$volume_idx]->hover(1);
my $group_id = $self->volumes->[$volume_idx]->select_group_id;
if ($group_id != -1) {
$_->hover(1) for grep { $_->select_group_id == $group_id } @{$self->volumes};
}*/
//$self->on_hover->($volume_idx) if $self->on_hover;
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glFlush();
glFinish();
glEnable(GL_LIGHTING);
color_volumes();
mouse = false;
}
} } // Namespace Slic3r::GUI

View File

@ -4,18 +4,44 @@
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include "Plater/PlaterObject.hpp"
#include "Scene3D.hpp"
#include "Settings.hpp"
#include "Model.hpp"
#include "Config.hpp"
namespace Slic3r { namespace GUI {
class Plate3D : public wxPanel {
class Plate3D : public Scene3D {
public:
void update() {};
Plate3D(wxWindow* parent, const wxSize& size, std::vector<PlaterObject>& _objects, std::shared_ptr<Model> _model, std::shared_ptr<Config> _config) :
wxPanel(parent, wxID_ANY, wxDefaultPosition, size, wxTAB_TRAVERSAL), objects(_objects), model(_model), config(_config)
{}
Plate3D(wxWindow* parent, const wxSize& size, std::vector<PlaterObject>& _objects, std::shared_ptr<Model> _model, std::shared_ptr<Config> _config);
/// Called to regenerate rendered volumes from the model
void update();
/// Registered function to fire when objects are selected.
std::function<void (const unsigned int obj_idx)> on_select_object {};
/// Registered function to fire when an instance is moved.
std::function<void ()> on_instances_moved {};
void selection_changed(){Refresh();}
protected:
// Render each volume as a different color and check what color is beneath
// the mouse to detemine the hovered volume
void before_render();
// Mouse events are needed to handle selecting and moving objects
void mouse_up(wxMouseEvent &e);
void mouse_move(wxMouseEvent &e);
void mouse_down(wxMouseEvent &e);
private:
void color_volumes();
Point pos, move_start;
bool hover = false, mouse = false, moving = false;
uint hover_volume, hover_object, moving_volume;
std::vector<PlaterObject>& objects; //< reference to parent vector
std::shared_ptr<Slic3r::Model> model;
std::shared_ptr<Slic3r::Config> config;

View File

@ -0,0 +1,145 @@
#include "Preview3D.hpp"
#include <wx/event.h>
namespace Slic3r { namespace GUI {
Preview3D::Preview3D(wxWindow* parent, const wxSize& size, std::shared_ptr<Slic3r::Print> _print, std::vector<PlaterObject>& _objects, std::shared_ptr<Model> _model, std::shared_ptr<Config> _config) :
wxPanel(parent, wxID_ANY, wxDefaultPosition, size, wxTAB_TRAVERSAL), print(_print), objects(_objects), model(_model), config(_config), canvas(this,size)
{
// init GUI elements
slider = new wxSlider(
this, -1,
0, // default
0, // min
// we set max to a bogus non-zero value because the MSW implementation of wxSlider
// will skip drawing the slider if max <= min:
1, // max
wxDefaultPosition,
wxDefaultSize,
wxVERTICAL | wxSL_INVERSE
);
this->z_label = new wxStaticText(this, -1, "", wxDefaultPosition,
wxSize(40,-1), wxALIGN_CENTRE_HORIZONTAL);
//z_label->SetFont(Slic3r::GUI::small_font);
auto* vsizer = new wxBoxSizer(wxVERTICAL);
vsizer->Add(slider, 1, wxALL | wxEXPAND | wxALIGN_CENTER, 3);
vsizer->Add(z_label, 0, wxALL | wxEXPAND | wxALIGN_CENTER, 3);
auto* sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(&canvas, 1, wxALL | wxEXPAND, 0);
sizer->Add(vsizer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5);
this->Bind(wxEVT_SLIDER, [this](wxCommandEvent &e){
//$self->set_z($self->{layers_z}[$slider->GetValue])
// if $self->enabled;
});
this->Bind(wxEVT_CHAR, [this](wxKeyEvent &e) {
/*my ($s, $event) = @_;
my $key = $event->GetKeyCode;
if ($key == 85 || $key == 315) {
$slider->SetValue($slider->GetValue + 1);
$self->set_z($self->{layers_z}[$slider->GetValue]);
} elsif ($key == 68 || $key == 317) {
$slider->SetValue($slider->GetValue - 1);
$self->set_z($self->{layers_z}[$slider->GetValue]);
} else {
$event->Skip;
}*/
});
SetSizer(sizer);
SetMinSize(GetSize());
sizer->SetSizeHints(this);
// init canvas
reload_print();
}
void Preview3D::reload_print(){
canvas.resetObjects();
loaded = false;
load_print();
}
void Preview3D::load_print() {
if(loaded) return;
// we require that there's at least one object and the posSlice step
// is performed on all of them (this ensures that _shifted_copies was
// populated and we know the number of layers)
if(!print->step_done(posSlice)) {
_enabled = false;
slider->Hide();
canvas.Refresh(); // clears canvas
return;
}
size_t z_idx = 0;
{
layers_z.clear();
// Load all objects on the plater + support material
for(auto* object : print->objects) {
for(auto layer : object->layers){
layers_z.push_back(layer->print_z);
}
for(auto layer : object->support_layers) {
layers_z.push_back(layer->print_z);
}
}
_enabled = true;
std::sort(layers_z.begin(),layers_z.end());
slider->SetRange(0, layers_z.size()-1);
z_idx = slider->GetValue();
// If invalide z_idx, move the slider to the top
if (z_idx >= layers_z.size() || slider->GetValue() == 0) {
slider->SetValue(layers_z.size()-1);
//$z_idx = @{$self->{layer_z}} ? -1 : undef;
z_idx = slider->GetValue(); // not sure why the perl version makes z_idx invalid
}
slider->Show();
Layout();
}
if (IsShown()) {
// set colors
/*canvas.color_toolpaths_by($Slic3r::GUI::Settings->{_}{color_toolpaths_by});
if ($self->canvas->color_toolpaths_by eq 'extruder') {
my @filament_colors = map { s/^#//; [ map $_/255, (unpack 'C*', pack 'H*', $_), 255 ] }
@{$self->print->config->filament_colour};
$self->canvas->colors->[$_] = $filament_colors[$_] for 0..$#filament_colors;
} else {
$self->canvas->colors([ $self->canvas->default_colors ]);
}*/
// load skirt and brim
//$self->canvas->load_print_toolpaths($self->print);
/*foreach my $object (@{$self->print->objects}) {
canvas.load_print_object_toolpaths($object);
}*/
loaded = true;
}
set_z(layers_z.at(z_idx));
}
void Preview3D::set_z(float z) {
if(!_enabled) return;
z_label->SetLabel(std::to_string(z));
//canvas.set_toolpaths_range(0, $z);
if(IsShown())canvas.Refresh();
}
/*
void set_bed_shape() {
my ($self, $bed_shape) = @_;
$self->canvas->set_bed_shape($bed_shape);
}
*/
} } // Namespace Slic3r::GUI

View File

@ -5,20 +5,36 @@
#include <wx/wx.h>
#endif
#include "PlaterObject.hpp"
#include "Scene3D.hpp"
#include "Model.hpp"
#include "Config.hpp"
#include "Print.hpp"
namespace Slic3r { namespace GUI {
class PreviewScene3D : public Scene3D {
public:
PreviewScene3D(wxWindow* parent, const wxSize& size) : Scene3D(parent,size){}
// TODO: load_print_toolpaths(Print);
// TODO: load_print_object_toolpaths(PrintObject);
void resetObjects(){volumes.clear();}
};
class Preview3D : public wxPanel {
public:
void reload_print() {};
Preview3D(wxWindow* parent, const wxSize& size, std::vector<PlaterObject>& _objects, std::shared_ptr<Model> _model, std::shared_ptr<Config> _config) :
wxPanel(parent, wxID_ANY, wxDefaultPosition, size, wxTAB_TRAVERSAL), objects(_objects), model(_model), config(_config)
{}
void reload_print();
Preview3D(wxWindow* parent, const wxSize& size, std::shared_ptr<Slic3r::Print> _print, std::vector<PlaterObject>& _objects, std::shared_ptr<Model> _model, std::shared_ptr<Config> _config);
void enabled(bool enable = true) {}
private:
void load_print();
void set_z(float z);
bool loaded = false, _enabled = false;
std::vector<float> layers_z;
std::shared_ptr<Slic3r::Print> print;
PreviewScene3D canvas;
wxSlider* slider;
wxStaticText* z_label;
std::vector<PlaterObject>& objects; //< reference to parent vector
std::shared_ptr<Slic3r::Model> model;
std::shared_ptr<Slic3r::Config> config;

View File

@ -35,7 +35,7 @@ public:
bool compatible(const Preset& other) {return (this->group == preset_t::Printer || (compatible(other.name) && other.group == preset_t::Printer));}
/// Format the name appropriately.
wxString dropdown_name() { return (this->dirty() ? wxString(this->name) << " " << _("(modified)") : this->name); }
wxString dropdown_name() { return (this->dirty() ? wxString(this->name) << " " << _("(modified)") : wxString(this->name)); }
bool file_exists(wxString name);
@ -56,8 +56,8 @@ public:
/// Returns whether or not this config is different from its modified state.
bool dirty();
/// Loads the selected config from file and return a reference.
Slic3r::Config& load_config();
/// Loads the selected config from file and return a shared_ptr to that config
config_ptr load_config();
/// Pass-through to Slic3r::Config, returns whether or not a config was loaded.
bool loaded() { return !this->config.empty(); }

496
src/GUI/Scene3D.cpp Normal file
View File

@ -0,0 +1,496 @@
#include "Scene3D.hpp"
#include "Line.hpp"
#include "ClipperUtils.hpp"
#include "misc_ui.hpp"
#ifdef __APPLE__
#include <OpenGL/glu.h>
#else
#include <GL/glu.h>
#endif
namespace Slic3r { namespace GUI {
Scene3D::Scene3D(wxWindow* parent, const wxSize& size) :
wxGLCanvas(parent, wxID_ANY, nullptr, wxDefaultPosition, size)
{
this->glContext = new wxGLContext(this);
this->Bind(wxEVT_PAINT, [this](wxPaintEvent &e) { this->repaint(e); });
this->Bind(wxEVT_SIZE, [this](wxSizeEvent &e ){
dirty = true;
Refresh();
});
// Bind the varying mouse events
this->Bind(wxEVT_MOTION, [this](wxMouseEvent &e) { this->mouse_move(e); });
this->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &e) { this->mouse_up(e); });
this->Bind(wxEVT_RIGHT_UP, [this](wxMouseEvent &e) { this->mouse_up(e); });
this->Bind(wxEVT_MIDDLE_DCLICK, [this](wxMouseEvent &e) { this->mouse_dclick(e); });
this->Bind(wxEVT_MOUSEWHEEL, [this](wxMouseEvent &e) { this->mouse_wheel(e); });
Points p;
const coord_t w = scale_(200), z = 0;
p.push_back(Point(z,z));
p.push_back(Point(z,w));
p.push_back(Point(w,w));
p.push_back(Point(w,z));
set_bed_shape(p);
}
float clamp(float low, float x, float high){
if(x < low) return low;
if(x > high) return high;
return x;
}
Linef3 Scene3D::mouse_ray(Point win){
GLdouble proj[16], mview[16];
glGetDoublev(GL_MODELVIEW_MATRIX, mview);
glGetDoublev(GL_PROJECTION_MATRIX, proj);
GLint view[4];
glGetIntegerv(GL_VIEWPORT, view);
win.y = view[3]-win.y;
GLdouble x = 0.0, y = 0.0, z = 0.0;
gluUnProject(win.x,win.y,0,mview,proj,view,&x,&y,&z);
Pointf3 first = Pointf3(x,y,z);
GLint a = gluUnProject(win.x,win.y,1,mview,proj,view,&x,&y,&z);
return Linef3(first,Pointf3(x,y,z));
}
void Scene3D::mouse_move(wxMouseEvent &e){
if(e.Dragging()){
//const auto s = GetSize();
const auto pos = Point(e.GetX(),e.GetY());
if(dragging){
if (e.ShiftDown()) { // TODO: confirm alt -> shift is ok
// Move the camera center on the Z axis based on mouse Y axis movement
_camera_target.translate(0, 0, (pos.y - drag_start.y));
} else if (e.LeftIsDown()) {
// if dragging over blank area with left button, rotate
//if (TURNTABLE_MODE) {
const float TRACKBALLSIZE = 0.8f, GIMBAL_LOCK_THETA_MAX = 170.0f;
phi += (pos.x - drag_start.x) * TRACKBALLSIZE;
theta -= (pos.y - drag_start.y) * TRACKBALLSIZE;
theta = clamp(0, theta, GIMBAL_LOCK_THETA_MAX);
/*} else {
my $size = $self->GetClientSize;
my @quat = trackball(
$orig->x / ($size->width / 2) - 1,
1 - $orig->y / ($size->height / 2), #/
$pos->x / ($size->width / 2) - 1,
1 - $pos->y / ($size->height / 2), #/
);
$self->_quat(mulquats($self->_quat, \@quat));
}*/
} else if (e.MiddleIsDown() || e.RightIsDown()) {
// if dragging over blank area with right button, translate
// get point in model space at Z = 0
const auto current = mouse_ray(pos).intersect_plane(0);
const auto old = mouse_ray(drag_start).intersect_plane(0);
_camera_target.translate(current.vector_to(old));
}
//$self->on_viewport_changed->() if $self->on_viewport_changed;
Refresh();
}
dragging = true;
drag_start = pos;
}else{
e.Skip();
}
}
void Scene3D::mouse_up(wxMouseEvent &e){
dragging = false;
Refresh();
}
void Scene3D::mouse_wheel(wxMouseEvent &e){
// Calculate the zoom delta and apply it to the current zoom factor
auto _zoom = ((float)e.GetWheelRotation()) / e.GetWheelDelta();
/*if ($Slic3r::GUI::Settings->{_}{invert_zoom}) {
_zoom *= -1;
}*/
_zoom = clamp(-4, _zoom,4);
_zoom /= 10;
zoom /= 1-_zoom;
/*
# In order to zoom around the mouse point we need to translate
# the camera target
my $size = Slic3r::Pointf->new($self->GetSizeWH);
my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #-
$self->_camera_target->translate(
# ($pos - $size/2) represents the vector from the viewport center
# to the mouse point. By multiplying it by $zoom we get the new,
# transformed, length of such vector.
# Since we want that point to stay fixed, we move our camera target
# in the opposite direction by the delta of the length of such vector
# ($zoom - 1). We then scale everything by 1/$self->_zoom since
# $self->_camera_target is expressed in terms of model units.
-($pos->x - $size->x/2) * ($zoom) / $self->_zoom,
-($pos->y - $size->y/2) * ($zoom) / $self->_zoom,
0,
) if 0;
*/
dirty = true;
Refresh();
}
void Scene3D::mouse_dclick(wxMouseEvent &e){
/*
if (@{$self->volumes}) {
$self->zoom_to_volumes;
} else {
$self->zoom_to_bed;
}*/
dirty = true;
Refresh();
}
void Scene3D::resize(){
if(!dirty)return;
dirty = false;
const auto s = GetSize();
glViewport(0,0,s.GetWidth(),s.GetHeight());
const auto x = s.GetWidth()/zoom,
y = s.GetHeight()/zoom,
depth = 1000.0f; // my $depth = 10 * max(@{ $self->max_bounding_box->size });
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(
-x/2, x/2, -y/2, y/2,
-depth, 2*depth
);
glMatrixMode(GL_MODELVIEW);
}
void Scene3D::set_bed_shape(Points _bed_shape){
bed_shape = _bed_shape;
const float GROUND_Z = -0.02f;
// triangulate bed
const auto expoly = ExPolygon(Polygon(bed_shape));
const auto box = expoly.bounding_box();
bed_bound = box;
{
std::vector<Polygon> triangles;
expoly.triangulate(&triangles);
bed_verts.clear();
for(const auto &triangle : triangles){
for(const auto &point : triangle.points){
bed_verts.push_back(unscale(point.x));
bed_verts.push_back(unscale(point.y));
bed_verts.push_back(GROUND_Z);
}
}
}
{
std::vector<Polyline> lines;
Points tmp;
for (coord_t x = box.min.x; x <= box.max.x; x += scale_(10)) {
lines.push_back(Polyline());
lines.back().append(Point(x,box.min.y));
lines.back().append(Point(x,box.max.y));
}
for (coord_t y = box.min.y; y <= box.max.y; y += scale_(10)) {
lines.push_back(Polyline());
lines.back().append(Point(box.min.x,y));
lines.back().append(Point(box.max.x,y));
}
// clip with a slightly grown expolygon because our lines lay on the contours and
// may get erroneously clipped
// my @lines = map Slic3r::Line->new(@$_[0,-1]),
grid_verts.clear();
const Polylines clipped = intersection_pl(lines,offset_ex(expoly,SCALED_EPSILON).at(0));
for(const Polyline &line : clipped){
for(const Point &point : line.points){
grid_verts.push_back(unscale(point.x));
grid_verts.push_back(unscale(point.y));
grid_verts.push_back(GROUND_Z);
}
}
// append bed contours
for(const Line &line : expoly.lines()){
grid_verts.push_back(unscale(line.a.x));
grid_verts.push_back(unscale(line.a.y));
grid_verts.push_back(GROUND_Z);
grid_verts.push_back(unscale(line.b.x));
grid_verts.push_back(unscale(line.b.y));
grid_verts.push_back(GROUND_Z);
}
}
//$self->origin(Slic3r::Pointf->new(0,0));
}
void Scene3D::init_gl(){
if(this->init)return;
this->init = true;
glClearColor(0, 0, 0, 1);
glColor3f(1, 0, 0);
glEnable(GL_DEPTH_TEST);
glClearDepth(1.0);
glDepthFunc(GL_LEQUAL);
glEnable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Set antialiasing/multisampling
glDisable(GL_LINE_SMOOTH);
glDisable(GL_POLYGON_SMOOTH);
//glEnable(GL_MULTISAMPLE) if ($self->{can_multisample});
// ambient lighting
GLfloat ambient[] = {0.1f, 0.1f, 0.1f, 1.0f};
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHT1);
// light from camera
GLfloat pos[] = {1.0f, 0.0f, 1.0f, 0.0f}, spec[] = {0.8f, 0.8f, 0.8f, 1.0f}, diff[] = {0.4f, 0.4f, 0.4f, 1.0f};
glLightfv(GL_LIGHT1, GL_POSITION, pos);
glLightfv(GL_LIGHT1, GL_SPECULAR, spec);
glLightfv(GL_LIGHT1, GL_DIFFUSE, diff);
// Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. Default: GL_SMOOTH
glShadeModel(GL_SMOOTH);
GLfloat fbdiff[] = {0.3f, 0.3f, 0.3f,1}, fbspec[] = {1.0f, 1.0f, 1.0f, 1.0f}, fbemis[] = {0.1f,0.1f,0.1f,0.9f};
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, fbdiff);
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, fbspec);
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50);
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, fbemis);
// A handy trick -- have surface material mirror the color.
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
glEnable(GL_COLOR_MATERIAL);
//glEnable(GL_MULTISAMPLE) if ($self->{can_multisample});
}
void Scene3D::draw_background(){
glDisable(GL_LIGHTING);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glBegin(GL_QUADS);
auto bottom = ui_settings->color->BOTTOM_COLOR(), top = ui_settings->color->TOP_COLOR();
if(ui_settings->color->SOLID_BACKGROUNDCOLOR()){
bottom = top = ui_settings->color->BACKGROUND_COLOR();
}
glColor3ub(bottom.Red(), bottom.Green(), bottom.Blue());
glVertex2f(-1.0,-1.0);
glVertex2f(1,-1.0);
glColor3ub(top.Red(), top.Green(), top.Blue());
glVertex2f(1, 1);
glVertex2f(-1.0, 1);
glEnd();
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}
void Scene3D::draw_ground(){
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnableClientState(GL_VERTEX_ARRAY);
/*my $triangle_vertex;
if (HAS_VBO) {
($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(3, GL_FLOAT, 0, bed_verts.data());
const auto ground = ui_settings->color->GROUND_COLOR(), grid = ui_settings->color->GRID_COLOR();
glColor4ub(ground.Red(), ground.Green(), ground.Blue(),ground.Alpha());
glNormal3d(0,0,1);
glDrawArrays(GL_TRIANGLES, 0, bed_verts.size() / 3);
// we need depth test for grid, otherwise it would disappear when looking
// the object from below
glEnable(GL_DEPTH_TEST);
// draw grid
glLineWidth(2);
/*my $grid_vertex;
if (HAS_VBO) {
($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(3, GL_FLOAT, 0, grid_verts.data());
glColor4ub(grid.Red(), grid.Green(), grid.Blue(),grid.Alpha());
glNormal3d(0,0,1);
glDrawArrays(GL_LINES, 0, grid_verts.size() / 3);
glDisableClientState(GL_VERTEX_ARRAY);
glDisable(GL_BLEND);
/*if (HAS_VBO) {
# Turn off buffer objects to let the rest of the draw code work.
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
glDeleteBuffersARB_p($grid_vertex);
glDeleteBuffersARB_p($triangle_vertex);
}*/
}
void Scene3D::draw_axes (Pointf3 center, float length, int width, bool always_visible){
/*
my $volumes_bb = $self->volumes_bounding_box;
{
# draw axes
# disable depth testing so that axes are not covered by ground
glDisable(GL_DEPTH_TEST);
my $origin = $self->origin;
my $axis_len = max(
max(@{ $self->bed_bounding_box->size }),
1.2 * max(@{ $volumes_bb->size }),
);
glLineWidth(2);
glBegin(GL_LINES);
# draw line for x axis
glColor3f(1, 0, 0);
glVertex3f(@$origin, $ground_z);
glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,,
# draw line for y axis
glColor3f(0, 1, 0);
glVertex3f(@$origin, $ground_z);
glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++
glEnd();
# draw line for Z axis
# (re-enable depth test so that axis is correctly shown when objects are behind it)
glEnable(GL_DEPTH_TEST);
glBegin(GL_LINES);
glColor3f(0, 0, 1);
glVertex3f(@$origin, $ground_z);
glVertex3f(@$origin, $ground_z+$axis_len);
glEnd();
}
*/
if (always_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(center.x, center.y, center.z);
glVertex3f(center.x + length, center.y, center.z);
// draw line for y axis
glColor3f(0, 1, 0);
glVertex3f(center.x, center.y, center.z);
glVertex3f(center.x, center.y + length, center.z);
// draw line for Z axis
glColor3f(0, 0, 1);
glVertex3f(center.x, center.y, center.z);
glVertex3f(center.x, center.y, center.z + length);
glEnd();
glEnable(GL_DEPTH_TEST);
}
void Scene3D::draw_volumes(){
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
for(const Volume &volume : volumes){
glPushMatrix();
glTranslatef(volume.origin.x, volume.origin.y, volume.origin.z);
glCullFace(GL_BACK);
glVertexPointer(3, GL_FLOAT, 0, volume.model.verts.data());
glNormalPointer(GL_FLOAT, 0, volume.model.norms.data());
glColor4ub(volume.color.Red(), volume.color.Green(), volume.color.Blue(), volume.color.Alpha());
glDrawArrays(GL_TRIANGLES, 0, volume.model.verts.size()/3);
glPopMatrix();
}
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glDisable(GL_BLEND);
}
void Scene3D::repaint(wxPaintEvent& e) {
if(!this->IsShownOnScreen())return;
// There should be a context->IsOk check once wx is updated
if(!this->SetCurrent(*(this->glContext)))return;
init_gl();
resize();
glClearColor(1, 1, 1, 1);
glClearDepth(1);
glDepthFunc(GL_LESS);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(-theta, 1, 0, 0); // pitch
glRotatef(phi, 0, 0, 1); // yaw
/*} else {
my @rotmat = quat_to_rotmatrix($self->quat);
glMultMatrixd_p(@rotmat[0..15]);
}*/
glTranslatef(-_camera_target.x, -_camera_target.y, -_camera_target.z);
// light from above
GLfloat pos[] = {-0.5f, -0.5f, 1.0f, 0.0f}, spec[] = {0.2f, 0.2f, 0.2f, 1.0f}, diff[] = {0.5f, 0.5f, 0.5f, 1.0f};
glLightfv(GL_LIGHT0, GL_POSITION, pos);
glLightfv(GL_LIGHT0, GL_SPECULAR, spec);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diff);
before_render();
draw_background();
draw_ground();
/*my $origin = $self->origin;
my $axis_len = max(
max(@{ $self->bed_bounding_box->size }),
1.2 * max(@{ $volumes_bb->size }),
);*/
draw_axes(Pointf3(0.0f,0.0f,0.0f),
unscale(bed_bound.radius()),2,true/*origin,calulcated length,2, true*/);
// draw objects
glEnable(GL_LIGHTING);
draw_volumes();
after_render();
if (dragging/*defined $self->_drag_start_pos || defined $self->_drag_start_xy*/) {
draw_axes(_camera_target, 10.0f, 1, true/*camera,10,1,true*/);
draw_axes(_camera_target, 10.0f, 4, false/*camera,10,4,false*/);
}
glFlush();
SwapBuffers();
// Calling glFinish has a performance penalty, but it seems to fix some OpenGL driver hang-up with extremely large scenes.
glFinish();
}
} } // Namespace Slic3r::GUI

74
src/GUI/Scene3D.hpp Normal file
View File

@ -0,0 +1,74 @@
#ifndef SCENE3D_HPP
#define SCENE3D_HPP
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include <wx/glcanvas.h>
#include "Settings.hpp"
#include "Point.hpp"
#include "3DScene.hpp"
#include "BoundingBox.hpp"
#include "Model.hpp"
namespace Slic3r { namespace GUI {
struct Volume {
wxColor color;
Pointf3 origin;
GLVertexArray model;
BoundingBoxf3 bb;
};
class Scene3D : public wxGLCanvas {
public:
Scene3D(wxWindow* parent, const wxSize& size);
private:
wxGLContext* glContext;
// Camera settings
float zoom = 5.0f, phi = 0.0f, theta = 0.0f;
Pointf3 _camera_target = Pointf3(0.0f,0.0f,0.0f);
// Optional point used for dragging calculations
bool dragging = false;
Point drag_start = Point(0,0);
// Bed Stuff
std::vector<float> bed_verts, grid_verts;
Points bed_shape;
BoundingBox bed_bound;
void repaint(wxPaintEvent &e); // Redraws every frame
bool dirty = true; // Resize needs to be called before render
void resize(); // Handle glViewport and projection matrices
bool init = false; // Has opengl been initted
void init_gl(); // Handles lights and materials
// Useded in repaint
void draw_background();
void draw_ground();
void draw_axes(Pointf3 center, float length, int width, bool alwaysvisible);
protected:
Linef3 mouse_ray(Point win); // Utility for backtracking from window coordinates
void draw_volumes(); // Draws volumes (for use in before_render)
void set_bed_shape(Points _bed_shape);
std::vector<Volume> volumes;
Volume load_object(ModelVolume &mv, ModelInstance &mi);
// Virtual methods to override
virtual void mouse_up(wxMouseEvent &e);
virtual void mouse_move(wxMouseEvent &e);
virtual void mouse_dclick(wxMouseEvent &e);
virtual void mouse_wheel(wxMouseEvent &e);
virtual void before_render(){};
virtual void after_render(){};
};
} } // Namespace Slic3r::GUI
#endif

View File

@ -1,4 +1,5 @@
#include "misc_ui.hpp"
#include "utils.hpp"
#include <wx/stdpaths.h>
#include <wx/msgdlg.h>
#include <wx/arrstr.h>
@ -148,16 +149,10 @@ std::vector<wxString> open_model(wxWindow* parent, wxWindow* top) {
return tmp;
}
/// Remove extra zeroes generated from std::to_string on doubles
std::string trim_zeroes(std::string in) {
std::string result {""};
std::regex strip_zeroes("(0*)$");
std::regex_replace (std::back_inserter(result), in.begin(), in.end(), strip_zeroes, "");
if (result.back() == '.') result.append("0");
return result;
}
wxString trim_zeroes(wxString in) { return wxString(trim_zeroes(in.ToStdString())); }
wxString trim_zeroes(wxString in) { return wxString(_trim_zeroes(in.ToStdString())); }
}} // namespace Slic3r::GUI

View File

@ -155,7 +155,6 @@ inline Slic3r::Point new_scale(const wxPoint& p) { return Slic3r::Point::new_sca
/// Singleton for UI settings.
extern std::unique_ptr<Settings> ui_settings;
std::string trim_zeroes(std::string in);
wxString trim_zeroes(wxString in);

View File

@ -22,8 +22,6 @@
using namespace Slic3r;
void confess_at(const char *file, int line, const char *func, const char *pat, ...){}
int
main(int argc, char **argv)
{

View File

@ -0,0 +1,113 @@
#include <catch.hpp>
#ifndef WX_PRECOMP
#include <wx/app.h>
#include <wx/sizer.h>
#include <wx/uiaction.h>
#include <wx/colour.h>
#include <wx/clrpicker.h>
#endif // WX_PRECOMP
#include <iostream>
#include "testableframe.h"
#include "OptionsGroup/Field.hpp"
#include "ConfigBase.hpp"
using namespace std::string_literals;
SCENARIO("UI_Color: default values from options and basic accessor methods") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
auto simple_option {ConfigOptionDef()};
auto* default_color {new ConfigOptionString("#FFFF00")};
auto event_count {0};
auto changefunc {[&event_count] (const std::string& opt_id, const std::string& color) { event_count++; }};
GIVEN("A Color Picker") {
simple_option.default_value = default_color;
auto test_field {Slic3r::GUI::UI_Color(wxTheApp->GetTopWindow(), simple_option)};
test_field.on_change = changefunc;
WHEN("Object is constructed with default_value of '#FFFF00'.") {
THEN("get_string() returns '#FFFF00'") {
REQUIRE(test_field.get_string() == "#FFFF00"s);
}
THEN("get_int() returns 0") {
REQUIRE(test_field.get_int() == 0);
}
}
WHEN("Color picker receives a color picked event") {
event_count = 0;
test_field.disable_change_event = false;
auto ev {wxFocusEvent(wxEVT_COLOURPICKER_CHANGED, test_field.picker()->GetId())};
ev.SetEventObject(test_field.picker());
test_field.picker()->ProcessWindowEvent(ev);
THEN("_on_change fires.") {
REQUIRE(event_count == 1);
}
}
}
}
SCENARIO( "Color string value tests") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
auto simple_option {ConfigOptionDef()};
auto* default_color {new ConfigOptionString("#FFFFFF")};
GIVEN("A Color Picker") {
auto test_field {Slic3r::GUI::UI_Color(wxTheApp->GetTopWindow(), simple_option)};
WHEN("Set_value is called with a string of '#FFFFFF'") {
test_field.set_value("#FFFFFF");
THEN("Internal wxColor is equal to wxWhite") {
REQUIRE(test_field.picker()->GetColour() == wxColour(*wxWHITE));
}
}
WHEN("Set_value is called with a string of '#FFAACC'") {
test_field.set_value("#FFAACC");
THEN("Internal wxColor is equal to wxColor(255, 170, 204)") {
REQUIRE(test_field.picker()->GetColour() == wxColour(255, 170, 204));
}
}
WHEN("Set_value is called with a string of '#3020FF'") {
test_field.set_value("#3020FF");
THEN("Internal wxColor is equal to wxColor(48, 32, 255)") {
REQUIRE(test_field.picker()->GetColour() == wxColour(48,32,255));
}
}
WHEN("Set_value is called with a string of '#01A06D'") {
test_field.set_value("#01A06D");
THEN("Internal wxColor is equal to wxColor(01, 160, 109)") {
REQUIRE(test_field.picker()->GetColour() == wxColour(1,160,109));
}
}
WHEN("Internal color is set to wxWHITE") {
test_field.picker()->SetColour(wxColour(*wxWHITE));
THEN("String value is #FFFFFF") {
REQUIRE(test_field.get_string() == "#FFFFFF"s);
}
}
WHEN("Internal color is set to wxRED") {
test_field.picker()->SetColour(wxColour(*wxRED));
THEN("String value is #FF0000") {
REQUIRE(test_field.get_string() == "#FF0000"s);
}
}
WHEN("Internal color is set to wxGREEN") {
test_field.picker()->SetColour(wxColour(*wxGREEN));
THEN("String value is #00FF00") {
REQUIRE(test_field.get_string() == "#00FF00"s);
}
}
WHEN("Internal color is set to wxBLUE") {
test_field.picker()->SetColour(wxColour(*wxBLUE));
THEN("String value is #0000FF") {
REQUIRE(test_field.get_string() == "#0000FF"s);
}
}
}
delete default_color;
}

View File

@ -0,0 +1,207 @@
#include <catch.hpp>
#ifndef WX_PRECOMP
#include <wx/app.h>
#include <wx/sizer.h>
#include <wx/uiaction.h>
#include <wx/slider.h>
#endif // WX_PRECOMP
#include "testableframe.h"
#include "OptionsGroup/Field.hpp"
#include "ConfigBase.hpp"
using namespace std::string_literals;
SCENARIO( "UI_Slider: Defaults, Min/max handling, accessors.") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
auto simple_option {ConfigOptionDef()};
auto* default_color {new ConfigOptionFloat(30.0)};
simple_option.min = 0;
simple_option.max = 60;
simple_option.default_value = default_color;
GIVEN("A UI Slider with default scale") {
auto event_count {0};
auto changefunc {[&event_count] (const std::string& opt_id, const double& color) { event_count++; }};
WHEN("Option min is 0") {
simple_option.default_value = default_color;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
THEN("default of 0 is used.") {
REQUIRE(test_field.slider()->GetMin() == 0);
}
}
WHEN("Option max is 0") {
simple_option.max = 0;
simple_option.default_value = default_color;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
THEN("default of 100*10(scale) is used.") {
REQUIRE(test_field.slider()->GetMax() == 1000);
}
}
WHEN("Default value is used.") {
simple_option.default_value = default_color;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
THEN("Raw slider value is 30 * scale (10) = 300") {
REQUIRE(test_field.slider()->GetValue() == 300);
}
}
WHEN("set_scale is called with 25 for argument") {
simple_option.default_value = default_color;
simple_option.max = 100;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
test_field.set_scale(25);
THEN("Slider min is 0") {
REQUIRE(test_field.slider()->GetMin() == 0);
}
THEN("Slider max is 100*25 = 2500") {
REQUIRE(test_field.slider()->GetMax() == 2500);
}
THEN("Slider raw value is 30*25 = 750") {
REQUIRE(test_field.slider()->GetValue() == 750);
}
THEN("UI_Slider get_double still reads 30") {
REQUIRE(test_field.get_double() == Approx(30));
}
THEN("UI_Slider get_int reads 30") {
REQUIRE(test_field.get_int() == 30);
}
THEN("Textctrl raw value still reads 30") {
REQUIRE(test_field.textctrl()->GetValue() == "30.0"s);
}
test_field.on_change = changefunc;
THEN("on_change does not fire.") {
REQUIRE(event_count == 0);
}
}
}
GIVEN("A UI Slider with default scale") {
WHEN("No default value is given from config.") {
simple_option.default_value = nullptr;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
THEN("Initial value of slider is 0.") {
REQUIRE(test_field.get_int() == 0);
REQUIRE(test_field.get_double() == 0);
REQUIRE(test_field.slider()->GetValue() == 0);
REQUIRE(test_field.textctrl()->GetValue() == "0.0");
}
}
WHEN("disable() is called") {
simple_option.default_value = default_color;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
test_field.slider()->Enable();
test_field.textctrl()->Enable();
test_field.textctrl()->SetEditable(true);
test_field.disable();
THEN("Internal slider is disabled.") {
REQUIRE(test_field.slider()->IsEnabled() == false);
}
THEN("Internal textctrl is disabled.") {
REQUIRE(test_field.textctrl()->IsEnabled() == false);
REQUIRE(test_field.textctrl()->IsEditable() == false);
}
}
WHEN("enable() is called") {
simple_option.default_value = default_color;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
test_field.slider()->Disable();
test_field.textctrl()->Disable();
test_field.textctrl()->SetEditable(false);
test_field.enable();
THEN("Internal slider is enabled.") {
REQUIRE(test_field.slider()->IsEnabled() == true);
}
THEN("Internal textctrl is enabled.") {
REQUIRE(test_field.textctrl()->IsEnabled() == true);
REQUIRE(test_field.textctrl()->IsEditable() == true);
}
}
}
GIVEN("A UI Slider with scale of 1") {
WHEN("Option min is 0") {
simple_option.default_value = default_color;
simple_option.min = 0;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option, 1)};
WHEN("default of 0 is used.") {
REQUIRE(test_field.slider()->GetMin() == 0);
}
}
WHEN("Option max is 0") {
simple_option.default_value = default_color;
simple_option.max = 0;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option, 1)};
WHEN("default of 10 * 100 = 1000 is used.") {
REQUIRE(test_field.slider()->GetMax() == 100);
}
}
WHEN("Default value is used.") {
simple_option.default_value = default_color;
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option, 1)};
THEN("Raw slider value is 30 * scale (1)") {
REQUIRE(test_field.slider()->GetValue() == 30);
}
}
}
}
SCENARIO( "UI_Slider: Event handlers") {
wxTestableFrame* old = dynamic_cast<wxTestableFrame*>(wxTheApp->GetTopWindow());
old->Destroy();
wxTheApp->SetTopWindow(new wxTestableFrame());
wxUIActionSimulator sim;
wxMilliSleep(500);
auto simple_option {ConfigOptionDef()};
auto* default_color {new ConfigOptionString("30")};
simple_option.min = 0;
simple_option.max = 60;
simple_option.default_value = default_color;
auto event_count {0};
auto changefunc {[&event_count] (const std::string& opt_id, const double& color) { event_count++; }};
auto killfunc {[&event_count](const std::string& opt_id) { event_count += 1; }};
GIVEN("A UI Slider") {
auto test_field {Slic3r::GUI::UI_Slider(wxTheApp->GetTopWindow(), simple_option)};
test_field.on_change = changefunc;
test_field.on_kill_focus = killfunc;
WHEN("UI Slider receives a text change event (enter is pressed).") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.textctrl()->GetId())};
ev.SetEventObject(test_field.textctrl());
test_field.textctrl()->ProcessWindowEvent(ev);
THEN("_on_change fires") {
REQUIRE(event_count == 1);
}
}
WHEN("UI Slider textbox receives a text change event (enter is not pressed).") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_TEXT, test_field.textctrl()->GetId())};
ev.SetEventObject(test_field.textctrl());
test_field.textctrl()->ProcessWindowEvent(ev);
THEN("Nothing happens") {
REQUIRE(event_count == 0);
}
}
WHEN("UI Slider receives a slider changed event.") {
event_count = 0;
auto ev {wxCommandEvent(wxEVT_SLIDER, test_field.slider()->GetId())};
ev.SetEventObject(test_field.slider());
test_field.slider()->ProcessWindowEvent(ev);
THEN("on_change fires") {
REQUIRE(event_count == 1);
}
}
WHEN("UI_Slider text ctrl receives a kill focus event.") {
event_count = 0;
auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.textctrl()->GetId())};
ev.SetEventObject(test_field.textctrl());
test_field.textctrl()->ProcessWindowEvent(ev);
THEN("_kill_focus and on_change fires") {
REQUIRE(event_count == 2);
}
}
}
}

View File

@ -0,0 +1,29 @@
#include <catch.hpp>
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/sizer.h"
#include "wx/uiaction.h"
#endif // WX_PRECOMP
#include <iostream>
#include "OptionsGroup.hpp"
#include "ConfigBase.hpp"
#include "Config.hpp"
using namespace Slic3r::GUI;
SCENARIO("OptionsGroup: Construction.") {
}
SCENARIO("ConfigOptionsGroup: Factory methods") {
GIVEN("A default Slic3r config") {
OptionsGroup optgroup;
WHEN("add_single_option is called for avoid_crossing_perimeters") {
THEN("a UI_Checkbox is added to the field map") {
REQUIRE(optgroup.get_field("avoid_crossing_perimeters") != nullptr);
}
THEN("a ConfigOptionBool is added to the option map") {
REQUIRE(optgroup.get_field("avoid_crossing_perimeters") != nullptr);
}
}
}
}

21
src/test/inputs/README.md Normal file
View File

@ -0,0 +1,21 @@
Directory containing test-specific input files that are not source code.
---
Rules
===
* Each folder shall be named for the test that it supports.
* `test_config` directory supports `test_config.cpp`, etc.
* If a specific test file is reused across multiple tests, it should go in the `common` directory.
* Each extra input file should be named in such a way that it is relevant to the specific feature of it.
* No files should have special characters in the name (unless those special characters are part of the test).
* No spaces should be in the file paths (again, unless testing the presence of spaces is part of the test).
* Input files that are 3D models should be as small/specific as possible.
* Do not add copyrighted models without permission from the copyright holder.
* Do not add NonCommercial models, these would need to be relicensed from the original holder.
* Add any necessary licensing or attributation information as `<filename>.license`
* Example: A CC-By-SA 3D model called `cool_statue_bro.stl` should have its attributation/license information included in a file called `cool_statue_bro.license`
* Any submitted files without an accompanying `.license` file are assumed to be licensed under [CC-By-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/us/).
* The author of a commit adding any extra input files asserts that, via the process of committing those files, that they
* have abided by the terms of all licensing agreements involved with the files added or
* are the author of the files in question and submit them to the Slic3r project under the terms of [CC-By-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/us/).

View File

@ -0,0 +1,252 @@
# generated by Slic3r 1.3.0 on Mon Jul 16 14:02:26 2018
[presets]
filament = - default -
print = Untitled
print_1 = - default -
printer = Untitled
[print:- default -]
adaptive_slicing = 0
adaptive_slicing_quality = 75%
avoid_crossing_perimeters = 0
bottom_infill_pattern = rectilinear
bottom_solid_layers = 3
bridge_acceleration = 0
bridge_flow_ratio = 1
bridge_speed = 60
brim_connections_width = 0
brim_width = 0
compatible_printers =
complete_objects = 0
default_acceleration = 0
dont_support_bridges = 1
external_perimeter_extrusion_width = 0
external_perimeter_speed = 50%
external_perimeters_first = 0
extra_perimeters = 1
extruder_clearance_height = 20
extruder_clearance_radius = 20
extrusion_width = 0
fill_angle = 45
fill_density = 20%
fill_gaps = 1
fill_pattern = stars
first_layer_acceleration = 0
first_layer_extrusion_width = 200%
first_layer_height = 0.35
first_layer_speed = 30
gap_fill_speed = 20
gcode_comments = 0
infill_acceleration = 0
infill_every_layers = 1
infill_extruder = 1
infill_extrusion_width = 0
infill_first = 0
infill_only_where_needed = 0
infill_overlap = 55%
infill_speed = 80
interface_shells = 0
interior_brim_width = 0
layer_height = 0.3
match_horizontal_surfaces = 0
max_print_speed = 80
max_volumetric_speed = 0
min_skirt_length = 0
notes =
only_retract_when_crossing_perimeters = 1
ooze_prevention = 0
output_filename_format = [input_filename_base].gcode
overhangs = 1
perimeter_acceleration = 0
perimeter_extruder = 1
perimeter_extrusion_width = 0
perimeter_speed = 60
perimeters = 3
post_process =
print_settings_id =
raft_layers = 0
regions_overlap = 0
resolution = 0
seam_position = aligned
shortcuts = support_material
skirt_distance = 6
skirt_height = 1
skirts = 1
small_perimeter_speed = 15
solid_infill_below_area = 70
solid_infill_every_layers = 0
solid_infill_extruder = 1
solid_infill_extrusion_width = 0
solid_infill_speed = 20
spiral_vase = 0
standby_temperature_delta = -5
support_material = 1
support_material_angle = 0
support_material_buildplate_only = 0
support_material_contact_distance = 0.2
support_material_enforce_layers = 0
support_material_extruder = 1
support_material_extrusion_width = 0
support_material_interface_extruder = 1
support_material_interface_extrusion_width = 0
support_material_interface_layers = 3
support_material_interface_spacing = 0
support_material_interface_speed = 100%
support_material_max_layers = 0
support_material_pattern = pillars
support_material_spacing = 2.5
support_material_speed = 60
support_material_threshold = 60%
thin_walls = 1
top_infill_extrusion_width = 0
top_infill_pattern = rectilinear
top_solid_infill_speed = 15
top_solid_layers = 3
travel_speed = 130
xy_size_compensation = 0
[print:Untitled]
adaptive_slicing = 0
adaptive_slicing_quality = 75%
avoid_crossing_perimeters = 0
bottom_infill_pattern = rectilinear
bottom_solid_layers = 3
bridge_acceleration = 0
bridge_flow_ratio = 1
bridge_speed = 60
brim_connections_width = 0
brim_width = 0
compatible_printers =
complete_objects = 0
default_acceleration = 0
dont_support_bridges = 1
external_perimeter_extrusion_width = 0
external_perimeter_speed = 50%
external_perimeters_first = 0
extra_perimeters = 1
extruder_clearance_height = 20
extruder_clearance_radius = 20
extrusion_width = 0
fill_angle = 45
fill_density = 20%
fill_gaps = 1
fill_pattern = stars
first_layer_acceleration = 0
first_layer_extrusion_width = 200%
first_layer_height = 0.35
first_layer_speed = 30
gap_fill_speed = 20
gcode_comments = 1
infill_acceleration = 0
infill_every_layers = 1
infill_extruder = 1
infill_extrusion_width = 0
infill_first = 0
infill_only_where_needed = 0
infill_overlap = 55%
infill_speed = 80
interface_shells = 0
interior_brim_width = 0
layer_height = 0.3
match_horizontal_surfaces = 0
max_print_speed = 80
max_volumetric_speed = 0
min_skirt_length = 0
notes =
only_retract_when_crossing_perimeters = 1
ooze_prevention = 0
output_filename_format = [input_filename_base].gcode
overhangs = 1
perimeter_acceleration = 0
perimeter_extruder = 1
perimeter_extrusion_width = 0
perimeter_speed = 60
perimeters = 3
post_process =
print_settings_id =
raft_layers = 0
regions_overlap = 0
resolution = 0
seam_position = aligned
shortcuts = support_material
skirt_distance = 6
skirt_height = 1
skirts = 1
small_perimeter_speed = 15
solid_infill_below_area = 70
solid_infill_every_layers = 0
solid_infill_extruder = 1
solid_infill_extrusion_width = 0
solid_infill_speed = 20
spiral_vase = 0
standby_temperature_delta = -5
support_material = 0
support_material_angle = 0
support_material_buildplate_only = 0
support_material_contact_distance = 0.2
support_material_enforce_layers = 0
support_material_extruder = 1
support_material_extrusion_width = 0
support_material_interface_extruder = 1
support_material_interface_extrusion_width = 0
support_material_interface_layers = 3
support_material_interface_spacing = 0
support_material_interface_speed = 100%
support_material_max_layers = 0
support_material_pattern = pillars
support_material_spacing = 2.5
support_material_speed = 60
support_material_threshold = 60%
thin_walls = 1
top_infill_extrusion_width = 0
top_infill_pattern = rectilinear
top_solid_infill_speed = 15
top_solid_layers = 3
travel_speed = 130
xy_size_compensation = 0
[printer:Untitled]
bed_shape = 0x0,200x0,200x200,0x200
before_layer_gcode =
between_objects_gcode =
end_gcode = M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n
extruder_offset = 0x0
gcode_flavor = reprap
has_heatbed = 1
host_type = octoprint
layer_gcode = ; LAYER START [layer_z] [layer_num]
max_layer_height = 0.3
min_layer_height = 0.15
nozzle_diameter = 0.5
octoprint_apikey = dsdsdsd
pressure_advance = 0
print_host = 127.0.0.1
printer_notes =
printer_settings_id =
retract_before_travel = 2
retract_layer_change = 1
retract_length = 2
retract_length_toolchange = 10
retract_lift = 1
retract_lift_above = 0
retract_lift_below = 0
retract_restart_extra = 0
retract_restart_extra_toolchange = 0
retract_speed = 40
serial_port =
serial_speed = 250000
start_gcode = G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n
toolchange_gcode =
use_firmware_retraction = 0
use_relative_e_distances = 0
use_set_and_wait_bed = 0
use_set_and_wait_extruder = 0
use_volumetric_e = 0
vibration_limit = 0
wipe = 1
z_offset = 0
z_steps_per_mm = 0
[settings]
autocenter = 1

View File

@ -0,0 +1,167 @@
# generated by Slic3r 1.3.0 on
adaptive_slicing = 0
adaptive_slicing_quality = 75%
avoid_crossing_perimeters = 0
bed_shape = 0x0,200x0,200x200,0x200
bed_temperature = 0
before_layer_gcode =
between_objects_gcode =
bottom_infill_pattern = rectilinear
bottom_solid_layers = 3
bridge_acceleration = 0
bridge_fan_speed = 100
bridge_flow_ratio = 1
bridge_speed = 60
brim_connections_width = 0
brim_width = 0
compatible_printers =
complete_objects = 0
cooling = 1
default_acceleration = 0
disable_fan_first_layers = 3
dont_support_bridges = 1
duplicate_distance = 6
end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n"
end_gcode = M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n
external_perimeter_extrusion_width = 0
external_perimeter_speed = 50%
external_perimeters_first = 0
extra_perimeters = 1
extruder_clearance_height = 20
extruder_clearance_radius = 20
extruder_offset = 0x0
extrusion_axis = E
extrusion_multiplier = 1
extrusion_width = 0
fan_always_on = 0
fan_below_layer_time = 60
filament_colour = #FFFFFF
filament_cost = 0
filament_density = 0
filament_diameter = 3
filament_max_volumetric_speed = 0
filament_notes = ""
filament_settings_id =
fill_angle = 45
fill_density = 20%
fill_gaps = 1
fill_pattern = stars
first_layer_acceleration = 0
first_layer_bed_temperature = 0
first_layer_extrusion_width = 200%
first_layer_height = 0.35
first_layer_speed = 30
first_layer_temperature = 200
gap_fill_speed = 20
gcode_arcs = 0
gcode_comments = 1
gcode_flavor = reprap
has_heatbed = 1
host_type = octoprint
infill_acceleration = 0
infill_every_layers = 1
infill_extruder = 1
infill_extrusion_width = 0
infill_first = 0
infill_only_where_needed = 0
infill_overlap = 55%
infill_speed = 80
interface_shells = 0
interior_brim_width = 0
layer_gcode = ; LAYER START [layer_z] [layer_num]
layer_height = 0.3
match_horizontal_surfaces = 0
max_fan_speed = 100
max_layer_height = 0.3
max_print_speed = 80
max_volumetric_speed = 0
min_fan_speed = 35
min_layer_height = 0.15
min_print_speed = 10
min_skirt_length = 0
notes =
nozzle_diameter = 0.5
octoprint_apikey = dsdsdsd
only_retract_when_crossing_perimeters = 1
ooze_prevention = 0
output_filename_format = [input_filename_base].gcode
overhangs = 1
perimeter_acceleration = 0
perimeter_extruder = 1
perimeter_extrusion_width = 0
perimeter_speed = 60
perimeters = 3
post_process =
pressure_advance = 0
print_host = 127.0.0.1
print_settings_id =
printer_notes =
printer_settings_id =
raft_layers = 0
regions_overlap = 0
resolution = 0
retract_before_travel = 2
retract_layer_change = 1
retract_length = 2
retract_length_toolchange = 10
retract_lift = 1
retract_lift_above = 0
retract_lift_below = 0
retract_restart_extra = 0
retract_restart_extra_toolchange = 0
retract_speed = 40
seam_position = aligned
sequential_print_priority = 0
serial_port =
serial_speed = 250000
shortcuts = support_material
skirt_distance = 6
skirt_height = 1
skirts = 1
slowdown_below_layer_time = 5
small_perimeter_speed = 15
solid_infill_below_area = 70
solid_infill_every_layers = 0
solid_infill_extruder = 1
solid_infill_extrusion_width = 0
solid_infill_speed = 20
spiral_vase = 0
standby_temperature_delta = -5
start_filament_gcode = "; Filament gcode\n"
start_gcode = G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n
support_material = 0
support_material_angle = 0
support_material_buildplate_only = 0
support_material_contact_distance = 0.2
support_material_enforce_layers = 0
support_material_extruder = 1
support_material_extrusion_width = 0
support_material_interface_extruder = 1
support_material_interface_extrusion_width = 0
support_material_interface_layers = 3
support_material_interface_spacing = 0
support_material_interface_speed = 100%
support_material_max_layers = 0
support_material_pattern = pillars
support_material_spacing = 2.5
support_material_speed = 60
support_material_threshold = 60%
temperature = 200
thin_walls = 1
threads = 8
toolchange_gcode =
top_infill_extrusion_width = 0
top_infill_pattern = rectilinear
top_solid_infill_speed = 15
top_solid_layers = 3
travel_speed = 130
use_firmware_retraction = 0
use_relative_e_distances = 0
use_set_and_wait_bed = 0
use_set_and_wait_extruder = 0
use_volumetric_e = 0
vibration_limit = 0
wipe = 1
xy_size_compensation = 0
z_offset = 0
z_steps_per_mm = 0

View File

@ -0,0 +1,211 @@
#include <catch.hpp>
#include "Config.hpp"
#include <test_options.hpp>
#include <string>
using namespace Slic3r;
using namespace std::literals::string_literals;
SCENARIO("Generic config validation performs as expected.") {
GIVEN("A config generated from default options") {
auto config {Slic3r::Config::new_from_defaults()};
WHEN( "perimeter_extrusion_width is set to 250%, a valid value") {
config->set("perimeter_extrusion_width", "250%");
THEN( "The config is read as valid.") {
REQUIRE(config->validate() == true);
}
}
WHEN( "perimeter_extrusion_width is set to -10, an invalid value") {
config->set("perimeter_extrusion_width", -10);
THEN( "An InvalidOptionValue exception is thrown.") {
auto except_thrown {false};
try {
config->validate();
} catch (const InvalidOptionValue& e) {
except_thrown = true;
}
REQUIRE(except_thrown == true);
}
}
WHEN( "perimeters is set to -10, an invalid value") {
config->set("perimeters", -10);
THEN( "An InvalidOptionValue exception is thrown.") {
auto except_thrown {false};
try {
config->validate();
} catch (const InvalidOptionValue& e) {
except_thrown = true;
}
REQUIRE(except_thrown == true);
}
}
}
}
SCENARIO("Config accessor functions perform as expected.") {
GIVEN("A config generated from default options") {
auto config {Slic3r::Config::new_from_defaults()};
WHEN("A boolean option is set through the bool interface") {
config->set("gcode_comments", true);
THEN("The underlying value is set correctly.") {
REQUIRE(config->get<ConfigOptionBool>("gcode_comments").getBool() == true);
}
}
WHEN("A boolean option is set through the string interface") {
config->set("gcode_comments", "1");
THEN("The underlying value is set correctly.") {
REQUIRE(config->get<ConfigOptionBool>("gcode_comments").getBool() == true);
}
}
WHEN("A boolean option is set through the int interface") {
config->set("gcode_comments", 1);
THEN("The underlying value is set correctly.") {
REQUIRE(config->get<ConfigOptionBool>("gcode_comments").getBool() == true);
}
}
WHEN("A numeric option is set through the string interface") {
config->set("bed_temperature", "100");
THEN("The underlying value is set correctly.") {
REQUIRE(config->get<ConfigOptionInt>("bed_temperature").getInt() == 100);
}
}
WHEN("An integer-based option is set through the integer interface") {
config->set("bed_temperature", 100);
THEN("The underlying value is set correctly.") {
REQUIRE(config->get<ConfigOptionInt>("bed_temperature").getInt() == 100);
}
}
WHEN("An floating-point option is set through the integer interface") {
config->set("perimeter_speed", 10);
THEN("The underlying value is set correctly.") {
REQUIRE(config->get<ConfigOptionFloat>("perimeter_speed").getFloat() == 10.0);
}
}
WHEN("A floating-point option is set through the double interface") {
config->set("perimeter_speed", 5.5);
THEN("The underlying value is set correctly.") {
REQUIRE(config->get<ConfigOptionFloat>("perimeter_speed").getFloat() == 5.5);
}
}
WHEN("An integer-based option is set through the double interface") {
config->set("bed_temperature", 5.5);
THEN("The underlying value is set, rounded to the nearest integer.") {
REQUIRE(config->get<ConfigOptionInt>("bed_temperature").getInt() == 6);
}
}
WHEN("A numeric option is set to a non-numeric value.") {
THEN("An InvalidOptionValue exception is thown.") {
REQUIRE_THROWS_AS(config->set("perimeter_speed", "zzzz"), InvalidOptionValue);
}
THEN("The value does not change.") {
REQUIRE(config->get<ConfigOptionFloat>("perimeter_speed").getFloat() == 60.0);
}
}
WHEN("A string option is set through the string interface") {
config->set("octoprint_apikey", "100");
THEN("The underlying value is set correctly.") {
REQUIRE(config->get<ConfigOptionString>("octoprint_apikey").getString() == "100");
}
}
WHEN("A string option is set through the integer interface") {
config->set("octoprint_apikey", 100);
THEN("The underlying value is set correctly.") {
REQUIRE(config->get<ConfigOptionString>("octoprint_apikey").getString() == "100");
}
}
WHEN("A string option is set through the double interface") {
config->set("octoprint_apikey", 100.5);
THEN("The underlying value is set correctly.") {
REQUIRE(config->get<ConfigOptionString>("octoprint_apikey").getString() == std::to_string(100.5));
}
}
WHEN("A float or percent is set as a percent through the string interface.") {
config->set("first_layer_extrusion_width", "100%");
THEN("Value and percent flag are 100/true") {
auto tmp {config->get<ConfigOptionFloatOrPercent>("first_layer_extrusion_width")};
REQUIRE(tmp.percent == true);
REQUIRE(tmp.value == 100);
}
}
WHEN("A float or percent is set as a float through the string interface.") {
config->set("first_layer_extrusion_width", "100");
THEN("Value and percent flag are 100/false") {
auto tmp {config->get<ConfigOptionFloatOrPercent>("first_layer_extrusion_width")};
REQUIRE(tmp.percent == false);
REQUIRE(tmp.value == 100);
}
}
WHEN("A float or percent is set as a float through the int interface.") {
config->set("first_layer_extrusion_width", 100);
THEN("Value and percent flag are 100/false") {
auto tmp {config->get<ConfigOptionFloatOrPercent>("first_layer_extrusion_width")};
REQUIRE(tmp.percent == false);
REQUIRE(tmp.value == 100);
}
}
WHEN("A float or percent is set as a float through the double interface.") {
config->set("first_layer_extrusion_width", 100.5);
THEN("Value and percent flag are 100.5/false") {
auto tmp {config->get<ConfigOptionFloatOrPercent>("first_layer_extrusion_width")};
REQUIRE(tmp.percent == false);
REQUIRE(tmp.value == 100.5);
}
}
WHEN("An invalid option is requested during set.") {
THEN("An InvalidOptionType exception is thrown.") {
REQUIRE_THROWS_AS(config->set("deadbeef_invalid_option", 1), InvalidOptionType);
REQUIRE_THROWS_AS(config->set("deadbeef_invalid_option", 1.0), InvalidOptionType);
REQUIRE_THROWS_AS(config->set("deadbeef_invalid_option", "1"), InvalidOptionType);
REQUIRE_THROWS_AS(config->set("deadbeef_invalid_option", true), InvalidOptionType);
}
}
WHEN("An invalid option is requested during get.") {
THEN("An InvalidOptionType exception is thrown.") {
REQUIRE_THROWS_AS(config->get<ConfigOptionString>("deadbeef_invalid_option", false), InvalidOptionType);
REQUIRE_THROWS_AS(config->get<ConfigOptionFloat>("deadbeef_invalid_option", false), InvalidOptionType);
REQUIRE_THROWS_AS(config->get<ConfigOptionInt>("deadbeef_invalid_option", false), InvalidOptionType);
REQUIRE_THROWS_AS(config->get<ConfigOptionBool>("deadbeef_invalid_option", false), InvalidOptionType);
}
}
WHEN("An invalid option is requested during get_ptr.") {
THEN("An InvalidOptionType exception is thrown.") {
REQUIRE_THROWS_AS(config->get_ptr<ConfigOptionString>("deadbeef_invalid_option", false), InvalidOptionType);
REQUIRE_THROWS_AS(config->get_ptr<ConfigOptionFloat>("deadbeef_invalid_option", false), InvalidOptionType);
REQUIRE_THROWS_AS(config->get_ptr<ConfigOptionInt>("deadbeef_invalid_option", false), InvalidOptionType);
REQUIRE_THROWS_AS(config->get_ptr<ConfigOptionBool>("deadbeef_invalid_option", false), InvalidOptionType);
}
}
WHEN("getX called on an unset option.") {
THEN("The default is returned.") {
REQUIRE(config->getFloat("layer_height") == 0.3);
REQUIRE(config->getString("layer_height") == "0.3");
REQUIRE(config->getString("layer_height") == "0.3");
REQUIRE(config->getInt("raft_layers") == 0);
REQUIRE(config->getBool("support_material") == false);
}
}
WHEN("getFloat called on an option that has been set.") {
config->set("layer_height", 0.5);
THEN("The set value is returned.") {
REQUIRE(config->getFloat("layer_height") == 0.5);
REQUIRE(config->getString("layer_height") == "0.5");
}
}
}
}
SCENARIO("Config ini load/save interface", "[!mayfail]") {
WHEN("new_from_ini is called") {
auto config {Slic3r::Config::new_from_ini(std::string(testfile_dir) + "test_config/new_from_ini.ini"s) };
THEN("Config object contains ini file options.") {
}
}
REQUIRE(false);
}

View File

@ -0,0 +1,186 @@
#include <catch.hpp>
#include <numeric>
#include <sstream>
#include "test_data.hpp" // get access to init_print, etc
#include "Config.hpp"
#include "Model.hpp"
#include "ConfigBase.hpp"
#include "GCodeReader.hpp"
#include "Flow.hpp"
#include "libslic3r.h"
using namespace Slic3r::Test;
using namespace Slic3r;
SCENARIO("Extrusion width specifics", "[!mayfail]") {
GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer and a 20mm cube mesh") {
// this is a sharedptr
auto config {Slic3r::Config::new_from_defaults()};
config->set("skirts", 1);
config->set("brim_width", 2);
config->set("perimeters", 3);
config->set("fill_density", 40);
config->set("first_layer_height", "100%");
WHEN("first layer width set to 2mm") {
Slic3r::Model model;
config->set("first_layer_extrusion_width", 2.0);
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
std::vector<double> E_per_mm_bottom;
auto gcode {std::stringstream("")};
Slic3r::Test::gcode(gcode, print);
auto parser {Slic3r::GCodeReader()};
const auto layer_height { config->getFloat("layer_height") };
parser.parse_stream(gcode, [&E_per_mm_bottom, layer_height] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
{
if (self.Z == Approx(layer_height).margin(0.01)) { // only consider first layer
if (line.extruding() && line.dist_XY() > 0) {
E_per_mm_bottom.emplace_back(line.dist_E() / line.dist_XY());
}
}
});
THEN(" First layer width applies to everything on first layer.") {
bool pass = false;
auto avg_E {std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast<double>(E_per_mm_bottom.size())};
pass = (std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [avg_E] (const double& v) { return v == Approx(avg_E); }) == 0);
REQUIRE(pass == true);
REQUIRE(E_per_mm_bottom.size() > 0); // make sure it actually passed because of extrusion
}
THEN(" First layer width does not apply to upper layer.") {
}
}
}
}
// needs gcode export
SCENARIO(" Bridge flow specifics.", "[!mayfail]") {
GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio and an overhang mesh.") {
WHEN("bridge_flow_ratio is set to 1.0") {
THEN("Output flow is as expected.") {
}
}
WHEN("bridge_flow_ratio is set to 0.5") {
THEN("Output flow is as expected.") {
}
}
WHEN("bridge_flow_ratio is set to 2.0") {
THEN("Output flow is as expected.") {
}
}
}
GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio, fixed extrusion width of 0.4mm and an overhang mesh.") {
WHEN("bridge_flow_ratio is set to 1.0") {
THEN("Output flow is as expected.") {
}
}
WHEN("bridge_flow_ratio is set to 0.5") {
THEN("Output flow is as expected.") {
}
}
WHEN("bridge_flow_ratio is set to 2.0") {
THEN("Output flow is as expected.") {
}
}
}
}
/// Test the expected behavior for auto-width,
/// spacing, etc
SCENARIO("Flow: Flow math for non-bridges", "[!mayfail]") {
GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") {
auto width {ConfigOptionFloatOrPercent(1.0, false)};
float spacing {0.4};
float nozzle_diameter {0.4};
float bridge_flow {1.0};
float layer_height {0.5};
// Spacing for non-bridges is has some overlap
THEN("External perimeter flow has spacing fixed to 1.1*nozzle_diameter") {
auto flow {Flow::new_from_config_width(frExternalPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, 0.0f)};
REQUIRE(flow.spacing() == Approx((1.1*nozzle_diameter) - layer_height * (1.0 - PI / 4.0)));
}
THEN("Internal perimeter flow has spacing of 1.05 (minimum)") {
auto flow {Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, 0.0f)};
REQUIRE(flow.spacing() == Approx((1.05*nozzle_diameter) - layer_height * (1.0 - PI / 4.0)));
}
THEN("Spacing for supplied width is 0.8927f") {
auto flow {Flow::new_from_config_width(frExternalPerimeter, width, nozzle_diameter, layer_height, 0.0f)};
REQUIRE(flow.spacing() == Approx(width() - layer_height * (1.0 - PI / 4.0)));
flow = Flow::new_from_config_width(frPerimeter, width, nozzle_diameter, layer_height, 0.0f);
REQUIRE(flow.spacing() == Approx(width() - layer_height * (1.0 - PI / 4.0)));
}
}
/// Check the min/max
GIVEN("Nozzle Diameter of 0.25") {
float spacing {0.4};
float nozzle_diameter {0.25};
float bridge_flow {0.0};
float layer_height {0.5};
WHEN("layer height is set to 0.2") {
layer_height = 0.15f;
THEN("Max width is set.") {
auto flow {Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, 0.0f)};
REQUIRE(flow.width == Approx(1.4*nozzle_diameter));
}
}
WHEN("Layer height is set to 0.2") {
layer_height = 0.3f;
THEN("Min width is set.") {
auto flow {Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height, 0.0f)};
REQUIRE(flow.width == Approx(1.05*nozzle_diameter));
}
}
}
}
/// Spacing, width calculation for bridge extrusions
SCENARIO("Flow: Flow math for bridges", "[!mayfail]") {
GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") {
auto width {ConfigOptionFloatOrPercent(1.0, false)};
auto spacing {0.4};
auto nozzle_diameter {0.4};
auto bridge_flow {1.0};
auto layer_height {0.5};
WHEN("Flow role is frExternalPerimeter") {
auto flow {Flow::new_from_config_width(frExternalPerimeter, width, nozzle_diameter, layer_height, bridge_flow)};
THEN("Bridge width is same as nozzle diameter") {
REQUIRE(flow.width == Approx(nozzle_diameter));
}
THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") {
REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING));
}
}
WHEN("Flow role is frInfill") {
auto flow {Flow::new_from_config_width(frInfill, width, nozzle_diameter, layer_height, bridge_flow)};
THEN("Bridge width is same as nozzle diameter") {
REQUIRE(flow.width == Approx(nozzle_diameter));
}
THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") {
REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING));
}
}
WHEN("Flow role is frPerimeter") {
auto flow {Flow::new_from_config_width(frPerimeter, width, nozzle_diameter, layer_height, bridge_flow)};
THEN("Bridge width is same as nozzle diameter") {
REQUIRE(flow.width == Approx(nozzle_diameter));
}
THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") {
REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING));
}
}
WHEN("Flow role is frSupportMaterial") {
auto flow {Flow::new_from_config_width(frSupportMaterial, width, nozzle_diameter, layer_height, bridge_flow)};
THEN("Bridge width is same as nozzle diameter") {
REQUIRE(flow.width == Approx(nozzle_diameter));
}
THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") {
REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING));
}
}
}
}

View File

@ -0,0 +1,286 @@
#include <catch.hpp>
#include "Point.hpp"
#include "BoundingBox.hpp"
#include "Polygon.hpp"
#include "Polyline.hpp"
#include "Line.hpp"
#include "Geometry.hpp"
#include "ClipperUtils.hpp"
using namespace Slic3r;
TEST_CASE("Polygon::contains works properly", ""){
// this test was failing on Windows (GH #1950)
auto polygon = Polygon(std::vector<Point>({
Point(207802834,-57084522),
Point(196528149,-37556190),
Point(173626821,-25420928),
Point(171285751,-21366123),
Point(118673592,-21366123),
Point(116332562,-25420928),
Point(93431208,-37556191),
Point(82156517,-57084523),
Point(129714478,-84542120),
Point(160244873,-84542120)
}));
auto point = Point(95706562, -57294774);
REQUIRE(polygon.contains(point));
}
SCENARIO("Intersections of line segments"){
GIVEN("Integer coordinates"){
auto line1 = Line(Point(5,15),Point(30,15));
auto line2 = Line(Point(10,20), Point(10,10));
THEN("The intersection is valid"){
Point point;
line1.intersection(line2,&point);
REQUIRE(Point(10,15) == point);
}
}
GIVEN("Scaled coordinates"){
auto line1 = Line(Point(73.6310778185108/0.0000001, 371.74239268924/0.0000001), Point(73.6310778185108/0.0000001, 501.74239268924/0.0000001));
auto line2 = Line(Point(75/0.0000001, 437.9853/0.0000001), Point(62.7484/0.0000001, 440.4223/0.0000001));
THEN("There is still an intersection"){
Point point;
REQUIRE(line1.intersection(line2,&point));
}
}
}
/*
Tests for unused methods still written in perl
{
my $polygon = Slic3r::Polygon->new(
[45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800],
[43749700, 343843000], [45422300, 352251500], [52362100, 362637800], [62748400, 369577600],
[75000000, 372014700], [87251500, 369577600], [97637800, 362637800], [104577600, 352251500],
[107014700, 340000000], [104577600, 327748400], [97637800, 317362100], [87251500, 310422300],
[82789200, 309534700], [69846100, 294726100], [254081000, 294726100], [285273900, 348753500],
[285273900, 461246400], [254081000, 515273900],
);
# this points belongs to $polyline
# note: it's actually a vertex, while we should better check an intermediate point
my $point = Slic3r::Point->new(104577600, 327748400);
local $Slic3r::Geometry::epsilon = 1E-5;
is_deeply Slic3r::Geometry::polygon_segment_having_point($polygon, $point)->pp,
[ [107014700, 340000000], [104577600, 327748400] ],
'polygon_segment_having_point';
}
{
auto point = Point(736310778.185108, 5017423926.8924);
auto line = Line(Point((long int) 627484000, (long int) 3695776000), Point((long int) 750000000, (long int)3720147000));
//is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment';
}
// Possible to delete
{
//my $p1 = [10, 10];
//my $p2 = [10, 20];
//my $p3 = [10, 30];
//my $p4 = [20, 20];
//my $p5 = [0, 20];
THEN("Points in a line give the correct angles"){
//is Slic3r::Geometry::angle3points($p2, $p3, $p1), PI(), 'angle3points';
//is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points';
}
THEN("Left turns give the correct angle"){
//is Slic3r::Geometry::angle3points($p2, $p4, $p3), PI()/2, 'angle3points';
//is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2, 'angle3points';
}
THEN("Right turns give the correct angle"){
//is Slic3r::Geometry::angle3points($p2, $p3, $p4), PI()/2*3, 'angle3points';
//is Slic3r::Geometry::angle3points($p2, $p1, $p5), PI()/2*3, 'angle3points';
}
//my $p1 = [30, 30];
//my $p2 = [20, 20];
//my $p3 = [10, 10];
//my $p4 = [30, 10];
//is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points';
//is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2*3, 'angle3points';
//is Slic3r::Geometry::angle3points($p2, $p1, $p1), 2*PI(), 'angle3points';
}
SCENARIO("polygon_is_convex works"){
GIVEN("A square of dimension 10"){
//my $cw_square = [ [0,0], [0,10], [10,10], [10,0] ];
THEN("It is not convex clockwise"){
//is polygon_is_convex($cw_square), 0, 'cw square is not convex';
}
THEN("It is convex counter-clockwise"){
//is polygon_is_convex([ reverse @$cw_square ]), 1, 'ccw square is convex';
}
}
GIVEN("A concave polygon"){
//my $convex1 = [ [0,0], [10,0], [10,10], [0,10], [0,6], [4,6], [4,4], [0,4] ];
THEN("It is concave"){
//is polygon_is_convex($convex1), 0, 'concave polygon';
}
}
}*/
TEST_CASE("Creating a polyline generates the obvious lines"){
auto polyline = Polyline();
polyline.points = std::vector<Point>({Point(0, 0), Point(10, 0), Point(20, 0)});
REQUIRE(polyline.lines().at(0).a == Point(0,0));
REQUIRE(polyline.lines().at(0).b == Point(10,0));
REQUIRE(polyline.lines().at(1).a == Point(10,0));
REQUIRE(polyline.lines().at(1).b == Point(20,0));
}
TEST_CASE("Splitting a Polygon generates a polyline correctly"){
auto polygon = Polygon(std::vector<Point>({Point(0, 0), Point(10, 0), Point(5, 5)}));
auto split = polygon.split_at_index(1);
REQUIRE(split.points[0]==Point(10,0));
REQUIRE(split.points[1]==Point(5,5));
REQUIRE(split.points[2]==Point(0,0));
REQUIRE(split.points[3]==Point(10,0));
}
TEST_CASE("Bounding boxes are scaled appropriately"){
auto bb = BoundingBox(std::vector<Point>({Point(0, 1), Point(10, 2), Point(20, 2)}));
bb.scale(2);
REQUIRE(bb.min == Point(0,2));
REQUIRE(bb.max == Point(40,4));
}
TEST_CASE("Offseting a line generates a polygon correctly"){
auto line = Line(Point(10,10), Point(20,10));
Polyline tmp(line);
Polygon area = offset(tmp,5).at(0);
REQUIRE(area.area() == Polygon(std::vector<Point>({Point(10,5),Point(20,5),Point(20,15),Point(10,15)})).area());
}
TEST_CASE("Chained path working correctly"){
// if chained_path() works correctly, these points should be joined with no diagonal paths
// (thus 26 units long)
std::vector<Point> points = {Point(26,26),Point(52,26),Point(0,26),Point(26,52),Point(26,0),Point(0,52),Point(52,52),Point(52,0)};
std::vector<Points::size_type> indices;
Geometry::chained_path(points,indices);
for(Points::size_type i = 0; i < indices.size()-1;i++){
double dist = points.at(indices.at(i)).distance_to(points.at(indices.at(i+1)));
REQUIRE(abs(dist-26) <= Geometry::epsilon);
}
}
SCENARIO("Line distances"){
GIVEN("A line"){
auto line = Line(Point(0, 0), Point(20, 0));
THEN("Points on the line segment have 0 distance"){
REQUIRE(Point(0, 0).distance_to(line) == 0);
REQUIRE(Point(20, 0).distance_to(line) == 0);
REQUIRE(Point(10, 0).distance_to(line) == 0);
}
THEN("Points off the line have the appropriate distance"){
REQUIRE(Point(10, 10).distance_to(line) == 10);
REQUIRE(Point(50, 0).distance_to(line) == 30);
}
}
}
SCENARIO("Polygon convex/concave detection"){
GIVEN(("A Square with dimension 100")){
auto square = Polygon /*new_scale*/(std::vector<Point>({
Point(100,100),
Point(200,100),
Point(200,200),
Point(100,200)}));
THEN("It has 4 convex points counterclockwise"){
REQUIRE(square.concave_points(PI*4/3).size() == 0);
REQUIRE(square.convex_points(PI*2/3).size() == 4);
}
THEN("It has 4 concave points clockwise"){
square.make_clockwise();
REQUIRE(square.concave_points(PI*4/3).size() == 4);
REQUIRE(square.convex_points(PI*2/3).size() == 0);
}
}
GIVEN("A Square with an extra colinearvertex"){
auto square = Polygon /*new_scale*/(std::vector<Point>({
Point(150,100),
Point(200,100),
Point(200,200),
Point(100,200),
Point(100,100)}));
THEN("It has 4 convex points counterclockwise"){
REQUIRE(square.concave_points(PI*4/3).size() == 0);
REQUIRE(square.convex_points(PI*2/3).size() == 4);
}
}
GIVEN("A Square with an extra collinear vertex in different order"){
auto square = Polygon /*new_scale*/(std::vector<Point>({
Point(200,200),
Point(100,200),
Point(100,100),
Point(150,100),
Point(200,100)}));
THEN("It has 4 convex points counterclockwise"){
REQUIRE(square.concave_points(PI*4/3).size() == 0);
REQUIRE(square.convex_points(PI*2/3).size() == 4);
}
}
GIVEN("A triangle"){
auto triangle = Polygon(std::vector<Point>({
Point(16000170,26257364),
Point(714223,461012),
Point(31286371,461008)
}));
THEN("it has three convex vertices"){
REQUIRE(triangle.concave_points(PI*4/3).size() == 0);
REQUIRE(triangle.convex_points(PI*2/3).size() == 3);
}
}
GIVEN("A triangle with an extra collinear point"){
auto triangle = Polygon(std::vector<Point>({
Point(16000170,26257364),
Point(714223,461012),
Point(20000000,461012),
Point(31286371,461012)
}));
THEN("it has three convex vertices"){
REQUIRE(triangle.concave_points(PI*4/3).size() == 0);
REQUIRE(triangle.convex_points(PI*2/3).size() == 3);
}
}
GIVEN("A polygon with concave vertices with angles of specifically 4/3pi"){
// Two concave vertices of this polygon have angle = PI*4/3, so this test fails
// if epsilon is not used.
auto polygon = Polygon(std::vector<Point>({
Point(60246458,14802768),Point(64477191,12360001),
Point(63727343,11060995),Point(64086449,10853608),
Point(66393722,14850069),Point(66034704,15057334),
Point(65284646,13758387),Point(61053864,16200839),
Point(69200258,30310849),Point(62172547,42483120),
Point(61137680,41850279),Point(67799985,30310848),
Point(51399866,1905506),Point(38092663,1905506),
Point(38092663,692699),Point(52100125,692699)
}));
THEN("the correct number of points are detected"){
REQUIRE(polygon.concave_points(PI*4/3).size() == 6);
REQUIRE(polygon.convex_points(PI*2/3).size() == 10);
}
}
}
TEST_CASE("Triangle Simplification does not result in less than 3 points"){
auto triangle = Polygon(std::vector<Point>({
Point(16000170,26257364), Point(714223,461012), Point(31286371,461008)
}));
REQUIRE(triangle.simplify(250000).at(0).points.size() == 3);
}

View File

@ -0,0 +1,50 @@
#include <catch.hpp>
#include "Model.hpp"
#include "test_data.hpp" // get access to init_print, etc
using namespace Slic3r::Test;
SCENARIO("Model construction") {
GIVEN("A Slic3r Model") {
auto model {Slic3r::Model()};
auto sample_mesh {Slic3r::TriangleMesh::make_cube(20,20,20)};
sample_mesh.repair();
auto config {Slic3r::Config::new_from_defaults()};
std::shared_ptr<Slic3r::Print> print = std::make_shared<Slic3r::Print>();
print->apply_config(config);
WHEN("Model object is added") {
ModelObject* mo {model.add_object()};
THEN("Model object list == 1") {
REQUIRE(model.objects.size() == 1);
}
mo->add_volume(sample_mesh);
THEN("Model volume list == 1") {
REQUIRE(mo->volumes.size() == 1);
}
THEN("Model volume modifier is false") {
REQUIRE(mo->volumes.front()->modifier == false);
}
THEN("Mesh is equivalent to input mesh.") {
REQUIRE(sample_mesh.vertices() == mo->volumes.front()->mesh.vertices());
}
ModelInstance* inst {mo->add_instance()};
inst->rotation = 0;
inst->scaling_factor = 1.0;
model.arrange_objects(print->config.min_object_distance());
model.center_instances_around_point(Slic3r::Pointf(100,100));
print->auto_assign_extruders(mo);
print->add_model_object(mo);
THEN("Print works?") {
print->process();
auto gcode {std::stringstream("")};
print->export_gcode(gcode, true);
REQUIRE(gcode.str().size() > 0);
}
}
}
}

View File

@ -0,0 +1,98 @@
#include <catch.hpp>
#include <string>
#include "test_data.hpp"
#include "libslic3r.h"
using namespace Slic3r::Test;
using namespace std::literals;
SCENARIO("PrintObject: Perimeter generation") {
GIVEN("20mm cube and default config") {
auto config {Slic3r::Config::new_from_defaults()};
TestMesh m { TestMesh::cube_20x20x20 };
Slic3r::Model model;
auto event_counter {0U};
std::string stage;
int value {0};
auto callback {[&event_counter, &stage, &value] (int a, const char* b) { stage = std::string(b); event_counter++; value = a; }};
config->set("fill_density", 0);
WHEN("make_perimeters() is called") {
auto print {Slic3r::Test::init_print({m}, model, config)};
const auto& object = *(print->objects.at(0));
print->objects[0]->make_perimeters();
THEN("67 layers exist in the model") {
REQUIRE(object.layers.size() == 67);
}
THEN("Every layer in region 0 has 1 island of perimeters") {
for(auto* layer : object.layers) {
REQUIRE(layer->regions[0]->perimeters.size() == 1);
}
}
THEN("Every layer in region 0 has 3 paths in its perimeters list.") {
for(auto* layer : object.layers) {
REQUIRE(layer->regions[0]->perimeters.items_count() == 3);
}
}
}
}
}
SCENARIO("Print: Skirt generation") {
GIVEN("20mm cube and default config") {
auto config {Slic3r::Config::new_from_defaults()};
TestMesh m { TestMesh::cube_20x20x20 };
Slic3r::Model model;
auto event_counter {0U};
std::string stage;
int value {0};
config->set("skirt_height", 1);
config->set("skirt_distance", 1);
WHEN("Skirts is set to 2 loops") {
config->set("skirts", 2);
auto print {Slic3r::Test::init_print({m}, model, config)};
print->make_skirt();
THEN("Skirt Extrusion collection has 2 loops in it") {
REQUIRE(print->skirt.items_count() == 2);
REQUIRE(print->skirt.flatten().entities.size() == 2);
}
}
}
}
SCENARIO("Print: Brim generation") {
GIVEN("20mm cube and default config, 1mm first layer width") {
auto config {Slic3r::Config::new_from_defaults()};
TestMesh m { TestMesh::cube_20x20x20 };
Slic3r::Model model;
auto event_counter {0U};
std::string stage;
int value {0};
config->set("first_layer_extrusion_width", 1);
WHEN("Brim is set to 3mm") {
config->set("brim_width", 3);
auto print {Slic3r::Test::init_print({m}, model, config)};
print->make_brim();
THEN("Brim Extrusion collection has 3 loops in it") {
REQUIRE(print->brim.items_count() == 3);
}
}
WHEN("Brim is set to 6mm") {
config->set("brim_width", 6);
auto print {Slic3r::Test::init_print({m}, model, config)};
print->make_brim();
THEN("Brim Extrusion collection has 6 loops in it") {
REQUIRE(print->brim.items_count() == 6);
}
}
WHEN("Brim is set to 6mm, extrusion width 0.5mm") {
config->set("brim_width", 6);
config->set("first_layer_extrusion_width", 0.5);
auto print {Slic3r::Test::init_print({m}, model, config)};
print->make_brim();
THEN("Brim Extrusion collection has 12 loops in it") {
REQUIRE(print->brim.items_count() == 12);
}
}
}
}

View File

@ -0,0 +1,210 @@
#include <catch.hpp>
#include <regex>
#include "test_data.hpp"
#include "libslic3r.h"
#include "GCodeReader.hpp"
using namespace Slic3r::Test;
using namespace Slic3r;
std::regex perimeters_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; perimeter");
std::regex infill_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; infill");
std::regex skirt_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; skirt");
SCENARIO( "PrintGCode basic functionality") {
GIVEN("A default configuration and a print test object") {
auto config {Slic3r::Config::new_from_defaults()};
auto gcode {std::stringstream("")};
WHEN("the output is executed with no support material") {
config->set("first_layer_extrusion_width", 0);
config->set("gcode_comments", true);
config->set("start_gcode", "");
Slic3r::Model model;
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
print->process();
Slic3r::Test::gcode(gcode, print);
auto exported {gcode.str()};
THEN("Some text output is generated.") {
REQUIRE(exported.size() > 0);
}
THEN("Exported text contains slic3r version") {
REQUIRE(exported.find(SLIC3R_VERSION) != std::string::npos);
}
THEN("Exported text contains git commit id") {
REQUIRE(exported.find("; Git Commit") != std::string::npos);
REQUIRE(exported.find(BUILD_COMMIT) != std::string::npos);
}
THEN("Exported text contains extrusion statistics.") {
REQUIRE(exported.find("; external perimeters extrusion width") != std::string::npos);
REQUIRE(exported.find("; perimeters extrusion width") != std::string::npos);
REQUIRE(exported.find("; infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; solid infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; top solid infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; support material extrusion width") == std::string::npos);
REQUIRE(exported.find("; first layer extrusion width") == std::string::npos);
}
THEN("GCode preamble is emitted.") {
REQUIRE(exported.find("G21 ; set units to millimeters") != std::string::npos);
}
THEN("Config options emitted for print config, default region config, default object config") {
REQUIRE(exported.find("; first_layer_temperature") != std::string::npos);
REQUIRE(exported.find("; layer_height") != std::string::npos);
REQUIRE(exported.find("; fill_density") != std::string::npos);
}
THEN("Infill is emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(exported, has_match, infill_regex));
}
THEN("Perimeters are emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(exported, has_match, perimeters_regex));
}
THEN("Skirt is emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(exported, has_match, skirt_regex));
}
THEN("final Z height is ~20mm") {
double final_z {0.0};
auto reader {GCodeReader()};
reader.apply_config(print->config);
reader.parse(exported, [&final_z] (GCodeReader& self, const GCodeReader::GCodeLine& line)
{
final_z = std::max(final_z, static_cast<double>(self.Z)); // record the highest Z point we reach
});
REQUIRE(final_z == Approx(20.15));
}
}
WHEN("output is executed with complete objects and two differently-sized meshes") {
Slic3r::Model model;
config->set("first_layer_extrusion_width", 0);
config->set("first_layer_height", 0.3);
config->set("support_material", false);
config->set("raft_layers", 0);
config->set("complete_objects", true);
config->set("gcode_comments", true);
config->set("between_objects_gcode", "; between-object-gcode");
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20, TestMesh::ipadstand}, model, config)};
Slic3r::Test::gcode(gcode, print);
auto exported {gcode.str()};
THEN("Some text output is generated.") {
REQUIRE(exported.size() > 0);
}
THEN("Infill is emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(exported, has_match, infill_regex));
}
THEN("Perimeters are emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(exported, has_match, perimeters_regex));
}
THEN("Skirt is emitted.") {
std::smatch has_match;
REQUIRE(std::regex_search(exported, has_match, skirt_regex));
}
THEN("Between-object-gcode is emitted.") {
REQUIRE(exported.find("; between-object-gcode") != std::string::npos);
}
THEN("final Z height is ~27mm") {
double final_z {0.0};
auto reader {GCodeReader()};
reader.apply_config(print->config);
reader.parse(exported, [&final_z] (GCodeReader& self, const GCodeReader::GCodeLine& line)
{
final_z = std::max(final_z, static_cast<double>(self.Z)); // record the highest Z point we reach
});
REQUIRE(final_z == Approx(30).margin(0.1)); // close enough
}
THEN("Z height resets on object change") {
double final_z {0.0};
bool reset {false};
auto reader {GCodeReader()};
reader.apply_config(print->config);
reader.parse(exported, [&final_z, &reset] (GCodeReader& self, const GCodeReader::GCodeLine& line)
{
if (final_z > 0 && std::abs(self.Z - 0.3) < 0.01 ) { // saw higher Z before this, now it's lower
reset = true;
} else {
final_z = std::max(final_z, static_cast<double>(self.Z)); // record the highest Z point we reach
}
});
REQUIRE(reset == true);
}
THEN("Shorter object is printed before taller object.") {
double final_z {0.0};
bool reset {false};
auto reader {GCodeReader()};
reader.apply_config(print->config);
reader.parse(exported, [&final_z, &reset] (GCodeReader& self, const GCodeReader::GCodeLine& line)
{
if (final_z > 0 && std::abs(self.Z - 0.3) < 0.01 ) {
reset = (final_z > 20.0);
} else {
final_z = std::max(final_z, static_cast<double>(self.Z)); // record the highest Z point we reach
}
});
REQUIRE(reset == true);
}
}
WHEN("the output is executed with support material") {
Slic3r::Model model;
config->set("first_layer_extrusion_width", 0);
config->set("support_material", true);
config->set("raft_layers", 3);
config->set("gcode_comments", true);
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
Slic3r::Test::gcode(gcode, print);
auto exported {gcode.str()};
THEN("Some text output is generated.") {
REQUIRE(exported.size() > 0);
}
THEN("Exported text contains extrusion statistics.") {
REQUIRE(exported.find("; external perimeters extrusion width") != std::string::npos);
REQUIRE(exported.find("; perimeters extrusion width") != std::string::npos);
REQUIRE(exported.find("; infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; solid infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; top solid infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; support material extrusion width") != std::string::npos);
REQUIRE(exported.find("; first layer extrusion width") == std::string::npos);
}
THEN("Raft is emitted.") {
REQUIRE(exported.find("; raft") != std::string::npos);
}
}
WHEN("the output is executed with a separate first layer extrusion width") {
Slic3r::Model model;
config->set("first_layer_extrusion_width", 0.5);
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
Slic3r::Test::gcode(gcode, print);
auto exported {gcode.str()};
THEN("Some text output is generated.") {
REQUIRE(exported.size() > 0);
}
THEN("Exported text contains extrusion statistics.") {
REQUIRE(exported.find("; external perimeters extrusion width") != std::string::npos);
REQUIRE(exported.find("; perimeters extrusion width") != std::string::npos);
REQUIRE(exported.find("; infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; solid infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; top solid infill extrusion width") != std::string::npos);
REQUIRE(exported.find("; support material extrusion width") == std::string::npos);
REQUIRE(exported.find("; first layer extrusion width") != std::string::npos);
}
}
WHEN("Cooling is enabled and the fan is disabled.") {
config->set("cooling", true);
config->set("disable_fan_first_layers", 5);
Slic3r::Model model;
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
Slic3r::Test::gcode(gcode, print);
auto exported {gcode.str()};
THEN("GCode to disable fan is emitted."){
REQUIRE(exported.find("M107") != std::string::npos);
}
}
gcode.clear();
}
}

View File

@ -0,0 +1,122 @@
#include <catch.hpp>
#include "test_data.hpp" // get access to init_print, etc
#include "GCodeReader.hpp"
using namespace Slic3r::Test;
using namespace Slic3r;
SCENARIO("Original Slic3r Skirt/Brim tests", "[!mayfail]") {
GIVEN("Configuration with a skirt height of 2") {
auto config {Config::new_from_defaults()};
config->set("skirts", 1);
config->set("skirt_height", 2);
config->set("perimeters", 0);
config->set("support_material_speed", 99);
// avoid altering speeds unexpectedly
config->set("cooling", 0);
config->set("first_layer_speed", "100%");
WHEN("multiple objects are printed") {
auto gcode {std::stringstream("")};
Slic3r::Model model;
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20, TestMesh::cube_20x20x20}, model, config)};
std::map<double, bool> layers_with_skirt;
Slic3r::Test::gcode(gcode, print);
auto parser {Slic3r::GCodeReader()};
parser.parse_stream(gcode, [&layers_with_skirt, &config] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
{
if (self.Z > 0) {
if (line.extruding() && line.new_F() == config->getFloat("support_material_speed") * 60.0) {
layers_with_skirt[self.Z] = 1;
}
}
});
THEN("skirt_height is honored") {
REQUIRE(layers_with_skirt.size() == (size_t)config->getInt("skirt_height"));
}
}
}
GIVEN("A default configuration") {
auto config {Config::new_from_defaults()};
config->set("support_material_speed", 99);
// avoid altering speeds unexpectedly
config->set("cooling", 0);
config->set("first_layer_speed", "100%");
// remove noise from top/solid layers
config->set("top_solid_layers", 0);
config->set("bottom_solid_layers", 0);
WHEN("Brim width is set to 5") {
config->set("perimeters", 0);
config->set("skirts", 0);
config->set("brim_width", 5);
THEN("Brim is generated") {
REQUIRE(false);
}
}
WHEN("Skirt area is smaller than the brim") {
config->set("skirts", 1);
config->set("brim_width", 10);
THEN("GCode generates successfully.") {
REQUIRE(false);
}
}
WHEN("Skirt height is 0 and skirts > 0") {
config->set("skirts", 2);
config->set("skirt_height", 0);
THEN("GCode generates successfully.") {
REQUIRE(false);
}
}
WHEN("Perimeter extruder = 2 and support extruders = 3") {
THEN("Brim is printed with the extruder used for the perimeters of first object") {
REQUIRE(false);
}
}
WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") {
THEN("brim is printed with same extruder as skirt") {
REQUIRE(false);
}
}
WHEN("Object is plated with overhang support and a brim") {
config->set("layer_height", 0.4);
config->set("first_layer_height", 0.4);
config->set("skirts", 1);
config->set("skirt_distance", 0);
config->set("support_material_speed", 99);
config->set("perimeter_extruder", 1);
config->set("support_material_extruder", 2);
config->set("cooling", 0); // to prevent speeds to be altered
config->set("first_layer_speed", "100%"); // to prevent speeds to be altered
Slic3r::Model model;
auto print {Slic3r::Test::init_print({TestMesh::overhang}, model, config)};
print->process();
config->set("support_material", true); // to prevent speeds to be altered
THEN("skirt length is large enough to contain object with support") {
REQUIRE(false);
}
}
WHEN("Large minimum skirt length is used.") {
config->set("min_skirt_length", 20);
auto gcode {std::stringstream("")};
Slic3r::Model model;
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
THEN("Gcode generation doesn't crash") {
Slic3r::Test::gcode(gcode, print);
auto exported {gcode.str()};
REQUIRE(exported.size() > 0);
}
}
}
}

View File

@ -0,0 +1,295 @@
#include <catch.hpp>
#include <libslic3r/IO.hpp>
#include <libslic3r/GCodeReader.hpp>
#include "libslic3r.h"
#include "TriangleMesh.hpp"
#include "Model.hpp"
#include "SupportMaterial.hpp"
using namespace std;
using namespace Slic3r;
void test_1_checks(Print &print, bool &a, bool &b, bool &c, bool &d);
bool test_6_checks(Print &print);
// Testing 0.1: supports material member functions.
TEST_CASE("", "")
{
// Create a mesh & modelObject.
TriangleMesh mesh = TriangleMesh::make_cube(20, 20, 20);
// Create modelObject.
Model model = Model();
ModelObject *object = model.add_object();
object->add_volume(mesh);
model.add_default_instances();
// Align to origin.
model.align_instances_to_origin();
// Create Print.
Print print = Print();
vector<coordf_t> contact_z = {1.9};
vector<coordf_t> top_z = {1.1};
print.default_object_config.support_material = 1;
print.default_object_config.set_deserialize("raft_layers", "3");
print.add_model_object(model.objects[0]);
print.objects.front()->_slice();
SupportMaterial *support = print.objects.front()->_support_material();
support->generate(print.objects.front());
REQUIRE(print.objects.front()->support_layer_count() == 3);
}
// Test 1.
SCENARIO("SupportMaterial: support_layers_z and contact_distance")
{
GIVEN("A print object having one modelObject") {
// Create a mesh & modelObject.
TriangleMesh mesh = TriangleMesh::make_cube(20, 20, 20);
// Create modelObject.
Model model = Model();
ModelObject *object = model.add_object();
object->add_volume(mesh);
model.add_default_instances();
// Align to origin.
model.align_instances_to_origin();
// Create Print.
Print print = Print();
print.default_object_config.set_deserialize("support_material", "1");
WHEN("First layer height = 0.4") {
print.default_object_config.set_deserialize("layer_height", "0.2");
print.default_object_config.set_deserialize("first_layer_height", "0.4");
print.add_model_object(model.objects[0]);
print.objects.front()->_slice();
bool a, b, c, d;
test_1_checks(print, a, b, c, d);
THEN("First layer height is honored") {
REQUIRE(a == true);
}
THEN("No null or negative support layers") {
REQUIRE(b == true);
}
THEN("No layers thicker than nozzle diameter") {
REQUIRE(c == true);
}
THEN("Layers above top surfaces are spaced correctly") {
REQUIRE(d == true);
}
}
WHEN("Layer height = 0.2 and, first layer height = 0.3") {
print.default_object_config.set_deserialize("layer_height", "0.2");
print.default_object_config.set_deserialize("first_layer_height", "0.3");
print.add_model_object(model.objects[0]);
print.objects.front()->_slice();
bool a, b, c, d;
test_1_checks(print, a, b, c, d);
THEN("First layer height is honored") {
REQUIRE(a == true);
}
THEN("No null or negative support layers") {
REQUIRE(b == true);
}
THEN("No layers thicker than nozzle diameter") {
REQUIRE(c == true);
}
THEN("Layers above top surfaces are spaced correctly") {
REQUIRE(d == true);
}
}
WHEN("Layer height = nozzle_diameter[0]") {
print.default_object_config.set_deserialize("layer_height", "0.2");
print.default_object_config.set_deserialize("first_layer_height", "0.3");
print.add_model_object(model.objects[0]);
print.objects.front()->_slice();
bool a, b, c, d;
test_1_checks(print, a, b, c, d);
THEN("First layer height is honored") {
REQUIRE(a == true);
}
THEN("No null or negative support layers") {
REQUIRE(b == true);
}
THEN("No layers thicker than nozzle diameter") {
REQUIRE(c == true);
}
THEN("Layers above top surfaces are spaced correctly") {
REQUIRE(d == true);
}
}
}
}
// Test 8.
TEST_CASE("SupportMaterial: forced support is generated", "")
{
// Create a mesh & modelObject.
TriangleMesh mesh = TriangleMesh::make_cube(20, 20, 20);
Model model = Model();
ModelObject *object = model.add_object();
object->add_volume(mesh);
model.add_default_instances();
model.align_instances_to_origin();
Print print = Print();
vector<coordf_t> contact_z = {1.9};
vector<coordf_t> top_z = {1.1};
print.default_object_config.support_material_enforce_layers = 100;
print.default_object_config.support_material = 0;
print.default_object_config.layer_height = 0.2;
print.default_object_config.set_deserialize("first_layer_height", "0.3");
print.add_model_object(model.objects[0]);
print.objects.front()->_slice();
SupportMaterial *support = print.objects.front()->_support_material();
auto support_z = support->support_layers_z(contact_z, top_z, print.default_object_config.layer_height);
bool check = true;
for (size_t i = 1; i < support_z.size(); i++) {
if (support_z[i] - support_z[i - 1] <= 0)
check = false;
}
REQUIRE(check == true);
}
// Test 6.
SCENARIO("SupportMaterial: Checking bridge speed")
{
GIVEN("Print object") {
// Create a mesh & modelObject.
TriangleMesh mesh = TriangleMesh::make_cube(20, 20, 20);
Model model = Model();
ModelObject *object = model.add_object();
object->add_volume(mesh);
model.add_default_instances();
model.align_instances_to_origin();
Print print = Print();
print.config.brim_width = 0;
print.config.skirts = 0;
print.config.skirts = 0;
print.default_object_config.support_material = 1;
print.default_region_config.top_solid_layers = 0; // so that we don't have the internal bridge over infill.
print.default_region_config.bridge_speed = 99;
print.config.cooling = 0;
print.config.set_deserialize("first_layer_speed", "100%");
WHEN("support_material_contact_distance = 0.2") {
print.default_object_config.support_material_contact_distance = 0.2;
print.add_model_object(model.objects[0]);
bool check = test_6_checks(print);
REQUIRE(check == true); // bridge speed is used.
}
WHEN("support_material_contact_distance = 0") {
print.default_object_config.support_material_contact_distance = 0;
print.add_model_object(model.objects[0]);
bool check = test_6_checks(print);
REQUIRE(check == true); // bridge speed is not used.
}
WHEN("support_material_contact_distance = 0.2 & raft_layers = 5") {
print.default_object_config.support_material_contact_distance = 0.2;
print.default_object_config.raft_layers = 5;
print.add_model_object(model.objects[0]);
bool check = test_6_checks(print);
REQUIRE(check == true); // bridge speed is used.
}
WHEN("support_material_contact_distance = 0 & raft_layers = 5") {
print.default_object_config.support_material_contact_distance = 0;
print.default_object_config.raft_layers = 5;
print.add_model_object(model.objects[0]);
bool check = test_6_checks(print);
REQUIRE(check == true); // bridge speed is not used.
}
}
}
void test_1_checks(Print &print, bool &a, bool &b, bool &c, bool &d)
{
vector<coordf_t> contact_z = {1.9};
vector<coordf_t> top_z = {1.1};
SupportMaterial *support = print.objects.front()->_support_material();
vector<coordf_t>
support_z = support->support_layers_z(contact_z, top_z, print.default_object_config.layer_height);
a = (support_z[0] == print.default_object_config.first_layer_height.value);
b = true;
for (size_t i = 1; i < support_z.size(); ++i)
if (support_z[i] - support_z[i - 1] <= 0) b = false;
c = true;
for (size_t i = 1; i < support_z.size(); ++i)
if (support_z[i] - support_z[i - 1] > print.config.nozzle_diameter.get_at(0) + EPSILON)
c = false;
coordf_t expected_top_spacing = support
->contact_distance(print.default_object_config.layer_height,
print.config.nozzle_diameter.get_at(0));
bool wrong_top_spacing = 0;
for (coordf_t top_z_el : top_z) {
// find layer index of this top surface.
size_t layer_id = -1;
for (size_t i = 0; i < support_z.size(); i++) {
if (abs(support_z[i] - top_z_el) < EPSILON) {
layer_id = i;
i = static_cast<int>(support_z.size());
}
}
// check that first support layer above this top surface (or the next one) is spaced with nozzle diameter
if (abs(support_z[layer_id + 1] - support_z[layer_id] - expected_top_spacing) > EPSILON
&& abs(support_z[layer_id + 2] - support_z[layer_id] - expected_top_spacing) > EPSILON) {
wrong_top_spacing = 1;
}
}
d = !wrong_top_spacing;
}
// TODO
bool test_6_checks(Print &print)
{
bool has_bridge_speed = true;
// Pre-Processing.
PrintObject *print_object = print.objects.front();
print_object->_infill();
SupportMaterial *support_material = print.objects.front()->_support_material();
support_material->generate(print_object);
// TODO but not needed in test 6 (make brims and make skirts).
// Exporting gcode.
// TODO validation found in Simple.pm
return has_bridge_speed;
}

View File

@ -0,0 +1,29 @@
#include <catch.hpp>
#include <sstream>
#include "test_data.hpp"
using namespace Slic3r::Test;
SCENARIO("init_print functionality") {
GIVEN("A default config") {
config_ptr config {Slic3r::Config::new_from_defaults()};
std::stringstream gcode;
WHEN("init_print is called with a single mesh.") {
Slic3r::Model model;
auto print = init_print({TestMesh::cube_20x20x20}, model, config, true);
gcode.clear();
THEN("One mesh/printobject is in the resulting Print object.") {
REQUIRE(print->objects.size() == 1);
}
THEN("print->process() doesn't crash.") {
REQUIRE_NOTHROW(print->process());
}
THEN("Export gcode functions outputs text.") {
print->process();
print->export_gcode(gcode, true);
REQUIRE(gcode.str().size() > 0);
}
}
}
}

View File

@ -7,8 +7,57 @@
#include <algorithm>
using namespace Slic3r;
using namespace std;
SCENARIO( "TriangleMesh: Basic mesh statistics") {
GIVEN( "A 20mm cube, built from constexpr std::array" ) {
constexpr std::array<Pointf3, 8> vertices { Pointf3(20,20,0), Pointf3(20,0,0), Pointf3(0,0,0), Pointf3(0,20,0), Pointf3(20,20,20), Pointf3(0,20,20), Pointf3(0,0,20), Pointf3(20,0,20) };
constexpr std::array<Point3, 12> facets { Point3(0,1,2), Point3(0,2,3), Point3(4,5,6), Point3(4,6,7), Point3(0,4,7), Point3(0,7,1), Point3(1,7,6), Point3(1,6,2), Point3(2,6,5), Point3(2,5,3), Point3(4,0,3), Point3(4,3,5) };
auto cube {TriangleMesh(vertices, facets)};
cube.repair();
THEN( "Volume is appropriate for 20mm square cube.") {
REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2);
}
THEN( "Vertices array matches input.") {
for (auto i = 0U; i < cube.vertices().size(); i++) {
REQUIRE(cube.vertices().at(i) == vertices.at(i));
}
for (auto i = 0U; i < vertices.size(); i++) {
REQUIRE(vertices.at(i) == cube.vertices().at(i));
}
}
THEN( "Vertex count matches vertex array size.") {
REQUIRE(cube.facets_count() == facets.size());
}
THEN( "Facet array matches input.") {
for (auto i = 0U; i < cube.facets().size(); i++) {
REQUIRE(cube.facets().at(i) == facets.at(i));
}
for (auto i = 0U; i < facets.size(); i++) {
REQUIRE(facets.at(i) == cube.facets().at(i));
}
}
THEN( "Facet count matches facet array size.") {
REQUIRE(cube.facets_count() == facets.size());
}
THEN( "Number of normals is equal to the number of facets.") {
REQUIRE(cube.normals().size() == facets.size());
}
THEN( "center() returns the center of the object.") {
REQUIRE(cube.center() == Pointf3(10.0,10.0,10.0));
}
THEN( "Size of cube is (20,20,20)") {
REQUIRE(cube.size() == Pointf3(20,20,20));
}
}
GIVEN( "A 20mm cube with one corner on the origin") {
const Pointf3s vertices { Pointf3(20,20,0), Pointf3(20,0,0), Pointf3(0,0,0), Pointf3(0,20,0), Pointf3(20,20,20), Pointf3(0,20,20), Pointf3(0,0,20), Pointf3(20,0,20) };
const Point3s facets { Point3(0,1,2), Point3(0,2,3), Point3(4,5,6), Point3(4,6,7), Point3(0,4,7), Point3(0,7,1), Point3(1,7,6), Point3(1,6,2), Point3(2,6,5), Point3(2,5,3), Point3(4,0,3), Point3(4,3,5) };

265
src/test/test_data.cpp Normal file

File diff suppressed because one or more lines are too long

72
src/test/test_data.hpp Normal file
View File

@ -0,0 +1,72 @@
#ifndef SLIC3R_TEST_DATA_HPP
#include "Point.hpp"
#include "TriangleMesh.hpp"
#include "Geometry.hpp"
#include "Model.hpp"
#include "Print.hpp"
#include "Config.hpp"
#include <unordered_map>
namespace Slic3r { namespace Test {
/// Enumeration of test meshes
enum class TestMesh {
A,
L,
V,
_40x10,
cube_20x20x20,
sphere_50mm,
bridge,
bridge_with_hole,
cube_with_concave_hole,
cube_with_hole,
gt2_teeth,
ipadstand,
overhang,
pyramid,
sloping_hole,
slopy_cube,
small_dorito,
step,
two_hollow_squares
};
// Neccessary for <c++17
struct TestMeshHash {
std::size_t operator()(TestMesh tm) const {
return static_cast<std::size_t>(tm);
}
};
/// Mesh enumeration to name mapping
extern const std::unordered_map<TestMesh, const char*, TestMeshHash> mesh_names;
/// Port of Slic3r::Test::mesh
/// Basic cubes/boxes should call TriangleMesh::make_cube() directly and rescale/translate it
TriangleMesh mesh(TestMesh m);
TriangleMesh mesh(TestMesh m, Pointf3 translate, Pointf3 scale = Pointf3(1.0, 1.0, 1.0));
TriangleMesh mesh(TestMesh m, Pointf3 translate, double scale = 1.0);
/// Templated function to see if two values are equivalent (+/- epsilon)
template <typename T>
bool _equiv(const T& a, const T& b) { return abs(a - b) < Slic3r::Geometry::epsilon; }
template <typename T>
bool _equiv(const T& a, const T& b, double epsilon) { return abs(a - b) < epsilon; }
//Slic3r::Model model(const std::string& model_name, TestMesh m, Pointf3 translate = Pointf3(0,0,0), Pointf3 scale = Pointf3(1.0,1.0,1.0));
//Slic3r::Model model(const std::string& model_name, TestMesh m, Pointf3 translate = Pointf3(0,0,0), double scale = 1.0);
Slic3r::Model model(const std::string& model_name, TriangleMesh&& _mesh);
shared_Print init_print(std::initializer_list<TestMesh> meshes, Slic3r::Model& model, config_ptr _config = Slic3r::Config::new_from_defaults(), bool comments = false);
void gcode(std::stringstream& gcode, shared_Print print);
} } // namespace Slic3r::Test
#endif // SLIC3R_TEST_DATA_HPP

View File

@ -0,0 +1,6 @@
#ifndef TEST_OPTIONS_HPP
/// Directory path, passed in from the outside, for the path to the test inputs dir.
constexpr auto* testfile_dir {"@TESTFILE_DIR@"};
#endif // TEST_OPTIONS_HPP

View File

@ -15,6 +15,8 @@ use Slic3r::Test;
my $config = Slic3r::Config->new_from_defaults;
$config->set('perimeter_extrusion_width', '250%');
ok $config->validate, 'percent extrusion width is validated';
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2);
}
__END__

8
utils/clang_format Normal file
View File

@ -0,0 +1,8 @@
---
IndentWidth: '4'
BreakBeforeBraces: 'Linux'
ColumnLimit: '100'
AllowAllParametersOfDeclarationOnNextLine: 'true'
AccessModifierOffset: '-4'
FixNamespaceComments: 'true'
...

View File

@ -2,8 +2,12 @@
#include "Config.hpp"
#include "Log.hpp"
#include <string>
namespace Slic3r {
extern const PrintConfigDef print_config_def;
std::shared_ptr<Config>
Config::new_from_defaults()
{
@ -22,7 +26,7 @@ Config::new_from_defaults(t_config_option_keys init)
for (auto& opt_key : init) {
if (print_config_def.has(opt_key)) {
const std::string value { print_config_def.get(opt_key)->default_value->serialize() };
my_config->set_deserialize(opt_key, value);
my_config->_config.set_deserialize(opt_key, value);
}
}
@ -35,6 +39,320 @@ new_from_cli(const int& argc, const char* argv[])
return std::make_shared<Config>();
}
std::shared_ptr<Config>
Config::new_from_ini(const std::string& inifile)
{
auto my_config(std::make_shared<Config>());
my_config->read_ini(inifile);
return my_config;
}
bool
Config::validate()
{
// general validation
for (auto k : this->_config.keys()) {
if (print_config_def.options.count(k) == 0) continue; // skip over keys that aren't in the master list
const auto& opt {print_config_def.options.at(k)};
if (opt.cli == "" || std::regex_search(opt.cli, _match_info, _cli_pattern) == false) continue;
auto type {_match_info.str(1)};
std::vector<std::string> values;
if (std::regex_search(type, _match_info, std::regex("@$"))) {
type = std::regex_replace(type, std::regex("@$"), std::string("")); // strip off the @ for later;
ConfigOptionVectorBase* tmp_opt;
try {
tmp_opt = get_ptr<ConfigOptionVectorBase>(k);
} catch (std::bad_cast& e) {
throw InvalidOptionType((std::string("(cast failure) Invalid value for ") + std::string(k)).c_str());
}
auto tmp_str {tmp_opt->vserialize()};
values.insert(values.end(), tmp_str.begin(), tmp_str.end());
} else {
Slic3r::Log::debug("Config::validate", std::string("Not an array"));
Slic3r::Log::debug("Config::validate", type);
values.emplace_back(get_ptr<ConfigOption>(k)->serialize());
}
// Verify each value
for (auto v : values) {
if (type == "i" || type == "f" || opt.type == coPercent || opt.type == coFloatOrPercent) {
if (opt.type == coPercent || opt.type == coFloatOrPercent)
v = std::regex_replace(v, std::regex("%$"), std::string(""));
if ((type == "i" && !is_valid_int(type, opt, v)) || !is_valid_float(type, opt, v)) {
throw InvalidOptionValue((std::string("Invalid value for ") + std::string(k)).c_str());
}
}
}
}
return true;
}
void
Config::set(const t_config_option_key& opt_key, const std::string& value)
{
try {
const auto& def {print_config_def.options.at(opt_key)};
switch (def.type) {
case coInt:
{
auto* ptr {dynamic_cast<ConfigOptionInt*>(this->_config.optptr(opt_key, true))};
ptr->value = std::stoi(value);
} break;
case coInts:
{
auto* ptr {dynamic_cast<ConfigOptionInts*>(this->_config.optptr(opt_key, true))};
if (!ptr->deserialize(value, true) ) {
throw InvalidOptionValue(std::string(opt_key) + std::string(" set with invalid value."));
}
} break;
case coFloat:
{
auto* ptr {dynamic_cast<ConfigOptionFloat*>(this->_config.optptr(opt_key, true))};
ptr->setFloat(std::stod(value));
} break;
case coFloatOrPercent:
{
auto* ptr {dynamic_cast<ConfigOptionFloatOrPercent*>(this->_config.optptr(opt_key, true))};
const size_t perc = value.find("%");
ptr->percent = (perc != std::string::npos);
if (ptr->percent) {
ptr->setFloat(std::stod(std::string(value).replace(value.find("%"), std::string("%").length(), "")));
ptr->percent = true;
} else {
ptr->setFloat(std::stod(value));
ptr->percent = false;
}
} break;
case coFloats:
{
auto* ptr {dynamic_cast<ConfigOptionFloats*>(this->_config.optptr(opt_key, true))};
if (!ptr->deserialize(value, true) ) {
throw InvalidOptionValue(std::string(opt_key) + std::string(" set with invalid value."));
}
} break;
case coString:
{
auto* ptr {dynamic_cast<ConfigOptionString*>(this->_config.optptr(opt_key, true))};
if (!ptr->deserialize(value) ) {
throw InvalidOptionValue(std::string(opt_key) + std::string(" set with invalid value."));
}
} break;
case coBool:
{
auto* ptr {dynamic_cast<ConfigOptionBool*>(this->_config.optptr(opt_key, true))};
ptr->deserialize(value);
} break;
default:
Slic3r::Log::warn("Config::set", "Unknown set type.");
}
} catch (std::invalid_argument& e) {
throw InvalidOptionValue(std::string(opt_key) + std::string(" set with invalid value."));
} catch (std::out_of_range& e) {
throw InvalidOptionType(std::string(opt_key) + std::string(" is an invalid Slic3r option."));
}
}
void
Config::set(const t_config_option_key& opt_key, const bool value)
{
try {
const auto& def {print_config_def.options.at(opt_key)};
switch (def.type) {
case coBool:
{
auto* ptr {dynamic_cast<ConfigOptionBool*>(this->_config.optptr(opt_key, true))};
ptr->value = value;
} break;
case coInt:
{
auto* ptr {dynamic_cast<ConfigOptionInt*>(this->_config.optptr(opt_key, true))};
ptr->setInt(value);
} break;
case coInts:
{
auto* ptr {dynamic_cast<ConfigOptionInts*>(this->_config.optptr(opt_key, true))};
ptr->deserialize(std::to_string(value), true);
} break;
case coFloat:
{
auto* ptr {dynamic_cast<ConfigOptionFloat*>(this->_config.optptr(opt_key, true))};
ptr->setFloat(value);
} break;
case coFloatOrPercent:
{
auto* ptr {dynamic_cast<ConfigOptionFloatOrPercent*>(this->_config.optptr(opt_key, true))};
ptr->setFloat(value);
ptr->percent = false;
} break;
case coFloats:
{
auto* ptr {dynamic_cast<ConfigOptionFloats*>(this->_config.optptr(opt_key, true))};
ptr->deserialize(std::to_string(value), true);
} break;
case coString:
{
auto* ptr {dynamic_cast<ConfigOptionString*>(this->_config.optptr(opt_key, true))};
if (!ptr->deserialize(std::to_string(value)) ) {
throw InvalidOptionValue(std::string(opt_key) + std::string(" set with invalid value."));
}
} break;
default:
Slic3r::Log::warn("Config::set", "Unknown set type.");
}
} catch (std::out_of_range &e) {
throw InvalidOptionType(std::string(opt_key) + std::string(" is an invalid Slic3r option."));
}
}
void
Config::set(const t_config_option_key& opt_key, const int value)
{
try {
const auto& def {print_config_def.options.at(opt_key)};
switch (def.type) {
case coBool:
{
auto* ptr {dynamic_cast<ConfigOptionBool*>(this->_config.optptr(opt_key, true))};
ptr->value = (value != 0);
} break;
case coInt:
{
auto* ptr {dynamic_cast<ConfigOptionInt*>(this->_config.optptr(opt_key, true))};
ptr->setInt(value);
} break;
case coInts:
{
auto* ptr {dynamic_cast<ConfigOptionInts*>(this->_config.optptr(opt_key, true))};
ptr->deserialize(std::to_string(value), true);
} break;
case coFloat:
{
auto* ptr {dynamic_cast<ConfigOptionFloat*>(this->_config.optptr(opt_key, true))};
ptr->setFloat(value);
} break;
case coFloatOrPercent:
{
auto* ptr {dynamic_cast<ConfigOptionFloatOrPercent*>(this->_config.optptr(opt_key, true))};
ptr->setFloat(value);
ptr->percent = false;
} break;
case coFloats:
{
auto* ptr {dynamic_cast<ConfigOptionFloats*>(this->_config.optptr(opt_key, true))};
ptr->deserialize(std::to_string(value), true);
} break;
case coString:
{
auto* ptr {dynamic_cast<ConfigOptionString*>(this->_config.optptr(opt_key, true))};
if (!ptr->deserialize(std::to_string(value)) ) {
throw InvalidOptionValue(std::string(opt_key) + std::string(" set with invalid value."));
}
} break;
default:
Slic3r::Log::warn("Config::set", "Unknown set type.");
}
} catch (std::out_of_range &e) {
throw InvalidOptionType(std::string(opt_key) + std::string(" is an invalid Slic3r option."));
}
}
void
Config::set(const t_config_option_key& opt_key, const double value)
{
try {
const auto& def {print_config_def.options.at(opt_key)};
switch (def.type) {
case coInt:
{
auto* ptr {dynamic_cast<ConfigOptionInt*>(this->_config.optptr(opt_key, true))};
ptr->setInt(std::round(value));
} break;
case coInts:
{
auto* ptr {dynamic_cast<ConfigOptionInts*>(this->_config.optptr(opt_key, true))};
ptr->deserialize(std::to_string(std::round(value)), true);
} break;
case coFloat:
{
auto* ptr {dynamic_cast<ConfigOptionFloat*>(this->_config.optptr(opt_key, true))};
ptr->setFloat(value);
} break;
case coFloatOrPercent:
{
auto* ptr {dynamic_cast<ConfigOptionFloatOrPercent*>(this->_config.optptr(opt_key, true))};
ptr->setFloat(value);
ptr->percent = false;
} break;
case coFloats:
{
auto* ptr {dynamic_cast<ConfigOptionFloats*>(this->_config.optptr(opt_key, true))};
ptr->deserialize(std::to_string(value), true);
} break;
case coString:
{
auto* ptr {dynamic_cast<ConfigOptionString*>(this->_config.optptr(opt_key, true))};
if (!ptr->deserialize(std::to_string(value)) ) {
throw InvalidOptionValue(std::string(opt_key) + std::string(" set with invalid value."));
}
} break;
default:
Slic3r::Log::warn("Config::set", "Unknown set type.");
}
} catch (std::out_of_range &e) {
throw InvalidOptionType(std::string(opt_key) + std::string(" is an invalid Slic3r option."));
}
}
void
Config::read_ini(const std::string& file)
{
}
void
Config::write_ini(const std::string& file) const
{
}
bool
is_valid_int(const std::string& type, const ConfigOptionDef& opt, const std::string& ser_value)
{
std::regex _valid_int {"^-?\\d+$"};
std::smatch match;
ConfigOptionInt tmp;
bool result {type == "i"};
if (result)
result = result && std::regex_search(ser_value, match, _valid_int);
if (result) {
tmp.deserialize(ser_value);
result = result & (tmp.getInt() <= opt.max && tmp.getInt() >= opt.min);
}
return result;
}
bool
is_valid_float(const std::string& type, const ConfigOptionDef& opt, const std::string& ser_value)
{
std::regex _valid_float {"^-?(?:\\d+|\\d*\\.\\d+)$"};
std::smatch match;
ConfigOptionFloat tmp;
Slic3r::Log::debug("is_valid_float", ser_value);
bool result {type == "f" || opt.type == coPercent || opt.type == coFloatOrPercent};
if (result)
result = result && std::regex_search(ser_value, match, _valid_float);
if (result) {
tmp.deserialize(ser_value);
result = result & (tmp.getFloat() <= opt.max && tmp.getFloat() >= opt.min);
}
return result;
}
Config::Config() : _config(DynamicPrintConfig()) {};
} // namespace Slic3r

View File

@ -6,33 +6,143 @@
#include <initializer_list>
#include <memory>
#include <regex>
#include "PrintConfig.hpp"
#include "ConfigBase.hpp"
namespace Slic3r {
/// Exception class for invalid (but correct type) option values.
/// Thrown by validate()
class InvalidOptionValue : public std::runtime_error {
public:
InvalidOptionValue(const char* v) : runtime_error(v) {}
InvalidOptionValue(const std::string v) : runtime_error(v.c_str()) {}
};
/// Exception class to handle config options that don't exist.
class InvalidConfigOption : public std::runtime_error {};
/// Exception class for type mismatches
class InvalidOptionType : public std::runtime_error {
public:
InvalidOptionType(const char* v) : runtime_error(v) {}
InvalidOptionType(const std::string v) : runtime_error(v.c_str()) {}
};
class Config;
using config_ptr = std::shared_ptr<Config>;
class Config : public DynamicPrintConfig {
class Config {
public:
/// Factory method to construct a Config with all default values loaded.
static std::shared_ptr<Config> new_from_defaults();
/// Factory method to construct a Config with specific default values loaded.
static std::shared_ptr<Config> new_from_defaults(std::initializer_list<std::string> init);
/// Factory method to construct a Config with specific default values loaded.
static std::shared_ptr<Config> new_from_defaults(t_config_option_keys init);
/// Factory method to construct a Config from CLI options.
static std::shared_ptr<Config> new_from_cli(const int& argc, const char* argv[]);
void write_ini(const std::string& file) { save(file); }
void read_ini(const std::string& file) { load(file); }
/// Factory method to construct a Config from an ini file.
static std::shared_ptr<Config> new_from_ini(const std::string& inifile);
/// Write a windows-style opt=value ini file with categories from the configuration store.
void write_ini(const std::string& file) const;
/// Parse a windows-style opt=value ini file with categories and load the configuration store.
void read_ini(const std::string& file);
double getFloat(const t_config_option_key& opt_key, bool create=true) {
if (print_config_def.options.count(opt_key) == 0) throw InvalidOptionType(opt_key + std::string(" is an invalid option."));
return (dynamic_cast<ConfigOption*>(this->_config.optptr(opt_key, create)))->getFloat();
}
int getInt(const t_config_option_key& opt_key, bool create=true) {
if (print_config_def.options.count(opt_key) == 0) throw InvalidOptionType(opt_key + std::string(" is an invalid option."));
return (dynamic_cast<ConfigOption*>(this->_config.optptr(opt_key, create)))->getInt();
}
bool getBool(const t_config_option_key& opt_key, bool create=true) {
if (print_config_def.options.count(opt_key) == 0) throw InvalidOptionType(opt_key + std::string(" is an invalid option."));
return (dynamic_cast<ConfigOption*>(this->_config.optptr(opt_key, create)))->getBool();
}
std::string getString(const t_config_option_key& opt_key, bool create=true) {
if (print_config_def.options.count(opt_key) == 0) throw InvalidOptionType(opt_key + std::string(" is an invalid option."));
return (dynamic_cast<ConfigOption*>(this->_config.optptr(opt_key, create)))->getString();
}
/// Template function to dynamic cast and leave it in pointer form.
template <class T>
T* get_ptr(const t_config_option_key& opt_key, bool create=true) {
if (print_config_def.options.count(opt_key) == 0) throw InvalidOptionType(opt_key + std::string(" is an invalid option."));
return dynamic_cast<T*>(this->_config.optptr(opt_key, create));
}
/// Template function to retrieve and cast in hopefully a slightly nicer
/// format than longwinded dynamic_cast<>
template <class T>
T& get(const t_config_option_key& opt_key, bool create=false) {
return *(dynamic_cast<T*>(this->optptr(opt_key, create)));
T& get(const t_config_option_key& opt_key, bool create=true) {
if (print_config_def.options.count(opt_key) == 0) throw InvalidOptionType(opt_key + std::string(" is an invalid option."));
return *(dynamic_cast<T*>(this->_config.optptr(opt_key, create)));
}
/// Function to parse value from a string to whatever opt_key is.
void set(const t_config_option_key& opt_key, const std::string& value);
void set(const t_config_option_key& opt_key, const char* value) { this->set(opt_key, std::string(value));}
/// Function to parse value from an integer to whatever opt_key is, if
/// opt_key is a numeric type. This will throw an exception and do
/// nothing if called with an incorrect type.
void set(const t_config_option_key& opt_key, const int value);
/// Function to parse value from an boolean to whatever opt_key is, if
/// opt_key is a numeric type. This will throw an exception and do
/// nothing if called with an incorrect type.
void set(const t_config_option_key& opt_key, const bool value);
/// Function to parse value from an integer to whatever opt_key is, if
/// opt_key is a numeric type. This will throw an exception and do
/// nothing if called with an incorrect type.
void set(const t_config_option_key& opt_key, const double value);
/// Method to validate the different configuration options.
/// It will throw InvalidConfigOption exceptions on failure.
bool validate();
const DynamicPrintConfig& config() const { return _config; }
bool empty() const { return _config.empty(); }
/// Pass-through of apply()
void apply(const config_ptr& other) { _config.apply(other->config()); }
void apply(const Slic3r::Config& other) { _config.apply(other.config()); }
/// Allow other configs to be applied to this one.
void apply(const Slic3r::ConfigBase& other) { _config.apply(other); }
/// Do not use; prefer static factory methods instead.
Config();
private:
std::regex _cli_pattern {"=(.+)$"};
std::smatch _match_info {};
/// Underlying configuration store.
DynamicPrintConfig _config {};
};
bool is_valid_int(const std::string& type, const ConfigOptionDef& opt, const std::string& ser_value);
bool is_valid_float(const std::string& type, const ConfigOptionDef& opt, const std::string& ser_value);
} // namespace Slic3r
#endif // CONFIG_HPP

View File

@ -11,7 +11,9 @@
#include <string>
#include <vector>
#include "libslic3r.h"
#include "utils.hpp"
#include "Point.hpp"
#include "Geometry.hpp"
namespace Slic3r {
@ -40,6 +42,8 @@ class ConfigOption {
virtual double getFloat() const { return 0; };
virtual bool getBool() const { return false; };
virtual void setInt(int val) {};
virtual void setFloat(double val) {};
virtual void setString(std::string val) {};
virtual std::string getString() const { return ""; };
friend bool operator== (const ConfigOption &a, const ConfigOption &b);
friend bool operator!= (const ConfigOption &a, const ConfigOption &b);
@ -52,6 +56,7 @@ class ConfigOptionSingle : public ConfigOption {
T value;
ConfigOptionSingle(T _value) : value(_value) {};
operator T() const { return this->value; };
T operator()() const { return this->value; };
void set(const ConfigOption &option) {
const ConfigOptionSingle<T>* other = dynamic_cast< const ConfigOptionSingle<T>* >(&option);
@ -97,21 +102,28 @@ class ConfigOptionFloat : public ConfigOptionSingle<double>
public:
ConfigOptionFloat() : ConfigOptionSingle<double>(0) {};
ConfigOptionFloat(double _value) : ConfigOptionSingle<double>(_value) {};
ConfigOptionFloat* clone() const { return new ConfigOptionFloat(this->value); };
ConfigOptionFloat* clone() const override { return new ConfigOptionFloat(this->value); };
double getFloat() const { return this->value; };
double getFloat() const override { return this->value; };
void setFloat(double val) override { this->value = val; }
void setInt(int val) override { this->value = val; }
std::string getString() const override { return trim_zeroes(std::to_string(this->value)); }
std::string serialize() const {
std::string serialize() const override {
std::ostringstream ss;
ss << this->value;
return ss.str();
};
bool deserialize(std::string str, bool append = false) {
bool deserialize(std::string str, bool append = false) override {
std::istringstream iss(str);
iss >> this->value;
return !iss.fail();
};
/// Floating point values we conpare within some small value for equality.
template<typename Y>
bool operator==(const Y& other) { return std::abs(value - other) < Slic3r::Geometry::epsilon; }
};
/// Vector form of template specialization for floating point numbers.
@ -161,18 +173,19 @@ class ConfigOptionInt : public ConfigOptionSingle<int>
public:
ConfigOptionInt() : ConfigOptionSingle<int>(0) {};
ConfigOptionInt(double _value) : ConfigOptionSingle<int>(_value) {};
ConfigOptionInt* clone() const { return new ConfigOptionInt(this->value); };
ConfigOptionInt* clone() const override { return new ConfigOptionInt(this->value); };
int getInt() const { return this->value; };
void setInt(int val) { this->value = val; };
int getInt() const override { return this->value; };
void setInt(int val) override { this->value = val; };
std::string getString() const override { return std::to_string(this->value); }
std::string serialize() const {
std::string serialize() const override {
std::ostringstream ss;
ss << this->value;
return ss.str();
};
bool deserialize(std::string str, bool append = false) {
bool deserialize(std::string str, bool append = false) override {
std::istringstream iss(str);
iss >> this->value;
return !iss.fail();
@ -184,9 +197,9 @@ class ConfigOptionInts : public ConfigOptionVector<int>
public:
ConfigOptionInts() {};
ConfigOptionInts(const std::vector<int> _values) : ConfigOptionVector<int>(_values) {};
ConfigOptionInts* clone() const { return new ConfigOptionInts(this->values); };
ConfigOptionInts* clone() const override { return new ConfigOptionInts(this->values); };
std::string serialize() const {
std::string serialize() const override {
std::ostringstream ss;
for (std::vector<int>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
if (it - this->values.begin() != 0) ss << ",";
@ -195,7 +208,7 @@ class ConfigOptionInts : public ConfigOptionVector<int>
return ss.str();
};
std::vector<std::string> vserialize() const {
std::vector<std::string> vserialize() const override {
std::vector<std::string> vv;
vv.reserve(this->values.size());
for (std::vector<int>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
@ -206,7 +219,7 @@ class ConfigOptionInts : public ConfigOptionVector<int>
return vv;
};
bool deserialize(std::string str, bool append = false) {
bool deserialize(std::string str, bool append = false) override {
if (!append) this->values.clear();
std::istringstream is(str);
std::string item_str;

View File

@ -52,13 +52,14 @@ class ExPolygon
std::string dump_perl() const;
};
inline Polygons
to_polygons(const ExPolygons &expolygons)
// Count a nuber of polygons stored inside the vector of expolygons.
// Useful for allocating space for polygons when converting expolygons to polygons.
inline size_t number_polygons(const ExPolygons &expolys)
{
Polygons pp;
for (ExPolygons::const_iterator ex = expolygons.begin(); ex != expolygons.end(); ++ex)
append_to(pp, (Polygons)*ex);
return pp;
size_t n_polygons = 0;
for (ExPolygons::const_iterator it = expolys.begin(); it != expolys.end(); ++ it)
n_polygons += it->holes.size() + 1;
return n_polygons;
}
inline ExPolygons
@ -69,8 +70,110 @@ operator+(ExPolygons src1, const ExPolygons &src2) {
std::ostream& operator <<(std::ostream &s, const ExPolygons &expolygons);
inline void
polygons_append(Polygons &dst, const ExPolygon &src)
{
dst.reserve(dst.size() + src.holes.size() + 1);
dst.push_back(src.contour);
dst.insert(dst.end(), src.holes.begin(), src.holes.end());
}
inline void
polygons_append(Polygons &dst, const ExPolygons &src)
{
dst.reserve(dst.size() + number_polygons(src));
for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++ it) {
dst.push_back(it->contour);
dst.insert(dst.end(), it->holes.begin(), it->holes.end());
}
}
inline void
polygons_append(Polygons &dst, ExPolygon &&src)
{
dst.reserve(dst.size() + src.holes.size() + 1);
dst.push_back(std::move(src.contour));
std::move(std::begin(src.holes), std::end(src.holes), std::back_inserter(dst));
src.holes.clear();
}
inline void
polygons_append(Polygons &dst, ExPolygons &&src)
{
dst.reserve(dst.size() + number_polygons(src));
for (ExPolygons::iterator it = src.begin(); it != src.end(); ++ it) {
dst.push_back(std::move(it->contour));
std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(dst));
it->holes.clear();
}
}
inline void
expolygons_append(ExPolygons &dst, const ExPolygons &src)
{
dst.insert(dst.end(), src.begin(), src.end());
}
inline void
expolygons_append(ExPolygons &dst, ExPolygons &&src)
{
if (dst.empty()) {
dst = std::move(src);
} else {
std::move(std::begin(src), std::end(src), std::back_inserter(dst));
src.clear();
}
}
inline Polygons
to_polygons(const ExPolygon &src)
{
Polygons polygons;
polygons.reserve(src.holes.size() + 1);
polygons.push_back(src.contour);
polygons.insert(polygons.end(), src.holes.begin(), src.holes.end());
return polygons;
}
inline Polygons
to_polygons(const ExPolygons &src)
{
Polygons polygons;
polygons.reserve(number_polygons(src));
for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++it) {
polygons.push_back(it->contour);
polygons.insert(polygons.end(), it->holes.begin(), it->holes.end());
}
return polygons;
}
inline Polygons
to_polygons(ExPolygon &&src)
{
Polygons polygons;
polygons.reserve(src.holes.size() + 1);
polygons.push_back(std::move(src.contour));
std::move(std::begin(src.holes), std::end(src.holes), std::back_inserter(polygons));
src.holes.clear();
return polygons;
}
inline Polygons
to_polygons(ExPolygons &&src)
{
Polygons polygons;
polygons.reserve(number_polygons(src));
for (ExPolygons::iterator it = src.begin(); it != src.end(); ++it) {
polygons.push_back(std::move(it->contour));
std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(polygons));
it->holes.clear();
}
return polygons;
}
} // namespace Slic3r
// start Boost
#include <boost/polygon/polygon.hpp>
namespace boost { namespace polygon {

View File

@ -41,6 +41,14 @@ class ExPolygonCollection
/// ExPolygons and check if at least one contains the point.
bool contains(const Point &point) const;
size_t size() const { return expolygons.size(); }
ExPolygons::iterator begin() { return expolygons.begin(); }
ExPolygons::iterator end() { return expolygons.end(); }
ExPolygons::const_iterator cbegin() const { return expolygons.cbegin();}
ExPolygons::const_iterator cend() const { return expolygons.cend();}
ExPolygon& at(size_t i) { return expolygons.at(i); }
const ExPolygon& at(size_t i) const { return expolygons.at(i); }
};
inline ExPolygonCollection&

View File

@ -52,6 +52,9 @@ public:
virtual double min_mm3_per_mm() const = 0;
virtual Polyline as_polyline() const = 0;
virtual double length() const { return 0; };
virtual bool is_perimeter() const {return false;};
virtual bool is_infill() const {return false;};
virtual bool is_solid_infill() const {return false;};
};
typedef std::vector<ExtrusionEntity*> ExtrusionEntitiesPtr;

View File

@ -11,7 +11,11 @@ class ExtrusionEntityCollection : public ExtrusionEntity
{
public:
ExtrusionEntityCollection* clone() const;
/// Owned ExtrusionEntities and descendent ExtrusionEntityCollections.
/// Iterating over this needs to check each child to see if it, too is a collection.
ExtrusionEntitiesPtr entities; // we own these entities
std::vector<size_t> orig_indices; // handy for XS
bool no_sort;
ExtrusionEntityCollection(): no_sort(false) {};
@ -19,11 +23,15 @@ class ExtrusionEntityCollection : public ExtrusionEntity
ExtrusionEntityCollection(const ExtrusionPaths &paths);
ExtrusionEntityCollection& operator= (const ExtrusionEntityCollection &other);
~ExtrusionEntityCollection();
/// Operator to convert and flatten this collection to a single vefctor of ExtrusionPaths.
operator ExtrusionPaths() const;
/// This particular ExtrusionEntity is a collection.
bool is_collection() const {
return true;
};
bool can_reverse() const {
return !this->no_sort;
};
@ -31,6 +39,7 @@ class ExtrusionEntityCollection : public ExtrusionEntity
return this->entities.empty();
};
void clear();
size_t size() const { return this->entities.size(); }
void swap (ExtrusionEntityCollection &c);
void append(const ExtrusionEntity &entity);
void append(const ExtrusionEntitiesPtr &entities);
@ -45,14 +54,30 @@ class ExtrusionEntityCollection : public ExtrusionEntity
Point first_point() const;
Point last_point() const;
Polygons grow() const;
/// Recursively count paths and loops contained in this collection
size_t items_count() const;
/// Returns a single vector of pointers to all non-collection items contained in this one
void flatten(ExtrusionEntityCollection* retval) const;
/// Returns a flattened copy of this ExtrusionEntityCollection. That is, all of the items in its entities vector are not collections.
/// You should be iterating over flatten().entities if you are interested in the underlying ExtrusionEntities (and don't care about hierarchy).
ExtrusionEntityCollection flatten() const;
double min_mm3_per_mm() const;
Polyline as_polyline() const {
CONFESS("Calling as_polyline() on a ExtrusionEntityCollection");
return Polyline();
};
ExtrusionEntitiesPtr::iterator begin() { return entities.begin(); }
ExtrusionEntitiesPtr::iterator end() { return entities.end(); }
ExtrusionEntitiesPtr::const_iterator cbegin() const { return entities.cbegin(); }
ExtrusionEntitiesPtr::const_iterator cend() const { return entities.cend(); }
};
}

View File

@ -12,13 +12,13 @@ constexpr auto OVERLAP_FACTOR = 1.0;
/// Enumeration for different flow roles
enum FlowRole {
frExternalPerimeter,
frPerimeter,
frInfill,
frSolidInfill,
frTopSolidInfill,
frSupportMaterial,
frSupportMaterialInterface,
frExternalPerimeter = 0b1,
frPerimeter = 0b10,
frInfill = 0b100,
frSolidInfill = 0b1000,
frTopSolidInfill = 0b10000,
frSupportMaterial = 0b100000,
frSupportMaterialInterface = 0b1000000,
};

View File

@ -233,22 +233,6 @@ GCode::apply_print_config(const PrintConfig &print_config)
this->config.apply(print_config);
}
void
GCode::set_extruders(const std::vector<unsigned int> &extruder_ids)
{
this->writer.set_extruders(extruder_ids);
// enable wipe path generation if any extruder has wipe enabled
this->wipe.enable = false;
for (std::vector<unsigned int>::const_iterator it = extruder_ids.begin();
it != extruder_ids.end(); ++it) {
if (this->config.wipe.get_at(*it)) {
this->wipe.enable = true;
break;
}
}
}
void
GCode::set_origin(const Pointf &pointf)
{
@ -424,9 +408,10 @@ GCode::extrude(ExtrusionLoop loop, std::string description, double speed)
if (paths.front().is_perimeter()
&& !loop.has(erOverhangPerimeter)
&& loop.length() <= SMALL_PERIMETER_LENGTH
&& speed == -1)
&& speed == -1) {
speed = this->config.get_abs_value("small_perimeter_speed");
description = "small perimeter";
}
if (paths.front().role == erExternalPerimeter)
description = std::string("external ") + description;

View File

@ -12,6 +12,8 @@
#include "PrintConfig.hpp"
#include "ConditionalGCode.hpp"
#include <string>
#include <vector>
#include <set>
namespace Slic3r {
@ -98,7 +100,30 @@ class GCode {
void set_last_pos(const Point &pos);
bool last_pos_defined() const;
void apply_print_config(const PrintConfig &print_config);
void set_extruders(const std::vector<unsigned int> &extruder_ids);
/// Template function.
template <typename Iter>
void set_extruders(Iter begin, Iter end) {
this->writer.set_extruders(begin, end);
// enable wipe path generation if any extruder has wipe enabled
this->wipe.enable = false;
for (Iter it = begin; it != end; ++it) {
if (this->config.wipe.get_at(*it)) {
this->wipe.enable = true;
break;
}
}
}
template <typename T>
void set_extruders(const std::vector<T> &extruder_ids) {
this->set_extruders(extruder_ids.cbegin(), extruder_ids.cend());
}
template <typename T>
void set_extruders(const std::set<T> &extruder_ids) {
this->set_extruders(extruder_ids.cbegin(), extruder_ids.cend());
}
void set_origin(const Pointf &pointf);
std::string preamble();
std::string notes();

View File

@ -17,8 +17,13 @@ void
GCodeReader::parse(const std::string &gcode, callback_t callback)
{
std::istringstream ss(gcode);
this->parse_stream(ss, callback);
}
void GCodeReader::parse_stream(std::istream &gcode, callback_t callback)
{
std::string line;
while (std::getline(ss, line))
while (std::getline(gcode, line))
this->parse_line(line, callback);
}

View File

@ -54,6 +54,7 @@ class GCodeReader {
GCodeReader() : X(0), Y(0), Z(0), E(0), F(0), verbose(false), _extrusion_axis('E') {};
void apply_config(const PrintConfigBase &config);
void parse(const std::string &gcode, callback_t callback);
void parse_stream(std::istream &gcode, callback_t callback);
void parse_line(std::string line, callback_t callback);
void parse_file(const std::string &file, callback_t callback);

View File

@ -21,18 +21,6 @@ GCodeWriter::apply_print_config(const PrintConfig &print_config)
this->_extrusion_axis = this->config.get_extrusion_axis();
}
void
GCodeWriter::set_extruders(const std::vector<unsigned int> &extruder_ids)
{
for (std::vector<unsigned int>::const_iterator i = extruder_ids.begin(); i != extruder_ids.end(); ++i)
this->extruders.insert( std::pair<unsigned int,Extruder>(*i, Extruder(*i, &this->config)) );
/* we enable support for multiple extruder if any extruder greater than 0 is used
(even if prints only uses that one) since we need to output Tx commands
first extruder has index 0 */
this->multiple_extruders = (*std::max_element(extruder_ids.begin(), extruder_ids.end())) > 0;
}
std::string
GCodeWriter::notes()
{

View File

@ -22,7 +22,24 @@ public:
Extruder* extruder() const { return this->_extruder; }
std::string extrusion_axis() const { return this->_extrusion_axis; }
void apply_print_config(const PrintConfig &print_config);
void set_extruders(const std::vector<unsigned int> &extruder_ids);
template <typename Iter>
void set_extruders(Iter begin, Iter end) {
for (auto i = begin; i != end; ++i)
this->extruders.insert( std::pair<unsigned int,Extruder>(*i, Extruder(*i, &this->config)) );
/* we enable support for multiple extruder if any extruder greater than 0 is used
(even if prints only uses that one) since we need to output Tx commands
first extruder has index 0 */
this->multiple_extruders = (*std::max_element(begin, end)) > 0;
}
template <typename T>
void set_extruders(const std::vector<T> &extruder_ids) { this->set_extruders(extruder_ids.cbegin(), extruder_ids.cend()); }
template <typename T>
void set_extruders(const std::set<T> &extruder_ids) { this->set_extruders(extruder_ids.cbegin(), extruder_ids.cend()); }
/// Write any notes provided by the user as comments in the gcode header.
std::string notes();

View File

@ -343,6 +343,69 @@ linint(double value, double oldmin, double oldmax, double newmin, double newmax)
return (value - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin;
}
/*
== Perl implementations for methods tested in geometry.t but not translated. ==
== The first three are unreachable in the current perl code and the fourth is ==
== only called from ArcFitting which currently dies before reaching the call. ==
sub point_in_segment {
my ($point, $line) = @_;
my ($x, $y) = @$point;
my $line_p = $line->pp;
my @line_x = sort { $a <=> $b } $line_p->[A][X], $line_p->[B][X];
my @line_y = sort { $a <=> $b } $line_p->[A][Y], $line_p->[B][Y];
# check whether the point is in the segment bounding box
return 0 unless $x >= ($line_x[0] - epsilon) && $x <= ($line_x[1] + epsilon)
&& $y >= ($line_y[0] - epsilon) && $y <= ($line_y[1] + epsilon);
# if line is vertical, check whether point's X is the same as the line
if ($line_p->[A][X] == $line_p->[B][X]) {
return abs($x - $line_p->[A][X]) < epsilon ? 1 : 0;
}
# calculate the Y in line at X of the point
my $y3 = $line_p->[A][Y] + ($line_p->[B][Y] - $line_p->[A][Y])
* ($x - $line_p->[A][X]) / ($line_p->[B][X] - $line_p->[A][X]);
return abs($y3 - $y) < epsilon ? 1 : 0;
}
# given a $polygon, return the (first) segment having $point
sub polygon_segment_having_point {
my ($polygon, $point) = @_;
foreach my $line (@{ $polygon->lines }) {
return $line if point_in_segment($point, $line);
}
return undef;
}
# polygon must be simple (non complex) and ccw
sub polygon_is_convex {
my ($points) = @_;
for (my $i = 0; $i <= $#$points; $i++) {
my $angle = angle3points($points->[$i-1], $points->[$i-2], $points->[$i]);
return 0 if $angle < PI;
}
return 1;
}
# this assumes a CCW rotation from $p2 to $p3 around $p1
sub angle3points {
my ($p1, $p2, $p3) = @_;
# p1 is the center
my $angle = atan2($p2->[X] - $p1->[X], $p2->[Y] - $p1->[Y])
- atan2($p3->[X] - $p1->[X], $p3->[Y] - $p1->[Y]);
# we only want to return only positive angles
return $angle <= 0 ? $angle + 2*PI() : $angle;
}
*/
class ArrangeItem {
public:
Pointf pos;

View File

@ -25,6 +25,10 @@ double rad2deg(double angle);
double rad2deg_dir(double angle);
double deg2rad(double angle);
/// Epsilon value
constexpr double epsilon { 1e-4 };
constexpr coord_t scaled_epsilon { static_cast<coord_t>(epsilon / SCALING_FACTOR) };
double linint(double value, double oldmin, double oldmax, double newmin, double newmax);
bool arrange(
// input

View File

@ -248,7 +248,8 @@ void
Layer::make_fills()
{
#ifdef SLIC3R_DEBUG
printf("Making fills for layer %zu\n", this->id());
Slic3r::Log::debug("Layer") << "Making fills for layer "
<< this->id() << "\n";
#endif
FOREACH_LAYERREGION(this, it_layerm) {

View File

@ -137,6 +137,9 @@ class Layer {
void detect_surfaces_type();
/// Processes the external surfaces
void process_external_surfaces();
/// polymorphic id
virtual bool is_support() const { return false;}
protected:
size_t _id; ///< sequential number of layer, 0-based
@ -169,6 +172,9 @@ class SupportLayer : public Layer {
/// Populated in SupportMaterial.pm in sub generate_toolpaths
ExtrusionEntityCollection support_interface_fills;
/// polymorphic id
bool is_support() const override { return true;}
protected:
/// Constructor
SupportLayer(size_t id, PrintObject *object, coordf_t height,

View File

@ -176,12 +176,14 @@ bool LayerHeightSpline::_updateBSpline()
this->_spline_layers.push_back(this->_spline_layers.back()+1);
this->_spline_layer_heights = this->_layer_heights;
this->_spline_layer_heights[0] = this->_spline_layer_heights[1]; // override fixed first layer height with first "real" layer
if (this->_layer_heights.size() > 1)
this->_spline_layer_heights.at(0) = this->_spline_layer_heights.at(1); // override fixed first layer height with first "real" layer
this->_spline_layer_heights.push_back(this->_spline_layer_heights.back());
this->_layer_height_spline.reset(new BSpline<double>(&this->_spline_layers[0],
this->_layer_height_spline.reset(new BSpline<double>(&this->_spline_layers.at(0),
this->_spline_layers.size(),
&this->_spline_layer_heights[0],
&this->_spline_layer_heights.at(0),
0,
1,
0)

View File

@ -50,7 +50,23 @@ public:
std::cerr << topic << " DEBUG" << ": ";
std::cerr << message << std::endl;
}
static std::ostream& error(std::string topic) {
std::cerr << topic << " ERR" << ": ";
return std::cerr;
}
static std::ostream& debug(std::string topic) {
std::cerr << topic << " DEBUG" << ": ";
return std::cerr;
}
static std::ostream& warn(std::string topic) {
std::cerr << topic << " WARN" << ": ";
return std::cerr;
}
static std::ostream& info(std::string topic) {
std::cerr << topic << " INFO" << ": ";
return std::cerr;
}
};
/// Utility debug function to transform a std::vector of anything that

View File

@ -33,17 +33,18 @@ class Point
public:
coord_t x;
coord_t y;
Point(coord_t _x = 0, coord_t _y = 0): x(_x), y(_y) {};
Point(int _x, int _y): x(_x), y(_y) {};
Point(long long _x, long long _y): x(_x), y(_y) {}; // for Clipper
constexpr Point(coord_t _x = 0, coord_t _y = 0): x(_x), y(_y) {};
constexpr Point(int _x, int _y): x(_x), y(_y) {};
constexpr Point(long long _x, long long _y): x(_x), y(_y) {}; // for Clipper
Point(double x, double y);
static Point new_scale(coordf_t x, coordf_t y) {
static constexpr Point new_scale(coordf_t x, coordf_t y) {
return Point(scale_(x), scale_(y));
};
/// Scale and create a Point from a Pointf.
static Point new_scale(Pointf p);
bool operator==(const Point& rhs) const;
bool operator!=(const Point& rhs) const { return !(*this == rhs); }
std::string wkt() const;
std::string dump_perl() const;
void scale(double factor);
@ -102,8 +103,8 @@ class Point3 : public Point
{
public:
coord_t z;
explicit Point3(coord_t _x = 0, coord_t _y = 0, coord_t _z = 0): Point(_x, _y), z(_z) {};
bool coincides_with(const Point3 &point3) const { return this->x == point3.x && this->y == point3.y && this->z == point3.z; }
explicit constexpr Point3(coord_t _x = 0, coord_t _y = 0, coord_t _z = 0): Point(_x, _y), z(_z) {};
bool constexpr coincides_with(const Point3 &point3) const { return this->x == point3.x && this->y == point3.y && this->z == point3.z; }
};
std::ostream& operator<<(std::ostream &stm, const Pointf &pointf);
@ -113,11 +114,11 @@ class Pointf
public:
coordf_t x;
coordf_t y;
explicit Pointf(coordf_t _x = 0, coordf_t _y = 0): x(_x), y(_y) {};
static Pointf new_unscale(coord_t x, coord_t y) {
explicit constexpr Pointf(coordf_t _x = 0, coordf_t _y = 0): x(_x), y(_y) {};
static constexpr Pointf new_unscale(coord_t x, coord_t y) {
return Pointf(unscale(x), unscale(y));
};
static Pointf new_unscale(const Point &p) {
static constexpr Pointf new_unscale(const Point &p) {
return Pointf(unscale(p.x), unscale(p.y));
};
@ -141,8 +142,8 @@ class Pointf3 : public Pointf
{
public:
coordf_t z;
explicit Pointf3(coordf_t _x = 0, coordf_t _y = 0, coordf_t _z = 0): Pointf(_x, _y), z(_z) {};
static Pointf3 new_unscale(coord_t x, coord_t y, coord_t z) {
explicit constexpr Pointf3(coordf_t _x = 0, coordf_t _y = 0, coordf_t _z = 0): Pointf(_x, _y), z(_z) {};
static constexpr Pointf3 new_unscale(coord_t x, coord_t y, coord_t z) {
return Pointf3(unscale(x), unscale(y), unscale(z));
};
void scale(double factor);

View File

@ -21,6 +21,7 @@ class Polygon : public MultiPoint {
const Point& operator[](Points::size_type idx) const;
Polygon() {};
explicit Polygon(const Points &points): MultiPoint(points) {};
Point last_point() const;
virtual Lines lines() const;
@ -52,6 +53,21 @@ class Polygon : public MultiPoint {
static Polygon new_scale(const Pointfs& p);
};
// Append a vector of polygons at the end of another vector of polygons.
inline void polygons_append(Polygons &dst, const Polygons &src) { dst.insert(dst.end(), src.begin(), src.end()); }
inline void polygons_append(Polygons &dst, Polygons &&src)
{
if (dst.empty()) {
dst = std::move(src);
} else {
std::move(std::begin(src), std::end(src), std::back_inserter(dst));
src.clear();
}
}
inline Polygons
operator+(Polygons src1, const Polygons &src2) {
append_to(src1, src2);

View File

@ -1,4 +1,5 @@
#include "Print.hpp"
#include "PrintGCode.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "Fill/Fill.hpp"
@ -91,6 +92,209 @@ Print::delete_object(size_t idx)
// TODO: purge unused regions
}
#ifndef SLIC3RXS
void
Print::process()
{
/// No need to call this as we call it as part of prepare_infill()
/// until we fix the idempotency issue.
// if (this->status_cb != nullptr)
// this->status_cb(20, "Generating perimeters");
// for(auto& obj : this->objects) { obj->make_perimeters(); }
if (this->status_cb != nullptr)
this->status_cb(70, "Infilling layers");
for(auto& obj : this->objects) { obj->infill(); }
for(auto& obj : this->objects) { obj->generate_support_material(); }
this->make_skirt();
this->make_brim(); // must follow make_skirt
}
void
Print::make_brim()
{
if (this->state.is_done(psBrim)) return;
// prereqs
for(auto& obj: this->objects) {
obj->make_perimeters();
obj->infill();
obj->generate_support_material();
}
this->state.set_started(psBrim);
if (this->status_cb != nullptr)
this->status_cb(88, "Generating brim");
this->_make_brim();
this->state.set_done(psBrim);
}
void
Print::make_skirt()
{
// prereqs
for(auto& obj: this->objects) {
obj->make_perimeters();
obj->infill();
obj->generate_support_material();
}
if (this->state.is_done(psSkirt)) return;
this->state.set_started(psSkirt);
// since this method must be idempotent, we clear skirt paths *before*
// checking whether we need to generate them
this->skirt.clear();
if (!this->has_skirt()) {
this->state.set_done(psSkirt);
return;
}
if (this->status_cb != nullptr)
this->status_cb(88, "Generating skirt");
// First off we need to decide how tall the skirt must be.
// The skirt_height option from config is expressed in layers, but our
// object might have different layer heights, so we need to find the print_z
// of the highest layer involved.
// Note that unless has_infinite_skirt() == true
// the actual skirt might not reach this $skirt_height_z value since the print
// order of objects on each layer is not guaranteed and will not generally
// include the thickest object first. It is just guaranteed that a skirt is
// prepended to the first 'n' layers (with 'n' = skirt_height).
// $skirt_height_z in this case is the highest possible skirt height for safety.
double skirt_height_z {-1.0};
for (const auto& object : this->objects) {
size_t skirt_height {
this->has_infinite_skirt() ? object->layer_count() :
std::min(size_t(this->config.skirt_height()), object->layer_count())
};
std::cerr << object->layer_count();
auto* highest_layer {object->get_layer(skirt_height - 1)};
skirt_height_z = std::max(skirt_height_z, highest_layer->print_z);
}
// collect points from all layers contained in skirt height
Points points;
for(auto* object : this->objects) {
Points object_points;
// get object layers up to skirt_height_z
for(auto* layer : object->layers) {
if(layer->print_z > skirt_height_z)break;
for(ExPolygon poly : layer->slices){
for(Point point : static_cast<Points>(poly)){
object_points.push_back(point);
}
}
}
// get support layers up to $skirt_height_z
for(auto* layer : object->support_layers) {
if(layer->print_z > skirt_height_z)break;
for(auto* ee : layer->support_fills){
for(Point point : ee->as_polyline().points){
object_points.push_back(point);
}
}
for(auto* ee : layer->support_interface_fills){
for(Point point : ee->as_polyline().points){
object_points.push_back(point);
}
}
}
// repeat points for each object copy
for(auto copy : object->_shifted_copies) {
for(Point point : object_points){
point.translate(copy);
points.push_back(point);
}
}
}
if (points.size() < 3) return; // at least three points required for a convex hull
// find out convex hull
auto convex = Geometry::convex_hull(points);
// skirt may be printed on several layers, having distinct layer heights,
// but loops must be aligned so can't vary width/spacing
// TODO: use each extruder's own flow
auto first_layer_height = this->skirt_first_layer_height();
auto flow = this->skirt_flow();
auto spacing = flow.spacing();
auto mm3_per_mm = flow.mm3_per_mm();
auto skirts = this->config.skirts;
if(this->has_infinite_skirt() && skirts == 0){
skirts = 1;
}
//my @extruded_length = (); # for each extruder
//extruders_e_per_mm = ();
//size_t extruder_idx = 0;
// new to the cpp implementation
float e_per_mm, extruded_length = 0;
size_t extruders_warm = 0;
if (this->config.min_skirt_length.getFloat() > 0) {
//my $config = Config::GCode();
//$config->apply_static($self->config);
auto extruder = Extruder(0, &this->config);
e_per_mm = extruder.e_per_mm(mm3_per_mm);
}
// draw outlines from outside to inside
// loop while we have less skirts than required or any extruder hasn't reached the min length if any
float distance = scale_(std::max(this->config.skirt_distance.getFloat(), this->config.brim_width.getFloat()));
for (int i = skirts; i > 0; i--) {
distance += scale_(spacing);
auto loop = offset(Polygons{convex}, distance, 1, jtRound, scale_(0.1)).at(0);
auto epath = ExtrusionPath(erSkirt,
mm3_per_mm, // this will be overridden at G-code export time
flow.width,
first_layer_height // this will be overridden at G-code export time
);
epath.polyline = loop.split_at_first_point();
auto eloop = ExtrusionLoop(epath,elrSkirt);
this->skirt.append(eloop);
if (this->config.min_skirt_length.getFloat() > 0) {
// Alternative simpler method
extruded_length += unscale(loop.length()) * e_per_mm;
if(extruded_length >= this->config.min_skirt_length.getFloat()){
extruders_warm++;
extruded_length = 0;
}
if (extruders_warm < this->extruders().size()){
i++;
}
/*$extruded_length[$extruder_idx] ||= 0;
if (!$extruders_e_per_mm[$extruder_idx]) {
my $config = Slic3r::Config::GCode->new;
$config->apply_static($self->config);
my $extruder = Slic3r::Extruder->new($extruder_idx, $config);
$extruders_e_per_mm[$extruder_idx] = $extruder->e_per_mm($mm3_per_mm);
}
$extruded_length[$extruder_idx] += unscale $loop->length * $extruders_e_per_mm[$extruder_idx];
$i++ if defined first { ($extruded_length[$_] // 0) < $self->config->min_skirt_length } 0 .. $#{$self->extruders};
if ($extruded_length[$extruder_idx] >= $self->config->min_skirt_length) {
if ($extruder_idx < $#{$self->extruders}) {
$extruder_idx++;
next;
}
}*/
}
}
this->skirt.reverse();
this->state.set_done(psSkirt);
}
#endif // SLIC3RXS
void
Print::reload_object(size_t idx)
{
@ -500,6 +704,27 @@ Print::add_model_object(ModelObject* model_object, int idx)
}
}
#ifndef SLIC3RXS
void
Print::export_gcode(std::ostream& output, bool quiet)
{
this->process();
if (this->status_cb != nullptr)
this->status_cb(90, "Exporting G-Code...");
auto export_handler {Slic3r::PrintGCode(*this, output)};
export_handler.output();
}
bool
Print::apply_config(config_ptr config) {
// dereference the stored pointer and pass the resulting data to apply_config()
return this->apply_config(config->config());
}
#endif
bool
Print::apply_config(DynamicPrintConfig config)
{

View File

@ -5,16 +5,21 @@
#include <set>
#include <string>
#include <vector>
#include <memory>
#include <boost/thread.hpp>
#include "BoundingBox.hpp"
#include "Flow.hpp"
#include "PrintConfig.hpp"
#include "Config.hpp"
#include "Point.hpp"
#include "Layer.hpp"
#include "Model.hpp"
#include "PlaceholderParser.hpp"
#include "SlicingAdaptive.hpp"
#include "LayerHeightSpline.hpp"
#include "SupportMaterial.hpp"
#include <exception>
#include <exception>
@ -25,6 +30,7 @@ class InvalidObjectException : public std::exception {};
class Print;
class PrintObject;
class ModelObject;
class SupportMaterial;
// Print step IDs for keeping track of the print state.
enum PrintStep {
@ -109,6 +115,7 @@ class PrintObject
Print* print();
ModelObject* model_object() { return this->_model_object; };
const ModelObject& model_object() const { return *(this->_model_object); };
Points copies() const;
bool add_copy(const Pointf &point);
@ -132,6 +139,9 @@ class PrintObject
Layer* add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z);
void delete_layer(int idx);
SupportMaterial* _support_material();
Flow _support_material_flow(FlowRole role = frSupportMaterial);
size_t support_layer_count() const;
void clear_support_layers();
SupportLayer* get_support_layer(int idx) { return this->support_layers.at(idx); };
@ -147,14 +157,50 @@ class PrintObject
bool has_support_material() const;
void detect_surfaces_type();
void process_external_surfaces();
void bridge_over_infill();
coordf_t adjust_layer_height(coordf_t layer_height) const;
std::vector<coordf_t> generate_object_layers(coordf_t first_layer_height);
void _slice();
std::vector<ExPolygons> _slice_region(size_t region_id, std::vector<float> z, bool modifier);
void _make_perimeters();
void _infill();
#ifndef SLIC3RXS
/// Initialize and generate support material.
void generate_support_material();
/// Generate perimeters for this PrintObject.
void make_perimeters();
/// Combine fill surfaces across layers.
/// 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.
void combine_infill();
/// Preparation step for generating infill.
void prepare_infill();
/// Generate infill for this PrintObject.
void infill();
/// Kick off the slice process for this object
void slice();
/// Find all horizontal shells in this object
void discover_horizontal_shells();
/// Only active if config->infill_only_where_needed. This step trims the sparse infill,
/// so it acts as an internal support. It maintains all other infill types intact.
/// Here the internal surfaces and perimeters have to be supported by the sparse infill.
///FIXME The surfaces are supported by a sparse infill, but the sparse infill is only as large as the area to support.
/// Likely the sparse infill will not be anchored correctly, so it will not work as intended.
/// Also one wishes the perimeters to be supported by a full infill.
/// 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.
void clip_fill_surfaces();
#endif // SLIC3RXS
private:
Print* _print;
ModelObject* _model_object;
@ -164,6 +210,14 @@ class PrintObject
// parameter
PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox);
~PrintObject();
#ifndef SLIC3RXS
/// Outer loop of logic for horizontal shell discovery
void _discover_external_horizontal_shells(LayerRegion* layerm, const size_t& i, const size_t& region_id);
/// Inner loop of logic for horizontal shell discovery
void _discover_neighbor_horizontal_shells(LayerRegion* layerm, const size_t& i, const size_t& region_id, const SurfaceType& type, Polygons& solid, const size_t& solid_layers);
#endif // SLIC3RXS
};
typedef std::vector<PrintObject*> PrintObjectPtrs;
@ -180,6 +234,13 @@ class Print
PrintRegionPtrs regions;
PlaceholderParser placeholder_parser;
// TODO: status_cb
#ifndef SLIC3RXS
std::function<void(int, const std::string&)> status_cb {nullptr};
/// Function pointer for the UI side to call post-processing scripts.
/// Vector is assumed to be the executable script and all arguments.
std::function<void(std::vector<std::string>)> post_process_cb {nullptr};
#endif
double total_used_filament, total_extruded_volume, total_cost, total_weight;
std::map<size_t,float> filament_stats;
PrintState<PrintStep> state;
@ -202,6 +263,21 @@ class Print
PrintRegion* get_region(size_t idx) { return this->regions.at(idx); };
const PrintRegion* get_region(size_t idx) const { return this->regions.at(idx); };
PrintRegion* add_region();
#ifndef SLIC3RXS
/// Triggers the rest of the print process
void process();
/// Performs a gcode export.
void export_gcode(std::ostream& output, bool quiet = false);
/// Performs a gcode export and then runs post-processing scripts (if any)
void export_gcode(const std::string& filename, bool quiet = false);
/// commands a gcode export to a temporary file and return its name
std::string export_gcode(bool quiet = false);
#endif // SLIC3RXS
// methods for handling state
bool invalidate_state_by_config(const PrintConfigBase &config);
@ -210,6 +286,10 @@ class Print
bool step_done(PrintObjectStep step) const;
void add_model_object(ModelObject* model_object, int idx = -1);
#ifndef SLIC3RXS
/// Apply a provided configuration to the internal copy
bool apply_config(config_ptr config);
#endif // SLIC3RXS
bool apply_config(DynamicPrintConfig config);
bool has_infinite_skirt() const;
bool has_skirt() const;
@ -221,6 +301,16 @@ class Print
Flow brim_flow() const;
Flow skirt_flow() const;
void _make_brim();
#ifndef SLIC3RXS
/// Generates a skirt around the union of all of
/// the objects in the print.
void make_skirt();
/// Generates a brim around all of the objects in the print.
void make_brim();
#endif // SLIC3RXS
std::set<size_t> object_extruders() const;
std::set<size_t> support_material_extruders() const;
@ -232,13 +322,14 @@ class Print
void auto_assign_extruders(ModelObject* model_object) const;
std::string output_filename();
std::string output_filepath(const std::string &path);
private:
void clear_regions();
void delete_region(size_t idx);
PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume);
};
using shared_Print = std::shared_ptr<Print>;
#define FOREACH_BASE(type, container, iterator) for (type::const_iterator iterator = (container).begin(); iterator != (container).end(); ++iterator)
#define FOREACH_REGION(print, region) FOREACH_BASE(PrintRegionPtrs, (print)->regions, region)
#define FOREACH_OBJECT(print, object) FOREACH_BASE(PrintObjectPtrs, (print)->objects, object)

View File

@ -0,0 +1,748 @@
#ifndef SLIC3RXS
#include "PrintGCode.hpp"
#include "PrintConfig.hpp"
#include <ctime>
#include <iostream>
namespace Slic3r {
void
PrintGCode::output()
{
auto& gcodegen {this->_gcodegen};
auto& fh {this->fh};
auto& print {this->_print};
const auto& config {this->config};
const auto extruders {print.extruders()};
// Write information about the generator.
time_t rawtime; tm * timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
fh << "; generated by Slic3r " << SLIC3R_VERSION << " on ";
fh << asctime(timeinfo) << "\n";
fh << "; Git Commit: " << BUILD_COMMIT << "\n\n";
// Writes notes (content of all Settings tabs -> Notes)
fh << gcodegen.notes();
// Write some terse information on the slicing parameters.
auto& first_object {*(this->objects.at(0))};
auto layer_height {first_object.config.layer_height.getFloat()};
for (auto* region : print.regions) {
{
auto flow {region->flow(frExternalPerimeter, layer_height, false, false, -1, first_object)};
auto vol_speed {flow.mm3_per_mm() * region->config.get_abs_value("external_perimeter_speed")};
if (config.max_volumetric_speed.getInt() > 0)
vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat());
fh << "; external perimeters extrusion width = ";
fh << std::fixed << std::setprecision(2) << flow.width << "mm ";
fh << "(" << vol_speed << "mm^3/s)\n";
}
{
auto flow {region->flow(frPerimeter, layer_height, false, false, -1, first_object)};
auto vol_speed {flow.mm3_per_mm() * region->config.get_abs_value("perimeter_speed")};
if (config.max_volumetric_speed.getInt() > 0)
vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat());
fh << "; perimeters extrusion width = ";
fh << std::fixed << std::setprecision(2) << flow.width << "mm ";
fh << "(" << vol_speed << "mm^3/s)\n";
}
{
auto flow {region->flow(frInfill, layer_height, false, false, -1, first_object)};
auto vol_speed {flow.mm3_per_mm() * region->config.get_abs_value("infill_speed")};
if (config.max_volumetric_speed.getInt() > 0)
vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat());
fh << "; infill extrusion width = ";
fh << std::fixed << std::setprecision(2) << flow.width << "mm ";
fh << "(" << vol_speed << "mm^3/s)\n";
}
{
auto flow {region->flow(frSolidInfill, layer_height, false, false, -1, first_object)};
auto vol_speed {flow.mm3_per_mm() * region->config.get_abs_value("solid_infill_speed")};
if (config.max_volumetric_speed.getInt() > 0)
vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat());
fh << "; solid infill extrusion width = ";
fh << std::fixed << std::setprecision(2) << flow.width << "mm ";
fh << "(" << vol_speed << "mm^3/s)\n";
}
{
auto flow {region->flow(frTopSolidInfill, layer_height, false, false, -1, first_object)};
auto vol_speed {flow.mm3_per_mm() * region->config.get_abs_value("top_solid_infill_speed")};
if (config.max_volumetric_speed.getInt() > 0)
vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat());
fh << "; top solid infill extrusion width = ";
fh << std::fixed << std::setprecision(2) << flow.width << "mm ";
fh << "(" << vol_speed << "mm^3/s)\n";
}
if (print.has_support_material()) {
auto flow {first_object._support_material_flow()};
auto vol_speed {flow.mm3_per_mm() * first_object.config.get_abs_value("support_material_speed")};
if (config.max_volumetric_speed.getInt() > 0)
vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat());
fh << "; support material extrusion width = ";
fh << std::fixed << std::setprecision(2) << flow.width << "mm ";
fh << "(" << vol_speed << "mm^3/s)\n";
}
if (print.config.first_layer_extrusion_width.getFloat() > 0) {
auto flow {region->flow(frPerimeter, layer_height, false, false, -1, first_object)};
// auto vol_speed {flow.mm3_per_mm() * print.config.get_abs_value("first_layer_speed")};
// if (config.max_volumetric_speed.getInt() > 0)
// vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat());
fh << "; first layer extrusion width = ";
fh << std::fixed << std::setprecision(2) << flow.width << "mm ";
// fh << "(" << vol_speed << "mm^3/s)\n";
}
fh << std::endl;
}
// Prepare the helper object for replacing placeholders in custom G-Code and output filename
print.placeholder_parser.update_timestamp();
// GCode sets this automatically when change_layer() is called, but needed for skirt/brim as well
gcodegen.first_layer = true;
// disable fan
if (config.cooling.getBool() && config.disable_fan_first_layers.getInt() > 0) {
fh << gcodegen.writer.set_fan(0,1) << "\n";
}
// set bed temperature
auto temp{config.first_layer_bed_temperature.getFloat()};
if (config.has_heatbed && temp > 0 && std::regex_search(config.start_gcode.getString(), bed_temp_regex)) {
fh << gcodegen.writer.set_bed_temperature(temp, 1);
}
// Set extruder(s) temperature before and after start gcode.
auto include_start_extruder_temp {!std::regex_search(config.start_gcode.getString(), ex_temp_regex)};
for(const auto& start_gcode : config.start_filament_gcode.values) {
include_start_extruder_temp = include_start_extruder_temp && !std::regex_search(start_gcode, ex_temp_regex);
}
auto include_end_extruder_temp {!std::regex_search(config.end_gcode.getString(), ex_temp_regex)};
for(const auto& end_gcode : config.end_filament_gcode.values) {
include_end_extruder_temp = include_end_extruder_temp && !std::regex_search(end_gcode, ex_temp_regex);
}
if (include_start_extruder_temp) this->_print_first_layer_temperature(0);
// Apply gcode math to start and end gcode
fh << apply_math(gcodegen.placeholder_parser->process(config.start_gcode.value));
for(const auto& start_gcode : config.start_filament_gcode.values) {
fh << apply_math(gcodegen.placeholder_parser->process(start_gcode));
}
if (include_start_extruder_temp) this->_print_first_layer_temperature(1);
// Set other general things (preamble)
fh << gcodegen.preamble();
// initialize motion planner for object-to-object travel moves
if (config.avoid_crossing_perimeters.getBool()) {
// compute the offsetted convex hull for each object and repeat it for each copy
Polygons islands_p {};
for (auto object : this->objects) {
Polygons polygons {};
// Add polygons that aren't just thin walls.
for (auto layer : object->layers) {
const auto& slice {ExPolygons(layer->slices)};
std::for_each(slice.cbegin(), slice.cend(), [&polygons] (const ExPolygon& a) { polygons.emplace_back(a.contour); });
}
if (polygons.size() == 0) continue;
for (auto copy : object->_shifted_copies) {
Polygons copy_islands_p {polygons};
std::for_each(copy_islands_p.begin(), copy_islands_p.end(), [copy] (Polygon& obj) { obj.translate(copy); });
islands_p.insert(islands_p.cend(), copy_islands_p.begin(), copy_islands_p.end());
}
}
gcodegen.avoid_crossing_perimeters.init_external_mp(union_ex(islands_p));
}
// Calculate wiping points if needed.
if (config.ooze_prevention && extruders.size() > 1) {
}
// Set initial extruder only after custom start gcode
fh << gcodegen.set_extruder(*(extruders.begin()));
// Do all objects for each layer.
if (config.complete_objects) {
// print objects from the smallest to the tallest to avoid collisions
// when moving onto next object starting point
std::sort(print.objects.begin(), print.objects.end(), [] (const PrintObject* a, const PrintObject* b) {
return (a->config.sequential_print_priority < a->config.sequential_print_priority) || (a->size.z < b->size.z);
});
size_t finished_objects {0};
for (size_t obj_idx {0}; obj_idx < print.objects.size(); ++obj_idx) {
PrintObject& object {*(this->objects.at(obj_idx))};
for (const Point& copy : object._shifted_copies) {
if (finished_objects > 0) {
gcodegen.set_origin(Pointf::new_unscale(copy));
gcodegen.enable_cooling_markers = false;
gcodegen.avoid_crossing_perimeters.use_external_mp_once = true;
fh << gcodegen.retract();
fh << gcodegen.travel_to(Point(0,0), erNone, "move to origin position for next object");
gcodegen.enable_cooling_markers = true;
// disable motion planner when traveling to first object point
gcodegen.avoid_crossing_perimeters.disable_once = true;
}
std::vector<Layer*> layers;
layers.reserve(object.layers.size() + object.support_layers.size());
for (auto l : object.layers) {
layers.emplace_back(l);
}
for (auto l : object.support_layers) {
layers.emplace_back(static_cast<Layer*>(l));
}
std::sort(layers.begin(), layers.end(), [] (const Layer* a, const Layer* b) { return a->print_z < b->print_z; });
for (Layer* layer : layers) {
// if we are printing the bottom layer of an object, and we have already finished
// another one, set first layer temperatures. this happens before the Z move
// is triggered, so machine has more time to reach such temperatures
if (layer->id() == 0 && finished_objects > 0) {
if (config.first_layer_bed_temperature > 0 &&
config.has_heatbed &&
std::regex_search(config.between_objects_gcode.getString(), bed_temp_regex))
{
fh << gcodegen.writer.set_bed_temperature(config.first_layer_bed_temperature);
}
if (std::regex_search(config.between_objects_gcode.getString(), ex_temp_regex)) {
_print_first_layer_temperature(false);
}
}
this->process_layer(obj_idx, layer, Points({copy}));
}
this->flush_filters();
finished_objects++;
this->_second_layer_things_done = false;
}
}
} else {
// order objects using a nearest neighbor search
std::vector<Points::size_type> obj_idx {};
Points p;
for (const auto obj : this->objects )
p.emplace_back(obj->_shifted_copies.at(0));
Geometry::chained_path(p, obj_idx);
std::vector<size_t> z;
z.reserve(100); // preallocate with 100 layers
std::map<coord_t, std::map<size_t, LayerPtrs > > layers {};
for (size_t idx = 0U; idx < print.objects.size(); ++idx) {
const auto& object {*(objects.at(idx))};
// sort layers by Z into buckets
for (Layer* layer : object.layers) {
if (layers.count(scale_(layer->print_z)) == 0) { // initialize bucket if empty
layers[scale_(layer->print_z)] = std::map<size_t, LayerPtrs >();
layers[scale_(layer->print_z)][idx] = LayerPtrs();
z.emplace_back(scale_(layer->print_z));
}
layers[scale_(layer->print_z)][idx].emplace_back(layer);
}
for (Layer* layer : object.support_layers) { // don't use auto here to not have to cast later
if (layers.count(scale_(layer->print_z)) == 0) { // initialize bucket if empty
layers[scale_(layer->print_z)] = std::map<size_t, LayerPtrs >();
layers[scale_(layer->print_z)][idx] = LayerPtrs();
}
layers[scale_(layer->print_z)][idx].emplace_back(layer);
}
}
// pass the comparator to leave no doubt.
std::sort(z.begin(), z.end(), std::less<size_t>());
// call process_layers in the order given by obj_idx
for (const auto& print_z : z) {
for (const auto& idx : obj_idx) {
for (const auto* layer : layers[print_z][idx] ) {
this->process_layer(idx, layer, layer->object()->_shifted_copies);
}
}
}
this->flush_filters();
}
// Write end commands to file.
fh << gcodegen.retract(); // TODO: process this retract through PressureRegulator in order to discharge fully
// set bed temperature
if (config.has_heatbed && temp > 0 && std::regex_search(config.end_gcode.getString(), bed_temp_regex)) {
fh << gcodegen.writer.set_bed_temperature(0, 0);
}
// Get filament stats
print.filament_stats.clear();
print.total_used_filament = 0.0;
print.total_extruded_volume = 0.0;
print.total_weight = 0.0;
print.total_cost = 0.0;
for (auto extruder_pair : gcodegen.writer.extruders) {
const auto& extruder {extruder_pair.second};
auto used_material {extruder.used_filament()};
auto extruded_volume {extruder.extruded_volume()};
auto material_weight {extruded_volume * extruder.filament_density() / 1000.0};
auto material_cost { material_weight * (extruder.filament_cost() / 1000.0)};
print.filament_stats[extruder.id] = used_material;
fh << "; material used = ";
fh << std::fixed << std::setprecision(2) << used_material << "mm ";
fh << "(" << std::fixed << std::setprecision(2)
<< extruded_volume / 1000.0
<< used_material << "cm3)\n";
if (material_weight > 0) {
print.total_weight += material_weight;
fh << "; material used = "
<< std::fixed << std::setprecision(2) << material_weight << "g\n";
if (material_cost > 0) {
print.total_cost += material_cost;
fh << "; material cost = "
<< std::fixed << std::setprecision(2) << material_weight << "g\n";
}
}
print.total_used_filament += used_material;
print.total_extruded_volume += extruded_volume;
}
fh << "; total filament cost = "
<< std::fixed << std::setprecision(2) << print.total_cost << "\n";
// Append full config
fh << std::endl;
// print config
_print_config(print.config);
_print_config(print.default_object_config);
_print_config(print.default_region_config);
}
std::string
PrintGCode::filter(const std::string& in, bool wait)
{
return in;
}
void
PrintGCode::process_layer(size_t idx, const Layer* layer, const Points& copies)
{
std::string gcode {""};
auto& gcodegen {this->_gcodegen};
const auto& print {this->_print};
const auto& config {this->config};
const auto& obj {*(layer->object())};
gcodegen.config.apply(obj.config, true);
// check for usage of spiralvase logic.
// if using spiralvase, disable loop clipping.
// initialize autospeed.
{
// get the minimum cross-section used in the layer.
std::vector<double> mm3_per_mm;
for (auto region_id = 0U; region_id < print.regions.size(); ++region_id) {
const auto& region {print.regions.at(region_id)};
const auto& layerm {layer->get_region(region_id)};
if (!(region->config.get_abs_value("perimeter_speed") > 0 &&
region->config.get_abs_value("small_perimeter_speed") > 0 &&
region->config.get_abs_value("external_perimeter_speed") > 0 &&
region->config.get_abs_value("bridge_speed") > 0))
{
mm3_per_mm.emplace_back(layerm->perimeters.min_mm3_per_mm());
}
if (!(region->config.get_abs_value("infill_speed") > 0 &&
region->config.get_abs_value("solid_infill_speed") > 0 &&
region->config.get_abs_value("top_solid_infill_speed") > 0 &&
region->config.get_abs_value("bridge_speed") > 0 &&
region->config.get_abs_value("gap_fill_speed") > 0)) // TODO: make this configurable?
{
mm3_per_mm.emplace_back(layerm->fills.min_mm3_per_mm());
}
}
if (typeid(layer) == typeid(SupportLayer*)) {
const SupportLayer* slayer = dynamic_cast<const SupportLayer*>(layer);
if (!(obj.config.get_abs_value("support_material_speed") > 0 &&
obj.config.get_abs_value("support_material_interface_speed") > 0))
{
mm3_per_mm.emplace_back(slayer->support_fills.min_mm3_per_mm());
mm3_per_mm.emplace_back(slayer->support_interface_fills.min_mm3_per_mm());
}
}
// ignore too-thin segments.
// TODO make the definition of "too thin" based on a config somewhere
mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [] (const double& vol) { return vol <= 0.01;} ), mm3_per_mm.end());
if (mm3_per_mm.size() > 0) {
const auto min_mm3_per_mm {*(std::min_element(mm3_per_mm.begin(), mm3_per_mm.end()))};
// In order to honor max_print_speed we need to find a target volumetric
// speed that we can use throughout the print. So we define this target
// volumetric speed as the volumetric speed produced by printing the
// smallest cross-section at the maximum speed: any larger cross-section
// will need slower feedrates.
auto volumetric_speed {min_mm3_per_mm * config.max_print_speed};
if (config.max_volumetric_speed > 0) {
volumetric_speed = std::min(volumetric_speed, config.max_volumetric_speed.getFloat());
}
gcodegen.volumetric_speed = volumetric_speed;
}
}
// set the second layer + temp
if (!this->_second_layer_things_done && layer->id() == 1) {
for (const auto& extruder_ref : gcodegen.writer.extruders) {
const auto& extruder { extruder_ref.second };
auto temp { config.temperature.get_at(extruder.id) };
if (temp > 0 && temp != config.first_layer_temperature.get_at(extruder.id) )
gcode += gcodegen.writer.set_temperature(temp, 0, extruder.id);
}
if (config.has_heatbed && print.config.first_layer_bed_temperature > 0 && print.config.bed_temperature != print.config.first_layer_bed_temperature) {
gcode += gcodegen.writer.set_bed_temperature(print.config.bed_temperature);
}
this->_second_layer_things_done = true;
}
// set new layer - this will change Z and force a retraction if retract_layer_change is enabled
if (print.config.before_layer_gcode.getString().size() > 0) {
auto pp {*(gcodegen.placeholder_parser)};
pp.set("layer_num", gcodegen.layer_index);
pp.set("layer_z", layer->print_z);
pp.set("current_retraction", gcodegen.writer.extruder()->retracted);
gcode += apply_math(pp.process(print.config.before_layer_gcode.getString()));
gcode += "\n";
}
gcode += gcodegen.change_layer(*layer);
if (print.config.layer_gcode.getString().size() > 0) {
auto pp {*(gcodegen.placeholder_parser)};
pp.set("layer_num", gcodegen.layer_index);
pp.set("layer_z", layer->print_z);
pp.set("current_retraction", gcodegen.writer.extruder()->retracted);
gcode += apply_math(pp.process(print.config.layer_gcode.getString()));
gcode += "\n";
}
// extrude skirt along raft layers and normal obj layers
// (not along interlaced support material layers)
if (layer->id() < static_cast<size_t>(obj.config.raft_layers)
|| ((print.has_infinite_skirt() || _skirt_done.size() == 0 || (_skirt_done.rbegin())->first < print.config.skirt_height)
&& _skirt_done.count(scale_(layer->print_z)) == 0
&& typeid(layer) != typeid(SupportLayer*)) ) {
gcodegen.set_origin(Pointf(0,0));
gcodegen.avoid_crossing_perimeters.use_external_mp = true;
/// data load
std::vector<size_t> extruder_ids;
extruder_ids.reserve(gcodegen.writer.extruders.size());
std::transform(gcodegen.writer.extruders.cbegin(), gcodegen.writer.extruders.cend(), std::back_inserter(extruder_ids),
[] (const std::pair<unsigned int, Extruder>& z) -> std::size_t { return z.second.id; } );
gcode += gcodegen.set_extruder(extruder_ids.at(0));
// skip skirt if a large brim
if (print.has_infinite_skirt() || layer->id() < static_cast<size_t>(print.config.skirt_height)) {
const auto& skirt_flow {print.skirt_flow()};
// distribute skirt loops across all extruders in layer 0
auto skirt_loops {print.skirt.flatten().entities};
for (size_t i = 0; i < skirt_loops.size(); ++i) {
// when printing layers > 0 ignore 'min_skirt_length' and
// just use the 'skirts' setting; also just use the current extruder
if (layer->id() > 0 && i >= static_cast<size_t>(print.config.skirts)) break;
const auto extruder_id { extruder_ids.at((i / extruder_ids.size()) % extruder_ids.size()) };
if (layer->id() == 0)
gcode += gcodegen.set_extruder(extruder_id);
// adjust flow according to layer height
auto& loop {*(dynamic_cast<ExtrusionLoop*>(skirt_loops.at(i)))};
{
Flow layer_skirt_flow(skirt_flow);
layer_skirt_flow.height = layer->height;
auto mm3_per_mm {layer_skirt_flow.mm3_per_mm()};
for (auto& path : loop.paths) {
path.height = layer->height;
path.mm3_per_mm = mm3_per_mm;
}
}
gcode += gcodegen.extrude(loop, "skirt", obj.config.support_material_speed);
}
}
this->_skirt_done[scale_(layer->print_z)] = true;
gcodegen.avoid_crossing_perimeters.use_external_mp = false;
if (layer->id() == 0) gcodegen.avoid_crossing_perimeters.disable_once = true;
}
// extrude brim
if (this->_brim_done) {
gcode += gcodegen.set_extruder(print.brim_extruder() - 1);
gcodegen.set_origin(Pointf(0,0));
gcodegen.avoid_crossing_perimeters.use_external_mp = true;
for (const auto& b : print.brim.entities) {
gcode += gcodegen.extrude(*b, "brim", obj.config.get_abs_value("support_material_speed"));
}
this->_brim_done = true;
gcodegen.avoid_crossing_perimeters.use_external_mp = false;
// allow a straight travel move to the first object point
gcodegen.avoid_crossing_perimeters.disable_once = true;
}
auto copy_idx = 0U;
for (const auto& copy : copies) {
if (config.label_printed_objects) {
gcode += "; printing object " + obj.model_object().name + " id:" + std::to_string(idx) + " copy " + std::to_string(copy_idx) + "\n";
}
// when starting a new object, use the external motion planner for the first travel move
if (this->_last_obj_copy.first != copy && this->_last_obj_copy.second )
gcodegen.avoid_crossing_perimeters.use_external_mp = true;
this->_last_obj_copy.first = copy;
this->_last_obj_copy.second = true;
gcodegen.set_origin(Pointf::new_unscale(copy));
// extrude support material before other things because it might use a lower Z
// and also because we avoid travelling on other things when printing it
if(layer->is_support()) {
const SupportLayer* slayer = dynamic_cast<const SupportLayer*>(layer);
ExtrusionEntityCollection paths;
if (slayer->support_interface_fills.size() > 0) {
gcode += gcodegen.set_extruder(obj.config.support_material_interface_extruder - 1);
slayer->support_interface_fills.chained_path_from(gcodegen.last_pos(), &paths, false);
for (const auto& path : paths) {
gcode += gcodegen.extrude(*path, "support material interface", obj.config.get_abs_value("support_material_interface_speed"));
}
}
if (slayer->support_fills.size() > 0) {
gcode += gcodegen.set_extruder(obj.config.support_material_extruder - 1);
slayer->support_fills.chained_path_from(gcodegen.last_pos(), &paths, false);
for (const auto& path : paths) {
gcode += gcodegen.extrude(*path, "support material", obj.config.get_abs_value("support_material_speed"));
}
}
}
// We now define a strategy for building perimeters and fills. The separation
// between regions doesn't matter in terms of printing order, as we follow
// another logic instead:
// - we group all extrusions by extruder so that we minimize toolchanges
// - we start from the last used extruder
// - for each extruder, we group extrusions by island
// - for each island, we extrude perimeters first, unless user set the infill_first
// option
// (Still, we have to keep track of regions because we need to apply their config)
// group extrusions by extruder and then by island
// extruder island
std::map<size_t,std::map<size_t,
// region
std::tuple<std::map<size_t,ExtrusionEntityCollection>, // perimeters
std::map<size_t,ExtrusionEntityCollection>> // infill
>> by_extruder;
// cache bounding boxes of layer slices
std::vector<BoundingBox> layer_slices_bb;
std::transform(layer->slices.cbegin(), layer->slices.cend(), std::back_inserter(layer_slices_bb), [] (const ExPolygon& s)-> BoundingBox { return s.bounding_box(); });
auto point_inside_surface { [&layer_slices_bb, &layer] (size_t i, Point point) -> bool {
const auto& bbox {layer_slices_bb.at(i)};
return bbox.contains(point) && layer->slices.at(i).contour.contains(point);
}};
const auto n_slices {layer->slices.size()};
for (auto region_id = 0U; region_id < print.regions.size(); ++region_id) {
const LayerRegion* layerm;
try {
layerm = layer->get_region(region_id); // we promise to be good and not give this to anyone who will modify it
} catch (std::out_of_range &e) {
continue; // if no regions, bail;
}
auto* region {print.get_region(region_id)};
// process perimeters
{
auto extruder_id = region->config.perimeter_extruder-1;
// Casting away const just to avoid double dereferences
for(auto* perimeter_coll : layerm->perimeters.flatten().entities) {
if(perimeter_coll->length() == 0) continue; // this shouldn't happen but first_point() would fail
// perimeter_coll is an ExtrusionPath::Collection object representing a single slice
for(auto i = 0U; i < n_slices; i++){
if (// perimeter_coll->first_point does not fit inside any slice
i == n_slices - 1
// perimeter_coll->first_point fits inside ith slice
|| point_inside_surface(i, perimeter_coll->first_point())) {
std::get<0>(by_extruder[extruder_id][i])[region_id].append(*perimeter_coll);
break;
}
}
}
}
// process infill
// $layerm->fills is a collection of ExtrusionPath::Collection objects, each one containing
// the ExtrusionPath objects of a certain infill "group" (also called "surface"
// throughout the code). We can redefine the order of such Collections but we have to
// do each one completely at once.
for(auto* fill : layerm->fills.flatten().entities) {
if(fill->length() == 0) continue; // this shouldn't happen but first_point() would fail
auto extruder_id = fill->is_solid_infill()
? region->config.solid_infill_extruder-1
: region->config.infill_extruder-1;
// $fill is an ExtrusionPath::Collection object
for(auto i = 0U; i < n_slices; i++){
if (i == n_slices - 1
|| point_inside_surface(i, fill->first_point())) {
std::get<1>(by_extruder[extruder_id][i])[region_id].append(*fill);
break;
}
}
}
}
// tweak extruder ordering to save toolchanges
auto last_extruder = gcodegen.writer.extruder()->id;
if (by_extruder.count(last_extruder)) {
for(auto &island : by_extruder[last_extruder]) {
if (print.config.infill_first()) {
gcode += this->_extrude_infill(std::get<1>(island.second));
gcode += this->_extrude_perimeters(std::get<0>(island.second));
} else {
gcode += this->_extrude_perimeters(std::get<0>(island.second));
gcode += this->_extrude_infill(std::get<1>(island.second));
}
}
}
for(auto &pair : by_extruder) {
if(pair.first == last_extruder)continue;
gcode += gcodegen.set_extruder(pair.first);
for(auto &island : pair.second) {
if (print.config.infill_first()) {
gcode += this->_extrude_infill(std::get<1>(island.second));
gcode += this->_extrude_perimeters(std::get<0>(island.second));
} else {
gcode += this->_extrude_perimeters(std::get<0>(island.second));
gcode += this->_extrude_infill(std::get<1>(island.second));
}
}
}
if (config.label_printed_objects) {
gcode += "; stop printing object " + obj.model_object().name + " id:" + std::to_string(idx) + " copy " + std::to_string(copy_idx) + "\n";
}
copy_idx++;
}
// write the resulting gcode
fh << this->filter(gcode);
}
// Extrude perimeters: Decide where to put seams (hide or align seams).
std::string
PrintGCode::_extrude_perimeters(std::map<size_t,ExtrusionEntityCollection> &by_region)
{
std::string gcode = "";
for(auto& pair : by_region) {
this->_gcodegen.config.apply(this->_print.get_region(pair.first)->config);
for(auto& ee : pair.second){
gcode += this->_gcodegen.extrude(*ee, "perimeter");
}
}
return gcode;
}
// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance.
std::string
PrintGCode::_extrude_infill(std::map<size_t,ExtrusionEntityCollection> &by_region)
{
std::string gcode = "";
for(auto& pair : by_region) {
this->_gcodegen.config.apply(this->_print.get_region(pair.first)->config);
ExtrusionEntityCollection tmp;
pair.second.chained_path_from(this->_gcodegen.last_pos(),&tmp);
for(auto& ee : tmp){
gcode += this->_gcodegen.extrude(*ee, "infill");
}
}
return gcode;
}
void
PrintGCode::_print_first_layer_temperature(bool wait)
{
auto& gcodegen {this->_gcodegen};
auto& fh {this->fh};
const auto& print {this->_print};
const auto& config {this->config};
const auto extruders {print.extruders()};
for (auto& t : extruders) {
auto temp { config.first_layer_temperature.get_at(t) };
if (config.ooze_prevention.value) temp += config.standby_temperature_delta.value;
if (temp > 0) fh << gcodegen.writer.set_temperature(temp, wait, t);
}
}
void
PrintGCode::_print_config(const ConfigBase& config)
{
for (const auto& key : config.keys()) {
// skip if a shortcut option
// if (std::find(print_config_def.cbegin(), print_config_def.cend(), key) > 0) continue;
fh << "; " << key << " = " << config.serialize(key) << "\n";
}
}
PrintGCode::PrintGCode(Slic3r::Print& print, std::ostream& _fh) :
_print(print),
config(print.config),
_gcodegen(Slic3r::GCode()),
objects(print.objects),
fh(_fh),
_cooling_buffer(Slic3r::CoolingBuffer(this->_gcodegen)),
_spiral_vase(Slic3r::SpiralVase(this->config))
{
size_t layer_count {0};
if (config.complete_objects) {
layer_count = std::accumulate(objects.cbegin(), objects.cend(), layer_count, [](const size_t& ret, const PrintObject* obj){ return ret + (obj->copies().size() * obj->total_layer_count()); });
} else {
layer_count = std::accumulate(objects.cbegin(), objects.cend(), layer_count, [](const size_t& ret, const PrintObject* obj){ return ret + obj->total_layer_count(); });
}
_gcodegen.placeholder_parser = &(print.placeholder_parser); // initialize
_gcodegen.layer_count = layer_count;
_gcodegen.enable_cooling_markers = true;
_gcodegen.apply_print_config(config);
auto extruders {print.extruders()};
_gcodegen.set_extruders(extruders.cbegin(), extruders.cend());
}
} // namespace Slic3r
#endif //SLIC3RXS

View File

@ -0,0 +1,80 @@
#ifndef SLIC3RXS
#ifndef slic3r_PrintGCode_hpp
#define slic3r_PrintGCode_hpp
#include "GCode.hpp"
#include "GCode/CoolingBuffer.hpp"
#include "GCode/SpiralVase.hpp"
#include "Geometry.hpp"
#include "Flow.hpp"
#include "ExtrusionEntity.hpp"
#include "libslic3r.h"
#include <string>
#include <iostream>
#include <regex>
namespace Slic3r {
class PrintGCode {
public:
/// Constructor.
PrintGCode(Slic3r::Print& print, std::ostream& _fh);
/// Perform the export. export is a reserved name in C++, so changed to output
void output();
/// Process an individual output for export. Writes to the ostream.
void process_layer(size_t idx, const Layer* layer, const Points& copies);
void flush_filters() { fh << this->filter(this->_cooling_buffer.flush(), true); }
/// Applies various filters, if enabled.
std::string filter(const std::string& in, bool wait = false);
private:
Slic3r::Print& _print;
const Slic3r::PrintConfig& config;
Slic3r::GCode _gcodegen;
const PrintObjectPtrs& objects;
std::ostream& fh;
Slic3r::CoolingBuffer _cooling_buffer;
Slic3r::SpiralVase _spiral_vase;
// Slic3r::VibrationLimit _vibration_limit;
// Slic3r::ArcFitting _arc_fitting;
// Slic3r::PressureRegulator _pressure_regulator;
/// presence in the array indicates that the
std::map<coord_t, bool> _skirt_done {};
bool _brim_done {false};
bool _second_layer_things_done {false};
std::pair<Point, bool> _last_obj_copy {std::pair<Point, bool>(Point(), false)};
bool _autospeed {false};
void _print_first_layer_temperature(bool wait);
void _print_off_temperature(bool wait);
/// Utility function to print config options as gcode comments
void _print_config(const ConfigBase& config);
// Extrude perimeters: Decide where to put seams (hide or align seams).
std::string _extrude_perimeters(std::map<size_t,ExtrusionEntityCollection> &by_region);
// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance.
std::string _extrude_infill(std::map<size_t,ExtrusionEntityCollection> &by_region);
/// regular expression to match heater gcodes
std::regex bed_temp_regex { std::regex("M(?:190|140)", std::regex_constants::icase)};
/// regular expression to match heater gcodes
std::regex ex_temp_regex { std::regex("M(?:109|104)", std::regex_constants::icase)};
};
} // namespace Slic3r
#endif // slic3r_PrintGCode_hpp
#endif // SLIC3RXS

View File

@ -2,6 +2,7 @@
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "Log.hpp"
#include <algorithm>
#include <vector>
@ -170,9 +171,8 @@ PrintObject::clear_layers()
Layer*
PrintObject::add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z)
{
Layer* layer = new Layer(id, this, height, print_z, slice_z);
layers.push_back(layer);
return layer;
layers.emplace_back(new Layer(id, this, height, print_z, slice_z));
return layers.back();
}
void
@ -925,10 +925,10 @@ PrintObject::_slice_region(size_t region_id, std::vector<float> z, bool modifier
// compose mesh
TriangleMesh mesh;
for (std::vector<int>::const_iterator it = region_volumes.begin();
it != region_volumes.end(); ++it) {
for (const auto& i : region_volumes) {
const ModelVolume &volume = *object.volumes[*it];
const ModelVolume &volume = *(object.volumes[i]);
if (volume.modifier != modifier) continue;
mesh.merge(volume.mesh);
@ -952,6 +952,53 @@ PrintObject::_slice_region(size_t region_id, std::vector<float> z, bool modifier
return layers;
}
#ifndef SLIC3RXS
void
PrintObject::make_perimeters()
{
if (this->state.is_done(posPerimeters)) return;
if (this->typed_slices)
this->state.invalidate(posSlice);
this->slice(); // take care of prereqs
this->_make_perimeters();
}
void
PrintObject::slice()
{
auto* print {this->print()};
if (this->state.is_done(posSlice)) return;
this->state.set_started(posSlice);
if (print->status_cb != nullptr) {
print->status_cb(10, "Processing triangulated mesh");
}
this->_slice();
// detect slicing errors
bool warning_thrown = false;
for (size_t i = 0U; i < this->layer_count(); ++i) {
auto* layer {this->get_layer(i)};
if (!layer->slicing_errors) continue;
if (!warning_thrown) {
Slic3r::Log::warn("PrintObject") << "The model has overlapping or self-intersecting facets. "
<< "I tried to repair it, however you might want to check "
<< "the results or repair the input file and retry.\n";
warning_thrown = true;
}
}
if (this->layers.size() == 0) {
Slic3r::Log::error("PrintObject") << "slice(): " << "No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n";
return; // make this throw an exception instead?
}
this->typed_slices = false;
this->state.set_done(posSlice);
}
#endif // SLIC3RXS
void
PrintObject::_make_perimeters()
{
@ -1095,4 +1142,510 @@ PrintObject::_infill()
this->state.set_done(posInfill);
}
#ifndef SLIC3RXS
void
PrintObject::prepare_infill()
{
if (this->state.is_done(posInfill)) return;
// This prepare_infill() is not really idempotent.
// TODO: It should clear and regenerate fill_surfaces at every run
// instead of modifying it in place.
this->state.invalidate(posPerimeters);
this->make_perimeters();
this->state.set_started(posPrepareInfill);
// prerequisites
this->detect_surfaces_type();
if (this->print()->status_cb != nullptr)
this->print()->status_cb(30, "Preparing infill");
// decide what surfaces are to be filled
for (auto& layer : this->layers) {
for (auto& region : layer->regions) {
region->prepare_fill_surfaces();
}
}
// this will detect bridges and reverse bridges
// and rearrange top/bottom/internal surfaces
this->process_external_surfaces();
// detect which fill surfaces are near external layers
// they will be split in internal and internal-solid surfaces
this->discover_horizontal_shells();
this->clip_fill_surfaces();
// the following step needs to be done before combination because it may need
// to remove only half of the combined infill
this->bridge_over_infill();
// combine fill surfaces to honor the "infill every N layers" option
this->combine_infill();
this->state.set_done(posPrepareInfill);
}
void
PrintObject::combine_infill()
{
// Work on each region separately.
for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) {
const PrintRegion *region = this->print()->regions[region_id];
const int every = region->config.infill_every_layers.value;
if (every < 2 || region->config.fill_density == 0.)
continue;
// Limit the number of combined layers to the maximum height allowed by this regions' nozzle.
//FIXME limit the layer height to max_layer_height
double nozzle_diameter = std::min(
this->print()->config.nozzle_diameter.get_at(region->config.infill_extruder.value - 1),
this->print()->config.nozzle_diameter.get_at(region->config.solid_infill_extruder.value - 1));
// define the combinations
std::vector<size_t> combine(this->layers.size(), 0);
{
double current_height = 0.;
size_t num_layers = 0;
for (size_t layer_idx = 0; layer_idx < this->layers.size(); ++ layer_idx) {
const Layer *layer = this->layers[layer_idx];
if (layer->id() == 0)
// Skip first print layer (which may not be first layer in array because of raft).
continue;
// Check whether the combination of this layer with the lower layers' buffer
// would exceed max layer height or max combined layer count.
if (current_height + layer->height >= nozzle_diameter + EPSILON || num_layers >= every) {
// Append combination to lower layer.
combine[layer_idx - 1] = num_layers;
current_height = 0.;
num_layers = 0;
}
current_height += layer->height;
++ num_layers;
}
// Append lower layers (if any) to uppermost layer.
combine[this->layers.size() - 1] = num_layers;
}
// loop through layers to which we have assigned layers to combine
for (size_t layer_idx = 0; layer_idx < this->layers.size(); ++ layer_idx) {
size_t num_layers = combine[layer_idx];
if (num_layers <= 1)
continue;
// Get all the LayerRegion objects to be combined.
std::vector<LayerRegion*> layerms;
layerms.reserve(num_layers);
for (size_t i = layer_idx + 1 - num_layers; i <= layer_idx; ++ i)
layerms.emplace_back(this->layers[i]->regions[region_id]);
// We need to perform a multi-layer intersection, so let's split it in pairs.
// Initialize the intersection with the candidates of the lowest layer.
ExPolygons intersection = to_expolygons(layerms.front()->fill_surfaces.filter_by_type(stInternal));
// Start looping from the second layer and intersect the current intersection with it.
for (size_t i = 1; i < layerms.size(); ++ i)
intersection = intersection_ex(
to_polygons(intersection),
to_polygons(layerms[i]->fill_surfaces.filter_by_type(stInternal)),
false);
double area_threshold = layerms.front()->infill_area_threshold();
if (! intersection.empty() && area_threshold > 0.)
intersection.erase(std::remove_if(intersection.begin(), intersection.end(),
[area_threshold](const ExPolygon &expoly) { return expoly.area() <= area_threshold; }),
intersection.end());
if (intersection.empty())
continue;
// Slic3r::debugf " combining %d %s regions from layers %d-%d\n",
// scalar(@$intersection),
// ($type == S_TYPE_INTERNAL ? 'internal' : 'internal-solid'),
// $layer_idx-($every-1), $layer_idx;
// intersection now contains the regions that can be combined across the full amount of layers,
// so let's remove those areas from all layers.
Polygons intersection_with_clearance;
intersection_with_clearance.reserve(intersection.size());
float clearance_offset =
0.5f * layerms.back()->flow(frPerimeter).scaled_width() +
// Because fill areas for rectilinear and honeycomb are grown
// later to overlap perimeters, we need to counteract that too.
((region->config.fill_pattern == ipRectilinear ||
region->config.fill_pattern == ipGrid ||
region->config.fill_pattern == ipHoneycomb) ? 1.5f : 0.5f) *
layerms.back()->flow(frSolidInfill).scaled_width();
for (ExPolygon &expoly : intersection)
polygons_append(intersection_with_clearance, offset(expoly, clearance_offset));
for (LayerRegion *layerm : layerms) {
Polygons internal = to_polygons(layerm->fill_surfaces.filter_by_type(stInternal));
layerm->fill_surfaces.remove_type(stInternal);
layerm->fill_surfaces.append(diff_ex(internal, intersection_with_clearance, false), stInternal);
if (layerm == layerms.back()) {
// Apply surfaces back with adjusted depth to the uppermost layer.
Surface templ(stInternal, ExPolygon());
templ.thickness = 0.;
for (LayerRegion *layerm2 : layerms)
templ.thickness += layerm2->layer()->height;
templ.thickness_layers = (unsigned short)layerms.size();
layerm->fill_surfaces.append(intersection, templ);
} else {
// Save void surfaces.
layerm->fill_surfaces.append(
intersection_ex(internal, intersection_with_clearance, false),
stInternalVoid);
}
}
}
}
}
void
PrintObject::infill()
{
this->prepare_infill();
this->_infill();
}
#endif //SLIC3RXS
SupportMaterial *
PrintObject::_support_material()
{
// TODO what does this line do //= FLOW_ROLE_SUPPORT_MATERIAL;
Flow first_layer_flow = Flow::new_from_config_width(
frSupportMaterial,
print()->config
.first_layer_extrusion_width, // check why this line is put || config.support_material_extrusion_width,
static_cast<float>(print()->config.nozzle_diameter.get_at(static_cast<size_t>(
config.support_material_extruder
- 1))), // Check why this is put in perl "// $self->print->config->nozzle_diameter->[0]"
static_cast<float>(config.get_abs_value("first_layer_height")),
0 // No bridge flow ratio.
);
return new SupportMaterial(
&print()->config,
&config,
first_layer_flow,
_support_material_flow(),
_support_material_flow(frSupportMaterialInterface)
);
}
Flow
PrintObject::_support_material_flow(FlowRole role)
{
// Create support flow.
int extruder =
(role == frSupportMaterial) ?
config.support_material_extruder.value : config
.support_material_interface_extruder.value;
auto width = config.support_material_extrusion_width; // || config.extrusion_width;
if (role == frSupportMaterialInterface)
width = config.support_material_interface_extrusion_width; // || width;
// We use a bogus layer_height because we use the same flow for all
// support material layers.
Flow support_flow = Flow::new_from_config_width(
role,
width,
static_cast<float>(print()->config.nozzle_diameter
.get_at(static_cast<size_t>(extruder - 1))), // Check this line $self->print->config->nozzle_diameter->[0].
static_cast<float>(config.layer_height.value),
0
);
return support_flow;
}
#ifndef SLIC3RXS
void
PrintObject::generate_support_material()
{
auto* print { this->_print };
const auto& config { this->config };
//prereqs
this->slice();
if (this->state.is_done(posSupportMaterial)) { return; }
this->state.set_started(posSupportMaterial);
this->clear_support_layers();
if ((!config.support_material
&& config.raft_layers == 0
&& config.support_material_enforce_layers == 0)
|| this->layers.size() < 2
) {
this->state.set_done(posSupportMaterial);
return;
}
if (print->status_cb != nullptr)
print->status_cb(85, "Generating support material");
this->_support_material()->generate(this);
this->state.set_done(posSupportMaterial);
std::stringstream stats {""};
if (print->status_cb != nullptr)
print->status_cb(85, stats.str().c_str());
}
void
PrintObject::discover_horizontal_shells()
{
auto* print {this->print()};
for (size_t region_id = 0U; region_id < print->regions.size(); ++region_id) {
for (size_t i = 0; i < this->layer_count(); ++i) {
auto* layerm {this->get_layer(i)->regions.at(region_id)};
const auto& region_config {layerm->region()->config};
if (region_config.solid_infill_every_layers() > 0 && region_config.fill_density() > 0
&& (i % region_config.solid_infill_every_layers()) == 0) {
const auto type {region_config.fill_density() == 100 ? stInternalSolid : stInternalBridge };
// set the surface type to internal for the types
std::for_each(layerm->fill_surfaces.begin(), layerm->fill_surfaces.end(), [type] (Surface& s) { s.surface_type = (s.surface_type == type ? stInternal : s.surface_type); });
}
this->_discover_external_horizontal_shells(layerm, i, region_id);
}
}
}
void
PrintObject::_discover_external_horizontal_shells(LayerRegion* layerm, const size_t& i, const size_t& region_id)
{
auto* print {this->print()};
const auto& region_config {layerm->region()->config};
for (auto& type : { stTop, stBottom, stBottomBridge }) {
// find slices of current type for current layer
// use slices instead of fill_surfaces because they also include the perimeter area
// which needs to be propagated in shells; we need to grow slices like we did for
// fill_surfaces though. Using both ungrown slices and grown fill_surfaces will
// not work in some situations, as there won't be any grown region in the perimeter
// area (this was seen in a model where the top layer had one extra perimeter, thus
// its fill_surfaces were thinner than the lower layer's infill), however it's the best
// solution so far. Growing the external slices by EXTERNAL_INFILL_MARGIN will put
// too much solid infill inside nearly-vertical slopes.
Polygons solid;
auto tmp {layerm->slices.filter_by_type(type)};
polygons_append(solid, tmp);
tmp.clear();
tmp = layerm->fill_surfaces.filter_by_type(type);
polygons_append(solid, tmp);
if (solid.size() == 0) continue;
auto solid_layers { type == stTop ? region_config.top_solid_layers() : region_config.bottom_solid_layers() };
if (region_config.min_top_bottom_shell_thickness() > 0) {
auto current_shell_thick { static_cast<coordf_t>(solid_layers) * this->get_layer(i)->height };
const auto& min_shell_thick { region_config.min_top_bottom_shell_thickness() };
while (std::abs(min_shell_thick - current_shell_thick) > Slic3r::Geometry::epsilon) {
solid_layers++;
current_shell_thick = static_cast<coordf_t>(solid_layers) * this->get_layer(i)->height;
}
}
_discover_neighbor_horizontal_shells(layerm, i, region_id, type, solid, solid_layers);
}
}
void
PrintObject::_discover_neighbor_horizontal_shells(LayerRegion* layerm, const size_t& i, const size_t& region_id, const SurfaceType& type, Polygons& solid, const size_t& solid_layers)
{
auto* print {this->print()};
const auto& region_config {layerm->region()->config};
for (int n = (type == stTop ? i-1 : i+1); std::abs(n-int(i)) < solid_layers; (type == stTop ? n-- : n++)) {
if (n < 0 || n >= this->layer_count()) continue;
auto* neighbor_layerm { this->get_layer(n)->regions.at(region_id) };
// make a copy so we can use them even after clearing the original collection
auto neighbor_fill_surfaces{ SurfaceCollection(neighbor_layerm->fill_surfaces) };
// find intersection between neighbor and current layer's surfaces
// intersections have contours and holes
Polygons filtered_poly;
polygons_append(filtered_poly, neighbor_fill_surfaces.filter_by_type({stInternal, stInternalSolid}));
auto new_internal_solid { intersection(solid, filtered_poly , 1 ) };
if (new_internal_solid.size() == 0) {
// 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(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.
return;
} else {
// If we have internal infill, we can generate internal solid shells freely.
continue;
}
}
if (region_config.fill_density == 0) {
// if we're printing a hollow object we discard any solid shell thinner
// than a perimeter width, since it's probably just crossing a sloping wall
// and it's not wanted in a hollow print even if it would make sense when
// obeying the solid shell count option strictly (DWIM!)
auto margin { neighbor_layerm->flow(frExternalPerimeter).scaled_width()};
auto too_narrow { diff(new_internal_solid, offset2(new_internal_solid, -margin, +margin, CLIPPER_OFFSET_SCALE, ClipperLib::jtMiter, 5), 1)};
if (too_narrow.size() > 0)
new_internal_solid = solid = diff(new_internal_solid, too_narrow);
}
// make sure the new internal solid is wide enough, as it might get collapsed
// when spacing is added in Slic3r::Fill
{
auto margin {3 * layerm->flow(frSolidInfill).scaled_width()};
// we use a higher miterLimit here to handle areas with acute angles
// in those cases, the default miterLimit would cut the corner and we'd
// get a triangle in $too_narrow; if we grow it below then the shell
// would have a different shape from the external surface and we'd still
// have the same angle, so the next shell would be grown even more and so on.
auto too_narrow { diff(new_internal_solid, offset2(new_internal_solid, -margin, +margin, CLIPPER_OFFSET_SCALE, ClipperLib::jtMiter, 5), 1) };
if (too_narrow.size() > 0) {
// grow the collapsing parts and add the extra area to the neighbor layer
// as well as to our original surfaces so that we support this
// additional area in the next shell too
// make sure our grown surfaces don't exceed the fill area
Polygons tmp_internal;
for (auto& s : neighbor_fill_surfaces) {
if (s.is_internal() && !s.is_bridge()) tmp_internal.emplace_back(Polygon(s.expolygon));
}
auto grown {intersection(
offset(too_narrow, +margin),
// Discard bridges as they are grown for anchoring and we cant
// remove such anchors. (This may happen when a bridge is being
// anchored onto a wall where little space remains after the bridge
// is grown, and that little space is an internal solid shell so
// it triggers this too_narrow logic.)
tmp_internal)
};
new_internal_solid = solid = diff(new_internal_solid, too_narrow);
}
}
// internal-solid are the union of the existing internal-solid surfaces
// and new ones
Polygons tmp_internal { to_polygons(neighbor_fill_surfaces.filter_by_type(stInternalSolid)) };
polygons_append(tmp_internal, neighbor_fill_surfaces.surfaces);
auto internal_solid {union_ex(tmp_internal)};
// subtract intersections from layer surfaces to get resulting internal surfaces
tmp_internal = to_polygons(neighbor_fill_surfaces.filter_by_type(stInternal));
auto internal { diff_ex(tmp_internal, to_polygons(internal_solid), 1) };
// assign resulting internal surfaces to layer
neighbor_fill_surfaces.clear();
for (const auto& poly : internal) {
neighbor_fill_surfaces.surfaces.emplace_back(Surface(stInternal, poly));
}
// assign new internal-solid surfaces to layer
for (const auto& poly : internal_solid) {
neighbor_fill_surfaces.surfaces.emplace_back(Surface(stInternalSolid, poly));
}
// assign top and bottom surfaces to layer
SurfaceCollection tmp_collection;
for (auto& s : tmp_collection) {
Polygons pp;
append_to(pp, (Polygons)s);
ExPolygons both_solids;
both_solids.reserve(internal_solid.size() + internal.size());
both_solids.insert(both_solids.end(), internal_solid.begin(), internal_solid.end());
both_solids.insert(both_solids.end(), internal.begin(), internal.end());
auto solid_surfaces { diff_ex(pp, to_polygons(both_solids), 1) };
for (auto exp : solid_surfaces)
neighbor_fill_surfaces.surfaces.emplace_back(Surface(s.surface_type, exp));
}
}
}
void
PrintObject::clip_fill_surfaces()
{
if (! this->config.infill_only_where_needed.value ||
! std::any_of(this->print()->regions.begin(), this->print()->regions.end(),
[](const PrintRegion *region) { return region->config.fill_density > 0; }))
return;
// We only want infill under ceilings; this is almost like an
// internal support material.
// Proceed top-down, skipping the bottom layer.
Polygons upper_internal;
for (int layer_id = int(this->layers.size()) - 1; layer_id > 0; -- layer_id) {
Layer *layer = this->layers[layer_id];
Layer *lower_layer = this->layers[layer_id - 1];
// Detect things that we need to support.
// Cummulative slices.
Polygons slices;
for (const ExPolygon &expoly : layer->slices.expolygons)
polygons_append(slices, to_polygons(expoly));
// Cummulative fill surfaces.
Polygons fill_surfaces;
// Solid surfaces to be supported.
Polygons overhangs;
for (const LayerRegion *layerm : layer->regions)
for (const Surface &surface : layerm->fill_surfaces.surfaces) {
Polygons polygons = to_polygons(surface.expolygon);
if (surface.is_solid())
polygons_append(overhangs, polygons);
polygons_append(fill_surfaces, std::move(polygons));
}
Polygons lower_layer_fill_surfaces;
Polygons lower_layer_internal_surfaces;
for (const LayerRegion *layerm : lower_layer->regions)
for (const Surface &surface : layerm->fill_surfaces.surfaces) {
Polygons polygons = to_polygons(surface.expolygon);
if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
polygons_append(lower_layer_internal_surfaces, polygons);
polygons_append(lower_layer_fill_surfaces, std::move(polygons));
}
// We also need to support perimeters when there's at least one full unsupported loop
{
// Get perimeters area as the difference between slices and fill_surfaces
// Only consider the area that is not supported by lower perimeters
Polygons perimeters = intersection(diff(slices, fill_surfaces), lower_layer_fill_surfaces);
// Only consider perimeter areas that are at least one extrusion width thick.
//FIXME Offset2 eats out from both sides, while the perimeters are create outside in.
//Should the pw not be half of the current value?
float pw = FLT_MAX;
for (const LayerRegion *layerm : layer->regions)
pw = std::min<float>(pw, layerm->flow(frPerimeter).scaled_width());
// Append such thick perimeters to the areas that need support
polygons_append(overhangs, offset2(perimeters, -pw, +pw));
}
// Find new internal infill.
polygons_append(overhangs, std::move(upper_internal));
upper_internal = intersection(overhangs, lower_layer_internal_surfaces);
// Apply new internal infill to regions.
for (LayerRegion *layerm : lower_layer->regions) {
if (layerm->region()->config.fill_density.value == 0)
continue;
Polygons internal;
for (Surface &surface : layerm->fill_surfaces.surfaces)
if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
polygons_append(internal, std::move(surface.expolygon));
layerm->fill_surfaces.remove_types({ stInternal, stInternalVoid });
layerm->fill_surfaces.append(intersection_ex(internal, upper_internal, true), stInternal);
layerm->fill_surfaces.append(diff_ex (internal, upper_internal, true), stInternalVoid);
// If there are voids it means that our internal infill is not adjacent to
// perimeters. In this case it would be nice to add a loop around infill to
// make it more robust and nicer. TODO.
}
}
}
#endif // SLIC3RXS
}

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,164 @@
#ifndef slic3r_SupportMaterial_hpp_
#define slic3r_SupportMaterial_hpp_
namespace Slic3r {
#include <numeric>
#include <vector>
#include <iostream>
#include <algorithm>
#include "libslic3r.h"
#include "PrintConfig.hpp"
#include "Flow.hpp"
#include "Layer.hpp"
#include "Geometry.hpp"
#include "Print.hpp"
#include "ClipperUtils.hpp"
#include "ExPolygon.hpp"
#include "SVG.hpp"
#include <libslic3r/Fill/Fill.hpp>
using namespace std;
namespace Slic3r
{
// how much we extend support around the actual contact area
constexpr coordf_t SUPPORT_MATERIAL_MARGIN = 1.5;
constexpr coordf_t MARGIN_STEP = SUPPORT_MATERIAL_MARGIN / 3;
constexpr coordf_t PILLAR_SIZE = 2.5;
constexpr coordf_t PILLAR_SPACING = 10;
/// Struct for carrying the toolpaths parameters needed for each thread.
struct toolpaths_params
{
int contact_loops;
coordf_t circle_radius;
coordf_t circle_distance;
Polygon circle;
SupportMaterialPattern pattern;
vector<int> angles;
double interface_angle{};
double interface_spacing{};
float interface_density{};
double support_spacing{};
double support_density{};
toolpaths_params(int contact_loops = 0,
coordf_t circle_radius = 0,
coordf_t circle_distance = 0,
const Polygon &circle = Polygon(),
const SupportMaterialPattern &pattern = SupportMaterialPattern(),
const vector<int> &angles = vector<int>())
: contact_loops(contact_loops),
circle_radius(circle_radius),
circle_distance(circle_distance),
circle(circle),
pattern(pattern),
angles(angles)
{}
};
class SupportMaterial
{
public:
friend PrintObject;
PrintConfig *config; ///< The print config
PrintObjectConfig *object_config; ///< The object print config.
Flow flow; ///< The intermediate layers print flow.
Flow first_layer_flow; ///< The first (base) layers print flow.
Flow interface_flow; ///< The interface layers print flow.
/// Generate the extrusions paths for the support matterial generated for the given print object.
void generate_toolpaths(PrintObject *object,
map<coordf_t, Polygons> overhang,
map<coordf_t, Polygons> contact,
map<int, Polygons> interface,
map<int, Polygons> base);
/// Generate support material for the given print object.
void generate(PrintObject *object);
/// Generate the support layers slicing z coordinates.
vector<coordf_t> support_layers_z(vector<coordf_t> contact_z,
vector<coordf_t> top_z,
coordf_t max_object_layer_height);
pair<map<coordf_t, Polygons>, map<coordf_t, Polygons>> contact_area(PrintObject *object);
map<coordf_t, Polygons> object_top(PrintObject *object, map<coordf_t, Polygons> *contact);
void generate_pillars_shape(const map<coordf_t, Polygons> &contact,
const vector<coordf_t> &support_z,
map<int, Polygons> &shape);
map<int, Polygons> generate_base_layers(vector<coordf_t> support_z,
map<coordf_t, Polygons> contact,
map<int, Polygons> interface,
map<coordf_t, Polygons> top);
map<int, Polygons> generate_interface_layers(vector<coordf_t> support_z,
map<coordf_t, Polygons> contact,
map<coordf_t, Polygons> top);
void generate_bottom_interface_layers(const vector<coordf_t> &support_z,
map<int, Polygons> &base,
map<coordf_t, Polygons> &top,
map<int, Polygons> &interface);
coordf_t contact_distance(coordf_t layer_height, coordf_t nozzle_diameter);
/// This method returns the indices of the layers overlapping with the given one.
vector<int> overlapping_layers(int layer_idx, const vector<coordf_t> &support_z);
void clip_with_shape(map<int, Polygons> &support, map<int, Polygons> &shape);
// This method removes object silhouette from support material
// (it's used with interface and base only). It removes a bit more,
// leaving a thin gap between object and support in the XY plane.
void clip_with_object(map<int, Polygons> &support, vector<coordf_t> support_z, PrintObject &object);
void process_layer(int layer_id, toolpaths_params params);
private:
/// SupportMaterial is generated by PrintObject.
SupportMaterial(PrintConfig *print_config,
PrintObjectConfig *print_object_config,
Flow flow,
Flow first_layer_flow,
Flow interface_flow)
: config(print_config),
object_config(print_object_config),
flow(Flow(0, 0, 0)),
first_layer_flow(Flow(0, 0, 0)),
interface_flow(Flow(0, 0, 0)),
object(nullptr)
{}
// Get the maximum layer height given a print object.
coordf_t get_max_layer_height(PrintObject *object);
// (Deprecated) use append_to instead
void append_polygons(Polygons &dst, Polygons &src);
// Return polygon vector given a vector of surfaces.
Polygons p(SurfacesPtr &surfaces);
vector<coordf_t> get_keys_sorted(map<coordf_t, Polygons> _map);
Polygon create_circle(coordf_t radius);
// Used during generate_toolpaths function.
PrintObject *object;
map<coordf_t, Polygons> overhang;
map<coordf_t, Polygons> contact;
map<int, Polygons> interface;
map<int, Polygons> base;
};
}
#endif

View File

@ -6,7 +6,17 @@
namespace Slic3r {
enum SurfaceType { stTop, stBottom, stBottomBridge, stInternal, stInternalSolid, stInternalBridge, stInternalVoid };
/// Surface type enumerations.
/// As it is very unlikely that there will be more than 32 or 64 of these surface types, pack into a flag
enum SurfaceType {
stTop = 0b1,
stBottom = 0b10,
stBottomBridge = 0b100,
stInternal = 0b1000,
stInternalSolid = 0b10000,
stInternalBridge = 0b100000,
stInternalVoid = 0b1000000
};
class Surface
{
@ -149,6 +159,15 @@ inline void polygons_append(Polygons &dst, SurfacesPtr &&src)
}
}
inline bool surfaces_could_merge(const Surface &s1, const Surface &s2)
{
return
s1.surface_type == s2.surface_type &&
s1.thickness == s2.thickness &&
s1.thickness_layers == s2.thickness_layers &&
s1.bridge_angle == s2.bridge_angle;
}
} // namespace Slic3r
#endif

View File

@ -100,6 +100,19 @@ SurfaceCollection::filter_by_type(SurfaceType type)
return ss;
}
SurfacesPtr
SurfaceCollection::filter_by_type(std::initializer_list<SurfaceType> types)
{
size_t n {0};
SurfacesPtr ss;
for (const auto& t : types) {
n |= t;
}
for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) {
if (surface->surface_type & n == surface->surface_type) ss.push_back(&*surface);
}
return ss;
}
void
SurfaceCollection::filter_by_type(SurfaceType type, Polygons* polygons)
{
@ -147,5 +160,75 @@ SurfaceCollection::polygons_count() const
count += 1 + it->expolygon.holes.size();
return count;
}
void
SurfaceCollection::remove_type(const SurfaceType type)
{
// Use stl remove_if to remove
auto ptr = std::remove_if(surfaces.begin(), surfaces.end(),[type] (Surface& s) { return s.surface_type == type; });
surfaces.erase(ptr, surfaces.cend());
}
void
SurfaceCollection::remove_types(const SurfaceType *types, size_t ntypes)
{
for (size_t i = 0; i < ntypes; ++i)
this->remove_type(types[i]);
}
void
SurfaceCollection::remove_types(std::initializer_list<SurfaceType> types) {
for (const auto& t : types) {
this->remove_type(t);
}
}
void
SurfaceCollection::keep_type(const SurfaceType type)
{
// Use stl remove_if to remove
auto ptr = std::remove_if(surfaces.begin(), surfaces.end(),[type] (const Surface& s) { return s.surface_type != type; });
surfaces.erase(ptr, surfaces.cend());
}
void
SurfaceCollection::keep_types(const SurfaceType *types, size_t ntypes)
{
size_t n {0};
for (size_t i = 0; i < ntypes; ++i)
n |= types[i]; // form bitmask.
// Use stl remove_if to remove
auto ptr = std::remove_if(surfaces.begin(), surfaces.end(),[n] (const Surface& s) { return s.surface_type & n != s.surface_type; });
surfaces.erase(ptr, surfaces.cend());
}
void
SurfaceCollection::keep_types(std::initializer_list<SurfaceType> types) {
for (const auto& t : types) {
this->keep_type(t);
}
}
/* group surfaces by common properties */
void
SurfaceCollection::group(std::vector<SurfacesPtr> *retval)
{
for (Surfaces::iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++it) {
// find a group with the same properties
SurfacesPtr* group = NULL;
for (std::vector<SurfacesPtr>::iterator git = retval->begin(); git != retval->end(); ++git)
if (! git->empty() && surfaces_could_merge(*git->front(), *it)) {
group = &*git;
break;
}
// if no group with these properties exists, add one
if (group == NULL) {
retval->resize(retval->size() + 1);
group = &retval->back();
}
// append surface to group
group->push_back(&*it);
}
}
} // namespace Slic3r

View File

@ -22,8 +22,32 @@ class SurfaceCollection
template <class T> bool any_internal_contains(const T &item) const;
template <class T> bool any_bottom_contains(const T &item) const;
SurfacesPtr filter_by_type(SurfaceType type);
SurfacesPtr filter_by_type(std::initializer_list<SurfaceType> types);
void filter_by_type(SurfaceType type, Polygons* polygons);
/// deletes all surfaces that match the supplied type.
void remove_type(const SurfaceType type);
void remove_types(std::initializer_list<SurfaceType> types);
template<int N>
void remove_types(std::array<SurfaceType, N> types) {
remove_types(types.data(), types.size());
}
/// group surfaces by common properties
void group(std::vector<SurfacesPtr> *retval);
/// Deletes every surface other than the ones that match the provided type.
void keep_type(const SurfaceType type);
/// Deletes every surface other than the ones that match the provided types.
void keep_types(std::initializer_list<SurfaceType> types);
/// Deletes every surface other than the ones that match the provided types.
void keep_types(const SurfaceType *types, size_t ntypes);
/// deletes all surfaces that match the supplied aggregate of types.
void remove_types(const SurfaceType *types, size_t ntypes);
void set(const SurfaceCollection &coll) { surfaces = coll.surfaces; }
void set(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); }
void set(const ExPolygons &src, SurfaceType surfaceType) { clear(); this->append(src, surfaceType); }
@ -42,6 +66,10 @@ class SurfaceCollection
size_t size() const { return this->surfaces.size(); };
void clear() { this->surfaces.clear(); };
void erase(size_t i) { this->surfaces.erase(this->surfaces.begin() + i); };
Surfaces::iterator begin() { return this->surfaces.begin();}
Surfaces::iterator end() { return this->surfaces.end();}
Surfaces::const_iterator cbegin() const { return this->surfaces.cbegin();}
Surfaces::const_iterator cend() const { return this->surfaces.cend();}
};
}

View File

@ -28,7 +28,7 @@ TriangleMesh::TriangleMesh()
stl_initialize(&this->stl);
}
TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Point3>& facets )
TriangleMesh::TriangleMesh(const Pointf3* points, const Point3* facets, size_t n_facets)
: repaired(false)
{
stl_initialize(&this->stl);
@ -37,7 +37,7 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Point3>& fa
stl.stats.type = inmemory;
// count facets and allocate memory
stl.stats.number_of_facets = facets.size();
stl.stats.number_of_facets = n_facets;
stl.stats.original_num_facets = stl.stats.number_of_facets;
stl_allocate(&stl);
@ -73,6 +73,20 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Point3>& fa
TriangleMesh::TriangleMesh(const TriangleMesh &other)
: stl(other.stl), repaired(other.repaired)
{
this->clone(other);
}
TriangleMesh& TriangleMesh::operator= (const TriangleMesh& other)
{
this->stl = other.stl;
this->repaired = other.repaired;
this->clone(other);
return *this;
}
void TriangleMesh::clone(const TriangleMesh& other) {
this->stl.heads = NULL;
this->stl.tail = NULL;
this->stl.error = other.stl.error;
@ -93,11 +107,23 @@ TriangleMesh::TriangleMesh(const TriangleMesh &other)
std::copy(other.stl.v_shared, other.stl.v_shared + other.stl.stats.shared_vertices, this->stl.v_shared);
}
}
TriangleMesh& TriangleMesh::operator= (TriangleMesh other)
#ifndef SLIC3RXS
TriangleMesh::TriangleMesh(TriangleMesh&& other) {
this->repaired = std::move(other.repaired);
this->stl = std::move(other.stl);
stl_initialize(&other.stl);
}
TriangleMesh& TriangleMesh::operator= (TriangleMesh&& other)
{
this->swap(other);
this->repaired = std::move(other.repaired);
this->stl = std::move(other.stl);
stl_initialize(&other.stl);
return *this;
}
#endif
void
TriangleMesh::swap(TriangleMesh &other)
@ -388,7 +414,7 @@ Pointf3s TriangleMesh::vertices()
} else {
Slic3r::Log::warn("TriangleMesh", "vertices() requires repair()");
}
return std::move(tmp);
return tmp;
}
Point3s TriangleMesh::facets()
@ -404,7 +430,7 @@ Point3s TriangleMesh::facets()
} else {
Slic3r::Log::warn("TriangleMesh", "facets() requires repair()");
}
return std::move(tmp);
return tmp;
}
Pointf3s TriangleMesh::normals() const
@ -424,7 +450,7 @@ Pointf3s TriangleMesh::normals() const
Pointf3 TriangleMesh::size() const
{
const auto& sz {stl.stats.size};
return std::move(Pointf3(sz.x, sz.y, sz.z));
return Pointf3(sz.x, sz.y, sz.z);
}
@ -444,7 +470,7 @@ TriangleMesh::slice(const std::vector<double>& z)
mslicer.slice(z_f, &layers);
return std::move(layers);
return layers;
}
mesh_stats
@ -460,13 +486,13 @@ TriangleMesh::stats() const {
tmp_stats.facets_reversed = this->stl.stats.facets_reversed;
tmp_stats.backwards_edges = this->stl.stats.backwards_edges;
tmp_stats.normals_fixed = this->stl.stats.normals_fixed;
return std::move(tmp_stats);
return tmp_stats;
}
BoundingBoxf3 TriangleMesh::bb3() const {
Pointf3 min(this->stl.stats.min.x, this->stl.stats.min.y, this->stl.stats.min.z);
Pointf3 max(this->stl.stats.max.x, this->stl.stats.max.y, this->stl.stats.max.z);
return std::move(BoundingBoxf3(min, max));
return BoundingBoxf3(min, max);
}
@ -736,7 +762,7 @@ TriangleMesh::make_cube(double x, double y, double z) {
TriangleMesh mesh(vertices ,facets);
mesh.repair();
return std::move(mesh);
return mesh;
}
// Generate the mesh for a cylinder and return it, using
@ -782,7 +808,7 @@ TriangleMesh::make_cylinder(double r, double h, double fa) {
TriangleMesh mesh(vertices, facets);
mesh.repair();
return std::move(mesh);
return mesh;
}
// Generates mesh for a sphere centered about the origin, using the generated angle
@ -864,7 +890,7 @@ TriangleMesh::make_sphere(double rho, double fa) {
id++;
TriangleMesh mesh(vertices, facets);
mesh.repair();
return std::move(mesh);
return mesh;
}
template <Axis A>

View File

@ -36,9 +36,22 @@ class TriangleMesh
{
public:
TriangleMesh();
TriangleMesh(const Pointf3s &points, const std::vector<Point3> &facets);
/// Templated constructor to adapt containers that offer .data() and .size()
/// First argument is a container (either vector or array) of Pointf3 for the vertex data.
/// Second argument is container of facets (currently Point3).
template <typename Vertex_Cont, typename Facet_Cont>
TriangleMesh(const Vertex_Cont& vertices, const Facet_Cont& facets) : TriangleMesh(vertices.data(), facets.data(), facets.size()) {}
TriangleMesh(const TriangleMesh &other);
TriangleMesh& operator= (TriangleMesh other);
/// copy assignment
TriangleMesh& operator= (const TriangleMesh& other);
#ifndef SLIC3RXS
/// Move assignment
TriangleMesh& operator= (TriangleMesh&& other);
TriangleMesh(TriangleMesh&& other);
#endif
void swap(TriangleMesh &other);
~TriangleMesh();
void ReadSTLFile(const std::string &input_file);
@ -137,6 +150,15 @@ class TriangleMesh
bool repaired;
private:
/// Private constructor that is called from the public sphere.
/// It doesn't do any bounds checking on points and operates on raw pointers, so we hide it.
/// Other constructors can call this one!
TriangleMesh(const Pointf3* points, const Point3* facets, size_t n_facets);
/// Perform the mechanics of a stl copy
void clone(const TriangleMesh& other);
friend class TriangleMeshSlicer<X>;
friend class TriangleMeshSlicer<Y>;
friend class TriangleMeshSlicer<Z>;

View File

@ -38,6 +38,15 @@ namespace Slic3r {
constexpr auto SLIC3R_VERSION = "1.3.1-dev";
#ifndef SLIC3RXS
#ifndef SLIC3R_BUILD_COMMIT
#define SLIC3R_BUILD_COMMIT (Unknown revision)
#endif
#define VER1_(x) #x
#define VER_(x) VER1_(x)
#define BUILD_COMMIT VER_(SLIC3R_BUILD_COMMIT)
#endif
typedef long coord_t;
typedef double coordf_t;

View File

@ -1,9 +1,9 @@
#include "utils.hpp"
#include <regex>
#ifndef NO_PERL
#include <xsinit.h>
#include <xsinit.h>
#else
#include "Log.hpp"
#include "Log.hpp"
#endif
#include <cstdarg>
@ -50,3 +50,13 @@ split_at_regex(const std::string& input, const std::string& regex) {
last;
return {first, last};
}
std::string _trim_zeroes(std::string in) { return trim_zeroes(in); }
/// Remove extra zeroes generated from std::to_string on doubles
std::string trim_zeroes(std::string in) {
std::string result {""};
std::regex strip_zeroes("(0*)$");
std::regex_replace (std::back_inserter(result), in.begin(), in.end(), strip_zeroes, "");
if (result.back() == '.') result.append("0");
return result;
}

View File

@ -10,4 +10,7 @@
std::vector<std::string>
split_at_regex(const std::string& input, const std::string& regex);
std::string trim_zeroes(std::string in);
std::string _trim_zeroes(std::string in);
#endif // UTILS_HPP