diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 6df7343c2..a6b946f9e 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -25,7 +25,6 @@ use Slic3r::GUI::Plater::3D; use Slic3r::GUI::Plater::3DPreview; use Slic3r::GUI::Plater::ObjectPartsPanel; use Slic3r::GUI::Plater::ObjectCutDialog; -use Slic3r::GUI::Plater::ObjectLayersDialog; use Slic3r::GUI::Plater::ObjectSettingsDialog; use Slic3r::GUI::Plater::LambdaObjectDialog; use Slic3r::GUI::Plater::OverrideSettingsPanel; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index ef1409938..561634cd8 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -172,7 +172,7 @@ sub new { $self->{htoolbar}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new($Slic3r::var->("package.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_LAYERS, "Layer heights…", Wx::Bitmap->new($Slic3r::var->("layers.png"), wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_LAYERS, "Layer heights…", Wx::Bitmap->new($Slic3r::var->("variable_layer_height.png"), wxBITMAP_TYPE_PNG), ''); } else { my %tbar_buttons = ( add => "Add…", @@ -1386,7 +1386,7 @@ sub async_apply_config { # reset preview canvases (invalidated contents will be hidden) $self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; $self->{preview3D}->reload_print if $self->{preview3D}; - $self->{ObjectLayersDialog}->reload_preview if $self->{ObjectLayersDialog}; + $self->{AdaptiveLayersDialog}->reload_preview if $self->{AdaptiveLayersDialog}; if ($invalidated) { if (!$Slic3r::GUI::Settings->{_}{background_processing}) { @@ -1463,7 +1463,7 @@ sub stop_background_process { $self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; $self->{preview3D}->reload_print if $self->{preview3D}; - $self->{ObjectLayersDialog}->reload_preview if $self->{ObjectLayersDialog}; + $self->{AdaptiveLayersDialog}->reload_preview if $self->{AdaptiveLayersDialog}; if ($self->{process_thread}) { Slic3r::debugf "Killing background process.\n"; @@ -1610,7 +1610,7 @@ sub on_process_completed { return if $error; $self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; $self->{preview3D}->reload_print if $self->{preview3D}; - $self->{ObjectLayersDialog}->reload_preview if $self->{ObjectLayersDialog}; + $self->{AdaptiveLayersDialog}->reload_preview if $self->{AdaptiveLayersDialog}; # if we have an export filename, start a new thread for exporting G-code if ($self->{export_gcode_output_file}) { @@ -2108,32 +2108,12 @@ sub object_layers_dialog { my $self = shift; my ($obj_idx) = @_; - if (!defined $obj_idx) { - ($obj_idx, undef) = $self->selected_object; - } - - if (!$Slic3r::GUI::have_OpenGL) { - Slic3r::GUI::show_error($self, "Please install the OpenGL modules to use this feature (see build instructions)."); - return; - } - - $self->{ObjectLayersDialog} = Slic3r::GUI::Plater::ObjectLayersDialog->new($self, - object => $self->{objects}[$obj_idx], - model_object => $self->{model}->objects->[$obj_idx], - obj_idx => $obj_idx, - ); - $self->{ObjectLayersDialog}->Show(); - - EVT_CLOSE($self->{ObjectLayersDialog}, sub { - my ($dlg, $event) = @_; - $dlg->Destroy; - $self->{ObjectLayersDialog} = undef; - }); + $self->object_settings_dialog($obj_idx, adaptive_layers => 1); } sub object_settings_dialog { my $self = shift; - my ($obj_idx) = @_; + my ($obj_idx, %params) = @_; if (!defined $obj_idx) { ($obj_idx, undef) = $self->selected_object; @@ -2148,9 +2128,15 @@ sub object_settings_dialog { my $dlg = Slic3r::GUI::Plater::ObjectSettingsDialog->new($self, object => $self->{objects}[$obj_idx], model_object => $model_object, + obj_idx => $obj_idx, ); + # store pointer to the adaptive layer tab to push preview updates + $self->{AdaptiveLayersDialog} = $dlg->{adaptive_layers}; + # and jump directly to the tab if called by "promo-button" + $dlg->{tabpanel}->SetSelection(1) if $params{adaptive_layers}; $self->pause_background_process; $dlg->ShowModal; + $self->{AdaptiveLayersDialog} = undef; # update thumbnail since parts may have changed if ($dlg->PartsChanged) { diff --git a/lib/Slic3r/GUI/Plater/ObjectLayersDialog.pm b/lib/Slic3r/GUI/Plater/ObjectLayersDialog.pm deleted file mode 100644 index e81204a7b..000000000 --- a/lib/Slic3r/GUI/Plater/ObjectLayersDialog.pm +++ /dev/null @@ -1,150 +0,0 @@ -package Slic3r::GUI::Plater::ObjectLayersDialog; -use strict; -use warnings; -use utf8; - -use Slic3r::Geometry qw(PI X Y Z scale unscale); -use Slic3r::Print::State ':steps'; -use List::Util qw(min max sum first); -use Wx qw(wxTheApp :dialog :id :misc :sizer :slider :statictext wxTAB_TRAVERSAL); -use Wx::Event qw(EVT_CLOSE EVT_BUTTON EVT_SLIDER); -use base 'Wx::Dialog'; - -sub new { - my $class = shift; - my ($parent, %params) = @_; - my $self = $class->SUPER::new($parent, -1, $params{object}->name, wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); - $self->{model_object} = $params{model_object}; - my $model_object = $self->{model_object} = $params{model_object}; - my $obj_idx = $self->{obj_idx} = $params{obj_idx}; - my $plater = $self->{plater} = $parent; - my $object = $self->{plater}->{print}->get_object($self->{obj_idx}); - - $self->{update_spline_control} = 0; - - # Initialize 3D toolpaths preview - if ($Slic3r::GUI::have_OpenGL) { - $self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self, $plater->{print}); - $self->{preview3D}->canvas->set_auto_bed_shape; - $self->{preview3D}->canvas->SetSize([500,500]); - $self->{preview3D}->canvas->SetMinSize($self->{preview3D}->canvas->GetSize); - $self->{preview3D}->load_print; - $self->{preview3D}->canvas->zoom_to_volumes; - } - - $self->{splineControl} = Slic3r::GUI::Plater::SplineControl->new($self, Wx::Size->new(150, 200), $model_object); - - my $optgroup; - $optgroup = $self->{optgroup} = Slic3r::GUI::OptionsGroup->new( - parent => $self, - title => 'Adaptive quality %', - on_change => sub { - my ($opt_id) = @_; - # There seems to be an issue with wxWidgets 3.0.2/3.0.3, where the slider - # genates tens of events for a single value change. - # Only trigger the recalculation if the value changes - # or a live preview was activated and the mesh cut is not valid yet. - if ($self->{adaptive_quality} != $optgroup->get_value($opt_id)) { - $self->{adaptive_quality} = $optgroup->get_value($opt_id); - $self->{model_object}->config->set('adaptive_slicing_quality', $optgroup->get_value($opt_id)); - # trigger re-slicing - $self->_trigger_slicing; - } - }, - label_width => 0, - ); - - $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( - opt_id => 'adaptive_slicing_quality', - type => 'slider', - label => '', - default => $object->config->get('adaptive_slicing_quality'), - min => 0, - max => 100, - full_width => 1, - )); - $optgroup->get_field('adaptive_slicing_quality')->set_scale(1); - $self->{adaptive_quality} = $object->config->get('adaptive_slicing_quality'); - # init quality slider - if(!$object->config->get('adaptive_slicing')) { - # disable slider - $optgroup->get_field('adaptive_slicing_quality')->disable; - } - - - my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); - $right_sizer->Add($self->{splineControl}, 1, wxEXPAND | wxALL, 0); - $right_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 0); - - - $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL); - $self->{sizer}->Add($self->{preview3D}, 3, wxEXPAND | wxTOP | wxBOTTOM, 0) if $self->{preview3D}; - $self->{sizer}->Add($right_sizer, 1, wxEXPAND | wxTOP | wxBOTTOM, 10); - - $self->SetSizerAndFit($self->{sizer}); - $self->SetSize([800, 600]); - $self->SetMinSize($self->GetSize); - - # init spline control values - # determine min and max layer height from perimeter extruder capabilities. - my %extruders; - for my $region_id (0 .. ($object->region_count - 1)) { - foreach (qw(perimeter_extruder infill_extruder solid_infill_extruder)) { - my $extruder_id = $self->{plater}->{print}->get_region($region_id)->config->get($_)-1; - $extruders{$extruder_id} = $extruder_id; - } - } - my $min_height = max(map {$self->{plater}->{print}->config->get_at('min_layer_height', $_)} (values %extruders)); - my $max_height = min(map {$self->{plater}->{print}->config->get_at('max_layer_height', $_)} (values %extruders)); - - $self->{splineControl}->set_size_parameters($min_height, $max_height, unscale($object->size->z)); - - - $self->{splineControl}->on_layer_update(sub { - # trigger re-slicing - $self->_trigger_slicing; - }); - - $self->{splineControl}->on_z_indicator(sub { - my ($z) = @_; - $self->{preview3D}->canvas->SetCuttingPlane(Z, $z, []); - $self->{preview3D}->canvas->Render; - }); - - return $self; -} - -sub _trigger_slicing { - my ($self) = @_; - my $object = $self->{plater}->{print}->get_object($self->{obj_idx}); - $self->{plater}->pause_background_process; - $self->{plater}->stop_background_process; - if (!$Slic3r::GUI::Settings->{_}{background_processing}) { - $self->{plater}->statusbar->SetCancelCallback(sub { - $self->{plater}->stop_background_process; - $self->{plater}->statusbar->SetStatusText("Slicing cancelled"); - $self->{plater}->preview_notebook->SetSelection(0); - }); - $self->{plater}->{print}->reload_object($self->{obj_idx}); - $self->{plater}->on_model_change; - $self->{plater}->start_background_process; - }else{ - $self->{plater}->{print}->reload_object($self->{obj_idx}); - $self->{plater}->on_model_change; - $self->{plater}->schedule_background_process; - } -} - -sub reload_preview { - my ($self) = @_; - $self->{splineControl}->update; - $self->{preview3D}->reload_print; - my $object = $self->{plater}->{print}->get_object($self->{obj_idx}); - if($object->layer_count-1 > 0) { - # causes segfault... - my $top_layer = $object->get_layer($object->layer_count-1); - $self->{preview3D}->set_z($top_layer->print_z); - } -} - -1; diff --git a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm index c0979cd13..ba4d9999d 100644 --- a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm @@ -14,11 +14,16 @@ use base 'Wx::Dialog'; sub new { my $class = shift; my ($parent, %params) = @_; - my $self = $class->SUPER::new($parent, -1, "Settings for " . $params{object}->name, wxDefaultPosition, [700,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); + my $self = $class->SUPER::new($parent, -1, "Settings for " . $params{object}->name, wxDefaultPosition, [1000,700], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); $self->{$_} = $params{$_} for keys %params; $self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); $self->{tabpanel}->AddPage($self->{parts} = Slic3r::GUI::Plater::ObjectPartsPanel->new($self->{tabpanel}, model_object => $params{model_object}), "Parts"); + $self->{tabpanel}->AddPage($self->{adaptive_layers} = Slic3r::GUI::Plater::ObjectDialog::AdaptiveLayersTab->new( $self->{tabpanel}, + plater => $parent, + model_object => $params{model_object}, + obj_idx => $params{obj_idx} + ), "Adaptive Layers"); $self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}), "Layers"); my $buttons = $self->CreateStdDialogButtonSizer(wxOK); @@ -62,6 +67,157 @@ sub model_object { return $self->GetParent->GetParent->{model_object}; } +package Slic3r::GUI::Plater::ObjectDialog::AdaptiveLayersTab; +use Slic3r::Geometry qw(X Y Z scale unscale); +use List::Util qw(min max sum first); +use Wx qw(wxTheApp :dialog :id :misc :sizer :systemsettings :statictext wxTAB_TRAVERSAL); +use base 'Slic3r::GUI::Plater::ObjectDialog::BaseTab'; + +sub new { + my $class = shift; + my ($parent, %params) = @_; + my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize); + my $model_object = $self->{model_object} = $params{model_object}; + my $obj_idx = $self->{obj_idx} = $params{obj_idx}; + my $plater = $self->{plater} = $params{plater}; + my $object = $self->{plater}->{print}->get_object($self->{obj_idx}); + + # Initialize 3D toolpaths preview + if ($Slic3r::GUI::have_OpenGL) { + $self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self, $plater->{print}); + $self->{preview3D}->canvas->set_auto_bed_shape; + $self->{preview3D}->canvas->SetSize([500,500]); + $self->{preview3D}->canvas->SetMinSize($self->{preview3D}->canvas->GetSize); + # object already processed? + wxTheApp->CallAfter(sub { + if (!$plater->{processed}) { + $self->_trigger_slicing; + }else{ + $self->{preview3D}->load_print; + $self->{preview3D}->canvas->zoom_to_volumes; + $self->{preview_zoomed} = 1; + } + }); + } + + $self->{splineControl} = Slic3r::GUI::Plater::SplineControl->new($self, Wx::Size->new(150, 200), $model_object); + + my $optgroup; + $optgroup = $self->{optgroup} = Slic3r::GUI::OptionsGroup->new( + parent => $self, + title => 'Adaptive quality %', + on_change => sub { + my ($opt_id) = @_; + # There seems to be an issue with wxWidgets 3.0.2/3.0.3, where the slider + # genates tens of events for a single value change. + # Only trigger the recalculation if the value changes + # or a live preview was activated and the mesh cut is not valid yet. + if ($self->{adaptive_quality} != $optgroup->get_value($opt_id)) { + $self->{adaptive_quality} = $optgroup->get_value($opt_id); + $self->{model_object}->config->set('adaptive_slicing_quality', $optgroup->get_value($opt_id)); + # trigger re-slicing + $self->_trigger_slicing; + } + }, + label_width => 0, + ); + + $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( + opt_id => 'adaptive_slicing_quality', + type => 'slider', + label => '', + default => $object->config->get('adaptive_slicing_quality'), + min => 0, + max => 100, + full_width => 1, + )); + $optgroup->get_field('adaptive_slicing_quality')->set_scale(1); + $self->{adaptive_quality} = $object->config->get('adaptive_slicing_quality'); + # init quality slider + if(!$object->config->get('adaptive_slicing')) { + # disable slider + $optgroup->get_field('adaptive_slicing_quality')->disable; + } + + my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); + $right_sizer->Add($self->{splineControl}, 1, wxEXPAND | wxALL, 0); + $right_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 0); + + + $self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL); + $self->{sizer}->Add($self->{preview3D}, 3, wxEXPAND | wxTOP | wxBOTTOM, 0) if $self->{preview3D}; + $self->{sizer}->Add($right_sizer, 1, wxEXPAND | wxTOP | wxBOTTOM, 10); + + $self->SetSizerAndFit($self->{sizer}); + + # init spline control values + # determine min and max layer height from perimeter extruder capabilities. + my %extruders; + for my $region_id (0 .. ($object->region_count - 1)) { + foreach (qw(perimeter_extruder infill_extruder solid_infill_extruder)) { + my $extruder_id = $self->{plater}->{print}->get_region($region_id)->config->get($_)-1; + $extruders{$extruder_id} = $extruder_id; + } + } + my $min_height = max(map {$self->{plater}->{print}->config->get_at('min_layer_height', $_)} (values %extruders)); + my $max_height = min(map {$self->{plater}->{print}->config->get_at('max_layer_height', $_)} (values %extruders)); + + $self->{splineControl}->set_size_parameters($min_height, $max_height, unscale($object->size->z)); + + $self->{splineControl}->on_layer_update(sub { + # trigger re-slicing + $self->_trigger_slicing; + }); + + $self->{splineControl}->on_z_indicator(sub { + my ($z) = @_; + $self->{preview3D}->canvas->SetCuttingPlane(Z, $z, []); + $self->{preview3D}->canvas->Render; + }); + + return $self; +} + +# This is called by the plater after processing to update the preview and spline +sub reload_preview { + my ($self) = @_; + $self->{splineControl}->update; + $self->{preview3D}->reload_print; + my $object = $self->{plater}->{print}->get_object($self->{obj_idx}); + if($object->layer_count-1 > 0) { + my $top_layer = $object->get_layer($object->layer_count-1); + $self->{preview3D}->set_z($top_layer->print_z); + if(!$self->{preview_zoomed}) { + $self->{preview3D}->canvas->set_auto_bed_shape; + $self->{preview3D}->canvas->zoom_to_volumes; + $self->{preview_zoomed} = 1; + } + } +} + +# Trigger background slicing at the plater +sub _trigger_slicing { + my ($self) = @_; + my $object = $self->{plater}->{print}->get_object($self->{obj_idx}); + $self->{plater}->pause_background_process; + $self->{plater}->stop_background_process; + if (!$Slic3r::GUI::Settings->{_}{background_processing}) { + $self->{plater}->statusbar->SetCancelCallback(sub { + $self->{plater}->stop_background_process; + $self->{plater}->statusbar->SetStatusText("Slicing cancelled"); + $self->{plater}->preview_notebook->SetSelection(0); + }); + $self->{plater}->{print}->reload_object($self->{obj_idx}); + $self->{plater}->on_model_change; + $self->{plater}->start_background_process; + }else{ + $self->{plater}->{print}->reload_object($self->{obj_idx}); + $self->{plater}->on_model_change; + $self->{plater}->schedule_background_process; + } +} + + package Slic3r::GUI::Plater::ObjectDialog::LayersTab; use Wx qw(:dialog :id :misc :sizer :systemsettings); use Wx::Grid; diff --git a/lib/Slic3r/GUI/Plater/SplineControl.pm b/lib/Slic3r/GUI/Plater/SplineControl.pm index 6de3ba0b3..f940ff11d 100644 --- a/lib/Slic3r/GUI/Plater/SplineControl.pm +++ b/lib/Slic3r/GUI/Plater/SplineControl.pm @@ -16,6 +16,7 @@ sub new { my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, $size, wxTAB_TRAVERSAL); $self->{object} = $object; + $self->{is_valid} = 0; # This has only effect on MacOS. On Windows and Linux/GTK, the background is painted by $self->repaint(). $self->SetBackgroundColour(Wx::wxWHITE); @@ -74,31 +75,33 @@ sub repaint { $dc->DrawLabel(sprintf('%.4g', $self->{min_layer_height}), Wx::Rect->new(0, $size[1]/2, $size[0], $size[1]/2), wxALIGN_LEFT | wxALIGN_BOTTOM); $dc->DrawLabel(sprintf('%.4g', $self->{max_layer_height}), Wx::Rect->new(0, $size[1]/2, $size[0], $size[1]/2), wxALIGN_RIGHT | wxALIGN_BOTTOM); + if($self->{is_valid}) { - # draw original spline as reference - if($self->{original_height_spline}) { - #draw spline - $self->_draw_layer_height_spline($dc, $self->{original_height_spline}, $self->{original_pen}); - } - - # draw interactive (currently modified by the user) layers as lines and spline - if($self->{interactive_height_spline}) { - # draw layer lines - my @interpolated_layers = @{$self->{interactive_height_spline}->getInterpolatedLayers}; - $self->_draw_layers_as_lines($dc, $self->{interactive_pen}, \@interpolated_layers); - - #draw spline - $self->_draw_layer_height_spline($dc, $self->{interactive_height_spline}, $self->{interactive_pen}); + # draw original spline as reference + if($self->{original_height_spline}) { + #draw spline + $self->_draw_layer_height_spline($dc, $self->{original_height_spline}, $self->{original_pen}); + } + + # draw interactive (currently modified by the user) layers as lines and spline + if($self->{interactive_height_spline}) { + # draw layer lines + my @interpolated_layers = @{$self->{interactive_height_spline}->getInterpolatedLayers}; + $self->_draw_layers_as_lines($dc, $self->{interactive_pen}, \@interpolated_layers); + + #draw spline + $self->_draw_layer_height_spline($dc, $self->{interactive_height_spline}, $self->{interactive_pen}); + } + + # draw resulting layers as lines + unless($self->{interactive_heights}) { + $self->_draw_layers_as_lines($dc, $self->{resulting_pen}, $self->{interpolated_layers}); + } + + # Always draw current BSpline, gives a reference during a modification + $self->_draw_layer_height_spline($dc, $self->{object}->layer_height_spline, $self->{line_pen}); } - # draw resulting layers as lines - unless($self->{interactive_heights}) { - $self->_draw_layers_as_lines($dc, $self->{resulting_pen}, $self->{interpolated_layers}); - } - - # Always draw current BSpline, gives a reference during a modification - $self->_draw_layer_height_spline($dc, $self->{object}->layer_height_spline, $self->{line_pen}); - $event->Skip; } @@ -120,7 +123,7 @@ sub set_size_parameters { sub update { my $self = shift; - if($self->{object}->layer_height_spline->layersUpdated || !$self->{heights}) { + if(($self->{object}->layer_height_spline->layersUpdated || !$self->{heights}) && $self->{object}->layer_height_spline->hasData) { $self->{original_height_spline} = $self->{object}->layer_height_spline->clone; # make a copy to display the unmodified original spline $self->{original_layers} = $self->{object}->layer_height_spline->getOriginalLayers; $self->{interpolated_layers} = $self->{object}->layer_height_spline->getInterpolatedLayers; # Initialize to current values @@ -131,6 +134,7 @@ sub update { foreach my $z (@{$self->{original_layers}}) { push (@{$self->{heights}}, $self->{object}->layer_height_spline->getLayerHeightAt($z)); } + $self->{is_valid} = 1; } $self->Refresh; }