From fe3b92870f217a3356094a0a6ad60b25ccf18996 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 17 Sep 2018 15:12:13 +0200 Subject: [PATCH] Merged with dev --- lib/Slic3r/GUI.pm | 24 - lib/Slic3r/GUI/MainFrame.pm | 12 +- lib/Slic3r/GUI/Plater.pm | 257 ++++--- lib/Slic3r/GUI/Plater/3DPreview.pm | 354 ++++----- resources/icons/support_blocker.png | Bin 0 -> 656 bytes resources/icons/support_enforcer.png | Bin 0 -> 509 bytes xs/CMakeLists.txt | 3 +- xs/src/libnest2d/libnest2d/libnest2d.hpp | 8 + .../libnest2d/selections/djd_heuristic.hpp | 14 +- .../libnest2d/libnest2d/selections/filler.hpp | 2 +- .../libnest2d/selections/firstfit.hpp | 11 +- .../selections/selection_boilerplate.hpp | 8 +- xs/src/libslic3r/EdgeGrid.cpp | 6 +- xs/src/libslic3r/EdgeGrid.hpp | 2 +- xs/src/libslic3r/ExPolygonCollection.cpp | 9 +- xs/src/libslic3r/ExtrusionEntity.hpp | 40 +- .../libslic3r/ExtrusionEntityCollection.hpp | 16 +- xs/src/libslic3r/Fill/FillHoneycomb.cpp | 4 +- xs/src/libslic3r/Flow.cpp | 9 +- xs/src/libslic3r/Format/3mf.cpp | 20 +- xs/src/libslic3r/Format/AMF.cpp | 64 +- xs/src/libslic3r/Format/PRUS.cpp | 18 +- xs/src/libslic3r/GCode.cpp | 11 +- xs/src/libslic3r/GCode.hpp | 1 + xs/src/libslic3r/GCode/WipeTower.hpp | 6 + xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 47 +- xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp | 10 +- xs/src/libslic3r/LayerRegion.cpp | 3 +- xs/src/libslic3r/Model.cpp | 103 ++- xs/src/libslic3r/Model.hpp | 69 +- xs/src/libslic3r/ModelArrange.hpp | 83 ++- xs/src/libslic3r/MultiPoint.hpp | 4 +- xs/src/libslic3r/Point.hpp | 1 + xs/src/libslic3r/Polygon.hpp | 6 + xs/src/libslic3r/Polyline.cpp | 14 +- xs/src/libslic3r/Polyline.hpp | 2 + xs/src/libslic3r/Print.cpp | 13 +- xs/src/libslic3r/Print.hpp | 18 +- xs/src/libslic3r/PrintConfig.cpp | 31 +- xs/src/libslic3r/PrintConfig.hpp | 11 + xs/src/libslic3r/PrintObject.cpp | 97 ++- xs/src/libslic3r/PrintRegion.cpp | 5 + xs/src/libslic3r/Slicing.cpp | 6 +- xs/src/libslic3r/SupportMaterial.cpp | 671 ++++++++++++------ xs/src/libslic3r/SupportMaterial.hpp | 16 + xs/src/libslic3r/SurfaceCollection.hpp | 5 + xs/src/libslic3r/Technologies.hpp | 12 + xs/src/libslic3r/TriangleMesh.cpp | 472 ++++++++---- xs/src/libslic3r/TriangleMesh.hpp | 37 +- xs/src/libslic3r/Utils.hpp | 1 + xs/src/libslic3r/libslic3r.h | 2 + xs/src/libslic3r/utils.cpp | 10 + xs/src/slic3r/AppController.cpp | 382 +--------- xs/src/slic3r/AppController.hpp | 72 +- xs/src/slic3r/AppControllerWx.cpp | 66 +- xs/src/slic3r/GUI/3DScene.cpp | 28 +- xs/src/slic3r/GUI/3DScene.hpp | 1 + xs/src/slic3r/GUI/AppConfig.cpp | 8 + xs/src/slic3r/GUI/AppConfig.hpp | 8 + xs/src/slic3r/GUI/ConfigWizard.cpp | 16 +- xs/src/slic3r/GUI/FirmwareDialog.cpp | 25 +- xs/src/slic3r/GUI/GLCanvas3D.cpp | 16 + xs/src/slic3r/GUI/GLCanvas3D.hpp | 3 +- xs/src/slic3r/GUI/GLCanvas3DManager.cpp | 6 + xs/src/slic3r/GUI/GLCanvas3DManager.hpp | 1 + xs/src/slic3r/GUI/GLGizmo.cpp | 261 ++++--- xs/src/slic3r/GUI/GLGizmo.hpp | 23 +- xs/src/slic3r/GUI/GUI.cpp | 169 +++-- xs/src/slic3r/GUI/GUI.hpp | 30 +- xs/src/slic3r/GUI/GUI_ObjectParts.cpp | 655 ++++++++++++----- xs/src/slic3r/GUI/GUI_ObjectParts.hpp | 30 +- xs/src/slic3r/GUI/LambdaObjectDialog.cpp | 55 +- xs/src/slic3r/GUI/LambdaObjectDialog.hpp | 11 +- xs/src/slic3r/GUI/OptionsGroup.cpp | 2 +- xs/src/slic3r/GUI/OptionsGroup.hpp | 6 +- xs/src/slic3r/GUI/Preset.cpp | 2 +- xs/src/slic3r/GUI/ProgressIndicator.hpp | 10 +- xs/src/slic3r/GUI/ProgressStatusBar.cpp | 2 +- xs/src/slic3r/GUI/ProgressStatusBar.hpp | 3 +- xs/src/slic3r/GUI/Tab.cpp | 5 +- xs/src/slic3r/GUI/wxExtensions.cpp | 261 ++++++- xs/src/slic3r/GUI/wxExtensions.hpp | 136 +++- xs/src/slic3r/Utils/Serial.cpp | 7 +- xs/xsp/GUI.xsp | 58 +- xs/xsp/GUI_3DScene.xsp | 9 + xs/xsp/Layer.xsp | 2 - xs/xsp/Model.xsp | 27 +- xs/xsp/Print.xsp | 6 + xs/xsp/ProgressStatusBar.xsp | 3 +- xs/xsp/XS.xsp | 5 + 90 files changed, 3310 insertions(+), 1748 deletions(-) create mode 100644 resources/icons/support_blocker.png create mode 100644 resources/icons/support_enforcer.png create mode 100644 xs/src/libslic3r/Technologies.hpp diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 216be3441..31f614ba9 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -334,28 +334,4 @@ sub set_menu_item_icon { } } -sub save_window_pos { - my ($self, $window, $name) = @_; - - $self->{app_config}->set("${name}_pos", join ',', $window->GetScreenPositionXY); - $self->{app_config}->set("${name}_size", join ',', $window->GetSizeWH); - $self->{app_config}->set("${name}_maximized", $window->IsMaximized); - $self->{app_config}->save; -} - -sub restore_window_pos { - my ($self, $window, $name) = @_; - if ($self->{app_config}->has("${name}_pos")) { - my $size = [ split ',', $self->{app_config}->get("${name}_size"), 2 ]; - $window->SetSize($size); - - my $display = Wx::Display->new->GetClientArea(); - my $pos = [ split ',', $self->{app_config}->get("${name}_pos"), 2 ]; - if (($pos->[0] + $size->[0]/2) < $display->GetRight && ($pos->[1] + $size->[1]/2) < $display->GetBottom) { - $window->Move($pos); - } - $window->Maximize(1) if $self->{app_config}->get("${name}_maximized"); - } -} - 1; diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 3f023c06f..c1975cd5d 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -95,7 +95,7 @@ sub new { $self->Fit; $self->SetMinSize([760, 490]); $self->SetSize($self->GetMinSize); - wxTheApp->restore_window_pos($self, "main_frame"); + Slic3r::GUI::restore_window_size($self, "main_frame"); $self->Show; $self->Layout; } @@ -108,7 +108,7 @@ sub new { return; } # save window size - wxTheApp->save_window_pos($self, "main_frame"); + Slic3r::GUI::save_window_size($self, "main_frame"); # Save the slic3r.ini. Usually the ini file is saved from "on idle" callback, # but in rare cases it may not have been called yet. wxTheApp->{app_config}->save; @@ -207,10 +207,12 @@ sub _init_tabpanel { EVT_COMMAND($self, -1, $OBJECT_SELECTION_CHANGED_EVENT, sub { my ($self, $event) = @_; my $obj_idx = $event->GetId; - my $child = $event->GetInt == 1 ? 1 : undef; +# my $child = $event->GetInt == 1 ? 1 : undef; +# $self->{plater}->select_object($obj_idx < 0 ? undef: $obj_idx, $child); +# $self->{plater}->item_changed_selection($obj_idx); - $self->{plater}->select_object($obj_idx < 0 ? undef: $obj_idx, $child); - $self->{plater}->item_changed_selection($obj_idx); + my $vol_idx = $event->GetInt; + $self->{plater}->select_object_from_cpp($obj_idx < 0 ? undef: $obj_idx, $vol_idx<0 ? -1 : $vol_idx); }); # The following event is emited by the C++ GUI implementation on object settings change. diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index d55264112..5a8bf3f8a 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -104,10 +104,11 @@ sub new { # 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); + Slic3r::GUI::enable_action_buttons($enable); +# $self->{btn_export_gcode}->Enable($enable); +# $self->{btn_reslice}->Enable($enable); +# $self->{btn_print}->Enable($enable); +# $self->{btn_send_gcode}->Enable($enable); }; # callback to react to gizmo scale @@ -230,7 +231,14 @@ sub new { my ($obj_idx, $object) = $self->selected_object; if (defined $obj_idx) { my $vol_idx = Slic3r::GUI::_3DScene::get_first_volume_id($self->{canvas3D}, $obj_idx); - Slic3r::GUI::_3DScene::select_volume($self->{canvas3D}, $vol_idx) if ($vol_idx != -1); + #Slic3r::GUI::_3DScene::select_volume($self->{canvas3D}, $vol_idx) if ($vol_idx != -1); + my $inst_cnt = $self->{model}->objects->[$obj_idx]->instances_count; + for (0..$inst_cnt-1){ + Slic3r::GUI::_3DScene::select_volume($self->{canvas3D}, $_ + $vol_idx) if ($vol_idx != -1); + } + + my $volume_idx = Slic3r::GUI::_3DScene::get_in_object_volume_id($self->{canvas3D}, $vol_idx); + Slic3r::GUI::select_current_volume($obj_idx, $volume_idx) if ($volume_idx != -1); } } }; @@ -368,26 +376,35 @@ sub new { # } ### Panel for right column -# $self->{right_panel} = Wx::Panel->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - $self->{right_panel} = Wx::ScrolledWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - $self->{right_panel}->SetScrollbars(0, 1, 1, 1); + $self->{right_panel} = Wx::Panel->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); +# $self->{right_panel} = Wx::ScrolledWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); +# $self->{right_panel}->SetScrollbars(0, 1, 1, 1); + + ### Scrolled Window for panel without "Export G-code" and "Slice now" buttons + my $scrolled_window_sizer = $self->{scrolled_window_sizer} = Wx::BoxSizer->new(wxVERTICAL); + $scrolled_window_sizer->SetMinSize([320, -1]); + my $scrolled_window_panel = $self->{scrolled_window_panel} = Wx::ScrolledWindow->new($self->{right_panel}, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + $scrolled_window_panel->SetSizer($scrolled_window_sizer); + $scrolled_window_panel->SetScrollbars(0, 1, 1, 1); # right pane buttons - $self->{btn_export_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Export G-code…"), wxDefaultPosition, [-1, 30], wxNO_BORDER);#, wxBU_LEFT); - $self->{btn_reslice} = Wx::Button->new($self->{right_panel}, -1, L("Slice now"), wxDefaultPosition, [-1, 30], wxBU_LEFT); - $self->{btn_print} = Wx::Button->new($self->{right_panel}, -1, L("Print…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); - $self->{btn_send_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Send to printer"), wxDefaultPosition, [-1, 30], wxBU_LEFT); - $self->{btn_export_stl} = Wx::Button->new($self->{right_panel}, -1, L("Export STL…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); + $self->{btn_export_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Export G-code…"), wxDefaultPosition, [-1, 30],);# wxNO_BORDER);#, wxBU_LEFT); + $self->{btn_reslice} = Wx::Button->new($self->{right_panel}, -1, L("Slice now"), wxDefaultPosition, [-1, 30]);#, wxNO_BORDER);#, wxBU_LEFT); +# $self->{btn_print} = Wx::Button->new($self->{right_panel}, -1, L("Print…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); +# $self->{btn_send_gcode} = Wx::Button->new($self->{right_panel}, -1, L("Send to printer"), wxDefaultPosition, [-1, 30], wxBU_LEFT); + $self->{btn_print} = Wx::Button->new($scrolled_window_panel, -1, L("Print…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); + $self->{btn_send_gcode} = Wx::Button->new($scrolled_window_panel, -1, L("Send to printer"), wxDefaultPosition, [-1, 30], wxBU_LEFT); + #$self->{btn_export_stl} = Wx::Button->new($self->{right_panel}, -1, L("Export STL…"), wxDefaultPosition, [-1, 30], wxBU_LEFT); #$self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font); #$self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font); $self->{btn_print}->Hide; $self->{btn_send_gcode}->Hide; # export_gcode cog_go.png +#! reslice reslice.png my %icons = qw( print arrow_up.png send_gcode arrow_up.png - reslice reslice.png export_stl brick_go.png ); for (grep $self->{"btn_$_"}, keys %icons) { @@ -494,9 +511,11 @@ sub new { # $self->{preset_choosers}{$group}[$idx] $self->{preset_choosers} = {}; for my $group (qw(print filament sla_material printer)) { - my $text = Wx::StaticText->new($self->{right_panel}, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); +# my $text = Wx::StaticText->new($self->{right_panel}, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); + my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); $text->SetFont($Slic3r::GUI::small_font); - my $choice = Wx::BitmapComboBox->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); +# my $choice = Wx::BitmapComboBox->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); + my $choice = Wx::BitmapComboBox->new($scrolled_window_panel, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); if ($group eq 'filament') { EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down(0, @_); } ); } @@ -515,26 +534,13 @@ sub new { } my $frequently_changed_parameters_sizer = $self->{frequently_changed_parameters_sizer} = Wx::BoxSizer->new(wxVERTICAL); - Slic3r::GUI::add_frequently_changed_parameters($self->{right_panel}, $frequently_changed_parameters_sizer, $presets); - - my $expert_mode_part_sizer = Wx::BoxSizer->new(wxVERTICAL); - Slic3r::GUI::add_expert_mode_part( $self->{right_panel}, $expert_mode_part_sizer, - $self->{model}, - $self->{event_object_selection_changed}, - $self->{event_object_settings_changed}, - $self->{event_remove_object}, - $self->{event_update_scene}); -# if ($expert_mode_part_sizer->IsShown(2)==1) -# { -# $expert_mode_part_sizer->Layout; -# $expert_mode_part_sizer->Show(2, 0); # ? Why doesn't work -# $self->{right_panel}->Layout; -# } +#! Slic3r::GUI::add_frequently_changed_parameters($self->{right_panel}, $frequently_changed_parameters_sizer, $presets); + Slic3r::GUI::add_frequently_changed_parameters($self->{scrolled_window_panel}, $frequently_changed_parameters_sizer, $presets); my $object_info_sizer; { -# my $box = Wx::StaticBox->new($scrolled_window_panel, -1, L("Info")); - my $box = Wx::StaticBox->new($self->{right_panel}, -1, L("Info")); + my $box = Wx::StaticBox->new($scrolled_window_panel, -1, L("Info")); +# my $box = Wx::StaticBox->new($self->{right_panel}, -1, L("Info")); $box->SetFont($Slic3r::GUI::small_bold_font); $object_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); $object_info_sizer->SetMinSize([300,-1]); @@ -554,25 +560,26 @@ sub new { ); while (my $field = shift @info) { my $label = shift @info; -# my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - my $text = Wx::StaticText->new($self->{right_panel}, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + my $text = Wx::StaticText->new($scrolled_window_panel, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); +# my $text = Wx::StaticText->new($self->{right_panel}, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $text->SetFont($Slic3r::GUI::small_font); #!$grid_sizer->Add($text, 0); -# $self->{"object_info_$field"} = Wx::StaticText->new($scrolled_window_panel, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - $self->{"object_info_$field"} = Wx::StaticText->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + $self->{"object_info_$field"} = Wx::StaticText->new($scrolled_window_panel, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); +# $self->{"object_info_$field"} = Wx::StaticText->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $self->{"object_info_$field"}->SetFont($Slic3r::GUI::small_font); if ($field eq 'manifold') { -# $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($scrolled_window_panel, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); - $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($self->{right_panel}, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); + $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($scrolled_window_panel, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); +# $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($self->{right_panel}, -1, Wx::Bitmap->new(Slic3r::var("error.png"), wxBITMAP_TYPE_PNG)); #$self->{object_info_manifold_warning_icon}->Hide; $self->{"object_info_manifold_warning_icon_show"} = sub { if ($self->{object_info_manifold_warning_icon}->IsShown() != $_[0]) { + # this fuction show/hide info_manifold_warning_icon on the c++ side now Slic3r::GUI::set_show_manifold_warning_icon($_[0]); - my $mode = wxTheApp->{app_config}->get("view_mode"); - return if ($mode eq "" || $mode eq "simple"); - $self->{object_info_manifold_warning_icon}->Show($_[0]); - $self->Layout + #my $mode = wxTheApp->{app_config}->get("view_mode"); + #return if ($mode eq "" || $mode eq "simple"); + #$self->{object_info_manifold_warning_icon}->Show($_[0]); + #$self->Layout } }; $self->{"object_info_manifold_warning_icon_show"}->(0); @@ -590,18 +597,19 @@ sub new { } } - my $print_info_sizer = $self->{print_info_sizer} = Wx::StaticBoxSizer->new( -# Wx::StaticBox->new($scrolled_window_panel, -1, L("Sliced Info")), wxVERTICAL); - Wx::StaticBox->new($self->{right_panel}, -1, L("Sliced Info")), wxVERTICAL); + my $print_info_box = Wx::StaticBox->new($scrolled_window_panel, -1, L("Sliced Info")); + $print_info_box->SetFont($Slic3r::GUI::small_bold_font); + my $print_info_sizer = $self->{print_info_sizer} = Wx::StaticBoxSizer->new($print_info_box, wxVERTICAL); +# Wx::StaticBox->new($self->{right_panel}, -1, L("Sliced Info")), wxVERTICAL); $print_info_sizer->SetMinSize([300,-1]); my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL); $self->{buttons_sizer} = $buttons_sizer; $buttons_sizer->AddStretchSpacer(1); - $buttons_sizer->Add($self->{btn_export_stl}, 0, wxALIGN_RIGHT, 0); - $buttons_sizer->Add($self->{btn_reslice}, 0, wxALIGN_RIGHT, 0); - $buttons_sizer->Add($self->{btn_print}, 0, wxALIGN_RIGHT, 0); - $buttons_sizer->Add($self->{btn_send_gcode}, 0, wxALIGN_RIGHT, 0); +# $buttons_sizer->Add($self->{btn_export_stl}, 0, wxALIGN_RIGHT, 0); +#! $buttons_sizer->Add($self->{btn_reslice}, 0, wxALIGN_RIGHT, 0); + $buttons_sizer->Add($self->{btn_print}, 0, wxALIGN_RIGHT | wxBOTTOM | wxTOP, 5); + $buttons_sizer->Add($self->{btn_send_gcode}, 0, wxALIGN_RIGHT | wxBOTTOM | wxTOP, 5); # $scrolled_window_sizer->Add($self->{list}, 1, wxEXPAND, 5); # $scrolled_window_sizer->Add($object_info_sizer, 0, wxEXPAND, 0); @@ -611,24 +619,39 @@ sub new { ### Sizer for info boxes my $info_sizer = $self->{info_sizer} = Wx::BoxSizer->new(wxVERTICAL); $info_sizer->SetMinSize([318, -1]); - $info_sizer->Add($object_info_sizer, 0, wxEXPAND | wxBOTTOM, 5); - $info_sizer->Add($print_info_sizer, 0, wxEXPAND | wxBOTTOM, 5); + $info_sizer->Add($object_info_sizer, 0, wxEXPAND | wxTOP, 20); + $info_sizer->Add($print_info_sizer, 0, wxEXPAND | wxTOP, 20); + + $scrolled_window_sizer->Add($presets, 0, wxEXPAND | wxLEFT, 2) if defined $presets; + $scrolled_window_sizer->Add($frequently_changed_parameters_sizer, 1, wxEXPAND | wxLEFT, 0) if defined $frequently_changed_parameters_sizer; + $scrolled_window_sizer->Add($buttons_sizer, 0, wxEXPAND, 0); + $scrolled_window_sizer->Add($info_sizer, 0, wxEXPAND | wxLEFT, 20); + # Show the box initially, let it be shown after the slicing is finished. + $self->print_info_box_show(0); + + ### Sizer for "Export G-code" & "Slice now" buttons + my $btns_sizer = Wx::BoxSizer->new(wxVERTICAL); + $btns_sizer->SetMinSize([318, -1]); + $btns_sizer->Add($self->{btn_reslice}, 0, wxEXPAND, 0); + $btns_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxTOP, 5); my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); $self->{right_panel}->SetSizer($right_sizer); $right_sizer->SetMinSize([320, -1]); - $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; - $right_sizer->Add($frequently_changed_parameters_sizer, 1, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; - $right_sizer->Add($expert_mode_part_sizer, 0, wxEXPAND | wxTOP, 10) if defined $expert_mode_part_sizer; - $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM | wxTOP, 10); - $right_sizer->Add($info_sizer, 0, wxEXPAND | wxLEFT, 20); +#! $right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets; +#! $right_sizer->Add($frequently_changed_parameters_sizer, 1, wxEXPAND | wxTOP, 0) if defined $frequently_changed_parameters_sizer; +#! $right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM | wxTOP, 10); +#! $right_sizer->Add($info_sizer, 0, wxEXPAND | wxLEFT, 20); # Show the box initially, let it be shown after the slicing is finished. - $self->print_info_box_show(0); - $right_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 20); +#! $self->print_info_box_show(0); + $right_sizer->Add($scrolled_window_panel, 1, wxEXPAND | wxTOP, 5); +# $right_sizer->Add($self->{btn_reslice}, 0, wxEXPAND | wxLEFT | wxTOP, 20); +# $right_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxLEFT | wxTOP, 20); + $right_sizer->Add($btns_sizer, 0, wxEXPAND | wxLEFT | wxTOP, 20); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); $hsizer->Add($self->{preview_notebook}, 1, wxEXPAND | wxTOP, 1); - $hsizer->Add($self->{right_panel}, 0, wxEXPAND | wxLEFT | wxRIGHT, 3); + $hsizer->Add($self->{right_panel}, 0, wxEXPAND | wxLEFT | wxRIGHT, 0);#3); my $sizer = Wx::BoxSizer->new(wxVERTICAL); # $sizer->Add($self->{htoolbar}, 0, wxEXPAND, 0) if $self->{htoolbar}; @@ -639,16 +662,21 @@ sub new { $self->SetSizer($sizer); # Send sizers/buttons to C++ - Slic3r::GUI::set_objects_from_perl( $self->{right_panel}, - $frequently_changed_parameters_sizer, - $expert_mode_part_sizer, - $info_sizer, - $self->{btn_export_gcode}, - $self->{btn_export_stl}, - $self->{btn_reslice}, - $self->{btn_print}, - $self->{btn_send_gcode}, - $self->{object_info_manifold_warning_icon} ); + Slic3r::GUI::set_objects_from_perl( $self->{scrolled_window_panel}, + $frequently_changed_parameters_sizer, + $info_sizer, + $self->{btn_export_gcode}, + # $self->{btn_export_stl}, + $self->{btn_reslice}, + $self->{btn_print}, + $self->{btn_send_gcode}, + $self->{object_info_manifold_warning_icon} ); + + Slic3r::GUI::set_model_events_from_perl( $self->{model}, + $self->{event_object_selection_changed}, + $self->{event_object_settings_changed}, + $self->{event_remove_object}, + $self->{event_update_scene}); } # Last correct selected item for each preset @@ -1594,14 +1622,16 @@ sub print_info_box_show { # my $scrolled_window_panel = $self->{scrolled_window_panel}; # my $scrolled_window_sizer = $self->{scrolled_window_sizer}; # return if (!$show && ($scrolled_window_sizer->IsShown(2) == $show)); - my $panel = $self->{right_panel}; + my $panel = $self->{scrolled_window_panel};#$self->{right_panel}; my $sizer = $self->{info_sizer}; - return if (!$sizer || !$show && ($sizer->IsShown(1) == $show)); +# return if (!$sizer || !$show && ($sizer->IsShown(1) == $show)); + return if (!$sizer); Slic3r::GUI::set_show_print_info($show); - return if (wxTheApp->{app_config}->get("view_mode") eq "simple"); +# return if (wxTheApp->{app_config}->get("view_mode") eq "simple"); - if ($show) { +# if ($show) + { my $print_info_sizer = $self->{print_info_sizer}; $print_info_sizer->Clear(1); my $grid_sizer = Wx::FlexGridSizer->new(2, 2, 5, 5); @@ -1609,20 +1639,37 @@ sub print_info_box_show { $grid_sizer->AddGrowableCol(1, 1); $grid_sizer->AddGrowableCol(3, 1); $print_info_sizer->Add($grid_sizer, 0, wxEXPAND); + my $is_wipe_tower = $self->{print}->total_wipe_tower_filament > 0; my @info = ( L("Used Filament (m)") - => sprintf("%.2f" , $self->{print}->total_used_filament / 1000), + => $is_wipe_tower ? + sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_used_filament / 1000, + ($self->{print}->total_used_filament - $self->{print}->total_wipe_tower_filament) / 1000, + L("objects"), + $self->{print}->total_wipe_tower_filament / 1000, + L("wipe tower")) : + sprintf("%.2f" , $self->{print}->total_used_filament / 1000), + L("Used Filament (mm³)") => sprintf("%.2f" , $self->{print}->total_extruded_volume), L("Used Filament (g)"), => sprintf("%.2f" , $self->{print}->total_weight), L("Cost"), - => sprintf("%.2f" , $self->{print}->total_cost), + => $is_wipe_tower ? + sprintf("%.2f (%.2f %s + %.2f %s)" , $self->{print}->total_cost, + ($self->{print}->total_cost - $self->{print}->total_wipe_tower_cost), + L("objects"), + $self->{print}->total_wipe_tower_cost, + L("wipe tower")) : + sprintf("%.2f" , $self->{print}->total_cost), L("Estimated printing time (normal mode)") => $self->{print}->estimated_normal_print_time, L("Estimated printing time (silent mode)") => $self->{print}->estimated_silent_print_time ); + # if there is a wipe tower, insert number of toolchanges info into the array: + splice (@info, 8, 0, L("Number of tool changes") => sprintf("%.d", $self->{print}->wipe_tower_number_of_toolchanges)) if ($is_wipe_tower); + while ( my $label = shift @info) { my $value = shift @info; next if $value eq "N/A"; @@ -1639,7 +1686,7 @@ sub print_info_box_show { # $scrolled_window_sizer->Show(2, $show); # $scrolled_window_panel->Layout; - $sizer->Show(1, $show); + $sizer->Show(1, $show && wxTheApp->{app_config}->get("view_mode") ne "simple"); $self->Layout; $panel->Refresh; @@ -1823,6 +1870,7 @@ sub _get_export_file { # (i.e. when an object is added/removed/moved/rotated/scaled) sub update { my ($self, $force_autocenter) = @_; + $self->Freeze; if (wxTheApp->{app_config}->get("autocenter") || $force_autocenter) { $self->{model}->center_instances_around_point($self->bed_centerf); } @@ -1836,6 +1884,7 @@ sub update { $self->{preview3D}->reset_gcode_preview_data if $self->{preview3D}; $self->{preview3D}->reload_print if $self->{preview3D}; $self->schedule_background_process; + $self->Thaw; } # When a printer technology is changed, the UI needs to be updated to show/hide needed preset combo boxes. @@ -1868,7 +1917,8 @@ sub on_extruders_change { my @presets = $choices->[0]->GetStrings; # initialize new choice - my $choice = Wx::BitmapComboBox->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY); +# my $choice = Wx::BitmapComboBox->new($self->{right_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY); + my $choice = Wx::BitmapComboBox->new($self->{scrolled_window_panel}, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY); my $extruder_idx = scalar @$choices; EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down($extruder_idx, @_); } ); push @$choices, $choice; @@ -1895,6 +1945,7 @@ sub on_extruders_change { $choices->[-1]->Destroy; pop @$choices; } + $self->{right_panel}->Layout; $self->Layout; } @@ -2221,15 +2272,59 @@ sub select_object { if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); # Select current object in the list on c++ side, if item isn't child - if (!defined $child){ - Slic3r::GUI::select_current_object($obj_idx);} +# if (!defined $child){ +# Slic3r::GUI::select_current_object($obj_idx);} # all selections in the object list is on c++ side } else { # Unselect all objects in the list on c++ side - Slic3r::GUI::unselect_objects(); +# Slic3r::GUI::unselect_objects(); # all selections in the object list is on c++ side } $self->selection_changed(1); } +sub select_object_from_cpp { + my ($self, $obj_idx, $vol_idx) = @_; + + # remove current selection + foreach my $o (0..$#{$self->{objects}}) { + $self->{objects}->[$o]->selected(0); + } + + my $curr = Slic3r::GUI::_3DScene::get_select_by($self->{canvas3D}); + + if (defined $obj_idx) { + if ($vol_idx == -1){ + if ($curr eq 'object') { + $self->{objects}->[$obj_idx]->selected(1); + } + elsif ($curr eq 'volume') { + Slic3r::GUI::_3DScene::set_select_by($self->{canvas3D}, 'object'); + } + + my $selections = $self->collect_selections; + Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); + Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1); + } + else { + if ($curr eq 'object') { + Slic3r::GUI::_3DScene::set_select_by($self->{canvas3D}, 'volume'); + } + + my $selections = []; + Slic3r::GUI::_3DScene::set_objects_selections($self->{canvas3D}, \@$selections); + Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas3D}); + Slic3r::GUI::_3DScene::reload_scene($self->{canvas3D}, 1); + my $volume_idx = Slic3r::GUI::_3DScene::get_first_volume_id($self->{canvas3D}, $obj_idx); + + my $inst_cnt = $self->{model}->objects->[$obj_idx]->instances_count; + for (0..$inst_cnt-1){ + Slic3r::GUI::_3DScene::select_volume($self->{canvas3D}, $vol_idx*$inst_cnt + $_ + $volume_idx) if ($volume_idx != -1); + } + } + } + + $self->selection_changed(1); +} + sub selected_object { my ($self) = @_; my $obj_idx = first { $self->{objects}[$_]->selected } 0..$#{ $self->{objects} }; diff --git a/lib/Slic3r/GUI/Plater/3DPreview.pm b/lib/Slic3r/GUI/Plater/3DPreview.pm index 09c2f0b8c..06d1a798b 100644 --- a/lib/Slic3r/GUI/Plater/3DPreview.pm +++ b/lib/Slic3r/GUI/Plater/3DPreview.pm @@ -5,12 +5,12 @@ use utf8; use Slic3r::Print::State ':steps'; use Wx qw(:misc :sizer :slider :statictext :keycode wxWHITE wxCB_READONLY); -use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN EVT_CHECKBOX EVT_CHOICE EVT_CHECKLISTBOX); +use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN EVT_CHECKBOX EVT_CHOICE EVT_CHECKLISTBOX EVT_SIZE); use base qw(Wx::Panel Class::Accessor); use Wx::Locale gettext => 'L'; -__PACKAGE__->mk_accessors(qw(print gcode_preview_data enabled _loaded canvas slider_low slider_high single_layer)); +__PACKAGE__->mk_accessors(qw(print gcode_preview_data enabled _loaded canvas slider_low slider_high single_layer double_slider_sizer)); sub new { my $class = shift; @@ -27,47 +27,47 @@ sub new { Slic3r::GUI::_3DScene::enable_shader($canvas, 1); Slic3r::GUI::_3DScene::set_config($canvas, $config); $self->canvas($canvas); - my $slider_low = Wx::Slider->new( - $self, -1, - 0, # default - 0, # min +# my $slider_low = Wx::Slider->new( +# $self, -1, +# 0, # default +# 0, # min # we set max to a bogus non-zero value because the MSW implementation of wxSlider # will skip drawing the slider if max <= min: - 1, # max - wxDefaultPosition, - wxDefaultSize, - wxVERTICAL | wxSL_INVERSE, - ); - $self->slider_low($slider_low); - my $slider_high = Wx::Slider->new( - $self, -1, - 0, # default - 0, # min +# 1, # max +# wxDefaultPosition, +# wxDefaultSize, +# wxVERTICAL | wxSL_INVERSE, +# ); +# $self->slider_low($slider_low); +# my $slider_high = Wx::Slider->new( +# $self, -1, +# 0, # default +# 0, # min # we set max to a bogus non-zero value because the MSW implementation of wxSlider # will skip drawing the slider if max <= min: - 1, # max - wxDefaultPosition, - wxDefaultSize, - wxVERTICAL | wxSL_INVERSE, - ); - $self->slider_high($slider_high); +# 1, # max +# wxDefaultPosition, +# wxDefaultSize, +# wxVERTICAL | wxSL_INVERSE, +# ); +# $self->slider_high($slider_high); - my $z_label_low = $self->{z_label_low} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, - [40,-1], wxALIGN_CENTRE_HORIZONTAL); - $z_label_low->SetFont($Slic3r::GUI::small_font); - my $z_label_high = $self->{z_label_high} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, - [40,-1], wxALIGN_CENTRE_HORIZONTAL); - $z_label_high->SetFont($Slic3r::GUI::small_font); +# my $z_label_low = $self->{z_label_low} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, +# [40,-1], wxALIGN_CENTRE_HORIZONTAL); +# $z_label_low->SetFont($Slic3r::GUI::small_font); +# my $z_label_high = $self->{z_label_high} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, +# [40,-1], wxALIGN_CENTRE_HORIZONTAL); +# $z_label_high->SetFont($Slic3r::GUI::small_font); - my $z_label_low_idx = $self->{z_label_low_idx} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, - [40,-1], wxALIGN_CENTRE_HORIZONTAL); - $z_label_low_idx->SetFont($Slic3r::GUI::small_font); - my $z_label_high_idx = $self->{z_label_high_idx} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, - [40,-1], wxALIGN_CENTRE_HORIZONTAL); - $z_label_high_idx->SetFont($Slic3r::GUI::small_font); +# my $z_label_low_idx = $self->{z_label_low_idx} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, +# [40,-1], wxALIGN_CENTRE_HORIZONTAL); +# $z_label_low_idx->SetFont($Slic3r::GUI::small_font); +# my $z_label_high_idx = $self->{z_label_high_idx} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, +# [40,-1], wxALIGN_CENTRE_HORIZONTAL); +# $z_label_high_idx->SetFont($Slic3r::GUI::small_font); - $self->single_layer(0); - my $checkbox_singlelayer = $self->{checkbox_singlelayer} = Wx::CheckBox->new($self, -1, L("1 Layer")); +# $self->single_layer(0); +# my $checkbox_singlelayer = $self->{checkbox_singlelayer} = Wx::CheckBox->new($self, -1, L("1 Layer")); my $label_view_type = $self->{label_view_type} = Wx::StaticText->new($self, -1, L("View")); @@ -102,26 +102,31 @@ sub new { .L("Wipe tower")."|" .L("Custom"); Slic3r::GUI::create_combochecklist($combochecklist_features, $feature_text, $feature_items, 1); + + my $double_slider_sizer = Wx::BoxSizer->new(wxHORIZONTAL); + Slic3r::GUI::create_double_slider($self, $double_slider_sizer, $self->canvas); + $self->double_slider_sizer($double_slider_sizer); my $checkbox_travel = $self->{checkbox_travel} = Wx::CheckBox->new($self, -1, L("Travel")); my $checkbox_retractions = $self->{checkbox_retractions} = Wx::CheckBox->new($self, -1, L("Retractions")); my $checkbox_unretractions = $self->{checkbox_unretractions} = Wx::CheckBox->new($self, -1, L("Unretractions")); my $checkbox_shells = $self->{checkbox_shells} = Wx::CheckBox->new($self, -1, L("Shells")); - my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); - my $vsizer = Wx::BoxSizer->new(wxVERTICAL); - my $vsizer_outer = Wx::BoxSizer->new(wxVERTICAL); - $vsizer->Add($slider_low, 3, wxALIGN_CENTER_HORIZONTAL, 0); - $vsizer->Add($z_label_low_idx, 0, wxALIGN_CENTER_HORIZONTAL, 0); - $vsizer->Add($z_label_low, 0, wxALIGN_CENTER_HORIZONTAL, 0); - $hsizer->Add($vsizer, 0, wxEXPAND, 0); - $vsizer = Wx::BoxSizer->new(wxVERTICAL); - $vsizer->Add($slider_high, 3, wxALIGN_CENTER_HORIZONTAL, 0); - $vsizer->Add($z_label_high_idx, 0, wxALIGN_CENTER_HORIZONTAL, 0); - $vsizer->Add($z_label_high, 0, 0, 0); - $hsizer->Add($vsizer, 0, wxEXPAND, 0); - $vsizer_outer->Add($hsizer, 3, wxALIGN_CENTER_HORIZONTAL, 0); - $vsizer_outer->Add($checkbox_singlelayer, 0, wxTOP | wxALIGN_CENTER_HORIZONTAL, 5); +# my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); +# my $vsizer = Wx::BoxSizer->new(wxVERTICAL); +# my $vsizer_outer = Wx::BoxSizer->new(wxVERTICAL); +# $vsizer->Add($slider_low, 3, wxALIGN_CENTER_HORIZONTAL, 0); +# $vsizer->Add($z_label_low_idx, 0, wxALIGN_CENTER_HORIZONTAL, 0); +# $vsizer->Add($z_label_low, 0, wxALIGN_CENTER_HORIZONTAL, 0); +# $hsizer->Add($vsizer, 0, wxEXPAND, 0); +# $vsizer = Wx::BoxSizer->new(wxVERTICAL); +# $vsizer->Add($slider_high, 3, wxALIGN_CENTER_HORIZONTAL, 0); +# $vsizer->Add($z_label_high_idx, 0, wxALIGN_CENTER_HORIZONTAL, 0); +# $vsizer->Add($z_label_high, 0, 0, 0); +# $hsizer->Add($vsizer, 0, wxEXPAND, 0); +# $vsizer_outer->Add($hsizer, 3, wxALIGN_CENTER_HORIZONTAL, 0); +# $vsizer_outer->Add($double_slider_sizer, 3, wxEXPAND, 0); +# $vsizer_outer->Add($checkbox_singlelayer, 0, wxTOP | wxALIGN_CENTER_HORIZONTAL, 5); my $bottom_sizer = Wx::BoxSizer->new(wxHORIZONTAL); $bottom_sizer->Add($label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5); @@ -140,81 +145,83 @@ sub new { my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); $sizer->Add($canvas, 1, wxALL | wxEXPAND, 0); - $sizer->Add($vsizer_outer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5); +# $sizer->Add($vsizer_outer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5); + $sizer->Add($double_slider_sizer, 0, wxEXPAND, 0);#wxTOP | wxBOTTOM | wxEXPAND, 5); my $main_sizer = Wx::BoxSizer->new(wxVERTICAL); $main_sizer->Add($sizer, 1, wxALL | wxEXPAND, 0); $main_sizer->Add($bottom_sizer, 0, wxALL | wxEXPAND, 0); - EVT_SLIDER($self, $slider_low, sub { - $slider_high->SetValue($slider_low->GetValue) if $self->single_layer; - $self->set_z_idx_low ($slider_low ->GetValue) - }); - EVT_SLIDER($self, $slider_high, sub { - $slider_low->SetValue($slider_high->GetValue) if $self->single_layer; - $self->set_z_idx_high($slider_high->GetValue) - }); - EVT_KEY_DOWN($canvas, sub { - my ($s, $event) = @_; - my $key = $event->GetKeyCode; - if ($event->HasModifiers) { - $event->Skip; - } else { - if ($key == ord('U')) { - $slider_high->SetValue($slider_high->GetValue + 1); - $slider_low->SetValue($slider_high->GetValue) if ($event->ShiftDown()); - $self->set_z_idx_high($slider_high->GetValue); - } elsif ($key == ord('D')) { - $slider_high->SetValue($slider_high->GetValue - 1); - $slider_low->SetValue($slider_high->GetValue) if ($event->ShiftDown()); - $self->set_z_idx_high($slider_high->GetValue); - } elsif ($key == ord('S')) { - $checkbox_singlelayer->SetValue(! $checkbox_singlelayer->GetValue()); - $self->single_layer($checkbox_singlelayer->GetValue()); - if ($self->single_layer) { - $slider_low->SetValue($slider_high->GetValue); - $self->set_z_idx_high($slider_high->GetValue); - } - } else { - $event->Skip; - } - } - }); - EVT_KEY_DOWN($slider_low, sub { - my ($s, $event) = @_; - my $key = $event->GetKeyCode; - if ($event->HasModifiers) { - $event->Skip; - } else { - if ($key == WXK_LEFT) { - } elsif ($key == WXK_RIGHT) { - $slider_high->SetFocus; - } else { - $event->Skip; - } - } - }); - EVT_KEY_DOWN($slider_high, sub { - my ($s, $event) = @_; - my $key = $event->GetKeyCode; - if ($event->HasModifiers) { - $event->Skip; - } else { - if ($key == WXK_LEFT) { - $slider_low->SetFocus; - } elsif ($key == WXK_RIGHT) { - } else { - $event->Skip; - } - } - }); - EVT_CHECKBOX($self, $checkbox_singlelayer, sub { - $self->single_layer($checkbox_singlelayer->GetValue()); - if ($self->single_layer) { - $slider_low->SetValue($slider_high->GetValue); - $self->set_z_idx_high($slider_high->GetValue); - } - }); +# EVT_SLIDER($self, $slider_low, sub { +# $slider_high->SetValue($slider_low->GetValue) if $self->single_layer; +# $self->set_z_idx_low ($slider_low ->GetValue) +# }); +# EVT_SLIDER($self, $slider_high, sub { +# $slider_low->SetValue($slider_high->GetValue) if $self->single_layer; +# $self->set_z_idx_high($slider_high->GetValue) +# }); +# EVT_KEY_DOWN($canvas, sub { +# my ($s, $event) = @_; +# Slic3r::GUI::update_double_slider_from_canvas($event); +# my $key = $event->GetKeyCode; +# if ($event->HasModifiers) { +# $event->Skip; +# } else { +# if ($key == ord('U')) { +# $slider_high->SetValue($slider_high->GetValue + 1); +# $slider_low->SetValue($slider_high->GetValue) if ($event->ShiftDown()); +# $self->set_z_idx_high($slider_high->GetValue); +# } elsif ($key == ord('D')) { +# $slider_high->SetValue($slider_high->GetValue - 1); +# $slider_low->SetValue($slider_high->GetValue) if ($event->ShiftDown()); +# $self->set_z_idx_high($slider_high->GetValue); +# } elsif ($key == ord('S')) { +# $checkbox_singlelayer->SetValue(! $checkbox_singlelayer->GetValue()); +# $self->single_layer($checkbox_singlelayer->GetValue()); +# if ($self->single_layer) { +# $slider_low->SetValue($slider_high->GetValue); +# $self->set_z_idx_high($slider_high->GetValue); +# } +# } else { +# $event->Skip; +# } +# } +# }); +# EVT_KEY_DOWN($slider_low, sub { +# my ($s, $event) = @_; +# my $key = $event->GetKeyCode; +# if ($event->HasModifiers) { +# $event->Skip; +# } else { +# if ($key == WXK_LEFT) { +# } elsif ($key == WXK_RIGHT) { +# $slider_high->SetFocus; +# } else { +# $event->Skip; +# } +# } +# }); +# EVT_KEY_DOWN($slider_high, sub { +# my ($s, $event) = @_; +# my $key = $event->GetKeyCode; +# if ($event->HasModifiers) { +# $event->Skip; +# } else { +# if ($key == WXK_LEFT) { +# $slider_low->SetFocus; +# } elsif ($key == WXK_RIGHT) { +# } else { +# $event->Skip; +# } +# } +# }); +# EVT_CHECKBOX($self, $checkbox_singlelayer, sub { +# $self->single_layer($checkbox_singlelayer->GetValue()); +# if ($self->single_layer) { +# $slider_low->SetValue($slider_high->GetValue); +# $self->set_z_idx_high($slider_high->GetValue); +# } +# }); EVT_CHOICE($self, $choice_view_type, sub { my $selection = $choice_view_type->GetCurrentSelection(); $self->{preferred_color_mode} = ($selection == $self->{tool_idx}) ? 'tool' : 'feature'; @@ -243,6 +250,12 @@ sub new { $self->gcode_preview_data->set_shells_visible($checkbox_shells->IsChecked()); $self->refresh_print; }); + + EVT_SIZE($self, sub { + my ($s, $event) = @_; + $event->Skip; + $self->Refresh; + }); $self->SetSizer($main_sizer); $self->SetMinSize($self->GetSize); @@ -392,62 +405,69 @@ sub load_print { sub reset_sliders { my ($self) = @_; $self->enabled(0); - $self->set_z_range(0,0); - $self->slider_low->Hide; - $self->slider_high->Hide; - $self->{z_label_low}->SetLabel(""); - $self->{z_label_high}->SetLabel(""); - $self->{z_label_low_idx}->SetLabel(""); - $self->{z_label_high_idx}->SetLabel(""); +# $self->set_z_range(0,0); +# $self->slider_low->Hide; +# $self->slider_high->Hide; +# $self->{z_label_low}->SetLabel(""); +# $self->{z_label_high}->SetLabel(""); +# $self->{z_label_low_idx}->SetLabel(""); +# $self->{z_label_high_idx}->SetLabel(""); + + Slic3r::GUI::reset_double_slider(); + $self->double_slider_sizer->Hide(0); } sub update_sliders { my ($self, $n_layers) = @_; - my $z_idx_low = $self->slider_low->GetValue; - my $z_idx_high = $self->slider_high->GetValue; +# my $z_idx_low = $self->slider_low->GetValue; +# my $z_idx_high = $self->slider_high->GetValue; $self->enabled(1); - $self->slider_low->SetRange(0, $n_layers - 1); - $self->slider_high->SetRange(0, $n_layers - 1); +# $self->slider_low->SetRange(0, $n_layers - 1); +# $self->slider_high->SetRange(0, $n_layers - 1); - if ($self->{force_sliders_full_range}) { - $z_idx_low = 0; - $z_idx_high = $n_layers - 1; - } elsif ($z_idx_high < $n_layers && ($self->single_layer || $z_idx_high != 0)) { - # search new indices for nearest z (size of $self->{layers_z} may change in dependence of what is shown) - if (defined($self->{z_low})) { - for (my $i = scalar(@{$self->{layers_z}}) - 1; $i >= 0; $i -= 1) { - if ($self->{layers_z}[$i] <= $self->{z_low}) { - $z_idx_low = $i; - last; - } - } - } - if (defined($self->{z_high})) { - for (my $i = scalar(@{$self->{layers_z}}) - 1; $i >= 0; $i -= 1) { - if ($self->{layers_z}[$i] <= $self->{z_high}) { - $z_idx_high = $i; - last; - } - } - } - } elsif ($z_idx_high >= $n_layers) { - # Out of range. Disable 'single layer' view. - $self->single_layer(0); - $self->{checkbox_singlelayer}->SetValue(0); - $z_idx_low = 0; - $z_idx_high = $n_layers - 1; - } else { - $z_idx_low = 0; - $z_idx_high = $n_layers - 1; - } +# if ($self->{force_sliders_full_range}) { +# $z_idx_low = 0; +# $z_idx_high = $n_layers - 1; +# } elsif ($z_idx_high < $n_layers && ($self->single_layer || $z_idx_high != 0)) { +# # search new indices for nearest z (size of $self->{layers_z} may change in dependence of what is shown) +# if (defined($self->{z_low})) { +# for (my $i = scalar(@{$self->{layers_z}}) - 1; $i >= 0; $i -= 1) { +# if ($self->{layers_z}[$i] <= $self->{z_low}) { +# $z_idx_low = $i; +# last; +# } +# } +# } +# if (defined($self->{z_high})) { +# for (my $i = scalar(@{$self->{layers_z}}) - 1; $i >= 0; $i -= 1) { +# if ($self->{layers_z}[$i] <= $self->{z_high}) { +# $z_idx_high = $i; +# last; +# } +# } +# } +# } elsif ($z_idx_high >= $n_layers) { +# # Out of range. Disable 'single layer' view. +# $self->single_layer(0); +# $self->{checkbox_singlelayer}->SetValue(0); +# $z_idx_low = 0; +# $z_idx_high = $n_layers - 1; +# } else { +# $z_idx_low = 0; +# $z_idx_high = $n_layers - 1; +# } - $self->slider_low->SetValue($z_idx_low); - $self->slider_high->SetValue($z_idx_high); - $self->slider_low->Show; - $self->slider_high->Show; - $self->set_z_range($self->{layers_z}[$z_idx_low], $self->{layers_z}[$z_idx_high]); +# $self->slider_low->SetValue($z_idx_low); +# $self->slider_high->SetValue($z_idx_high); +# $self->slider_low->Show; +# $self->slider_high->Show; +# $self->set_z_range($self->{layers_z}[$z_idx_low], $self->{layers_z}[$z_idx_high]); + + Slic3r::GUI::update_double_slider($self->{force_sliders_full_range}); + $self->double_slider_sizer->Show(0); + $self->Layout; } diff --git a/resources/icons/support_blocker.png b/resources/icons/support_blocker.png new file mode 100644 index 0000000000000000000000000000000000000000..8a66c73f4f6aff7721a84c2ce768d78cf0aac128 GIT binary patch literal 656 zcmV;B0&o3^P)3tMfkRIh^O5_aHwQOgY^tr#t)fIa>v|l+!Iu&j8?5 zD&5$3|LEPgI4eX@x30IEwdTq6#7Z>)zN`NQ%Vfy# z7XVTy!ZBoaS1;y8HLt0ybQA(_#j70v1iMWx;22}K!q*zRhPDmIWh#2wInrDgO~xe3 zE?yc?r*pE~B})ME0;1i`$y%dKS$aNfe-bh3jGk9Xbvh@zEzSUdQd&?d!ktaiyDDYY z*7N@0$wxaMN93of$+KL*F~&MVZIn{2vV4nD?>YLAFG!_Rj@k9T;}uy@LTG=>YXyLA z$DP{^_Yw<Ej`Df#>9`*nR_mu-4n!x_N*QLMSDKC>SrNDU|}B-U*!2uup$1 qFjxG()tt0=-#>3KFScK2jQs;LJU(a{EOJ%=00007xp;PyQ4hxbfI(F{TE$onlr!JkMLv%4_b<9)@c8Cc-L1UY_VnnU< z`##UxVG_-m(tCS(`Ml4|hc|L}{7-f7>ZdK_qE2ahPlbTD@p(WA~Wj%jHP<%{<(*p7aN%A&0KzR z$;{07vM?L{5qEp;dcHqj`m#((PIJv%c)Fh@Gty}!opjL&V6u4aMCuep2j=GA6mP!m z25_rbFk?EIrW8ETCD9}RgQ=6{wUz2-ZEJgr;9*|TwNwNkH=%pT3QjPGH|f>9dFj2l&{SD#&Z0pQ}J^8f;aksW~9 z`|^U3X4d5`{U8P4-O@rj+4naP?epGIw8!)}S~4KOas1W>00000NkvXXu0mjf^^oOe literal 0 HcmV?d00001 diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index a442530db..878ce4dfe 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -179,6 +179,7 @@ add_library(libslic3r STATIC ${LIBDIR}/libslic3r/SurfaceCollection.hpp ${LIBDIR}/libslic3r/SVG.cpp ${LIBDIR}/libslic3r/SVG.hpp + ${LIBDIR}/libslic3r/Technologies.hpp ${LIBDIR}/libslic3r/TriangleMesh.cpp ${LIBDIR}/libslic3r/TriangleMesh.hpp ${LIBDIR}/libslic3r/SLABasePool.hpp @@ -568,7 +569,7 @@ if (WIN32) endif () # SLIC3R_MSVC_PDB -if (MSVC AND SLIC3R_MSVC_PDB AND ${CMAKE_BUILD_TYPE} STREQUAL "Release") +if (MSVC AND SLIC3R_MSVC_PDB AND "${CMAKE_BUILD_TYPE}" STREQUAL "Release") set_target_properties(XS PROPERTIES COMPILE_FLAGS "/Zi" LINK_FLAGS "/DEBUG /OPT:REF /OPT:ICF" diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp index 4d1e62f99..8841d1b73 100644 --- a/xs/src/libnest2d/libnest2d/libnest2d.hpp +++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp @@ -640,6 +640,7 @@ public: // The progress function will be called with the number of placed items using ProgressFunction = std::function; +using StopCondition = std::function; /** * A wrapper interface (trait) class for any selections strategy provider. @@ -674,6 +675,8 @@ public: */ void progressIndicator(ProgressFunction fn) { impl_.progressIndicator(fn); } + void stopCondition(StopCondition cond) { impl_.stopCondition(cond); } + /** * \brief A method to start the calculation on the input sequence. * @@ -864,6 +867,11 @@ public: selector_.progressIndicator(func); return *this; } + /// Set a predicate to tell when to abort nesting. + inline Nester& stopCondition(StopCondition fn) { + selector_.stopCondition(fn); return *this; + } + inline PackGroup lastResult() { PackGroup ret; for(size_t i = 0; i < selector_.binCount(); i++) { diff --git a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp index ee93d0592..39761f557 100644 --- a/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp +++ b/xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp @@ -551,7 +551,7 @@ public: // Safety test: try to pack each item into an empty bin. If it fails // then it should be removed from the not_packed list { auto it = store_.begin(); - while (it != store_.end()) { + while (it != store_.end() && !this->stopcond_()) { Placer p(bin); p.configure(pconfig); if(!p.pack(*it, rem(it, store_))) { it = store_.erase(it); @@ -592,9 +592,11 @@ public: bool do_pairs = config_.try_pairs; bool do_triplets = config_.try_triplets; + StopCondition stopcond = this->stopcond_; // The DJD heuristic algorithm itself: auto packjob = [INITIAL_FILL_AREA, bin_area, w, do_triplets, do_pairs, + stopcond, &tryOneByOne, &tryGroupsOfTwo, &tryGroupsOfThree, @@ -606,12 +608,12 @@ public: double waste = .0; bool lasttry = false; - while(!not_packed.empty()) { + while(!not_packed.empty() && !stopcond()) { {// Fill the bin up to INITIAL_FILL_PROPORTION of its capacity auto it = not_packed.begin(); - while(it != not_packed.end() && + while(it != not_packed.end() && !stopcond() && filled_area < INITIAL_FILL_AREA) { if(placer.pack(*it, rem(it, not_packed))) { @@ -623,14 +625,14 @@ public: } } - // try pieses one by one + // try pieces one by one while(tryOneByOne(placer, not_packed, waste, free_area, filled_area)) { waste = 0; lasttry = false; makeProgress(placer, idx, 1); } - // try groups of 2 pieses + // try groups of 2 pieces while(do_pairs && tryGroupsOfTwo(placer, not_packed, waste, free_area, filled_area)) { @@ -638,7 +640,7 @@ public: makeProgress(placer, idx, 2); } - // try groups of 3 pieses + // try groups of 3 pieces while(do_triplets && tryGroupsOfThree(placer, not_packed, waste, free_area, filled_area)) { diff --git a/xs/src/libnest2d/libnest2d/selections/filler.hpp b/xs/src/libnest2d/libnest2d/selections/filler.hpp index 0da7220a1..5f95a6eff 100644 --- a/xs/src/libnest2d/libnest2d/selections/filler.hpp +++ b/xs/src/libnest2d/libnest2d/selections/filler.hpp @@ -60,7 +60,7 @@ public: placer.configure(pconfig); auto it = store_.begin(); - while(it != store_.end()) { + while(it != store_.end() && !this->stopcond_()) { if(!placer.pack(*it, {std::next(it), store_.end()})) { if(packed_bins_.back().empty()) ++it; placer.clearItems(); diff --git a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp index bca7497db..6bb9c60e4 100644 --- a/xs/src/libnest2d/libnest2d/selections/firstfit.hpp +++ b/xs/src/libnest2d/libnest2d/selections/firstfit.hpp @@ -56,10 +56,12 @@ public: this->progress_(static_cast(--total)); }; + auto& cancelled = this->stopcond_; + // Safety test: try to pack each item into an empty bin. If it fails // then it should be removed from the list { auto it = store_.begin(); - while (it != store_.end()) { + while (it != store_.end() && !cancelled()) { Placer p(bin); p.configure(pconfig); if(!p.pack(*it)) { it = store_.erase(it); @@ -67,13 +69,14 @@ public: } } + auto it = store_.begin(); - while(it != store_.end()) { + while(it != store_.end() && !cancelled()) { bool was_packed = false; size_t j = 0; - while(!was_packed) { - for(; j < placers.size() && !was_packed; j++) { + while(!was_packed && !cancelled()) { + for(; j < placers.size() && !was_packed && !cancelled(); j++) { if((was_packed = placers[j].pack(*it, rem(it, store_) ))) makeProgress(placers[j], j); } diff --git a/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp b/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp index 05bbae658..cfb98a9c8 100644 --- a/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp +++ b/xs/src/libnest2d/libnest2d/selections/selection_boilerplate.hpp @@ -2,6 +2,7 @@ #define SELECTION_BOILERPLATE_HPP #include "../libnest2d.hpp" +#include namespace libnest2d { namespace selections { @@ -25,14 +26,15 @@ public: return packed_bins_[binIndex]; } - inline void progressIndicator(ProgressFunction fn) { - progress_ = fn; - } + inline void progressIndicator(ProgressFunction fn) { progress_ = fn; } + + inline void stopCondition(StopCondition cond) { stopcond_ = cond; } protected: PackGroup packed_bins_; ProgressFunction progress_ = [](unsigned){}; + StopCondition stopcond_ = [](){ return false; }; }; } diff --git a/xs/src/libslic3r/EdgeGrid.cpp b/xs/src/libslic3r/EdgeGrid.cpp index 50f424e6d..2b2893c80 100644 --- a/xs/src/libslic3r/EdgeGrid.cpp +++ b/xs/src/libslic3r/EdgeGrid.cpp @@ -1225,8 +1225,10 @@ bool EdgeGrid::Grid::signed_distance(const Point &pt, coord_t search_radius, coo return true; } -Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const +Polygons EdgeGrid::Grid::contours_simplified(coord_t offset, bool fill_holes) const { + assert(std::abs(2 * offset) < m_resolution); + typedef std::unordered_multimap EndPointMapType; // 0) Prepare a binary grid. size_t cell_rows = m_rows + 2; @@ -1237,7 +1239,7 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const cell_inside[r * cell_cols + c] = cell_inside_or_crossing(r - 1, c - 1); // Fill in empty cells, which have a left / right neighbor filled. // Fill in empty cells, which have the top / bottom neighbor filled. - { + if (fill_holes) { std::vector cell_inside2(cell_inside); for (int r = 1; r + 1 < int(cell_rows); ++ r) { for (int c = 1; c + 1 < int(cell_cols); ++ c) { diff --git a/xs/src/libslic3r/EdgeGrid.hpp b/xs/src/libslic3r/EdgeGrid.hpp index 3eb741865..ab1aa4ed0 100644 --- a/xs/src/libslic3r/EdgeGrid.hpp +++ b/xs/src/libslic3r/EdgeGrid.hpp @@ -58,7 +58,7 @@ public: const size_t cols() const { return m_cols; } // For supports: Contours enclosing the rasterized edges. - Polygons contours_simplified(coord_t offset) const; + Polygons contours_simplified(coord_t offset, bool fill_holes) const; protected: struct Cell { diff --git a/xs/src/libslic3r/ExPolygonCollection.cpp b/xs/src/libslic3r/ExPolygonCollection.cpp index e52498ecb..6933544b6 100644 --- a/xs/src/libslic3r/ExPolygonCollection.cpp +++ b/xs/src/libslic3r/ExPolygonCollection.cpp @@ -61,12 +61,11 @@ ExPolygonCollection::rotate(double angle, const Point ¢er) } template -bool -ExPolygonCollection::contains(const T &item) const +bool ExPolygonCollection::contains(const T &item) const { - for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { - if (it->contains(item)) return true; - } + for (const ExPolygon &poly : this->expolygons) + if (poly.contains(item)) + return true; return false; } template bool ExPolygonCollection::contains(const Point &item) const; diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index 2bd03600c..cce3020f8 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -91,6 +91,8 @@ public: // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. virtual double min_mm3_per_mm() const = 0; virtual Polyline as_polyline() const = 0; + virtual void collect_polylines(Polylines &dst) const = 0; + virtual Polylines as_polylines() const { Polylines dst; this->collect_polylines(dst); return dst; } virtual double length() const = 0; virtual double total_volume() const = 0; }; @@ -123,8 +125,11 @@ public: ExtrusionPath* clone() const { return new ExtrusionPath (*this); } void reverse() { this->polyline.reverse(); } - Point first_point() const { return this->polyline.points.front(); } - Point last_point() const { return this->polyline.points.back(); } + Point first_point() const override { return this->polyline.points.front(); } + Point last_point() const override { return this->polyline.points.back(); } + size_t size() const { return this->polyline.size(); } + bool empty() const { return this->polyline.empty(); } + bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); } // Produce a list of extrusion paths into retval by clipping this path by ExPolygonCollection. // Currently not used. void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const; @@ -133,8 +138,8 @@ public: void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const; void clip_end(double distance); void simplify(double tolerance); - virtual double length() const; - virtual ExtrusionRole role() const { return m_role; } + double length() const override; + ExtrusionRole role() const override { return m_role; } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; @@ -149,7 +154,8 @@ public: // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. double min_mm3_per_mm() const { return this->mm3_per_mm; } Polyline as_polyline() const { return this->polyline; } - virtual double total_volume() const { return mm3_per_mm * unscale(length()); } + void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); } + double total_volume() const override { return mm3_per_mm * unscale(length()); } private: void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; @@ -178,10 +184,10 @@ public: bool can_reverse() const { return true; } ExtrusionMultiPath* clone() const { return new ExtrusionMultiPath(*this); } void reverse(); - Point first_point() const { return this->paths.front().polyline.points.front(); } - Point last_point() const { return this->paths.back().polyline.points.back(); } - virtual double length() const; - virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); } + Point first_point() const override { return this->paths.front().polyline.points.front(); } + Point last_point() const override { return this->paths.back().polyline.points.back(); } + double length() const override; + ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; @@ -196,7 +202,8 @@ public: // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. double min_mm3_per_mm() const; Polyline as_polyline() const; - virtual double total_volume() const { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } + void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); } + double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } }; // Single continuous extrusion loop, possibly with varying extrusion thickness, extrusion height or bridging / non bridging. @@ -218,18 +225,18 @@ public: bool make_clockwise(); bool make_counter_clockwise(); void reverse(); - Point first_point() const { return this->paths.front().polyline.points.front(); } - Point last_point() const { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); } + Point first_point() const override { return this->paths.front().polyline.points.front(); } + Point last_point() const override { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); } Polygon polygon() const; - virtual double length() const; + double length() const override; bool split_at_vertex(const Point &point); void split_at(const Point &point, bool prefer_non_overhang); void clip_end(double distance, ExtrusionPaths* paths) const; // Test, whether the point is extruded by a bridging flow. // This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead. bool has_overhang_point(const Point &point) const; - virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); } - ExtrusionLoopRole loop_role() const { return m_loop_role; } + ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); } + ExtrusionLoopRole loop_role() const { return m_loop_role; } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; @@ -244,7 +251,8 @@ public: // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. double min_mm3_per_mm() const; Polyline as_polyline() const { return this->polygon().split_at_first_point(); } - virtual double total_volume() const { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } + void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); } + double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } private: ExtrusionLoopRole m_loop_role; diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.hpp b/xs/src/libslic3r/ExtrusionEntityCollection.hpp index 3b34145f8..ee0d3d5cd 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.hpp @@ -24,7 +24,7 @@ public: explicit operator ExtrusionPaths() const; bool is_collection() const { return true; }; - virtual ExtrusionRole role() const { + ExtrusionRole role() const override { ExtrusionRole out = erNone; for (const ExtrusionEntity *ee : entities) { ExtrusionRole er = ee->role(); @@ -71,11 +71,11 @@ public: Point last_point() const { return this->entities.back()->last_point(); } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. - virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; + void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override; // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill. - virtual void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const; + void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override; Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const @@ -84,14 +84,20 @@ public: void flatten(ExtrusionEntityCollection* retval) const; ExtrusionEntityCollection flatten() const; double min_mm3_per_mm() const; - virtual double total_volume() const {double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; } + double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; } // Following methods shall never be called on an ExtrusionEntityCollection. Polyline as_polyline() const { CONFESS("Calling as_polyline() on a ExtrusionEntityCollection"); return Polyline(); }; - virtual double length() const { + + void collect_polylines(Polylines &dst) const override { + for (ExtrusionEntity* extrusion_entity : this->entities) + extrusion_entity->collect_polylines(dst); + } + + double length() const override { CONFESS("Calling length() on a ExtrusionEntityCollection"); return 0.; } diff --git a/xs/src/libslic3r/Fill/FillHoneycomb.cpp b/xs/src/libslic3r/Fill/FillHoneycomb.cpp index 6f26167a2..cbfe926f2 100644 --- a/xs/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/xs/src/libslic3r/Fill/FillHoneycomb.cpp @@ -86,8 +86,8 @@ void FillHoneycomb::_fill_surface_single( Polylines paths; { Polylines p; - for (Polygons::iterator it = polygons.begin(); it != polygons.end(); ++ it) - p.push_back((Polyline)(*it)); + for (Polygon &poly : polygons) + p.emplace_back(poly.points); paths = intersection_pl(p, to_polygons(expolygon)); } diff --git a/xs/src/libslic3r/Flow.cpp b/xs/src/libslic3r/Flow.cpp index c33de8b6e..9ad482daf 100644 --- a/xs/src/libslic3r/Flow.cpp +++ b/xs/src/libslic3r/Flow.cpp @@ -115,7 +115,8 @@ Flow support_material_flow(const PrintObject *object, float layer_height) // if object->config().support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)), (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value), - false); + // bridge_flow_ratio + 0.f); } Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height) @@ -127,7 +128,8 @@ Flow support_material_1st_layer_flow(const PrintObject *object, float layer_heig (width.value > 0) ? width : object->config().extrusion_width, float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)), (layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value)), - false); + // bridge_flow_ratio + 0.f); } Flow support_material_interface_flow(const PrintObject *object, float layer_height) @@ -139,7 +141,8 @@ Flow support_material_interface_flow(const PrintObject *object, float layer_heig // if object->config().support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_interface_extruder-1)), (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value), - false); + // bridge_flow_ratio + 0.f); } } diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp index 464cbb8e6..43c99f19f 100644 --- a/xs/src/libslic3r/Format/3mf.cpp +++ b/xs/src/libslic3r/Format/3mf.cpp @@ -71,6 +71,7 @@ const char* VOLUME_TYPE = "volume"; const char* NAME_KEY = "name"; const char* MODIFIER_KEY = "modifier"; +const char* VOLUME_TYPE_KEY = "volume_type"; const unsigned int VALID_OBJECT_TYPES_COUNT = 1; const char* VALID_OBJECT_TYPES[] = @@ -1252,9 +1253,13 @@ namespace Slic3r { // we extract from the given matrix only the values currently used // translation +#if ENABLE_MODELINSTANCE_3D_OFFSET + Vec3d offset(transform(0, 3), transform(1, 3), transform(2, 3)); +#else double offset_x = transform(0, 3); double offset_y = transform(1, 3); double offset_z = transform(2, 3); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET // scale double sx = ::sqrt(sqr(transform(0, 0)) + sqr(transform(1, 0)) + sqr(transform(2, 0))); @@ -1287,8 +1292,12 @@ namespace Slic3r { double angle_z = (rotation.axis() == Vec3d::UnitZ()) ? rotation.angle() : -rotation.angle(); +#if ENABLE_MODELINSTANCE_3D_OFFSET + instance.set_offset(offset); +#else instance.offset(0) = offset_x; instance.offset(1) = offset_y; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET instance.scaling_factor = sx; instance.rotation = angle_z; } @@ -1434,7 +1443,9 @@ namespace Slic3r { if (metadata.key == NAME_KEY) volume->name = metadata.value; else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1")) - volume->modifier = true; + volume->set_type(ModelVolume::PARAMETER_MODIFIER); + else if (metadata.key == VOLUME_TYPE_KEY) + volume->set_type(ModelVolume::type_from_string(metadata.value)); else volume->config.set_deserialize(metadata.key, metadata.value); } @@ -1949,9 +1960,12 @@ namespace Slic3r { if (!volume->name.empty()) stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n"; - // stores volume's modifier field - if (volume->modifier) + // stores volume's modifier field (legacy, to support old slicers) + if (volume->is_modifier()) stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MODIFIER_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; + // stores volume's type (overrides the modifier field above) + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " << + VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n"; // stores volume's config data for (const std::string& key : volume->config.keys()) diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp index 81617ad72..803d9ee54 100644 --- a/xs/src/libslic3r/Format/AMF.cpp +++ b/xs/src/libslic3r/Format/AMF.cpp @@ -29,7 +29,12 @@ // VERSION NUMBERS // 0 : .amf, .amf.xml and .zip.amf files saved by older slic3r. No version definition in them. // 1 : Introduction of amf versioning. No other change in data saved into amf files. +#if ENABLE_MODELINSTANCE_3D_OFFSET +// 2 : Added z component of offset. +const unsigned int VERSION_AMF = 2; +#else const unsigned int VERSION_AMF = 1; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version"; const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config"; @@ -119,19 +124,31 @@ struct AMFParserContext NODE_TYPE_INSTANCE, // amf/constellation/instance NODE_TYPE_DELTAX, // amf/constellation/instance/deltax NODE_TYPE_DELTAY, // amf/constellation/instance/deltay +#if ENABLE_MODELINSTANCE_3D_OFFSET + NODE_TYPE_DELTAZ, // amf/constellation/instance/deltaz +#endif // ENABLE_MODELINSTANCE_3D_OFFSET NODE_TYPE_RZ, // amf/constellation/instance/rz NODE_TYPE_SCALE, // amf/constellation/instance/scale NODE_TYPE_METADATA, // anywhere under amf/*/metadata }; struct Instance { +#if ENABLE_MODELINSTANCE_3D_OFFSET + Instance() : deltax_set(false), deltay_set(false), deltaz_set(false), rz_set(false), scale_set(false) {} +#else Instance() : deltax_set(false), deltay_set(false), rz_set(false), scale_set(false) {} +#endif // ENABLE_MODELINSTANCE_3D_OFFSET // Shift in the X axis. float deltax; bool deltax_set; // Shift in the Y axis. float deltay; bool deltay_set; +#if ENABLE_MODELINSTANCE_3D_OFFSET + // Shift in the Z axis. + float deltaz; + bool deltaz_set; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET // Rotation around the Z axis. float rz; bool rz_set; @@ -254,6 +271,10 @@ void AMFParserContext::startElement(const char *name, const char **atts) node_type_new = NODE_TYPE_DELTAX; else if (strcmp(name, "deltay") == 0) node_type_new = NODE_TYPE_DELTAY; +#if ENABLE_MODELINSTANCE_3D_OFFSET + else if (strcmp(name, "deltaz") == 0) + node_type_new = NODE_TYPE_DELTAZ; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET else if (strcmp(name, "rz") == 0) node_type_new = NODE_TYPE_RZ; else if (strcmp(name, "scale") == 0) @@ -314,7 +335,15 @@ void AMFParserContext::characters(const XML_Char *s, int len) { switch (m_path.size()) { case 4: +#if ENABLE_MODELINSTANCE_3D_OFFSET + if (m_path.back() == NODE_TYPE_DELTAX || + m_path.back() == NODE_TYPE_DELTAY || + m_path.back() == NODE_TYPE_DELTAZ || + m_path.back() == NODE_TYPE_RZ || + m_path.back() == NODE_TYPE_SCALE) +#else if (m_path.back() == NODE_TYPE_DELTAX || m_path.back() == NODE_TYPE_DELTAY || m_path.back() == NODE_TYPE_RZ || m_path.back() == NODE_TYPE_SCALE) +#endif // ENABLE_MODELINSTANCE_3D_OFFSET m_value[0].append(s, len); break; case 6: @@ -354,6 +383,14 @@ void AMFParserContext::endElement(const char * /* name */) m_instance->deltay_set = true; m_value[0].clear(); break; +#if ENABLE_MODELINSTANCE_3D_OFFSET + case NODE_TYPE_DELTAZ: + assert(m_instance); + m_instance->deltaz = float(atof(m_value[0].c_str())); + m_instance->deltaz_set = true; + m_value[0].clear(); + break; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET case NODE_TYPE_RZ: assert(m_instance); m_instance->rz = float(atof(m_value[0].c_str())); @@ -458,9 +495,14 @@ void AMFParserContext::endElement(const char * /* name */) p = end + 1; } m_object->layer_height_profile_valid = true; - } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume && strcmp(opt_key, "modifier") == 0) { - // Is this volume a modifier volume? - m_volume->modifier = atoi(m_value[1].c_str()) == 1; + } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) { + if (strcmp(opt_key, "modifier") == 0) { + // Is this volume a modifier volume? + // "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag. + m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); + } else if (strcmp(opt_key, "volume_type") == 0) { + m_volume->set_type(ModelVolume::type_from_string(m_value[1])); + } } } else if (m_path.size() == 3) { if (m_path[1] == NODE_TYPE_MATERIAL) { @@ -498,8 +540,12 @@ void AMFParserContext::endDocument() for (const Instance &instance : object.second.instances) if (instance.deltax_set && instance.deltay_set) { ModelInstance *mi = m_model.objects[object.second.idx]->add_instance(); +#if ENABLE_MODELINSTANCE_3D_OFFSET + mi->set_offset(Vec3d((double)instance.deltax, (double)instance.deltay, (double)instance.deltaz)); +#else mi->offset(0) = instance.deltax; mi->offset(1) = instance.deltay; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET mi->rotation = instance.rz_set ? instance.rz : 0.f; mi->scaling_factor = instance.scale_set ? instance.scale : 1.f; } @@ -781,8 +827,9 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c stream << " " << volume->config.serialize(key) << "\n"; if (!volume->name.empty()) stream << " " << xml_escape(volume->name) << "\n"; - if (volume->modifier) + if (volume->is_modifier()) stream << " 1\n"; + stream << " " << ModelVolume::type_to_string(volume->type()) << "\n"; for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) { stream << " \n"; for (int j = 0; j < 3; ++j) @@ -800,12 +847,21 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c " \n" " %lf\n" " %lf\n" +#if ENABLE_MODELINSTANCE_3D_OFFSET + " %lf\n" +#endif // ENABLE_MODELINSTANCE_3D_OFFSET " %lf\n" " %lf\n" " \n", object_id, +#if ENABLE_MODELINSTANCE_3D_OFFSET + instance->get_offset(X), + instance->get_offset(Y), + instance->get_offset(Z), +#else instance->offset(0), instance->offset(1), +#endif // ENABLE_MODELINSTANCE_3D_OFFSET instance->rotation, instance->scaling_factor); //FIXME missing instance->scaling_factor diff --git a/xs/src/libslic3r/Format/PRUS.cpp b/xs/src/libslic3r/Format/PRUS.cpp index 3cf3fc075..45eb56c63 100644 --- a/xs/src/libslic3r/Format/PRUS.cpp +++ b/xs/src/libslic3r/Format/PRUS.cpp @@ -166,7 +166,11 @@ bool load_prus(const char *path, Model *model) float trafo[3][4] = { 0 }; double instance_rotation = 0.; double instance_scaling_factor = 1.f; - Vec2d instance_offset(0., 0.); +#if ENABLE_MODELINSTANCE_3D_OFFSET + Vec3d instance_offset = Vec3d::Zero(); +#else + Vec2d instance_offset(0., 0.); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET bool trafo_set = false; unsigned int group_id = (unsigned int)-1; unsigned int extruder_id = (unsigned int)-1; @@ -207,8 +211,12 @@ bool load_prus(const char *path, Model *model) for (size_t c = 0; c < 3; ++ c) trafo[r][c] += mat_trafo(r, c); } +#if ENABLE_MODELINSTANCE_3D_OFFSET + instance_offset = Vec3d((double)(position[0] - zero[0]), (double)(position[1] - zero[1]), (double)(position[2] - zero[2])); +#else instance_offset(0) = position[0] - zero[0]; instance_offset(1) = position[1] - zero[1]; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET trafo[2][3] = position[2] / instance_scaling_factor; trafo_set = true; } @@ -360,8 +368,12 @@ bool load_prus(const char *path, Model *model) ModelInstance *instance = model_object->add_instance(); instance->rotation = instance_rotation; instance->scaling_factor = instance_scaling_factor; - instance->offset = instance_offset; - ++ num_models; +#if ENABLE_MODELINSTANCE_3D_OFFSET + instance->set_offset(instance_offset); +#else + instance->offset = instance_offset; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET + ++num_models; if (group_id != (size_t)-1) group_to_model_object[group_id] = model_object; } else { diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index ac09db4bd..3c6e6de53 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -277,7 +277,6 @@ std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gco } - std::string WipeTowerIntegration::prime(GCode &gcodegen) { assert(m_layer_idx == 0); @@ -1000,8 +999,8 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) print.m_print_statistics.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms(); print.m_print_statistics.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A"; for (const Extruder &extruder : m_writer.extruders()) { - double used_filament = extruder.used_filament(); - double extruded_volume = extruder.extruded_volume(); + double used_filament = extruder.used_filament() + (has_wipe_tower ? print.wipe_tower_data().used_filament[extruder.id()] : 0.f); + double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? print.wipe_tower_data().used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter double filament_weight = extruded_volume * extruder.filament_density() * 0.001; double filament_cost = filament_weight * extruder.filament_cost() * 0.001; print.m_print_statistics.filament_stats.insert(std::pair(extruder.id(), (float)used_filament)); @@ -1014,8 +1013,10 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) _write_format(file, "; filament cost = %.1lf\n", filament_cost); } } - print.m_print_statistics.total_used_filament = print.m_print_statistics.total_used_filament + used_filament; - print.m_print_statistics.total_extruded_volume = print.m_print_statistics.total_extruded_volume + extruded_volume; + print.m_print_statistics.total_used_filament += used_filament; + print.m_print_statistics.total_extruded_volume += extruded_volume; + print.m_print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.; + print.m_print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.; } _write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost); _write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str()); diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index 34183012a..45f17a68a 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -98,6 +98,7 @@ public: void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; } std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer); std::string finalize(GCode &gcodegen); + std::vector used_filament_length() const; private: WipeTowerIntegration& operator=(const WipeTowerIntegration&); diff --git a/xs/src/libslic3r/GCode/WipeTower.hpp b/xs/src/libslic3r/GCode/WipeTower.hpp index c35a0feb3..5cbbc1ca9 100644 --- a/xs/src/libslic3r/GCode/WipeTower.hpp +++ b/xs/src/libslic3r/GCode/WipeTower.hpp @@ -154,6 +154,12 @@ public: // the wipe tower has been completely covered by the tool change extrusions, // or the rest of the tower has been filled by a sparse infill with the finish_layer() method. virtual bool layer_finished() const = 0; + + // Returns used filament length per extruder: + virtual std::vector get_used_filament() const = 0; + + // Returns total number of toolchanges: + virtual int get_number_of_toolchanges() const = 0; }; }; // namespace Slic3r diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 2c96cce7b..54bdfdfd6 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -111,9 +111,10 @@ public: const WipeTower::xy start_pos_rotated() const { return m_start_pos; } const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); } float elapsed_time() const { return m_elapsed_time; } + float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; } // Extrude with an explicitely provided amount of extrusion. - Writer& extrude_explicit(float x, float y, float e, float f = 0.f) + Writer& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false) { if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate)) // Neither extrusion nor a travel move. @@ -122,6 +123,8 @@ public: float dx = x - m_current_pos.x; float dy = y - m_current_pos.y; double len = sqrt(dx*dx+dy*dy); + if (record_length) + m_used_filament_length += e; // Now do the "internal rotation" with respect to the wipe tower center @@ -162,8 +165,8 @@ public: return *this; } - Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f) - { return extrude_explicit(dest.x, dest.y, e, f); } + Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f, bool record_length = false) + { return extrude_explicit(dest.x, dest.y, e, f, record_length); } // Travel to a new XY position. f=0 means use the current value. Writer& travel(float x, float y, float f = 0.f) @@ -177,7 +180,7 @@ public: { float dx = x - m_current_pos.x; float dy = y - m_current_pos.y; - return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f); + return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true); } Writer& extrude(const WipeTower::xy &dest, const float f = 0.f) @@ -259,8 +262,8 @@ public: // extrude quickly amount e to x2 with feed f. Writer& ram(float x1, float x2, float dy, float e0, float e, float f) { - extrude_explicit(x1, m_current_pos.y + dy, e0, f); - extrude_explicit(x2, m_current_pos.y, e); + extrude_explicit(x1, m_current_pos.y + dy, e0, f, true); + extrude_explicit(x2, m_current_pos.y, e, 0.f, true); return *this; } @@ -404,6 +407,7 @@ private: float m_last_fan_speed = 0.f; int current_temp = -1; const float m_default_analyzer_line_width; + float m_used_filament_length = 0.f; std::string set_format_X(float x) { @@ -525,6 +529,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( ++ m_num_tool_changes; } + m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear + // in the output gcode - we should not remember emitting them (we will output them twice in the worst case) + // Reset the extruder current to a normal value. writer.set_extruder_trimpot(550) .feedrate(6000) @@ -537,6 +544,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( // so that tool_change() will know to extrude the wipe tower brim: m_print_brim = true; + // Ask our writer about how much material was consumed: + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + ToolChangeResult result; result.priming = true; result.print_z = this->m_z_pos; @@ -606,10 +616,10 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo toolchange_Load(writer, cleaning_box); writer.travel(writer.x(),writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area. + ++ m_num_tool_changes; } else toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature); - ++ m_num_tool_changes; m_depth_traversed += wipe_area; if (last_change_in_layer) {// draw perimeter line @@ -632,6 +642,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo ";------------------\n" "\n\n"); + // Ask our writer about how much material was consumed: + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + ToolChangeResult result; result.priming = false; result.print_z = this->m_z_pos; @@ -683,6 +696,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo m_print_brim = false; // Mark the brim as extruded + // Ask our writer about how much material was consumed: + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + ToolChangeResult result; result.priming = false; result.print_z = this->m_z_pos; @@ -804,8 +820,9 @@ void WipeTowerPrusaMM::toolchange_Unload( .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed) .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/ .resume_preview(); - - if (new_temperature != 0 && new_temperature != m_old_temperature ) { // Set the extruder temperature, but don't wait. + if (new_temperature != 0 && (new_temperature != m_old_temperature || m_is_first_layer) ) { // Set the extruder temperature, but don't wait. + // If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset) + // However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off). writer.set_extruder_temp(new_temperature, false); m_old_temperature = new_temperature; } @@ -849,6 +866,9 @@ void WipeTowerPrusaMM::toolchange_Change( const unsigned int new_tool, material_type new_material) { + // Ask the writer about how much of the old filament we consumed: + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + // Speed override for the material. Go slow for flex and soluble materials. int speed_override; switch (new_material) { @@ -911,7 +931,6 @@ void WipeTowerPrusaMM::toolchange_Wipe( const float& xl = cleaning_box.ld.x; const float& xr = cleaning_box.rd.x; - // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least // the ordered volume, even if it means violating the box. This can later be removed and simply // wipe until the end of the assigned area. @@ -926,7 +945,6 @@ void WipeTowerPrusaMM::toolchange_Wipe( m_left_to_right = !m_left_to_right; } - // now the wiping itself: for (int i = 0; true; ++i) { if (i!=0) { @@ -935,7 +953,7 @@ void WipeTowerPrusaMM::toolchange_Wipe( else if (wipe_speed < 2210.f) wipe_speed = 4200.f; else wipe_speed = std::min(4800.f, wipe_speed + 50.f); } - + float traversed_x = writer.x(); if (m_left_to_right) writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff); @@ -1050,6 +1068,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() m_depth_traversed = m_wipe_tower_depth-m_perimeter_width; + // Ask our writer about how much material was consumed: + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + ToolChangeResult result; result.priming = false; result.print_z = this->m_z_pos; @@ -1166,6 +1187,8 @@ void WipeTowerPrusaMM::generate(std::vector layer_result; for (auto layer : m_plan) diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp index 305dbc40a..06625d189 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -46,7 +46,7 @@ public: WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction, float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, float bridging, const std::vector>& wiping_matrix, unsigned int initial_tool) : - m_wipe_tower_pos(x, y), + m_wipe_tower_pos(x, y), m_wipe_tower_width(width), m_wipe_tower_rotation_angle(rotation_angle), m_y_shift(0.f), @@ -94,6 +94,8 @@ public: m_filpar[idx].ramming_step_multiplicator /= 100; while (stream >> speed) m_filpar[idx].ramming_speed.push_back(speed); + + m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later } @@ -172,6 +174,9 @@ public: return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed); } + virtual std::vector get_used_filament() const override { return m_used_filament_length; } + virtual int get_number_of_toolchanges() const override { return m_num_tool_changes; } + private: WipeTowerPrusaMM(); @@ -331,6 +336,9 @@ private: std::vector m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...)) std::vector::iterator m_layer_info = m_plan.end(); + // Stores information about used filament length per extruder: + std::vector m_used_filament_length; + // Returns gcode for wipe tower brim // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index 65184c9d1..e0f97703c 100644 --- a/xs/src/libslic3r/LayerRegion.cpp +++ b/xs/src/libslic3r/LayerRegion.cpp @@ -15,8 +15,7 @@ namespace Slic3r { -Flow -LayerRegion::flow(FlowRole role, bool bridge, double width) const +Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const { return m_region->flow( role, diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index dc21ab4ae..8e8396e7a 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -242,10 +242,19 @@ void Model::center_instances_around_point(const Vec2d &point) for (size_t i = 0; i < o->instances.size(); ++ i) bb.merge(o->instance_bounding_box(i, false)); +#if ENABLE_MODELINSTANCE_3D_OFFSET + Vec2d shift2 = point - to_2d(bb.center()); + Vec3d shift3 = Vec3d(shift2(0), shift2(1), 0.0); +#else Vec2d shift = point - 0.5 * to_2d(bb.size()) - to_2d(bb.min); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET for (ModelObject *o : this->objects) { for (ModelInstance *i : o->instances) +#if ENABLE_MODELINSTANCE_3D_OFFSET + i->set_offset(i->get_offset() + shift3); +#else i->offset += shift; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET o->invalidate_bounding_box(); } } @@ -311,8 +320,13 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) size_t idx = 0; for (ModelObject *o : this->objects) { for (ModelInstance *i : o->instances) { +#if ENABLE_MODELINSTANCE_3D_OFFSET + Vec2d offset_xy = positions[idx] - instance_centers[idx]; + i->set_offset(Vec3d(offset_xy(0), offset_xy(1), i->get_offset(Z))); +#else i->offset = positions[idx] - instance_centers[idx]; - ++ idx; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET + ++idx; } o->invalidate_bounding_box(); } @@ -336,7 +350,11 @@ void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) for (const ModelInstance *i : instances) { for (const Vec2d &pos : positions) { ModelInstance *instance = o->add_instance(*i); +#if ENABLE_MODELINSTANCE_3D_OFFSET + instance->set_offset(instance->get_offset() + Vec3d(pos(0), pos(1), 0.0)); +#else instance->offset += pos; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET } } o->invalidate_bounding_box(); @@ -366,13 +384,21 @@ void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist) ModelObject* object = this->objects.front(); object->clear_instances(); +#if ENABLE_MODELINSTANCE_3D_OFFSET + Vec3d ext_size = object->bounding_box().size() + dist * Vec3d::Ones(); +#else Vec3d size = object->bounding_box().size(); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET for (size_t x_copy = 1; x_copy <= x; ++x_copy) { for (size_t y_copy = 1; y_copy <= y; ++y_copy) { ModelInstance* instance = object->add_instance(); - instance->offset(0) = (size(0) + dist) * (x_copy-1); - instance->offset(1) = (size(1) + dist) * (y_copy-1); +#if ENABLE_MODELINSTANCE_3D_OFFSET + instance->set_offset(Vec3d(ext_size(0) * (double)(x_copy - 1), ext_size(1) * (double)(y_copy - 1), 0.0)); +#else + instance->offset(0) = (size(0) + dist) * (x_copy - 1); + instance->offset(1) = (size(1) + dist) * (y_copy - 1); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET } } } @@ -601,7 +627,8 @@ const BoundingBoxf3& ModelObject::bounding_box() const if (! m_bounding_box_valid) { BoundingBoxf3 raw_bbox; for (const ModelVolume *v : this->volumes) - if (! v->modifier) + if (v->is_model_part()) + // mesh.bounding_box() returns a cached value. raw_bbox.merge(v->mesh.bounding_box()); BoundingBoxf3 bb; for (const ModelInstance *i : this->instances) @@ -632,7 +659,7 @@ TriangleMesh ModelObject::raw_mesh() const { TriangleMesh mesh; for (const ModelVolume *v : this->volumes) - if (! v->modifier) + if (v->is_model_part()) mesh.merge(v->mesh); return mesh; } @@ -643,7 +670,7 @@ BoundingBoxf3 ModelObject::raw_bounding_box() const { BoundingBoxf3 bb; for (const ModelVolume *v : this->volumes) - if (! v->modifier) { + if (v->is_model_part()) { if (this->instances.empty()) CONFESS("Can't call raw_bounding_box() with no instances"); bb.merge(this->instances.front()->transform_mesh_bounding_box(&v->mesh, true)); } @@ -655,7 +682,7 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_ { BoundingBoxf3 bb; for (ModelVolume *v : this->volumes) - if (! v->modifier) + if (v->is_model_part()) bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(&v->mesh, dont_translate)); return bb; } @@ -666,7 +693,7 @@ void ModelObject::center_around_origin() // center this object around the origin BoundingBoxf3 bb; for (ModelVolume *v : this->volumes) - if (! v->modifier) + if (v->is_model_part()) bb.merge(v->mesh.bounding_box()); // Shift is the vector from the center of the bottom face of the bounding box to the origin @@ -676,12 +703,21 @@ void ModelObject::center_around_origin() this->translate(shift); this->origin_translation += shift; +#if ENABLE_MODELINSTANCE_3D_OFFSET + // set z to zero, translation in z has already been done within the mesh + shift(2) = 0.0; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET + if (!this->instances.empty()) { for (ModelInstance *i : this->instances) { // apply rotation and scaling to vector as well before translating instance, // in order to leave final position unaltered +#if ENABLE_MODELINSTANCE_3D_OFFSET + i->set_offset(i->get_offset() + i->transform_vector(-shift, true)); +#else Vec3d i_shift = i->world_matrix(true) * shift; i->offset -= to_2d(i_shift); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET } this->invalidate_bounding_box(); } @@ -763,7 +799,7 @@ size_t ModelObject::facets_count() const { size_t num = 0; for (const ModelVolume *v : this->volumes) - if (! v->modifier) + if (v->is_model_part()) num += v->mesh.stl.stats.number_of_facets; return num; } @@ -771,7 +807,7 @@ size_t ModelObject::facets_count() const bool ModelObject::needed_repair() const { for (const ModelVolume *v : this->volumes) - if (! v->modifier && v->mesh.needed_repair()) + if (v->is_model_part() && v->mesh.needed_repair()) return true; return false; } @@ -787,7 +823,7 @@ void ModelObject::cut(coordf_t z, Model* model) const lower->input_file = ""; for (ModelVolume *volume : this->volumes) { - if (volume->modifier) { + if (! volume->is_model_part()) { // don't cut modifiers upper->add_volume(*volume); lower->add_volume(*volume); @@ -839,7 +875,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects) ModelVolume* new_volume = new_object->add_volume(*mesh); new_volume->name = volume->name; new_volume->config = volume->config; - new_volume->modifier = volume->modifier; + new_volume->set_type(volume->type()); new_volume->material_id(volume->material_id()); new_objects->push_back(new_object); @@ -854,7 +890,7 @@ void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_ { for (const ModelVolume* vol : this->volumes) { - if (!vol->modifier) + if (vol->is_model_part()) { for (ModelInstance* inst : this->instances) { @@ -951,6 +987,38 @@ const TriangleMesh& ModelVolume::get_convex_hull() const return m_convex_hull; } +ModelVolume::Type ModelVolume::type_from_string(const std::string &s) +{ + // Legacy support + if (s == "1") + return PARAMETER_MODIFIER; + // New type (supporting the support enforcers & blockers) + if (s == "ModelPart") + return MODEL_PART; + if (s == "ParameterModifier") + return PARAMETER_MODIFIER; + if (s == "SupportEnforcer") + return SUPPORT_ENFORCER; + if (s == "SupportBlocker") + return SUPPORT_BLOCKER; + assert(s == "0"); + // Default value if invalud type string received. + return MODEL_PART; +} + +std::string ModelVolume::type_to_string(const Type t) +{ + switch (t) { + case MODEL_PART: return "ModelPart"; + case PARAMETER_MODIFIER: return "ParameterModifier"; + case SUPPORT_ENFORCER: return "SupportEnforcer"; + case SUPPORT_BLOCKER: return "SupportBlocker"; + default: + assert(false); + return "ModelPart"; + } +} + // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. // This is useful to assign different materials to different volumes of an object. @@ -1005,8 +1073,13 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mes } // Translate the bounding box. if (! dont_translate) { +#if ENABLE_MODELINSTANCE_3D_OFFSET + bbox.min += this->m_offset; + bbox.max += this->m_offset; +#else Eigen::Map(bbox.min.data()) += this->offset; Eigen::Map(bbox.max.data()) += this->offset; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET } } return bbox; @@ -1033,7 +1106,11 @@ Transform3d ModelInstance::world_matrix(bool dont_translate, bool dont_rotate, b Transform3d m = Transform3d::Identity(); if (!dont_translate) +#if ENABLE_MODELINSTANCE_3D_OFFSET + m.translate(m_offset); +#else m.translate(Vec3d(offset(0), offset(1), 0.0)); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET if (!dont_rotate) m.rotate(Eigen::AngleAxisd(rotation, Vec3d::UnitZ())); diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index 8ca1d8ea9..4c2356429 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -166,15 +166,27 @@ public: // Configuration parameters specific to an object model geometry or a modifier volume, // overriding the global Slic3r settings and the ModelObject settings. DynamicPrintConfig config; - // Is it an object to be printed, or a modifier volume? - bool modifier; - + + enum Type { + MODEL_TYPE_INVALID = -1, + MODEL_PART = 0, + PARAMETER_MODIFIER, + SUPPORT_ENFORCER, + SUPPORT_BLOCKER, + }; + // A parent object owning this modifier volume. - ModelObject* get_object() const { return this->object; }; + ModelObject* get_object() const { return this->object; }; + Type type() const { return m_type; } + void set_type(const Type t) { m_type = t; } + bool is_model_part() const { return m_type == MODEL_PART; } + bool is_modifier() const { return m_type == PARAMETER_MODIFIER; } + bool is_support_enforcer() const { return m_type == SUPPORT_ENFORCER; } + bool is_support_blocker() const { return m_type == SUPPORT_BLOCKER; } t_model_material_id material_id() const { return this->_material_id; } - void material_id(t_model_material_id material_id); - ModelMaterial* material() const; - void set_material(t_model_material_id material_id, const ModelMaterial &material); + void material_id(t_model_material_id material_id); + ModelMaterial* material() const; + void set_material(t_model_material_id material_id, const ModelMaterial &material); // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. // This is useful to assign different materials to different volumes of an object. @@ -185,24 +197,30 @@ public: void calculate_convex_hull(); const TriangleMesh& get_convex_hull() const; + // Helpers for loading / storing into AMF / 3MF files. + static Type type_from_string(const std::string &s); + static std::string type_to_string(const Type t); + private: // Parent object owning this ModelVolume. - ModelObject* object; - t_model_material_id _material_id; + ModelObject* object; + // Is it an object to be printed, or a modifier volume? + Type m_type; + t_model_material_id _material_id; - ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object) + ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(MODEL_PART), object(object) { if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); } - ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), modifier(false), object(object) {} + ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(MODEL_PART), object(object) {} ModelVolume(ModelObject *object, const ModelVolume &other) : - name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), modifier(other.modifier), object(object) + name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object) { this->material_id(other.material_id()); } ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : - name(other.name), mesh(std::move(mesh)), config(other.config), modifier(other.modifier), object(object) + name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object) { this->material_id(other.material_id()); if (mesh.stl.stats.number_of_facets > 1) @@ -225,15 +243,32 @@ public: friend class ModelObject; +#if ENABLE_MODELINSTANCE_3D_OFFSET +private: + Vec3d m_offset; // in unscaled coordinates + +public: +#endif // ENABLE_MODELINSTANCE_3D_OFFSET + double rotation; // Rotation around the Z axis, in radians around mesh center point double scaling_factor; +#if !ENABLE_MODELINSTANCE_3D_OFFSET Vec2d offset; // in unscaled coordinates - +#endif // !ENABLE_MODELINSTANCE_3D_OFFSET + // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state()) EPrintVolumeState print_volume_state; ModelObject* get_object() const { return this->object; } +#if ENABLE_MODELINSTANCE_3D_OFFSET + const Vec3d& get_offset() const { return m_offset; } + double get_offset(Axis axis) const { return m_offset(axis); } + + void set_offset(const Vec3d& offset) { m_offset = offset; } + void set_offset(Axis axis, double offset) { m_offset(axis) = offset; } +#endif // ENABLE_MODELINSTANCE_3D_OFFSET + // To be called on an external mesh void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; // Calculate a bounding box of a transformed mesh. To be called on an external mesh. @@ -253,9 +288,15 @@ private: // Parent object, owning this instance. ModelObject* object; +#if ENABLE_MODELINSTANCE_3D_OFFSET + ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), m_offset(Vec3d::Zero()), object(object), print_volume_state(PVS_Inside) {} + ModelInstance(ModelObject *object, const ModelInstance &other) : + rotation(other.rotation), scaling_factor(other.scaling_factor), m_offset(other.m_offset), object(object), print_volume_state(PVS_Inside) {} +#else ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), offset(Vec2d::Zero()), object(object), print_volume_state(PVS_Inside) {} ModelInstance(ModelObject *object, const ModelInstance &other) : rotation(other.rotation), scaling_factor(other.scaling_factor), offset(other.offset), object(object), print_volume_state(PVS_Inside) {} +#endif // ENABLE_MODELINSTANCE_3D_OFFSET }; diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp index 36bd8ed97..372689c39 100644 --- a/xs/src/libslic3r/ModelArrange.hpp +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -130,6 +130,7 @@ objfunc(const PointImpl& bincenter, double norm, // A norming factor for physical dimensions // a spatial index to quickly get neighbors of the candidate item const SpatIndex& spatindex, + const SpatIndex& smalls_spatindex, const ItemGroup& remaining ) { @@ -161,7 +162,7 @@ objfunc(const PointImpl& bincenter, // Will hold the resulting score double score = 0; - if(isBig(item.area())) { + if(isBig(item.area()) || spatindex.empty()) { // This branch is for the bigger items.. auto minc = ibb.minCorner(); // bottom left corner @@ -183,6 +184,8 @@ objfunc(const PointImpl& bincenter, // The smalles distance from the arranged pile center: auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; + auto bindist = pl::distance(ibb.center(), bincenter) / norm; + dist = 0.8*dist + 0.2*bindist; // Density is the pack density: how big is the arranged pile double density = 0; @@ -207,14 +210,20 @@ objfunc(const PointImpl& bincenter, // candidate to be aligned with only one item. auto alignment_score = 1.0; - density = (fullbb.width()*fullbb.height()) / (norm*norm); + density = std::sqrt((fullbb.width() / norm )* + (fullbb.height() / norm)); auto querybb = item.boundingBox(); // Query the spatial index for the neighbors std::vector result; result.reserve(spatindex.size()); - spatindex.query(bgi::intersects(querybb), - std::back_inserter(result)); + if(isBig(item.area())) { + spatindex.query(bgi::intersects(querybb), + std::back_inserter(result)); + } else { + smalls_spatindex.query(bgi::intersects(querybb), + std::back_inserter(result)); + } for(auto& e : result) { // now get the score for the best alignment auto idx = e.second; @@ -235,12 +244,8 @@ objfunc(const PointImpl& bincenter, if(result.empty()) score = 0.5 * dist + 0.5 * density; else - score = 0.45 * dist + 0.45 * density + 0.1 * alignment_score; + score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; } - } else if( !isBig(item.area()) && spatindex.empty()) { - auto bindist = pl::distance(ibb.center(), bincenter) / norm; - // Bindist is surprisingly enough... - score = bindist; } else { // Here there are the small items that should be placed around the // already processed bigger items. @@ -291,6 +296,7 @@ protected: PConfig pconf_; // Placement configuration double bin_area_; SpatIndex rtree_; + SpatIndex smallsrtree_; double norm_; Pile merged_pile_; Box pilebb_; @@ -299,7 +305,8 @@ protected: public: _ArrBase(const TBin& bin, Distance dist, - std::function progressind): + std::function progressind, + std::function stopcond): pck_(bin, dist), bin_area_(sl::area(bin)), norm_(std::sqrt(sl::area(bin))) { @@ -317,6 +324,7 @@ public: pilebb_ = sl::boundingBox(merged_pile); rtree_.clear(); + smallsrtree_.clear(); // We will treat big items (compared to the print bed) differently auto isBig = [this](double a) { @@ -326,10 +334,12 @@ public: for(unsigned idx = 0; idx < items.size(); ++idx) { Item& itm = items[idx]; if(isBig(itm.area())) rtree_.insert({itm.boundingBox(), idx}); + smallsrtree_.insert({itm.boundingBox(), idx}); } }; pck_.progressIndicator(progressind); + pck_.stopCondition(stopcond); } template inline IndexedPackGroup operator()(Args&&...args) { @@ -343,8 +353,9 @@ class AutoArranger: public _ArrBase { public: AutoArranger(const Box& bin, Distance dist, - std::function progressind): - _ArrBase(bin, dist, progressind) + std::function progressind, + std::function stopcond): + _ArrBase(bin, dist, progressind, stopcond) { pconf_.object_function = [this, bin] (const Item &item) { @@ -357,6 +368,7 @@ public: bin_area_, norm_, rtree_, + smallsrtree_, remaining_); double score = std::get<0>(result); @@ -380,8 +392,9 @@ class AutoArranger: public _ArrBase { public: AutoArranger(const lnCircle& bin, Distance dist, - std::function progressind): - _ArrBase(bin, dist, progressind) { + std::function progressind, + std::function stopcond): + _ArrBase(bin, dist, progressind, stopcond) { pconf_.object_function = [this, &bin] (const Item &item) { @@ -393,6 +406,7 @@ public: bin_area_, norm_, rtree_, + smallsrtree_, remaining_); double score = std::get<0>(result); @@ -421,8 +435,9 @@ template<> class AutoArranger: public _ArrBase { public: AutoArranger(const PolygonImpl& bin, Distance dist, - std::function progressind): - _ArrBase(bin, dist, progressind) + std::function progressind, + std::function stopcond): + _ArrBase(bin, dist, progressind, stopcond) { pconf_.object_function = [this, &bin] (const Item &item) { @@ -435,6 +450,7 @@ public: bin_area_, norm_, rtree_, + smallsrtree_, remaining_); double score = std::get<0>(result); @@ -449,8 +465,9 @@ template<> // Specialization with no bin class AutoArranger: public _ArrBase { public: - AutoArranger(Distance dist, std::function progressind): - _ArrBase(Box(0, 0), dist, progressind) + AutoArranger(Distance dist, std::function progressind, + std::function stopcond): + _ArrBase(Box(0, 0), dist, progressind, stopcond) { this->pconf_.object_function = [this] (const Item &item) { @@ -462,6 +479,7 @@ public: 0, norm_, rtree_, + smallsrtree_, remaining_); return std::get<0>(result); }; @@ -511,8 +529,13 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) { if(item.vertexCount() > 3) { item.rotation(objinst->rotation); item.translation( { +#if ENABLE_MODELINSTANCE_3D_OFFSET + ClipperLib::cInt(objinst->get_offset(X) / SCALING_FACTOR), + ClipperLib::cInt(objinst->get_offset(Y) / SCALING_FACTOR) +#else ClipperLib::cInt(objinst->offset(0)/SCALING_FACTOR), ClipperLib::cInt(objinst->offset(1)/SCALING_FACTOR) +#endif // ENABLE_MODELINSTANCE_3D_OFFSET }); ret.emplace_back(objinst, item); } @@ -649,11 +672,19 @@ void applyResult( // appropriately auto off = item.translation(); Radians rot = item.rotation(); +#if ENABLE_MODELINSTANCE_3D_OFFSET + Vec3d foff(off.X*SCALING_FACTOR + batch_offset, off.Y*SCALING_FACTOR, 0.0); +#else Vec2d foff(off.X*SCALING_FACTOR + batch_offset, off.Y*SCALING_FACTOR); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET // write the tranformation data into the model instance inst_ptr->rotation = rot; +#if ENABLE_MODELINSTANCE_3D_OFFSET + inst_ptr->set_offset(foff); +#else inst_ptr->offset = foff; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET } } @@ -680,12 +711,16 @@ void applyResult( * remaining items which do not fit onto the print area next to the print * bed or leave them untouched (let the user arrange them by hand or remove * them). + * \param progressind Progress indicator callback called when an object gets + * packed. The unsigned argument is the number of items remaining to pack. + * \param stopcondition A predicate returning true if abort is needed. */ bool arrange(Model &model, coordf_t min_obj_distance, const Slic3r::Polyline& bed, BedShapeHint bedhint, bool first_bin_only, - std::function progressind) + std::function progressind, + std::function stopcondition) { using ArrangeResult = _IndexedPackGroup; @@ -710,6 +745,8 @@ bool arrange(Model &model, coordf_t min_obj_distance, BoundingBox bbb(bed); + auto& cfn = stopcondition; + auto binbb = Box({ static_cast(bbb.min(0)), static_cast(bbb.min(1)) @@ -723,7 +760,7 @@ bool arrange(Model &model, coordf_t min_obj_distance, case BedShapeType::BOX: { // Create the arranger for the box shaped bed - AutoArranger arrange(binbb, min_obj_distance, progressind); + AutoArranger arrange(binbb, min_obj_distance, progressind, cfn); // Arrange and return the items with their respective indices within the // input sequence. @@ -735,7 +772,7 @@ bool arrange(Model &model, coordf_t min_obj_distance, auto c = bedhint.shape.circ; auto cc = lnCircle(c); - AutoArranger arrange(cc, min_obj_distance, progressind); + AutoArranger arrange(cc, min_obj_distance, progressind, cfn); result = arrange(shapes.begin(), shapes.end()); break; } @@ -747,7 +784,7 @@ bool arrange(Model &model, coordf_t min_obj_distance, auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); P irrbed = sl::create(std::move(ctour)); - AutoArranger

arrange(irrbed, min_obj_distance, progressind); + AutoArranger

arrange(irrbed, min_obj_distance, progressind, cfn); // Arrange and return the items with their respective indices within the // input sequence. @@ -756,7 +793,7 @@ bool arrange(Model &model, coordf_t min_obj_distance, } }; - if(result.empty()) return false; + if(result.empty() || stopcondition()) return false; if(first_bin_only) { applyResult(result.front(), 0, shapemap); diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp index 1fef4083b..03b89df51 100644 --- a/xs/src/libslic3r/MultiPoint.hpp +++ b/xs/src/libslic3r/MultiPoint.hpp @@ -35,8 +35,10 @@ public: Point first_point() const; virtual Point last_point() const = 0; virtual Lines lines() const = 0; + size_t size() const { return points.size(); } + bool empty() const { return points.empty(); } double length() const; - bool is_valid() const { return this->points.size() >= 2; } + bool is_valid() const { return this->points.size() >= 2; } int find_point(const Point &point) const; bool has_boundary_point(const Point &point) const; diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index c1aff561f..6d9d82d25 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -22,6 +22,7 @@ typedef Point Vector; // Vector types with a fixed point coordinate base type. typedef Eigen::Matrix Vec2crd; typedef Eigen::Matrix Vec3crd; +typedef Eigen::Matrix Vec3i; typedef Eigen::Matrix Vec2i64; typedef Eigen::Matrix Vec3i64; diff --git a/xs/src/libslic3r/Polygon.hpp b/xs/src/libslic3r/Polygon.hpp index 54909352c..63162d953 100644 --- a/xs/src/libslic3r/Polygon.hpp +++ b/xs/src/libslic3r/Polygon.hpp @@ -103,6 +103,12 @@ inline void polygons_rotate(Polygons &polys, double angle) p.rotate(cos_angle, sin_angle); } +inline void polygons_reverse(Polygons &polys) +{ + for (Polygon &p : polys) + p.reverse(); +} + inline Points to_points(const Polygon &poly) { return poly.points; diff --git a/xs/src/libslic3r/Polyline.cpp b/xs/src/libslic3r/Polyline.cpp index b2e50bca3..e0cd5221c 100644 --- a/xs/src/libslic3r/Polyline.cpp +++ b/xs/src/libslic3r/Polyline.cpp @@ -184,15 +184,13 @@ void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const bool Polyline::is_straight() const { - /* Check that each segment's direction is equal to the line connecting - first point and last point. (Checking each line against the previous - one would cause the error to accumulate.) */ + // Check that each segment's direction is equal to the line connecting + // first point and last point. (Checking each line against the previous + // one would cause the error to accumulate.) double dir = Line(this->first_point(), this->last_point()).direction(); - - Lines lines = this->lines(); - for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { - if (!line->parallel_to(dir)) return false; - } + for (const auto &line: this->lines()) + if (! line.parallel_to(dir)) + return false; return true; } diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp index 0c934e074..925b88aca 100644 --- a/xs/src/libslic3r/Polyline.hpp +++ b/xs/src/libslic3r/Polyline.hpp @@ -21,6 +21,8 @@ public: Polyline(Polyline &&other) : MultiPoint(std::move(other.points)) {} Polyline(std::initializer_list list) : MultiPoint(list) {} explicit Polyline(const Point &p1, const Point &p2) { points.reserve(2); points.emplace_back(p1); points.emplace_back(p2); } + explicit Polyline(const Points &points) : MultiPoint(points) {} + explicit Polyline(Points &&points) : MultiPoint(std::move(points)) {} Polyline& operator=(const Polyline &other) { points = other.points; return *this; } Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; } static Polyline new_scale(const std::vector &points) { diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 03034cfdc..85634d1bb 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -392,9 +392,12 @@ void Print::add_model_object(ModelObject* model_object, int idx) //FIXME lock mutex! this->invalidate_all_steps(); - for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) { + size_t volume_id = 0; + for (const ModelVolume *volume : model_object->volumes) { + if (! volume->is_model_part() && ! volume->is_modifier()) + continue; // Get the config applied to this volume. - PrintRegionConfig config = this->_region_config_from_model_volume(*model_object->volumes[volume_id]); + PrintRegionConfig config = this->_region_config_from_model_volume(*volume); // Find an existing print region with the same config. size_t region_id = size_t(-1); for (size_t i = 0; i < m_regions.size(); ++ i) @@ -409,6 +412,7 @@ void Print::add_model_object(ModelObject* model_object, int idx) } // Assign volume to a region. object->add_region_volume(region_id, volume_id); + ++ volume_id; } // Apply config to print object. @@ -893,7 +897,7 @@ void Print::auto_assign_extruders(ModelObject* model_object) const for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) { ModelVolume *volume = model_object->volumes[volume_id]; //FIXME Vojtech: This assigns an extruder ID even to a modifier volume, if it has a material assigned. - if (! volume->material_id().empty() && ! volume->config.has("extruder")) + if ((volume->is_model_part() || volume->is_modifier()) && ! volume->material_id().empty() && ! volume->config.has("extruder")) volume->config.opt("extruder", true)->value = int(volume_id + 1); } } @@ -1295,6 +1299,9 @@ void Print::_make_wipe_tower() } m_wipe_tower_data.final_purge = Slic3r::make_unique( wipe_tower.tool_change((unsigned int)-1, false)); + + m_wipe_tower_data.used_filament = wipe_tower.get_used_filament(); + m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); } std::string Print::output_filename() const diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 8999f27d7..19da875ab 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -135,7 +135,10 @@ public: const Print* print() const { return m_print; } const PrintRegionConfig& config() const { return m_config; } Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const; + // Average diameter of nozzles participating on extruding this region. coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const; + // Average diameter of nozzles participating on extruding this region. + coordf_t bridging_height_avg(const PrintConfig &print_config) const; // Methods modifying the PrintRegion's state: public: @@ -252,6 +255,10 @@ public: // Called when slicing to SVG (see Print.pm sub export_svg), and used by perimeters.t void slice(); + // Helpers to slice support enforcer / blocker meshes by the support generator. + std::vector slice_support_enforcers() const; + std::vector slice_support_blockers() const; + private: void make_perimeters(); void prepare_infill(); @@ -297,6 +304,7 @@ private: void set_started(PrintObjectStep step); void set_done(PrintObjectStep step); std::vector _slice_region(size_t region_id, const std::vector &z, bool modifier); + std::vector _slice_volumes(const std::vector &z, const std::vector &volumes) const; }; struct WipeTowerData @@ -309,6 +317,8 @@ struct WipeTowerData std::unique_ptr priming; std::vector> tool_changes; std::unique_ptr final_purge; + std::vector used_filament; + int number_of_toolchanges; // Depth of the wipe tower to pass to GLCanvas3D for exact bounding box: float depth; @@ -318,6 +328,8 @@ struct WipeTowerData priming.reset(nullptr); tool_changes.clear(); final_purge.reset(nullptr); + used_filament.clear(); + number_of_toolchanges = -1; depth = 0.f; } }; @@ -331,6 +343,8 @@ struct PrintStatistics double total_extruded_volume; double total_cost; double total_weight; + double total_wipe_tower_cost; + double total_wipe_tower_filament; std::map filament_stats; void clear() { @@ -340,6 +354,8 @@ struct PrintStatistics total_extruded_volume = 0.; total_cost = 0.; total_weight = 0.; + total_wipe_tower_cost = 0.; + total_wipe_tower_filament = 0.; filament_stats.clear(); } }; @@ -463,7 +479,7 @@ private: // If the background processing stop was requested, throw CanceledException. // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. - void throw_if_canceled() { if (m_canceled) throw CanceledException(); } + void throw_if_canceled() const { if (m_canceled) throw CanceledException(); } void _make_skirt(); void _make_brim(); diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index a3e84a356..ed02f6d43 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -161,7 +161,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Speed for printing bridges."); def->sidetext = L("mm/s"); def->cli = "bridge-speed=f"; - def->aliases.push_back("bridge_feed_rate"); + def->aliases = { "bridge_feed_rate" }; def->min = 0; def->default_value = new ConfigOptionFloat(60); @@ -274,7 +274,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Distance used for the auto-arrange feature of the plater."); def->sidetext = L("mm"); def->cli = "duplicate-distance=f"; - def->aliases.push_back("multiply_distance"); + def->aliases = { "multiply_distance" }; def->min = 0; def->default_value = new ConfigOptionFloat(6); @@ -335,7 +335,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Archimedean Chords")); def->enum_labels.push_back(L("Octagram Spiral")); // solid_fill_pattern is an obsolete equivalent to external_fill_pattern. - def->aliases.push_back("solid_fill_pattern"); + def->aliases = { "solid_fill_pattern" }; def->default_value = new ConfigOptionEnum(ipRectilinear); def = this->add("external_perimeter_extrusion_width", coFloatOrPercent); @@ -923,8 +923,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Speed for printing the internal fill. Set to zero for auto."); def->sidetext = L("mm/s"); def->cli = "infill-speed=f"; - def->aliases.push_back("print_feed_rate"); - def->aliases.push_back("infill_feed_rate"); + def->aliases = { "print_feed_rate", "infill_feed_rate" }; def->min = 0; def->default_value = new ConfigOptionFloat(80); @@ -1272,7 +1271,7 @@ void PrintConfigDef::init_fff_params() def->category = L("Extruders"); def->tooltip = L("The extruder to use when printing perimeters and brim. First extruder is 1."); def->cli = "perimeter-extruder=i"; - def->aliases.push_back("perimeters_extruder"); + def->aliases = { "perimeters_extruder" }; def->min = 1; def->default_value = new ConfigOptionInt(1); @@ -1285,7 +1284,7 @@ void PrintConfigDef::init_fff_params() "If expressed as percentage (for example 200%) it will be computed over layer height."); def->sidetext = L("mm or % (leave 0 for default)"); def->cli = "perimeter-extrusion-width=s"; - def->aliases.push_back("perimeters_extrusion_width"); + def->aliases = { "perimeters_extrusion_width" }; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("perimeter_speed", coFloat); @@ -1294,7 +1293,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Speed for perimeters (contours, aka vertical shells). Set to zero for auto."); def->sidetext = L("mm/s"); def->cli = "perimeter-speed=f"; - def->aliases.push_back("perimeter_feed_rate"); + def->aliases = { "perimeter_feed_rate" }; def->min = 0; def->default_value = new ConfigOptionFloat(60); @@ -1307,7 +1306,7 @@ void PrintConfigDef::init_fff_params() "if the Extra Perimeters option is enabled."); def->sidetext = L("(minimum)"); def->cli = "perimeters=i"; - def->aliases.push_back("perimeter_offsets"); + def->aliases = { "perimeter_offsets" }; def->min = 0; def->default_value = new ConfigOptionInt(3); @@ -1635,7 +1634,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s or %"); def->cli = "solid-infill-speed=s"; def->ratio_over = "infill_speed"; - def->aliases.push_back("solid_infill_feed_rate"); + def->aliases = { "solid_infill_feed_rate" }; def->min = 0; def->default_value = new ConfigOptionFloatOrPercent(20, false); @@ -1717,6 +1716,14 @@ void PrintConfigDef::init_fff_params() def->cli = "support-material!"; def->default_value = new ConfigOptionBool(false); + def = this->add("support_material_auto", coBool); + def->label = L("Auto generated supports"); + def->category = L("Support material"); + def->tooltip = L("If checked, supports will be generated automatically based on the overhang threshold value."\ + " If unchecked, supports will be generated inside the \"Support Enforcer\" volumes only."); + def->cli = "support-material-auto!"; + def->default_value = new ConfigOptionBool(true); + def = this->add("support_material_xy_spacing", coFloatOrPercent); def->label = L("XY separation between an object and its support"); def->category = L("Support material"); @@ -1755,7 +1762,7 @@ void PrintConfigDef::init_fff_params() "for the first object layer."); def->sidetext = L("mm"); def->cli = "support-material-contact-distance=f"; - def->min = 0; +// def->min = 0; def->enum_values.push_back("0"); def->enum_values.push_back("0.2"); def->enum_labels.push_back((boost::format("0 (%1%)") % L("soluble")).str()); @@ -1981,7 +1988,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Speed for travel moves (jumps between distant extrusion points)."); def->sidetext = L("mm/s"); def->cli = "travel-speed=f"; - def->aliases.push_back("travel_feed_rate"); + def->aliases = { "travel_feed_rate" }; def->min = 1; def->default_value = new ConfigOptionFloat(130); diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 5bc99a51a..bc3b0ef49 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -345,6 +345,7 @@ public: ConfigOptionFloatOrPercent extrusion_width; ConfigOptionFloatOrPercent first_layer_height; ConfigOptionBool infill_only_where_needed; + // Force the generation of solid shells between adjacent materials/volumes. ConfigOptionBool interface_shells; ConfigOptionFloat layer_height; ConfigOptionInt raft_layers; @@ -352,6 +353,9 @@ public: // ConfigOptionFloat seam_preferred_direction; // ConfigOptionFloat seam_preferred_direction_jitter; ConfigOptionBool support_material; + // Automatic supports (generated based on support_material_threshold). + ConfigOptionBool support_material_auto; + // Direction of the support pattern (in XY plane). ConfigOptionFloat support_material_angle; ConfigOptionBool support_material_buildplate_only; ConfigOptionFloat support_material_contact_distance; @@ -361,12 +365,15 @@ public: ConfigOptionBool support_material_interface_contact_loops; ConfigOptionInt support_material_interface_extruder; ConfigOptionInt support_material_interface_layers; + // Spacing between interface lines (the hatching distance). Set zero to get a solid interface. ConfigOptionFloat support_material_interface_spacing; ConfigOptionFloatOrPercent support_material_interface_speed; ConfigOptionEnum support_material_pattern; + // Spacing between support material lines (the hatching distance). ConfigOptionFloat support_material_spacing; ConfigOptionFloat support_material_speed; ConfigOptionBool support_material_synchronize_layers; + // Overhang angle threshold. ConfigOptionInt support_material_threshold; ConfigOptionBool support_material_with_sheath; ConfigOptionFloatOrPercent support_material_xy_spacing; @@ -389,6 +396,7 @@ protected: // OPT_PTR(seam_preferred_direction); // OPT_PTR(seam_preferred_direction_jitter); OPT_PTR(support_material); + OPT_PTR(support_material_auto); OPT_PTR(support_material_angle); OPT_PTR(support_material_buildplate_only); OPT_PTR(support_material_contact_distance); @@ -436,10 +444,12 @@ public: ConfigOptionInt infill_every_layers; ConfigOptionFloatOrPercent infill_overlap; ConfigOptionFloat infill_speed; + // Detect bridging perimeters ConfigOptionBool overhangs; ConfigOptionInt perimeter_extruder; ConfigOptionFloatOrPercent perimeter_extrusion_width; ConfigOptionFloat perimeter_speed; + // Total number of perimeters. ConfigOptionInt perimeters; ConfigOptionFloatOrPercent small_perimeter_speed; ConfigOptionFloat solid_infill_below_area; @@ -447,6 +457,7 @@ public: ConfigOptionFloatOrPercent solid_infill_extrusion_width; ConfigOptionInt solid_infill_every_layers; ConfigOptionFloatOrPercent solid_infill_speed; + // Detect thin walls. ConfigOptionBool thin_walls; ConfigOptionFloatOrPercent top_infill_extrusion_width; ConfigOptionInt top_solid_layers; diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 7882c58a8..ef2364dc4 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -112,8 +112,18 @@ bool PrintObject::reload_model_instances() Points copies; copies.reserve(m_model_object->instances.size()); for (const ModelInstance *mi : m_model_object->instances) + { +#if ENABLE_MODELINSTANCE_3D_OFFSET + if (mi->is_printable()) + { + const Vec3d& offset = mi->get_offset(); + copies.emplace_back(Point::new_scale(offset(0), offset(1))); + } +#else if (mi->is_printable()) copies.emplace_back(Point::new_scale(mi->offset(0), mi->offset(1))); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET + } return this->set_copies(copies); } @@ -490,6 +500,7 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector PrintObject::_slice_region(size_t region_id, const std::vector &z, bool modifier) { - std::vector layers; + std::vector volumes; if (region_id < this->region_volumes.size()) { - std::vector &volumes = this->region_volumes[region_id]; - if (! volumes.empty()) { - // Compose mesh. - //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. - TriangleMesh mesh; - for (int volume_id : volumes) { - ModelVolume *volume = this->model_object()->volumes[volume_id]; - if (volume->modifier == modifier) - mesh.merge(volume->mesh); - m_print->throw_if_canceled(); - } - if (mesh.stl.stats.number_of_facets > 0) { - // transform mesh - // we ignore the per-instance transformations currently and only - // consider the first one - this->model_object()->instances.front()->transform_mesh(&mesh, true); - // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift - mesh.translate(- unscale(m_copies_shift(0)), - unscale(m_copies_shift(1)), - float(this->model_object()->bounding_box().min(2))); - // perform actual slicing - TriangleMeshSlicer mslicer; - Print *print = this->print(); - auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); - mslicer.init(&mesh, callback); - mslicer.slice(z, &layers, callback); - m_print->throw_if_canceled(); - } + for (int volume_id : this->region_volumes[region_id]) { + const ModelVolume *volume = this->model_object()->volumes[volume_id]; + if (modifier ? volume->is_modifier() : volume->is_model_part()) + volumes.emplace_back(volume); + } + } + return this->_slice_volumes(z, volumes); +} + +std::vector PrintObject::slice_support_enforcers() const +{ + std::vector volumes; + for (const ModelVolume *volume : this->model_object()->volumes) + if (volume->is_support_enforcer()) + volumes.emplace_back(volume); + std::vector zs; + zs.reserve(this->layers().size()); + for (const Layer *l : this->layers()) + zs.emplace_back(l->slice_z); + return this->_slice_volumes(zs, volumes); +} + +std::vector PrintObject::slice_support_blockers() const +{ + std::vector volumes; + for (const ModelVolume *volume : this->model_object()->volumes) + if (volume->is_support_blocker()) + volumes.emplace_back(volume); + std::vector zs; + zs.reserve(this->layers().size()); + for (const Layer *l : this->layers()) + zs.emplace_back(l->slice_z); + return this->_slice_volumes(zs, volumes); +} + +std::vector PrintObject::_slice_volumes(const std::vector &z, const std::vector &volumes) const +{ + std::vector layers; + if (! volumes.empty()) { + // Compose mesh. + //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. + TriangleMesh mesh; + for (const ModelVolume *v : volumes) + mesh.merge(v->mesh); + if (mesh.stl.stats.number_of_facets > 0) { + // transform mesh + // we ignore the per-instance transformations currently and only + // consider the first one + this->model_object()->instances.front()->transform_mesh(&mesh, true); + // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift + mesh.translate(- unscale(m_copies_shift(0)), - unscale(m_copies_shift(1)), - float(this->model_object()->bounding_box().min(2))); + // perform actual slicing + TriangleMeshSlicer mslicer; + const Print *print = this->print(); + auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); + mslicer.init(&mesh, callback); + mslicer.slice(z, &layers, callback); + m_print->throw_if_canceled(); } } return layers; diff --git a/xs/src/libslic3r/PrintRegion.cpp b/xs/src/libslic3r/PrintRegion.cpp index cdc8c532a..c112548b4 100644 --- a/xs/src/libslic3r/PrintRegion.cpp +++ b/xs/src/libslic3r/PrintRegion.cpp @@ -56,4 +56,9 @@ coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const print_config.nozzle_diameter.get_at(m_config.solid_infill_extruder.value - 1)) / 3.; } +coordf_t PrintRegion::bridging_height_avg(const PrintConfig &print_config) const +{ + return this->nozzle_dmr_avg(print_config) * sqrt(m_config.bridge_flow_ratio.value); +} + } diff --git a/xs/src/libslic3r/Slicing.cpp b/xs/src/libslic3r/Slicing.cpp index 1bc38502b..b3e314549 100644 --- a/xs/src/libslic3r/Slicing.cpp +++ b/xs/src/libslic3r/Slicing.cpp @@ -224,9 +224,9 @@ std::vector layer_height_profile_adaptive( // 1) Initialize the SlicingAdaptive class with the object meshes. SlicingAdaptive as; as.set_slicing_parameters(slicing_params); - for (ModelVolumePtrs::const_iterator it = volumes.begin(); it != volumes.end(); ++ it) - if (! (*it)->modifier) - as.add_mesh(&(*it)->mesh); + for (const ModelVolume *volume : volumes) + if (volume->is_model_part()) + as.add_mesh(&volume->mesh); as.prepare(); // 2) Generate layers using the algorithm of @platsch diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index a6b6c1bb8..2bcf597e6 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -248,10 +248,10 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) #ifdef SLIC3R_DEBUG static int iRun = 0; iRun ++; - for (MyLayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it) + for (const MyLayer *layer : top_contacts) Slic3r::SVG::export_expolygons( - debug_out_path("support-top-contacts-%d-%lf.svg", iRun, (*it)->print_z), - union_ex((*it)->polygons, false)); + debug_out_path("support-top-contacts-%d-%lf.svg", iRun, layer->print_z), + union_ex(layer->polygons, false)); #endif /* SLIC3R_DEBUG */ BOOST_LOG_TRIVIAL(info) << "Support generator - Creating bottom contacts"; @@ -282,7 +282,17 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers( object, bottom_contacts, top_contacts, layer_storage); - this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy); +// this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy); + this->trim_support_layers_by_object(object, top_contacts, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); + +#ifdef SLIC3R_DEBUG + for (const MyLayer *layer : top_contacts) + Slic3r::SVG::export_expolygons( + debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z), + union_ex(layer->polygons, false)); +#endif BOOST_LOG_TRIVIAL(info) << "Support generator - Creating base layers"; @@ -420,29 +430,17 @@ Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_t { // 1) Count the new polygons first. size_t n_polygons_new = 0; - for (const LayerRegion* pregion : layer.regions()) { - const LayerRegion ®ion = *pregion; - const SurfaceCollection &slices = region.slices; - for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) { - const Surface &surface = *it; + for (const LayerRegion *region : layer.regions()) + for (const Surface &surface : region->slices.surfaces) if (surface.surface_type == surface_type) n_polygons_new += surface.expolygon.holes.size() + 1; - } - } - // 2) Collect the new polygons. Polygons out; out.reserve(n_polygons_new); - for (const LayerRegion *pregion : layer.regions()) { - const LayerRegion ®ion = *pregion; - const SurfaceCollection &slices = region.slices; - for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) { - const Surface &surface = *it; + for (const LayerRegion *region : layer.regions()) + for (const Surface &surface : region->slices.surfaces) if (surface.surface_type == surface_type) polygons_append(out, surface.expolygon); - } - } - return out; } @@ -452,8 +450,8 @@ Polygons collect_slices_outer(const Layer &layer) { Polygons out; out.reserve(out.size() + layer.slices.expolygons.size()); - for (ExPolygons::const_iterator it = layer.slices.expolygons.begin(); it != layer.slices.expolygons.end(); ++ it) - out.push_back(it->contour); + for (const ExPolygon &expoly : layer.slices.expolygons) + out.emplace_back(expoly.contour); return out; } @@ -461,8 +459,11 @@ class SupportGridPattern { public: SupportGridPattern( + // Support islands, to be stretched into a grid. Already trimmed with min(lower_layer_offset, m_gap_xy) const Polygons &support_polygons, - const Polygons &trimming_polygons, + // Trimming polygons, to trim the stretched support islands. support_polygons were already trimmed with trimming_polygons. + const Polygons &trimming_polygons, + // Grid spacing, given by "support_material_spacing" + m_support_material_flow.spacing() coordf_t support_spacing, coordf_t support_angle) : m_support_polygons(&support_polygons), m_trimming_polygons(&trimming_polygons), @@ -485,7 +486,8 @@ public: m_grid.set_bbox(bbox); m_grid.create(*m_support_polygons, grid_resolution); m_grid.calculate_sdf(); - // Extract a bounding contour from the grid, trim by the object. + // Sample a single point per input support polygon, keep it as a reference to maintain corresponding + // polygons if ever these polygons get split into parts by the trimming polygons. m_island_samples = island_samples(*m_support_polygons); } @@ -493,22 +495,22 @@ public: // and trim the extracted polygons by trimming_polygons. // Trimming by the trimming_polygons may split the extracted polygons into pieces. // Remove all the pieces, which do not contain any of the island_samples. - Polygons extract_support(const coord_t offset_in_grid) + Polygons extract_support(const coord_t offset_in_grid, bool fill_holes) { // Generate islands, so each island may be tested for overlap with m_island_samples. + assert(std::abs(2 * offset_in_grid) < m_grid.resolution()); ExPolygons islands = diff_ex( - m_grid.contours_simplified(offset_in_grid), + m_grid.contours_simplified(offset_in_grid, fill_holes), *m_trimming_polygons, false); // Extract polygons, which contain some of the m_island_samples. Polygons out; - std::vector> samples_inside; - for (ExPolygon &island : islands) { BoundingBox bbox = get_extents(island.contour); + // Samples are sorted lexicographically. auto it_lower = std::lower_bound(m_island_samples.begin(), m_island_samples.end(), Point(bbox.min - Point(1, 1))); auto it_upper = std::upper_bound(m_island_samples.begin(), m_island_samples.end(), Point(bbox.max + Point(1, 1))); - samples_inside.clear(); + std::vector> samples_inside; for (auto it = it_lower; it != it_upper; ++ it) if (bbox.contains(*it)) samples_inside.push_back(std::make_pair(*it, false)); @@ -569,8 +571,10 @@ public: private: SupportGridPattern& operator=(const SupportGridPattern &rhs); +#if 0 // Get some internal point of an expolygon, to be used as a representative // sample to test, whether this island is inside another island. + //FIXME this was quick, but not sufficiently robust. static Point island_sample(const ExPolygon &expoly) { // Find the lowest point lexicographically. @@ -591,7 +595,10 @@ private: double coef = 20. / sqrt(l2); return Point(p2(0) + coef * v(0), p2(1) + coef * v(1)); } +#endif + // Sample one internal point per expolygon. + // FIXME this is quite an overkill to calculate a complete offset just to get a single point, but at least it is robust. static Points island_samples(const ExPolygons &expolygons) { Points pts; @@ -629,9 +636,164 @@ private: coordf_t m_support_spacing; Slic3r::EdgeGrid::Grid m_grid; + // Internal sample points of supporting expolygons. These internal points are used to pick regions corresponding + // to the initial supporting regions, after these regions werre grown and possibly split to many by the trimming polygons. Points m_island_samples; }; +namespace SupportMaterialInternal { + static inline bool has_bridging_perimeters(const ExtrusionLoop &loop) + { + for (const ExtrusionPath &ep : loop.paths) + if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) + return ep.size() >= (ep.is_closed() ? 3 : 2); + return false; + } + static bool has_bridging_perimeters(const ExtrusionEntityCollection &perimeters) + { + for (const ExtrusionEntity *ee : perimeters.entities) { + if (ee->is_collection()) { + for (const ExtrusionEntity *ee2 : static_cast(ee)->entities) { + assert(! ee2->is_collection()); + if (ee2->is_loop()) + if (has_bridging_perimeters(*static_cast(ee2))) + return true; + } + } else if (ee->is_loop() && has_bridging_perimeters(*static_cast(ee))) + return true; + } + return false; + } + static bool has_bridging_fills(const ExtrusionEntityCollection &fills) + { + for (const ExtrusionEntity *ee : fills.entities) { + assert(ee->is_collection()); + for (const ExtrusionEntity *ee2 : static_cast(ee)->entities) { + assert(! ee2->is_collection()); + assert(! ee2->is_loop()); + if (ee2->role() == erBridgeInfill) + return true; + } + } + return false; + } + static bool has_bridging_extrusions(const Layer &layer) + { + for (const LayerRegion *region : layer.regions()) { + if (SupportMaterialInternal::has_bridging_perimeters(region->perimeters)) + return true; + if (region->fill_surfaces.has(stBottomBridge) && has_bridging_fills(region->fills)) + return true; + } + return false; + } + + static inline void collect_bridging_perimeter_areas(const ExtrusionLoop &loop, const float expansion_scaled, Polygons &out) + { + assert(expansion_scaled >= 0.f); + for (const ExtrusionPath &ep : loop.paths) + if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) { + float exp = 0.5f * scale_(ep.width) + expansion_scaled; + if (ep.is_closed()) { + if (ep.size() >= 3) { + // This is a complete loop. + // Add the outer contour first. + Polygon poly; + poly.points = ep.polyline.points; + poly.points.pop_back(); + if (poly.area() < 0) + poly.reverse(); + polygons_append(out, offset(poly, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + Polygons holes = offset(poly, - exp, SUPPORT_SURFACES_OFFSET_PARAMETERS); + polygons_reverse(holes); + polygons_append(out, holes); + } + } else if (ep.size() >= 2) { + // Offset the polyline. + polygons_append(out, offset(ep.polyline, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + } + } + } + static void collect_bridging_perimeter_areas(const ExtrusionEntityCollection &perimeters, const float expansion_scaled, Polygons &out) + { + for (const ExtrusionEntity *ee : perimeters.entities) { + if (ee->is_collection()) { + for (const ExtrusionEntity *ee2 : static_cast(ee)->entities) { + assert(! ee2->is_collection()); + if (ee2->is_loop()) + collect_bridging_perimeter_areas(*static_cast(ee2), expansion_scaled, out); + } + } else if (ee->is_loop()) + collect_bridging_perimeter_areas(*static_cast(ee), expansion_scaled, out); + } + } + + static void remove_bridges_from_contacts( + const PrintConfig &print_config, + const Layer &lower_layer, + const Polygons &lower_layer_polygons, + LayerRegion *layerm, + float fw, + Polygons &contact_polygons) + { + // compute the area of bridging perimeters + Polygons bridges; + { + // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. + Polygons lower_grown_slices = offset(lower_layer_polygons, + //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. + 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder-1))), + SUPPORT_SURFACES_OFFSET_PARAMETERS); + // Collect perimeters of this layer. + //FIXME split_at_first_point() could split a bridge mid-way + #if 0 + Polylines overhang_perimeters = layerm->perimeters.as_polylines(); + // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() + for (Polyline &polyline : overhang_perimeters) + polyline.points[0].x += 1; + // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. + overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); + #else + Polylines overhang_perimeters = diff_pl(layerm->perimeters.as_polylines(), lower_grown_slices); + #endif + + // only consider straight overhangs + // only consider overhangs having endpoints inside layer's slices + // convert bridging polylines into polygons by inflating them with their thickness + // since we're dealing with bridges, we can't assume width is larger than spacing, + // so we take the largest value and also apply safety offset to be ensure no gaps + // are left in between + Flow bridge_flow = layerm->flow(frPerimeter, true); + float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing())); + for (Polyline &polyline : overhang_perimeters) + if (polyline.is_straight()) { + // This is a bridge + polyline.extend_start(fw); + polyline.extend_end(fw); + // Is the straight perimeter segment supported at both sides? + if (lower_layer.slices.contains(polyline.first_point()) && lower_layer.slices.contains(polyline.last_point())) + // Offset a polyline into a thick line. + polygons_append(bridges, offset(polyline, 0.5f * w + 10.f)); + } + bridges = union_(bridges); + } + // remove the entire bridges and only support the unsupported edges + //FIXME the brided regions are already collected as layerm->bridged. Use it? + for (const Surface &surface : layerm->fill_surfaces.surfaces) + if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) + polygons_append(bridges, surface.expolygon); + //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? + contact_polygons = diff(contact_polygons, bridges, true); + // Add the bridge anchors into the region. + //FIXME add supports at regular intervals to support long bridges! + polygons_append(contact_polygons, + intersection( + // Offset unsupported edges into polygons. + offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS), + bridges)); + } +} + // Generate top contact layers supporting overhangs. // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. // If supports over bed surface only are requested, don't generate contact layers over an object. @@ -643,9 +805,14 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ ++ iRun; #endif /* SLIC3R_DEBUG */ + // Slice support enforcers / support blockers. + std::vector enforcers = object.slice_support_enforcers(); + std::vector blockers = object.slice_support_blockers(); + // Output layers, sorted by top Z. MyLayersPtr contact_out; + const bool support_auto = m_object_config->support_material_auto.value; // If user specified a custom angle threshold, convert it to radians. // Zero means automatic overhang detection. const double threshold_rad = (m_object_config->support_material_threshold.value > 0) ? @@ -680,10 +847,13 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Note that layer_id < layer->id when raft_layers > 0 as the layer->id incorporates the raft layers. // So layer_id == 0 means first object layer and layer->id == 0 means first print layer if there are no explicit raft layers. size_t num_layers = this->has_support() ? object.layer_count() : 1; - contact_out.assign(num_layers, nullptr); + // For each overhang layer, two supporting layers may be generated: One for the overhangs extruded with a bridging flow, + // and the other for the overhangs extruded with a normal flow. + contact_out.assign(num_layers * 2, nullptr); tbb::spin_mutex layer_storage_mutex; tbb::parallel_for(tbb::blocked_range(this->has_raft() ? 0 : 1, num_layers), - [this, &object, &buildplate_covered, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out](const tbb::blocked_range& range) { + [this, &object, &buildplate_covered, &enforcers, &blockers, support_auto, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out] + (const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { const Layer &layer = *object.layers()[layer_id]; @@ -694,6 +864,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ Polygons contact_polygons; Polygons slices_margin_cached; float slices_margin_cached_offset = -1.; + Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id-1]->slices.expolygons); + // Offset of the lower layer, to trim the support polygons with to calculate dense supports. + float no_interface_offset = 0.f; if (layer_id == 0) { // This is the first object layer, so the object is being printed on a raft and // we're here just to get the object footprint for the raft. @@ -708,6 +881,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Extrusion width accounts for the roundings of the extrudates. // It is the maximum widh of the extrudate. float fw = float(layerm->flow(frExternalPerimeter).scaled_width()); + no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw); float lower_layer_offset = (layer_id < m_object_config->support_material_enforce_layers.value) ? // Enforce a full possible support, ignore the overhang angle. @@ -720,7 +894,6 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Overhang polygons for this layer and region. Polygons diff_polygons; Polygons layerm_polygons = to_polygons(layerm->slices); - Polygons lower_layer_polygons = to_polygons(lower_layer.slices.expolygons); if (lower_layer_offset == 0.f) { // Support everything. diff_polygons = diff(layerm_polygons, lower_layer_polygons); @@ -730,28 +903,61 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); } } else { - // Get the regions needing a suport, collapse very tiny spots. - //FIXME cache the lower layer offset if this layer has multiple regions. - diff_polygons = offset2( - diff(layerm_polygons, - offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)), - -0.1f*fw, +0.1f*fw); - if (! buildplate_covered.empty()) { - // Don't support overhangs above the top surfaces. - // This step is done before the contact surface is calculated by growing the overhang region. - diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); + if (support_auto) { + // Get the regions needing a suport, collapse very tiny spots. + //FIXME cache the lower layer offset if this layer has multiple regions. + #if 1 + diff_polygons = offset2( + diff(layerm_polygons, + offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)), + //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to + // no support at all for not so steep overhangs. + - 0.1f * fw, 0.1f * fw); + #else + diff_polygons = + diff(layerm_polygons, + offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + #endif + if (! buildplate_covered.empty()) { + // Don't support overhangs above the top surfaces. + // This step is done before the contact surface is calculated by growing the overhang region. + diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); + } + if (! diff_polygons.empty()) { + // Offset the support regions back to a full overhang, restrict them to the full overhang. + // This is done to increase size of the supporting columns below, as they are calculated by + // propagating these contact surfaces downwards. + diff_polygons = diff( + intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), + lower_layer_polygons); + } } - if (diff_polygons.empty()) - continue; - // Offset the support regions back to a full overhang, restrict them to the full overhang. - diff_polygons = diff( - intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), - lower_layer_polygons); + if (! enforcers.empty()) { + // Apply the "support enforcers". + //FIXME add the "enforcers" to the sparse support regions only. + const ExPolygons &enforcer = enforcers[layer_id - 1]; + if (! enforcer.empty()) { + // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes. + Polygons new_contacts = diff(intersection(layerm_polygons, to_polygons(enforcer)), + offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (! new_contacts.empty()) { + if (diff_polygons.empty()) + diff_polygons = std::move(new_contacts); + else + diff_polygons = union_(diff_polygons, new_contacts); + } + } + } + } + // Apply the "support blockers". + if (! diff_polygons.empty() && ! blockers.empty() && ! blockers[layer_id].empty()) { + // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes. + diff_polygons = diff(diff_polygons, to_polygons(blockers[layer_id])); } if (diff_polygons.empty()) continue; - #ifdef SLIC3R_DEBUG + #ifdef SLIC3R_DEBUG { ::Slic3r::SVG svg(debug_out_path("support-top-contacts-raw-run%d-layer%d-region%d.svg", iRun, layer_id, @@ -762,73 +968,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ } #endif /* SLIC3R_DEBUG */ - if (m_object_config->dont_support_bridges) { - // compute the area of bridging perimeters - // Note: this is duplicate code from GCode.pm, we need to refactor - if (true) { - Polygons bridged_perimeters; - { - Flow bridge_flow = layerm->flow(frPerimeter, true); - coordf_t nozzle_diameter = m_print_config->nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder-1); - Polygons lower_grown_slices = offset(lower_layer_polygons, 0.5f*float(scale_(nozzle_diameter)), SUPPORT_SURFACES_OFFSET_PARAMETERS); - - // Collect perimeters of this layer. - // TODO: split_at_first_point() could split a bridge mid-way - Polylines overhang_perimeters; - for (ExtrusionEntity* extrusion_entity : layerm->perimeters.entities) { - const ExtrusionEntityCollection *island = dynamic_cast(extrusion_entity); - assert(island != NULL); - for (size_t i = 0; i < island->entities.size(); ++ i) { - ExtrusionEntity *entity = island->entities[i]; - ExtrusionLoop *loop = dynamic_cast(entity); - overhang_perimeters.push_back(loop ? - loop->as_polyline() : - dynamic_cast(entity)->polyline); - } - } - - // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() - for (Polyline &polyline : overhang_perimeters) - polyline.points[0](0) += 1; - // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. - overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); - - // only consider straight overhangs - // only consider overhangs having endpoints inside layer's slices - // convert bridging polylines into polygons by inflating them with their thickness - // since we're dealing with bridges, we can't assume width is larger than spacing, - // so we take the largest value and also apply safety offset to be ensure no gaps - // are left in between - float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing())); - for (Polyline &polyline : overhang_perimeters) - if (polyline.is_straight()) { - // This is a bridge - polyline.extend_start(fw); - polyline.extend_end(fw); - // Is the straight perimeter segment supported at both sides? - if (layer.slices.contains(polyline.first_point()) && layer.slices.contains(polyline.last_point())) - // Offset a polyline into a thick line. - polygons_append(bridged_perimeters, offset(polyline, 0.5f * w + 10.f)); - } - bridged_perimeters = union_(bridged_perimeters); - } - // remove the entire bridges and only support the unsupported edges - Polygons bridges; - for (const Surface &surface : layerm->fill_surfaces.surfaces) - if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) - polygons_append(bridges, surface.expolygon); - diff_polygons = diff(diff_polygons, bridges, true); - polygons_append(bridges, bridged_perimeters); - polygons_append(diff_polygons, - intersection( - // Offset unsupported edges into polygons. - offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS), - bridges)); - } else { - // just remove bridged areas - diff_polygons = diff(diff_polygons, layerm->bridged, true); - } - } // if (m_objconfig->dont_support_bridges) + if (this->m_object_config->dont_support_bridges) + SupportMaterialInternal::remove_bridges_from_contacts( + *m_print_config, lower_layer, lower_layer_polygons, layerm, fw, diff_polygons); if (diff_polygons.empty()) continue; @@ -842,7 +984,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ union_ex(diff_polygons, false)); #endif /* SLIC3R_DEBUG */ - if (this->has_contact_loops()) + //FIXME the overhang_polygons are used to construct the support towers as well. + //if (this->has_contact_loops()) + // Store the exact contour of the overhang for the contact loops. polygons_append(overhang_polygons, diff_polygons); // Let's define the required contact area by using a max gap of half the upper @@ -851,12 +995,15 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // on the other side of the object (if it's very thin). { //FIMXE 1) Make the offset configurable, 2) Make the Z span configurable. + //FIXME one should trim with the layer span colliding with the support layer, this layer + // may be lower than lower_layer, so the support area needed may need to be actually bigger! + // For the same reason, the non-bridging support area may be smaller than the bridging support area! float slices_margin_offset = std::min(lower_layer_offset, float(scale_(m_gap_xy))); if (slices_margin_cached_offset != slices_margin_offset) { slices_margin_cached_offset = slices_margin_offset; slices_margin_cached = (slices_margin_offset == 0.f) ? - to_polygons(lower_layer.slices.expolygons) : - offset(lower_layer.slices.expolygons, slices_margin_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS); + lower_layer_polygons : + offset2(to_polygons(lower_layer.slices.expolygons), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); if (! buildplate_covered.empty()) { // Trim the inflated contact surfaces by the top surfaces as well. polygons_append(slices_margin_cached, buildplate_covered[layer_id]); @@ -879,58 +1026,72 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ } // for each layer.region } // end of Generate overhang/contact_polygons for non-raft layers. - // now apply the contact areas to the layer were they need to be made + // Now apply the contact areas to the layer where they need to be made. if (! contact_polygons.empty()) { - // get the average nozzle diameter used on this layer MyLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, sltTopContact); new_layer.idx_object_layer_above = layer_id; - if (m_slicing_params.soluble_interface) { + MyLayer *bridging_layer = nullptr; + if (layer_id == 0) { + // This is a raft contact layer sitting directly on the print bed. + assert(this->has_raft()); + new_layer.print_z = m_slicing_params.raft_contact_top_z; + new_layer.bottom_z = m_slicing_params.raft_interface_top_z; + new_layer.height = m_slicing_params.contact_raft_layer_height; + } else if (m_slicing_params.soluble_interface) { // Align the contact surface height with a layer immediately below the supported layer. - new_layer.print_z = layer.print_z - layer.height; - if (layer_id == 0) { - // This is a raft contact layer sitting directly on the print bed. - new_layer.height = m_slicing_params.contact_raft_layer_height; - new_layer.bottom_z = m_slicing_params.raft_interface_top_z; - } else { - // Interface layer will be synchronized with the object. - assert(layer_id > 0); - new_layer.height = object.layers()[layer_id - 1]->height; - new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers()[layer_id - 2]->print_z; - } + // Interface layer will be synchronized with the object. + new_layer.print_z = layer.print_z - layer.height; + new_layer.height = object.layers()[layer_id - 1]->height; + new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers()[layer_id - 2]->print_z; } else { - // Contact layer will be printed with a normal flow, but - // it will support layers printed with a bridging flow. - //FIXME Probably printing with the bridge flow? How about the unsupported perimeters? Are they printed with the bridging flow? - // In the future we may switch to a normal extrusion flow for the supported bridges. - // Get the average nozzle diameter used on this layer. - coordf_t nozzle_dmr = 0.; - for (const LayerRegion *region : layer.regions()) - nozzle_dmr += region->region()->nozzle_dmr_avg(*m_print_config); - nozzle_dmr /= coordf_t(layer.regions().size()); - new_layer.print_z = layer.print_z - nozzle_dmr - m_object_config->support_material_contact_distance; + new_layer.print_z = layer.print_z - layer.height - m_object_config->support_material_contact_distance; new_layer.bottom_z = new_layer.print_z; new_layer.height = 0.; - if (layer_id == 0) { - // This is a raft contact layer sitting directly on the print bed. - assert(this->has_raft()); - new_layer.bottom_z = m_slicing_params.raft_interface_top_z; - new_layer.height = m_slicing_params.contact_raft_layer_height; + // Ignore this contact area if it's too low. + // Don't want to print a layer below the first layer height as it may not stick well. + //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact + // and it may actually make sense to do it with a thinner layer than the first layer height. + if (new_layer.print_z < m_slicing_params.first_print_layer_height - EPSILON) { + // This contact layer is below the first layer height, therefore not printable. Don't support this surface. + continue; + } else if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { + // Align the layer with the 1st layer height. + new_layer.print_z = m_slicing_params.first_print_layer_height; + new_layer.bottom_z = 0; + new_layer.height = m_slicing_params.first_print_layer_height; } else { - // Ignore this contact area if it's too low. - // Don't want to print a layer below the first layer height as it may not stick well. - //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact - // and it may actually make sense to do it with a thinner layer than the first layer height. - if (new_layer.print_z < m_slicing_params.first_print_layer_height - EPSILON) { - // This contact layer is below the first layer height, therefore not printable. Don't support this surface. - continue; - } else if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { - // Align the layer with the 1st layer height. - new_layer.print_z = m_slicing_params.first_print_layer_height; - new_layer.bottom_z = 0; - new_layer.height = m_slicing_params.first_print_layer_height; - } else { - // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and - // its height will be set adaptively later on. + // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and + // its height will be set adaptively later on. + } + + // Contact layer will be printed with a normal flow, but + // it will support layers printed with a bridging flow. + if (SupportMaterialInternal::has_bridging_extrusions(layer)) { + coordf_t bridging_height = 0.; + for (const LayerRegion *region : layer.regions()) + bridging_height += region->region()->bridging_height_avg(*m_print_config); + bridging_height /= coordf_t(layer.regions().size()); + coordf_t bridging_print_z = layer.print_z - bridging_height - m_object_config->support_material_contact_distance; + if (bridging_print_z >= m_slicing_params.first_print_layer_height - EPSILON) { + // Not below the first layer height means this layer is printable. + if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { + // Align the layer with the 1st layer height. + bridging_print_z = m_slicing_params.first_print_layer_height; + } + if (bridging_print_z < new_layer.print_z - EPSILON) { + // Allocate the new layer. + bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, sltTopContact); + bridging_layer->idx_object_layer_above = layer_id; + bridging_layer->print_z = bridging_print_z; + if (bridging_print_z == m_slicing_params.first_print_layer_height) { + bridging_layer->bottom_z = 0; + bridging_layer->height = m_slicing_params.first_print_layer_height; + } else { + // Don't know the height yet. + bridging_layer->bottom_z = bridging_print_z; + bridging_layer->height = 0; + } + } } } } @@ -940,27 +1101,112 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ contact_polygons, // Trimming polygons, to trim the stretched support islands. slices_margin_cached, - // How much to offset the extracted contour outside of the grid. + // Grid resolution. m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), Geometry::deg2rad(m_object_config->support_material_angle.value)); - // 1) infill polygons, expand them by half the extrusion width + a tiny bit of extra. - new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5); - // 2) Contact polygons will be projected down. To keep the interface and base layers to grow, return a contour a tiny bit smaller than the grid cells. - new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3)); + // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. + new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3, true)); + // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra. + if (layer_id == 0 || m_slicing_params.soluble_interface) { + // if (no_interface_offset == 0.f) { + new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, true); + } else { + // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. + Polygons dense_interface_polygons = diff(overhang_polygons, + offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS)); +// offset(lower_layer_polygons, no_interface_offset * 0.6f, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (! dense_interface_polygons.empty()) { + //FIXME do it for the bridges only? + SupportGridPattern support_grid_pattern( + // Support islands, to be stretched into a grid. + dense_interface_polygons, + // Trimming polygons, to trim the stretched support islands. + slices_margin_cached, + // Grid resolution. + m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), + Geometry::deg2rad(m_object_config->support_material_angle.value)); + new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, false); + } + } // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded. // Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons. // Store the overhang polygons. // The overhang polygons are used in the path generator for planning of the contact loops. - // if (this->has_contact_loops()) + // if (this->has_contact_loops()). Compared to "polygons", "overhang_polygons" are snug. new_layer.overhang_polygons = new Polygons(std::move(overhang_polygons)); - contact_out[layer_id] = &new_layer; + contact_out[layer_id * 2] = &new_layer; + if (bridging_layer != nullptr) { + bridging_layer->polygons = new_layer.polygons; + bridging_layer->contact_polygons = new Polygons(*new_layer.contact_polygons); + bridging_layer->overhang_polygons = new Polygons(*new_layer.overhang_polygons); + contact_out[layer_id * 2 + 1] = bridging_layer; + } } } }); + // Compress contact_out, remove the nullptr items. remove_nulls(contact_out); + // Sort the layers, as one layer may produce bridging and non-bridging contact layers with different print_z. + std::sort(contact_out.begin(), contact_out.end(), [](const MyLayer *l1, const MyLayer *l2) { return l1->print_z < l2->print_z; }); + + // Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter), + // the top contact layer is merged into the bottom contact layer. + { + int i = 0; + int k = 0; + { + // Find the span of layers, which are to be printed at the first layer height. + int j = 0; + for (; j < contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j); + if (j > 0) { + // Merge the contact_out layers (0) to (j - 1) into the contact_out[0]. + MyLayer &dst = *contact_out.front(); + for (int u = 1; u < j; ++ u) { + MyLayer &src = *contact_out[u]; + // The union_() does not support move semantic yet, but maybe one day it will. + dst.polygons = union_(dst.polygons, std::move(src.polygons)); + *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons)); + *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons)); + // Source polygon is no more needed, it will not be refrenced. Release its data. + src.reset(); + } + // Snap the first layer to the 1st layer height. + dst.print_z = m_slicing_params.first_print_layer_height; + dst.height = m_slicing_params.first_print_layer_height; + dst.bottom_z = 0; + ++ k; + } + i = j; + } + for (; i < int(contact_out.size()); ++ k) { + // Find the span of layers closer than m_support_layer_height_min. + int j = i + 1; + coordf_t zmax = contact_out[i]->print_z + m_support_layer_height_min + EPSILON; + for (; j < contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ; + if (i + 1 < j) { + // Merge the contact_out layers (i + 1) to (j - 1) into the contact_out[i]. + MyLayer &dst = *contact_out[i]; + for (int u = i + 1; u < j; ++ u) { + MyLayer &src = *contact_out[u]; + // The union_() does not support move semantic yet, but maybe one day it will. + dst.polygons = union_(dst.polygons, std::move(src.polygons)); + *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons)); + *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons)); + // Source polygon is no more needed, it will not be refrenced. Release its data. + src.reset(); + } + } + if (k < i) + contact_out[k] = contact_out[i]; + i = j; + } + if (k < contact_out.size()) + contact_out.erase(contact_out.begin() + k, contact_out.end()); + } + BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end"; return contact_out; @@ -996,7 +1242,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta BOOST_LOG_TRIVIAL(trace) << "Support generator - bottom_contact_layers - layer " << layer_id; const Layer &layer = *object.get_layer(layer_id); // Collect projections of all contact areas above or at the same level as this top surface. - for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z >= layer.print_z; -- contact_idx) { + for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z - EPSILON; -- contact_idx) { Polygons polygons_new; // Contact surfaces are expanded away from the object, trimmed by the object. // Use a slight positive offset to overlap the touching regions. @@ -1004,7 +1250,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta // Merge and collect the contact polygons. The contact polygons are inflated, but not extended into a grid form. polygons_append(polygons_new, offset(*top_contacts[contact_idx]->contact_polygons, SCALED_EPSILON)); #else - // Consume the contact_polygons. The contact polygons are already expanded into a grid form. + // Consume the contact_polygons. The contact polygons are already expanded into a grid form, and they are a tiny bit smaller + // than the grid cells. polygons_append(polygons_new, std::move(*top_contacts[contact_idx]->contact_polygons)); #endif // These are the overhang surfaces. They are touching the object and they are not expanded away from the object. @@ -1016,9 +1263,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta continue; Polygons projection_raw = union_(projection); - // Top surfaces of this layer, to be used to stop the surface volume from growing down. tbb::task_group task_group; if (! m_object_config->support_material_buildplate_only) + // Find the bottom contact layers above the top surfaces of this layer. task_group.run([this, &object, &top_contacts, contact_idx, &layer, layer_id, &layer_storage, &layer_support_areas, &bottom_contacts, &projection_raw] { Polygons top = collect_region_slices_by_type(layer, stTop); #ifdef SLIC3R_DEBUG @@ -1046,28 +1293,34 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta // Grow top surfaces so that interface and support generation are generated // with some spacing from object - it looks we don't need the actual // top shapes so this can be done here + //FIXME calculate layer height based on the actual thickness of the layer: + // If the layer is extruded with no bridging flow, support just the normal extrusions. layer_new.height = m_slicing_params.soluble_interface ? // Align the interface layer with the object's layer height. object.layers()[layer_id + 1]->height : // Place a bridge flow interface layer over the top surface. + //FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?) + // According to Jindrich the bottom surfaces work well. + //FIXME test the bridging flow instead? m_support_material_interface_flow.nozzle_diameter; layer_new.print_z = m_slicing_params.soluble_interface ? object.layers()[layer_id + 1]->print_z : layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value; layer_new.bottom_z = layer.print_z; layer_new.idx_object_layer_below = layer_id; layer_new.bridging = ! m_slicing_params.soluble_interface; - //FIXME how much to inflate the top surface? + //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow. + //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks. layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); if (! m_slicing_params.soluble_interface) { // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface, // so there will be no support surfaces generated with thickness lower than m_support_layer_height_min. for (size_t top_idx = size_t(std::max(0, contact_idx)); - top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + m_support_layer_height_min; + top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + this->m_support_layer_height_min + EPSILON; ++ top_idx) { - if (top_contacts[top_idx]->print_z > layer_new.print_z - m_support_layer_height_min) { + if (top_contacts[top_idx]->print_z > layer_new.print_z - this->m_support_layer_height_min - EPSILON) { // A top layer has been found, which is close to the new bottom layer. coordf_t diff = layer_new.print_z - top_contacts[top_idx]->print_z; - assert(std::abs(diff) <= m_support_layer_height_min); + assert(std::abs(diff) <= this->m_support_layer_height_min + EPSILON); if (diff > 0.) { // The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer. assert(diff < layer_new.height + EPSILON); @@ -1091,10 +1344,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta union_ex(layer_new.polygons, false)); #endif /* SLIC3R_DEBUG */ // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. + //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage? touching = offset(touching, float(SCALED_EPSILON)); for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) { const Layer &layer_above = *object.layers()[layer_id_above]; - if (layer_above.print_z > layer_new.print_z + EPSILON) + if (layer_above.print_z > layer_new.print_z - EPSILON) break; if (! layer_support_areas[layer_id_above].empty()) { #ifdef SLIC3R_DEBUG @@ -1147,7 +1401,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta projection, // Trimming polygons, to trim the stretched support islands. trimming, - // How much to offset the extracted contour outside of the grid. + // Grid spacing. m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), Geometry::deg2rad(m_object_config->support_material_angle.value)); tbb::task_group task_group_inner; @@ -1158,7 +1412,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta , &layer #endif /* SLIC3R_DEBUG */ ] { - layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25); + layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25, true); #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-layer_support_area-gridded-%d-%lf.svg", iRun, layer.print_z), @@ -1172,7 +1426,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta , &layer #endif /* SLIC3R_DEBUG */ ] { - projection_new = support_grid_pattern.extract_support(-5); + projection_new = support_grid_pattern.extract_support(-5, true); #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), @@ -1185,7 +1439,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta task_group.wait(); } std::reverse(bottom_contacts.begin(), bottom_contacts.end()); - trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy); +// trim_support_layers_by_object(object, bottom_contacts, 0., 0., m_gap_xy); + trim_support_layers_by_object(object, bottom_contacts, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); + } // ! top_contacts.empty() return bottom_contacts; @@ -1502,9 +1760,6 @@ void PrintObjectSupportMaterial::generate_base_layers( assert(idx_intermediate == 0 || layer_intermediate.print_z >= intermediate_layers[idx_intermediate - 1]->print_z); // Find a top_contact layer touching the layer_intermediate from above, if any, and collect its polygons into polygons_new. - idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above, - [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; }); - // New polygons for layer_intermediate. Polygons polygons_new; @@ -1523,12 +1778,10 @@ void PrintObjectSupportMaterial::generate_base_layers( // 3) base.print_z > top.print_z && base.bottom_z >= top.bottom_z -> Overlap, which will be solved inside generate_toolpaths() by reducing the base layer height where it overlaps the top layer. No trimming needed here. // 4) base.print_z > top.bottom_z && base.bottom_z < top.bottom_z -> Base overlaps with top.bottom_z. This must not happen. // 5) base.print_z <= top.print_z && base.bottom_z >= top.bottom_z -> Base is fully inside top. Trim base by top. - int idx_top_contact_overlapping = idx_top_contact_above; - while (idx_top_contact_overlapping >= 0 && - top_contacts[idx_top_contact_overlapping]->bottom_z > layer_intermediate.print_z - EPSILON) - -- idx_top_contact_overlapping; + idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above, + [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; }); // Collect all the top_contact layer intersecting with this layer. - for (; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) { + for ( int idx_top_contact_overlapping = idx_top_contact_above; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) { MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping]; if (layer_top_overlapping.print_z < layer_intermediate.bottom_z + EPSILON) break; @@ -1608,7 +1861,10 @@ void PrintObjectSupportMaterial::generate_base_layers( ++ iRun; #endif /* SLIC3R_DEBUG */ - trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, m_gap_xy); +// trim_support_layers_by_object(object, intermediate_layers, 0., 0., m_gap_xy); + this->trim_support_layers_by_object(object, intermediate_layers, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, + m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy); } void PrintObjectSupportMaterial::trim_support_layers_by_object( @@ -1653,19 +1909,23 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( const Layer &object_layer = *object.layers()[i]; if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON) break; - polygons_append(polygons_trimming, (Polygons)object_layer.slices); + polygons_append(polygons_trimming, offset(object_layer.slices.expolygons, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); } if (! m_slicing_params.soluble_interface) { // Collect all bottom surfaces, which will be extruded with a bridging flow. for (; i < object.layers().size(); ++ i) { const Layer &object_layer = *object.layers()[i]; bool some_region_overlaps = false; - for (LayerRegion* region : object_layer.regions()) { - coordf_t nozzle_dmr = region->region()->nozzle_dmr_avg(*m_print_config); - if (object_layer.print_z - nozzle_dmr > support_layer.print_z + gap_extra_above - EPSILON) + for (LayerRegion *region : object_layer.regions()) { + coordf_t bridging_height = region->region()->bridging_height_avg(*this->m_print_config); + if (object_layer.print_z - bridging_height > support_layer.print_z + gap_extra_above - EPSILON) break; some_region_overlaps = true; - polygons_append(polygons_trimming, to_polygons(region->slices.filter_by_type(stBottomBridge))); + polygons_append(polygons_trimming, + offset(to_expolygons(region->fill_surfaces.filter_by_type(stBottomBridge)), + gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (region->region()->config().overhangs.value) + SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming); } if (! some_region_overlaps) break; @@ -1675,9 +1935,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( // perimeter's width. $support contains the full shape of support // material, thus including the width of its foremost extrusion. // We leave a gap equal to a full extrusion width. - support_layer.polygons = diff( - support_layer.polygons, - offset(polygons_trimming, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + support_layer.polygons = diff(support_layer.polygons, polygons_trimming); } }); BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end"; @@ -1800,11 +2058,12 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_int coordf_t top_z = intermediate_layers[std::min(intermediate_layers.size()-1, idx_intermediate_layer + m_object_config->support_material_interface_layers - 1)]->print_z; coordf_t bottom_z = intermediate_layers[std::max(0, int(idx_intermediate_layer) - int(m_object_config->support_material_interface_layers) + 1)]->bottom_z; // Move idx_top_contact_first up until above the current print_z. - idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); + idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON // Collect the top contact areas above this intermediate layer, below top_z. Polygons polygons_top_contact_projected; for (size_t idx_top_contact = idx_top_contact_first; idx_top_contact < top_contacts.size(); ++ idx_top_contact) { const MyLayer &top_contact_layer = *top_contacts[idx_top_contact]; + //FIXME maybe this adds one interface layer in excess? if (top_contact_layer.bottom_z - EPSILON > top_z) break; polygons_append(polygons_top_contact_projected, top_contact_layer.polygons); @@ -1861,8 +2120,8 @@ static inline void fill_expolygons_generate_paths( fill_params.density = density; fill_params.complete = true; fill_params.dont_adjust = true; - for (ExPolygons::const_iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++ it_expolygon) { - Surface surface(stInternal, *it_expolygon); + for (const ExPolygon &expoly : expolygons) { + Surface surface(stInternal, expoly); extrusion_entities_append_paths( dst, filler->fill_surface(&surface, fill_params), @@ -1883,8 +2142,8 @@ static inline void fill_expolygons_generate_paths( fill_params.density = density; fill_params.complete = true; fill_params.dont_adjust = true; - for (ExPolygons::iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++ it_expolygon) { - Surface surface(stInternal, std::move(*it_expolygon)); + for (ExPolygon &expoly : expolygons) { + Surface surface(stInternal, std::move(expoly)); extrusion_entities_append_paths( dst, filler->fill_surface(&surface, fill_params), @@ -2359,7 +2618,7 @@ void modulate_extrusion_by_overlapping_layers( (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); } private: - ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) = delete; + ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) {} const std::vector &m_path_fragments; }; const coord_t search_radius = 7; @@ -2711,6 +2970,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( continue; //FIXME When paralellizing, each thread shall have its own copy of the fillers. bool interface_as_base = (&layer_ex == &interface_layer) && m_object_config->support_material_interface_layers.value == 0; + //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore + // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) Flow interface_flow( float(layer_ex.layer->bridging ? layer_ex.layer->height : (interface_as_base ? m_support_material_flow.width : m_support_material_interface_flow.width)), float(layer_ex.layer->height), diff --git a/xs/src/libslic3r/SupportMaterial.hpp b/xs/src/libslic3r/SupportMaterial.hpp index e20d7c69f..2e1a05946 100644 --- a/xs/src/libslic3r/SupportMaterial.hpp +++ b/xs/src/libslic3r/SupportMaterial.hpp @@ -12,6 +12,7 @@ class PrintConfig; class PrintObjectConfig; // how much we extend support around the actual contact area +//FIXME this should be dependent on the nozzle diameter! #define SUPPORT_MATERIAL_MARGIN 1.5 // This class manages raft and supports for a single PrintObject. @@ -71,6 +72,21 @@ public: overhang_polygons = nullptr; } + void reset() { + layer_type = sltUnknown; + print_z = 0.; + bottom_z = 0.; + height = 0.; + idx_object_layer_above = size_t(-1); + idx_object_layer_below = size_t(-1); + bridging = false; + polygons.clear(); + delete contact_polygons; + contact_polygons = nullptr; + delete overhang_polygons; + overhang_polygons = nullptr; + } + bool operator==(const MyLayer &layer2) const { return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging; } diff --git a/xs/src/libslic3r/SurfaceCollection.hpp b/xs/src/libslic3r/SurfaceCollection.hpp index 29cfeb1db..9544748e9 100644 --- a/xs/src/libslic3r/SurfaceCollection.hpp +++ b/xs/src/libslic3r/SurfaceCollection.hpp @@ -37,6 +37,11 @@ public: void clear() { surfaces.clear(); } bool empty() const { return surfaces.empty(); } + bool has(SurfaceType type) const { + for (const Surface &surface : this->surfaces) + if (surface.surface_type == type) return true; + return false; + } void set(const SurfaceCollection &coll) { surfaces = coll.surfaces; } void set(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); } diff --git a/xs/src/libslic3r/Technologies.hpp b/xs/src/libslic3r/Technologies.hpp new file mode 100644 index 000000000..5c4f8617d --- /dev/null +++ b/xs/src/libslic3r/Technologies.hpp @@ -0,0 +1,12 @@ +#ifndef _technologies_h_ +#define _technologies_h_ + +// 1.42.0 techs +#define ENABLE_1_42_0 1 + +// Add z coordinate to model instances' offset +#define ENABLE_MODELINSTANCE_3D_OFFSET (1 && ENABLE_1_42_0) + +#endif // _technologies_h_ + + diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 7b8f85b6b..01adac337 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -21,16 +21,20 @@ #include +// for SLIC3R_DEBUG_SLICE_PROCESSING +#include "libslic3r.h" + #if 0 #define DEBUG #define _DEBUG #undef NDEBUG + #define SLIC3R_DEBUG +// #define SLIC3R_TRIANGLEMESH_DEBUG #endif #include -#ifdef SLIC3R_DEBUG -// #define SLIC3R_TRIANGLEMESH_DEBUG +#if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING) #include "SVG.hpp" #endif @@ -156,7 +160,6 @@ void TriangleMesh::repair() BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished"; } - float TriangleMesh::volume() { if (this->stl.stats.volume == -1) @@ -320,7 +323,7 @@ bool TriangleMesh::has_multiple_patches() const facet_visited[facet_idx] = true; for (int j = 0; j < 3; ++ j) { int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j]; - if (! facet_visited[neighbor_idx]) + if (neighbor_idx != -1 && ! facet_visited[neighbor_idx]) facet_queue[facet_queue_cnt ++] = neighbor_idx; } } @@ -363,7 +366,7 @@ size_t TriangleMesh::number_of_patches() const facet_visited[facet_idx] = true; for (int j = 0; j < 3; ++ j) { int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j]; - if (! facet_visited[neighbor_idx]) + if (neighbor_idx != -1 && ! facet_visited[neighbor_idx]) facet_queue[facet_queue_cnt ++] = neighbor_idx; } } @@ -623,6 +626,20 @@ void TriangleMesh::require_shared_vertices() BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices"; stl_generate_shared_vertices(&(this->stl)); } +#ifdef _DEBUG + // Verify validity of neighborship data. + for (int facet_idx = 0; facet_idx < stl.stats.number_of_facets; ++facet_idx) { + const stl_neighbors &nbr = stl.neighbors_start[facet_idx]; + const int *vertices = stl.v_indices[facet_idx].vertex; + for (int nbr_idx = 0; nbr_idx < 3; ++nbr_idx) { + int nbr_face = this->stl.neighbors_start[facet_idx].neighbor[nbr_idx]; + if (nbr_face != -1) { + assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[(nbr_idx + 1) % 3]); + assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[nbr_idx]); + } + } + } +#endif /* _DEBUG */ BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; } @@ -699,13 +716,13 @@ void TriangleMeshSlicer::init(TriangleMesh *_mesh, throw_on_cancel_callback_type } } // Assign an edge index to the 1st face. - this->facets_edges[edge_i.face * 3 + std::abs(edge_i.face_edge) - 1] = num_edges; + this->facets_edges[edge_i.face * 3 + std::abs(edge_i.face_edge) - 1] = num_edges; if (found) { EdgeToFace &edge_j = edges_map[j]; - this->facets_edges[edge_j.face * 3 + std::abs(edge_j.face_edge) - 1] = num_edges; - // Mark the edge as connected. - edge_j.face = -1; - } + this->facets_edges[edge_j.face * 3 + std::abs(edge_j.face_edge) - 1] = num_edges; + // Mark the edge as connected. + edge_j.face = -1; + } ++ num_edges; if ((i & 0x0ffff) == 0) throw_on_cancel(); @@ -781,13 +798,30 @@ void TriangleMeshSlicer::slice(const std::vector &z, std::vector FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0](2), facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1](2), facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2](2)); printf("z: min = %.2f, max = %.2f\n", min_z, max_z); - #endif + #endif /* SLIC3R_TRIANGLEMESH_DEBUG */ // find layer extents std::vector::const_iterator min_layer, max_layer; min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z - #ifdef SLIC3R_DEBUG + #ifdef SLIC3R_TRIANGLEMESH_DEBUG printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); - #endif + #endif /* SLIC3R_TRIANGLEMESH_DEBUG */ for (std::vector::const_iterator it = min_layer; it != max_layer + 1; ++it) { std::vector::size_type layer_idx = it - z.begin(); IntersectionLine il; - if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il)) { + if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il) == TriangleMeshSlicer::Slicing) { boost::lock_guard l(*lines_mutex); if (il.edge_type == feHorizontal) { - // Insert all three edges of the face. + // Insert all marked edges of the face. The marked edges do not share an edge with another horizontal face + // (they may not have a nighbor, or their neighbor is vertical) const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; const bool reverse = this->mesh->stl.facet_start[facet_idx].normal(2) < 0; - for (int j = 0; j < 3; ++ j) { - int a_id = vertices[j % 3]; - int b_id = vertices[(j+1) % 3]; - if (reverse) - std::swap(a_id, b_id); - const stl_vertex &a = this->v_scaled_shared[a_id]; - const stl_vertex &b = this->v_scaled_shared[b_id]; - il.a(0) = a(0); - il.a(1) = a(1); - il.b(0) = b(0); - il.b(1) = b(1); - il.a_id = a_id; - il.b_id = b_id; - (*lines)[layer_idx].emplace_back(il); - } + for (int j = 0; j < 3; ++ j) + if (il.flags & ((IntersectionLine::EDGE0_NO_NEIGHBOR | IntersectionLine::EDGE0_FOLD) << j)) { + int a_id = vertices[j % 3]; + int b_id = vertices[(j+1) % 3]; + if (reverse) + std::swap(a_id, b_id); + const stl_vertex &a = this->v_scaled_shared[a_id]; + const stl_vertex &b = this->v_scaled_shared[b_id]; + il.a(0) = a(0); + il.a(1) = a(1); + il.b(0) = b(0); + il.b(1) = b(1); + il.a_id = a_id; + il.b_id = b_id; + assert(il.a != il.b); + // This edge will not be used as a seed for loop extraction if it was added due to a fold of two overlapping horizontal faces. + il.set_no_seed((IntersectionLine::EDGE0_FOLD << j) != 0); + (*lines)[layer_idx].emplace_back(il); + } } else (*lines)[layer_idx].emplace_back(il); } @@ -861,7 +900,7 @@ void TriangleMeshSlicer::slice(const std::vector &z, std::vector& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf("Layer " PRINTF_ZU " (slice_z = %.2f):\n", layer_id, z[layer_id]); + printf("Layer " PRINTF_ZU " (slice_z = %.2f):\n", layer_id, z[layer_id]); #endif throw_on_cancel(); this->make_expolygons(layers_p[layer_id], &(*layers)[layer_id]); @@ -871,23 +910,22 @@ void TriangleMeshSlicer::slice(const std::vector &z, std::vectormesh->stl.v_indices[facet_idx].vertex; int i = (facet.vertex[1](2) == min_z) ? 1 : ((facet.vertex[2](2) == min_z) ? 2 : 0); - for (int j = i; j - i < 3; ++ j) { // loop through facet edges + for (int j = i; j - i < 3; ++j ) { // loop through facet edges int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)]; - const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; int a_id = vertices[j % 3]; int b_id = vertices[(j+1) % 3]; const stl_vertex &a = this->v_scaled_shared[a_id]; @@ -900,116 +938,279 @@ bool TriangleMeshSlicer::slice_facet( const stl_vertex &v1 = this->v_scaled_shared[vertices[1]]; const stl_vertex &v2 = this->v_scaled_shared[vertices[2]]; bool swap = false; + const stl_normal &normal = this->mesh->stl.facet_start[facet_idx].normal; + // We may ignore this edge for slicing purposes, but we may still use it for object cutting. + FacetSliceType result = Slicing; + const stl_neighbors &nbr = this->mesh->stl.neighbors_start[facet_idx]; if (min_z == max_z) { // All three vertices are aligned with slice_z. line_out->edge_type = feHorizontal; - if (this->mesh->stl.facet_start[facet_idx].normal(2) < 0) { + // Mark neighbor edges, which do not have a neighbor. + uint32_t edges = 0; + for (int nbr_idx = 0; nbr_idx != 3; ++ nbr_idx) { + // If the neighbor with an edge starting with a vertex idx (nbr_idx - 2) shares no + // opposite face, add it to the edges to process when slicing. + if (nbr.neighbor[nbr_idx] == -1) { + // Mark this edge to be added to the slice. + edges |= (IntersectionLine::EDGE0_NO_NEIGHBOR << nbr_idx); + } +#if 1 + else if (normal(2) > 0) { + // Produce edges for opposite faced overlapping horizontal faces aka folds. + // This method often produces connecting lines (noise) at the cutting plane. + // Produce the edges for the top facing face of the pair of top / bottom facing faces. + + // Index of a neighbor face. + const int nbr_face = nbr.neighbor[nbr_idx]; + const int *nbr_vertices = this->mesh->stl.v_indices[nbr_face].vertex; + int idx_vertex_opposite = nbr_vertices[nbr.which_vertex_not[nbr_idx]]; + const stl_vertex &c2 = this->v_scaled_shared[idx_vertex_opposite]; + if (c2(2) == slice_z) { + // Edge shared by facet_idx and nbr_face. + int a_id = vertices[nbr_idx]; + int b_id = vertices[(nbr_idx + 1) % 3]; + int c1_id = vertices[(nbr_idx + 2) % 3]; + const stl_vertex &a = this->v_scaled_shared[a_id]; + const stl_vertex &b = this->v_scaled_shared[b_id]; + const stl_vertex &c1 = this->v_scaled_shared[c1_id]; + // Verify that the two neighbor faces share a common edge. + assert(nbr_vertices[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == b_id); + assert(nbr_vertices[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id); + double n1 = (double(c1(0)) - double(a(0))) * (double(b(1)) - double(a(1))) - (double(c1(1)) - double(a(1))) * (double(b(0)) - double(a(0))); + double n2 = (double(c2(0)) - double(a(0))) * (double(b(1)) - double(a(1))) - (double(c2(1)) - double(a(1))) * (double(b(0)) - double(a(0))); + if (n1 * n2 > 0) + // The two faces overlap. This indicates an invalid mesh geometry (non-manifold), + // but these are the real world objects, and leaving out these edges leads to missing contours. + edges |= (IntersectionLine::EDGE0_FOLD << nbr_idx); + } + } +#endif + } + // Use some edges of this triangle for slicing only if at least one of its edge does not have an opposite face. + result = (edges == 0) ? Cutting : Slicing; + line_out->flags |= edges; + if (normal(2) < 0) { // If normal points downwards this is a bottom horizontal facet so we reverse its point order. swap = true; } - } else if (v0(2) < slice_z || v1(2) < slice_z || v2(2) < slice_z) { - // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. - line_out->edge_type = feTop; - swap = true; } else { - // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. - line_out->edge_type = feBottom; + // Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane. + int nbr_idx = j % 3; + int nbr_face = nbr.neighbor[nbr_idx]; + // Is the third vertex below the cutting plane? + bool third_below = v0(2) < slice_z || v1(2) < slice_z || v2(2) < slice_z; + // Is this a concave corner? + if (nbr_face == -1) { +#ifdef _DEBUG + printf("Face has no neighbor!\n"); +#endif + } else { + assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == b_id); + assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id); + int idx_vertex_opposite = this->mesh->stl.v_indices[nbr_face].vertex[nbr.which_vertex_not[nbr_idx]]; + const stl_vertex &c = this->v_scaled_shared[idx_vertex_opposite]; + if (c(2) == slice_z) { + double normal_nbr = (double(c(0)) - double(a(0))) * (double(b(1)) - double(a(1))) - (double(c(1)) - double(a(1))) * (double(b(0)) - double(a(0))); +#if 0 + if ((normal_nbr < 0) == third_below) { + printf("Flipped normal?\n"); + } +#endif + result = + // A vertical face shares edge with a horizontal face. Verify, whether the shared edge makes a convex or concave corner. + // Unfortunately too often there are flipped normals, which brake our assumption. Let's rather return every edge, + // and leth the code downstream hopefully handle it. + #if 1 + // Ignore concave corners for slicing. + // This method has the unfortunate property, that folds in a horizontal plane create concave corners, + // leading to broken contours, if these concave corners are not replaced by edges of the folds, see above. + ((normal_nbr < 0) == third_below) ? Cutting : Slicing; + #else + // Use concave corners for slicing. This leads to the test 01_trianglemesh.t "slicing a top tangent plane includes its area" failing, + // and rightly so. + Slicing; + #endif + } else { + // For a pair of faces touching exactly at the cutting plane, ignore one of them. An arbitrary rule is to ignore the face with a higher index. + result = (facet_idx < nbr_face) ? Slicing : Cutting; + } + } + if (third_below) { + line_out->edge_type = feTop; + swap = true; + } else + line_out->edge_type = feBottom; } line_out->a = to_2d(swap ? b : a).cast(); line_out->b = to_2d(swap ? a : b).cast(); line_out->a_id = swap ? b_id : a_id; line_out->b_id = swap ? a_id : b_id; - return true; + assert(line_out->a != line_out->b); + return result; } if (a(2) == slice_z) { // Only point a alings with the cutting plane. - points_on_layer[num_points_on_layer ++] = num_points; - IntersectionPoint &point = points[num_points ++]; - point(0) = a(0); - point(1) = a(1); - point.point_id = a_id; + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { + point_on_layer = num_points; + IntersectionPoint &point = points[num_points ++]; + point(0) = a(0); + point(1) = a(1); + point.point_id = a_id; + } } else if (b(2) == slice_z) { // Only point b alings with the cutting plane. - points_on_layer[num_points_on_layer ++] = num_points; - IntersectionPoint &point = points[num_points ++]; - point(0) = b(0); - point(1) = b(1); - point.point_id = b_id; + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { + point_on_layer = num_points; + IntersectionPoint &point = points[num_points ++]; + point(0) = b(0); + point(1) = b(1); + point.point_id = b_id; + } } else if ((a(2) < slice_z && b(2) > slice_z) || (b(2) < slice_z && a(2) > slice_z)) { // A general case. The face edge intersects the cutting plane. Calculate the intersection point. - IntersectionPoint &point = points[num_points ++]; - point(0) = b(0) + (a(0) - b(0)) * (slice_z - b(2)) / (a(2) - b(2)); - point(1) = b(1) + (a(1) - b(1)) * (slice_z - b(2)) / (a(2) - b(2)); - point.edge_id = edge_id; + assert(a_id != b_id); + // Sort the edge to give a consistent answer. + const stl_vertex *pa = &a; + const stl_vertex *pb = &b; + if (a_id > b_id) { + std::swap(a_id, b_id); + std::swap(pa, pb); + } + IntersectionPoint &point = points[num_points]; + double t = (double(slice_z) - double((*pb)(2))) / (double((*pa)(2)) - double((*pb)(2))); + if (t <= 0.) { + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { + point(0) = (*pa)(0); + point(1) = (*pa)(1); + point_on_layer = num_points ++; + point.point_id = a_id; + } + } else if (t >= 1.) { + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { + point(0) = (*pb)(0); + point(1) = (*pb)(1); + point_on_layer = num_points ++; + point.point_id = b_id; + } + } else { + point(0) = coord_t(floor(double((*pb)(0)) + (double((*pa)(0)) - double((*pb)(0))) * t + 0.5)); + point(1) = coord_t(floor(double((*pb)(1)) + (double((*pa)(1)) - double((*pb)(1))) * t + 0.5)); + point.edge_id = edge_id; + ++ num_points; + } } } - // We can't have only one point on layer because each vertex gets detected - // twice (once for each edge), and we can't have three points on layer, - // because we assume this code is not getting called for horizontal facets. - assert(num_points_on_layer == 0 || num_points_on_layer == 2); - if (num_points_on_layer > 0) { - assert(points[points_on_layer[0]].point_id == points[points_on_layer[1]].point_id); - assert(num_points == 2 || num_points == 3); - if (num_points < 3) - // This triangle touches the cutting plane with a single vertex. Ignore it. - return false; - // Erase one of the duplicate points. - -- num_points; - for (int i = points_on_layer[1]; i < num_points; ++ i) - points[i] = points[i + 1]; - } - - // Facets must intersect each plane 0 or 2 times. - assert(num_points == 0 || num_points == 2); + // Facets must intersect each plane 0 or 2 times, or it may touch the plane at a single vertex only. + assert(num_points < 3); if (num_points == 2) { - line_out->edge_type = feNone; + line_out->edge_type = feGeneral; line_out->a = (Point)points[1]; line_out->b = (Point)points[0]; line_out->a_id = points[1].point_id; line_out->b_id = points[0].point_id; line_out->edge_a_id = points[1].edge_id; line_out->edge_b_id = points[0].edge_id; - return true; + // Not a zero lenght edge. + //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. + //assert(line_out->a != line_out->b); + // The plane cuts at least one edge in a general position. + assert(line_out->a_id == -1 || line_out->b_id == -1); + assert(line_out->edge_a_id != -1 || line_out->edge_b_id != -1); + // General slicing position, use the segment for both slicing and object cutting. +#if 0 + if (line_out->a_id != -1 && line_out->b_id != -1) { + // Solving a degenerate case, where both the intersections snapped to an edge. + // Correctly classify the face as below or above based on the position of the 3rd point. + int i = vertices[0]; + if (i == line_out->a_id || i == line_out->b_id) + i = vertices[1]; + if (i == line_out->a_id || i == line_out->b_id) + i = vertices[2]; + assert(i != line_out->a_id && i != line_out->b_id); + line_out->edge_type = (this->v_scaled_shared[i].z < slice_z) ? feTop : feBottom; + } +#endif + return Slicing; + } + return NoSlice; +} + +//FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing +// and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces. +// So the following code makes only sense now to handle degenerate meshes with more than two faces +// sharing a single edge. +static inline void remove_tangent_edges(std::vector &lines) +{ + std::vector by_vertex_pair; + by_vertex_pair.reserve(lines.size()); + for (IntersectionLine& line : lines) + if (line.edge_type != feGeneral && line.a_id != -1) + // This is a face edge. Check whether there is its neighbor stored in lines. + by_vertex_pair.emplace_back(&line); + auto edges_lower_sorted = [](const IntersectionLine *l1, const IntersectionLine *l2) { + // Sort vertices of l1, l2 lexicographically + int l1a = l1->a_id; + int l1b = l1->b_id; + int l2a = l2->a_id; + int l2b = l2->b_id; + if (l1a > l1b) + std::swap(l1a, l1b); + if (l2a > l2b) + std::swap(l2a, l2b); + // Lexicographical "lower" operator on lexicographically sorted vertices should bring equal edges together when sored. + return l1a < l2a || (l1a == l2a && l1b < l2b); + }; + std::sort(by_vertex_pair.begin(), by_vertex_pair.end(), edges_lower_sorted); + for (auto line = by_vertex_pair.begin(); line != by_vertex_pair.end(); ++ line) { + IntersectionLine &l1 = **line; + if (! l1.skip()) { + // Iterate as long as line and line2 edges share the same end points. + for (auto line2 = line + 1; line2 != by_vertex_pair.end() && ! edges_lower_sorted(*line, *line2); ++ line2) { + // Lines must share the end points. + assert(! edges_lower_sorted(*line, *line2)); + assert(! edges_lower_sorted(*line2, *line)); + IntersectionLine &l2 = **line2; + if (l2.skip()) + continue; + if (l1.a_id == l2.a_id) { + assert(l1.b_id == l2.b_id); + l2.set_skip(); + // If they are both oriented upwards or downwards (like a 'V'), + // then we can remove both edges from this layer since it won't + // affect the sliced shape. + // If one of them is oriented upwards and the other is oriented + // downwards, let's only keep one of them (it doesn't matter which + // one since all 'top' lines were reversed at slicing). + if (l1.edge_type == l2.edge_type) { + l1.set_skip(); + break; + } + } else { + assert(l1.a_id == l2.b_id && l1.b_id == l2.a_id); + // If this edge joins two horizontal facets, remove both of them. + if (l1.edge_type == feHorizontal && l2.edge_type == feHorizontal) { + l1.set_skip(); + l2.set_skip(); + break; + } + } + } + } } - return false; } void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const { - // Remove tangent edges. - //FIXME This is O(n^2) in rare cases when many faces intersect the cutting plane. - for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++ line) - if (! line->skip && line->edge_type != feNone) { - // This line is af facet edge. There may be a duplicate line with the same end vertices. - // If the line is is an edge connecting two facets, find another facet edge - // having the same endpoints but in reverse order. - for (IntersectionLines::iterator line2 = line + 1; line2 != lines.end(); ++ line2) - if (! line2->skip && line2->edge_type != feNone) { - // Are these facets adjacent? (sharing a common edge on this layer) - if (line->a_id == line2->a_id && line->b_id == line2->b_id) { - line2->skip = true; - /* if they are both oriented upwards or downwards (like a 'V') - then we can remove both edges from this layer since it won't - affect the sliced shape */ - /* if one of them is oriented upwards and the other is oriented - downwards, let's only keep one of them (it doesn't matter which - one since all 'top' lines were reversed at slicing) */ - if (line->edge_type == line2->edge_type) { - line->skip = true; - break; - } - } else if (line->a_id == line2->b_id && line->b_id == line2->a_id) { - /* if this edge joins two horizontal facets, remove both of them */ - if (line->edge_type == feHorizontal && line2->edge_type == feHorizontal) { - line->skip = true; - line2->skip = true; - break; - } - } - } - } +#if 0 +//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. +//#ifdef _DEBUG + for (const Line &l : lines) + assert(l.a != l.b); +#endif /* _DEBUG */ + + remove_tangent_edges(lines); struct OpenPolyline { OpenPolyline() {}; @@ -1032,7 +1233,7 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo by_edge_a_id.reserve(lines.size()); by_a_id.reserve(lines.size()); for (IntersectionLine &line : lines) { - if (! line.skip) { + if (! line.skip()) { if (line.edge_a_id != -1) by_edge_a_id.emplace_back(&line); if (line.a_id != -1) @@ -1049,13 +1250,14 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo // take first spare line and start a new loop IntersectionLine *first_line = nullptr; for (; it_line_seed != lines.end(); ++ it_line_seed) - if (! it_line_seed->skip) { + if (it_line_seed->is_seed_candidate()) { + //if (! it_line_seed->skip()) { first_line = &(*it_line_seed ++); break; } if (first_line == nullptr) break; - first_line->skip = true; + first_line->set_skip(); Points loop_pts; loop_pts.emplace_back(first_line->a); IntersectionLine *last_line = first_line; @@ -1076,7 +1278,7 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo if (it_begin != by_edge_a_id.end()) { auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip) { + if (! (*it_line)->skip()) { next_line = *it_line; break; } @@ -1088,7 +1290,7 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo if (it_begin != by_a_id.end()) { auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip) { + if (! (*it_line)->skip()) { next_line = *it_line; break; } @@ -1119,7 +1321,7 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo */ loop_pts.emplace_back(next_line->a); last_line = next_line; - next_line->skip = true; + next_line->set_skip(); } } } @@ -1186,12 +1388,12 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo } } } - if (next_start == nullptr) { - // The current loop could not be closed. Unmark the segment. - opl.consumed = false; - break; - } - // Attach this polyline to the end of the initial polyline. + if (next_start == nullptr) { + // The current loop could not be closed. Unmark the segment. + opl.consumed = false; + break; + } + // Attach this polyline to the end of the initial polyline. if (next_start->start) { auto it = next_start->polyline->points.begin(); std::copy(++ it, next_start->polyline->points.end(), back_inserter(opl.points)); @@ -1211,8 +1413,8 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo if ((ip1.edge_id != -1 && ip1.edge_id == ip2.edge_id) || (ip1.point_id != -1 && ip1.point_id == ip2.point_id)) { // The current loop is complete. Add it to the output. - /*assert(opl.points.front().point_id == opl.points.back().point_id); - assert(opl.points.front().edge_id == opl.points.back().edge_id);*/ + //assert(opl.points.front().point_id == opl.points.back().point_id); + //assert(opl.points.front().edge_id == opl.points.back().edge_id); // Remove the duplicate last point. opl.points.pop_back(); if (opl.points.size() >= 3) { @@ -1227,9 +1429,9 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo loops->emplace_back(std::move(opl.points)); } opl.points.clear(); - break; + break; } - // Continue with the current loop. + // Continue with the current loop. } } } @@ -1277,15 +1479,15 @@ void TriangleMeshSlicer::make_expolygons_simple(std::vector &l if (slice_idx == -1) // Ignore this hole. continue; - assert(current_contour_area < std::numeric_limits::max() && current_contour_area >= -hole->area()); - (*slices)[slice_idx].holes.emplace_back(std::move(*hole)); + assert(current_contour_area < std::numeric_limits::max() && current_contour_area >= -hole->area()); + (*slices)[slice_idx].holes.emplace_back(std::move(*hole)); } #if 0 // If the input mesh is not valid, the holes may intersect with the external contour. // Rather subtract them from the outer contour. Polygons poly; - for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) { + for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) { if (it_slice->holes.empty()) { poly.emplace_back(std::move(it_slice->contour)); } else { @@ -1295,7 +1497,7 @@ void TriangleMeshSlicer::make_expolygons_simple(std::vector &l it->reverse(); polygons_append(poly, diff(contours, it_slice->holes)); } - } + } // If the input mesh is not valid, the input contours may intersect. *slices = union_ex(poly); #endif @@ -1412,7 +1614,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) // intersect facet with cutting plane IntersectionLine line; - if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line)) { + if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line) != TriangleMeshSlicer::NoSlice) { // Save intersection lines for generating correct triangulations. if (line.edge_type == feTop) { lower_lines.emplace_back(line); diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index ed3e6022d..f6e0baea9 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -83,7 +83,7 @@ private: enum FacetEdgeType { // A general case, the cutting plane intersect a face at two different edges. - feNone, + feGeneral, // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. feTop, // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. @@ -117,6 +117,14 @@ public: class IntersectionLine : public Line { public: + IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feGeneral), flags(0) {} + + bool skip() const { return (this->flags & SKIP) != 0; } + void set_skip() { this->flags |= SKIP; } + + bool is_seed_candidate() const { return (this->flags & NO_SEED) == 0 && ! this->skip(); } + void set_no_seed(bool set) { if (set) this->flags |= NO_SEED; else this->flags &= ~NO_SEED; } + // Inherits Point a, b // For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1. // Vertex indices of the line end points. @@ -125,11 +133,23 @@ public: // Source mesh edges of the line end points. int edge_a_id; int edge_b_id; - // feNone, feTop, feBottom, feHorizontal + // feGeneral, feTop, feBottom, feHorizontal FacetEdgeType edge_type; - // Used by TriangleMeshSlicer::make_loops() to skip duplicate edges. - bool skip; - IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feNone), skip(false) {}; + // Used by TriangleMeshSlicer::slice() to skip duplicate edges. + enum { + // Triangle edge added, because it has no neighbor. + EDGE0_NO_NEIGHBOR = 0x001, + EDGE1_NO_NEIGHBOR = 0x002, + EDGE2_NO_NEIGHBOR = 0x004, + // Triangle edge added, because it makes a fold with another horizontal edge. + EDGE0_FOLD = 0x010, + EDGE1_FOLD = 0x020, + EDGE2_FOLD = 0x040, + // The edge cannot be a seed of a greedy loop extraction (folds are not safe to become seeds). + NO_SEED = 0x100, + SKIP = 0x200, + }; + uint32_t flags; }; typedef std::vector IntersectionLines; typedef std::vector IntersectionLinePtrs; @@ -144,7 +164,12 @@ public: void init(TriangleMesh *mesh, throw_on_cancel_callback_type throw_on_cancel); void slice(const std::vector &z, std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const; void slice(const std::vector &z, std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const; - bool slice_facet(float slice_z, const stl_facet &facet, const int facet_idx, + enum FacetSliceType { + NoSlice = 0, + Slicing = 1, + Cutting = 2 + }; + FacetSliceType slice_facet(float slice_z, const stl_facet &facet, const int facet_idx, const float min_z, const float max_z, IntersectionLine *line_out) const; void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index dd05891dd..0d2df5a0b 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -9,6 +9,7 @@ namespace Slic3r { extern void set_logging_level(unsigned int level); extern void trace(unsigned int level, const char *message); +extern void disable_multi_threading(); // Set a path with GUI resource files. void set_var_dir(const std::string &path); diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 925e93031..cf0a19f7a 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -13,6 +13,8 @@ #include #include +#include "Technologies.hpp" + #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" #define SLIC3R_VERSION "1.41.0" #define SLIC3R_BUILD "UNKNOWN" diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 4c2d65605..46ee65a35 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -25,6 +25,8 @@ #include #include +#include + namespace Slic3r { static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error; @@ -83,6 +85,14 @@ void trace(unsigned int level, const char *message) (::boost::log::keywords::severity = severity)) << message; } +void disable_multi_threading() +{ + // Disable parallelization so the Shiny profiler works + static tbb::task_scheduler_init *tbb_init = nullptr; + if (tbb_init == nullptr) + tbb_init = new tbb::task_scheduler_init(1); +} + static std::string g_var_dir; void set_var_dir(const std::string &dir) diff --git a/xs/src/slic3r/AppController.cpp b/xs/src/slic3r/AppController.cpp index 5e259c367..4a36b5d7f 100644 --- a/xs/src/slic3r/AppController.cpp +++ b/xs/src/slic3r/AppController.cpp @@ -11,12 +11,11 @@ #include #include -#include -#include #include +#include +#include #include #include -#include namespace Slic3r { @@ -44,15 +43,6 @@ namespace GUI { PresetBundle* get_preset_bundle(); } -static const PrintObjectStep STEP_SLICE = posSlice; -static const PrintObjectStep STEP_PERIMETERS = posPerimeters; -static const PrintObjectStep STEP_PREPARE_INFILL = posPrepareInfill; -static const PrintObjectStep STEP_INFILL = posInfill; -static const PrintObjectStep STEP_SUPPORTMATERIAL = posSupportMaterial; -static const PrintStep STEP_SKIRT = psSkirt; -static const PrintStep STEP_BRIM = psBrim; -static const PrintStep STEP_WIPE_TOWER = psWipeTower; - AppControllerBoilerplate::ProgresIndicatorPtr AppControllerBoilerplate::global_progress_indicator() { ProgresIndicatorPtr ret; @@ -72,343 +62,8 @@ void AppControllerBoilerplate::global_progress_indicator( pri_data_->m.unlock(); } -#if 0 -void PrintController::make_skirt() -{ - assert(print_ != nullptr); - - // prerequisites - for(auto obj : print_->objects()) make_perimeters(obj); - for(auto obj : print_->objects()) infill(obj); - for(auto obj : print_->objects()) gen_support_material(obj); - - if(!print_->state.is_done(STEP_SKIRT)) { - print_->state.set_started(STEP_SKIRT); - print_->skirt.clear(); - if(print_->has_skirt()) print_->_make_skirt(); - - print_->state.set_done(STEP_SKIRT); - } -} - -void PrintController::make_brim() -{ - assert(print_ != nullptr); - - // prerequisites - for(auto obj : print_->objects()) make_perimeters(obj); - for(auto obj : print_->objects()) infill(obj); - for(auto obj : print_->objects()) gen_support_material(obj); - make_skirt(); - - if(!print_->state.is_done(STEP_BRIM)) { - print_->state.set_started(STEP_BRIM); - - // since this method must be idempotent, we clear brim paths *before* - // checking whether we need to generate them - print_->brim.clear(); - - if(print_->config.brim_width > 0) print_->_make_brim(); - - print_->state.set_done(STEP_BRIM); - } -} - -void PrintController::make_wipe_tower() -{ - assert(print_ != nullptr); - - // prerequisites - for(auto obj : print_->objects()) make_perimeters(obj); - for(auto obj : print_->objects()) infill(obj); - for(auto obj : print_->objects()) gen_support_material(obj); - make_skirt(); - make_brim(); - - if(!print_->state.is_done(STEP_WIPE_TOWER)) { - print_->state.set_started(STEP_WIPE_TOWER); - - // since this method must be idempotent, we clear brim paths *before* - // checking whether we need to generate them - print_->brim.clear(); - - if(print_->has_wipe_tower()) print_->_make_wipe_tower(); - - print_->state.set_done(STEP_WIPE_TOWER); - } -} - -void PrintController::slice(PrintObject *pobj) -{ - assert(pobj != nullptr && print_ != nullptr); - - if(pobj->state.is_done(STEP_SLICE)) return; - - pobj->state.set_started(STEP_SLICE); - - pobj->_slice(); - - auto msg = pobj->_fix_slicing_errors(); - if(!msg.empty()) report_issue(IssueType::WARN, msg); - - // simplify slices if required - if (print_->config.resolution) - pobj->_simplify_slices(scale_(print_->config.resolution)); - - - if(pobj->layers.empty()) - report_issue(IssueType::ERR, - _(L("No layers were detected. You might want to repair your " - "STL file(s) or check their size or thickness and retry")) - ); - - pobj->state.set_done(STEP_SLICE); -} - -void PrintController::make_perimeters(PrintObject *pobj) -{ - assert(pobj != nullptr); - - slice(pobj); - - if (!pobj->state.is_done(STEP_PERIMETERS)) { - pobj->_make_perimeters(); - } -} - -void PrintController::infill(PrintObject *pobj) -{ - assert(pobj != nullptr); - - make_perimeters(pobj); - - if (!pobj->state.is_done(STEP_PREPARE_INFILL)) { - pobj->state.set_started(STEP_PREPARE_INFILL); - - pobj->_prepare_infill(); - - pobj->state.set_done(STEP_PREPARE_INFILL); - } - - pobj->_infill(); -} - -void PrintController::gen_support_material(PrintObject *pobj) -{ - assert(pobj != nullptr); - - // prerequisites - slice(pobj); - - if(!pobj->state.is_done(STEP_SUPPORTMATERIAL)) { - pobj->state.set_started(STEP_SUPPORTMATERIAL); - - pobj->clear_support_layers(); - - if((pobj->config.support_material || pobj->config.raft_layers > 0) - && pobj->layers.size() > 1) { - pobj->_generate_support_material(); - } - - pobj->state.set_done(STEP_SUPPORTMATERIAL); - } -} - -PrintController::PngExportData -PrintController::query_png_export_data(const DynamicPrintConfig& conf) -{ - PngExportData ret; - - auto zippath = query_destination_path("Output zip file", "*.zip", "out"); - - ret.zippath = zippath; - - ret.width_mm = conf.opt_float("display_width"); - ret.height_mm = conf.opt_float("display_height"); - - ret.width_px = conf.opt_int("display_pixels_x"); - ret.height_px = conf.opt_int("display_pixels_y"); - - auto opt_corr = conf.opt("printer_correction"); - - if(opt_corr) { - ret.corr_x = opt_corr->values[0]; - ret.corr_y = opt_corr->values[1]; - ret.corr_z = opt_corr->values[2]; - } - - ret.exp_time_first_s = conf.opt_float("initial_exposure_time"); - ret.exp_time_s = conf.opt_float("exposure_time"); - - return ret; -} - -void PrintController::slice(AppControllerBoilerplate::ProgresIndicatorPtr pri) -{ - auto st = pri->state(); - - Slic3r::trace(3, "Starting the slicing process."); - - pri->update(st+20, _(L("Generating perimeters"))); - for(auto obj : print_->objects()) make_perimeters(obj); - - pri->update(st+60, _(L("Infilling layers"))); - for(auto obj : print_->objects()) infill(obj); - - pri->update(st+70, _(L("Generating support material"))); - for(auto obj : print_->objects()) gen_support_material(obj); - - pri->message_fmt(_(L("Weight: %.1fg, Cost: %.1f")), - print_->total_weight, print_->total_cost); - pri->state(st+85); - - - pri->update(st+88, _(L("Generating skirt"))); - make_skirt(); - - - pri->update(st+90, _(L("Generating brim"))); - make_brim(); - - pri->update(st+95, _(L("Generating wipe tower"))); - make_wipe_tower(); - - pri->update(st+100, _(L("Done"))); - - // time to make some statistics.. - - Slic3r::trace(3, _(L("Slicing process finished."))); -} - -void PrintController::slice() -{ - auto pri = global_progress_indicator(); - if(!pri) pri = create_progress_indicator(100, L("Slicing")); - slice(pri); -} - -void PrintController::slice_to_png() -{ - using Pointf3 = Vec3d; - - auto presetbundle = GUI::get_preset_bundle(); - - assert(presetbundle); - - auto pt = presetbundle->printers.get_selected_preset().printer_technology(); - if(pt != ptSLA) { - report_issue(IssueType::ERR, _("Printer technology is not SLA!"), - _("Error")); - return; - } - - auto conf = presetbundle->full_config(); - conf.validate(); - - auto exd = query_png_export_data(conf); - if(exd.zippath.empty()) return; - - try { - print_->apply_config(conf); - print_->validate(); - } catch(std::exception& e) { - report_issue(IssueType::ERR, e.what(), "Error"); - return; - } - - // TODO: copy the model and work with the copy only - bool correction = false; - if(exd.corr_x != 1.0 || exd.corr_y != 1.0 || exd.corr_z != 1.0) { - correction = true; - print_->invalidate_all_steps(); - - for(auto po : print_->objects()) { - po->model_object()->scale( - Pointf3(exd.corr_x, exd.corr_y, exd.corr_z) - ); - po->model_object()->invalidate_bounding_box(); - po->reload_model_instances(); - po->invalidate_all_steps(); - } - } - - // Turn back the correction scaling on the model. - auto scale_back = [this, correction, exd]() { - if(correction) { // scale the model back - print_->invalidate_all_steps(); - for(auto po : print_->objects()) { - po->model_object()->scale( - Pointf3(1.0/exd.corr_x, 1.0/exd.corr_y, 1.0/exd.corr_z) - ); - po->model_object()->invalidate_bounding_box(); - po->reload_model_instances(); - po->invalidate_all_steps(); - } - } - }; - - auto print_bb = print_->bounding_box(); - Vec2d punsc = unscale(print_bb.size()); - - // If the print does not fit into the print area we should cry about it. - if(px(punsc) > exd.width_mm || py(punsc) > exd.height_mm) { - std::stringstream ss; - - ss << _(L("Print will not fit and will be truncated!")) << "\n" - << _(L("Width needed: ")) << px(punsc) << " mm\n" - << _(L("Height needed: ")) << py(punsc) << " mm\n"; - - if(!report_issue(IssueType::WARN_Q, ss.str(), _(L("Warning")))) { - scale_back(); - return; - } - } - -// std::async(supports_asynch()? std::launch::async : std::launch::deferred, -// [this, exd, scale_back]() -// { - - auto pri = create_progress_indicator( - 200, _(L("Slicing to zipped png files..."))); - - try { - pri->update(0, _(L("Slicing..."))); - slice(pri); - } catch (std::exception& e) { - pri->cancel(); - report_issue(IssueType::ERR, e.what(), _(L("Exception occured"))); - scale_back(); - return; - } - - auto pbak = print_->progressindicator; - print_->progressindicator = pri; - - try { - print_to( *print_, exd.zippath, - exd.width_mm, exd.height_mm, - exd.width_px, exd.height_px, - exd.exp_time_s, exd.exp_time_first_s); - - } catch (std::exception& e) { - pri->cancel(); - report_issue(IssueType::ERR, e.what(), _(L("Exception occured"))); - } - - print_->progressindicator = pbak; - scale_back(); - -// }); -} - -const PrintConfig &PrintController::config() const -{ - return print_->config; -} -#endif - void ProgressIndicator::message_fmt( - const wxString &fmtstr, ...) { + const std::string &fmtstr, ...) { std::stringstream ss; va_list args; va_start(args, fmtstr); @@ -438,6 +93,11 @@ void AppController::arrange_model() { using Coord = libnest2d::TCoord; + if(arranging_.load()) return; + + // to prevent UI reentrancies + arranging_.store(true); + unsigned count = 0; for(auto obj : model_->objects) count += obj->instances.size(); @@ -451,8 +111,8 @@ void AppController::arrange_model() // Set the range of the progress to the object count pind->max(count); - pind->on_cancel([](){ - std::cout << "Cannot be cancelled!" << std::endl; + pind->on_cancel([this](){ + arranging_.store(false); }); } @@ -466,7 +126,7 @@ void AppController::arrange_model() for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - if(pind) pind->update(0, _(L("Arranging objects..."))); + if(pind) pind->update(0, L("Arranging objects...")); try { arr::BedShapeHint hint; @@ -478,24 +138,30 @@ void AppController::arrange_model() bed, hint, false, // create many piles not just one pile - [pind, count](unsigned rem) { + [this, pind, count](unsigned rem) { if(pind) - pind->update(count - rem, _(L("Arranging objects..."))); - }); + pind->update(count - rem, L("Arranging objects...")); + + process_events(); + }, [this] () { return !arranging_.load(); }); } catch(std::exception& e) { std::cerr << e.what() << std::endl; report_issue(IssueType::ERR, - _(L("Could not arrange model objects! " - "Some geometries may be invalid.")), - _(L("Exception occurred"))); + L("Could not arrange model objects! " + "Some geometries may be invalid."), + L("Exception occurred")); } // Restore previous max value if(pind) { pind->max(pmax); - pind->update(0, _(L("Arranging done."))); + pind->update(0, arranging_.load() ? L("Arranging done.") : + L("Arranging canceled.")); + pind->on_cancel(/*remove cancel function*/); } + + arranging_.store(false); } } diff --git a/xs/src/slic3r/AppController.hpp b/xs/src/slic3r/AppController.hpp index 740f9a630..71472835e 100644 --- a/xs/src/slic3r/AppController.hpp +++ b/xs/src/slic3r/AppController.hpp @@ -18,6 +18,7 @@ class Print; class PrintObject; class PrintConfig; class ProgressStatusBar; +class DynamicPrintConfig; /** * @brief A boilerplate class for creating application logic. It should provide @@ -48,7 +49,7 @@ public: AppControllerBoilerplate(); ~AppControllerBoilerplate(); - using Path = wxString; + using Path = std::string; using PathList = std::vector; /// Common runtime issue types @@ -69,20 +70,20 @@ public: * @return Returns a list of paths choosed by the user. */ PathList query_destination_paths( - const wxString& title, + const std::string& title, const std::string& extensions) const; /** * @brief Same as query_destination_paths but works for directories only. */ PathList query_destination_dirs( - const wxString& title) const; + const std::string& title) const; /** * @brief Same as query_destination_paths but returns only one path. */ Path query_destination_path( - const wxString& title, + const std::string& title, const std::string& extensions, const std::string& hint = "") const; @@ -97,11 +98,11 @@ public: * title. */ bool report_issue(IssueType issuetype, - const wxString& description, - const wxString& brief); + const std::string& description, + const std::string& brief); bool report_issue(IssueType issuetype, - const wxString& description); + const std::string& description); /** * @brief Return the global progress indicator for the current controller. @@ -152,12 +153,12 @@ protected: */ ProgresIndicatorPtr create_progress_indicator( unsigned statenum, - const wxString& title, - const wxString& firstmsg) const; + const std::string& title, + const std::string& firstmsg) const; ProgresIndicatorPtr create_progress_indicator( unsigned statenum, - const wxString& title) const; + const std::string& title) const; // This is a global progress indicator placeholder. In the Slic3r UI it can // contain the progress indicator on the statusbar. @@ -170,43 +171,6 @@ protected: */ class PrintController: public AppControllerBoilerplate { Print *print_ = nullptr; -protected: - - void make_skirt(); - void make_brim(); - void make_wipe_tower(); - - void make_perimeters(PrintObject *pobj); - void infill(PrintObject *pobj); - void gen_support_material(PrintObject *pobj); - - // Data structure with the png export input data - struct PngExportData { - std::string zippath; // output zip file - unsigned long width_px = 1440; // resolution - rows - unsigned long height_px = 2560; // resolution columns - double width_mm = 68.0, height_mm = 120.0; // dimensions in mm - double exp_time_first_s = 35.0; // first exposure time - double exp_time_s = 8.0; // global exposure time - double corr_x = 1.0; // offsetting in x - double corr_y = 1.0; // offsetting in y - double corr_z = 1.0; // offsetting in y - }; - - // Should display a dialog with the input fields for printing to png - PngExportData query_png_export_data(const DynamicPrintConfig&); - - // The previous export data, to pre-populate the dialog - PngExportData prev_expdata_; - - /** - * @brief Slice one pront object. - * @param pobj The print object. - */ - void slice(PrintObject *pobj); - - void slice(ProgresIndicatorPtr pri); - public: // Must be public for perl to use it @@ -221,15 +185,8 @@ public: return PrintController::Ptr( new PrintController(print) ); } - /** - * @brief Slice the loaded print scene. - */ - void slice(); - - /** - * @brief Slice the print into zipped png files. - */ - void slice_to_png(); + void slice() {} + void slice_to_png() {} const PrintConfig& config() const; }; @@ -253,6 +210,7 @@ public: class AppController: public AppControllerBoilerplate { Model *model_ = nullptr; PrintController::Ptr printctl; + std::atomic arranging_; public: /** @@ -288,7 +246,7 @@ public: * In perl we have a progress indicating status bar on the bottom of the * window which is defined and created in perl. We can pass the ID-s of the * gauge and the statusbar id and make a wrapper implementation of the - * IProgressIndicator interface so we can use this GUI widget from C++. + * ProgressIndicator interface so we can use this GUI widget from C++. * * This function should be called from perl. * diff --git a/xs/src/slic3r/AppControllerWx.cpp b/xs/src/slic3r/AppControllerWx.cpp index bd2859fce..4d67d5f66 100644 --- a/xs/src/slic3r/AppControllerWx.cpp +++ b/xs/src/slic3r/AppControllerWx.cpp @@ -28,16 +28,16 @@ bool AppControllerBoilerplate::supports_asynch() const void AppControllerBoilerplate::process_events() { - wxSafeYield(); + wxYieldIfNeeded(); } AppControllerBoilerplate::PathList AppControllerBoilerplate::query_destination_paths( - const wxString &title, + const std::string &title, const std::string &extensions) const { - wxFileDialog dlg(wxTheApp->GetTopWindow(), title ); + wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) ); dlg.SetWildcard(extensions); dlg.ShowModal(); @@ -53,11 +53,11 @@ AppControllerBoilerplate::query_destination_paths( AppControllerBoilerplate::Path AppControllerBoilerplate::query_destination_path( - const wxString &title, + const std::string &title, const std::string &extensions, const std::string& hint) const { - wxFileDialog dlg(wxTheApp->GetTopWindow(), title ); + wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) ); dlg.SetWildcard(extensions); dlg.SetFilename(hint); @@ -72,8 +72,8 @@ AppControllerBoilerplate::query_destination_path( } bool AppControllerBoilerplate::report_issue(IssueType issuetype, - const wxString &description, - const wxString &brief) + const std::string &description, + const std::string &brief) { auto icon = wxICON_INFORMATION; auto style = wxOK|wxCENTRE; @@ -85,15 +85,15 @@ bool AppControllerBoilerplate::report_issue(IssueType issuetype, case IssueType::FATAL: icon = wxICON_ERROR; } - auto ret = wxMessageBox(description, brief, icon | style); + auto ret = wxMessageBox(_(description), _(brief), icon | style); return ret != wxCANCEL; } bool AppControllerBoilerplate::report_issue( AppControllerBoilerplate::IssueType issuetype, - const wxString &description) + const std::string &description) { - return report_issue(issuetype, description, wxString()); + return report_issue(issuetype, description, std::string()); } wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent); @@ -126,7 +126,9 @@ class GuiProgressIndicator: void _state( unsigned st) { if(!gauge_.IsShown()) gauge_.ShowModal(); Base::state(st); - gauge_.Update(static_cast(st), message_); + if(!gauge_.Update(static_cast(st), message_)) { + cancel(); + } } public: @@ -140,7 +142,8 @@ public: inline GuiProgressIndicator(int range, const wxString& title, const wxString& firstmsg) : gauge_(title, firstmsg, range, wxTheApp->GetTopWindow(), - wxPD_APP_MODAL | wxPD_AUTO_HIDE), + wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT), + message_(firstmsg), range_(range), title_(title) { @@ -152,11 +155,6 @@ public: this, id_); } - virtual void cancel() override { - update(max(), "Abort"); - ProgressIndicator::cancel(); - } - virtual void state(float val) override { state(static_cast(val)); } @@ -171,26 +169,28 @@ public: } else _state(st); } - virtual void message(const wxString & msg) override { - message_ = msg; + virtual void message(const std::string & msg) override { + message_ = _(msg); } - virtual void messageFmt(const wxString& fmt, ...) { + virtual void messageFmt(const std::string& fmt, ...) { va_list arglist; va_start(arglist, fmt); - message_ = wxString::Format(wxString(fmt), arglist); + message_ = wxString::Format(_(fmt), arglist); va_end(arglist); } - virtual void title(const wxString & title) override { - title_ = title; + virtual void title(const std::string & title) override { + title_ = _(title); } }; } AppControllerBoilerplate::ProgresIndicatorPtr AppControllerBoilerplate::create_progress_indicator( - unsigned statenum, const wxString& title, const wxString& firstmsg) const + unsigned statenum, + const std::string& title, + const std::string& firstmsg) const { auto pri = std::make_shared(statenum, title, firstmsg); @@ -203,10 +203,10 @@ AppControllerBoilerplate::create_progress_indicator( } AppControllerBoilerplate::ProgresIndicatorPtr -AppControllerBoilerplate::create_progress_indicator(unsigned statenum, - const wxString &title) const +AppControllerBoilerplate::create_progress_indicator( + unsigned statenum, const std::string &title) const { - return create_progress_indicator(statenum, title, wxString()); + return create_progress_indicator(statenum, title, std::string()); } namespace { @@ -214,7 +214,7 @@ namespace { class Wrapper: public ProgressIndicator, public wxEvtHandler { ProgressStatusBar *sbar_; using Base = ProgressIndicator; - std::string message_; + wxString message_; AppControllerBoilerplate& ctl_; void showProgress(bool show = true) { @@ -271,18 +271,18 @@ public: } } - virtual void message(const wxString & msg) override { - message_ = msg; + virtual void message(const std::string & msg) override { + message_ = _(msg); } - virtual void message_fmt(const wxString& fmt, ...) override { + virtual void message_fmt(const std::string& fmt, ...) override { va_list arglist; va_start(arglist, fmt); - message_ = wxString::Format(fmt, arglist); + message_ = wxString::Format(_(fmt), arglist); va_end(arglist); } - virtual void title(const wxString & /*title*/) override {} + virtual void title(const std::string & /*title*/) override {} virtual void on_cancel(CancelFn fn) override { sbar_->set_cancel_callback(fn); diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index a488bfcde..b9f6a59bc 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -648,7 +648,7 @@ std::vector GLVolumeCollection::load_object( const ModelVolume *model_volume = model_object->volumes[volume_idx]; int extruder_id = -1; - if (!model_volume->modifier) + if (model_volume->is_model_part()) { extruder_id = model_volume->config.has("extruder") ? model_volume->config.option("extruder")->getInt() : 0; if (extruder_id == 0) @@ -661,7 +661,16 @@ std::vector GLVolumeCollection::load_object( volumes_idx.push_back(int(this->volumes.size())); float color[4]; memcpy(color, colors[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3); - color[3] = model_volume->modifier ? 0.5f : 1.f; + if (model_volume->is_support_blocker()) { + color[0] = 1.0f; + color[1] = 0.2f; + color[2] = 0.2f; + } else if (model_volume->is_support_enforcer()) { + color[0] = 0.2f; + color[1] = 0.2f; + color[2] = 1.0f; + } + color[3] = model_volume->is_model_part() ? 1.f : 0.5f; this->volumes.emplace_back(new GLVolume(color)); GLVolume &v = *this->volumes.back(); if (use_VBOs) @@ -675,16 +684,20 @@ std::vector GLVolumeCollection::load_object( v.composite_id = obj_idx * 1000000 + volume_idx * 1000 + instance_idx; v.set_select_group_id(select_by); v.set_drag_group_id(drag_by); - if (!model_volume->modifier) + if (model_volume->is_model_part()) { v.set_convex_hull(model_volume->get_convex_hull()); v.layer_height_texture = layer_height_texture; if (extruder_id != -1) v.extruder_id = extruder_id; } - v.is_modifier = model_volume->modifier; - v.shader_outside_printer_detection_enabled = !model_volume->modifier; + v.is_modifier = ! model_volume->is_model_part(); + v.shader_outside_printer_detection_enabled = model_volume->is_model_part(); +#if ENABLE_MODELINSTANCE_3D_OFFSET + v.set_offset(instance->get_offset()); +#else v.set_offset(Vec3d(instance->offset(0), instance->offset(1), 0.0)); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET v.set_rotation(instance->rotation); v.set_scaling_factor(instance->scaling_factor); } @@ -2164,6 +2177,11 @@ int _3DScene::get_first_volume_id(wxGLCanvas* canvas, int obj_idx) return s_canvas_mgr.get_first_volume_id(canvas, obj_idx); } +int _3DScene::get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx) +{ + return s_canvas_mgr.get_in_object_volume_id(canvas, scene_vol_idx); +} + void _3DScene::reload_scene(wxGLCanvas* canvas, bool force) { s_canvas_mgr.reload_scene(canvas, force); diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index b53fd8a23..f2d1c0786 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -577,6 +577,7 @@ public: static std::vector load_object(wxGLCanvas* canvas, const Model* model, int obj_idx); static int get_first_volume_id(wxGLCanvas* canvas, int obj_idx); + static int get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx); static void reload_scene(wxGLCanvas* canvas, bool force); diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index 0f77b1953..d7307cc32 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -60,6 +60,14 @@ void AppConfig::set_defaults() if (get("remember_output_path").empty()) set("remember_output_path", "1"); + + // Remove legacy window positions/sizes + erase("", "main_frame_maximized"); + erase("", "main_frame_pos"); + erase("", "main_frame_size"); + erase("", "object_settings_maximized"); + erase("", "object_settings_pos"); + erase("", "object_settings_size"); } void AppConfig::load() diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp index b742176ed..5af635a12 100644 --- a/xs/src/slic3r/GUI/AppConfig.hpp +++ b/xs/src/slic3r/GUI/AppConfig.hpp @@ -72,6 +72,14 @@ public: bool has(const std::string &key) const { return this->has("", key); } + void erase(const std::string §ion, const std::string &key) + { + auto it = m_storage.find(section); + if (it != m_storage.end()) { + it->second.erase(key); + } + } + void clear_section(const std::string §ion) { m_storage[section].clear(); } diff --git a/xs/src/slic3r/GUI/ConfigWizard.cpp b/xs/src/slic3r/GUI/ConfigWizard.cpp index 9f736ded8..e784d8525 100644 --- a/xs/src/slic3r/GUI/ConfigWizard.cpp +++ b/xs/src/slic3r/GUI/ConfigWizard.cpp @@ -409,11 +409,10 @@ PageFirmware::PageFirmware(ConfigWizard *parent) : void PageFirmware::apply_custom_config(DynamicPrintConfig &config) { - ConfigOptionEnum opt; - auto sel = gcode_picker->GetSelection(); - if (sel != wxNOT_FOUND && opt.deserialize(gcode_picker->GetString(sel).ToStdString())) { - config.set_key_value("gcode_flavor", &opt); + if (sel >= 0 && sel < gcode_opt.enum_labels.size()) { + auto *opt = new ConfigOptionEnum(static_cast(sel)); + config.set_key_value("gcode_flavor", opt); } } @@ -871,10 +870,11 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) : // If the screen is smaller, resize wizrad to match, which will enable scrollbars. auto wizard_size = GetSize(); unsigned width, height; - GUI::get_current_screen_size(width, height); - wizard_size.SetWidth(std::min(wizard_size.GetWidth(), (int)(width - 2 * DIALOG_MARGIN))); - wizard_size.SetHeight(std::min(wizard_size.GetHeight(), (int)(height - 2 * DIALOG_MARGIN))); - SetMinSize(wizard_size); + if (GUI::get_current_screen_size(this, width, height)) { + wizard_size.SetWidth(std::min(wizard_size.GetWidth(), (int)(width - 2 * DIALOG_MARGIN))); + wizard_size.SetHeight(std::min(wizard_size.GetHeight(), (int)(height - 2 * DIALOG_MARGIN))); + SetMinSize(wizard_size); + } Fit(); p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); }); diff --git a/xs/src/slic3r/GUI/FirmwareDialog.cpp b/xs/src/slic3r/GUI/FirmwareDialog.cpp index d0cd9f8cf..d5ac64d90 100644 --- a/xs/src/slic3r/GUI/FirmwareDialog.cpp +++ b/xs/src/slic3r/GUI/FirmwareDialog.cpp @@ -367,7 +367,7 @@ void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries) auto ports = Utils::scan_serial_ports_extended(); ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { - return port.id_vendor != USB_VID_PRUSA && port.id_product != USB_PID_MMU_BOOT; + return port.id_vendor != USB_VID_PRUSA || port.id_product != USB_PID_MMU_BOOT; }), ports.end()); if (ports.size() == 1) { @@ -390,23 +390,22 @@ void FirmwareDialog::priv::mmu_reboot(const SerialPortInfo &port) void FirmwareDialog::priv::lookup_port_mmu() { + static const auto msg_not_found = + "The Multi Material Control device was not found.\n" + "If the device is connected, please press the Reset button next to the USB connector ..."; + BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ..."; auto ports = Utils::scan_serial_ports_extended(); ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { - return port.id_vendor != USB_VID_PRUSA && + return port.id_vendor != USB_VID_PRUSA || port.id_product != USB_PID_MMU_BOOT && port.id_product != USB_PID_MMU_APP; }), ports.end()); if (ports.size() == 0) { BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ..."; - - queue_status(_(L( - "The Multi Material Control device was not found.\n" - "If the device is connected, please press the Reset button next to the USB connector ..." - ))); - + queue_status(_(L(msg_not_found))); wait_for_mmu_bootloader(30); } else if (ports.size() > 1) { BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found"; @@ -417,6 +416,13 @@ void FirmwareDialog::priv::lookup_port_mmu() BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port; mmu_reboot(ports[0]); wait_for_mmu_bootloader(10); + + if (! port) { + // The device in bootloader mode was not found, inform the user and wait some more... + BOOST_LOG_TRIVIAL(info) << "MMU 2.0 bootloader device not found after reboot, asking the user to press Reset and waiting for the device to show up ..."; + queue_status(_(L(msg_not_found))); + wait_for_mmu_bootloader(30); + } } else { port = ports[0]; } @@ -702,7 +708,8 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : panel->SetSizer(vsizer); auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:"))); - p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY); + p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString, wxFileSelectorPromptStr, + "Hex files (*.hex)|*.hex|All files|*.*"); auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:"))); p->port_picker = new wxComboBox(panel, wxID_ANY); diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp index ae42f014b..cb3250916 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp @@ -1142,8 +1142,10 @@ bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent) if (!gizmo->init()) return false; +#if !ENABLE_MODELINSTANCE_3D_OFFSET // temporary disable z grabber gizmo->disable_grabber(2); +#endif // !ENABLE_MODELINSTANCE_3D_OFFSET m_gizmos.insert(GizmosMap::value_type(Move, gizmo)); @@ -2377,7 +2379,11 @@ void GLCanvas3D::update_gizmos_data() ModelInstance* model_instance = model_object->instances[0]; if (model_instance != nullptr) { +#if ENABLE_MODELINSTANCE_3D_OFFSET + m_gizmos.set_position(model_instance->get_offset()); +#else m_gizmos.set_position(Vec3d(model_instance->offset(0), model_instance->offset(1), 0.0)); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET m_gizmos.set_scale(model_instance->scaling_factor); m_gizmos.set_angle_z(model_instance->rotation); m_gizmos.set_flattening_data(model_object); @@ -2495,6 +2501,11 @@ int GLCanvas3D::get_first_volume_id(int obj_idx) const return -1; } +int GLCanvas3D::get_in_object_volume_id(int scene_vol_idx) const +{ + return ((0 <= scene_vol_idx) && (scene_vol_idx < (int)m_volumes.volumes.size())) ? m_volumes.volumes[scene_vol_idx]->volume_idx() : -1; +} + void GLCanvas3D::reload_scene(bool force) { if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr)) @@ -5361,8 +5372,12 @@ void GLCanvas3D::_on_move(const std::vector& volume_idxs) ModelObject* model_object = m_model->objects[obj_idx]; if (model_object != nullptr) { +#if ENABLE_MODELINSTANCE_3D_OFFSET + model_object->instances[instance_idx]->set_offset(volume->get_offset()); +#else const Vec3d& offset = volume->get_offset(); model_object->instances[instance_idx]->offset = Vec2d(offset(0), offset(1)); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET model_object->invalidate_bounding_box(); update_position_values(); object_moved = true; @@ -5409,6 +5424,7 @@ void GLCanvas3D::_on_select(int volume_idx, int object_idx) } m_on_select_object_callback.call(obj_id, vol_id); + Slic3r::GUI::select_current_volume(obj_id, vol_id); } std::vector GLCanvas3D::_parse_colors(const std::vector& colors) diff --git a/xs/src/slic3r/GUI/GLCanvas3D.hpp b/xs/src/slic3r/GUI/GLCanvas3D.hpp index a200e7fdb..528f73fc1 100644 --- a/xs/src/slic3r/GUI/GLCanvas3D.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3D.hpp @@ -105,7 +105,6 @@ class GLCanvas3D void reset() { first_volumes.clear(); } }; -public: struct Camera { enum EType : unsigned char @@ -441,7 +440,6 @@ public: void render(const GLCanvas3D& canvas) const; }; -private: wxGLCanvas* m_canvas; wxGLContext* m_context; LegendTexture m_legend_texture; @@ -605,6 +603,7 @@ public: std::vector load_object(const Model& model, int obj_idx); int get_first_volume_id(int obj_idx) const; + int get_in_object_volume_id(int scene_vol_idx) const; void reload_scene(bool force); diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp index 3445d4b65..495f49425 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -548,6 +548,12 @@ int GLCanvas3DManager::get_first_volume_id(wxGLCanvas* canvas, int obj_idx) cons return (it != m_canvases.end()) ? it->second->get_first_volume_id(obj_idx) : -1; } +int GLCanvas3DManager::get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx) const +{ + CanvasesMap::const_iterator it = _get_canvas(canvas); + return (it != m_canvases.end()) ? it->second->get_in_object_volume_id(scene_vol_idx) : -1; +} + void GLCanvas3DManager::reload_scene(wxGLCanvas* canvas, bool force) { CanvasesMap::iterator it = _get_canvas(canvas); diff --git a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp index b808c022e..4922b6171 100644 --- a/xs/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/xs/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -138,6 +138,7 @@ public: std::vector load_object(wxGLCanvas* canvas, const Model* model, int obj_idx); int get_first_volume_id(wxGLCanvas* canvas, int obj_idx) const; + int get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx) const; void reload_scene(wxGLCanvas* canvas, bool force); diff --git a/xs/src/slic3r/GUI/GLGizmo.cpp b/xs/src/slic3r/GUI/GLGizmo.cpp index 4aa5ab32f..e23958c1d 100644 --- a/xs/src/slic3r/GUI/GLGizmo.cpp +++ b/xs/src/slic3r/GUI/GLGizmo.cpp @@ -20,90 +20,91 @@ static const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f namespace Slic3r { namespace GUI { - // returns the intersection of the given ray with the plane parallel to plane XY and passing through the given center - // coordinates are local to the plane - Vec3d intersection_on_plane_xy(const Linef3& ray, const Vec3d& center) +// returns the intersection of the given ray with the plane parallel to plane XY and passing through the given center +// coordinates are local to the plane +Vec3d intersection_on_plane_xy(const Linef3& ray, const Vec3d& center) +{ + Transform3d m = Transform3d::Identity(); + m.translate(-center); + Vec2d mouse_pos_2d = to_2d(transform(ray, m).intersect_plane(0.0)); + return Vec3d(mouse_pos_2d(0), mouse_pos_2d(1), 0.0); +} + +// returns the intersection of the given ray with the plane parallel to plane XZ and passing through the given center +// coordinates are local to the plane +Vec3d intersection_on_plane_xz(const Linef3& ray, const Vec3d& center) +{ + Transform3d m = Transform3d::Identity(); + m.rotate(Eigen::AngleAxisd(-0.5 * (double)PI, Vec3d::UnitX())); + m.translate(-center); + Vec2d mouse_pos_2d = to_2d(transform(ray, m).intersect_plane(0.0)); + return Vec3d(mouse_pos_2d(0), 0.0, mouse_pos_2d(1)); +} + +// returns the intersection of the given ray with the plane parallel to plane YZ and passing through the given center +// coordinates are local to the plane +Vec3d intersection_on_plane_yz(const Linef3& ray, const Vec3d& center) +{ + Transform3d m = Transform3d::Identity(); + m.rotate(Eigen::AngleAxisd(-0.5f * (double)PI, Vec3d::UnitY())); + m.translate(-center); + Vec2d mouse_pos_2d = to_2d(transform(ray, m).intersect_plane(0.0)); + + return Vec3d(0.0, mouse_pos_2d(1), -mouse_pos_2d(0)); +} + +// return an index: +// 0 for plane XY +// 1 for plane XZ +// 2 for plane YZ +// which indicates which plane is best suited for intersecting the given unit vector +// giving precedence to the plane with the given index +unsigned int select_best_plane(const Vec3d& unit_vector, unsigned int preferred_plane) +{ + unsigned int ret = preferred_plane; + + // 1st checks if the given vector is not parallel to the given preferred plane + double dot_to_normal = 0.0; + switch (ret) { - Transform3d m = Transform3d::Identity(); - m.translate(-center); - Vec2d mouse_pos_2d = to_2d(transform(ray, m).intersect_plane(0.0)); - return Vec3d(mouse_pos_2d(0), mouse_pos_2d(1), 0.0); + case 0: // plane xy + { + dot_to_normal = std::abs(unit_vector.dot(Vec3d::UnitZ())); + break; + } + case 1: // plane xz + { + dot_to_normal = std::abs(unit_vector.dot(-Vec3d::UnitY())); + break; + } + case 2: // plane yz + { + dot_to_normal = std::abs(unit_vector.dot(Vec3d::UnitX())); + break; + } + default: + { + break; + } } - // returns the intersection of the given ray with the plane parallel to plane XZ and passing through the given center - // coordinates are local to the plane - Vec3d intersection_on_plane_xz(const Linef3& ray, const Vec3d& center) + // if almost parallel, select the plane whose normal direction is closest to the given vector direction, + // otherwise return the given preferred plane index + if (dot_to_normal < 0.1) { - Transform3d m = Transform3d::Identity(); - m.rotate(Eigen::AngleAxisd(-0.5 * (double)PI, Vec3d::UnitX())); - m.translate(-center); - Vec2d mouse_pos_2d = to_2d(transform(ray, m).intersect_plane(0.0)); - return Vec3d(mouse_pos_2d(0), 0.0, mouse_pos_2d(1)); + typedef std::map ProjsMap; + ProjsMap projs_map; + projs_map.insert(ProjsMap::value_type(std::abs(unit_vector.dot(Vec3d::UnitZ())), 0)); // plane xy + projs_map.insert(ProjsMap::value_type(std::abs(unit_vector.dot(-Vec3d::UnitY())), 1)); // plane xz + projs_map.insert(ProjsMap::value_type(std::abs(unit_vector.dot(Vec3d::UnitX())), 2)); // plane yz + ret = projs_map.rbegin()->second; } - // returns the intersection of the given ray with the plane parallel to plane YZ and passing through the given center - // coordinates are local to the plane - Vec3d intersection_on_plane_yz(const Linef3& ray, const Vec3d& center) - { - Transform3d m = Transform3d::Identity(); - m.rotate(Eigen::AngleAxisd(-0.5f * (double)PI, Vec3d::UnitY())); - m.translate(-center); - Vec2d mouse_pos_2d = to_2d(transform(ray, m).intersect_plane(0.0)); - - return Vec3d(0.0, mouse_pos_2d(1), -mouse_pos_2d(0)); - } - - // return an index: - // 0 for plane XY - // 1 for plane XZ - // 2 for plane YZ - // which indicates which plane is best suited for intersecting the given unit vector - // giving precedence to the plane with the given index - unsigned int select_best_plane(const Vec3d& unit_vector, unsigned int preferred_plane) - { - unsigned int ret = preferred_plane; - - // 1st checks if the given vector is not parallel to the given preferred plane - double dot_to_normal = 0.0; - switch (ret) - { - case 0: // plane xy - { - dot_to_normal = std::abs(unit_vector.dot(Vec3d::UnitZ())); - break; - } - case 1: // plane xz - { - dot_to_normal = std::abs(unit_vector.dot(-Vec3d::UnitY())); - break; - } - case 2: // plane yz - { - dot_to_normal = std::abs(unit_vector.dot(Vec3d::UnitX())); - break; - } - default: - { - break; - } - } - - // if almost parallel, select the plane whose normal direction is closest to the given vector direction, - // otherwise return the given preferred plane index - if (dot_to_normal < 0.1) - { - typedef std::map ProjsMap; - ProjsMap projs_map; - projs_map.insert(ProjsMap::value_type(std::abs(unit_vector.dot(Vec3d::UnitZ())), 0)); // plane xy - projs_map.insert(ProjsMap::value_type(std::abs(unit_vector.dot(-Vec3d::UnitY())), 1)); // plane xz - projs_map.insert(ProjsMap::value_type(std::abs(unit_vector.dot(Vec3d::UnitX())), 2)); // plane yz - ret = projs_map.rbegin()->second; - } - - return ret; - } + return ret; +} - const float GLGizmoBase::Grabber::HalfSize = 2.0f; +const float GLGizmoBase::Grabber::SizeFactor = 0.025f; +const float GLGizmoBase::Grabber::MinHalfSize = 1.5f; const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f; GLGizmoBase::Grabber::Grabber() @@ -117,7 +118,7 @@ GLGizmoBase::Grabber::Grabber() color[2] = 1.0f; } -void GLGizmoBase::Grabber::render(bool hover) const +void GLGizmoBase::Grabber::render(bool hover, const BoundingBoxf3& box) const { float render_color[3]; if (hover) @@ -129,12 +130,15 @@ void GLGizmoBase::Grabber::render(bool hover) const else ::memcpy((void*)render_color, (const void*)color, 3 * sizeof(float)); - render(render_color, true); + render(box, render_color, true); } -void GLGizmoBase::Grabber::render(const float* render_color, bool use_lighting) const +void GLGizmoBase::Grabber::render(const BoundingBoxf3& box, const float* render_color, bool use_lighting) const { - float half_size = dragging ? HalfSize * DraggingScaleFactor : HalfSize; + float max_size = (float)box.max_size(); + float half_size = dragging ? max_size * SizeFactor * DraggingScaleFactor : max_size * SizeFactor; + half_size = std::max(half_size, MinHalfSize); + if (use_lighting) ::glEnable(GL_LIGHTING); @@ -291,16 +295,16 @@ float GLGizmoBase::picking_color_component(unsigned int id) const return (float)color / 255.0f; } -void GLGizmoBase::render_grabbers() const +void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const { for (int i = 0; i < (int)m_grabbers.size(); ++i) { if (m_grabbers[i].enabled) - m_grabbers[i].render(m_hover_id == i); + m_grabbers[i].render((m_hover_id == i), box); } } -void GLGizmoBase::render_grabbers_for_picking() const +void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const { for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) { @@ -309,7 +313,7 @@ void GLGizmoBase::render_grabbers_for_picking() const m_grabbers[i].color[0] = 1.0f; m_grabbers[i].color[1] = 1.0f; m_grabbers[i].color[2] = picking_color_component(i); - m_grabbers[i].render_for_picking(); + m_grabbers[i].render_for_picking(box); } } } @@ -440,7 +444,7 @@ void GLGizmoRotate::on_render(const BoundingBoxf3& box) const if (m_hover_id != -1) render_angle(); - render_grabber(); + render_grabber(box); ::glPopMatrix(); } @@ -452,7 +456,7 @@ void GLGizmoRotate::on_render_for_picking(const BoundingBoxf3& box) const ::glPushMatrix(); transform_to_local(); - render_grabbers_for_picking(); + render_grabbers_for_picking(box); ::glPopMatrix(); } @@ -544,7 +548,7 @@ void GLGizmoRotate::render_angle() const ::glEnd(); } -void GLGizmoRotate::render_grabber() const +void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const { double grabber_radius = (double)(m_radius + GrabberOffset); m_grabbers[0].center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0); @@ -558,7 +562,7 @@ void GLGizmoRotate::render_grabber() const ::glEnd(); ::memcpy((void*)m_grabbers[0].color, (const void*)m_highlight_color, 3 * sizeof(float)); - render_grabbers(); + render_grabbers(box); } void GLGizmoRotate::transform_to_local() const @@ -689,6 +693,7 @@ void GLGizmoRotate3D::on_render(const BoundingBoxf3& box) const } const float GLGizmoScale3D::Offset = 5.0f; +const Vec3d GLGizmoScale3D::OffsetVec = (double)GLGizmoScale3D::Offset * Vec3d::Ones(); GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent) : GLGizmoBase(parent) @@ -738,7 +743,7 @@ void GLGizmoScale3D::on_start_dragging(const BoundingBoxf3& box) { m_starting_drag_position = m_grabbers[m_hover_id].center; m_show_starting_box = true; - m_starting_box = box; + m_starting_box = BoundingBoxf3(box.min - OffsetVec, box.max + OffsetVec); } } @@ -772,9 +777,7 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const ::glEnable(GL_DEPTH_TEST); - Vec3d offset_vec = (double)Offset * Vec3d::Ones(); - - m_box = BoundingBoxf3(box.min - offset_vec, box.max + offset_vec); + m_box = BoundingBoxf3(box.min - OffsetVec, box.max + OffsetVec); const Vec3d& center = m_box.center(); // x axis @@ -829,7 +832,7 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const render_grabbers_connection(4, 5); } // draw grabbers - render_grabbers(); + render_grabbers(m_box); } else if ((m_hover_id == 0) || (m_hover_id == 1)) { @@ -846,8 +849,8 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const ::glColor3fv(m_grabbers[0].color); render_grabbers_connection(0, 1); // draw grabbers - m_grabbers[0].render(true); - m_grabbers[1].render(true); + m_grabbers[0].render(true, m_box); + m_grabbers[1].render(true, m_box); } else if ((m_hover_id == 2) || (m_hover_id == 3)) { @@ -864,8 +867,8 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const ::glColor3fv(m_grabbers[2].color); render_grabbers_connection(2, 3); // draw grabbers - m_grabbers[2].render(true); - m_grabbers[3].render(true); + m_grabbers[2].render(true, m_box); + m_grabbers[3].render(true, m_box); } else if ((m_hover_id == 4) || (m_hover_id == 5)) { @@ -882,8 +885,8 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const ::glColor3fv(m_grabbers[4].color); render_grabbers_connection(4, 5); // draw grabbers - m_grabbers[4].render(true); - m_grabbers[5].render(true); + m_grabbers[4].render(true, m_box); + m_grabbers[5].render(true, m_box); } else if (m_hover_id >= 6) { @@ -899,7 +902,7 @@ void GLGizmoScale3D::on_render(const BoundingBoxf3& box) const // draw grabbers for (int i = 6; i < 10; ++i) { - m_grabbers[i].render(true); + m_grabbers[i].render(true, m_box); } } } @@ -908,7 +911,7 @@ void GLGizmoScale3D::on_render_for_picking(const BoundingBoxf3& box) const { ::glDisable(GL_DEPTH_TEST); - render_grabbers_for_picking(); + render_grabbers_for_picking(box); } void GLGizmoScale3D::render_box(const BoundingBoxf3& box) const @@ -1029,6 +1032,7 @@ GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent) , m_position(Vec3d::Zero()) , m_starting_drag_position(Vec3d::Zero()) , m_starting_box_center(Vec3d::Zero()) + , m_starting_box_bottom_center(Vec3d::Zero()) { } @@ -1062,17 +1066,19 @@ void GLGizmoMove3D::on_start_dragging(const BoundingBoxf3& box) { m_starting_drag_position = m_grabbers[m_hover_id].center; m_starting_box_center = box.center(); + m_starting_box_bottom_center = box.center(); + m_starting_box_bottom_center(2) = box.min(2); } } void GLGizmoMove3D::on_update(const Linef3& mouse_ray) { if (m_hover_id == 0) - m_position(0) = 2.0 * m_starting_box_center(0) + calc_displacement(1, mouse_ray) - m_starting_drag_position(0); + m_position(0) = 2.0 * m_starting_box_center(0) + calc_projection(X, 1, mouse_ray) - m_starting_drag_position(0); else if (m_hover_id == 1) - m_position(1) = 2.0 * m_starting_box_center(1) + calc_displacement(2, mouse_ray) - m_starting_drag_position(1); + m_position(1) = 2.0 * m_starting_box_center(1) + calc_projection(Y, 2, mouse_ray) - m_starting_drag_position(1); else if (m_hover_id == 2) - m_position(2) = 2.0 * m_starting_box_center(2) + calc_displacement(1, mouse_ray) - m_starting_drag_position(2); + m_position(2) = 2.0 * m_starting_box_bottom_center(2) + calc_projection(Z, 1, mouse_ray) - m_starting_drag_position(2); } void GLGizmoMove3D::on_render(const BoundingBoxf3& box) const @@ -1118,7 +1124,7 @@ void GLGizmoMove3D::on_render(const BoundingBoxf3& box) const } // draw grabbers - render_grabbers(); + render_grabbers(box); } else { @@ -1130,7 +1136,7 @@ void GLGizmoMove3D::on_render(const BoundingBoxf3& box) const ::glEnd(); // draw grabber - m_grabbers[m_hover_id].render(true); + m_grabbers[m_hover_id].render(true, box); } } @@ -1138,43 +1144,43 @@ void GLGizmoMove3D::on_render_for_picking(const BoundingBoxf3& box) const { ::glDisable(GL_DEPTH_TEST); - render_grabbers_for_picking(); + render_grabbers_for_picking(box); } -double GLGizmoMove3D::calc_displacement(unsigned int preferred_plane_id, const Linef3& mouse_ray) const +double GLGizmoMove3D::calc_projection(Axis axis, unsigned int preferred_plane_id, const Linef3& mouse_ray) const { - double displacement = 0.0; + double projection = 0.0; - Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; + Vec3d starting_vec = (axis == Z) ? m_starting_drag_position - m_starting_box_bottom_center : m_starting_drag_position - m_starting_box_center; double len_starting_vec = starting_vec.norm(); if (len_starting_vec == 0.0) - return displacement; + return projection; Vec3d starting_vec_dir = starting_vec.normalized(); Vec3d mouse_dir = mouse_ray.unit_vector(); unsigned int plane_id = select_best_plane(mouse_dir, preferred_plane_id); - switch (plane_id) + switch (plane_id) { case 0: { - displacement = starting_vec_dir.dot(intersection_on_plane_xy(mouse_ray, m_starting_box_center)); + projection = starting_vec_dir.dot(intersection_on_plane_xy(mouse_ray, (axis == Z) ? m_starting_box_bottom_center : m_starting_box_center)); break; } case 1: { - displacement = starting_vec_dir.dot(intersection_on_plane_xz(mouse_ray, m_starting_box_center)); + projection = starting_vec_dir.dot(intersection_on_plane_xz(mouse_ray, (axis == Z) ? m_starting_box_bottom_center : m_starting_box_center)); break; } case 2: { - displacement = starting_vec_dir.dot(intersection_on_plane_yz(mouse_ray, m_starting_box_center)); + projection = starting_vec_dir.dot(intersection_on_plane_yz(mouse_ray, (axis == Z) ? m_starting_box_bottom_center : m_starting_box_center)); break; } } - return displacement; + return projection; } GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent) @@ -1230,10 +1236,19 @@ void GLGizmoFlatten::on_render(const BoundingBoxf3& box) const else ::glColor4f(0.9f, 0.9f, 0.9f, 0.5f); +#if ENABLE_MODELINSTANCE_3D_OFFSET + for (Vec3d offset : m_instances_positions) { + offset += dragged_offset; +#else for (Vec2d offset : m_instances_positions) { offset += to_2d(dragged_offset); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET ::glPushMatrix(); +#if ENABLE_MODELINSTANCE_3D_OFFSET + ::glTranslated(offset(0), offset(1), offset(2)); +#else ::glTranslatef((GLfloat)offset(0), (GLfloat)offset(1), 0.0f); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET ::glBegin(GL_POLYGON); for (const Vec3d& vertex : m_planes[i].vertices) ::glVertex3f((GLfloat)vertex(0), (GLfloat)vertex(1), (GLfloat)vertex(2)); @@ -1252,9 +1267,17 @@ void GLGizmoFlatten::on_render_for_picking(const BoundingBoxf3& box) const for (unsigned int i = 0; i < m_planes.size(); ++i) { ::glColor3f(1.0f, 1.0f, picking_color_component(i)); +#if ENABLE_MODELINSTANCE_3D_OFFSET + for (const Vec3d& offset : m_instances_positions) { +#else for (const Vec2d& offset : m_instances_positions) { +#endif // ENABLE_MODELINSTANCE_3D_OFFSET ::glPushMatrix(); +#if ENABLE_MODELINSTANCE_3D_OFFSET + ::glTranslated(offset(0), offset(1), offset(2)); +#else ::glTranslatef((GLfloat)offset(0), (GLfloat)offset(1), 0.0f); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET ::glBegin(GL_POLYGON); for (const Vec3d& vertex : m_planes[i].vertices) ::glVertex3f((GLfloat)vertex(0), (GLfloat)vertex(1), (GLfloat)vertex(2)); @@ -1272,7 +1295,11 @@ void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) if (m_model_object && !m_model_object->instances.empty()) { m_instances_positions.clear(); for (const auto* instance : m_model_object->instances) +#if ENABLE_MODELINSTANCE_3D_OFFSET + m_instances_positions.emplace_back(instance->get_offset()); +#else m_instances_positions.emplace_back(instance->offset); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET } if (is_plane_update_necessary()) diff --git a/xs/src/slic3r/GUI/GLGizmo.hpp b/xs/src/slic3r/GUI/GLGizmo.hpp index 8760b1c7d..2430b5af5 100644 --- a/xs/src/slic3r/GUI/GLGizmo.hpp +++ b/xs/src/slic3r/GUI/GLGizmo.hpp @@ -22,7 +22,8 @@ class GLGizmoBase protected: struct Grabber { - static const float HalfSize; + static const float SizeFactor; + static const float MinHalfSize; static const float DraggingScaleFactor; Vec3d center; @@ -33,11 +34,11 @@ protected: Grabber(); - void render(bool hover) const; - void render_for_picking() const { render(color, false); } + void render(bool hover, const BoundingBoxf3& box) const; + void render_for_picking(const BoundingBoxf3& box) const { render(box, color, false); } private: - void render(const float* render_color, bool use_lighting) const; + void render(const BoundingBoxf3& box, const float* render_color, bool use_lighting) const; void render_face(float half_size) const; }; @@ -109,8 +110,8 @@ protected: virtual void on_render_for_picking(const BoundingBoxf3& box) const = 0; float picking_color_component(unsigned int id) const; - void render_grabbers() const; - void render_grabbers_for_picking() const; + void render_grabbers(const BoundingBoxf3& box) const; + void render_grabbers_for_picking(const BoundingBoxf3& box) const; void set_tooltip(const std::string& tooltip) const; std::string format(float value, unsigned int decimals) const; @@ -163,7 +164,7 @@ private: void render_snap_radii() const; void render_reference_radius() const; void render_angle() const; - void render_grabber() const; + void render_grabber(const BoundingBoxf3& box) const; void transform_to_local() const; // returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate @@ -234,6 +235,7 @@ protected: class GLGizmoScale3D : public GLGizmoBase { static const float Offset; + static const Vec3d OffsetVec; mutable BoundingBoxf3 m_box; @@ -285,6 +287,7 @@ class GLGizmoMove3D : public GLGizmoBase Vec3d m_position; Vec3d m_starting_drag_position; Vec3d m_starting_box_center; + Vec3d m_starting_box_bottom_center; public: explicit GLGizmoMove3D(GLCanvas3D& parent); @@ -300,7 +303,7 @@ protected: virtual void on_render_for_picking(const BoundingBoxf3& box) const; private: - double calc_displacement(unsigned int preferred_plane_id, const Linef3& mouse_ray) const; + double calc_projection(Axis axis, unsigned int preferred_plane_id, const Linef3& mouse_ray) const; }; class GLGizmoFlatten : public GLGizmoBase @@ -326,7 +329,11 @@ private: SourceDataSummary m_source_data; std::vector m_planes; +#if ENABLE_MODELINSTANCE_3D_OFFSET + Pointf3s m_instances_positions; +#else std::vector m_instances_positions; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET Vec3d m_starting_center; const ModelObject* m_model_object = nullptr; diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 7ce858aec..decdb5691 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #if __APPLE__ #import @@ -121,7 +122,6 @@ wxPanel *g_wxPlater = nullptr; AppConfig *g_AppConfig = nullptr; PresetBundle *g_PresetBundle= nullptr; PresetUpdater *g_PresetUpdater = nullptr; -_3DScene *g_3DScene = nullptr; wxColour g_color_label_modified; wxColour g_color_label_sys; wxColour g_color_label_default; @@ -141,14 +141,9 @@ wxButton* g_wiping_dialog_button = nullptr; //showed/hided controls according to the view mode wxWindow *g_right_panel = nullptr; wxBoxSizer *g_frequently_changed_parameters_sizer = nullptr; -wxBoxSizer *g_expert_mode_part_sizer = nullptr; -wxBoxSizer *g_scrolled_window_sizer = nullptr; +wxBoxSizer *g_info_sizer = nullptr; wxBoxSizer *g_object_list_sizer = nullptr; -wxButton *g_btn_export_gcode = nullptr; -wxButton *g_btn_export_stl = nullptr; -wxButton *g_btn_reslice = nullptr; -wxButton *g_btn_print = nullptr; -wxButton *g_btn_send_gcode = nullptr; +std::vector g_buttons; wxStaticBitmap *g_manifold_warning_icon = nullptr; bool g_show_print_info = false; bool g_show_manifold_warning_icon = false; @@ -241,27 +236,36 @@ void set_preset_updater(PresetUpdater *updater) g_PresetUpdater = updater; } -void set_3DScene(_3DScene *scene) +enum ActionButtons { - g_3DScene = scene; -} + abExportGCode, + abReslice, + abPrint, + abSendGCode, +}; -void set_objects_from_perl( wxWindow* parent, wxBoxSizer *frequently_changed_parameters_sizer, - wxBoxSizer *expert_mode_part_sizer, wxBoxSizer *scrolled_window_sizer, +void set_objects_from_perl( wxWindow* parent, + wxBoxSizer *frequently_changed_parameters_sizer, + wxBoxSizer *info_sizer, wxButton *btn_export_gcode, - wxButton *btn_export_stl, wxButton *btn_reslice, - wxButton *btn_print, wxButton *btn_send_gcode, + wxButton *btn_reslice, + wxButton *btn_print, + wxButton *btn_send_gcode, wxStaticBitmap *manifold_warning_icon) { - g_right_panel = parent; + g_right_panel = parent->GetParent(); g_frequently_changed_parameters_sizer = frequently_changed_parameters_sizer; - g_expert_mode_part_sizer = expert_mode_part_sizer; - g_scrolled_window_sizer = scrolled_window_sizer; - g_btn_export_gcode = btn_export_gcode; - g_btn_export_stl = btn_export_stl; - g_btn_reslice = btn_reslice; - g_btn_print = btn_print; - g_btn_send_gcode = btn_send_gcode; + g_info_sizer = info_sizer; + + g_buttons.push_back(btn_export_gcode); + g_buttons.push_back(btn_reslice); + g_buttons.push_back(btn_print); + g_buttons.push_back(btn_send_gcode); + + // Update font style for buttons + for (auto btn : g_buttons) + btn->SetFont(bold_font()); + g_manifold_warning_icon = manifold_warning_icon; } @@ -273,6 +277,15 @@ void set_show_print_info(bool show) void set_show_manifold_warning_icon(bool show) { g_show_manifold_warning_icon = show; + if (!g_manifold_warning_icon) + return; + + // update manifold_warning_icon showing + if (show && !g_info_sizer->IsShown(static_cast(0))) + g_show_manifold_warning_icon = false; + + g_manifold_warning_icon->Show(g_show_manifold_warning_icon); + g_manifold_warning_icon->GetParent()->Layout(); } void set_objects_list_sizer(wxBoxSizer *objects_list_sizer){ @@ -963,12 +976,11 @@ wxString from_u8(const std::string &str) return wxString::FromUTF8(str.c_str()); } -void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, - Model &model, - int event_object_selection_changed, - int event_object_settings_changed, - int event_remove_object, - int event_update_scene) +void set_model_events_from_perl(Model &model, + int event_object_selection_changed, + int event_object_settings_changed, + int event_remove_object, + int event_update_scene) { set_event_object_selection_changed(event_object_selection_changed); set_event_object_settings_changed(event_object_settings_changed); @@ -1105,7 +1117,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl }; optgroup->append_line(line); - sizer->Add(optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 2); + sizer->Add(optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT, 2); m_optgroups.push_back(optgroup);// ogFrequentlyChangingParameters @@ -1132,24 +1144,23 @@ void show_frequently_changed_parameters(bool show) void show_buttons(bool show) { - g_btn_export_stl->Show(show); - g_btn_reslice->Show(show); + g_buttons[abReslice]->Show(show); for (size_t i = 0; i < g_wxTabPanel->GetPageCount(); ++i) { TabPrinter *tab = dynamic_cast(g_wxTabPanel->GetPage(i)); if (!tab) continue; if (g_PresetBundle->printers.get_selected_preset().printer_technology() == ptFFF) { - g_btn_print->Show(show && !tab->m_config->opt_string("serial_port").empty()); - g_btn_send_gcode->Show(show && !tab->m_config->opt_string("print_host").empty()); + g_buttons[abPrint]->Show(show && !tab->m_config->opt_string("serial_port").empty()); + g_buttons[abSendGCode]->Show(show && !tab->m_config->opt_string("print_host").empty()); } break; } } -void show_info_sizer(bool show) +void show_info_sizer(const bool show) { - g_scrolled_window_sizer->Show(static_cast(0), show); - g_scrolled_window_sizer->Show(1, show && g_show_print_info); + g_info_sizer->Show(static_cast(0), show); + g_info_sizer->Show(1, show && g_show_print_info); g_manifold_warning_icon->Show(show && g_show_manifold_warning_icon); } @@ -1162,31 +1173,22 @@ void show_object_name(bool show) void update_mode() { - wxWindowUpdateLocker noUpdates(g_right_panel); - - // TODO There is a not the best place of it! - //*** Update style of the "Export G-code" button**** - if (g_btn_export_gcode->GetFont() != bold_font()){ - g_btn_export_gcode->SetBackgroundColour(wxColour(252, 77, 1)); - g_btn_export_gcode->SetFont(bold_font()); - } - // *********************************** + wxWindowUpdateLocker noUpdates(g_right_panel->GetParent()); ConfigMenuIDs mode = get_view_mode(); -// show_frequently_changed_parameters(mode >= ConfigMenuModeRegular); -// g_expert_mode_part_sizer->Show(mode == ConfigMenuModeExpert); g_object_list_sizer->Show(mode == ConfigMenuModeExpert); show_info_sizer(mode == ConfigMenuModeExpert); show_buttons(mode == ConfigMenuModeExpert); show_object_name(mode == ConfigMenuModeSimple); + show_manipulation_sizer(mode == ConfigMenuModeSimple); // TODO There is a not the best place of it! // *** Update showing of the collpane_settings // show_collpane_settings(mode == ConfigMenuModeExpert); // ************************* g_right_panel->Layout(); - g_right_panel->GetParent()->GetParent()->Layout(); + g_right_panel->GetParent()->Layout(); } bool is_expert_mode(){ @@ -1254,12 +1256,77 @@ int get_export_option(wxFileDialog* dlg) } -void get_current_screen_size(unsigned &width, unsigned &height) +bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height) { - wxDisplay display(wxDisplay::GetFromWindow(g_wxMainFrame)); + const auto idx = wxDisplay::GetFromWindow(window); + if (idx == wxNOT_FOUND) { + return false; + } + + wxDisplay display(idx); const auto disp_size = display.GetClientArea(); width = disp_size.GetWidth(); height = disp_size.GetHeight(); + + return true; +} + +void save_window_size(wxTopLevelWindow *window, const std::string &name) +{ + const wxSize size = window->GetSize(); + const wxPoint pos = window->GetPosition(); + const auto maximized = window->IsMaximized() ? "1" : "0"; + + g_AppConfig->set((boost::format("window_%1%_size") % name).str(), (boost::format("%1%;%2%") % size.GetWidth() % size.GetHeight()).str()); + g_AppConfig->set((boost::format("window_%1%_maximized") % name).str(), maximized); +} + +void restore_window_size(wxTopLevelWindow *window, const std::string &name) +{ + // XXX: This still doesn't behave nicely in some situations (mostly on Linux). + // The problem is that it's hard to obtain window position with respect to screen geometry reliably + // from wxWidgets. Sometimes wxWidgets claim a window is located on a different screen than on which + // it's actually visible. I suspect this has something to do with window initialization (maybe we + // restore window geometry too early), but haven't yet found a workaround. + + const auto display_idx = wxDisplay::GetFromWindow(window); + if (display_idx == wxNOT_FOUND) { return; } + + const auto display = wxDisplay(display_idx).GetClientArea(); + std::vector pair; + + try { + const auto key_size = (boost::format("window_%1%_size") % name).str(); + if (g_AppConfig->has(key_size)) { + if (unescape_strings_cstyle(g_AppConfig->get(key_size), pair) && pair.size() == 2) { + auto width = boost::lexical_cast(pair[0]); + auto height = boost::lexical_cast(pair[1]); + + window->SetSize(width, height); + } + } + } catch(const boost::bad_lexical_cast &) {} + + // Maximizing should be the last thing to do. + // This ensure the size and position are sane when the user un-maximizes the window. + const auto key_maximized = (boost::format("window_%1%_maximized") % name).str(); + if (g_AppConfig->get(key_maximized) == "1") { + window->Maximize(true); + } +} + +void enable_action_buttons(bool enable) +{ + if (g_buttons.empty()) + return; + + // Update background colour for buttons + const wxColour bgrd_color = enable ? wxColour(224, 224, 224/*255, 96, 0*/) : wxColour(204, 204, 204); + + for (auto btn : g_buttons) { + btn->Enable(enable); + btn->SetBackgroundColour(bgrd_color); + } } void about() diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 05ed580a3..02327b325 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -27,6 +27,7 @@ class wxButton; class wxFileDialog; class wxStaticBitmap; class wxFont; +class wxTopLevelWindow; namespace Slic3r { @@ -38,7 +39,6 @@ class AppConfig; class PresetUpdater; class DynamicPrintConfig; class TabIface; -class _3DScene; #define _(s) Slic3r::GUI::I18N::translate((s)) @@ -106,13 +106,10 @@ void set_plater(wxPanel *plater); void set_app_config(AppConfig *app_config); void set_preset_bundle(PresetBundle *preset_bundle); void set_preset_updater(PresetUpdater *updater); -void set_3DScene(_3DScene *scene); void set_objects_from_perl( wxWindow* parent, wxBoxSizer *frequently_changed_parameters_sizer, - wxBoxSizer *expert_mode_part_sizer, - wxBoxSizer *scrolled_window_sizer, + wxBoxSizer *info_sizer, wxButton *btn_export_gcode, - wxButton *btn_export_stl, wxButton *btn_reslice, wxButton *btn_print, wxButton *btn_send_gcode, @@ -196,6 +193,8 @@ bool select_language(wxArrayString & names, wxArrayLong & identifiers); // update right panel of the Plater according to view mode void update_mode(); +void show_info_sizer(const bool show); + std::vector& get_tabs_list(); bool checked_tab(Tab* tab); void delete_tab_from_list(Tab* tab); @@ -214,12 +213,11 @@ wxString L_str(const std::string &str); // Return wxString from std::string in UTF8 wxString from_u8(const std::string &str); -void add_expert_mode_part( wxWindow* parent, wxBoxSizer* sizer, - Model &model, - int event_object_selection_changed, - int event_object_settings_changed, - int event_remove_object, - int event_update_scene); +void set_model_events_from_perl(Model &model, + int event_object_selection_changed, + int event_object_settings_changed, + int event_remove_object, + int event_update_scene); void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer); // Update view mode according to selected menu void update_mode(); @@ -236,7 +234,15 @@ void add_export_option(wxFileDialog* dlg, const std::string& format); int get_export_option(wxFileDialog* dlg); // Returns the dimensions of the screen on which the main frame is displayed -void get_current_screen_size(unsigned &width, unsigned &height); +bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height); + +// Save window size and maximized status into AppConfig +void save_window_size(wxTopLevelWindow *window, const std::string &name); +// Restore the above +void restore_window_size(wxTopLevelWindow *window, const std::string &name); + +// Update buttons view according to enable/disable +void enable_action_buttons(bool enable); // Display an About dialog extern void about(); diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp index 0c7b7a5d8..ae34359ce 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.cpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.cpp @@ -13,6 +13,9 @@ #include "Geometry.hpp" #include "slic3r/Utils/FixModelByWin10.hpp" +#include +#include "3DScene.hpp" + namespace Slic3r { namespace GUI @@ -23,10 +26,12 @@ wxSizer *m_sizer_object_movers = nullptr; wxDataViewCtrl *m_objects_ctrl = nullptr; PrusaObjectDataViewModel *m_objects_model = nullptr; wxCollapsiblePane *m_collpane_settings = nullptr; +PrusaDoubleSlider *m_slider = nullptr; +wxGLCanvas *m_preview_canvas = nullptr; -wxIcon m_icon_modifiermesh; -wxIcon m_icon_solidmesh; -wxIcon m_icon_manifold_warning; +wxBitmap m_icon_modifiermesh; +wxBitmap m_icon_solidmesh; +wxBitmap m_icon_manifold_warning; wxBitmap m_bmp_cog; wxBitmap m_bmp_split; @@ -64,8 +69,6 @@ bool m_part_settings_changed = false; wxString g_selected_extruder = ""; #endif //__WXOSX__ -// typedef std::map t_category_icon; -typedef std::map t_category_icon; inline t_category_icon& get_category_icon() { static t_category_icon CATEGORY_ICON; if (CATEGORY_ICON.empty()){ @@ -138,11 +141,11 @@ void set_objects_from_model(Model &model) { } void init_mesh_icons(){ - m_icon_modifiermesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("lambda.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); - m_icon_solidmesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); + m_icon_modifiermesh = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("lambda.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); + m_icon_solidmesh = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); // init icon for manifold warning - m_icon_manifold_warning = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("exclamation_mark_.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); + m_icon_manifold_warning = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("exclamation_mark_.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG); // init bitmap for "Split to sub-objects" context menu m_bmp_split = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("split.png")), wxBITMAP_TYPE_PNG); @@ -233,7 +236,7 @@ wxDataViewColumn* object_ctrl_create_extruder_column(int extruders_count) void create_objects_ctrl(wxWindow* win, wxBoxSizer*& objects_sz) { m_objects_ctrl = new wxDataViewCtrl(win, wxID_ANY, wxDefaultPosition, wxDefaultSize); - m_objects_ctrl->SetInitialSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects + m_objects_ctrl->SetMinSize(wxSize(-1, 150)); // TODO - Set correct height according to the opened/closed objects objects_sz = new wxBoxSizer(wxVERTICAL); objects_sz->Add(m_objects_ctrl, 1, wxGROW | wxLEFT, 20); @@ -245,9 +248,10 @@ void create_objects_ctrl(wxWindow* win, wxBoxSizer*& objects_sz) m_objects_ctrl->EnableDropTarget(wxDF_UNICODETEXT); #endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE - // column 0(Icon+Text) of the view control: - m_objects_ctrl->AppendIconTextColumn(_(L("Name")), 0, wxDATAVIEW_CELL_INERT, 200, - wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE); + // column 0(Icon+Text) of the view control: + // And Icon can be consisting of several bitmaps + m_objects_ctrl->AppendColumn(new wxDataViewColumn(_(L("Name")), new PrusaBitmapTextRenderer(), + 0, 200, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); // column 1 of the view control: m_objects_ctrl->AppendTextColumn(_(L("Copy")), 1, wxDATAVIEW_CELL_INERT, 45, @@ -278,7 +282,7 @@ wxBoxSizer* create_objects_list(wxWindow *win) m_objects_ctrl->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, [](wxDataViewEvent& event) { object_ctrl_context_menu(); - event.Skip(); +// event.Skip(); }); m_objects_ctrl->Bind(wxEVT_CHAR, [](wxKeyEvent& event) { object_ctrl_key_event(event); }); // doesn't work on OSX @@ -316,15 +320,15 @@ wxBoxSizer* create_edit_object_buttons(wxWindow* win) //*** button's functions btn_load_part->Bind(wxEVT_BUTTON, [win](wxEvent&) { - on_btn_load(win); +// on_btn_load(win); }); btn_load_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) { - on_btn_load(win, true); +// on_btn_load(win, true); }); btn_load_lambda_modifier->Bind(wxEVT_BUTTON, [win](wxEvent&) { - on_btn_load(win, true, true); +// on_btn_load(win, true, true); }); btn_delete ->Bind(wxEVT_BUTTON, [](wxEvent&) { on_btn_del(); }); @@ -660,7 +664,7 @@ void add_object_to_list(const std::string &name, ModelObject* model_object) int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + stats.facets_added + stats.facets_reversed + stats.backwards_edges; if (errors > 0) { - const wxDataViewIconText data(item_name, m_icon_manifold_warning); + const PrusaDataViewBitmapText data(item_name, m_icon_manifold_warning); wxVariant variant; variant << data; m_objects_model->SetValue(variant, item, 0); @@ -724,15 +728,26 @@ void select_current_object(int idx) { g_prevent_list_events = true; m_objects_ctrl->UnselectAll(); - if (idx < 0) { - g_prevent_list_events = false; - return; - } - m_objects_ctrl->Select(m_objects_model->GetItemById(idx)); + if (idx>=0) + m_objects_ctrl->Select(m_objects_model->GetItemById(idx)); part_selection_changed(); g_prevent_list_events = false; } +void select_current_volume(int idx, int vol_idx) +{ + if (vol_idx < 0) { + select_current_object(idx); + return; + } + g_prevent_list_events = true; + m_objects_ctrl->UnselectAll(); + if (idx >= 0) + m_objects_ctrl->Select(m_objects_model->GetItemByVolumeId(idx, vol_idx)); + part_selection_changed(); + g_prevent_list_events = false; +} + void remove() { auto item = m_objects_ctrl->GetSelection(); @@ -758,8 +773,17 @@ void object_ctrl_selection_changed() if (m_event_object_selection_changed > 0) { wxCommandEvent event(m_event_object_selection_changed); - event.SetInt(int(m_objects_model->GetParent(m_objects_ctrl->GetSelection()) != wxDataViewItem(0))); - event.SetId(m_selected_object_id); + event.SetId(m_selected_object_id); // set $obj_idx + const wxDataViewItem item = m_objects_ctrl->GetSelection(); + if (!item || m_objects_model->GetParent(item) == wxDataViewItem(0)) + event.SetInt(-1); // set $vol_idx + else { + const int vol_idx = m_objects_model->GetVolumeIdByItem(item); + if (vol_idx == -2) // is settings item + event.SetInt(m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item))); // set $vol_idx + else + event.SetInt(vol_idx); + } get_main_frame()->ProcessWindowEvent(event); } @@ -772,9 +796,9 @@ void object_ctrl_context_menu() { wxDataViewItem item; wxDataViewColumn* col; - printf("object_ctrl_context_menu\n"); +// printf("object_ctrl_context_menu\n"); const wxPoint pt = get_mouse_position_in_control(); - printf("mouse_position_in_control: x = %d, y = %d\n", pt.x, pt.y); +// printf("mouse_position_in_control: x = %d, y = %d\n", pt.x, pt.y); m_objects_ctrl->HitTest(pt, item, col); if (!item) #ifdef __WXOSX__ // #ys_FIXME temporary workaround for OSX @@ -788,9 +812,9 @@ void object_ctrl_context_menu() #else return; #endif // __WXOSX__ - printf("item exists\n"); +// printf("item exists\n"); const wxString title = col->GetTitle(); - printf("title = *%s*\n", title.data().AsChar()); +// printf("title = *%s*\n", title.data().AsChar()); if (title == " ") show_context_menu(); @@ -836,6 +860,15 @@ void object_ctrl_item_value_change(wxDataViewEvent& event) } } +void show_manipulation_og(const bool show) +{ + wxGridSizer* grid_sizer = get_optgroup(ogFrequentlyObjectSettings)->get_grid_sizer(); + if (show == grid_sizer->IsShown(2)) + return; + for (size_t id = 2; id < 12; id++) + grid_sizer->Show(id, show); +} + //update_optgroup void update_settings_list() { @@ -856,14 +889,19 @@ void update_settings_list() m_option_sizer->Clear(true); - if (m_config) + bool show_manipulations = true; + const auto item = m_objects_ctrl->GetSelection(); + if (m_config && m_objects_model->IsSettingsItem(item)) { auto extra_column = [](wxWindow* parent, const Line& line) { auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line - auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("erase.png")), wxBITMAP_TYPE_PNG), + auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(from_u8(var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); +#ifdef __WXMSW__ + btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif // __WXMSW__ btn->Bind(wxEVT_BUTTON, [opt_key](wxEvent &event){ (*m_config)->erase(opt_key); wxTheApp->CallAfter([]() { update_settings_list(); }); @@ -873,60 +911,75 @@ void update_settings_list() std::map> cat_options; auto opt_keys = (*m_config)->keys(); - if (opt_keys.size() == 1 && opt_keys[0] == "extruder") - return; + m_og_settings.resize(0); + std::vector categories; + if (!(opt_keys.size() == 1 && opt_keys[0] == "extruder"))// return; + { + auto extruders_cnt = get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA ? 1 : + get_preset_bundle()->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); - auto extruders_cnt = get_preset_bundle()->printers.get_selected_preset().printer_technology() == ptSLA ? 1 : - get_preset_bundle()->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); + for (auto& opt_key : opt_keys) { + auto category = (*m_config)->def()->get(opt_key)->category; + if (category.empty() || + (category == "Extruders" && extruders_cnt == 1)) continue; - for (auto& opt_key : opt_keys) { - auto category = (*m_config)->def()->get(opt_key)->category; - if (category.empty() || - (category == "Extruders" && extruders_cnt==1)) continue; + std::vector< std::string > new_category; - std::vector< std::string > new_category; + auto& cat_opt = cat_options.find(category) == cat_options.end() ? new_category : cat_options.at(category); + cat_opt.push_back(opt_key); + if (cat_opt.size() == 1) + cat_options[category] = cat_opt; + } - auto& cat_opt = cat_options.find(category) == cat_options.end() ? new_category : cat_options.at(category); - cat_opt.push_back(opt_key); - if (cat_opt.size() == 1) - cat_options[category] = cat_opt; - } + for (auto& cat : cat_options) { + if (cat.second.size() == 1 && cat.second[0] == "extruder") + continue; + auto optgroup = std::make_shared(parent, cat.first, *m_config, false, ogDEFAULT, extra_column); + optgroup->label_width = 150; + optgroup->sidetext_width = 70; - m_og_settings.resize(0); - for (auto& cat : cat_options) { - if (cat.second.size() == 1 && cat.second[0] == "extruder") - continue; + for (auto& opt : cat.second) + { + if (opt == "extruder") + continue; + Option option = optgroup->get_option(opt); + option.opt.width = 70; + optgroup->append_single_option_line(option); + } + optgroup->reload_config(); + m_option_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0); + m_og_settings.push_back(optgroup); - auto optgroup = std::make_shared(parent, cat.first, *m_config, false, ogDEFAULT, extra_column); - optgroup->label_width = 100; - optgroup->sidetext_width = 70; + categories.push_back(cat.first); + } + } - for (auto& opt : cat.second) - { - if (opt == "extruder") - continue; - Option option = optgroup->get_option(opt); - option.opt.width = 70; - optgroup->append_single_option_line(option); - } - optgroup->reload_config(); - m_option_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0); - m_og_settings.push_back(optgroup); - } + if (m_og_settings.empty()) { + m_objects_ctrl->Select(m_objects_model->Delete(item)); + part_selection_changed(); + } + else { + if (!categories.empty()) + m_objects_model->UpdateSettingsDigest(item, categories); + show_manipulations = false; + } } + show_manipulation_og(show_manipulations); + show_info_sizer(show_manipulations && item && m_objects_model->GetParent(item) == wxDataViewItem(0)); + #ifdef __linux__ no_updates.reset(nullptr); #endif - /*get_right_panel()*/parent->Layout(); - get_right_panel()->GetParent()->GetParent()->Layout(); + parent->Layout(); + get_right_panel()->GetParent()->Layout(); } void get_settings_choice(wxMenu *menu, int id, bool is_part) { - auto category_name = menu->GetLabel(id); + const auto category_name = menu->GetLabel(id); wxArrayString names; wxArrayInt selections; @@ -974,15 +1027,35 @@ void get_settings_choice(wxMenu *menu, int id, bool is_part) (*m_config)->set_key_value(opt_key, m_default_config.get()->option(opt_key)->clone()); } + + // Add settings item for object + const auto item = m_objects_ctrl->GetSelection(); + if (item) { + const auto settings_item = m_objects_model->HasSettings(item); + m_objects_ctrl->Select(settings_item ? settings_item : + m_objects_model->AddSettingsChild(item)); +#ifndef __WXOSX__ + part_selection_changed(); +#endif //no __WXOSX__ + } + else update_settings_list(); } -bool cur_item_hase_children() -{ - wxDataViewItemArray children; - if (m_objects_model->GetChildren(m_objects_ctrl->GetSelection(), children) > 0) - return true; - return false; +void menu_item_add_generic(wxMenuItem* &menu, int id) { + auto sub_menu = new wxMenu; + + std::vector menu_items = { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") }; + for (auto& item : menu_items) + sub_menu->Append(new wxMenuItem(sub_menu, ++id, _(item))); + +#ifndef __WXMSW__ + sub_menu->Bind(wxEVT_MENU, [sub_menu](wxEvent &event) { + load_lambda(sub_menu->GetLabel(event.GetId()).ToStdString()); + }); +#endif //no __WXMSW__ + + menu->SetSubMenu(sub_menu); } wxMenuItem* menu_item_split(wxMenu* menu, int id) { @@ -991,11 +1064,11 @@ wxMenuItem* menu_item_split(wxMenu* menu, int id) { return menu_item; } -wxMenuItem* menu_item_settings(wxMenu* menu, int id) { +wxMenuItem* menu_item_settings(wxMenu* menu, int id, const bool is_part) { auto menu_item = new wxMenuItem(menu, id, _(L("Add settings"))); menu_item->SetBitmap(m_bmp_cog); - auto sub_menu = create_add_settings_popupmenu(false); + auto sub_menu = create_add_settings_popupmenu(is_part); menu_item->SetSubMenu(sub_menu); return menu_item; } @@ -1005,44 +1078,54 @@ wxMenu *create_add_part_popupmenu() wxMenu *menu = new wxMenu; std::vector menu_items = { L("Add part"), L("Add modifier"), L("Add generic") }; - wxWindowID config_id_base = wxWindow::NewControlId(menu_items.size()+2); + wxWindowID config_id_base = wxWindow::NewControlId(menu_items.size()+4+2); int i = 0; for (auto& item : menu_items) { auto menu_item = new wxMenuItem(menu, config_id_base + i, _(item)); menu_item->SetBitmap(i == 0 ? m_icon_solidmesh : m_icon_modifiermesh); - menu->Append(menu_item); + if (item == "Add generic") + menu_item_add_generic(menu_item, config_id_base + i); + menu->Append(menu_item); i++; } menu->AppendSeparator(); - auto menu_item = menu_item_split(menu, config_id_base + i); + auto menu_item = menu_item_split(menu, config_id_base + i + 4); menu->Append(menu_item); - menu_item->Enable(!cur_item_hase_children()); + menu_item->Enable(is_splittable_object(false)); menu->AppendSeparator(); // Append settings popupmenu - menu->Append(menu_item_settings(menu, config_id_base + i + 1)); + menu->Append(menu_item_settings(menu, config_id_base + i + 5, false)); - wxWindow* win = get_tab_panel()->GetPage(0); - - menu->Bind(wxEVT_MENU, [config_id_base, win, menu](wxEvent &event){ + menu->Bind(wxEVT_MENU, [config_id_base, menu](wxEvent &event){ switch (event.GetId() - config_id_base) { case 0: - on_btn_load(win); + on_btn_load(); break; case 1: - on_btn_load(win, true); + on_btn_load(true); break; case 2: - on_btn_load(win, true, true); +// on_btn_load(true, true); break; - case 3: + case 3: + case 4: + case 5: + case 6: +#ifdef __WXMSW__ + load_lambda(menu->GetLabel(event.GetId()).ToStdString()); +#endif // __WXMSW__ + break; + case 7: //3: on_btn_split(false); break; - default:{ + default: +#ifdef __WXMSW__ get_settings_choice(menu, event.GetId(), false); - break;} +#endif // __WXMSW__ + break; } }); @@ -1054,11 +1137,13 @@ wxMenu *create_part_settings_popupmenu() wxMenu *menu = new wxMenu; wxWindowID config_id_base = wxWindow::NewControlId(2); - menu->Append(menu_item_split(menu, config_id_base)); + auto menu_item = menu_item_split(menu, config_id_base); + menu->Append(menu_item); + menu_item->Enable(is_splittable_object(true)); menu->AppendSeparator(); // Append settings popupmenu - menu->Append(menu_item_settings(menu, config_id_base + 1)); + menu->Append(menu_item_settings(menu, config_id_base + 1, true)); menu->Bind(wxEVT_MENU, [config_id_base, menu](wxEvent &event){ switch (event.GetId() - config_id_base) { @@ -1090,35 +1175,35 @@ wxMenu *create_add_settings_popupmenu(bool is_part) wxNullBitmap : categories.at(cat.first)); menu->Append(menu_item); } - - menu->Bind(wxEVT_MENU, [menu](wxEvent &event) { - get_settings_choice(menu, event.GetId(), true); +#ifndef __WXMSW__ + menu->Bind(wxEVT_MENU, [menu,is_part](wxEvent &event) { + get_settings_choice(menu, event.GetId(), is_part); }); - +#endif //no __WXMSW__ return menu; } void show_context_menu() { - auto item = m_objects_ctrl->GetSelection(); + const auto item = m_objects_ctrl->GetSelection(); if (item) { - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { - auto menu = create_add_part_popupmenu(); - get_tab_panel()->GetPage(0)->PopupMenu(menu); - } - else { - auto menu = create_part_settings_popupmenu(); - get_tab_panel()->GetPage(0)->PopupMenu(menu); - } + if (m_objects_model->IsSettingsItem(item)) + return; + const auto menu = m_objects_model->GetParent(item) == wxDataViewItem(0) ? + create_add_part_popupmenu() : + create_part_settings_popupmenu(); + get_tab_panel()->GetPage(0)->PopupMenu(menu); } } // ****** -void load_part( wxWindow* parent, ModelObject* model_object, +void load_part( ModelObject* model_object, wxArrayString& part_names, const bool is_modifier) { + wxWindow* parent = get_tab_panel()->GetPage(0); + wxArrayString input_files; open_model(parent, input_files); for (int i = 0; i < input_files.size(); ++i) { @@ -1137,7 +1222,7 @@ void load_part( wxWindow* parent, ModelObject* model_object, for ( auto object : model.objects) { for (auto volume : object->volumes) { auto new_volume = model_object->add_volume(*volume); - new_volume->modifier = is_modifier; + new_volume->set_type(is_modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); boost::filesystem::path(input_file).filename().string(); new_volume->name = boost::filesystem::path(input_file).filename().string(); @@ -1156,10 +1241,10 @@ void load_part( wxWindow* parent, ModelObject* model_object, } } -void load_lambda( wxWindow* parent, ModelObject* model_object, +void load_lambda( ModelObject* model_object, wxArrayString& part_names, const bool is_modifier) { - auto dlg = new LambdaObjectDialog(parent); + auto dlg = new LambdaObjectDialog(m_objects_ctrl->GetMainWindow()); if (dlg->ShowModal() == wxID_CANCEL) { return; } @@ -1195,7 +1280,8 @@ void load_lambda( wxWindow* parent, ModelObject* model_object, mesh.repair(); auto new_volume = model_object->add_volume(mesh); - new_volume->modifier = is_modifier; + new_volume->set_type(is_modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); + new_volume->name = name; // set a default extruder value, since user can't add it manually new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); @@ -1205,7 +1291,50 @@ void load_lambda( wxWindow* parent, ModelObject* model_object, m_parts_changed = true; } -void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/, bool is_lambda/* = false*/) +void load_lambda(const std::string& type_name) +{ + if (m_selected_object_id < 0) return; + + auto dlg = new LambdaObjectDialog(m_objects_ctrl->GetMainWindow(), type_name); + if (dlg->ShowModal() == wxID_CANCEL) + return; + + const std::string name = "lambda-"+type_name; + TriangleMesh mesh; + + const auto params = dlg->ObjectParameters(); + if (type_name == _("Box")) + mesh = make_cube(params.dim[0], params.dim[1], params.dim[2]); + else if (type_name == _("Cylinder")) + mesh = make_cylinder(params.cyl_r, params.cyl_h); + else if (type_name == _("Sphere")) + mesh = make_sphere(params.sph_rho); + else if (type_name == _("Slab")){ + const auto& size = (*m_objects)[m_selected_object_id]->bounding_box().size(); + mesh = make_cube(size(0)*1.5, size(1)*1.5, params.slab_h); + // box sets the base coordinate at 0, 0, move to center of plate and move it up to initial_z + mesh.translate(-size(0)*1.5 / 2.0, -size(1)*1.5 / 2.0, params.slab_z); + } + mesh.repair(); + + auto new_volume = (*m_objects)[m_selected_object_id]->add_volume(mesh); + new_volume->set_type(ModelVolume::PARAMETER_MODIFIER); + + new_volume->name = name; + // set a default extruder value, since user can't add it manually + new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); + + m_parts_changed = true; + parts_changed(m_selected_object_id); + + m_objects_ctrl->Select(m_objects_model->AddChild(m_objects_ctrl->GetSelection(), + name, m_icon_modifiermesh)); +#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME + object_ctrl_selection_changed(); +#endif //no __WXOSX__ //__WXMSW__ +} + +void on_btn_load(bool is_modifier /*= false*/, bool is_lambda/* = false*/) { auto item = m_objects_ctrl->GetSelection(); if (!item) @@ -1219,19 +1348,54 @@ void on_btn_load(wxWindow* parent, bool is_modifier /*= false*/, bool is_lambda/ if (obj_idx < 0) return; wxArrayString part_names; if (is_lambda) - load_lambda(parent, (*m_objects)[obj_idx], part_names, is_modifier); + load_lambda((*m_objects)[obj_idx], part_names, is_modifier); else - load_part(parent, (*m_objects)[obj_idx], part_names, is_modifier); + load_part((*m_objects)[obj_idx], part_names, is_modifier); parts_changed(obj_idx); for (int i = 0; i < part_names.size(); ++i) m_objects_ctrl->Select( m_objects_model->AddChild(item, part_names.Item(i), is_modifier ? m_icon_modifiermesh : m_icon_solidmesh)); -// part_selection_changed(); -#ifdef __WXMSW__ +#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME object_ctrl_selection_changed(); -#endif //__WXMSW__ +#endif //no __WXOSX__//__WXMSW__ +} + +void remove_settings_from_config() +{ + auto opt_keys = (*m_config)->keys(); + if (opt_keys.size() == 1 && opt_keys[0] == "extruder") + return; + int extruder = -1; + if ((*m_config)->has("extruder")) + extruder = (*m_config)->option("extruder")->value; + + (*m_config)->clear(); + + if (extruder >=0 ) + (*m_config)->set_key_value("extruder", new ConfigOptionInt(extruder)); +} + +bool remove_subobject_from_object(const int volume_id) +{ + const auto volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; + + // if user is deleting the last solid part, throw error + int solid_cnt = 0; + for (auto vol : (*m_objects)[m_selected_object_id]->volumes) + if (vol->is_model_part()) + ++solid_cnt; + if (volume->is_model_part() && solid_cnt == 1) { + Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last solid part from this object."))); + return false; + } + + (*m_objects)[m_selected_object_id]->delete_volume(volume_id); + m_parts_changed = true; + + parts_changed(m_selected_object_id); + return true; } void on_btn_del() @@ -1239,50 +1403,67 @@ void on_btn_del() auto item = m_objects_ctrl->GetSelection(); if (!item) return; - auto volume_id = m_objects_model->GetVolumeIdByItem(item); - if (volume_id < 0) + const auto volume_id = m_objects_model->GetVolumeIdByItem(item); + if (volume_id ==-1) return; - auto volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; - - // if user is deleting the last solid part, throw error - int solid_cnt = 0; - for (auto vol : (*m_objects)[m_selected_object_id]->volumes) - if (!vol->modifier) - ++solid_cnt; - if (!volume->modifier && solid_cnt == 1) { - Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last solid part from this object."))); - return; - } - - (*m_objects)[m_selected_object_id]->delete_volume(volume_id); - m_parts_changed = true; - - parts_changed(m_selected_object_id); + + if (volume_id ==-2) + remove_settings_from_config(); + else if (!remove_subobject_from_object(volume_id)) + return; m_objects_ctrl->Select(m_objects_model->Delete(item)); part_selection_changed(); -// #ifdef __WXMSW__ -// object_ctrl_selection_changed(); -// #endif //__WXMSW__ +} + +bool get_volume_by_item(const bool split_part, const wxDataViewItem& item, ModelVolume*& volume) +{ + if (!item || m_selected_object_id < 0) + return false; + const auto volume_id = m_objects_model->GetVolumeIdByItem(item); + if (volume_id < 0) { + if (split_part) return false; + volume = (*m_objects)[m_selected_object_id]->volumes[0]; + } + else + volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; + if (volume) + return true; + return false; +} + +bool is_splittable_object(const bool split_part) +{ + const wxDataViewItem item = m_objects_ctrl->GetSelection(); + if (!item) return false; + + wxDataViewItemArray children; + if (!split_part && m_objects_model->GetChildren(item, children) > 0) + return false; + + ModelVolume* volume; + if (!get_volume_by_item(split_part, item, volume) || !volume) + return false; + + TriangleMeshPtrs meshptrs = volume->mesh.split(); + if (meshptrs.size() <= 1) { + delete meshptrs.front(); + return false; + } + + return true; } void on_btn_split(const bool split_part) { - auto item = m_objects_ctrl->GetSelection(); + const auto item = m_objects_ctrl->GetSelection(); if (!item || m_selected_object_id<0) return; - auto volume_id = m_objects_model->GetVolumeIdByItem(item); ModelVolume* volume; - if (volume_id < 0) { - if (split_part) return; - else - volume = (*m_objects)[m_selected_object_id]->volumes[0]; } - else - volume = (*m_objects)[m_selected_object_id]->volumes[volume_id]; - DynamicPrintConfig& config = get_preset_bundle()->printers.get_edited_preset().config; - auto nozzle_dmrs_cnt = config.option("nozzle_diameter")->values.size(); - auto split_rez = volume->split(nozzle_dmrs_cnt); - if (split_rez == 1) { + if (!get_volume_by_item(split_part, item, volume)) return; + DynamicPrintConfig& config = get_preset_bundle()->printers.get_edited_preset().config; + const auto nozzle_dmrs_cnt = config.option("nozzle_diameter")->values.size(); + if (volume->split(nozzle_dmrs_cnt) == 1) { wxMessageBox(_(L("The selected object couldn't be split because it contains only one part."))); return; } @@ -1295,8 +1476,9 @@ void on_btn_split(const bool split_part) for (auto id = 0; id < model_object->volumes.size(); id++) m_objects_model->AddChild(parent, model_object->volumes[id]->name, - model_object->volumes[id]->modifier ? m_icon_modifiermesh : m_icon_solidmesh, - model_object->volumes[id]->config.option("extruder")->value, + model_object->volumes[id]->is_modifier() ? m_icon_modifiermesh : m_icon_solidmesh, + model_object->volumes[id]->config.has("extruder") ? + model_object->volumes[id]->config.option("extruder")->value : 0, false); m_objects_ctrl->Expand(parent); @@ -1305,10 +1487,14 @@ void on_btn_split(const bool split_part) for (auto id = 0; id < model_object->volumes.size(); id++) m_objects_model->AddChild(item, model_object->volumes[id]->name, m_icon_solidmesh, - model_object->volumes[id]->config.option("extruder")->value, + model_object->volumes[id]->config.has("extruder") ? + model_object->volumes[id]->config.option("extruder")->value : 0, false); m_objects_ctrl->Expand(item); } + + m_parts_changed = true; + parts_changed(m_selected_object_id); } void on_btn_move_up(){ @@ -1389,33 +1575,49 @@ void part_selection_changed() auto item = m_objects_ctrl->GetSelection(); int obj_idx = -1; auto og = get_optgroup(ogFrequentlyObjectSettings); + m_config = nullptr; + wxString object_name = wxEmptyString; if (item) { + const bool is_settings_item = m_objects_model->IsSettingsItem(item); bool is_part = false; - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + wxString og_name = wxEmptyString; + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { obj_idx = m_objects_model->GetIdByItem(item); - og->set_name(" " + _(L("Object Settings")) + " "); + og_name = _(L("Object manipulation")); m_config = std::make_shared(&(*m_objects)[obj_idx]->config); } else { auto parent = m_objects_model->GetParent(item); - // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene + // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene obj_idx = m_objects_model->GetIdByItem(parent); - og->set_name(" " + _(L("Part Settings")) + " "); - is_part = true; - auto volume_id = m_objects_model->GetVolumeIdByItem(item); - m_config = std::make_shared(&(*m_objects)[obj_idx]->volumes[volume_id]->config); + if (is_settings_item) { + if (m_objects_model->GetParent(parent) == wxDataViewItem(0)) { + og_name = _(L("Object Settings to modify")); + m_config = std::make_shared(&(*m_objects)[obj_idx]->config); + } + else { + og_name = _(L("Part Settings to modify")); + is_part = true; + auto main_parent = m_objects_model->GetParent(parent); + obj_idx = m_objects_model->GetIdByItem(main_parent); + const auto volume_id = m_objects_model->GetVolumeIdByItem(parent); + m_config = std::make_shared(&(*m_objects)[obj_idx]->volumes[volume_id]->config); + } + } + else { + og_name = _(L("Part manipulation")); + is_part = true; + const auto volume_id = m_objects_model->GetVolumeIdByItem(item); + m_config = std::make_shared(&(*m_objects)[obj_idx]->volumes[volume_id]->config); + } } - auto config = m_config; - og->set_value("object_name", m_objects_model->GetName(item)); + og->set_name(" " + og_name + " "); + object_name = m_objects_model->GetName(item); m_default_config = std::make_shared(*DynamicPrintConfig::new_from_defaults_keys(get_options(is_part))); } - else { - wxString empty_str = wxEmptyString; - og->set_value("object_name", empty_str); - m_config = nullptr; - } + og->set_value("object_name", object_name); update_settings_list(); @@ -1561,9 +1763,15 @@ void update_position_values() auto og = get_optgroup(ogFrequentlyObjectSettings); auto instance = (*m_objects)[m_selected_object_id]->instances.front(); +#if ENABLE_MODELINSTANCE_3D_OFFSET + og->set_value("position_x", int(instance->get_offset(X))); + og->set_value("position_y", int(instance->get_offset(Y))); + og->set_value("position_z", int(instance->get_offset(Z))); +#else og->set_value("position_x", int(instance->offset(0))); og->set_value("position_y", int(instance->offset(1))); og->set_value("position_z", 0); +#endif // ENABLE_MODELINSTANCE_3D_OFFSET } void update_position_values(const Vec3d& position) @@ -1638,7 +1846,7 @@ void on_begin_drag(wxDataViewEvent &event) wxDataViewItem item(event.GetItem()); // only allow drags for item, not containers - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + if (m_objects_model->GetParent(item) == wxDataViewItem(0) || m_objects_model->IsSettingsItem(item)) { event.Veto(); return; } @@ -1662,7 +1870,7 @@ void on_drop_possible(wxDataViewEvent &event) // only allow drags for item or background, not containers if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) || - event.GetDataFormat() != wxDF_UNICODETEXT) + event.GetDataFormat() != wxDF_UNICODETEXT || m_objects_model->IsSettingsItem(item)) event.Veto(); } @@ -1672,7 +1880,7 @@ void on_drop(wxDataViewEvent &event) // only allow drops for item, not containers if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) || - event.GetDataFormat() != wxDF_UNICODETEXT) { + event.GetDataFormat() != wxDF_UNICODETEXT || m_objects_model->IsSettingsItem(item)) { event.Veto(); return; } @@ -1700,6 +1908,9 @@ void on_drop(wxDataViewEvent &event) for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id+=delta, cnt++) std::swap(volumes[id], volumes[id +delta]); + m_parts_changed = true; + parts_changed(m_selected_object_id); + g_prevent_list_events = false; } @@ -1716,5 +1927,115 @@ void update_objects_list_extruder_column(int extruders_count) set_extruder_column_hidden(extruders_count <= 1); } +void create_double_slider(wxWindow* parent, wxBoxSizer* sizer, wxGLCanvas* canvas) +{ + m_slider = new PrusaDoubleSlider(parent, wxID_ANY, 0, 0, 0, 100); + sizer->Add(m_slider, 0, wxEXPAND, 0); + + m_preview_canvas = canvas; + m_preview_canvas->Bind(wxEVT_KEY_DOWN, update_double_slider_from_canvas); + + m_slider->Bind(wxEVT_SCROLL_CHANGED, [parent](wxEvent& event) { + _3DScene::set_toolpaths_range(m_preview_canvas, m_slider->GetLowerValueD() - 1e-6, m_slider->GetHigherValueD() + 1e-6); + if (parent->IsShown()) + m_preview_canvas->Refresh(); + }); +} + +void fill_slider_values(std::vector> &values, + const std::vector &layers_z) +{ + std::vector layers_all_z = _3DScene::get_current_print_zs(m_preview_canvas, false); + if (layers_all_z.size() == layers_z.size()) + for (int i = 0; i < layers_z.size(); i++) + values.push_back(std::pair(i+1, layers_z[i])); + else if (layers_all_z.size() > layers_z.size()) { + int cur_id = 0; + for (int i = 0; i < layers_z.size(); i++) + for (int j = cur_id; j < layers_all_z.size(); j++) + if (layers_z[i] - 1e-6 < layers_all_z[j] && layers_all_z[j] < layers_z[i] + 1e-6) { + values.push_back(std::pair(j+1, layers_z[i])); + cur_id = j; + break; + } + } +} + +void set_double_slider_thumbs( const bool force_sliders_full_range, + const std::vector &layers_z, + const double z_low, const double z_high) +{ + // Force slider full range only when slider is created. + // Support selected diapason on the all next steps + if (/*force_sliders_full_range*/z_high == 0.0) { + m_slider->SetLowerValue(0); + m_slider->SetHigherValue(layers_z.size() - 1); + return; + } + + for (int i = layers_z.size() - 1; i >= 0; i--) + if (z_low >= layers_z[i]) { + m_slider->SetLowerValue(i); + break; + } + for (int i = layers_z.size() - 1; i >= 0 ; i--) + if (z_high >= layers_z[i]) { + m_slider->SetHigherValue(i); + break; + } +} + +void update_double_slider(bool force_sliders_full_range) +{ + std::vector> values; + std::vector layers_z = _3DScene::get_current_print_zs(m_preview_canvas, true); + fill_slider_values(values, layers_z); + + const double z_low = m_slider->GetLowerValueD(); + const double z_high = m_slider->GetHigherValueD(); + m_slider->SetMaxValue(layers_z.size() - 1); + m_slider->SetSliderValues(values); + + set_double_slider_thumbs(force_sliders_full_range, layers_z, z_low, z_high); +} + +void reset_double_slider() +{ + m_slider->SetHigherValue(0); + m_slider->SetLowerValue(0); +} + +void update_double_slider_from_canvas(wxKeyEvent& event) +{ + if (event.HasModifiers()) { + event.Skip(); + return; + } + + const auto key = event.GetKeyCode(); + + if (key == 'U' || key == 'D') { + const int new_pos = key == 'U' ? m_slider->GetHigherValue() + 1 : m_slider->GetHigherValue() - 1; + m_slider->SetHigherValue(new_pos); + if (event.ShiftDown()) m_slider->SetLowerValue(m_slider->GetHigherValue()); + } + else if (key == 'S') + m_slider->ChangeOneLayerLock(); + else + event.Skip(); +} + +void show_manipulation_sizer(const bool is_simple_mode) +{ + auto item = m_objects_ctrl->GetSelection(); + if (!item || !is_simple_mode) + return; + + if (m_objects_model->IsSettingsItem(item)) { + m_objects_ctrl->Select(m_objects_model->GetParent(item)); + part_selection_changed(); + } +} + } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp index 8a1499e03..e66b4d1db 100644 --- a/xs/src/slic3r/GUI/GUI_ObjectParts.hpp +++ b/xs/src/slic3r/GUI/GUI_ObjectParts.hpp @@ -9,13 +9,15 @@ class wxArrayString; class wxMenu; class wxDataViewEvent; class wxKeyEvent; -class wxControl; +class wxGLCanvas; +class wxBitmap; namespace Slic3r { class ModelObject; class Model; namespace GUI { +//class wxGLCanvas; enum ogGroup{ ogFrequentlyChangingParameters, @@ -44,6 +46,9 @@ struct OBJECT_PARAMETERS double slab_z = 0.0; }; +typedef std::map t_category_icon; +inline t_category_icon& get_category_icon(); + void add_collapsible_panes(wxWindow* parent, wxBoxSizer* sizer); void add_objects_list(wxWindow* parent, wxBoxSizer* sizer); void add_object_settings(wxWindow* parent, wxBoxSizer* sizer); @@ -66,16 +71,17 @@ void set_object_count(int idx, int count); void unselect_objects(); // Select current object in the list on c++ side void select_current_object(int idx); +// Select current volume in the list on c++ side +void select_current_volume(int idx, int vol_idx); // Remove objects/sub-object from the list void remove(); -//void create_double_slider(wxWindow* parent, wxControl* slider); - void object_ctrl_selection_changed(); void object_ctrl_context_menu(); void object_ctrl_key_event(wxKeyEvent& event); void object_ctrl_item_value_change(wxDataViewEvent& event); void show_context_menu(); +bool is_splittable_object(const bool split_part); void init_mesh_icons(); void set_event_object_selection_changed(const int& event); @@ -87,13 +93,14 @@ void set_objects_from_model(Model &model); bool is_parts_changed(); bool is_part_settings_changed(); -void load_part( wxWindow* parent, ModelObject* model_object, +void load_part( ModelObject* model_object, wxArrayString& part_names, const bool is_modifier); -void load_lambda(wxWindow* parent, ModelObject* model_object, - wxArrayString& part_names, const bool is_modifier); +void load_lambda( ModelObject* model_object, + wxArrayString& part_names, const bool is_modifier); +void load_lambda( const std::string& type_name); -void on_btn_load(wxWindow* parent, bool is_modifier = false, bool is_lambda = false); +void on_btn_load(bool is_modifier = false, bool is_lambda = false); void on_btn_del(); void on_btn_split(const bool split_part); void on_btn_move_up(); @@ -126,6 +133,15 @@ void on_drop(wxDataViewEvent &event); // update extruder column for objects_ctrl according to extruders count void update_objects_list_extruder_column(int extruders_count); +// Create/Update/Reset double slider on 3dPreview +void create_double_slider(wxWindow* parent, wxBoxSizer* sizer, wxGLCanvas* canvas); +void update_double_slider(bool force_sliders_full_range); +void reset_double_slider(); +// update DoubleSlider after keyDown in canvas +void update_double_slider_from_canvas(wxKeyEvent& event); + +void show_manipulation_sizer(const bool is_simple_mode); + } //namespace GUI } //namespace Slic3r #endif //slic3r_GUI_ObjectParts_hpp_ \ No newline at end of file diff --git a/xs/src/slic3r/GUI/LambdaObjectDialog.cpp b/xs/src/slic3r/GUI/LambdaObjectDialog.cpp index 7543821c0..7d741be7f 100644 --- a/xs/src/slic3r/GUI/LambdaObjectDialog.cpp +++ b/xs/src/slic3r/GUI/LambdaObjectDialog.cpp @@ -10,10 +10,12 @@ namespace GUI { static wxString dots("…", wxConvUTF8); -LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent) +LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent, + const wxString type_name): + m_type_name(type_name) { Create(parent, wxID_ANY, _(L("Lambda Object")), - wxDefaultPosition, wxDefaultSize, + parent->GetScreenPosition(), wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); // instead of double dim[3] = { 1.0, 1.0, 1.0 }; @@ -24,11 +26,20 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent) sizer = new wxBoxSizer(wxVERTICAL); // modificator options - m_modificator_options_book = new wxChoicebook( this, wxID_ANY, wxDefaultPosition, - wxDefaultSize, wxCHB_TOP); - sizer->Add(m_modificator_options_book, 1, wxEXPAND| wxALL, 10); + if (m_type_name == wxEmptyString) { + m_modificator_options_book = new wxChoicebook( this, wxID_ANY, wxDefaultPosition, + wxDefaultSize, wxCHB_TOP); + sizer->Add(m_modificator_options_book, 1, wxEXPAND | wxALL, 10); + } + else { + m_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); + sizer->Add(m_panel, 1, wxEXPAND | wxALL, 10); + } + ConfigOptionDef def; + def.width = 70; auto optgroup = init_modificator_options_page(_(L("Box"))); + if (optgroup){ optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ int opt_id = opt_key == "l" ? 0 : opt_key == "w" ? 1 : @@ -37,8 +48,6 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent) object_parameters.dim[opt_id] = boost::any_cast(value); }; - ConfigOptionDef def; - def.width = 70; def.type = coFloat; def.default_value = new ConfigOptionFloat{ 1.0 }; def.label = L("L"); @@ -52,8 +61,10 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent) def.label = L("H"); option = Option(def, "h"); optgroup->append_single_option_line(option); + } optgroup = init_modificator_options_page(_(L("Cylinder"))); + if (optgroup){ optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ int val = boost::any_cast(value); if (opt_key == "cyl_r") @@ -66,14 +77,16 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent) def.type = coInt; def.default_value = new ConfigOptionInt{ 1 }; def.label = L("Radius"); - option = Option(def, "cyl_r"); + auto option = Option(def, "cyl_r"); optgroup->append_single_option_line(option); def.label = L("Height"); option = Option(def, "cyl_h"); optgroup->append_single_option_line(option); + } optgroup = init_modificator_options_page(_(L("Sphere"))); + if (optgroup){ optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ if (opt_key == "sph_rho") object_parameters.sph_rho = boost::any_cast(value); @@ -83,10 +96,12 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent) def.type = coFloat; def.default_value = new ConfigOptionFloat{ 1.0 }; def.label = L("Rho"); - option = Option(def, "sph_rho"); + auto option = Option(def, "sph_rho"); optgroup->append_single_option_line(option); + } optgroup = init_modificator_options_page(_(L("Slab"))); + if (optgroup){ optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){ double val = boost::any_cast(value); if (opt_key == "slab_z") @@ -96,13 +111,16 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent) else return; }; + def.type = coFloat; + def.default_value = new ConfigOptionFloat{ 1.0 }; def.label = L("H"); - option = Option(def, "slab_h"); + auto option = Option(def, "slab_h"); optgroup->append_single_option_line(option); def.label = L("Initial Z"); option = Option(def, "slab_z"); optgroup->append_single_option_line(option); + } Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e) { @@ -127,8 +145,7 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent) } })); - - auto button_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL); + const auto button_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL); wxButton* btn_OK = static_cast(FindWindowById(wxID_OK, this)); btn_OK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { @@ -155,9 +172,11 @@ LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent) // Called from the constructor. // Create a panel for a rectangular / circular / custom bed shape. -ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(wxString title){ +ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(const wxString& title){ + if (!m_type_name.IsEmpty() && m_type_name != title) + return nullptr; - auto panel = new wxPanel(m_modificator_options_book); + auto panel = m_type_name.IsEmpty() ? new wxPanel(m_modificator_options_book) : m_panel; ConfigOptionsGroupShp optgroup; optgroup = std::make_shared(panel, _(L("Add")) + " " +title + " " +dots); @@ -165,8 +184,12 @@ ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(wxString m_optgroups.push_back(optgroup); - panel->SetSizerAndFit(optgroup->sizer); - m_modificator_options_book->AddPage(panel, title); + if (m_type_name.IsEmpty()) { + panel->SetSizerAndFit(optgroup->sizer); + m_modificator_options_book->AddPage(panel, title); + } + else + panel->SetSizer(optgroup->sizer); return optgroup; } diff --git a/xs/src/slic3r/GUI/LambdaObjectDialog.hpp b/xs/src/slic3r/GUI/LambdaObjectDialog.hpp index a70c12449..8f3e8cd80 100644 --- a/xs/src/slic3r/GUI/LambdaObjectDialog.hpp +++ b/xs/src/slic3r/GUI/LambdaObjectDialog.hpp @@ -7,6 +7,8 @@ #include #include +class wxPanel; + namespace Slic3r { namespace GUI @@ -14,16 +16,19 @@ namespace GUI using ConfigOptionsGroupShp = std::shared_ptr; class LambdaObjectDialog : public wxDialog { - wxChoicebook* m_modificator_options_book; + wxChoicebook* m_modificator_options_book = nullptr; std::vector m_optgroups; + wxString m_type_name; + wxPanel* m_panel = nullptr; public: - LambdaObjectDialog(wxWindow* parent); + LambdaObjectDialog(wxWindow* parent, + const wxString type_name = wxEmptyString); ~LambdaObjectDialog(){} bool CanClose() { return true; } // ??? OBJECT_PARAMETERS& ObjectParameters(){ return object_parameters; } - ConfigOptionsGroupShp init_modificator_options_page(wxString title); + ConfigOptionsGroupShp init_modificator_options_page(const wxString& title); // Note whether the window was already closed, so a pending update is not executed. bool m_already_closed = false; diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 81d02a4cd..ea22b2cb5 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -158,7 +158,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* // if we have an extra column, build it if (extra_column) { if (extra_column) { - grid_sizer->Add(extra_column(parent(), line), 0, wxALIGN_CENTER_VERTICAL, 0); + grid_sizer->Add(extra_column(parent(), line), 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 3); } else { // if the callback provides no sizer for the extra cell, put a spacer diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index 5a18803a6..4941e5453 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -161,8 +161,10 @@ public: ogDrawFlag flag = ogDEFAULT, column_t extra_clmn = nullptr) : m_parent(_parent), title(title), m_show_modified_btns(is_tab_opt), staticbox(title!=""), m_flag(flag), extra_column(extra_clmn){ - stb = new wxStaticBox(_parent, wxID_ANY, title); - stb->SetFont(bold_font()); + if (staticbox) { + stb = new wxStaticBox(_parent, wxID_ANY, title); + stb->SetFont(bold_font()); + } sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); auto num_columns = 1U; if (label_width != 0) num_columns++; diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index 92912a3c4..9911caa5b 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -295,7 +295,7 @@ const std::vector& Preset::print_options() "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", "bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration", "bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", - "min_skirt_length", "brim_width", "support_material", "support_material_threshold", "support_material_enforce_layers", + "min_skirt_length", "brim_width", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", diff --git a/xs/src/slic3r/GUI/ProgressIndicator.hpp b/xs/src/slic3r/GUI/ProgressIndicator.hpp index 106f5e7ea..0cf8b4a17 100644 --- a/xs/src/slic3r/GUI/ProgressIndicator.hpp +++ b/xs/src/slic3r/GUI/ProgressIndicator.hpp @@ -4,8 +4,6 @@ #include #include -#include - namespace Slic3r { /** @@ -44,13 +42,13 @@ public: } /// Message shown on the next status update. - virtual void message(const wxString&) = 0; + virtual void message(const std::string&) = 0; /// Title of the operation. - virtual void title(const wxString&) = 0; + virtual void title(const std::string&) = 0; /// Formatted message for the next status update. Works just like sprintf. - virtual void message_fmt(const wxString& fmt, ...); + virtual void message_fmt(const std::string& fmt, ...); /// Set up a cancel callback for the operation if feasible. virtual void on_cancel(CancelFn func = CancelFn()) { cancelfunc_ = func; } @@ -62,7 +60,7 @@ public: virtual void cancel() { cancelfunc_(); } /// Convenience function to call message and status update in one function. - void update(float st, const wxString& msg) { + void update(float st, const std::string& msg) { message(msg); state(st); } }; diff --git a/xs/src/slic3r/GUI/ProgressStatusBar.cpp b/xs/src/slic3r/GUI/ProgressStatusBar.cpp index 67fcd1dcc..363e34cb2 100644 --- a/xs/src/slic3r/GUI/ProgressStatusBar.cpp +++ b/xs/src/slic3r/GUI/ProgressStatusBar.cpp @@ -134,7 +134,7 @@ void ProgressStatusBar::embed(wxFrame *frame) mf->SetStatusBar(self); } -void ProgressStatusBar::set_status_text(const std::string& txt) +void ProgressStatusBar::set_status_text(const wxString& txt) { self->SetStatusText(wxString::FromUTF8(txt.c_str())); } diff --git a/xs/src/slic3r/GUI/ProgressStatusBar.hpp b/xs/src/slic3r/GUI/ProgressStatusBar.hpp index 91c180ba6..3075805da 100644 --- a/xs/src/slic3r/GUI/ProgressStatusBar.hpp +++ b/xs/src/slic3r/GUI/ProgressStatusBar.hpp @@ -13,6 +13,7 @@ class wxTimerEvent; class wxStatusBar; class wxWindow; class wxFrame; +class wxString; namespace Slic3r { @@ -46,7 +47,7 @@ public: inline void remove_cancel_callback() { set_cancel_callback(); } void run(int rate); void embed(wxFrame *frame = nullptr); - void set_status_text(const std::string& txt); + void set_status_text(const wxString& txt); // Temporary methods to satisfy Perl side void show_cancel_button(); diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index 83b834219..e0db63803 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -880,6 +880,7 @@ void TabPrint::build() page = add_options_page(_(L("Support material")), "building.png"); optgroup = page->new_optgroup(_(L("Support material"))); optgroup->append_single_option_line("support_material"); + optgroup->append_single_option_line("support_material_auto"); optgroup->append_single_option_line("support_material_threshold"); optgroup->append_single_option_line("support_material_enforce_layers"); @@ -1219,13 +1220,15 @@ void TabPrint::update() bool have_raft = m_config->opt_int("raft_layers") > 0; bool have_support_material = m_config->opt_bool("support_material") || have_raft; + bool have_support_material_auto = have_support_material && m_config->opt_bool("support_material_auto"); bool have_support_interface = m_config->opt_int("support_material_interface_layers") > 0; bool have_support_soluble = have_support_material && m_config->opt_float("support_material_contact_distance") == 0; - for (auto el : {"support_material_threshold", "support_material_pattern", "support_material_with_sheath", + for (auto el : {"support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_angle", "support_material_interface_layers", "dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance", "support_material_xy_spacing" }) get_field(el)->toggle(have_support_material); + get_field("support_material_threshold")->toggle(have_support_material_auto); for (auto el : {"support_material_interface_spacing", "support_material_interface_extruder", "support_material_interface_speed", "support_material_interface_contact_loops" }) diff --git a/xs/src/slic3r/GUI/wxExtensions.cpp b/xs/src/slic3r/GUI/wxExtensions.cpp index e646ed0ec..13730a497 100644 --- a/xs/src/slic3r/GUI/wxExtensions.cpp +++ b/xs/src/slic3r/GUI/wxExtensions.cpp @@ -2,6 +2,7 @@ #include "GUI.hpp" #include "../../libslic3r/Utils.hpp" +#include "BitmapCache.hpp" #include #include @@ -357,11 +358,53 @@ void PrusaObjectDataViewModelNode::set_part_action_icon() { m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("cog.png")), wxBITMAP_TYPE_PNG); } +Slic3r::GUI::BitmapCache *m_bitmap_cache = nullptr; +bool PrusaObjectDataViewModelNode::update_settings_digest(const std::vector& categories) +{ + if (m_type != "settings" || m_opt_categories == categories) + return false; + + m_opt_categories = categories; + m_name = wxEmptyString; + m_icon = m_empty_icon; + + auto categories_icon = Slic3r::GUI::get_category_icon(); + + for (auto& cat : m_opt_categories) + m_name += cat + "; "; + + wxBitmap *bmp = m_bitmap_cache->find(m_name.ToStdString()); + if (bmp == nullptr) { + std::vector bmps; + for (auto& cat : m_opt_categories) + bmps.emplace_back(categories_icon.find(cat) == categories_icon.end() ? + wxNullBitmap : categories_icon.at(cat)); + bmp = m_bitmap_cache->insert(m_name.ToStdString(), bmps); + } + + m_bmp = *bmp; + + return true; +} + // ***************************************************************************** // ---------------------------------------------------------------------------- // PrusaObjectDataViewModel // ---------------------------------------------------------------------------- +PrusaObjectDataViewModel::PrusaObjectDataViewModel() +{ + m_bitmap_cache = new Slic3r::GUI::BitmapCache; +} + +PrusaObjectDataViewModel::~PrusaObjectDataViewModel() +{ + for (auto object : m_objects) + delete object; + delete m_bitmap_cache; + m_bitmap_cache = nullptr; +} + wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name) { auto root = new PrusaObjectDataViewModelNode(name); @@ -386,34 +429,50 @@ wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name, const int ins wxDataViewItem PrusaObjectDataViewModel::AddChild( const wxDataViewItem &parent_item, const wxString &name, - const wxIcon& icon, + const wxBitmap& icon, const int extruder/* = 0*/, const bool create_frst_child/* = true*/) { PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID(); if (!root) return wxDataViewItem(0); - wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder); + const wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder); - if (root->GetChildren().Count() == 0 && create_frst_child) + if (create_frst_child && (root->GetChildren().Count() == 0 || + (root->GetChildren().Count() == 1 && root->GetNthChild(0)->m_type == "settings"))) { - auto icon_solid_mesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("package.png")), wxBITMAP_TYPE_PNG); - auto node = new PrusaObjectDataViewModelNode(root, root->m_name, icon_solid_mesh, extruder_str, 0); + const auto icon_solid_mesh = wxIcon(Slic3r::GUI::from_u8(Slic3r::var("object.png")), wxBITMAP_TYPE_PNG); + const auto node = new PrusaObjectDataViewModelNode(root, root->m_name, icon_solid_mesh, extruder_str, 0); root->Append(node); // notify control - wxDataViewItem child((void*)node); + const wxDataViewItem child((void*)node); ItemAdded(parent_item, child); } - auto volume_id = root->GetChildCount(); - auto node = new PrusaObjectDataViewModelNode(root, name, icon, extruder_str, volume_id); + const auto volume_id = root->GetChildCount() > 0 && root->GetNthChild(0)->m_type == "settings" ? + root->GetChildCount() - 1 : root->GetChildCount(); + + const auto node = new PrusaObjectDataViewModelNode(root, name, icon, extruder_str, volume_id); root->Append(node); // notify control - wxDataViewItem child((void*)node); + const wxDataViewItem child((void*)node); ItemAdded(parent_item, child); return child; } +wxDataViewItem PrusaObjectDataViewModel::AddSettingsChild(const wxDataViewItem &parent_item) +{ + PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID(); + if (!root) return wxDataViewItem(0); + + const auto node = new PrusaObjectDataViewModelNode(root); + root->Insert(node, 0); + // notify control + const wxDataViewItem child((void*)node); + ItemAdded(parent_item, child); + return child; +} + wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) { auto ret_item = wxDataViewItem(0); @@ -438,7 +497,7 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) //update volume_id value for remaining child-nodes auto children = node_parent->GetChildren(); - for (size_t i = 0; i < node_parent->GetChildCount(); i++) + for (size_t i = 0; i < node_parent->GetChildCount() && v_id>=0; i++) { auto volume_id = children[i]->GetVolumeId(); if (volume_id > v_id) @@ -460,9 +519,10 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) delete node; // set m_containet to FALSE if parent has no child - if (node_parent && node_parent->GetChildCount() == 0){ + if (node_parent) { #ifndef __WXGTK__ - node_parent->m_container = false; + if (node_parent->GetChildCount() == 0) + node_parent->m_container = false; #endif //__WXGTK__ ret_item = parent; } @@ -522,6 +582,26 @@ wxDataViewItem PrusaObjectDataViewModel::GetItemById(int obj_idx) } +wxDataViewItem PrusaObjectDataViewModel::GetItemByVolumeId(int obj_idx, int volume_idx) +{ + if (obj_idx >= m_objects.size()) { + printf("Error! Out of objects range.\n"); + return wxDataViewItem(0); + } + + auto parent = m_objects[obj_idx]; + if (parent->GetChildCount() == 0) { + printf("Error! Object has no one volume.\n"); + return wxDataViewItem(0); + } + + for (size_t i = 0; i < parent->GetChildCount(); i++) + if (parent->GetNthChild(i)->m_volume_id == volume_idx) + return wxDataViewItem(parent->GetNthChild(i)); + + return wxDataViewItem(0); +} + int PrusaObjectDataViewModel::GetIdByItem(wxDataViewItem& item) { wxASSERT(item.IsOk()); @@ -534,7 +614,7 @@ int PrusaObjectDataViewModel::GetIdByItem(wxDataViewItem& item) return it - m_objects.begin(); } -int PrusaObjectDataViewModel::GetVolumeIdByItem(wxDataViewItem& item) +int PrusaObjectDataViewModel::GetVolumeIdByItem(const wxDataViewItem& item) { wxASSERT(item.IsOk()); @@ -568,6 +648,12 @@ wxIcon& PrusaObjectDataViewModel::GetIcon(const wxDataViewItem &item) const return node->m_icon; } +wxBitmap& PrusaObjectDataViewModel::GetBitmap(const wxDataViewItem &item) const +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + return node->m_bmp; +} + void PrusaObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const { wxASSERT(item.IsOk()); @@ -576,8 +662,8 @@ void PrusaObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem switch (col) { case 0:{ - const wxDataViewIconText data(node->m_name, node->m_icon); - variant << data; + const PrusaDataViewBitmapText data(node->m_name, node->m_bmp); + variant << data; break;} case 1: variant = node->m_copy; @@ -667,26 +753,35 @@ wxDataViewItem PrusaObjectDataViewModel::ReorganizeChildren(int current_volume_i if (!node_parent) // happens if item.IsOk()==false return ret_item; - PrusaObjectDataViewModelNode *deleted_node = node_parent->GetNthChild(current_volume_id); + const size_t shift = node_parent->GetChildren().Item(0)->m_type == "settings" ? 1 : 0; + + PrusaObjectDataViewModelNode *deleted_node = node_parent->GetNthChild(current_volume_id+shift); node_parent->GetChildren().Remove(deleted_node); ItemDeleted(parent, wxDataViewItem(deleted_node)); - node_parent->Insert(deleted_node, new_volume_id); + node_parent->Insert(deleted_node, new_volume_id+shift); ItemAdded(parent, wxDataViewItem(deleted_node)); + const auto settings_item = HasSettings(wxDataViewItem(deleted_node)); + if (settings_item) + ItemAdded(wxDataViewItem(deleted_node), settings_item); //update volume_id value for child-nodes auto children = node_parent->GetChildren(); int id_frst = current_volume_id < new_volume_id ? current_volume_id : new_volume_id; int id_last = current_volume_id > new_volume_id ? current_volume_id : new_volume_id; for (int id = id_frst; id <= id_last; ++id) - children[id]->SetVolumeId(id); + children[id+shift]->SetVolumeId(id); - return wxDataViewItem(node_parent->GetNthChild(new_volume_id)); + return wxDataViewItem(node_parent->GetNthChild(new_volume_id+shift)); } -// bool MyObjectTreeModel::IsEnabled(const wxDataViewItem &item, unsigned int col) const -// { -// -// } +bool PrusaObjectDataViewModel::IsEnabled(const wxDataViewItem &item, unsigned int col) const +{ + wxASSERT(item.IsOk()); + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + + // disable extruder selection for the "Settings" item + return !(col == 2 && node->m_extruder.IsEmpty()); +} wxDataViewItem PrusaObjectDataViewModel::GetParent(const wxDataViewItem &item) const { @@ -738,6 +833,87 @@ unsigned int PrusaObjectDataViewModel::GetChildren(const wxDataViewItem &parent, return count; } +wxDataViewItem PrusaObjectDataViewModel::HasSettings(const wxDataViewItem &item) const +{ + if (!item.IsOk()) + return wxDataViewItem(0); + + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (node->GetChildCount() == 0) + return wxDataViewItem(0); + + auto& children = node->GetChildren(); + if (children[0]->m_type == "settings") + return wxDataViewItem((void*)children[0]);; + + return wxDataViewItem(0); +} + +bool PrusaObjectDataViewModel::IsSettingsItem(const wxDataViewItem &item) const +{ + if (!item.IsOk()) + return false; + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + return node->m_type == "settings"; +} + + + +void PrusaObjectDataViewModel::UpdateSettingsDigest(const wxDataViewItem &item, + const std::vector& categories) +{ + if (!item.IsOk()) return; + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); + if (!node->update_settings_digest(categories)) + return; + ItemChanged(item); +} + +IMPLEMENT_VARIANT_OBJECT(PrusaDataViewBitmapText) +// --------------------------------------------------------- +// PrusaIconTextRenderer +// --------------------------------------------------------- + +bool PrusaBitmapTextRenderer::SetValue(const wxVariant &value) +{ + m_value << value; + return true; +} + +bool PrusaBitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const +{ + return false; +} + +bool PrusaBitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) +{ + int xoffset = 0; + + const wxBitmap& icon = m_value.GetBitmap(); + if (icon.IsOk()) + { + dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2); + xoffset = icon.GetWidth() + 4; + } + + RenderText(m_value.GetText(), xoffset, rect, dc, state); + + return true; +} + +wxSize PrusaBitmapTextRenderer::GetSize() const +{ + if (!m_value.GetText().empty()) + { + wxSize size = GetTextExtent(m_value.GetText()); + + if (m_value.GetBitmap().IsOk()) + size.x += m_value.GetBitmap().GetWidth() + 4; + return size; + } + return wxSize(80, 20); +} + // ---------------------------------------------------------------------------- // PrusaDoubleSlider @@ -829,16 +1005,28 @@ wxSize PrusaDoubleSlider::DoGetBestSize() const void PrusaDoubleSlider::SetLowerValue(const int lower_val) { + m_selection = ssLower; m_lower_value = lower_val; + correct_lower_value(); Refresh(); Update(); + + wxCommandEvent e(wxEVT_SCROLL_CHANGED); + e.SetEventObject(this); + ProcessWindowEvent(e); } void PrusaDoubleSlider::SetHigherValue(const int higher_val) { + m_selection = ssHigher; m_higher_value = higher_val; + correct_higher_value(); Refresh(); Update(); + + wxCommandEvent e(wxEVT_SCROLL_CHANGED); + e.SetEventObject(this); + ProcessWindowEvent(e); } void PrusaDoubleSlider::SetMaxValue(const int max_value) @@ -905,6 +1093,13 @@ void PrusaDoubleSlider::get_size(int *w, int *h) is_horizontal() ? *w -= m_lock_icon_dim : *h -= m_lock_icon_dim; } +double PrusaDoubleSlider::get_double_value(const SelectedSlider& selection) const +{ + if (m_values.empty()) + return 0.0; + return m_values[selection == ssLower ? m_lower_value : m_higher_value].second; +} + void PrusaDoubleSlider::get_lower_and_higher_position(int& lower_pos, int& higher_pos) { const double step = get_scroll_step(); @@ -1010,7 +1205,7 @@ wxString PrusaDoubleSlider::get_label(const SelectedSlider& selection) const void PrusaDoubleSlider::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const { - if (m_is_one_layer && selection != m_selection || !selection) + if ((m_is_one_layer || m_higher_value==m_lower_value) && selection != m_selection || !selection) return; wxCoord text_width, text_height; const wxString label = get_label(selection); @@ -1178,6 +1373,20 @@ bool PrusaDoubleSlider::is_point_in_rect(const wxPoint& pt, const wxRect& rect) return false; } +void PrusaDoubleSlider::ChangeOneLayerLock() +{ + m_is_one_layer = !m_is_one_layer; + m_selection == ssLower ? correct_lower_value() : correct_higher_value(); + if (!m_selection) m_selection = ssHigher; + + Refresh(); + Update(); + + wxCommandEvent e(wxEVT_SCROLL_CHANGED); + e.SetEventObject(this); + ProcessWindowEvent(e); +} + void PrusaDoubleSlider::OnLeftDown(wxMouseEvent& event) { this->CaptureMouse(); @@ -1245,6 +1454,10 @@ void PrusaDoubleSlider::OnMotion(wxMouseEvent& event) Refresh(); Update(); event.Skip(); + + wxCommandEvent e(wxEVT_SCROLL_CHANGED); + e.SetEventObject(this); + ProcessWindowEvent(e); } void PrusaDoubleSlider::OnLeftUp(wxMouseEvent& event) diff --git a/xs/src/slic3r/GUI/wxExtensions.hpp b/xs/src/slic3r/GUI/wxExtensions.hpp index 76e59f2eb..51c02035c 100644 --- a/xs/src/slic3r/GUI/wxExtensions.hpp +++ b/xs/src/slic3r/GUI/wxExtensions.hpp @@ -144,6 +144,49 @@ public: #endif //__WXMSW__ // ***************************************************************************** + +// ---------------------------------------------------------------------------- +// PrusaDataViewBitmapText: helper class used by PrusaBitmapTextRenderer +// ---------------------------------------------------------------------------- + +class PrusaDataViewBitmapText : public wxObject +{ +public: + PrusaDataViewBitmapText(const wxString &text = wxEmptyString, + const wxBitmap& bmp = wxNullBitmap) : + m_text(text), m_bmp(bmp) + { } + + PrusaDataViewBitmapText(const PrusaDataViewBitmapText &other) + : wxObject(), + m_text(other.m_text), + m_bmp(other.m_bmp) + { } + + void SetText(const wxString &text) { m_text = text; } + wxString GetText() const { return m_text; } + void SetBitmap(const wxIcon &icon) { m_bmp = icon; } + const wxBitmap &GetBitmap() const { return m_bmp; } + + bool IsSameAs(const PrusaDataViewBitmapText& other) const { + return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp); + } + + bool operator==(const PrusaDataViewBitmapText& other) const { + return IsSameAs(other); + } + + bool operator!=(const PrusaDataViewBitmapText& other) const { + return !IsSameAs(other); + } + +private: + wxString m_text; + wxBitmap m_bmp; +}; +DECLARE_VARIANT_OBJECT(PrusaDataViewBitmapText) + + // ---------------------------------------------------------------------------- // PrusaObjectDataViewModelNode: a node inside PrusaObjectDataViewModel // ---------------------------------------------------------------------------- @@ -155,7 +198,9 @@ class PrusaObjectDataViewModelNode { PrusaObjectDataViewModelNode* m_parent; MyObjectTreeModelNodePtrArray m_children; - wxIcon m_empty_icon; + wxIcon m_empty_icon; + wxBitmap m_empty_bmp; + std::vector< std::string > m_opt_categories; public: PrusaObjectDataViewModelNode(const wxString &name, const int instances_count=1) { m_parent = NULL; @@ -174,18 +219,31 @@ public: PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent, const wxString& sub_obj_name, - const wxIcon& icon, + const wxBitmap& bmp, const wxString& extruder, const int volume_id=-1) { m_parent = parent; m_name = sub_obj_name; m_copy = wxEmptyString; - m_icon = icon; + m_bmp = bmp; m_type = "volume"; m_volume_id = volume_id; - m_extruder = extruder; + m_extruder = extruder; +#ifdef __WXGTK__ + // it's necessary on GTK because of control have to know if this item will be container + // in another case you couldn't to add subitem for this item + // it will be produce "segmentation fault" + m_container = true; +#endif //__WXGTK__ set_part_action_icon(); - } + } + + PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent) : + m_parent(parent), + m_name("Settings to modified"), + m_copy(wxEmptyString), + m_type("settings"), + m_extruder(wxEmptyString) {} ~PrusaObjectDataViewModelNode() { @@ -200,9 +258,10 @@ public: wxString m_name; wxIcon& m_icon = m_empty_icon; + wxBitmap& m_bmp = m_empty_bmp; wxString m_copy; std::string m_type; - int m_volume_id; + int m_volume_id = -2; bool m_container = false; wxString m_extruder = "default"; wxBitmap m_action_icon; @@ -226,6 +285,8 @@ public: } void Insert(PrusaObjectDataViewModelNode* child, unsigned int n) { + if (!m_container) + m_container = true; m_children.Insert(child, n); } void Append(PrusaObjectDataViewModelNode* child) @@ -258,9 +319,9 @@ public: switch (col) { case 0:{ - wxDataViewIconText data; + PrusaDataViewBitmapText data; data << variant; - m_icon = data.GetIcon(); + m_bmp = data.GetBitmap(); m_name = data.GetText(); return true;} case 1: @@ -281,6 +342,11 @@ public: { m_icon = icon; } + + void SetBitmap(const wxBitmap &icon) + { + m_bmp = icon; + } void SetType(const std::string& type){ m_type = type; @@ -326,6 +392,7 @@ public: // Set action icons for node void set_object_action_icon(); void set_part_action_icon(); + bool update_settings_digest(const std::vector& categories); }; // ---------------------------------------------------------------------------- @@ -336,26 +403,24 @@ class PrusaObjectDataViewModel :public wxDataViewModel { std::vector m_objects; public: - PrusaObjectDataViewModel(){} - ~PrusaObjectDataViewModel() - { - for (auto object : m_objects) - delete object; - } + PrusaObjectDataViewModel(); + ~PrusaObjectDataViewModel(); wxDataViewItem Add(const wxString &name); wxDataViewItem Add(const wxString &name, const int instances_count); wxDataViewItem AddChild(const wxDataViewItem &parent_item, const wxString &name, - const wxIcon& icon, - const int = 0, + const wxBitmap& icon, + const int extruder = 0, const bool create_frst_child = true); + wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); wxDataViewItem Delete(const wxDataViewItem &item); void DeleteAll(); void DeleteChildren(wxDataViewItem& parent); wxDataViewItem GetItemById(int obj_idx); + wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); int GetIdByItem(wxDataViewItem& item); - int GetVolumeIdByItem(wxDataViewItem& item); + int GetVolumeIdByItem(const wxDataViewItem& item); bool IsEmpty() { return m_objects.empty(); } // helper method for wxLog @@ -363,6 +428,7 @@ public: wxString GetName(const wxDataViewItem &item) const; wxString GetCopy(const wxDataViewItem &item) const; wxIcon& GetIcon(const wxDataViewItem &item) const; + wxBitmap& GetBitmap(const wxDataViewItem &item) const; // helper methods to change the model @@ -383,8 +449,7 @@ public: int new_volume_id, const wxDataViewItem &parent); -// virtual bool IsEnabled(const wxDataViewItem &item, -// unsigned int col) const override; + virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const override; virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override; virtual bool IsContainer(const wxDataViewItem &item) const override; @@ -394,8 +459,35 @@ public: // Is the container just a header or an item with all columns // In our case it is an item with all columns virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } + + wxDataViewItem HasSettings(const wxDataViewItem &item) const; + bool IsSettingsItem(const wxDataViewItem &item) const; + void UpdateSettingsDigest(const wxDataViewItem &item, const std::vector& categories); }; +// ---------------------------------------------------------------------------- +// PrusaBitmapTextRenderer +// ---------------------------------------------------------------------------- + +class PrusaBitmapTextRenderer : public wxDataViewCustomRenderer +{ +public: + PrusaBitmapTextRenderer( wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT, + int align = wxDVR_DEFAULT_ALIGNMENT): + wxDataViewCustomRenderer(wxT("wxObject"), mode, align) {} + + bool SetValue(const wxVariant &value); + bool GetValue(wxVariant &value) const; + + virtual bool Render(wxRect cell, wxDC *dc, int state); + virtual wxSize GetSize() const; + + virtual bool HasEditorCtrl() const { return false; } + +private: +// wxDataViewIconText m_value; + PrusaDataViewBitmapText m_value; +}; // ---------------------------------------------------------------------------- @@ -516,7 +608,7 @@ public: int maxValue, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, - long style = wxSL_HORIZONTAL, + long style = wxSL_VERTICAL, const wxValidator& val = wxDefaultValidator, const wxString& name = wxEmptyString); ~PrusaDoubleSlider(){} @@ -528,6 +620,8 @@ public: return m_higher_value; } int GetActiveValue() const; + double GetLowerValueD() const { return get_double_value(ssLower); } + double GetHigherValueD() const { return get_double_value(ssHigher); } wxSize DoGetBestSize() const override; void SetLowerValue(const int lower_val); void SetHigherValue(const int higher_val); @@ -538,6 +632,7 @@ public: void SetSliderValues(const std::vector>& values) { m_values = values; } + void ChangeOneLayerLock(); void OnPaint(wxPaintEvent& ){ render();} void OnLeftDown(wxMouseEvent& event); @@ -583,6 +678,7 @@ protected: wxCoord get_position_from_value(const int value); wxSize get_size(); void get_size(int *w, int *h); + double get_double_value(const SelectedSlider& selection) const; private: int m_min_value; diff --git a/xs/src/slic3r/Utils/Serial.cpp b/xs/src/slic3r/Utils/Serial.cpp index 183119b44..601719b50 100644 --- a/xs/src/slic3r/Utils/Serial.cpp +++ b/xs/src/slic3r/Utils/Serial.cpp @@ -231,7 +231,12 @@ std::vector scan_serial_ports_extended() spi.port = path; #ifdef __linux__ auto friendly_name = sysfs_tty_prop(name, "product"); - spi.friendly_name = friendly_name ? (boost::format("%1% (%2%)") % *friendly_name % path).str() : path; + if (friendly_name) { + spi.is_printer = looks_like_printer(*friendly_name); + spi.friendly_name = (boost::format("%1% (%2%)") % *friendly_name % path).str(); + } else { + spi.friendly_name = path; + } auto vid = sysfs_tty_prop_hex(name, "idVendor"); auto pid = sysfs_tty_prop_hex(name, "idProduct"); if (vid && pid) { diff --git a/xs/xsp/GUI.xsp b/xs/xsp/GUI.xsp index 6700a4765..a4d656616 100644 --- a/xs/xsp/GUI.xsp +++ b/xs/xsp/GUI.xsp @@ -96,26 +96,21 @@ void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_siz void set_print_callback_event(Print *print, int id) %code%{ Slic3r::GUI::set_print_callback_event(print, id); %}; -void add_expert_mode_part( SV *ui_parent, SV *ui_sizer, - Model *model, - int event_object_selection_changed, - int event_object_settings_changed, - int event_remove_object, - int event_update_scene) - %code%{ Slic3r::GUI::add_expert_mode_part( (wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), - (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), - *model, - event_object_selection_changed, - event_object_settings_changed, - event_remove_object, - event_update_scene); %}; +void set_model_events_from_perl(Model *model, + int event_object_selection_changed, + int event_object_settings_changed, + int event_remove_object, + int event_update_scene) + %code%{ Slic3r::GUI::set_model_events_from_perl(*model, + event_object_selection_changed, + event_object_settings_changed, + event_remove_object, + event_update_scene); %}; void set_objects_from_perl( SV *ui_parent, SV *frequently_changed_parameters_sizer, - SV *expert_mode_part_sizer, - SV *scrolled_window_sizer, + SV *info_sizer, SV *btn_export_gcode, - SV *btn_export_stl, SV *btn_reslice, SV *btn_print, SV *btn_send_gcode, @@ -123,10 +118,8 @@ void set_objects_from_perl( SV *ui_parent, %code%{ Slic3r::GUI::set_objects_from_perl( (wxWindow *)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), (wxBoxSizer *)wxPli_sv_2_object(aTHX_ frequently_changed_parameters_sizer, "Wx::BoxSizer"), - (wxBoxSizer *)wxPli_sv_2_object(aTHX_ expert_mode_part_sizer, "Wx::BoxSizer"), - (wxBoxSizer *)wxPli_sv_2_object(aTHX_ scrolled_window_sizer, "Wx::BoxSizer"), + (wxBoxSizer *)wxPli_sv_2_object(aTHX_ info_sizer, "Wx::BoxSizer"), (wxButton *)wxPli_sv_2_object(aTHX_ btn_export_gcode, "Wx::Button"), - (wxButton *)wxPli_sv_2_object(aTHX_ btn_export_stl, "Wx::Button"), (wxButton *)wxPli_sv_2_object(aTHX_ btn_reslice, "Wx::Button"), (wxButton *)wxPli_sv_2_object(aTHX_ btn_print, "Wx::Button"), (wxButton *)wxPli_sv_2_object(aTHX_ btn_send_gcode, "Wx::Button"), @@ -161,6 +154,9 @@ void unselect_objects() void select_current_object(int idx) %code%{ Slic3r::GUI::select_current_object(idx); %}; +void select_current_volume(int idx, int vol_idx) + %code%{ Slic3r::GUI::select_current_volume(idx, vol_idx); %}; + void remove_obj() %code%{ Slic3r::GUI::remove(); %}; @@ -179,16 +175,28 @@ void desktop_open_datadir_folder() void fix_model_by_win10_sdk_gui(ModelObject *model_object_src, Print *print, Model *model_dst) %code%{ Slic3r::fix_model_by_win10_sdk_gui(*model_object_src, *print, *model_dst); %}; -void set_3DScene(SV *scene) - %code%{ Slic3r::GUI::set_3DScene((_3DScene *)wxPli_sv_2_object(aTHX_ scene, "Slic3r::Model::3DScene") ); %}; - void register_on_request_update_callback(SV* callback) %code%{ Slic3r::GUI::g_on_request_update_callback.register_callback(callback); %}; void deregister_on_request_update_callback() %code%{ Slic3r::GUI::g_on_request_update_callback.deregister_callback(); %}; -//void create_double_slider(SV *ui_parent, SV *ui_ds) -// %code%{ Slic3r::GUI::create_double_slider( (wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), -// (wxControl*)wxPli_sv_2_object(aTHX_ ui_ds, "Wx::Control")); %}; +void create_double_slider(SV *ui_parent, SV *ui_sizer, SV *ui_canvas) + %code%{ Slic3r::GUI::create_double_slider( (wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"), + (wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"), + (wxGLCanvas*)wxPli_sv_2_object(aTHX_ ui_canvas, "Wx::GLCanvas")); %}; +void update_double_slider(bool force_sliders_full_range) + %code%{ Slic3r::GUI::update_double_slider(force_sliders_full_range); %}; + +void reset_double_slider() + %code%{ Slic3r::GUI::reset_double_slider(); %}; + +void enable_action_buttons(bool enable) + %code%{ Slic3r::GUI::enable_action_buttons(enable); %}; + +void save_window_size(SV *window, std::string name) + %code%{ Slic3r::GUI::save_window_size((wxTopLevelWindow*)wxPli_sv_2_object(aTHX_ window, "Wx::TopLevelWindow"), name); %}; + +void restore_window_size(SV *window, std::string name) + %code%{ Slic3r::GUI::restore_window_size((wxTopLevelWindow*)wxPli_sv_2_object(aTHX_ window, "Wx::TopLevelWindow"), name); %}; diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index 4c3c5501e..c3e4ba3b7 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -767,6 +767,15 @@ get_first_volume_id(canvas, obj_idx) OUTPUT: RETVAL +int +get_in_object_volume_id(canvas, scene_vol_idx) + SV *canvas; + int scene_vol_idx; + CODE: + RETVAL = _3DScene::get_in_object_volume_id((wxGLCanvas*)wxPli_sv_2_object(aTHX_ canvas, "Wx::GLCanvas"), scene_vol_idx); + OUTPUT: + RETVAL + std::vector load_model(canvas, model, obj_idx) SV *canvas; diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index 4f09fb521..efd6c9ae6 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -17,8 +17,6 @@ %code%{ RETVAL = &THIS->thin_fills; %}; Ref fill_surfaces() %code%{ RETVAL = &THIS->fill_surfaces; %}; - Ref perimeter_surfaces() - %code%{ RETVAL = &THIS->perimeter_surfaces; %}; Polygons bridged() %code%{ RETVAL = THIS->bridged; %}; Ref unsupported_bridge_edges() diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index a5925775d..101faf564 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -340,9 +340,19 @@ ModelMaterial::attributes() %code%{ RETVAL = &THIS->mesh; %}; bool modifier() - %code%{ RETVAL = THIS->modifier; %}; + %code%{ RETVAL = THIS->is_modifier(); %}; void set_modifier(bool modifier) - %code%{ THIS->modifier = modifier; %}; + %code%{ THIS->set_type(modifier ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART); %}; + bool model_part() + %code%{ RETVAL = THIS->is_model_part(); %}; + bool support_enforcer() + %code%{ RETVAL = THIS->is_support_enforcer(); %}; + void set_support_enforcer() + %code%{ THIS->set_type(ModelVolume::SUPPORT_ENFORCER); %}; + bool support_blocker() + %code%{ RETVAL = THIS->is_support_blocker(); %}; + void set_support_blocker() + %code%{ THIS->set_type(ModelVolume::SUPPORT_BLOCKER); %}; size_t split(unsigned int max_extruders); @@ -358,15 +368,28 @@ ModelMaterial::attributes() %code%{ RETVAL = THIS->rotation; %}; double scaling_factor() %code%{ RETVAL = THIS->scaling_factor; %}; +#if ENABLE_MODELINSTANCE_3D_OFFSET + Vec2d* offset() + %code%{ RETVAL = new Vec2d(THIS->get_offset(X), THIS->get_offset(Y)); %}; +#else Ref offset() %code%{ RETVAL = &THIS->offset; %}; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET void set_rotation(double val) %code%{ THIS->rotation = val; THIS->get_object()->invalidate_bounding_box(); %}; void set_scaling_factor(double val) %code%{ THIS->scaling_factor = val; THIS->get_object()->invalidate_bounding_box(); %}; +#if ENABLE_MODELINSTANCE_3D_OFFSET + void set_offset(Vec2d *offset) + %code%{ + THIS->set_offset(X, (*offset)(0)); + THIS->set_offset(Y, (*offset)(1)); + %}; +#else void set_offset(Vec2d *offset) %code%{ THIS->offset = *offset; %}; +#endif // ENABLE_MODELINSTANCE_3D_OFFSET void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; void transform_polygon(Polygon* polygon) const; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index f29cb52fc..0424d8b7b 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -103,6 +103,12 @@ _constant() %code%{ RETVAL = THIS->print_statistics().total_weight; %}; double total_cost() %code%{ RETVAL = THIS->print_statistics().total_cost; %}; + double total_wipe_tower_cost() + %code%{ RETVAL = THIS->print_statistics().total_wipe_tower_cost; %}; + double total_wipe_tower_filament() + %code%{ RETVAL = THIS->print_statistics().total_wipe_tower_filament; %}; + int wipe_tower_number_of_toolchanges() + %code%{ RETVAL = THIS->wipe_tower_data().number_of_toolchanges; %}; PrintObjectPtrs* objects() %code%{ RETVAL = const_cast(&THIS->objects()); %}; void clear_objects(); diff --git a/xs/xsp/ProgressStatusBar.xsp b/xs/xsp/ProgressStatusBar.xsp index c089cfd7c..703a53b67 100644 --- a/xs/xsp/ProgressStatusBar.xsp +++ b/xs/xsp/ProgressStatusBar.xsp @@ -3,6 +3,7 @@ %{ #include #include "slic3r/GUI/ProgressStatusBar.hpp" +#include "slic3r/GUI/GUI.hpp" %} %name{Slic3r::GUI::ProgressStatusBar} class ProgressStatusBar { @@ -37,7 +38,7 @@ %code%{ THIS->embed(); %}; void SetStatusText(const char *txt) - %code%{ THIS->set_status_text(txt); %}; + %code%{ THIS->set_status_text(_(txt)); %}; void SetCancelCallback(SV* callback) %code%{ THIS->m_perl_cancel_callback.register_callback(callback); THIS->show_cancel_button();%}; diff --git a/xs/xsp/XS.xsp b/xs/xsp/XS.xsp index e900532aa..04969a7f9 100644 --- a/xs/xsp/XS.xsp +++ b/xs/xsp/XS.xsp @@ -48,6 +48,11 @@ trace(level, message) CODE: Slic3r::trace(level, message); +void +disable_multi_threading() + CODE: + Slic3r::disable_multi_threading(); + void set_var_dir(dir) char *dir;