diff options
author | Enrico Turri <enricoturri@seznam.cz> | 2017-12-11 14:01:30 +0300 |
---|---|---|
committer | Enrico Turri <enricoturri@seznam.cz> | 2017-12-11 14:01:30 +0300 |
commit | 50a45949d1a2878e114cd5e8728275c2586a60e2 (patch) | |
tree | 3f7fa3dbd70d5b6b2bac0b6b3c5226a0fa775810 | |
parent | bea9628be08d9c2be9ba49c7538112d39b1d6fa8 (diff) | |
parent | 19388285205ff46379ce0f9b0291aff1badd6568 (diff) |
merge with master
-rw-r--r-- | Build.PL | 2 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | lib/Slic3r.pm | 26 | ||||
-rw-r--r-- | lib/Slic3r/Config.pm | 300 | ||||
-rw-r--r-- | lib/Slic3r/GUI.pm | 251 | ||||
-rw-r--r-- | lib/Slic3r/GUI/3DScene.pm | 2 | ||||
-rw-r--r-- | lib/Slic3r/GUI/AboutDialog.pm | 2 | ||||
-rw-r--r-- | lib/Slic3r/GUI/ConfigWizard.pm | 93 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Controller.pm | 58 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Controller/ManualControlDialog.pm | 2 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Controller/PrinterPanel.pm | 28 | ||||
-rw-r--r-- | lib/Slic3r/GUI/MainFrame.pm | 393 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Notifier.pm | 2 | ||||
-rw-r--r-- | lib/Slic3r/GUI/OptionsGroup/Field.pm | 4 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Plater.pm | 452 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Plater/2D.pm | 7 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Plater/3D.pm | 11 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm | 28 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm | 4 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Preferences.pm | 31 | ||||
-rw-r--r-- | lib/Slic3r/GUI/Tab.pm | 915 | ||||
-rw-r--r-- | lib/Slic3r/Print.pm | 19 | ||||
-rw-r--r-- | lib/Slic3r/Print/Object.pm | 3 | ||||
-rw-r--r-- | resources/icons/Slic3r-console.ico (renamed from var/Slic3r-console.ico) | bin | 17542 -> 17542 bytes | |||
-rw-r--r-- | resources/icons/Slic3r.icns (renamed from var/Slic3r.icns) | bin | 42370 -> 42370 bytes | |||
-rw-r--r-- | resources/icons/Slic3r.ico (renamed from var/Slic3r.ico) | bin | 102134 -> 102134 bytes | |||
-rw-r--r-- | resources/icons/Slic3r.png (renamed from var/Slic3r.png) | bin | 16469 -> 16469 bytes | |||
-rw-r--r-- | resources/icons/Slic3r_128px.png (renamed from var/Slic3r_128px.png) | bin | 13016 -> 13016 bytes | |||
-rw-r--r-- | resources/icons/Slic3r_192px.png (renamed from var/Slic3r_192px.png) | bin | 21962 -> 21962 bytes | |||
-rw-r--r-- | resources/icons/Slic3r_192px_transparent.png (renamed from var/Slic3r_192px_transparent.png) | bin | 21125 -> 21125 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/add.png (renamed from var/add.png) | bin | 873 -> 873 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/application_view_tile.png (renamed from var/application_view_tile.png) | bin | 465 -> 465 bytes | |||
-rw-r--r-- | resources/icons/arrow_down.png (renamed from var/arrow_down.png) | bin | 379 -> 379 bytes | |||
-rw-r--r-- | resources/icons/arrow_left.png (renamed from var/arrow_left.png) | bin | 345 -> 345 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/arrow_out.png (renamed from var/arrow_out.png) | bin | 715 -> 715 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/arrow_refresh.png (renamed from var/arrow_refresh.png) | bin | 685 -> 685 bytes | |||
-rw-r--r-- | resources/icons/arrow_right.png (renamed from var/arrow_right.png) | bin | 349 -> 349 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/arrow_rotate_anticlockwise.png (renamed from var/arrow_rotate_anticlockwise.png) | bin | 716 -> 716 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/arrow_rotate_clockwise.png (renamed from var/arrow_rotate_clockwise.png) | bin | 706 -> 706 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/arrow_up.png (renamed from var/arrow_up.png) | bin | 503 -> 503 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/box.png (renamed from var/box.png) | bin | 968 -> 968 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/brick.png (renamed from var/brick.png) | bin | 525 -> 525 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/brick_add.png (renamed from var/brick_add.png) | bin | 835 -> 835 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/brick_delete.png (renamed from var/brick_delete.png) | bin | 865 -> 865 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/brick_go.png (renamed from var/brick_go.png) | bin | 915 -> 915 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/bricks.png (renamed from var/bricks.png) | bin | 960 -> 960 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/building.png (renamed from var/building.png) | bin | 728 -> 728 bytes | |||
-rw-r--r-- | resources/icons/bullet_arrow_down.png (renamed from var/bullet_arrow_down.png) | bin | 201 -> 201 bytes | |||
-rw-r--r-- | resources/icons/bullet_arrow_up.png (renamed from var/bullet_arrow_up.png) | bin | 201 -> 201 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/bullet_black.png (renamed from var/bullet_black.png) | bin | 287 -> 287 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/bullet_blue.png (renamed from var/bullet_blue.png) | bin | 289 -> 289 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/bullet_green.png (renamed from var/bullet_green.png) | bin | 295 -> 295 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/bullet_red.png (renamed from var/bullet_red.png) | bin | 287 -> 287 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/bullet_white.png (renamed from var/bullet_white.png) | bin | 276 -> 276 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/cog.png (renamed from var/cog.png) | bin | 591 -> 591 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/cog_go.png (renamed from var/cog_go.png) | bin | 1050 -> 1050 bytes | |||
-rw-r--r-- | resources/icons/control_pause.png (renamed from var/control_pause.png) | bin | 598 -> 598 bytes | |||
-rw-r--r-- | resources/icons/control_pause_blue.png (renamed from var/control_pause_blue.png) | bin | 721 -> 721 bytes | |||
-rw-r--r-- | resources/icons/control_play.png (renamed from var/control_play.png) | bin | 592 -> 592 bytes | |||
-rw-r--r-- | resources/icons/control_play_blue.png (renamed from var/control_play_blue.png) | bin | 717 -> 717 bytes | |||
-rw-r--r-- | resources/icons/control_stop.png (renamed from var/control_stop.png) | bin | 403 -> 403 bytes | |||
-rw-r--r-- | resources/icons/control_stop_blue.png (renamed from var/control_stop_blue.png) | bin | 695 -> 695 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/cross.png (renamed from var/cross.png) | bin | 846 -> 846 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/delete.png (renamed from var/delete.png) | bin | 844 -> 844 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/disk.png (renamed from var/disk.png) | bin | 974 -> 974 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/error.png (renamed from var/error.png) | bin | 808 -> 808 bytes | |||
-rw-r--r-- | resources/icons/flag-green-icon.png | bin | 0 -> 672 bytes | |||
-rw-r--r-- | resources/icons/flag-red-icon.png | bin | 0 -> 665 bytes | |||
-rw-r--r-- | resources/icons/funnel.png (renamed from var/funnel.png) | bin | 641 -> 641 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/hourglass.png (renamed from var/hourglass.png) | bin | 907 -> 907 bytes | |||
-rw-r--r-- | resources/icons/house.png (renamed from var/house.png) | bin | 806 -> 806 bytes | |||
-rw-r--r-- | resources/icons/infill.png (renamed from var/infill.png) | bin | 242 -> 242 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/joystick.png (renamed from var/joystick.png) | bin | 559 -> 559 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/layers.png (renamed from var/layers.png) | bin | 1124 -> 1124 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/lorry_add.png (renamed from var/lorry_add.png) | bin | 689 -> 689 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/lorry_go.png (renamed from var/lorry_go.png) | bin | 699 -> 699 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/note.png (renamed from var/note.png) | bin | 740 -> 740 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/package.png (renamed from var/package.png) | bin | 1114 -> 1114 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/package_green.png (renamed from var/package_green.png) | bin | 1117 -> 1117 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/page_white_go.png (renamed from var/page_white_go.png) | bin | 702 -> 702 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/plugin.png (renamed from var/plugin.png) | bin | 778 -> 778 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/plugin_add.png (renamed from var/plugin_add.png) | bin | 691 -> 691 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/plugin_go.png (renamed from var/plugin_go.png) | bin | 694 -> 694 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/printer_empty.png (renamed from var/printer_empty.png) | bin | 424 -> 424 bytes | |||
-rw-r--r-- | resources/icons/reslice.png (renamed from var/reslice.png) | bin | 665 -> 665 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/shape_flip_horizontal.png (renamed from var/shape_flip_horizontal.png) | bin | 403 -> 403 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/shape_handles.png (renamed from var/shape_handles.png) | bin | 538 -> 538 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/shape_ungroup.png (renamed from var/shape_ungroup.png) | bin | 803 -> 803 bytes | |||
-rw-r--r-- | resources/icons/spool.png (renamed from var/spool.png) | bin | 545 -> 545 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/tag_blue.png (renamed from var/tag_blue.png) | bin | 741 -> 741 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/textfield.png (renamed from var/textfield.png) | bin | 153 -> 153 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/time.png (renamed from var/time.png) | bin | 959 -> 959 bytes | |||
-rw-r--r-- | resources/icons/variable_layer_height.png (renamed from var/variable_layer_height.png) | bin | 209 -> 209 bytes | |||
-rw-r--r-- | resources/icons/variable_layer_height_reset.png (renamed from var/variable_layer_height_reset.png) | bin | 1050 -> 1050 bytes | |||
-rw-r--r-- | resources/icons/variable_layer_height_tooltip.png (renamed from var/variable_layer_height_tooltip.png) | bin | 10017 -> 10017 bytes | |||
-rw-r--r-- | resources/icons/wand.png (renamed from var/wand.png) | bin | 570 -> 570 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/wrench.png (renamed from var/wrench.png) | bin | 742 -> 742 bytes | |||
-rw-r--r--[-rwxr-xr-x] | resources/icons/zoom.png (renamed from var/zoom.png) | bin | 692 -> 692 bytes | |||
-rw-r--r-- | resources/profiles/Original Prusa i3 MK3, MK2S, MK2 and MK2S-MMU.ini | 3629 | ||||
-rwxr-xr-x | slic3r.pl | 28 | ||||
-rw-r--r-- | t/avoid_crossing_perimeters.t | 2 | ||||
-rw-r--r-- | t/bridges.t | 2 | ||||
-rw-r--r-- | t/combineinfill.t | 6 | ||||
-rw-r--r-- | t/config.t | 2 | ||||
-rw-r--r-- | t/cooling.t | 6 | ||||
-rw-r--r-- | t/custom_gcode.t | 124 | ||||
-rw-r--r-- | t/fill.t | 8 | ||||
-rw-r--r-- | t/flow.t | 4 | ||||
-rw-r--r-- | t/gaps.t | 2 | ||||
-rw-r--r-- | t/gcode.t | 22 | ||||
-rw-r--r-- | t/layers.t | 2 | ||||
-rw-r--r-- | t/multi.t | 8 | ||||
-rw-r--r-- | t/perimeters.t | 12 | ||||
-rw-r--r-- | t/print.t | 4 | ||||
-rw-r--r-- | t/retraction.t | 10 | ||||
-rw-r--r-- | t/shells.t | 14 | ||||
-rw-r--r-- | t/skirt_brim.t | 12 | ||||
-rw-r--r-- | t/support.t | 12 | ||||
-rw-r--r-- | t/thin.t | 2 | ||||
-rw-r--r-- | t/threads.t | 7 | ||||
-rwxr-xr-x | utils/config-bundle-to-config.pl | 58 | ||||
-rwxr-xr-x | utils/view-toolpaths.pl | 2 | ||||
-rw-r--r-- | xs/CMakeLists.txt | 8 | ||||
-rw-r--r-- | xs/lib/Slic3r/XS.pm | 8 | ||||
-rw-r--r-- | xs/src/boost/nowide/cenv.hpp | 8 | ||||
-rw-r--r-- | xs/src/libslic3r/BoundingBox.cpp | 20 | ||||
-rw-r--r-- | xs/src/libslic3r/Config.cpp | 69 | ||||
-rw-r--r-- | xs/src/libslic3r/Config.hpp | 454 | ||||
-rw-r--r-- | xs/src/libslic3r/ExPolygon.hpp | 3 | ||||
-rw-r--r-- | xs/src/libslic3r/Fill/Fill.cpp | 7 | ||||
-rw-r--r-- | xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp | 11 | ||||
-rw-r--r-- | xs/src/libslic3r/Fill/FillHoneycomb.cpp | 9 | ||||
-rw-r--r-- | xs/src/libslic3r/Fill/FillRectilinear.cpp | 11 | ||||
-rw-r--r-- | xs/src/libslic3r/Flow.cpp | 37 | ||||
-rw-r--r-- | xs/src/libslic3r/Flow.hpp | 3 | ||||
-rw-r--r-- | xs/src/libslic3r/GCode.cpp | 384 | ||||
-rw-r--r-- | xs/src/libslic3r/GCode.hpp | 15 | ||||
-rw-r--r-- | xs/src/libslic3r/GCode/ToolOrdering.hpp | 4 | ||||
-rw-r--r-- | xs/src/libslic3r/GCode/WipeTower.hpp | 6 | ||||
-rw-r--r-- | xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 2 | ||||
-rw-r--r-- | xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp | 2 | ||||
-rw-r--r-- | xs/src/libslic3r/PlaceholderParser.cpp | 963 | ||||
-rw-r--r-- | xs/src/libslic3r/PlaceholderParser.hpp | 27 | ||||
-rw-r--r-- | xs/src/libslic3r/Polygon.hpp | 2 | ||||
-rw-r--r-- | xs/src/libslic3r/Print.cpp | 180 | ||||
-rw-r--r-- | xs/src/libslic3r/PrintConfig.cpp | 782 | ||||
-rw-r--r-- | xs/src/libslic3r/PrintConfig.hpp | 5 | ||||
-rw-r--r-- | xs/src/libslic3r/Slicing.hpp | 2 | ||||
-rw-r--r-- | xs/src/libslic3r/SupportMaterial.cpp | 7 | ||||
-rw-r--r-- | xs/src/libslic3r/TriangleMesh.cpp | 2 | ||||
-rw-r--r-- | xs/src/libslic3r/Utils.hpp | 22 | ||||
-rw-r--r-- | xs/src/libslic3r/libslic3r.h | 2 | ||||
-rw-r--r-- | xs/src/libslic3r/utils.cpp | 40 | ||||
-rw-r--r-- | xs/src/perlglue.cpp | 2 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/AppConfig.cpp | 154 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/AppConfig.hpp | 91 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/GUI.cpp | 144 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/GUI.hpp | 19 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/Preset.cpp | 704 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/Preset.hpp | 175 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/PresetBundle.cpp | 693 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/PresetBundle.hpp | 95 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/PresetHints.cpp | 229 | ||||
-rw-r--r-- | xs/src/slic3r/GUI/PresetHints.hpp | 25 | ||||
-rw-r--r-- | xs/src/xsinit.h | 2 | ||||
-rw-r--r-- | xs/t/15_config.t | 3 | ||||
-rw-r--r-- | xs/xsp/Config.xsp | 42 | ||||
-rw-r--r-- | xs/xsp/Flow.xsp | 12 | ||||
-rw-r--r-- | xs/xsp/GCode.xsp | 9 | ||||
-rw-r--r-- | xs/xsp/GUI.xsp | 18 | ||||
-rw-r--r-- | xs/xsp/GUI_AppConfig.xsp | 44 | ||||
-rw-r--r-- | xs/xsp/GUI_Preset.xsp | 149 | ||||
-rw-r--r-- | xs/xsp/PlaceholderParser.xsp | 15 | ||||
-rw-r--r-- | xs/xsp/Print.xsp | 18 | ||||
-rw-r--r-- | xs/xsp/XS.xsp | 24 | ||||
-rw-r--r-- | xs/xsp/my.map | 17 | ||||
-rw-r--r-- | xs/xsp/typemap.xspt | 4 |
177 files changed, 9310 insertions, 3086 deletions
@@ -10,6 +10,8 @@ my %prereqs = qw( Devel::CheckLib 0 ExtUtils::MakeMaker 6.80 ExtUtils::ParseXS 3.22 + ExtUtils::XSpp 0 + ExtUtils::Typemaps 0 File::Basename 0 File::Spec 0 Getopt::Long 0 @@ -116,6 +116,8 @@ The author of the Silk icon set is Mark James. --gui Forces the GUI launch instead of command line slicing (if you supply a model file, it will be loaded into the plater) --no-plater Disable the plater tab + --no-gui Forces the command line slicing instead of gui. + This takes precedence over --gui if both are present. --autosave <file> Automatically export current configuration to the specified file Output options: diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 7729b6126..22a6ee389 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -23,25 +23,14 @@ sub debugf { our $loglevel = 0; # load threads before Moo as required by it -our $have_threads; BEGIN { # Test, whether the perl was compiled with ithreads support and ithreads actually work. use Config; - $have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1"; - warn "threads.pm >= 1.96 is required, please update\n" if $have_threads && $threads::VERSION < 1.96; - - ### temporarily disable threads if using the broken Moo version use Moo; - $have_threads = 0 if $Moo::VERSION == 1.003000; - - # Disable multi threading completely by an environment value. - # This is useful for debugging as the Perl debugger does not work - # in multi-threaded context at all. - # A good interactive perl debugger is the ActiveState Komodo IDE - # or the EPIC http://www.epic-ide.org/ - $have_threads = 0 if (defined($ENV{'SLIC3R_SINGLETHREADED'}) && $ENV{'SLIC3R_SINGLETHREADED'} == 1); - print "Threading disabled\n" if !$have_threads; - + my $have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1"; + die "Slic3r Prusa Edition requires working Perl threads.\n" if ! $have_threads; + die "threads.pm >= 1.96 is required, please update\n" if $threads::VERSION < 1.96; + die "Perl threading is broken with this Moo version: " . $Moo::VERSION . "\n" if $Moo::VERSION == 1.003000; $debug = 1 if (defined($ENV{'SLIC3R_DEBUGOUT'}) && $ENV{'SLIC3R_DEBUGOUT'} == 1); print "Debugging output enabled\n" if $debug; } @@ -50,8 +39,10 @@ warn "Running Slic3r under Perl 5.16 is neither supported nor recommended\n" if $^V == v5.16; use FindBin; -# Path to the images. -our $var = sub { decode_path($FindBin::Bin) . "/var/" . $_[0] }; + +# Let the XS module know where the GUI resources reside. +set_resources_dir(decode_path($FindBin::Bin) . (($^O eq 'darwin') ? '/../Resources' : '/resources')); +set_var_dir(resources_dir() . "/icons"); use Moo 1.003001; @@ -163,6 +154,7 @@ sub thread_cleanup { *Slic3r::Surface::Collection::DESTROY = sub {}; *Slic3r::Print::SupportMaterial2::DESTROY = sub {}; *Slic3r::TriangleMesh::DESTROY = sub {}; + *Slic3r::GUI::AppConfig::DESTROY = sub {}; *Slic3r::GUI::PresetBundle::DESTROY = sub {}; return undef; # this prevents a "Scalars leaked" warning } diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 638a02c9b9..a9c822b96 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -8,42 +8,19 @@ use utf8; use List::Util qw(first max); -# cemetery of old config settings -our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool acceleration - adjust_overhang_flow standby_temperature scale rotate duplicate duplicate_grid - rotate scale duplicate_grid start_perimeters_at_concave_points start_perimeters_at_non_overhang - randomize_start seal_position bed_size print_center g0 vibration_limit gcode_arcs pressure_advance); - # C++ Slic3r::PrintConfigDef exported as a Perl hash of hashes. # The C++ counterpart is a constant singleton. our $Options = print_config_def(); -# overwrite the hard-coded readonly value (this information is not available in XS) -$Options->{threads}{readonly} = !$Slic3r::have_threads; - -# generate accessors +# Generate accessors. { no strict 'refs'; for my $opt_key (keys %$Options) { - *{$opt_key} = sub { $_[0]->get($opt_key) }; - } -} - -# Fill in the underlying C++ Slic3r::DynamicPrintConfig with the content of the defaults -# provided by the C++ class Slic3r::FullPrintConfig. -# Used by the UI. -sub new_from_defaults { - my ($class, @opt_keys) = @_; - my $self = $class->new; - # Instantiating the C++ class Slic3r::FullPrintConfig. - my $defaults = Slic3r::Config::Full->new; - if (@opt_keys) { - $self->set($_, $defaults->get($_)) - for grep $defaults->has($_), @opt_keys; - } else { - $self->apply_static($defaults); + *{$opt_key} = sub { + #print "Slic3r::Config::accessor $opt_key\n"; + $_[0]->get($opt_key) + }; } - return $self; } # From command line parameters, used by slic3r.pl @@ -87,273 +64,6 @@ sub new_from_cli { return $self; } -sub merge { - my $class = shift; - my $config = $class->new; - $config->apply($_) for @_; - return $config; -} - -# Load a flat ini file without a category into the underlying C++ Slic3r::DynamicConfig class, -# convert legacy configuration names. -sub load { - my ($class, $file) = @_; - # Instead of using the /i modifier for case-insensitive matching, the case insensitivity is expressed - # explicitely to avoid having to bundle the UTF8 Perl library. - if ($file =~ /\.[gG][cC][oO][dD][eE]/ || $file =~ /\.[gG]/) { - my $config = $class->new; - $config->_load_from_gcode($file); - return $config; - } else { - my $config = $class->new; - $config->_load($file); - return $config; - } -} - -# Deserialize a perl hash into the underlying C++ Slic3r::DynamicConfig class, -# convert legacy configuration names. -# Used to load a config bundle. -sub load_ini_hash { - my ($class, $ini_hash) = @_; - my $config = $class->new; - $config->set_deserialize($_, $ini_hash->{$_}) for keys %$ini_hash; - return $config; -} - -sub clone { - my $self = shift; - my $new = (ref $self)->new; - $new->apply($self); - return $new; -} - -sub get_value { - my ($self, $opt_key) = @_; - return $Options->{$opt_key}{ratio_over} - ? $self->get_abs_value($opt_key) - : $self->get($opt_key); -} - -# Create a hash of hashes from the underlying C++ Slic3r::DynamicPrintConfig. -# The first hash key is '_' meaning no category. -# Used to create a config bundle. -sub as_ini { - my ($self) = @_; - my $ini = { _ => {} }; - foreach my $opt_key (sort @{$self->get_keys}) { - next if $Options->{$opt_key}{shortcut}; - $ini->{_}{$opt_key} = $self->serialize($opt_key); - } - return $ini; -} - -# this method is idempotent by design and only applies to ::DynamicConfig or ::Full -# objects because it performs cross checks -sub validate { - my $self = shift; - - # -j, --threads - die "Invalid value for --threads\n" - if $self->threads < 1; - - # --layer-height - die "Invalid value for --layer-height\n" - if $self->layer_height <= 0; - die "--layer-height must be a multiple of print resolution\n" - if $self->layer_height / &Slic3r::SCALING_FACTOR % 1 != 0; - - # --first-layer-height - die "Invalid value for --first-layer-height\n" - if $self->first_layer_height !~ /^(?:\d*(?:\.\d+)?)%?$/; - die "Invalid value for --first-layer-height\n" - if $self->get_value('first_layer_height') <= 0; - - # --filament-diameter - die "Invalid value for --filament-diameter\n" - if grep $_ < 1, @{$self->filament_diameter}; - - # --nozzle-diameter - die "Invalid value for --nozzle-diameter\n" - if grep $_ < 0, @{$self->nozzle_diameter}; - - # --perimeters - die "Invalid value for --perimeters\n" - if $self->perimeters < 0; - - # --solid-layers - die "Invalid value for --solid-layers\n" if defined $self->solid_layers && $self->solid_layers < 0; - die "Invalid value for --top-solid-layers\n" if $self->top_solid_layers < 0; - die "Invalid value for --bottom-solid-layers\n" if $self->bottom_solid_layers < 0; - - # --gcode-flavor - die "Invalid value for --gcode-flavor\n" - if !first { $_ eq $self->gcode_flavor } @{$Options->{gcode_flavor}{values}}; - - die "--use-firmware-retraction is only supported by Marlin, Smoothie, Repetier and Machinekit firmware\n" - if $self->use_firmware_retraction && $self->gcode_flavor ne 'smoothie' - && $self->gcode_flavor ne 'reprap' - && $self->gcode_flavor ne 'machinekit' - && $self->gcode_flavor ne 'repetier'; - - die "--use-firmware-retraction is not compatible with --wipe\n" - if $self->use_firmware_retraction && first {$_} @{$self->wipe}; - - # --fill-pattern - die "Invalid value for --fill-pattern\n" - if !first { $_ eq $self->fill_pattern } @{$Options->{fill_pattern}{values}}; - - # --external-fill-pattern - die "Invalid value for --external-fill-pattern\n" - if !first { $_ eq $self->external_fill_pattern } @{$Options->{external_fill_pattern}{values}}; - - # --fill-density - die "The selected fill pattern is not supposed to work at 100% density\n" - if $self->fill_density == 100 - && !first { $_ eq $self->fill_pattern } @{$Options->{external_fill_pattern}{values}}; - - # --infill-every-layers - die "Invalid value for --infill-every-layers\n" - if $self->infill_every_layers !~ /^\d+$/ || $self->infill_every_layers < 1; - - # --skirt-height - die "Invalid value for --skirt-height\n" - if $self->skirt_height < -1; # -1 means as tall as the object - - # --bridge-flow-ratio - die "Invalid value for --bridge-flow-ratio\n" - if $self->bridge_flow_ratio <= 0; - - # extruder clearance - die "Invalid value for --extruder-clearance-radius\n" - if $self->extruder_clearance_radius <= 0; - die "Invalid value for --extruder-clearance-height\n" - if $self->extruder_clearance_height <= 0; - - # --extrusion-multiplier - die "Invalid value for --extrusion-multiplier\n" - if defined first { $_ <= 0 } @{$self->extrusion_multiplier}; - - # --default-acceleration - die "Invalid zero value for --default-acceleration when using other acceleration settings\n" - if ($self->perimeter_acceleration || $self->infill_acceleration || $self->bridge_acceleration || $self->first_layer_acceleration) - && !$self->default_acceleration; - - # --spiral-vase - if ($self->spiral_vase) { - # Note that we might want to have more than one perimeter on the bottom - # solid layers. - die "Can't make more than one perimeter when spiral vase mode is enabled\n" - if $self->perimeters > 1; - - die "Can't make less than one perimeter when spiral vase mode is enabled\n" - if $self->perimeters < 1; - - die "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0\n" - if $self->fill_density > 0; - - die "Spiral vase mode is not compatible with top solid layers\n" - if $self->top_solid_layers > 0; - - die "Spiral vase mode is not compatible with support material\n" - if $self->support_material || $self->support_material_enforce_layers > 0; - } - - # extrusion widths - { - my $max_nozzle_diameter = max(@{ $self->nozzle_diameter }); - die "Invalid extrusion width (too large)\n" - if defined first { $_ > 10 * $max_nozzle_diameter } - map $self->get_abs_value_over("${_}_extrusion_width", $max_nozzle_diameter), - qw(perimeter infill solid_infill top_infill support_material first_layer); - } - - # general validation, quick and dirty - foreach my $opt_key (@{$self->get_keys}) { - my $opt = $Options->{$opt_key}; - next unless defined $self->$opt_key; - next unless defined $opt->{cli} && $opt->{cli} =~ /=(.+)$/; - my $type = $1; - my @values = (); - if ($type =~ s/\@$//) { - die "Invalid value for $opt_key\n" if ref($self->$opt_key) ne 'ARRAY'; - @values = @{ $self->$opt_key }; - } else { - @values = ($self->$opt_key); - } - foreach my $value (@values) { - if ($type eq 'i' || $type eq 'f' || $opt->{type} eq 'percent') { - $value =~ s/%$// if $opt->{type} eq 'percent'; - die "Invalid value for $opt_key\n" - if ($type eq 'i' && $value !~ /^-?\d+$/) - || (($type eq 'f' || $opt->{type} eq 'percent') && $value !~ /^-?(?:\d+|\d*\.\d+)$/) - || (defined $opt->{min} && $value < $opt->{min}) - || (defined $opt->{max} && $value > $opt->{max}); - } elsif ($type eq 's' && $opt->{type} eq 'select') { - die "Invalid value for $opt_key\n" - unless first { $_ eq $value } @{ $opt->{values} }; - } - } - } - - return 1; -} - -# CLASS METHODS: - -# Write a "Windows" style ini file with categories enclosed in squre brackets. -# Used by config-bundle-to-config.pl and to save slic3r.ini. -sub write_ini { - my $class = shift; - my ($file, $ini) = @_; - - Slic3r::open(\my $fh, '>', $file); - binmode $fh, ':utf8'; - my $localtime = localtime; - printf $fh "# generated by Slic3r $Slic3r::VERSION on %s\n", "$localtime"; - # make sure the _ category is the first one written - foreach my $category (sort { ($a eq '_') ? -1 : ($a cmp $b) } keys %$ini) { - printf $fh "\n[%s]\n", $category if $category ne '_'; - foreach my $key (sort keys %{$ini->{$category}}) { - printf $fh "%s = %s\n", $key, $ini->{$category}{$key}; - } - } - close $fh; -} - -# Parse a "Windows" style ini file with categories enclosed in squre brackets. -# Returns a hash of hashes over strings. -# {category}{name}=value -# Non-categorized entries are stored under a category '_'. -# Used by config-bundle-to-config.pl and to read slic3r.ini. -sub read_ini { - my $class = shift; - my ($file) = @_; - - local $/ = "\n"; - Slic3r::open(\my $fh, '<', $file) - or die "Unable to open $file: $!\n"; - binmode $fh, ':utf8'; - - my $ini = { _ => {} }; - my $category = '_'; - while (<$fh>) { - s/\R+$//; - next if /^\s+/; - next if /^$/; - next if /^\s*#/; - if (/^\[(.+?)\]$/) { - $category = $1; - next; - } - /^(\w+) *= *(.*)/ or die "Unreadable configuration file (invalid data at line $.)\n"; - $ini->{$category}{$1} = $2; - } - close $fh; - - return $ini; -} - package Slic3r::Config::Static; use parent 'Slic3r::Config'; diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index dc93e513f..5176c9c91 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -52,27 +52,12 @@ use constant FILE_WILDCARDS => { }; use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf prusa)}; +# Datadir provided on the command line. our $datadir; # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. -our $no_controller; our $no_plater; -our $autosave; our @cb; -our $Settings = { - _ => { - version_check => 1, - autocenter => 1, - # Disable background processing by default as it is not stable. - background_processing => 0, - # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. - # By default, Prusa has the controller hidden. - no_controller => 1, - # If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available. - no_defaults => 1, - }, -}; - our $small_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); $small_font->SetPointSize(11) if &Wx::wxMAC; our $small_bold_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); @@ -82,134 +67,71 @@ our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); $medium_font->SetPointSize(12); our $grey = Wx::Colour->new(200,200,200); -#our $VERSION_CHECK_EVENT : shared = Wx::NewEventType; - -our $DLP_projection_screen; - sub OnInit { my ($self) = @_; - $self->SetAppName('Slic3r'); + $self->SetAppName('Slic3rPE'); $self->SetAppDisplayName('Slic3r Prusa Edition'); Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION; - - $self->{notifier} = Slic3r::GUI::Notifier->new; - $self->{preset_bundle} = Slic3r::GUI::PresetBundle->new; - # locate or create data directory + # Set the Slic3r data directory at the Slic3r XS module. # Unix: ~/.Slic3r # Windows: "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r" # Mac: "~/Library/Application Support/Slic3r" - $datadir ||= Wx::StandardPaths::Get->GetUserDataDir; - my $enc_datadir = Slic3r::encode_path($datadir); - Slic3r::debugf "Data directory: %s\n", $datadir; + Slic3r::set_data_dir($datadir || Wx::StandardPaths::Get->GetUserDataDir); + Slic3r::GUI::set_wxapp($self); + + $self->{notifier} = Slic3r::GUI::Notifier->new; + $self->{app_config} = Slic3r::GUI::AppConfig->new; + $self->{preset_bundle} = Slic3r::GUI::PresetBundle->new; - # just checking for existence of $datadir is not enough: it may be an empty directory + # just checking for existence of Slic3r::data_dir is not enough: it may be an empty directory # supplied as argument to --datadir; in that case we should still run the wizard - my $run_wizard = (-d $enc_datadir && -e "$enc_datadir/slic3r.ini") ? 0 : 1; - foreach my $dir ($enc_datadir, "$enc_datadir/print", "$enc_datadir/filament", "$enc_datadir/printer") { - next if -d $dir; - if (!mkdir $dir) { - my $error = "Slic3r was unable to create its data directory at $dir ($!)."; - warn "$error\n"; - fatal_error(undef, $error); - } + eval { $self->{preset_bundle}->setup_directories() }; + if ($@) { + warn $@ . "\n"; + fatal_error(undef, $@); } - + my $run_wizard = ! $self->{app_config}->exists; # load settings - my $last_version; - if (-f "$enc_datadir/slic3r.ini") { - my $ini = eval { Slic3r::Config->read_ini("$datadir/slic3r.ini") }; - $Settings = $ini if $ini; - $last_version = $Settings->{_}{version}; - $Settings->{_}{autocenter} //= 1; - $Settings->{_}{background_processing} //= 1; - # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. - $Settings->{_}{no_controller} //= 1; - # If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available. - $Settings->{_}{no_defaults} //= 1; + $self->{app_config}->load if ! $run_wizard; + $self->{app_config}->set('version', $Slic3r::VERSION); + $self->{app_config}->save; + + # Suppress the '- default -' presets. + $self->{preset_bundle}->set_default_suppressed($self->{app_config}->get('no_defaults') ? 1 : 0); + eval { $self->{preset_bundle}->load_presets }; + if ($@) { + warn $@ . "\n"; + show_error(undef, $@); } - $Settings->{_}{version} = $Slic3r::VERSION; - $self->save_settings; + eval { $self->{preset_bundle}->load_selections($self->{app_config}) }; + $run_wizard = 1 if $self->{preset_bundle}->has_defauls_only; # application frame - Wx::Image::AddHandler(Wx::PNGHandler->new); + Wx::Image::FindHandlerType(wxBITMAP_TYPE_PNG) || Wx::Image::AddHandler(Wx::PNGHandler->new); $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new( # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. - no_controller => $no_controller // $Settings->{_}{no_controller}, + no_controller => $self->{app_config}->get('no_controller'), no_plater => $no_plater, ); $self->SetTopWindow($frame); - - # load init bundle - { - my @dirs = ($FindBin::Bin); - if (&Wx::wxMAC) { - push @dirs, qw(); - } elsif (&Wx::wxMSW) { - push @dirs, qw(); - } - my $init_bundle = first { -e $_ } map "$_/.init_bundle.ini", @dirs; - if ($init_bundle) { - Slic3r::debugf "Loading config bundle from %s\n", $init_bundle; - $self->{mainframe}->load_configbundle($init_bundle, 1); - $run_wizard = 0; - } - } - - if (!$run_wizard && (!defined $last_version || $last_version ne $Slic3r::VERSION)) { - # user was running another Slic3r version on this computer - if (!defined $last_version || $last_version =~ /^0\./) { - show_info($self->{mainframe}, "Hello! Support material was improved since the " - . "last version of Slic3r you used. It is strongly recommended to revert " - . "your support material settings to the factory defaults and start from " - . "those. Enjoy and provide feedback!", "Support Material"); - } - if (!defined $last_version || $last_version =~ /^(?:0|1\.[01])\./) { - show_info($self->{mainframe}, "Hello! In this version a new Bed Shape option was " - . "added. If the bed coordinates in the plater preview screen look wrong, go " - . "to Print Settings and click the \"Set\" button next to \"Bed Shape\".", "Bed Shape"); - } + if ($run_wizard) { + $self->{mainframe}->config_wizard; } - $self->{mainframe}->config_wizard if $run_wizard; - eval { $self->{preset_bundle}->load_presets($datadir) }; - -# $self->check_version -# if $self->have_version_check -# && ($Settings->{_}{version_check} // 1) -# && (!$Settings->{_}{last_version_check} || (time - $Settings->{_}{last_version_check}) >= 86400); - + EVT_IDLE($frame, sub { while (my $cb = shift @cb) { $cb->(); } + $self->{app_config}->save if $self->{app_config}->dirty; }); -# EVT_COMMAND($self, -1, $VERSION_CHECK_EVENT, sub { -# my ($self, $event) = @_; -# my ($success, $response, $manual_check) = @{$event->GetData}; -# -# if ($success) { -# if ($response =~ /^obsolete ?= ?([a-z0-9.-]+,)*\Q$Slic3r::VERSION\E(?:,|$)/) { -# my $res = Wx::MessageDialog->new(undef, "A new version is available. Do you want to open the Slic3r website now?", -# 'Update', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_INFORMATION | wxICON_ERROR)->ShowModal; -# Wx::LaunchDefaultBrowser('http://slic3r.org/') if $res == wxID_YES; -# } else { -# Slic3r::GUI::show_info(undef, "You're using the latest version. No updates are available.") if $manual_check; -# } -# $Settings->{_}{last_version_check} = time(); -# $self->save_settings; -# } else { -# Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $manual_check; -# } -# }); - return 1; } sub about { my ($self) = @_; - my $about = Slic3r::GUI::AboutDialog->new(undef); $about->ShowModal; $about->Destroy; @@ -217,7 +139,6 @@ sub about { sub system_info { my ($self) = @_; - my $slic3r_info = Slic3r::slic3r_info(format => 'html'); my $copyright_info = Slic3r::copyright_info(format => 'html'); my $system_info = Slic3r::system_info(format => 'html'); @@ -295,11 +216,6 @@ sub notify { $self->{notifier}->notify($message); } -sub save_settings { - my ($self) = @_; - Slic3r::Config->write_ini("$datadir/slic3r.ini", $Settings); -} - # Called after the Preferences dialog is closed and the program settings are saved. # Update the UI based on the current preferences. sub update_ui_from_settings { @@ -307,64 +223,11 @@ sub update_ui_from_settings { $self->{mainframe}->update_ui_from_settings; } -sub presets { - my ($self, $section) = @_; - - my %presets = (); - opendir my $dh, Slic3r::encode_path("$Slic3r::GUI::datadir/$section") - or die "Failed to read directory $Slic3r::GUI::datadir/$section (errno: $!)\n"; - # Instead of using the /i modifier for case-insensitive matching, the case insensitivity is expressed - # explicitely to avoid having to bundle the UTF8 Perl library. - foreach my $file (grep /\.[iI][nN][iI]$/, readdir $dh) { - $file = Slic3r::decode_path($file); - my $name = basename($file); - $name =~ s/\.ini$//; - $presets{$name} = "$Slic3r::GUI::datadir/$section/$file"; - } - closedir $dh; - - return %presets; -} - -#sub have_version_check { -# my ($self) = @_; -# -# # return an explicit 0 -# return ($Slic3r::have_threads && $Slic3r::build && $have_LWP) || 0; -#} - -#sub check_version { -# my ($self, $manual_check) = @_; -# -# Slic3r::debugf "Checking for updates...\n"; -# -# @_ = (); -# threads->create(sub { -# my $ua = LWP::UserAgent->new; -# $ua->timeout(10); -# my $response = $ua->get('http://slic3r.org/updatecheck'); -# Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $VERSION_CHECK_EVENT, -# threads::shared::shared_clone([ $response->is_success, $response->decoded_content, $manual_check ]))); -# Slic3r::thread_cleanup(); -# })->detach; -#} - -sub output_path { - my ($self, $dir) = @_; - - return ($Settings->{_}{last_output_path} && $Settings->{_}{remember_output_path}) - ? $Settings->{_}{last_output_path} - : $dir; -} - sub open_model { my ($self, $window) = @_; - my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} - || $Slic3r::GUI::Settings->{recent}{config_directory} - || ''; - - my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF/PRUSA):', $dir, "", + my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF/PRUSA):', + $self->{app_config}->get_last_dir, "", MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; @@ -380,31 +243,6 @@ sub CallAfter { push @cb, $cb; } -sub scan_serial_ports { - my ($self) = @_; - - my @ports = (); - - if ($^O eq 'MSWin32') { - # Windows - if (eval "use Win32::TieRegistry; 1") { - my $ts = Win32::TieRegistry->new("HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM", - { Access => 'KEY_READ' }); - if ($ts) { - # when no serial ports are available, the registry key doesn't exist and - # TieRegistry->new returns undef - $ts->Tie(\my %reg); - push @ports, sort values %reg; - } - } - } else { - # UNIX and OS X - push @ports, glob '/dev/{ttyUSB,ttyACM,tty.,cu.,rfcomm}*'; - } - - return grep !/Bluetooth|FireFly/, @ports; -} - sub append_menu_item { my ($self, $menu, $string, $description, $cb, $id, $icon, $kind) = @_; @@ -434,32 +272,31 @@ sub set_menu_item_icon { # SetBitmap was not available on OS X before Wx 0.9927 if ($icon && $menuItem->can('SetBitmap')) { - $menuItem->SetBitmap(Wx::Bitmap->new($Slic3r::var->($icon), wxBITMAP_TYPE_PNG)); + $menuItem->SetBitmap(Wx::Bitmap->new(Slic3r::var($icon), wxBITMAP_TYPE_PNG)); } } sub save_window_pos { my ($self, $window, $name) = @_; - $Settings->{_}{"${name}_pos"} = join ',', $window->GetScreenPositionXY; - $Settings->{_}{"${name}_size"} = join ',', $window->GetSizeWH; - $Settings->{_}{"${name}_maximized"} = $window->IsMaximized; - $self->save_settings; + $self->{app_config}->set("${name}_pos", join ',', $window->GetScreenPositionXY); + $self->{app_config}->set("${name}_size", join ',', $window->GetSizeWH); + $self->{app_config}->set("${name}_maximized", $window->IsMaximized); + $self->{app_config}->save; } sub restore_window_pos { my ($self, $window, $name) = @_; - - if (defined $Settings->{_}{"${name}_pos"}) { - my $size = [ split ',', $Settings->{_}{"${name}_size"}, 2 ]; + if ($self->{app_config}->has("${name}_pos")) { + my $size = [ split ',', $self->{app_config}->get("${name}_size"), 2 ]; $window->SetSize($size); my $display = Wx::Display->new->GetClientArea(); - my $pos = [ split ',', $Settings->{_}{"${name}_pos"}, 2 ]; + my $pos = [ split ',', $self->{app_config}->get("${name}_pos"), 2 ]; if (($pos->[0] + $size->[0]/2) < $display->GetRight && ($pos->[1] + $size->[1]/2) < $display->GetBottom) { $window->Move($pos); } - $window->Maximize(1) if $Settings->{_}{"${name}_maximized"}; + $window->Maximize(1) if $self->{app_config}->get("${name}_maximized"); } } diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index bb77ebd57..51c822590 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -1390,7 +1390,7 @@ sub _load_image_set_texture { my ($self, $file_name) = @_; # Load a PNG with an alpha channel. my $img = Wx::Image->new; - $img->LoadFile($Slic3r::var->($file_name), wxBITMAP_TYPE_PNG); + $img->LoadFile(Slic3r::var($file_name), wxBITMAP_TYPE_PNG); # Get RGB & alpha raw data from wxImage, interleave them into a Perl array. my @rgb = unpack 'C*', $img->GetData(); my @alpha = $img->HasAlpha ? unpack 'C*', $img->GetAlpha() : (255) x (int(@rgb) / 3); diff --git a/lib/Slic3r/GUI/AboutDialog.pm b/lib/Slic3r/GUI/AboutDialog.pm index 398fde97d..0879ea35b 100644 --- a/lib/Slic3r/GUI/AboutDialog.pm +++ b/lib/Slic3r/GUI/AboutDialog.pm @@ -97,7 +97,7 @@ sub new { my $class = shift; my $self = $class->SUPER::new(@_); - $self->{logo} = Wx::Bitmap->new($Slic3r::var->("Slic3r_192px.png"), wxBITMAP_TYPE_PNG); + $self->{logo} = Wx::Bitmap->new(Slic3r::var("Slic3r_192px.png"), wxBITMAP_TYPE_PNG); $self->SetMinSize(Wx::Size->new($self->{logo}->GetWidth, $self->{logo}->GetHeight)); EVT_PAINT($self, \&repaint); diff --git a/lib/Slic3r/GUI/ConfigWizard.pm b/lib/Slic3r/GUI/ConfigWizard.pm index 3d94f9a68..c18741396 100644 --- a/lib/Slic3r/GUI/ConfigWizard.pm +++ b/lib/Slic3r/GUI/ConfigWizard.pm @@ -14,14 +14,14 @@ our $wizard = 'Wizard'; $wizard = 'Assistant' if &Wx::wxMAC || &Wx::wxGTK; sub new { - my $class = shift; - my ($parent) = @_; + my ($class, $parent, $presets) = @_; my $self = $class->SUPER::new($parent, -1, "Configuration $wizard"); # initialize an empty repository $self->{config} = Slic3r::Config->new; - $self->add_page(Slic3r::GUI::ConfigWizard::Page::Welcome->new($self)); + my $welcome_page = Slic3r::GUI::ConfigWizard::Page::Welcome->new($self); + $self->add_page($welcome_page); $self->add_page(Slic3r::GUI::ConfigWizard::Page::Firmware->new($self)); $self->add_page(Slic3r::GUI::ConfigWizard::Page::Bed->new($self)); $self->add_page(Slic3r::GUI::ConfigWizard::Page::Nozzle->new($self)); @@ -32,12 +32,13 @@ sub new { $_->build_index for @{$self->{pages}}; + $welcome_page->set_selection_presets([@{$presets}, 'Other']); + return $self; } sub add_page { - my $self = shift; - my ($page) = @_; + my ($self, $page) = @_; my $n = push @{$self->{pages}}, $page; # add first page to the page area sizer @@ -48,13 +49,13 @@ sub add_page { } sub run { - my $self = shift; - + my ($self) = @_; + my $result = undef; if (Wx::Wizard::RunWizard($self, $self->{pages}[0])) { - - # it would be cleaner to have these defined inside each page class, - # in some event getting called before leaving the page - { + my $preset_name = $self->{pages}[0]->{preset_name}; + if ($preset_name eq 'Other') { + # it would be cleaner to have these defined inside each page class, + # in some event getting called before leaving the page # set first_layer_height + layer_height based on nozzle_diameter my $nozzle = $self->{config}->nozzle_diameter; $self->{config}->set('first_layer_height', $nozzle->[0]); @@ -66,14 +67,13 @@ sub run { # set first_layer_bed_temperature to temperature + 5 $self->{config}->set('first_layer_bed_temperature', [ ($self->{config}->bed_temperature->[0] > 0) ? ($self->{config}->bed_temperature->[0] + 5) : 0 ]); + $result = $self->{config}; + } else { + $result = $preset_name; } - - $self->Destroy; - return $self->{config}; - } else { - $self->Destroy; - return undef; } + $self->Destroy; + return $result; } package Slic3r::GUI::ConfigWizard::Index; @@ -89,11 +89,11 @@ sub new { push @{$self->{titles}}, $title; $self->{own_index} = 0; - $self->{bullets}->{before} = Wx::Bitmap->new($Slic3r::var->("bullet_black.png"), wxBITMAP_TYPE_PNG); - $self->{bullets}->{own} = Wx::Bitmap->new($Slic3r::var->("bullet_blue.png"), wxBITMAP_TYPE_PNG); - $self->{bullets}->{after} = Wx::Bitmap->new($Slic3r::var->("bullet_white.png"), wxBITMAP_TYPE_PNG); + $self->{bullets}->{before} = Wx::Bitmap->new(Slic3r::var("bullet_black.png"), wxBITMAP_TYPE_PNG); + $self->{bullets}->{own} = Wx::Bitmap->new(Slic3r::var("bullet_blue.png"), wxBITMAP_TYPE_PNG); + $self->{bullets}->{after} = Wx::Bitmap->new(Slic3r::var("bullet_white.png"), wxBITMAP_TYPE_PNG); - $self->{background} = Wx::Bitmap->new($Slic3r::var->("Slic3r_192px_transparent.png"), wxBITMAP_TYPE_PNG); + $self->{background} = Wx::Bitmap->new(Slic3r::var("Slic3r_192px_transparent.png"), wxBITMAP_TYPE_PNG); $self->SetMinSize(Wx::Size->new($self->{background}->GetWidth, $self->{background}->GetHeight)); EVT_PAINT($self, \&repaint); @@ -127,6 +127,8 @@ sub repaint { $dc->SetTextForeground(Wx::Colour->new(128, 128, 128)) if $i > $self->{own_index}; $dc->DrawLabel($_, $bullet, Wx::Rect->new(0, $i * ($label_h + $gap), $label_w, $label_h)); + # Only show the first bullet if this is the only wizard page to be displayed. + last if $i == 0 && $self->{just_welcome}; $i++; } @@ -202,7 +204,7 @@ sub append_option { # populate repository with the factory default my ($opt_key, $opt_index) = split /#/, $full_key, 2; - $self->config->apply(Slic3r::Config->new_from_defaults($opt_key)); + $self->config->apply(Slic3r::Config::new_from_defaults_keys([$opt_key])); # draw the control my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( @@ -263,19 +265,58 @@ sub config { package Slic3r::GUI::ConfigWizard::Page::Welcome; use base 'Slic3r::GUI::ConfigWizard::Page'; +use Wx qw(:misc :sizer wxID_FORWARD); +use Wx::Event qw(EVT_ACTIVATE EVT_CHOICE); sub new { my $class = shift; my ($parent) = @_; my $self = $class->SUPER::new($parent, "Welcome to the Slic3r Configuration $wizard", 'Welcome'); + $self->{full_wizard_workflow} = 1; + + $self->append_text('Hello, welcome to Slic3r Prusa Edition! This '.lc($wizard).' helps you with the initial configuration; just a few settings and you will be ready to print.'); + $self->append_text('Please select your printer vendor and printer type. If your printer is not listed, you may try your luck and select a similar one. If you select "Other", this ' . lc($wizard) . ' will let you set the basic 3D printer parameters.'); + # To import an existing configuration instead, cancel this '.lc($wizard).' and use the Open Config menu item found in the File menu.'); + $self->append_text('If you received a configuration file or a config bundle from your 3D printer vendor, cancel this '.lc($wizard).' and use the "File->Load Config" or "File->Load Config Bundle" menu.'); + + $self->{choice} = my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, wxDefaultSize, []); + $self->{vsizer}->Add($choice, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); + + EVT_CHOICE($parent, $choice, sub { + my $sel = $self->{choice}->GetStringSelection; + $self->{preset_name} = $sel; + $self->set_full_wizard_workflow(($sel eq 'Other') || ($sel eq '')); + }); - $self->append_text('Hello, welcome to Slic3r! This '.lc($wizard).' helps you with the initial configuration; just a few settings and you will be ready to print.'); - $self->append_text('To import an existing configuration instead, cancel this '.lc($wizard).' and use the Open Config menu item found in the File menu.'); - $self->append_text('To continue, click Next.'); + EVT_ACTIVATE($parent, sub { + $self->set_full_wizard_workflow($self->{preset_name} eq 'Other'); + }); return $self; } +sub set_full_wizard_workflow { + my ($self, $full_workflow) = @_; + $self->{full_wizard_workflow} = $full_workflow; + $self->{index}->{just_welcome} = !$full_workflow; + $self->{index}->Refresh; + my $next_button = $self->GetParent->FindWindow(wxID_FORWARD); + $next_button->SetLabel($full_workflow ? "&Next >" : "&Finish"); +} + +# Set the preset names, select the first item. +sub set_selection_presets { + my ($self, $names) = @_; + $self->{choice}->Append($names); + $self->{choice}->SetSelection(0); + $self->{preset_name} = $names->[0]; +} + +sub GetNext { + my $self = shift; + return $self->{full_wizard_workflow} ? $self->{next_page} : undef; +} + package Slic3r::GUI::ConfigWizard::Page::Firmware; use base 'Slic3r::GUI::ConfigWizard::Page'; @@ -300,7 +341,7 @@ sub new { $self->append_text('Set the shape of your printer\'s bed, then click Next.'); - $self->config->apply(Slic3r::Config->new_from_defaults('bed_shape')); + $self->config->apply(Slic3r::Config::new_from_defaults_keys(['bed_shape'])); $self->{bed_shape_panel} = my $panel = Slic3r::GUI::BedShapePanel->new($self, $self->config->bed_shape); $self->{bed_shape_panel}->on_change(sub { $self->config->set('bed_shape', $self->{bed_shape_panel}->GetValue); diff --git a/lib/Slic3r/GUI/Controller.pm b/lib/Slic3r/GUI/Controller.pm index 8c4dcbec7..6aa7b34cb 100644 --- a/lib/Slic3r/GUI/Controller.pm +++ b/lib/Slic3r/GUI/Controller.pm @@ -10,6 +10,7 @@ use utf8; use Wx qw(wxTheApp :frame :id :misc :sizer :bitmap :button :icon :dialog); use Wx::Event qw(EVT_CLOSE EVT_LEFT_DOWN EVT_MENU); use base qw(Wx::ScrolledWindow Class::Accessor); +use List::Util qw(first); __PACKAGE__->mk_accessors(qw(_selected_printer_preset)); @@ -32,27 +33,25 @@ sub new { # button for adding new printer panels { - my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("add.png"), wxBITMAP_TYPE_PNG), + my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); $btn->SetToolTipString("Add printerâĤ") if $btn->can('SetToolTipString'); EVT_LEFT_DOWN($btn, sub { - my $menu = Wx::Menu->new; - my %presets = wxTheApp->presets('printer'); - + my $menu = Wx::Menu->new; + my @panels = $self->print_panels; # remove printers that already exist - my @panels = $self->print_panels; - delete $presets{$_} for map $_->printer_name, @panels; - - foreach my $preset_name (sort keys %presets) { - my $config = Slic3r::Config->load($presets{$preset_name}); - next if !$config->serial_port; - + # update configs of currently loaded print panels + foreach my $preset (@{wxTheApp->{preset_bundle}->printer}) { + my $preset_name = $preset->name; + next if ! $preset->config->serial_port || + defined first { defined $_ && $_->printer_name eq $preset_name } @panels; + my $myconfig = $preset->config->clone_only(\@ConfigOptions); my $id = &Wx::NewId(); $menu->Append($id, $preset_name); EVT_MENU($menu, $id, sub { - $self->add_printer($preset_name, $config); + $self->add_printer($preset_name, $myconfig); }); } $self->PopupMenu($menu, $btn->GetPosition); @@ -98,12 +97,8 @@ sub OnActivate { my ($self) = @_; # get all available presets - my %presets = (); - { - my %all = wxTheApp->presets('printer'); - my %configs = map { my $name = $_; $name => Slic3r::Config->load($all{$name}) } keys %all; - %presets = map { $_ => $configs{$_} } grep $configs{$_}->serial_port, keys %all; - } + my %presets = map { $_->name => $_->config->clone_only(\@ConfigOptions) } + grep { $_->config->serial_port } @{wxTheApp->{preset_bundle}->printer}; # decide which ones we want to keep my %active = (); @@ -121,7 +116,7 @@ sub OnActivate { } if (!%active) { # enable printers whose port is available - my %ports = map { $_ => 1 } wxTheApp->scan_serial_ports; + my %ports = map { $_ => 1 } Slic3r::GUI::scan_serial_ports; $active{$_} = 1 for grep exists $ports{$presets{$_}->serial_port}, keys %presets; } @@ -177,26 +172,19 @@ sub print_panels { map $_->GetWindow, $self->{sizer}->GetChildren; } -# Called by -# Slic3r::GUI::Tab::Print::_on_presets_changed -# Slic3r::GUI::Tab::Filament::_on_presets_changed -# Slic3r::GUI::Tab::Printer::_on_presets_changed -# when the presets are loaded or the user select another preset. +# Called by Slic3r::GUI::Tab::Printer::_on_presets_changed +# when the presets are loaded or the user selects another preset. sub update_presets { - my $self = shift; - my ($group, $presets, $default_suppressed, $selected, $is_dirty) = @_; - + my ($self, $presets) = @_; # update configs of currently loaded print panels + my @presets = @$presets; foreach my $panel ($self->print_panels) { - foreach my $preset (@$presets) { - if ($panel->printer_name eq $preset->name) { - my $config = $preset->config(\@ConfigOptions); - $panel->config->apply($config); - } - } + my $preset = $presets->find_preset($panel->printer_name, 0); + $panel->config($preset->config->clone_only(\@ConfigOptions)) + if defined $preset; } - - $self->_selected_printer_preset($presets->[$selected]->name); + + $self->_selected_printer_preset($presets->get_selected_preset->name); } 1; diff --git a/lib/Slic3r/GUI/Controller/ManualControlDialog.pm b/lib/Slic3r/GUI/Controller/ManualControlDialog.pm index 8dcc05424..de0565255 100644 --- a/lib/Slic3r/GUI/Controller/ManualControlDialog.pm +++ b/lib/Slic3r/GUI/Controller/ManualControlDialog.pm @@ -34,7 +34,7 @@ sub new { my $btn = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); $btn->SetFont($bold ? $Slic3r::GUI::small_bold_font : $Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("$icon.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("$icon.png"), wxBITMAP_TYPE_PNG)); $btn->SetBitmapPosition($pos); EVT_BUTTON($self, $btn, $handler); $sizer->Add($btn, 1, wxEXPAND | wxALL, 0); diff --git a/lib/Slic3r/GUI/Controller/PrinterPanel.pm b/lib/Slic3r/GUI/Controller/PrinterPanel.pm index 6dba614e3..794ea4e4e 100644 --- a/lib/Slic3r/GUI/Controller/PrinterPanel.pm +++ b/lib/Slic3r/GUI/Controller/PrinterPanel.pm @@ -103,7 +103,7 @@ sub new { $serial_port_sizer->Add($self->{serial_port_combobox}, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 1); } { - $self->{btn_rescan_serial} = my $btn = Wx::BitmapButton->new($box, -1, Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), + $self->{btn_rescan_serial} = my $btn = Wx::BitmapButton->new($box, -1, Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, &Wx::wxBORDER_NONE); $btn->SetToolTipString("Rescan serial ports") if $btn->can('SetToolTipString'); @@ -127,7 +127,7 @@ sub new { { $self->{btn_disconnect} = my $btn = Wx::Button->new($box, -1, "Disconnect", wxDefaultPosition, wxDefaultSize); $btn->SetFont($Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG)); $serial_speed_sizer->Add($btn, 0, wxLEFT, 5); EVT_BUTTON($self, $btn, \&disconnect); } @@ -140,7 +140,7 @@ sub new { my $font = $btn->GetFont; $font->SetPointSize($font->GetPointSize + 2); $btn->SetFont($font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("arrow_up.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("arrow_up.png"), wxBITMAP_TYPE_PNG)); $left_sizer->Add($btn, 0, wxTOP, 15); EVT_BUTTON($self, $btn, \&connect); } @@ -160,7 +160,7 @@ sub new { { $self->{btn_manual_control} = my $btn = Wx::Button->new($box, -1, "Manual control", wxDefaultPosition, wxDefaultSize); $btn->SetFont($Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG)); $btn->Hide; $left_sizer->Add($btn, 0, wxTOP, 15); EVT_BUTTON($self, $btn, sub { @@ -348,7 +348,7 @@ sub update_serial_ports { my $cb = $self->{serial_port_combobox}; my $current = $cb->GetValue; $cb->Clear; - $cb->Append($_) for wxTheApp->scan_serial_ports; + $cb->Append($_) for Slic3r::GUI::scan_serial_ports; $cb->SetValue($current); } @@ -577,7 +577,7 @@ sub new { $btn->SetToolTipString("Delete this job from print queue") if $btn->can('SetToolTipString'); $btn->SetFont($Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG)); if ($job->printing) { $btn->Hide; } @@ -597,8 +597,8 @@ sub new { my $btn = $self->{btn_print} = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize, $button_style); $btn->SetFont($Slic3r::GUI::small_bold_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_play.png"), wxBITMAP_TYPE_PNG)); - $btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_play_blue.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_play.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_play_blue.png"), wxBITMAP_TYPE_PNG)); #$btn->SetBitmapPosition(wxRIGHT); $btn->Hide; $buttons_sizer->Add($btn, 0, wxBOTTOM, 2); @@ -616,8 +616,8 @@ sub new { if (!$job->printing || $job->paused) { $btn->Hide; } - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_pause.png"), wxBITMAP_TYPE_PNG)); - $btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_pause_blue.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_pause.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_pause_blue.png"), wxBITMAP_TYPE_PNG)); $buttons_sizer->Add($btn, 0, wxBOTTOM, 2); EVT_BUTTON($self, $btn, sub { @@ -633,8 +633,8 @@ sub new { if (!$job->printing || !$job->paused) { $btn->Hide; } - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_play.png"), wxBITMAP_TYPE_PNG)); - $btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_play_blue.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_play.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_play_blue.png"), wxBITMAP_TYPE_PNG)); $buttons_sizer->Add($btn, 0, wxBOTTOM, 2); EVT_BUTTON($self, $btn, sub { @@ -650,8 +650,8 @@ sub new { if (!$job->printing) { $btn->Hide; } - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_stop.png"), wxBITMAP_TYPE_PNG)); - $btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_stop_blue.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_stop.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_stop_blue.png"), wxBITMAP_TYPE_PNG)); $buttons_sizer->Add($btn, 0, wxBOTTOM, 2); EVT_BUTTON($self, $btn, sub { diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index ad2eca91d..a6baef8f9 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -6,6 +6,7 @@ use warnings; use utf8; use File::Basename qw(basename dirname); +use FindBin; use List::Util qw(min); use Slic3r::Geometry qw(X Y); use Wx qw(:frame :bitmap :id :misc :notebook :panel :sizer :menu :dialog :filedialog @@ -21,13 +22,14 @@ sub new { my ($class, %params) = @_; my $self = $class->SUPER::new(undef, -1, $Slic3r::FORK_NAME . ' - ' . $Slic3r::VERSION, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE); + Slic3r::GUI::set_main_frame($self); if ($^O eq 'MSWin32') { - # Load the icon either from the exe, or fron the ico file. - my $iconfile = $Slic3r::var->('..\slic3r.exe'); - $iconfile = $Slic3r::var->("Slic3r.ico") unless -f $iconfile; + # Load the icon either from the exe, or from the ico file. + my $iconfile = Slic3r::decode_path($FindBin::Bin) . '\slic3r.exe'; + $iconfile = Slic3r::var("Slic3r.ico") unless -f $iconfile; $self->SetIcon(Wx::Icon->new($iconfile, wxBITMAP_TYPE_ICO)); } else { - $self->SetIcon(Wx::Icon->new($Slic3r::var->("Slic3r_128px.png"), wxBITMAP_TYPE_PNG)); + $self->SetIcon(Wx::Icon->new(Slic3r::var("Slic3r_128px.png"), wxBITMAP_TYPE_PNG)); } # store input params @@ -69,15 +71,15 @@ sub new { # declare events EVT_CLOSE($self, sub { my (undef, $event) = @_; - if ($event->CanVeto && !$self->check_unsaved_changes) { $event->Veto; return; } - # save window size wxTheApp->save_window_pos($self, "main_frame"); - + # Save the slic3r.ini. Usually the ini file is saved from "on idle" callback, + # but in rare cases it may not have been called yet. + wxTheApp->{app_config}->save; # propagate event $event->Skip; }); @@ -91,6 +93,8 @@ sub _init_tabpanel { my ($self) = @_; $self->{tabpanel} = my $panel = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); + Slic3r::GUI::set_tab_panel($panel); + EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{tabpanel}, sub { my $panel = $self->{tabpanel}->GetCurrentPage; $panel->OnActivate if $panel->can('OnActivate'); @@ -112,7 +116,7 @@ sub _init_tabpanel { # Callback to be executed after any of the configuration fields (Perl class Slic3r::GUI::OptionsGroup::Field) change their value. $tab->on_value_change(sub { my ($opt_key, $value) = @_; - my $config = $tab->config; + my $config = $tab->{presets}->get_current_preset->config; if ($self->{plater}) { $self->{plater}->on_config_change($config); # propagate config change events to the plater $self->{plater}->on_extruders_change($value) if $opt_key eq 'extruders_count'; @@ -126,24 +130,38 @@ sub _init_tabpanel { if ($self->{plater}) { # Update preset combo boxes (Print settings, Filament, Printer) from their respective tabs. $self->{plater}->update_presets($tab_name, @_); - $self->{plater}->on_config_change($tab->config); - if ($self->{controller}) { - $self->{controller}->update_presets($tab_name, @_); + if ($tab_name eq 'printer') { + # Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. + wxTheApp->{preset_bundle}->print->update_tab_ui( + $self->{options_tabs}{'print'}->{presets_choice}, + $self->{options_tabs}{'print'}->{show_incompatible_presets}); + wxTheApp->{preset_bundle}->filament->update_tab_ui( + $self->{options_tabs}{'filament'}->{presets_choice}, + $self->{options_tabs}{'filament'}->{show_incompatible_presets}); + # Update the controller printers. + $self->{controller}->update_presets(@_) if $self->{controller}; } + $self->{plater}->on_config_change($tab->{presets}->get_current_preset->config); } }); - $tab->load_presets; + # Load the currently selected preset into the GUI, update the preset selection box. + $tab->load_current_preset; $panel->AddPage($tab, $tab->title); } + +#TODO this is an example of a Slic3r XS interface call to add a new preset editor page to the main view. +# Slic3r::GUI::create_preset_tab("print"); if ($self->{plater}) { $self->{plater}->on_select_preset(sub { - my ($group, $i) = @_; - $self->{options_tabs}{$group}->select_preset($i); + my ($group, $name) = @_; + $self->{options_tabs}{$group}->select_preset($name); }); - # load initial config - $self->{plater}->on_config_change($self->config); + my $full_config = wxTheApp->{preset_bundle}->full_config; + $self->{plater}->on_config_change($full_config); + # Show a correct number of filament fields. + $self->{plater}->on_extruders_change(int(@{$full_config->nozzle_diameter})); } } @@ -153,6 +171,9 @@ sub _init_menubar { # File menu my $fileMenu = Wx::Menu->new; { + wxTheApp->append_menu_item($fileMenu, "Open STL/OBJ/AMFâĤ\tCtrl+O", 'Open a model', sub { + $self->{plater}->add if $self->{plater}; + }, undef, undef); #'brick_add.png'); $self->_append_menu_item($fileMenu, "&Load ConfigâĤ\tCtrl+L", 'Load exported configuration file', sub { $self->load_config_file; }, undef, 'plugin_add.png'); @@ -262,13 +283,14 @@ sub _init_menubar { # \xA0 is a non-breaing space. It is entered here to spoil the automatic accelerators, # as the simple numeric accelerators spoil all numeric data entry. # The camera control accelerators are captured by 3DScene Perl module instead. - $self->_append_menu_item($self->{viewMenu}, "Iso\t\xA00" , 'Iso View' , sub { $self->select_view('iso' ); }); - $self->_append_menu_item($self->{viewMenu}, "Top\t\xA01" , 'Top View' , sub { $self->select_view('top' ); }); - $self->_append_menu_item($self->{viewMenu}, "Bottom\t\xA02" , 'Bottom View' , sub { $self->select_view('bottom' ); }); - $self->_append_menu_item($self->{viewMenu}, "Front\t\xA03" , 'Front View' , sub { $self->select_view('front' ); }); - $self->_append_menu_item($self->{viewMenu}, "Rear\t\xA04" , 'Rear View' , sub { $self->select_view('rear' ); }); - $self->_append_menu_item($self->{viewMenu}, "Left\t\xA05" , 'Left View' , sub { $self->select_view('left' ); }); - $self->_append_menu_item($self->{viewMenu}, "Right\t\xA06" , 'Right View' , sub { $self->select_view('right' ); }); + my $accel = ($^O eq 'MSWin32') ? sub { $_[0] . "\t\xA0" . $_[1] } : sub { $_[0] }; + $self->_append_menu_item($self->{viewMenu}, $accel->('Iso', '0'), 'Iso View' , sub { $self->select_view('iso' ); }); + $self->_append_menu_item($self->{viewMenu}, $accel->('Top', '1'), 'Top View' , sub { $self->select_view('top' ); }); + $self->_append_menu_item($self->{viewMenu}, $accel->('Bottom', '2'), 'Bottom View' , sub { $self->select_view('bottom' ); }); + $self->_append_menu_item($self->{viewMenu}, $accel->('Front', '3'), 'Front View' , sub { $self->select_view('front' ); }); + $self->_append_menu_item($self->{viewMenu}, $accel->('Rear', '4'), 'Rear View' , sub { $self->select_view('rear' ); }); + $self->_append_menu_item($self->{viewMenu}, $accel->('Left', '5'), 'Left View' , sub { $self->select_view('left' ); }); + $self->_append_menu_item($self->{viewMenu}, $accel->('Right', '6'), 'Right View' , sub { $self->select_view('right' ); }); } # Help menu @@ -317,6 +339,8 @@ sub _init_menubar { $menubar->Append($windowMenu, "&Window"); $menubar->Append($self->{viewMenu}, "&View") if $self->{viewMenu}; $menubar->Append($helpMenu, "&Help"); + # Add an optional debug menu. In production code, the add_debug_menu() call should do nothing. + Slic3r::GUI::add_debug_menu($menubar); $self->SetMenuBar($menubar); } } @@ -329,7 +353,6 @@ sub is_loaded { # Selection of a 3D object changed on the platter. sub on_plater_selection_changed { my ($self, $have_selection) = @_; - return if !defined $self->{object_menu}; $self->{object_menu}->Enable($_->GetId, $have_selection) for $self->{object_menu}->GetMenuItems; @@ -337,20 +360,20 @@ sub on_plater_selection_changed { # To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG". sub quick_slice { - my $self = shift; - my %params = @_; + my ($self, %params) = @_; my $progress_dialog; eval { # validate configuration - my $config = $self->config; + my $config = wxTheApp->{preset_bundle}->full_config(); $config->validate; # select input file my $input_file; - my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; if (!$params{reslice}) { - my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF/PRUSA):', $dir, "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF/PRUSA):', + wxTheApp->{app_config}->get_last_dir, "", + &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; return; @@ -372,8 +395,7 @@ sub quick_slice { $input_file = $qs_last_input_file; } my $input_file_basename = basename($input_file); - $Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file); - wxTheApp->save_settings; + wxTheApp->{app_config}->update_skein_dir(dirname($input_file)); my $print_center; { @@ -395,20 +417,19 @@ sub quick_slice { $sprint->apply_config($config); $sprint->set_model($model); - { - my $extra = $self->extra_variables; - $sprint->placeholder_parser->set($_, $extra->{$_}) for keys %$extra; - } - + # Copy the names of active presets into the placeholder parser. + wxTheApp->{preset_bundle}->export_selections_pp($sprint->placeholder_parser); + # select output file my $output_file; if ($params{reslice}) { $output_file = $qs_last_output_file if defined $qs_last_output_file; } elsif ($params{save_as}) { + # The following line may die if the output_filename_format template substitution fails. $output_file = $sprint->output_filepath; $output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/ if $params{export_svg}; my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', - wxTheApp->output_path(dirname($output_file)), + wxTheApp->{app_config}->get_last_output_dir(dirname($output_file)), basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; @@ -416,8 +437,7 @@ sub quick_slice { } $output_file = $dlg->GetPath; $qs_last_output_file = $output_file unless $params{export_svg}; - $Slic3r::GUI::Settings->{_}{last_output_path} = dirname($output_file); - wxTheApp->save_settings; + wxTheApp->{app_config}->update_last_output_dir(dirname($output_file)); $dlg->Destroy; } @@ -452,9 +472,7 @@ sub quick_slice { sub reslice_now { my ($self) = @_; - if ($self->{plater}) { - $self->{plater}->reslice; - } + $self->{plater}->reslice if $self->{plater}; } sub repair_stl { @@ -462,8 +480,9 @@ sub repair_stl { my $input_file; { - my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; - my $dialog = Wx::FileDialog->new($self, 'Select the STL file to repair:', $dir, "", &Slic3r::GUI::FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + my $dialog = Wx::FileDialog->new($self, 'Select the STL file to repair:', + wxTheApp->{app_config}->get_last_dir, "", + &Slic3r::GUI::FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; return; @@ -492,36 +511,25 @@ sub repair_stl { Slic3r::GUI::show_info($self, "Your file was repaired.", "Repair"); } -sub extra_variables { - my $self = shift; - my %extra_variables = (); - $extra_variables{"${_}_preset"} = $self->{options_tabs}{$_}->get_current_preset->name - for qw(print filament printer); - return { %extra_variables }; -} - sub export_config { my $self = shift; - - my $config = $self->config; - eval { - # validate configuration - $config->validate; - }; + # Generate a cummulative configuration for the selected print, filaments and printer. + my $config = wxTheApp->{preset_bundle}->full_config(); + # Validate the cummulative configuration. + eval { $config->validate; }; Slic3r::GUI::catch_error($self) and return; - - my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; - my $filename = $last_config ? basename($last_config) : "config.ini"; - my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $dir, $filename, + # Ask user for the file name for the config file. + my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', + $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, + $last_config ? basename($last_config) : "config.ini", &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if ($dlg->ShowModal == wxID_OK) { - my $file = $dlg->GetPath; - $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); - wxTheApp->save_settings; + my $file = ($dlg->ShowModal == wxID_OK) ? $dlg->GetPath : undef; + $dlg->Destroy; + if (defined $file) { + wxTheApp->{app_config}->update_config_dir(dirname($file)); $last_config = $file; $config->save($file); } - $dlg->Destroy; } # Load a config file containing a Print, Filament & Printer preset. @@ -529,222 +537,139 @@ sub load_config_file { my ($self, $file) = @_; if (!$file) { return unless $self->check_unsaved_changes; - my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; - my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini", - 'INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g', wxFD_OPEN | wxFD_FILE_MUST_EXIST); + my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', + $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, + "config.ini", + 'INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g', wxFD_OPEN | wxFD_FILE_MUST_EXIST); return unless $dlg->ShowModal == wxID_OK; $file = $dlg->GetPaths; $dlg->Destroy; } - for my $tab (values %{$self->{options_tabs}}) { - # Dont proceed further if the config file cannot be loaded. - return undef if ! $tab->load_config_file($file); - } - $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); - wxTheApp->save_settings; + eval { wxTheApp->{preset_bundle}->load_config_file($file); }; + # Dont proceed further if the config file cannot be loaded. + return if Slic3r::GUI::catch_error($self); + $_->load_current_preset for (values %{$self->{options_tabs}}); + wxTheApp->{app_config}->update_config_dir(dirname($file)); $last_config = $file; } sub export_configbundle { - my $self = shift; - - eval { - # validate current configuration in case it's dirty - $self->config->validate; - }; + my ($self) = @_; + return unless $self->check_unsaved_changes; + # validate current configuration in case it's dirty + eval { wxTheApp->{preset_bundle}->full_config->validate; }; Slic3r::GUI::catch_error($self) and return; - - my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; - my $filename = "Slic3r_config_bundle.ini"; - my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:', $dir, $filename, + # Ask user for a file name. + my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:', + $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, + "Slic3r_config_bundle.ini", &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if ($dlg->ShowModal == wxID_OK) { - my $file = $dlg->GetPath; - $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); - wxTheApp->save_settings; - - # leave default category empty to prevent the bundle from being parsed as a normal config file - my $ini = { _ => {} }; - $ini->{settings}{$_} = $Slic3r::GUI::Settings->{_}{$_} for qw(autocenter); - $ini->{presets} = $Slic3r::GUI::Settings->{presets}; - - foreach my $section (qw(print filament printer)) { - my %presets = wxTheApp->presets($section); - foreach my $preset_name (keys %presets) { - my $config = Slic3r::Config->load($presets{$preset_name}); - $ini->{"$section:$preset_name"} = $config->as_ini->{_}; - } - } - - Slic3r::Config->write_ini($file, $ini); - } + my $file = ($dlg->ShowModal == wxID_OK) ? $dlg->GetPath : undef; $dlg->Destroy; + if (defined $file) { + # Export the config bundle. + wxTheApp->{app_config}->update_config_dir(dirname($file)); + eval { wxTheApp->{preset_bundle}->export_configbundle($file); }; + Slic3r::GUI::catch_error($self) and return; + } } +# Loading a config bundle with an external file name used to be used +# to auto-install a config bundle on a fresh user account, +# but that behavior was not documented and likely buggy. sub load_configbundle { - my ($self, $file, $skip_no_id) = @_; - + my ($self, $file) = @_; + return unless $self->check_unsaved_changes; if (!$file) { - my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || ''; - my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $dir, "config.ini", - &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', + $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, + "config.ini", + &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); return unless $dlg->ShowModal == wxID_OK; $file = $dlg->GetPaths; $dlg->Destroy; } - $Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file); - wxTheApp->save_settings; - - # load .ini file - my $ini = Slic3r::Config->read_ini($file); - - if ($ini->{settings}) { - $Slic3r::GUI::Settings->{_}{$_} = $ini->{settings}{$_} for keys %{$ini->{settings}}; - wxTheApp->save_settings; - } - if ($ini->{presets}) { - $Slic3r::GUI::Settings->{presets} = $ini->{presets}; - wxTheApp->save_settings; - } + wxTheApp->{app_config}->update_config_dir(dirname($file)); - my $imported = 0; - INI_BLOCK: foreach my $ini_category (sort keys %$ini) { - next unless $ini_category =~ /^(print|filament|printer):(.+)$/; - my ($section, $preset_name) = ($1, $2); - my $config = Slic3r::Config->load_ini_hash($ini->{$ini_category}); - next if $skip_no_id && !$config->get($section . "_settings_id"); - - { - my %current_presets = Slic3r::GUI->presets($section); - my %current_ids = map { $_ => 1 } - grep $_, - map Slic3r::Config->load($_)->get($section . "_settings_id"), - values %current_presets; - next INI_BLOCK if exists $current_ids{$config->get($section . "_settings_id")}; - } - - $config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $section, $preset_name); - Slic3r::debugf "Imported %s preset %s\n", $section, $preset_name; - $imported++; - } + my $presets_imported = 0; + eval { $presets_imported = wxTheApp->{preset_bundle}->load_configbundle($file); }; + Slic3r::GUI::catch_error($self) and return; + + # Load the currently selected preset into the GUI, update the preset selection box. foreach my $tab (values %{$self->{options_tabs}}) { - $tab->load_presets; + $tab->load_current_preset; } - return if !$imported; - - my $message = sprintf "%d presets successfully imported.", $imported; + my $message = sprintf "%d presets successfully imported.", $presets_imported; Slic3r::GUI::show_info($self, $message); } # Load a provied DynamicConfig into the Print / Filament / Printer tabs, thus modifying the active preset. # Also update the platter with the new presets. sub load_config { - my $self = shift; - my ($config) = @_; - - foreach my $tab (values %{$self->{options_tabs}}) { - $tab->load_config($config); - } - if ($self->{plater}) { - $self->{plater}->on_config_change($config); - } + my ($self, $config) = @_; + $_->load_config($config) foreach values %{$self->{options_tabs}}; + $self->{plater}->on_config_change($config) if $self->{plater}; } sub config_wizard { - my $self = shift; - + my ($self) = @_; + # Exit wizard if there are unsaved changes and the user cancels the action. return unless $self->check_unsaved_changes; - if (my $config = Slic3r::GUI::ConfigWizard->new($self)->run) { - for my $tab (values %{$self->{options_tabs}}) { - $tab->select_default_preset; - } - $self->load_config($config); - for my $tab (values %{$self->{options_tabs}}) { - $tab->save_preset('My Settings'); + # Enumerate the profiles bundled with the Slic3r installation under resources/profiles. + my $directory = Slic3r::resources_dir() . "/profiles"; + my @profiles = (); + if (opendir(DIR, Slic3r::encode_path($directory))) { + while (my $file = readdir(DIR)) { + if ($file =~ /\.ini$/) { + $file =~ s/\.ini$//; + push @profiles, Slic3r::decode_path($file); + } } + closedir(DIR); } -} - -=head2 config - -This method collects all config values from the tabs and merges them into a single config object. - -=cut - -sub config { - my $self = shift; - - return Slic3r::Config->new_from_defaults - if !exists $self->{options_tabs}{print} - || !exists $self->{options_tabs}{filament} - || !exists $self->{options_tabs}{printer}; - - # retrieve filament presets and build a single config object for them - my $filament_config; - if (!$self->{plater} || $self->{plater}->filament_presets == 1) { - $filament_config = $self->{options_tabs}{filament}->config; - } else { - my $i = -1; - foreach my $preset_idx ($self->{plater}->filament_presets) { - $i++; - my $config; - if ($preset_idx == $self->{options_tabs}{filament}->current_preset) { - # the selected preset for this extruder is the one in the tab - # use the tab's config instead of the preset in case it is dirty - # perhaps plater shouldn't expose dirty presets at all in multi-extruder environments. - $config = $self->{options_tabs}{filament}->config; - } else { - my $preset = $self->{options_tabs}{filament}->get_preset($preset_idx); - $config = $self->{options_tabs}{filament}->get_preset_config($preset); + # Open the wizard. + if (my $config = Slic3r::GUI::ConfigWizard->new($self, \@profiles)->run) { + if (ref($config)) { + # Wizard returned a config. Add the config to each of the preset types. + for my $tab (values %{$self->{options_tabs}}) { + # Select the first visible preset, force. + $tab->select_preset(undef, 1); } - if (!$filament_config) { - $filament_config = $config->clone; - next; + # Load the config over the previously selected defaults. + $self->load_config($config); + for my $tab (values %{$self->{options_tabs}}) { + # Save the settings under a new name, select the name. + $tab->save_preset('My Settings'); } - foreach my $opt_key (@{$config->get_keys}) { - my $value = $filament_config->get($opt_key); - next unless ref $value eq 'ARRAY'; - $value->[$i] = $config->get($opt_key)->[0]; - $filament_config->set($opt_key, $value); + } else { + # Wizard returned a name of a preset bundle bundled with the installation. Unpack it. + eval { wxTheApp->{preset_bundle}->load_configbundle($directory . '/' . $config . '.ini'); }; + Slic3r::GUI::catch_error($self) and return; + # Load the currently selected preset into the GUI, update the preset selection box. + foreach my $tab (values %{$self->{options_tabs}}) { + $tab->load_current_preset; } } } - - my $config = Slic3r::Config->merge( - Slic3r::Config->new_from_defaults, - $self->{options_tabs}{print}->config, - $self->{options_tabs}{printer}->config, - $filament_config, - ); - - my $extruders_count = $self->{options_tabs}{printer}{extruders_count}; - $config->set("${_}_extruder", min($config->get("${_}_extruder"), $extruders_count)) - for qw(perimeter infill solid_infill support_material support_material_interface); - - return $config; -} - -sub filament_preset_names { - my ($self) = @_; - return map $self->{options_tabs}{filament}->get_preset($_)->name, - $self->{plater}->filament_presets; } +# This is called when closing the application, when loading a config file or when starting the config wizard +# to notify the user whether he is aware that some preset changes will be lost. sub check_unsaved_changes { my $self = shift; my @dirty = (); foreach my $tab (values %{$self->{options_tabs}}) { - push @dirty, $tab->title if $tab->is_dirty; + push @dirty, $tab->title if $tab->{presets}->current_is_dirty; } if (@dirty) { my $titles = join ', ', @dirty; my $confirm = Wx::MessageDialog->new($self, "You have unsaved changes ($titles). Discard changes and continue anyway?", 'Unsaved Presets', wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); - return ($confirm->ShowModal == wxID_YES); + return $confirm->ShowModal == wxID_YES; } return 1; @@ -765,21 +690,18 @@ sub select_view { sub _append_menu_item { my ($self, $menu, $string, $description, $cb, $id, $icon) = @_; - $id //= &Wx::NewId(); my $item = $menu->Append($id, $string, $description); $self->_set_menu_item_icon($item, $icon); - EVT_MENU($self, $id, $cb); return $item; } sub _set_menu_item_icon { my ($self, $menuItem, $icon) = @_; - # SetBitmap was not available on OS X before Wx 0.9927 if ($icon && $menuItem->can('SetBitmap')) { - $menuItem->SetBitmap(Wx::Bitmap->new($Slic3r::var->($icon), wxBITMAP_TYPE_PNG)); + $menuItem->SetBitmap(Wx::Bitmap->new(Slic3r::var($icon), wxBITMAP_TYPE_PNG)); } } @@ -787,8 +709,11 @@ sub _set_menu_item_icon { # Update the UI based on the current preferences. sub update_ui_from_settings { my ($self) = @_; - $self->{menu_item_reslice_now}->Enable(! $Slic3r::GUI::Settings->{_}{background_processing}); + $self->{menu_item_reslice_now}->Enable(! wxTheApp->{app_config}->get("background_processing")); $self->{plater}->update_ui_from_settings if ($self->{plater}); + for my $tab_name (qw(print filament printer)) { + $self->{options_tabs}{$tab_name}->update_ui_from_settings; + } } 1; diff --git a/lib/Slic3r/GUI/Notifier.pm b/lib/Slic3r/GUI/Notifier.pm index eb548d014..54afffae0 100644 --- a/lib/Slic3r/GUI/Notifier.pm +++ b/lib/Slic3r/GUI/Notifier.pm @@ -6,7 +6,7 @@ use Moo; has 'growler' => (is => 'rw'); -my $icon = $Slic3r::var->("Slic3r.png"); +my $icon = Slic3r::var("Slic3r.png"); sub BUILD { my ($self) = @_; diff --git a/lib/Slic3r/GUI/OptionsGroup/Field.pm b/lib/Slic3r/GUI/OptionsGroup/Field.pm index 531e13a5d..4ef2ce2ca 100644 --- a/lib/Slic3r/GUI/OptionsGroup/Field.pm +++ b/lib/Slic3r/GUI/OptionsGroup/Field.pm @@ -124,6 +124,10 @@ sub BUILD { }); } +sub get_value { + my ($self) = @_; + return $self->wxWindow->GetValue ? 1 : 0; +} package Slic3r::GUI::OptionsGroup::Field::SpinCtrl; use Moo; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 0f56da7ca..f687dcec4 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -46,15 +46,14 @@ use constant PROCESS_DELAY => 0.5 * 1000; # milliseconds my $PreventListEvents = 0; sub new { - my $class = shift; - my ($parent) = @_; + my ($class, $parent) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - $self->{config} = Slic3r::Config->new_from_defaults(qw( + $self->{config} = Slic3r::Config::new_from_defaults_keys([qw( bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height serial_port serial_speed octoprint_host octoprint_apikey nozzle_diameter single_extruder_multi_material wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe extruder_colour filament_colour - )); + )]); # C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm $self->{model} = Slic3r::Model->new; # C++ Slic3r::Print with Perl extensions in Slic3r/Print.pm @@ -64,12 +63,7 @@ sub new { $self->{print}->set_status_cb(sub { my ($percent, $message) = @_; - - if ($Slic3r::have_threads) { - Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROGRESS_BAR_EVENT, shared_clone([$percent, $message]))); - } else { - $self->on_progress_event($percent, $message); - } + Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROGRESS_BAR_EVENT, shared_clone([$percent, $message]))); }); # Initialize preview notebook @@ -105,6 +99,7 @@ sub new { $self->{canvas3D}->set_on_select_object($on_select_object); $self->{canvas3D}->set_on_double_click($on_double_click); $self->{canvas3D}->set_on_right_click(sub { $on_right_click->($self->{canvas3D}, @_); }); + $self->{canvas3D}->set_on_arrange(sub { $self->arrange }); $self->{canvas3D}->set_on_rotate_object_left(sub { $self->rotate(-45, Z, 'relative') }); $self->{canvas3D}->set_on_rotate_object_right(sub { $self->rotate( 45, Z, 'relative') }); $self->{canvas3D}->set_on_scale_object_uniformly(sub { $self->changescale(undef) }); @@ -120,7 +115,7 @@ sub new { $self->GetFrame->{options_tabs}{print}->load_config($cfg); }); $self->{canvas3D}->set_on_model_update(sub { - if ($Slic3r::GUI::Settings->{_}{background_processing}) { + if (wxTheApp->{app_config}->get("background_processing")) { $self->schedule_background_process; } else { # Hide the print info box, it is no more valid. @@ -166,22 +161,22 @@ sub new { if (!&Wx::wxMSW) { Wx::ToolTip::Enable(1); $self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL); - $self->{htoolbar}->AddTool(TB_ADD, "AddâĤ", Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_REMOVE, "Delete", Wx::Bitmap->new($Slic3r::var->("brick_delete.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_RESET, "Delete All", Wx::Bitmap->new($Slic3r::var->("cross.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_ARRANGE, "Arrange", Wx::Bitmap->new($Slic3r::var->("bricks.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_ADD, "AddâĤ", Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_REMOVE, "Delete", Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_RESET, "Delete All", Wx::Bitmap->new(Slic3r::var("cross.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_ARRANGE, "Arrange", Wx::Bitmap->new(Slic3r::var("bricks.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; - $self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new($Slic3r::var->("add.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_FEWER, "Fewer", Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_FEWER, "Fewer", Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; - $self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new($Slic3r::var->("arrow_rotate_anticlockwise.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_SCALE, "ScaleâĤ", Wx::Bitmap->new($Slic3r::var->("arrow_out.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new($Slic3r::var->("shape_ungroup.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_CUT, "CutâĤ", Wx::Bitmap->new($Slic3r::var->("package.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new(Slic3r::var("arrow_rotate_anticlockwise.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_SCALE, "ScaleâĤ", Wx::Bitmap->new(Slic3r::var("arrow_out.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_CUT, "CutâĤ", Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; - $self->{htoolbar}->AddTool(TB_SETTINGS, "SettingsâĤ", Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_LAYER_EDITING, 'Layer Editing', Wx::Bitmap->new($Slic3r::var->("variable_layer_height.png"), wxBITMAP_TYPE_PNG), wxNullBitmap, 1, 0, 'Layer Editing'); + $self->{htoolbar}->AddTool(TB_SETTINGS, "SettingsâĤ", Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_LAYER_EDITING, 'Layer Editing', Wx::Bitmap->new(Slic3r::var("variable_layer_height.png"), wxBITMAP_TYPE_PNG), wxNullBitmap, 1, 0, 'Layer Editing'); } else { my %tbar_buttons = ( add => "AddâĤ", @@ -256,7 +251,7 @@ sub new { settings cog.png ); for (grep $self->{"btn_$_"}, keys %icons) { - $self->{"btn_$_"}->SetBitmap(Wx::Bitmap->new($Slic3r::var->($icons{$_}), wxBITMAP_TYPE_PNG)); + $self->{"btn_$_"}->SetBitmap(Wx::Bitmap->new(Slic3r::var($icons{$_}), wxBITMAP_TYPE_PNG)); } $self->selection_changed(0); $self->object_list_changed; @@ -331,7 +326,7 @@ sub new { $self->on_process_completed($event->GetData); }); - if ($Slic3r::have_threads) { + { my $timer_id = Wx::NewId(); $self->{apply_config_timer} = Wx::Timer->new($self, $timer_id); EVT_TIMER($self, $timer_id, sub { @@ -366,20 +361,17 @@ sub new { # once a printer preset with multiple extruders is activated. # $self->{preset_choosers}{$group}[$idx] $self->{preset_choosers} = {}; - # Boolean indicating whether the '- default -' preset is shown by the combo box. - $self->{preset_choosers_default_suppressed} = {}; for my $group (qw(print filament printer)) { my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); $text->SetFont($Slic3r::GUI::small_font); my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down(0, @_); } ); $self->{preset_choosers}{$group} = [$choice]; - $self->{preset_choosers_default_suppressed}{$group} = 0; # setup the listener EVT_COMBOBOX($choice, $choice, sub { my ($choice) = @_; wxTheApp->CallAfter(sub { - $self->_on_select_preset($group, $choice); + $self->_on_select_preset($group, $choice, 0); }); }); $presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); @@ -414,7 +406,7 @@ sub new { $self->{"object_info_$field"} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $self->{"object_info_$field"}->SetFont($Slic3r::GUI::small_font); if ($field eq 'manifold') { - $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($self, -1, Wx::Bitmap->new($Slic3r::var->("error.png"), wxBITMAP_TYPE_PNG)); + $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($self, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); $self->{object_info_manifold_warning_icon}->Hide; my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL); @@ -438,6 +430,7 @@ sub new { $grid_sizer->AddGrowableCol(3, 1); $print_info_sizer->Add($grid_sizer, 0, wxEXPAND); my @info = ( + fil_m => "Used Filament (m)", fil_mm3 => "Used Filament (mm^3)", fil_g => "Used Filament (g)", cost => "Cost", @@ -453,7 +446,6 @@ sub new { $self->{"print_info_$field"}->SetFont($Slic3r::GUI::small_font); $grid_sizer->Add($self->{"print_info_$field"}, 0); } - } my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL); @@ -505,30 +497,26 @@ sub on_select_preset { $self->{on_select_preset} = $cb; } +# Called from the platter combo boxes selecting the active print, filament or printer. sub _on_select_preset { - my $self = shift; - my ($group, $choice) = @_; - + my ($self, $group, $choice, $idx) = @_; # If user changed a filament preset and the selected machine is equipped with multiple extruders, # there are multiple filament selection combo boxes shown at the platter. In that case # don't propagate the filament selection changes to the tab. - my $default_suppressed = $self->{preset_choosers_default_suppressed}{$group}; + if ($group eq 'filament') { + wxTheApp->{preset_bundle}->set_filament_preset($idx, $choice->GetStringSelection); + } if ($group eq 'filament' && @{$self->{preset_choosers}{filament}} > 1) { - # Indices of the filaments selected. - my @filament_presets = $self->filament_presets; - $Slic3r::GUI::Settings->{presets}{filament} = $choice->GetString($filament_presets[0] - $default_suppressed) . ".ini"; - $Slic3r::GUI::Settings->{presets}{"filament_${_}"} = $choice->GetString($filament_presets[$_] - $default_suppressed) - for 1 .. $#filament_presets; - wxTheApp->save_settings; - $self->update_filament_colors_preview($choice); + wxTheApp->{preset_bundle}->update_platter_filament_ui($idx, $choice); } else { # call GetSelection() in scalar context as it's context-aware - $self->{on_select_preset}->($group, scalar($choice->GetSelection) + $default_suppressed) + $self->{on_select_preset}->($group, $choice->GetStringSelection) if $self->{on_select_preset}; } - + # Synchronize config.ini with the current selections. + wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config}); # get new config and generate on_config_change() event for updating plater and other things - $self->on_config_change($self->GetFrame->config); + $self->on_config_change(wxTheApp->{preset_bundle}->full_config); } sub on_layer_editing_toggled { @@ -558,8 +546,8 @@ sub GetFrame { sub update_ui_from_settings { my ($self) = @_; - if (defined($self->{btn_reslice}) && $self->{buttons_sizer}->IsShown($self->{btn_reslice}) != (! $Slic3r::GUI::Settings->{_}{background_processing})) { - $self->{buttons_sizer}->Show($self->{btn_reslice}, ! $Slic3r::GUI::Settings->{_}{background_processing}); + if (defined($self->{btn_reslice}) && $self->{buttons_sizer}->IsShown($self->{btn_reslice}) != (! wxTheApp->{app_config}->get("background_processing"))) { + $self->{buttons_sizer}->Show($self->{btn_reslice}, ! wxTheApp->{app_config}->get("background_processing")); $self->{buttons_sizer}->Layout; } } @@ -573,128 +561,41 @@ sub update_ui_from_settings # For Print settings and Printer, synchronize the selection index with their tabs. # For Filament, synchronize the selection index for a single extruder printer only, otherwise keep the selection. sub update_presets { - my $self = shift; - # $presets: one of qw(print filament printer) - # $selected: index of the selected preset in the array. This may not correspond - # with the index of selection in the UI element, where not all items are displayed. - my ($group, $presets, $default_suppressed, $selected, $is_dirty) = @_; - - my @choosers = @{ $self->{preset_choosers}{$group} }; - my $choice_idx = 0; - foreach my $choice (@choosers) { - if ($group eq 'filament' && @choosers > 1) { - # if we have more than one filament chooser, keep our selection - # instead of importing the one from the tab - $selected = $choice->GetSelection + $self->{preset_choosers_default_suppressed}{$group}; - $is_dirty = 0; - } - $choice->Clear; - foreach my $preset (@$presets) { - next if ($preset->default && $default_suppressed); - my $bitmap; - if ($group eq 'filament') { - $bitmap = Wx::Bitmap->new($Slic3r::var->("spool.png"), wxBITMAP_TYPE_PNG); - } elsif ($group eq 'print') { - $bitmap = Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG); - } elsif ($group eq 'printer') { - $bitmap = Wx::Bitmap->new($Slic3r::var->("printer_empty.png"), wxBITMAP_TYPE_PNG); - } - $choice->AppendString($preset->name, $bitmap); - } - - if ($selected <= $#$presets) { - my $idx = $selected - $default_suppressed; - if ($idx >= 0) { - if ($is_dirty) { - $choice->SetString($idx, $choice->GetString($idx) . " (modified)"); - } - # call SetSelection() only after SetString() otherwise the new string - # won't be picked up as the visible string - $choice->SetSelection($idx); - } + # $group: one of qw(print filament printer) + # $presets: PresetCollection + my ($self, $group, $presets) = @_; + my @choosers = @{$self->{preset_choosers}{$group}}; + if ($group eq 'filament') { + my $choice_idx = 0; + if (int(@choosers) == 1) { + # Single filament printer, synchronize the filament presets. + wxTheApp->{preset_bundle}->set_filament_preset(0, wxTheApp->{preset_bundle}->filament->get_selected_preset->name); } - $choice_idx += 1; - } - - $self->{preset_choosers_default_suppressed}{$group} = $default_suppressed; - - wxTheApp->CallAfter(sub { $self->update_filament_colors_preview }) if $group eq 'filament' || $group eq 'printer'; -} - -# Update the color icon in front of each filament selection on the platter. -# If the extruder has a preview color assigned, apply the extruder color to the active selection. -# Always apply the filament color to the non-active selections. -sub update_filament_colors_preview { - my ($self, $extruder_idx) = shift; - - my @choosers = @{$self->{preset_choosers}{filament}}; - - if (ref $extruder_idx) { - # $extruder_idx is the chooser. - foreach my $chooser (@choosers) { - if ($extruder_idx == $chooser) { - $extruder_idx = $chooser; - last; - } + foreach my $choice (@choosers) { + wxTheApp->{preset_bundle}->update_platter_filament_ui($choice_idx, $choice); + $choice_idx += 1; } - } - - my @extruder_colors = @{$self->{config}->extruder_colour}; - - my @extruder_list; - if (defined $extruder_idx) { - @extruder_list = ($extruder_idx); - } else { - # Collect extruder indices. - @extruder_list = (0..$#extruder_colors); - } - - my $filament_tab = $self->GetFrame->{options_tabs}{filament}; - my $presets = $filament_tab->{presets}; - my $default_suppressed = $filament_tab->{default_suppressed}; - - foreach my $extruder_idx (@extruder_list) { - my $chooser = $choosers[$extruder_idx]; - my $extruder_color = $self->{config}->extruder_colour->[$extruder_idx]; - my $preset_idx = 0; - my $selection_idx = $chooser->GetSelection; - foreach my $preset (@$presets) { - my $bitmap; - if ($preset->default) { - next if $default_suppressed; - } else { - # Assign an extruder color to the selected item if the extruder color is defined. - my $filament_rgb = $preset->config(['filament_colour'])->filament_colour->[0]; - my $extruder_rgb = ($preset_idx == $selection_idx && $extruder_color =~ m/^#[[:xdigit:]]{6}/) ? $extruder_color : $filament_rgb; - $filament_rgb =~ s/^#//; - $extruder_rgb =~ s/^#//; - my $image = Wx::Image->new(24,16); - if ($filament_rgb ne $extruder_rgb) { - my @rgb = unpack 'C*', pack 'H*', $extruder_rgb; - $image->SetRGB(Wx::Rect->new(0,0,16,16), @rgb); - @rgb = unpack 'C*', pack 'H*', $filament_rgb; - $image->SetRGB(Wx::Rect->new(16,0,8,16), @rgb); - } else { - my @rgb = unpack 'C*', pack 'H*', $filament_rgb; - $image->SetRGB(Wx::Rect->new(0,0,24,16), @rgb); - } - $bitmap = Wx::Bitmap->new($image); - } - $chooser->SetItemBitmap($preset_idx, $bitmap) if $bitmap; - $preset_idx += 1; + } elsif ($group eq 'print') { + wxTheApp->{preset_bundle}->print->update_platter_ui($choosers[0]); + } elsif ($group eq 'printer') { + # Update the print choosers to only contain the compatible presets, update the dirty flags. + wxTheApp->{preset_bundle}->print->update_platter_ui($self->{preset_choosers}{print}->[0]); + # Update the printer choosers, update the dirty flags. + wxTheApp->{preset_bundle}->printer->update_platter_ui($choosers[0]); + # Update the filament choosers to only contain the compatible presets, update the color preview, + # update the dirty flags. + my $choice_idx = 0; + foreach my $choice (@{$self->{preset_choosers}{filament}}) { + wxTheApp->{preset_bundle}->update_platter_filament_ui($choice_idx, $choice); + $choice_idx += 1; } } -} - -# Return a vector of indices of filaments selected by the $self->{preset_choosers}{filament} combo boxes. -sub filament_presets { - my $self = shift; - # force scalar context for GetSelection() as it's context-aware - return map scalar($_->GetSelection) + $self->{preset_choosers_default_suppressed}{filament}, @{ $self->{preset_choosers}{filament} }; + # Synchronize config.ini with the current selections. + wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config}); } sub add { - my $self = shift; + my ($self) = @_; my @input_files = wxTheApp->open_model($self); $self->load_files(\@input_files); } @@ -759,8 +660,7 @@ sub load_files { } # Note the current directory for the file open dialog. - $Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_files->[-1]); - wxTheApp->save_settings; + wxTheApp->{app_config}->update_skein_dir(dirname($input_files->[-1])); $process_dialog->Destroy; $self->statusbar->SetStatusText("Loaded " . join(',', @loaded_files)); @@ -808,7 +708,7 @@ sub load_model_objects { } # if user turned autocentering off, automatic arranging would disappoint them - if (!$Slic3r::GUI::Settings->{_}{autocenter}) { + if (! wxTheApp->{app_config}->get("autocenter")) { $need_arrange = 0; } @@ -921,7 +821,7 @@ sub increase { # only autoarrange if user has autocentering enabled $self->stop_background_process; - if ($Slic3r::GUI::Settings->{_}{autocenter}) { + if (wxTheApp->{app_config}->get("autocenter")) { $self->arrange; } else { $self->update; @@ -1164,7 +1064,7 @@ sub arrange { $self->pause_background_process; my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape); - my $success = $self->{model}->arrange_objects($self->GetFrame->config->min_object_distance, $bb); + my $success = $self->{model}->arrange_objects(wxTheApp->{preset_bundle}->full_config->min_object_distance, $bb); # ignore arrange failures on purpose: user has visual feedback and we don't need to warn him # when parts don't fit in print bed @@ -1225,7 +1125,7 @@ sub async_apply_config { $self->pause_background_process; # apply new config - my $invalidated = $self->{print}->apply_config($self->GetFrame->config); + my $invalidated = $self->{print}->apply_config(wxTheApp->{preset_bundle}->full_config); # Just redraw the 3D canvas without reloading the scene. # $self->{canvas3D}->Refresh if ($invalidated && $self->{canvas3D}->layer_editing_enabled); @@ -1234,7 +1134,7 @@ sub async_apply_config { # Hide the slicing results if the current slicing status is no more valid. $self->{"print_info_box_show"}->(0) if $invalidated; - if ($Slic3r::GUI::Settings->{_}{background_processing}) { + if (wxTheApp->{app_config}->get("background_processing")) { if ($invalidated) { # kill current thread if any $self->stop_background_process; @@ -1256,7 +1156,6 @@ sub async_apply_config { sub start_background_process { my ($self) = @_; - return if !$Slic3r::have_threads; return if !@{$self->{objects}}; return if $self->{process_thread}; @@ -1267,7 +1166,7 @@ sub start_background_process { # don't start process thread if config is not valid eval { # this will throw errors if config is not valid - $self->GetFrame->config->validate; + wxTheApp->{preset_bundle}->full_config->validate; $self->{print}->validate; }; if ($@) { @@ -1275,11 +1174,8 @@ sub start_background_process { return; } - # apply extra variables - { - my $extra = $self->GetFrame->extra_variables; - $self->{print}->placeholder_parser->set($_, $extra->{$_}) for keys %$extra; - } + # Copy the names of active presets into the placeholder parser. + wxTheApp->{preset_bundle}->export_selections_pp($self->{print}->placeholder_parser); # start thread @_ = (); @@ -1350,7 +1246,7 @@ sub reslice { # explicitly cancel a previous thread and start a new one. my ($self) = @_; # Don't reslice if export of G-code or sending to OctoPrint is running. - if ($Slic3r::have_threads && ! defined($self->{export_gcode_output_file}) && ! defined($self->{send_gcode_file})) { + if (! defined($self->{export_gcode_output_file}) && ! defined($self->{send_gcode_file})) { # Stop the background processing threads, stop the async update timer. $self->stop_background_process; # Rather perform one additional unnecessary update of the print object instead of skipping a pending async update. @@ -1379,66 +1275,57 @@ sub export_gcode { # (we assume that if it is running, config is valid) eval { # this will throw errors if config is not valid - $self->GetFrame->config->validate; + wxTheApp->{preset_bundle}->full_config->validate; $self->{print}->validate; }; Slic3r::GUI::catch_error($self) and return; # apply config and validate print - my $config = $self->GetFrame->config; + my $config = wxTheApp->{preset_bundle}->full_config; eval { # this will throw errors if config is not valid $config->validate; $self->{print}->apply_config($config); $self->{print}->validate; }; - if (!$Slic3r::have_threads) { - Slic3r::GUI::catch_error($self) and return; - } + Slic3r::GUI::catch_error($self) and return; # select output file if ($output_file) { - $self->{export_gcode_output_file} = $self->{print}->output_filepath($output_file); + $self->{export_gcode_output_file} = eval { $self->{print}->output_filepath($output_file) }; + Slic3r::GUI::catch_error($self) and return; } else { - my $default_output_file = $self->{print}->output_filepath($main::opt{output} // ''); - my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', wxTheApp->output_path(dirname($default_output_file)), + my $default_output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') }; + Slic3r::GUI::catch_error($self) and return; + my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', + wxTheApp->{app_config}->get_last_output_dir(dirname($default_output_file)), basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return; } my $path = $dlg->GetPath; - $Slic3r::GUI::Settings->{_}{last_output_path} = dirname($path); - wxTheApp->save_settings; + wxTheApp->{app_config}->update_last_output_dir(dirname($path)); $self->{export_gcode_output_file} = $path; $dlg->Destroy; } $self->statusbar->StartBusy; - if ($Slic3r::have_threads) { - $self->statusbar->SetCancelCallback(sub { - $self->stop_background_process; - $self->statusbar->SetStatusText("Export cancelled"); - $self->{export_gcode_output_file} = undef; - $self->{send_gcode_file} = undef; - - # this updates buttons status - $self->object_list_changed; - }); + $self->statusbar->SetCancelCallback(sub { + $self->stop_background_process; + $self->statusbar->SetStatusText("Export cancelled"); + $self->{export_gcode_output_file} = undef; + $self->{send_gcode_file} = undef; - # start background process, whose completion event handler - # will detect $self->{export_gcode_output_file} and proceed with export - $self->start_background_process; - } else { - eval { - $self->{print}->process; - $self->{print}->export_gcode(output_file => $self->{export_gcode_output_file}); - }; - my $result = !Slic3r::GUI::catch_error($self); - $self->on_export_completed($result); - } + # this updates buttons status + $self->object_list_changed; + }); + + # start background process, whose completion event handler + # will detect $self->{export_gcode_output_file} and proceed with export + $self->start_background_process; # this updates buttons status $self->object_list_changed; @@ -1542,6 +1429,7 @@ sub on_export_completed { $self->{"print_info_fil_g"}->SetLabel(sprintf("%.2f" , $self->{print}->total_weight)); $self->{"print_info_fil_mm3"}->SetLabel(sprintf("%.2f" , $self->{print}->total_extruded_volume)); $self->{"print_info_time"}->SetLabel(sprintf("%.2f" , $self->{print}->estimated_print_time)); + $self->{"print_info_fil_m"}->SetLabel(sprintf("%.2f" , $self->{print}->total_used_filament / 1000)); $self->{"print_info_box_show"}->(1); # this updates buttons status @@ -1551,14 +1439,12 @@ sub on_export_completed { sub do_print { my ($self) = @_; - my $printer_tab = $self->GetFrame->{options_tabs}{printer}; - my $printer_name = $printer_tab->get_current_preset->name; - my $controller = $self->GetFrame->{controller}; - my $printer_panel = $controller->add_printer($printer_name, $printer_tab->config); + my $printer_preset = wxTheApp->{preset_bundle}->printer->get_edited_preset; + my $printer_panel = $controller->add_printer($printer_preset->name, $printer_preset->config); my $filament_stats = $self->{print}->filament_stats; - my @filament_names = $self->GetFrame->filament_preset_names; + my @filament_names = wxTheApp->{preset_bundle}->filament_presets; $filament_stats = { map { $filament_names[$_] => $filament_stats->{$_} } keys %$filament_stats }; $printer_panel->load_print_job($self->{print_file}, $filament_stats); @@ -1599,11 +1485,11 @@ sub send_gcode { } sub export_stl { - my $self = shift; - + my ($self) = @_; return if !@{$self->{objects}}; - + # Ask user for a file name to write into. my $output_file = $self->_get_export_file('STL') or return; + # Store a binary STL. $self->{model}->store_stl($output_file, 1); $self->statusbar->SetStatusText("STL file exported to $output_file"); } @@ -1615,6 +1501,7 @@ sub reload_from_disk { return if !defined $obj_idx; my $model_object = $self->{model}->objects->[$obj_idx]; + #FIXME convert to local file encoding return if !$model_object->input_file || !-e $model_object->input_file; @@ -1625,59 +1512,55 @@ sub reload_from_disk { my $o = $self->{model}->objects->[$new_obj_idx]; $o->clear_instances; $o->add_instance($_) for @{$model_object->instances}; + #$o->invalidate_bounding_box; if ($o->volumes_count == $model_object->volumes_count) { for my $i (0..($o->volumes_count-1)) { $o->get_volume($i)->config->apply($model_object->get_volume($i)->config); } } + #FIXME restore volumes and their configs, layer_height_ranges, layer_height_profile, layer_height_profile_valid, } $self->remove($obj_idx); } sub export_object_stl { - my $self = shift; - + my ($self) = @_; my ($obj_idx, $object) = $self->selected_object; return if !defined $obj_idx; - my $model_object = $self->{model}->objects->[$obj_idx]; - + # Ask user for a file name to write into. my $output_file = $self->_get_export_file('STL') or return; $model_object->mesh->write_binary($output_file); $self->statusbar->SetStatusText("STL file exported to $output_file"); } sub export_amf { - my $self = shift; - + my ($self) = @_; return if !@{$self->{objects}}; - + # Ask user for a file name to write into. my $output_file = $self->_get_export_file('AMF') or return; $self->{model}->store_amf($output_file); $self->statusbar->SetStatusText("AMF file exported to $output_file"); } +# Ask user to select an output file for a given file format (STl, AMF, 3MF). +# Propose a default file name based on the 'output_filename_format' configuration value. sub _get_export_file { - my $self = shift; - my ($format) = @_; - + my ($self, $format) = @_; my $suffix = $format eq 'STL' ? '.stl' : '.amf.xml'; - - my $output_file = $main::opt{output}; - { - $output_file = $self->{print}->output_filepath($output_file); - $output_file =~ s/\.[gG][cC][oO][dD][eE]$/$suffix/; - my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file), - basename($output_file), &Slic3r::GUI::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if ($dlg->ShowModal != wxID_OK) { - $dlg->Destroy; - return undef; - } - $output_file = $dlg->GetPath; + my $output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') }; + Slic3r::GUI::catch_error($self) and return undef; + $output_file =~ s/\.[gG][cC][oO][dD][eE]$/$suffix/; + my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file), + basename($output_file), &Slic3r::GUI::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; + return undef; } + $output_file = $dlg->GetPath; + $dlg->Destroy; return $output_file; } @@ -1691,7 +1574,7 @@ sub reset_thumbnail { sub update { my ($self, $force_autocenter) = @_; - if ($Slic3r::GUI::Settings->{_}{autocenter} || $force_autocenter) { + if (wxTheApp->{app_config}->get("autocenter") || $force_autocenter) { $self->{model}->center_instances_around_point($self->bed_centerf); } @@ -1714,11 +1597,13 @@ sub update { } # When a number of extruders changes, the UI needs to be updated to show a single filament selection combo box per extruder. +# Also the wxTheApp->{preset_bundle}->filament_presets needs to be resized accordingly +# and some reasonable default has to be selected for the additional extruders. sub on_extruders_change { my ($self, $num_extruders) = @_; - my $choices = $self->{preset_choosers}{filament}; - while (@$choices < $num_extruders) { + + while (int(@$choices) < $num_extruders) { # copy strings from first choice my @presets = $choices->[0]->GetStrings; @@ -1727,25 +1612,20 @@ sub on_extruders_change { my $extruder_idx = scalar @$choices; EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down($extruder_idx, @_); } ); push @$choices, $choice; - # copy icons from first choice $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; - # insert new choice into sizer $self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0); $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); - # setup the listener EVT_COMBOBOX($choice, $choice, sub { my ($choice) = @_; wxTheApp->CallAfter(sub { - $self->_on_select_preset('filament', $choice); + $self->_on_select_preset('filament', $choice, $extruder_idx); }); }); - # initialize selection - my $i = first { $choice->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets; - $choice->SetSelection($i || 0); + wxTheApp->{preset_bundle}->update_platter_filament_ui($extruder_idx, $choice); } # remove unused choices if any @@ -1759,8 +1639,7 @@ sub on_extruders_change { } sub on_config_change { - my $self = shift; - my ($config) = @_; + my ($self, $config) = @_; my $update_scheduled; foreach my $opt_key (@{$self->{config}->diff($config)}) { @@ -1817,7 +1696,6 @@ sub on_config_change { sub list_item_deselected { my ($self, $event) = @_; return if $PreventListEvents; - if ($self->{list}->GetFirstSelected == -1) { $self->select_object(undef); $self->{canvas}->Refresh; @@ -1829,7 +1707,6 @@ sub list_item_deselected { sub list_item_selected { my ($self, $event) = @_; return if $PreventListEvents; - my $obj_idx = $event->GetIndex; $self->select_object($obj_idx); $self->{canvas}->Refresh; @@ -1861,19 +1738,18 @@ sub filament_color_box_lmouse_down my $dialog = Wx::ColourDialog->new($self->GetFrame, $data); if ($dialog->ShowModal == wxID_OK) { my $cfg = Slic3r::Config->new; - my $colors = $self->GetFrame->config->get('extruder_colour'); + my $colors = wxTheApp->{preset_bundle}->full_config->get('extruder_colour'); $colors->[$extruder_idx] = $dialog->GetColourData->GetColour->GetAsString(wxC2S_HTML_SYNTAX); $cfg->set('extruder_colour', $colors); $self->GetFrame->{options_tabs}{printer}->load_config($cfg); - $self->update_filament_colors_preview($extruder_idx); + wxTheApp->{preset_bundle}->update_platter_filament_ui($extruder_idx, $combobox); } $dialog->Destroy(); } } sub object_cut_dialog { - my $self = shift; - my ($obj_idx) = @_; + my ($self, $obj_idx) = @_; if (!defined $obj_idx) { ($obj_idx, undef) = $self->selected_object; @@ -1898,23 +1774,20 @@ sub object_cut_dialog { } sub object_settings_dialog { - my $self = shift; - my ($obj_idx) = @_; - - if (!defined $obj_idx) { - ($obj_idx, undef) = $self->selected_object; - } + my ($self, $obj_idx) = @_; + ($obj_idx, undef) = $self->selected_object if !defined $obj_idx; my $model_object = $self->{model}->objects->[$obj_idx]; # validate config before opening the settings dialog because # that dialog can't be closed if validation fails, but user # can't fix any error which is outside that dialog - return unless $self->validate_config; + eval { wxTheApp->{preset_bundle}->full_config->validate; }; + return if Slic3r::GUI::catch_error($_[0]); my $dlg = Slic3r::GUI::Plater::ObjectSettingsDialog->new($self, object => $self->{objects}[$obj_idx], model_object => $model_object, - config => $self->GetFrame->config, + config => wxTheApp->{preset_bundle}->full_config, ); $self->pause_background_process; $dlg->ShowModal; @@ -1965,9 +1838,9 @@ sub object_list_changed { for grep $self->{"btn_$_"}, qw(reslice export_gcode print send_gcode); } +# Selection of an active 3D object changed. sub selection_changed { - my $self = shift; - + my ($self) = @_; my ($obj_idx, $object) = $self->selected_object; my $have_sel = defined $obj_idx; @@ -2028,7 +1901,6 @@ sub select_object { $_->selected(0) for @{ $self->{objects} }; if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); - # We use this flag to avoid circular event handling # Select() happens to fire a wxEVT_LIST_ITEM_SELECTED on Windows, # whose event handler calls this method again and again and again @@ -2042,26 +1914,13 @@ sub select_object { } sub selected_object { - my $self = shift; - + my ($self) = @_; my $obj_idx = first { $self->{objects}[$_]->selected } 0..$#{ $self->{objects} }; - return undef if !defined $obj_idx; - return ($obj_idx, $self->{objects}[$obj_idx]), -} - -sub validate_config { - my $self = shift; - - eval { - $self->GetFrame->config->validate; - }; - return 0 if Slic3r::GUI::catch_error($self); - return 1; + return defined $obj_idx ? ($obj_idx, $self->{objects}[$obj_idx]) : undef; } sub statusbar { - my $self = shift; - return $self->GetFrame->{statusbar}; + return $_[0]->GetFrame->{statusbar}; } sub object_menu { @@ -2069,23 +1928,24 @@ sub object_menu { my $frame = $self->GetFrame; my $menu = Wx::Menu->new; - $frame->_append_menu_item($menu, "Delete\t\xA0Del", 'Remove the selected object', sub { + my $accel = ($^O eq 'MSWin32') ? sub { $_[0] . "\t\xA0" . $_[1] } : sub { $_[0] }; + $frame->_append_menu_item($menu, $accel->('Delete', 'Del'), 'Remove the selected object', sub { $self->remove; }, undef, 'brick_delete.png'); - $frame->_append_menu_item($menu, "Increase copies\t\xA0+", 'Place one more copy of the selected object', sub { + $frame->_append_menu_item($menu, $accel->('Increase copies', '+'), 'Place one more copy of the selected object', sub { $self->increase; }, undef, 'add.png'); - $frame->_append_menu_item($menu, "Decrease copies\t\xA0-", 'Remove one copy of the selected object', sub { + $frame->_append_menu_item($menu, $accel->('Decrease copies', '-'), 'Remove one copy of the selected object', sub { $self->decrease; }, undef, 'delete.png'); $frame->_append_menu_item($menu, "Set number of copiesâĤ", 'Change the number of copies of the selected object', sub { $self->set_number_of_copies; }, undef, 'textfield.png'); $menu->AppendSeparator(); - $frame->_append_menu_item($menu, "Rotate 45° clockwise\t\xA0l", 'Rotate the selected object by 45° clockwise', sub { + $frame->_append_menu_item($menu, $accel->('Rotate 45° clockwise', 'l'), 'Rotate the selected object by 45° clockwise', sub { $self->rotate(-45, Z, 'relative'); }, undef, 'arrow_rotate_clockwise.png'); - $frame->_append_menu_item($menu, "Rotate 45° counter-clockwise\t\xA0r", 'Rotate the selected object by 45° counter-clockwise', sub { + $frame->_append_menu_item($menu, $accel->('Rotate 45° counter-clockwise', 'r'), 'Rotate the selected object by 45° counter-clockwise', sub { $self->rotate(+45, Z, 'relative'); }, undef, 'arrow_rotate_anticlockwise.png'); @@ -2118,7 +1978,7 @@ sub object_menu { my $scaleMenu = Wx::Menu->new; my $scaleMenuItem = $menu->AppendSubMenu($scaleMenu, "Scale", 'Scale the selected object along a single axis'); $frame->_set_menu_item_icon($scaleMenuItem, 'arrow_out.png'); - $frame->_append_menu_item($scaleMenu, "UniformlyâĤ\t\xA0s", 'Scale the selected object along the XYZ axes', sub { + $frame->_append_menu_item($scaleMenu, $accel->('UniformlyâĤ', 's'), 'Scale the selected object along the XYZ axes', sub { $self->changescale(undef); }); $frame->_append_menu_item($scaleMenu, "Along X axisâĤ", 'Scale the selected object along the X axis', sub { @@ -2187,24 +2047,19 @@ use Wx::DND; use base 'Wx::FileDropTarget'; sub new { - my $class = shift; - my ($window) = @_; + my ($class, $window) = @_; my $self = $class->SUPER::new; $self->{window} = $window; return $self; } sub OnDropFiles { - my $self = shift; - my ($x, $y, $filenames) = @_; - + my ($self, $x, $y, $filenames) = @_; # stop scalars leaking on older perl # https://rt.perl.org/rt3/Public/Bug/Display.html?id=70602 @_ = (); - # only accept STL, OBJ and AMF files return 0 if grep !/\.(?:[sS][tT][lL]|[oO][bB][jJ]|[aA][mM][fF](?:\.[xX][mM][lL])?|[pP][rR][uU][sS][aA])$/, @$filenames; - $self->{window}->load_files($filenames); } @@ -2220,10 +2075,8 @@ has 'selected' => (is => 'rw', default => sub { 0 }); sub make_thumbnail { my ($self, $model, $obj_idx) = @_; - # make method idempotent $self->thumbnail->clear; - # raw_mesh is the non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. my $mesh = $model->objects->[$obj_idx]->raw_mesh; #FIXME The "correct" variant could be extremely slow. @@ -2239,7 +2092,6 @@ sub make_thumbnail { my $convex_hull = Slic3r::ExPolygon->new($mesh->convex_hull); $self->thumbnail->append($convex_hull); # } - return $self->thumbnail; } diff --git a/lib/Slic3r/GUI/Plater/2D.pm b/lib/Slic3r/GUI/Plater/2D.pm index 089b25947..a1e077591 100644 --- a/lib/Slic3r/GUI/Plater/2D.pm +++ b/lib/Slic3r/GUI/Plater/2D.pm @@ -9,7 +9,7 @@ use utf8; use List::Util qw(min max first); use Slic3r::Geometry qw(X Y scale unscale convex_hull); use Slic3r::Geometry::Clipper qw(offset JT_ROUND intersection_pl); -use Wx qw(:misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL); +use Wx qw(wxTheApp :misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL); use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT EVT_ERASE_BACKGROUND EVT_SIZE); use base 'Wx::Panel'; @@ -102,7 +102,7 @@ sub repaint { } # draw print center - if (@{$self->{objects}} && $Slic3r::GUI::Settings->{_}{autocenter}) { + if (@{$self->{objects}} && wxTheApp->{app_config}->get("autocenter")) { my $center = $self->unscaled_point_to_pixel($self->{print_center}); $dc->SetPen($self->{print_center_pen}); $dc->DrawLine($center->[X], 0, $center->[X], $size[Y]); @@ -197,7 +197,6 @@ sub repaint { sub mouse_event { my ($self, $event) = @_; - my $pos = $event->GetPosition; my $point = $self->point_to_model_units([ $pos->x, $pos->y ]); #]] if ($event->ButtonDown) { @@ -257,7 +256,7 @@ sub mouse_event { } sub update_bed_size { - my $self = shift; + my ($self) = @_; # when the canvas is not rendered yet, its GetSize() method returns 0,0 my $canvas_size = $self->GetSize; diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm index 503a3d159..1c123e741 100644 --- a/lib/Slic3r/GUI/Plater/3D.pm +++ b/lib/Slic3r/GUI/Plater/3D.pm @@ -9,7 +9,7 @@ use Wx::Event qw(EVT_KEY_DOWN EVT_CHAR); use base qw(Slic3r::GUI::3DScene Class::Accessor); __PACKAGE__->mk_accessors(qw( - on_rotate_object_left on_rotate_object_right on_scale_object_uniformly + on_arrange on_rotate_object_left on_rotate_object_right on_scale_object_uniformly on_remove_object on_increase_objects on_decrease_objects)); sub new { @@ -88,7 +88,9 @@ sub new { $event->Skip; } else { my $key = $event->GetKeyCode; - if ($key == ord('l')) { + if ($key == ord('a')) { + $self->on_arrange->() if $self->on_arrange; + } elsif ($key == ord('l')) { $self->on_rotate_object_left->() if $self->on_rotate_object_left; } elsif ($key == ord('r')) { $self->on_rotate_object_right->() if $self->on_rotate_object_right; @@ -122,6 +124,11 @@ sub set_on_right_click { $self->on_right_click($cb); } +sub set_on_arrange { + my ($self, $cb) = @_; + $self->on_arrange($cb); +} + sub set_on_rotate_object_left { my ($self, $cb) = @_; $self->on_rotate_object_left($cb); diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index 707929e33..8a8e6064c 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -42,9 +42,9 @@ sub new { { $self->{tree_icons} = Wx::ImageList->new(16, 16, 1); $tree->AssignImageList($self->{tree_icons}); - $self->{tree_icons}->Add(Wx::Bitmap->new($Slic3r::var->("brick.png"), wxBITMAP_TYPE_PNG)); # ICON_OBJECT - $self->{tree_icons}->Add(Wx::Bitmap->new($Slic3r::var->("package.png"), wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH - $self->{tree_icons}->Add(Wx::Bitmap->new($Slic3r::var->("plugin.png"), wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH + $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("brick.png"), wxBITMAP_TYPE_PNG)); # ICON_OBJECT + $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH + $self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("plugin.png"), wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH my $rootId = $tree->AddRoot("Object", ICON_OBJECT); $tree->SetPlData($rootId, { type => 'object' }); @@ -58,13 +58,13 @@ sub new { $self->{btn_split} = Wx::Button->new($self, -1, "Split part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $self->{btn_move_up} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT); $self->{btn_move_down} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT); - $self->{btn_load_part}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG)); - $self->{btn_load_modifier}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG)); - $self->{btn_load_lambda_modifier}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG)); - $self->{btn_delete}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_delete.png"), wxBITMAP_TYPE_PNG)); - $self->{btn_split}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("shape_ungroup.png"), wxBITMAP_TYPE_PNG)); - $self->{btn_move_up}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("bullet_arrow_up.png"), wxBITMAP_TYPE_PNG)); - $self->{btn_move_down}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("bullet_arrow_down.png"), wxBITMAP_TYPE_PNG)); + $self->{btn_load_part}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG)); + $self->{btn_load_modifier}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG)); + $self->{btn_load_lambda_modifier}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG)); + $self->{btn_delete}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG)); + $self->{btn_split}->SetBitmap(Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG)); + $self->{btn_move_up}->SetBitmap(Wx::Bitmap->new(Slic3r::var("bullet_arrow_up.png"), wxBITMAP_TYPE_PNG)); + $self->{btn_move_down}->SetBitmap(Wx::Bitmap->new(Slic3r::var("bullet_arrow_down.png"), wxBITMAP_TYPE_PNG)); # buttons sizer my $buttons_sizer = Wx::GridSizer->new(2, 3); @@ -312,7 +312,7 @@ sub selection_changed { $config = $self->{model_object}->config; } # get default values - my $default_config = Slic3r::Config->new_from_defaults(@opt_keys); + my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys); # append default extruder push @opt_keys, 'extruder'; @@ -490,12 +490,12 @@ sub CanClose { # validate options before allowing user to dismiss the dialog # the validate method only works on full configs so we have # to merge our settings with the default ones - my $config = Slic3r::Config->merge($self->GetParent->GetParent->GetParent->GetParent->GetParent->config, $self->model_object->config); + my $config = $self->GetParent->GetParent->GetParent->GetParent->GetParent->config->clone; eval { + $config->apply($self->model_object->config); $config->validate; }; - return 0 if Slic3r::GUI::catch_error($self); - return 1; + return ! Slic3r::GUI::catch_error($self); } sub PartsChanged { diff --git a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm index 2f8b8ce73..3b10ed99f 100644 --- a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm +++ b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm @@ -46,7 +46,7 @@ sub new { # option selector { # create the button - my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("add.png"), wxBITMAP_TYPE_PNG), + my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); EVT_LEFT_DOWN($btn, sub { my $menu = Wx::Menu->new; @@ -146,7 +146,7 @@ sub update_optgroup { # disallow deleting fixed options return undef if $self->{fixed_options}{$opt_key}; - my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG), + my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE); EVT_BUTTON($self, $btn, sub { $self->{config}->erase($opt_key); diff --git a/lib/Slic3r/GUI/Preferences.pm b/lib/Slic3r/GUI/Preferences.pm index b19d623cd..d20ccb53f 100644 --- a/lib/Slic3r/GUI/Preferences.pm +++ b/lib/Slic3r/GUI/Preferences.pm @@ -10,6 +10,7 @@ sub new { my $self = $class->SUPER::new($parent, -1, "Preferences", wxDefaultPosition, wxDefaultSize); $self->{values} = {}; + my $app_config = wxTheApp->{app_config}; my $optgroup; $optgroup = Slic3r::GUI::OptionsGroup->new( parent => $self, @@ -25,7 +26,7 @@ sub new { # type => 'bool', # label => 'Check for updates', # tooltip => 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.', -# default => $Slic3r::GUI::Settings->{_}{version_check} // 1, +# default => $app_config->get("version_check") // 1, # readonly => !wxTheApp->have_version_check, # )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( @@ -33,36 +34,43 @@ sub new { type => 'bool', label => 'Remember output directory', tooltip => 'If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files.', - default => $Slic3r::GUI::Settings->{_}{remember_output_path}, + default => $app_config->get("remember_output_path"), )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'autocenter', type => 'bool', label => 'Auto-center parts', tooltip => 'If this is enabled, Slic3r will auto-center objects around the print bed center.', - default => $Slic3r::GUI::Settings->{_}{autocenter}, + default => $app_config->get("autocenter"), )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'background_processing', type => 'bool', label => 'Background processing', tooltip => 'If this is enabled, Slic3r will pre-process objects as soon as they\'re loaded in order to save time when exporting G-code.', - default => $Slic3r::GUI::Settings->{_}{background_processing}, - readonly => !$Slic3r::have_threads, + default => $app_config->get("background_processing"), )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'no_controller', type => 'bool', label => 'Disable USB/serial connection', tooltip => 'Disable communication with the printer over a serial / USB cable. This simplifies the user interface in case the printer is never attached to the computer.', - default => $Slic3r::GUI::Settings->{_}{no_controller}, + default => $app_config->get("no_controller"), )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'no_defaults', type => 'bool', label => 'Suppress "- default -" presets', tooltip => 'Suppress "- default -" presets in the Print / Filament / Printer selections once there are any other valid presets available.', - default => $Slic3r::GUI::Settings->{_}{no_defaults}, + default => $app_config->get("no_defaults"), + )); + $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 'show_incompatible_presets', + type => 'bool', + label => 'Show incompatible print and filament presets', + tooltip => 'When checked, the print and filament presets are shown in the preset editor even ' . + 'if they are marked as incompatible with the active printer', + default => $app_config->get("show_incompatible_presets"), )); my $sizer = Wx::BoxSizer->new(wxVERTICAL); @@ -79,14 +87,15 @@ sub new { } sub _accept { - my $self = shift; + my ($self) = @_; - if (defined($self->{values}{no_controller})) { + if (defined($self->{values}{no_controller}) || + defined($self->{values}{no_defaults})) { Slic3r::GUI::warning_catcher($self)->("You need to restart Slic3r to make the changes effective."); } - $Slic3r::GUI::Settings->{_}{$_} = $self->{values}{$_} for keys %{$self->{values}}; - wxTheApp->save_settings; + my $app_config = wxTheApp->{app_config}; + $app_config->set($_, $self->{values}{$_}) for keys %{$self->{values}}; $self->EndModal(wxID_OK); $self->Close; # needed on Linux diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index fb9d7e8b0..30429e88b 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -20,16 +20,12 @@ use utf8; use File::Basename qw(basename); use List::Util qw(first); use Wx qw(:bookctrl :dialog :keycode :icon :id :misc :panel :sizer :treectrl :window - :button wxTheApp); -use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN EVT_CHECKBOX EVT_TREE_SEL_CHANGED); + :button wxTheApp wxCB_READONLY); +use Wx::Event qw(EVT_BUTTON EVT_COMBOBOX EVT_KEY_DOWN EVT_CHECKBOX EVT_TREE_SEL_CHANGED); use base qw(Wx::Panel Class::Accessor); -# Index of the currently active preset. -__PACKAGE__->mk_accessors(qw(current_preset)); - sub new { - my $class = shift; - my ($parent, %params) = @_; + my ($class, $parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); # Vertical sizer to hold the choice menu and the rest of the page. @@ -41,23 +37,33 @@ sub new { { # choice menu - $self->{presets_choice} = Wx::Choice->new($self, -1, wxDefaultPosition, [270, -1], []); + $self->{presets_choice} = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, [270, -1], [], wxCB_READONLY); $self->{presets_choice}->SetFont($Slic3r::GUI::small_font); # buttons - $self->{btn_save_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("disk.png"), wxBITMAP_TYPE_PNG), + $self->{btn_save_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("disk.png"), wxBITMAP_TYPE_PNG), + wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); + $self->{btn_delete_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); - $self->{btn_delete_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG), + $self->{show_incompatible_presets} = 0; + $self->{bmp_show_incompatible_presets} = Wx::Bitmap->new(Slic3r::var("flag-red-icon.png"), wxBITMAP_TYPE_PNG); + $self->{bmp_hide_incompatible_presets} = Wx::Bitmap->new(Slic3r::var("flag-green-icon.png"), wxBITMAP_TYPE_PNG); + $self->{btn_hide_incompatible_presets} = Wx::BitmapButton->new($self, -1, + $self->{bmp_hide_incompatible_presets}, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); $self->{btn_save_preset}->SetToolTipString("Save current " . lc($self->title)); $self->{btn_delete_preset}->SetToolTipString("Delete this preset"); $self->{btn_delete_preset}->Disable; - my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); + my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); $self->{sizer}->Add($hsizer, 0, wxBOTTOM, 3); $hsizer->Add($self->{presets_choice}, 1, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); + $hsizer->AddSpacer(4); $hsizer->Add($self->{btn_save_preset}, 0, wxALIGN_CENTER_VERTICAL); + $hsizer->AddSpacer(4); $hsizer->Add($self->{btn_delete_preset}, 0, wxALIGN_CENTER_VERTICAL); + $hsizer->AddSpacer(16); + $hsizer->Add($self->{btn_hide_incompatible_presets}, 0, wxALIGN_CENTER_VERTICAL); } # Horizontal sizer to hold the tree and the selected page. @@ -72,8 +78,11 @@ sub new { $self->{treectrl} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [185, -1], wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_SUNKEN | wxWANTS_CHARS); $left_sizer->Add($self->{treectrl}, 1, wxEXPAND); $self->{icons} = Wx::ImageList->new(16, 16, 1); + # Map from an icon file name to its index in $self->{icons}. + $self->{icon_index} = {}; + # Index of the last icon inserted into $self->{icons}. + $self->{icon_count} = -1; $self->{treectrl}->AssignImageList($self->{icons}); - $self->{iconcount} = -1; $self->{treectrl}->AddRoot("root"); $self->{pages} = []; $self->{treectrl}->SetIndent(0); @@ -96,76 +105,28 @@ sub new { } }); - EVT_CHOICE($parent, $self->{presets_choice}, sub { - $self->on_select_preset; - $self->_on_presets_changed; + EVT_COMBOBOX($parent, $self->{presets_choice}, sub { + $self->select_preset($self->{presets_choice}->GetStringSelection); }); EVT_BUTTON($self, $self->{btn_save_preset}, sub { $self->save_preset }); + EVT_BUTTON($self, $self->{btn_delete_preset}, sub { $self->delete_preset }); + EVT_BUTTON($self, $self->{btn_hide_incompatible_presets}, sub { $self->_toggle_show_hide_incompatible }); - EVT_BUTTON($self, $self->{btn_delete_preset}, sub { - my $i = $self->current_preset; - # Don't let the user delete the '- default -' configuration. - # This shall not happen as the 'delete' button is disabled for the '- default -' entry, - # but better be safe than sorry. - return if ($i == 0 && $self->get_current_preset->{default}); - my $res = Wx::MessageDialog->new($self, "Are you sure you want to delete the selected preset?", 'Delete Preset', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION)->ShowModal; - return unless $res == wxID_YES; - # Delete the file. - my $path = $self->{presets}[$i]->file; - my $enc_path = Slic3r::encode_path($path); - if (-e $enc_path && ! unlink $enc_path) { - # Cannot delete the file, therefore the item will not be removed from the selection. - Slic3r::GUI::show_error($self, "Cannot delete file $path : $!"); - return; - } - # Delete the preset. - splice @{$self->{presets}}, $i, 1; - # Delete the item from the UI component. - $self->{presets_choice}->Delete($i - $self->{default_suppressed}); - $self->current_preset(undef); - if ($self->{default_suppressed} && scalar(@{$self->{presets}}) == 1) { - # Empty selection. Add the '- default -' item into the drop down selection. - $self->{presets_choice}->Append($self->{presets}->[0]->name); - # and remember that the '- default -' is shown. - $self->{default_suppressed} = 0; - } - # Select the 0th item. If default is suppressed, select the first valid. - $self->select_preset($self->{default_suppressed}); - $self->_on_presets_changed; - }); - - # C++ instance DynamicPrintConfig - $self->{config} = Slic3r::Config->new; # Initialize the DynamicPrintConfig by default keys/values. # Possible %params keys: no_controller $self->build(%params); - $self->update_tree; + $self->rebuild_page_tree; $self->_update; - if ($self->hidden_options) { - $self->{config}->apply(Slic3r::Config->new_from_defaults($self->hidden_options)); - } return $self; } -# Are the '- default -' selections suppressed by the Slic3r GUI preferences? -sub no_defaults { - return $Slic3r::GUI::Settings->{_}{no_defaults} ? 1 : 0; -} - -# Get a currently active preset (Perl class Slic3r::GUI::Tab::Preset). -sub get_current_preset { - my $self = shift; - return $self->get_preset($self->current_preset); -} - -# Get a preset (Perl class Slic3r::GUI::Tab::Preset) with an index $i. -sub get_preset { - my ($self, $i) = @_; - return $self->{presets}[$i]; -} - +# Save the current preset into file. +# This removes the "dirty" flag of the preset, possibly creates a new preset under a new name, +# and activates the new preset. +# Wizard calls save_preset with a name "My Settings", otherwise no name is provided and this method +# opens a Slic3r::GUI::SavePresetWindow dialog. sub save_preset { my ($self, $name) = @_; @@ -175,10 +136,9 @@ sub save_preset { $self->{treectrl}->SetFocus; if (!defined $name) { - my $preset = $self->get_current_preset; + my $preset = $self->{presets}->get_selected_preset; my $default_name = $preset->default ? 'Untitled' : $preset->name; $default_name =~ s/\.[iI][nN][iI]$//; - my $dlg = Slic3r::GUI::SavePresetWindow->new($self, title => lc($self->title), default => $default_name, @@ -187,184 +147,238 @@ sub save_preset { return unless $dlg->ShowModal == wxID_OK; $name = $dlg->get_name; } - - $self->config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $self->name, $name); - $self->load_presets; - $self->select_preset_by_name($name); + # Save the preset into Slic3r::data_dir/presets/section_name/preset_name.ini + eval { $self->{presets}->save_current_preset($name); }; + Slic3r::GUI::catch_error($self) and return; + # Add the new item into the UI component, remove dirty flags and activate the saved item. + $self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets}); + # Update the selection boxes at the platter. $self->_on_presets_changed; } +# Called for a currently selected preset. +sub delete_preset { + my ($self) = @_; + my $current_preset = $self->{presets}->get_selected_preset; + # Don't let the user delete the '- default -' configuration. + my $msg = 'Are you sure you want to ' . ($current_preset->external ? 'remove' : 'delete') . ' the selected preset?'; + my $title = ($current_preset->external ? 'Remove' : 'Delete') . ' Preset'; + return if $current_preset->default || + wxID_YES != Wx::MessageDialog->new($self, $msg, $title, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION)->ShowModal; + # Delete the file and select some other reasonable preset. + # The 'external' presets will only be removed from the preset list, their files will not be deleted. + eval { $self->{presets}->delete_current_preset; }; + Slic3r::GUI::catch_error($self) and return; + # Load the newly selected preset into the UI, update selection combo boxes with their dirty flags. + $self->load_current_preset; +} + +sub _toggle_show_hide_incompatible { + my ($self) = @_; + $self->{show_incompatible_presets} = ! $self->{show_incompatible_presets}; + $self->_update_show_hide_incompatible_button; + $self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets}); +} + +sub _update_show_hide_incompatible_button { + my ($self) = @_; + $self->{btn_hide_incompatible_presets}->SetBitmap($self->{show_incompatible_presets} ? + $self->{bmp_show_incompatible_presets} : $self->{bmp_hide_incompatible_presets}); + $self->{btn_hide_incompatible_presets}->SetToolTipString($self->{show_incompatible_presets} ? + "Both compatible an incompatible presets are shown. Click to hide presets not compatible with the current printer." : + "Only compatible presets are shown. Click to show both the presets compatible and not compatible with the current printer."); +} + +# Register the on_value_change callback. sub on_value_change { my ($self, $cb) = @_; $self->{on_value_change} = $cb; } +# Register the on_presets_changed callback. sub on_presets_changed { my ($self, $cb) = @_; $self->{on_presets_changed} = $cb; } # This method is called whenever an option field is changed by the user. -# Propagate event to the parent through the 'on_value_changed' callback +# Propagate event to the parent through the 'on_value_change' callback # and call _update. +# The on_value_change callback triggers Platter::on_config_change() to configure the 3D preview +# (colors, wipe tower positon etc) and to restart the background slicing process. sub _on_value_change { my ($self, $key, $value) = @_; $self->{on_value_change}->($key, $value) if $self->{on_value_change}; - $self->_update({ $key => 1 }); + $self->_update; } # Override this to capture changes of configuration caused either by loading or switching a preset, # or by a user changing an option field. +# This callback is useful for cross-validating configuration values of a single preset. sub _update {} +# Call a callback to update the selection of presets on the platter: +# To update the content of the selection boxes, +# to update the filament colors of the selection boxes, +# to update the "dirty" flags of the selection boxes, +# to uddate number of "filament" selection boxes when the number of extruders change. sub _on_presets_changed { - my $self = shift; - - $self->{on_presets_changed}->( - $self->{presets}, - $self->{default_suppressed}, - scalar($self->{presets_choice}->GetSelection) + $self->{default_suppressed}, - $self->is_dirty, - ) if $self->{on_presets_changed}; + my ($self) = @_; + $self->{on_presets_changed}->($self->{presets}) if $self->{on_presets_changed}; } +# For the printer profile, generate the extruder pages after a preset is loaded. sub on_preset_loaded {} -sub hidden_options {} -sub config { $_[0]->{config}->clone } - -sub select_default_preset { - my $self = shift; - $self->select_preset(0); -} -sub select_preset { - my ($self, $i) = @_; - if ($self->{default_suppressed} && $i == 0) { - # Selecting the '- default -'. Add it to the combo box. - $self->{default_suppressed} = 0; - $self->{presets_choice}->Insert($self->{presets}->[0]->name, 0); - } elsif ($self->no_defaults && ! $self->{default_suppressed} && $i > 0) { - # The user wants to hide the '- default -' items and a non-default item has been added to the presets. - # Hide the '- default -' item. - $self->{presets_choice}->Delete(0); - $self->{default_suppressed} = 1; +# If the current preset is dirty, the user is asked whether the changes may be discarded. +# if the current preset was not dirty, or the user agreed to discard the changes, 1 is returned. +sub may_discard_current_dirty_preset +{ + my ($self, $presets, $new_printer_name) = @_; + $presets //= $self->{presets}; + # Display a dialog showing the dirty options in a human readable form. + my $old_preset = $presets->get_current_preset; + my $type_name = $presets->name; + my $name = $old_preset->default ? + ('Default ' . $type_name . ' preset') : + ($type_name . " preset \"" . $old_preset->name . "\""); + # Collect descriptions of the dirty options. + my @option_names = (); + foreach my $opt_key (@{$presets->current_dirty_options}) { + my $opt = $Slic3r::Config::Options->{$opt_key}; + my $name = $opt->{full_label} // $opt->{label}; + $name = $opt->{category} . " > $name" if $opt->{category}; + push @option_names, $name; } - $self->{presets_choice}->SetSelection($i - $self->{default_suppressed}); - $self->on_select_preset; -} - -sub select_preset_by_name { - my ($self, $name) = @_; - - $name = Slic3r::normalize_utf8_nfc($name); - $self->select_preset(first { $self->{presets}[$_]->name eq $name } 0 .. $#{$self->{presets}}); + # Show a confirmation dialog with the list of dirty options. + my $changes = join "\n", map "- $_", @option_names; + my $message = (defined $new_printer_name) ? + "$name is not compatible with printer \"$new_printer_name\"\n and it has unsaved changes:" : + "$name has unsaved changes:"; + my $confirm = Wx::MessageDialog->new($self, + $message . "\n$changes\n\nDiscard changes and continue anyway?", + 'Unsaved Changes', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); + return $confirm->ShowModal == wxID_YES; } -sub on_select_preset { - my $self = shift; - - if ($self->is_dirty) { - # Display a dialog showing the dirty options in a human readable form. - my $old_preset = $self->get_current_preset; - my $name = $old_preset->default ? 'Default preset' : "Preset \"" . $old_preset->name . "\""; - - my @option_names = (); - foreach my $opt_key (@{$self->dirty_options}) { - my $opt = $Slic3r::Config::Options->{$opt_key}; - my $name = $opt->{full_label} // $opt->{label}; - if ($opt->{category}) { - $name = $opt->{category} . " > $name"; +# Called by the UI combo box when the user switches profiles. +# Select a preset by a name. If ! defined(name), then the default preset is selected. +# If the current profile is modified, user is asked to save the changes. +sub select_preset { + my ($self, $name, $force) = @_; + $force //= 0; + my $current_dirty = $self->{presets}->current_is_dirty; + my $canceled = 0; + my $printer_tab = $self->{presets}->name eq 'printer'; + if (! $force && $current_dirty && ! $self->may_discard_current_dirty_preset) { + $canceled = 1; + } elsif ($printer_tab) { + # Before switching the printer to a new one, verify, whether the currently active print and filament + # are compatible with the new printer. + # If they are not compatible and the the current print or filament are dirty, let user decide + # whether to discard the changes or keep the current printer selection. + my $new_printer_name = $name // ''; + my $new_printer_preset = $self->{presets}->find_preset($new_printer_name, 1); + # my $new_nozzle_dmrs = $new_printer_preset->config->get('nozzle_diameter'); + my $print_presets = wxTheApp->{preset_bundle}->print; + if ($print_presets->current_is_dirty && + ! $print_presets->get_edited_preset->is_compatible_with_printer($new_printer_name)) { + if ($self->may_discard_current_dirty_preset($print_presets, $new_printer_name)) { + $canceled = 1; + } else { + $print_presets->discard_current_changes; } - push @option_names, $name; } - - my $changes = join "\n", map "- $_", @option_names; - my $confirm = Wx::MessageDialog->new($self, "$name has unsaved changes:\n$changes\n\nDiscard changes and continue anyway?", - 'Unsaved Changes', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); - if ($confirm->ShowModal == wxID_NO) { - $self->{presets_choice}->SetSelection($self->current_preset - $self->{default_suppressed}); - - # trigger the on_presets_changed event so that we also restore the previous value - # in the plater selector - $self->_on_presets_changed; - return; + my $filament_presets = wxTheApp->{preset_bundle}->filament; + # if ((@$new_nozzle_dmrs <= 1) && + if (! $canceled && $filament_presets->current_is_dirty && + ! $filament_presets->get_edited_preset->is_compatible_with_printer($new_printer_name)) { + if ($self->may_discard_current_dirty_preset($filament_presets, $new_printer_name)) { + $canceled = 1; + } else { + $filament_presets->discard_current_changes; + } } } - - $self->current_preset($self->{presets_choice}->GetSelection + $self->{default_suppressed}); - my $preset = $self->get_current_preset; - my $preset_config = $self->get_preset_config($preset); + if ($canceled) { + $self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets}); + # Trigger the on_presets_changed event so that we also restore the previous value in the plater selector. + $self->_on_presets_changed; + } else { + if (defined $name) { + $self->{presets}->select_preset_by_name($name); + } else { + $self->{presets}->select_preset(0); + } + # Mark the print & filament enabled if they are compatible with the currently selected preset. + wxTheApp->{preset_bundle}->update_compatible_with_printer(1) + if $current_dirty || $printer_tab; + # Initialize the UI from the current preset. + $self->load_current_preset; + } +} + +# Initialize the UI from the current preset. +sub load_current_preset { + my ($self) = @_; + my $preset = $self->{presets}->get_current_preset; eval { local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); - my %keys_modified = (); - foreach my $opt_key (@{$self->{config}->get_keys}) { - if ($preset_config->has($opt_key)) { - if ($self->{config}->serialize($opt_key) ne $preset_config->serialize($opt_key)) { - $self->{config}->set($opt_key, $preset_config->get($opt_key)); - $keys_modified{$opt_key} = 1; - } - } - } - ($preset->default || $preset->external) - ? $self->{btn_delete_preset}->Disable - : $self->{btn_delete_preset}->Enable; - - $self->_update(\%keys_modified); + my $method = $preset->default ? 'Disable' : 'Enable'; + $self->{btn_delete_preset}->$method; + $self->_update; + # For the printer profile, generate the extruder pages. $self->on_preset_loaded; - $self->reload_config; - $Slic3r::GUI::Settings->{presets}{$self->name} = $preset->file ? basename($preset->file) : ''; + # Reload preset pages with the new configuration values. + $self->_reload_config; }; - if ($@) { - $@ = "I was unable to load the selected config file: $@"; - Slic3r::GUI::catch_error($self); - $self->select_default_preset; - } - # use CallAfter because some field triggers schedule on_change calls using CallAfter, # and we don't want them to be called after this update_dirty() as they would mark the # preset dirty again # (not sure this is true anymore now that update_dirty is idempotent) wxTheApp->CallAfter(sub { + $self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets}); $self->_on_presets_changed; - $self->update_dirty; }); - - wxTheApp->save_settings; -} - -sub init_config_options { - my ($self, @opt_keys) = @_; - $self->{config}->apply(Slic3r::Config->new_from_defaults(@opt_keys)); } sub add_options_page { - my $self = shift; - my ($title, $icon, %params) = @_; - + my ($self, $title, $icon, %params) = @_; + # Index of $icon in an icon list $self->{icons}. + my $icon_idx = 0; if ($icon) { - my $bitmap = Wx::Bitmap->new($Slic3r::var->($icon), wxBITMAP_TYPE_PNG); - $self->{icons}->Add($bitmap); - $self->{iconcount}++; + $icon_idx = $self->{icon_index}->{$icon}; + if (! defined $icon_idx) { + # Add a new icon to the icon list. + my $bitmap = Wx::Bitmap->new(Slic3r::var($icon), wxBITMAP_TYPE_PNG); + $self->{icons}->Add($bitmap); + $icon_idx = $self->{icon_count} + 1; + $self->{icon_count} = $icon_idx; + $self->{icon_index}->{$icon} = $icon_idx; + } } - - my $page = Slic3r::GUI::Tab::Page->new($self, $title, $self->{iconcount}); + # Initialize the page. + my $page = Slic3r::GUI::Tab::Page->new($self, $title, $icon_idx); $page->Hide; $self->{hsizer}->Add($page, 1, wxEXPAND | wxLEFT, 5); push @{$self->{pages}}, $page; return $page; } -sub reload_config { - my $self = shift; +# Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. +sub _reload_config { + my ($self) = @_; + $self->Freeze; $_->reload_config for @{$self->{pages}}; + $self->Thaw; } -sub update_tree { +# Regerenerate content of the page tree. +sub rebuild_page_tree { my ($self) = @_; - + $self->Freeze; # get label of the currently selected item - my $selected = $self->{treectrl}->GetItemText($self->{treectrl}->GetSelection); - + my $selected = $self->{treectrl}->GetItemText($self->{treectrl}->GetSelection); my $rootItem = $self->{treectrl}->GetRootItem; $self->{treectrl}->DeleteChildren($rootItem); my $have_selection = 0; @@ -377,147 +391,158 @@ sub update_tree { $have_selection = 1; } } - if (!$have_selection) { # this is triggered on first load, so we don't disable the sel change event $self->{treectrl}->SelectItem($self->{treectrl}->GetFirstChild($rootItem)); } + $self->Thaw; } # Update the combo box label of the selected preset based on its "dirty" state, # comparing the selected preset config with $self->{config}. sub update_dirty { my ($self) = @_; - my $list_updated; - foreach my $i ($self->{default_suppressed}..$#{$self->{presets}}) { - my $preset = $self->get_preset($i); - my $label = ($i == $self->current_preset && $self->is_dirty) ? $preset->name . " (modified)" : $preset->name; - my $idx = $i - $self->{default_suppressed}; - if ($self->{presets_choice}->GetString($idx) ne $label) { - $self->{presets_choice}->SetString($idx, $label); - $list_updated = 1; - } - } - $self->{presets_choice}->SetSelection($self->current_preset - $self->{default_suppressed}) if ($list_updated); # http://trac.wxwidgets.org/ticket/13769 - $self->_on_presets_changed; -} - -# Has the selected preset been modified? -sub is_dirty { - my ($self) = @_; - return @{$self->dirty_options} > 0; -} - -# Which options of the selected preset were modified? -sub dirty_options { - my ($self) = @_; - return [] if !defined $self->current_preset; # happens during initialization - return $self->get_preset_config($self->get_current_preset)->diff($self->{config}); -} - -# Search all ini files in the presets directory, add them into the list of $self->{presets} in the form of Slic3r::GUI::Tab::Preset. -# Initialize the drop down list box. -sub load_presets { - my ($self) = @_; - - $self->{presets} = [ - Slic3r::GUI::Tab::Preset->new( - default => 1, - name => '- default -', - ), - ]; - - my %presets = wxTheApp->presets($self->name); - foreach my $preset_name (sort keys %presets) { - push @{$self->{presets}}, Slic3r::GUI::Tab::Preset->new( - name => $preset_name, - file => $presets{$preset_name}, - ); - } - $self->current_preset(undef); - $self->{default_suppressed} = Slic3r::GUI::Tab->no_defaults && scalar(@{$self->{presets}}) > 1; - $self->{presets_choice}->Clear; - foreach my $preset (@{$self->{presets}}) { - next if ($preset->default && $self->{default_suppressed}); - $self->{presets_choice}->Append($preset->name); - } - { - # load last used preset - my $i = first { basename($self->{presets}[$_]->file) eq ($Slic3r::GUI::Settings->{presets}{$self->name} || '') } 1 .. $#{$self->{presets}}; - $self->select_preset($i || $self->{default_suppressed}); - } + $self->{presets}->update_dirty_ui($self->{presets_choice}); $self->_on_presets_changed; } -# Load a config file containing a Print, Filament & Printer preset. -sub load_config_file { - my ($self, $file) = @_; - # look for the loaded config among the existing menu items - my $i = first { $self->{presets}[$_]{file} eq $file && $self->{presets}[$_]{external} } 1..$#{$self->{presets}}; - if (!$i) { - my $preset_name = basename($file); # keep the .ini suffix - my $preset_new = Slic3r::GUI::Tab::Preset->new( - file => $file, - name => $preset_name, - external => 1, - ); - # Try to load the config file before it is entered into the list. If the loading fails, an undef is returned. - return undef if ! defined $preset_new->config; - push @{$self->{presets}}, $preset_new; - $self->{presets_choice}->Append($preset_name); - $i = $#{$self->{presets}}; - } - $self->{presets_choice}->SetSelection($i - $self->{default_suppressed}); - $self->on_select_preset; - $self->_on_presets_changed; - return 1; -} - # Load a provied DynamicConfig into the tab, modifying the active preset. # This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view. sub load_config { my ($self, $config) = @_; - - my %keys_modified = (); + my $modified = 0; foreach my $opt_key (@{$self->{config}->diff($config)}) { $self->{config}->set($opt_key, $config->get($opt_key)); - $keys_modified{$opt_key} = 1; + $modified = 1; } - if (keys(%keys_modified)) { + if ($modified) { $self->update_dirty; # Initialize UI components with the config values. - $self->reload_config; - $self->_update(\%keys_modified); + $self->_reload_config; + $self->_update; } } -# Load and return a config from the file associated with the $preset (Perl type Slic3r::GUI::Tab::Preset). -sub get_preset_config { - my ($self, $preset) = @_; - return $preset->config($self->{config}->get_keys); +# To be called by custom widgets, load a value into a config, +# update the preset selection boxes (the dirty flags) +sub _load_key_value { + my ($self, $opt_key, $value) = @_; + $self->{config}->set($opt_key, $value); + # Mark the print & filament enabled if they are compatible with the currently selected preset. + if ($opt_key eq 'compatible_printers') { + wxTheApp->{preset_bundle}->update_compatible_with_printer(0); + $self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets}); + } else { + $self->{presets}->update_dirty_ui($self->{presets_choice}); + } + $self->_on_presets_changed; + $self->_update; } +# Find a field with an index over all pages of this tab. +# This method is used often and everywhere, therefore it shall be quick. sub get_field { my ($self, $opt_key, $opt_index) = @_; - - foreach my $page (@{ $self->{pages} }) { + foreach my $page (@{$self->{pages}}) { my $field = $page->get_field($opt_key, $opt_index); return $field if defined $field; } return undef; } +# Set a key/value pair on this page. Return true if the value has been modified. +# Currently used for distributing extruders_count over preset pages of Slic3r::GUI::Tab::Printer +# after a preset is loaded. sub set_value { - my $self = shift; - my ($opt_key, $value) = @_; - + my ($self, $opt_key, $value) = @_; my $changed = 0; - foreach my $page (@{ $self->{pages} }) { + foreach my $page (@{$self->{pages}}) { $changed = 1 if $page->set_value($opt_key, $value); } return $changed; } +# Return a callback to create a Tab widget to mark the preferences as compatible / incompatible to the current printer. +sub _compatible_printers_widget { + my ($self) = @_; + + return sub { + my ($parent) = @_; + + my $checkbox = $self->{compatible_printers_checkbox} = Wx::CheckBox->new($parent, -1, "All"); + + my $btn = $self->{compatible_printers_btn} = Wx::Button->new($parent, -1, "SetâĤ", wxDefaultPosition, wxDefaultSize, + wxBU_LEFT | wxBU_EXACTFIT); + $btn->SetFont($Slic3r::GUI::small_font); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("printer_empty.png"), wxBITMAP_TYPE_PNG)); + + my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); + $sizer->Add($checkbox, 0, wxALIGN_CENTER_VERTICAL); + $sizer->Add($btn, 0, wxALIGN_CENTER_VERTICAL); + + EVT_CHECKBOX($self, $checkbox, sub { + my $method = $checkbox->GetValue ? 'Disable' : 'Enable'; + $btn->$method; + # All printers have been made compatible with this preset. + $self->_load_key_value('compatible_printers', []) if $checkbox->GetValue; + }); + + EVT_BUTTON($self, $btn, sub { + # Collect names of non-default non-external printer profiles. + my @presets = map $_->name, grep !$_->default && !$_->external, + @{wxTheApp->{preset_bundle}->printer}; + my $dlg = Wx::MultiChoiceDialog->new($self, + "Select the printers this profile is compatible with.", + "Compatible printers", \@presets); + # Collect and set indices of printers marked as compatible. + my @selections = (); + foreach my $preset_name (@{ $self->{config}->get('compatible_printers') }) { + my $idx = first { $presets[$_] eq $preset_name } 0..$#presets; + push @selections, $idx if defined $idx; + } + $dlg->SetSelections(@selections); + # Show the dialog. + if ($dlg->ShowModal == wxID_OK) { + my $value = [ @presets[$dlg->GetSelections] ]; + if (!@$value) { + $checkbox->SetValue(1); + $btn->Disable; + } + # All printers have been made compatible with this preset. + $self->_load_key_value('compatible_printers', $value); + } + }); + + return $sizer; + }; +} + +sub _reload_compatible_printers_widget { + my ($self) = @_; + my $has_any = int(@{$self->{config}->get('compatible_printers')}) > 0; + my $method = $has_any ? 'Enable' : 'Disable'; + $self->{compatible_printers_checkbox}->SetValue(! $has_any); + $self->{compatible_printers_btn}->$method; +} + +sub update_ui_from_settings { + my ($self) = @_; + # Show the 'show / hide presets' button only for the print and filament tabs, and only if enabled + # in application preferences. + my $show = wxTheApp->{app_config}->get("show_incompatible_presets") && $self->{presets}->name ne 'printer'; + my $method = $show ? 'Show' : 'Hide'; + $self->{btn_hide_incompatible_presets}->$method; + # If the 'show / hide presets' button is hidden, hide the incompatible presets. + if ($show) { + $self->_update_show_hide_incompatible_button; + } else { + if ($self->{show_incompatible_presets}) { + $self->{show_incompatible_presets} = 0; + $self->{presets}->update_tab_ui($self->{presets_choice}, 0); + } + } +} + package Slic3r::GUI::Tab::Print; use base 'Slic3r::GUI::Tab'; @@ -530,7 +555,8 @@ sub title { 'Print Settings' } sub build { my $self = shift; - $self->{config}->apply(wxTheApp->{preset_bundle}->prints->default_preset->config); + $self->{presets} = wxTheApp->{preset_bundle}->print; + $self->{config} = $self->{presets}->get_edited_preset->config; { my $page = $self->add_options_page('Layers and perimeters', 'layers.png'); @@ -734,7 +760,7 @@ sub build { $optgroup->append_single_option_line('clip_multipart_objects'); $optgroup->append_single_option_line('elefant_foot_compensation'); $optgroup->append_single_option_line('xy_size_compensation'); -# $optgroup->append_single_option_line('threads') if $Slic3r::have_threads; +# $optgroup->append_single_option_line('threads'); $optgroup->append_single_option_line('resolution'); } } @@ -787,19 +813,33 @@ sub build { $optgroup->append_single_option_line($option); } } + + { + my $page = $self->add_options_page('Dependencies', 'wrench.png'); + { + my $optgroup = $page->new_optgroup('Profile dependencies'); + { + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => 'Compatible printers', + widget => $self->_compatible_printers_widget, + ); + $optgroup->append_line($line); + } + } + } } -sub reload_config { +# Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. +sub _reload_config { my ($self) = @_; -# $self->_reload_compatible_printers_widget; - $self->SUPER::reload_config; + $self->_reload_compatible_printers_widget; + $self->SUPER::_reload_config; } # Slic3r::GUI::Tab::Print::_update is called after a configuration preset is loaded or switched, or when a single option is modifed by the user. sub _update { - # $keys_modified is a reference to hash with modified keys set to 1, unmodified keys missing. - my ($self, $keys_modified) = @_; - $keys_modified //= {}; + my ($self) = @_; + $self->Freeze; my $config = $self->{config}; @@ -879,10 +919,6 @@ sub _update { $self->load_config($new_conf); } - if ($keys_modified->{'layer_height'}) { - # If the user had set the variable layer height, reset it and let the user know. - } - if ($config->support_material) { # Ask only once. if (! $self->{support_material_overhangs_queried}) { @@ -997,9 +1033,9 @@ sub _update { my $have_wipe_tower = $config->wipe_tower; $self->get_field($_)->toggle($have_wipe_tower) for qw(wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe); -} -#sub hidden_options { !$Slic3r::have_threads ? qw(threads) : () } + $self->Thaw; +} package Slic3r::GUI::Tab::Filament; use base 'Slic3r::GUI::Tab'; @@ -1011,7 +1047,8 @@ sub title { 'Filament Settings' } sub build { my $self = shift; - $self->{config}->apply(wxTheApp->{preset_bundle}->filaments->default_preset->config); + $self->{presets} = wxTheApp->{preset_bundle}->filament; + $self->{config} = $self->{presets}->get_edited_preset->config; { my $page = $self->add_options_page('Filament', 'spool.png'); @@ -1059,7 +1096,7 @@ sub build { full_width => 1, widget => sub { my ($parent) = @_; - return $self->{description_line} = Slic3r::GUI::OptionsGroup::StaticText->new($parent); + return $self->{cooling_description_line} = Slic3r::GUI::OptionsGroup::StaticText->new($parent); }, ); $optgroup->append_line($line); @@ -1098,6 +1135,16 @@ sub build { $optgroup = $page->new_optgroup('Print speed override'); $optgroup->append_single_option_line('filament_max_volumetric_speed', 0); + + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => '', + full_width => 1, + widget => sub { + my ($parent) = @_; + return $self->{volumetric_speed_description_line} = Slic3r::GUI::OptionsGroup::StaticText->new($parent); + }, + ); + $optgroup->append_line($line); } } @@ -1135,14 +1182,37 @@ sub build { $optgroup->append_single_option_line($option); } } + + { + my $page = $self->add_options_page('Dependencies', 'wrench.png'); + { + my $optgroup = $page->new_optgroup('Profile dependencies'); + { + my $line = Slic3r::GUI::OptionsGroup::Line->new( + label => 'Compatible printers', + widget => $self->_compatible_printers_widget, + ); + $optgroup->append_line($line); + } + } + } +} + +# Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. +sub _reload_config { + my ($self) = @_; + $self->_reload_compatible_printers_widget; + $self->SUPER::_reload_config; } # Slic3r::GUI::Tab::Filament::_update is called after a configuration preset is loaded or switched, or when a single option is modifed by the user. sub _update { - # $keys_modified is a reference to hash with modified keys set to 1, unmodified keys missing. - my ($self, $keys_modified) = @_; + my ($self) = @_; - $self->_update_description; + $self->{cooling_description_line}->SetText( + Slic3r::GUI::PresetHints::cooling_description($self->{presets}->get_edited_preset)); + $self->{volumetric_speed_description_line}->SetText( + Slic3r::GUI::PresetHints::maximum_volumetric_flow_description(wxTheApp->{preset_bundle})); my $cooling = $self->{config}->cooling->[0]; my $fan_always_on = $cooling || $self->{config}->fan_always_on->[0]; @@ -1152,35 +1222,6 @@ sub _update { for qw(min_fan_speed disable_fan_first_layers); } -sub _update_description { - my $self = shift; - - my $config = $self->config; - - my $msg = ""; - my $fan_other_layers = $config->fan_always_on->[0] - ? sprintf "will always run at %d%%%s.", $config->min_fan_speed->[0], - ($config->disable_fan_first_layers->[0] > 1 - ? " except for the first " . $config->disable_fan_first_layers->[0] . " layers" - : $config->disable_fan_first_layers->[0] == 1 - ? " except for the first layer" - : "") - : "will be turned off."; - - if ($config->cooling->[0]) { - $msg = sprintf "If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).", - $config->slowdown_below_layer_time->[0], $config->max_fan_speed->[0], $config->slowdown_below_layer_time->[0], $config->min_print_speed->[0]; - if ($config->fan_below_layer_time->[0] > $config->slowdown_below_layer_time->[0]) { - $msg .= sprintf "\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.", - $config->fan_below_layer_time->[0], $config->max_fan_speed->[0], $config->min_fan_speed->[0]; - } - $msg .= "\nDuring the other layers, fan $fan_other_layers" - } else { - $msg = "Fan $fan_other_layers"; - } - $self->{description_line}->SetText($msg); -} - package Slic3r::GUI::Tab::Printer; use base 'Slic3r::GUI::Tab'; use Wx qw(wxTheApp :sizer :button :bitmap :misc :id :icon :dialog); @@ -1190,37 +1231,31 @@ sub name { 'printer' } sub title { 'Printer Settings' } sub build { - my $self = shift; - my (%params) = @_; - - $self->{config}->apply(wxTheApp->{preset_bundle}->printers->default_preset->config); + my ($self, %params) = @_; + $self->{presets} = wxTheApp->{preset_bundle}->printer; + $self->{config} = $self->{presets}->get_edited_preset->config; + $self->{extruders_count} = scalar @{$self->{config}->nozzle_diameter}; + my $bed_shape_widget = sub { my ($parent) = @_; my $btn = Wx::Button->new($parent, -1, "SetâĤ", wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); $btn->SetFont($Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("printer_empty.png"), wxBITMAP_TYPE_PNG)); my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); $sizer->Add($btn); EVT_BUTTON($self, $btn, sub { my $dlg = Slic3r::GUI::BedShapeDialog->new($self, $self->{config}->bed_shape); - if ($dlg->ShowModal == wxID_OK) { - my $value = $dlg->GetValue; - $self->{config}->set('bed_shape', $value); - $self->update_dirty; - $self->_on_value_change('bed_shape', $value); - } + $self->_load_key_value('bed_shape', $dlg->GetValue) if $dlg->ShowModal == wxID_OK; }); return $sizer; }; - - $self->{extruders_count} = 1; - + { my $page = $self->add_options_page('General', 'printer_empty.png'); { @@ -1249,13 +1284,16 @@ sub build { $optgroup->append_single_option_line('single_extruder_multi_material'); } $optgroup->on_change(sub { - my ($opt_id) = @_; - if ($opt_id eq 'extruders_count') { - wxTheApp->CallAfter(sub { + my ($opt_key, $value) = @_; + wxTheApp->CallAfter(sub { + if ($opt_key eq 'extruders_count') { $self->_extruders_count_changed($optgroup->get_value('extruders_count')); - }); - $self->update_dirty; - } + $self->update_dirty; + } else { + $self->update_dirty; + $self->_on_value_change($opt_key, $value); + } + }); }); } if (!$params{no_controller}) @@ -1268,7 +1306,7 @@ sub build { $serial_port->side_widget(sub { my ($parent) = @_; - my $btn = Wx::BitmapButton->new($parent, -1, Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), + my $btn = Wx::BitmapButton->new($parent, -1, Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, &Wx::wxBORDER_NONE); $btn->SetToolTipString("Rescan serial ports") if $btn->can('SetToolTipString'); @@ -1282,7 +1320,7 @@ sub build { my $btn = $self->{serial_test_btn} = Wx::Button->new($parent, -1, "Test", wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); $btn->SetFont($Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("wrench.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("wrench.png"), wxBITMAP_TYPE_PNG)); EVT_BUTTON($self, $btn, sub { my $sender = Slic3r::GCode::Sender->new; @@ -1312,7 +1350,7 @@ sub build { my $btn = Wx::Button->new($parent, -1, "BrowseâĤ", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $btn->SetFont($Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("zoom.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("zoom.png"), wxBITMAP_TYPE_PNG)); if (!eval "use Net::Bonjour; 1") { $btn->Disable; @@ -1328,13 +1366,8 @@ sub build { } if (@{$entries}) { my $dlg = Slic3r::GUI::BonjourBrowser->new($self, $entries); - if ($dlg->ShowModal == wxID_OK) { - my $value = $dlg->GetValue . ":" . $dlg->GetPort; - $self->{config}->set('octoprint_host', $value); - $self->update_dirty; - $self->_on_value_change('octoprint_host', $value); - $self->reload_config; - } + $self->_load_key_value('octoprint_host', $dlg->GetValue . ":" . $dlg->GetPort) + if $dlg->ShowModal == wxID_OK; } else { Wx::MessageDialog->new($self, 'No Bonjour device found', 'Device Browser', wxOK | wxICON_INFORMATION)->ShowModal; } @@ -1348,7 +1381,7 @@ sub build { my $btn = $self->{octoprint_host_test_btn} = Wx::Button->new($parent, -1, "Test", wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); $btn->SetFont($Slic3r::GUI::small_font); - $btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("wrench.png"), wxBITMAP_TYPE_PNG)); + $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("wrench.png"), wxBITMAP_TYPE_PNG)); EVT_BUTTON($self, $btn, sub { my $ua = LWP::UserAgent->new; @@ -1434,6 +1467,15 @@ sub build { $option->height(150); $optgroup->append_single_option_line($option); } + { + my $optgroup = $page->new_optgroup('Between objects G-code (for sequential printing)', + label_width => 0, + ); + my $option = $optgroup->get_option('between_objects_gcode'); + $option->full_width(1); + $option->height(150); + $optgroup->append_single_option_line($option); + } } { @@ -1458,43 +1500,23 @@ sub build { sub _update_serial_ports { my ($self) = @_; - $self->get_field('serial_port')->set_values([ wxTheApp->scan_serial_ports ]); + $self->get_field('serial_port')->set_values([ Slic3r::GUI::scan_serial_ports ]); } sub _extruders_count_changed { my ($self, $extruders_count) = @_; - $self->{extruders_count} = $extruders_count; + wxTheApp->{preset_bundle}->printer->get_edited_preset->set_num_extruders($extruders_count); + wxTheApp->{preset_bundle}->update_multi_material_filament_presets; $self->_build_extruder_pages; $self->_on_value_change('extruders_count', $extruders_count); } -sub _extruder_options { - qw(nozzle_diameter min_layer_height max_layer_height extruder_offset - retract_length retract_lift retract_lift_above retract_lift_below retract_speed deretract_speed - retract_before_wipe retract_restart_extra retract_before_travel wipe - retract_layer_change retract_length_toolchange retract_restart_extra_toolchange extruder_colour) } - sub _build_extruder_pages { - my $self = shift; - + my ($self) = @_; my $default_config = Slic3r::Config::Full->new; - + foreach my $extruder_idx (@{$self->{extruder_pages}} .. $self->{extruders_count}-1) { - # extend options - foreach my $opt_key ($self->_extruder_options) { - my $values = $self->{config}->get($opt_key); - if (!defined $values) { - $values = [ $default_config->get_at($opt_key, 0) ]; - } else { - # use last extruder's settings for the new one - my $last_value = $values->[-1]; - $values->[$extruder_idx] //= $last_value; - } - $self->{config}->set($opt_key, $values) - or die "Unable to extend $opt_key"; - } - # build page my $page = $self->{extruder_pages}[$extruder_idx] = $self->add_options_page("Extruder " . ($extruder_idx + 1), 'funnel.png'); { @@ -1544,14 +1566,6 @@ sub _build_extruder_pages { splice @{$self->{extruder_pages}}, $self->{extruders_count}; } - # remove extra config values - foreach my $opt_key ($self->_extruder_options) { - my $values = $self->{config}->get($opt_key); - splice @$values, $self->{extruders_count} if $self->{extruders_count} <= $#$values; - $self->{config}->set($opt_key, $values) - or die "Unable to truncate $opt_key"; - } - # rebuild page list my @pages_without_extruders = (grep $_->{title} !~ /^Extruder \d+/, @{$self->{pages}}); my $page_notes = pop @pages_without_extruders; @@ -1560,14 +1574,14 @@ sub _build_extruder_pages { @{$self->{extruder_pages}}[ 0 .. $self->{extruders_count}-1 ], $page_notes ); - $self->update_tree; + $self->rebuild_page_tree; } # Slic3r::GUI::Tab::Printer::_update is called after a configuration preset is loaded or switched, or when a single option is modifed by the user. sub _update { - # $keys_modified is a reference to hash with modified keys set to 1, unmodified keys missing. - my ($self, $keys_modified) = @_; - + my ($self) = @_; + $self->Freeze; + my $config = $self->{config}; my $serial_speed = $self->get_field('serial_speed'); @@ -1639,42 +1653,27 @@ sub _update { $self->get_field('retract_restart_extra_toolchange', $i)->toggle ($have_multiple_extruders && $toolchange_retraction); } + + $self->Thaw; } # this gets executed after preset is loaded and before GUI fields are updated sub on_preset_loaded { - my $self = shift; - + my ($self) = @_; # update the extruders count field - { - # update the GUI field according to the number of nozzle diameters supplied - my $extruders_count = scalar @{ $self->{config}->nozzle_diameter }; - $self->set_value('extruders_count', $extruders_count); - $self->_extruders_count_changed($extruders_count); - } -} - -# Load a config file containing a Print, Filament & Printer preset. -sub load_config_file { - my $self = shift; - if ($self->SUPER::load_config_file(@_)) { - Slic3r::GUI::warning_catcher($self)->( - "Your configuration was imported. However, Slic3r is currently only able to import settings " - . "for the first defined filament. We recommend you don't use exported configuration files " - . "for multi-extruder setups and rely on the built-in preset management system instead.") - if @{ $self->{config}->nozzle_diameter } > 1; - return 1; - } - return undef; + my $extruders_count = scalar @{ $self->{config}->nozzle_diameter }; + $self->set_value('extruders_count', $extruders_count); + # update the GUI field according to the number of nozzle diameters supplied + $self->_extruders_count_changed($extruders_count); } +# Single Tab page containing a {vsizer} of {optgroups} package Slic3r::GUI::Tab::Page; use Wx qw(wxTheApp :misc :panel :sizer); use base 'Wx::ScrolledWindow'; sub new { - my $class = shift; - my ($parent, $title, $iconID) = @_; + my ($class, $parent, $title, $iconID) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $self->{optgroups} = []; $self->{title} = $title; @@ -1718,7 +1717,6 @@ sub reload_config { sub get_field { my ($self, $opt_key, $opt_index) = @_; - foreach my $optgroup (@{ $self->{optgroups} }) { my $field = $optgroup->get_fieldc($opt_key, $opt_index); return $field if defined $field; @@ -1727,9 +1725,7 @@ sub get_field { } sub set_value { - my $self = shift; - my ($opt_key, $value) = @_; - + my ($self, $opt_key, $value) = @_; my $changed = 0; foreach my $optgroup (@{$self->{optgroups}}) { $changed = 1 if $optgroup->set_value($opt_key, $value); @@ -1737,14 +1733,15 @@ sub set_value { return $changed; } +# Dialog to select a new file name for a modified preset to be saved. +# Called from Tab::save_preset(). package Slic3r::GUI::SavePresetWindow; use Wx qw(:combobox :dialog :id :misc :sizer); use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER); use base 'Wx::Dialog'; sub new { - my $class = shift; - my ($parent, %params) = @_; + my ($class, $parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, "Save preset", wxDefaultPosition, wxDefaultSize); my @values = @{$params{values}}; @@ -1770,8 +1767,7 @@ sub new { sub accept { my ($self, $event) = @_; - - if (($self->{chosen_name} = $self->{combo}->GetValue)) { + if (($self->{chosen_name} = Slic3r::normalize_utf8_nfc($self->{combo}->GetValue))) { if ($self->{chosen_name} !~ /^[^<>:\/\\|?*\"]+$/) { Slic3r::GUI::show_error($self, "The supplied name is not valid; the following characters are not allowed: <>:/\|?*\""); } elsif ($self->{chosen_name} eq '- default -') { @@ -1783,57 +1779,8 @@ sub accept { } sub get_name { - my $self = shift; + my ($self) = @_; return $self->{chosen_name}; } -package Slic3r::GUI::Tab::Preset; -use Moo; -use List::Util qw(any); - -# The preset represents a "default" set of properties. -has 'default' => (is => 'ro', default => sub { 0 }); -has 'external' => (is => 'ro', default => sub { 0 }); -has 'name' => (is => 'rw', required => 1); -has 'file' => (is => 'rw'); - -# Load a config file, return a C++ class Slic3r::DynamicPrintConfig with $keys initialized from the config file. -# In case of a "default" config item, return the default values. -sub config { - my ($self, $keys) = @_; - - if ($self->default) { - # Perl class Slic3r::Config extends the C++ class Slic3r::DynamicPrintConfig - return Slic3r::Config->new_from_defaults(@$keys); - } else { - if (!-e Slic3r::encode_path($self->file)) { - Slic3r::GUI::show_error(undef, "The selected preset does not exist anymore (" . $self->file . ")."); - return undef; - } - - # apply preset values on top of defaults - my $config = Slic3r::Config->new_from_defaults(@$keys); - my $external_config = eval { Slic3r::Config->load($self->file); }; - if ($@) { - Slic3r::GUI::show_error(undef, $@); - return undef; - } - $config->set($_, $external_config->get($_)) - for grep $external_config->has($_), @$keys; - - if (any { $_ eq 'nozzle_diameter' } @$keys) { - # Loaded the Printer settings. Verify, that all extruder dependent values have enough values. - my $nozzle_diameter = $config->nozzle_diameter; - my $num_extruders = scalar(@{$nozzle_diameter}); - foreach my $key (qw(deretract_speed extruder_colour retract_before_wipe)) { - my $vec = $config->get($key); - push @{$vec}, ($vec->[0]) x ($num_extruders - @{$vec}); - $config->set($key, $vec); - } - } - - return $config; - } -} - 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 160f70e06..12ad2f12f 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -65,6 +65,9 @@ sub process { } # G-code export process, running at a background thread. +# The export_gcode may die for various reasons (fails to process output_filename_format, +# write error into the G-code, cannot execute post-processing scripts). +# It is up to the caller to show an error message. sub export_gcode { my $self = shift; my %params = @_; @@ -73,11 +76,12 @@ sub export_gcode { $self->process; # output everything to a G-code file + # The following call may die if the output_filename_format template substitution fails. my $output_file = $self->output_filepath($params{output_file} // ''); $self->status_cb->(90, "Exporting G-code" . ($output_file ? " to $output_file" : "")); - die "G-code export to " . $output_file . " failed\n" - if ! Slic3r::GCode->new->do_export($self, $output_file); + # The following line may die for multiple reasons. + Slic3r::GCode->new->do_export($self, $output_file); # run post-processing scripts if (@{$self->config->post_process}) { @@ -99,6 +103,7 @@ sub export_gcode { } # Export SVG slices for the offline SLA printing. +# The export_svg is expected to be executed inside an eval block. sub export_svg { my $self = shift; my %params = @_; @@ -107,6 +112,7 @@ sub export_svg { my $fh = $params{output_fh}; if (!$fh) { + # The following line may die if the output_filename_format template substitution fails. my $output_file = $self->output_filepath($params{output_file}); $output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/; Slic3r::open(\$fh, ">", $output_file) or die "Failed to open $output_file for writing\n"; @@ -255,13 +261,4 @@ sub make_wipe_tower { $self->set_step_done(STEP_WIPE_TOWER); } -# Wrapper around the C++ Slic3r::Print::validate() -# to produce a Perl exception without a hang-up on some Strawberry perls. -sub validate -{ - my $self = shift; - my $err = $self->_validate; - die $err . "\n" if (defined($err) && $err ne ''); -} - 1; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 793654300..e275fdde5 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -11,9 +11,6 @@ use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union use Slic3r::Print::State ':steps'; use Slic3r::Surface ':types'; -# If enabled, phases of prepare_infill will be written into SVG files to an "out" directory. -our $SLIC3R_DEBUG_SLICE_PROCESSING = 0; - sub layers { my $self = shift; return [ map $self->get_layer($_), 0..($self->layer_count - 1) ]; diff --git a/var/Slic3r-console.ico b/resources/icons/Slic3r-console.ico Binary files differindex 64e818340..64e818340 100644 --- a/var/Slic3r-console.ico +++ b/resources/icons/Slic3r-console.ico diff --git a/var/Slic3r.icns b/resources/icons/Slic3r.icns Binary files differindex c1eba897e..c1eba897e 100644 --- a/var/Slic3r.icns +++ b/resources/icons/Slic3r.icns diff --git a/var/Slic3r.ico b/resources/icons/Slic3r.ico Binary files differindex 5765f77d2..5765f77d2 100644 --- a/var/Slic3r.ico +++ b/resources/icons/Slic3r.ico diff --git a/var/Slic3r.png b/resources/icons/Slic3r.png Binary files differindex 896cc4460..896cc4460 100644 --- a/var/Slic3r.png +++ b/resources/icons/Slic3r.png diff --git a/var/Slic3r_128px.png b/resources/icons/Slic3r_128px.png Binary files differindex e8b2e3e3e..e8b2e3e3e 100644 --- a/var/Slic3r_128px.png +++ b/resources/icons/Slic3r_128px.png diff --git a/var/Slic3r_192px.png b/resources/icons/Slic3r_192px.png Binary files differindex 9af2bfc74..9af2bfc74 100644 --- a/var/Slic3r_192px.png +++ b/resources/icons/Slic3r_192px.png diff --git a/var/Slic3r_192px_transparent.png b/resources/icons/Slic3r_192px_transparent.png Binary files differindex f85c835b3..f85c835b3 100644 --- a/var/Slic3r_192px_transparent.png +++ b/resources/icons/Slic3r_192px_transparent.png diff --git a/var/add.png b/resources/icons/add.png Binary files differindex dde16d5e9..dde16d5e9 100755..100644 --- a/var/add.png +++ b/resources/icons/add.png diff --git a/var/application_view_tile.png b/resources/icons/application_view_tile.png Binary files differindex 3bc0bd32f..3bc0bd32f 100755..100644 --- a/var/application_view_tile.png +++ b/resources/icons/application_view_tile.png diff --git a/var/arrow_down.png b/resources/icons/arrow_down.png Binary files differindex 2c4e27937..2c4e27937 100644 --- a/var/arrow_down.png +++ b/resources/icons/arrow_down.png diff --git a/var/arrow_left.png b/resources/icons/arrow_left.png Binary files differindex 5dc696781..5dc696781 100644 --- a/var/arrow_left.png +++ b/resources/icons/arrow_left.png diff --git a/var/arrow_out.png b/resources/icons/arrow_out.png Binary files differindex 557d7b60b..557d7b60b 100755..100644 --- a/var/arrow_out.png +++ b/resources/icons/arrow_out.png diff --git a/var/arrow_refresh.png b/resources/icons/arrow_refresh.png Binary files differindex 0de26566d..0de26566d 100755..100644 --- a/var/arrow_refresh.png +++ b/resources/icons/arrow_refresh.png diff --git a/var/arrow_right.png b/resources/icons/arrow_right.png Binary files differindex b1a181923..b1a181923 100644 --- a/var/arrow_right.png +++ b/resources/icons/arrow_right.png diff --git a/var/arrow_rotate_anticlockwise.png b/resources/icons/arrow_rotate_anticlockwise.png Binary files differindex abc7535df..abc7535df 100755..100644 --- a/var/arrow_rotate_anticlockwise.png +++ b/resources/icons/arrow_rotate_anticlockwise.png diff --git a/var/arrow_rotate_clockwise.png b/resources/icons/arrow_rotate_clockwise.png Binary files differindex 8624dfd11..8624dfd11 100755..100644 --- a/var/arrow_rotate_clockwise.png +++ b/resources/icons/arrow_rotate_clockwise.png diff --git a/var/arrow_up.png b/resources/icons/arrow_up.png Binary files differindex 3319fcc72..3319fcc72 100755..100644 --- a/var/arrow_up.png +++ b/resources/icons/arrow_up.png diff --git a/var/box.png b/resources/icons/box.png Binary files differindex 424648192..424648192 100755..100644 --- a/var/box.png +++ b/resources/icons/box.png diff --git a/var/brick.png b/resources/icons/brick.png Binary files differindex 660d48d4f..660d48d4f 100755..100644 --- a/var/brick.png +++ b/resources/icons/brick.png diff --git a/var/brick_add.png b/resources/icons/brick_add.png Binary files differindex 978b9fa35..978b9fa35 100755..100644 --- a/var/brick_add.png +++ b/resources/icons/brick_add.png diff --git a/var/brick_delete.png b/resources/icons/brick_delete.png Binary files differindex 0e8ecdbdc..0e8ecdbdc 100755..100644 --- a/var/brick_delete.png +++ b/resources/icons/brick_delete.png diff --git a/var/brick_go.png b/resources/icons/brick_go.png Binary files differindex 409c8f4cb..409c8f4cb 100755..100644 --- a/var/brick_go.png +++ b/resources/icons/brick_go.png diff --git a/var/bricks.png b/resources/icons/bricks.png Binary files differindex 8eb0091ea..8eb0091ea 100755..100644 --- a/var/bricks.png +++ b/resources/icons/bricks.png diff --git a/var/building.png b/resources/icons/building.png Binary files differindex 37f638f27..37f638f27 100755..100644 --- a/var/building.png +++ b/resources/icons/building.png diff --git a/var/bullet_arrow_down.png b/resources/icons/bullet_arrow_down.png Binary files differindex 9b23c06d7..9b23c06d7 100644 --- a/var/bullet_arrow_down.png +++ b/resources/icons/bullet_arrow_down.png diff --git a/var/bullet_arrow_up.png b/resources/icons/bullet_arrow_up.png Binary files differindex 24df0f421..24df0f421 100644 --- a/var/bullet_arrow_up.png +++ b/resources/icons/bullet_arrow_up.png diff --git a/var/bullet_black.png b/resources/icons/bullet_black.png Binary files differindex 0ad73727e..0ad73727e 100755..100644 --- a/var/bullet_black.png +++ b/resources/icons/bullet_black.png diff --git a/var/bullet_blue.png b/resources/icons/bullet_blue.png Binary files differindex a7651ec8a..a7651ec8a 100755..100644 --- a/var/bullet_blue.png +++ b/resources/icons/bullet_blue.png diff --git a/var/bullet_green.png b/resources/icons/bullet_green.png Binary files differindex 058ad261f..058ad261f 100755..100644 --- a/var/bullet_green.png +++ b/resources/icons/bullet_green.png diff --git a/var/bullet_red.png b/resources/icons/bullet_red.png Binary files differindex 0cd803115..0cd803115 100755..100644 --- a/var/bullet_red.png +++ b/resources/icons/bullet_red.png diff --git a/var/bullet_white.png b/resources/icons/bullet_white.png Binary files differindex 2ff85a6fc..2ff85a6fc 100755..100644 --- a/var/bullet_white.png +++ b/resources/icons/bullet_white.png diff --git a/var/cog.png b/resources/icons/cog.png Binary files differindex 3c858b7b2..3c858b7b2 100755..100644 --- a/var/cog.png +++ b/resources/icons/cog.png diff --git a/var/cog_go.png b/resources/icons/cog_go.png Binary files differindex ee647364a..ee647364a 100755..100644 --- a/var/cog_go.png +++ b/resources/icons/cog_go.png diff --git a/var/control_pause.png b/resources/icons/control_pause.png Binary files differindex 2d9ce9c4e..2d9ce9c4e 100644 --- a/var/control_pause.png +++ b/resources/icons/control_pause.png diff --git a/var/control_pause_blue.png b/resources/icons/control_pause_blue.png Binary files differindex ec61099b0..ec61099b0 100644 --- a/var/control_pause_blue.png +++ b/resources/icons/control_pause_blue.png diff --git a/var/control_play.png b/resources/icons/control_play.png Binary files differindex 0846555d0..0846555d0 100644 --- a/var/control_play.png +++ b/resources/icons/control_play.png diff --git a/var/control_play_blue.png b/resources/icons/control_play_blue.png Binary files differindex f8c8ec683..f8c8ec683 100644 --- a/var/control_play_blue.png +++ b/resources/icons/control_play_blue.png diff --git a/var/control_stop.png b/resources/icons/control_stop.png Binary files differindex 893bb60e5..893bb60e5 100644 --- a/var/control_stop.png +++ b/resources/icons/control_stop.png diff --git a/var/control_stop_blue.png b/resources/icons/control_stop_blue.png Binary files differindex e6f75d232..e6f75d232 100644 --- a/var/control_stop_blue.png +++ b/resources/icons/control_stop_blue.png diff --git a/var/cross.png b/resources/icons/cross.png Binary files differindex 54f790b47..54f790b47 100755..100644 --- a/var/cross.png +++ b/resources/icons/cross.png diff --git a/var/delete.png b/resources/icons/delete.png Binary files differindex 143c70944..143c70944 100755..100644 --- a/var/delete.png +++ b/resources/icons/delete.png diff --git a/var/disk.png b/resources/icons/disk.png Binary files differindex 70dd3d3f1..70dd3d3f1 100755..100644 --- a/var/disk.png +++ b/resources/icons/disk.png diff --git a/var/error.png b/resources/icons/error.png Binary files differindex 428360d27..428360d27 100755..100644 --- a/var/error.png +++ b/resources/icons/error.png diff --git a/resources/icons/flag-green-icon.png b/resources/icons/flag-green-icon.png Binary files differnew file mode 100644 index 000000000..e4bc611f8 --- /dev/null +++ b/resources/icons/flag-green-icon.png diff --git a/resources/icons/flag-red-icon.png b/resources/icons/flag-red-icon.png Binary files differnew file mode 100644 index 000000000..e8a602da7 --- /dev/null +++ b/resources/icons/flag-red-icon.png diff --git a/var/funnel.png b/resources/icons/funnel.png Binary files differindex 07ab7ad44..07ab7ad44 100644 --- a/var/funnel.png +++ b/resources/icons/funnel.png diff --git a/var/hourglass.png b/resources/icons/hourglass.png Binary files differindex e39feab8b..e39feab8b 100755..100644 --- a/var/hourglass.png +++ b/resources/icons/hourglass.png diff --git a/var/house.png b/resources/icons/house.png Binary files differindex fed62219f..fed62219f 100644 --- a/var/house.png +++ b/resources/icons/house.png diff --git a/var/infill.png b/resources/icons/infill.png Binary files differindex 69d3c7254..69d3c7254 100644 --- a/var/infill.png +++ b/resources/icons/infill.png diff --git a/var/joystick.png b/resources/icons/joystick.png Binary files differindex 62168f56f..62168f56f 100755..100644 --- a/var/joystick.png +++ b/resources/icons/joystick.png diff --git a/var/layers.png b/resources/icons/layers.png Binary files differindex e1358a362..e1358a362 100755..100644 --- a/var/layers.png +++ b/resources/icons/layers.png diff --git a/var/lorry_add.png b/resources/icons/lorry_add.png Binary files differindex a2c512499..a2c512499 100755..100644 --- a/var/lorry_add.png +++ b/resources/icons/lorry_add.png diff --git a/var/lorry_go.png b/resources/icons/lorry_go.png Binary files differindex 1c296a6aa..1c296a6aa 100755..100644 --- a/var/lorry_go.png +++ b/resources/icons/lorry_go.png diff --git a/var/note.png b/resources/icons/note.png Binary files differindex 40e206f17..40e206f17 100755..100644 --- a/var/note.png +++ b/resources/icons/note.png diff --git a/var/package.png b/resources/icons/package.png Binary files differindex 78945fe19..78945fe19 100755..100644 --- a/var/package.png +++ b/resources/icons/package.png diff --git a/var/package_green.png b/resources/icons/package_green.png Binary files differindex 2e5104016..2e5104016 100755..100644 --- a/var/package_green.png +++ b/resources/icons/package_green.png diff --git a/var/page_white_go.png b/resources/icons/page_white_go.png Binary files differindex fee12fb25..fee12fb25 100755..100644 --- a/var/page_white_go.png +++ b/resources/icons/page_white_go.png diff --git a/var/plugin.png b/resources/icons/plugin.png Binary files differindex 322685238..322685238 100755..100644 --- a/var/plugin.png +++ b/resources/icons/plugin.png diff --git a/var/plugin_add.png b/resources/icons/plugin_add.png Binary files differindex ae43690ec..ae43690ec 100755..100644 --- a/var/plugin_add.png +++ b/resources/icons/plugin_add.png diff --git a/var/plugin_go.png b/resources/icons/plugin_go.png Binary files differindex 41da9913d..41da9913d 100755..100644 --- a/var/plugin_go.png +++ b/resources/icons/plugin_go.png diff --git a/var/printer_empty.png b/resources/icons/printer_empty.png Binary files differindex ddaae7e8a..ddaae7e8a 100755..100644 --- a/var/printer_empty.png +++ b/resources/icons/printer_empty.png diff --git a/var/reslice.png b/resources/icons/reslice.png Binary files differindex 167c0eccd..167c0eccd 100644 --- a/var/reslice.png +++ b/resources/icons/reslice.png diff --git a/var/shape_flip_horizontal.png b/resources/icons/shape_flip_horizontal.png Binary files differindex 8667c81f8..8667c81f8 100755..100644 --- a/var/shape_flip_horizontal.png +++ b/resources/icons/shape_flip_horizontal.png diff --git a/var/shape_handles.png b/resources/icons/shape_handles.png Binary files differindex ce27fe3a0..ce27fe3a0 100755..100644 --- a/var/shape_handles.png +++ b/resources/icons/shape_handles.png diff --git a/var/shape_ungroup.png b/resources/icons/shape_ungroup.png Binary files differindex 97aaceb23..97aaceb23 100755..100644 --- a/var/shape_ungroup.png +++ b/resources/icons/shape_ungroup.png diff --git a/var/spool.png b/resources/icons/spool.png Binary files differindex ab06d9912..ab06d9912 100644 --- a/var/spool.png +++ b/resources/icons/spool.png diff --git a/var/tag_blue.png b/resources/icons/tag_blue.png Binary files differindex 45aebfbb1..45aebfbb1 100755..100644 --- a/var/tag_blue.png +++ b/resources/icons/tag_blue.png diff --git a/var/textfield.png b/resources/icons/textfield.png Binary files differindex d37e7304e..d37e7304e 100755..100644 --- a/var/textfield.png +++ b/resources/icons/textfield.png diff --git a/var/time.png b/resources/icons/time.png Binary files differindex 3b8d2995f..3b8d2995f 100755..100644 --- a/var/time.png +++ b/resources/icons/time.png diff --git a/var/variable_layer_height.png b/resources/icons/variable_layer_height.png Binary files differindex 2fbdd6920..2fbdd6920 100644 --- a/var/variable_layer_height.png +++ b/resources/icons/variable_layer_height.png diff --git a/var/variable_layer_height_reset.png b/resources/icons/variable_layer_height_reset.png Binary files differindex 6e051fe95..6e051fe95 100644 --- a/var/variable_layer_height_reset.png +++ b/resources/icons/variable_layer_height_reset.png diff --git a/var/variable_layer_height_tooltip.png b/resources/icons/variable_layer_height_tooltip.png Binary files differindex 182005292..182005292 100644 --- a/var/variable_layer_height_tooltip.png +++ b/resources/icons/variable_layer_height_tooltip.png diff --git a/var/wand.png b/resources/icons/wand.png Binary files differindex 44ccbf812..44ccbf812 100644 --- a/var/wand.png +++ b/resources/icons/wand.png diff --git a/var/wrench.png b/resources/icons/wrench.png Binary files differindex 4dc553cdd..4dc553cdd 100755..100644 --- a/var/wrench.png +++ b/resources/icons/wrench.png diff --git a/var/zoom.png b/resources/icons/zoom.png Binary files differindex 908612e39..908612e39 100755..100644 --- a/var/zoom.png +++ b/resources/icons/zoom.png diff --git a/resources/profiles/Original Prusa i3 MK3, MK2S, MK2 and MK2S-MMU.ini b/resources/profiles/Original Prusa i3 MK3, MK2S, MK2 and MK2S-MMU.ini new file mode 100644 index 000000000..ae21f862d --- /dev/null +++ b/resources/profiles/Original Prusa i3 MK3, MK2S, MK2 and MK2S-MMU.ini @@ -0,0 +1,3629 @@ +# generated by Slic3r Prusa Edition 1.38.4 on 2017-12-10 at 09:05:01 + +[print:0.05mm DETAIL] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 10 +bridge_acceleration = 300 +bridge_angle = 0 +bridge_flow_ratio = 0.7 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2";"Original Prusa i3 MK2 MM Single Mode" +complete_objects = 0 +default_acceleration = 500 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 20 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 25% +fill_pattern = cubic +first_layer_acceleration = 500 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 20 +gcode_comments = 0 +infill_acceleration = 800 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.5 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 30 +interface_shells = 0 +layer_height = 0.05 +max_print_speed = 80 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 300 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 30 +perimeters = 3 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 15 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 30 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 1 +support_material_extrusion_width = 0.3 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 1 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 1.5 +support_material_speed = 30 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 20 +top_solid_layers = 15 +travel_speed = 180 +wipe_tower = 0 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.05mm DETAIL 0.25 nozzle] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 10 +bridge_acceleration = 300 +bridge_angle = 0 +bridge_flow_ratio = 0.7 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2 0.25 nozzle";"Original Prusa i3 MK2 MM Single Mode 0.25 nozzle" +complete_objects = 0 +default_acceleration = 500 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0 +external_perimeter_speed = 20 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.28 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 500 +first_layer_extrusion_width = 0.3 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 20 +gcode_comments = 0 +infill_acceleration = 800 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 20 +interface_shells = 0 +layer_height = 0.05 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 300 +perimeter_extruder = 1 +perimeter_extrusion_width = 0 +perimeter_speed = 20 +perimeters = 4 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 10 +solid_infill_below_area = 0 +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.15 +support_material_enforce_layers = 0 +support_material_extruder = 1 +support_material_extrusion_width = 0.18 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 1 +support_material_interface_layers = 0 +support_material_interface_spacing = 0.15 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 1 +support_material_speed = 20 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 150% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0 +top_solid_infill_speed = 20 +top_solid_layers = 15 +travel_speed = 200 +wipe_tower = 0 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.05mm DETAIL MK3] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 10 +bridge_acceleration = 300 +bridge_angle = 0 +bridge_flow_ratio = 0.7 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK3" +complete_objects = 0 +default_acceleration = 500 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 20 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 25% +fill_pattern = cubic +first_layer_acceleration = 500 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 20 +gcode_comments = 0 +infill_acceleration = 800 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.5 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 30 +interface_shells = 0 +layer_height = 0.05 +max_print_speed = 80 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 300 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 30 +perimeters = 3 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 15 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 30 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 1 +support_material_extrusion_width = 0.3 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 1 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 1.5 +support_material_speed = 30 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 20 +top_solid_layers = 15 +travel_speed = 180 +wipe_tower = 0 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.10mm DETAIL] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 7 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.7 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2";"Original Prusa i3 MK2 MM Single Mode" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 40 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 60 +interface_shells = 0 +layer_height = 0.1 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 50 +perimeters = 3 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 50 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 1 +support_material_extrusion_width = 0.35 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 1 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 40 +top_solid_layers = 9 +travel_speed = 120 +wipe_tower = 0 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.10mm DETAIL 0.25 nozzle] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 7 +bridge_acceleration = 600 +bridge_angle = 0 +bridge_flow_ratio = 0.7 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2 0.25 nozzle";"Original Prusa i3 MK2 MM Single Mode 0.25 nozzle" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.25 +external_perimeter_speed = 20 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.25 +fill_angle = 45 +fill_density = 15% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.25 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 1600 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.25 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 40 +interface_shells = 0 +layer_height = 0.1 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 600 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.25 +perimeter_speed = 25 +perimeters = 4 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 10 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.25 +solid_infill_speed = 40 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 1 +support_material_extrusion_width = 0.18 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 1 +support_material_interface_layers = 0 +support_material_interface_spacing = 0.15 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 1 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 150% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.25 +top_solid_infill_speed = 30 +top_solid_layers = 9 +travel_speed = 120 +wipe_tower = 0 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.10mm DETAIL MK3] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 4 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 30 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK3" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 40 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 3500 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 35% +infill_speed = 200 +interface_shells = 0 +layer_height = 0.1 +max_print_speed = 250 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 60 +perimeters = 2 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 200 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.35 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 0 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 50 +top_solid_layers = 5 +travel_speed = 250 +wipe_tower = 0 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.15mm 100mms Linear Advance] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 4 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.95 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2";"Original Prusa i3 MK2 MM Single Mode";"Original Prusa i3 MK2 MultiMaterial" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 50 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 100 +interface_shells = 0 +layer_height = 0.15 +max_print_speed = 150 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 60 +perimeters = 2 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 30 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 100 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.35 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 0 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 60 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 70 +top_solid_layers = 5 +travel_speed = 120 +wipe_tower = 1 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.15mm OPTIMAL] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 5 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2";"Original Prusa i3 MK2 MM Single Mode";"Original Prusa i3 MK2 MultiMaterial" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 40 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 60 +interface_shells = 0 +layer_height = 0.15 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 50 +perimeters = 3 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 50 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 1 +support_material_extrusion_width = 0.35 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 1 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 40 +top_solid_layers = 7 +travel_speed = 120 +wipe_tower = 1 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.15mm OPTIMAL 0.25 nozzle] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 5 +bridge_acceleration = 600 +bridge_angle = 0 +bridge_flow_ratio = 0.7 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2 0.25 nozzle";"Original Prusa i3 MK2 MM Single Mode 0.25 nozzle";"Original Prusa i3 MK2 MultiMaterial 0.25 nozzle" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.25 +external_perimeter_speed = 20 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.25 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.25 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 1600 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.25 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 40 +interface_shells = 0 +layer_height = 0.15 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 600 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.25 +perimeter_speed = 25 +perimeters = 2 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 10 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.25 +solid_infill_speed = 40 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 1 +support_material_extrusion_width = 0.2 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 1 +support_material_interface_layers = 0 +support_material_interface_spacing = 0.15 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 1 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 35 +support_material_with_sheath = 0 +support_material_xy_spacing = 150% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.25 +top_solid_infill_speed = 30 +top_solid_layers = 7 +travel_speed = 120 +wipe_tower = 1 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.15mm OPTIMAL 0.6 nozzle] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 5 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2 0.6 nozzle";"Original Prusa i3 MK2 MM Single Mode 0.6 nozzle";"Original Prusa i3 MK2 MultiMaterial 0.6 nozzle" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.61 +external_perimeter_speed = 40 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.67 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.65 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.75 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 60 +interface_shells = 0 +layer_height = 0.15 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.65 +perimeter_speed = 50 +perimeters = 3 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.65 +solid_infill_speed = 50 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.35 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 1 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.6 +top_solid_infill_speed = 40 +top_solid_layers = 7 +travel_speed = 120 +wipe_tower = 1 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.15mm OPTIMAL MK3] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 4 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 30 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK3" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 40 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 3500 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 35% +infill_speed = 200 +interface_shells = 0 +layer_height = 0.15 +max_print_speed = 250 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 60 +perimeters = 2 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 200 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.35 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 0 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 50 +top_solid_layers = 5 +travel_speed = 250 +wipe_tower = 1 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.15mm OPTIMAL SOLUBLE FULL] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 5 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2 MultiMaterial" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 25 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 60 +interface_shells = 0 +layer_height = 0.15 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 1 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 40 +perimeters = 3 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 0 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 40 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 1 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0 +support_material_enforce_layers = 0 +support_material_extruder = 4 +support_material_extrusion_width = 0.45 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 4 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.1 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 1 +support_material_threshold = 80 +support_material_with_sheath = 1 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 +top_solid_layers = 7 +travel_speed = 120 +wipe_tower = 1 +wipe_tower_per_color_wipe = 20 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.15mm OPTIMAL SOLUBLE INTERFACE] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 5 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2 MultiMaterial" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 25 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 60 +interface_shells = 0 +layer_height = 0.15 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 1 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 40 +perimeters = 3 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 0 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 40 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 1 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0 +support_material_enforce_layers = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.45 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 4 +support_material_interface_layers = 3 +support_material_interface_spacing = 0.1 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 1 +support_material_threshold = 80 +support_material_with_sheath = 0 +support_material_xy_spacing = 120% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 30 +top_solid_layers = 7 +travel_speed = 120 +wipe_tower = 1 +wipe_tower_per_color_wipe = 20 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.20mm 100mms Linear Advance] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 4 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.95 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2";"Original Prusa i3 MK2 MM Single Mode";"Original Prusa i3 MK2 MultiMaterial" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 50 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 100 +interface_shells = 0 +layer_height = 0.2 +max_print_speed = 150 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 60 +perimeters = 2 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 30 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 100 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.35 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 0 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 60 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 70 +top_solid_layers = 5 +travel_speed = 120 +wipe_tower = 1 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.20mm NORMAL] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 4 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.95 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2";"Original Prusa i3 MK2 MM Single Mode";"Original Prusa i3 MK2 MultiMaterial" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 40 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 60 +interface_shells = 0 +layer_height = 0.2 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 50 +perimeters = 2 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 50 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.35 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 0 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 40 +top_solid_layers = 5 +travel_speed = 120 +wipe_tower = 1 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.20mm NORMAL 0.6 nozzle] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 4 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2 0.6 nozzle";"Original Prusa i3 MK2 MM Single Mode 0.6 nozzle";"Original Prusa i3 MK2 MultiMaterial 0.6 nozzle" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.61 +external_perimeter_speed = 40 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.67 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.65 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.75 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 60 +interface_shells = 0 +layer_height = 0.2 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.65 +perimeter_speed = 50 +perimeters = 3 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.65 +solid_infill_speed = 50 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.35 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 1 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.6 +top_solid_infill_speed = 40 +top_solid_layers = 5 +travel_speed = 120 +wipe_tower = 1 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.20mm NORMAL MK3] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 4 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 30 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK3" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 40 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 3500 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 35% +infill_speed = 200 +interface_shells = 0 +layer_height = 0.2 +max_print_speed = 250 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 60 +perimeters = 2 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 200 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.35 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 0 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 50 +top_solid_layers = 5 +travel_speed = 250 +wipe_tower = 1 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.20mm NORMAL SOLUBLE FULL] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 4 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.95 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2 MultiMaterial" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 30 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 60 +interface_shells = 0 +layer_height = 0.2 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 1 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 40 +perimeters = 2 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 0 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 40 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 1 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0 +support_material_enforce_layers = 0 +support_material_extruder = 4 +support_material_extrusion_width = 0.45 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 4 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.1 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 1 +support_material_threshold = 80 +support_material_with_sheath = 1 +support_material_xy_spacing = 120% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 30 +top_solid_layers = 5 +travel_speed = 120 +wipe_tower = 1 +wipe_tower_per_color_wipe = 20 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.20mm NORMAL SOLUBLE INTERFACE] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 4 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.95 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2 MultiMaterial" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 30 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 60 +interface_shells = 0 +layer_height = 0.2 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 1 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 40 +perimeters = 2 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 0 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +solid_infill_speed = 40 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 1 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0 +support_material_enforce_layers = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.45 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 4 +support_material_interface_layers = 3 +support_material_interface_spacing = 0.1 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 1 +support_material_threshold = 80 +support_material_with_sheath = 0 +support_material_xy_spacing = 120% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 30 +top_solid_layers = 5 +travel_speed = 120 +wipe_tower = 1 +wipe_tower_per_color_wipe = 20 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.35mm FAST] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 3 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.95 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2";"Original Prusa i3 MK2 MM Single Mode";"Original Prusa i3 MK2 MultiMaterial" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 40 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.7 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 60 +interface_shells = 0 +layer_height = 0.35 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.43 +perimeter_speed = 50 +perimeters = 2 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.7 +solid_infill_speed = 60 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 1 +support_material_extrusion_width = 0.35 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 1 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.43 +top_solid_infill_speed = 50 +top_solid_layers = 4 +travel_speed = 120 +wipe_tower = 1 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.35mm FAST 0.6 nozzle] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 7 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2 0.6 nozzle";"Original Prusa i3 MK2 MM Single Mode 0.6 nozzle";"Original Prusa i3 MK2 MultiMaterial 0.6 nozzle" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.61 +external_perimeter_speed = 40 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.67 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.65 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.75 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 60 +interface_shells = 0 +layer_height = 0.35 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.65 +perimeter_speed = 50 +perimeters = 3 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.65 +solid_infill_speed = 60 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.35 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 1 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.6 +top_solid_infill_speed = 50 +top_solid_layers = 9 +travel_speed = 120 +wipe_tower = 1 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.35mm FAST MK3] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 4 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 30 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK3" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 40 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 3500 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.7 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 35% +infill_speed = 200 +interface_shells = 0 +layer_height = 0.35 +max_print_speed = 250 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 0 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 60 +perimeters = 2 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 1 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.7 +solid_infill_speed = 200 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.35 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 0 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 50 +top_solid_layers = 4 +travel_speed = 250 +wipe_tower = 1 +wipe_tower_per_color_wipe = 15 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.35mm FAST sol full 0.6 nozzle] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 3 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 30 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.67 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.65 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.75 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 60 +interface_shells = 0 +layer_height = 0.35 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 1 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.65 +perimeter_speed = 40 +perimeters = 2 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 0 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.65 +solid_infill_speed = 60 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 1 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0 +support_material_enforce_layers = 0 +support_material_extruder = 4 +support_material_extrusion_width = 0.55 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 4 +support_material_interface_layers = 3 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 1 +support_material_threshold = 80 +support_material_with_sheath = 0 +support_material_xy_spacing = 120% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.57 +top_solid_infill_speed = 50 +top_solid_layers = 4 +travel_speed = 120 +wipe_tower = 1 +wipe_tower_per_color_wipe = 20 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:0.35mm FAST sol int 0.6 nozzle] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 3 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle" +complete_objects = 0 +default_acceleration = 1000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 30 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.67 +fill_angle = 45 +fill_density = 20% +fill_pattern = cubic +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.65 +first_layer_height = 0.2 +first_layer_speed = 30 +gap_fill_speed = 40 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.75 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 60 +interface_shells = 0 +layer_height = 0.35 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = [input_filename_base].gcode +overhangs = 1 +perimeter_acceleration = 800 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.65 +perimeter_speed = 40 +perimeters = 2 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +skirt_distance = 2 +skirt_height = 3 +skirts = 0 +small_perimeter_speed = 20 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.65 +solid_infill_speed = 60 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 1 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_contact_distance = 0 +support_material_enforce_layers = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.55 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 4 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 1 +support_material_threshold = 80 +support_material_with_sheath = 0 +support_material_xy_spacing = 150% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.57 +top_solid_infill_speed = 50 +top_solid_layers = 4 +travel_speed = 120 +wipe_tower = 1 +wipe_tower_per_color_wipe = 20 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[filament:ColorFabb Brass Bronze 1.75mm] +bed_temperature = 60 +bridge_fan_speed = 100 +compatible_printers = "Original Prusa i3 MK2";"Original Prusa i3 MK2 0.6 nozzle";"Original Prusa i3 MK2 MM Single Mode";"Original Prusa i3 MK2 MM Single Mode 0.6 nozzle";"Original Prusa i3 MK2 MultiMaterial";"Original Prusa i3 MK2 MultiMaterial 0.6 nozzle";"Original Prusa i3 MK3" +cooling = 1 +disable_fan_first_layers = 1 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1.3 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #804040 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 10 +filament_notes = "" +filament_settings_id = +filament_soluble = 0 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 210 +max_fan_speed = 100 +min_fan_speed = 85 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}10{endif}; Filament gcode" +temperature = 210 + +[filament:ColorFabb HT 1.75mm] +bed_temperature = 105 +bridge_fan_speed = 30 +compatible_printers = +cooling = 1 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_colour = #FF8000 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 10 +filament_notes = "" +filament_settings_id = +filament_soluble = 0 +filament_type = PLA +first_layer_bed_temperature = 105 +first_layer_temperature = 270 +max_fan_speed = 20 +min_fan_speed = 10 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}45{endif}; Filament gcode" +temperature = 270 + +[filament:ColorFabb PLA-PHA] +bed_temperature = 60 +bridge_fan_speed = 100 +compatible_printers = +cooling = 1 +disable_fan_first_layers = 1 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #FF3232 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 15 +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +filament_settings_id = +filament_soluble = 0 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 85 +min_print_speed = 15 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}30{endif}; Filament gcode" +temperature = 210 + +[filament:ColorFabb Woodfil 1.75mm] +bed_temperature = 60 +bridge_fan_speed = 100 +compatible_printers = "Original Prusa i3 MK2";"Original Prusa i3 MK2 0.6 nozzle";"Original Prusa i3 MK2 MM Single Mode";"Original Prusa i3 MK2 MM Single Mode 0.6 nozzle";"Original Prusa i3 MK2 MultiMaterial";"Original Prusa i3 MK2 MultiMaterial 0.6 nozzle";"Original Prusa i3 MK3" +cooling = 1 +disable_fan_first_layers = 1 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1.2 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #804040 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 10 +filament_notes = "" +filament_settings_id = +filament_soluble = 0 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 200 +max_fan_speed = 100 +min_fan_speed = 85 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}10{endif}; Filament gcode" +temperature = 200 + +[filament:ColorFabb XT 1.75mm] +bed_temperature = 65 +bridge_fan_speed = 50 +compatible_printers = +cooling = 1 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 20 +filament_colour = #FF8000 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 10 +filament_notes = "" +filament_settings_id = +filament_soluble = 0 +filament_type = PLA +first_layer_bed_temperature = 90 +first_layer_temperature = 240 +max_fan_speed = 50 +min_fan_speed = 30 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}45{endif}; Filament gcode" +temperature = 240 + +[filament:ColorFabb XT-CF20 1.75mm] +bed_temperature = 90 +bridge_fan_speed = 50 +compatible_printers = +cooling = 1 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1.2 +fan_always_on = 1 +fan_below_layer_time = 20 +filament_colour = #804040 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 1 +filament_notes = "" +filament_settings_id = +filament_soluble = 0 +filament_type = PET +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +max_fan_speed = 50 +min_fan_speed = 30 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}30{endif}; Filament gcode" +temperature = 260 + +[filament:ColorFabb nGen 1.75mm] +bed_temperature = 85 +bridge_fan_speed = 40 +compatible_printers = +cooling = 1 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_colour = #FF8000 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 10 +filament_notes = "" +filament_settings_id = +filament_soluble = 0 +filament_type = NGEN +first_layer_bed_temperature = 85 +first_layer_temperature = 240 +max_fan_speed = 35 +min_fan_speed = 20 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}45{endif}; Filament gcode" +temperature = 240 + +[filament:ColorFabb nGen flex] +bed_temperature = 85 +bridge_fan_speed = 40 +compatible_printers = +cooling = 1 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_colour = #FF8000 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 5 +filament_notes = "" +filament_settings_id = +filament_soluble = 0 +filament_type = FLEX +first_layer_bed_temperature = 85 +first_layer_temperature = 260 +max_fan_speed = 35 +min_fan_speed = 20 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}10{endif}; Filament gcode" +temperature = 260 + +[filament:E3D Edge] +bed_temperature = 90 +bridge_fan_speed = 50 +compatible_printers = +cooling = 1 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 20 +filament_colour = #FF8000 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 10 +filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty MladeÄ PETG" +filament_settings_id = +filament_soluble = 0 +filament_type = PET +first_layer_bed_temperature = 85 +first_layer_temperature = 230 +max_fan_speed = 50 +min_fan_speed = 30 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}45{endif}; Filament gcode" +temperature = 240 + +[filament:E3D PC-ABS 1.75mm] +bed_temperature = 100 +bridge_fan_speed = 30 +compatible_printers = +cooling = 0 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #3A80CA +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 13 +filament_notes = "" +filament_settings_id = +filament_soluble = 0 +filament_type = PLA +first_layer_bed_temperature = 100 +first_layer_temperature = 270 +max_fan_speed = 30 +min_fan_speed = 10 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}30{endif}; Filament gcode" +temperature = 270 + +[filament:Fillamentum ABS 1.75mm] +bed_temperature = 100 +bridge_fan_speed = 30 +compatible_printers = +cooling = 0 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #3A80CA +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 13 +filament_notes = "" +filament_settings_id = +filament_soluble = 0 +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 240 +max_fan_speed = 30 +min_fan_speed = 10 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}30{endif}; Filament gcode" +temperature = 240 + +[filament:Fillamentum ASA 1.75mm] +bed_temperature = 100 +bridge_fan_speed = 30 +compatible_printers = +cooling = 0 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 20 +filament_colour = #3A80CA +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 13 +filament_notes = "" +filament_settings_id = +filament_soluble = 0 +filament_type = PLA +first_layer_bed_temperature = 100 +first_layer_temperature = 265 +max_fan_speed = 30 +min_fan_speed = 10 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}30{endif}; Filament gcode" +temperature = 265 + +[filament:Fillamentum CPE HG100 HM100] +bed_temperature = 90 +bridge_fan_speed = 50 +compatible_printers = +cooling = 1 +disable_fan_first_layers = 1 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 20 +filament_colour = #FF8000 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 10 +filament_notes = "CPE HG100 , CPE HM100" +filament_settings_id = +filament_soluble = 0 +filament_type = PET +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +max_fan_speed = 80 +min_fan_speed = 80 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}45{endif}; Filament gcode" +temperature = 260 + +[filament:Fillamentum Timberfil] +bed_temperature = 60 +bridge_fan_speed = 100 +compatible_printers = "Original Prusa i3 MK2";"Original Prusa i3 MK2 0.6 nozzle";"Original Prusa i3 MK2 MM Single Mode";"Original Prusa i3 MK2 MM Single Mode 0.6 nozzle";"Original Prusa i3 MK2 MultiMaterial";"Original Prusa i3 MK2 MultiMaterial 0.6 nozzle";"Original Prusa i3 MK3" +cooling = 1 +disable_fan_first_layers = 1 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1.2 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #804040 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 10 +filament_notes = "" +filament_settings_id = +filament_soluble = 0 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 190 +max_fan_speed = 100 +min_fan_speed = 85 +min_print_speed = 15 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}10{endif}; Filament gcode" +temperature = 190 + +[filament:Generic ABS 1.75mm] +bed_temperature = 100 +bridge_fan_speed = 30 +compatible_printers = +cooling = 0 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #3A80CA +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 13 +filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty MladeÄ ABS" +filament_settings_id = +filament_soluble = 0 +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 30 +min_fan_speed = 10 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}30{endif}; Filament gcode" +temperature = 255 + +[filament:Generic PET 1.75mm] +bed_temperature = 90 +bridge_fan_speed = 50 +compatible_printers = +cooling = 1 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 20 +filament_colour = #FF8000 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 10 +filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty MladeÄ PETG" +filament_settings_id = +filament_soluble = 0 +filament_type = PET +first_layer_bed_temperature = 85 +first_layer_temperature = 230 +max_fan_speed = 50 +min_fan_speed = 30 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}45{endif}; Filament gcode" +temperature = 240 + +[filament:Generic PLA 1.75mm] +bed_temperature = 60 +bridge_fan_speed = 100 +compatible_printers = +cooling = 1 +disable_fan_first_layers = 1 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #FF3232 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 15 +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +filament_settings_id = +filament_soluble = 0 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 85 +min_print_speed = 15 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}30{endif}; Filament gcode" +temperature = 210 + +[filament:Polymaker PC-Max] +bed_temperature = 120 +bridge_fan_speed = 30 +compatible_printers = +cooling = 0 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #3A80CA +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 13 +filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty MladeÄ ABS" +filament_settings_id = +filament_soluble = 0 +filament_type = ABS +first_layer_bed_temperature = 120 +first_layer_temperature = 270 +max_fan_speed = 30 +min_fan_speed = 10 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}30{endif}; Filament gcode" +temperature = 270 + +[filament:Primavalue PVA] +bed_temperature = 60 +bridge_fan_speed = 100 +compatible_printers = +cooling = 0 +disable_fan_first_layers = 1 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #FFFFD7 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 10 +filament_notes = "List of materials tested with standart PVA print settings for MK2:\n\nPrimaSelect PVA+\nICE FILAMENTS PVA 'NAUGHTY NATURAL'\nVerbatim BVOH" +filament_settings_id = +filament_soluble = 1 +filament_type = PVA +first_layer_bed_temperature = 60 +first_layer_temperature = 195 +max_fan_speed = 100 +min_fan_speed = 85 +min_print_speed = 15 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}10{endif}; Filament gcode" +temperature = 195 + +[filament:Prusa ABS 1.75mm] +bed_temperature = 100 +bridge_fan_speed = 30 +compatible_printers = +cooling = 0 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #3A80CA +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 13 +filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty MladeÄ ABS" +filament_settings_id = +filament_soluble = 0 +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 30 +min_fan_speed = 10 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}30{endif}; Filament gcode" +temperature = 255 + +[filament:Prusa HIPS 1.75mm] +bed_temperature = 100 +bridge_fan_speed = 50 +compatible_printers = +cooling = 1 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 0.9 +fan_always_on = 1 +fan_below_layer_time = 10 +filament_colour = #FFFFD7 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 13 +filament_notes = "" +filament_settings_id = +filament_soluble = 1 +filament_type = HIPS +first_layer_bed_temperature = 100 +first_layer_temperature = 220 +max_fan_speed = 20 +min_fan_speed = 20 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}10{endif}; Filament gcode" +temperature = 220 + +[filament:Prusa PET 1.75mm] +bed_temperature = 90 +bridge_fan_speed = 50 +compatible_printers = +cooling = 1 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 20 +filament_colour = #FF8000 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 10 +filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty MladeÄ PETG" +filament_settings_id = +filament_soluble = 0 +filament_type = PET +first_layer_bed_temperature = 85 +first_layer_temperature = 230 +max_fan_speed = 50 +min_fan_speed = 30 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}45{endif}; Filament gcode" +temperature = 240 + +[filament:Prusa PLA 1.75mm] +bed_temperature = 60 +bridge_fan_speed = 100 +compatible_printers = +cooling = 1 +disable_fan_first_layers = 1 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #FF3232 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 15 +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +filament_settings_id = +filament_soluble = 0 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 85 +min_print_speed = 15 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}30{endif}; Filament gcode" +temperature = 210 + +[filament:SemiFlex or Flexfill 98A] +bed_temperature = 50 +bridge_fan_speed = 100 +compatible_printers = "Original Prusa i3 MK2";"Original Prusa i3 MK2 0.6 nozzle";"Original Prusa i3 MK2 MM Single Mode";"Original Prusa i3 MK2 MM Single Mode 0.6 nozzle";"Original Prusa i3 MK3" +cooling = 0 +disable_fan_first_layers = 1 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1.2 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #00CA0A +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 2.5 +filament_notes = "List of materials tested with FLEX print settings & FLEX material settings for MK2:\n\nFillamentum Flex 98A\nFillamentum Flex 92A\nPlasty MladeÄ PP\nPlasty MladeÄ TPE32 \nPlasty MladeÄ TPE88" +filament_settings_id = +filament_soluble = 0 +filament_type = FLEX +first_layer_bed_temperature = 50 +first_layer_temperature = 220 +max_fan_speed = 90 +min_fan_speed = 70 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}10{endif}; Filament gcode" +temperature = 230 + +[filament:Taulman Bridge 1.75mm] +bed_temperature = 50 +bridge_fan_speed = 40 +compatible_printers = +cooling = 0 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #DEE0E6 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 10 +filament_notes = "" +filament_settings_id = +filament_soluble = 0 +filament_type = PLA +first_layer_bed_temperature = 90 +first_layer_temperature = 240 +max_fan_speed = 5 +min_fan_speed = 0 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}10{endif}; Filament gcode" +temperature = 250 + +[filament:Taulman T-Glase 1.75mm] +bed_temperature = 90 +bridge_fan_speed = 40 +compatible_printers = +cooling = 0 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 20 +filament_colour = #FF8000 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 10 +filament_notes = "" +filament_settings_id = +filament_soluble = 0 +filament_type = PET +first_layer_bed_temperature = 90 +first_layer_temperature = 240 +max_fan_speed = 5 +min_fan_speed = 0 +min_print_speed = 5 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}30{endif}; Filament gcode" +temperature = 240 + +[filament:Verbatim BVOH] +bed_temperature = 60 +bridge_fan_speed = 100 +compatible_printers = +cooling = 0 +disable_fan_first_layers = 1 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 100 +filament_colour = #FFFFD7 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 10 +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +filament_settings_id = +filament_soluble = 1 +filament_type = PLA +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 85 +min_print_speed = 15 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}10{endif}; Filament gcode" +temperature = 210 + +[filament:Verbatim PP] +bed_temperature = 100 +bridge_fan_speed = 100 +compatible_printers = +cooling = 1 +disable_fan_first_layers = 2 +end_filament_gcode = "; Filament-specific end gcode" +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #DEE0E6 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_max_volumetric_speed = 5 +filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nEsun PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nEUMAKERS PLA" +filament_settings_id = +filament_soluble = 0 +filament_type = PLA +first_layer_bed_temperature = 100 +first_layer_temperature = 220 +max_fan_speed = 100 +min_fan_speed = 100 +min_print_speed = 15 +slowdown_below_layer_time = 10 +start_filament_gcode = "M900 K{if printer_preset==\"Original Prusa i3 MK2 MultiMaterial\"}200{elsif printer_preset==\"Original Prusa i3 MK2 MM Single Mode\"}200{else}10{endif}; Filament gcode" +temperature = 220 + +[printer:Original Prusa i3 MK2] +bed_shape = 0x0,250x0,250x210,0x210 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n +between_objects_gcode = +deretract_speed = 0 +end_gcode = G4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +extruder_colour = #FFFF00 +extruder_offset = 0x0 +gcode_flavor = reprap +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +max_layer_height = 0.25 +min_layer_height = 0.07 +nozzle_diameter = 0.4 +octoprint_apikey = +octoprint_host = +printer_notes = +printer_settings_id = +retract_before_travel = 1 +retract_before_wipe = 0% +retract_layer_change = 1 +retract_length = 0.8 +retract_length_toolchange = 3 +retract_lift = 0.5 +retract_lift_above = 1 +retract_lift_below = 199 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 35 +serial_port = +serial_speed = 250000 +single_extruder_multi_material = 0 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 1 +wipe = 1 +z_offset = 0 + +[printer:Original Prusa i3 MK2 0.25 nozzle] +bed_shape = 0x0,250x0,250x210,0x210 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n +between_objects_gcode = +deretract_speed = 0 +end_gcode = G4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +extruder_colour = #FFFF00 +extruder_offset = 0x0 +gcode_flavor = reprap +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +max_layer_height = 0.1 +min_layer_height = 0.05 +nozzle_diameter = 0.25 +octoprint_apikey = +octoprint_host = +printer_notes = +printer_settings_id = +retract_before_travel = 1 +retract_before_wipe = 0% +retract_layer_change = 1 +retract_length = 1 +retract_length_toolchange = 3 +retract_lift = 0.5 +retract_lift_above = 1 +retract_lift_below = 199 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 50 +serial_port = +serial_speed = 250000 +single_extruder_multi_material = 0 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 0 +wipe = 1 +z_offset = 0 + +[printer:Original Prusa i3 MK2 0.6 nozzle] +bed_shape = 0x0,250x0,250x210,0x210 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n +between_objects_gcode = +deretract_speed = 0 +end_gcode = G4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +extruder_colour = #FFFF00 +extruder_offset = 0x0 +gcode_flavor = reprap +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +max_layer_height = 0.35 +min_layer_height = 0.1 +nozzle_diameter = 0.6 +octoprint_apikey = +octoprint_host = +printer_notes = +printer_settings_id = +retract_before_travel = 1 +retract_before_wipe = 0% +retract_layer_change = 1 +retract_length = 0.8 +retract_length_toolchange = 3 +retract_lift = 0.5 +retract_lift_above = 1 +retract_lift_below = 199 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 35 +serial_port = +serial_speed = 250000 +single_extruder_multi_material = 0 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 1 +wipe = 1 +z_offset = 0 + +[printer:Original Prusa i3 MK2 MM Single Mode] +bed_shape = 0x0,250x0,250x210,0x210 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n +between_objects_gcode = +deretract_speed = 50 +end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n +extruder_colour = #FFAA55 +extruder_offset = 0x0 +gcode_flavor = reprap +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +max_layer_height = 0.25 +min_layer_height = 0.07 +nozzle_diameter = 0.4 +octoprint_apikey = +octoprint_host = +printer_notes = +printer_settings_id = +retract_before_travel = 3 +retract_before_wipe = 0% +retract_layer_change = 0 +retract_length = 4 +retract_length_toolchange = 6 +retract_lift = 0.5 +retract_lift_above = 0 +retract_lift_below = 199 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 80 +serial_port = +serial_speed = 250000 +single_extruder_multi_material = 1 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 1 +wipe = 1 +z_offset = 0 + +[printer:Original Prusa i3 MK2 MM Single Mode 0.6 nozzle] +bed_shape = 0x0,250x0,250x210,0x210 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n +between_objects_gcode = +deretract_speed = 50 +end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n +extruder_colour = #FFAA55 +extruder_offset = 0x0 +gcode_flavor = reprap +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +max_layer_height = 0.25 +min_layer_height = 0.07 +nozzle_diameter = 0.6 +octoprint_apikey = +octoprint_host = +printer_notes = +printer_settings_id = +retract_before_travel = 3 +retract_before_wipe = 0% +retract_layer_change = 0 +retract_length = 4 +retract_length_toolchange = 6 +retract_lift = 0.5 +retract_lift_above = 0 +retract_lift_below = 199 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 80 +serial_port = +serial_speed = 250000 +single_extruder_multi_material = 1 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 1 +wipe = 1 +z_offset = 0 + +[printer:Original Prusa i3 MK2 MultiMaterial] +bed_shape = 0x0,250x0,250x210,0x210 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n +between_objects_gcode = +deretract_speed = 50,50,50,50 +end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes.\nG1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\n{endif}\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors +extruder_colour = #FFAA55;#5182DB;#4ECDD3;#FB7259 +extruder_offset = 0x0,0x0,0x0,0x0 +gcode_flavor = reprap +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +max_layer_height = 0.25,0.25,0.25,0.25 +min_layer_height = 0.07,0.07,0.07,0.07 +nozzle_diameter = 0.4,0.4,0.4,0.4 +octoprint_apikey = +octoprint_host = +printer_notes = +printer_settings_id = +retract_before_travel = 3,3,3,3 +retract_before_wipe = 60%,60%,60%,60% +retract_layer_change = 0,0,0,0 +retract_length = 4,4,4,4 +retract_length_toolchange = 4,4,4,4 +retract_lift = 0.5,0.5,0.5,0.5 +retract_lift_above = 0,0,0,0 +retract_lift_below = 199,199,199,199 +retract_restart_extra = 0,0,0,0 +retract_restart_extra_toolchange = 0,0,0,0 +retract_speed = 80,80,80,80 +serial_port = +serial_speed = 250000 +single_extruder_multi_material = 1 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 0 +wipe = 1,1,1,1 +z_offset = 0 + +[printer:Original Prusa i3 MK2 MultiMaterial 0.6 nozzle] +bed_shape = 0x0,250x0,250x210,0x210 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n +between_objects_gcode = +deretract_speed = 50,50,50,50 +end_gcode = {if not has_wipe_tower}\nG1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\n{endif}\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n +extruder_colour = #FFAA55;#5182DB;#4ECDD3;#FB7259 +extruder_offset = 0x0,0x0,0x0,0x0 +gcode_flavor = reprap +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +max_layer_height = 0.25,0.25,0.25,0.25 +min_layer_height = 0.07,0.07,0.07,0.07 +nozzle_diameter = 0.6,0.6,0.6,0.6 +octoprint_apikey = +octoprint_host = +printer_notes = +printer_settings_id = +retract_before_travel = 3,3,3,3 +retract_before_wipe = 60%,60%,60%,60% +retract_layer_change = 0,0,0,0 +retract_length = 4,4,4,4 +retract_length_toolchange = 4,4,4,4 +retract_lift = 0.5,0.5,0.5,0.5 +retract_lift_above = 0,0,0,0 +retract_lift_below = 199,199,199,199 +retract_restart_extra = 0,0,0,0 +retract_restart_extra_toolchange = 0,0,0,0 +retract_speed = 80,80,80,80 +serial_port = +serial_speed = 250000 +single_extruder_multi_material = 1 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 0 +wipe = 1,1,1,1 +z_offset = 0 + +[printer:Original Prusa i3 MK3] +bed_shape = 0x0,250x0,250x210,0x210 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n +between_objects_gcode = +deretract_speed = 0 +end_gcode = G4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors +extruder_colour = #FFFF00 +extruder_offset = 0x0 +gcode_flavor = reprap +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +max_layer_height = 0.25 +min_layer_height = 0.07 +nozzle_diameter = 0.4 +octoprint_apikey = +octoprint_host = +printer_notes = +retract_before_travel = 1 +retract_before_wipe = 0% +retract_layer_change = 1 +retract_length = 0.8 +retract_length_toolchange = 3 +retract_lift = 0.5 +retract_lift_above = 1 +retract_lift_below = 199 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 35 +serial_port = +serial_speed = 250000 +single_extruder_multi_material = 0 +start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside pritn area\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 1 +wipe = 1 +z_offset = 0 + +[presets] +print = 0.15mm 100mms Linear Advance +printer = Original Prusa i3 MK2 +filament = Prusa PLA 1.75mm @@ -31,6 +31,7 @@ my %cli_options = (); 'debug' => \$Slic3r::debug, 'gui' => \$opt{gui}, + 'no-gui' => \$opt{no_gui}, 'o|output=s' => \$opt{output}, 'save=s' => \$opt{save}, @@ -92,20 +93,20 @@ if ($opt{save}) { if (@{$cli_config->get_keys} > 0) { $cli_config->save($opt{save}); } else { - Slic3r::Config->new_from_defaults->save($opt{save}); + Slic3r::Config::new_from_defaults->save($opt{save}); } } # apply command line config on top of default config -my $config = Slic3r::Config->new_from_defaults; +my $config = Slic3r::Config::new_from_defaults; $config->apply($cli_config); # launch GUI my $gui; -if ((!@ARGV || $opt{gui}) && !$opt{save} && eval "require Slic3r::GUI; 1") { +if ((!@ARGV || $opt{gui}) && !$opt{no_gui} && !$opt{save} && eval "require Slic3r::GUI; 1") { { no warnings 'once'; - $Slic3r::GUI::datadir = $opt{datadir} // ''; + $Slic3r::GUI::datadir = Slic3r::decode_path($opt{datadir} // ''); $Slic3r::GUI::no_controller = $opt{no_controller}; $Slic3r::GUI::no_plater = $opt{no_plater}; $Slic3r::GUI::autosave = $opt{autosave}; @@ -218,6 +219,8 @@ if (@ARGV) { # slicing from command line $sprint->export_svg; } else { my $t0 = [gettimeofday]; + # The following call may die if the output_filename_format template substitution fails, + # if the file cannot be written into, or if the post processing scripts cannot be executed. $sprint->export_gcode; # output some statistics @@ -236,16 +239,7 @@ if (@ARGV) { # slicing from command line sub usage { my ($exit_code) = @_; - - my $config = Slic3r::Config->new_from_defaults->as_hash; - - my $j = ''; - if ($Slic3r::have_threads) { - $j = <<"EOF"; - -j, --threads <num> Number of threads to use (1+, default: $config->{threads}) -EOF - } - + my $config = Slic3r::Config::new_from_defaults->as_hash; print <<"EOF"; Slic3r $Slic3r::VERSION is a STL-to-GCODE translator for RepRap 3D printers written by Alessandro Ranellucci <aar\@cpan.org> - http://slic3r.org/ @@ -270,12 +264,14 @@ Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ... them as <name>_upper.stl and <name>_lower.stl --split Split the shells contained in given STL file into several STL files --info Output information about the supplied file(s) and exit - -$j + -j, --threads <num> Number of threads to use (1+, default: $config->{threads}) + GUI options: --gui Forces the GUI launch instead of command line slicing (if you supply a model file, it will be loaded into the plater) --no-plater Disable the plater tab + --no-gui Forces the command line slicing instead of gui. + This takes precedence over --gui if both are present. --autosave <file> Automatically export current configuration to the specified file Output options: diff --git a/t/avoid_crossing_perimeters.t b/t/avoid_crossing_perimeters.t index 60442f47f..86c3e91cb 100644 --- a/t/avoid_crossing_perimeters.t +++ b/t/avoid_crossing_perimeters.t @@ -13,7 +13,7 @@ use Slic3r; use Slic3r::Test; { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('avoid_crossing_perimeters', 2); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); ok my $gcode = Slic3r::Test::gcode($print), "no crash with avoid_crossing_perimeters and multiple objects"; diff --git a/t/bridges.t b/t/bridges.t index e3c89faa7..ca55862b6 100644 --- a/t/bridges.t +++ b/t/bridges.t @@ -109,7 +109,7 @@ sub check_angle { } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('top_solid_layers', 0); # to prevent bridging on sparse infill $config->set('bridge_speed', 99); diff --git a/t/combineinfill.t b/t/combineinfill.t index 66e461d45..e94cf9eb5 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -54,7 +54,7 @@ plan tests => 8; 'infill is only present in correct number of layers'; }; - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.2); $config->set('nozzle_diameter', [0.5]); @@ -73,7 +73,7 @@ plan tests => 8; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.2); $config->set('nozzle_diameter', [0.5]); @@ -98,7 +98,7 @@ plan tests => 8; # the following needs to be adapted to the new API if (0) { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('solid_layers', 0); $config->set('bottom_solid_layers', 0); diff --git a/t/config.t b/t/config.t index 829ef5f39..f4a1867de 100644 --- a/t/config.t +++ b/t/config.t @@ -12,7 +12,7 @@ use Slic3r; use Slic3r::Test; { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('perimeter_extrusion_width', '250%'); ok $config->validate, 'percent extrusion width is validated'; } diff --git a/t/cooling.t b/t/cooling.t index 87a557a04..ee4f6abea 100644 --- a/t/cooling.t +++ b/t/cooling.t @@ -42,7 +42,7 @@ my $print_time1 = 100 / (3000 / 60); # 2 sec my $gcode2 = $gcode1 . "G1 X0 E1 F3000\n"; my $print_time2 = 2 * $print_time1; # 4 sec -my $config = Slic3r::Config->new_from_defaults; +my $config = Slic3r::Config::new_from_defaults; # Default cooling settings. $config->set('bridge_fan_speed', [ 100 ]); $config->set('cooling', [ 1 ]); @@ -138,7 +138,7 @@ $config->set('disable_fan_first_layers', [ 0 ]); } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('cooling', [ 1 ]); $config->set('bridge_fan_speed', [ 100 ]); $config->set('fan_below_layer_time', [ 0 ]); @@ -172,7 +172,7 @@ $config->set('disable_fan_first_layers', [ 0 ]); } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('cooling', [ 1 ]); $config->set('fan_below_layer_time', [ 0 ]); $config->set('slowdown_below_layer_time', [ 10 ]); diff --git a/t/custom_gcode.t b/t/custom_gcode.t index 653bb26ae..1ccf738aa 100644 --- a/t/custom_gcode.t +++ b/t/custom_gcode.t @@ -1,4 +1,4 @@ -use Test::More tests => 15; +use Test::More tests => 49; use strict; use warnings; @@ -13,7 +13,7 @@ use Slic3r; use Slic3r::Test; { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; my $test = sub { my ($conf) = @_; @@ -47,15 +47,30 @@ use Slic3r::Test; { my $parser = Slic3r::GCode::PlaceholderParser->new; - $parser->apply_config(my $config = Slic3r::Config->new_from_defaults); - $parser->set('foo' => '0'); + $parser->apply_config(my $config = Slic3r::Config::new_from_defaults); + $parser->set('foo' => 0); + $parser->set('bar' => 2); is $parser->process('[temperature_[foo]]'), $config->temperature->[0], - "nested config options"; + "nested config options (legacy syntax)"; + is $parser->process('{temperature[foo]}'), + $config->temperature->[0], + "array reference"; + is $parser->process("test [ temperature_ [foo] ] \n hu"), + "test " . $config->temperature->[0] . " \n hu", + "whitespaces and newlines are maintained"; + is $parser->process('{2*3}'), '6', 'math: 2*3'; + is $parser->process('{2*3/6}'), '1', 'math: 2*3/6'; + is $parser->process('{2*3/12}'), '0', 'math: 2*3/12'; + ok abs($parser->process('{2.*3/12}') - 0.5) < 1e-7, 'math: 2.*3/12'; + is $parser->process('{2*(3-12)}'), '-18', 'math: 2*(3-12)'; + is $parser->process('{2*foo*(3-12)}'), '0', 'math: 2*foo*(3-12)'; + is $parser->process('{2*bar*(3-12)}'), '-36', 'math: 2*bar*(3-12)'; + ok abs($parser->process('{2.5*bar*(3-12)}') - -45) < 1e-7, 'math: 2.5*bar*(3-12)'; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('output_filename_format', 'ts_[travel_speed]_lh_[layer_height].gcode'); $config->set('start_gcode', "TRAVEL:[travel_speed] HEIGHT:[layer_height]\n"); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); @@ -90,25 +105,44 @@ use Slic3r::Test; ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for second extruder'; } - $config->set('start_gcode', qq! + my @start_gcode = (qq! ;__temp0:[first_layer_temperature_0]__ ;__temp1:[first_layer_temperature_1]__ ;__temp2:[first_layer_temperature_2]__ + !, qq! +;__temp0:{first_layer_temperature[0]}__ +;__temp1:{first_layer_temperature[1]}__ +;__temp2:{first_layer_temperature[2]}__ + !); + my @syntax_description = (' (legacy syntax)', ' (new syntax)'); + for my $i (0, 1) { + $config->set('start_gcode', $start_gcode[$i]); + { + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + # we use the [infill_extruder] placeholder to make sure this test doesn't + # catch a false positive caused by the unparsed start G-code option itself + # being embedded in the G-code + ok $gcode =~ /temp0:200/, 'temperature placeholder for first extruder correctly populated' . $syntax_description[$i]; + ok $gcode =~ /temp1:205/, 'temperature placeholder for second extruder correctly populated' . $syntax_description[$i]; + ok $gcode =~ /temp2:200/, 'temperature placeholder for unused extruder populated with first value' . $syntax_description[$i]; + } + } + + $config->set('start_gcode', qq! +;substitution:{if infill_extruder==1}extruder1 + {elsif infill_extruder==2}extruder2 + {else}extruder3{endif} !); { my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $gcode = Slic3r::Test::gcode($print); - # we use the [infill_extruder] placeholder to make sure this test doesn't - # catch a false positive caused by the unparsed start G-code option itself - # being embedded in the G-code - ok $gcode =~ /temp0:200/, 'temperature placeholder for first extruder correctly populated'; - ok $gcode =~ /temp1:205/, 'temperature placeholder for second extruder correctly populated'; - ok $gcode =~ /temp2:200/, 'temperature placeholder for unused extruder populated with first value'; + ok $gcode =~ /substitution:extruder1/, 'if / else / endif - first block returned'; } } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('before_layer_gcode', ';BEFORE [layer_num]'); $config->set('layer_gcode', ';CHANGE [layer_num]'); $config->set('support_material', 1); @@ -132,4 +166,66 @@ use Slic3r::Test; 'layer_num grows continously'; # i.e. no duplicates or regressions } +{ + my $config = Slic3r::Config->new; + $config->set('start_gcode', qq! +;substitution:{if infill_extruder==1}if block + {elsif infill_extruder==2}elsif block 1 + {elsif infill_extruder==3}elsif block 2 + {elsif infill_extruder==4}elsif block 3 + {else}endif block{endif} + !); + my @returned = ('', 'if block', 'elsif block 1', 'elsif block 2', 'elsif block 3', 'endif block'); + for my $i (1,2,3,4,5) { + $config->set('infill_extruder', $i); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + my $found_other = 0; + for my $j (1,2,3,4,5) { + next if $i == $j; + $found_other = 1 if $gcode =~ /substitution:$returned[$j]/; + } + ok $gcode =~ /substitution:$returned[$i]/, 'if / else / endif - ' . $returned[$i] . ' returned'; + ok !$found_other, 'if / else / endif - only ' . $returned[$i] . ' returned'; + } +} + +{ + my $config = Slic3r::Config->new; + $config->set('start_gcode', + ';substitution:{if infill_extruder==1}{if perimeter_extruder==1}block11{else}block12{endif}' . + '{elsif infill_extruder==2}{if perimeter_extruder==1}block21{else}block22{endif}' . + '{else}{if perimeter_extruder==1}block31{else}block32{endif}{endif}:end'); + for my $i (1,2,3) { + $config->set('infill_extruder', $i); + for my $j (1,2) { + $config->set('perimeter_extruder', $j); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + ok $gcode =~ /substitution:block$i$j:end/, "two level if / else / endif - block$i$j returned"; + } + } +} + +{ + my $config = Slic3r::Config->new; + $config->set('start_gcode', + ';substitution:{if notes=="MK2"}MK2{elsif notes=="MK3"}MK3{else}MK1{endif}:end'); + for my $printer_name ("MK2", "MK3", "MK1") { + $config->set('notes', $printer_name); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $gcode = Slic3r::Test::gcode($print); + ok $gcode =~ /substitution:$printer_name:end/, "printer name $printer_name matched"; + } +} + +{ + my $config = Slic3r::Config::new_from_defaults; + $config->set('complete_objects', 1); + $config->set('between_objects_gcode', '_MY_CUSTOM_GCODE_'); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 3); + my $gcode = Slic3r::Test::gcode($print); + is scalar(() = $gcode =~ /^_MY_CUSTOM_GCODE_/gm), 2, 'between_objects_gcode is applied correctly'; +} + __END__ @@ -163,7 +163,7 @@ SKIP: } for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('fill_pattern', $pattern); $config->set('external_fill_pattern', $pattern); $config->set('perimeters', 1); @@ -194,7 +194,7 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('infill_only_where_needed', 1); $config->set('bottom_solid_layers', 0); $config->set('infill_extruder', 2); @@ -240,7 +240,7 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 1); $config->set('fill_density', 0); @@ -270,7 +270,7 @@ for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 3); $config->set('fill_density', 0); @@ -14,7 +14,7 @@ use Slic3r::Geometry qw(scale PI); use Slic3r::Test; { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 1); $config->set('brim_width', 2); $config->set('perimeters', 3); @@ -41,7 +41,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('bridge_speed', 99); $config->set('bridge_flow_ratio', 1); $config->set('cooling', [ 0 ]); # to prevent speeds from being altered @@ -16,7 +16,7 @@ use Slic3r::Surface ':types'; use Slic3r::Test; { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('perimeter_speed', 66); $config->set('external_perimeter_speed', 66); @@ -21,7 +21,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('wipe', [1]); $config->set('retract_layer_change', [0]); @@ -52,7 +52,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('z_offset', 5); $config->set('start_gcode', ''); @@ -86,7 +86,7 @@ use Slic3r::Test; # - Z moves are correctly generated for both objects # - no travel moves go outside skirt # - temperatures are set correctly - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('gcode_comments', 1); $config->set('complete_objects', 1); $config->set('extrusion_axis', 'A'); @@ -130,7 +130,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('retract_length', [1000000]); $config->set('use_relative_e_distances', 1); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); @@ -162,7 +162,7 @@ use Slic3r::Test; }; { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('gcode_flavor', 'sailfish'); $config->set('raft_layers', 3); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); @@ -170,21 +170,21 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('gcode_flavor', 'sailfish'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); $test->($print, 'two copies of single object'); } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('gcode_flavor', 'sailfish'); my $print = Slic3r::Test::init_print(['20mm_cube','20mm_cube'], config => $config); $test->($print, 'two objects'); } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('gcode_flavor', 'sailfish'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale_xyz => [1,1, 1/(20/$config->layer_height) ]); $test->($print, 'one layer object'); @@ -192,7 +192,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('start_gcode', 'START:[input_filename]'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $gcode = Slic3r::Test::gcode($print); @@ -200,7 +200,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('spiral_vase', 1); my $print = Slic3r::Test::init_print('cube_with_hole', config => $config); @@ -220,7 +220,7 @@ use Slic3r::Test; { # Tests that the Repetier flavor produces M201 Xnnn Ynnn for resetting # acceleration, also that M204 Snnn syntax is not generated. - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('gcode_flavor', 'repetier'); $config->set('default_acceleration', 1337); my $print = Slic3r::Test::init_print('cube_with_hole', config => $config); diff --git a/t/layers.t b/t/layers.t index a85998077..a9f7dfe39 100644 --- a/t/layers.t +++ b/t/layers.t @@ -13,7 +13,7 @@ use Slic3r; use Slic3r::Test qw(_eq); { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; my $test = sub { my ($conf) = @_; @@ -15,7 +15,7 @@ use Slic3r::Geometry::Clipper qw(offset); use Slic3r::Test; { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('raft_layers', 2); $config->set('infill_extruder', 2); $config->set('solid_infill_extruder', 3); @@ -88,7 +88,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('support_material_extruder', 3); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); @@ -125,7 +125,7 @@ use Slic3r::Test; $upper_config->set('extruder', 2); $upper_config->set('bottom_solid_layers', 1); $upper_config->set('top_solid_layers', 0); - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('fill_density', 0); $config->set('solid_infill_speed', 99); $config->set('top_solid_infill_speed', 99); @@ -171,7 +171,7 @@ use Slic3r::Test; my $model = stacked_cubes(); my $object = $model->objects->[0]; - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.4); $config->set('first_layer_height', '100%'); $config->set('skirts', 0); diff --git a/t/perimeters.t b/t/perimeters.t index 2c190fd69..ee332616d 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -156,7 +156,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('fill_density', 0); $config->set('perimeters', 3); @@ -284,7 +284,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 3); $config->set('layer_height', 0.4); @@ -314,7 +314,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('nozzle_diameter', [0.4]); $config->set('perimeters', 2); $config->set('perimeter_extrusion_width', 0.4); @@ -372,7 +372,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 3); $config->set('layer_height', 0.4); @@ -401,7 +401,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('seam_position', 'random'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok Slic3r::Test::gcode($print), 'successful generation of G-code with seam_position = random'; @@ -410,7 +410,7 @@ use Slic3r::Test; { my $test = sub { my ($model_name) = @_; - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('seam_position', 'aligned'); $config->set('skirts', 0); $config->set('perimeters', 1); @@ -14,7 +14,7 @@ use Slic3r::Geometry qw(unscale X Y); use Slic3r::Test; { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; my $print_center = [100,100]; my $print = Slic3r::Test::init_print('20mm_cube', config => $config, print_center => $print_center); my @extrusion_points = (); @@ -33,7 +33,7 @@ use Slic3r::Test; { # this represents the aggregate config from presets - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; # user adds one object to the plater my $print = Slic3r::Test::init_print(my $model = Slic3r::Test::model('20mm_cube'), config => $config); diff --git a/t/retraction.t b/t/retraction.t index a0631f31a..d7f1ea145 100644 --- a/t/retraction.t +++ b/t/retraction.t @@ -13,7 +13,7 @@ use Slic3r; use Slic3r::Test qw(_eq); { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; my $duplicate = 1; my $test = sub { @@ -131,7 +131,7 @@ use Slic3r::Test qw(_eq); } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('start_gcode', ''); # prevent any default priming Z move from affecting our lift detection $config->set('retract_length', [0]); $config->set('retract_layer_change', [0]); @@ -165,7 +165,7 @@ use Slic3r::Test qw(_eq); } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('use_firmware_retraction', 1); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); @@ -188,7 +188,7 @@ use Slic3r::Test qw(_eq); } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('use_firmware_retraction', 1); $config->set('retract_length', [0]); @@ -206,7 +206,7 @@ use Slic3r::Test qw(_eq); } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('start_gcode', ''); $config->set('retract_lift', [3, 4]); diff --git a/t/shells.t b/t/shells.t index 03bc5665c..eac95aa32 100644 --- a/t/shells.t +++ b/t/shells.t @@ -14,7 +14,7 @@ use Slic3r::Geometry qw(epsilon); use Slic3r::Test; { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 0); $config->set('solid_infill_speed', 99); @@ -82,7 +82,7 @@ use Slic3r::Test; # issue #1161 { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.3); $config->set('first_layer_height', '100%'); $config->set('bottom_solid_layers', 0); @@ -106,7 +106,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; # we need to check against one perimeter because this test is calibrated # (shape, extrusion_width) so that perimeters cover the bottom surfaces of # their lower layer - the test checks that shells are not generated on the @@ -137,7 +137,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('perimeters', 3); $config->set('cooling', [ 0 ]); # prevent speed alteration $config->set('first_layer_speed', '100%'); # prevent speed alteration @@ -161,7 +161,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('perimeters', 1); $config->set('fill_density', 0); $config->set('top_solid_layers', 0); @@ -221,7 +221,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('spiral_vase', 1); $config->set('perimeters', 1); $config->set('fill_density', 0); @@ -292,7 +292,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('perimeters', 1); $config->set('fill_density', 0); $config->set('top_solid_layers', 0); diff --git a/t/skirt_brim.t b/t/skirt_brim.t index 4d0155eba..225b0a92f 100644 --- a/t/skirt_brim.t +++ b/t/skirt_brim.t @@ -14,7 +14,7 @@ use Slic3r::Geometry qw(unscale convex_hull); use Slic3r::Test; { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 1); $config->set('skirt_height', 2); $config->set('perimeters', 0); @@ -46,7 +46,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 0); $config->set('top_solid_layers', 0); # to prevent solid shells and their speeds @@ -72,7 +72,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 1); $config->set('brim_width', 10); @@ -81,7 +81,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 1); $config->set('skirt_height', 0); @@ -90,7 +90,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.4); $config->set('first_layer_height', 0.4); $config->set('skirts', 1); @@ -135,7 +135,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('min_skirt_length', 20); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok Slic3r::Test::gcode($print), 'no crash when using min_skirt_length'; diff --git a/t/support.t b/t/support.t index ed68c276e..a0cac1470 100644 --- a/t/support.t +++ b/t/support.t @@ -19,7 +19,7 @@ use Slic3r::Geometry::Clipper qw(diff); use Slic3r::Test; { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('support_material', 1); my @contact_z = my @top_z = (); @@ -77,7 +77,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('raft_layers', 3); $config->set('brim_width', 0); $config->set('skirts', 0); @@ -108,7 +108,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('raft_layers', 3); $config->set('support_material_pattern', 'honeycomb'); @@ -153,7 +153,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('layer_height', 0.35); $config->set('first_layer_height', 0.3); @@ -192,7 +192,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('brim_width', 0); $config->set('skirts', 0); $config->set('support_material', 1); @@ -232,7 +232,7 @@ use Slic3r::Test; } { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('start_gcode', ''); $config->set('raft_layers', 8); @@ -16,7 +16,7 @@ use Slic3r::Test; # Disable this until a more robust implementation is provided. It currently # fails on Linux 32bit because some spurious extrudates are generated. if (0) { - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.2); $config->set('first_layer_height', '100%'); $config->set('extrusion_width', 0.5); diff --git a/t/threads.t b/t/threads.t index 7fcd86f0e..7fede3328 100644 --- a/t/threads.t +++ b/t/threads.t @@ -1,4 +1,4 @@ -use Test::More; +use Test::More tests => 2; use strict; use warnings; @@ -12,11 +12,6 @@ use List::Util qw(first); use Slic3r; use Slic3r::Test; -if (!$Slic3r::have_threads) { - plan skip_all => "this perl is not compiled with threads"; -} -plan tests => 2; - { my $print = Slic3r::Test::init_print('20mm_cube'); { diff --git a/utils/config-bundle-to-config.pl b/utils/config-bundle-to-config.pl deleted file mode 100755 index e1d7f6143..000000000 --- a/utils/config-bundle-to-config.pl +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/perl -# This script extracts a full active config from a config bundle. -# (Often users reporting issues don't attach plain configs, but -# bundles...) - -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Getopt::Long qw(:config no_auto_abbrev); -use Slic3r; -use Slic3r::Test; -$|++; - -my %opt = (); -{ - my %options = ( - 'help' => sub { usage() }, - 'output=s' => \$opt{output}, - ); - GetOptions(%options) or usage(1); - $ARGV[0] or usage(1); -} - -($ARGV[0] && $opt{output}) or usage(1); - -{ - my $bundle_ini = Slic3r::Config->read_ini($ARGV[0]) - or die "Failed to read $ARGV[0]\n"; - - my $config_ini = { _ => {} }; - foreach my $section (qw(print filament printer)) { - my $preset_name = $bundle_ini->{presets}{$section}; - $preset_name =~ s/\.ini$//; - my $preset = $bundle_ini->{"$section:$preset_name"} - or die "Failed to find preset $preset_name in bundle\n"; - $config_ini->{_}{$_} = $preset->{$_} for keys %$preset; - } - - Slic3r::Config->write_ini($opt{output}, $config_ini); -} - - -sub usage { - my ($exit_code) = @_; - - print <<"EOF"; -Usage: config-bundle-to-config.pl --output config.ini bundle.ini -EOF - exit ($exit_code || 0); -} - -__END__ diff --git a/utils/view-toolpaths.pl b/utils/view-toolpaths.pl index d4c47f072..234a8123d 100755 --- a/utils/view-toolpaths.pl +++ b/utils/view-toolpaths.pl @@ -33,7 +33,7 @@ my %opt = (); my $model = Slic3r::Model->read_from_file($ARGV[0]); # load config - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config::new_from_defaults; if ($opt{load}) { $config->apply(Slic3r::Config->load($opt{load})); } diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 7224353d4..07442f248 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -26,6 +26,7 @@ include_directories(${LIBDIR}/libslic3r) if(WIN32) # BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking. add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB) + # -D_ITERATOR_DEBUG_LEVEL) endif() add_definitions(-DwxUSE_UNICODE -D_UNICODE -DUNICODE) @@ -162,12 +163,18 @@ add_library(libslic3r STATIC ) add_library(libslic3r_gui STATIC + ${LIBDIR}/slic3r/GUI/AppConfig.cpp + ${LIBDIR}/slic3r/GUI/AppConfig.hpp ${LIBDIR}/slic3r/GUI/3DScene.cpp ${LIBDIR}/slic3r/GUI/3DScene.hpp ${LIBDIR}/slic3r/GUI/GLShader.cpp ${LIBDIR}/slic3r/GUI/GLShader.hpp ${LIBDIR}/slic3r/GUI/Preset.cpp ${LIBDIR}/slic3r/GUI/Preset.hpp + ${LIBDIR}/slic3r/GUI/PresetBundle.cpp + ${LIBDIR}/slic3r/GUI/PresetBundle.hpp + ${LIBDIR}/slic3r/GUI/PresetHints.cpp + ${LIBDIR}/slic3r/GUI/PresetHints.hpp ${LIBDIR}/slic3r/GUI/GUI.cpp ${LIBDIR}/slic3r/GUI/GUI.hpp ) @@ -279,6 +286,7 @@ set(XS_XSP_FILES ${XSP_DIR}/GCodeSender.xsp ${XSP_DIR}/Geometry.xsp ${XSP_DIR}/GUI.xsp + ${XSP_DIR}/GUI_AppConfig.xsp ${XSP_DIR}/GUI_3DScene.xsp ${XSP_DIR}/GUI_Preset.xsp ${XSP_DIR}/Layer.xsp diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 0e995acf2..26c8befe2 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -179,14 +179,6 @@ sub new_from_width { ); } -sub new_from_spacing { - my ($class, %args) = @_; - - return $class->_new_from_spacing( - @args{qw(spacing nozzle_diameter layer_height bridge)}, - ); -} - package Slic3r::Surface; sub new { diff --git a/xs/src/boost/nowide/cenv.hpp b/xs/src/boost/nowide/cenv.hpp index 5f6d084af..a38a24b97 100644 --- a/xs/src/boost/nowide/cenv.hpp +++ b/xs/src/boost/nowide/cenv.hpp @@ -101,18 +101,16 @@ namespace nowide { { char const *key = string; char const *key_end = string; - while(*key_end!='=' && key_end!='\0') - key_end++; - if(*key_end == '\0') + while(*key_end != '=' && *key_end != 0) + ++ key_end; + if(*key_end == 0) return -1; wshort_stackstring wkey; if(!wkey.convert(key,key_end)) return -1; - wstackstring wvalue; if(!wvalue.convert(key_end+1)) return -1; - if(SetEnvironmentVariableW(wkey.c_str(),wvalue.c_str())) return 0; return -1; diff --git a/xs/src/libslic3r/BoundingBox.cpp b/xs/src/libslic3r/BoundingBox.cpp index 08be120b6..46a43ca69 100644 --- a/xs/src/libslic3r/BoundingBox.cpp +++ b/xs/src/libslic3r/BoundingBox.cpp @@ -7,7 +7,8 @@ namespace Slic3r { template <class PointClass> BoundingBoxBase<PointClass>::BoundingBoxBase(const std::vector<PointClass> &points) { - if (points.empty()) CONFESS("Empty point set supplied to BoundingBoxBase constructor"); + if (points.empty()) + CONFESS("Empty point set supplied to BoundingBoxBase constructor"); typename std::vector<PointClass>::const_iterator it = points.begin(); this->min.x = this->max.x = it->x; this->min.y = this->max.y = it->y; @@ -26,7 +27,8 @@ template <class PointClass> BoundingBox3Base<PointClass>::BoundingBox3Base(const std::vector<PointClass> &points) : BoundingBoxBase<PointClass>(points) { - if (points.empty()) CONFESS("Empty point set supplied to BoundingBox3Base constructor"); + if (points.empty()) + CONFESS("Empty point set supplied to BoundingBox3Base constructor"); typename std::vector<PointClass>::const_iterator it = points.begin(); this->min.z = this->max.z = it->z; for (++it; it != points.end(); ++it) { @@ -39,9 +41,10 @@ template BoundingBox3Base<Pointf3>::BoundingBox3Base(const std::vector<Pointf3> BoundingBox::BoundingBox(const Lines &lines) { Points points; - for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { - points.push_back(line->a); - points.push_back(line->b); + points.reserve(lines.size()); + for (const Line &line : lines) { + points.emplace_back(line.a); + points.emplace_back(line.b); } *this = BoundingBox(points); } @@ -190,9 +193,9 @@ BoundingBox3Base<PointClass>::size() const } template Pointf3 BoundingBox3Base<Pointf3>::size() const; -template <class PointClass> double -BoundingBoxBase<PointClass>::radius() const +template <class PointClass> double BoundingBoxBase<PointClass>::radius() const { + assert(this->defined); double x = this->max.x - this->min.x; double y = this->max.y - this->min.y; return 0.5 * sqrt(x*x+y*y); @@ -200,8 +203,7 @@ BoundingBoxBase<PointClass>::radius() const template double BoundingBoxBase<Point>::radius() const; template double BoundingBoxBase<Pointf>::radius() const; -template <class PointClass> double -BoundingBox3Base<PointClass>::radius() const +template <class PointClass> double BoundingBox3Base<PointClass>::radius() const { double x = this->max.x - this->min.x; double y = this->max.y - this->min.y; diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index b8954cc07..728ed608b 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -1,6 +1,6 @@ #include "Config.hpp" +#include "Utils.hpp" #include <assert.h> -#include <ctime> #include <fstream> #include <iostream> #include <exception> // std::runtime_error @@ -16,7 +16,6 @@ #include <boost/nowide/cenv.hpp> #include <boost/nowide/fstream.hpp> #include <boost/property_tree/ini_parser.hpp> -#include <boost/property_tree/ptree.hpp> #include <string.h> #if defined(_WIN32) && !defined(setenv) && defined(_putenv_s) @@ -235,13 +234,16 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con { t_config_option_key opt_key = opt_key_src; // Try to deserialize the option by its name. - const ConfigOptionDef* optdef = this->def()->get(opt_key); + const ConfigDef *def = this->def(); + if (def == nullptr) + throw NoDefinitionException(); + const ConfigOptionDef *optdef = def->get(opt_key); if (optdef == nullptr) { // If we didn't find an option, look for any other option having this as an alias. - for (const auto &opt : this->def()->options) { + for (const auto &opt : def->options) { for (const t_config_option_key &opt_key2 : opt.second.aliases) { if (opt_key2 == opt_key) { - opt_key = opt_key2; + opt_key = opt.first; optdef = &opt.second; break; } @@ -278,10 +280,16 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const return static_cast<const ConfigOptionFloat*>(raw_opt)->value; if (raw_opt->type() == coFloatOrPercent) { // Get option definition. - const ConfigOptionDef *def = this->def()->get(opt_key); - assert(def != nullptr); + const ConfigDef *def = this->def(); + if (def == nullptr) + throw NoDefinitionException(); + const ConfigOptionDef *opt_def = def->get(opt_key); + assert(opt_def != nullptr); // Compute absolute value over the absolute value of the base option. - return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(this->get_abs_value(def->ratio_over)); + //FIXME there are some ratio_over chains, which end with empty ratio_with. + // For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly. + return opt_def->ratio_over.empty() ? 0. : + static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over)); } throw std::runtime_error("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()"); } @@ -321,11 +329,23 @@ void ConfigBase::setenv_() void ConfigBase::load(const std::string &file) { - namespace pt = boost::property_tree; - pt::ptree tree; + if (boost::iends_with(file, ".gcode") || boost::iends_with(file, ".g")) + this->load_from_gcode(file); + else + this->load_from_ini(file); +} + +void ConfigBase::load_from_ini(const std::string &file) +{ + boost::property_tree::ptree tree; boost::nowide::ifstream ifs(file); - pt::read_ini(ifs, tree); - for (const pt::ptree::value_type &v : tree) { + boost::property_tree::read_ini(ifs, tree); + this->load(tree); +} + +void ConfigBase::load(const boost::property_tree::ptree &tree) +{ + for (const boost::property_tree::ptree::value_type &v : tree) { try { t_config_option_key opt_key = v.first; this->set_deserialize(opt_key, v.second.get_value<std::string>()); @@ -414,18 +434,24 @@ void ConfigBase::save(const std::string &file) const { boost::nowide::ofstream c; c.open(file, std::ios::out | std::ios::trunc); - { - std::time_t now; - time(&now); - char buf[sizeof "0000-00-00 00:00:00"]; - strftime(buf, sizeof(buf), "%F %T", gmtime(&now)); - c << "# generated by Slic3r " << SLIC3R_VERSION << " on " << buf << std::endl; - } + c << "# " << Slic3r::header_slic3r_generated() << std::endl; for (const std::string &opt_key : this->keys()) c << opt_key << " = " << this->serialize(opt_key) << std::endl; c.close(); } +bool DynamicConfig::operator==(const DynamicConfig &rhs) const +{ + t_options_map::const_iterator it1 = this->options.begin(); + t_options_map::const_iterator it1_end = this->options.end(); + t_options_map::const_iterator it2 = rhs.options.begin(); + t_options_map::const_iterator it2_end = rhs.options.end(); + for (; it1 != it1_end && it2 != it2_end; ++ it1, ++ it2) + if (*it1->second != *it2->second) + return false; + return it1 == it1_end && it2 == it2_end; +} + ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create) { t_options_map::iterator it = options.find(opt_key); @@ -436,7 +462,10 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre // Option was not found and a new option shall not be created. return nullptr; // Try to create a new ConfigOption. - const ConfigOptionDef *optdef = this->def()->get(opt_key); + const ConfigDef *def = this->def(); + if (def == nullptr) + throw NoDefinitionException(); + const ConfigOptionDef *optdef = def->get(opt_key); if (optdef == nullptr) // throw std::runtime_error(std::string("Invalid option name: ") + opt_key); // Let the parent decide what to do if the opt_key is not defined by this->def(). diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index e4968a24b..c203f3be2 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -13,6 +13,8 @@ #include "libslic3r.h" #include "Point.hpp" +#include <boost/property_tree/ptree.hpp> + namespace Slic3r { // Name of the configuration option. @@ -27,41 +29,42 @@ extern bool unescape_strings_cstyle(const std::string &str, std::vector< // Type of a configuration value. enum ConfigOptionType { - coNone, + coVectorType = 0x4000, + coNone = 0, // single float - coFloat, + coFloat = 1, // vector of floats - coFloats, + coFloats = coFloat + coVectorType, // single int - coInt, + coInt = 2, // vector of ints - coInts, + coInts = coInt + coVectorType, // single string - coString, + coString = 3, // vector of strings - coStrings, + coStrings = coString + coVectorType, // percent value. Currently only used for infill. - coPercent, + coPercent = 4, // percents value. Currently used for retract before wipe only. - coPercents, + coPercents = coPercent + coVectorType, // a fraction or an absolute value - coFloatOrPercent, + coFloatOrPercent = 5, // single 2d point. Currently not used. - coPoint, + coPoint = 6, // vector of 2d points. Currently used for the definition of the print bed and for the extruder offsets. - coPoints, + coPoints = coPoint + coVectorType, // single boolean value - coBool, + coBool = 7, // vector of boolean values - coBools, + coBools = coBool + coVectorType, // a generic enum - coEnum, + coEnum = 8, }; // A generic value of a configuration option. class ConfigOption { public: - virtual ~ConfigOption() {}; + virtual ~ConfigOption() {} virtual ConfigOptionType type() const = 0; virtual std::string serialize() const = 0; @@ -75,8 +78,13 @@ public: virtual void setInt(int /* val */) { throw std::runtime_error("Calling ConfigOption::setInt on a non-int ConfigOption"); } virtual bool operator==(const ConfigOption &rhs) const = 0; bool operator!=(const ConfigOption &rhs) const { return ! (*this == rhs); } + bool is_scalar() const { return (int(this->type()) & int(coVectorType)) == 0; } + bool is_vector() const { return ! this->is_scalar(); } }; +typedef ConfigOption* ConfigOptionPtr; +typedef const ConfigOption* ConfigOptionConstPtr; + // Value of a single valued option (bool, int, float, string, point, enum) template <class T> class ConfigOptionSingle : public ConfigOption { @@ -91,7 +99,7 @@ public: throw std::runtime_error("ConfigOptionSingle: Assigning an incompatible type"); assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs)); this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value; - }; + } bool operator==(const ConfigOption &rhs) const override { @@ -110,6 +118,25 @@ class ConfigOptionVectorBase : public ConfigOption { public: // Currently used only to initialize the PlaceholderParser. virtual std::vector<std::string> vserialize() const = 0; + // Set from a vector of ConfigOptions. + // If the rhs ConfigOption is scalar, then its value is used, + // otherwise for each of rhs, the first value of a vector is used. + // This function is useful to collect values for multiple extrder / filament settings. + virtual void set(const std::vector<const ConfigOption*> &rhs) = 0; + // Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions. + // This function is useful to split values from multiple extrder / filament settings into separate configurations. + virtual void set_at(const ConfigOption *rhs, size_t i, size_t j) = 0; + + virtual void resize(size_t n, const ConfigOption *opt_default = nullptr) = 0; + + // Get size of this vector. + virtual size_t size() const = 0; + // Is this vector empty? + virtual bool empty() const = 0; + +protected: + // Used to verify type compatibility when assigning to / from a scalar ConfigOption. + ConfigOptionType scalar_type() const { return static_cast<ConfigOptionType>(this->type() - coVectorType); } }; // Value of a vector valued option (bools, ints, floats, strings, points), template @@ -117,6 +144,11 @@ template <class T> class ConfigOptionVector : public ConfigOptionVectorBase { public: + ConfigOptionVector() {} + explicit ConfigOptionVector(size_t n, const T &value) : values(n, value) {} + explicit ConfigOptionVector(std::initializer_list<T> il) : values(std::move(il)) {} + explicit ConfigOptionVector(const std::vector<T> &values) : values(values) {} + explicit ConfigOptionVector(std::vector<T> &&values) : values(std::move(values)) {} std::vector<T> values; void set(const ConfigOption *rhs) override @@ -125,16 +157,88 @@ public: throw std::runtime_error("ConfigOptionVector: Assigning an incompatible type"); assert(dynamic_cast<const ConfigOptionVector<T>*>(rhs)); this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values; - }; + } + + // Set from a vector of ConfigOptions. + // If the rhs ConfigOption is scalar, then its value is used, + // otherwise for each of rhs, the first value of a vector is used. + // This function is useful to collect values for multiple extrder / filament settings. + void set(const std::vector<const ConfigOption*> &rhs) override + { + this->values.clear(); + this->values.reserve(rhs.size()); + for (const ConfigOption *opt : rhs) { + if (opt->type() == this->type()) { + auto other = static_cast<const ConfigOptionVector<T>*>(opt); + if (other->values.empty()) + throw std::runtime_error("ConfigOptionVector::set(): Assigning from an empty vector"); + this->values.emplace_back(other->values.front()); + } else if (opt->type() == this->scalar_type()) + this->values.emplace_back(static_cast<const ConfigOptionSingle<T>*>(opt)->value); + else + throw std::runtime_error("ConfigOptionVector::set():: Assigning an incompatible type"); + } + } + + // Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions. + // This function is useful to split values from multiple extrder / filament settings into separate configurations. + void set_at(const ConfigOption *rhs, size_t i, size_t j) override + { + // It is expected that the vector value has at least one value, which is the default, if not overwritten. + assert(! this->values.empty()); + if (this->values.size() <= i) { + // Resize this vector, fill in the new vector fields with the copy of the first field. + T v = this->values.front(); + this->values.resize(i + 1, v); + } + if (rhs->type() == this->type()) { + // Assign the first value of the rhs vector. + auto other = static_cast<const ConfigOptionVector<T>*>(rhs); + if (other->values.empty()) + throw std::runtime_error("ConfigOptionVector::set_at(): Assigning from an empty vector"); + this->values[i] = other->get_at(j); + } else if (rhs->type() == this->scalar_type()) + this->values[i] = static_cast<const ConfigOptionSingle<T>*>(rhs)->value; + else + throw std::runtime_error("ConfigOptionVector::set_at(): Assigning an incompatible type"); + } T& get_at(size_t i) { assert(! this->values.empty()); return (i < this->values.size()) ? this->values[i] : this->values.front(); - }; + } const T& get_at(size_t i) const { return const_cast<ConfigOptionVector<T>*>(this)->get_at(i); } + // Resize this vector by duplicating the last value. + // If the current vector is empty, the default value is used instead. + void resize(size_t n, const ConfigOption *opt_default = nullptr) override + { + assert(opt_default == nullptr || opt_default->is_vector()); +// assert(opt_default == nullptr || dynamic_cast<ConfigOptionVector<T>>(opt_default)); + assert(! this->values.empty() || opt_default != nullptr); + if (n == 0) + this->values.clear(); + else if (n < this->values.size()) + this->values.erase(this->values.begin() + n, this->values.end()); + else if (n > this->values.size()) { + if (this->values.empty()) { + if (opt_default == nullptr) + throw std::runtime_error("ConfigOptionVector::resize(): No default value provided."); + if (opt_default->type() != this->type()) + throw std::runtime_error("ConfigOptionVector::resize(): Extending with an incompatible type."); + this->values.resize(n, static_cast<const ConfigOptionVector<T>*>(opt_default)->values.front()); + } else { + // Resize by duplicating the last value. + this->values.resize(n, this->values.back()); + } + } + } + + size_t size() const override { return this->values.size(); } + bool empty() const override { return this->values.empty(); } + bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) @@ -150,13 +254,14 @@ public: class ConfigOptionFloat : public ConfigOptionSingle<double> { public: - ConfigOptionFloat() : ConfigOptionSingle<double>(0) {}; - explicit ConfigOptionFloat(double _value) : ConfigOptionSingle<double>(_value) {}; - - ConfigOptionType type() const override { return coFloat; } - double getFloat() const override { return this->value; } - ConfigOption* clone() const override { return new ConfigOptionFloat(*this); } - bool operator==(const ConfigOptionFloat &rhs) const { return this->value == rhs.value; } + ConfigOptionFloat() : ConfigOptionSingle<double>(0) {} + explicit ConfigOptionFloat(double _value) : ConfigOptionSingle<double>(_value) {} + + static ConfigOptionType static_type() { return coFloat; } + ConfigOptionType type() const override { return static_type(); } + double getFloat() const override { return this->value; } + ConfigOption* clone() const override { return new ConfigOptionFloat(*this); } + bool operator==(const ConfigOptionFloat &rhs) const { return this->value == rhs.value; } std::string serialize() const override { @@ -183,9 +288,14 @@ public: class ConfigOptionFloats : public ConfigOptionVector<double> { public: - ConfigOptionType type() const override { return coFloats; } - ConfigOption* clone() const override { return new ConfigOptionFloats(*this); } - bool operator==(const ConfigOptionFloats &rhs) const { return this->values == rhs.values; } + ConfigOptionFloats() : ConfigOptionVector<double>() {} + explicit ConfigOptionFloats(size_t n, double value) : ConfigOptionVector<double>(n, value) {} + explicit ConfigOptionFloats(std::initializer_list<double> il) : ConfigOptionVector<double>(std::move(il)) {} + + static ConfigOptionType static_type() { return coFloats; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionFloats(*this); } + bool operator==(const ConfigOptionFloats &rhs) const { return this->values == rhs.values; } std::string serialize() const override { @@ -195,7 +305,7 @@ public: ss << *it; } return ss.str(); - }; + } std::vector<std::string> vserialize() const override { @@ -234,15 +344,16 @@ public: class ConfigOptionInt : public ConfigOptionSingle<int> { public: - ConfigOptionInt() : ConfigOptionSingle<int>(0) {}; - explicit ConfigOptionInt(int value) : ConfigOptionSingle<int>(value) {}; - explicit ConfigOptionInt(double _value) : ConfigOptionSingle<int>(int(floor(_value + 0.5))) {}; + ConfigOptionInt() : ConfigOptionSingle<int>(0) {} + explicit ConfigOptionInt(int value) : ConfigOptionSingle<int>(value) {} + explicit ConfigOptionInt(double _value) : ConfigOptionSingle<int>(int(floor(_value + 0.5))) {} - ConfigOptionType type() const override { return coInt; } - int getInt() const override { return this->value; }; - void setInt(int val) { this->value = val; }; - ConfigOption* clone() const override { return new ConfigOptionInt(*this); } - bool operator==(const ConfigOptionInt &rhs) const { return this->value == rhs.value; } + static ConfigOptionType static_type() { return coInt; } + ConfigOptionType type() const override { return static_type(); } + int getInt() const override { return this->value; } + void setInt(int val) { this->value = val; } + ConfigOption* clone() const override { return new ConfigOptionInt(*this); } + bool operator==(const ConfigOptionInt &rhs) const { return this->value == rhs.value; } std::string serialize() const override { @@ -269,10 +380,15 @@ public: class ConfigOptionInts : public ConfigOptionVector<int> { public: - ConfigOptionType type() const override { return coInts; } - ConfigOption* clone() const override { return new ConfigOptionInts(*this); } - ConfigOptionInts& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionInts &rhs) const { return this->values == rhs.values; } + ConfigOptionInts() : ConfigOptionVector<int>() {} + explicit ConfigOptionInts(size_t n, int value) : ConfigOptionVector<int>(n, value) {} + explicit ConfigOptionInts(std::initializer_list<int> il) : ConfigOptionVector<int>(std::move(il)) {} + + static ConfigOptionType static_type() { return coInts; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionInts(*this); } + ConfigOptionInts& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionInts &rhs) const { return this->values == rhs.values; } std::string serialize() const override { std::ostringstream ss; @@ -314,13 +430,14 @@ public: class ConfigOptionString : public ConfigOptionSingle<std::string> { public: - ConfigOptionString() : ConfigOptionSingle<std::string>("") {}; - explicit ConfigOptionString(std::string _value) : ConfigOptionSingle<std::string>(_value) {}; + ConfigOptionString() : ConfigOptionSingle<std::string>("") {} + explicit ConfigOptionString(const std::string &value) : ConfigOptionSingle<std::string>(value) {} - ConfigOptionType type() const override { return coString; } - ConfigOption* clone() const override { return new ConfigOptionString(*this); } - ConfigOptionString& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionString &rhs) const { return this->value == rhs.value; } + static ConfigOptionType static_type() { return coString; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionString(*this); } + ConfigOptionString& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionString &rhs) const { return this->value == rhs.value; } std::string serialize() const override { @@ -338,10 +455,17 @@ public: class ConfigOptionStrings : public ConfigOptionVector<std::string> { public: - ConfigOptionType type() const override { return coStrings; } - ConfigOption* clone() const override { return new ConfigOptionStrings(*this); } - ConfigOptionStrings& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionStrings &rhs) const { return this->values == rhs.values; } + ConfigOptionStrings() : ConfigOptionVector<std::string>() {} + explicit ConfigOptionStrings(size_t n, const std::string &value) : ConfigOptionVector<std::string>(n, value) {} + explicit ConfigOptionStrings(const std::vector<std::string> &values) : ConfigOptionVector<std::string>(values) {} + explicit ConfigOptionStrings(std::vector<std::string> &&values) : ConfigOptionVector<std::string>(std::move(values)) {} + explicit ConfigOptionStrings(std::initializer_list<std::string> il) : ConfigOptionVector<std::string>(std::move(il)) {} + + static ConfigOptionType static_type() { return coStrings; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionStrings(*this); } + ConfigOptionStrings& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionStrings &rhs) const { return this->values == rhs.values; } std::string serialize() const override { @@ -364,14 +488,15 @@ public: class ConfigOptionPercent : public ConfigOptionFloat { public: - ConfigOptionPercent() : ConfigOptionFloat(0) {}; - explicit ConfigOptionPercent(double _value) : ConfigOptionFloat(_value) {}; + ConfigOptionPercent() : ConfigOptionFloat(0) {} + explicit ConfigOptionPercent(double _value) : ConfigOptionFloat(_value) {} - ConfigOptionType type() const override { return coPercent; } - ConfigOption* clone() const override { return new ConfigOptionPercent(*this); } - ConfigOptionPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionPercent &rhs) const { return this->value == rhs.value; } - double get_abs_value(double ratio_over) const { return ratio_over * this->value / 100; } + static ConfigOptionType static_type() { return coPercent; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionPercent(*this); } + ConfigOptionPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionPercent &rhs) const { return this->value == rhs.value; } + double get_abs_value(double ratio_over) const { return ratio_over * this->value / 100; } std::string serialize() const override { @@ -395,10 +520,15 @@ public: class ConfigOptionPercents : public ConfigOptionFloats { public: - ConfigOptionType type() const override { return coPercents; } - ConfigOption* clone() const override { return new ConfigOptionPercents(*this); } - ConfigOptionPercents& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionPercents &rhs) const { return this->values == rhs.values; } + ConfigOptionPercents() : ConfigOptionFloats() {} + explicit ConfigOptionPercents(size_t n, double value) : ConfigOptionFloats(n, value) {} + explicit ConfigOptionPercents(std::initializer_list<double> il) : ConfigOptionFloats(std::move(il)) {} + + static ConfigOptionType static_type() { return coPercents; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionPercents(*this); } + ConfigOptionPercents& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionPercents &rhs) const { return this->values == rhs.values; } std::string serialize() const override { @@ -445,10 +575,11 @@ class ConfigOptionFloatOrPercent : public ConfigOptionPercent { public: bool percent; - ConfigOptionFloatOrPercent() : ConfigOptionPercent(0), percent(false) {}; - explicit ConfigOptionFloatOrPercent(double _value, bool _percent) : ConfigOptionPercent(_value), percent(_percent) {}; - - ConfigOptionType type() const override { return coFloatOrPercent; } + ConfigOptionFloatOrPercent() : ConfigOptionPercent(0), percent(false) {} + explicit ConfigOptionFloatOrPercent(double _value, bool _percent) : ConfigOptionPercent(_value), percent(_percent) {} + + static ConfigOptionType static_type() { return coFloatOrPercent; } + ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionFloatOrPercent(*this); } ConfigOptionFloatOrPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionFloatOrPercent &rhs) const @@ -485,13 +616,14 @@ public: class ConfigOptionPoint : public ConfigOptionSingle<Pointf> { public: - ConfigOptionPoint() : ConfigOptionSingle<Pointf>(Pointf(0,0)) {}; - explicit ConfigOptionPoint(const Pointf &value) : ConfigOptionSingle<Pointf>(value) {}; + ConfigOptionPoint() : ConfigOptionSingle<Pointf>(Pointf(0,0)) {} + explicit ConfigOptionPoint(const Pointf &value) : ConfigOptionSingle<Pointf>(value) {} - ConfigOptionType type() const override { return coPoint; } - ConfigOption* clone() const override { return new ConfigOptionPoint(*this); } - ConfigOptionPoint& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionPoint &rhs) const { return this->value == rhs.value; } + static ConfigOptionType static_type() { return coPoint; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionPoint(*this); } + ConfigOptionPoint& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionPoint &rhs) const { return this->value == rhs.value; } std::string serialize() const override { @@ -517,10 +649,15 @@ public: class ConfigOptionPoints : public ConfigOptionVector<Pointf> { public: - ConfigOptionType type() const override { return coPoints; } - ConfigOption* clone() const override { return new ConfigOptionPoints(*this); } - ConfigOptionPoints& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionPoints &rhs) const { return this->values == rhs.values; } + ConfigOptionPoints() : ConfigOptionVector<Pointf>() {} + explicit ConfigOptionPoints(size_t n, const Pointf &value) : ConfigOptionVector<Pointf>(n, value) {} + explicit ConfigOptionPoints(std::initializer_list<Pointf> il) : ConfigOptionVector<Pointf>(std::move(il)) {} + + static ConfigOptionType static_type() { return coPoints; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionPoints(*this); } + ConfigOptionPoints& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionPoints &rhs) const { return this->values == rhs.values; } std::string serialize() const override { @@ -570,14 +707,15 @@ public: class ConfigOptionBool : public ConfigOptionSingle<bool> { public: - ConfigOptionBool() : ConfigOptionSingle<bool>(false) {}; - explicit ConfigOptionBool(bool _value) : ConfigOptionSingle<bool>(_value) {}; + ConfigOptionBool() : ConfigOptionSingle<bool>(false) {} + explicit ConfigOptionBool(bool _value) : ConfigOptionSingle<bool>(_value) {} - ConfigOptionType type() const override { return coBool; } - bool getBool() const override { return this->value; }; - ConfigOption* clone() const override { return new ConfigOptionBool(*this); } - ConfigOptionBool& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionBool &rhs) const { return this->value == rhs.value; } + static ConfigOptionType static_type() { return coBool; } + ConfigOptionType type() const override { return static_type(); } + bool getBool() const override { return this->value; } + ConfigOption* clone() const override { return new ConfigOptionBool(*this); } + ConfigOptionBool& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionBool &rhs) const { return this->value == rhs.value; } std::string serialize() const override { @@ -595,10 +733,15 @@ public: class ConfigOptionBools : public ConfigOptionVector<unsigned char> { public: - ConfigOptionType type() const override { return coBools; } - ConfigOption* clone() const override { return new ConfigOptionBools(*this); } - ConfigOptionBools& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionBools &rhs) const { return this->values == rhs.values; } + ConfigOptionBools() : ConfigOptionVector<unsigned char>() {} + explicit ConfigOptionBools(size_t n, bool value) : ConfigOptionVector<unsigned char>(n, (unsigned char)value) {} + explicit ConfigOptionBools(std::initializer_list<bool> il) { values.reserve(il.size()); for (bool b : il) values.emplace_back((unsigned char)b); } + + static ConfigOptionType static_type() { return coBools; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionBools(*this); } + ConfigOptionBools& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionBools &rhs) const { return this->values == rhs.values; } bool& get_at(size_t i) { assert(! this->values.empty()); @@ -652,13 +795,14 @@ class ConfigOptionEnum : public ConfigOptionSingle<T> { public: // by default, use the first value (0) of the T enum type - ConfigOptionEnum() : ConfigOptionSingle<T>(static_cast<T>(0)) {}; - explicit ConfigOptionEnum(T _value) : ConfigOptionSingle<T>(_value) {}; + ConfigOptionEnum() : ConfigOptionSingle<T>(static_cast<T>(0)) {} + explicit ConfigOptionEnum(T _value) : ConfigOptionSingle<T>(_value) {} - ConfigOptionType type() const override { return coEnum; } - ConfigOption* clone() const override { return new ConfigOptionEnum<T>(*this); } - ConfigOptionEnum<T>& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionEnum<T> &rhs) const { return this->value == rhs.value; } + static ConfigOptionType static_type() { return coEnum; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionEnum<T>(*this); } + ConfigOptionEnum<T>& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionEnum<T> &rhs) const { return this->value == rhs.value; } std::string serialize() const override { @@ -717,8 +861,9 @@ public: const t_config_enum_values* keys_map; - ConfigOptionType type() const override { return coEnum; } - ConfigOption* clone() const override { return new ConfigOptionEnumGeneric(*this); } + static ConfigOptionType static_type() { return coEnum; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionEnumGeneric(*this); } ConfigOptionEnumGeneric& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionEnumGeneric &rhs) const { return this->value == rhs.value; } @@ -874,6 +1019,16 @@ public: { return const_cast<ConfigBase*>(this)->option(opt_key, false); } ConfigOption* option(const t_config_option_key &opt_key, bool create = false) { return this->optptr(opt_key, create); } + template<typename TYPE> + TYPE* option(const t_config_option_key &opt_key, bool create = false) + { + ConfigOption *opt = this->optptr(opt_key, create); + assert(opt == nullptr || opt->type() == TYPE::static_type()); + return (opt == nullptr || opt->type() != TYPE::static_type()) ? nullptr : static_cast<TYPE*>(opt); + } + template<typename TYPE> + const TYPE* option(const t_config_option_key &opt_key) const + { return const_cast<ConfigBase*>(this)->option<TYPE>(opt_key, false); } // Apply all keys of other ConfigBase defined by this->def() to this ConfigBase. // An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(), // or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set. @@ -893,7 +1048,9 @@ public: double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; void setenv_(); void load(const std::string &file); + void load_from_ini(const std::string &file); void load_from_gcode(const std::string &file); + void load(const boost::property_tree::ptree &tree); void save(const std::string &file) const; private: @@ -906,26 +1063,77 @@ private: class DynamicConfig : public virtual ConfigBase { public: + DynamicConfig() {} DynamicConfig(const DynamicConfig& other) { *this = other; } DynamicConfig(DynamicConfig&& other) : options(std::move(other.options)) { other.options.clear(); } virtual ~DynamicConfig() { clear(); } - DynamicConfig& operator=(const DynamicConfig &other) + // Copy a content of one DynamicConfig to another DynamicConfig. + // If rhs.def() is not null, then it has to be equal to this->def(). + DynamicConfig& operator=(const DynamicConfig &rhs) { + assert(this->def() == nullptr || this->def() == rhs.def()); this->clear(); - for (const auto &kvp : other.options) + for (const auto &kvp : rhs.options) this->options[kvp.first] = kvp.second->clone(); return *this; } - DynamicConfig& operator=(DynamicConfig &&other) + // Move a content of one DynamicConfig to another DynamicConfig. + // If rhs.def() is not null, then it has to be equal to this->def(). + DynamicConfig& operator=(DynamicConfig &&rhs) { + assert(this->def() == nullptr || this->def() == rhs.def()); this->clear(); - this->options = std::move(other.options); - other.options.clear(); + this->options = std::move(rhs.options); + rhs.options.clear(); + return *this; + } + + // Add a content of one DynamicConfig to another DynamicConfig. + // If rhs.def() is not null, then it has to be equal to this->def(). + DynamicConfig& operator+=(const DynamicConfig &rhs) + { + assert(this->def() == nullptr || this->def() == rhs.def()); + for (const auto &kvp : rhs.options) { + auto it = this->options.find(kvp.first); + if (it == this->options.end()) + this->options[kvp.first] = kvp.second->clone(); + else { + assert(it->second->type() == kvp.second->type()); + if (it->second->type() == kvp.second->type()) + *it->second = *kvp.second; + else { + delete it->second; + it->second = kvp.second->clone(); + } + } + } + return *this; + } + + // Move a content of one DynamicConfig to another DynamicConfig. + // If rhs.def() is not null, then it has to be equal to this->def(). + DynamicConfig& operator+=(DynamicConfig &&rhs) + { + assert(this->def() == nullptr || this->def() == rhs.def()); + for (const auto &kvp : rhs.options) { + auto it = this->options.find(kvp.first); + if (it == this->options.end()) { + this->options[kvp.first] = kvp.second; + } else { + assert(it->second->type() == kvp.second->type()); + delete it->second; + it->second = kvp.second; + } + } + rhs.options.clear(); return *this; } + bool operator==(const DynamicConfig &rhs) const; + bool operator!=(const DynamicConfig &rhs) const { return ! (*this == rhs); } + void swap(DynamicConfig &other) { std::swap(this->options, other.options); @@ -948,30 +1156,49 @@ public: return true; } - template<class T> T* opt(const t_config_option_key &opt_key, bool create = false) + // Allow DynamicConfig to be instantiated on ints own without a definition. + // If the definition is not defined, the method requiring the definition will throw NoDefinitionException. + const ConfigDef* def() const override { return nullptr; }; + template<class T> T* opt(const t_config_option_key &opt_key, bool create = false) { return dynamic_cast<T*>(this->option(opt_key, create)); } // Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override; // Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. t_config_option_keys keys() const override; - std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return dynamic_cast<ConfigOptionString*>(this->option(opt_key, create))->value; } + // Set a value for an opt_key. Returns true if the value did not exist yet. + // This DynamicConfig will take ownership of opt. + // Be careful, as this method does not test the existence of opt_key in this->def(). + bool set_key_value(const std::string &opt_key, ConfigOption *opt) + { + auto it = this->options.find(opt_key); + if (it == this->options.end()) { + this->options[opt_key] = opt; + return true; + } else { + delete it->second; + it->second = opt; + return false; + } + } + + std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option<ConfigOptionString>(opt_key, create)->value; } const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key); } - std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast<ConfigOptionStrings*>(this->option(opt_key))->get_at(idx); } + std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); } const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key, idx); } - double& opt_float(const t_config_option_key &opt_key) { return dynamic_cast<ConfigOptionFloat*>(this->option(opt_key))->value; } + double& opt_float(const t_config_option_key &opt_key) { return this->option<ConfigOptionFloat>(opt_key)->value; } const double opt_float(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionFloat*>(this->option(opt_key))->value; } - double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast<ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); } + double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloats>(opt_key)->get_at(idx); } const double opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); } - int& opt_int(const t_config_option_key &opt_key) { return dynamic_cast<ConfigOptionInt*>(this->option(opt_key))->value; } + int& opt_int(const t_config_option_key &opt_key) { return this->option<ConfigOptionInt>(opt_key)->value; } const int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; } - int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast<ConfigOptionInts*>(this->option(opt_key))->get_at(idx); } + int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionInts>(opt_key)->get_at(idx); } const int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionInts*>(this->option(opt_key))->get_at(idx); } -protected: - DynamicConfig() {} + bool opt_bool(const t_config_option_key &opt_key) const { return this->option<ConfigOptionBool>(opt_key)->value != 0; } + bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; } private: typedef std::map<t_config_option_key,ConfigOption*> t_options_map; @@ -1001,6 +1228,13 @@ public: const char* what() const noexcept override { return "Unknown config option"; } }; +/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null). +class NoDefinitionException : public std::exception +{ +public: + const char* what() const noexcept override { return "No config definition"; } +}; + } #endif diff --git a/xs/src/libslic3r/ExPolygon.hpp b/xs/src/libslic3r/ExPolygon.hpp index af34694b7..f4782ba55 100644 --- a/xs/src/libslic3r/ExPolygon.hpp +++ b/xs/src/libslic3r/ExPolygon.hpp @@ -149,7 +149,6 @@ inline Polylines to_polylines(const ExPolygons &src) return polylines; } -#if SLIC3R_CPPVER >= 11 inline Polylines to_polylines(ExPolygon &&src) { Polylines polylines; @@ -166,6 +165,7 @@ inline Polylines to_polylines(ExPolygon &&src) assert(idx == polylines.size()); return polylines; } + inline Polylines to_polylines(ExPolygons &&src) { Polylines polylines; @@ -184,7 +184,6 @@ inline Polylines to_polylines(ExPolygons &&src) assert(idx == polylines.size()); return polylines; } -#endif inline Polygons to_polygons(const ExPolygon &src) { diff --git a/xs/src/libslic3r/Fill/Fill.cpp b/xs/src/libslic3r/Fill/Fill.cpp index ffae7fa5b..5333fcfec 100644 --- a/xs/src/libslic3r/Fill/Fill.cpp +++ b/xs/src/libslic3r/Fill/Fill.cpp @@ -149,8 +149,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) // ); } - for (Surfaces::const_iterator surface_it = surfaces.begin(); surface_it != surfaces.end(); ++ surface_it) { - const Surface &surface = *surface_it; + for (const Surface &surface : surfaces) { if (surface.surface_type == stInternalVoid) continue; InfillPattern fill_pattern = layerm.region()->config.fill_pattern.value; @@ -262,10 +261,10 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) // Unpacks the collection, creates multiple collections per path. // The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection. // Why the paths are unpacked? - for (ExtrusionEntitiesPtr::iterator thin_fill = layerm.thin_fills.entities.begin(); thin_fill != layerm.thin_fills.entities.end(); ++ thin_fill) { + for (const ExtrusionEntity *thin_fill : layerm.thin_fills.entities) { ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection()); out.entities.push_back(&collection); - collection.entities.push_back((*thin_fill)->clone()); + collection.entities.push_back(thin_fill->clone()); } } diff --git a/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp index d03c1f208..aa9774784 100644 --- a/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -176,11 +176,7 @@ void Fill3DHoneycomb::_fill_surface_single( } } Polylines chained = PolylineCollection::chained_path_from( -#if SLIC3R_CPPVER >= 11 std::move(polylines), -#else - polylines, -#endif PolylineCollection::leftmost_point(polylines), false); // reverse allowed bool first = true; for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { @@ -199,12 +195,7 @@ void Fill3DHoneycomb::_fill_surface_single( } } // The lines cannot be connected. -#if SLIC3R_CPPVER >= 11 - polylines_out.push_back(std::move(*it_polyline)); -#else - polylines_out.push_back(Polyline()); - std::swap(polylines_out.back(), *it_polyline); -#endif + polylines_out.emplace_back(std::move(*it_polyline)); first = false; } } diff --git a/xs/src/libslic3r/Fill/FillHoneycomb.cpp b/xs/src/libslic3r/Fill/FillHoneycomb.cpp index 22dea85da..aa0e0f6b0 100644 --- a/xs/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/xs/src/libslic3r/Fill/FillHoneycomb.cpp @@ -17,12 +17,7 @@ void FillHoneycomb::_fill_surface_single( CacheID cache_id(params.density, this->spacing); Cache::iterator it_m = this->cache.find(cache_id); if (it_m == this->cache.end()) { -#if 0 -// #if SLIC3R_CPPVER > 11 - it_m = this->cache.emplace_hint(it_m); -#else it_m = this->cache.insert(it_m, std::pair<CacheID, CacheData>(cache_id, CacheData())); -#endif CacheData &m = it_m->second; coord_t min_spacing = scale_(this->spacing); m.distance = min_spacing / params.density; @@ -99,11 +94,7 @@ void FillHoneycomb::_fill_surface_single( // connect paths if (! paths.empty()) { // prevent calling leftmost_point() on empty collections Polylines chained = PolylineCollection::chained_path_from( -#if SLIC3R_CPPVER >= 11 std::move(paths), -#else - paths, -#endif PolylineCollection::leftmost_point(paths), false); assert(paths.empty()); paths.clear(); diff --git a/xs/src/libslic3r/Fill/FillRectilinear.cpp b/xs/src/libslic3r/Fill/FillRectilinear.cpp index 991adc0b3..5ba30ba51 100644 --- a/xs/src/libslic3r/Fill/FillRectilinear.cpp +++ b/xs/src/libslic3r/Fill/FillRectilinear.cpp @@ -93,11 +93,7 @@ void FillRectilinear::_fill_surface_single( } } Polylines chained = PolylineCollection::chained_path_from( -#if SLIC3R_CPPVER >= 11 std::move(polylines), -#else - polylines, -#endif PolylineCollection::leftmost_point(polylines), false); // reverse allowed bool first = true; for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { @@ -118,12 +114,7 @@ void FillRectilinear::_fill_surface_single( } } // The lines cannot be connected. -#if SLIC3R_CPPVER >= 11 - polylines_out.push_back(std::move(*it_polyline)); -#else - polylines_out.push_back(Polyline()); - std::swap(polylines_out.back(), *it_polyline); -#endif + polylines_out.emplace_back(std::move(*it_polyline)); first = false; } } diff --git a/xs/src/libslic3r/Flow.cpp b/xs/src/libslic3r/Flow.cpp index 25a44ea8f..b60e26dcc 100644 --- a/xs/src/libslic3r/Flow.cpp +++ b/xs/src/libslic3r/Flow.cpp @@ -8,39 +8,6 @@ namespace Slic3r { // This static method returns a sane extrusion width default. static inline float auto_extrusion_width(FlowRole role, float nozzle_diameter, float height) { -#if 0 - // Here we calculate a sane default by matching the flow speed (at the nozzle) and the feed rate. - // shape: rectangle with semicircles at the ends - // This "sane" extrusion width gives the following results for a 0.4mm dmr nozzle: - // Layer Calculated Calculated width - // heigh extrusion over nozzle - // width diameter - // 0.40 0.40 1.00 - // 0.35 0.43 1.09 - // 0.30 0.48 1.21 - // 0.25 0.56 1.39 - // 0.20 0.67 1.68 - // 0.15 0.87 2.17 - // 0.10 1.28 3.20 - // 0.05 2.52 6.31 - // - float width = float(0.25 * (nozzle_diameter * nozzle_diameter) * PI / height + height * (1.0 - 0.25 * PI)); - - switch (role) { - case frExternalPerimeter: - case frSupportMaterial: - case frSupportMaterialInterface: - return nozzle_diameter; - case frPerimeter: - case frSolidInfill: - case frTopSolidInfill: - // do not limit width for sparse infill so that we use full native flow for it - return std::min(std::max(width, nozzle_diameter * 1.05f), nozzle_diameter * 1.7f); - case frInfill: - default: - return std::max(width, nozzle_diameter * 1.05f); - } -#else switch (role) { case frSupportMaterial: case frSupportMaterialInterface: @@ -53,7 +20,6 @@ static inline float auto_extrusion_width(FlowRole role, float nozzle_diameter, f case frInfill: return 1.125f * nozzle_diameter; } -#endif } // This constructor builds a Flow object from an extrusion width config setting @@ -154,10 +120,11 @@ Flow support_material_flow(const PrintObject *object, float layer_height) Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height) { + const auto &width = (object->print()->config.first_layer_extrusion_width.value > 0) ? object->print()->config.first_layer_extrusion_width : object->config.support_material_extrusion_width; return Flow::new_from_config_width( frSupportMaterial, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. - (object->print()->config.first_layer_extrusion_width.value > 0) ? object->print()->config.first_layer_extrusion_width : object->config.support_material_extrusion_width, + (width.value > 0) ? width : object->config.extrusion_width, float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)), (layer_height > 0.f) ? layer_height : float(object->config.first_layer_height.get_abs_value(object->config.layer_height.value)), false); diff --git a/xs/src/libslic3r/Flow.hpp b/xs/src/libslic3r/Flow.hpp index a058cc8b7..516490b35 100644 --- a/xs/src/libslic3r/Flow.hpp +++ b/xs/src/libslic3r/Flow.hpp @@ -52,6 +52,9 @@ public: coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); }; static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio); + // Create a flow from the spacing of extrusion lines. + // This method is used exclusively to calculate new flow of 100% infill, where the extrusion width was allowed to scale + // to fit a region with integer number of lines. static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge); }; diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index cf826ff68..0e85c55a8 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -4,6 +4,7 @@ #include "Geometry.hpp" #include "GCode/PrintExtents.hpp" #include "GCode/WipeTowerPrusaMM.hpp" +#include "Utils.hpp" #include <algorithm> #include <cstdlib> @@ -11,7 +12,6 @@ #include <boost/algorithm/string.hpp> #include <boost/algorithm/string/find.hpp> -#include <boost/date_time/local_time/local_time.hpp> #include <boost/foreach.hpp> #include <boost/nowide/iostream.hpp> @@ -30,6 +30,13 @@ #include <assert.h> namespace Slic3r { + +// Only add a newline in case the current G-code does not end with a newline. +static inline void check_add_eol(std::string &gcode) +{ + if (! gcode.empty() && gcode.back() != '\n') + gcode += '\n'; +} // Plan a travel move while minimizing the number of perimeter crossings. // point is in unscaled coordinates, in the coordinate system of the current active object @@ -157,6 +164,8 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T { std::string gcode; + // Disable linear advance for the wipe tower operations. + gcode += "M900 K0\n"; // Move over the wipe tower. // Retract for a tool change, using the toolchange retract value and setting the priming extra length. gcode += gcodegen.retract(true); @@ -171,8 +180,17 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T // Inform the G-code writer about the changes done behind its back. gcode += tcr.gcode; // Let the m_writer know the current extruder_id, but ignore the generated G-code. - if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) + if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) { gcodegen.writer().toolchange(new_extruder_id); + // Append the filament start G-code. + const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id); + if (! start_filament_gcode.empty()) { + // Process the start_filament_gcode for the active filament only. + gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); + gcode += gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id); + check_add_eol(gcode); + } + } // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy(Pointf(tcr.end_pos.x, tcr.end_pos.y)); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos)); @@ -199,15 +217,18 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) std::string gcode; if (&m_priming != nullptr && ! m_priming.extrusions.empty()) { + // Disable linear advance for the wipe tower operations. + gcode += "M900 K0\n"; // Let the tool change be executed by the wipe tower class. // Inform the G-code writer about the changes done behind its back. gcode += m_priming.gcode; // Let the m_writer know the current extruder_id, but ignore the generated G-code. - gcodegen.writer().toolchange(m_priming.extrusions.back().tool); + unsigned int current_extruder_id = m_priming.extrusions.back().tool; + gcodegen.writer().toolchange(current_extruder_id); + gcodegen.placeholder_parser().set("current_extruder", current_extruder_id); // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy(Pointf(m_priming.end_pos.x, m_priming.end_pos.y)); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos)); - // Prepare a future wipe. gcodegen.m_wipe.path.points.clear(); // Start the wipe at the current position. @@ -220,19 +241,6 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) return gcode; } -std::string WipeTowerIntegration::prime_single_color_print(const Print & /* print */, unsigned int initial_tool, GCode & /* gcodegen */) -{ - std::string gcode = "\ -G1 Z0.250 F7200.000\n\ -G1 X50.0 E80.0 F1000.0\n\ -G1 X160.0 E20.0 F1000.0\n\ -G1 Z0.200 F7200.000\n\ -G1 X220.0 E13 F1000.0\n\ -G1 X240.0 E0 F1000.0\n\ -G1 E-4 F1000.0\n"; - return gcode; -} - std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer) { std::string gcode; @@ -313,10 +321,15 @@ inline void write_format(FILE* file, const char* format, ...) write(file, buffer); } +// Write a string into a file. Add a newline, if the string does not end with a newline already. +// Used to export a custom G-code section processed by the PlaceholderParser. inline void writeln(FILE *file, const std::string &what) { if (! what.empty()) { - write_format(file, "%s\n", what.c_str()); + if (what.back() != '\n') + write_format(file, "%s\n", what.c_str()); + else + write(file, what); } } @@ -399,7 +412,7 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec return layers_to_print; } -bool GCode::do_export(Print *print, const char *path) +void GCode::do_export(Print *print, const char *path) { // Remove the old g-code if it exists. boost::nowide::remove(path); @@ -409,24 +422,37 @@ bool GCode::do_export(Print *print, const char *path) FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb"); if (file == nullptr) - return false; + throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); - bool result = this->_do_export(*print, file); + this->m_placeholder_parser_failed_templates.clear(); + this->_do_export(*print, file); + fflush(file); + if (ferror(file)) { + fclose(file); + boost::nowide::remove(path_tmp.c_str()); + throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); + } fclose(file); - - if (result && boost::nowide::rename(path_tmp.c_str(), path) != 0) { - boost::nowide::cerr << "Failed to remove the output G-code file from " << path_tmp << " to " << path - << ". Is " << path_tmp << " locked?" << std::endl; - result = false; + if (! this->m_placeholder_parser_failed_templates.empty()) { + // G-code export proceeded, but some of the PlaceholderParser substitutions failed. + std::string msg = std::string("G-code export to ") + path + " failed due to invalid custom G-code sections:\n\n"; + for (const std::string &name : this->m_placeholder_parser_failed_templates) + msg += std::string("\t") + name + "\n"; + msg += "\nPlease inspect the file "; + msg += path_tmp + " for error messages enclosed between\n"; + msg += " !!!!! Failed to process the custom G-code template ...\n"; + msg += "and\n"; + msg += " !!!!! End of an error report for the custom G-code template ...\n"; + throw std::runtime_error(msg); } - if (! result) - boost::nowide::remove(path_tmp.c_str()); - - return result; + if (boost::nowide::rename(path_tmp.c_str(), path) != 0) + throw std::runtime_error( + std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + + "Is " + path_tmp + " locked?" + '\n'); } -bool GCode::_do_export(Print &print, FILE *file) +void GCode::_do_export(Print &print, FILE *file) { // resets time estimator m_time_estimator.reset(); @@ -515,15 +541,7 @@ bool GCode::_do_export(Print &print, FILE *file) m_enable_extrusion_role_markers = (bool)m_pressure_equalizer; // Write information on the generator. - { - const auto now = boost::posix_time::second_clock::local_time(); - const auto date = now.date(); - fprintf(file, "; generated by Slic3r %s on %04d-%02d-%02d at %02d:%02d:%02d\n\n", - SLIC3R_VERSION, - // Local date in an ANSII format. - int(now.date().year()), int(now.date().month()), int(now.date().day()), - int(now.time_of_day().hours()), int(now.time_of_day().minutes()), int(now.time_of_day().seconds())); - } + fprintf(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str()); // Write notes (content of the Print Settings tab -> Notes) { std::list<std::string> lines; @@ -541,6 +559,7 @@ bool GCode::_do_export(Print &print, FILE *file) { const PrintObject *first_object = print.objects.front(); const double layer_height = first_object->config.layer_height.value; + const double first_layer_height = first_object->config.first_layer_height.get_abs_value(layer_height); for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) { auto region = print.regions[region_id]; fprintf(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width); @@ -551,7 +570,7 @@ bool GCode::_do_export(Print &print, FILE *file) if (print.has_support_material()) fprintf(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width); if (print.config.first_layer_extrusion_width.value > 0) - fprintf(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, layer_height, false, true, -1., *first_object).width); + fprintf(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width); fprintf(file, "\n"); } } @@ -566,6 +585,7 @@ bool GCode::_do_export(Print &print, FILE *file) unsigned int initial_extruder_id = (unsigned int)-1; unsigned int final_extruder_id = (unsigned int)-1; size_t initial_print_object_id = 0; + bool has_wipe_tower = false; if (print.config.complete_objects.value) { // Find the 1st printing object, find its tool ordering and the initial extruder ID. for (; initial_print_object_id < print.objects.size(); ++initial_print_object_id) { @@ -580,6 +600,7 @@ bool GCode::_do_export(Print &print, FILE *file) ToolOrdering(print, initial_extruder_id) : print.m_tool_ordering; initial_extruder_id = tool_ordering.first_extruder(); + has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower(); } if (initial_extruder_id == (unsigned int)-1) { // Nothing to print! @@ -596,28 +617,35 @@ bool GCode::_do_export(Print &print, FILE *file) if (! print.config.cooling.get_at(initial_extruder_id) || print.config.disable_fan_first_layers.get_at(initial_extruder_id)) write(file, m_writer.set_fan(0, true)); - // Set bed temperature if the start G-code does not contain any bed temp control G-codes. - { - // Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if - // the custom start G-code emited these. - //FIXME Should one parse the custom G-code to initialize the "current" bed temp state at m_writer? - std::string gcode = m_writer.set_bed_temperature(print.config.first_layer_bed_temperature.get_at(initial_extruder_id), true); - if (boost::ifind_first(print.config.start_gcode.value, std::string("M140")).empty() && - boost::ifind_first(print.config.start_gcode.value, std::string("M190")).empty()) - write(file, gcode); - } - - // Set extruder(s) temperature before and after start G-code. - this->_print_first_layer_extruder_temperatures(file, print, initial_extruder_id, false); // Let the start-up script prime the 1st printing tool. m_placeholder_parser.set("initial_tool", initial_extruder_id); m_placeholder_parser.set("initial_extruder", initial_extruder_id); m_placeholder_parser.set("current_extruder", initial_extruder_id); - writeln(file, m_placeholder_parser.process(print.config.start_gcode.value, initial_extruder_id)); + // Useful for sequential prints. + m_placeholder_parser.set("current_object_idx", 0); + // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. + m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); + std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config.start_gcode.value, initial_extruder_id); + + // Set bed temperature if the start G-code does not contain any bed temp control G-codes. + this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true); + // Set extruder(s) temperature before and after start G-code. + this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false); + // Write the custom start G-code + writeln(file, start_gcode); // Process filament-specific gcode in extruder order. - for (const std::string &start_gcode : print.config.start_filament_gcode.values) - writeln(file, m_placeholder_parser.process(start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front()))); - this->_print_first_layer_extruder_temperatures(file, print, initial_extruder_id, true); + if (print.config.single_extruder_multi_material) { + if (has_wipe_tower) { + // Wipe tower will control the extruder switching, it will call the start_filament_gcode. + } else { + // Only initialize the initial extruder. + writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config.start_filament_gcode.values[initial_extruder_id], initial_extruder_id)); + } + } else { + for (const std::string &start_gcode : print.config.start_filament_gcode.values) + writeln(file, this->placeholder_parser_process("start_gcode", start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front()))); + } + this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true); // Set other general things. write(file, this->preamble()); @@ -707,9 +735,12 @@ bool GCode::_do_export(Print &print, FILE *file) // Ff 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. - write(file, m_writer.set_bed_temperature(print.config.first_layer_bed_temperature.get_at(initial_extruder_id))); - // Set first layer extruder. - this->_print_first_layer_extruder_temperatures(file, print, initial_extruder_id, false); + m_placeholder_parser.set("current_object_idx", int(finished_objects)); + std::string between_objects_gcode = this->placeholder_parser_process("between_objects_gcode", print.config.between_objects_gcode.value, initial_extruder_id); + // Set first layer bed and extruder temperatures, don't wait for it to reach the temperature. + this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false); + this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false); + writeln(file, between_objects_gcode); } // Reset the cooling buffer internal state (the current position, feed rate, accelerations). m_cooling_buffer->reset(); @@ -740,33 +771,29 @@ bool GCode::_do_export(Print &print, FILE *file) // All extrusion moves with the same top layer height are extruded uninterrupted. std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print); // Prusa Multi-Material wipe tower. - if (print.has_wipe_tower() && ! layers_to_print.empty()) { - if (tool_ordering.has_wipe_tower()) { - m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get())); - write(file, m_wipe_tower->prime(*this)); - // Verify, whether the print overaps the priming extrusions. - BoundingBoxf bbox_print(get_print_extrusions_extents(print)); - coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; - for (const PrintObject *print_object : print.objects) - bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz)); - bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz)); - BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print)); - bbox_prime.offset(0.5f); - // Beep for 500ms, tone 800Hz. Yet better, play some Morse. - write(file, this->retract()); - write(file, "M300 S800 P500\n"); - if (bbox_prime.overlap(bbox_print)) { - // Wait for the user to remove the priming extrusions, otherwise they would - // get covered by the print. - write(file, "M1 Remove priming towers and click button.\n"); + if (has_wipe_tower && ! layers_to_print.empty()) { + m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get())); + write(file, m_wipe_tower->prime(*this)); + // Verify, whether the print overaps the priming extrusions. + BoundingBoxf bbox_print(get_print_extrusions_extents(print)); + coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; + for (const PrintObject *print_object : print.objects) + bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz)); + bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz)); + BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print)); + bbox_prime.offset(0.5f); + // Beep for 500ms, tone 800Hz. Yet better, play some Morse. + write(file, this->retract()); + write(file, "M300 S800 P500\n"); + if (bbox_prime.overlap(bbox_print)) { + // Wait for the user to remove the priming extrusions, otherwise they would + // get covered by the print. + write(file, "M1 Remove priming towers and click button.\n"); + } else { + // Just wait for a bit to let the user check, that the priming succeeded. + //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. + write(file, "M1 S10\n"); } - else { - // Just wait for a bit to let the user check, that the priming succeeded. - //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. - write(file, "M1 S10\n"); - } - } else - write(file, WipeTowerIntegration::prime_single_color_print(print, initial_extruder_id, *this)); } // Extrude the layers. for (auto &layer : layers_to_print) { @@ -786,9 +813,14 @@ bool GCode::_do_export(Print &print, FILE *file) write(file, this->retract()); write(file, m_writer.set_fan(false)); // Process filament-specific gcode in extruder order. - for (const std::string &end_gcode : print.config.end_filament_gcode.values) - writeln(file, m_placeholder_parser.process(end_gcode, (unsigned int)(&end_gcode - &print.config.end_filament_gcode.values.front()))); - writeln(file, m_placeholder_parser.process(print.config.end_gcode, m_writer.extruder()->id())); + if (print.config.single_extruder_multi_material) { + // Process the end_filament_gcode for the active filament only. + writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config.end_filament_gcode.get_at(m_writer.extruder()->id()), m_writer.extruder()->id())); + } else { + for (const std::string &end_gcode : print.config.end_filament_gcode.values) + writeln(file, this->placeholder_parser_process("end_gcode", end_gcode, (unsigned int)(&end_gcode - &print.config.end_filament_gcode.values.front()))); + } + writeln(file, this->placeholder_parser_process("end_gcode", print.config.end_gcode, m_writer.extruder()->id())); write(file, m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100% write(file, m_writer.postamble()); @@ -830,22 +862,117 @@ bool GCode::_do_export(Print &print, FILE *file) for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++ i) { StaticPrintConfig *cfg = configs[i]; for (const std::string &key : cfg->keys()) - fprintf(file, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str()); + if (key != "compatible_printers") + fprintf(file, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str()); } } +} - return true; +std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) +{ + try { + return m_placeholder_parser.process(templ, current_extruder_id, config_override); + } catch (std::runtime_error &err) { + // Collect the names of failed template substitutions for error reporting. + this->m_placeholder_parser_failed_templates.insert(name); + // Insert the macro error message into the G-code. + return + std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" + + err.what() + + "!!!!! End of an error report for the custom G-code template " + name + "\n\n"; + } +} + +// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait inside the custom G-code. +// Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out. +static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, int &temp_out) +{ + temp_out = -1; + if (gcode.empty()) + return false; + + const char *ptr = gcode.data(); + bool temp_set_by_gcode = false; + while (*ptr != 0) { + // Skip whitespaces. + for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); + if (*ptr == 'M') { + // Line starts with 'M'. It is a machine command. + ++ ptr; + // Parse the M code value. + char *endptr = nullptr; + int mcode = int(strtol(ptr, &endptr, 10)); + if (endptr != nullptr && endptr != ptr && (mcode == mcode_set_temp_dont_wait || mcode == mcode_set_temp_and_wait)) { + // M104/M109 or M140/M190 found. + ptr = endptr; + // Let the caller know that the custom G-code sets the temperature. + temp_set_by_gcode = true; + // Now try to parse the temperature value. + // While not at the end of the line: + while (strchr(";\r\n\0", *ptr) == nullptr) { + // Skip whitespaces. + for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); + if (*ptr == 'S') { + // Skip whitespaces. + for (++ ptr; *ptr == ' ' || *ptr == '\t'; ++ ptr); + // Parse an int. + endptr = nullptr; + long temp_parsed = strtol(ptr, &endptr, 10); + if (endptr > ptr) { + ptr = endptr; + temp_out = temp_parsed; + } + } else { + // Skip this word. + for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr); + } + } + } + } + // Skip the rest of the line. + for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr); + // Skip the end of line indicators. + for (; *ptr == '\r' || *ptr == '\n'; ++ ptr); + } + return temp_set_by_gcode; +} + +// Write 1st layer bed temperatures into the G-code. +// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. +// M140 - Set Extruder Temperature +// M190 - Set Extruder Temperature and Wait +void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) +{ + // Initial bed temperature based on the first extruder. + int temp = print.config.first_layer_bed_temperature.get_at(first_printing_extruder_id); + // Is the bed temperature set by the provided custom G-code? + int temp_by_gcode = -1; + bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, temp_by_gcode); + if (temp_set_by_gcode && temp_by_gcode >= 0 && temp_by_gcode < 1000) + temp = temp_by_gcode; + // Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if + // the custom start G-code emited these. + std::string set_temp_gcode = m_writer.set_bed_temperature(temp, wait); + if (! temp_set_by_gcode) + write(file, set_temp_gcode); } // Write 1st layer extruder temperatures into the G-code. // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. -// FIXME this does not work correctly for multi-extruder, single heater configuration as it emits multiple preheat commands for the same heater. // M104 - Set Extruder Temperature // M109 - Set Extruder Temperature and Wait -void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, unsigned int first_printing_extruder_id, bool wait) +void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) { - if (boost::ifind_first(print.config.start_gcode.value, std::string("M104")).empty() && - boost::ifind_first(print.config.start_gcode.value, std::string("M109")).empty()) { + // Is the bed temperature set by the provided custom G-code? + int temp_by_gcode = -1; + if (custom_gcode_sets_temperature(gcode, 104, 109, temp_by_gcode)) { + // Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code. + int temp = print.config.first_layer_temperature.get_at(first_printing_extruder_id); + if (temp_by_gcode >= 0 && temp_by_gcode < 1000) + temp = temp_by_gcode; + m_writer.set_temperature(temp_by_gcode, wait, first_printing_extruder_id); + } else { + // Custom G-code does not set the extruder temperature. Do it now. if (print.config.single_extruder_multi_material.value) { // Set temperature of the first printing extruder only. int temp = print.config.first_layer_temperature.get_at(first_printing_extruder_id); @@ -953,18 +1080,22 @@ void GCode::process_layer( // Set new layer - this will change Z and force a retraction if retract_layer_change is enabled. if (! print.config.before_layer_gcode.value.empty()) { - PlaceholderParser pp(m_placeholder_parser); - pp.set("layer_num", m_layer_index + 1); - pp.set("layer_z", print_z); - gcode += pp.process(print.config.before_layer_gcode.value, m_writer.extruder()->id()) + "\n"; + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + gcode += this->placeholder_parser_process("before_layer_gcode", + print.config.before_layer_gcode.value, m_writer.extruder()->id(), &config) + + "\n"; } gcode += this->change_layer(print_z); // this will increase m_layer_index m_layer = &layer; if (! print.config.layer_gcode.value.empty()) { - PlaceholderParser pp(m_placeholder_parser); - pp.set("layer_num", m_layer_index); - pp.set("layer_z", print_z); - gcode += pp.process(print.config.layer_gcode.value, m_writer.extruder()->id()) + "\n"; + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + gcode += this->placeholder_parser_process("layer_gcode", + print.config.layer_gcode.value, m_writer.extruder()->id(), &config) + + "\n"; } if (! first_layer && ! m_second_layer_things_done) { @@ -2141,13 +2272,14 @@ GCode::retract(bool toolchange) std::string GCode::set_extruder(unsigned int extruder_id) { - m_placeholder_parser.set("current_extruder", extruder_id); if (!m_writer.need_toolchange(extruder_id)) return ""; // if we are running a single-extruder setup, just set the extruder and return nothing - if (!m_writer.multiple_extruders) + if (!m_writer.multiple_extruders) { + m_placeholder_parser.set("current_extruder", extruder_id); return m_writer.toolchange(extruder_id); + } // prepend retraction on the current extruder std::string gcode = this->retract(true); @@ -2155,21 +2287,41 @@ std::string GCode::set_extruder(unsigned int extruder_id) // Always reset the extrusion path, even if the tool change retract is set to zero. m_wipe.reset_path(); - // append custom toolchange G-code - if (m_writer.extruder() != nullptr && !m_config.toolchange_gcode.value.empty()) { - PlaceholderParser pp = m_placeholder_parser; - pp.set("previous_extruder", m_writer.extruder()->id()); - pp.set("next_extruder", extruder_id); - gcode += pp.process(m_config.toolchange_gcode.value, extruder_id) + '\n'; + if (m_writer.extruder() != nullptr) { + // Process the custom end_filament_gcode in case of single_extruder_multi_material. + unsigned int old_extruder_id = m_writer.extruder()->id(); + const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id); + if (m_config.single_extruder_multi_material && ! end_filament_gcode.empty()) { + gcode += placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id); + check_add_eol(gcode); + } + } + + m_placeholder_parser.set("current_extruder", extruder_id); + + if (m_writer.extruder() != nullptr && ! m_config.toolchange_gcode.value.empty()) { + // Process the custom toolchange_gcode. + DynamicConfig config; + config.set_key_value("previous_extruder", new ConfigOptionInt((int)m_writer.extruder()->id())); + config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); + gcode += placeholder_parser_process("toolchange_gcode", m_config.toolchange_gcode.value, extruder_id, &config); + check_add_eol(gcode); } - // if ooze prevention is enabled, park current extruder in the nearest - // standby point and set it to the standby temperature + // If ooze prevention is enabled, park current extruder in the nearest + // standby point and set it to the standby temperature. if (m_ooze_prevention.enable && m_writer.extruder() != nullptr) gcode += m_ooze_prevention.pre_toolchange(*this); - // append the toolchange command + // Append the toolchange command. gcode += m_writer.toolchange(extruder_id); - // set the new extruder to the operating temperature + // Append the filament start G-code for single_extruder_multi_material. + const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); + if (m_config.single_extruder_multi_material && ! start_filament_gcode.empty()) { + // Process the start_filament_gcode for the active filament only. + gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id); + check_add_eol(gcode); + } + // Set the new extruder to the operating temperature. if (m_ooze_prevention.enable) gcode += m_ooze_prevention.post_toolchange(*this); diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index b29a6ed2f..bb40c4ea3 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -91,7 +91,6 @@ public: m_brim_done(false) {} std::string prime(GCode &gcodegen); - static std::string prime_single_color_print(const Print & /* print */, unsigned int initial_tool, GCode & /* gcodegen */); void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; } std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer); std::string finalize(GCode &gcodegen); @@ -132,7 +131,8 @@ public: {} ~GCode() {} - bool do_export(Print *print, const char *path); + // throws std::runtime_exception + void do_export(Print *print, const char *path); // Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests. const Pointf& origin() const { return m_origin; } @@ -144,6 +144,10 @@ public: const FullPrintConfig &config() const { return m_config; } const Layer* layer() const { return m_layer; } GCodeWriter& writer() { return m_writer; } + PlaceholderParser& placeholder_parser() { return m_placeholder_parser; } + // Process a template through the placeholder parser, collect error messages to be reported + // inside the generated string and after the G-code export finishes. + std::string placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr); bool enable_cooling_markers() const { return m_enable_cooling_markers; } // For Perl bindings, to be used exclusively by unit tests. @@ -152,7 +156,7 @@ public: void apply_print_config(const PrintConfig &print_config); protected: - bool _do_export(Print &print, FILE *file); + void _do_export(Print &print, FILE *file); // Object and support extrusions of the same PrintObject at the same print_z. struct LayerToPrint @@ -224,6 +228,8 @@ protected: FullPrintConfig m_config; GCodeWriter m_writer; PlaceholderParser m_placeholder_parser; + // Collection of templates, on which the placeholder substitution failed. + std::set<std::string> m_placeholder_parser_failed_templates; OozePrevention m_ooze_prevention; Wipe m_wipe; AvoidCrossingPerimeters m_avoid_crossing_perimeters; @@ -272,7 +278,8 @@ protected: GCodeTimeEstimator m_time_estimator; std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1); - void _print_first_layer_extruder_temperatures(FILE *file, Print &print, unsigned int first_printing_extruder_id, bool wait); + void _print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); + void _print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); // this flag triggers first layer speeds bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; } diff --git a/xs/src/libslic3r/GCode/ToolOrdering.hpp b/xs/src/libslic3r/GCode/ToolOrdering.hpp index fe2c394c5..abf1aa6b2 100644 --- a/xs/src/libslic3r/GCode/ToolOrdering.hpp +++ b/xs/src/libslic3r/GCode/ToolOrdering.hpp @@ -53,14 +53,14 @@ public: void clear() { m_layer_tools.clear(); } - // Get the first extruder printing the layer_tools, returns -1 if there is no layer printed. + // Get the first extruder printing, including the extruder priming areas, returns -1 if there is no layer printed. unsigned int first_extruder() const { return m_first_printing_extruder; } // Get the first extruder printing the layer_tools, returns -1 if there is no layer printed. unsigned int last_extruder() const { return m_last_printing_extruder; } // For a multi-material print, the printing extruders are ordered in the order they shall be primed. - std::vector<unsigned int> all_extruders() const { return m_all_printing_extruders; } + const std::vector<unsigned int>& all_extruders() const { return m_all_printing_extruders; } // Find LayerTools with the closest print_z. LayerTools& tools_for_layer(coordf_t print_z); diff --git a/xs/src/libslic3r/GCode/WipeTower.hpp b/xs/src/libslic3r/GCode/WipeTower.hpp index c5fa27392..2c92e16e6 100644 --- a/xs/src/libslic3r/GCode/WipeTower.hpp +++ b/xs/src/libslic3r/GCode/WipeTower.hpp @@ -21,8 +21,8 @@ public: xy operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; } xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; } xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; } - bool operator==(const xy &rhs) { return x == rhs.x && y == rhs.y; } - bool operator!=(const xy &rhs) { return x != rhs.x || y != rhs.y; } + bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; } + bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; } float x; float y; }; @@ -109,7 +109,7 @@ public: // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. - std::vector<unsigned int> tools, + const std::vector<unsigned int> &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. // If false, the last priming are will be large enough to wipe the last extruder sufficiently. bool last_wipe_inside_wipe_tower, diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 951d71075..b81a8b04b 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -368,7 +368,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. - std::vector<unsigned int> tools, + const std::vector<unsigned int> &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. // If false, the last priming are will be large enough to wipe the last extruder sufficiently. bool last_wipe_inside_wipe_tower, diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp index 15a96d367..b8c7ab31f 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -112,7 +112,7 @@ public: // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. - std::vector<unsigned int> tools, + const std::vector<unsigned int> &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. // If false, the last priming are will be large enough to wipe the last extruder sufficiently. bool last_wipe_inside_wipe_tower, diff --git a/xs/src/libslic3r/PlaceholderParser.cpp b/xs/src/libslic3r/PlaceholderParser.cpp index e25ec9526..77ef64e8d 100644 --- a/xs/src/libslic3r/PlaceholderParser.cpp +++ b/xs/src/libslic3r/PlaceholderParser.cpp @@ -21,11 +21,42 @@ #endif #endif +#include <boost/algorithm/string.hpp> +// Unicode iterator to iterate over utf8. +#include <boost/regex/pending/unicode_iterator.hpp> + +// Spirit v2.5 allows you to suppress automatic generation +// of predefined terminals to speed up complation. With +// BOOST_SPIRIT_NO_PREDEFINED_TERMINALS defined, you are +// responsible in creating instances of the terminals that +// you need (e.g. see qi::uint_type uint_ below). +//#define BOOST_SPIRIT_NO_PREDEFINED_TERMINALS + +#define BOOST_RESULT_OF_USE_DECLTYPE +#define BOOST_SPIRIT_USE_PHOENIX_V3 +#include <boost/config/warning_disable.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/spirit/include/qi.hpp> +#include <boost/spirit/include/qi_lit.hpp> +#include <boost/spirit/include/phoenix_core.hpp> +#include <boost/spirit/include/phoenix_operator.hpp> +#include <boost/spirit/include/phoenix_fusion.hpp> +#include <boost/spirit/include/phoenix_stl.hpp> +#include <boost/spirit/include/phoenix_object.hpp> +#include <boost/fusion/include/adapt_struct.hpp> +#include <boost/spirit/repository/include/qi_distinct.hpp> +#include <boost/spirit/repository/include/qi_iter_pos.hpp> +#include <boost/variant/recursive_variant.hpp> +#include <boost/phoenix/bind/bind_function.hpp> + +#include <iostream> +#include <string> + namespace Slic3r { PlaceholderParser::PlaceholderParser() { - this->set("version", SLIC3R_VERSION); + this->set("version", std::string(SLIC3R_VERSION)); this->apply_env_variables(); this->update_timestamp(); } @@ -61,130 +92,880 @@ void PlaceholderParser::update_timestamp() // are expected to be addressed by the extruder ID, therefore // if a vector configuration value is addressed without an index, // a current extruder ID is used. -void PlaceholderParser::apply_config(const DynamicPrintConfig &config) +void PlaceholderParser::apply_config(const DynamicPrintConfig &rhs) { - for (const t_config_option_key &opt_key : config.keys()) { - const ConfigOptionDef* def = config.def()->get(opt_key); - if (def->multiline || opt_key == "post_process") + const ConfigDef *def = rhs.def(); + for (const t_config_option_key &opt_key : rhs.keys()) { + const ConfigOptionDef *opt_def = def->get(opt_key); + if ((opt_def->multiline && boost::ends_with(opt_key, "_gcode")) || opt_key == "post_process") continue; - - const ConfigOption* opt = config.option(opt_key); - const ConfigOptionVectorBase* optv = dynamic_cast<const ConfigOptionVectorBase*>(opt); - if (optv != nullptr && opt_key != "bed_shape") { - // set placeholders for options with multiple values - this->set(opt_key, optv->vserialize()); - } else if (const ConfigOptionPoint* optp = dynamic_cast<const ConfigOptionPoint*>(opt)) { - this->set(opt_key, optp->serialize()); - Pointf val = *optp; - this->set(opt_key + "_X", val.x); - this->set(opt_key + "_Y", val.y); - } else { - // set single-value placeholders - this->set(opt_key, opt->serialize()); - } + const ConfigOption *opt = rhs.option(opt_key); + // Store a copy of the config option. + // Convert FloatOrPercent values to floats first. + //FIXME there are some ratio_over chains, which end with empty ratio_with. + // For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly. + this->set(opt_key, (opt->type() == coFloatOrPercent) ? + new ConfigOptionFloat(rhs.get_abs_value(opt_key)) : + opt->clone()); } } void PlaceholderParser::apply_env_variables() { - for (char** env = environ; *env; env++) { + for (char** env = environ; *env; ++ env) { if (strncmp(*env, "SLIC3R_", 7) == 0) { std::stringstream ss(*env); std::string key, value; std::getline(ss, key, '='); ss >> value; - this->set(key, value); } } } -void PlaceholderParser::set(const std::string &key, const std::string &value) -{ - m_single[key] = value; - m_multiple.erase(key); -} +namespace spirit = boost::spirit; +namespace qi = boost::spirit::qi; +namespace px = boost::phoenix; -void PlaceholderParser::set(const std::string &key, int value) +namespace client { - std::ostringstream ss; - ss << value; - this->set(key, ss.str()); -} + template<typename Iterator> + struct OptWithPos { + OptWithPos() {} + OptWithPos(ConfigOptionConstPtr opt, boost::iterator_range<Iterator> it_range) : opt(opt), it_range(it_range) {} + ConfigOptionConstPtr opt = nullptr; + boost::iterator_range<Iterator> it_range; + }; -void PlaceholderParser::set(const std::string &key, unsigned int value) -{ - std::ostringstream ss; - ss << value; - this->set(key, ss.str()); -} + template<typename ITERATOR> + std::ostream& operator<<(std::ostream& os, OptWithPos<ITERATOR> const& opt) + { + os << std::string(opt.it_range.begin(), opt.it_range.end()); + return os; + } -void PlaceholderParser::set(const std::string &key, double value) -{ - std::ostringstream ss; - ss << value; - this->set(key, ss.str()); -} + template<typename Iterator> + struct expr + { + expr() : type(TYPE_EMPTY) {} + explicit expr(bool b) : type(TYPE_BOOL) { data.b = b; } + explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_BOOL), it_range(it_begin, it_end) { data.b = b; } + explicit expr(int i) : type(TYPE_INT) { data.i = i; } + explicit expr(int i, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_INT), it_range(it_begin, it_end) { data.i = i; } + explicit expr(double d) : type(TYPE_DOUBLE) { data.d = d; } + explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_DOUBLE), it_range(it_begin, it_end) { data.d = d; } + explicit expr(const char *s) : type(TYPE_STRING) { data.s = new std::string(s); } + explicit expr(const std::string &s) : type(TYPE_STRING) { data.s = new std::string(s); } + explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) : + type(TYPE_STRING), it_range(it_begin, it_end) { data.s = new std::string(s); } + expr(const expr &rhs) : type(rhs.type), it_range(rhs.it_range) + { if (rhs.type == TYPE_STRING) data.s = new std::string(*rhs.data.s); else data.set(rhs.data); } + explicit expr(expr &&rhs) : type(rhs.type), it_range(rhs.it_range) + { data.set(rhs.data); rhs.type = TYPE_EMPTY; } + explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : type(rhs.type), it_range(it_begin, it_end) + { data.set(rhs.data); rhs.type = TYPE_EMPTY; } + ~expr() { this->reset(); } -void PlaceholderParser::set(const std::string &key, std::vector<std::string> values) -{ - m_single.erase(key); - if (values.empty()) - m_multiple.erase(key); - else - m_multiple[key] = values; -} + expr &operator=(const expr &rhs) + { + this->type = rhs.type; + this->it_range = rhs.it_range; + if (rhs.type == TYPE_STRING) + this->data.s = new std::string(*rhs.data.s); + else + this->data.set(rhs.data); + return *this; + } -std::string PlaceholderParser::process(std::string str, unsigned int current_extruder_id) const -{ - char key[2048]; + expr &operator=(expr &&rhs) + { + type = rhs.type; + this->it_range = rhs.it_range; + data.set(rhs.data); + rhs.type = TYPE_EMPTY; + return *this; + } + + void reset() + { + if (this->type == TYPE_STRING) + delete data.s; + this->type = TYPE_EMPTY; + } - // Replace extruder independent single options, like [foo]. - for (const auto &key_value : m_single) { - sprintf(key, "[%s]", key_value.first.c_str()); - const std::string &replace = key_value.second; - for (size_t i = 0; (i = str.find(key, i)) != std::string::npos;) { - str.replace(i, key_value.first.size() + 2, replace); - i += replace.size(); + bool& b() { return data.b; } + bool b() const { return data.b; } + void set_b(bool v) { this->reset(); this->data.b = v; this->type = TYPE_BOOL; } + int& i() { return data.i; } + int i() const { return data.i; } + void set_i(int v) { this->reset(); this->data.i = v; this->type = TYPE_INT; } + int as_i() const { return (this->type == TYPE_INT) ? this->i() : int(this->d()); } + double& d() { return data.d; } + double d() const { return data.d; } + void set_d(double v) { this->reset(); this->data.d = v; this->type = TYPE_DOUBLE; } + double as_d() const { return (this->type == TYPE_DOUBLE) ? this->d() : double(this->i()); } + std::string& s() { return *data.s; } + const std::string& s() const { return *data.s; } + void set_s(const std::string &s) { this->reset(); this->data.s = new std::string(s); this->type = TYPE_STRING; } + void set_s(std::string &&s) { this->reset(); this->data.s = new std::string(std::move(s)); this->type = TYPE_STRING; } + + std::string to_string() const + { + std::string out; + switch (type) { + case TYPE_BOOL: out = boost::to_string(data.b); break; + case TYPE_INT: out = boost::to_string(data.i); break; + case TYPE_DOUBLE: out = boost::to_string(data.d); break; + case TYPE_STRING: out = *data.s; break; + default: break; + } + return out; } + + union Data { + // Raw image of the other data members. + // The C++ compiler will consider a possible aliasing of char* with any other union member, + // therefore copying the raw data is safe. + char raw[8]; + bool b; + int i; + double d; + std::string *s; + + // Copy the largest member variable through char*, which will alias with all other union members by default. + void set(const Data &rhs) { memcpy(this->raw, rhs.raw, sizeof(rhs.raw)); } + } data; + + enum Type { + TYPE_EMPTY = 0, + TYPE_BOOL, + TYPE_INT, + TYPE_DOUBLE, + TYPE_STRING, + }; + + Type type; + + // Range of input iterators covering this expression. + // Used for throwing parse exceptions. + boost::iterator_range<Iterator> it_range; + + expr unary_minus(const Iterator start_pos) const + { + switch (this->type) { + case TYPE_INT : + return expr<Iterator>(- this->i(), start_pos, this->it_range.end()); + case TYPE_DOUBLE: + return expr<Iterator>(- this->d(), start_pos, this->it_range.end()); + default: + this->throw_exception("Cannot apply unary minus operator."); + } + assert(false); + // Suppress compiler warnings. + return expr(); + } + + expr unary_not(const Iterator start_pos) const + { + switch (this->type) { + case TYPE_BOOL : + return expr<Iterator>(! this->b(), start_pos, this->it_range.end()); + default: + this->throw_exception("Cannot apply a not operator."); + } + assert(false); + // Suppress compiler warnings. + return expr(); + } + + expr &operator+=(const expr &rhs) + { + if (this->type == TYPE_STRING) { + // Convert the right hand side to string and append. + *this->data.s += rhs.to_string(); + } else if (rhs.type == TYPE_STRING) { + // Conver the left hand side to string, append rhs. + this->data.s = new std::string(this->to_string() + rhs.s()); + this->type = TYPE_STRING; + } else { + const char *err_msg = "Cannot add non-numeric types."; + this->throw_if_not_numeric(err_msg); + rhs.throw_if_not_numeric(err_msg); + if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { + double d = this->as_d() + rhs.as_d(); + this->data.d = d; + this->type = TYPE_DOUBLE; + } else + this->data.i += rhs.i(); + } + this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end()); + return *this; + } + + expr &operator-=(const expr &rhs) + { + const char *err_msg = "Cannot subtract non-numeric types."; + this->throw_if_not_numeric(err_msg); + rhs.throw_if_not_numeric(err_msg); + if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { + double d = this->as_d() - rhs.as_d(); + this->data.d = d; + this->type = TYPE_DOUBLE; + } else + this->data.i -= rhs.i(); + this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end()); + return *this; + } + + expr &operator*=(const expr &rhs) + { + const char *err_msg = "Cannot multiply with non-numeric type."; + this->throw_if_not_numeric(err_msg); + rhs.throw_if_not_numeric(err_msg); + if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { + double d = this->as_d() * rhs.as_d(); + this->data.d = d; + this->type = TYPE_DOUBLE; + } else + this->data.i *= rhs.i(); + this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end()); + return *this; + } + + expr &operator/=(const expr &rhs) + { + this->throw_if_not_numeric("Cannot divide a non-numeric type."); + rhs.throw_if_not_numeric("Cannot divide with a non-numeric type."); + if ((this->type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.)) + rhs.throw_exception("Division by zero"); + if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { + double d = this->as_d() / rhs.as_d(); + this->data.d = d; + this->type = TYPE_DOUBLE; + } else + this->data.i /= rhs.i(); + this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end()); + return *this; + } + + static void to_string2(expr &self, std::string &out) + { + out = self.to_string(); + } + + static void evaluate_boolean(expr &self, bool &out) + { + if (self.type != TYPE_BOOL) + self.throw_exception("Not a boolean expression"); + out = self.b(); + } + + // Is lhs==rhs? Store the result into lhs. + static void compare_op(expr &lhs, expr &rhs, char op) + { + bool value = false; + if ((lhs.type == TYPE_INT || lhs.type == TYPE_DOUBLE) && + (rhs.type == TYPE_INT || rhs.type == TYPE_DOUBLE)) { + // Both types are numeric. + value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? + (lhs.as_d() == rhs.as_d()) : (lhs.i() == rhs.i()); + } else if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) { + // Both type are bool. + value = lhs.b() == rhs.b(); + } else if (lhs.type == TYPE_STRING || rhs.type == TYPE_STRING) { + // One type is string, the other could be converted to string. + value = lhs.to_string() == rhs.to_string(); + } else { + boost::throw_exception(qi::expectation_failure<Iterator>( + lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types."))); + } + lhs.type = TYPE_BOOL; + lhs.data.b = (op == '=') ? value : !value; + } + static void equal(expr &lhs, expr &rhs) { compare_op(lhs, rhs, '='); } + static void not_equal(expr &lhs, expr &rhs) { compare_op(lhs, rhs, '!'); } + + static void set_if(bool &cond, bool ¬_yet_consumed, std::string &str_in, std::string &str_out) + { + if (cond && not_yet_consumed) { + str_out = str_in; + not_yet_consumed = false; + } + } + + void throw_exception(const char *message) const + { + boost::throw_exception(qi::expectation_failure<Iterator>( + this->it_range.begin(), this->it_range.end(), spirit::info(std::string("*") + message))); + } + + void throw_if_not_numeric(const char *message) const + { + if (this->type != TYPE_INT && this->type != TYPE_DOUBLE) + this->throw_exception(message); + } + }; + + template<typename ITERATOR> + std::ostream& operator<<(std::ostream &os, const expr<ITERATOR> &expression) + { + typedef expr<ITERATOR> Expr; + os << std::string(expression.it_range.begin(), expression.it_range.end()) << " - "; + switch (expression.type) { + case Expr::TYPE_EMPTY: os << "empty"; break; + case Expr::TYPE_BOOL: os << "bool (" << expression.b() << ")"; break; + case Expr::TYPE_INT: os << "int (" << expression.i() << ")"; break; + case Expr::TYPE_DOUBLE: os << "double (" << expression.d() << ")"; break; + case Expr::TYPE_STRING: os << "string (" << expression.s() << ")"; break; + default: os << "unknown"; + }; + return os; } - // Replace extruder dependent single options with the value for the active extruder. - // For example, [temperature] will be replaced with the current extruder temperature. - for (const auto &key_value : m_multiple) { - sprintf(key, "[%s]", key_value.first.c_str()); - const std::string &replace = key_value.second[(current_extruder_id < key_value.second.size()) ? current_extruder_id : 0]; - for (size_t i = 0; (i = str.find(key, i)) != std::string::npos;) { - str.replace(i, key_value.first.size() + 2, replace); - i += replace.size(); + struct MyContext { + const PlaceholderParser *pp = nullptr; + const DynamicConfig *config_override = nullptr; + const size_t current_extruder_id = 0; + std::string error_message; + + const ConfigOption* resolve_symbol(const std::string &opt_key) const + { + const ConfigOption *opt = nullptr; + if (config_override != nullptr) + opt = config_override->option(opt_key); + if (opt == nullptr) + opt = pp->option(opt_key); + return opt; } + + template <typename Iterator> + static void legacy_variable_expansion( + const MyContext *ctx, + boost::iterator_range<Iterator> &opt_key, + std::string &output) + { + std::string opt_key_str(opt_key.begin(), opt_key.end()); + const ConfigOption *opt = ctx->resolve_symbol(opt_key_str); + size_t idx = ctx->current_extruder_id; + if (opt == nullptr) { + // Check whether this is a legacy vector indexing. + idx = opt_key_str.rfind('_'); + if (idx != std::string::npos) { + opt = ctx->resolve_symbol(opt_key_str.substr(0, idx)); + if (opt != nullptr) { + if (! opt->is_vector()) + ctx->throw_exception("Trying to index a scalar variable", opt_key); + char *endptr = nullptr; + idx = strtol(opt_key_str.c_str() + idx + 1, &endptr, 10); + if (endptr == nullptr || *endptr != 0) + ctx->throw_exception("Invalid vector index", boost::iterator_range<Iterator>(opt_key.begin() + idx + 1, opt_key.end())); + } + } + } + if (opt == nullptr) + ctx->throw_exception("Variable does not exist", boost::iterator_range<Iterator>(opt_key.begin(), opt_key.end())); + if (opt->is_scalar()) + output = opt->serialize(); + else { + const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt); + if (vec->empty()) + ctx->throw_exception("Indexing an empty vector variable", opt_key); + output = vec->vserialize()[(idx >= vec->size()) ? 0 : idx]; + } + } + + template <typename Iterator> + static void legacy_variable_expansion2( + const MyContext *ctx, + boost::iterator_range<Iterator> &opt_key, + boost::iterator_range<Iterator> &opt_vector_index, + std::string &output) + { + std::string opt_key_str(opt_key.begin(), opt_key.end()); + const ConfigOption *opt = ctx->resolve_symbol(opt_key_str); + if (opt == nullptr) { + // Check whether the opt_key ends with '_'. + if (opt_key_str.back() == '_') + opt_key_str.resize(opt_key_str.size() - 1); + opt = ctx->resolve_symbol(opt_key_str); + } + if (! opt->is_vector()) + ctx->throw_exception("Trying to index a scalar variable", opt_key); + const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt); + if (vec->empty()) + ctx->throw_exception("Indexing an empty vector variable", boost::iterator_range<Iterator>(opt_key.begin(), opt_key.end())); + const ConfigOption *opt_index = ctx->resolve_symbol(std::string(opt_vector_index.begin(), opt_vector_index.end())); + if (opt_index == nullptr) + ctx->throw_exception("Variable does not exist", opt_key); + if (opt_index->type() != coInt) + ctx->throw_exception("Indexing variable has to be integer", opt_key); + int idx = opt_index->getInt(); + if (idx < 0) + ctx->throw_exception("Negative vector index", opt_key); + output = vec->vserialize()[(idx >= (int)vec->size()) ? 0 : idx]; + } + + template <typename Iterator> + static void resolve_variable( + const MyContext *ctx, + boost::iterator_range<Iterator> &opt_key, + OptWithPos<Iterator> &output) + { + const ConfigOption *opt = ctx->resolve_symbol(std::string(opt_key.begin(), opt_key.end())); + if (opt == nullptr) + ctx->throw_exception("Not a variable name", opt_key); + output.opt = opt; + output.it_range = opt_key; + } + + template <typename Iterator> + static void scalar_variable_reference( + const MyContext *ctx, + OptWithPos<Iterator> &opt, + expr<Iterator> &output) + { + if (opt.opt->is_vector()) + ctx->throw_exception("Referencing a scalar variable in a vector context", opt.it_range); + switch (opt.opt->type()) { + case coFloat: output.set_d(opt.opt->getFloat()); break; + case coInt: output.set_i(opt.opt->getInt()); break; + case coString: output.set_s(static_cast<const ConfigOptionString*>(opt.opt)->value); break; + case coPercent: output.set_d(opt.opt->getFloat()); break; + case coPoint: output.set_s(opt.opt->serialize()); break; + case coBool: output.set_b(opt.opt->getBool()); break; + case coFloatOrPercent: + ctx->throw_exception("FloatOrPercent variables are not supported", opt.it_range); + default: + ctx->throw_exception("Unknown scalar variable type", opt.it_range); + } + output.it_range = opt.it_range; + } + + template <typename Iterator> + static void vector_variable_reference( + const MyContext *ctx, + OptWithPos<Iterator> &opt, + int &index, + Iterator it_end, + expr<Iterator> &output) + { + if (opt.opt->is_scalar()) + ctx->throw_exception("Referencing a vector variable in a scalar context", opt.it_range); + const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt.opt); + if (vec->empty()) + ctx->throw_exception("Indexing an empty vector variable", opt.it_range); + size_t idx = (index < 0) ? 0 : (index >= int(vec->size())) ? 0 : size_t(index); + switch (opt.opt->type()) { + case coFloats: output.set_d(static_cast<const ConfigOptionFloats *>(opt.opt)->values[idx]); break; + case coInts: output.set_i(static_cast<const ConfigOptionInts *>(opt.opt)->values[idx]); break; + case coStrings: output.set_s(static_cast<const ConfigOptionStrings *>(opt.opt)->values[idx]); break; + case coPercents: output.set_d(static_cast<const ConfigOptionPercents*>(opt.opt)->values[idx]); break; + case coPoints: output.set_s(static_cast<const ConfigOptionPoints *>(opt.opt)->values[idx].dump_perl()); break; + case coBools: output.set_b(static_cast<const ConfigOptionBools *>(opt.opt)->values[idx] != 0); break; + default: + ctx->throw_exception("Unknown vector variable type", opt.it_range); + } + output.it_range = boost::iterator_range<Iterator>(opt.it_range.begin(), it_end); + } + + // Verify that the expression returns an integer, which may be used + // to address a vector. + template <typename Iterator> + static void evaluate_index(expr<Iterator> &expr_index, int &output) + { + if (expr_index.type != expr<Iterator>::TYPE_INT) + expr_index.throw_exception("Non-integer index is not allowed to address a vector variable."); + output = expr_index.i(); + } + + template <typename Iterator> + static void throw_exception(const std::string &msg, const boost::iterator_range<Iterator> &it_range) + { + // An asterix is added to the start of the string to differentiate the boost::spirit::info::tag content + // between the grammer terminal / non-terminal symbol name and a free-form error message. + boost::throw_exception(qi::expectation_failure<Iterator>(it_range.begin(), it_range.end(), spirit::info(std::string("*") + msg))); + } + + template <typename Iterator> + static void process_error_message(const MyContext *context, const boost::spirit::info &info, const Iterator &it_begin, const Iterator &it_end, const Iterator &it_error) + { + std::string &msg = const_cast<MyContext*>(context)->error_message; + std::string first(it_begin, it_error); + std::string last(it_error, it_end); + auto first_pos = first.rfind('\n'); + auto last_pos = last.find('\n'); + int line_nr = 1; + if (first_pos == std::string::npos) + first_pos = 0; + else { + // Calculate the current line number. + for (size_t i = 0; i <= first_pos; ++ i) + if (first[i] == '\n') + ++ line_nr; + ++ first_pos; + } + auto error_line = std::string(first, first_pos) + std::string(last, 0, last_pos); + // Position of the it_error from the start of its line. + auto error_pos = (it_error - it_begin) - first_pos; + msg += "Parsing error at line " + std::to_string(line_nr); + if (! info.tag.empty() && info.tag.front() == '*') { + // The gat contains an explanatory string. + msg += ": "; + msg += info.tag.substr(1); + } else { + // A generic error report based on the nonterminal or terminal symbol name. + msg += ". Expecting tag "; + msg += info.tag; + } + msg += '\n'; + msg += error_line; + msg += '\n'; + for (size_t i = 0; i < error_pos; ++ i) + msg += ' '; + msg += "^\n"; + } + }; + + // For debugging the boost::spirit parsers. Print out the string enclosed in it_range. + template<typename Iterator> + std::ostream& operator<<(std::ostream& os, const boost::iterator_range<Iterator> &it_range) + { + os << std::string(it_range.begin(), it_range.end()); + return os; } - // Replace multiple options like [foo_0]. - for (const auto &key_value : m_multiple) { - sprintf(key, "[%s_", key_value.first.c_str()); - const std::vector<std::string> &values = key_value.second; - for (size_t i = 0; (i = str.find(key, i)) != std::string::npos;) { - size_t k = str.find(']', i + key_value.first.size() + 2); - if (k != std::string::npos) { - // Parse the key index and the closing bracket. - ++ k; - int idx = 0; - if (sscanf(str.c_str() + i + key_value.first.size() + 2, "%d]", &idx) == 1 && idx >= 0) { - if (idx >= int(values.size())) - idx = 0; - str.replace(i, k - i, values[idx]); - i += values[idx].size(); - continue; + // Disable parsing int numbers (without decimals) and Inf/NaN symbols by the double parser. + struct strict_real_policies_without_nan_inf : public qi::strict_real_policies<double> + { + template <typename It, typename Attr> static bool parse_nan(It&, It const&, Attr&) { return false; } + template <typename It, typename Attr> static bool parse_inf(It&, It const&, Attr&) { return false; } + }; + + // This parser is to be used inside a raw[] directive to accept a single valid UTF-8 character. + // If an invalid UTF-8 sequence is encountered, a qi::expectation_failure is thrown. + struct utf8_char_skipper_parser : qi::primitive_parser<utf8_char_skipper_parser> + { + // Define the attribute type exposed by this parser component + template <typename Context, typename Iterator> + struct attribute + { + typedef wchar_t type; + }; + + // This function is called during the actual parsing process + template <typename Iterator, typename Context , typename Skipper, typename Attribute> + bool parse(Iterator& first, Iterator const& last, Context& context, Skipper const& skipper, Attribute& attr) const + { + // The skipper shall always be empty, any white space will be accepted. + // skip_over(first, last, skipper); + if (first == last) + return false; + // Iterator over the UTF-8 sequence. + auto it = first; + // Read the first byte of the UTF-8 sequence. + unsigned char c = static_cast<boost::uint8_t>(*it ++); + unsigned int cnt = 0; + // UTF-8 sequence must not start with a continuation character: + if ((c & 0xC0) == 0x80) + goto err; + // Skip high surrogate first if there is one. + // If the most significant bit with a zero in it is in position + // 8-N then there are N bytes in this UTF-8 sequence: + { + unsigned char mask = 0x80u; + unsigned int result = 0; + while (c & mask) { + ++ result; + mask >>= 1; } + cnt = (result == 0) ? 1 : ((result > 4) ? 4 : result); + } + // Since we haven't read in a value, we need to validate the code points: + for (-- cnt; cnt > 0; -- cnt) { + if (it == last) + goto err; + c = static_cast<boost::uint8_t>(*it ++); + // We must have a continuation byte: + if (cnt > 1 && (c & 0xC0) != 0x80) + goto err; } - // The key does not match the pattern [foo_%d]. Skip just [foo_.] with the hope that there was a missing ']', - // so an opening '[' may be found somewhere before the position k. - i += key_value.first.size() + 3; + first = it; + return true; + err: + MyContext::throw_exception("Invalid utf8 sequence", boost::iterator_range<Iterator>(first, last)); + return false; } + + // This function is called during error handling to create a human readable string for the error context. + template <typename Context> + spirit::info what(Context&) const + { + return spirit::info("unicode_char"); + } + }; + + /////////////////////////////////////////////////////////////////////////// + // Our calculator grammar + /////////////////////////////////////////////////////////////////////////// + // Inspired by the C grammar rules https://www.lysator.liu.se/c/ANSI-C-grammar-y.html + template <typename Iterator> + struct calculator : qi::grammar<Iterator, std::string(const MyContext*), spirit::ascii::space_type> + { + calculator() : calculator::base_type(start) + { + using namespace qi::labels; + qi::alpha_type alpha; + qi::alnum_type alnum; + qi::eps_type eps; + qi::raw_type raw; + qi::lit_type lit; + qi::lexeme_type lexeme; + qi::no_skip_type no_skip; + qi::real_parser<double, strict_real_policies_without_nan_inf> strict_double; + spirit::ascii::char_type char_; + utf8_char_skipper_parser utf8char; + spirit::bool_type bool_; + spirit::int_type int_; + spirit::double_type double_; + spirit::ascii::string_type string; + spirit::repository::qi::iter_pos_type iter_pos; + auto kw = spirit::repository::qi::distinct(qi::copy(alnum | '_')); + + qi::_val_type _val; + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_4_type _4; + qi::_a_type _a; + qi::_b_type _b; + qi::_r1_type _r1; + + // Starting symbol of the grammer. + // The leading eps is required by the "expectation point" operator ">". + // Without it, some of the errors would not trigger the error handler. + start = eps > text_block(_r1); + start.name("start"); + qi::on_error<qi::fail>(start, px::bind(&MyContext::process_error_message<Iterator>, _r1, _4, _1, _2, _3)); + + text_block = *( + text [_val+=_1] + // Allow back tracking after '{' in case of a text_block embedded inside a condition. + // In that case the inner-most {else} wins and the {if}/{elsif}/{else} shall be paired. + // {elsif}/{else} without an {if} will be allowed to back track from the embedded text_block. + | (lit('{') >> macro(_r1) [_val+=_1] > '}') + | (lit('[') > legacy_variable_expansion(_r1) [_val+=_1] > ']') + ); + text_block.name("text_block"); + + // Free-form text up to a first brace, including spaces and newlines. + // The free-form text will be inserted into the processed text without a modification. + text = no_skip[raw[+(utf8char - char_('[') - char_('{'))]]; + text.name("text"); + + // New style of macro expansion. + // The macro expansion may contain numeric or string expressions, ifs and cases. + macro = + (kw["if"] > if_else_output(_r1) [_val = _1]) + | (kw["switch"] > switch_output(_r1) [_val = _1]) + | additive_expression(_r1) [ px::bind(&expr<Iterator>::to_string2, _1, _val) ]; + macro.name("macro"); + + // An if expression enclosed in {} (the outmost {} are already parsed by the caller). + if_else_output = + eps[_b=true] > + bool_expr_eval(_r1)[_a=_1] > '}' > + text_block(_r1)[px::bind(&expr<Iterator>::set_if, _a, _b, _1, _val)] > '{' > + *(kw["elsif"] > bool_expr_eval(_r1)[_a=_1] > '}' > + text_block(_r1)[px::bind(&expr<Iterator>::set_if, _a, _b, _1, _val)] > '{') > + -(kw["else"] > lit('}') > + text_block(_r1)[px::bind(&expr<Iterator>::set_if, _b, _b, _1, _val)] > '{') > + kw["endif"]; + if_else_output.name("if_else_output"); + // A switch expression enclosed in {} (the outmost {} are already parsed by the caller). +/* + switch_output = + eps[_b=true] > + omit[expr(_r1)[_a=_1]] > '}' > text_block(_r1)[px::bind(&expr<Iterator>::set_if_equal, _a, _b, _1, _val)] > '{' > + *("elsif" > omit[bool_expr_eval(_r1)[_a=_1]] > '}' > text_block(_r1)[px::bind(&expr<Iterator>::set_if, _a, _b, _1, _val)]) >> + -("else" > '}' >> text_block(_r1)[px::bind(&expr<Iterator>::set_if, _b, _b, _1, _val)]) > + "endif"; +*/ + + // Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index]. + legacy_variable_expansion = + (identifier >> &lit(']')) + [ px::bind(&MyContext::legacy_variable_expansion<Iterator>, _r1, _1, _val) ] + | (identifier > lit('[') > identifier > ']') + [ px::bind(&MyContext::legacy_variable_expansion2<Iterator>, _r1, _1, _2, _val) ] + ; + legacy_variable_expansion.name("legacy_variable_expansion"); + + identifier = + ! kw[keywords] >> + raw[lexeme[(alpha | '_') >> *(alnum | '_')]]; + identifier.name("identifier"); + + bool_expr = + additive_expression(_r1) [_val = _1] + >> *( ("==" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::equal, _val, _1)] + | ("!=" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::not_equal, _val, _1)] + | ("<>" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::not_equal, _val, _1)] + ); + bool_expr.name("bool expression"); + + // Evaluate a boolean expression stored as expr into a boolean value. + // Throw if the bool_expr does not produce a expr of boolean type. + bool_expr_eval = bool_expr(_r1) [ px::bind(&expr<Iterator>::evaluate_boolean, _1, _val) ]; + bool_expr_eval.name("bool_expr_eval"); + + additive_expression = + term(_r1) [_val = _1] + >> *( (lit('+') > term(_r1) ) [_val += _1] + | (lit('-') > term(_r1) ) [_val -= _1] + ); + additive_expression.name("additive_expression"); + + term = + factor(_r1) [_val = _1] + >> *( (lit('*') > factor(_r1) ) [_val *= _1] + | (lit('/') > factor(_r1) ) [_val /= _1] + ); + term.name("term"); + + struct FactorActions { + static void set_start_pos(Iterator &start_pos, expr<Iterator> &out) + { out.it_range = boost::iterator_range<Iterator>(start_pos, start_pos); } + static void int_(int &value, Iterator &end_pos, expr<Iterator> &out) + { out = expr<Iterator>(value, out.it_range.begin(), end_pos); } + static void double_(double &value, Iterator &end_pos, expr<Iterator> &out) + { out = expr<Iterator>(value, out.it_range.begin(), end_pos); } + static void bool_(bool &value, Iterator &end_pos, expr<Iterator> &out) + { out = expr<Iterator>(value, out.it_range.begin(), end_pos); } + static void string_(boost::iterator_range<Iterator> &it_range, expr<Iterator> &out) + { out = expr<Iterator>(std::string(it_range.begin() + 1, it_range.end() - 1), it_range.begin(), it_range.end()); } + static void expr_(expr<Iterator> &value, Iterator &end_pos, expr<Iterator> &out) + { out = expr<Iterator>(std::move(value), out.it_range.begin(), end_pos); } + static void minus_(expr<Iterator> &value, expr<Iterator> &out) + { out = value.unary_minus(out.it_range.begin()); } + static void not_(expr<Iterator> &value, expr<Iterator> &out) + { out = value.unary_not(out.it_range.begin()); } + }; + factor = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( + scalar_variable_reference(_r1) [ _val = _1 ] + | (lit('(') > additive_expression(_r1) > ')' > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ] + | (lit('-') > factor(_r1) ) [ px::bind(&FactorActions::minus_, _1, _val) ] + | (lit('+') > factor(_r1) > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ] + | ((kw["not"] | '!') > factor(_r1) > iter_pos) [ px::bind(&FactorActions::not_, _1, _val) ] + | (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ] + | (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ] + | (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ] + | raw[lexeme['"' > *((utf8char - char_('\\') - char_('"')) | ('\\' > char_)) > '"']] + [ px::bind(&FactorActions::string_, _1, _val) ] + ); + factor.name("factor"); + + scalar_variable_reference = + variable_reference(_r1)[_a=_1] >> + ( + ('[' > additive_expression(_r1)[px::bind(&MyContext::evaluate_index<Iterator>, _1, _b)] > ']' > + iter_pos[px::bind(&MyContext::vector_variable_reference<Iterator>, _r1, _a, _b, _1, _val)]) + | eps[px::bind(&MyContext::scalar_variable_reference<Iterator>, _r1, _a, _val)] + ); + scalar_variable_reference.name("scalar variable reference"); + + variable_reference = identifier + [ px::bind(&MyContext::resolve_variable<Iterator>, _r1, _1, _val) ]; + variable_reference.name("variable reference"); + + keywords.add + ("and") + ("if") + //("inf") + ("else") + ("elsif") + ("endif") + ("false") + ("not") + ("or") + ("true"); + + if (0) { + debug(start); + debug(text); + debug(text_block); + debug(macro); + debug(if_else_output); + debug(switch_output); + debug(legacy_variable_expansion); + debug(identifier); + debug(bool_expr); + debug(bool_expr_eval); + debug(additive_expression); + debug(term); + debug(factor); + debug(scalar_variable_reference); + debug(variable_reference); + } + } + + // The start of the grammar. + qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> start; + // A free-form text. + qi::rule<Iterator, std::string(), spirit::ascii::space_type> text; + // A free-form text, possibly empty, possibly containing macro expansions. + qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> text_block; + // Statements enclosed in curely braces {} + qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> macro; + // Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index]. + qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> legacy_variable_expansion; + // Parsed identifier name. + qi::rule<Iterator, boost::iterator_range<Iterator>(), spirit::ascii::space_type> identifier; + // Math expression consisting of +- operators over terms. + qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> additive_expression; + // Boolean expressions over expressions. + qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> bool_expr; + // Evaluate boolean expression into bool. + qi::rule<Iterator, bool(const MyContext*), spirit::ascii::space_type> bool_expr_eval; + // Math expression consisting of */ operators over factors. + qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> term; + // Number literals, functions, braced expressions, variable references, variable indexing references. + qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> factor; + // Reference of a scalar variable, or reference to a field of a vector variable. + qi::rule<Iterator, expr<Iterator>(const MyContext*), qi::locals<OptWithPos<Iterator>, int>, spirit::ascii::space_type> scalar_variable_reference; + // Rule to translate an identifier to a ConfigOption, or to fail. + qi::rule<Iterator, OptWithPos<Iterator>(const MyContext*), spirit::ascii::space_type> variable_reference; + + qi::rule<Iterator, std::string(const MyContext*), qi::locals<bool, bool>, spirit::ascii::space_type> if_else_output; + qi::rule<Iterator, std::string(const MyContext*), qi::locals<expr<Iterator>, bool, std::string>, spirit::ascii::space_type> switch_output; + + qi::symbols<char> keywords; + }; +} + +std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) const +{ + typedef std::string::const_iterator iterator_type; + typedef client::calculator<iterator_type> calculator; + + // Our whitespace skipper. + spirit::ascii::space_type space; + // Our grammar. + calculator calc; + // Iterators over the source template. + std::string::const_iterator iter = templ.begin(); + std::string::const_iterator end = templ.end(); + // Accumulator for the processed template. + std::string output; + client::MyContext context; + context.pp = this; + context.config_override = config_override; + bool res = phrase_parse(iter, end, calc(&context), space, output); + if (! context.error_message.empty()) { + if (context.error_message.back() != '\n' && context.error_message.back() != '\r') + context.error_message += '\n'; + throw std::runtime_error(context.error_message); } - - return str; + return output; } } diff --git a/xs/src/libslic3r/PlaceholderParser.hpp b/xs/src/libslic3r/PlaceholderParser.hpp index a8abf8871..25264d461 100644 --- a/xs/src/libslic3r/PlaceholderParser.hpp +++ b/xs/src/libslic3r/PlaceholderParser.hpp @@ -11,23 +11,28 @@ namespace Slic3r { class PlaceholderParser { -public: - std::map<std::string, std::string> m_single; - std::map<std::string, std::vector<std::string>> m_multiple; - +public: PlaceholderParser(); + void update_timestamp(); void apply_config(const DynamicPrintConfig &config); void apply_env_variables(); - void set(const std::string &key, const std::string &value); - void set(const std::string &key, int value); - void set(const std::string &key, unsigned int value); - void set(const std::string &key, double value); - void set(const std::string &key, std::vector<std::string> values); - std::string process(std::string str, unsigned int current_extruder_id) const; + + // Add new ConfigOption values to m_config. + void set(const std::string &key, const std::string &value) { this->set(key, new ConfigOptionString(value)); } + void set(const std::string &key, int value) { this->set(key, new ConfigOptionInt(value)); } + void set(const std::string &key, unsigned int value) { this->set(key, int(value)); } + void set(const std::string &key, bool value) { this->set(key, new ConfigOptionBool(value)); } + void set(const std::string &key, double value) { this->set(key, new ConfigOptionFloat(value)); } + void set(const std::string &key, const std::vector<std::string> &values) { this->set(key, new ConfigOptionStrings(values)); } + void set(const std::string &key, ConfigOption *opt) { m_config.set_key_value(key, opt); } + const ConfigOption* option(const std::string &key) const { return m_config.option(key); } + + // Fill in the template. + std::string process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr) const; private: - bool find_and_replace(std::string &source, std::string const &find, std::string const &replace) const; + DynamicConfig m_config; }; } diff --git a/xs/src/libslic3r/Polygon.hpp b/xs/src/libslic3r/Polygon.hpp index 6ea300cee..f36abc185 100644 --- a/xs/src/libslic3r/Polygon.hpp +++ b/xs/src/libslic3r/Polygon.hpp @@ -154,7 +154,6 @@ inline Polylines to_polylines(const Polygons &polys) return polylines; } -#if SLIC3R_CPPVER >= 11 inline Polylines to_polylines(Polygons &&polys) { Polylines polylines; @@ -168,7 +167,6 @@ inline Polylines to_polylines(Polygons &&polys) assert(idx == polylines.size()); return polylines; } -#endif } // Slic3r diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index cbf0a3b46..190fbfe6e 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -81,80 +81,80 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option // Cache the plenty of parameters, which influence the G-code generator only, // or they are only notes not influencing the generated G-code. - static std::unordered_set<std::string> steps_ignore; - if (steps_ignore.empty()) { - steps_ignore.insert("avoid_crossing_perimeters"); - steps_ignore.insert("bed_shape"); - steps_ignore.insert("bed_temperature"); - steps_ignore.insert("before_layer_gcode"); - steps_ignore.insert("bridge_acceleration"); - steps_ignore.insert("bridge_fan_speed"); - steps_ignore.insert("cooling"); - steps_ignore.insert("default_acceleration"); - steps_ignore.insert("deretract_speed"); - steps_ignore.insert("disable_fan_first_layers"); - steps_ignore.insert("duplicate_distance"); - steps_ignore.insert("end_gcode"); - steps_ignore.insert("end_filament_gcode"); - steps_ignore.insert("extrusion_axis"); - steps_ignore.insert("extruder_clearance_height"); - steps_ignore.insert("extruder_clearance_radius"); - steps_ignore.insert("extruder_colour"); - steps_ignore.insert("extruder_offset"); - steps_ignore.insert("extrusion_multiplier"); - steps_ignore.insert("fan_always_on"); - steps_ignore.insert("fan_below_layer_time"); - steps_ignore.insert("filament_colour"); - steps_ignore.insert("filament_diameter"); - steps_ignore.insert("filament_density"); - steps_ignore.insert("filament_notes"); - steps_ignore.insert("filament_cost"); - steps_ignore.insert("filament_max_volumetric_speed"); - steps_ignore.insert("first_layer_acceleration"); - steps_ignore.insert("first_layer_bed_temperature"); - steps_ignore.insert("first_layer_speed"); - steps_ignore.insert("gcode_comments"); - steps_ignore.insert("gcode_flavor"); - steps_ignore.insert("infill_acceleration"); - steps_ignore.insert("infill_first"); - steps_ignore.insert("layer_gcode"); - steps_ignore.insert("min_fan_speed"); - steps_ignore.insert("max_fan_speed"); - steps_ignore.insert("min_print_speed"); - steps_ignore.insert("max_print_speed"); - steps_ignore.insert("max_volumetric_speed"); - steps_ignore.insert("max_volumetric_extrusion_rate_slope_positive"); - steps_ignore.insert("max_volumetric_extrusion_rate_slope_negative"); - steps_ignore.insert("notes"); - steps_ignore.insert("only_retract_when_crossing_perimeters"); - steps_ignore.insert("output_filename_format"); - steps_ignore.insert("perimeter_acceleration"); - steps_ignore.insert("post_process"); - steps_ignore.insert("printer_notes"); - steps_ignore.insert("retract_before_travel"); - steps_ignore.insert("retract_before_wipe"); - steps_ignore.insert("retract_layer_change"); - steps_ignore.insert("retract_length"); - steps_ignore.insert("retract_length_toolchange"); - steps_ignore.insert("retract_lift"); - steps_ignore.insert("retract_lift_above"); - steps_ignore.insert("retract_lift_below"); - steps_ignore.insert("retract_restart_extra"); - steps_ignore.insert("retract_restart_extra_toolchange"); - steps_ignore.insert("retract_speed"); - steps_ignore.insert("slowdown_below_layer_time"); - steps_ignore.insert("standby_temperature_delta"); - steps_ignore.insert("start_gcode"); - steps_ignore.insert("start_filament_gcode"); - steps_ignore.insert("toolchange_gcode"); - steps_ignore.insert("threads"); - steps_ignore.insert("travel_speed"); - steps_ignore.insert("use_firmware_retraction"); - steps_ignore.insert("use_relative_e_distances"); - steps_ignore.insert("use_volumetric_e"); - steps_ignore.insert("variable_layer_height"); - steps_ignore.insert("wipe"); - } + static std::unordered_set<std::string> steps_ignore = { + "avoid_crossing_perimeters", + "bed_shape", + "bed_temperature", + "before_layer_gcode", + "between_objects_gcode", + "bridge_acceleration", + "bridge_fan_speed", + "cooling", + "default_acceleration", + "deretract_speed", + "disable_fan_first_layers", + "duplicate_distance", + "end_gcode", + "end_filament_gcode", + "extrusion_axis", + "extruder_clearance_height", + "extruder_clearance_radius", + "extruder_colour", + "extruder_offset", + "extrusion_multiplier", + "fan_always_on", + "fan_below_layer_time", + "filament_colour", + "filament_diameter", + "filament_density", + "filament_notes", + "filament_cost", + "filament_max_volumetric_speed", + "first_layer_acceleration", + "first_layer_bed_temperature", + "first_layer_speed", + "gcode_comments", + "gcode_flavor", + "infill_acceleration", + "infill_first", + "layer_gcode", + "min_fan_speed", + "max_fan_speed", + "min_print_speed", + "max_print_speed", + "max_volumetric_speed", + "max_volumetric_extrusion_rate_slope_positive", + "max_volumetric_extrusion_rate_slope_negative", + "notes", + "only_retract_when_crossing_perimeters", + "output_filename_format", + "perimeter_acceleration", + "post_process", + "printer_notes", + "retract_before_travel", + "retract_before_wipe", + "retract_layer_change", + "retract_length", + "retract_length_toolchange", + "retract_lift", + "retract_lift_above", + "retract_lift_below", + "retract_restart_extra", + "retract_restart_extra_toolchange", + "retract_speed", + "slowdown_below_layer_time", + "standby_temperature_delta", + "start_gcode", + "start_filament_gcode", + "toolchange_gcode", + "threads", + "travel_speed", + "use_firmware_retraction", + "use_relative_e_distances", + "use_volumetric_e", + "variable_layer_height", + "wipe" + }; std::vector<PrintStep> steps; std::vector<PrintObjectStep> osteps; @@ -707,7 +707,10 @@ double Print::skirt_first_layer_height() const Flow Print::brim_flow() const { ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width; - if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width; + if (width.value == 0) + width = this->regions.front()->config.perimeter_extrusion_width; + if (width.value == 0) + width = this->objects.front()->config.extrusion_width; /* We currently use a random region's perimeter extruder. While this works for most cases, we should probably consider all of the perimeter @@ -726,7 +729,10 @@ Flow Print::brim_flow() const Flow Print::skirt_flow() const { ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width; - if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width; + if (width.value == 0) + width = this->regions.front()->config.perimeter_extrusion_width; + if (width.value == 0) + width = this->objects.front()->config.extrusion_width; /* We currently use a random object's support material extruder. While this works for most cases, we should probably consider all of the support material @@ -968,7 +974,6 @@ void Print::_make_wipe_tower() // Let the ToolOrdering class know there will be initial priming extrusions at the start of the print. m_tool_ordering = ToolOrdering(*this, (unsigned int)-1, true); - unsigned int initial_extruder_id = m_tool_ordering.first_extruder(); if (! m_tool_ordering.has_wipe_tower()) // Don't generate any wipe tower. return; @@ -977,7 +982,7 @@ void Print::_make_wipe_tower() WipeTowerPrusaMM wipe_tower( float(this->config.wipe_tower_x.value), float(this->config.wipe_tower_y.value), float(this->config.wipe_tower_width.value), float(this->config.wipe_tower_per_color_wipe.value), - initial_extruder_id); + m_tool_ordering.first_extruder()); //wipe_tower.set_retract(); //wipe_tower.set_zhop(); @@ -1000,7 +1005,8 @@ void Print::_make_wipe_tower() // Generate the wipe tower layers. m_wipe_tower_tool_changes.reserve(m_tool_ordering.layer_tools().size()); - unsigned int current_extruder_id = initial_extruder_id; + // Set current_extruder_id to the last extruder primed. + unsigned int current_extruder_id = m_tool_ordering.all_extruders().back(); for (const ToolOrdering::LayerTools &layer_tools : m_tool_ordering.layer_tools()) { if (! layer_tools.has_wipe_tower) // This is a support only layer, or the wipe tower does not reach to this height. @@ -1015,7 +1021,11 @@ void Print::_make_wipe_tower() last_layer); std::vector<WipeTower::ToolChangeResult> tool_changes; for (unsigned int extruder_id : layer_tools.extruders) - if ((first_layer && extruder_id == initial_extruder_id) || extruder_id != current_extruder_id) { + // Call the wipe_tower.tool_change() at the first layer for the initial extruder + // to extrude the wipe tower brim, + if ((first_layer && extruder_id == m_tool_ordering.all_extruders().back()) || + // or when an extruder shall be switched. + extruder_id != current_extruder_id) { tool_changes.emplace_back(wipe_tower.tool_change(extruder_id, extruder_id == layer_tools.extruders.back(), WipeTower::PURPOSE_EXTRUDE)); current_extruder_id = extruder_id; } @@ -1065,7 +1075,11 @@ void Print::_make_wipe_tower() std::string Print::output_filename() { this->placeholder_parser.update_timestamp(); - return this->placeholder_parser.process(this->config.output_filename_format.value, 0); + try { + return this->placeholder_parser.process(this->config.output_filename_format.value, 0); + } catch (std::runtime_error &err) { + throw std::runtime_error(std::string("Failed processing of the output_filename_format template.\n") + err.what()); + } } std::string Print::output_filepath(const std::string &path) @@ -1079,13 +1093,13 @@ std::string Print::output_filepath(const std::string &path) if (! input_file.empty()) break; } - return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).string(); + return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).make_preferred().string(); } // if we were supplied a directory, use it and append our automatically generated filename boost::filesystem::path p(path); if (boost::filesystem::is_directory(p)) - return (p / this->output_filename()).string(); + return (p / this->output_filename()).make_preferred().string(); // if we were supplied a file which is not a directory, use it return path; diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index c8a87022e..ed54d9b6c 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1,7 +1,10 @@ #include "PrintConfig.hpp" + +#include <set> #include <boost/algorithm/string/replace.hpp> #include <boost/lexical_cast.hpp> #include <boost/thread.hpp> + #include <float.h> namespace Slic3r { @@ -14,43 +17,46 @@ PrintConfigDef::PrintConfigDef() def = this->add("avoid_crossing_perimeters", coBool); def->label = "Avoid crossing perimeters"; - def->tooltip = "Optimize travel moves in order to minimize the crossing of perimeters. This is mostly useful with Bowden extruders which suffer from oozing. This feature slows down both the print and the G-code generation."; + def->tooltip = "Optimize travel moves in order to minimize the crossing of perimeters. " + "This is mostly useful with Bowden extruders which suffer from oozing. " + "This feature slows down both the print and the G-code generation."; def->cli = "avoid-crossing-perimeters!"; def->default_value = new ConfigOptionBool(false); def = this->add("bed_shape", coPoints); def->label = "Bed shape"; - { - ConfigOptionPoints* opt = new ConfigOptionPoints(); - opt->values.push_back(Pointf(0,0)); - opt->values.push_back(Pointf(200,0)); - opt->values.push_back(Pointf(200,200)); - opt->values.push_back(Pointf(0,200)); - def->default_value = opt; - } + def->default_value = new ConfigOptionPoints { Pointf(0,0), Pointf(200,0), Pointf(200,200), Pointf(0,200) }; def = this->add("bed_temperature", coInts); def->label = "Other layers"; - def->tooltip = "Bed temperature for layers after the first one. Set this to zero to disable bed temperature control commands in the output."; + def->tooltip = "Bed temperature for layers after the first one. " + "Set this to zero to disable bed temperature control commands in the output."; def->cli = "bed-temperature=i@"; def->full_label = "Bed temperature"; def->min = 0; def->max = 300; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 0 }; def = this->add("before_layer_gcode", coString); def->label = "Before layer change G-code"; - def->tooltip = "This custom code is inserted at every layer change, right before the Z move. Note that you can use placeholder variables for all Slic3r settings as well as [layer_num] and [layer_z]."; + def->tooltip = "This custom code is inserted at every layer change, right before the Z move. " + "Note that you can use placeholder variables for all Slic3r settings as well " + "as [layer_num] and [layer_z]."; def->cli = "before-layer-gcode=s"; def->multiline = true; def->full_width = true; def->height = 50; def->default_value = new ConfigOptionString(""); + def = this->add("between_objects_gcode", coString); + def->label = "Between objects G-code"; + def->tooltip = "This code is inserted between objects when using sequential printing. By default extruder and bed temperature are reset using non-wait command; however if M104, M109, M140 or M190 are detected in this custom code, Slic3r will not add temperature commands. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want."; + def->cli = "between-objects-gcode=s"; + def->multiline = true; + def->full_width = true; + def->height = 120; + def->default_value = new ConfigOptionString(""); + def = this->add("bottom_solid_layers", coInt); def->label = "Bottom"; def->category = "Layers and Perimeters"; @@ -62,7 +68,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("bridge_acceleration", coFloat); def->label = "Bridge"; - def->tooltip = "This is the acceleration your printer will use for bridges. Set zero to disable acceleration control for bridges."; + def->tooltip = "This is the acceleration your printer will use for bridges. " + "Set zero to disable acceleration control for bridges."; def->sidetext = "mm/s²"; def->cli = "bridge-acceleration=f"; def->min = 0; @@ -71,7 +78,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("bridge_angle", coFloat); def->label = "Bridging angle"; def->category = "Infill"; - def->tooltip = "Bridging angle override. If left to zero, the bridging angle will be calculated automatically. Otherwise the provided angle will be used for all bridges. Use 180° for zero angle."; + def->tooltip = "Bridging angle override. If left to zero, the bridging angle will be calculated " + "automatically. Otherwise the provided angle will be used for all bridges. " + "Use 180° for zero angle."; def->sidetext = "°"; def->cli = "bridge-angle=f"; def->min = 0; @@ -84,16 +93,15 @@ PrintConfigDef::PrintConfigDef() def->cli = "bridge-fan-speed=i@"; def->min = 0; def->max = 100; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(100); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 100 }; def = this->add("bridge_flow_ratio", coFloat); def->label = "Bridge flow ratio"; def->category = "Advanced"; - def->tooltip = "This factor affects the amount of plastic for bridging. You can decrease it slightly to pull the extrudates and prevent sagging, although default settings are usually good and you should experiment with cooling (use a fan) before tweaking this."; + def->tooltip = "This factor affects the amount of plastic for bridging. " + "You can decrease it slightly to pull the extrudates and prevent sagging, " + "although default settings are usually good and you should experiment " + "with cooling (use a fan) before tweaking this."; def->cli = "bridge-flow-ratio=f"; def->min = 0; def->default_value = new ConfigOptionFloat(1); @@ -118,7 +126,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("clip_multipart_objects", coBool); def->label = "Clip multi-part objects"; - def->tooltip = "When printing multi-material objects, this settings will make slic3r to clip the overlapping object parts one by the other (2nd part will be clipped by the 1st, 3rd part will be clipped by the 1st and 2nd etc)."; + def->tooltip = "When printing multi-material objects, this settings will make slic3r " + "to clip the overlapping object parts one by the other " + "(2nd part will be clipped by the 1st, 3rd part will be clipped by the 1st and 2nd etc)."; def->cli = "clip-multipart-objects!"; def->default_value = new ConfigOptionBool(false); @@ -128,23 +138,25 @@ PrintConfigDef::PrintConfigDef() def = this->add("complete_objects", coBool); def->label = "Complete individual objects"; - def->tooltip = "When printing multiple objects or copies, this feature will complete each object before moving onto next one (and starting it from its bottom layer). This feature is useful to avoid the risk of ruined prints. Slic3r should warn and prevent you from extruder collisions, but beware."; + def->tooltip = "When printing multiple objects or copies, this feature will complete " + "each object before moving onto next one (and starting it from its bottom layer). " + "This feature is useful to avoid the risk of ruined prints. " + "Slic3r should warn and prevent you from extruder collisions, but beware."; def->cli = "complete-objects!"; def->default_value = new ConfigOptionBool(false); def = this->add("cooling", coBools); def->label = "Enable auto cooling"; - def->tooltip = "This flag enables the automatic cooling logic that adjusts print speed and fan speed according to layer printing time."; + def->tooltip = "This flag enables the automatic cooling logic that adjusts print speed " + "and fan speed according to layer printing time."; def->cli = "cooling!"; - { - ConfigOptionBools* opt = new ConfigOptionBools(); - opt->values.push_back(true); - def->default_value = opt; - } + def->default_value = new ConfigOptionBools { true }; def = this->add("default_acceleration", coFloat); def->label = "Default"; - def->tooltip = "This is the acceleration your printer will be reset to after the role-specific acceleration values are used (perimeter/infill). Set zero to prevent resetting acceleration at all."; + def->tooltip = "This is the acceleration your printer will be reset to after " + "the role-specific acceleration values are used (perimeter/infill). " + "Set zero to prevent resetting acceleration at all."; def->sidetext = "mm/s²"; def->cli = "default-acceleration=f"; def->min = 0; @@ -152,21 +164,19 @@ PrintConfigDef::PrintConfigDef() def = this->add("disable_fan_first_layers", coInts); def->label = "Disable fan for the first"; - def->tooltip = "You can set this to a positive value to disable fan at all during the first layers, so that it does not make adhesion worse."; + def->tooltip = "You can set this to a positive value to disable fan at all " + "during the first layers, so that it does not make adhesion worse."; def->sidetext = "layers"; def->cli = "disable-fan-first-layers=i@"; def->min = 0; def->max = 1000; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(3); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 3 }; def = this->add("dont_support_bridges", coBool); def->label = "Don't support bridges"; def->category = "Support material"; - def->tooltip = "Experimental option for preventing support material from being generated under bridged areas."; + def->tooltip = "Experimental option for preventing support material from being generated " + "under bridged areas."; def->cli = "dont-support-bridges!"; def->default_value = new ConfigOptionBool(true); @@ -182,7 +192,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("elefant_foot_compensation", coFloat); def->label = "Elefant foot compensation"; def->category = "Advanced"; - def->tooltip = "The first layer will be shrunk in the XY plane by the configured value to compensate for the 1st layer squish aka an Elefant Foot effect."; + def->tooltip = "The first layer will be shrunk in the XY plane by the configured value " + "to compensate for the 1st layer squish aka an Elefant Foot effect."; def->sidetext = "mm"; def->cli = "elefant-foot-compensation=f"; def->min = 0; @@ -190,7 +201,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("end_gcode", coString); def->label = "End G-code"; - def->tooltip = "This end procedure is inserted at the end of the output file. Note that you can use placeholder variables for all Slic3r settings."; + def->tooltip = "This end procedure is inserted at the end of the output file. " + "Note that you can use placeholder variables for all Slic3r settings."; def->cli = "end-gcode=s"; def->multiline = true; def->full_width = true; @@ -199,28 +211,28 @@ PrintConfigDef::PrintConfigDef() def = this->add("end_filament_gcode", coStrings); def->label = "End G-code"; - def->tooltip = "This end procedure is inserted at the end of the output file, before the printer end gcode. Note that you can use placeholder variables for all Slic3r settings. If you have multiple extruders, the gcode is processed in extruder order."; + def->tooltip = "This end procedure is inserted at the end of the output file, before the printer end gcode. " + "Note that you can use placeholder variables for all Slic3r settings. " + "If you have multiple extruders, the gcode is processed in extruder order."; def->cli = "end-filament-gcode=s@"; def->multiline = true; def->full_width = true; def->height = 120; - { - ConfigOptionStrings* opt = new ConfigOptionStrings(); - opt->values.push_back("; Filament-specific end gcode \n;END gcode for filament\n"); - def->default_value = opt; - } + def->default_value = new ConfigOptionStrings { "; Filament-specific end gcode \n;END gcode for filament\n" }; def = this->add("ensure_vertical_shell_thickness", coBool); def->label = "Ensure vertical shell thickness"; def->category = "Layers and Perimeters"; - def->tooltip = "Add solid infill near sloping surfaces to guarantee the vertical shell thickness (top+bottom solid layers)."; + def->tooltip = "Add solid infill near sloping surfaces to guarantee the vertical shell thickness " + "(top+bottom solid layers)."; def->cli = "ensure-vertical-shell-thickness!"; def->default_value = new ConfigOptionBool(false); def = this->add("external_fill_pattern", coEnum); def->label = "Top/bottom fill pattern"; def->category = "Infill"; - def->tooltip = "Fill pattern for top/bottom infill. This only affects the external visible layer, and not its adjacent solid shells."; + def->tooltip = "Fill pattern for top/bottom infill. This only affects the external visible layer, " + "and not its adjacent solid shells."; def->cli = "external-fill-pattern|solid-fill-pattern=s"; def->enum_keys_map = &ConfigOptionEnum<InfillPattern>::get_enum_values(); def->enum_values.push_back("rectilinear"); @@ -233,13 +245,16 @@ PrintConfigDef::PrintConfigDef() def->enum_labels.push_back("Hilbert Curve"); def->enum_labels.push_back("Archimedean Chords"); def->enum_labels.push_back("Octagram Spiral"); + // solid_fill_pattern is an obsolete equivalent to external_fill_pattern. def->aliases.push_back("solid_fill_pattern"); def->default_value = new ConfigOptionEnum<InfillPattern>(ipRectilinear); def = this->add("external_perimeter_extrusion_width", coFloatOrPercent); def->label = "External perimeters"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width for external perimeters. If left zero, an automatic value will be used that maximizes accuracy of the external visible surfaces. If expressed as percentage (for example 200%) it will be computed over layer height."; + def->tooltip = "Set this to a non-zero value to set a manual extrusion width for external perimeters. " + "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. " + "If expressed as percentage (for example 200%), it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "external-perimeter-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -247,7 +262,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("external_perimeter_speed", coFloatOrPercent); def->label = "External perimeters"; def->category = "Speed"; - def->tooltip = "This separate setting will affect the speed of external perimeters (the visible ones). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above. Set to zero for auto."; + def->tooltip = "This separate setting will affect the speed of external perimeters (the visible ones). " + "If expressed as percentage (for example: 80%) it will be calculated " + "on the perimeters speed setting above. Set to zero for auto."; def->sidetext = "mm/s or %"; def->cli = "external-perimeter-speed=s"; def->ratio_over = "perimeter_speed"; @@ -257,14 +274,17 @@ PrintConfigDef::PrintConfigDef() def = this->add("external_perimeters_first", coBool); def->label = "External perimeters first"; def->category = "Layers and Perimeters"; - def->tooltip = "Print contour perimeters from the outermost one to the innermost one instead of the default inverse order."; + def->tooltip = "Print contour perimeters from the outermost one to the innermost one " + "instead of the default inverse order."; def->cli = "external-perimeters-first!"; def->default_value = new ConfigOptionBool(false); def = this->add("extra_perimeters", coBool); def->label = "Extra perimeters if needed"; def->category = "Layers and Perimeters"; - def->tooltip = "Add more perimeters when needed for avoiding gaps in sloping walls. Slic3r keeps adding perimeters, until more than 70% of the loop immediately above is supported."; + def->tooltip = "Add more perimeters when needed for avoiding gaps in sloping walls. " + "Slic3r keeps adding perimeters, until more than 70% of the loop immediately above " + "is supported."; def->cli = "extra-perimeters!"; def->default_value = new ConfigOptionBool(true); @@ -272,7 +292,8 @@ PrintConfigDef::PrintConfigDef() def->gui_type = "i_enum_open"; def->label = "Extruder"; def->category = "Extruders"; - def->tooltip = "The extruder to use (unless more specific extruder settings are specified). This value overrides perimeter and infill extruders, but not the support extruders."; + def->tooltip = "The extruder to use (unless more specific extruder settings are specified). " + "This value overrides perimeter and infill extruders, but not the support extruders."; def->cli = "extruder=i"; def->min = 0; // 0 = inherit defaults def->enum_labels.push_back("default"); // override label for item 0 @@ -283,7 +304,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("extruder_clearance_height", coFloat); def->label = "Height"; - def->tooltip = "Set this to the vertical distance between your nozzle tip and (usually) the X carriage rods. In other words, this is the height of the clearance cylinder around your extruder, and it represents the maximum depth the extruder can peek before colliding with other printed objects."; + def->tooltip = "Set this to the vertical distance between your nozzle tip and (usually) the X carriage rods. " + "In other words, this is the height of the clearance cylinder around your extruder, " + "and it represents the maximum depth the extruder can peek before colliding with " + "other printed objects."; def->sidetext = "mm"; def->cli = "extruder-clearance-height=f"; def->min = 0; @@ -291,7 +315,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("extruder_clearance_radius", coFloat); def->label = "Radius"; - def->tooltip = "Set this to the clearance radius around your extruder. If the extruder is not centered, choose the largest value for safety. This setting is used to check for collisions and to display the graphical preview in the plater."; + def->tooltip = "Set this to the clearance radius around your extruder. " + "If the extruder is not centered, choose the largest value for safety. " + "This setting is used to check for collisions and to display the graphical preview " + "in the plater."; def->sidetext = "mm"; def->cli = "extruder-clearance-radius=f"; def->min = 0; @@ -302,83 +329,70 @@ PrintConfigDef::PrintConfigDef() def->tooltip = "This is only used in the Slic3r interface as a visual help."; def->cli = "extruder-color=s@"; def->gui_type = "color"; - { - ConfigOptionStrings* opt = new ConfigOptionStrings(); - // Empty string means no color assigned yet. -// opt->values.push_back("#FFFFFF"); - opt->values.push_back(""); - def->default_value = opt; - } + // Empty string means no color assigned yet. + def->default_value = new ConfigOptionStrings { "" }; def = this->add("extruder_offset", coPoints); def->label = "Extruder offset"; - def->tooltip = "If your firmware doesn't handle the extruder displacement you need the G-code to take it into account. This option lets you specify the displacement of each extruder with respect to the first one. It expects positive coordinates (they will be subtracted from the XY coordinate)."; + def->tooltip = "If your firmware doesn't handle the extruder displacement you need the G-code " + "to take it into account. This option lets you specify the displacement of each extruder " + "with respect to the first one. It expects positive coordinates (they will be subtracted " + "from the XY coordinate)."; def->sidetext = "mm"; def->cli = "extruder-offset=s@"; - { - ConfigOptionPoints* opt = new ConfigOptionPoints(); - opt->values.push_back(Pointf(0,0)); - def->default_value = opt; - } + def->default_value = new ConfigOptionPoints { Pointf(0,0) }; def = this->add("extrusion_axis", coString); def->label = "Extrusion axis"; - def->tooltip = "Use this option to set the axis letter associated to your printer's extruder (usually E but some printers use A)."; + def->tooltip = "Use this option to set the axis letter associated to your printer's extruder " + "(usually E but some printers use A)."; def->cli = "extrusion-axis=s"; def->default_value = new ConfigOptionString("E"); def = this->add("extrusion_multiplier", coFloats); def->label = "Extrusion multiplier"; - def->tooltip = "This factor changes the amount of flow proportionally. You may need to tweak this setting to get nice surface finish and correct single wall widths. Usual values are between 0.9 and 1.1. If you think you need to change this more, check filament diameter and your firmware E steps."; + def->tooltip = "This factor changes the amount of flow proportionally. You may need to tweak " + "this setting to get nice surface finish and correct single wall widths. " + "Usual values are between 0.9 and 1.1. If you think you need to change this more, " + "check filament diameter and your firmware E steps."; def->cli = "extrusion-multiplier=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(1); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 1. }; def = this->add("extrusion_width", coFloatOrPercent); def->label = "Default extrusion width"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width. If left to zero, Slic3r calculates a width automatically. If expressed as percentage (for example: 230%) it will be computed over layer height."; + def->tooltip = "Set this to a non-zero value to allow a manual extrusion width. " + "If left to zero, Slic3r derives extrusion widths from the nozzle diameter " + "(see the tooltips for perimeter extrusion width, infill extrusion width etc). " + "If expressed as percentage (for example: 230%), it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for auto)"; def->cli = "extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("fan_always_on", coBools); def->label = "Keep fan always on"; - def->tooltip = "If this is enabled, fan will never be disabled and will be kept running at least at its minimum speed. Useful for PLA, harmful for ABS."; + def->tooltip = "If this is enabled, fan will never be disabled and will be kept running at least " + "at its minimum speed. Useful for PLA, harmful for ABS."; def->cli = "fan-always-on!"; - { - ConfigOptionBools* opt = new ConfigOptionBools(); - opt->values.push_back(false); - def->default_value = opt; - } + def->default_value = new ConfigOptionBools { false }; def = this->add("fan_below_layer_time", coInts); def->label = "Enable fan if layer print time is below"; - def->tooltip = "If layer print time is estimated below this number of seconds, fan will be enabled and its speed will be calculated by interpolating the minimum and maximum speeds."; + def->tooltip = "If layer print time is estimated below this number of seconds, fan will be enabled " + "and its speed will be calculated by interpolating the minimum and maximum speeds."; def->sidetext = "approximate seconds"; def->cli = "fan-below-layer-time=i@"; def->width = 60; def->min = 0; def->max = 1000; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(60); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 60 }; def = this->add("filament_colour", coStrings); def->label = "Color"; def->tooltip = "This is only used in the Slic3r interface as a visual help."; def->cli = "filament-color=s@"; def->gui_type = "color"; - { - ConfigOptionStrings* opt = new ConfigOptionStrings(); - opt->values.push_back("#FFFFFF"); - def->default_value = opt; - } + def->default_value = new ConfigOptionStrings { "#29b2b2" }; def = this->add("filament_notes", coStrings); def->label = "Filament notes"; @@ -387,51 +401,43 @@ PrintConfigDef::PrintConfigDef() def->multiline = true; def->full_width = true; def->height = 130; - { - ConfigOptionStrings* opt = new ConfigOptionStrings(); - opt->values.push_back(""); - def->default_value = opt; - } + def->default_value = new ConfigOptionStrings { "" }; def = this->add("filament_max_volumetric_speed", coFloats); def->label = "Max volumetric speed"; - def->tooltip = "Maximum volumetric speed allowed for this filament. Limits the maximum volumetric speed of a print to the minimum of print and filament volumetric speed. Set to zero for no limit."; + def->tooltip = "Maximum volumetric speed allowed for this filament. Limits the maximum volumetric " + "speed of a print to the minimum of print and filament volumetric speed. " + "Set to zero for no limit."; def->sidetext = "mm³/s"; def->cli = "filament-max-volumetric-speed=f@"; def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0.f); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("filament_diameter", coFloats); def->label = "Diameter"; - def->tooltip = "Enter your filament diameter here. Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average."; + def->tooltip = "Enter your filament diameter here. Good precision is required, so use a caliper " + "and do multiple measurements along the filament, then compute the average."; def->sidetext = "mm"; def->cli = "filament-diameter=f@"; def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(3); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 3. }; def = this->add("filament_density", coFloats); def->label = "Density"; - def->tooltip = "Enter your filament density here. This is only for statistical information. A decent way is to weigh a known length of filament and compute the ratio of the length to volume. Better is to calculate the volume directly through displacement."; + def->tooltip = "Enter your filament density here. This is only for statistical information. " + "A decent way is to weigh a known length of filament and compute the ratio " + "of the length to volume. Better is to calculate the volume directly through displacement."; def->sidetext = "g/cm^3"; def->cli = "filament-density=f@"; def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("filament_type", coStrings); def->label = "Filament type"; - def->tooltip = "If you want to process the output G-code through custom scripts, just list their absolute paths here. Separate multiple scripts with a semicolon. Scripts will be passed the absolute path to the G-code file as the first argument, and they can access the Slic3r config settings by reading environment variables."; + def->tooltip = "If you want to process the output G-code through custom scripts, just list their " + "absolute paths here. Separate multiple scripts with a semicolon. Scripts will be passed " + "the absolute path to the G-code file as the first argument, and they can access " + "the Slic3r config settings by reading environment variables."; def->cli = "filament_type=s@"; def->gui_type = "f_enum_open"; def->gui_flags = "show_value"; @@ -444,21 +450,13 @@ PrintConfigDef::PrintConfigDef() def->enum_values.push_back("EDGE"); def->enum_values.push_back("NGEN"); def->enum_values.push_back("PVA"); - { - ConfigOptionStrings* opt = new ConfigOptionStrings(); - opt->values.push_back("PLA"); - def->default_value = opt; - } + def->default_value = new ConfigOptionStrings { "PLA" }; def = this->add("filament_soluble", coBools); def->label = "Soluble material"; def->tooltip = "Soluble material is most likely used for a soluble support."; def->cli = "filament-soluble!"; - { - ConfigOptionBools* opt = new ConfigOptionBools(); - opt->values.push_back(false); - def->default_value = opt; - } + def->default_value = new ConfigOptionBools { false }; def = this->add("filament_cost", coFloats); def->label = "Cost"; @@ -466,19 +464,17 @@ PrintConfigDef::PrintConfigDef() def->sidetext = "money/kg"; def->cli = "filament-cost=f@"; def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } - - def = this->add("filament_settings_id", coString); - def->default_value = new ConfigOptionString(""); + def->default_value = new ConfigOptionFloats { 0. }; + def = this->add("filament_settings_id", coStrings); + def->default_value = new ConfigOptionStrings { "" }; + def = this->add("fill_angle", coFloat); def->label = "Fill angle"; def->category = "Infill"; - def->tooltip = "Default base angle for infill orientation. Cross-hatching will be applied to this. Bridges will be infilled using the best direction Slic3r can detect, so this setting does not affect them."; + def->tooltip = "Default base angle for infill orientation. Cross-hatching will be applied to this. " + "Bridges will be infilled using the best direction Slic3r can detect, so this setting " + "does not affect them."; def->sidetext = "°"; def->cli = "fill-angle=f"; def->min = 0; @@ -559,7 +555,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("first_layer_acceleration", coFloat); def->label = "First layer"; - def->tooltip = "This is the acceleration your printer will use for first layer. Set zero to disable acceleration control for first layer."; + def->tooltip = "This is the acceleration your printer will use for first layer. Set zero " + "to disable acceleration control for first layer."; def->sidetext = "mm/s²"; def->cli = "first-layer-acceleration=f"; def->min = 0; @@ -567,20 +564,20 @@ PrintConfigDef::PrintConfigDef() def = this->add("first_layer_bed_temperature", coInts); def->label = "First layer"; - def->tooltip = "Heated build plate temperature for the first layer. Set this to zero to disable bed temperature control commands in the output."; + def->tooltip = "Heated build plate temperature for the first layer. Set this to zero to disable " + "bed temperature control commands in the output."; def->cli = "first-layer-bed-temperature=i@"; def->max = 0; def->max = 300; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 0 }; def = this->add("first_layer_extrusion_width", coFloatOrPercent); def->label = "First layer"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width for first layer. You can use this to force fatter extrudates for better adhesion. If expressed as percentage (for example 120%) it will be computed over first layer height. If set to zero, it will use the Default Extrusion Width."; + def->tooltip = "Set this to a non-zero value to set a manual extrusion width for first layer. " + "You can use this to force fatter extrudates for better adhesion. If expressed " + "as percentage (for example 120%) it will be computed over first layer height. " + "If set to zero, it will use the default extrusion width."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "first-layer-extrusion-width=s"; def->ratio_over = "first_layer_height"; @@ -589,7 +586,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("first_layer_height", coFloatOrPercent); def->label = "First layer height"; def->category = "Layers and Perimeters"; - def->tooltip = "When printing with very low layer heights, you might still want to print a thicker bottom layer to improve adhesion and tolerance for non perfect build plates. This can be expressed as an absolute value or as a percentage (for example: 150%) over the default layer height."; + def->tooltip = "When printing with very low layer heights, you might still want to print a thicker " + "bottom layer to improve adhesion and tolerance for non perfect build plates. " + "This can be expressed as an absolute value or as a percentage (for example: 150%) " + "over the default layer height."; def->sidetext = "mm or %"; def->cli = "first-layer-height=s"; def->ratio_over = "layer_height"; @@ -597,7 +597,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("first_layer_speed", coFloatOrPercent); def->label = "First layer speed"; - def->tooltip = "If expressed as absolute value in mm/s, this speed will be applied to all the print moves of the first layer, regardless of their type. If expressed as a percentage (for example: 40%) it will scale the default speeds."; + def->tooltip = "If expressed as absolute value in mm/s, this speed will be applied to all the print moves " + "of the first layer, regardless of their type. If expressed as a percentage " + "(for example: 40%) it will scale the default speeds."; def->sidetext = "mm/s or %"; def->cli = "first-layer-speed=s"; def->min = 0; @@ -605,20 +607,18 @@ PrintConfigDef::PrintConfigDef() def = this->add("first_layer_temperature", coInts); def->label = "First layer"; - def->tooltip = "Extruder temperature for first layer. If you want to control temperature manually during print, set this to zero to disable temperature control commands in the output file."; + def->tooltip = "Extruder temperature for first layer. If you want to control temperature manually " + "during print, set this to zero to disable temperature control commands in the output file."; def->cli = "first-layer-temperature=i@"; def->min = 0; def->max = 500; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(200); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 200 }; def = this->add("gap_fill_speed", coFloat); def->label = "Gap fill"; def->category = "Speed"; - def->tooltip = "Speed for filling small gaps using short zigzag moves. Keep this reasonably low to avoid too much shaking and resonance issues. Set zero to disable gaps filling."; + def->tooltip = "Speed for filling small gaps using short zigzag moves. Keep this reasonably low " + "to avoid too much shaking and resonance issues. Set zero to disable gaps filling."; def->sidetext = "mm/s"; def->cli = "gap-fill-speed=f"; def->min = 0; @@ -626,13 +626,17 @@ PrintConfigDef::PrintConfigDef() def = this->add("gcode_comments", coBool); def->label = "Verbose G-code"; - def->tooltip = "Enable this to get a commented G-code file, with each line explained by a descriptive text. If you print from SD card, the additional weight of the file could make your firmware slow down."; + def->tooltip = "Enable this to get a commented G-code file, with each line explained by a descriptive text. " + "If you print from SD card, the additional weight of the file could make your firmware " + "slow down."; def->cli = "gcode-comments!"; def->default_value = new ConfigOptionBool(0); def = this->add("gcode_flavor", coEnum); def->label = "G-code flavor"; - def->tooltip = "Some G/M-code commands, including temperature control and others, are not universal. Set this option to your printer's firmware to get a compatible output. The \"No extrusion\" flavor prevents Slic3r from exporting any extrusion value at all."; + def->tooltip = "Some G/M-code commands, including temperature control and others, are not universal. " + "Set this option to your printer's firmware to get a compatible output. " + "The \"No extrusion\" flavor prevents Slic3r from exporting any extrusion value at all."; def->cli = "gcode-flavor=s"; def->enum_keys_map = &ConfigOptionEnum<GCodeFlavor>::get_enum_values(); def->enum_values.push_back("reprap"); @@ -657,7 +661,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("infill_acceleration", coFloat); def->label = "Infill"; - def->tooltip = "This is the acceleration your printer will use for infill. Set zero to disable acceleration control for infill."; + def->tooltip = "This is the acceleration your printer will use for infill. Set zero to disable " + "acceleration control for infill."; def->sidetext = "mm/s²"; def->cli = "infill-acceleration=f"; def->min = 0; @@ -666,7 +671,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("infill_every_layers", coInt); def->label = "Combine infill every"; def->category = "Infill"; - def->tooltip = "This feature allows to combine infill and speed up your print by extruding thicker infill layers while preserving thin perimeters, thus accuracy."; + def->tooltip = "This feature allows to combine infill and speed up your print by extruding thicker " + "infill layers while preserving thin perimeters, thus accuracy."; def->sidetext = "layers"; def->cli = "infill-every-layers=i"; def->full_label = "Combine infill every n layers"; @@ -684,7 +690,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("infill_extrusion_width", coFloatOrPercent); def->label = "Infill"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width for infill. You may want to use fatter extrudates to speed up the infill and make your parts stronger. If expressed as percentage (for example 90%) it will be computed over layer height."; + def->tooltip = "Set this to a non-zero value to set a manual extrusion width for infill. " + "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. " + "You may want to use fatter extrudates to speed up the infill and make your parts stronger. " + "If expressed as percentage (for example 90%) it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "infill-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -698,14 +707,18 @@ PrintConfigDef::PrintConfigDef() def = this->add("infill_only_where_needed", coBool); def->label = "Only infill where needed"; def->category = "Infill"; - def->tooltip = "This option will limit infill to the areas actually needed for supporting ceilings (it will act as internal support material). If enabled, slows down the G-code generation due to the multiple checks involved."; + def->tooltip = "This option will limit infill to the areas actually needed for supporting ceilings " + "(it will act as internal support material). If enabled, slows down the G-code generation " + "due to the multiple checks involved."; def->cli = "infill-only-where-needed!"; def->default_value = new ConfigOptionBool(false); def = this->add("infill_overlap", coFloatOrPercent); def->label = "Infill/perimeters overlap"; def->category = "Advanced"; - def->tooltip = "This setting applies an additional overlap between infill and perimeters for better bonding. Theoretically this shouldn't be needed, but backlash might cause gaps. If expressed as percentage (example: 15%) it is calculated over perimeter extrusion width."; + def->tooltip = "This setting applies an additional overlap between infill and perimeters for better bonding. " + "Theoretically this shouldn't be needed, but backlash might cause gaps. If expressed " + "as percentage (example: 15%) it is calculated over perimeter extrusion width."; def->sidetext = "mm or %"; def->cli = "infill-overlap=s"; def->ratio_over = "perimeter_extrusion_width"; @@ -724,14 +737,18 @@ PrintConfigDef::PrintConfigDef() def = this->add("interface_shells", coBool); def->label = "Interface shells"; - def->tooltip = "Force the generation of solid shells between adjacent materials/volumes. Useful for multi-extruder prints with translucent materials or manual soluble support material."; + def->tooltip = "Force the generation of solid shells between adjacent materials/volumes. " + "Useful for multi-extruder prints with translucent materials or manual soluble " + "support material."; def->cli = "interface-shells!"; def->category = "Layers and Perimeters"; def->default_value = new ConfigOptionBool(false); def = this->add("layer_gcode", coString); def->label = "After layer change G-code"; - def->tooltip = "This custom code is inserted at every layer change, right after the Z move and before the extruder moves to the first layer point. Note that you can use placeholder variables for all Slic3r settings as well as [layer_num] and [layer_z]."; + def->tooltip = "This custom code is inserted at every layer change, right after the Z move " + "and before the extruder moves to the first layer point. Note that you can use " + "placeholder variables for all Slic3r settings as well as [layer_num] and [layer_z]."; def->cli = "after-layer-gcode|layer-gcode=s"; def->multiline = true; def->full_width = true; @@ -741,7 +758,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("layer_height", coFloat); def->label = "Layer height"; def->category = "Layers and Perimeters"; - def->tooltip = "This setting controls the height (and thus the total number) of the slices/layers. Thinner layers give better accuracy but take more time to print."; + def->tooltip = "This setting controls the height (and thus the total number) of the slices/layers. " + "Thinner layers give better accuracy but take more time to print."; def->sidetext = "mm"; def->cli = "layer-height=f"; def->min = 0; @@ -754,27 +772,24 @@ PrintConfigDef::PrintConfigDef() def->cli = "max-fan-speed=i@"; def->min = 0; def->max = 100; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(100); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 100 }; def = this->add("max_layer_height", coFloats); def->label = "Max"; - def->tooltip = "This is the highest printable layer height for this extruder, used to cap the variable layer height and support layer height. Maximum recommended layer height is 75% of the extrusion width to achieve reasonable inter-layer adhesion. If set to 0, layer height is limited to 75% of the nozzle diameter."; + def->tooltip = "This is the highest printable layer height for this extruder, used to cap " + "the variable layer height and support layer height. Maximum recommended layer height " + "is 75% of the extrusion width to achieve reasonable inter-layer adhesion. " + "If set to 0, layer height is limited to 75% of the nozzle diameter."; def->sidetext = "mm"; def->cli = "max-layer-height=f@"; def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("max_print_speed", coFloat); def->label = "Max print speed"; - def->tooltip = "When setting other speed settings to 0 Slic3r will autocalculate the optimal speed in order to keep constant extruder pressure. This experimental setting is used to set the highest print speed you want to allow."; + def->tooltip = "When setting other speed settings to 0 Slic3r will autocalculate the optimal speed " + "in order to keep constant extruder pressure. This experimental setting is used " + "to set the highest print speed you want to allow."; def->sidetext = "mm/s"; def->cli = "max-print-speed=f"; def->min = 1; @@ -782,7 +797,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("max_volumetric_speed", coFloat); def->label = "Max volumetric speed"; - def->tooltip = "This experimental setting is used to set the maximum volumetric speed your extruder supports."; + def->tooltip = "This experimental setting is used to set the maximum volumetric speed your " + "extruder supports."; def->sidetext = "mm³/s"; def->cli = "max-volumetric-speed=f"; def->min = 0; @@ -790,8 +806,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("max_volumetric_extrusion_rate_slope_positive", coFloat); def->label = "Max volumetric slope positive"; - def->tooltip = "This experimental setting is used to limit the speed of change in extrusion rate. A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " - "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."; + def->tooltip = "This experimental setting is used to limit the speed of change in extrusion rate. " + "A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " + "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) " + "to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."; def->sidetext = "mm³/s²"; def->cli = "max-volumetric-extrusion-rate-slope-positive=f"; def->min = 0; @@ -799,8 +817,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("max_volumetric_extrusion_rate_slope_negative", coFloat); def->label = "Max volumetric slope negative"; - def->tooltip = "This experimental setting is used to limit the speed of change in extrusion rate. A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " - "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."; + def->tooltip = "This experimental setting is used to limit the speed of change in extrusion rate. " + "A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " + "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) " + "to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."; def->sidetext = "mm³/s²"; def->cli = "max-volumetric-extrusion-rate-slope-negative=f"; def->min = 0; @@ -813,23 +833,16 @@ PrintConfigDef::PrintConfigDef() def->cli = "min-fan-speed=i@"; def->min = 0; def->max = 100; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(35); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 35 }; def = this->add("min_layer_height", coFloats); def->label = "Min"; - def->tooltip = "This is the lowest printable layer height for this extruder and limits the resolution for variable layer height. Typical values are between 0.05 mm and 0.1 mm."; + def->tooltip = "This is the lowest printable layer height for this extruder and limits " + "the resolution for variable layer height. Typical values are between 0.05 mm and 0.1 mm."; def->sidetext = "mm"; def->cli = "min-layer-height=f@"; def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0.07); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0.07 }; def = this->add("min_print_speed", coFloats); def->label = "Min print speed"; @@ -837,15 +850,13 @@ PrintConfigDef::PrintConfigDef() def->sidetext = "mm/s"; def->cli = "min-print-speed=f@"; def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(10.); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 10. }; def = this->add("min_skirt_length", coFloat); def->label = "Minimum extrusion length"; - def->tooltip = "Generate no less than the number of skirt loops required to consume the specified amount of filament on the bottom layer. For multi-extruder machines, this minimum applies to each extruder."; + def->tooltip = "Generate no less than the number of skirt loops required to consume " + "the specified amount of filament on the bottom layer. For multi-extruder machines, " + "this minimum applies to each extruder."; def->sidetext = "mm"; def->cli = "min-skirt-length=f"; def->min = 0; @@ -853,7 +864,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("notes", coString); def->label = "Configuration notes"; - def->tooltip = "You can put here your personal notes. This text will be added to the G-code header comments."; + def->tooltip = "You can put here your personal notes. This text will be added to the G-code " + "header comments."; def->cli = "notes=s"; def->multiline = true; def->full_width = true; @@ -865,39 +877,43 @@ PrintConfigDef::PrintConfigDef() def->tooltip = "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)"; def->sidetext = "mm"; def->cli = "nozzle-diameter=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0.5); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0.5 }; def = this->add("octoprint_apikey", coString); def->label = "API Key"; - def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain the API Key required for authentication."; + def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain " + "the API Key required for authentication."; def->cli = "octoprint-apikey=s"; def->default_value = new ConfigOptionString(""); def = this->add("octoprint_host", coString); def->label = "Host or IP"; - def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain the hostname or IP address of the OctoPrint instance."; + def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain " + "the hostname or IP address of the OctoPrint instance."; def->cli = "octoprint-host=s"; def->default_value = new ConfigOptionString(""); def = this->add("only_retract_when_crossing_perimeters", coBool); def->label = "Only retract when crossing perimeters"; - def->tooltip = "Disables retraction when the travel path does not exceed the upper layer's perimeters (and thus any ooze will be probably invisible)."; + def->tooltip = "Disables retraction when the travel path does not exceed the upper layer's perimeters " + "(and thus any ooze will be probably invisible)."; def->cli = "only-retract-when-crossing-perimeters!"; def->default_value = new ConfigOptionBool(true); def = this->add("ooze_prevention", coBool); def->label = "Enable"; - def->tooltip = "This option will drop the temperature of the inactive extruders to prevent oozing. It will enable a tall skirt automatically and move extruders outside such skirt when changing temperatures."; + def->tooltip = "This option will drop the temperature of the inactive extruders to prevent oozing. " + "It will enable a tall skirt automatically and move extruders outside such " + "skirt when changing temperatures."; def->cli = "ooze-prevention!"; def->default_value = new ConfigOptionBool(false); def = this->add("output_filename_format", coString); def->label = "Output filename format"; - def->tooltip = "You can use all configuration options as variables inside this template. For example: [layer_height], [fill_density] etc. You can also use [timestamp], [year], [month], [day], [hour], [minute], [second], [version], [input_filename], [input_filename_base]."; + def->tooltip = "You can use all configuration options as variables inside this template. " + "For example: [layer_height], [fill_density] etc. You can also use [timestamp], " + "[year], [month], [day], [hour], [minute], [second], [version], [input_filename], " + "[input_filename_base]."; def->cli = "output-filename-format=s"; def->full_width = true; def->default_value = new ConfigOptionString("[input_filename_base].gcode"); @@ -905,13 +921,16 @@ PrintConfigDef::PrintConfigDef() def = this->add("overhangs", coBool); def->label = "Detect bridging perimeters"; def->category = "Layers and Perimeters"; - def->tooltip = "Experimental option to adjust flow for overhangs (bridge flow will be used), to apply bridge speed to them and enable fan."; + def->tooltip = "Experimental option to adjust flow for overhangs (bridge flow will be used), " + "to apply bridge speed to them and enable fan."; def->cli = "overhangs!"; def->default_value = new ConfigOptionBool(true); def = this->add("perimeter_acceleration", coFloat); def->label = "Perimeters"; - def->tooltip = "This is the acceleration your printer will use for perimeters. A high value like 9000 usually gives good results if your hardware is up to the job. Set zero to disable acceleration control for perimeters."; + def->tooltip = "This is the acceleration your printer will use for perimeters. " + "A high value like 9000 usually gives good results if your hardware is up to the job. " + "Set zero to disable acceleration control for perimeters."; def->sidetext = "mm/s²"; def->cli = "perimeter-acceleration=f"; def->default_value = new ConfigOptionFloat(0); @@ -928,7 +947,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("perimeter_extrusion_width", coFloatOrPercent); def->label = "Perimeters"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width for perimeters. You may want to use thinner extrudates to get more accurate surfaces. If expressed as percentage (for example 200%) it will be computed over layer height."; + def->tooltip = "Set this to a non-zero value to set a manual extrusion width for perimeters. " + "You may want to use thinner extrudates to get more accurate surfaces. " + "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. " + "If expressed as percentage (for example 200%) it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "perimeter-extrusion-width=s"; def->aliases.push_back("perimeters_extrusion_width"); @@ -947,7 +969,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("perimeters", coInt); def->label = "Perimeters"; def->category = "Layers and Perimeters"; - def->tooltip = "This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled."; + def->tooltip = "This option sets the number of perimeters to generate for each layer. " + "Note that Slic3r may increase this number automatically when it detects " + "sloping surfaces which benefit from a higher number of perimeters " + "if the Extra Perimeters option is enabled."; def->sidetext = "(minimum)"; def->cli = "perimeters=i"; def->aliases.push_back("perimeter_offsets"); @@ -956,7 +981,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("post_process", coStrings); def->label = "Post-processing scripts"; - def->tooltip = "If you want to process the output G-code through custom scripts, just list their absolute paths here. Separate multiple scripts with a semicolon. Scripts will be passed the absolute path to the G-code file as the first argument, and they can access the Slic3r config settings by reading environment variables."; + def->tooltip = "If you want to process the output G-code through custom scripts, " + "just list their absolute paths here. Separate multiple scripts with a semicolon. " + "Scripts will be passed the absolute path to the G-code file as the first argument, " + "and they can access the Slic3r config settings by reading environment variables."; def->cli = "post-process=s@"; def->gui_flags = "serialized"; def->multiline = true; @@ -981,7 +1009,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("raft_layers", coInt); def->label = "Raft layers"; def->category = "Support material"; - def->tooltip = "The object will be raised by this number of layers, and support material will be generated under it."; + def->tooltip = "The object will be raised by this number of layers, and support material " + "will be generated under it."; def->sidetext = "layers"; def->cli = "raft-layers=i"; def->min = 0; @@ -989,7 +1018,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("resolution", coFloat); def->label = "Resolution"; - def->tooltip = "Minimum detail resolution, used to simplify the input file for speeding up the slicing job and reducing memory usage. High-resolution models often carry more detail than printers can render. Set to zero to disable any simplification and use full resolution from input."; + def->tooltip = "Minimum detail resolution, used to simplify the input file for speeding up " + "the slicing job and reducing memory usage. High-resolution models often carry " + "more detail than printers can render. Set to zero to disable any simplification " + "and use full resolution from input."; def->sidetext = "mm"; def->cli = "resolution=f"; def->min = 0; @@ -1000,113 +1032,84 @@ PrintConfigDef::PrintConfigDef() def->tooltip = "Retraction is not triggered when travel moves are shorter than this length."; def->sidetext = "mm"; def->cli = "retract-before-travel=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(2); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 2. }; def = this->add("retract_before_wipe", coPercents); def->label = "Retract amount before wipe"; - def->tooltip = "With bowden extruders, it may be wise to do some amount of quick retract before doing the wipe movement."; + def->tooltip = "With bowden extruders, it may be wise to do some amount of quick retract " + "before doing the wipe movement."; def->sidetext = "%"; def->cli = "retract-before-wipe=s@"; - { - ConfigOptionPercents* opt = new ConfigOptionPercents(); - opt->values.push_back(0.f); - def->default_value = opt; - } + def->default_value = new ConfigOptionPercents { 0. }; def = this->add("retract_layer_change", coBools); def->label = "Retract on layer change"; def->tooltip = "This flag enforces a retraction whenever a Z move is done."; def->cli = "retract-layer-change!"; - { - ConfigOptionBools* opt = new ConfigOptionBools(); - opt->values.push_back(false); - def->default_value = opt; - } + def->default_value = new ConfigOptionBools { false }; def = this->add("retract_length", coFloats); def->label = "Length"; def->full_label = "Retraction Length"; - def->tooltip = "When retraction is triggered, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder)."; + def->tooltip = "When retraction is triggered, filament is pulled back by the specified amount " + "(the length is measured on raw filament, before it enters the extruder)."; def->sidetext = "mm (zero to disable)"; def->cli = "retract-length=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(2); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 2. }; def = this->add("retract_length_toolchange", coFloats); def->label = "Length"; def->full_label = "Retraction Length (Toolchange)"; - def->tooltip = "When retraction is triggered before changing tool, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder)."; + def->tooltip = "When retraction is triggered before changing tool, filament is pulled back " + "by the specified amount (the length is measured on raw filament, before it enters " + "the extruder)."; def->sidetext = "mm (zero to disable)"; def->cli = "retract-length-toolchange=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(10); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 10. }; def = this->add("retract_lift", coFloats); def->label = "Lift Z"; - def->tooltip = "If you set this to a positive value, Z is quickly raised every time a retraction is triggered. When using multiple extruders, only the setting for the first extruder will be considered."; + def->tooltip = "If you set this to a positive value, Z is quickly raised every time a retraction " + "is triggered. When using multiple extruders, only the setting for the first extruder " + "will be considered."; def->sidetext = "mm"; def->cli = "retract-lift=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_lift_above", coFloats); def->label = "Above Z"; def->full_label = "Only lift Z above"; - def->tooltip = "If you set this to a positive value, Z lift will only take place above the specified absolute Z. You can tune this setting for skipping lift on the first layers."; + def->tooltip = "If you set this to a positive value, Z lift will only take place above the specified " + "absolute Z. You can tune this setting for skipping lift on the first layers."; def->sidetext = "mm"; def->cli = "retract-lift-above=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_lift_below", coFloats); def->label = "Below Z"; def->full_label = "Only lift Z below"; - def->tooltip = "If you set this to a positive value, Z lift will only take place below the specified absolute Z. You can tune this setting for limiting lift to the first layers."; + def->tooltip = "If you set this to a positive value, Z lift will only take place below " + "the specified absolute Z. You can tune this setting for limiting lift " + "to the first layers."; def->sidetext = "mm"; def->cli = "retract-lift-below=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_restart_extra", coFloats); def->label = "Extra length on restart"; - def->tooltip = "When the retraction is compensated after the travel move, the extruder will push this additional amount of filament. This setting is rarely needed."; + def->tooltip = "When the retraction is compensated after the travel move, the extruder will push " + "this additional amount of filament. This setting is rarely needed."; def->sidetext = "mm"; def->cli = "retract-restart-extra=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_restart_extra_toolchange", coFloats); def->label = "Extra length on restart"; - def->tooltip = "When the retraction is compensated after changing tool, the extruder will push this additional amount of filament."; + def->tooltip = "When the retraction is compensated after changing tool, the extruder will push " + "this additional amount of filament."; def->sidetext = "mm"; def->cli = "retract-restart-extra-toolchange=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_speed", coFloats); def->label = "Retraction Speed"; @@ -1114,23 +1117,16 @@ PrintConfigDef::PrintConfigDef() def->tooltip = "The speed for retractions (it only applies to the extruder motor)."; def->sidetext = "mm/s"; def->cli = "retract-speed=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(40); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 40. }; def = this->add("deretract_speed", coFloats); def->label = "Deretraction Speed"; def->full_label = "Deretraction Speed"; - def->tooltip = "The speed for loading of a filament into extruder after retraction (it only applies to the extruder motor). If left to zero, the retraction speed is used."; + def->tooltip = "The speed for loading of a filament into extruder after retraction " + "(it only applies to the extruder motor). If left to zero, the retraction speed is used."; def->sidetext = "mm/s"; def->cli = "retract-speed=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("seam_position", coEnum); def->label = "Seam position"; @@ -1195,7 +1191,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("skirt_distance", coFloat); def->label = "Distance from object"; - def->tooltip = "Distance between skirt and object(s). Set this to zero to attach the skirt to the object(s) and get a brim for better adhesion."; + def->tooltip = "Distance between skirt and object(s). Set this to zero to attach the skirt " + "to the object(s) and get a brim for better adhesion."; def->sidetext = "mm"; def->cli = "skirt-distance=f"; def->min = 0; @@ -1203,7 +1200,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("skirt_height", coInt); def->label = "Skirt height"; - def->tooltip = "Height of skirt expressed in layers. Set this to a tall value to use skirt as a shield against drafts."; + def->tooltip = "Height of skirt expressed in layers. Set this to a tall value to use skirt " + "as a shield against drafts."; def->sidetext = "layers"; def->cli = "skirt-height=i"; def->default_value = new ConfigOptionInt(1); @@ -1211,29 +1209,30 @@ PrintConfigDef::PrintConfigDef() def = this->add("skirts", coInt); def->label = "Loops (minimum)"; def->full_label = "Skirt Loops"; - def->tooltip = "Number of loops for the skirt. If the Minimum Extrusion Length option is set, the number of loops might be greater than the one configured here. Set this to zero to disable skirt completely."; + def->tooltip = "Number of loops for the skirt. If the Minimum Extrusion Length option is set, " + "the number of loops might be greater than the one configured here. Set this to zero " + "to disable skirt completely."; def->cli = "skirts=i"; def->min = 0; def->default_value = new ConfigOptionInt(1); def = this->add("slowdown_below_layer_time", coInts); def->label = "Slow down if layer print time is below"; - def->tooltip = "If layer print time is estimated below this number of seconds, print moves speed will be scaled down to extend duration to this value."; + def->tooltip = "If layer print time is estimated below this number of seconds, print moves " + "speed will be scaled down to extend duration to this value."; def->sidetext = "approximate seconds"; def->cli = "slowdown-below-layer-time=i@"; def->width = 60; def->min = 0; def->max = 1000; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(5); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 5 }; def = this->add("small_perimeter_speed", coFloatOrPercent); def->label = "Small perimeters"; def->category = "Speed"; - def->tooltip = "This separate setting will affect the speed of perimeters having radius <= 6.5mm (usually holes). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above. Set to zero for auto."; + def->tooltip = "This separate setting will affect the speed of perimeters having radius <= 6.5mm " + "(usually holes). If expressed as percentage (for example: 80%) it will be calculated " + "on the perimeters speed setting above. Set to zero for auto."; def->sidetext = "mm/s or %"; def->cli = "small-perimeter-speed=s"; def->ratio_over = "perimeter_speed"; @@ -1260,7 +1259,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("solid_infill_every_layers", coInt); def->label = "Solid infill every"; def->category = "Infill"; - def->tooltip = "This feature allows to force a solid layer every given number of layers. Zero to disable. You can set this to any value (for example 9999); Slic3r will automatically choose the maximum possible number of layers to combine according to nozzle diameter and layer height."; + def->tooltip = "This feature allows to force a solid layer every given number of layers. " + "Zero to disable. You can set this to any value (for example 9999); " + "Slic3r will automatically choose the maximum possible number of layers " + "to combine according to nozzle diameter and layer height."; def->sidetext = "layers"; def->cli = "solid-infill-every-layers=i"; def->min = 0; @@ -1269,7 +1271,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("solid_infill_extrusion_width", coFloatOrPercent); def->label = "Solid infill"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for solid surfaces. If expressed as percentage (for example 90%) it will be computed over layer height."; + def->tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for solid surfaces. " + "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. " + "If expressed as percentage (for example 90%) it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "solid-infill-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -1277,7 +1281,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("solid_infill_speed", coFloatOrPercent); def->label = "Solid infill"; def->category = "Speed"; - def->tooltip = "Speed for printing solid regions (top/bottom/internal horizontal shells). This can be expressed as a percentage (for example: 80%) over the default infill speed above. Set to zero for auto."; + def->tooltip = "Speed for printing solid regions (top/bottom/internal horizontal shells). " + "This can be expressed as a percentage (for example: 80%) over the default " + "infill speed above. Set to zero for auto."; def->sidetext = "mm/s or %"; def->cli = "solid-infill-speed=s"; def->ratio_over = "infill_speed"; @@ -1295,13 +1301,18 @@ PrintConfigDef::PrintConfigDef() def = this->add("spiral_vase", coBool); def->label = "Spiral vase"; - def->tooltip = "This feature will raise Z gradually while printing a single-walled object in order to remove any visible seam. This option requires a single perimeter, no infill, no top solid layers and no support material. You can still set any number of bottom solid layers as well as skirt/brim loops. It won't work when printing more than an object."; + def->tooltip = "This feature will raise Z gradually while printing a single-walled object " + "in order to remove any visible seam. This option requires a single perimeter, " + "no infill, no top solid layers and no support material. You can still set " + "any number of bottom solid layers as well as skirt/brim loops. " + "It won't work when printing more than an object."; def->cli = "spiral-vase!"; def->default_value = new ConfigOptionBool(false); def = this->add("standby_temperature_delta", coInt); def->label = "Temperature variation"; - def->tooltip = "Temperature difference to be applied when an extruder is not active. Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped."; + def->tooltip = "Temperature difference to be applied when an extruder is not active. " + "Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped."; def->sidetext = "â°C"; def->cli = "standby-temperature-delta=i"; def->min = -500; @@ -1310,7 +1321,13 @@ PrintConfigDef::PrintConfigDef() def = this->add("start_gcode", coString); def->label = "Start G-code"; - def->tooltip = "This start procedure is inserted at the beginning, after bed has reached the target temperature and extruder just started heating, and before extruder has finished heating. If Slic3r detects M104 or M190 in your custom codes, such commands will not be prepended automatically so you're free to customize the order of heating commands and other custom actions. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want."; + def->tooltip = "This start procedure is inserted at the beginning, after bed has reached " + "the target temperature and extruder just started heating, and before extruder " + "has finished heating. If Slic3r detects M104 or M190 in your custom codes, " + "such commands will not be prepended automatically so you're free to customize " + "the order of heating commands and other custom actions. Note that you can use " + "placeholder variables for all Slic3r settings, so you can put " + "a \"M109 S[first_layer_temperature]\" command wherever you want."; def->cli = "start-gcode=s"; def->multiline = true; def->full_width = true; @@ -1319,16 +1336,19 @@ PrintConfigDef::PrintConfigDef() def = this->add("start_filament_gcode", coStrings); def->label = "Start G-code"; - def->tooltip = "This start procedure is inserted at the beginning, after any printer start gcode. This is used to override settings for a specific filament. If Slic3r detects M104, M109, M140 or M190 in your custom codes, such commands will not be prepended automatically so you're free to customize the order of heating commands and other custom actions. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want. If you have multiple extruders, the gcode is processed in extruder order."; + def->tooltip = "This start procedure is inserted at the beginning, after any printer start gcode. " + "This is used to override settings for a specific filament. If Slic3r detects " + "M104, M109, M140 or M190 in your custom codes, such commands will " + "not be prepended automatically so you're free to customize the order " + "of heating commands and other custom actions. Note that you can use placeholder variables " + "for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command " + "wherever you want. If you have multiple extruders, the gcode is processed " + "in extruder order."; def->cli = "start-filament-gcode=s@"; def->multiline = true; def->full_width = true; def->height = 120; - { - ConfigOptionStrings* opt = new ConfigOptionStrings(); - opt->values.push_back("; Filament gcode\n"); - def->default_value = opt; - } + def->default_value = new ConfigOptionStrings { "; Filament gcode\n" }; def = this->add("single_extruder_multi_material", coBool); def->label = "Single Extruder Multi Material"; @@ -1346,7 +1366,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_xy_spacing", coFloatOrPercent); def->label = "XY separation between an object and its support"; def->category = "Support material"; - def->tooltip = "XY separation between an object and its support. If expressed as percentage (for example 50%), it will be calculated over external perimeter width."; + def->tooltip = "XY separation between an object and its support. If expressed as percentage " + "(for example 50%), it will be calculated over external perimeter width."; def->sidetext = "mm or %"; def->cli = "support-material-xy-spacing=s"; def->ratio_over = "external_perimeter_extrusion_width"; @@ -1375,7 +1396,9 @@ PrintConfigDef::PrintConfigDef() def->gui_type = "f_enum_open"; def->label = "Contact Z distance"; def->category = "Support material"; - def->tooltip = "The vertical distance between object and support material interface. Setting this to 0 will also prevent Slic3r from using bridge flow and speed for the first object layer."; + def->tooltip = "The vertical distance between object and support material interface. " + "Setting this to 0 will also prevent Slic3r from using bridge flow and speed " + "for the first object layer."; def->sidetext = "mm"; def->cli = "support-material-contact-distance=f"; def->min = 0; @@ -1388,7 +1411,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_enforce_layers", coInt); def->label = "Enforce support for the first"; def->category = "Support material"; - def->tooltip = "Generate support material for the specified number of layers counting from bottom, regardless of whether normal support material is enabled or not and regardless of any angle threshold. This is useful for getting more adhesion of objects having a very thin or poor footprint on the build plate."; + def->tooltip = "Generate support material for the specified number of layers counting from bottom, " + "regardless of whether normal support material is enabled or not and regardless " + "of any angle threshold. This is useful for getting more adhesion of objects " + "having a very thin or poor footprint on the build plate."; def->sidetext = "layers"; def->cli = "support-material-enforce-layers=f"; def->full_label = "Enforce support for the first n layers"; @@ -1398,7 +1424,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_extruder", coInt); def->label = "Support material/raft/skirt extruder"; def->category = "Extruders"; - def->tooltip = "The extruder to use when printing support material, raft and skirt (1+, 0 to use the current extruder to minimize tool changes)."; + def->tooltip = "The extruder to use when printing support material, raft and skirt " + "(1+, 0 to use the current extruder to minimize tool changes)."; def->cli = "support-material-extruder=i"; def->min = 0; def->default_value = new ConfigOptionInt(1); @@ -1406,7 +1433,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_extrusion_width", coFloatOrPercent); def->label = "Support material"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width for support material. If expressed as percentage (for example 90%) it will be computed over layer height."; + def->tooltip = "Set this to a non-zero value to set a manual extrusion width for support material. " + "If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. " + "If expressed as percentage (for example 90%) it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "support-material-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -1421,7 +1450,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_interface_extruder", coInt); def->label = "Support material/raft interface extruder"; def->category = "Extruders"; - def->tooltip = "The extruder to use when printing support material interface (1+, 0 to use the current extruder to minimize tool changes). This affects raft too."; + def->tooltip = "The extruder to use when printing support material interface " + "(1+, 0 to use the current extruder to minimize tool changes). This affects raft too."; def->cli = "support-material-interface-extruder=i"; def->min = 0; def->default_value = new ConfigOptionInt(1); @@ -1447,7 +1477,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_interface_speed", coFloatOrPercent); def->label = "Support material interface"; def->category = "Support material"; - def->tooltip = "Speed for printing support material interface layers. If expressed as percentage (for example 50%) it will be calculated over support material speed."; + def->tooltip = "Speed for printing support material interface layers. If expressed as percentage " + "(for example 50%) it will be calculated over support material speed."; def->sidetext = "mm/s or %"; def->cli = "support-material-interface-speed=s"; def->ratio_over = "support_material_speed"; @@ -1491,14 +1522,19 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_synchronize_layers", coBool); def->label = "Synchronize with object layers"; def->category = "Support material"; - def->tooltip = "Synchronize support layers with the object print layers. This is useful with multi-material printers, where the extruder switch is expensive."; + def->tooltip = "Synchronize support layers with the object print layers. This is useful " + "with multi-material printers, where the extruder switch is expensive."; def->cli = "support-material-synchronize-layers!"; def->default_value = new ConfigOptionBool(false); def = this->add("support_material_threshold", coInt); def->label = "Overhang threshold"; def->category = "Support material"; - def->tooltip = "Support material will not be generated for overhangs whose slope angle (90° = vertical) is above the given threshold. In other words, this value represent the most horizontal slope (measured from the horizontal plane) that you can print without support material. Set to zero for automatic detection (recommended)."; + def->tooltip = "Support material will not be generated for overhangs whose slope angle " + "(90° = vertical) is above the given threshold. In other words, this value " + "represent the most horizontal slope (measured from the horizontal plane) " + "that you can print without support material. Set to zero for automatic detection " + "(recommended)."; def->sidetext = "°"; def->cli = "support-material-threshold=i"; def->min = 0; @@ -1508,33 +1544,33 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_with_sheath", coBool); def->label = "With sheath around the support"; def->category = "Support material"; - def->tooltip = "Add a sheath (a single perimeter line) around the base support. This makes the support more reliable, but also more difficult to remove."; + def->tooltip = "Add a sheath (a single perimeter line) around the base support. This makes " + "the support more reliable, but also more difficult to remove."; def->cli = "support-material-with-sheath!"; def->default_value = new ConfigOptionBool(true); def = this->add("temperature", coInts); def->label = "Other layers"; - def->tooltip = "Extruder temperature for layers after the first one. Set this to zero to disable temperature control commands in the output."; + def->tooltip = "Extruder temperature for layers after the first one. Set this to zero to disable " + "temperature control commands in the output."; def->cli = "temperature=i@"; def->full_label = "Temperature"; def->max = 0; def->max = 500; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(200); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 200 }; def = this->add("thin_walls", coBool); def->label = "Detect thin walls"; def->category = "Layers and Perimeters"; - def->tooltip = "Detect single-width walls (parts where two extrusions don't fit and we need to collapse them into a single trace)."; + def->tooltip = "Detect single-width walls (parts where two extrusions don't fit and we need " + "to collapse them into a single trace)."; def->cli = "thin-walls!"; def->default_value = new ConfigOptionBool(true); def = this->add("threads", coInt); def->label = "Threads"; - def->tooltip = "Threads are used to parallelize long-running tasks. Optimal threads number is slightly above the number of available cores/processors."; + def->tooltip = "Threads are used to parallelize long-running tasks. Optimal threads number " + "is slightly above the number of available cores/processors."; def->cli = "threads|j=i"; def->readonly = true; def->min = 1; @@ -1545,7 +1581,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("toolchange_gcode", coString); def->label = "Tool change G-code"; - def->tooltip = "This custom code is inserted right before every extruder change. Note that you can use placeholder variables for all Slic3r settings as well as [previous_extruder] and [next_extruder]."; + def->tooltip = "This custom code is inserted right before every extruder change. " + "Note that you can use placeholder variables for all Slic3r settings as well " + "as [previous_extruder] and [next_extruder]."; def->cli = "toolchange-gcode=s"; def->multiline = true; def->full_width = true; @@ -1555,7 +1593,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("top_infill_extrusion_width", coFloatOrPercent); def->label = "Top solid infill"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for top surfaces. You may want to use thinner extrudates to fill all narrow regions and get a smoother finish. If expressed as percentage (for example 90%) it will be computed over layer height."; + def->tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for top surfaces. " + "You may want to use thinner extrudates to fill all narrow regions and get a smoother finish. " + "If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. " + "If expressed as percentage (for example 90%) it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "top-infill-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -1563,7 +1604,11 @@ PrintConfigDef::PrintConfigDef() def = this->add("top_solid_infill_speed", coFloatOrPercent); def->label = "Top solid infill"; def->category = "Speed"; - def->tooltip = "Speed for printing top solid layers (it only applies to the uppermost external layers and not to their internal solid layers). You may want to slow down this to get a nicer surface finish. This can be expressed as a percentage (for example: 80%) over the solid infill speed above. Set to zero for auto."; + def->tooltip = "Speed for printing top solid layers (it only applies to the uppermost " + "external layers and not to their internal solid layers). You may want " + "to slow down this to get a nicer surface finish. This can be expressed " + "as a percentage (for example: 80%) over the solid infill speed above. " + "Set to zero for auto."; def->sidetext = "mm/s or %"; def->cli = "top-solid-infill-speed=s"; def->ratio_over = "solid_infill_speed"; @@ -1590,41 +1635,47 @@ PrintConfigDef::PrintConfigDef() def = this->add("use_firmware_retraction", coBool); def->label = "Use firmware retraction"; - def->tooltip = "This experimental setting uses G10 and G11 commands to have the firmware handle the retraction. This is only supported in recent Marlin."; + def->tooltip = "This experimental setting uses G10 and G11 commands to have the firmware " + "handle the retraction. This is only supported in recent Marlin."; def->cli = "use-firmware-retraction!"; def->default_value = new ConfigOptionBool(false); def = this->add("use_relative_e_distances", coBool); def->label = "Use relative E distances"; - def->tooltip = "If your firmware requires relative E values, check this, otherwise leave it unchecked. Most firmwares use absolute values."; + def->tooltip = "If your firmware requires relative E values, check this, " + "otherwise leave it unchecked. Most firmwares use absolute values."; def->cli = "use-relative-e-distances!"; def->default_value = new ConfigOptionBool(false); def = this->add("use_volumetric_e", coBool); def->label = "Use volumetric E"; - def->tooltip = "This experimental setting uses outputs the E values in cubic millimeters instead of linear millimeters. If your firmware doesn't already know filament diameter(s), you can put commands like 'M200 D[filament_diameter_0] T0' in your start G-code in order to turn volumetric mode on and use the filament diameter associated to the filament selected in Slic3r. This is only supported in recent Marlin."; + def->tooltip = "This experimental setting uses outputs the E values in cubic millimeters " + "instead of linear millimeters. If your firmware doesn't already know " + "filament diameter(s), you can put commands like 'M200 D[filament_diameter_0] T0' " + "in your start G-code in order to turn volumetric mode on and use the filament " + "diameter associated to the filament selected in Slic3r. This is only supported " + "in recent Marlin."; def->cli = "use-volumetric-e!"; def->default_value = new ConfigOptionBool(false); def = this->add("variable_layer_height", coBool); def->label = "Enable variable layer height feature"; - def->tooltip = "Some printers or printer setups may have difficulties printing with a variable layer height. Enabled by default."; + def->tooltip = "Some printers or printer setups may have difficulties printing " + "with a variable layer height. Enabled by default."; def->cli = "variable-layer-height!"; def->default_value = new ConfigOptionBool(true); def = this->add("wipe", coBools); def->label = "Wipe while retracting"; - def->tooltip = "This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders."; + def->tooltip = "This flag will move the nozzle while retracting to minimize the possible blob " + "on leaky extruders."; def->cli = "wipe!"; - { - ConfigOptionBools* opt = new ConfigOptionBools(); - opt->values.push_back(false); - def->default_value = opt; - } + def->default_value = new ConfigOptionBools { false }; def = this->add("wipe_tower", coBool); def->label = "Enable"; - def->tooltip = "Multi material printers may need to prime or purge extruders on tool changes. Extrude the excess material into the wipe tower."; + def->tooltip = "Multi material printers may need to prime or purge extruders on tool changes. " + "Extrude the excess material into the wipe tower."; def->cli = "wipe-tower!"; def->default_value = new ConfigOptionBool(false); @@ -1651,7 +1702,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("wipe_tower_per_color_wipe", coFloat); def->label = "Per color change depth"; - def->tooltip = "Depth of a wipe color per color change. For N colors, there will be maximum (N-1) tool switches performed, therefore the total depth of the wipe tower will be (N-1) times this value."; + def->tooltip = "Depth of a wipe color per color change. For N colors, there will be " + "maximum (N-1) tool switches performed, therefore the total depth " + "of the wipe tower will be (N-1) times this value."; def->sidetext = "mm"; def->cli = "wipe-tower-per-color-wipe=f"; def->default_value = new ConfigOptionFloat(15.); @@ -1659,14 +1712,19 @@ PrintConfigDef::PrintConfigDef() def = this->add("xy_size_compensation", coFloat); def->label = "XY Size Compensation"; def->category = "Advanced"; - def->tooltip = "The object will be grown/shrunk in the XY plane by the configured value (negative = inwards, positive = outwards). This might be useful for fine-tuning hole sizes."; + def->tooltip = "The object will be grown/shrunk in the XY plane by the configured value " + "(negative = inwards, positive = outwards). This might be useful " + "for fine-tuning hole sizes."; def->sidetext = "mm"; def->cli = "xy-size-compensation=f"; def->default_value = new ConfigOptionFloat(0); def = this->add("z_offset", coFloat); def->label = "Z offset"; - def->tooltip = "This value will be added (or subtracted) from all the Z coordinates in the output G-code. It is used to compensate for bad Z endstop position: for example, if your endstop zero actually leaves the nozzle 0.3mm far from the print bed, set this to -0.3 (or fix your endstop)."; + def->tooltip = "This value will be added (or subtracted) from all the Z coordinates " + "in the output G-code. It is used to compensate for bad Z endstop position: " + "for example, if your endstop zero actually leaves the nozzle 0.3mm far " + "from the print bed, set this to -0.3 (or fix your endstop)."; def->sidetext = "mm"; def->cli = "z-offset=f"; def->default_value = new ConfigOptionFloat(0); @@ -1719,18 +1777,16 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va value = "60%"; } - // cemetery of old config settings - if (opt_key == "duplicate_x" || opt_key == "duplicate_y" || opt_key == "multiply_x" - || opt_key == "multiply_y" || opt_key == "support_material_tool" - || opt_key == "acceleration" || opt_key == "adjust_overhang_flow" - || opt_key == "standby_temperature" || opt_key == "scale" || opt_key == "rotate" - || opt_key == "duplicate" || opt_key == "duplicate_grid" || opt_key == "rotate" - || opt_key == "scale" || opt_key == "duplicate_grid" - || opt_key == "start_perimeters_at_concave_points" - || opt_key == "start_perimeters_at_non_overhang" || opt_key == "randomize_start" - || opt_key == "seal_position" || opt_key == "bed_size" || opt_key == "octoprint_host" - || opt_key == "print_center" || opt_key == "g0" || opt_key == "threads") - { + // Ignore the following obsolete configuration keys: + static std::set<std::string> ignore = { + "duplicate_x", "duplicate_y", "gcode_arcs", "multiply_x", "multiply_y", + "support_material_tool", "acceleration", "adjust_overhang_flow", + "standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid", + "start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start", + "seal_position", "vibration_limit", "bed_size", "octoprint_host", + "print_center", "g0", "threads", "pressure_advance" + }; + if (ignore.find(opt_key) != ignore.end()) { opt_key = ""; return; } @@ -1744,6 +1800,18 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va PrintConfigDef print_config_def; +DynamicPrintConfig* DynamicPrintConfig::new_from_defaults() +{ + return new_from_defaults_keys(FullPrintConfig::defaults().keys()); +} + +DynamicPrintConfig* DynamicPrintConfig::new_from_defaults_keys(const std::vector<std::string> &keys) +{ + auto *out = new DynamicPrintConfig(); + out->apply_only(FullPrintConfig::defaults(), keys); + return out; +} + void DynamicPrintConfig::normalize() { if (this->has("extruder")) { @@ -1785,7 +1853,7 @@ std::string DynamicPrintConfig::validate() { // Full print config is initialized from the defaults. FullPrintConfig fpc; - fpc.apply(*this); + fpc.apply(*this, true); // Verify this print options through the FullPrintConfig. return fpc.validate(); } @@ -1825,7 +1893,7 @@ std::string FullPrintConfig::validate() // --nozzle-diameter for (double nd : this->nozzle_diameter.values) - if (nd < 1) + if (nd < 0.005) return "Invalid value for --nozzle-diameter"; // --perimeters diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 18beeff6f..ab58aa356 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -142,6 +142,9 @@ public: DynamicPrintConfig() {} DynamicPrintConfig(const DynamicPrintConfig &other) : DynamicConfig(other) {} + static DynamicPrintConfig* new_from_defaults(); + static DynamicPrintConfig* new_from_defaults_keys(const std::vector<std::string> &keys); + // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. const ConfigDef* def() const override { return &print_config_def; } @@ -449,6 +452,7 @@ class GCodeConfig : public StaticPrintConfig STATIC_PRINT_CONFIG_CACHE(GCodeConfig) public: ConfigOptionString before_layer_gcode; + ConfigOptionString between_objects_gcode; ConfigOptionFloats deretract_speed; ConfigOptionString end_gcode; ConfigOptionStrings end_filament_gcode; @@ -497,6 +501,7 @@ protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { OPT_PTR(before_layer_gcode); + OPT_PTR(between_objects_gcode); OPT_PTR(deretract_speed); OPT_PTR(end_gcode); OPT_PTR(end_filament_gcode); diff --git a/xs/src/libslic3r/Slicing.hpp b/xs/src/libslic3r/Slicing.hpp index 1534e19f5..b4a074bb5 100644 --- a/xs/src/libslic3r/Slicing.hpp +++ b/xs/src/libslic3r/Slicing.hpp @@ -103,7 +103,7 @@ inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters sp1.layer_height == sp2.layer_height && sp1.min_layer_height == sp2.min_layer_height && sp1.max_layer_height == sp2.max_layer_height && - sp1.max_suport_layer_height == sp2.max_suport_layer_height && +// sp1.max_suport_layer_height == sp2.max_suport_layer_height && sp1.first_print_layer_height == sp2.first_print_layer_height && sp1.first_object_layer_height == sp2.first_object_layer_height && sp1.first_object_layer_bridging == sp2.first_object_layer_bridging && diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index 7a05cf998..07943fb15 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -2569,7 +2569,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // TODO: use brim ordering algorithm to_infill_polygons = to_polygons(to_infill); // TODO: use offset2_ex() - to_infill = offset_ex(to_infill, float(- flow.scaled_spacing())); + to_infill = offset_ex(to_infill, float(- 0.4 * flow.scaled_spacing())); extrusion_entities_append_paths( support_layer.support_fills.entities, to_polylines(STDMOVE(to_infill_polygons)), @@ -2602,7 +2602,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Base flange. filler->angle = raft_angle_1st_layer; filler->spacing = m_first_layer_flow.spacing(); - density = 0.5f; + // 70% of density on the 1st layer. + density = 0.7f; } else if (support_layer_id >= m_slicing_params.base_raft_layers) { filler->angle = raft_angle_interface; // We don't use $base_flow->spacing because we need a constant spacing @@ -2776,7 +2777,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // TODO: use brim ordering algorithm Polygons to_infill_polygons = to_polygons(to_infill); // TODO: use offset2_ex() - to_infill = offset_ex(to_infill, - float(flow.scaled_spacing())); + to_infill = offset_ex(to_infill, - 0.4 * float(flow.scaled_spacing())); extrusion_entities_append_paths( base_layer.extrusions, to_polylines(STDMOVE(to_infill_polygons)), diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 38d89938a..0dbbf5f8d 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -1208,6 +1208,8 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slic // perform a safety offset to merge very close facets (TODO: find test case for this) double safety_offset = scale_(0.0499); +//FIXME see https://github.com/prusa3d/Slic3r/issues/520 +// double safety_offset = scale_(0.0001); ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset); #ifdef SLIC3R_TRIANGLEMESH_DEBUG diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index 79e1c2e2e..3afbc912f 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_Utils_hpp_ #define slic3r_Utils_hpp_ +#include <locale> + namespace Slic3r { extern void set_logging_level(unsigned int level); @@ -8,15 +10,31 @@ extern void trace(unsigned int level, const char *message); // Set a path with GUI resource files. void set_var_dir(const std::string &path); -// Return a path to the GUI resource files. +// Return a full path to the GUI resource files. const std::string& var_dir(); -// Return a resource path for a file_name. +// Return a full resource path for a file_name. std::string var(const std::string &file_name); +// Set a path with various static definition data (for example the initial config bundles). +void set_resources_dir(const std::string &path); +// Return a full path to the resources directory. +const std::string& resources_dir(); + +// Set a path with preset files. +void set_data_dir(const std::string &path); +// Return a full path to the GUI resource files. +const std::string& data_dir(); + extern std::string encode_path(const char *src); extern std::string decode_path(const char *src); extern std::string normalize_utf8_nfc(const char *src); +// Timestamp formatted for header_slic3r_generated(). +extern std::string timestamp_str(); +// Standard "generated by Slic3r version xxx timestamp xxx" header string, +// to be placed at the top of Slic3r generated files. +inline std::string header_slic3r_generated() { return std::string("generated by " SLIC3R_FORK_NAME " " SLIC3R_VERSION " " ) + timestamp_str(); } + // Compute the next highest power of 2 of 32-bit v // http://graphics.stanford.edu/~seander/bithacks.html template<typename T> diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index e70113710..f71311e7f 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -14,7 +14,7 @@ #include <boost/thread.hpp> #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" -#define SLIC3R_VERSION "1.33.8.devel" +#define SLIC3R_VERSION "1.38.4" #define SLIC3R_BUILD "UNKNOWN" typedef long coord_t; diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 6e60f978d..ee579161f 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -1,4 +1,5 @@ #include <locale> +#include <ctime> #include <boost/log/core.hpp> #include <boost/log/trivial.hpp> @@ -6,8 +7,9 @@ #include <boost/locale.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/date_time/local_time/local_time.hpp> #include <boost/filesystem.hpp> - #include <boost/nowide/integration/filesystem.hpp> #include <boost/nowide/convert.hpp> @@ -87,6 +89,30 @@ std::string var(const std::string &file_name) return file.string(); } +static std::string g_resources_dir; + +void set_resources_dir(const std::string &dir) +{ + g_resources_dir = dir; +} + +const std::string& resources_dir() +{ + return g_resources_dir; +} + +static std::string g_data_dir; + +void set_data_dir(const std::string &dir) +{ + g_data_dir = dir; +} + +const std::string& data_dir() +{ + return g_data_dir; +} + } // namespace Slic3r #ifdef SLIC3R_HAS_BROKEN_CROAK @@ -207,4 +233,16 @@ std::string normalize_utf8_nfc(const char *src) return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8); } +std::string timestamp_str() +{ + const auto now = boost::posix_time::second_clock::local_time(); + const auto date = now.date(); + char buf[2048]; + sprintf(buf, "on %04d-%02d-%02d at %02d:%02d:%02d", + // Local date in an ANSII format. + int(now.date().year()), int(now.date().month()), int(now.date().day()), + int(now.time_of_day().hours()), int(now.time_of_day().minutes()), int(now.time_of_day().seconds())); + return buf; +} + }; // namespace Slic3r diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index b89b6abc0..948fcfd93 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -54,12 +54,14 @@ REGISTER_CLASS(Surface, "Surface"); REGISTER_CLASS(SurfaceCollection, "Surface::Collection"); REGISTER_CLASS(PrintObjectSupportMaterial, "Print::SupportMaterial2"); REGISTER_CLASS(TriangleMesh, "TriangleMesh"); +REGISTER_CLASS(AppConfig, "GUI::AppConfig"); REGISTER_CLASS(GLShader, "GUI::_3DScene::GLShader"); REGISTER_CLASS(GLVolume, "GUI::_3DScene::GLVolume"); REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection"); REGISTER_CLASS(Preset, "GUI::Preset"); REGISTER_CLASS(PresetCollection, "GUI::PresetCollection"); REGISTER_CLASS(PresetBundle, "GUI::PresetBundle"); +REGISTER_CLASS(PresetHints, "GUI::PresetHints"); SV* ConfigBase__as_hash(ConfigBase* THIS) { diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp new file mode 100644 index 000000000..3161c5b78 --- /dev/null +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -0,0 +1,154 @@ +#include <GL/glew.h> + +#include "../../libslic3r/libslic3r.h" +#include "../../libslic3r/Utils.hpp" +#include "AppConfig.hpp" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <utility> +#include <assert.h> + +#include <boost/filesystem.hpp> +#include <boost/nowide/cenv.hpp> +#include <boost/nowide/fstream.hpp> +#include <boost/property_tree/ini_parser.hpp> +#include <boost/property_tree/ptree.hpp> + +namespace Slic3r { + +void AppConfig::reset() +{ + m_storage.clear(); + set_defaults(); +}; + +// Override missing or keys with their defaults. +void AppConfig::set_defaults() +{ + // Reset the empty fields to defaults. + if (get("autocenter").empty()) + set("autocenter", "1"); + // Disable background processing by default as it is not stable. + if (get("background_processing").empty()) + set("background_processing", "0"); + // If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. + // By default, Prusa has the controller hidden. + if (get("no_controller").empty()) + set("no_controller", "1"); + // If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available. + if (get("no_defaults").empty()) + set("no_defaults", "1"); + if (get("show_incompatible_presets").empty()) + set("show_incompatible_presets", "0"); + // Version check is enabled by default in the config, but it is not implemented yet. + if (get("version_check").empty()) + set("version_check", "1"); +} + +void AppConfig::load() +{ + // 1) Read the complete config file into a boost::property_tree. + namespace pt = boost::property_tree; + pt::ptree tree; + boost::nowide::ifstream ifs(AppConfig::config_path()); + pt::read_ini(ifs, tree); + + // 2) Parse the property_tree, extract the sections and key / value pairs. + for (const auto §ion : tree) { + if (section.second.empty()) { + // This may be a top level (no section) entry, or an empty section. + std::string data = section.second.data(); + if (! data.empty()) + // If there is a non-empty data, then it must be a top-level (without a section) config entry. + m_storage[""][section.first] = data; + } else { + // This must be a section name. Read the entries of a section. + std::map<std::string, std::string> &storage = m_storage[section.first]; + for (auto &kvp : section.second) + storage[kvp.first] = kvp.second.data(); + } + } + + // Override missing or keys with their defaults. + this->set_defaults(); + m_dirty = false; +} + +void AppConfig::save() +{ + boost::nowide::ofstream c; + c.open(AppConfig::config_path(), std::ios::out | std::ios::trunc); + c << "# " << Slic3r::header_slic3r_generated() << std::endl; + // Make sure the "no" category is written first. + for (const std::pair<std::string, std::string> &kvp : m_storage[""]) + c << kvp.first << " = " << kvp.second << std::endl; + // Write the other categories. + for (const auto category : m_storage) { + if (category.first.empty()) + continue; + c << std::endl << "[" << category.first << "]" << std::endl; + for (const std::pair<std::string, std::string> &kvp : category.second) + c << kvp.first << " = " << kvp.second << std::endl; + } + c.close(); + m_dirty = false; +} + +std::string AppConfig::get_last_dir() const +{ + const auto it = m_storage.find("recent"); + if (it != m_storage.end()) { + { + const auto it2 = it->second.find("skein_directory"); + if (it2 != it->second.end() && ! it2->second.empty()) + return it2->second; + } + { + const auto it2 = it->second.find("config_directory"); + if (it2 != it->second.end() && ! it2->second.empty()) + return it2->second; + } + } + return std::string(); +} + +void AppConfig::update_config_dir(const std::string &dir) +{ + this->set("recent", "config_directory", dir); +} + +void AppConfig::update_skein_dir(const std::string &dir) +{ + this->set("recent", "skein_directory", dir); +} + +std::string AppConfig::get_last_output_dir(const std::string &alt) const +{ + const auto it = m_storage.find(""); + if (it != m_storage.end()) { + const auto it2 = it->second.find("last_output_path"); + const auto it3 = it->second.find("remember_output_path"); + if (it2 != it->second.end() && it3 != it->second.end() && ! it2->second.empty() && it3->second == "1") + return it2->second; + } + return alt; +} + +void AppConfig::update_last_output_dir(const std::string &dir) +{ + this->set("", "last_output_path", dir); +} + +std::string AppConfig::config_path() +{ + return (boost::filesystem::path(Slic3r::data_dir()) / "slic3r.ini").make_preferred().string(); +} + +bool AppConfig::exists() +{ + return boost::filesystem::exists(AppConfig::config_path()); +} + +}; // namespace Slic3r diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp new file mode 100644 index 000000000..c6d7766a4 --- /dev/null +++ b/xs/src/slic3r/GUI/AppConfig.hpp @@ -0,0 +1,91 @@ +#ifndef slic3r_AppConfig_hpp_ +#define slic3r_AppConfig_hpp_ + +#include <map> +#include <string> + +namespace Slic3r { + +class AppConfig +{ +public: + AppConfig() : m_dirty(false) { this->reset(); } + + // Clear and reset to defaults. + void reset(); + // Override missing or keys with their defaults. + void set_defaults(); + + // Load the slic3r.ini from a user profile directory (or a datadir, if configured). + void load(); + // Store the slic3r.ini into a user profile directory (or a datadir, if configured). + void save(); + + // Does this config need to be saved? + bool dirty() const { return m_dirty; } + + // Const accessor, it will return false if a section or a key does not exist. + bool get(const std::string §ion, const std::string &key, std::string &value) const + { + value.clear(); + auto it = m_storage.find(section); + if (it == m_storage.end()) + return false; + auto it2 = it->second.find(key); + if (it2 == it->second.end()) + return false; + value = it2->second; + return true; + } + std::string get(const std::string §ion, const std::string &key) const + { std::string value; this->get(section, key, value); return value; } + std::string get(const std::string &key) const + { std::string value; this->get("", key, value); return value; } + void set(const std::string §ion, const std::string &key, const std::string &value) + { + std::string &old = m_storage[section][key]; + if (old != value) { + old = value; + m_dirty = true; + } + } + void set(const std::string &key, const std::string &value) + { this->set("", key, value); } + bool has(const std::string §ion, const std::string &key) const + { + auto it = m_storage.find(section); + if (it == m_storage.end()) + return false; + auto it2 = it->second.find(key); + return it2 != it->second.end() && ! it2->second.empty(); + } + bool has(const std::string &key) const + { return this->has("", key); } + + void clear_section(const std::string §ion) + { m_storage[section].clear(); } + + // return recent/skein_directory or recent/config_directory or empty string. + std::string get_last_dir() const; + void update_config_dir(const std::string &dir); + void update_skein_dir(const std::string &dir); + + std::string get_last_output_dir(const std::string &alt) const; + void update_last_output_dir(const std::string &dir); + + // Get the default config path from Slic3r::data_dir(). + static std::string config_path(); + + // Does the config file exist? + static bool exists(); + +private: + // Map of section, name -> value + std::map<std::string, std::map<std::string, std::string>> m_storage; + // Has any value been modified since the config.ini has been last saved or loaded? + bool m_dirty; +}; + +}; // namespace Slic3r + +#endif /* slic3r_AppConfig_hpp_ */ diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 934d07ebd..8db0508f1 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -1,20 +1,33 @@ #include "GUI.hpp" +#include <assert.h> + +#include <boost/algorithm/string/predicate.hpp> +#include <boost/filesystem.hpp> + #if __APPLE__ #import <IOKit/pwr_mgt/IOPMLib.h> #elif _WIN32 #include <Windows.h> +#include "boost/nowide/convert.hpp" #pragma comment(lib, "user32.lib") #endif +#include <wx/app.h> +#include <wx/button.h> +#include <wx/frame.h> +#include <wx/menu.h> +#include <wx/notebook.h> +#include <wx/panel.h> +#include <wx/sizer.h> + namespace Slic3r { namespace GUI { #if __APPLE__ IOPMAssertionID assertionID; #endif -void -disable_screensaver() +void disable_screensaver() { #if __APPLE__ CFStringRef reasonForActivity = CFSTR("Slic3r"); @@ -26,8 +39,7 @@ disable_screensaver() #endif } -void -enable_screensaver() +void enable_screensaver() { #if __APPLE__ IOReturn success = IOPMAssertionRelease(assertionID); @@ -36,8 +48,84 @@ enable_screensaver() #endif } -bool -debugged() +std::vector<std::string> scan_serial_ports() +{ + std::vector<std::string> out; +#ifdef _WIN32 + // 1) Open the registry key SERIALCOM. + HKEY hKey; + LONG lRes = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_READ, &hKey); + assert(lRes == ERROR_SUCCESS); + if (lRes == ERROR_SUCCESS) { + // 2) Get number of values of SERIALCOM key. + DWORD cValues; // number of values for key + { + TCHAR achKey[255]; // buffer for subkey name + DWORD cbName; // size of name string + TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name + DWORD cchClassName = MAX_PATH; // size of class string + DWORD cSubKeys=0; // number of subkeys + DWORD cbMaxSubKey; // longest subkey size + DWORD cchMaxClass; // longest class string + DWORD cchMaxValue; // longest value name + DWORD cbMaxValueData; // longest value data + DWORD cbSecurityDescriptor; // size of security descriptor + FILETIME ftLastWriteTime; // last write time + // Get the class name and the value count. + lRes = RegQueryInfoKey( + hKey, // key handle + achClass, // buffer for class name + &cchClassName, // size of class string + NULL, // reserved + &cSubKeys, // number of subkeys + &cbMaxSubKey, // longest subkey size + &cchMaxClass, // longest class string + &cValues, // number of values for this key + &cchMaxValue, // longest value name + &cbMaxValueData, // longest value data + &cbSecurityDescriptor, // security descriptor + &ftLastWriteTime); // last write time + assert(lRes == ERROR_SUCCESS); + } + // 3) Read the SERIALCOM values. + { + DWORD dwIndex = 0; + for (int i = 0; i < cValues; ++ i, ++ dwIndex) { + wchar_t valueName[2048]; + DWORD valNameLen = 2048; + DWORD dataType; + wchar_t data[2048]; + DWORD dataSize = 4096; + lRes = ::RegEnumValueW(hKey, dwIndex, valueName, &valNameLen, nullptr, &dataType, (BYTE*)&data, &dataSize); + if (lRes == ERROR_SUCCESS && dataType == REG_SZ && valueName[0] != 0) + out.emplace_back(boost::nowide::narrow(data)); + } + } + ::RegCloseKey(hKey); + } +#else + // UNIX and OS X + std::initializer_list<const char*> prefixes { "ttyUSB" , "ttyACM", "tty.", "cu.", "rfcomm" }; + for (auto &dir_entry : boost::filesystem::directory_iterator(boost::filesystem::path("/dev"))) { + std::string name = dir_entry.path().filename().string(); + for (const char *prefix : prefixes) { + if (boost::starts_with(name, prefix)) { + out.emplace_back(dir_entry.path().string()); + break; + } + } + } +#endif + + out.erase(std::remove_if(out.begin(), out.end(), + [](const std::string &key){ + return boost::starts_with(key, "Bluetooth") || boost::starts_with(key, "FireFly"); + }), + out.end()); + return out; +} + +bool debugged() { #ifdef _WIN32 return IsDebuggerPresent(); @@ -46,8 +134,7 @@ debugged() #endif /* _WIN32 */ } -void -break_to_debugger() +void break_to_debugger() { #ifdef _WIN32 if (IsDebuggerPresent()) @@ -55,4 +142,45 @@ break_to_debugger() #endif /* _WIN32 */ } +// Passing the wxWidgets GUI classes instantiated by the Perl part to C++. +wxApp *g_wxApp = nullptr; +wxFrame *g_wxMainFrame = nullptr; +wxNotebook *g_wxTabPanel = nullptr; + +void set_wxapp(wxApp *app) +{ + g_wxApp = app; +} + +void set_main_frame(wxFrame *main_frame) +{ + g_wxMainFrame = main_frame; +} + +void set_tab_panel(wxNotebook *tab_panel) +{ + g_wxTabPanel = tab_panel; +} + +void add_debug_menu(wxMenuBar *menu) +{ +#if 0 + auto debug_menu = new wxMenu(); + debug_menu->Append(wxWindow::NewControlId(1), "Some debug"); + menu->Append(debug_menu, _T("&Debug")); +#endif +} + +void create_preset_tab(const char *name) +{ + auto *panel = new wxPanel(g_wxTabPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); + // Vertical sizer to hold the choice menu and the rest of the page. + auto *sizer = new wxBoxSizer(wxVERTICAL); + sizer->SetSizeHints(panel); + panel->SetSizer(sizer); + auto *button = new wxButton(panel, wxID_ANY, "Hello World", wxDefaultPosition, wxDefaultSize, 0); + sizer->Add(button, 0, 0, 0); + g_wxTabPanel->AddPage(panel, name); +} + } } diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 85656c0a3..3634e0bc8 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -1,13 +1,32 @@ #ifndef slic3r_GUI_hpp_ #define slic3r_GUI_hpp_ +#include <string> +#include <vector> + +class wxApp; +class wxFrame; +class wxMenuBar; +class wxNotebook; + namespace Slic3r { namespace GUI { void disable_screensaver(); void enable_screensaver(); +std::vector<std::string> scan_serial_ports(); bool debugged(); void break_to_debugger(); +// Passing the wxWidgets GUI classes instantiated by the Perl part to C++. +void set_wxapp(wxApp *app); +void set_main_frame(wxFrame *main_frame); +void set_tab_panel(wxNotebook *tab_panel); + +void add_debug_menu(wxMenuBar *menu); +// Create a new preset tab (print, filament or printer), +// add it at the end of the tab panel. +void create_preset_tab(const char *name); + } } #endif diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 0accb51a1..8ba3029ba 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -1,22 +1,113 @@ +//#undef NDEBUG +#include <cassert> + #include "Preset.hpp" #include <fstream> #include <boost/filesystem.hpp> #include <boost/algorithm/string/predicate.hpp> +#include <boost/nowide/cenv.hpp> +#include <boost/nowide/cstdio.hpp> +#include <boost/nowide/fstream.hpp> +#include <boost/property_tree/ini_parser.hpp> +#include <boost/property_tree/ptree.hpp> +#include <boost/locale.hpp> + #include <wx/image.h> +#include <wx/choice.h> #include <wx/bmpcbox.h> +#include <wx/wupdlock.h> -#if 0 -#define DEBUG -#define _DEBUG -#undef NDEBUG -#endif - -#include <assert.h> +#include "../../libslic3r/libslic3r.h" +#include "../../libslic3r/Utils.hpp" namespace Slic3r { +ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree) +{ + size_t app_config = 0; + size_t bundle = 0; + size_t config = 0; + for (const boost::property_tree::ptree::value_type &v : tree) { + if (v.second.empty()) { + if (v.first == "background_processing" || + v.first == "last_output_path" || + v.first == "no_controller" || + v.first == "no_defaults") + ++ app_config; + else if (v.first == "nozzle_diameter" || + v.first == "filament_diameter") + ++ config; + } else if (boost::algorithm::starts_with(v.first, "print:") || + boost::algorithm::starts_with(v.first, "filament:") || + boost::algorithm::starts_with(v.first, "printer:") || + v.first == "settings") + ++ bundle; + else if (v.first == "presets") { + ++ app_config; + ++ bundle; + } else if (v.first == "recent") { + for (auto &kvp : v.second) + if (kvp.first == "config_directory" || kvp.first == "skein_directory") + ++ app_config; + } + } + return (app_config > bundle && app_config > config) ? CONFIG_FILE_TYPE_APP_CONFIG : + (bundle > config) ? CONFIG_FILE_TYPE_CONFIG_BUNDLE : CONFIG_FILE_TYPE_CONFIG; +} + +// Suffix to be added to a modified preset name in the combo box. +static std::string g_suffix_modified = " (modified)"; +const std::string& Preset::suffix_modified() +{ + return g_suffix_modified; +} +// Remove an optional "(modified)" suffix from a name. +// This converts a UI name to a unique preset identifier. +std::string Preset::remove_suffix_modified(const std::string &name) +{ + return boost::algorithm::ends_with(name, g_suffix_modified) ? + name.substr(0, name.size() - g_suffix_modified.size()) : + name; +} + +void Preset::set_num_extruders(DynamicPrintConfig &config, unsigned int num_extruders) +{ + const auto &defaults = FullPrintConfig::defaults(); + for (const std::string &key : Preset::nozzle_options()) { + auto *opt = config.option(key, false); + assert(opt != nullptr); + assert(opt->is_vector()); + if (opt != nullptr && opt->is_vector()) + static_cast<ConfigOptionVectorBase*>(opt)->resize(num_extruders, defaults.option(key)); + } +} + +// Update new extruder fields at the printer profile. +void Preset::normalize(DynamicPrintConfig &config) +{ + auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter")); + if (nozzle_diameter != nullptr) + // Loaded the Printer settings. Verify, that all extruder dependent values have enough values. + set_num_extruders(config, (unsigned int)nozzle_diameter->values.size()); + if (config.option("filament_diameter") != nullptr) { + // This config contains single or multiple filament presets. + // Ensure that the filament preset vector options contain the correct number of values. + size_t n = (nozzle_diameter == nullptr) ? 1 : nozzle_diameter->values.size(); + const auto &defaults = FullPrintConfig::defaults(); + for (const std::string &key : Preset::filament_options()) { + if (key == "compatible_printers") + continue; + auto *opt = config.option(key, false); + assert(opt != nullptr); + assert(opt->is_vector()); + if (opt != nullptr && opt->is_vector()) + static_cast<ConfigOptionVectorBase*>(opt)->resize(n, defaults.option(key)); + } + } +} + // Load a config file, return a C++ class Slic3r::DynamicPrintConfig with $keys initialized from the config file. // In case of a "default" config item, return the default values. DynamicPrintConfig& Preset::load(const std::vector<std::string> &keys) @@ -24,342 +115,433 @@ DynamicPrintConfig& Preset::load(const std::vector<std::string> &keys) // Set the configuration from the defaults. Slic3r::FullPrintConfig defaults; this->config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); - if (! this->is_default) { // Load the preset file, apply preset values on top of defaults. try { - if (boost::algorithm::iends_with(this->file, ".gcode") || boost::algorithm::iends_with(this->file, ".g")) - this->config.load_from_gcode(this->file); - else - this->config.load(this->file); - } catch (const std::ifstream::failure&) { - throw std::runtime_error(std::string("The selected preset does not exist anymore: ") + this->file); - } catch (const std::runtime_error&) { - throw std::runtime_error(std::string("Failed loading the preset file: ") + this->file); - } - - if (this->type == TYPE_PRINTER && std::find(keys.begin(), keys.end(), "nozzle_diameter") != keys.end()) { - // Loaded the Printer settings. Verify, that all extruder dependent values have enough values. - auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(this->config.option("nozzle_diameter")); - size_t num_extruders = nozzle_diameter->values.size(); - auto *deretract_speed = dynamic_cast<ConfigOptionFloats*>(this->config.option("deretract_speed")); - deretract_speed->values.resize(num_extruders, deretract_speed->values.empty() ? - defaults.deretract_speed.values.front() : deretract_speed->values.front()); - auto *extruder_colour = dynamic_cast<ConfigOptionStrings*>(this->config.option("extruder_colour")); - extruder_colour->values.resize(num_extruders, extruder_colour->values.empty() ? - defaults.extruder_colour.values.front() : extruder_colour->values.front()); - auto *retract_before_wipe = dynamic_cast<ConfigOptionPercents*>(this->config.option("retract_before_wipe")); - retract_before_wipe->values.resize(num_extruders, retract_before_wipe->values.empty() ? - defaults.retract_before_wipe.values.front() : retract_before_wipe->values.front()); + this->config.load_from_ini(this->file); + Preset::normalize(this->config); + } catch (const std::ifstream::failure &err) { + throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + this->file + "\n\tReason: " + err.what()); + } catch (const std::runtime_error &err) { + throw std::runtime_error(std::string("Failed loading the preset file: ") + this->file + "\n\tReason: " + err.what()); } } - this->loaded = true; return this->config; } -bool Preset::enable_compatible(const std::string &active_printer) +void Preset::save() +{ + this->config.save(this->file); +} + +// Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty. +std::string Preset::label() const { - auto *compatible_printers = dynamic_cast<const ConfigOptionStrings*>(this->config.optptr("compatible_printers")); - this->is_visible = compatible_printers && ! compatible_printers->values.empty() && + return this->name + (this->is_dirty ? g_suffix_modified : ""); +} + +bool Preset::is_compatible_with_printer(const std::string &active_printer) const +{ + auto *compatible_printers = dynamic_cast<const ConfigOptionStrings*>(this->config.option("compatible_printers")); + return this->is_default || active_printer.empty() || + compatible_printers == nullptr || compatible_printers->values.empty() || std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer) != compatible_printers->values.end(); - return this->is_visible; +} + +bool Preset::update_compatible_with_printer(const std::string &active_printer) +{ + return this->is_compatible = is_compatible_with_printer(active_printer); +} + +const std::vector<std::string>& Preset::print_options() +{ + static std::vector<std::string> s_opts { + "layer_height", "first_layer_height", "perimeters", "spiral_vase", "top_solid_layers", "bottom_solid_layers", + "extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs", + "seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "external_fill_pattern", + "infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle", + "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", "max_print_speed", + "max_volumetric_speed", "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", + "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed", + "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", + "bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration", + "bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", + "min_skirt_length", "brim_width", "support_material", "support_material_threshold", "support_material_enforce_layers", + "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", + "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", + "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", + "support_material_buildplate_only", "dont_support_bridges", "notes", "complete_objects", "extruder_clearance_radius", + "extruder_clearance_height", "gcode_comments", "output_filename_format", "post_process", "perimeter_extruder", + "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", + "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", + "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", + "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects", + "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", + "wipe_tower_width", "wipe_tower_per_color_wipe", + "compatible_printers" + }; + return s_opts; +} + +const std::vector<std::string>& Preset::filament_options() +{ + static std::vector<std::string> s_opts { + "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed", + "extrusion_multiplier", "filament_density", "filament_cost", "temperature", "first_layer_temperature", "bed_temperature", + "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", + "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", + "end_filament_gcode", + "compatible_printers" + }; + return s_opts; +} + +const std::vector<std::string>& Preset::printer_options() +{ + static std::vector<std::string> s_opts; + if (s_opts.empty()) { + s_opts = { + "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", + "octoprint_host", "octoprint_apikey", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", + "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", + "between_objects_gcode", "printer_notes" + }; + s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end()); + } + return s_opts; +} + +const std::vector<std::string>& Preset::nozzle_options() +{ + // ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings + static std::vector<std::string> s_opts { + "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", + "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", + "retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe", + "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour" + }; + return s_opts; } PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::string> &keys) : m_type(type), - m_edited_preset(type, "", false) + m_edited_preset(type, "", false), + m_idx_selected(0), + m_bitmap_main_frame(new wxBitmap) { // Insert just the default preset. m_presets.emplace_back(Preset(type, "- default -", true)); m_presets.front().load(keys); + m_edited_preset.config.apply(m_presets.front().config); +} + +PresetCollection::~PresetCollection() +{ + delete m_bitmap_main_frame; + m_bitmap_main_frame = nullptr; } // Load all presets found in dir_path. // Throws an exception on error. void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir) { + boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred(); + m_dir_path = dir.string(); m_presets.erase(m_presets.begin()+1, m_presets.end()); t_config_option_keys keys = this->default_preset().config.keys(); - for (auto &file : boost::filesystem::directory_iterator(boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred())) - if (boost::filesystem::is_regular_file(file.status()) && boost::algorithm::iends_with(file.path().filename().string(), ".ini")) { - std::string name = file.path().filename().string(); + std::string errors_cummulative; + for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) + if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) { + std::string name = dir_entry.path().filename().string(); // Remove the .ini suffix. name.erase(name.size() - 4); try { Preset preset(m_type, name, false); - preset.file = file.path().string(); + preset.file = dir_entry.path().string(); preset.load(keys); m_presets.emplace_back(preset); - } catch (const boost::filesystem::filesystem_error &err) { - - } + } catch (const std::runtime_error &err) { + errors_cummulative += err.what(); + errors_cummulative += "\n"; + } } + std::sort(m_presets.begin() + 1, m_presets.end()); + this->select_preset(first_visible_idx()); + if (! errors_cummulative.empty()) + throw std::runtime_error(errors_cummulative); } -void PresetCollection::set_default_suppressed(bool default_suppressed) +// Load a preset from an already parsed config file, insert it into the sorted sequence of presets +// and select it, losing previous modifications. +Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select) { - if (m_default_suppressed != default_suppressed) { - m_default_suppressed = default_suppressed; - m_presets.front().is_visible = ! default_suppressed || m_presets.size() > 1; - } + DynamicPrintConfig cfg(this->default_preset().config); + cfg.apply_only(config, cfg.keys(), true); + return this->load_preset(path, name, std::move(cfg)); } -void PresetCollection::enable_disable_compatible_to_printer(const std::string &active_printer) +Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select) { - size_t num_visible = 0; - for (size_t idx_preset = 1; idx_preset < m_presets.size(); ++ idx_preset) - if (m_presets[idx_preset].enable_compatible(active_printer)) - ++ num_visible; - if (num_visible == 0) - // Show the "-- default --" preset. - m_presets.front().is_visible = true; + auto it = this->find_preset_internal(name); + if (it == m_presets.end() || it->name != name) { + // The preset was not found. Create a new preset. + it = m_presets.emplace(it, Preset(m_type, name, false)); + } + Preset &preset = *it; + preset.file = path; + preset.config = std::move(config); + preset.loaded = true; + preset.is_dirty = false; + if (select) + this->select_preset_by_name(name, true); + return preset; } -static std::string g_suffix_modified = " (modified)"; - -// Update the wxChoice UI component from this list of presets. -// Hide the -void PresetCollection::update_editor_ui(wxBitmapComboBox *ui) +void PresetCollection::save_current_preset(const std::string &new_name) { - if (ui == nullptr) - return; - - size_t n_visible = this->num_visible(); - size_t n_choice = size_t(ui->GetCount()); - std::string name_selected = dynamic_cast<wxItemContainerImmutable*>(ui)->GetStringSelection().ToUTF8().data(); - if (boost::algorithm::iends_with(name_selected, g_suffix_modified)) - // Remove the g_suffix_modified. - name_selected.erase(name_selected.end() - g_suffix_modified.size(), name_selected.end()); -#if 0 - if (std::abs(int(n_visible) - int(n_choice)) <= 1) { - // The number of items differs by at most one, update the choice. - size_t i_preset = 0; - size_t i_ui = 0; - while (i_preset < presets.size()) { - std::string name_ui = ui->GetString(i_ui).ToUTF8(); - if (boost::algorithm::iends_with(name_ui, g_suffix_modified)) - // Remove the g_suffix_modified. - name_ui.erase(name_ui.end() - g_suffix_modified.size(), name_ui.end()); - while (this->presets[i_preset].name ) - const Preset &preset = this->presets[i_preset]; - if (preset) - } - } else -#endif - { - // Otherwise fill in the list from scratch. - ui->Clear(); - for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) { - const Preset &preset = this->m_presets[i]; - const wxBitmap *bmp = (i == 0 || preset.is_visible) ? m_bitmap_compatible : m_bitmap_incompatible; - ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), (bmp == 0) ? wxNullBitmap : *bmp, (void*)i); - if (name_selected == preset.name) - ui->SetSelection(ui->GetCount() - 1); - } + // 1) Find the preset with a new_name or create a new one, + // initialize it with the edited config. + auto it = this->find_preset_internal(new_name); + if (it != m_presets.end() && it->name == new_name) { + // Preset with the same name found. + Preset &preset = *it; + if (preset.is_default) + // Cannot overwrite the default preset. + return; + // Overwriting an existing preset. + preset.config = std::move(m_edited_preset.config); + } else { + // Creating a new preset. + Preset &preset = *m_presets.insert(it, m_edited_preset); + std::string file_name = new_name; + if (! boost::iends_with(file_name, ".ini")) + file_name += ".ini"; + preset.name = new_name; + preset.file = (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); } + // 2) Activate the saved preset. + this->select_preset_by_name(new_name, true); + // 2) Store the active preset to disk. + this->get_selected_preset().save(); } -void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) +void PresetCollection::delete_current_preset() { - if (ui == nullptr) + const Preset &selected = this->get_selected_preset(); + if (selected.is_default) return; + if (! selected.is_external) { + // Erase the preset file. + boost::nowide::remove(selected.file.c_str()); + } + // Remove the preset from the list. + m_presets.erase(m_presets.begin() + m_idx_selected); + // Find the next visible preset. + size_t new_selected_idx = m_idx_selected; + if (new_selected_idx < m_presets.size()) + for (; new_selected_idx < m_presets.size() && ! m_presets[new_selected_idx].is_visible; ++ new_selected_idx) ; + if (new_selected_idx == m_presets.size()) + for (--new_selected_idx; new_selected_idx > 0 && !m_presets[new_selected_idx].is_visible; --new_selected_idx); + this->select_preset(new_selected_idx); +} - size_t n_visible = this->num_visible(); - size_t n_choice = size_t(ui->GetCount()); - if (std::abs(int(n_visible) - int(n_choice)) <= 1) { - // The number of items differs by at most one, update the choice. - } else { - // Otherwise fill in the list from scratch. - } +bool PresetCollection::load_bitmap_default(const std::string &file_name) +{ + return m_bitmap_main_frame->LoadFile(wxString::FromUTF8(Slic3r::var(file_name).c_str()), wxBITMAP_TYPE_PNG); } -PresetBundle::PresetBundle() : - prints(Preset::TYPE_PRINT, print_options()), - filaments(Preset::TYPE_FILAMENT, filament_options()), - printers(Preset::TYPE_PRINTER, printer_options()), - m_bitmapCompatible(new wxBitmap), - m_bitmapIncompatible(new wxBitmap) +// Return a preset by its name. If the preset is active, a temporary copy is returned. +// If a preset is not found by its name, null is returned. +Preset* PresetCollection::find_preset(const std::string &name, bool first_visible_if_not_found) { - // Create the ID config keys, as they are not part of the Static print config classes. - this->prints.preset(0).config.opt_string("print_settings_id", true); - this->filaments.preset(0).config.opt_string("filament_settings_id", true); - this->printers.preset(0).config.opt_string("print_settings_id", true); - // Create the "compatible printers" keys, as they are not part of the Static print config classes. - this->filaments.preset(0).config.optptr("compatible_printers", true); - this->prints.preset(0).config.optptr("compatible_printers", true); + Preset key(m_type, name, false); + auto it = this->find_preset_internal(name); + // Ensure that a temporary copy is returned if the preset found is currently selected. + return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin()) : + first_visible_if_not_found ? &this->first_visible() : nullptr; } -PresetBundle::~PresetBundle() +// Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible. +size_t PresetCollection::first_visible_idx() const { - assert(m_bitmapCompatible != nullptr); - assert(m_bitmapIncompatible != nullptr); - delete m_bitmapCompatible; - m_bitmapCompatible = nullptr; - delete m_bitmapIncompatible; - m_bitmapIncompatible = nullptr; - for (std::pair<const std::string, wxBitmap*> &bitmap : m_mapColorToBitmap) - delete bitmap.second; + size_t idx = m_default_suppressed ? 1 : 0; + for (; idx < this->m_presets.size(); ++ idx) + if (m_presets[idx].is_visible) + break; + if (idx == this->m_presets.size()) + idx = 0; + return idx; } -void PresetBundle::load_presets(const std::string &dir_path) +// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible. +size_t PresetCollection::first_compatible_idx() const { - this->prints.load_presets(dir_path, "print"); - this->prints.load_presets(dir_path, "filament"); - this->prints.load_presets(dir_path, "printer"); + size_t idx = m_default_suppressed ? 1 : 0; + for (; idx < this->m_presets.size(); ++ idx) + if (m_presets[idx].is_compatible) + break; + if (idx == this->m_presets.size()) + idx = 0; + return idx; } -bool PresetBundle::load_bitmaps(const std::string &path_bitmap_compatible, const std::string &path_bitmap_incompatible) +void PresetCollection::set_default_suppressed(bool default_suppressed) { - bool loaded_compatible = m_bitmapCompatible ->LoadFile(wxString::FromUTF8(path_bitmap_compatible.c_str())); - bool loaded_incompatible = m_bitmapIncompatible->LoadFile(wxString::FromUTF8(path_bitmap_incompatible.c_str())); - if (loaded_compatible) { - prints .set_bitmap_compatible(m_bitmapCompatible); - filaments.set_bitmap_compatible(m_bitmapCompatible); - printers .set_bitmap_compatible(m_bitmapCompatible); - } - if (loaded_incompatible) { - prints .set_bitmap_compatible(m_bitmapIncompatible); - filaments.set_bitmap_compatible(m_bitmapIncompatible); - printers .set_bitmap_compatible(m_bitmapIncompatible); + if (m_default_suppressed != default_suppressed) { + m_default_suppressed = default_suppressed; + m_presets.front().is_visible = ! default_suppressed || (m_presets.size() > 1 && m_idx_selected > 0); } - return loaded_compatible && loaded_incompatible; } -static inline int hex_digit_to_int(const char c) +void PresetCollection::update_compatible_with_printer(const std::string &active_printer, bool select_other_if_incompatible) { - return - (c >= '0' && c <= '9') ? int(c - '0') : - (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : - (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; + size_t num_visible = 0; + for (size_t idx_preset = 1; idx_preset < m_presets.size(); ++ idx_preset) { + bool selected = idx_preset == m_idx_selected; + Preset &preset_selected = m_presets[idx_preset]; + Preset &preset_edited = selected ? m_edited_preset : preset_selected; + if (preset_edited.update_compatible_with_printer(active_printer)) + // Mark compatible presets as visible. + preset_selected.is_visible = true; + else if (selected && select_other_if_incompatible) { + preset_selected.is_visible = false; + m_idx_selected = (size_t)-1; + } + if (selected) + preset_selected.is_compatible = preset_edited.is_compatible; + if (preset_selected.is_visible) + ++ num_visible; + } + if (m_idx_selected == (size_t)-1) + // Find some other visible preset. + this->select_preset(first_visible_idx()); + else if (num_visible == 0) + // Show the "-- default --" preset. + m_presets.front().is_visible = true; } -static inline bool parse_color(const std::string &scolor, unsigned char *rgb_out) +// Save the preset under a new name. If the name is different from the old one, +// a new preset is stored into the list of presets. +// All presets are marked as not modified and the new preset is activated. +//void PresetCollection::save_current_preset(const std::string &new_name); + +// Delete the current preset, activate the first visible preset. +//void PresetCollection::delete_current_preset(); + +// Update the wxChoice UI component from this list of presets. +// Hide the +void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) { - rgb_out[0] = rgb_out[1] = rgb_out[2] = 0; - const char *c = scolor.data() + 1; - if (scolor.size() != 7 || scolor.front() != '#') - return false; - for (size_t i = 0; i < 3; ++ i) { - int digit1 = hex_digit_to_int(*c ++); - int digit2 = hex_digit_to_int(*c ++); - if (digit1 == -1 || digit2 == -1) - return false; - rgb_out[i] = (unsigned char)(digit1 * 16 + digit2); + if (ui == nullptr) + return; + // Otherwise fill in the list from scratch. + ui->Freeze(); + ui->Clear(); + for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) { + const Preset &preset = this->m_presets[i]; + if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected)) + continue; + const wxBitmap *bmp = (i == 0 || preset.is_compatible) ? m_bitmap_main_frame : m_bitmap_incompatible; + ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), + (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); + if (i == m_idx_selected) + ui->SetSelection(ui->GetCount() - 1); } - return true; + ui->Thaw(); } -// Update the colors preview at the platter extruder combo box. -void PresetBundle::update_platter_filament_ui_colors(wxBitmapComboBox *ui, unsigned int idx_extruder, unsigned int idx_filament) +void PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible) { - unsigned char rgb[3]; - std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder); - if (! parse_color(extruder_color, rgb)) - // Extruder color is not defined. - extruder_color.clear(); + if (ui == nullptr) + return; + ui->Freeze(); + ui->Clear(); + for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) { + const Preset &preset = this->m_presets[i]; + if (! show_incompatible && ! preset.is_compatible && i != m_idx_selected) + continue; + const wxBitmap *bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible; + ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), + (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); + if (i == m_idx_selected) + ui->SetSelection(ui->GetCount() - 1); + } + ui->Thaw(); +} +// Update a dirty floag of the current preset, update the labels of the UI component accordingly. +// Return true if the dirty flag changed. +bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) +{ + wxWindowUpdateLocker noUpdates(ui); + // 1) Update the dirty flag of the current preset. + bool was_dirty = this->get_selected_preset().is_dirty; + bool is_dirty = current_is_dirty(); + this->get_selected_preset().is_dirty = is_dirty; + this->get_edited_preset().is_dirty = is_dirty; + // 2) Update the labels. for (unsigned int ui_id = 0; ui_id < ui->GetCount(); ++ ui_id) { - if (! ui->HasClientUntypedData()) - continue; - size_t filament_preset_id = size_t(ui->GetClientData(ui_id)); - const Preset &filament_preset = filaments.preset(filament_preset_id); - // Assign an extruder color to the selected item if the extruder color is defined. - std::string filament_rgb = filament_preset.config.opt_string("filament_colour", 0); - std::string extruder_rgb = (int(ui_id) == ui->GetSelection() && ! extruder_color.empty()) ? extruder_color : filament_rgb; - wxBitmap *bitmap = nullptr; - if (filament_rgb == extruder_rgb) { - auto it = m_mapColorToBitmap.find(filament_rgb); - if (it == m_mapColorToBitmap.end()) { - // Create the bitmap. - parse_color(filament_rgb, rgb); - wxImage image(24, 16); - image.SetRGB(wxRect(0, 0, 24, 16), rgb[0], rgb[1], rgb[2]); - m_mapColorToBitmap[filament_rgb] = new wxBitmap(image); - } else { - bitmap = it->second; - } - } else { - std::string bitmap_key = filament_rgb + extruder_rgb; - auto it = m_mapColorToBitmap.find(bitmap_key); - if (it == m_mapColorToBitmap.end()) { - // Create the bitmap. - wxImage image(24, 16); - parse_color(extruder_rgb, rgb); - image.SetRGB(wxRect(0, 0, 16, 16), rgb[0], rgb[1], rgb[2]); - parse_color(filament_rgb, rgb); - image.SetRGB(wxRect(16, 0, 8, 16), rgb[0], rgb[1], rgb[2]); - m_mapColorToBitmap[filament_rgb] = new wxBitmap(image); - } else { - bitmap = it->second; - } - } - ui->SetItemBitmap(ui_id, *bitmap); + std::string old_label = ui->GetString(ui_id).utf8_str().data(); + std::string preset_name = Preset::remove_suffix_modified(old_label); + const Preset *preset = this->find_preset(preset_name, false); + assert(preset != nullptr); + std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name; + if (old_label != new_label) + ui->SetString(ui_id, wxString::FromUTF8(new_label.c_str())); } + return was_dirty != is_dirty; } -const std::vector<std::string>& PresetBundle::print_options() -{ - const char *opts[] = { - "layer_height", "first_layer_height", "perimeters", "spiral_vase", "top_solid_layers", "bottom_solid_layers", - "extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs", - "seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "external_fill_pattern", - "infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle", - "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", "max_print_speed", - "max_volumetric_speed", "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", - "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed", - "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", - "bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration", - "bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", - "min_skirt_length", "brim_width", "support_material", "support_material_threshold", "support_material_enforce_layers", - "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", - "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", - "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", - "support_material_buildplate_only", "dont_support_bridges", "notes", "complete_objects", "extruder_clearance_radius", - "extruder_clearance_height", "gcode_comments", "output_filename_format", "post_process", "perimeter_extruder", - "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", - "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", - "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", - "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects", - "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", - "wipe_tower_width", "wipe_tower_per_color_wipe" - }; - static std::vector<std::string> s_opts; - if (s_opts.empty()) - s_opts.assign(opts, opts + (sizeof(opts) / sizeof(opts[0]))); - return s_opts; +Preset& PresetCollection::select_preset(size_t idx) +{ + for (Preset &preset : m_presets) + preset.is_dirty = false; + if (idx >= m_presets.size()) + idx = first_visible_idx(); + m_idx_selected = idx; + m_edited_preset = m_presets[idx]; + m_presets.front().is_visible = ! m_default_suppressed || m_idx_selected == 0; + return m_presets[idx]; } -const std::vector<std::string>& PresetBundle::filament_options() -{ - const char *opts[] = { - "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed", - "extrusion_multiplier", "filament_density", "filament_cost", "temperature", "first_layer_temperature", "bed_temperature", - "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", - "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", - "end_filament_gcode" - }; - static std::vector<std::string> s_opts; - if (s_opts.empty()) - s_opts.assign(opts, opts + (sizeof(opts) / sizeof(opts[0]))); - return s_opts; +bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force) +{ + std::string name = Preset::remove_suffix_modified(name_w_suffix); + // 1) Try to find the preset by its name. + auto it = this->find_preset_internal(name); + size_t idx = 0; + if (it != m_presets.end() && it->name == name) + // Preset found by its name. + idx = it - m_presets.begin(); + else { + // Find the first visible preset. + for (size_t i = m_default_suppressed ? 1 : 0; i < m_presets.size(); ++ i) + if (m_presets[i].is_visible) { + idx = i; + break; + } + // If the first visible preset was not found, return the 0th element, which is the default preset. + } + + // 2) Select the new preset. + if (m_idx_selected != idx || force) { + this->select_preset(idx); + return true; + } + + return false; } -const std::vector<std::string>& PresetBundle::printer_options() -{ - const char *opts[] = { - "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", - "octoprint_host", "octoprint_apikey", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", - "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", - "nozzle_diameter", "extruder_offset", "retract_length", "retract_lift", "retract_speed", "deretract_speed", - "retract_before_wipe", "retract_restart_extra", "retract_before_travel", "retract_layer_change", "wipe", - "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", "printer_notes" - }; - static std::vector<std::string> s_opts; - if (s_opts.empty()) - s_opts.assign(opts, opts + (sizeof(opts) / sizeof(opts[0]))); - return s_opts; +std::string PresetCollection::name() const +{ + switch (this->type()) { + case Preset::TYPE_PRINT: return "print"; + case Preset::TYPE_FILAMENT: return "filament"; + case Preset::TYPE_PRINTER: return "printer"; + default: return "invalid"; + } } } // namespace Slic3r diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index 5c8ec8106..74ae7411c 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -1,14 +1,28 @@ #ifndef slic3r_Preset_hpp_ #define slic3r_Preset_hpp_ +#include <deque> + #include "../../libslic3r/libslic3r.h" #include "../../libslic3r/PrintConfig.hpp" class wxBitmap; +class wxChoice; class wxBitmapComboBox; +class wxItemContainer; namespace Slic3r { +enum ConfigFileType +{ + CONFIG_FILE_TYPE_UNKNOWN, + CONFIG_FILE_TYPE_APP_CONFIG, + CONFIG_FILE_TYPE_CONFIG, + CONFIG_FILE_TYPE_CONFIG_BUNDLE, +}; + +extern ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree); + class Preset { public: @@ -35,6 +49,8 @@ public: bool is_visible = true; // Has this preset been modified? bool is_dirty = false; + // Is this preset compatible with the currently active printer? + bool is_compatible = true; // Name of the preset, usually derived form the file name. std::string name; @@ -53,10 +69,41 @@ public: // Throws std::runtime_error in case the file cannot be read. DynamicPrintConfig& load(const std::vector<std::string> &keys); + void save(); + + // Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty. + std::string label() const; + // Set the is_dirty flag if the provided config is different from the active one. void set_dirty(const DynamicPrintConfig &config) { this->is_dirty = ! this->config.diff(config).empty(); } + void set_dirty(bool dirty = true) { this->is_dirty = dirty; } void reset_dirty() { this->is_dirty = false; } - bool enable_compatible(const std::string &active_printer); + + bool is_compatible_with_printer(const std::string &active_printer) const; + // Mark this preset as compatible if it is compatible with active_printer. + bool update_compatible_with_printer(const std::string &active_printer); + + // Resize the extruder specific fields, initialize them with the content of the 1st extruder. + void set_num_extruders(unsigned int n) { set_num_extruders(this->config, n); } + + // Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection. + bool operator<(const Preset &other) const { return this->name < other.name; } + + static const std::vector<std::string>& print_options(); + static const std::vector<std::string>& filament_options(); + // Printer options contain the nozzle options. + static const std::vector<std::string>& printer_options(); + // Nozzle options of the printer options. + static const std::vector<std::string>& nozzle_options(); + +protected: + friend class PresetCollection; + friend class PresetBundle; + static void normalize(DynamicPrintConfig &config); + // Resize the extruder specific vectors () + static void set_num_extruders(DynamicPrintConfig &config, unsigned int n); + static const std::string& suffix_modified(); + static std::string remove_suffix_modified(const std::string &name); }; // Collections of presets of the same type (one of the Print, Filament or Printer type). @@ -65,10 +112,31 @@ class PresetCollection public: // Initialize the PresetCollection with the "- default -" preset. PresetCollection(Preset::Type type, const std::vector<std::string> &keys); + ~PresetCollection(); + + Preset::Type type() const { return m_type; } + std::string name() const; + const std::deque<Preset>& operator()() const { return m_presets; } // Load ini files of the particular type from the provided directory path. void load_presets(const std::string &dir_path, const std::string &subdir); + // Load a preset from an already parsed config file, insert it into the sorted sequence of presets + // and select it, losing previous modifications. + Preset& load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select = true); + Preset& load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select = true); + + // Save the preset under a new name. If the name is different from the old one, + // a new preset is stored into the list of presets. + // All presets are marked as not modified and the new preset is activated. + void save_current_preset(const std::string &new_name); + + // Delete the current preset, activate the first visible preset. + void delete_current_preset(); + + // Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame. + bool load_bitmap_default(const std::string &file_name); + // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items. void set_bitmap_compatible (const wxBitmap *bmp) { m_bitmap_compatible = bmp; } void set_bitmap_incompatible(const wxBitmap *bmp) { m_bitmap_incompatible = bmp; } @@ -82,70 +150,101 @@ public: // Return the selected preset, without the user modifications applied. Preset& get_selected_preset() { return m_presets[m_idx_selected]; } const Preset& get_selected_preset() const { return m_presets[m_idx_selected]; } + int get_selected_idx() const { return m_idx_selected; } // Return the selected preset including the user modifications. Preset& get_edited_preset() { return m_edited_preset; } const Preset& get_edited_preset() const { return m_edited_preset; } // Return a preset possibly with modifications. const Preset& default_preset() const { return m_presets.front(); } + // Return a preset by an index. If the preset is active, a temporary copy is returned. Preset& preset(size_t idx) { return (int(idx) == m_idx_selected) ? m_edited_preset : m_presets[idx]; } const Preset& preset(size_t idx) const { return const_cast<PresetCollection*>(this)->preset(idx); } + void discard_current_changes() { m_edited_preset = m_presets[m_idx_selected]; } + + // Return a preset by its name. If the preset is active, a temporary copy is returned. + // If a preset is not found by its name, null is returned. + Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false); + const Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false) const + { return const_cast<PresetCollection*>(this)->find_preset(name, first_visible_if_not_found); } + + size_t first_visible_idx() const; + size_t first_compatible_idx() const; + // Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible. + // Return the first visible preset. Certainly at least the '- default -' preset shall be visible. + Preset& first_visible() { return this->preset(this->first_visible_idx()); } + const Preset& first_visible() const { return this->preset(this->first_visible_idx()); } + Preset& first_compatible() { return this->preset(this->first_compatible_idx()); } + const Preset& first_compatible() const { return this->preset(this->first_compatible_idx()); } + + // Return number of presets including the "- default -" preset. size_t size() const { return this->m_presets.size(); } // For Print / Filament presets, disable those, which are not compatible with the printer. - void enable_disable_compatible_to_printer(const std::string &active_printer); + void update_compatible_with_printer(const std::string &active_printer, bool select_other_if_incompatible); size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); } - void delete_preset(const size_t idx); + + // Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ. + bool current_is_dirty() { return ! this->current_dirty_options().empty(); } + // Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ. + std::vector<std::string> current_dirty_options() { return this->get_selected_preset().config.diff(this->get_edited_preset().config); } // Update the choice UI from the list of presets. - void update_editor_ui(wxBitmapComboBox *ui); + // If show_incompatible, all presets are shown, otherwise only the compatible presets are shown. + // If an incompatible preset is selected, it is shown as well. + void update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible); + // Update the choice UI from the list of presets. + // Only the compatible presets are shown. + // If an incompatible preset is selected, it is shown as well. void update_platter_ui(wxBitmapComboBox *ui); + // Update a dirty floag of the current preset, update the labels of the UI component accordingly. + // Return true if the dirty flag changed. + bool update_dirty_ui(wxBitmapComboBox *ui); + + // Select a profile by its name. Return true if the selection changed. + // Without force, the selection is only updated if the index changes. + // With force, the changes are reverted if the new index is the same as the old index. + bool select_preset_by_name(const std::string &name, bool force); + private: + PresetCollection(); + PresetCollection(const PresetCollection &other); + PresetCollection& operator=(const PresetCollection &other); + + // Find a preset in the sorted list of presets. + // The "-- default -- " preset is always the first, so it needs + // to be handled differently. + std::deque<Preset>::iterator find_preset_internal(const std::string &name) + { + Preset key(m_type, name); + auto it = std::lower_bound(m_presets.begin() + 1, m_presets.end(), key); + return (it == m_presets.end() && m_presets.front().name == name) ? m_presets.begin() : it; + } + std::deque<Preset>::const_iterator find_preset_internal(const std::string &name) const + { return const_cast<PresetCollection*>(this)->find_preset_internal(name); } + // Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER. Preset::Type m_type; // List of presets, starting with the "- default -" preset. - std::vector<Preset> m_presets; + // Use deque to force the container to allocate an object per each entry, + // so that the addresses of the presets don't change during resizing of the container. + std::deque<Preset> m_presets; + // Initially this preset contains a copy of the selected preset. Later on, this copy may be modified by the user. Preset m_edited_preset; // Selected preset. int m_idx_selected; // Is the "- default -" preset suppressed? bool m_default_suppressed = true; - // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items. + // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Platter. + // These bitmaps are not owned by PresetCollection, but by a PresetBundle. const wxBitmap *m_bitmap_compatible = nullptr; const wxBitmap *m_bitmap_incompatible = nullptr; -}; - -// Bundle of Print + Filament + Printer presets. -class PresetBundle -{ -public: - PresetBundle(); - ~PresetBundle(); - - bool load_bitmaps(const std::string &path_bitmap_compatible, const std::string &path_bitmap_incompatible); - - // Load ini files of all types (print, filament, printer) from the provided directory path. - void load_presets(const std::string &dir_path); - - PresetCollection prints; - PresetCollection filaments; - PresetCollection printers; - - // Update the colors preview at the platter extruder combo box. - void update_platter_filament_ui_colors(wxBitmapComboBox *ui, unsigned int idx_extruder, unsigned int idx_filament); - - static const std::vector<std::string>& print_options(); - static const std::vector<std::string>& filament_options(); - static const std::vector<std::string>& printer_options(); - -private: - // Indicator, that the preset is compatible with the selected printer. - wxBitmap *m_bitmapCompatible; - // Indicator, that the preset is NOT compatible with the selected printer. - wxBitmap *m_bitmapIncompatible; - // Caching color bitmaps for the - std::map<std::string, wxBitmap*> m_mapColorToBitmap; + // Marks placed at the wxBitmapComboBox of a MainFrame. + // These bitmaps are owned by PresetCollection. + wxBitmap *m_bitmap_main_frame; + // Path to the directory to store the config files into. + std::string m_dir_path; }; } // namespace Slic3r diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp new file mode 100644 index 000000000..76fe3ae95 --- /dev/null +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -0,0 +1,693 @@ +//#undef NDEBUG +#include <cassert> + +#include "PresetBundle.hpp" + +#include <fstream> +#include <boost/filesystem.hpp> +#include <boost/algorithm/clamp.hpp> +#include <boost/algorithm/string/predicate.hpp> + +#include <boost/nowide/cenv.hpp> +#include <boost/nowide/cstdio.hpp> +#include <boost/nowide/fstream.hpp> +#include <boost/property_tree/ini_parser.hpp> +#include <boost/property_tree/ptree.hpp> +#include <boost/locale.hpp> + +#include <wx/dcmemory.h> +#include <wx/image.h> +#include <wx/choice.h> +#include <wx/bmpcbox.h> +#include <wx/wupdlock.h> + +#include "../../libslic3r/libslic3r.h" +#include "../../libslic3r/PlaceholderParser.hpp" +#include "../../libslic3r/Utils.hpp" + +namespace Slic3r { + +PresetBundle::PresetBundle() : + prints(Preset::TYPE_PRINT, Preset::print_options()), + filaments(Preset::TYPE_FILAMENT, Preset::filament_options()), + printers(Preset::TYPE_PRINTER, Preset::printer_options()), + m_bitmapCompatible(new wxBitmap), + m_bitmapIncompatible(new wxBitmap) +{ + if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) + wxImage::AddHandler(new wxPNGHandler); + + // Create the ID config keys, as they are not part of the Static print config classes. + this->prints.preset(0).config.opt_string("print_settings_id", true); + this->filaments.preset(0).config.opt_string("filament_settings_id", true); + this->printers.preset(0).config.opt_string("print_settings_id", true); + // Create the "compatible printers" keys, as they are not part of the Static print config classes. + this->filaments.preset(0).config.optptr("compatible_printers", true); + this->prints.preset(0).config.optptr("compatible_printers", true); + + this->prints .load_bitmap_default("cog.png"); + this->filaments.load_bitmap_default("spool.png"); + this->printers .load_bitmap_default("printer_empty.png"); + this->load_compatible_bitmaps(); +} + +PresetBundle::~PresetBundle() +{ + assert(m_bitmapCompatible != nullptr); + assert(m_bitmapIncompatible != nullptr); + delete m_bitmapCompatible; + m_bitmapCompatible = nullptr; + delete m_bitmapIncompatible; + m_bitmapIncompatible = nullptr; + for (std::pair<const std::string, wxBitmap*> &bitmap : m_mapColorToBitmap) + delete bitmap.second; +} + +void PresetBundle::setup_directories() +{ + boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); + std::initializer_list<boost::filesystem::path> paths = { + data_dir, + data_dir / "presets", + data_dir / "presets" / "print", + data_dir / "presets" / "filament", + data_dir / "presets" / "printer" }; + for (const boost::filesystem::path &path : paths) { + boost::filesystem::path subdir = path; + subdir.make_preferred(); + if (! boost::filesystem::is_directory(subdir) && + ! boost::filesystem::create_directory(subdir)) + throw std::runtime_error(std::string("Slic3r was unable to create its data directory at ") + subdir.string()); + } +} + +void PresetBundle::load_presets() +{ + std::string errors_cummulative; + const std::string dir_path = data_dir() + "/presets"; + try { + this->prints.load_presets(dir_path, "print"); + } catch (const std::runtime_error &err) { + errors_cummulative += err.what(); + } + try { + this->filaments.load_presets(dir_path, "filament"); + } catch (const std::runtime_error &err) { + errors_cummulative += err.what(); + } + try { + this->printers.load_presets(dir_path, "printer"); + } catch (const std::runtime_error &err) { + errors_cummulative += err.what(); + } + this->update_multi_material_filament_presets(); + if (! errors_cummulative.empty()) + throw std::runtime_error(errors_cummulative); +} + +static inline std::string remove_ini_suffix(const std::string &name) +{ + std::string out = name; + if (boost::iends_with(out, ".ini")) + out.erase(out.end() - 4, out.end()); + return out; +} + +// Load selections (current print, current filaments, current printer) from config.ini +// This is done just once on application start up. +void PresetBundle::load_selections(const AppConfig &config) +{ + prints.select_preset_by_name(remove_ini_suffix(config.get("presets", "print")), true); + filaments.select_preset_by_name(remove_ini_suffix(config.get("presets", "filament")), true); + printers.select_preset_by_name(remove_ini_suffix(config.get("presets", "printer")), true); + auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(printers.get_selected_preset().config.option("nozzle_diameter")); + size_t num_extruders = nozzle_diameter->values.size(); + this->set_filament_preset(0, filaments.get_selected_preset().name); + for (unsigned int i = 1; i < (unsigned int)num_extruders; ++ i) { + char name[64]; + sprintf(name, "filament_%d", i); + if (! config.has("presets", name)) + break; + this->set_filament_preset(i, remove_ini_suffix(config.get("presets", name))); + } + // Update visibility of presets based on their compatibility with the active printer. + // This will switch the print or filament presets to compatible if the active presets are incompatible. + this->update_compatible_with_printer(false); +} + +// Export selections (current print, current filaments, current printer) into config.ini +void PresetBundle::export_selections(AppConfig &config) +{ + assert(filament_presets.size() >= 1); + assert(filament_presets.size() > 1 || filaments.get_selected_preset().name == filament_presets.front()); + config.clear_section("presets"); + config.set("presets", "print", prints.get_selected_preset().name); + config.set("presets", "filament", filament_presets.front()); + for (int i = 1; i < filament_presets.size(); ++i) { + char name[64]; + sprintf(name, "filament_%d", i); + config.set("presets", name, filament_presets[i]); + } + config.set("presets", "printer", printers.get_selected_preset().name); +} + +void PresetBundle::export_selections(PlaceholderParser &pp) +{ + assert(filament_presets.size() >= 1); + assert(filament_presets.size() > 1 || filaments.get_selected_preset().name == filament_presets.front()); + pp.set("print_preset", prints.get_selected_preset().name); + pp.set("filament_preset", filament_presets); + pp.set("printer_preset", printers.get_selected_preset().name); +} + +bool PresetBundle::load_compatible_bitmaps() +{ + const std::string path_bitmap_compatible = "flag-green-icon.png"; + const std::string path_bitmap_incompatible = "flag-red-icon.png"; + bool loaded_compatible = m_bitmapCompatible ->LoadFile( + wxString::FromUTF8(Slic3r::var(path_bitmap_compatible).c_str()), wxBITMAP_TYPE_PNG); + bool loaded_incompatible = m_bitmapIncompatible->LoadFile( + wxString::FromUTF8(Slic3r::var(path_bitmap_incompatible).c_str()), wxBITMAP_TYPE_PNG); + if (loaded_compatible) { + prints .set_bitmap_compatible(m_bitmapCompatible); + filaments.set_bitmap_compatible(m_bitmapCompatible); +// printers .set_bitmap_compatible(m_bitmapCompatible); + } + if (loaded_incompatible) { + prints .set_bitmap_incompatible(m_bitmapIncompatible); + filaments.set_bitmap_incompatible(m_bitmapIncompatible); +// printers .set_bitmap_incompatible(m_bitmapIncompatible); + } + return loaded_compatible && loaded_incompatible; +} + +DynamicPrintConfig PresetBundle::full_config() const +{ + DynamicPrintConfig out; + out.apply(FullPrintConfig()); + out.apply(this->prints.get_edited_preset().config); + out.apply(this->printers.get_edited_preset().config); + + auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(out.option("nozzle_diameter")); + size_t num_extruders = nozzle_diameter->values.size(); + + if (num_extruders <= 1) { + out.apply(this->filaments.get_edited_preset().config); + } else { + // Retrieve filament presets and build a single config object for them. + // First collect the filament configurations based on the user selection of this->filament_presets. + std::vector<const DynamicPrintConfig*> filament_configs; + for (const std::string &filament_preset_name : this->filament_presets) + filament_configs.emplace_back(&this->filaments.find_preset(filament_preset_name, true)->config); + while (filament_configs.size() < num_extruders) + filament_configs.emplace_back(&this->filaments.first_visible().config); + // Option values to set a ConfigOptionVector from. + std::vector<const ConfigOption*> filament_opts(num_extruders, nullptr); + // loop through options and apply them to the resulting config. + for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) { + if (key == "compatible_printers") + continue; + // Get a destination option. + ConfigOption *opt_dst = out.option(key, false); + if (opt_dst->is_scalar()) { + // Get an option, do not create if it does not exist. + const ConfigOption *opt_src = filament_configs.front()->option(key); + if (opt_src != nullptr) + opt_dst->set(opt_src); + } else { + // Setting a vector value from all filament_configs. + for (size_t i = 0; i < filament_opts.size(); ++ i) + filament_opts[i] = filament_configs[i]->option(key); + static_cast<ConfigOptionVectorBase*>(opt_dst)->set(filament_opts); + } + } + } + + out.erase("compatible_printers"); + + static const char *keys[] = { "perimeter", "infill", "solid_infill", "support_material", "support_material_interface" }; + for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++ i) { + std::string key = std::string(keys[i]) + "_extruder"; + auto *opt = dynamic_cast<ConfigOptionInt*>(out.option(key, false)); + assert(opt != nullptr); + opt->value = boost::algorithm::clamp<int>(opt->value, 0, int(num_extruders)); + } + + return out; +} + +// Load an external config file containing the print, filament and printer presets. +// Instead of a config file, a G-code may be loaded containing the full set of parameters. +// In the future the configuration will likely be read from an AMF file as well. +// If the file is loaded successfully, its print / filament / printer profiles will be activated. +void PresetBundle::load_config_file(const std::string &path) +{ + if (boost::iends_with(path, ".gcode") || boost::iends_with(path, ".g")) { + DynamicPrintConfig config; + config.apply(FullPrintConfig::defaults()); + config.load_from_gcode(path); + Preset::normalize(config); + load_config_file_config(path, std::move(config)); + return; + } + + // 1) Try to load the config file into a boost property tree. + boost::property_tree::ptree tree; + try { + boost::nowide::ifstream ifs(path); + boost::property_tree::read_ini(ifs, tree); + } catch (const std::ifstream::failure &err) { + throw std::runtime_error(std::string("The config file cannot be loaded: ") + path + "\n\tReason: " + err.what()); + } catch (const std::runtime_error &err) { + throw std::runtime_error(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what()); + } + + // 2) Continue based on the type of the configuration file. + ConfigFileType config_file_type = guess_config_file_type(tree); + switch (config_file_type) { + case CONFIG_FILE_TYPE_UNKNOWN: + throw std::runtime_error(std::string("Unknown configuration file type: ") + path); + case CONFIG_FILE_TYPE_APP_CONFIG: + throw std::runtime_error(std::string("Invalid configuration file: ") + path + ". This is an application config file."); + case CONFIG_FILE_TYPE_CONFIG: + { + // Initialize a config from full defaults. + DynamicPrintConfig config; + config.apply(FullPrintConfig::defaults()); + config.load(tree); + Preset::normalize(config); + load_config_file_config(path, std::move(config)); + break; + } + case CONFIG_FILE_TYPE_CONFIG_BUNDLE: + load_config_file_config_bundle(path, tree); + break; + } +} + +// Load a config file from a boost property_tree. This is a private method called from load_config_file. +void PresetBundle::load_config_file_config(const std::string &path, DynamicPrintConfig &&config) +{ + // The "compatible_printers" field should not have been exported into a config.ini or a G-code anyway, + // but some of the alpha versions of Slic3r did. + { + ConfigOption *opt_compatible = config.optptr("compatible_printers"); + if (opt_compatible != nullptr) { + assert(opt_compatible->type() == coStrings); + if (opt_compatible->type() == coStrings) + static_cast<ConfigOptionStrings*>(opt_compatible)->values.clear(); + } + } + + // 1) Create a name from the file name. + // Keep the suffix (.ini, .gcode, .amf, .3mf etc) to differentiate it from the normal profiles. + std::string name = boost::filesystem::path(path).filename().string(); + + // 2) If the loading succeeded, split and load the config into print / filament / printer settings. + // First load the print and printer presets. + for (size_t i_group = 0; i_group < 2; ++ i_group) { + PresetCollection &presets = (i_group == 0) ? this->prints : this->printers; + presets.load_preset(path, name, config).is_external = true; + } + + // 3) Now load the filaments. If there are multiple filament presets, split them and load them. + auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter")); + auto *filament_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("filament_diameter")); + size_t num_extruders = std::min(nozzle_diameter->values.size(), filament_diameter->values.size()); + if (num_extruders <= 1) { + this->filaments.load_preset(path, name, config).is_external = true; + this->filament_presets.clear(); + this->filament_presets.emplace_back(name); + } else { + // Split the filament presets, load each of them separately. + std::vector<DynamicPrintConfig> configs(num_extruders, this->filaments.default_preset().config); + // loop through options and scatter them into configs. + for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) { + const ConfigOption *other_opt = config.option(key); + if (other_opt == nullptr) + continue; + if (other_opt->is_scalar()) { + for (size_t i = 0; i < configs.size(); ++ i) + configs[i].option(key, false)->set(other_opt); + } else if (key != "compatible_printers") { + for (size_t i = 0; i < configs.size(); ++ i) + static_cast<ConfigOptionVectorBase*>(configs[i].option(key, false))->set_at(other_opt, 0, i); + } + } + // Load the configs into this->filaments and make them active. + this->filament_presets.clear(); + for (size_t i = 0; i < configs.size(); ++ i) { + char suffix[64]; + if (i == 0) + suffix[0] = 0; + else + sprintf(suffix, " (%d)", i); + // Load all filament presets, but only select the first one in the preset dialog. + this->filaments.load_preset(path, name + suffix, std::move(configs[i]), i == 0).is_external = true; + this->filament_presets.emplace_back(name + suffix); + } + } +} + +// Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file. +void PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree) +{ + // 1) Load the config bundle into a temp data. + PresetBundle tmp_bundle; + tmp_bundle.load_configbundle(path); + std::string bundle_name = std::string(" - ") + boost::filesystem::path(path).filename().string(); + + // 2) Extract active configs from the config bundle, copy them and activate them in this bundle. + auto load_one = [this, &path, &bundle_name](PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string { + Preset *preset_src = collection_src.find_preset(preset_name_src, false); + Preset *preset_dst = collection_dst.find_preset(preset_name_src, false); + assert(preset_src != nullptr); + std::string preset_name_dst; + if (preset_dst != nullptr && preset_dst->is_default) { + // No need to copy a default preset, it always exists in collection_dst. + if (activate) + collection_dst.select_preset(0); + return preset_name_src; + } else if (preset_dst != nullptr && preset_src->config == preset_dst->config) { + // Don't save as the config exists in the current bundle and its content is the same. + return preset_name_src; + } else { + // Generate a new unique name. + preset_name_dst = preset_name_src + bundle_name; + Preset *preset_dup = nullptr; + for (size_t i = 1; (preset_dup = collection_dst.find_preset(preset_name_dst, false)) != nullptr; ++ i) { + if (preset_src->config == preset_dup->config) + // The preset has been already copied into collection_dst. + return preset_name_dst; + // Try to generate another name. + char buf[64]; + sprintf(buf, " (%d)", i); + preset_name_dst = preset_name_src + buf + bundle_name; + } + } + assert(! preset_name_dst.empty()); + // Save preset_src->config into collection_dst under preset_name_dst. + // The "compatible_printers" field should not have been exported into a config.ini or a G-code anyway, + // but some of the alpha versions of Slic3r did. + ConfigOption *opt_compatible = preset_src->config.optptr("compatible_printers"); + if (opt_compatible != nullptr) { + assert(opt_compatible->type() == coStrings); + if (opt_compatible->type() == coStrings) + static_cast<ConfigOptionStrings*>(opt_compatible)->values.clear(); + } + collection_dst.load_preset(path, preset_name_dst, std::move(preset_src->config), activate).is_external = true; + return preset_name_dst; + }; + load_one(this->prints, tmp_bundle.prints, tmp_bundle.prints .get_selected_preset().name, true); + load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filaments.get_selected_preset().name, true); + load_one(this->printers, tmp_bundle.printers, tmp_bundle.printers .get_selected_preset().name, true); + this->update_multi_material_filament_presets(); + for (size_t i = 1; i < std::min(tmp_bundle.filament_presets.size(), this->filament_presets.size()); ++ i) + this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false); +} + +// Load a config bundle file, into presets and store the loaded presets into separate files +// of the local configuration directory. +size_t PresetBundle::load_configbundle(const std::string &path) +{ + // 1) Read the complete config file into a boost::property_tree. + namespace pt = boost::property_tree; + pt::ptree tree; + boost::nowide::ifstream ifs(path); + pt::read_ini(ifs, tree); + + // 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files. + std::vector<std::string> loaded_prints; + std::vector<std::string> loaded_filaments; + std::vector<std::string> loaded_printers; + std::string active_print; + std::vector<std::string> active_filaments; + std::string active_printer; + size_t presets_loaded = 0; + for (const auto §ion : tree) { + PresetCollection *presets = nullptr; + std::vector<std::string> *loaded = nullptr; + std::string preset_name; + if (boost::starts_with(section.first, "print:")) { + presets = &prints; + loaded = &loaded_prints; + preset_name = section.first.substr(6); + } else if (boost::starts_with(section.first, "filament:")) { + presets = &filaments; + loaded = &loaded_filaments; + preset_name = section.first.substr(9); + } else if (boost::starts_with(section.first, "printer:")) { + presets = &printers; + loaded = &loaded_printers; + preset_name = section.first.substr(8); + } else if (section.first == "presets") { + // Load the names of the active presets. + for (auto &kvp : section.second) { + if (kvp.first == "print") { + active_print = kvp.second.data(); + } else if (boost::starts_with(kvp.first, "filament")) { + int idx = 0; + if (kvp.first == "filament" || sscanf(kvp.first.c_str(), "filament_%d", &idx) == 1) { + if (int(active_filaments.size()) <= idx) + active_filaments.resize(idx + 1, std::string()); + active_filaments[idx] = kvp.second.data(); + } + } else if (kvp.first == "printer") { + active_printer = kvp.second.data(); + } + } + } else if (section.first == "settings") { + // Load the settings. + for (auto &kvp : section.second) { + if (kvp.first == "autocenter") { + } + } + } else + // Ignore an unknown section. + continue; + if (presets != nullptr) { + // Load the print, filament or printer preset. + DynamicPrintConfig config(presets->default_preset().config); + for (auto &kvp : section.second) + config.set_deserialize(kvp.first, kvp.second.data()); + Preset::normalize(config); + // Decide a full path to this .ini file. + auto file_name = boost::algorithm::iends_with(preset_name, ".ini") ? preset_name : preset_name + ".ini"; + auto file_path = (boost::filesystem::path(data_dir()) / "presets" / presets->name() / file_name).make_preferred(); + // Load the preset into the list of presets, save it to disk. + presets->load_preset(file_path.string(), preset_name, std::move(config), false).save(); + ++ presets_loaded; + } + } + + // 3) Activate the presets. + if (! active_print.empty()) + prints.select_preset_by_name(active_print, true); + if (! active_printer.empty()) + printers.select_preset_by_name(active_printer, true); + // Activate the first filament preset. + if (! active_filaments.empty() && ! active_filaments.front().empty()) + filaments.select_preset_by_name(active_filaments.front(), true); + + this->update_multi_material_filament_presets(); + for (size_t i = 0; i < std::min(this->filament_presets.size(), active_filaments.size()); ++ i) + this->filament_presets[i] = filaments.find_preset(active_filaments[i], true)->name; + return presets_loaded; +} + +void PresetBundle::update_multi_material_filament_presets() +{ + // Verify and select the filament presets. + auto *nozzle_diameter = static_cast<const ConfigOptionFloats*>(printers.get_edited_preset().config.option("nozzle_diameter")); + size_t num_extruders = nozzle_diameter->values.size(); + // Verify validity of the current filament presets. + for (size_t i = 0; i < std::min(this->filament_presets.size(), num_extruders); ++ i) + this->filament_presets[i] = this->filaments.find_preset(this->filament_presets[i], true)->name; + // Append the rest of filament presets. +// if (this->filament_presets.size() < num_extruders) + this->filament_presets.resize(num_extruders, this->filament_presets.empty() ? this->filaments.first_visible().name : this->filament_presets.back()); +} + +void PresetBundle::update_compatible_with_printer(bool select_other_if_incompatible) +{ + this->prints.update_compatible_with_printer(this->printers.get_selected_preset().name, select_other_if_incompatible); + this->filaments.update_compatible_with_printer(this->printers.get_selected_preset().name, select_other_if_incompatible); + if (select_other_if_incompatible) { + // Verify validity of the current filament presets. + for (std::string &filament_name : this->filament_presets) { + Preset *preset = this->filaments.find_preset(filament_name, false); + if (preset == nullptr || ! preset->is_compatible) + filament_name = this->filaments.first_compatible().name; + } + } +} + +void PresetBundle::export_configbundle(const std::string &path) //, const DynamicPrintConfig &settings +{ + boost::nowide::ofstream c; + c.open(path, std::ios::out | std::ios::trunc); + + // Put a comment at the first line including the time stamp and Slic3r version. + c << "# " << Slic3r::header_slic3r_generated() << std::endl; + + // Export the print, filament and printer profiles. + for (size_t i_group = 0; i_group < 3; ++ i_group) { + const PresetCollection &presets = (i_group == 0) ? this->prints : (i_group == 1) ? this->filaments : this->printers; + for (const Preset &preset : presets()) { + if (preset.is_default || preset.is_external) + // Only export the common presets, not external files or the default preset. + continue; + c << std::endl << "[" << presets.name() << ":" << preset.name << "]" << std::endl; + for (const std::string &opt_key : preset.config.keys()) + c << opt_key << " = " << preset.config.serialize(opt_key) << std::endl; + } + } + + // Export the names of the active presets. + c << std::endl << "[presets]" << std::endl; + c << "print = " << this->prints.get_selected_preset().name << std::endl; + c << "printer = " << this->printers.get_selected_preset().name << std::endl; + for (size_t i = 0; i < this->filament_presets.size(); ++ i) { + char suffix[64]; + if (i > 0) + sprintf(suffix, "_%d", i); + else + suffix[0] = 0; + c << "filament" << suffix << " = " << this->filament_presets[i] << std::endl; + } + +#if 0 + // Export the following setting values from the provided setting repository. + static const char *settings_keys[] = { "autocenter" }; + c << "[settings]" << std::endl; + for (size_t i = 0; i < sizeof(settings_keys) / sizeof(settings_keys[0]); ++ i) + c << settings_keys[i] << " = " << settings.serialize(settings_keys[i]) << std::endl; +#endif + + c.close(); +} + +// Set the filament preset name. As the name could come from the UI selection box, +// an optional "(modified)" suffix will be removed from the filament name. +void PresetBundle::set_filament_preset(size_t idx, const std::string &name) +{ + if (idx >= filament_presets.size()) + filament_presets.resize(idx + 1, filaments.default_preset().name); + filament_presets[idx] = Preset::remove_suffix_modified(name); +} + +static inline int hex_digit_to_int(const char c) +{ + return + (c >= '0' && c <= '9') ? int(c - '0') : + (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : + (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; +} + +static inline bool parse_color(const std::string &scolor, unsigned char *rgb_out) +{ + rgb_out[0] = rgb_out[1] = rgb_out[2] = 0; + if (scolor.size() != 7 || scolor.front() != '#') + return false; + const char *c = scolor.data() + 1; + for (size_t i = 0; i < 3; ++ i) { + int digit1 = hex_digit_to_int(*c ++); + int digit2 = hex_digit_to_int(*c ++); + if (digit1 == -1 || digit2 == -1) + return false; + rgb_out[i] = (unsigned char)(digit1 * 16 + digit2); + } + return true; +} + +void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui) +{ + if (ui == nullptr) + return; + + unsigned char rgb[3]; + std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder); + if (! parse_color(extruder_color, rgb)) + // Extruder color is not defined. + extruder_color.clear(); + + // Fill in the list from scratch. + ui->Freeze(); + ui->Clear(); + const Preset *selected_preset = this->filaments.find_preset(this->filament_presets[idx_extruder]); + // Show wide icons if the currently selected preset is not compatible with the current printer, + // and draw a red flag in front of the selected preset. + bool wide_icons = selected_preset != nullptr && ! selected_preset->is_compatible && m_bitmapIncompatible != nullptr; + assert(selected_preset != nullptr); + for (int i = this->filaments().front().is_visible ? 0 : 1; i < int(this->filaments().size()); ++ i) { + const Preset &preset = this->filaments.preset(i); + bool selected = this->filament_presets[idx_extruder] == preset.name; + if (! preset.is_visible || (! preset.is_compatible && ! selected)) + continue; + // Assign an extruder color to the selected item if the extruder color is defined. + std::string filament_rgb = preset.config.opt_string("filament_colour", 0); + std::string extruder_rgb = (selected && !extruder_color.empty()) ? extruder_color : filament_rgb; + bool single_bar = filament_rgb == extruder_rgb; + std::string bitmap_key = single_bar ? filament_rgb : filament_rgb + extruder_rgb; + // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left + // to the filament color image. + if (wide_icons) + bitmap_key += preset.is_compatible ? "comp" : "notcomp"; + auto it = m_mapColorToBitmap.find(bitmap_key); + wxBitmap *bitmap = (it == m_mapColorToBitmap.end()) ? nullptr : it->second; + if (bitmap == nullptr) { + // Create the bitmap with color bars. + bitmap = new wxBitmap((wide_icons ? 16 : 0) + 24, 16); +#if defined(__APPLE__) || defined(_MSC_VER) + bitmap->UseAlpha(); +#endif + wxMemoryDC memDC; + memDC.SelectObject(*bitmap); + memDC.SetBackground(*wxTRANSPARENT_BRUSH); + memDC.Clear(); + if (wide_icons && ! preset.is_compatible) + // Paint the red flag. + memDC.DrawBitmap(*m_bitmapIncompatible, 0, 0, true); + // Paint the color bars. + parse_color(filament_rgb, rgb); + wxImage image(24, 16); + image.InitAlpha(); + unsigned char* imgdata = image.GetData(); + unsigned char* imgalpha = image.GetAlpha(); + for (size_t i = 0; i < image.GetWidth() * image.GetHeight(); ++ i) { + *imgdata ++ = rgb[0]; + *imgdata ++ = rgb[1]; + *imgdata ++ = rgb[2]; + *imgalpha ++ = wxALPHA_OPAQUE; + } + if (! single_bar) { + parse_color(extruder_rgb, rgb); + imgdata = image.GetData(); + for (size_t r = 0; r < 16; ++ r) { + imgdata = image.GetData() + r * image.GetWidth() * 3; + for (size_t c = 0; c < 16; ++ c) { + *imgdata ++ = rgb[0]; + *imgdata ++ = rgb[1]; + *imgdata ++ = rgb[2]; + } + } + } + memDC.DrawBitmap(wxBitmap(image), wide_icons ? 16 : 0, 0, true); + memDC.SelectObject(wxNullBitmap); + m_mapColorToBitmap[bitmap_key] = bitmap; + } + ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), (bitmap == 0) ? wxNullBitmap : *bitmap); + if (selected) + ui->SetSelection(ui->GetCount() - 1); + } + ui->Thaw(); +} + +void PresetBundle::set_default_suppressed(bool default_suppressed) +{ + prints.set_default_suppressed(default_suppressed); + filaments.set_default_suppressed(default_suppressed); + printers.set_default_suppressed(default_suppressed); +} + +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/PresetBundle.hpp b/xs/src/slic3r/GUI/PresetBundle.hpp new file mode 100644 index 000000000..238e7c802 --- /dev/null +++ b/xs/src/slic3r/GUI/PresetBundle.hpp @@ -0,0 +1,95 @@ +#ifndef slic3r_PresetBundle_hpp_ +#define slic3r_PresetBundle_hpp_ + +#include "AppConfig.hpp" +#include "Preset.hpp" + +namespace Slic3r { + +class PlaceholderParser; + +// Bundle of Print + Filament + Printer presets. +class PresetBundle +{ +public: + PresetBundle(); + ~PresetBundle(); + + void setup_directories(); + + // Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets. + void load_presets(); + + // Load selections (current print, current filaments, current printer) from config.ini + // This is done just once on application start up. + void load_selections(const AppConfig &config); + // Export selections (current print, current filaments, current printer) into config.ini + void export_selections(AppConfig &config); + // Export selections (current print, current filaments, current printer) into a placeholder parser. + void export_selections(PlaceholderParser &pp); + + PresetCollection prints; + PresetCollection filaments; + PresetCollection printers; + // Filament preset names for a multi-extruder or multi-material print. + // extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size() + std::vector<std::string> filament_presets; + + bool has_defauls_only() const + { return prints.size() <= 1 && filaments.size() <= 1 && printers.size() <= 1; } + + DynamicPrintConfig full_config() const; + + // Load an external config file containing the print, filament and printer presets. + // Instead of a config file, a G-code may be loaded containing the full set of parameters. + // In the future the configuration will likely be read from an AMF file as well. + // If the file is loaded successfully, its print / filament / printer profiles will be activated. + void load_config_file(const std::string &path); + + // Load a config bundle file, into presets and store the loaded presets into separate files + // of the local configuration directory. + // Load settings into the provided settings instance. + // Activate the presets stored in the config bundle. + // Returns the number of presets loaded successfully. + size_t load_configbundle(const std::string &path); + + // Export a config bundle file containing all the presets and the names of the active presets. + void export_configbundle(const std::string &path); // , const DynamicPrintConfig &settings); + + // Update a filament selection combo box on the platter for an idx_extruder. + void update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui); + + // Enable / disable the "- default -" preset. + void set_default_suppressed(bool default_suppressed); + + // Set the filament preset name. As the name could come from the UI selection box, + // an optional "(modified)" suffix will be removed from the filament name. + void set_filament_preset(size_t idx, const std::string &name); + + // Read out the number of extruders from an active printer preset, + // update size and content of filament_presets. + void update_multi_material_filament_presets(); + + // Update the is_compatible flag of all print and filament presets depending on whether they are marked + // as compatible with the currently selected printer. + // Also updates the is_visible flag of each preset. + // If select_other_if_incompatible is true, then the print or filament preset is switched to some compatible + // preset if the current print or filament preset is not compatible. + void update_compatible_with_printer(bool select_other_if_incompatible); + +private: + void load_config_file_config(const std::string &path, DynamicPrintConfig &&config); + void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree); + bool load_compatible_bitmaps(); + + // Indicator, that the preset is compatible with the selected printer. + wxBitmap *m_bitmapCompatible; + // Indicator, that the preset is NOT compatible with the selected printer. + wxBitmap *m_bitmapIncompatible; + // Caching color bitmaps for the + std::map<std::string, wxBitmap*> m_mapColorToBitmap; +}; + +} // namespace Slic3r + +#endif /* slic3r_PresetBundle_hpp_ */ diff --git a/xs/src/slic3r/GUI/PresetHints.cpp b/xs/src/slic3r/GUI/PresetHints.cpp new file mode 100644 index 000000000..13478f980 --- /dev/null +++ b/xs/src/slic3r/GUI/PresetHints.cpp @@ -0,0 +1,229 @@ +//#undef NDEBUG +#include <cassert> + +#include "PresetBundle.hpp" +#include "PresetHints.hpp" +#include "Flow.hpp" + +#include <boost/algorithm/string/predicate.hpp> + +#include "../../libslic3r/libslic3r.h" + +namespace Slic3r { + +std::string PresetHints::cooling_description(const Preset &preset) +{ + std::string out; + char buf[4096]; + if (preset.config.opt_bool("cooling", 0)) { + int slowdown_below_layer_time = preset.config.opt_int("slowdown_below_layer_time", 0); + int min_fan_speed = preset.config.opt_int("min_fan_speed", 0); + int max_fan_speed = preset.config.opt_int("max_fan_speed", 0); + int min_print_speed = int(preset.config.opt_float("min_print_speed", 0) + 0.5); + int fan_below_layer_time = preset.config.opt_int("fan_below_layer_time", 0); + sprintf(buf, "If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).", + slowdown_below_layer_time, max_fan_speed, slowdown_below_layer_time, min_print_speed); + out += buf; + if (fan_below_layer_time > slowdown_below_layer_time) { + sprintf(buf, "\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.", + fan_below_layer_time, max_fan_speed, min_fan_speed); + out += buf; + } + out += "\nDuring the other layers, fan "; + } else { + out = "Fan "; + } + if (preset.config.opt_bool("fan_always_on", 0)) { + int disable_fan_first_layers = preset.config.opt_int("disable_fan_first_layers", 0); + int min_fan_speed = preset.config.opt_int("min_fan_speed", 0); + sprintf(buf, "will always run at %d%% ", min_fan_speed); + out += buf; + if (disable_fan_first_layers > 1) { + sprintf(buf, "except for the first %d layers", disable_fan_first_layers); + out += buf; + } + else if (disable_fan_first_layers == 1) + out += "except for the first layer"; + } else + out += "will be turned off."; + + return out; +} + +static const ConfigOptionFloatOrPercent& first_positive(const ConfigOptionFloatOrPercent *v1, const ConfigOptionFloatOrPercent &v2, const ConfigOptionFloatOrPercent &v3) +{ + return (v1 != nullptr && v1->value > 0) ? *v1 : ((v2.value > 0) ? v2 : v3); +} + +static double first_positive(double v1, double v2) +{ + return (v1 > 0.) ? v1 : v2; +} + +std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle &preset_bundle) +{ + // Find out, to which nozzle index is the current filament profile assigned. + int idx_extruder = 0; + int num_extruders = (int)preset_bundle.filament_presets.size(); + for (; idx_extruder < num_extruders; ++ idx_extruder) + if (preset_bundle.filament_presets[idx_extruder] == preset_bundle.filaments.get_selected_preset().name) + break; + if (idx_extruder == num_extruders) + // The current filament preset is not active for any extruder. + idx_extruder = -1; + + const DynamicPrintConfig &print_config = preset_bundle.prints .get_edited_preset().config; + const DynamicPrintConfig &filament_config = preset_bundle.filaments.get_edited_preset().config; + const DynamicPrintConfig &printer_config = preset_bundle.printers .get_edited_preset().config; + + // Current printer values. + float nozzle_diameter = (float)printer_config.opt_float("nozzle_diameter", idx_extruder); + + // Print config values + double layer_height = print_config.opt_float("layer_height"); + double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height); + double support_material_speed = print_config.opt_float("support_material_speed"); + double support_material_interface_speed = print_config.get_abs_value("support_material_interface_speed", support_material_speed); + double bridge_speed = print_config.opt_float("bridge_speed"); + double bridge_flow_ratio = print_config.opt_float("bridge_flow_ratio"); + double perimeter_speed = print_config.opt_float("perimeter_speed"); + double external_perimeter_speed = print_config.get_abs_value("external_perimeter_speed", perimeter_speed); + double gap_fill_speed = print_config.opt_float("gap_fill_speed"); + double infill_speed = print_config.opt_float("infill_speed"); + double small_perimeter_speed = print_config.get_abs_value("small_perimeter_speed", perimeter_speed); + double solid_infill_speed = print_config.get_abs_value("solid_infill_speed", infill_speed); + double top_solid_infill_speed = print_config.get_abs_value("top_solid_infill_speed", solid_infill_speed); + // Maximum print speed when auto-speed is enabled by setting any of the above speed values to zero. + double max_print_speed = print_config.opt_float("max_print_speed"); + // Maximum volumetric speed allowed for the print profile. + double max_volumetric_speed = print_config.opt_float("max_volumetric_speed"); + + const auto &extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("extrusion_width"); + const auto &external_perimeter_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("external_perimeter_extrusion_width"); + const auto &first_layer_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("first_layer_extrusion_width"); + const auto &infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("infill_extrusion_width"); + const auto &perimeter_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("perimeter_extrusion_width"); + const auto &solid_infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("solid_infill_extrusion_width"); + const auto &support_material_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("support_material_extrusion_width"); + const auto &top_infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("top_infill_extrusion_width"); + + // Index of an extruder assigned to a feature. If set to 0, an active extruder will be used for a multi-material print. + // If different from idx_extruder, it will not be taken into account for this hint. + auto feature_extruder_active = [idx_extruder, num_extruders](int i) { + return i <= 0 || i > num_extruders || idx_extruder == -1 || idx_extruder == i - 1; + }; + bool perimeter_extruder_active = feature_extruder_active(print_config.opt_int("perimeter_extruder")); + bool infill_extruder_active = feature_extruder_active(print_config.opt_int("infill_extruder")); + bool solid_infill_extruder_active = feature_extruder_active(print_config.opt_int("solid_infill_extruder")); + bool support_material_extruder_active = feature_extruder_active(print_config.opt_int("support_material_extruder")); + bool support_material_interface_extruder_active = feature_extruder_active(print_config.opt_int("support_material_interface_extruder")); + + // Current filament values + double filament_diameter = filament_config.opt_float("filament_diameter", 0); + double filament_crossection = M_PI * 0.25 * filament_diameter * filament_diameter; + double extrusion_multiplier = filament_config.opt_float("extrusion_multiplier", 0); + // The following value will be annotated by this hint, so it does not take part in the calculation. +// double filament_max_volumetric_speed = filament_config.opt_float("filament_max_volumetric_speed", 0); + + std::string out; + for (size_t idx_type = (first_layer_extrusion_width.value == 0) ? 1 : 0; idx_type < 3; ++ idx_type) { + // First test the maximum volumetric extrusion speed for non-bridging extrusions. + bool first_layer = idx_type == 0; + bool bridging = idx_type == 2; + const ConfigOptionFloatOrPercent *first_layer_extrusion_width_ptr = (first_layer && first_layer_extrusion_width.value > 0) ? + &first_layer_extrusion_width : nullptr; + const float lh = float(first_layer ? first_layer_height : layer_height); + const float bfr = bridging ? bridge_flow_ratio : 0.f; + double max_flow = 0.; + std::string max_flow_extrusion_type; + if (perimeter_extruder_active) { + double external_perimeter_rate = Flow::new_from_config_width(frExternalPerimeter, + first_positive(first_layer_extrusion_width_ptr, external_perimeter_extrusion_width, extrusion_width), + nozzle_diameter, lh, bfr).mm3_per_mm() * + (bridging ? bridge_speed : + first_positive(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed)); + if (max_flow < external_perimeter_rate) { + max_flow = external_perimeter_rate; + max_flow_extrusion_type = "external perimeters"; + } + double perimeter_rate = Flow::new_from_config_width(frPerimeter, + first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width), + nozzle_diameter, lh, bfr).mm3_per_mm() * + (bridging ? bridge_speed : + first_positive(std::max(perimeter_speed, small_perimeter_speed), max_print_speed)); + if (max_flow < perimeter_rate) { + max_flow = perimeter_rate; + max_flow_extrusion_type = "perimeters"; + } + } + if (! bridging && infill_extruder_active) { + double infill_rate = Flow::new_from_config_width(frInfill, + first_positive(first_layer_extrusion_width_ptr, infill_extrusion_width, extrusion_width), + nozzle_diameter, lh, bfr).mm3_per_mm() * first_positive(infill_speed, max_print_speed); + if (max_flow < infill_rate) { + max_flow = infill_rate; + max_flow_extrusion_type = "infill"; + } + } + if (solid_infill_extruder_active) { + double solid_infill_rate = Flow::new_from_config_width(frInfill, + first_positive(first_layer_extrusion_width_ptr, solid_infill_extrusion_width, extrusion_width), + nozzle_diameter, lh, 0).mm3_per_mm() * + (bridging ? bridge_speed : first_positive(solid_infill_speed, max_print_speed)); + if (max_flow < solid_infill_rate) { + max_flow = solid_infill_rate; + max_flow_extrusion_type = "solid infill"; + } + if (! bridging) { + double top_solid_infill_rate = Flow::new_from_config_width(frInfill, + first_positive(first_layer_extrusion_width_ptr, top_infill_extrusion_width, extrusion_width), + nozzle_diameter, lh, bfr).mm3_per_mm() * first_positive(top_solid_infill_speed, max_print_speed); + if (max_flow < top_solid_infill_rate) { + max_flow = top_solid_infill_rate; + max_flow_extrusion_type = "top solid infill"; + } + } + } + if (support_material_extruder_active) { + double support_material_rate = Flow::new_from_config_width(frSupportMaterial, + first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width), + nozzle_diameter, lh, bfr).mm3_per_mm() * + (bridging ? bridge_speed : first_positive(support_material_speed, max_print_speed)); + if (max_flow < support_material_rate) { + max_flow = support_material_rate; + max_flow_extrusion_type = "support"; + } + } + if (support_material_interface_extruder_active) { + double support_material_interface_rate = Flow::new_from_config_width(frSupportMaterialInterface, + first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width), + nozzle_diameter, lh, bfr).mm3_per_mm() * + (bridging ? bridge_speed : first_positive(support_material_interface_speed, max_print_speed)); + if (max_flow < support_material_interface_rate) { + max_flow = support_material_interface_rate; + max_flow_extrusion_type = "support interface"; + } + } + + //FIXME handle gap_fill_speed + if (! out.empty()) + out += "\n"; + out += (first_layer ? "First layer volumetric" : (bridging ? "Bridging volumetric" : "Volumetric")); + out += " flow rate is maximized "; + out += ((max_volumetric_speed > 0 && max_volumetric_speed < max_flow) ? + "by the print profile maximum" : + ("when printing " + max_flow_extrusion_type)) + + " with a volumetric rate "; + if (max_volumetric_speed > 0 && max_volumetric_speed < max_flow) + max_flow = max_volumetric_speed; + char buf[2048]; + sprintf(buf, "%3.2f mm³/s", max_flow); + out += buf; + sprintf(buf, " at filament speed %3.2f mm/s.", max_flow / filament_crossection); + out += buf; + } + + return out; +} + +}; // namespace Slic3r diff --git a/xs/src/slic3r/GUI/PresetHints.hpp b/xs/src/slic3r/GUI/PresetHints.hpp new file mode 100644 index 000000000..589cc2f98 --- /dev/null +++ b/xs/src/slic3r/GUI/PresetHints.hpp @@ -0,0 +1,25 @@ +#ifndef slic3r_PresetHints_hpp_ +#define slic3r_PresetHints_hpp_ + +#include <string> + +#include "PresetBundle.hpp" + +namespace Slic3r { + +// GUI utility functions to produce hint messages from the current profile. +class PresetHints +{ +public: + // Produce a textual description of the cooling logic of a currently active filament. + static std::string cooling_description(const Preset &preset); + // Produce a textual description of the maximum flow achived for the current configuration + // (the current printer, filament and print settigns). + // This description will be useful for getting a gut feeling for the maximum volumetric + // print speed achievable with the extruder. + static std::string maximum_volumetric_flow_description(const PresetBundle &preset_bundle); +}; + +} // namespace Slic3r + +#endif /* slic3r_PresetHints_hpp_ */ diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index dcf54bdbc..49981b74b 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -54,6 +54,8 @@ extern "C" { #ifdef _MSC_VER // Undef some of the macros set by Perl <xsinit.h>, which cause compilation errors on Win32 #undef connect + #undef link + #undef unlink #undef seek #undef send #undef write diff --git a/xs/t/15_config.t b/xs/t/15_config.t index 946297358..6e73c8a9a 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -244,9 +244,8 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo { use Cwd qw(abs_path); use File::Basename qw(dirname); - my $class = Slic3r::Config->new; my $path = abs_path($0); - my $config = $class->_load(dirname($path)."/inc/22_config_bad_config_options.ini"); + my $config = Slic3r::Config::load(dirname($path)."/inc/22_config_bad_config_options.ini"); ok 1, 'did not crash on reading invalid items in config'; } diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 0e76b4cf1..6adfc49a2 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -8,6 +8,11 @@ %name{Slic3r::Config} class DynamicPrintConfig { DynamicPrintConfig(); ~DynamicPrintConfig(); + static DynamicPrintConfig* new_from_defaults(); + static DynamicPrintConfig* new_from_defaults_keys(std::vector<std::string> keys); + DynamicPrintConfig* clone() %code{% RETVAL = new DynamicPrintConfig(*THIS); %}; + DynamicPrintConfig* clone_only(std::vector<std::string> keys) + %code{% RETVAL = new DynamicPrintConfig(); RETVAL->apply_only(*THIS, keys, true); %}; bool has(t_config_option_key opt_key); SV* as_hash() %code{% RETVAL = ConfigBase__as_hash(THIS); %}; @@ -15,6 +20,13 @@ %code{% RETVAL = ConfigBase__get(THIS, opt_key); %}; SV* get_at(t_config_option_key opt_key, int i) %code{% RETVAL = ConfigBase__get_at(THIS, opt_key, i); %}; + SV* get_value(t_config_option_key opt_key) + %code{% + const ConfigOptionDef *def = THIS->def()->get(opt_key); + RETVAL = (def != nullptr && ! def->ratio_over.empty()) ? + newSVnv(THIS->get_abs_value(opt_key)) : + ConfigBase__get(THIS, opt_key); + %}; bool set(t_config_option_key opt_key, SV* value) %code{% RETVAL = ConfigBase__set(THIS, opt_key, value); %}; bool set_deserialize(t_config_option_key opt_key, SV* str) @@ -38,16 +50,24 @@ void normalize(); %name{setenv} void setenv_(); double min_object_distance() %code{% RETVAL = PrintConfig::min_object_distance(THIS); %}; - %name{_load} void load(std::string file); - %name{_load_from_gcode} void load_from_gcode(std::string input_file) + static DynamicPrintConfig* load(char *path) %code%{ + auto config = new DynamicPrintConfig(); try { - THIS->load_from_gcode(input_file); + config->load(path); + RETVAL = config; } catch (std::exception& e) { - croak("Error extracting configuration from a g-code %s:\n%s\n", input_file.c_str(), e.what()); + delete config; + croak("Error extracting configuration from %s:\n%s\n", path, e.what()); } %}; void save(std::string file); + int validate() %code%{ + std::string err = THIS->validate(); + if (! err.empty()) + croak("Configuration is not valid: %s\n", err.c_str()); + RETVAL = 1; + %}; }; %name{Slic3r::Config::Static} class StaticPrintConfig { @@ -94,8 +114,18 @@ %}; %name{setenv} void setenv_(); double min_object_distance() %code{% RETVAL = PrintConfig::min_object_distance(THIS); %}; - %name{_load} void load(std::string file); - %name{_load_from_gcode} void load_from_gcode(std::string file); + static StaticPrintConfig* load(char *path) + %code%{ + auto config = new FullPrintConfig(); + try { + config->load(path); + RETVAL = static_cast<PrintObjectConfig*>(config); + } catch (std::exception& e) { + delete config; + croak("Error extracting configuration from %s:\n%s\n", path, e.what()); + } + %}; + void save(std::string file); }; diff --git a/xs/xsp/Flow.xsp b/xs/xsp/Flow.xsp index d09f0b351..d9b7a45c0 100644 --- a/xs/xsp/Flow.xsp +++ b/xs/xsp/Flow.xsp @@ -46,18 +46,6 @@ _new_from_width(CLASS, role, width, nozzle_diameter, height, bridge_flow_ratio) OUTPUT: RETVAL -Flow* -_new_from_spacing(CLASS, spacing, nozzle_diameter, height, bridge) - char* CLASS; - float spacing; - float nozzle_diameter; - float height; - bool bridge; - CODE: - RETVAL = new Flow(Flow::new_from_spacing(spacing, nozzle_diameter, height, bridge)); - OUTPUT: - RETVAL - %} }; diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 92341394f..9e6df85dc 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -17,7 +17,14 @@ %name{Slic3r::GCode} class GCode { GCode(); ~GCode(); - std::string do_export(Print *print, const char *path); + void do_export(Print *print, const char *path) + %code%{ + try { + THIS->do_export(print, path); + } catch (std::exception& e) { + croak(e.what()); + } + %}; Ref<Pointf> origin() %code{% RETVAL = &(THIS->origin()); %}; diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 875c49e26..d6b55dbf1 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -14,8 +14,26 @@ void disable_screensaver() void enable_screensaver() %code{% Slic3r::GUI::enable_screensaver(); %}; +std::vector<std::string> scan_serial_ports() + %code{% RETVAL=Slic3r::GUI::scan_serial_ports(); %}; + bool debugged() %code{% RETVAL=Slic3r::GUI::debugged(); %}; void break_to_debugger() %code{% Slic3r::GUI::break_to_debugger(); %}; + +void set_wxapp(SV *ui) + %code%{ Slic3r::GUI::set_wxapp((wxApp*)wxPli_sv_2_object(aTHX_ ui, "Wx::App")); %}; + +void set_main_frame(SV *ui) + %code%{ Slic3r::GUI::set_main_frame((wxFrame*)wxPli_sv_2_object(aTHX_ ui, "Wx::Frame")); %}; + +void set_tab_panel(SV *ui) + %code%{ Slic3r::GUI::set_tab_panel((wxNotebook*)wxPli_sv_2_object(aTHX_ ui, "Wx::Notebook")); %}; + +void add_debug_menu(SV *ui) + %code%{ Slic3r::GUI::add_debug_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar")); %}; + +void create_preset_tab(const char *name) + %code%{ Slic3r::GUI::create_preset_tab(name); %}; diff --git a/xs/xsp/GUI_AppConfig.xsp b/xs/xsp/GUI_AppConfig.xsp new file mode 100644 index 000000000..8162b9a5e --- /dev/null +++ b/xs/xsp/GUI_AppConfig.xsp @@ -0,0 +1,44 @@ + +%module{Slic3r::XS}; + +%{ +#include <xsinit.h> +#include "slic3r/GUI/AppConfig.hpp" +%} + +%name{Slic3r::GUI::AppConfig} class AppConfig { + AppConfig(); + ~AppConfig(); + + void reset(); + void set_defaults(); + + void load() + %code%{ + try { + THIS->load(); + } catch (std::exception& e) { + croak("Loading an application config file failed:\n%s\n", e.what()); + } + %}; + void save() + %code%{ + try { + THIS->save(); + } catch (std::exception& e) { + croak("Saving an application config file failed:\n%s\n", e.what()); + } + %}; + bool exists(); + bool dirty(); + + std::string get(char *name); + void set(char *name, char *value); + bool has(char *section); + + std::string get_last_dir(); + void update_config_dir(char *dir); + void update_skein_dir(char *dir); + std::string get_last_output_dir(const char *alt = ""); + void update_last_output_dir(char *dir); +}; diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp index c6d97cabd..d6af38d15 100644 --- a/xs/xsp/GUI_Preset.xsp +++ b/xs/xsp/GUI_Preset.xsp @@ -3,22 +3,27 @@ %{ #include <xsinit.h> #include "slic3r/GUI/Preset.hpp" +#include "slic3r/GUI/PresetBundle.hpp" +#include "slic3r/GUI/PresetHints.hpp" %} %name{Slic3r::GUI::Preset} class Preset { // owned by PresetCollection, no constructor/destructor - bool is_default() %code%{ RETVAL = THIS->is_default; %}; - bool is_external() %code%{ RETVAL = THIS->is_external; %}; - bool is_visible() %code%{ RETVAL = THIS->is_visible; %}; - bool is_dirty() %code%{ RETVAL = THIS->is_dirty; %}; + bool default() %code%{ RETVAL = THIS->is_default; %}; + bool external() %code%{ RETVAL = THIS->is_external; %}; + bool visible() %code%{ RETVAL = THIS->is_visible; %}; + bool dirty() %code%{ RETVAL = THIS->is_dirty; %}; + bool is_compatible_with_printer(char *active_printer) const; - const char* name() %code%{ RETVAL = THIS->name.c_str(); %}; - const char* file() %code%{ RETVAL = THIS->file.c_str(); %}; + const char* name() %code%{ RETVAL = THIS->name.c_str(); %}; + const char* file() %code%{ RETVAL = THIS->file.c_str(); %}; - bool loaded() %code%{ RETVAL = THIS->loaded; %}; + bool loaded() %code%{ RETVAL = THIS->loaded; %}; - Ref<DynamicPrintConfig> config() %code%{ RETVAL = &THIS->config; %}; + Ref<DynamicPrintConfig> config() %code%{ RETVAL = &THIS->config; %}; + + void set_num_extruders(int num_extruders); }; %name{Slic3r::GUI::PresetCollection} class PresetCollection { @@ -27,6 +32,50 @@ Ref<Preset> default_preset() %code%{ RETVAL = &THIS->default_preset(); %}; size_t size() const; size_t num_visible() const; + std::string name() const; + + Ref<Preset> get_selected_preset() %code%{ RETVAL = &THIS->get_selected_preset(); %}; + Ref<Preset> get_current_preset() %code%{ RETVAL = &THIS->get_edited_preset(); %}; + std::string get_current_preset_name() %code%{ RETVAL = THIS->get_selected_preset().name; %}; + Ref<Preset> get_edited_preset() %code%{ RETVAL = &THIS->get_edited_preset(); %}; + + Ref<Preset> find_preset(char *name, bool first_visible_if_not_found = false) %code%{ RETVAL = THIS->find_preset(name, first_visible_if_not_found); %}; + + bool current_is_dirty(); + std::vector<std::string> current_dirty_options(); + + void update_tab_ui(SV *ui, bool show_incompatible) + %code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object( aTHX_ ui, "Wx::BitmapComboBox" ); + THIS->update_tab_ui(cb, show_incompatible); %}; + + void update_platter_ui(SV *ui) + %code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object( aTHX_ ui, "Wx::BitmapComboBox" ); + THIS->update_platter_ui(cb); %}; + + bool update_dirty_ui(SV *ui) + %code%{ RETVAL = THIS->update_dirty_ui((wxBitmapComboBox*)wxPli_sv_2_object(aTHX_ ui, "Wx::BitmapComboBox")); %}; + + void select_preset(int idx); + bool select_preset_by_name(char *name) %code%{ RETVAL = THIS->select_preset_by_name(name, true); %}; + void discard_current_changes(); + + void save_current_preset(char *new_name) + %code%{ + try { + THIS->save_current_preset(new_name); + } catch (std::exception& e) { + croak("Error saving a preset %s:\n%s\n", new_name, e.what()); + } + %}; + void delete_current_preset() + %code%{ + try { + THIS->delete_current_preset(); + } catch (std::exception& e) { + croak("Error deleting a preset file %s:\n%s\n", THIS->get_selected_preset().file.c_str(), e.what()); + } + %}; + %{ SV* @@ -34,10 +83,9 @@ PresetCollection::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->size()-1); - int i = 0; - for (size_t i = 0; i < THIS->size(); ++ i) { + for (int i = 0; i < int(THIS->size()); ++ i) { Preset &preset = THIS->preset(i); - av_store(av, i++, perl_to_SV_ref(preset)); + av_store(av, i, perl_to_SV_ref(preset)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: @@ -46,15 +94,82 @@ PresetCollection::arrayref() %} }; - %name{Slic3r::GUI::PresetBundle} class PresetBundle { PresetBundle(); ~PresetBundle(); - void load_bitmaps(std::string path_bitmap_compatible, std::string path_bitmap_incompatible); - void load_presets(std::string dir_path); + void setup_directories() + %code%{ + try { + THIS->setup_directories(); + } catch (std::exception& e) { + croak("Cannot create configuration directories:\n%s\n", e.what()); + } + %}; + void load_presets() + %code%{ + try { + THIS->load_presets(); + } catch (std::exception& e) { + croak("Loading of Slic3r presets from %s failed.\n\n%s\n", + Slic3r::data_dir().c_str(), e.what()); + } + %}; + void load_config_file(const char *path) + %code%{ + try { + THIS->load_config_file(path); + } catch (std::exception& e) { + croak("Loading a configuration file %s failed:\n%s\n", path, e.what()); + } + %}; + size_t load_configbundle(const char *path) + %code%{ + try { + RETVAL = THIS->load_configbundle(path); + } catch (std::exception& e) { + croak("Loading of a config bundle %s failed:\n%s\n", path, e.what()); + } + %}; + void export_configbundle(char *path) + %code%{ + try { + THIS->export_configbundle(path); + } catch (std::exception& e) { + croak("Export of a config bundle %s failed:\n%s\n", path, e.what()); + } + %}; + + void set_default_suppressed(bool default_suppressed); + + void load_selections (AppConfig *config) %code%{ THIS->load_selections(*config); %}; + void export_selections(AppConfig *config) %code%{ THIS->export_selections(*config); %}; + void export_selections_pp(PlaceholderParser *pp) %code%{ THIS->export_selections(*pp); %}; + + Ref<PresetCollection> print() %code%{ RETVAL = &THIS->prints; %}; + Ref<PresetCollection> filament() %code%{ RETVAL = &THIS->filaments; %}; + Ref<PresetCollection> printer() %code%{ RETVAL = &THIS->printers; %}; + bool has_defauls_only(); + + std::vector<std::string> filament_presets() %code%{ RETVAL = THIS->filament_presets; %}; + void set_filament_preset(int idx, const char *name); + void update_multi_material_filament_presets(); + + void update_compatible_with_printer(bool select_other_if_incompatible); + + Clone<DynamicPrintConfig> full_config() %code%{ RETVAL = THIS->full_config(); %}; + + void update_platter_filament_ui(int extruder_idx, SV *ui) + %code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object(aTHX_ ui, "Wx::BitmapComboBox"); + THIS->update_platter_filament_ui(extruder_idx, cb); %}; +}; + +%name{Slic3r::GUI::PresetHints} class PresetHints { + PresetHints(); + ~PresetHints(); - Ref<PresetCollection> prints() %code%{ RETVAL = &THIS->prints; %}; - Ref<PresetCollection> filaments() %code%{ RETVAL = &THIS->filaments; %}; - Ref<PresetCollection> printers() %code%{ RETVAL = &THIS->printers; %}; + static std::string cooling_description(Preset *preset) + %code%{ RETVAL = PresetHints::cooling_description(*preset); %}; + static std::string maximum_volumetric_flow_description(PresetBundle *preset) + %code%{ RETVAL = PresetHints::maximum_volumetric_flow_description(*preset); %}; }; diff --git a/xs/xsp/PlaceholderParser.xsp b/xs/xsp/PlaceholderParser.xsp index 3f1588ace..ab06450f9 100644 --- a/xs/xsp/PlaceholderParser.xsp +++ b/xs/xsp/PlaceholderParser.xsp @@ -9,15 +9,16 @@ %name{Slic3r::GCode::PlaceholderParser} class PlaceholderParser { PlaceholderParser(); ~PlaceholderParser(); - Clone<PlaceholderParser> clone() - %code{% RETVAL = THIS; %}; - void update_timestamp(); - void apply_env_variables(); void apply_config(DynamicPrintConfig *config) %code%{ THIS->apply_config(*config); %}; - void set(std::string key, std::string value); - %name{set_multiple} void set(std::string key, std::vector<std::string> values); + void set(std::string key, int value); std::string process(std::string str) const - %code%{ RETVAL = THIS->process(str, 0); %}; + %code%{ + try { + RETVAL = THIS->process(str, 0); + } catch (std::exception& e) { + croak(e.what()); + } + %}; }; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index cb2f63399..0dff871b7 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -206,8 +206,14 @@ _constant() double max_allowed_layer_height() const; bool has_support_material() const; void auto_assign_extruders(ModelObject* model_object); - std::string output_filename(); - std::string output_filepath(std::string path = ""); + std::string output_filepath(std::string path = "") + %code%{ + try { + RETVAL = THIS->output_filepath(path); + } catch (std::exception& e) { + croak(e.what()); + } + %}; void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig* config) @@ -215,8 +221,12 @@ _constant() bool has_infinite_skirt(); bool has_skirt(); std::vector<unsigned int> extruders() const; - std::string _validate() - %code%{ RETVAL = THIS->validate(); %}; + int validate() %code%{ + std::string err = THIS->validate(); + if (! err.empty()) + croak("Configuration is not valid: %s\n", err.c_str()); + RETVAL = 1; + %}; Clone<BoundingBox> bounding_box(); Clone<BoundingBox> total_bounding_box(); double skirt_first_layer_height(); diff --git a/xs/xsp/XS.xsp b/xs/xsp/XS.xsp index 7265334ae..b2f772834 100644 --- a/xs/xsp/XS.xsp +++ b/xs/xsp/XS.xsp @@ -60,6 +60,18 @@ var_dir() RETVAL = const_cast<char*>(Slic3r::var_dir().c_str()); OUTPUT: RETVAL +void +set_resources_dir(dir) + char *dir; + CODE: + Slic3r::set_resources_dir(dir); + +char* +resources_dir() + CODE: + RETVAL = const_cast<char*>(Slic3r::resources_dir().c_str()); + OUTPUT: RETVAL + std::string var(file_name) const char *file_name; @@ -67,6 +79,18 @@ var(file_name) RETVAL = Slic3r::var(file_name); OUTPUT: RETVAL +void +set_data_dir(dir) + char *dir; + CODE: + Slic3r::set_data_dir(dir); + +char* +data_dir() + CODE: + RETVAL = const_cast<char*>(Slic3r::data_dir().c_str()); + OUTPUT: RETVAL + std::string encode_path(src) const char *src; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 50073d87d..7b59623d4 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -210,12 +210,15 @@ PrintObjectSupportMaterial* O_OBJECT_SLIC3R Ref<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T Clone<PrintObjectSupportMaterial> O_OBJECT_SLIC3R_T -GLShader* O_OBJECT_SLIC3R -Ref<GLShader> O_OBJECT_SLIC3R_T -GLVolume* O_OBJECT_SLIC3R -Ref<GLVolume> O_OBJECT_SLIC3R_T -GLVolumeCollection* O_OBJECT_SLIC3R -Ref<GLVolumeCollection> O_OBJECT_SLIC3R_T +AppConfig* O_OBJECT_SLIC3R +Ref<AppConfig> O_OBJECT_SLIC3R_T + +GLShader* O_OBJECT_SLIC3R +Ref<GLShader> O_OBJECT_SLIC3R_T +GLVolume* O_OBJECT_SLIC3R +Ref<GLVolume> O_OBJECT_SLIC3R_T +GLVolumeCollection* O_OBJECT_SLIC3R +Ref<GLVolumeCollection> O_OBJECT_SLIC3R_T Preset* O_OBJECT_SLIC3R Ref<Preset> O_OBJECT_SLIC3R_T @@ -223,6 +226,8 @@ PresetCollection* O_OBJECT_SLIC3R Ref<PresetCollection> O_OBJECT_SLIC3R_T PresetBundle* O_OBJECT_SLIC3R Ref<PresetBundle> O_OBJECT_SLIC3R_T +PresetHints* O_OBJECT_SLIC3R +Ref<PresetHints> O_OBJECT_SLIC3R_T Axis T_UV ExtrusionLoopRole T_UV diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index d9d547b2b..d6c74659c 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -191,6 +191,8 @@ %typemap{ModelInstancePtrs*}; %typemap{Ref<ModelInstancePtrs>}{simple}; %typemap{Clone<ModelInstancePtrs>}{simple}; +%typemap{AppConfig*}; +%typemap{Ref<AppConfig>}{simple}; %typemap{GLShader*}; %typemap{Ref<GLShader>}{simple}; %typemap{GLVolume*}; @@ -203,6 +205,8 @@ %typemap{Ref<PresetCollection>}{simple}; %typemap{PresetBundle*}; %typemap{Ref<PresetBundle>}{simple}; +%typemap{PresetHints*}; +%typemap{Ref<PresetHints>}{simple}; %typemap{PrintRegionPtrs*}; %typemap{PrintObjectPtrs*}; |