diff --git a/doc/How to build - Windows.md b/doc/How to build - Windows.md index 104720b34..8b7d37cdd 100644 --- a/doc/How to build - Windows.md +++ b/doc/How to build - Windows.md @@ -1,8 +1,9 @@ # Building Slic3r PE on Microsoft Windows -The currently supported way of building Slic3r PE on Windows is with MS Visual Studio 2013 +The currently supported way of building Slic3r PE on Windows is with CMake and MS Visual Studio 2013 using our Perl binary distribution (compiled from official Perl sources). You can use the free [Visual Studio 2013 Community Edition](https://www.visualstudio.com/vs/older-downloads/). +CMake installer can be downloaded from [the official website](https://cmake.org/download/). Other setups (such as mingw + Strawberry Perl) _may_ work, but we cannot guarantee this will work and cannot provide guidance. @@ -26,8 +27,8 @@ Apart from wxWidgets and Perl, you will also need additional dependencies: We have prepared a binary package of the listed libraries: - - 32 bit: [slic3r-destdir-32.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=slic3r-destdir-32.7z) - - 64 bit: [slic3r-destdir-64.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=slic3r-destdir-64.7z) + - 32 bit: [slic3r-destdir-32.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=2%2Fslic3r-destdir-32.7z) + - 64 bit: [slic3r-destdir-64.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=2%2Fslic3r-destdir-64.7z) It is recommended you unpack this package into `C:\local\` as the environment setup script expects it there. diff --git a/doc/deps-build/windows/slic3r-makedeps.ps1 b/doc/deps-build/windows/slic3r-makedeps.ps1 index 8b39cae30..e256d61e4 100644 --- a/doc/deps-build/windows/slic3r-makedeps.ps1 +++ b/doc/deps-build/windows/slic3r-makedeps.ps1 @@ -37,7 +37,7 @@ if ($destdir -eq "") { } $BOOST = 'boost_1_63_0' -$CURL = 'curl-7.28.0' +$CURL = 'curl-7.58.0' $TBB_SHA = 'a0dc9bf76d0120f917b641ed095360448cabc85b' $TBB = "tbb-$TBB_SHA" diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 74456ca2f..4ec388c14 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -106,12 +106,11 @@ sub OnInit { # Suppress the '- default -' presets. $self->{preset_bundle}->set_default_suppressed($self->{app_config}->get('no_defaults') ? 1 : 0); - eval { $self->{preset_bundle}->load_presets }; + eval { $self->{preset_bundle}->load_presets($self->{app_config}); }; if ($@) { warn $@ . "\n"; show_error(undef, $@); } - eval { $self->{preset_bundle}->load_selections($self->{app_config}) }; $run_wizard = 1 if $self->{preset_bundle}->has_defauls_only; Slic3r::GUI::set_preset_bundle($self->{preset_bundle}); diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index c00484fde..c05de06c6 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -1316,12 +1316,18 @@ sub Render { $self->mark_volumes_for_layer_height; $self->volumes->set_print_box($self->bed_bounding_box->x_min, $self->bed_bounding_box->y_min, 0.0, $self->bed_bounding_box->x_max, $self->bed_bounding_box->y_max, $self->{config}->get('max_print_height')); $self->volumes->update_outside_state($self->{config}, 0); + # do not cull backfaces to show broken geometry, if any + glDisable(GL_CULL_FACE); } $self->{plain_shader}->enable if $self->{plain_shader}; $self->volumes->render_VBOs; $self->{plain_shader}->disable; + glEnable(GL_CULL_FACE) if ($self->enable_picking); } else { + # do not cull backfaces to show broken geometry, if any + glDisable(GL_CULL_FACE) if ($self->enable_picking); $self->volumes->render_legacy; + glEnable(GL_CULL_FACE) if ($self->enable_picking); } # draw cutting plane @@ -1358,6 +1364,9 @@ sub draw_volumes { # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking. my ($self, $fakecolor) = @_; + # do not cull backfaces to show broken geometry, if any + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -1385,6 +1394,8 @@ sub draw_volumes { } glDisableClientState(GL_NORMAL_ARRAY); glDisable(GL_BLEND); + + glEnable(GL_CULL_FACE); if (defined $self->cutting_plane_z) { glLineWidth(2); @@ -1812,6 +1823,7 @@ sub _fragment_shader_Gouraud { return <<'FRAGMENT'; #version 110 +const vec4 OUTSIDE_COLOR = vec4(0.24, 0.42, 0.62, 1.0); const vec3 ZERO = vec3(0.0, 0.0, 0.0); // x = tainted, y = specular; @@ -1824,13 +1836,11 @@ uniform vec4 uniform_color; void main() { - gl_FragColor = vec4(intensity.y, intensity.y, intensity.y, 0.0) + uniform_color * intensity.x; + // if the fragment is outside the print volume use predefined color + vec4 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? OUTSIDE_COLOR : uniform_color; - // if the fragment is outside the print volume darken it and set it as transparent - if (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) - gl_FragColor = vec4(mix(gl_FragColor.xyz, ZERO, 0.5), 0.5 * uniform_color.a); - else - gl_FragColor.a = uniform_color.a; + gl_FragColor = vec4(intensity.y, intensity.y, intensity.y, 0.0) + color * intensity.x; + gl_FragColor.a = color.a; } FRAGMENT diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 4868ed7e2..b2f51b9e1 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -666,7 +666,13 @@ sub config_wizard { wxTheApp->{preset_bundle}->load_config('My Settings', $result->{config}); } else { # Wizard returned a name of a preset bundle bundled with the installation. Unpack it. - wxTheApp->{preset_bundle}->load_configbundle($directory . '/' . $result->{preset_name} . '.ini'); + wxTheApp->{preset_bundle}->install_vendor_configbundle($directory . '/' . $result->{preset_name} . '.ini'); + # Reset the print / filament / printer selections, so that following line will select some sensible defaults. + if ($fresh_start) { + wxTheApp->{app_config}->reset_selections; + } + # Reload all presets after the vendor config bundle has been installed. + wxTheApp->{preset_bundle}->load_presets(wxTheApp->{app_config}); } }; Slic3r::GUI::catch_error($self) and return; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index d3513897f..14c2d66ae 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -98,6 +98,16 @@ sub new { $self->update; }; + # callback to enable/disable action buttons + my $enable_action_buttons = sub { + my ($enable) = @_; + $self->{btn_export_gcode}->Enable($enable); + $self->{btn_reslice}->Enable($enable); + $self->{btn_print}->Enable($enable); + $self->{btn_send_gcode}->Enable($enable); + $self->{btn_export_stl}->Enable($enable); + }; + # Initialize 3D plater if ($Slic3r::GUI::have_OpenGL) { $self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{print}, $self->{config}); @@ -113,6 +123,7 @@ sub new { $self->{canvas3D}->set_on_decrease_objects(sub { $self->decrease() }); $self->{canvas3D}->set_on_remove_object(sub { $self->remove() }); $self->{canvas3D}->set_on_instances_moved($on_instances_moved); + $self->{canvas3D}->set_on_enable_action_buttons($enable_action_buttons); $self->{canvas3D}->use_plain_shader(1); $self->{canvas3D}->set_on_wipe_tower_moved(sub { my ($new_pos_3f) = @_; @@ -1470,7 +1481,11 @@ sub on_export_completed { # Send $self->{send_gcode_file} to OctoPrint. if ($send_gcode) { my $op = Slic3r::OctoPrint->new($self->{config}); - $op->send_gcode($self->GetId(), $PROGRESS_BAR_EVENT, $ERROR_EVENT, $self->{send_gcode_file}); + if ($op->send_gcode($self->{send_gcode_file})) { + $self->statusbar->SetStatusText(L("OctoPrint upload finished.")); + } else { + $self->statusbar->SetStatusText(""); + } } $self->{print_file} = undef; @@ -1560,7 +1575,7 @@ sub export_amf { return if !@{$self->{objects}}; # Ask user for a file name to write into. my $output_file = $self->_get_export_file('AMF') or return; - my $res = $self->{model}->store_amf($output_file, $self->{print}); + my $res = $self->{model}->store_amf($output_file, $self->{print}, $self->{export_option}); if ($res) { $self->statusbar->SetStatusText(L("AMF file exported to ").$output_file); @@ -1576,7 +1591,7 @@ sub export_3mf { return if !@{$self->{objects}}; # Ask user for a file name to write into. my $output_file = $self->_get_export_file('3MF') or return; - my $res = $self->{model}->store_3mf($output_file, $self->{print}); + my $res = $self->{model}->store_3mf($output_file, $self->{print}, $self->{export_option}); if ($res) { $self->statusbar->SetStatusText(L("3MF file exported to ").$output_file); @@ -1618,11 +1633,13 @@ sub _get_export_file { $output_file =~ s/\.[gG][cC][oO][dD][eE]$/$suffix/; my $dlg = Wx::FileDialog->new($self, L("Save ").$format.L(" file as:"), dirname($output_file), basename($output_file), &Slic3r::GUI::FILE_WILDCARDS->{$wildcard}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + Slic3r::GUI::add_export_option($dlg, $format); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return undef; } $output_file = $dlg->GetPath; + $self->{export_option} = Slic3r::GUI::get_export_option($dlg); $dlg->Destroy; return $output_file; } diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm index 37e1321ae..09cc02930 100644 --- a/lib/Slic3r/GUI/Plater/3D.pm +++ b/lib/Slic3r/GUI/Plater/3D.pm @@ -12,7 +12,7 @@ use Wx::Locale gettext => 'L'; __PACKAGE__->mk_accessors(qw( on_arrange on_rotate_object_left on_rotate_object_right on_scale_object_uniformly - on_remove_object on_increase_objects on_decrease_objects)); + on_remove_object on_increase_objects on_decrease_objects on_enable_action_buttons)); sub new { my $class = shift; @@ -176,6 +176,11 @@ sub set_on_model_update { $self->on_model_update($cb); } +sub set_on_enable_action_buttons { + my ($self, $cb) = @_; + $self->on_enable_action_buttons($cb); +} + sub reload_scene { my ($self, $force) = @_; @@ -217,10 +222,12 @@ sub reload_scene { if (!$self->{model}->fits_print_volume($self->{config})) { $self->set_warning_enabled(1); Slic3r::GUI::_3DScene::generate_warning_texture(L("Detected object outside print volume")); + $self->on_enable_action_buttons->(0) if ($self->on_enable_action_buttons); } else { $self->set_warning_enabled(0); $self->volumes->update_outside_state($self->{config}, 1); Slic3r::GUI::_3DScene::reset_warning_texture(); + $self->on_enable_action_buttons->(1) if ($self->on_enable_action_buttons); } } } diff --git a/lib/Slic3r/GUI/Plater/3DPreview.pm b/lib/Slic3r/GUI/Plater/3DPreview.pm index 8f3fa49f5..537cb0c8f 100644 --- a/lib/Slic3r/GUI/Plater/3DPreview.pm +++ b/lib/Slic3r/GUI/Plater/3DPreview.pm @@ -69,6 +69,7 @@ sub new { $choice_view_type->Append(L("Height")); $choice_view_type->Append(L("Width")); $choice_view_type->Append(L("Speed")); + $choice_view_type->Append(L("Volumetric flow rate")); $choice_view_type->Append(L("Tool")); $choice_view_type->SetSelection(0); diff --git a/resources/icons/lock.png b/resources/icons/lock.png new file mode 100644 index 000000000..2ebc4f6f9 Binary files /dev/null and b/resources/icons/lock.png differ diff --git a/resources/icons/lock_open.png b/resources/icons/lock_open.png new file mode 100644 index 000000000..a471765ff Binary files /dev/null and b/resources/icons/lock_open.png differ diff --git a/resources/icons/sys_lock.png b/resources/icons/sys_lock.png new file mode 100644 index 000000000..0519c44a1 Binary files /dev/null and b/resources/icons/sys_lock.png differ diff --git a/resources/icons/sys_unlock.png b/resources/icons/sys_unlock.png new file mode 100644 index 000000000..189bc24e7 Binary files /dev/null and b/resources/icons/sys_unlock.png differ diff --git a/resources/localization/uk/Slic3rPE.mo b/resources/localization/uk/Slic3rPE.mo index c980ae64d..7ced15dc8 100644 Binary files a/resources/localization/uk/Slic3rPE.mo and b/resources/localization/uk/Slic3rPE.mo differ diff --git a/resources/localization/uk/Slic3rPE_uk.po b/resources/localization/uk/Slic3rPE_uk.po index 60354fd61..24b12095d 100644 --- a/resources/localization/uk/Slic3rPE_uk.po +++ b/resources/localization/uk/Slic3rPE_uk.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-02-28 13:53+0100\n" -"PO-Revision-Date: 2018-02-28 14:04+0100\n" +"PO-Revision-Date: 2018-03-21 16:01+0100\n" "Last-Translator: Oleksandra Iushchenko \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -3330,7 +3330,7 @@ msgstr "" #: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:137 msgid "None" -msgstr "Жадне" +msgstr "Жодне" #: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:138 #: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:80 @@ -3718,11 +3718,11 @@ msgstr "Файл" #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:419 msgid "&Plater" -msgstr "Платер" +msgstr "&Платер" #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:420 msgid "&Object" -msgstr "Об'єкт" +msgstr "&Об'єкт" #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:421 msgid "&Window" diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index f7c21bab8..e8cc47548 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -912,10 +912,10 @@ use_volumetric_e = 0 variable_layer_height = 1 wipe = 1 z_offset = 0 -#printer_model = MK2S -#printer_variant = 0.4 -#default_print_profile = 0.15mm OPTIMAL -#default_filament_profile = Prusa PLA +printer_model = MK2S +printer_variant = 0.4 +default_print_profile = 0.15mm OPTIMAL +default_filament_profile = Prusa PLA [printer:*multimaterial*] inherits = *common* @@ -931,7 +931,7 @@ retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 80 single_extruder_multi_material = 1 -#printer_model = MK2SMM +printer_model = MK2SMM [printer:*mm-single*] inherits = *multimaterial* @@ -959,15 +959,15 @@ nozzle_diameter = 0.25 retract_length = 1 retract_speed = 50 variable_layer_height = 0 -#printer_variant = 0.25 -#default_print_profile = 0.10mm DETAIL 0.25 nozzle +printer_variant = 0.25 +default_print_profile = 0.10mm DETAIL 0.25 nozzle [printer:Original Prusa i3 MK2 0.6 nozzle] inherits = *common* max_layer_height = 0.35 min_layer_height = 0.1 nozzle_diameter = 0.6 -#printer_variant = 0.6 +printer_variant = 0.6 [printer:Original Prusa i3 MK2 MM Single Mode] inherits = *mm-single* @@ -975,7 +975,7 @@ inherits = *mm-single* [printer:Original Prusa i3 MK2 MM Single Mode 0.6 nozzle] inherits = *mm-single* nozzle_diameter = 0.6 -#printer_variant = 0.6 +printer_variant = 0.6 [printer:Original Prusa i3 MK2 MultiMaterial] inherits = *mm-multi* @@ -984,7 +984,7 @@ nozzle_diameter = 0.4,0.4,0.4,0.4 [printer:Original Prusa i3 MK2 MultiMaterial 0.6 nozzle] inherits = *mm-multi* nozzle_diameter = 0.6,0.6,0.6,0.6 -#printer_variant = 0.6 +printer_variant = 0.6 [printer:Original Prusa i3 MK3] inherits = *common* @@ -992,8 +992,8 @@ end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\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\nM221 S{if layer_height==0.05}100{else}95{endif} -#printer_model = MK3 -#default_print_profile = 0.15mm OPTIMAL MK3 +printer_model = MK3 +default_print_profile = 0.15mm OPTIMAL MK3 [printer:Original Prusa i3 MK3 0.25 nozzle] inherits = *common* @@ -1002,8 +1002,8 @@ end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\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\nM221 S{if layer_height==0.05}100{else}95{endif} -#printer_model = MK3 -#default_print_profile = 0.10mm DETAIL MK3 +printer_model = MK3 +default_print_profile = 0.10mm DETAIL MK3 [printer:Original Prusa i3 MK3 0.6 nozzle] inherits = *common* @@ -1012,8 +1012,8 @@ end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\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\nM221 S{if layer_height==0.05}100{else}95{endif} -#printer_model = MK3 -#default_print_profile = 0.15mm OPTIMAL MK3 +printer_model = MK3 +default_print_profile = 0.15mm OPTIMAL MK3 [presets] print = 0.15mm OPTIMAL MK3 diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 2bf28ec01..deb0d71e9 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -54,6 +54,7 @@ add_library(libslic3r STATIC ${LIBDIR}/libslic3r/ExtrusionEntityCollection.hpp ${LIBDIR}/libslic3r/ExtrusionSimulator.cpp ${LIBDIR}/libslic3r/ExtrusionSimulator.hpp + ${LIBDIR}/libslic3r/FileParserError.hpp ${LIBDIR}/libslic3r/Fill/Fill.cpp ${LIBDIR}/libslic3r/Fill/Fill.hpp ${LIBDIR}/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -173,6 +174,8 @@ add_library(libslic3r STATIC add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/AppConfig.cpp ${LIBDIR}/slic3r/GUI/AppConfig.hpp + ${LIBDIR}/slic3r/GUI/BitmapCache.cpp + ${LIBDIR}/slic3r/GUI/BitmapCache.hpp ${LIBDIR}/slic3r/GUI/3DScene.cpp ${LIBDIR}/slic3r/GUI/3DScene.hpp ${LIBDIR}/slic3r/GUI/GLShader.cpp @@ -203,6 +206,10 @@ add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/wxExtensions.hpp ${LIBDIR}/slic3r/GUI/BonjourDialog.cpp ${LIBDIR}/slic3r/GUI/BonjourDialog.hpp + ${LIBDIR}/slic3r/Config/Snapshot.cpp + ${LIBDIR}/slic3r/Config/Snapshot.hpp + ${LIBDIR}/slic3r/Config/Version.cpp + ${LIBDIR}/slic3r/Config/Version.hpp ${LIBDIR}/slic3r/Utils/ASCIIFolding.cpp ${LIBDIR}/slic3r/Utils/ASCIIFolding.hpp ${LIBDIR}/slic3r/Utils/Http.cpp @@ -301,6 +308,11 @@ add_library(Shiny STATIC ${LIBDIR}/Shiny/ShinyZone.h ) +add_library(semver STATIC + ${LIBDIR}/semver/semver.h + ${LIBDIR}/semver/semver.c +) + # Generate the Slic3r Perl module (XS) typemap file. set(MyTypemap ${CMAKE_CURRENT_BINARY_DIR}/typemap) add_custom_command( diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index 1a0b8fb4a..a4eaf3072 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -185,7 +185,7 @@ void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys // This is only possible if other is of DynamicConfig type. if (ignore_nonexistent) continue; - throw UnknownOptionException(); + throw UnknownOptionException(opt_key); } const ConfigOption *other_opt = other.option(opt_key); if (other_opt != nullptr) @@ -206,6 +206,18 @@ t_config_option_keys ConfigBase::diff(const ConfigBase &other) const return diff; } +t_config_option_keys ConfigBase::equal(const ConfigBase &other) const +{ + t_config_option_keys equal; + for (const t_config_option_key &opt_key : this->keys()) { + const ConfigOption *this_opt = this->option(opt_key); + const ConfigOption *other_opt = other.option(opt_key); + if (this_opt != nullptr && other_opt != nullptr && *this_opt == *other_opt) + equal.emplace_back(opt_key); + } + return equal; +} + std::string ConfigBase::serialize(const t_config_option_key &opt_key) const { const ConfigOption* opt = this->option(opt_key); @@ -232,7 +244,7 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con // Try to deserialize the option by its name. const ConfigDef *def = this->def(); if (def == nullptr) - throw NoDefinitionException(); + throw NoDefinitionException(opt_key); 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. @@ -248,7 +260,7 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con break; } if (optdef == nullptr) - throw UnknownOptionException(); + throw UnknownOptionException(opt_key); } if (! optdef->shortcut.empty()) { @@ -278,7 +290,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const // Get option definition. const ConfigDef *def = this->def(); if (def == nullptr) - throw NoDefinitionException(); + throw NoDefinitionException(opt_key); const ConfigOptionDef *opt_def = def->get(opt_key); assert(opt_def != nullptr); // Compute absolute value over the absolute value of the base option. @@ -468,7 +480,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre // Try to create a new ConfigOption. const ConfigDef *def = this->def(); if (def == nullptr) - throw NoDefinitionException(); + throw NoDefinitionException(opt_key); const ConfigOptionDef *optdef = def->get(opt_key); if (optdef == nullptr) // throw std::runtime_error(std::string("Invalid option name: ") + opt_key); diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 3dccedbf0..6eb307c5c 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -1046,6 +1046,7 @@ public: void apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false); bool equals(const ConfigBase &other) const { return this->diff(other).empty(); } t_config_option_keys diff(const ConfigBase &other) const; + t_config_option_keys equal(const ConfigBase &other) const; std::string serialize(const t_config_option_key &opt_key) const; // Set a configuration value from a string, it will call an overridable handle_legacy() // to resolve renamed and removed configuration keys. @@ -1232,17 +1233,22 @@ protected: }; /// Specialization of std::exception to indicate that an unknown config option has been encountered. -class UnknownOptionException : public std::exception -{ +class UnknownOptionException : public std::runtime_error { public: - const char* what() const noexcept override { return "Unknown config option"; } + UnknownOptionException() : + std::runtime_error("Unknown option exception") {} + UnknownOptionException(const std::string &opt_key) : + std::runtime_error(std::string("Unknown option exception: ") + opt_key) {} }; /// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null). -class NoDefinitionException : public std::exception +class NoDefinitionException : public std::runtime_error { public: - const char* what() const noexcept override { return "No config definition"; } + NoDefinitionException() : + std::runtime_error("No definition exception") {} + NoDefinitionException(const std::string &opt_key) : + std::runtime_error(std::string("No definition exception: ") + opt_key) {} }; } diff --git a/xs/src/libslic3r/FileParserError.hpp b/xs/src/libslic3r/FileParserError.hpp new file mode 100644 index 000000000..82a6b328e --- /dev/null +++ b/xs/src/libslic3r/FileParserError.hpp @@ -0,0 +1,48 @@ +#ifndef slic3r_FileParserError_hpp_ +#define slic3r_FileParserError_hpp_ + +#include "libslic3r.h" + +#include +#include + +namespace Slic3r { + +// Generic file parser error, mostly copied from boost::property_tree::file_parser_error +class file_parser_error: public std::runtime_error +{ +public: + file_parser_error(const std::string &msg, const std::string &file, unsigned long line = 0) : + std::runtime_error(format_what(msg, file, line)), + m_message(msg), m_filename(file), m_line(line) {} + // gcc 3.4.2 complains about lack of throw specifier on compiler + // generated dtor + ~file_parser_error() throw() {} + + // Get error message (without line and file - use what() to get full message) + std::string message() const { return m_message; } + // Get error filename + std::string filename() const { return m_filename; } + // Get error line number + unsigned long line() const { return m_line; } + +private: + std::string m_message; + std::string m_filename; + unsigned long m_line; + + // Format error message to be returned by std::runtime_error::what() + static std::string format_what(const std::string &msg, const std::string &file, unsigned long l) + { + std::stringstream stream; + stream << (file.empty() ? "" : file.c_str()); + if (l > 0) + stream << '(' << l << ')'; + stream << ": " << msg; + return stream.str(); + } +}; + +}; // Slic3r + +#endif // slic3r_FileParserError_hpp_ diff --git a/xs/src/libslic3r/Fill/FillGyroid.cpp b/xs/src/libslic3r/Fill/FillGyroid.cpp index e63ce0bfd..dbe6ec896 100644 --- a/xs/src/libslic3r/Fill/FillGyroid.cpp +++ b/xs/src/libslic3r/Fill/FillGyroid.cpp @@ -41,7 +41,7 @@ static inline Polyline make_wave_horizontal( polyline.points.emplace_back(Point(0, coord_t(clamp(0., height, y0) * scaleFactor))); double phase_offset_sin = (z_sin < 0 ? M_PI : 0) + (flip ? 0 : M_PI); double phase_offset_cos = z_sin < 0 ? M_PI : 0.; - for (double x=0.; x < width + segmentSize; x += segmentSize) { + for (double x = 0.; x < width + segmentSize; x += segmentSize) { x = std::min(x, width); double a = cos(x + phase_offset_cos); double b = - z_sin; @@ -55,17 +55,17 @@ static inline Polyline make_wave_horizontal( return polyline; } -static Polylines make_gyroid_waves(double gridZ, double density, double layer_width, double width, double height) +static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height) { - double scaleFactor = scale_(layer_width) / density; - double segmentSize = 0.5 * density; + double scaleFactor = scale_(line_spacing) / density_adjusted; + double segmentSize = 0.5 * density_adjusted; //scale factor for 5% : 8 712 388 // 1z = 10^-6 mm ? double z = gridZ / scaleFactor; double z_sin = sin(z); double z_cos = cos(z); Polylines result; - if (abs(z_sin) <= abs(z_cos)) { + if (std::abs(z_sin) <= std::abs(z_cos)) { // Vertical wave double x0 = M_PI * (int)((- 0.5 * M_PI) / M_PI - 1.); bool flip = ((int)(x0 / M_PI + 1.) & 1) != 0; @@ -74,7 +74,7 @@ static Polylines make_gyroid_waves(double gridZ, double density, double layer_wi } else { // Horizontal wave bool flip = true; - for (double y0 = 0.; y0 < width; y0 += M_PI, flip = !flip) + for (double y0 = 0.; y0 < height; y0 += M_PI, flip = !flip) result.emplace_back(make_wave_horizontal(width, height, y0, segmentSize, scaleFactor, z_cos, z_sin, flip)); } return result; @@ -87,17 +87,20 @@ void FillGyroid::_fill_surface_single( ExPolygon &expolygon, Polylines &polylines_out) { - // no rotation is supported for this infill pattern + // no rotation is supported for this infill pattern (yet) BoundingBox bb = expolygon.contour.bounding_box(); - coord_t distance = coord_t(scale_(this->spacing) / (params.density*this->scaling)); + // Density adjusted to have a good %of weight. + double density_adjusted = params.density * 1.75; + // Distance between the gyroid waves in scaled coordinates. + coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); // align bounding box to a multiple of our grid module - bb.merge(_align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance))); + bb.merge(_align_to_grid(bb.min, Point(2.*M_PI*distance, 2.*M_PI*distance))); // generate pattern Polylines polylines = make_gyroid_waves( scale_(this->z), - params.density*this->scaling, + density_adjusted, this->spacing, ceil(bb.size().x / distance) + 1., ceil(bb.size().y / distance) + 1.); diff --git a/xs/src/libslic3r/Fill/FillGyroid.hpp b/xs/src/libslic3r/Fill/FillGyroid.hpp index 758652a5c..17924b5ab 100644 --- a/xs/src/libslic3r/Fill/FillGyroid.hpp +++ b/xs/src/libslic3r/Fill/FillGyroid.hpp @@ -17,10 +17,6 @@ public: virtual bool use_bridge_flow() const { return true; } protected: - - // mult of density, to have a good %of weight for each density parameter - float scaling = 1.75; - virtual void _fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp index b34b8989e..89f9b277f 100644 --- a/xs/src/libslic3r/Format/3mf.cpp +++ b/xs/src/libslic3r/Format/3mf.cpp @@ -1443,10 +1443,10 @@ namespace Slic3r { IdToObjectDataMap m_objects_data; public: - bool save_model_to_file(const std::string& filename, Model& model, const Print& print); + bool save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config); private: - bool _save_model_to_file(const std::string& filename, Model& model, const Print& print); + bool _save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config); bool _add_content_types_file_to_archive(mz_zip_archive& archive); bool _add_relationships_file_to_archive(mz_zip_archive& archive); bool _add_model_file_to_archive(mz_zip_archive& archive, Model& model); @@ -1457,13 +1457,13 @@ namespace Slic3r { bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model); }; - bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const Print& print) + bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config) { clear_errors(); - return _save_model_to_file(filename, model, print); + return _save_model_to_file(filename, model, print, export_print_config); } - bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const Print& print) + bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config) { mz_zip_archive archive; mz_zip_zero_struct(&archive); @@ -1502,11 +1502,14 @@ namespace Slic3r { } // adds slic3r print config file - if (!_add_print_config_file_to_archive(archive, print)) + if (export_print_config) { - mz_zip_writer_end(&archive); - boost::filesystem::remove(filename); - return false; + if (!_add_print_config_file_to_archive(archive, print)) + { + mz_zip_writer_end(&archive); + boost::filesystem::remove(filename); + return false; + } } // adds slic3r model config file @@ -1863,13 +1866,13 @@ namespace Slic3r { return res; } - bool store_3mf(const char* path, Model* model, Print* print) + bool store_3mf(const char* path, Model* model, Print* print, bool export_print_config) { if ((path == nullptr) || (model == nullptr) || (print == nullptr)) return false; _3MF_Exporter exporter; - bool res = exporter.save_model_to_file(path, *model, *print); + bool res = exporter.save_model_to_file(path, *model, *print, export_print_config); if (!res) exporter.log_errors(); diff --git a/xs/src/libslic3r/Format/3mf.hpp b/xs/src/libslic3r/Format/3mf.hpp index 9b48c860b..85bc812e3 100644 --- a/xs/src/libslic3r/Format/3mf.hpp +++ b/xs/src/libslic3r/Format/3mf.hpp @@ -12,7 +12,7 @@ namespace Slic3r { // Save the given model and the config data contained in the given Print into a 3mf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices - extern bool store_3mf(const char* path, Model* model, Print* print); + extern bool store_3mf(const char* path, Model* model, Print* print, bool export_print_config); }; // namespace Slic3r diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp index a52dd532a..98683cd8a 100644 --- a/xs/src/libslic3r/Format/AMF.cpp +++ b/xs/src/libslic3r/Format/AMF.cpp @@ -576,8 +576,7 @@ bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) return false; } - std::string internal_amf_filename = boost::ireplace_last_copy(boost::filesystem::path(path).filename().string(), ".zip.amf", ".amf"); - if (internal_amf_filename != stat.m_filename) + if (!boost::iends_with(stat.m_filename, ".amf")) { printf("Found invalid internal filename\n"); mz_zip_reader_end(&archive); @@ -644,15 +643,20 @@ bool load_amf(const char *path, PresetBundle* bundle, Model *model) return false; } -bool store_amf(const char *path, Model *model, Print* print) +bool store_amf(const char *path, Model *model, Print* print, bool export_print_config) { if ((path == nullptr) || (model == nullptr) || (print == nullptr)) return false; + // forces ".zip.amf" extension + std::string export_path = path; + if (!boost::iends_with(export_path, ".zip.amf")) + export_path = boost::filesystem::path(export_path).replace_extension(".zip.amf").string(); + mz_zip_archive archive; mz_zip_zero_struct(&archive); - mz_bool res = mz_zip_writer_init_file(&archive, path, 0); + mz_bool res = mz_zip_writer_init_file(&archive, export_path.c_str(), 0); if (res == 0) return false; @@ -661,9 +665,12 @@ bool store_amf(const char *path, Model *model, Print* print) stream << "\n"; stream << "Slic3r " << SLIC3R_VERSION << "\n"; - std::string config = "\n"; - GCode::append_full_config(*print, config); - stream << "" << config << "\n"; + if (export_print_config) + { + std::string config = "\n"; + GCode::append_full_config(*print, config); + stream << "" << config << "\n"; + } for (const auto &material : model->materials) { if (material.first.empty()) @@ -767,20 +774,20 @@ bool store_amf(const char *path, Model *model, Print* print) } stream << "\n"; - std::string internal_amf_filename = boost::ireplace_last_copy(boost::filesystem::path(path).filename().string(), ".zip.amf", ".amf"); + std::string internal_amf_filename = boost::ireplace_last_copy(boost::filesystem::path(export_path).filename().string(), ".zip.amf", ".amf"); std::string out = stream.str(); if (!mz_zip_writer_add_mem(&archive, internal_amf_filename.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { mz_zip_writer_end(&archive); - boost::filesystem::remove(path); + boost::filesystem::remove(export_path); return false; } if (!mz_zip_writer_finalize_archive(&archive)) { mz_zip_writer_end(&archive); - boost::filesystem::remove(path); + boost::filesystem::remove(export_path); return false; } diff --git a/xs/src/libslic3r/Format/AMF.hpp b/xs/src/libslic3r/Format/AMF.hpp index 027ebdab3..4779e9a51 100644 --- a/xs/src/libslic3r/Format/AMF.hpp +++ b/xs/src/libslic3r/Format/AMF.hpp @@ -12,7 +12,7 @@ extern bool load_amf(const char *path, PresetBundle* bundle, Model *model); // Save the given model and the config data contained in the given Print into an amf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices -extern bool store_amf(const char *path, Model *model, Print* print); +extern bool store_amf(const char *path, Model *model, Print* print, bool export_print_config); }; // namespace Slic3r diff --git a/xs/src/libslic3r/GCode/Analyzer.cpp b/xs/src/libslic3r/GCode/Analyzer.cpp index 6530806c4..799bd6661 100644 --- a/xs/src/libslic3r/GCode/Analyzer.cpp +++ b/xs/src/libslic3r/GCode/Analyzer.cpp @@ -97,8 +97,8 @@ GCodeAnalyzer::GCodeAnalyzer() void GCodeAnalyzer::reset() { _set_units(Millimeters); - _set_positioning_xyz_type(Absolute); - _set_positioning_e_type(Relative); + _set_global_positioning_type(Absolute); + _set_e_local_positioning_type(Absolute); _set_extrusion_role(erNone); _set_extruder_id(DEFAULT_EXTRUDER_ID); _set_mm3_per_mm(Default_mm3_per_mm); @@ -177,6 +177,16 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi _processG1(line); break; } + case 10: // Retract + { + _processG10(line); + break; + } + case 11: // Unretract + { + _processG11(line); + break; + } case 22: // Firmware controlled Retract { _processG22(line); @@ -237,13 +247,13 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi } // Returns the new absolute position on the given axis in dependence of the given parameters -float axis_absolute_position_from_G1_line(GCodeAnalyzer::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeAnalyzer::EUnits units, GCodeAnalyzer::EPositioningType type, float current_absolute_position) +float axis_absolute_position_from_G1_line(GCodeAnalyzer::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeAnalyzer::EUnits units, bool is_relative, float current_absolute_position) { float lengthsScaleFactor = (units == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f; if (lineG1.has(Slic3r::Axis(axis))) { float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor; - return (type == GCodeAnalyzer::Absolute) ? ret : current_absolute_position + ret; + return is_relative ? current_absolute_position + ret : ret; } else return current_absolute_position; @@ -256,7 +266,11 @@ void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line) float new_pos[Num_Axis]; for (unsigned char a = X; a < Num_Axis; ++a) { - new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, (a == E) ? _get_positioning_e_type() : _get_positioning_xyz_type(), _get_axis_position((EAxis)a)); + bool is_relative = (_get_global_positioning_type() == Relative); + if (a == E) + is_relative |= (_get_e_local_positioning_type() == Relative); + + new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, is_relative, _get_axis_position((EAxis)a)); } // updates feedrate from line, if present @@ -305,6 +319,18 @@ void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line) _store_move(type); } +void GCodeAnalyzer::_processG10(const GCodeReader::GCodeLine& line) +{ + // stores retract move + _store_move(GCodeMove::Retract); +} + +void GCodeAnalyzer::_processG11(const GCodeReader::GCodeLine& line) +{ + // stores unretract move + _store_move(GCodeMove::Unretract); +} + void GCodeAnalyzer::_processG22(const GCodeReader::GCodeLine& line) { // stores retract move @@ -319,12 +345,12 @@ void GCodeAnalyzer::_processG23(const GCodeReader::GCodeLine& line) void GCodeAnalyzer::_processG90(const GCodeReader::GCodeLine& line) { - _set_positioning_xyz_type(Absolute); + _set_global_positioning_type(Absolute); } void GCodeAnalyzer::_processG91(const GCodeReader::GCodeLine& line) { - _set_positioning_xyz_type(Relative); + _set_global_positioning_type(Relative); } void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line) @@ -367,12 +393,12 @@ void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line) void GCodeAnalyzer::_processM82(const GCodeReader::GCodeLine& line) { - _set_positioning_e_type(Absolute); + _set_e_local_positioning_type(Absolute); } void GCodeAnalyzer::_processM83(const GCodeReader::GCodeLine& line) { - _set_positioning_e_type(Relative); + _set_e_local_positioning_type(Relative); } void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) @@ -466,24 +492,24 @@ GCodeAnalyzer::EUnits GCodeAnalyzer::_get_units() const return m_state.units; } -void GCodeAnalyzer::_set_positioning_xyz_type(GCodeAnalyzer::EPositioningType type) +void GCodeAnalyzer::_set_global_positioning_type(GCodeAnalyzer::EPositioningType type) { - m_state.positioning_xyz_type = type; + m_state.global_positioning_type = type; } -GCodeAnalyzer::EPositioningType GCodeAnalyzer::_get_positioning_xyz_type() const +GCodeAnalyzer::EPositioningType GCodeAnalyzer::_get_global_positioning_type() const { - return m_state.positioning_xyz_type; + return m_state.global_positioning_type; } -void GCodeAnalyzer::_set_positioning_e_type(GCodeAnalyzer::EPositioningType type) +void GCodeAnalyzer::_set_e_local_positioning_type(GCodeAnalyzer::EPositioningType type) { - m_state.positioning_e_type = type; + m_state.e_local_positioning_type = type; } -GCodeAnalyzer::EPositioningType GCodeAnalyzer::_get_positioning_e_type() const +GCodeAnalyzer::EPositioningType GCodeAnalyzer::_get_e_local_positioning_type() const { - return m_state.positioning_e_type; + return m_state.e_local_positioning_type; } void GCodeAnalyzer::_set_extrusion_role(ExtrusionRole extrusion_role) @@ -648,14 +674,16 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ float z = FLT_MAX; Polyline polyline; Pointf3 position(FLT_MAX, FLT_MAX, FLT_MAX); + float volumetric_rate = FLT_MAX; GCodePreviewData::Range height_range; GCodePreviewData::Range width_range; GCodePreviewData::Range feedrate_range; + GCodePreviewData::Range volumetric_rate_range; // constructs the polylines while traversing the moves for (const GCodeMove& move : extrude_moves->second) { - if ((data != move.data) || (data.feedrate != move.data.feedrate) || (z != move.start_position.z) || (position != move.start_position)) + if ((data != move.data) || (z != move.start_position.z) || (position != move.start_position) || (volumetric_rate != move.data.feedrate * (float)move.data.mm3_per_mm)) { // store current polyline polyline.remove_duplicate_points(); @@ -671,9 +699,11 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ // update current values data = move.data; z = move.start_position.z; + volumetric_rate = move.data.feedrate * (float)move.data.mm3_per_mm; height_range.update_from(move.data.height); width_range.update_from(move.data.width); feedrate_range.update_from(move.data.feedrate); + volumetric_rate_range.update_from(volumetric_rate); } else // append end vertex of the move to current polyline @@ -688,9 +718,10 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ Helper::store_polyline(polyline, data, z, preview_data); // updates preview ranges data - preview_data.extrusion.ranges.height.set_from(height_range); - preview_data.extrusion.ranges.width.set_from(width_range); - preview_data.extrusion.ranges.feedrate.set_from(feedrate_range); + preview_data.ranges.height.set_from(height_range); + preview_data.ranges.width.set_from(width_range); + preview_data.ranges.feedrate.set_from(feedrate_range); + preview_data.ranges.volumetric_rate.set_from(volumetric_rate_range); } void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) @@ -717,6 +748,10 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) float feedrate = FLT_MAX; unsigned int extruder_id = -1; + GCodePreviewData::Range height_range; + GCodePreviewData::Range width_range; + GCodePreviewData::Range feedrate_range; + // constructs the polylines while traversing the moves for (const GCodeMove& move : travel_moves->second) { @@ -745,11 +780,19 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) type = move_type; feedrate = move.data.feedrate; extruder_id = move.data.extruder_id; + height_range.update_from(move.data.height); + width_range.update_from(move.data.width); + feedrate_range.update_from(move.data.feedrate); } // store last polyline polyline.remove_duplicate_points(); Helper::store_polyline(polyline, type, direction, feedrate, extruder_id, preview_data); + + // updates preview ranges data + preview_data.ranges.height.set_from(height_range); + preview_data.ranges.width.set_from(width_range); + preview_data.ranges.feedrate.set_from(feedrate_range); } void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data) diff --git a/xs/src/libslic3r/GCode/Analyzer.hpp b/xs/src/libslic3r/GCode/Analyzer.hpp index 7939d432d..03dbab338 100644 --- a/xs/src/libslic3r/GCode/Analyzer.hpp +++ b/xs/src/libslic3r/GCode/Analyzer.hpp @@ -90,8 +90,8 @@ private: struct State { EUnits units; - EPositioningType positioning_xyz_type; - EPositioningType positioning_e_type; + EPositioningType global_positioning_type; + EPositioningType e_local_positioning_type; Metadata data; Pointf3 start_position; float start_extrusion; @@ -127,6 +127,12 @@ private: // Move void _processG1(const GCodeReader::GCodeLine& line); + // Retract + void _processG10(const GCodeReader::GCodeLine& line); + + // Unretract + void _processG11(const GCodeReader::GCodeLine& line); + // Firmware controlled Retract void _processG22(const GCodeReader::GCodeLine& line); @@ -170,11 +176,11 @@ private: void _set_units(EUnits units); EUnits _get_units() const; - void _set_positioning_xyz_type(EPositioningType type); - EPositioningType _get_positioning_xyz_type() const; + void _set_global_positioning_type(EPositioningType type); + EPositioningType _get_global_positioning_type() const; - void _set_positioning_e_type(EPositioningType type); - EPositioningType _get_positioning_e_type() const; + void _set_e_local_positioning_type(EPositioningType type); + EPositioningType _get_e_local_positioning_type() const; void _set_extrusion_role(ExtrusionRole extrusion_role); ExtrusionRole _get_extrusion_role() const; diff --git a/xs/src/libslic3r/GCode/PreviewData.cpp b/xs/src/libslic3r/GCode/PreviewData.cpp index 1923505e4..69fd3524d 100644 --- a/xs/src/libslic3r/GCode/PreviewData.cpp +++ b/xs/src/libslic3r/GCode/PreviewData.cpp @@ -85,6 +85,12 @@ void GCodePreviewData::Range::update_from(float value) max = std::max(max, value); } +void GCodePreviewData::Range::update_from(const Range& other) +{ + min = std::min(min, other.min); + max = std::max(max, other.max); +} + void GCodePreviewData::Range::set_from(const Range& other) { min = other.min; @@ -158,9 +164,6 @@ void GCodePreviewData::Extrusion::set_default() view_type = Default_View_Type; ::memcpy((void*)role_colors, (const void*)Default_Extrusion_Role_Colors, Num_Extrusion_Roles * sizeof(Color)); - ::memcpy((void*)ranges.height.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); - ::memcpy((void*)ranges.width.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); - ::memcpy((void*)ranges.feedrate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); for (unsigned int i = 0; i < Num_Extrusion_Roles; ++i) { @@ -198,6 +201,7 @@ void GCodePreviewData::Travel::set_default() width = Default_Width; height = Default_Height; ::memcpy((void*)type_colors, (const void*)Default_Type_Colors, Num_Types * sizeof(Color)); + is_visible = false; } @@ -228,6 +232,11 @@ GCodePreviewData::GCodePreviewData() void GCodePreviewData::set_default() { + ::memcpy((void*)ranges.height.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); + ::memcpy((void*)ranges.width.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); + ::memcpy((void*)ranges.feedrate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); + ::memcpy((void*)ranges.volumetric_rate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); + extrusion.set_default(); travel.set_default(); retraction.set_default(); @@ -237,6 +246,10 @@ void GCodePreviewData::set_default() void GCodePreviewData::reset() { + ranges.width.reset(); + ranges.height.reset(); + ranges.feedrate.reset(); + ranges.volumetric_rate.reset(); extrusion.layers.clear(); travel.polylines.clear(); retraction.positions.clear(); @@ -253,19 +266,24 @@ const GCodePreviewData::Color& GCodePreviewData::get_extrusion_role_color(Extrus return extrusion.role_colors[role]; } -const GCodePreviewData::Color& GCodePreviewData::get_extrusion_height_color(float height) const +const GCodePreviewData::Color& GCodePreviewData::get_height_color(float height) const { - return extrusion.ranges.height.get_color_at(height); + return ranges.height.get_color_at(height); } -const GCodePreviewData::Color& GCodePreviewData::get_extrusion_width_color(float width) const +const GCodePreviewData::Color& GCodePreviewData::get_width_color(float width) const { - return extrusion.ranges.width.get_color_at(width); + return ranges.width.get_color_at(width); } -const GCodePreviewData::Color& GCodePreviewData::get_extrusion_feedrate_color(float feedrate) const +const GCodePreviewData::Color& GCodePreviewData::get_feedrate_color(float feedrate) const { - return extrusion.ranges.feedrate.get_color_at(feedrate); + return ranges.feedrate.get_color_at(feedrate); +} + +const GCodePreviewData::Color& GCodePreviewData::get_volumetric_rate_color(float rate) const +{ + return ranges.volumetric_rate.get_color_at(rate); } void GCodePreviewData::set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha) @@ -334,6 +352,8 @@ std::string GCodePreviewData::get_legend_title() const return L("Width (mm)"); case Extrusion::Feedrate: return L("Speed (mm/s)"); + case Extrusion::VolumetricRate: + return L("Volumetric flow rate (mm3/s)"); case Extrusion::Tool: return L("Tool"); } @@ -348,10 +368,11 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: static void FillListFromRange(LegendItemsList& list, const Range& range, unsigned int decimals, float scale_factor) { list.reserve(Range::Colors_Count); + float step = range.step_size(); for (unsigned int i = 0; i < Range::Colors_Count; ++i) { - char buf[32]; + char buf[1024]; sprintf(buf, "%.*f/%.*f", decimals, scale_factor * (range.min + (float)i * step), decimals, scale_factor * (range.min + (float)(i + 1) * step)); list.emplace_back(buf, range.colors[i]); } @@ -377,17 +398,22 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: } case Extrusion::Height: { - Helper::FillListFromRange(items, extrusion.ranges.height, 3, 1.0f); + Helper::FillListFromRange(items, ranges.height, 3, 1.0f); break; } case Extrusion::Width: { - Helper::FillListFromRange(items, extrusion.ranges.width, 3, 1.0f); + Helper::FillListFromRange(items, ranges.width, 3, 1.0f); break; } case Extrusion::Feedrate: { - Helper::FillListFromRange(items, extrusion.ranges.feedrate, 0, 1.0f); + Helper::FillListFromRange(items, ranges.feedrate, 0, 1.0f); + break; + } + case Extrusion::VolumetricRate: + { + Helper::FillListFromRange(items, ranges.volumetric_rate, 3, 1.0f); break; } case Extrusion::Tool: diff --git a/xs/src/libslic3r/GCode/PreviewData.hpp b/xs/src/libslic3r/GCode/PreviewData.hpp index 9fb2dc464..e9c5f7515 100644 --- a/xs/src/libslic3r/GCode/PreviewData.hpp +++ b/xs/src/libslic3r/GCode/PreviewData.hpp @@ -37,6 +37,7 @@ public: void reset(); bool empty() const; void update_from(float value); + void update_from(const Range& other); void set_from(const Range& other); float step_size() const; @@ -44,6 +45,14 @@ public: const Color& get_color_at_max() const; }; + struct Ranges + { + Range height; + Range width; + Range feedrate; + Range volumetric_rate; + }; + struct LegendItem { std::string text; @@ -62,6 +71,7 @@ public: Height, Width, Feedrate, + VolumetricRate, Tool, Num_View_Types }; @@ -71,13 +81,6 @@ public: static const std::string Default_Extrusion_Role_Names[Num_Extrusion_Roles]; static const EViewType Default_View_Type; - struct Ranges - { - Range height; - Range width; - Range feedrate; - }; - struct Layer { float z; @@ -91,7 +94,6 @@ public: EViewType view_type; Color role_colors[Num_Extrusion_Roles]; std::string role_names[Num_Extrusion_Roles]; - Ranges ranges; LayersList layers; unsigned int role_flags; @@ -178,6 +180,7 @@ public: Retraction retraction; Retraction unretraction; Shell shell; + Ranges ranges; GCodePreviewData(); @@ -186,9 +189,10 @@ public: bool empty() const; const Color& get_extrusion_role_color(ExtrusionRole role) const; - const Color& get_extrusion_height_color(float height) const; - const Color& get_extrusion_width_color(float width) const; - const Color& get_extrusion_feedrate_color(float feedrate) const; + const Color& get_height_color(float height) const; + const Color& get_width_color(float width) const; + const Color& get_feedrate_color(float feedrate) const; + const Color& get_volumetric_rate_color(float rate) const; void set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha); void set_extrusion_paths_colors(const std::vector& colors); diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp index 912799ca9..176159ff5 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.cpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp @@ -369,24 +369,24 @@ namespace Slic3r { return _state.units; } - void GCodeTimeEstimator::set_positioning_xyz_type(GCodeTimeEstimator::EPositioningType type) + void GCodeTimeEstimator::set_global_positioning_type(GCodeTimeEstimator::EPositioningType type) { - _state.positioning_xyz_type = type; + _state.global_positioning_type = type; } - GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_positioning_xyz_type() const + GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_global_positioning_type() const { - return _state.positioning_xyz_type; + return _state.global_positioning_type; } - void GCodeTimeEstimator::set_positioning_e_type(GCodeTimeEstimator::EPositioningType type) + void GCodeTimeEstimator::set_e_local_positioning_type(GCodeTimeEstimator::EPositioningType type) { - _state.positioning_e_type = type; + _state.e_local_positioning_type = type; } - GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_positioning_e_type() const + GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_e_local_positioning_type() const { - return _state.positioning_e_type; + return _state.e_local_positioning_type; } void GCodeTimeEstimator::add_additional_time(float timeSec) @@ -408,8 +408,8 @@ namespace Slic3r { { set_units(Millimeters); set_dialect(gcfRepRap); - set_positioning_xyz_type(Absolute); - set_positioning_e_type(Relative); + set_global_positioning_type(Absolute); + set_e_local_positioning_type(Absolute); set_feedrate(DEFAULT_FEEDRATE); set_acceleration(DEFAULT_ACCELERATION); @@ -628,13 +628,13 @@ namespace Slic3r { } // Returns the new absolute position on the given axis in dependence of the given parameters - float axis_absolute_position_from_G1_line(GCodeTimeEstimator::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeTimeEstimator::EUnits units, GCodeTimeEstimator::EPositioningType type, float current_absolute_position) + float axis_absolute_position_from_G1_line(GCodeTimeEstimator::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeTimeEstimator::EUnits units, bool is_relative, float current_absolute_position) { float lengthsScaleFactor = (units == GCodeTimeEstimator::Inches) ? INCHES_TO_MM : 1.0f; if (lineG1.has(Slic3r::Axis(axis))) { float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor; - return (type == GCodeTimeEstimator::Absolute) ? ret : current_absolute_position + ret; + return is_relative ? current_absolute_position + ret : ret; } else return current_absolute_position; @@ -647,7 +647,11 @@ namespace Slic3r { float new_pos[Num_Axis]; for (unsigned char a = X; a < Num_Axis; ++a) { - new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, (a == E) ? get_positioning_e_type() : get_positioning_xyz_type(), get_axis_position((EAxis)a)); + bool is_relative = (get_global_positioning_type() == Relative); + if (a == E) + is_relative |= (get_e_local_positioning_type() == Relative); + + new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, is_relative, get_axis_position((EAxis)a)); } // updates feedrate from line, if present @@ -865,14 +869,12 @@ namespace Slic3r { void GCodeTimeEstimator::_processG90(const GCodeReader::GCodeLine& line) { - set_positioning_xyz_type(Absolute); + set_global_positioning_type(Absolute); } void GCodeTimeEstimator::_processG91(const GCodeReader::GCodeLine& line) { - // TODO: THERE ARE DIALECT VARIANTS - - set_positioning_xyz_type(Relative); + set_global_positioning_type(Relative); } void GCodeTimeEstimator::_processG92(const GCodeReader::GCodeLine& line) @@ -922,12 +924,12 @@ namespace Slic3r { void GCodeTimeEstimator::_processM82(const GCodeReader::GCodeLine& line) { - set_positioning_e_type(Absolute); + set_e_local_positioning_type(Absolute); } void GCodeTimeEstimator::_processM83(const GCodeReader::GCodeLine& line) { - set_positioning_e_type(Relative); + set_e_local_positioning_type(Relative); } void GCodeTimeEstimator::_processM109(const GCodeReader::GCodeLine& line) diff --git a/xs/src/libslic3r/GCodeTimeEstimator.hpp b/xs/src/libslic3r/GCodeTimeEstimator.hpp index 5ad5b8d0c..8f948abd1 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.hpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.hpp @@ -61,8 +61,8 @@ namespace Slic3r { { GCodeFlavor dialect; EUnits units; - EPositioningType positioning_xyz_type; - EPositioningType positioning_e_type; + EPositioningType global_positioning_type; + EPositioningType e_local_positioning_type; Axis axis[Num_Axis]; float feedrate; // mm/s float acceleration; // mm/s^2 @@ -257,11 +257,11 @@ namespace Slic3r { void set_units(EUnits units); EUnits get_units() const; - void set_positioning_xyz_type(EPositioningType type); - EPositioningType get_positioning_xyz_type() const; + void set_global_positioning_type(EPositioningType type); + EPositioningType get_global_positioning_type() const; - void set_positioning_e_type(EPositioningType type); - EPositioningType get_positioning_e_type() const; + void set_e_local_positioning_type(EPositioningType type); + EPositioningType get_e_local_positioning_type() const; void add_additional_time(float timeSec); void set_additional_time(float timeSec); diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 6f1ce1ce5..ad2ce54cd 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -450,6 +450,8 @@ bool Model::fits_print_volume(const DynamicPrintConfig* config) const BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min.x), unscale(bed_box_2D.min.y), 0.0), Pointf3(unscale(bed_box_2D.max.x), unscale(bed_box_2D.max.y), config->opt_float("max_print_height"))); + // Allow the objects to protrude below the print bed + print_volume.min.z = -1e10; return print_volume.contains(transformed_bounding_box()); } @@ -459,6 +461,8 @@ bool Model::fits_print_volume(const FullPrintConfig &config) const return true; BoundingBox bed_box_2D = get_extents(Polygon::new_scale(config.bed_shape.values)); BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min.x), unscale(bed_box_2D.min.y), 0.0), Pointf3(unscale(bed_box_2D.max.x), unscale(bed_box_2D.max.y), config.max_print_height)); + // Allow the objects to protrude below the print bed + print_volume.min.z = -1e10; return print_volume.contains(transformed_bounding_box()); } diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 7fc37d673..3249123b6 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -187,6 +187,18 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloat(0); + def = this->add("default_filament_profile", coStrings); + def->label = L("Default filament profile"); + def->tooltip = L("Default filament profile associated with the current printer profile. " + "On selection of the current printer profile, this filament profile will be activated."); + def->default_value = new ConfigOptionStrings(); + + def = this->add("default_print_profile", coString); + def->label = L("Default print profile"); + def->tooltip = L("Default print profile associated with the current printer profile. " + "On selection of the current printer profile, this print profile will be activated."); + def->default_value = new ConfigOptionString(); + def = this->add("disable_fan_first_layers", coInts); def->label = L("Disable fan for the first"); def->tooltip = L("You can set this to a positive value to disable fan at all " @@ -448,7 +460,7 @@ PrintConfigDef::PrintConfigDef() def->tooltip = L("This is only used in the Slic3r interface as a visual help."); def->cli = "filament-color=s@"; def->gui_type = "color"; - def->default_value = new ConfigOptionStrings { "#29b2b2" }; + def->default_value = new ConfigOptionStrings { "#29B2B2" }; def = this->add("filament_notes", coStrings); def->label = L("Filament notes"); @@ -795,6 +807,13 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloat(80); + def = this->add("inherits", coString); + def->label = L("Inherits profile"); + def->tooltip = L("Name of the profile, from which this profile inherits."); + def->full_width = true; + def->height = 50; + def->default_value = new ConfigOptionString(""); + def = this->add("interface_shells", coBool); def->label = L("Interface shells"); def->tooltip = L("Force the generation of solid shells between adjacent materials/volumes. " @@ -1064,7 +1083,12 @@ PrintConfigDef::PrintConfigDef() def->multiline = true; def->full_width = true; def->height = 60; - def->default_value = new ConfigOptionStrings{ "" }; + def->default_value = new ConfigOptionStrings(); + + def = this->add("printer_model", coString); + def->label = L("Printer type"); + def->tooltip = L("Type of the printer."); + def->default_value = new ConfigOptionString(); def = this->add("printer_notes", coString); def->label = L("Printer notes"); @@ -1075,6 +1099,16 @@ PrintConfigDef::PrintConfigDef() def->height = 130; def->default_value = new ConfigOptionString(""); + def = this->add("printer_vendor", coString); + def->label = L("Printer vendor"); + def->tooltip = L("Name of the printer vendor."); + def->default_value = new ConfigOptionString(); + + def = this->add("printer_variant", coString); + def->label = L("Printer variant"); + def->tooltip = L("Name of the printer variant. For example, the printer variants may be differentiated by a nozzle diameter."); + def->default_value = new ConfigOptionString(); + def = this->add("print_settings_id", coString); def->default_value = new ConfigOptionString(""); diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index 27e7fad6b..5727d6c89 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -60,9 +60,6 @@ extern std::string timestamp_str(); // 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(); } -// Encode a file into a multi-part HTTP response with a given boundary. -std::string octoprint_encode_file_send_request_content(const char *path, bool select, bool print, const char *boundary); - // Compute the next highest power of 2 of 32-bit v // http://graphics.stanford.edu/~seander/bithacks.html template diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 34b9eaa9f..733757e25 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -263,7 +263,6 @@ namespace PerlUtils { 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. @@ -272,31 +271,4 @@ std::string timestamp_str() return buf; } -std::string octoprint_encode_file_send_request_content(const char *cpath, bool select, bool print, const char *boundary) -{ - // Read the complete G-code string into a string buffer. - // It will throw if the file cannot be open or read. - std::stringstream str_stream; - { - boost::nowide::ifstream ifs(cpath); - str_stream << ifs.rdbuf(); - } - - boost::filesystem::path path(cpath); - std::string request = boundary + '\n'; - request += "Content-Disposition: form-data; name=\""; - request += path.stem().string() + "\"; filename=\"" + path.filename().string() + "\"\n"; - request += "Content-Type: application/octet-stream\n\n"; - request += str_stream.str(); - request += boundary + '\n'; - request += "Content-Disposition: form-data; name=\"select\"\n\n"; - request += select ? "true\n" : "false\n"; - request += boundary + '\n'; - request += "Content-Disposition: form-data; name=\"print\"\n\n"; - request += print ? "true\n" : "false\n"; - request += boundary + '\n'; - - return request; -} - }; // namespace Slic3r diff --git a/xs/src/semver/semver.c b/xs/src/semver/semver.c new file mode 100644 index 000000000..29bc1868d --- /dev/null +++ b/xs/src/semver/semver.c @@ -0,0 +1,617 @@ +/* + * semver.c + * + * Copyright (c) 2015-2017 Tomas Aparicio + * MIT licensed + */ + +#include +#include +#include +#include "semver.h" + +#define SLICE_SIZE 50 +#define DELIMITER "." +#define PR_DELIMITER "-" +#define MT_DELIMITER "+" +#define NUMBERS "0123456789" +#define ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define DELIMITERS DELIMITER PR_DELIMITER MT_DELIMITER +#define VALID_CHARS NUMBERS ALPHA DELIMITERS + +static const size_t MAX_SIZE = sizeof(char) * 255; +static const int MAX_SAFE_INT = (unsigned int) -1 >> 1; + +/** + * Define comparison operators, storing the + * ASCII code per each symbol in hexadecimal notation. + */ + +enum operators { + SYMBOL_GT = 0x3e, + SYMBOL_LT = 0x3c, + SYMBOL_EQ = 0x3d, + SYMBOL_TF = 0x7e, + SYMBOL_CF = 0x5e +}; + +/** + * Private helpers + */ + +/* + * Remove [begin:len-begin] from str by moving len data from begin+len to begin. + * If len is negative cut out to the end of the string. + */ +static int +strcut (char *str, int begin, int len) { + size_t l; + l = strlen(str); + + if((int)l < 0 || (int)l > MAX_SAFE_INT) return -1; + + if (len < 0) len = l - begin + 1; + if (begin + len > (int)l) len = l - begin; + memmove(str + begin, str + begin + len, l - len + 1 - begin); + + return len; +} + +static int +contains (const char c, const char *matrix, size_t len) { + size_t x; + for (x = 0; x < len; x++) + if ((char) matrix[x] == c) return 1; + return 0; +} + +static int +has_valid_chars (const char *str, const char *matrix) { + size_t i, len, mlen; + len = strlen(str); + mlen = strlen(matrix); + + for (i = 0; i < len; i++) + if (contains(str[i], matrix, mlen) == 0) + return 0; + + return 1; +} + +static int +binary_comparison (int x, int y) { + if (x == y) return 0; + if (x > y) return 1; + return -1; +} + +static int +parse_int (const char *s) { + int valid, num; + valid = has_valid_chars(s, NUMBERS); + if (valid == 0) return -1; + + num = strtol(s, NULL, 10); + if (num > MAX_SAFE_INT) return -1; + + return num; +} + +/* + * Return a string allocated on the heap with the content from sep to end and + * terminate buf at sep. + */ +static char * +parse_slice (char *buf, char sep) { + char *pr, *part; + int plen; + + /* Find separator in buf */ + pr = strchr(buf, sep); + if (pr == NULL) return NULL; + /* Length from separator to end of buf */ + plen = strlen(pr); + + /* Copy from buf into new string */ + part = calloc(plen + 1, sizeof(*part)); + if (part == NULL) return NULL; + memcpy(part, pr + 1, plen); + /* Null terminate new string */ + part[plen] = '\0'; + + /* Terminate buf where separator was */ + *pr = '\0'; + + return part; +} + +/** + * Parses a string as semver expression. + * + * Returns: + * + * `0` - Parsed successfully + * `-1` - In case of error + */ + +int +semver_parse (const char *str, semver_t *ver) { + int valid, res; + size_t len; + char *buf; + valid = semver_is_valid(str); + if (!valid) return -1; + + len = strlen(str); + buf = calloc(len + 1, sizeof(*buf)); + if (buf == NULL) return -1; + strcpy(buf, str); + + ver->metadata = parse_slice(buf, MT_DELIMITER[0]); + ver->prerelease = parse_slice(buf, PR_DELIMITER[0]); + + res = semver_parse_version(buf, ver); + free(buf); +#if DEBUG > 0 + printf("[debug] semver.c %s = %d.%d.%d, %s %s\n", str, ver->major, ver->minor, ver->patch, ver->prerelease, ver->metadata); +#endif + return res; +} + +/** + * Parses a given string as semver expression. + * + * Returns: + * + * `0` - Parsed successfully + * `-1` - Parse error or invalid + */ + +int +semver_parse_version (const char *str, semver_t *ver) { + size_t len; + int index, value; + char *slice, *next, *endptr; + slice = (char *) str; + index = 0; + + while (slice != NULL && index++ < 4) { + next = strchr(slice, DELIMITER[0]); + if (next == NULL) + len = strlen(slice); + else + len = next - slice; + if (len > SLICE_SIZE) return -1; + + /* Cast to integer and store */ + value = strtol(slice, &endptr, 10); + if (endptr != next && *endptr != '\0') return -1; + + switch (index) { + case 1: ver->major = value; break; + case 2: ver->minor = value; break; + case 3: ver->patch = value; break; + } + + /* Continue with the next slice */ + if (next == NULL) + slice = NULL; + else + slice = next + 1; + } + + return 0; +} + +static int +compare_prerelease (char *x, char *y) { + char *lastx, *lasty, *xptr, *yptr, *endptr; + int xlen, ylen, xisnum, yisnum, xnum, ynum; + int xn, yn, min, res; + if (x == NULL && y == NULL) return 0; + if (y == NULL && x) return -1; + if (x == NULL && y) return 1; + + lastx = x; + lasty = y; + xlen = strlen(x); + ylen = strlen(y); + + while (1) { + if ((xptr = strchr(lastx, DELIMITER[0])) == NULL) + xptr = x + xlen; + if ((yptr = strchr(lasty, DELIMITER[0])) == NULL) + yptr = y + ylen; + + xnum = strtol(lastx, &endptr, 10); + xisnum = endptr == xptr ? 1 : 0; + ynum = strtol(lasty, &endptr, 10); + yisnum = endptr == yptr ? 1 : 0; + + if (xisnum && !yisnum) return -1; + if (!xisnum && yisnum) return 1; + + if (xisnum && yisnum) { + /* Numerical comparison */ + if (xnum != ynum) return xnum < ynum ? -1 : 1; + } else { + /* String comparison */ + xn = xptr - lastx; + yn = yptr - lasty; + min = xn < yn ? xn : yn; + if ((res = strncmp(lastx, lasty, min))) return res < 0 ? -1 : 1; + if (xn != yn) return xn < yn ? -1 : 1; + } + + lastx = xptr + 1; + lasty = yptr + 1; + if (lastx == x + xlen + 1 && lasty == y + ylen + 1) break; + if (lastx == x + xlen + 1) return -1; + if (lasty == y + ylen + 1) return 1; + } + + return 0; +} + +int +semver_compare_prerelease (semver_t x, semver_t y) { + return compare_prerelease(x.prerelease, y.prerelease); +} + +/** + * Performs a major, minor and patch binary comparison (x, y). + * This function is mostly used internally + * + * Returns: + * + * `0` - If versiona are equal + * `1` - If x is higher than y + * `-1` - If x is lower than y + */ + +int +semver_compare_version (semver_t x, semver_t y) { + int res; + + if ((res = binary_comparison(x.major, y.major)) == 0) { + if ((res = binary_comparison(x.minor, y.minor)) == 0) { + return binary_comparison(x.patch, y.patch); + } + } + + return res; +} + +/** + * Compare two semantic versions (x, y). + * + * Returns: + * - `1` if x is higher than y + * - `0` if x is equal to y + * - `-1` if x is lower than y + */ + +int +semver_compare (semver_t x, semver_t y) { + int res; + + if ((res = semver_compare_version(x, y)) == 0) { + return semver_compare_prerelease(x, y); + } + + return res; +} + +/** + * Performs a `greater than` comparison + */ + +int +semver_gt (semver_t x, semver_t y) { + return semver_compare(x, y) == 1; +} + +/** + * Performs a `lower than` comparison + */ + +int +semver_lt (semver_t x, semver_t y) { + return semver_compare(x, y) == -1; +} + +/** + * Performs a `equality` comparison + */ + +int +semver_eq (semver_t x, semver_t y) { + return semver_compare(x, y) == 0; +} + +/** + * Performs a `non equal to` comparison + */ + +int +semver_neq (semver_t x, semver_t y) { + return semver_compare(x, y) != 0; +} + +/** + * Performs a `greater than or equal` comparison + */ + +int +semver_gte (semver_t x, semver_t y) { + return semver_compare(x, y) >= 0; +} + +/** + * Performs a `lower than or equal` comparison + */ + +int +semver_lte (semver_t x, semver_t y) { + return semver_compare(x, y) <= 0; +} + +/** + * Checks if version `x` can be satisfied by `y` + * performing a comparison with caret operator. + * + * See: https://docs.npmjs.com/misc/semver#caret-ranges-1-2-3-0-2-5-0-0-4 + * + * Returns: + * + * `1` - Can be satisfied + * `0` - Cannot be satisfied + */ + +int +semver_satisfies_caret (semver_t x, semver_t y) { + if (x.major == y.major) { + if (x.major == 0) { + return x.minor >= y.minor; + } + return 1; + } + return 0; +} + +/** + * Checks if version `x` can be satisfied by `y` + * performing a comparison with tilde operator. + * + * See: https://docs.npmjs.com/misc/semver#tilde-ranges-1-2-3-1-2-1 + * + * Returns: + * + * `1` - Can be satisfied + * `0` - Cannot be satisfied + */ + +int +semver_satisfies_patch (semver_t x, semver_t y) { + return x.major == y.major + && x.minor == y.minor; +} + +/** + * Checks if both versions can be satisfied + * based on the given comparison operator. + * + * Allowed operators: + * + * - `=` - Equality + * - `>=` - Higher or equal to + * - `<=` - Lower or equal to + * - `<` - Lower than + * - `>` - Higher than + * - `^` - Caret comparison (see https://docs.npmjs.com/misc/semver#caret-ranges-1-2-3-0-2-5-0-0-4) + * - `~` - Tilde comparison (see https://docs.npmjs.com/misc/semver#tilde-ranges-1-2-3-1-2-1) + * + * Returns: + * + * `1` - Can be satisfied + * `0` - Cannot be satisfied + */ + +int +semver_satisfies (semver_t x, semver_t y, const char *op) { + int first, second; + /* Extract the comparison operator */ + first = op[0]; + second = op[1]; + + /* Caret operator */ + if (first == SYMBOL_CF) + return semver_satisfies_caret(x, y); + + /* Tilde operator */ + if (first == SYMBOL_TF) + return semver_satisfies_patch(x, y); + + /* Strict equality */ + if (first == SYMBOL_EQ) + return semver_eq(x, y); + + /* Greater than or equal comparison */ + if (first == SYMBOL_GT) { + if (second == SYMBOL_EQ) { + return semver_gte(x, y); + } + return semver_gt(x, y); + } + + /* Lower than or equal comparison */ + if (first == SYMBOL_LT) { + if (second == SYMBOL_EQ) { + return semver_lte(x, y); + } + return semver_lt(x, y); + } + + return 0; +} + +/** + * Free heep allocated memory of a given semver. + * This is just a convenient function that you + * should call when you're done. + */ + +void +semver_free (semver_t *x) { + if (x->metadata) { + free(x->metadata); + x->metadata = NULL; + } + if (x->prerelease) { + free(x->prerelease); + x->prerelease = NULL; + } +} + +/** + * Renders + */ + +static void +concat_num (char * str, int x, char * sep) { + char buf[SLICE_SIZE] = {0}; + if (sep == NULL) sprintf(buf, "%d", x); + else sprintf(buf, "%s%d", sep, x); + strcat(str, buf); +} + +static void +concat_char (char * str, char * x, char * sep) { + char buf[SLICE_SIZE] = {0}; + sprintf(buf, "%s%s", sep, x); + strcat(str, buf); +} + +/** + * Render a given semver as string + */ + +void +semver_render (semver_t *x, char *dest) { + if (x->major) concat_num(dest, x->major, NULL); + if (x->minor) concat_num(dest, x->minor, DELIMITER); + if (x->patch) concat_num(dest, x->patch, DELIMITER); + if (x->prerelease) concat_char(dest, x->prerelease, PR_DELIMITER); + if (x->metadata) concat_char(dest, x->metadata, MT_DELIMITER); +} + +/** + * Version bump helpers + */ + +void +semver_bump (semver_t *x) { + x->major++; +} + +void +semver_bump_minor (semver_t *x) { + x->minor++; +} + +void +semver_bump_patch (semver_t *x) { + x->patch++; +} + +/** + * Helpers + */ + +static int +has_valid_length (const char *s) { + return strlen(s) <= MAX_SIZE; +} + +/** + * Checks if a given semver string is valid + * + * Returns: + * + * `1` - Valid expression + * `0` - Invalid + */ + +int +semver_is_valid (const char *s) { + return has_valid_length(s) + && has_valid_chars(s, VALID_CHARS); +} + +/** + * Removes non-valid characters in the given string. + * + * Returns: + * + * `0` - Valid + * `-1` - Invalid input + */ + +int +semver_clean (char *s) { + size_t i, len, mlen; + int res; + if (has_valid_length(s) == 0) return -1; + + len = strlen(s); + mlen = strlen(VALID_CHARS); + + for (i = 0; i < len; i++) { + if (contains(s[i], VALID_CHARS, mlen) == 0) { + res = strcut(s, i, 1); + if(res == -1) return -1; + --len; --i; + } + } + + return 0; +} + +static int +char_to_int (const char * str) { + int buf; + size_t i,len, mlen; + buf = 0; + len = strlen(str); + mlen = strlen(VALID_CHARS); + + for (i = 0; i < len; i++) + if (contains(str[i], VALID_CHARS, mlen)) + buf += (int) str[i]; + + return buf; +} + +/** + * Render a given semver as numeric value. + * Useful for ordering and filtering. + */ + +int +semver_numeric (semver_t *x) { + int num; + char buf[SLICE_SIZE * 3]; + memset(&buf, 0, SLICE_SIZE * 3); + + if (x->major) concat_num(buf, x->major, NULL); + if (x->minor) concat_num(buf, x->minor, NULL); + if (x->patch) concat_num(buf, x->patch, NULL); + + num = parse_int(buf); + if(num == -1) return -1; + + if (x->prerelease) num += char_to_int(x->prerelease); + if (x->metadata) num += char_to_int(x->metadata); + + return num; +} diff --git a/xs/src/semver/semver.h b/xs/src/semver/semver.h new file mode 100644 index 000000000..1b48670ca --- /dev/null +++ b/xs/src/semver/semver.h @@ -0,0 +1,105 @@ +/* + * semver.h + * + * Copyright (c) 2015-2017 Tomas Aparicio + * MIT licensed + */ + +#ifndef __SEMVER_H +#define __SEMVER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef SEMVER_VERSION +#define SEMVER_VERSION "0.2.0" +#endif + +/** + * semver_t struct + */ + +typedef struct semver_version_s { + int major; + int minor; + int patch; + char * metadata; + char * prerelease; +} semver_t; + +/** + * Set prototypes + */ + +int +semver_satisfies (semver_t x, semver_t y, const char *op); + +int +semver_satisfies_caret (semver_t x, semver_t y); + +int +semver_satisfies_patch (semver_t x, semver_t y); + +int +semver_compare (semver_t x, semver_t y); + +int +semver_compare_version (semver_t x, semver_t y); + +int +semver_compare_prerelease (semver_t x, semver_t y); + +int +semver_gt (semver_t x, semver_t y); + +int +semver_gte (semver_t x, semver_t y); + +int +semver_lt (semver_t x, semver_t y); + +int +semver_lte (semver_t x, semver_t y); + +int +semver_eq (semver_t x, semver_t y); + +int +semver_neq (semver_t x, semver_t y); + +int +semver_parse (const char *str, semver_t *ver); + +int +semver_parse_version (const char *str, semver_t *ver); + +void +semver_render (semver_t *x, char *dest); + +int +semver_numeric (semver_t *x); + +void +semver_bump (semver_t *x); + +void +semver_bump_minor (semver_t *x); + +void +semver_bump_patch (semver_t *x); + +void +semver_free (semver_t *x); + +int +semver_is_valid (const char *s); + +int +semver_clean (char *s); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/xs/src/slic3r/Config/Snapshot.cpp b/xs/src/slic3r/Config/Snapshot.cpp new file mode 100644 index 000000000..559e4c63c --- /dev/null +++ b/xs/src/slic3r/Config/Snapshot.cpp @@ -0,0 +1,308 @@ +#include "Snapshot.hpp" +#include "../GUI/AppConfig.hpp" +#include "../Utils/Time.hpp" + +#include + +#include +#include +#include +#include +#include + +#include "../../libslic3r/libslic3r.h" +#include "../../libslic3r/Config.hpp" +#include "../../libslic3r/FileParserError.hpp" +#include "../../libslic3r/Utils.hpp" + +#define SLIC3R_SNAPSHOTS_DIR "snapshots" +#define SLIC3R_SNAPSHOT_FILE "snapshot.ini" + +namespace Slic3r { +namespace GUI { +namespace Config { + +void Snapshot::clear() +{ + this->id.clear(); + this->time_captured = 0; + this->slic3r_version_captured = Semver::invalid(); + this->comment.clear(); + this->reason = SNAPSHOT_UNKNOWN; + this->print.clear(); + this->filaments.clear(); + this->printer.clear(); +} + +void Snapshot::load_ini(const std::string &path) +{ + this->clear(); + + auto throw_on_parse_error = [&path](const std::string &msg) { + throw file_parser_error(std::string("Failed loading the snapshot file. Reason: ") + msg, path); + }; + + // Load the snapshot.ini file. + 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 file_parser_error(std::string("The snapshot file cannot be loaded. Reason: ") + err.what(), path); + } catch (const std::runtime_error &err) { + throw_on_parse_error(err.what()); + } + + // Parse snapshot.ini + std::string group_name_vendor = "Vendor:"; + std::string key_filament = "filament"; + for (auto §ion : tree) { + if (section.first == "snapshot") { + // Parse the common section. + for (auto &kvp : section.second) { + if (kvp.first == "id") + this->id = kvp.second.data(); + else if (kvp.first == "time_captured") { + this->time_captured = Slic3r::Utils::parse_time_ISO8601Z(kvp.second.data()); + if (this->time_captured == (time_t)-1) + throw_on_parse_error("invalid timestamp"); + } else if (kvp.first == "slic3r_version_captured") { + auto semver = Semver::parse(kvp.second.data()); + if (! semver) + throw_on_parse_error("invalid slic3r_version_captured semver"); + this->slic3r_version_captured = *semver; + } else if (kvp.first == "comment") { + this->comment = kvp.second.data(); + } else if (kvp.first == "reason") { + std::string rsn = kvp.second.data(); + if (rsn == "upgrade") + this->reason = SNAPSHOT_UPGRADE; + else if (rsn == "downgrade") + this->reason = SNAPSHOT_DOWNGRADE; + else if (rsn == "user") + this->reason = SNAPSHOT_USER; + else + this->reason = SNAPSHOT_UNKNOWN; + } + } + } else if (section.first == "presets") { + // Load the names of the active presets. + for (auto &kvp : section.second) { + if (kvp.first == "print") { + this->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(this->filaments.size()) <= idx) + this->filaments.resize(idx + 1, std::string()); + this->filaments[idx] = kvp.second.data(); + } + } else if (kvp.first == "printer") { + this->printer = kvp.second.data(); + } + } + } else if (boost::starts_with(section.first, group_name_vendor) && section.first.size() > group_name_vendor.size()) { + // Vendor specific section. + VendorConfig vc; + vc.name = section.first.substr(group_name_vendor.size()); + for (auto &kvp : section.second) { + if (boost::starts_with(kvp.first, "model_")) { + //model:MK2S = 0.4;xxx + //model:MK3 = 0.4;xxx + } else if (kvp.first == "version" || kvp.first == "min_slic3r_version" || kvp.first == "max_slic3r_version") { + // Version of the vendor specific config bundle bundled with this snapshot. + auto semver = Semver::parse(kvp.second.data()); + if (! semver) + throw_on_parse_error("invalid " + kvp.first + " format for " + section.first); + if (kvp.first == "version") + vc.version = *semver; + else if (kvp.first == "min_slic3r_version") + vc.min_slic3r_version = *semver; + else + vc.max_slic3r_version = *semver; + } + } + } + } +} + +void Snapshot::save_ini(const std::string &path) +{ + boost::nowide::ofstream c; + c.open(path, std::ios::out | std::ios::trunc); + c << "# " << Slic3r::header_slic3r_generated() << std::endl; + + // Export the common "snapshot". + c << std::endl << "[snapshot]" << std::endl; + c << "id = " << this->id << std::endl; + c << "time_captured = " << Slic3r::Utils::format_time_ISO8601Z(this->time_captured) << std::endl; + c << "slic3r_version_captured = " << this->slic3r_version_captured.to_string() << std::endl; + c << "comment = " << this->comment << std::endl; + c << "reason = " << this->reason << std::endl; + + // Export the active presets at the time of the snapshot. + c << std::endl << "[presets]" << std::endl; + c << "print = " << this->print << std::endl; + c << "filament = " << this->filaments.front() << std::endl; + for (size_t i = 1; i < this->filaments.size(); ++ i) + c << "filament_" << std::to_string(i) << " = " << this->filaments[i] << std::endl; + c << "printer = " << this->printer << std::endl; + + // Export the vendor configs. + for (const VendorConfig &vc : this->vendor_configs) { + c << std::endl << "[Vendor:" << vc.name << "]" << std::endl; + c << "version = " << vc.version.to_string() << std::endl; + c << "min_slic3r_version = " << vc.min_slic3r_version.to_string() << std::endl; + c << "max_slic3r_version = " << vc.max_slic3r_version.to_string() << std::endl; + } + c.close(); +} + +void Snapshot::export_selections(AppConfig &config) const +{ + assert(filaments.size() >= 1); + config.clear_section("presets"); + config.set("presets", "print", print); + config.set("presets", "filament", filaments.front()); + for (int i = 1; i < filaments.size(); ++i) { + char name[64]; + sprintf(name, "filament_%d", i); + config.set("presets", name, filaments[i]); + } + config.set("presets", "printer", printer); +} + +size_t SnapshotDB::load_db() +{ + boost::filesystem::path snapshots_dir = SnapshotDB::create_db_dir(); + + m_snapshots.clear(); + + // Walk over the snapshot directories and load their index. + std::string errors_cummulative; + for (auto &dir_entry : boost::filesystem::directory_iterator(snapshots_dir)) + if (boost::filesystem::is_directory(dir_entry.status())) { + // Try to read "snapshot.ini". + boost::filesystem::path path_ini = dir_entry.path() / SLIC3R_SNAPSHOT_FILE; + Snapshot snapshot; + try { + snapshot.load_ini(path_ini.string()); + } catch (const std::runtime_error &err) { + errors_cummulative += err.what(); + errors_cummulative += "\n"; + continue; + } + // Check that the name of the snapshot directory matches the snapshot id stored in the snapshot.ini file. + if (dir_entry.path().filename().string() != snapshot.id) { + errors_cummulative += std::string("Snapshot ID ") + snapshot.id + " does not match the snapshot directory " + dir_entry.path().filename().string() + "\n"; + continue; + } + m_snapshots.emplace_back(std::move(snapshot)); + } + + if (! errors_cummulative.empty()) + throw std::runtime_error(errors_cummulative); + return m_snapshots.size(); +} + +static void copy_config_dir_single_level(const boost::filesystem::path &path_src, const boost::filesystem::path &path_dst) +{ + if (! boost::filesystem::is_directory(path_dst) && + ! boost::filesystem::create_directory(path_dst)) + throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + path_dst.string()); + + for (auto &dir_entry : boost::filesystem::directory_iterator(path_src)) + if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) + boost::filesystem::copy_file(dir_entry.path(), path_dst / dir_entry.path().filename(), boost::filesystem::copy_option::overwrite_if_exists); +} + +static void delete_existing_ini_files(const boost::filesystem::path &path) +{ + if (! boost::filesystem::is_directory(path)) + return; + for (auto &dir_entry : boost::filesystem::directory_iterator(path)) + if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) + boost::filesystem::remove(dir_entry.path()); +} + +const Snapshot& SnapshotDB::make_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment) +{ + boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); + boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir(); + + // 1) Prepare the snapshot structure. + Snapshot snapshot; + // Snapshot header. + snapshot.time_captured = Slic3r::Utils::get_current_time_utc(); + snapshot.id = Slic3r::Utils::format_time_ISO8601Z(snapshot.time_captured); + snapshot.slic3r_version_captured = *Semver::parse(SLIC3R_VERSION); + snapshot.comment = comment; + snapshot.reason = reason; + // Active presets at the time of the snapshot. + snapshot.print = app_config.get("presets", "print"); + snapshot.filaments.emplace_back(app_config.get("presets", "filament")); + snapshot.printer = app_config.get("presets", "printer"); + for (unsigned int i = 1; i < 1000; ++ i) { + char name[64]; + sprintf(name, "filament_%d", i); + if (! app_config.has("presets", name)) + break; + snapshot.filaments.emplace_back(app_config.get("presets", name)); + } + // Vendor specific config bundles and installed printers. + + // Backup the presets. + boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id; + for (const char *subdir : { "print", "filament", "printer", "vendor" }) + copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir); + snapshot.save_ini((snapshot_dir / "snapshot.ini").string()); + m_snapshots.emplace_back(std::move(snapshot)); + return m_snapshots.back(); +} + +void SnapshotDB::restore_snapshot(const std::string &id, AppConfig &app_config) +{ + for (const Snapshot &snapshot : m_snapshots) + if (snapshot.id == id) { + this->restore_snapshot(snapshot, app_config); + return; + } + throw std::runtime_error(std::string("Snapshot with id " + id + " was not found.")); +} + +void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_config) +{ + boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); + boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir(); + boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id; + + // Remove existing ini files and restore the ini files from the snapshot. + for (const char *subdir : { "print", "filament", "printer", "vendor" }) { + delete_existing_ini_files(data_dir / subdir); + copy_config_dir_single_level(snapshot_dir / subdir, data_dir / subdir); + } + + // Update app_config from the snapshot. + snapshot.export_selections(app_config); + + // Store information about the snapshot. + +} + +boost::filesystem::path SnapshotDB::create_db_dir() +{ + boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); + boost::filesystem::path snapshots_dir = data_dir / SLIC3R_SNAPSHOTS_DIR; + for (const boost::filesystem::path &path : { data_dir, snapshots_dir }) { + 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 a directory at ") + subdir.string()); + } + return snapshots_dir; +} + +} // namespace Config +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/Config/Snapshot.hpp b/xs/src/slic3r/Config/Snapshot.hpp new file mode 100644 index 000000000..358797bf7 --- /dev/null +++ b/xs/src/slic3r/Config/Snapshot.hpp @@ -0,0 +1,106 @@ +#ifndef slic3r_GUI_Snapshot_ +#define slic3r_GUI_Snapshot_ + +#include +#include + +#include + +#include "../Utils/Semver.hpp" + +namespace Slic3r { + +class AppConfig; + +namespace GUI { +namespace Config { + +// A snapshot contains: +// Slic3r.ini +// vendor/ +// print/ +// filament/ +// printer/ +class Snapshot +{ +public: + enum Reason { + SNAPSHOT_UNKNOWN, + SNAPSHOT_UPGRADE, + SNAPSHOT_DOWNGRADE, + SNAPSHOT_USER, + }; + + Snapshot() { clear(); } + + void clear(); + void load_ini(const std::string &path); + void save_ini(const std::string &path); + + // Export the print / filament / printer selections to be activated into the AppConfig. + void export_selections(AppConfig &config) const; + + // ID of a snapshot should equal to the name of the snapshot directory. + // The ID contains the date/time, reason and comment to be human readable. + std::string id; + std::time_t time_captured; + // Which Slic3r version captured this snapshot? + Semver slic3r_version_captured = Semver::invalid(); + // Comment entered by the user at the start of the snapshot capture. + std::string comment; + Reason reason; + + // Active presets at the time of the snapshot. + std::string print; + std::vector filaments; + std::string printer; + + // Annotation of the vendor configuration stored in the snapshot. + // This information is displayed to the user and used to decide compatibility + // of the configuration stored in the snapshot with the running Slic3r version. + struct VendorConfig { + // Name of the vendor contained in this snapshot. + std::string name; + // Version of the vendor config contained in this snapshot. + Semver version = Semver::invalid(); + // Minimum Slic3r version compatible with this vendor configuration. + Semver min_slic3r_version = Semver::zero(); + // Maximum Slic3r version compatible with this vendor configuration, or empty. + Semver max_slic3r_version = Semver::inf(); + }; + // List of vendor configs contained in this snapshot. + std::vector vendor_configs; +}; + +class SnapshotDB +{ +public: + typedef std::vector::const_iterator const_iterator; + + // Load the snapshot database from the snapshots directory. + // If the snapshot directory or its parent does not exist yet, it will be created. + // Returns a number of snapshots loaded. + size_t load_db(); + + // Create a snapshot directory, copy the vendor config bundles, user print/filament/printer profiles, + // create an index. + const Snapshot& make_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment); + void restore_snapshot(const std::string &id, AppConfig &app_config); + void restore_snapshot(const Snapshot &snapshot, AppConfig &app_config); + + const_iterator begin() const { return m_snapshots.begin(); } + const_iterator end() const { return m_snapshots.end(); } + const std::vector& snapshots() const { return m_snapshots; } + +private: + // Create the snapshots directory if it does not exist yet. + static boost::filesystem::path create_db_dir(); + + std::vector m_snapshots; +}; + +} // namespace Config +} // namespace GUI +} // namespace Slic3r + +#endif /* slic3r_GUI_Snapshot_ */ diff --git a/xs/src/slic3r/Config/Version.cpp b/xs/src/slic3r/Config/Version.cpp new file mode 100644 index 000000000..1102f3149 --- /dev/null +++ b/xs/src/slic3r/Config/Version.cpp @@ -0,0 +1,136 @@ +#include "Version.hpp" + +#include +#include +#include + +#include "../../libslic3r/libslic3r.h" +#include "../../libslic3r/Config.hpp" + +namespace Slic3r { +namespace GUI { +namespace Config { + +static boost::optional s_current_slic3r_semver = Semver::parse(SLIC3R_VERSION); + +bool Version::is_current_slic3r_supported() const +{ + return this->is_slic3r_supported(*s_current_slic3r_semver); +} + +inline char* left_trim(char *c) +{ + for (; *c == ' ' || *c == '\t'; ++ c); + return c; +} + +inline char* right_trim(char *start) +{ + char *end = start + strlen(start) - 1; + for (; end >= start && (*end == ' ' || *end == '\t'); -- end); + *(++ end) = 0; + return end; +} + +inline std::string unquote_value(char *value, char *end, const std::string &path, int idx_line) +{ + std::string svalue; + if (value == end) { + // Empty string is a valid string. + } else if (*value == '"') { + if (++ value < -- end || *end != '"') + throw file_parser_error("String not enquoted correctly", path, idx_line); + *end = 0; + if (! unescape_string_cstyle(value, svalue)) + throw file_parser_error("Invalid escape sequence inside a quoted string", path, idx_line); + } + return svalue; +} + +inline std::string unquote_version_comment(char *value, char *end, const std::string &path, int idx_line) +{ + std::string svalue; + if (value == end) { + // Empty string is a valid string. + } else if (*value == '"') { + if (++ value < -- end || *end != '"') + throw file_parser_error("Version comment not enquoted correctly", path, idx_line); + *end = 0; + if (! unescape_string_cstyle(value, svalue)) + throw file_parser_error("Invalid escape sequence inside a quoted version comment", path, idx_line); + } + return svalue; +} + +size_t Index::load(const std::string &path) +{ + m_configs.clear(); + + boost::nowide::ifstream ifs(path); + std::string line; + size_t idx_line = 0; + Version ver; + while (std::getline(ifs, line)) { + ++ idx_line; + // Skip the initial white spaces. + char *key = left_trim(const_cast(line.data())); + // Right trim the line. + char *end = right_trim(key); + // Keyword may only contain alphanumeric characters. Semantic version may in addition contain "+.-". + char *key_end = key; + bool maybe_semver = false; + for (;; ++ key) { + if (strchr("+.-", *key) != nullptr) + maybe_semver = true; + else if (! std::isalnum(*key)) + break; + } + if (*key != 0 && *key != ' ' && *key != '\t' && *key != '=') + throw file_parser_error("Invalid keyword or semantic version", path, idx_line); + *key_end = 0; + boost::optional semver; + if (maybe_semver) + semver = Semver::parse(key); + char *value = left_trim(key_end); + if (*value == '=') { + if (semver) + throw file_parser_error("Key cannot be a semantic version", path, idx_line); + // Verify validity of the key / value pair. + std::string svalue = unquote_value(left_trim(++ value), end, path, idx_line); + if (key == "min_sic3r_version" || key == "max_slic3r_version") { + if (! svalue.empty()) + semver = Semver::parse(key); + if (! semver) + throw file_parser_error(std::string(key) + " must referece a valid semantic version", path, idx_line); + if (key == "min_sic3r_version") + ver.min_slic3r_version = *semver; + else + ver.max_slic3r_version = *semver; + } else { + // Ignore unknown keys, as there may come new keys in the future. + } + } + if (! semver) + throw file_parser_error("Invalid semantic version", path, idx_line); + ver.config_version = *semver; + ver.comment = (end <= key_end) ? "" : unquote_version_comment(value, end, path, idx_line); + m_configs.emplace_back(ver); + } + + return m_configs.size(); +} + +Index::const_iterator Index::recommended() const +{ + int idx = -1; + const_iterator highest = m_configs.end(); + for (const_iterator it = this->begin(); it != this->end(); ++ it) + if (it->is_current_slic3r_supported() && + (highest == this->end() || highest->max_slic3r_version < it->max_slic3r_version)) + highest = it; + return highest; +} + +} // namespace Config +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/Config/Version.hpp b/xs/src/slic3r/Config/Version.hpp new file mode 100644 index 000000000..7af1d4b5b --- /dev/null +++ b/xs/src/slic3r/Config/Version.hpp @@ -0,0 +1,75 @@ +#ifndef slic3r_GUI_ConfigIndex_ +#define slic3r_GUI_ConfigIndex_ + +#include +#include + +#include "../../libslic3r/FileParserError.hpp" +#include "../Utils/Semver.hpp" + +namespace Slic3r { +namespace GUI { +namespace Config { + +// Configuration bundle version. +struct Version +{ + // Version of this config. + Semver config_version = Semver::invalid(); + // Minimum Slic3r version, for which this config is applicable. + Semver min_slic3r_version = Semver::zero(); + // Maximum Slic3r version, for which this config is recommended. + // Slic3r should read older configuration and upgrade to a newer format, + // but likely there has been a better configuration published, using the new features. + Semver max_slic3r_version = Semver::inf(); + // Single comment line. + std::string comment; + + bool is_slic3r_supported(const Semver &slicer_version) const { return slicer_version.in_range(min_slic3r_version, max_slic3r_version); } + bool is_current_slic3r_supported() const; +}; + +// Index of vendor specific config bundle versions and Slic3r compatibilities. +// The index is being downloaded from the internet, also an initial version of the index +// is contained in the Slic3r installation. +// +// The index has a simple format: +// +// min_sic3r_version = +// max_slic3r_version = +// config_version "comment" +// config_version "comment" +// ... +// min_slic3r_version = +// max_slic3r_version = +// config_version comment +// config_version comment +// ... +// +// The min_slic3r_version, max_slic3r_version keys are applied to the config versions below, +// empty slic3r version means an open interval. +class Index +{ +public: + typedef std::vector::const_iterator const_iterator; + // Read a config index file in the simple format described in the Index class comment. + // Throws Slic3r::file_parser_error and the standard std file access exceptions. + size_t load(const std::string &path); + + const_iterator begin() const { return m_configs.begin(); } + const_iterator end() const { return m_configs.end(); } + const std::vector& configs() const { return m_configs; } + // Finds a recommended config to be installed for the current Slic3r version. + // Returns configs().end() if such version does not exist in the index. This shall never happen + // if the index is valid. + const_iterator recommended() const; + +private: + std::vector m_configs; +}; + +} // namespace Config +} // namespace GUI +} // namespace Slic3r + +#endif /* slic3r_GUI_ConfigIndex_ */ diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index f6be96a78..a459b7cbf 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -194,8 +194,8 @@ void GLIndexedVertexArray::render( const float GLVolume::SELECTED_COLOR[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; const float GLVolume::HOVER_COLOR[4] = { 0.4f, 0.9f, 0.1f, 1.0f }; -const float GLVolume::OUTSIDE_COLOR[4] = { 0.75f, 0.0f, 0.75f, 1.0f }; -const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 1.0f, 0.0f, 1.0f, 1.0f }; +const float GLVolume::OUTSIDE_COLOR[4] = { 0.0f, 0.38f, 0.8f, 1.0f }; +const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 0.19f, 0.58f, 1.0f, 1.0f }; void GLVolume::set_render_color(float r, float g, float b, float a) { @@ -627,6 +627,8 @@ void GLVolumeCollection::update_outside_state(const DynamicPrintConfig* config, BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min.x), unscale(bed_box_2D.min.y), 0.0), Pointf3(unscale(bed_box_2D.max.x), unscale(bed_box_2D.max.y), config->opt_float("max_print_height"))); + // Allow the objects to protrude below the print bed + print_volume.min.z = -1e10; for (GLVolume* volume : this->volumes) { @@ -642,20 +644,25 @@ void GLVolumeCollection::update_outside_state(const DynamicPrintConfig* config, std::vector GLVolumeCollection::get_current_print_zs() const { + // Collect layer top positions of all volumes. std::vector print_zs; - for (GLVolume *vol : this->volumes) - { - for (coordf_t z : vol->print_zs) - { - double round_z = (double)round(z * 100000.0f) / 100000.0f; - if (std::find(print_zs.begin(), print_zs.end(), round_z) == print_zs.end()) - print_zs.push_back(round_z); - } - } - + append(print_zs, vol->print_zs); std::sort(print_zs.begin(), print_zs.end()); + // Replace intervals of layers with similar top positions with their average value. + int n = int(print_zs.size()); + int k = 0; + for (int i = 0; i < n;) { + int j = i + 1; + coordf_t zmax = print_zs[i] + EPSILON; + for (; j < n && print_zs[j] <= zmax; ++ j) ; + print_zs[k ++] = (j > i + 1) ? (0.5 * (print_zs[i] + print_zs[j - 1])) : print_zs[i]; + i = j; + } + if (k < n) + print_zs.erase(print_zs.begin() + k, print_zs.end()); + return print_zs; } @@ -2039,6 +2046,8 @@ void _3DScene::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, return path.width; case GCodePreviewData::Extrusion::Feedrate: return path.feedrate; + case GCodePreviewData::Extrusion::VolumetricRate: + return path.feedrate * (float)path.mm3_per_mm; case GCodePreviewData::Extrusion::Tool: return (float)path.extruder_id; } @@ -2053,11 +2062,13 @@ void _3DScene::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, case GCodePreviewData::Extrusion::FeatureType: return data.get_extrusion_role_color((ExtrusionRole)(int)value); case GCodePreviewData::Extrusion::Height: - return data.get_extrusion_height_color(value); + return data.get_height_color(value); case GCodePreviewData::Extrusion::Width: - return data.get_extrusion_width_color(value); + return data.get_width_color(value); case GCodePreviewData::Extrusion::Feedrate: - return data.get_extrusion_feedrate_color(value); + return data.get_feedrate_color(value); + case GCodePreviewData::Extrusion::VolumetricRate: + return data.get_volumetric_rate_color(value); case GCodePreviewData::Extrusion::Tool: { static GCodePreviewData::Color color; @@ -2337,7 +2348,7 @@ bool _3DScene::_travel_paths_by_feedrate(const GCodePreviewData& preview_data, G // creates a new volume for each feedrate for (Feedrate& feedrate : feedrates) { - GLVolume* volume = new GLVolume(preview_data.get_extrusion_feedrate_color(feedrate.value).rgba); + GLVolume* volume = new GLVolume(preview_data.get_feedrate_color(feedrate.value).rgba); if (volume == nullptr) return false; else diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index 99339e2f3..e32b645b4 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -145,6 +145,17 @@ void AppConfig::update_last_output_dir(const std::string &dir) this->set("", "last_output_path", dir); } +void AppConfig::reset_selections() +{ + auto it = m_storage.find("presets"); + if (it != m_storage.end()) { + it->second.erase("print"); + it->second.erase("filament"); + it->second.erase("printer"); + m_dirty = true; + } +} + std::string AppConfig::config_path() { return (boost::filesystem::path(Slic3r::data_dir()) / "slic3r.ini").make_preferred().string(); diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp index c6d7766a4..9b1d5a712 100644 --- a/xs/src/slic3r/GUI/AppConfig.hpp +++ b/xs/src/slic3r/GUI/AppConfig.hpp @@ -73,6 +73,11 @@ public: std::string get_last_output_dir(const std::string &alt) const; void update_last_output_dir(const std::string &dir); + // reset the current print / filament / printer selections, so that + // the PresetBundle::load_selections(const AppConfig &config) call will select + // the first non-default preset when called. + void reset_selections(); + // Get the default config path from Slic3r::data_dir(). static std::string config_path(); diff --git a/xs/src/slic3r/GUI/BedShapeDialog.cpp b/xs/src/slic3r/GUI/BedShapeDialog.cpp index 51dbd6a27..24d84a7df 100644 --- a/xs/src/slic3r/GUI/BedShapeDialog.cpp +++ b/xs/src/slic3r/GUI/BedShapeDialog.cpp @@ -183,7 +183,8 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points) vertex_distances.push_back(distance); avg_dist += distance; } - + + avg_dist /= vertex_distances.size(); bool defined_value = true; for (auto el: vertex_distances) { diff --git a/xs/src/slic3r/GUI/BitmapCache.cpp b/xs/src/slic3r/GUI/BitmapCache.cpp new file mode 100644 index 000000000..f92369650 --- /dev/null +++ b/xs/src/slic3r/GUI/BitmapCache.cpp @@ -0,0 +1,120 @@ +#include "BitmapCache.hpp" + +namespace Slic3r { namespace GUI { + +void BitmapCache::clear() +{ + for (std::pair &bitmap : m_map) + delete bitmap.second; +} + +wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_t height) +{ + wxBitmap *bitmap = nullptr; + auto it = m_map.find(bitmap_key); + if (it == m_map.end()) { + bitmap = new wxBitmap(width, height); + m_map[bitmap_key] = bitmap; + } else { + bitmap = it->second; + if (bitmap->GetWidth() != width || bitmap->GetHeight() != height) + bitmap->Create(width, height); + } +#if defined(__APPLE__) || defined(_MSC_VER) + bitmap->UseAlpha(); +#endif + return bitmap; +} + +wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp) +{ + wxBitmap *bitmap = this->insert(bitmap_key, bmp.GetWidth(), bmp.GetHeight()); + + wxMemoryDC memDC; + memDC.SelectObject(*bitmap); + memDC.SetBackground(*wxTRANSPARENT_BRUSH); + memDC.Clear(); + memDC.DrawBitmap(bmp, 0, 0, true); + memDC.SelectObject(wxNullBitmap); + + return bitmap; +} + +wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2) +{ + wxBitmap *bitmap = this->insert(bitmap_key, bmp.GetWidth() + bmp2.GetWidth(), std::max(bmp.GetHeight(), bmp2.GetHeight())); + + wxMemoryDC memDC; + memDC.SelectObject(*bitmap); + memDC.SetBackground(*wxTRANSPARENT_BRUSH); + memDC.Clear(); + if (bmp.GetWidth() > 0) + memDC.DrawBitmap(bmp, 0, 0, true); + if (bmp2.GetWidth() > 0) + memDC.DrawBitmap(bmp2, bmp.GetWidth(), 0, true); + memDC.SelectObject(wxNullBitmap); + + return bitmap; +} + +wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3) +{ + wxBitmap *bitmap = this->insert(bitmap_key, bmp.GetWidth() + bmp2.GetWidth() + bmp3.GetWidth(), std::max(std::max(bmp.GetHeight(), bmp2.GetHeight()), bmp3.GetHeight())); + + wxMemoryDC memDC; + memDC.SelectObject(*bitmap); + memDC.SetBackground(*wxTRANSPARENT_BRUSH); + memDC.Clear(); + if (bmp.GetWidth() > 0) + memDC.DrawBitmap(bmp, 0, 0, true); + if (bmp2.GetWidth() > 0) + memDC.DrawBitmap(bmp2, bmp.GetWidth(), 0, true); + if (bmp3.GetWidth() > 0) + memDC.DrawBitmap(bmp3, bmp.GetWidth() + bmp2.GetWidth(), 0, true); + memDC.SelectObject(wxNullBitmap); + + return bitmap; +} + +wxBitmap* BitmapCache::insert(const std::string &bitmap_key, std::vector &bmps) +{ + size_t width = 0; + size_t height = 0; + for (wxBitmap &bmp : bmps) { + width += bmp.GetWidth(); + height = std::max(height, bmp.GetHeight()); + } + wxBitmap *bitmap = this->insert(bitmap_key, width, height); + + wxMemoryDC memDC; + memDC.SelectObject(*bitmap); + memDC.SetBackground(*wxTRANSPARENT_BRUSH); + memDC.Clear(); + size_t x = 0; + for (wxBitmap &bmp : bmps) { + if (bmp.GetWidth() > 0) + memDC.DrawBitmap(bmp, x, 0, true); + x += bmp.GetWidth(); + } + memDC.SelectObject(wxNullBitmap); + + return bitmap; +} + +wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency) +{ + wxImage image(width, height); + image.InitAlpha(); + unsigned char* imgdata = image.GetData(); + unsigned char* imgalpha = image.GetAlpha(); + for (size_t i = 0; i < width * height; ++ i) { + *imgdata ++ = r; + *imgdata ++ = g; + *imgdata ++ = b; + *imgalpha ++ = transparency; + } + return wxBitmap(std::move(image)); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/BitmapCache.hpp b/xs/src/slic3r/GUI/BitmapCache.hpp new file mode 100644 index 000000000..0cf9d8acf --- /dev/null +++ b/xs/src/slic3r/GUI/BitmapCache.hpp @@ -0,0 +1,43 @@ +#ifndef SLIC3R_GUI_BITMAP_CACHE_HPP +#define SLIC3R_GUI_BITMAP_CACHE_HPP + +#include +#ifndef WX_PRECOMP + #include +#endif + +#include "../../libslic3r/libslic3r.h" +#include "../../libslic3r/Config.hpp" + +#include "GUI.hpp" + +namespace Slic3r { namespace GUI { + +class BitmapCache +{ +public: + BitmapCache() {} + ~BitmapCache() { clear(); } + void clear(); + + wxBitmap* find(const std::string &name) { auto it = m_map.find(name); return (it == m_map.end()) ? nullptr : it->second; } + const wxBitmap* find(const std::string &name) const { return const_cast(this)->find(name); } + + wxBitmap* insert(const std::string &name, size_t width, size_t height); + wxBitmap* insert(const std::string &name, const wxBitmap &bmp); + wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2); + wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3); + wxBitmap* insert(const std::string &name, std::vector &bmps); + + static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency); + static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); } + static wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); } + +private: + std::map m_map; +}; + +} // GUI +} // Slic3r + +#endif /* SLIC3R_GUI_BITMAP_CACHE_HPP */ diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index 94050b9c0..c88a525d4 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -19,16 +19,24 @@ namespace Slic3r { namespace GUI { } void Field::PostInitialize(){ - m_Undo_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); - // use bouth of temporary_icons till don't have "undo_icon" auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - if (wxMSW) m_Undo_btn->SetBackgroundColour(color); + m_Undo_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + m_Undo_to_sys_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + if (wxMSW) { + m_Undo_btn->SetBackgroundColour(color); + m_Undo_to_sys_btn->SetBackgroundColour(color); + } m_Undo_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); })); + m_Undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); })); BUILD(); } + void Field::set_nonsys_btn_icon(const std::string& icon){ + m_Undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(icon)), wxBITMAP_TYPE_PNG)); + } + void Field::on_kill_focus(wxEvent& event) { // Without this, there will be nasty focus bugs on Windows. // Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all @@ -45,12 +53,16 @@ namespace Slic3r { namespace GUI { m_on_change(m_opt_id, get_value()); } - void Field::on_back_to_initial_value() - { + void Field::on_back_to_initial_value(){ if (m_back_to_initial_value != nullptr && m_is_modified_value) m_back_to_initial_value(m_opt_id); } + void Field::on_back_to_sys_value(){ + if (m_back_to_sys_value != nullptr && m_is_nonsys_value) + m_back_to_sys_value(m_opt_id); + } + wxString Field::get_tooltip_text(const wxString& default_string) { wxString tooltip_text(""); @@ -161,7 +173,8 @@ namespace Slic3r { namespace GUI { temp->Bind(wxEVT_KILL_FOCUS, ([this, temp](wxEvent& e) { - on_kill_focus(e); +// on_kill_focus(e); + e.Skip(); temp->GetToolTip()->Enable(true); }), temp->GetId()); @@ -253,8 +266,8 @@ void SpinCtrl::BUILD() { auto temp = new wxSpinCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, 0, min_val, max_val, default_value); - temp->Bind(wxEVT_SPINCTRL, ([this](wxCommandEvent e) { tmp_value = undef_spin_val; on_change_field(); }), temp->GetId()); - temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { tmp_value = undef_spin_val; on_kill_focus(e); }), temp->GetId()); +// temp->Bind(wxEVT_SPINCTRL, ([this](wxCommandEvent e) { tmp_value = undef_spin_val; on_change_field(); }), temp->GetId()); +// temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { tmp_value = undef_spin_val; on_kill_focus(e); }), temp->GetId()); temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { // # On OSX / Cocoa, wxSpinCtrl::GetValue() doesn't return the new value @@ -392,6 +405,7 @@ void Choice::set_value(boost::any value, bool change_event) case coInt: case coFloat: case coPercent: + case coString: case coStrings:{ wxString text_value; if (m_opt.type == coInt) @@ -405,7 +419,6 @@ void Choice::set_value(boost::any value, bool change_event) break; ++idx; } -// if (m_opt.type == coPercent) text_value += "%"; idx == m_opt.enum_values.size() ? dynamic_cast(window)->SetValue(text_value) : dynamic_cast(window)->SetSelection(idx); @@ -434,6 +447,7 @@ void Choice::set_values(const std::vector values) auto ww = dynamic_cast(window); auto value = ww->GetValue(); ww->Clear(); + ww->Append(""); for (auto el : values) ww->Append(wxString(el)); ww->SetValue(value); diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index 2ddb5d9f8..da3e23ccd 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -52,6 +52,8 @@ protected: void on_change_field(); /// Call the attached m_back_to_initial_value method. void on_back_to_initial_value(); + /// Call the attached m_back_to_sys_value method. + void on_back_to_sys_value(); public: /// parent wx item, opportunity to refactor (probably not necessary - data duplication) @@ -63,13 +65,14 @@ public: /// Function object to store callback passed in from owning object. t_change m_on_change {nullptr}; - /// Function object to store callback passed in from owning object. + /// Function object to store callback passed in from owning object. t_back_to_init m_back_to_initial_value{ nullptr }; + t_back_to_init m_back_to_sys_value{ nullptr }; // This is used to avoid recursive invocation of the field change/update by wxWidgets. bool m_disable_change_event {false}; - // This is used to avoid recursive invocation of the field change/update by wxWidgets. bool m_is_modified_value {false}; + bool m_is_nonsys_value {true}; /// Copy of ConfigOption for deduction purposes const ConfigOptionDef m_opt {ConfigOptionDef()}; @@ -89,12 +92,16 @@ public: wxStaticText* m_Label = nullptr; wxButton* m_Undo_btn = nullptr; + wxButton* m_Undo_to_sys_btn = nullptr; /// Fires the enable or disable function, based on the input. inline void toggle(bool en) { en ? enable() : disable(); } virtual wxString get_tooltip_text(const wxString& default_string); + // set icon to "UndoToSystemValue" button according to an inheritance of preset + void set_nonsys_btn_icon(const std::string& icon); + Field(const ConfigOptionDef& opt, const t_config_option_key& id) : m_opt(opt), m_opt_id(id) {}; Field(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : m_parent(parent), m_opt(opt), m_opt_id(id) {}; @@ -196,11 +203,12 @@ public: } void set_value(boost::any value, bool change_event = false) { m_disable_change_event = !change_event; - dynamic_cast(window)->SetValue(boost::any_cast(value)); + tmp_value = boost::any_cast(value); + dynamic_cast(window)->SetValue(tmp_value/*boost::any_cast(value)*/); m_disable_change_event = false; } boost::any get_value() override { - return boost::any(dynamic_cast(window)->GetValue()); + return boost::any(tmp_value); } void enable() override { dynamic_cast(window)->Enable(); } @@ -280,9 +288,7 @@ public: wxSizer* getSizer() override { return sizer; } }; - -#endif } // GUI } // Slic3r - +#endif /* SLIC3R_GUI_FIELD_HPP */ diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 3d6bf45dd..19c288420 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -1,6 +1,7 @@ #include "GUI.hpp" #include +#include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include "wxExtensions.hpp" @@ -174,6 +176,8 @@ wxFrame *g_wxMainFrame = nullptr; wxNotebook *g_wxTabPanel = nullptr; AppConfig *g_AppConfig = nullptr; PresetBundle *g_PresetBundle= nullptr; +wxColour g_color_label_modified; +wxColour g_color_label_sys; std::vector g_tabs_list; @@ -182,9 +186,22 @@ wxLocale* g_wxLocale; std::shared_ptr m_optgroup; double m_brim_width = 0.0; +static void init_label_colours() +{ + auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + if (luma >= 128) { + g_color_label_modified = wxColour(253, 88, 0); + g_color_label_sys = wxColour(26, 132, 57); + } else { + g_color_label_modified = wxColour(253, 111, 40); + g_color_label_sys = wxColour(115, 220, 103); + } +} + void set_wxapp(wxApp *app) { g_wxApp = app; + init_label_colours(); } void set_main_frame(wxFrame *main_frame) @@ -428,8 +445,7 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b std::vector values = boost::any_cast>(value); if (values.size() == 1 && values[0] == "") break; - for (auto el : values) - config.option(opt_key)->values.push_back(el); + config.option(opt_key)->values = values; } else{ ConfigOptionStrings* vec_new = new ConfigOptionStrings{ boost::any_cast(value) }; @@ -465,6 +481,10 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b } break; case coPoints:{ + if (opt_key.compare("bed_shape") == 0){ + config.option(opt_key)->values = boost::any_cast>(value); + break; + } ConfigOptionPoints* vec_new = new ConfigOptionPoints{ boost::any_cast(value) }; config.option(opt_key)->set_at(vec_new, opt_index, 0); } @@ -511,9 +531,25 @@ wxApp* get_app(){ return g_wxApp; } -wxColour* get_modified_label_clr() +const wxColour& get_modified_label_clr() { + return g_color_label_modified; +} + +const wxColour& get_sys_label_clr() { + return g_color_label_sys; +} + +unsigned get_colour_approx_luma(const wxColour &colour) { - return new wxColour(253, 88, 0); + double r = colour.Red(); + double g = colour.Green(); + double b = colour.Blue(); + + return std::round(std::sqrt( + r * r * .241 + + g * g * .691 + + b * b * .068 + )); } void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value) @@ -686,4 +722,49 @@ ConfigOptionsGroup* get_optgroup() return m_optgroup.get(); } +wxWindow* export_option_creator(wxWindow* parent) +{ + wxPanel* panel = new wxPanel(parent, -1); + wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); + wxCheckBox* cbox = new wxCheckBox(panel, wxID_HIGHEST + 1, L("Export print config")); + sizer->AddSpacer(5); + sizer->Add(cbox, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5); + panel->SetSizer(sizer); + sizer->SetSizeHints(panel); + return panel; +} + +void add_export_option(wxFileDialog* dlg, const std::string& format) +{ + if ((dlg != nullptr) && (format == "AMF") || (format == "3MF")) + { + if (dlg->SupportsExtraControl()) + dlg->SetExtraControlCreator(export_option_creator); + } +} + +int get_export_option(wxFileDialog* dlg) +{ + if (dlg != nullptr) + { + wxWindow* wnd = dlg->GetExtraControl(); + if (wnd != nullptr) + { + wxPanel* panel = dynamic_cast(wnd); + if (panel != nullptr) + { + wxWindow* child = panel->FindWindow(wxID_HIGHEST + 1); + if (child != nullptr) + { + wxCheckBox* cbox = dynamic_cast(child); + if (cbox != nullptr) + return cbox->IsChecked() ? 1 : 0; + } + } + } + } + + return 0; +} + } } diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 084b6de46..24c3ec3f4 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -18,6 +18,7 @@ class wxArrayLong; class wxColour; class wxBoxSizer; class wxFlexGridSizer; +class wxFileDialog; namespace Slic3r { @@ -78,7 +79,10 @@ void set_preset_bundle(PresetBundle *preset_bundle); AppConfig* get_app_config(); wxApp* get_app(); -wxColour* get_modified_label_clr(); + +const wxColour& get_modified_label_clr(); +const wxColour& get_sys_label_clr(); +unsigned get_colour_approx_luma(const wxColour &colour); void add_debug_menu(wxMenuBar *menu, int event_language_change); @@ -130,6 +134,8 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl ConfigOptionsGroup* get_optgroup(); +void add_export_option(wxFileDialog* dlg, const std::string& format); +int get_export_option(wxFileDialog* dlg); } } diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 28823a0ac..2532f28b9 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -82,7 +82,16 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co if (!this->m_disabled) this->back_to_initial_value(opt_id); }; - if (!m_is_tab_opt) field->m_Undo_btn->Hide(); + field->m_back_to_sys_value = [this](std::string opt_id){ + if (!this->m_disabled) + this->back_to_sys_value(opt_id); + }; + if (!m_is_tab_opt) { + field->m_Undo_btn->Hide(); + field->m_Undo_to_sys_btn->Hide(); + } + if (nonsys_btn_icon != nullptr) + field->set_nonsys_btn_icon(nonsys_btn_icon()); // assign function objects for callbacks, etc. return field; @@ -112,7 +121,10 @@ void OptionsGroup::append_line(const Line& line) { const auto& option = option_set.front(); const auto& field = build_field(option); - sizer->Add(field->m_Undo_btn); + auto btn_sizer = new wxBoxSizer(wxHORIZONTAL); + btn_sizer->Add(field->m_Undo_to_sys_btn); + btn_sizer->Add(field->m_Undo_btn); + sizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 0); if (is_window_field(field)) sizer->Add(field->getWindow(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); if (is_sizer_field(field)) @@ -149,6 +161,7 @@ void OptionsGroup::append_line(const Line& line) { const auto& option = option_set.front(); const auto& field = build_field(option, label); + sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL); if (is_window_field(field)) sizer->Add(field->getWindow(), 0, (option.opt.full_width ? wxEXPAND : 0) | @@ -177,6 +190,7 @@ void OptionsGroup::append_line(const Line& line) { // add field const Option& opt_ref = opt; auto& field = build_field(opt_ref, label); + sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL, 0); is_sizer_field(field) ? sizer->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) : @@ -287,7 +301,20 @@ void ConfigOptionsGroup::back_to_initial_value(const std::string opt_key) { if (m_get_initial_config == nullptr) return; - DynamicPrintConfig config = m_get_initial_config(); + back_to_config_value(m_get_initial_config(), opt_key); +} + +void ConfigOptionsGroup::back_to_sys_value(const std::string opt_key) +{ + if (m_get_sys_config == nullptr) + return; + if (!have_sys_config()) + return; + back_to_config_value(m_get_sys_config(), opt_key); +} + +void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, const std::string opt_key) +{ boost::any value; if (opt_key == "extruders_count"){ auto *nozzle_diameter = dynamic_cast(config.option("nozzle_diameter")); @@ -300,15 +327,18 @@ void ConfigOptionsGroup::back_to_initial_value(const std::string opt_key) int opt_index = m_opt_map.at(opt_id).second; value = get_config_value(config, opt_short_key, opt_index); } - else + else{ value = get_config_value(config, opt_key); + change_opt_value(*m_config, opt_key, value); + return; + } set_value(opt_key, value); on_change_OG(opt_key, get_value(opt_key)); } void ConfigOptionsGroup::reload_config(){ - for (std::map< std::string, std::pair >::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) { + for (t_opt_map::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) { auto opt_id = it->first; std::string opt_key = m_opt_map.at(opt_id).first; int opt_index = m_opt_map.at(opt_id).second; @@ -335,7 +365,7 @@ boost::any ConfigOptionsGroup::config_value(std::string opt_key, int opt_index, } } -boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std::string opt_key, int opt_index/* = -1*/) +boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config, std::string opt_key, int opt_index /*= -1*/) { size_t idx = opt_index == -1 ? 0 : opt_index; @@ -379,8 +409,9 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std: ret = text_value; else if (opt->gui_flags.compare("serialized") == 0){ std::vector values = config.option(opt_key)->values; - for (auto el : values) - text_value += el + ";"; + if (!values.empty() && values[0].compare("") != 0) + for (auto el : values) + text_value += el + ";"; ret = text_value; } else @@ -414,7 +445,10 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std: } break; case coPoints: - ret = config.option(opt_key)->get_at(idx); + if (opt_key.compare("bed_shape") == 0) + ret = config.option(opt_key)->values; + else + ret = config.option(opt_key)->get_at(idx); break; case coNone: default: @@ -428,7 +462,7 @@ Field* ConfigOptionsGroup::get_fieldc(t_config_option_key opt_key, int opt_index if (field != nullptr) return field; std::string opt_id = ""; - for (std::map< std::string, std::pair >::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) { + for (t_opt_map::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) { if (opt_key == m_opt_map.at(it->first).first && opt_index == m_opt_map.at(it->first).second){ opt_id = it->first; break; diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index aa0563866..e58d9c9a9 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -69,6 +69,7 @@ private: }; using t_optionfield_map = std::map; +using t_opt_map = std::map< std::string, std::pair >; class OptionsGroup { public: @@ -79,10 +80,14 @@ public: column_t extra_column {nullptr}; t_change m_on_change {nullptr}; std::function m_get_initial_config{ nullptr }; + std::function m_get_sys_config{ nullptr }; + std::function have_sys_config{ nullptr }; wxFont sidetext_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; wxFont label_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; + std::function nonsys_btn_icon{ nullptr }; + /// Returns a copy of the pointer of the parent wxWindow. /// Accessor function is because users are not allowed to change the parent /// but defining it as const means a lot of const_casts to deal with wx functions. @@ -149,6 +154,7 @@ protected: virtual void on_kill_focus (){}; virtual void on_change_OG(t_config_option_key opt_id, boost::any value); virtual void back_to_initial_value(const std::string opt_key){}; + virtual void back_to_sys_value(const std::string opt_key){}; }; class ConfigOptionsGroup: public OptionsGroup { @@ -159,7 +165,7 @@ public: /// reference to libslic3r config, non-owning pointer (?). DynamicPrintConfig* m_config {nullptr}; bool m_full_labels {0}; - std::map< std::string, std::pair > m_opt_map; + t_opt_map m_opt_map; Option get_option(const std::string opt_key, int opt_index = -1); Line create_single_option_line(const std::string title, int idx = -1) /*const*/{ @@ -177,11 +183,13 @@ public: void on_change_OG(t_config_option_key opt_id, boost::any value) override; void back_to_initial_value(const std::string opt_key) override; + void back_to_sys_value(const std::string opt_key) override; + void back_to_config_value(const DynamicPrintConfig& config, const std::string opt_key); void on_kill_focus() override{ reload_config();} void reload_config(); boost::any config_value(std::string opt_key, int opt_index, bool deserialize); // return option value from config - boost::any get_config_value(DynamicPrintConfig& config, std::string opt_key, int opt_index = -1); + boost::any get_config_value(const DynamicPrintConfig& config, std::string opt_key, int opt_index = -1); Field* get_fieldc(t_config_option_key opt_key, int opt_index); }; diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 78a115aa8..9604efb76 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -80,7 +80,7 @@ void Preset::set_num_extruders(DynamicPrintConfig &config, unsigned int num_extr auto *opt = config.option(key, false); assert(opt != nullptr); assert(opt->is_vector()); - if (opt != nullptr && opt->is_vector()) + if (opt != nullptr && opt->is_vector() && key != "default_filament_profile") static_cast(opt)->resize(num_extruders, defaults.option(key)); } } @@ -201,7 +201,7 @@ const std::vector& Preset::print_options() "over_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", "only_one_perimeter_top", - "compatible_printers", "compatible_printers_condition" + "compatible_printers", "compatible_printers_condition", "inherits" }; return s_opts; } @@ -214,7 +214,7 @@ const std::vector& Preset::filament_options() "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", "compatible_printers_condition" + "compatible_printers", "compatible_printers_condition", "inherits" }; return s_opts; } @@ -227,13 +227,16 @@ const std::vector& Preset::printer_options() "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "octoprint_host", "octoprint_apikey", "octoprint_cafile", "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", "max_print_height" + "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "max_print_height", + "default_print_profile", "inherits", }; s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end()); } return s_opts; } +// The following nozzle options of a printer profile will be adjusted to match the size +// of the nozzle_diameter vector. const std::vector& Preset::nozzle_options() { // ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings @@ -241,7 +244,8 @@ const std::vector& Preset::nozzle_options() "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" + "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", + "default_filament_profile" }; return s_opts; } @@ -270,7 +274,7 @@ void PresetCollection::reset(bool delete_files) if (delete_files) { // Erase the preset files. for (Preset &preset : m_presets) - if (! preset.is_default && ! preset.is_external) + if (! preset.is_default && ! preset.is_external && ! preset.is_system) boost::nowide::remove(preset.file.c_str()); } // Don't use m_presets.resize() here as it requires a default constructor for Preset. @@ -285,7 +289,6 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri { 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(); std::string errors_cummulative; for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) @@ -293,6 +296,10 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri std::string name = dir_entry.path().filename().string(); // Remove the .ini suffix. name.erase(name.size() - 4); + if (this->find_preset(name, false)) { + errors_cummulative += "The user preset \"" + name + "\" cannot be loaded. A system preset of the same name has already been loaded."; + continue; + } try { Preset preset(m_type, name, false); preset.file = dir_entry.path().string(); @@ -343,16 +350,33 @@ void PresetCollection::save_current_preset(const std::string &new_name) if (it != m_presets.end() && it->name == new_name) { // Preset with the same name found. Preset &preset = *it; - if (preset.is_default) + if (preset.is_default || preset.is_external || preset.is_system) // 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); + Preset &preset = *m_presets.insert(it, m_edited_preset); + std::string &inherits = preset.config.opt_string("inherits", true); + std::string old_name = preset.name; preset.name = new_name; preset.file = this->path_from_name(new_name); + preset.vendor = nullptr; + if (preset.is_system) { + // Inheriting from a system preset. + inherits = /* preset.vendor->name + "/" + */ old_name; + } else if (inherits.empty()) { + // Inheriting from a user preset. Link the new preset to the old preset. + // inherits = old_name; + } else { + // Inherited from a user preset. Just maintain the "inherited" flag, + // meaning it will inherit from either the system preset, or the inherited user preset. + } + preset.inherits = inherits; + preset.is_default = false; + preset.is_system = false; + preset.is_external = false; } // 2) Activate the saved preset. this->select_preset_by_name(new_name, true); @@ -365,7 +389,7 @@ void PresetCollection::delete_current_preset() const Preset &selected = this->get_selected_preset(); if (selected.is_default) return; - if (! selected.is_external) { + if (! selected.is_external && ! selected.is_system) { // Erase the preset file. boost::nowide::remove(selected.file.c_str()); } @@ -385,6 +409,15 @@ 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); } +const Preset* PresetCollection::get_selected_preset_parent() const +{ + auto *inherits = dynamic_cast(this->get_edited_preset().config.option("inherits")); + if (inherits == nullptr || inherits->value.empty()) + return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; // nullptr; + const Preset* preset = this->find_preset(inherits->value, false); + return (preset == nullptr || preset->is_default || preset->is_external) ? nullptr : preset; +} + // 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) @@ -528,20 +561,34 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) return was_dirty != is_dirty; } -std::vector PresetCollection::current_dirty_options() const -{ - std::vector changed = this->get_selected_preset().config.diff(this->get_edited_preset().config); - // The "compatible_printers" option key is handled differently from the others: - // It is not mandatory. If the key is missing, it means it is compatible with any printer. - // If the key exists and it is empty, it means it is compatible with no printer. - std::initializer_list optional_keys { "compatible_printers", "compatible_printers_condition" }; - for (auto &opt_key : optional_keys) { - if (this->get_selected_preset().config.has(opt_key) != this->get_edited_preset().config.has(opt_key)) - changed.emplace_back(opt_key); +std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference) +{ + std::vector changed; + if (edited != nullptr && reference != nullptr) { + changed = reference->config.diff(edited->config); + // The "compatible_printers" option key is handled differently from the others: + // It is not mandatory. If the key is missing, it means it is compatible with any printer. + // If the key exists and it is empty, it means it is compatible with no printer. + std::initializer_list optional_keys { "compatible_printers", "compatible_printers_condition" }; + for (auto &opt_key : optional_keys) { + if (reference->config.has(opt_key) != edited->config.has(opt_key)) + changed.emplace_back(opt_key); + } } return changed; } +std::vector PresetCollection::system_equal_options() const +{ + const Preset *edited = &this->get_edited_preset(); + const Preset *reference = this->get_selected_preset_parent(); + std::vector equal; + if (edited != nullptr && reference != nullptr) { + equal = reference->config.equal(edited->config); + } + return equal; +} + // Select a new preset. This resets all the edits done to the currently selected preset. // If the preset with index idx does not exist, a first visible preset is selected. Preset& PresetCollection::select_preset(size_t idx) diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index c1d85833b..3634c5dd9 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -23,6 +23,46 @@ enum ConfigFileType extern ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree); +class VendorProfile +{ +public: + std::string name; + std::string id; + std::string config_version; + std::string config_update_url; + + struct PrinterVariant { + PrinterVariant() {} + PrinterVariant(const std::string &name) : name(name) {} + std::string name; + bool enabled = true; + }; + + struct PrinterModel { + PrinterModel() {} + PrinterModel(const std::string &name) : name(name) {} + std::string name; + bool enabled = true; + std::vector variants; + PrinterVariant* variant(const std::string &name) { + for (auto &v : this->variants) + if (v.name == name) + return &v; + return nullptr; + } + const PrinterVariant* variant(const std::string &name) const { return const_cast(this)->variant(name); } + + bool operator< (const PrinterModel &rhs) const { return this->name < rhs.name; } + bool operator==(const PrinterModel &rhs) const { return this->name == rhs.name; } + }; + std::set models; + + size_t num_variants() const { size_t n = 0; for (auto &model : models) n += model.variants.size(); return n; } + + bool operator< (const VendorProfile &rhs) const { return this->id < rhs.id; } + bool operator==(const VendorProfile &rhs) const { return this->id == rhs.id; } +}; + class Preset { public: @@ -44,6 +84,8 @@ public: // External preset points to a configuration, which has been loaded but not imported // into the Slic3r default configuration location. bool is_external = false; + // System preset is read-only. + bool is_system = false; // Preset is visible, if it is compatible with the active Printer. // Also the "default" preset is only visible, if it is the only preset in the list. bool is_visible = true; @@ -55,9 +97,14 @@ public: // Name of the preset, usually derived form the file name. std::string name; // File name of the preset. This could be a Print / Filament / Printer preset, - // or a Configuration file bundling the Print + Filament + Printer presets (in that case is_external will be true), + // or a Configuration file bundling the Print + Filament + Printer presets (in that case is_external and possibly is_system will be true), // or it could be a G-code (again, is_external will be true). std::string file; + // A user profile may inherit its settings either from a system profile, or from a user profile. + // A system profile shall never derive from any other profile, as the system profile hierarchy is being flattened during loading. + std::string inherits; + // If this is a system profile, then there should be a vendor data available to display at the UI. + const VendorProfile *vendor = nullptr; // Has this profile been loaded? bool loaded = false; @@ -144,6 +191,8 @@ public: // 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; } + void set_bitmap_lock (const wxBitmap *bmp) { m_bitmap_lock = bmp; } + void set_bitmap_lock_open (const wxBitmap *bmp) { m_bitmap_lock_open = bmp; } // Enable / disable the "- default -" preset. void set_default_suppressed(bool default_suppressed); @@ -155,6 +204,11 @@ public: 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; } + // For the current edited preset, return the parent preset if there is one. + // If there is no parent preset, nullptr is returned. + // The parent preset may be a system preset or a user preset, which will be + // reflected by the UI. + const Preset* get_selected_preset_parent() const; // 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; } @@ -191,7 +245,13 @@ public: // Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ. bool current_is_dirty() const { 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 current_dirty_options() const; + std::vector current_dirty_options() const + { return dirty_options(&this->get_edited_preset(), &this->get_selected_preset()); } + // Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ. + std::vector current_different_from_parent_options() const + { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent()); } + // Compare the content of get_selected_preset() with get_selected_preset_parent() configs, return the list of keys where they equal. + std::vector system_equal_options() const; // Update the choice UI from the list of presets. // If show_incompatible, all presets are shown, otherwise only the compatible presets are shown. @@ -231,6 +291,8 @@ private: std::deque::const_iterator find_preset_internal(const std::string &name) const { return const_cast(this)->find_preset_internal(name); } + static std::vector dirty_options(const Preset *edited, const Preset *reference); + // Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER. Preset::Type m_type; // List of presets, starting with the "- default -" preset. @@ -245,8 +307,10 @@ private: bool m_default_suppressed = true; // 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_compatible = nullptr; const wxBitmap *m_bitmap_incompatible = nullptr; + const wxBitmap *m_bitmap_lock = nullptr; + const wxBitmap *m_bitmap_lock_open = nullptr; // Marks placed at the wxBitmapComboBox of a MainFrame. // These bitmaps are owned by PresetCollection. wxBitmap *m_bitmap_main_frame; diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index bf79c6562..7131bf771 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -2,6 +2,7 @@ #include #include "PresetBundle.hpp" +#include "BitmapCache.hpp" #include #include @@ -37,7 +38,10 @@ PresetBundle::PresetBundle() : filaments(Preset::TYPE_FILAMENT, Preset::filament_options()), printers(Preset::TYPE_PRINTER, Preset::printer_options()), m_bitmapCompatible(new wxBitmap), - m_bitmapIncompatible(new wxBitmap) + m_bitmapIncompatible(new wxBitmap), + m_bitmapLock(new wxBitmap), + m_bitmapLockOpen(new wxBitmap), + m_bitmapCache(new GUI::BitmapCache) { if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) wxImage::AddHandler(new wxPNGHandler); @@ -51,6 +55,14 @@ PresetBundle::PresetBundle() : this->filaments.preset(0).config.optptr("compatible_printers_condition", true); this->prints.preset(0).config.optptr("compatible_printers", true); this->prints.preset(0).config.optptr("compatible_printers_condition", true); + // Create the "inherits" keys. + this->prints.preset(0).config.optptr("inherits", true); + this->filaments.preset(0).config.optptr("inherits", true); + this->printers.preset(0).config.optptr("inherits", true); + // Create the "printer_vendor", "printer_model" and "printer_variant" keys. + this->printers.preset(0).config.optptr("printer_vendor", true); + this->printers.preset(0).config.optptr("printer_model", true); + this->printers.preset(0).config.optptr("printer_variant", true); this->prints .load_bitmap_default("cog.png"); this->filaments.load_bitmap_default("spool.png"); @@ -62,12 +74,18 @@ PresetBundle::~PresetBundle() { assert(m_bitmapCompatible != nullptr); assert(m_bitmapIncompatible != nullptr); + assert(m_bitmapLock != nullptr); + assert(m_bitmapLockOpen != nullptr); delete m_bitmapCompatible; m_bitmapCompatible = nullptr; delete m_bitmapIncompatible; m_bitmapIncompatible = nullptr; - for (std::pair &bitmap : m_mapColorToBitmap) - delete bitmap.second; + delete m_bitmapLock; + m_bitmapLock = nullptr; + delete m_bitmapLockOpen; + m_bitmapLockOpen = nullptr; + delete m_bitmapCache; + m_bitmapCache = nullptr; } void PresetBundle::reset(bool delete_files) @@ -84,7 +102,8 @@ void PresetBundle::setup_directories() { boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); std::initializer_list paths = { - data_dir, + data_dir, + data_dir / "vendor", #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR // Store the print/filament/printer presets into a "presets" directory. data_dir / "presets", @@ -107,10 +126,12 @@ void PresetBundle::setup_directories() } } -void PresetBundle::load_presets() +void PresetBundle::load_presets(const AppConfig &config) { - std::string errors_cummulative; - const std::string dir_path = data_dir() + // First load the vendor specific system presets. + std::string errors_cummulative = this->load_system_presets(); + + const std::string dir_user_presets = data_dir() #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR // Store the print/filament/printer presets into a "presets" directory. + "/presets" @@ -119,17 +140,17 @@ void PresetBundle::load_presets() #endif ; try { - this->prints.load_presets(dir_path, "print"); + this->prints.load_presets(dir_user_presets, "print"); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { - this->filaments.load_presets(dir_path, "filament"); + this->filaments.load_presets(dir_user_presets, "filament"); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { - this->printers.load_presets(dir_path, "printer"); + this->printers.load_presets(dir_user_presets, "printer"); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } @@ -137,6 +158,31 @@ void PresetBundle::load_presets() this->update_compatible_with_printer(false); if (! errors_cummulative.empty()) throw std::runtime_error(errors_cummulative); + + this->load_selections(config); +} + +// Load system presets into this PresetBundle. +// For each vendor, there will be a single PresetBundle loaded. +std::string PresetBundle::load_system_presets() +{ + // Here the vendor specific read only Config Bundles are stored. + boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred(); + 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 { + // Load the config bundle, flatten it. + this->load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM); + } catch (const std::runtime_error &err) { + errors_cummulative += err.what(); + errors_cummulative += "\n"; + } + } + return errors_cummulative; } static inline std::string remove_ini_suffix(const std::string &name) @@ -147,6 +193,14 @@ static inline std::string remove_ini_suffix(const std::string &name) return out; } +// Set the "enabled" flag for printer vendors, printer models and printer variants +// based on the user configuration. +// If the "vendor" section is missing, enable all models and variants of the particular vendor. +void PresetBundle::load_installed_printers(const AppConfig &config) +{ + // m_storage +} + // 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) @@ -200,10 +254,16 @@ 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"; + const std::string path_bitmap_lock = "lock.png"; + const std::string path_bitmap_lock_open = "lock_open.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); + bool loaded_lock = m_bitmapLock->LoadFile( + wxString::FromUTF8(Slic3r::var(path_bitmap_lock).c_str()), wxBITMAP_TYPE_PNG); + bool loaded_lock_open = m_bitmapLockOpen->LoadFile( + wxString::FromUTF8(Slic3r::var(path_bitmap_lock_open).c_str()), wxBITMAP_TYPE_PNG); if (loaded_compatible) { prints .set_bitmap_compatible(m_bitmapCompatible); filaments.set_bitmap_compatible(m_bitmapCompatible); @@ -214,7 +274,17 @@ bool PresetBundle::load_compatible_bitmaps() filaments.set_bitmap_incompatible(m_bitmapIncompatible); // printers .set_bitmap_incompatible(m_bitmapIncompatible); } - return loaded_compatible && loaded_incompatible; + if (loaded_lock) { + prints .set_bitmap_lock(m_bitmapLock); + filaments.set_bitmap_lock(m_bitmapLock); + printers .set_bitmap_lock(m_bitmapLock); + } + if (loaded_lock_open) { + prints .set_bitmap_lock_open(m_bitmapLock); + filaments.set_bitmap_lock_open(m_bitmapLock); + printers .set_bitmap_lock_open(m_bitmapLock); + } + return loaded_compatible && loaded_incompatible && loaded_lock && loaded_lock_open; } DynamicPrintConfig PresetBundle::full_config() const @@ -594,11 +664,54 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree) flatten_configbundle_hierarchy(tree, "printer"); } +static void load_vendor_profile(const boost::property_tree::ptree &tree, VendorProfile &vendor_profile) +{ + const std::string printer_model_key = "printer_model:"; + for (auto §ion : tree) + if (section.first == "vendor") { + // Load the names of the active presets. + for (auto &kvp : section.second) { + if (kvp.first == "name") + vendor_profile.name = kvp.second.data(); + else if (kvp.first == "id") + vendor_profile.id = kvp.second.data(); + else if (kvp.first == "config_version") + vendor_profile.config_version = kvp.second.data(); + else if (kvp.first == "config_update_url") + vendor_profile.config_update_url = kvp.second.data(); + } + } else if (boost::starts_with(section.first, printer_model_key)) { + VendorProfile::PrinterModel model; + model.name = section.first.substr(printer_model_key.size()); + section.second.get("variants", ""); + std::vector variants; + if (Slic3r::unescape_strings_cstyle(section.second.get("variants", ""), variants)) { + for (const std::string &variant_name : variants) { + if (model.variant(variant_name) == nullptr) + model.variants.emplace_back(VendorProfile::PrinterVariant(variant_name)); + } + } else { + // Log error? + } + if (! model.name.empty() && ! model.variants.empty()) + vendor_profile.models.insert(model); + } +} + +// Load a config bundle file, into presets and store the loaded presets into separate files +// of the local configuration directory. +void PresetBundle::install_vendor_configbundle(const std::string &src_path0) +{ + boost::filesystem::path src_path(src_path0); + boost::filesystem::copy_file(src_path, (boost::filesystem::path(data_dir()) / "vendor" / src_path.filename()).make_preferred(), boost::filesystem::copy_option::overwrite_if_exists); +} + // 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, unsigned int flags) { - if (flags & LOAD_CFGBNDLE_RESET_USER_PROFILE) + if (flags & (LOAD_CFGBNDLE_RESET_USER_PROFILE | LOAD_CFGBNDLE_SYSTEM)) + // Reset this bundle, delete user profile files if LOAD_CFGBNDLE_SAVE. this->reset(flags & LOAD_CFGBNDLE_SAVE); // 1) Read the complete config file into a boost::property_tree. @@ -609,6 +722,17 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla // Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed. flatten_configbundle_hierarchy(tree); + const VendorProfile *vendor_profile = nullptr; + if (flags & LOAD_CFGBNDLE_SYSTEM) { + VendorProfile vp; + load_vendor_profile(tree, vp); + if (vp.name.empty()) + throw std::runtime_error(std::string("Vendor Config Bundle is not valid: Missing vendor name key.")); + if (vp.num_variants() == 0) + return 0; + vendor_profile = &(*this->vendors.insert(vp).first); + } + // 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files. std::vector loaded_prints; std::vector loaded_filaments; @@ -622,15 +746,15 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla std::vector *loaded = nullptr; std::string preset_name; if (boost::starts_with(section.first, "print:")) { - presets = &prints; + presets = &this->prints; loaded = &loaded_prints; preset_name = section.first.substr(6); } else if (boost::starts_with(section.first, "filament:")) { - presets = &filaments; + presets = &this->filaments; loaded = &loaded_filaments; preset_name = section.first.substr(9); } else if (boost::starts_with(section.first, "printer:")) { - presets = &printers; + presets = &this->printers; loaded = &loaded_printers; preset_name = section.first.substr(8); } else if (section.first == "presets") { @@ -664,6 +788,40 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla for (auto &kvp : section.second) config.set_deserialize(kvp.first, kvp.second.data()); Preset::normalize(config); + if ((flags & LOAD_CFGBNDLE_SYSTEM) && presets == &printers) { + // Filter out printer presets, which are not mentioned in the vendor profile. + // These presets are considered not installed. + auto printer_model = config.opt_string("printer_model"); + if (printer_model.empty()) { + BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << + section.first << "\" defines no printer model, it will be ignored."; + continue; + } + auto printer_variant = config.opt_string("printer_variant"); + if (printer_variant.empty()) { + BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << + section.first << "\" defines no printer variant, it will be ignored."; + continue; + } + auto it_model = vendor_profile->models.find(VendorProfile::PrinterModel(printer_model)); + if (it_model == vendor_profile->models.end()) { + BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << + section.first << "\" defines invalid printer model \"" << printer_model << "\", it will be ignored."; + continue; + } + auto it_variant = it_model->variant(printer_variant); + if (it_variant == nullptr) { + BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << + section.first << "\" defines invalid printer variant \"" << printer_variant << "\", it will be ignored."; + continue; + } + const Preset *preset_existing = presets->find_preset(section.first, false); + if (preset_existing != nullptr) { + BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << + section.first << "\" has already been loaded from another Confing Bundle."; + continue; + } + } // 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()) @@ -678,24 +836,29 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla Preset &loaded = presets->load_preset(file_path.string(), preset_name, std::move(config), false); if (flags & LOAD_CFGBNDLE_SAVE) loaded.save(); + if (flags & LOAD_CFGBNDLE_SYSTEM) { + loaded.is_system = true; + loaded.vendor = vendor_profile; + } ++ 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); + if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) { + 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; + this->update_compatible_with_printer(false); + } - 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; - - this->update_compatible_with_printer(false); return presets_loaded; } @@ -836,49 +999,29 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma // 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; + bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + if (preset.is_dirty) + bitmap_key += ",drty"; + wxBitmap *bitmap = m_bitmapCache->find(bitmap_key); 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); + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(preset.is_compatible ? m_bitmapCache->mkclear(16, 16) : *m_bitmapIncompatible); // 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; - } + bmps.emplace_back(m_bitmapCache->mksolid(single_bar ? 24 : 16, 16, rgb)); 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]; - } - } + bmps.emplace_back(m_bitmapCache->mksolid(8, 16, rgb)); } - memDC.DrawBitmap(wxBitmap(image), wide_icons ? 16 : 0, 0, true); - memDC.SelectObject(wxNullBitmap); - m_mapColorToBitmap[bitmap_key] = bitmap; + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmapCache->mkclear(4, 16)); + bmps.emplace_back((preset.is_system || preset.is_default) ? + (preset.is_dirty ? *m_bitmapLockOpen : *m_bitmapLock) : m_bitmapCache->mkclear(16, 16)); + bitmap = m_bitmapCache->insert(bitmap_key, bmps); } ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), (bitmap == 0) ? wxNullBitmap : *bitmap); if (selected) diff --git a/xs/src/slic3r/GUI/PresetBundle.hpp b/xs/src/slic3r/GUI/PresetBundle.hpp index e6aea206d..f481ba374 100644 --- a/xs/src/slic3r/GUI/PresetBundle.hpp +++ b/xs/src/slic3r/GUI/PresetBundle.hpp @@ -4,8 +4,14 @@ #include "AppConfig.hpp" #include "Preset.hpp" +#include + namespace Slic3r { +namespace GUI { + class BitmapCache; +}; + class PlaceholderParser; // Bundle of Print + Filament + Printer presets. @@ -22,11 +28,10 @@ public: 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); + void load_presets(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. @@ -39,6 +44,10 @@ public: // extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size() std::vector filament_presets; + // There will be an entry for each system profile loaded, + // and the system profiles will point to the VendorProfile instances owned by PresetBundle::vendors. + std::set vendors; + bool has_defauls_only() const { return prints.size() <= 1 && filaments.size() <= 1 && printers.size() <= 1; } @@ -69,11 +78,16 @@ public: // Save the profiles, which have been loaded. LOAD_CFGBNDLE_SAVE = 1, // Delete all old config profiles before loading. - LOAD_CFGBNDLE_RESET_USER_PROFILE = 2 + LOAD_CFGBNDLE_RESET_USER_PROFILE = 2, + // Load a system config bundle. + LOAD_CFGBNDLE_SYSTEM = 4, }; // Load the config bundle, store it to the user profile directory by default. size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE); + // Install the Vendor specific config bundle into user's directory. + void install_vendor_configbundle(const std::string &src_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); @@ -99,6 +113,17 @@ public: void update_compatible_with_printer(bool select_other_if_incompatible); private: + std::string load_system_presets(); + + // Set the "enabled" flag for printer vendors, printer models and printer variants + // based on the user configuration. + // If the "vendor" section is missing, enable all models and variants of the particular vendor. + void load_installed_printers(const AppConfig &config); + + // 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); + // Load print, filament & printer presets from a config. If it is an external config, then the name is extracted from the external path. // and the external config is just referenced, not stored into user profile directory. // If it is not an external config, then the config will be stored into the user profile directory. @@ -110,8 +135,12 @@ private: wxBitmap *m_bitmapCompatible; // Indicator, that the preset is NOT compatible with the selected printer. wxBitmap *m_bitmapIncompatible; - // Caching color bitmaps for the - std::map m_mapColorToBitmap; + // Indicator, that the preset is system and not modified. + wxBitmap *m_bitmapLock; + // Indicator, that the preset is system and user modified. + wxBitmap *m_bitmapLockOpen; + // Caching color bitmaps for the filament combo box. + GUI::BitmapCache *m_bitmapCache; }; } // namespace Slic3r diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index ad5d0629c..8028a48ac 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -59,6 +59,17 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_btn_delete_preset->SetToolTip(_(L("Delete this preset"))); m_btn_delete_preset->Disable(); + m_undo_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + m_undo_to_sys_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + if (wxMSW) { + m_undo_btn->SetBackgroundColour(color); + m_undo_to_sys_btn->SetBackgroundColour(color); + } + m_undo_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); + m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); })); + m_undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); + m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); })); + m_hsizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_hsizer, 0, wxBOTTOM, 3); m_hsizer->Add(m_presets_choice, 1, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); @@ -68,6 +79,9 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_hsizer->Add(m_btn_delete_preset, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(16); m_hsizer->Add(m_btn_hide_incompatible_presets, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->AddSpacer(64); + m_hsizer->Add(m_undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->Add(m_undo_btn, 0, wxALIGN_CENTER_VERTICAL); //Horizontal sizer to hold the tree and the selected page. m_hsizer = new wxBoxSizer(wxHORIZONTAL); @@ -101,7 +115,6 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) if (selected_item >= 0){ std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); select_preset(selected_string); - update_changed_ui(); } })); @@ -117,6 +130,14 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) update(); } +void Tab::load_initial_data() +{ + m_config = &m_presets->get_edited_preset().config; + m_nonsys_btn_icon = m_presets->get_selected_preset_parent() == nullptr ? + "bullet_white.png" : + wxMSW ? "sys_unlock.png" : "lock_open.png"; +} + PageShp Tab::add_options_page(wxString title, std::string icon, bool is_extruder_pages/* = false*/) { // Index of icon in an icon list $self->{icons}. @@ -157,6 +178,23 @@ void add_correct_opts_to_dirty_options(const std::string &opt_key, std::vector +void add_correct_opts_to_sys_options(const std::string &opt_key, std::vector *vec, TabPrinter *tab) +{ + const Preset* sys_preset = tab->m_presets->get_selected_preset_parent(); + if (sys_preset == nullptr) + return; + T *opt_cur = static_cast(tab->m_config->option(opt_key)); + const T *opt_sys = static_cast(sys_preset->config.option(opt_key)); + int opt_max_id = opt_sys->values.size()-1; + for (int i = 0; i < opt_cur->values.size(); i++) + { + int init_id = i <= opt_max_id ? i : 0; + if (opt_cur->values[i] == opt_sys->values[init_id]) + vec->emplace_back(opt_key + "#" + std::to_string(i)); + } +} + // Update UI according to changes void Tab::update_changed_ui() { @@ -165,63 +203,273 @@ void Tab::update_changed_ui() if (name() == "printer"){ // Update dirty_options in case changes of Extruder's options TabPrinter* tab = static_cast(this); - std::vector new_dirty; + m_dirty_options.resize(0); for (auto opt_key : dirty_options) { + if (opt_key == "bed_shape"){ m_dirty_options.emplace_back(opt_key); continue; } switch (m_config->option(opt_key)->type()) { - case coInts: add_correct_opts_to_dirty_options(opt_key, &new_dirty, tab); break; - case coBools: add_correct_opts_to_dirty_options(opt_key, &new_dirty, tab); break; - case coFloats: add_correct_opts_to_dirty_options(opt_key, &new_dirty, tab); break; - case coStrings: add_correct_opts_to_dirty_options(opt_key, &new_dirty, tab); break; - case coPercents:add_correct_opts_to_dirty_options(opt_key, &new_dirty, tab); break; - case coPoints: add_correct_opts_to_dirty_options(opt_key, &new_dirty, tab); break; - default: new_dirty.emplace_back(opt_key); break; + case coInts: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; + case coBools: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; + case coFloats: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; + case coStrings: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; + case coPercents:add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; + case coPoints: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; + default: m_dirty_options.emplace_back(opt_key); break; } } + if (tab->m_initial_extruders_count != tab->m_extruders_count) + m_dirty_options.emplace_back("extruders_count"); - dirty_options.resize(0); - dirty_options = new_dirty; - if (tab->m_initial_extruders_count != tab->m_extruders_count){ - dirty_options.emplace_back("extruders_count"); - } - } - - // Add new dirty options to m_dirty_options - for (auto opt_key : dirty_options){ - Field* field = get_field(opt_key); - if (field != nullptr && find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()){ - if (field->m_Label != nullptr){ - field->m_Label->SetForegroundColour(*get_modified_label_clr()); - field->m_Label->Refresh(true); + m_sys_options.resize(0); + const auto sys_preset = m_presets->get_selected_preset_parent(); + if (sys_preset){ + for (auto opt_key : m_config->keys()) + { + if (opt_key == "bed_shape"){ + if (*tab->m_config->option(opt_key) == *sys_preset->config.option(opt_key)) + m_sys_options.emplace_back(opt_key); + continue; + } + switch (m_config->option(opt_key)->type()) + { + case coInts: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; + case coBools: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; + case coFloats: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; + case coStrings: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; + case coPercents:add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; + case coPoints: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; + default:{ + const ConfigOption *opt_cur = tab->m_config->option(opt_key); + const ConfigOption *opt_sys = sys_preset->config.option(opt_key); + if (opt_cur != nullptr && opt_sys != nullptr && *opt_cur == *opt_sys) + m_sys_options.emplace_back(opt_key); + break; + } + } } - field->m_Undo_btn->SetBitmap(wxBitmap(from_u8(wxMSW ? var("action_undo.png") : var("arrow_undo.png")), wxBITMAP_TYPE_PNG)); - field->m_is_modified_value = true; - m_dirty_options.push_back(opt_key); + if (tab->m_sys_extruders_count == tab->m_extruders_count) + m_sys_options.emplace_back("extruders_count"); } } + else{ + m_sys_options = m_presets->system_equal_options(); + m_dirty_options = dirty_options; + } - // Delete clear options from m_dirty_options - for (auto i = 0; i < m_dirty_options.size(); ++i) + //update options "decoration" + for (const auto opt_key : m_full_options_list) { - const std::string &opt_key = m_dirty_options[i]; - Field* field = get_field(opt_key); - if (field != nullptr && find(dirty_options.begin(), dirty_options.end(), opt_key) == dirty_options.end()) + bool is_nonsys_value = false; + bool is_modified_value = true; + std::string sys_icon = wxMSW ? "sys_lock.png" : "lock.png"; + std::string icon = wxMSW ? "action_undo.png" : "arrow_undo.png"; + wxColour color = get_sys_label_clr(); + if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) { + is_nonsys_value = true; + sys_icon = m_nonsys_btn_icon; + if(find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) + color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + else + color = get_modified_label_clr(); + } + if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) { - field->m_Undo_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); + is_modified_value = false; + icon = "bullet_white.png"; + } + Field* field = get_field(opt_key); + if (field == nullptr) continue; + field->m_is_nonsys_value = is_nonsys_value; + field->m_is_modified_value = is_modified_value; + field->m_Undo_btn->SetBitmap(wxBitmap(from_u8(var(icon)), wxBITMAP_TYPE_PNG)); + field->m_Undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(sys_icon)), wxBITMAP_TYPE_PNG)); + if (field->m_Label != nullptr){ + field->m_Label->SetForegroundColour(color); + field->m_Label->Refresh(true); + } + } + + wxTheApp->CallAfter([this]() { + update_changed_tree_ui(); + }); +} + +template +void add_correct_opts_to_full_options_list(const std::string &opt_key, std::vector *vec, TabPrinter *tab) +{ + T *opt_cur = static_cast(tab->m_config->option(opt_key)); + for (int i = 0; i < opt_cur->values.size(); i++) + vec->emplace_back(opt_key + "#" + std::to_string(i)); +} + +void Tab::update_full_options_list() +{ + if (!m_full_options_list.empty()) + m_full_options_list.resize(0); + + if (m_name != "printer"){ + m_full_options_list = m_config->keys(); + return; + } + + TabPrinter* tab = static_cast(this); + for (const auto opt_key : m_config->keys()) + { + if (opt_key == "bed_shape"){ + m_full_options_list.emplace_back(opt_key); + continue; + } + switch (m_config->option(opt_key)->type()) + { + case coInts: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; + case coBools: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; + case coFloats: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; + case coStrings: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; + case coPercents:add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; + case coPoints: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; + default: m_full_options_list.emplace_back(opt_key); break; + } + } + m_full_options_list.emplace_back("extruders_count"); +} + +void Tab::update_sys_ui_after_sel_preset() +{ + for (const auto opt_key : m_full_options_list){ + Field* field = get_field(opt_key); + if (field != nullptr){ + field->m_Undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(m_nonsys_btn_icon)), wxBITMAP_TYPE_PNG)); + field->m_is_nonsys_value = true; if (field->m_Label != nullptr){ - field->m_Label->SetForegroundColour(wxSYS_COLOUR_WINDOWTEXT); + field->m_Label->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); field->m_Label->Refresh(true); } - field->m_is_modified_value = false; - std::vector::iterator itr = find(m_dirty_options.begin(), m_dirty_options.end(), opt_key); - if (itr != m_dirty_options.end()){ - m_dirty_options.erase(itr); - --i; - } } } + m_sys_options.resize(0); +} + +void Tab::update_changed_tree_ui() +{ + auto cur_item = m_treectrl->GetFirstVisibleItem(); + auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); + while (cur_item){ + auto title = m_treectrl->GetItemText(cur_item); + int i=0; + for (auto page : m_pages) + { + if (page->title() != title) + continue; + bool sys_page = true; + bool modified_page = false; + if (title == _("General")){ + std::initializer_list optional_keys{ "extruders_count", "bed_shape" }; + for (auto &opt_key : optional_keys) { + if (sys_page && find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) + sys_page = false; + if (!modified_page && find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) + modified_page = true; + } + } + for (auto group : page->m_optgroups) + { + for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { + const std::string& opt_key = it->first; + if (sys_page && find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) + sys_page = false; + if (!modified_page && find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) + modified_page = true; + } + if (!sys_page && modified_page) + break; + } + if (sys_page) + m_treectrl->SetItemTextColour(cur_item, get_sys_label_clr()); + else if (modified_page) + m_treectrl->SetItemTextColour(cur_item, get_modified_label_clr()); + else + m_treectrl->SetItemTextColour(cur_item, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); + + page->m_is_nonsys_values = !sys_page; + page->m_is_modified_values = modified_page; + + if (selection == title){ + m_is_nonsys_values = page->m_is_nonsys_values; + m_is_modified_values = page->m_is_modified_values; + } + break; + } + auto next_item = m_treectrl->GetNextVisible(cur_item); + cur_item = next_item; + } + update_undo_buttons(); +} + +void Tab::update_undo_buttons() +{ + const std::string& undo_icon = !m_is_modified_values ? "bullet_white.png" : + wxMSW ? "action_undo.png" : "arrow_undo.png"; + const std::string& undo_to_sys_icon = m_is_nonsys_values ? m_nonsys_btn_icon : + wxMSW ? "sys_lock.png" : "lock.png"; + + m_undo_btn->SetBitmap(wxBitmap(from_u8(var(undo_icon)), wxBITMAP_TYPE_PNG)); + m_undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(undo_to_sys_icon)), wxBITMAP_TYPE_PNG)); +} + +void Tab::on_back_to_initial_value() +{ + if (!m_is_modified_values) return; + + auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); + for (auto page : m_pages) + if (page->title() == selection) { + for (auto group : page->m_optgroups){ + if (group->title == _("Capabilities")){ + if (find(m_dirty_options.begin(), m_dirty_options.end(), "extruders_count") != m_dirty_options.end()) + group->back_to_initial_value("extruders_count"); + } + if (group->title == _("Size and coordinates")){ + if (find(m_dirty_options.begin(), m_dirty_options.end(), "bed_shape") != m_dirty_options.end()) + group->back_to_initial_value("bed_shape"); + } + for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { + const std::string& opt_key = it->first; + if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) + group->back_to_initial_value(opt_key); + } + } + break; + } + update_changed_ui(); +} + +void Tab::on_back_to_sys_value() +{ + if (!m_is_nonsys_values) return; + + auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); + for (auto page : m_pages) + if (page->title() == selection) { + for (auto group : page->m_optgroups) { + if (group->title == _("Capabilities")){ + if (find(m_sys_options.begin(), m_sys_options.end(), "extruders_count") == m_sys_options.end()) + group->back_to_sys_value("extruders_count"); + } + if (group->title == _("Size and coordinates")){ + if (find(m_sys_options.begin(), m_sys_options.end(), "bed_shape") == m_sys_options.end()) + group->back_to_sys_value("bed_shape"); + } + for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { + const std::string& opt_key = it->first; + if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) + group->back_to_sys_value(opt_key); + } + } + break; + } + update_changed_ui(); } // Update the combo box label of the selected preset based on its "dirty" state, @@ -350,6 +598,13 @@ void Tab::on_presets_changed() event.SetString(m_name); g_wxMainFrame->ProcessWindowEvent(event); } + + const Preset* parent = m_presets->get_selected_preset_parent(); + const wxString description_line = parent == nullptr ? + _(L("It's default preset")) : parent == &m_presets->get_selected_preset() ? + _(L("It's system preset")) : + _(L("Current preset is inherited from")) + ":\n" + parent->name; + m_parent_preset_description_line->SetText(description_line); } void Tab::update_frequently_changed_parameters() @@ -379,7 +634,7 @@ void Tab::reload_compatible_printers_widget() void TabPrint::build() { m_presets = &m_preset_bundle->prints; - m_config = &m_presets->get_edited_preset().config; + load_initial_data(); auto page = add_options_page(_(L("Layers and perimeters")), "layers.png"); auto optgroup = page->new_optgroup(_(L("Layer height"))); @@ -523,7 +778,7 @@ void TabPrint::build() optgroup->append_single_option_line("interface_shells"); page = add_options_page(_(L("Advanced")), "wrench.png"); - optgroup = page->new_optgroup(_(L("Extrusion width")), 180); + optgroup = page->new_optgroup(_(L("Extrusion width"))); optgroup->append_single_option_line("extrusion_width"); optgroup->append_single_option_line("first_layer_extrusion_width"); optgroup->append_single_option_line("perimeter_extrusion_width"); @@ -589,6 +844,13 @@ void TabPrint::build() option = optgroup->get_option("compatible_printers_condition"); option.opt.full_width = true; optgroup->append_single_option_line(option); + + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); } // Reload current config (aka presets->edited_preset->config) into the UI fields. @@ -601,9 +863,11 @@ void TabPrint::update() { Freeze(); + double fill_density = m_config->option("fill_density")->value; + if (m_config->opt_bool("spiral_vase") && !(m_config->opt_int("perimeters") == 1 && m_config->opt_int("top_solid_layers") == 0 && - m_config->option("fill_density")->value == 0)) { + fill_density == 0)) { wxString msg_text = _(L("The Spiral Vase mode requires:\n" "- one perimeter\n" "- no top solid layers\n" @@ -620,11 +884,13 @@ void TabPrint::update() new_conf.set_key_value("support_material", new ConfigOptionBool(false)); new_conf.set_key_value("support_material_enforce_layers", new ConfigOptionInt(0)); new_conf.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionBool(false)); + fill_density = 0; } else { new_conf.set_key_value("spiral_vase", new ConfigOptionBool(false)); } load_config(new_conf); + on_value_change("fill_density", fill_density); } auto first_layer_height = m_config->option("first_layer_height")->value; @@ -744,7 +1010,6 @@ void TabPrint::update() "\nShall I switch to rectilinear fill pattern?")); auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Infill")), wxICON_WARNING | wxYES | wxNO); DynamicPrintConfig new_conf = *m_config; - double fill_density; if (dialog->ShowModal() == wxID_YES) { new_conf.set_key_value("fill_pattern", new ConfigOptionEnum(ipRectilinear)); fill_density = 100; @@ -865,7 +1130,7 @@ void TabPrint::OnActivate() void TabFilament::build() { m_presets = &m_preset_bundle->filaments; - m_config = &m_preset_bundle->filaments.get_edited_preset().config; + load_initial_data(); auto page = add_options_page(_(L("Filament")), "spool.png"); auto optgroup = page->new_optgroup(_(L("Filament"))); @@ -959,6 +1224,13 @@ void TabFilament::build() option = optgroup->get_option("compatible_printers_condition"); option.opt.full_width = true; optgroup->append_single_option_line(option); + + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); } // Reload current config (aka presets->edited_preset->config) into the UI fields. @@ -1012,11 +1284,13 @@ bool Tab::current_preset_is_dirty() void TabPrinter::build() { m_presets = &m_preset_bundle->printers; - m_config = &m_preset_bundle->printers.get_edited_preset().config; - auto default_config = m_preset_bundle->full_config(); + load_initial_data(); auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); m_initial_extruders_count = m_extruders_count = nozzle_diameter->values.size(); + const Preset* parent_preset = m_presets->get_selected_preset_parent(); + m_sys_extruders_count = parent_preset == nullptr ? 0 : + static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); auto page = add_options_page(_(L("General")), "printer_empty.png"); auto optgroup = page->new_optgroup(_(L("Size and coordinates"))); @@ -1034,8 +1308,10 @@ void TabPrinter::build() { auto dlg = new BedShapeDialog(this); dlg->build_dialog(m_config->option("bed_shape")); - if (dlg->ShowModal() == wxID_OK) + if (dlg->ShowModal() == wxID_OK){ load_key_value("bed_shape", dlg->GetValue()); + update_changed_ui(); + } })); return sizer; @@ -1253,6 +1529,15 @@ void TabPrinter::build() option.opt.height = 250; optgroup->append_single_option_line(option); + page = add_options_page(_(L("Dependencies")), "wrench.png"); + optgroup = page->new_optgroup(_(L("Profile dependencies"))); + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); + build_extruder_pages(); if (!m_no_controller) @@ -1429,12 +1714,17 @@ void TabPrinter::update(){ void Tab::load_current_preset() { auto preset = m_presets->get_edited_preset(); + preset.is_default ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); update(); // For the printer profile, generate the extruder pages. on_preset_loaded(); // Reload preset pages with the new configuration values. reload_config(); + const Preset* parent = m_presets->get_selected_preset_parent(); + m_nonsys_btn_icon = parent == nullptr ? + "bullet_white.png" : + wxMSW ? "sys_unlock.png" : "lock_open.png"; // 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 @@ -1449,8 +1739,14 @@ void Tab::load_current_preset() if (name() == "print") update_frequently_changed_parameters(); - if (m_name == "printer") + if (m_name == "printer"){ static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; + const Preset* parent_preset = m_presets->get_selected_preset_parent(); + static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : + static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); + } + update_sys_ui_after_sel_preset(); + update_full_options_list(); update_changed_ui(); }); } @@ -1596,6 +1892,8 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) if (p->title() == selection) { page = p.get(); + m_is_nonsys_values = page->m_is_nonsys_values; + m_is_modified_values = page->m_is_modified_values; break; } if (page == nullptr) return; @@ -1605,6 +1903,8 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) page->Show(); m_hsizer->Layout(); Refresh(); + + update_undo_buttons(); } void Tab::OnKeyDown(wxKeyEvent& event) @@ -1640,7 +1940,7 @@ void Tab::save_preset(std::string name /*= ""*/) std::vector values; for (size_t i = 0; i < m_presets->size(); ++i) { const Preset &preset = m_presets->preset(i); - if (preset.is_default || preset.is_external) + if (preset.is_default || preset.is_system || preset.is_external) continue; values.push_back(preset.name); } @@ -1654,6 +1954,15 @@ void Tab::save_preset(std::string name /*= ""*/) show_error(this, _(L("The supplied name is empty. It can't be saved."))); return; } + const Preset *existing = m_presets->find_preset(name, false); + if (existing && (existing->is_default || existing->is_system)) { + show_error(this, _(L("Cannot overwrite a system profile."))); + return; + } + if (existing && (existing->is_external)) { + show_error(this, _(L("Cannot overwrite an external."))); + return; + } } // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini @@ -1664,6 +1973,10 @@ void Tab::save_preset(std::string name /*= ""*/) update_tab_ui(); // Update the selection boxes at the platter. on_presets_changed(); + + if (m_name == "printer") + static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; + update_changed_ui(); } // Called for a currently selected preset. @@ -1753,7 +2066,7 @@ wxSizer* Tab::compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox for (size_t idx = 0; idx < printers->size(); ++idx) { Preset& preset = printers->preset(idx); - if (!preset.is_default && !preset.is_external) + if (!preset.is_default && !preset.is_external && !preset.is_system) presets.Add(preset.name); } @@ -1839,6 +2152,19 @@ ConfigOptionsGroupShp Page::new_optgroup(wxString title, int noncommon_label_wid return config; }; + optgroup->m_get_sys_config = [this](){ + DynamicPrintConfig config = static_cast(GetParent())->m_presets->get_selected_preset_parent()->config; + return config; + }; + + optgroup->have_sys_config = [this](){ + return static_cast(GetParent())->m_presets->get_selected_preset_parent() != nullptr; + }; + + optgroup->nonsys_btn_icon = [this](){ + return static_cast(GetParent())->m_nonsys_btn_icon; + }; + vsizer()->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); m_optgroups.push_back(optgroup); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 4f65f1475..f39ff728c 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -54,6 +54,9 @@ public: } ~Page(){} + bool m_is_modified_values{ false }; + bool m_is_nonsys_values{ true }; + public: std::vector m_optgroups; DynamicPrintConfig* m_config; @@ -90,26 +93,35 @@ protected: wxImageList* m_icons; wxCheckBox* m_compatible_printers_checkbox; wxButton* m_compatible_printers_btn; + wxButton* m_undo_btn; + wxButton* m_undo_to_sys_btn; int m_icon_count; - std::map m_icon_index; // Map from an icon file name to its index in $self->{icons}. - std::vector m_pages; // $self->{pages} = []; + std::map m_icon_index; // Map from an icon file name to its index + std::vector m_pages; bool m_disable_tree_sel_changed_event; bool m_show_incompatible_presets; bool m_no_controller; std::vector m_reload_dependent_tabs = {}; std::vector m_dirty_options = {}; + std::vector m_sys_options = {}; + std::vector m_full_options_list = {}; // The two following two event IDs are generated at Plater.pm by calling Wx::NewEventType. wxEventType m_event_value_change = 0; wxEventType m_event_presets_changed = 0; + bool m_is_modified_values{ false }; + bool m_is_nonsys_values{ true }; + public: PresetBundle* m_preset_bundle; bool m_show_btn_incompatible_presets = false; PresetCollection* m_presets; DynamicPrintConfig* m_config; + std::string m_nonsys_btn_icon; + ogStaticText* m_parent_preset_description_line; public: Tab() {} @@ -147,13 +159,21 @@ public: void update_show_hide_incompatible_button(); void update_ui_from_settings(); void update_changed_ui(); - + void update_full_options_list(); + void update_sys_ui_after_sel_preset(); + void update_changed_tree_ui(); + void update_undo_buttons(); + + void on_back_to_initial_value(); + void on_back_to_sys_value(); + PageShp add_options_page(wxString title, std::string icon, bool is_extruder_pages = false); virtual void OnActivate(){} virtual void on_preset_loaded(){} virtual void build() = 0; virtual void update() = 0; + void load_initial_data(); void update_dirty(); void update_tab_ui(); void load_config(DynamicPrintConfig config); @@ -220,6 +240,7 @@ public: size_t m_extruders_count; size_t m_initial_extruders_count; + size_t m_sys_extruders_count; std::vector m_extruder_pages; TabPrinter() {} diff --git a/xs/src/slic3r/Utils/Http.cpp b/xs/src/slic3r/Utils/Http.cpp index de28904e2..0826284d8 100644 --- a/xs/src/slic3r/Utils/Http.cpp +++ b/xs/src/slic3r/Utils/Http.cpp @@ -36,16 +36,20 @@ struct Http::priv ::curl_slist *headerlist; std::string buffer; size_t limit; + bool cancel; std::thread io_thread; Http::CompleteFn completefn; Http::ErrorFn errorfn; + Http::ProgressFn progressfn; priv(const std::string &url); ~priv(); static bool ca_file_supported(::CURL *curl); static size_t writecb(void *data, size_t size, size_t nmemb, void *userp); + static int xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); + static int xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow); std::string curl_error(CURLcode curlcode); std::string body_size_error(); void http_perform(); @@ -55,7 +59,8 @@ Http::priv::priv(const std::string &url) : curl(::curl_easy_init()), form(nullptr), form_end(nullptr), - headerlist(nullptr) + headerlist(nullptr), + cancel(false) { if (curl == nullptr) { throw std::runtime_error(std::string("Could not construct Curl object")); @@ -112,6 +117,24 @@ size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp) return realsize; } +int Http::priv::xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) +{ + auto self = static_cast(userp); + bool cb_cancel = false; + + if (self->progressfn) { + Progress progress(dltotal, dlnow, ultotal, ulnow); + self->progressfn(progress, cb_cancel); + } + + return self->cancel || cb_cancel; +} + +int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow) +{ + return xfercb(userp, dltotal, dlnow, ultotal, ulnow); +} + std::string Http::priv::curl_error(CURLcode curlcode) { return (boost::format("%1% (%2%)") @@ -132,6 +155,16 @@ void Http::priv::http_perform() ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb); ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast(this)); + ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); +#if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 32 + ::curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xfercb); + ::curl_easy_setopt(curl, CURLOPT_XFERINFODATA, static_cast(this)); + (void)xfercb_legacy; // prevent unused function warning +#else + ::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, xfercb); + ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, static_cast(this)); +#endif + #ifndef NDEBUG ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); #endif @@ -149,16 +182,16 @@ void Http::priv::http_perform() ::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status); if (res != CURLE_OK) { - std::string error; - if (res == CURLE_WRITE_ERROR) { - error = std::move(body_size_error()); - } else { - error = std::move(curl_error(res)); - }; - - if (errorfn) { - errorfn(std::move(buffer), std::move(error), http_status); + if (res == CURLE_ABORTED_BY_CALLBACK) { + Progress dummyprogress(0, 0, 0, 0); + bool cancel = true; + if (progressfn) { progressfn(dummyprogress, cancel); } } + else if (res == CURLE_WRITE_ERROR) { + if (errorfn) { errorfn(std::move(buffer), std::move(body_size_error()), http_status); } + } else { + if (errorfn) { errorfn(std::move(buffer), std::move(curl_error(res)), http_status); } + }; } else { if (completefn) { completefn(std::move(buffer), http_status); @@ -258,6 +291,12 @@ Http& Http::on_error(ErrorFn fn) return *this; } +Http& Http::on_progress(ProgressFn fn) +{ + if (p) { p->progressfn = std::move(fn); } + return *this; +} + Http::Ptr Http::perform() { auto self = std::make_shared(std::move(*this)); @@ -277,6 +316,11 @@ void Http::perform_sync() if (p) { p->http_perform(); } } +void Http::cancel() +{ + if (p) { p->cancel = true; } +} + Http Http::get(std::string url) { return std::move(Http{std::move(url)}); @@ -297,5 +341,16 @@ bool Http::ca_file_supported() return res; } +std::ostream& operator<<(std::ostream &os, const Http::Progress &progress) +{ + os << "Http::Progress(" + << "dltotal = " << progress.dltotal + << ", dlnow = " << progress.dlnow + << ", ultotal = " << progress.ultotal + << ", ulnow = " << progress.ulnow + << ")"; + return os; +} + } diff --git a/xs/src/slic3r/Utils/Http.hpp b/xs/src/slic3r/Utils/Http.hpp index 6ac5fcce1..7ed8196e6 100644 --- a/xs/src/slic3r/Utils/Http.hpp +++ b/xs/src/slic3r/Utils/Http.hpp @@ -14,9 +14,22 @@ class Http : public std::enable_shared_from_this { private: struct priv; public: + struct Progress + { + size_t dltotal; + size_t dlnow; + size_t ultotal; + size_t ulnow; + + Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow) : + dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow) + {} + }; + typedef std::shared_ptr Ptr; typedef std::function CompleteFn; typedef std::function ErrorFn; + typedef std::function ProgressFn; Http(Http &&other); @@ -37,9 +50,11 @@ public: Http& on_complete(CompleteFn fn); Http& on_error(ErrorFn fn); + Http& on_progress(ProgressFn fn); Ptr perform(); void perform_sync(); + void cancel(); static bool ca_file_supported(); private: @@ -48,6 +63,8 @@ private: std::unique_ptr p; }; +std::ostream& operator<<(std::ostream &, const Http::Progress &); + } diff --git a/xs/src/slic3r/Utils/OctoPrint.cpp b/xs/src/slic3r/Utils/OctoPrint.cpp index 5bf51f470..e63a16c38 100644 --- a/xs/src/slic3r/Utils/OctoPrint.cpp +++ b/xs/src/slic3r/Utils/OctoPrint.cpp @@ -1,10 +1,11 @@ #include "OctoPrint.hpp" -#include +#include #include #include #include +#include #include "libslic3r/PrintConfig.hpp" #include "slic3r/GUI/GUI.hpp" @@ -39,36 +40,53 @@ bool OctoPrint::test(wxString &msg) const return res; } -void OctoPrint::send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print) const +bool OctoPrint::send_gcode(const std::string &filename, bool print) const { + enum { PROGRESS_RANGE = 1000 }; + + const auto errortitle = _(L("Error while uploading to the OctoPrint server")); + + wxProgressDialog progress_dialog( + _(L("OctoPrint upload")), + _(L("Sending G-code file to the OctoPrint server...")), + PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT); + progress_dialog.Pulse(); + + wxString test_msg; + if (!test(test_msg)) { + auto errormsg = wxString::Format("%s: %s", errortitle, test_msg); + GUI::show_error(&progress_dialog, std::move(errormsg)); + return false; + } + + bool res = true; + auto http = Http::post(std::move(make_url("api/files/local"))); set_auth(http); http.form_add("print", print ? "true" : "false") .form_add_file("file", filename) - .on_complete([=](std::string body, unsigned status) { - wxWindow *window = wxWindow::FindWindowById(windowId); - if (window == nullptr) { return; } - - wxCommandEvent* evt = new wxCommandEvent(completeEvt); - evt->SetString(_(L("G-code file successfully uploaded to the OctoPrint server"))); - evt->SetInt(100); - wxQueueEvent(window, evt); + .on_complete([&](std::string body, unsigned status) { + progress_dialog.Update(PROGRESS_RANGE); }) - .on_error([=](std::string body, std::string error, unsigned status) { - wxWindow *window = wxWindow::FindWindowById(windowId); - if (window == nullptr) { return; } - - wxCommandEvent* evt_complete = new wxCommandEvent(completeEvt); - evt_complete->SetInt(100); - wxQueueEvent(window, evt_complete); - - wxCommandEvent* evt_error = new wxCommandEvent(errorEvt); - evt_error->SetString(wxString::Format("%s: %s", - _(L("Error while uploading to the OctoPrint server")), - format_error(error, status))); - wxQueueEvent(window, evt_error); + .on_error([&](std::string body, std::string error, unsigned status) { + auto errormsg = wxString::Format("%s: %s", errortitle, format_error(error, status)); + GUI::show_error(&progress_dialog, std::move(errormsg)); + res = false; }) - .perform(); + .on_progress([&](Http::Progress progress, bool &cancel) { + if (cancel) { + // Upload was canceled + res = false; + } else if (progress.ultotal > 0) { + int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal; + cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1)); // Cap the value to prevent premature dialog closing + } else { + cancel = !progress_dialog.Pulse(); + } + }) + .perform_sync(); + + return res; } void OctoPrint::set_auth(Http &http) const diff --git a/xs/src/slic3r/Utils/OctoPrint.hpp b/xs/src/slic3r/Utils/OctoPrint.hpp index 1f544295c..744b4fcc1 100644 --- a/xs/src/slic3r/Utils/OctoPrint.hpp +++ b/xs/src/slic3r/Utils/OctoPrint.hpp @@ -17,7 +17,7 @@ public: OctoPrint(DynamicPrintConfig *config); bool test(wxString &curl_msg) const; - void send_gcode(int windowId, int completeEvt, int errorEvt, const std::string &filename, bool print = false) const; + bool send_gcode(const std::string &filename, bool print = false) const; private: std::string host; std::string apikey; diff --git a/xs/src/slic3r/Utils/Semver.hpp b/xs/src/slic3r/Utils/Semver.hpp new file mode 100644 index 000000000..7fc3b8033 --- /dev/null +++ b/xs/src/slic3r/Utils/Semver.hpp @@ -0,0 +1,114 @@ +#ifndef slic3r_Semver_hpp_ +#define slic3r_Semver_hpp_ + +#include +#include +#include +#include + +#include "semver/semver.h" + +namespace Slic3r { + + +class Semver +{ +public: + struct Major { const int i; Major(int i) : i(i) {} }; + struct Minor { const int i; Minor(int i) : i(i) {} }; + struct Patch { const int i; Patch(int i) : i(i) {} }; + + static boost::optional parse(const std::string &str) + { + semver_t ver; + if (::semver_parse(str.c_str(), &ver) == 0) { + return Semver(ver); + } else { + return boost::none; + } + } + + static const Semver zero() + { + static semver_t ver = { 0, 0, 0, nullptr, nullptr }; + return Semver(ver); + } + + static const Semver inf() + { + static semver_t ver = { std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max(), nullptr, nullptr }; + return Semver(ver); + } + + static const Semver invalid() + { + static semver_t ver = { -1, 0, 0, nullptr, nullptr }; + return Semver(ver); + } + + Semver(Semver &&other) { *this = std::move(other); } + Semver(const Semver &other) { *this = other; } + + Semver &operator=(Semver &&other) + { + ver = other.ver; + other.ver.major = other.ver.minor = other.ver.patch = 0; + other.ver.metadata = other.ver.prerelease = nullptr; + return *this; + } + + Semver &operator=(const Semver &other) + { + ::semver_free(&ver); + ver = other.ver; + if (other.ver.metadata != nullptr) { std::strcpy(ver.metadata, other.ver.metadata); } + if (other.ver.prerelease != nullptr) { std::strcpy(ver.prerelease, other.ver.prerelease); } + return *this; + } + + ~Semver() { ::semver_free(&ver); } + + // Comparison + bool operator<(const Semver &b) const { return ::semver_compare(ver, b.ver) == -1; } + bool operator<=(const Semver &b) const { return ::semver_compare(ver, b.ver) <= 0; } + bool operator==(const Semver &b) const { return ::semver_compare(ver, b.ver) == 0; } + bool operator!=(const Semver &b) const { return ::semver_compare(ver, b.ver) != 0; } + bool operator>=(const Semver &b) const { return ::semver_compare(ver, b.ver) >= 0; } + bool operator>(const Semver &b) const { return ::semver_compare(ver, b.ver) == 1; } + // We're using '&' instead of the '~' operator here as '~' is unary-only: + // Satisfies patch if Major and minor are equal. + bool operator&(const Semver &b) const { return ::semver_satisfies_patch(ver, b.ver); } + bool operator^(const Semver &b) const { return ::semver_satisfies_caret(ver, b.ver); } + bool in_range(const Semver &low, const Semver &high) const { return low <= *this && *this <= high; } + + // Conversion + std::string to_string() const { + auto res = (boost::format("%1%.%2%.%3%") % ver.major % ver.minor % ver.patch).str(); + if (ver.prerelease != nullptr) { res += '-'; res += ver.prerelease; } + if (ver.metadata != nullptr) { res += '+'; res += ver.metadata; } + return res; + } + + // Arithmetics + Semver& operator+=(const Major &b) { ver.major += b.i; return *this; } + Semver& operator+=(const Minor &b) { ver.minor += b.i; return *this; } + Semver& operator+=(const Patch &b) { ver.patch += b.i; return *this; } + Semver& operator-=(const Major &b) { ver.major -= b.i; return *this; } + Semver& operator-=(const Minor &b) { ver.minor -= b.i; return *this; } + Semver& operator-=(const Patch &b) { ver.patch -= b.i; return *this; } + Semver operator+(const Major &b) const { Semver res(*this); return res += b; } + Semver operator+(const Minor &b) const { Semver res(*this); return res += b; } + Semver operator+(const Patch &b) const { Semver res(*this); return res += b; } + Semver operator-(const Major &b) const { Semver res(*this); return res -= b; } + Semver operator-(const Minor &b) const { Semver res(*this); return res -= b; } + Semver operator-(const Patch &b) const { Semver res(*this); return res -= b; } + +private: + semver_t ver; + + Semver(semver_t ver) : ver(ver) {} +}; + + +} +#endif diff --git a/xs/src/slic3r/Utils/Time.cpp b/xs/src/slic3r/Utils/Time.cpp new file mode 100644 index 000000000..c4123c7bb --- /dev/null +++ b/xs/src/slic3r/Utils/Time.cpp @@ -0,0 +1,63 @@ +#include "Time.hpp" + +namespace Slic3r { +namespace Utils { + +time_t parse_time_ISO8601Z(const std::string &sdate) +{ + int y, M, d, h, m; + float s; + if (sscanf(sdate.c_str(), "%d-%d-%dT%d:%d:%fZ", &y, &M, &d, &h, &m, &s) != 6) + return (time_t)-1; + struct tm tms; + tms.tm_year = y - 1900; // Year since 1900 + tms.tm_mon = M - 1; // 0-11 + tms.tm_mday = d; // 1-31 + tms.tm_hour = h; // 0-23 + tms.tm_min = m; // 0-59 + tms.tm_sec = (int)s; // 0-61 (0-60 in C++11) + return mktime(&tms); +} + +std::string format_time_ISO8601Z(time_t time) +{ + struct tm tms; +#ifdef WIN32 + gmtime_s(time, &tms); +#else + gmtime_r(&tms, time); +#endif + char buf[128]; + sprintf(buf, "%d-%d-%dT%d:%d:%fZ", + tms.tm_year + 1900 + tms.tm_mon + 1 + tms.tm_mday + tms.tm_hour + tms.tm_min + tms.tm_sec); + return buf; +} + +time_t get_current_time_utc() +{ +#ifdef WIN32 + SYSTEMTIME st; + ::GetSystemTime(&st); + std::tm tm; + tm.tm_sec = st.wSecond; + tm.tm_min = st.wMinute; + tm.tm_hour = st.wHour; + tm.tm_mday = st.wDay; + tm.tm_mon = st.wMonth - 1; + tm.tm_year = st.wYear - 1900; + tm.tm_isdst = -1; + return mktime(&tm); +#else + return gmtime(); +#endif +} + +}; // namespace Utils +}; // namespace Slic3r + +#endif /* slic3r_Utils_Time_hpp_ */ diff --git a/xs/src/slic3r/Utils/Time.hpp b/xs/src/slic3r/Utils/Time.hpp new file mode 100644 index 000000000..6b2fbf893 --- /dev/null +++ b/xs/src/slic3r/Utils/Time.hpp @@ -0,0 +1,22 @@ +#ifndef slic3r_Utils_Time_hpp_ +#define slic3r_Utils_Time_hpp_ + +#include +#include + +namespace Slic3r { +namespace Utils { + +// Utilities to convert an UTC time_t to/from an ISO8601 time format, +// useful for putting timestamps into file and directory names. +// Returns (time_t)-1 on error. +extern time_t parse_time_ISO8601Z(const std::string &s); +extern std::string format_time_ISO8601Z(time_t time); + +// There is no gmtime() on windows. +time_t get_current_time_utc(); + +}; // namespace Utils +}; // namespace Slic3r + +#endif /* slic3r_Utils_Time_hpp_ */ diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index d306f12ce..1376ff164 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -67,3 +67,10 @@ void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_siz std::string fold_utf8_to_ascii(const char *src) %code%{ RETVAL = Slic3r::fold_utf8_to_ascii(src); %}; + +void add_export_option(SV *ui, std::string format) + %code%{ Slic3r::GUI::add_export_option((wxFileDialog*)wxPli_sv_2_object(aTHX_ ui, "Wx::FileDialog"), format); %}; + +int get_export_option(SV *ui) + %code%{ RETVAL = Slic3r::GUI::get_export_option((wxFileDialog*)wxPli_sv_2_object(aTHX_ ui, "Wx::FileDialog")); %}; + \ No newline at end of file diff --git a/xs/xsp/GUI_AppConfig.xsp b/xs/xsp/GUI_AppConfig.xsp index 8162b9a5e..08a88883d 100644 --- a/xs/xsp/GUI_AppConfig.xsp +++ b/xs/xsp/GUI_AppConfig.xsp @@ -41,4 +41,6 @@ void update_skein_dir(char *dir); std::string get_last_output_dir(const char *alt = ""); void update_last_output_dir(char *dir); + + void reset_selections(); }; diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp index 0033ebd0e..84efdde53 100644 --- a/xs/xsp/GUI_Preset.xsp +++ b/xs/xsp/GUI_Preset.xsp @@ -12,6 +12,7 @@ bool default() %code%{ RETVAL = THIS->is_default; %}; bool external() %code%{ RETVAL = THIS->is_external; %}; + bool system() %code%{ RETVAL = THIS->is_system; %}; bool visible() %code%{ RETVAL = THIS->is_visible; %}; bool dirty() %code%{ RETVAL = THIS->is_dirty; %}; bool compatible() %code%{ RETVAL = THIS->is_compatible; %}; @@ -110,10 +111,10 @@ PresetCollection::arrayref() croak("Cannot create configuration directories:\n%s\n", e.what()); } %}; - void load_presets() + void load_presets(AppConfig *config) %code%{ try { - THIS->load_presets(); + THIS->load_presets(*config); } catch (std::exception& e) { croak("Loading of Slic3r presets from %s failed.\n\n%s\n", Slic3r::data_dir().c_str(), e.what()); @@ -143,6 +144,14 @@ PresetCollection::arrayref() croak("Loading of a config bundle %s failed:\n%s\n", path, e.what()); } %}; + void install_vendor_configbundle(const char *path) + %code%{ + try { + THIS->install_vendor_configbundle(path); + } catch (std::exception& e) { + croak("Installing a vendor config bundle %s failed:\n%s\n", path, e.what()); + } + %}; void export_configbundle(char *path) %code%{ try { @@ -154,7 +163,6 @@ PresetCollection::arrayref() 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); %}; diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 78c94661e..702839537 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -104,10 +104,10 @@ bool store_stl(char *path, bool binary) %code%{ TriangleMesh mesh = THIS->mesh(); RETVAL = Slic3r::store_stl(path, &mesh, binary); %}; - bool store_amf(char *path, Print* print) - %code%{ RETVAL = Slic3r::store_amf(path, THIS, print); %}; - bool store_3mf(char *path, Print* print) - %code%{ RETVAL = Slic3r::store_3mf(path, THIS, print); %}; + bool store_amf(char *path, Print* print, bool export_print_config) + %code%{ RETVAL = Slic3r::store_amf(path, THIS, print, export_print_config); %}; + bool store_3mf(char *path, Print* print, bool export_print_config) + %code%{ RETVAL = Slic3r::store_3mf(path, THIS, print, export_print_config); %}; %{ diff --git a/xs/xsp/Utils_OctoPrint.xsp b/xs/xsp/Utils_OctoPrint.xsp index 124f66cb5..282a3055d 100644 --- a/xs/xsp/Utils_OctoPrint.xsp +++ b/xs/xsp/Utils_OctoPrint.xsp @@ -9,5 +9,5 @@ OctoPrint(DynamicPrintConfig *config); ~OctoPrint(); - void send_gcode(int windowId, int completeEvt, int errorEvt, std::string filename, bool print = false) const; + bool send_gcode(std::string filename, bool print = false) const; };