diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index f1269debbb..6c34572f6f 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -1,7 +1,7 @@ package Slic3r::GCode; use Moo; -use List::Util qw(min first); +use List::Util qw(min max first); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon points_coincide PI X Y B); @@ -27,6 +27,7 @@ has 'speed' => (is => 'rw'); has '_extrusion_axis' => (is => 'rw'); has '_retract_lift' => (is => 'rw'); has 'extruders' => (is => 'ro', default => sub {{}}); +has 'multiple_extruders' => (is => 'rw', default => sub {0}); has 'extruder' => (is => 'rw'); has 'speeds' => (is => 'lazy'); # mm/min has 'external_mp' => (is => 'rw'); @@ -55,6 +56,11 @@ sub set_extruders { $self->extruders->{$i} = my $e = Slic3r::Extruder->new_from_config($self->print_config, $i); $self->enable_wipe(1) if $e->wipe; } + + # we enable support for multiple extruder if any extruder greater than 0 is used + # (even if prints only uses that one) since we need to output Tx commands + # first extruder has index 0 + $self->multiple_extruders(max(@$extruder_ids) > 0); } sub _build_speeds { @@ -81,11 +87,6 @@ my %role_speeds = ( &EXTR_ROLE_GAPFILL => 'gap_fill', ); -sub multiple_extruders { - my $self = shift; - return (keys %{$self->extruders}) > 1; -} - sub set_shift { my ($self, @shift) = @_; diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index 31b7dc03db..b911c8141c 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -162,7 +162,7 @@ sub selection_changed { $material //= $volume->assign_unique_material; $self->{staticbox}->SetLabel('Part Settings'); $self->{settings_panel}->enable; - $self->{settings_panel}->set_opt_keys(Slic3r::Config::PrintRegion->new->get_keys); + $self->{settings_panel}->set_opt_keys([ 'extruder', @{Slic3r::Config::PrintRegion->new->get_keys} ]); $self->{settings_panel}->set_config($material->config); } elsif ($itemData->{type} eq 'object') { # select all object volumes in 3D preview @@ -174,7 +174,7 @@ sub selection_changed { $self->{staticbox}->SetLabel('Object Settings'); $self->{settings_panel}->enable; $self->{settings_panel}->set_opt_keys( - [ map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new ] + [ 'extruder', map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new ] ); $self->{settings_panel}->set_config($self->{model_object}->config); } diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 19af91f481..2fdf93559a 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -528,7 +528,7 @@ sub build { $self->add_options_page('Multiple Extruders', 'funnel.png', optgroups => [ { - title => 'Override extruders', + title => 'Extruders', options => [qw(perimeter_extruder infill_extruder support_material_extruder support_material_interface_extruder)], }, { diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index b2a3be6250..c9b52f6e80 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -32,6 +32,9 @@ has 'brim' => (is => 'rw', default => sub { Slic3r::ExtrusionPath::Collection->n sub apply_config { my ($self, $config) = @_; + $config = $config->clone; + $config->normalize; + # apply variables to placeholder parser $self->placeholder_parser->apply_config($config); @@ -52,7 +55,9 @@ sub apply_config { my $new = $self->default_object_config->clone; # we override the new config with object-specific options - $new->apply_dynamic($object->model_object->config); + my $model_object_config = $object->model_object->config->clone; + $model_object_config->normalize; + $new->apply_dynamic($model_object_config); # check whether the new config is different from the current one my $diff = $object->config->diff($new); @@ -66,55 +71,42 @@ sub apply_config { # 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; + # All regions now have distinct settings. + # Check whether applying the new region config defaults we'd get different regions. + my $rearrange_regions = 0; + REGION: foreach my $region_id (0..$#{$self->regions}) { 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; + + my $new = $self->default_region_config->clone; + { + my $model_object_config = $object->model_object->config->clone; + $model_object_config->normalize; + $new->apply_dynamic($model_object_config); + } + if (defined $volume->material_id) { + my $material_config = $object->model_object->model->materials->{$volume->material_id}->config->clone; + $material_config->normalize; + $new->apply_dynamic($material_config); + } + if (!$new->equals($self->regions->[$region_id]->config)) { + $rearrange_regions = 1; + last REGION; + } } } } - # 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. + # Some optimization is possible: if the volumes-regions mappings don't change + # but still region configs are changed somehow, we could just apply the diff + # and invalidate the affected steps. + if ($rearrange_regions) { + # 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->objects}; $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}; } } @@ -131,6 +123,9 @@ sub add_model_object { my $self = shift; my ($object, $obj_idx) = @_; + my $object_config = $object->config->clone; + $object_config->normalize; + my %volumes = (); # region_id => [ volume_id, ... ] foreach my $volume_id (0..$#{$object->volumes}) { my $volume = $object->volumes->[$volume_id]; @@ -140,10 +135,11 @@ sub add_model_object { $config->apply($self->default_region_config); # override the defaults with per-object config and then with per-material config - $config->apply_dynamic($object->config); + $config->apply_dynamic($object_config); if (defined $volume->material_id) { - my $material_config = $object->model->materials->{ $volume->material_id }->config; + my $material_config = $object->model->materials->{ $volume->material_id }->config->clone; + $material_config->normalize; $config->apply_dynamic($material_config); } @@ -182,7 +178,7 @@ sub add_model_object { # apply config to print object $o->config->apply($self->default_object_config); - $o->config->apply_dynamic($object->config); + $o->config->apply_dynamic($object_config); # store print object at the given position if (defined $obj_idx) { @@ -1102,6 +1098,9 @@ sub invalidate_step { sub auto_assign_extruders { my ($self, $model_object) = @_; + # only assign extruders if object has more than one volume + return if @{$model_object->volumes} == 1; + my $extruders = scalar @{ $self->config->nozzle_diameter }; foreach my $i (0..$#{$model_object->volumes}) { my $volume = $model_object->volumes->[$i]; diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index b9d3d9aa1c..e4663afbc9 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -108,7 +108,8 @@ sub model { my $model = Slic3r::Model->new; my $object = $model->add_object(input_file => "${model_name}.stl"); - $object->add_volume(mesh => mesh($model_name, %params)); + $model->set_material($model_name); + $object->add_volume(mesh => mesh($model_name, %params), material_id => $model_name); $object->add_instance( offset => [0,0], rotation => $params{rotation} // 0, @@ -134,7 +135,10 @@ sub init_print { } $model->arrange_objects($print->config->min_object_distance); $model->center_instances_around_point($print->config->print_center); - $print->add_model_object($_) for @{$model->objects}; + foreach my $model_object (@{$model->objects}) { + $print->auto_assign_extruders($model_object); + $print->add_model_object($model_object); + } } $print->validate; diff --git a/t/print.t b/t/print.t index 746ce5d3c2..863c17252e 100644 --- a/t/print.t +++ b/t/print.t @@ -1,4 +1,4 @@ -use Test::More tests => 3; +use Test::More tests => 5; use strict; use warnings; @@ -35,16 +35,24 @@ use Slic3r::Test; my $config = Slic3r::Config->new_from_defaults; # user adds one object to the plater - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $print = Slic3r::Test::init_print(my $model = Slic3r::Test::model('20mm_cube'), config => $config); - # user sets a per-object option - $print->objects->[0]->config->set('fill_density', 100); + # user sets a per-region option + $print->objects->[0]->model_object->config->set('fill_density', 100); $print->reload_object(0); # user exports G-code, thus the default config is reapplied $print->apply_config($config); - is $print->objects->[0]->config->fill_density, 100, 'apply_config() does not override per-object settings'; + is $print->regions->[0]->config->fill_density, 100, 'apply_config() does not override per-object settings'; + + # user assigns object extruders + $print->objects->[0]->model_object->config->set('extruder', 3); + $print->objects->[0]->model_object->config->set('perimeter_extruder', 2); + $print->reload_object(0); + + is $print->regions->[0]->config->infill_extruder, 3, 'extruder setting is correctly expanded'; + is $print->regions->[0]->config->perimeter_extruder, 2, 'extruder setting does not override explicitely specified extruders'; } __END__ diff --git a/xs/src/PrintConfig.cpp b/xs/src/PrintConfig.cpp index 99dd8535ae..711740897d 100644 --- a/xs/src/PrintConfig.cpp +++ b/xs/src/PrintConfig.cpp @@ -4,44 +4,4 @@ namespace Slic3r { t_optiondef_map PrintConfigDef::def = PrintConfigDef::build_def(); -void -StaticPrintConfig::prepare_extruder_option(const t_config_option_key opt_key, DynamicPrintConfig& other) -{ - // don't apply role-based extruders if their value is zero - if (other.has(opt_key) && other.option(opt_key)->getInt() == 0) - other.erase(opt_key); - - // only apply default extruder if our role-based value is zero - // (i.e. default extruder has the lowest priority among all other values) - if (other.has("extruder")) { - int extruder = other.option("extruder")->getInt(); - if (extruder > 0) { - if (!other.has(opt_key) && this->option(opt_key)->getInt() == 0) - other.option(opt_key, true)->setInt(extruder); - } - } -} - -void -PrintObjectConfig::apply(const ConfigBase &other, bool ignore_nonexistent) { - DynamicPrintConfig other_clone; - other_clone.apply(other); - - this->prepare_extruder_option("support_material_extruder", other_clone); - this->prepare_extruder_option("support_material_interface_extruder", other_clone); - - StaticConfig::apply(other_clone, ignore_nonexistent); -}; - -void -PrintRegionConfig::apply(const ConfigBase &other, bool ignore_nonexistent) { - DynamicPrintConfig other_clone; - other_clone.apply(other); - - this->prepare_extruder_option("infill_extruder", other_clone); - this->prepare_extruder_option("perimeter_extruder", other_clone); - - StaticConfig::apply(other_clone, ignore_nonexistent); -}; - } diff --git a/xs/src/PrintConfig.hpp b/xs/src/PrintConfig.hpp index 26012c1bfc..646c525983 100644 --- a/xs/src/PrintConfig.hpp +++ b/xs/src/PrintConfig.hpp @@ -399,10 +399,9 @@ class PrintConfigDef Options["infill_extruder"].type = coInt; Options["infill_extruder"].label = "Infill extruder"; Options["infill_extruder"].category = "Extruders"; - Options["infill_extruder"].tooltip = "The extruder to use when printing infill. First extruder is 1, while zero means use default extruder."; - Options["infill_extruder"].sidetext = "(leave 0 for default)"; + Options["infill_extruder"].tooltip = "The extruder to use when printing infill."; Options["infill_extruder"].cli = "infill-extruder=i"; - Options["infill_extruder"].min = 0; + Options["infill_extruder"].min = 1; Options["infill_extrusion_width"].type = coFloatOrPercent; Options["infill_extrusion_width"].label = "Infill"; @@ -524,11 +523,10 @@ class PrintConfigDef Options["perimeter_extruder"].type = coInt; Options["perimeter_extruder"].label = "Perimeter extruder"; Options["perimeter_extruder"].category = "Extruders"; - Options["perimeter_extruder"].tooltip = "The extruder to use when printing perimeters. First extruder is 1, while zero means use default extruder."; - Options["perimeter_extruder"].sidetext = "(leave 0 for default)"; + Options["perimeter_extruder"].tooltip = "The extruder to use when printing perimeters. First extruder is 1."; Options["perimeter_extruder"].cli = "perimeter-extruder=i"; Options["perimeter_extruder"].aliases.push_back("perimeters_extruder"); - Options["perimeter_extruder"].min = 0; + Options["perimeter_extruder"].min = 1; Options["perimeter_extrusion_width"].type = coFloatOrPercent; Options["perimeter_extrusion_width"].label = "Perimeters"; @@ -774,10 +772,9 @@ class PrintConfigDef Options["support_material_extruder"].type = coInt; Options["support_material_extruder"].label = "Support material extruder"; Options["support_material_extruder"].category = "Extruders"; - Options["support_material_extruder"].tooltip = "The extruder to use when printing support material. This affects brim and raft too. First extruder is 1, while zero means use default extruder."; - Options["support_material_extruder"].sidetext = "(leave 0 for default)"; + Options["support_material_extruder"].tooltip = "The extruder to use when printing support material. This affects brim and raft too."; Options["support_material_extruder"].cli = "support-material-extruder=i"; - Options["support_material_extruder"].min = 0; + Options["support_material_extruder"].min = 1; Options["support_material_extrusion_width"].type = coFloatOrPercent; Options["support_material_extrusion_width"].label = "Support material"; @@ -789,10 +786,9 @@ class PrintConfigDef Options["support_material_interface_extruder"].type = coInt; Options["support_material_interface_extruder"].label = "Support material interface extruder"; Options["support_material_interface_extruder"].category = "Extruders"; - Options["support_material_interface_extruder"].tooltip = "The extruder to use when printing support material interface. This affects raft too. First extruder is 1, while zero means use default extruder."; - Options["support_material_interface_extruder"].sidetext = "(leave 0 for default)"; + Options["support_material_interface_extruder"].tooltip = "The extruder to use when printing support material interface. This affects raft too."; Options["support_material_interface_extruder"].cli = "support-material-interface-extruder=i"; - Options["support_material_interface_extruder"].min = 0; + Options["support_material_interface_extruder"].min = 1; Options["support_material_interface_layers"].type = coInt; Options["support_material_interface_layers"].label = "Interface layers"; @@ -940,6 +936,21 @@ class DynamicPrintConfig : public DynamicConfig DynamicPrintConfig() { this->def = &PrintConfigDef::def; }; + + void normalize() { + if (this->has("extruder")) { + int extruder = this->option("extruder")->getInt(); + this->erase("extruder"); + if (!this->has("infill_extruder")) + this->option("infill_extruder", true)->setInt(extruder); + if (!this->has("perimeter_extruder")) + this->option("infill_extruder", true)->setInt(extruder); + if (!this->has("support_material_extruder")) + this->option("support_material_extruder", true)->setInt(extruder); + if (!this->has("support_material_interface_extruder")) + this->option("support_material_interface_extruder", true)->setInt(extruder); + } + }; }; class StaticPrintConfig : public virtual StaticConfig @@ -948,9 +959,6 @@ class StaticPrintConfig : public virtual StaticConfig StaticPrintConfig() { this->def = &PrintConfigDef::def; }; - - protected: - void prepare_extruder_option(const t_config_option_key opt_key, DynamicPrintConfig& other); }; class PrintObjectConfig : public virtual StaticPrintConfig @@ -987,10 +995,10 @@ class PrintObjectConfig : public virtual StaticPrintConfig this->support_material.value = false; this->support_material_angle.value = 0; this->support_material_enforce_layers.value = 0; - this->support_material_extruder.value = 0; + this->support_material_extruder.value = 1; this->support_material_extrusion_width.value = 0; this->support_material_extrusion_width.percent = false; - this->support_material_interface_extruder.value = 0; + this->support_material_interface_extruder.value = 1; this->support_material_interface_layers.value = 3; this->support_material_interface_spacing.value = 0; this->support_material_pattern.value = smpHoneycomb; @@ -1021,8 +1029,6 @@ class PrintObjectConfig : public virtual StaticPrintConfig return NULL; }; - - void apply(const ConfigBase &other, bool ignore_nonexistent = false); }; class PrintRegionConfig : public virtual StaticPrintConfig @@ -1053,11 +1059,11 @@ class PrintRegionConfig : public virtual StaticPrintConfig this->fill_angle.value = 45; this->fill_density.value = 40; this->fill_pattern.value = ipHoneycomb; - this->infill_extruder.value = 0; + this->infill_extruder.value = 1; this->infill_extrusion_width.value = 0; this->infill_extrusion_width.percent = false; this->infill_every_layers.value = 1; - this->perimeter_extruder.value = 0; + this->perimeter_extruder.value = 1; this->perimeter_extrusion_width.value = 0; this->perimeter_extrusion_width.percent = false; this->perimeters.value = 3; @@ -1094,8 +1100,6 @@ class PrintRegionConfig : public virtual StaticPrintConfig return NULL; }; - - void apply(const ConfigBase &other, bool ignore_nonexistent = false); }; class PrintConfig : public virtual StaticPrintConfig @@ -1398,12 +1402,6 @@ class FullPrintConfig : public PrintObjectConfig, public PrintRegionConfig, publ if ((opt = PrintConfig::option(opt_key, create)) != NULL) return opt; return NULL; }; - - void apply(const ConfigBase &other, bool ignore_nonexistent = false) { - PrintObjectConfig::apply(other, ignore_nonexistent); - PrintRegionConfig::apply(other, ignore_nonexistent); - PrintConfig::apply(other, ignore_nonexistent); - }; }; } diff --git a/xs/t/15_config.t b/xs/t/15_config.t index b1e2391f20..347e68edf6 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 99; +use Test::More tests => 94; foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) { $config->set('layer_height', 0.3); @@ -126,64 +126,6 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) { is $config->get('fill_pattern'), 'line', 'no interferences between DynamicConfig objects'; } -{ - { - my $config = Slic3r::Config->new; - $config->set('infill_extruder', 2); - my $config2 = Slic3r::Config::PrintRegion->new; - $config2->apply($config); - is $config2->get('infill_extruder'), $config->get('infill_extruder'), - 'non-zero infill_extruder is applied'; - } - { - my $config = Slic3r::Config->new; - $config->set('infill_extruder', 0); - my $config2 = Slic3r::Config::PrintRegion->new; - $config2->set('infill_extruder', 3); - $config2->apply($config); - isnt $config2->get('infill_extruder'), $config->get('infill_extruder'), - 'zero infill_extruder is not applied'; - } - { - my $config = Slic3r::Config->new; - $config->set('extruder', 3); - my $config2 = Slic3r::Config::PrintRegion->new; - $config2->set('infill_extruder', 2); - $config2->apply($config); - is $config2->get('infill_extruder'), 2, 'extruder does not overwrite non-zero role extruders'; - is $config2->get('perimeter_extruder'), 3, 'extruder overwrites zero role extruders'; - } -} - -{ - { - my $config = Slic3r::Config->new; - $config->set('support_material_extruder', 2); - my $config2 = Slic3r::Config::PrintObject->new; - $config2->apply($config); - is $config2->get('support_material_extruder'), $config->get('support_material_extruder'), - 'non-zero support_material_extruder is applied'; - } - { - my $config = Slic3r::Config->new; - $config->set('support_material_extruder', 0); - my $config2 = Slic3r::Config::PrintObject->new; - $config2->set('support_material_extruder', 3); - $config2->apply($config); - isnt $config2->get('support_material_extruder'), $config->get('support_material_extruder'), - 'zero support_material_extruder is not applied'; - } - { - my $config = Slic3r::Config->new; - $config->set('extruder', 3); - my $config2 = Slic3r::Config::PrintObject->new; - $config2->set('support_material_extruder', 2); - $config2->apply($config); - is $config2->get('support_material_extruder'), 2, 'extruder does not overwrite non-zero role extruders'; - is $config2->get('support_material_interface_extruder'), 3, 'extruder overwrites zero role extruders'; - } -} - { my $config = Slic3r::Config->new; # the pair [0,0] is part of the test, since it checks whether the 0x0 serialized value is correctly parsed @@ -193,4 +135,14 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) { is_deeply $config->get('extruder_offset'), $config2->get('extruder_offset'), 'apply dynamic over dynamic'; } +{ + my $config = Slic3r::Config->new; + $config->set('extruder', 2); + $config->set('perimeter_extruder', 3); + $config->normalize; + ok !$config->has('extruder'), 'extruder option is removed after normalize()'; + is $config->get('infill_extruder'), 2, 'undefined extruder is populated with default extruder'; + is $config->get('perimeter_extruder'), 3, 'defined extruder is not overwritten by default extruder'; +} + __END__ diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index b7cb8ea3a7..5a9a0b13c0 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -25,6 +25,7 @@ std::vector get_keys() %code{% THIS->keys(&RETVAL); %}; void erase(t_config_option_key opt_key); + void normalize(); }; %name{Slic3r::Config::Print} class PrintConfig {