diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 41e5fa975..ae166b6c5 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -76,7 +76,7 @@ sub load { my ($file) = @_; my $ini = __PACKAGE__->read_ini($file); - my $config = __PACKAGE__->new; + my $config = $class->new; foreach my $opt_key (keys %{$ini->{_}}) { ($opt_key, my $value) = _handle_legacy($opt_key, $ini->{_}{$opt_key}); next if !defined $opt_key; @@ -88,7 +88,7 @@ sub load { sub clone { my $self = shift; - my $new = __PACKAGE__->new; + my $new = (ref $self)->new; $new->apply($self); return $new; } @@ -180,13 +180,14 @@ sub equals { return @{ $self->diff($other) } == 0; } +# this will *ignore* options not present in both configs sub diff { my ($self, $other) = @_; my @diff = (); foreach my $opt_key (sort @{$self->get_keys}) { push @diff, $opt_key - if !$other->has($opt_key) || $other->serialize($opt_key) ne $self->serialize($opt_key); + if $other->has($opt_key) && $other->serialize($opt_key) ne $self->serialize($opt_key); } return [@diff]; } @@ -265,27 +266,11 @@ sub validate { die "Invalid value for --infill-every-layers\n" if $self->infill_every_layers !~ /^\d+$/ || $self->infill_every_layers < 1; - # --scale - die "Invalid value for --scale\n" - if $self->scale <= 0; - # --bed-size die "Invalid value for --bed-size\n" if !ref $self->bed_size && (!$self->bed_size || $self->bed_size !~ /^\d+,\d+$/); - # --duplicate-grid - die "Invalid value for --duplicate-grid\n" - if !ref $self->duplicate_grid - && (!$self->duplicate_grid || $self->duplicate_grid !~ /^\d+,\d+$/); - - # --duplicate - die "Invalid value for --duplicate or --duplicate-grid\n" - if !$self->duplicate || $self->duplicate < 1 || !$self->duplicate_grid - || (grep !$_, @{$self->duplicate_grid}); - die "Use either --duplicate or --duplicate-grid (using both doesn't make sense)\n" - if $self->duplicate > 1 && $self->duplicate_grid && (grep $_ && $_ > 1, @{$self->duplicate_grid}); - # --skirt-height die "Invalid value for --skirt-height\n" if $self->skirt_height < -1; # -1 means as tall as the object diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index e0b0fcf4a..1fb606c3c 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -754,11 +754,15 @@ sub export_gcode2 { } if $Slic3r::have_threads; my $print = $self->{print}; - $print->apply_config($config); - $print->apply_extra_variables($extra_variables); + eval { - $print->config->validate; + # this will throw errors if config is not valid + $config->validate; + + $print->apply_config($config); + $print->apply_extra_variables($extra_variables); + $print->validate; { diff --git a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm index e681d7238..b2f5c3ce0 100644 --- a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm @@ -114,7 +114,7 @@ sub update_optgroup { my $config = $self->model_object->config; my %categories = (); - foreach my $opt_key (keys %$config) { + foreach my $opt_key (@{$config->get_keys}) { my $category = $Slic3r::Config::Options->{$opt_key}{category}; $categories{$category} ||= []; push @{$categories{$category}}, $opt_key; @@ -288,31 +288,41 @@ sub new { # get unique materials used in this object $self->{materials} = [ $self->model_object->unique_materials ]; - # build an OptionsGroup - $self->{mapping} = { - (map { $self->{materials}[$_] => $_+1 } 0..$#{ $self->{materials} }), # defaults - %{$self->model_object->material_mapping}, - }; - my $optgroup = Slic3r::GUI::OptionsGroup->new( - parent => $self, - title => 'Extruders', - label_width => 300, - options => [ - map { - my $i = $_; - my $material_id = $self->{materials}[$i]; - { - opt_key => "material_extruder_$_", - type => 'i', - label => $self->model_object->model->get_material_name($material_id), - min => 1, - default => $self->{mapping}{$material_id}, - on_change => sub { $self->{mapping}{$material_id} = $_[0] }, - } - } 0..$#{ $self->{materials} } - ], - ); - $self->{sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10); + # get the current mapping + $self->{mapping} = {}; + foreach my $material_id (@{ $self->{materials}}) { + my $config = $self->model_object->model->materials->{ $material_id }->config; + $self->{mapping}{$material_id} = ($config->perimeter_extruder // 0) + 1; + } + + if (@{$self->{materials}} > 0) { + # build an OptionsGroup + my $optgroup = Slic3r::GUI::OptionsGroup->new( + parent => $self, + title => 'Extruders', + label_width => 300, + options => [ + map { + my $i = $_; + my $material_id = $self->{materials}[$i]; + { + opt_key => "material_extruder_$_", + type => 'i', + label => $self->model_object->model->get_material_name($material_id), + min => 1, + default => $self->{mapping}{$material_id} // 1, + on_change => sub { $self->{mapping}{$material_id} = $_[0] }, + } + } 0..$#{ $self->{materials} } + ], + ); + $self->{sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10); + } else { + my $label = Wx::StaticText->new($self, -1, "This object does not contain named materials.", + wxDefaultPosition, [-1, 25]); + $label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); + $self->{sizer}->Add($label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10); + } $self->SetSizer($self->{sizer}); $self->{sizer}->SetSizeHints($self); @@ -324,10 +334,10 @@ sub Closing { my $self = shift; # save mappings into the plater object - foreach my $volume (@{$self->model_object}) { + foreach my $volume (@{$self->model_object->volumes}) { if (defined $volume->material_id) { my $config = $self->model_object->model->materials->{ $volume->material_id }->config; - $config->set('extruder', $self->{mapping}{ $volume->material_id }); + $config->set('extruder', $self->{mapping}{ $volume->material_id }-1); } } } diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 401deb47e..3cbd97493 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -54,10 +54,11 @@ sub add_object { if (defined $volume->material_id) { # merge material attributes (should we rename materials in case of duplicates?) - $self->set_material($volume->material_id, { - %{ $object->model->materials->{$volume->material_id} }, - %{ $self->materials->{$volume->material_id} || {} }, - }); + my %attributes = %{ $object->model->materials->{$volume->material_id}->attributes }; + if (exists $self->materials->{$volume->material_id}) { + %attributes = (%attributes, %{ $self->materials->{$volume->material_id}->attributes }); + } + $self->set_material($volume->material_id, {%attributes}); } } @@ -478,7 +479,8 @@ sub unique_materials { my $self = shift; my %materials = (); - $materials{ $_->material_id // '_' } = 1 for @{$self->volumes}; + $materials{ $_->material_id } = 1 + for grep { defined $_->material_id } @{$self->volumes}; return sort keys %materials; } diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index d44a06b6c..c0ed2e11f 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -31,9 +31,87 @@ has 'brim' => (is => 'rw', default => sub { Slic3r::ExtrusionPath::Collection->n sub apply_config { my ($self, $config) = @_; - $self->config->apply_dynamic($config); + # handle changes to print config + my $print_diff = $self->config->diff($config); + if (@$print_diff) { + $self->config->apply_dynamic($config); + + # TODO: only invalidate changed steps + $self->_state->invalidate_all; + } + + # handle changes to object config defaults $self->default_object_config->apply_dynamic($config); + foreach my $object (@{$self->objects}) { + # we don't assume that $config contains a full ObjectConfig, + # so we base it on the current print-wise default + my $new = $self->default_object_config->clone; + + # we override the new config with object-specific options + $new->apply_dynamic($object->model_object->config); + + # check whether the new config is different from the current one + my $diff = $object->config->diff($new); + if (@$diff) { + $object->config->apply($new); + # TODO: only invalidate changed steps + $object->_state->invalidate_all; + } + } + + # handle changes to regions config defaults $self->default_region_config->apply_dynamic($config); + + # check whether after applying the new region config defaults to all existing regions + # they still have distinct configs; if not we need to re-add objects in order to + # merge the now-equal regions + + # first compute the transformed region configs + my @new_region_configs = (); + foreach my $region_id (0..$#{$self->regions}) { + my $new = $self->default_region_config->clone; + foreach my $object (@{$self->objects}) { + foreach my $volume_id (@{ $object->region_volumes->[$region_id] }) { + my $volume = $object->model_object->volumes->[$volume_id]; + next if !defined $volume->material_id; + my $material = $object->model_object->model->materials->{$volume->material_id}; + $new->apply_dynamic($material->config); + } + } + push @new_region_configs, $new; + } + + # then find the first pair of identical configs + my $have_identical_configs = 0; + my $region_diff = []; + for my $i (0..$#new_region_configs) { + for my $j (($i+1)..$#new_region_configs) { + if ($new_region_configs[$i]->equals($new_region_configs[$j])) { + $have_identical_configs = 1; + } + } + my $diff = $self->regions->[$i]->config->diff($new_region_configs[$i]); + push @$region_diff, @$diff; + } + + if ($have_identical_configs) { + # okay, the current subdivision of regions does not make sense anymore. + # we need to remove all objects and re-add them + my @model_objects = map $_->model_object, @{$self->object}; + $self->delete_all_objects; + $self->add_model_object($_) for @model_objects; + } elsif (@$region_diff > 0) { + # if there are no identical regions even after applying the change in + # region config defaults, but at least one region config option changed, + # store the new region configs and invalidate + # the affected step(s) + foreach my $region_id (0..$#{$self->regions}) { + $self->regions->[$region_id]->config->apply($new_region_configs[$region_id]); + } + + # TODO: only invalidate changed steps + $_->_state->invalidate_all for @{$self->objects}; + } } sub has_support_material { @@ -991,7 +1069,7 @@ sub auto_assign_extruders { if (defined $volume->material_id) { my $material = $model_object->model->materials->{ $volume->material_id }; my $config = $material->config; - $config->set_ifndef('perimeters_extruder', $i); + $config->set_ifndef('perimeter_extruder', $i); $config->set_ifndef('infill_extruder', $i); $config->set_ifndef('support_material_extruder', $i); $config->set_ifndef('support_material_interface_extruder', $i); diff --git a/xs/src/Config.hpp b/xs/src/Config.hpp index 9b9a7bf95..3af99a448 100644 --- a/xs/src/Config.hpp +++ b/xs/src/Config.hpp @@ -354,6 +354,7 @@ class ConfigOptionDef public: ConfigOptionType type; std::string label; + std::string full_label; std::string category; std::string tooltip; std::string sidetext; @@ -361,7 +362,6 @@ class ConfigOptionDef std::string scope; t_config_option_key ratio_over; bool multiline; - bool full_label; bool full_width; bool readonly; int height; diff --git a/xs/src/Print.cpp b/xs/src/Print.cpp index f5dd9fe7d..539206c49 100644 --- a/xs/src/Print.cpp +++ b/xs/src/Print.cpp @@ -33,4 +33,11 @@ PrintState::invalidate(PrintStep step) this->_done.erase(step); } +void +PrintState::invalidate_all() +{ + this->_started.clear(); + this->_done.clear(); +} + } diff --git a/xs/src/Print.hpp b/xs/src/Print.hpp index a79d544df..9e93408b2 100644 --- a/xs/src/Print.hpp +++ b/xs/src/Print.hpp @@ -22,6 +22,7 @@ class PrintState void set_started(PrintStep step); void set_done(PrintStep step); void invalidate(PrintStep step); + void invalidate_all(); }; } diff --git a/xs/src/PrintConfig.hpp b/xs/src/PrintConfig.hpp index dd9f2633c..8ac01f504 100644 --- a/xs/src/PrintConfig.hpp +++ b/xs/src/PrintConfig.hpp @@ -61,7 +61,7 @@ class PrintConfigDef Options["bed_temperature"].tooltip = "Bed temperature for layers after the first one. Set this to zero to disable bed temperature control commands in the output."; Options["bed_temperature"].sidetext = "°C"; Options["bed_temperature"].cli = "bed-temperature=i"; - Options["bed_temperature"].full_label = true; + Options["bed_temperature"].full_label = "Bed temperature"; Options["bed_temperature"].max = 300; Options["bottom_solid_layers"].type = coInt; @@ -70,7 +70,7 @@ class PrintConfigDef Options["bottom_solid_layers"].tooltip = "Number of solid layers to generate on bottom surfaces."; Options["bottom_solid_layers"].cli = "bottom-solid-layers=i"; Options["bottom_solid_layers"].scope = "object"; - Options["bottom_solid_layers"].full_label = true; + Options["bottom_solid_layers"].full_label = "Bottom solid layers"; Options["bridge_acceleration"].type = coFloat; Options["bridge_acceleration"].label = "Bridge"; @@ -346,7 +346,7 @@ class PrintConfigDef Options["infill_every_layers"].sidetext = "layers"; Options["infill_every_layers"].cli = "infill-every-layers=i"; Options["infill_every_layers"].scope = "object"; - Options["infill_every_layers"].full_label = true; + Options["infill_every_layers"].full_label = "Combine infill every n layers"; Options["infill_every_layers"].min = 1; Options["infill_extruder"].type = coInt; @@ -715,7 +715,7 @@ class PrintConfigDef Options["support_material_enforce_layers"].sidetext = "layers"; Options["support_material_enforce_layers"].cli = "support-material-enforce-layers=f"; Options["support_material_enforce_layers"].scope = "object"; - Options["support_material_enforce_layers"].full_label = true; + Options["support_material_enforce_layers"].full_label = "Enforce support for the first n layers"; Options["support_material_extruder"].type = coInt; Options["support_material_extruder"].label = "Support material extruder"; @@ -789,7 +789,7 @@ class PrintConfigDef Options["temperature"].tooltip = "Extruder temperature for layers after the first one. Set this to zero to disable temperature control commands in the output."; Options["temperature"].sidetext = "°C"; Options["temperature"].cli = "temperature=i@"; - Options["temperature"].full_label = true; + Options["temperature"].full_label = "Temperature"; Options["temperature"].max = 400; Options["thin_walls"].type = coBool; @@ -835,7 +835,7 @@ class PrintConfigDef Options["top_solid_layers"].tooltip = "Number of solid layers to generate on top surfaces."; Options["top_solid_layers"].cli = "top-solid-layers=i"; Options["top_solid_layers"].scope = "object"; - Options["top_solid_layers"].full_label = true; + Options["top_solid_layers"].full_label = "Top solid layers"; Options["travel_speed"].type = coFloat; Options["travel_speed"].label = "Travel"; diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index b7f6f3ec8..18deda295 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -142,6 +142,7 @@ print_config_def() } (void)hv_stores( hv, "type", newSVpv(opt_type, 0) ); (void)hv_stores( hv, "label", newSVpvn(optdef->label.c_str(), optdef->label.length()) ); + (void)hv_stores( hv, "full_label", newSVpvn(optdef->full_label.c_str(), optdef->full_label.length()) ); (void)hv_stores( hv, "category", newSVpvn(optdef->category.c_str(), optdef->category.length()) ); (void)hv_stores( hv, "tooltip", newSVpvn(optdef->tooltip.c_str(), optdef->tooltip.length()) ); (void)hv_stores( hv, "sidetext", newSVpvn(optdef->sidetext.c_str(), optdef->sidetext.length()) ); @@ -149,7 +150,6 @@ print_config_def() (void)hv_stores( hv, "scope", newSVpvn(optdef->scope.c_str(), optdef->scope.length()) ); (void)hv_stores( hv, "ratio_over", newSVpvn(optdef->ratio_over.c_str(), optdef->ratio_over.length()) ); (void)hv_stores( hv, "multiline", newSViv(optdef->multiline ? 1 : 0) ); - (void)hv_stores( hv, "full_label", newSViv(optdef->full_label ? 1 : 0) ); (void)hv_stores( hv, "full_width", newSViv(optdef->full_width ? 1 : 0) ); (void)hv_stores( hv, "readonly", newSViv(optdef->readonly ? 1 : 0) ); (void)hv_stores( hv, "height", newSViv(optdef->height) ); diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 0eef527ea..25f52a71c 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -13,6 +13,7 @@ void set_started(PrintStep step); void set_done(PrintStep step); void invalidate(PrintStep step); + void invalidate_all(); %{ %}