diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 1a1fa6f70..f2bea526c 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -68,6 +68,7 @@ use Slic3r::Polyline; use Slic3r::Print; use Slic3r::Print::Object; use Slic3r::Print::Region; +use Slic3r::Print::Simple; use Slic3r::Print::SupportMaterial; use Slic3r::Surface; use Slic3r::TriangleMesh; @@ -76,9 +77,8 @@ our $build = eval "use Slic3r::Build; 1"; use constant SCALING_FACTOR => 0.000001; use constant RESOLUTION => 0.0125; use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR; -use constant OVERLAP_FACTOR => 1; use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI; -use constant LOOP_CLIPPING_LENGTH_OVER_SPACING => 0.15; +use constant LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER => 0.15; use constant INFILL_OVERLAP_OVER_SPACING => 0.45; use constant EXTERNAL_INFILL_MARGIN => 3; @@ -132,7 +132,10 @@ sub thread_cleanup { # prevent destruction of shared objects no warnings 'redefine'; *Slic3r::Config::DESTROY = sub {}; + *Slic3r::Config::Full::DESTROY = sub {}; *Slic3r::Config::Print::DESTROY = sub {}; + *Slic3r::Config::PrintObject::DESTROY = sub {}; + *Slic3r::Config::PrintRegion::DESTROY = sub {}; *Slic3r::ExPolygon::DESTROY = sub {}; *Slic3r::ExPolygon::Collection::DESTROY = sub {}; *Slic3r::ExtrusionLoop::DESTROY = sub {}; diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 0db6e69e1..ae166b6c5 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -7,7 +7,7 @@ use List::Util qw(first); # cemetery of old config settings our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool acceleration - adjust_overhang_flow standby_temperature); + adjust_overhang_flow standby_temperature scale rotate duplicate duplicate_grid); our $Options = print_config_def(); @@ -24,7 +24,7 @@ sub new_from_defaults { my (@opt_keys) = @_; my $self = $class->new; - my $defaults = Slic3r::Config::Print->new; + my $defaults = Slic3r::Config::Full->new; if (@opt_keys) { $self->set($_, $defaults->get($_)) for @opt_keys; } else { @@ -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; } @@ -175,7 +175,25 @@ sub setenv { } } -# this method is idempotent by design +sub equals { + my ($self, $other) = @_; + 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); + } + return [@diff]; +} + +# this method is idempotent by design and only applies to ::DynamicConfig or ::Full +# objects because it performs cross checks sub validate { my $self = shift; @@ -248,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 @@ -292,8 +294,33 @@ sub validate { if ($self->perimeter_acceleration || $self->infill_acceleration || $self->bridge_acceleration || $self->first_layer_acceleration) && !$self->default_acceleration; + # --spiral-vase + if ($self->spiral_vase) { + # Note that we might want to have more than one perimeter on the bottom + # solid layers. + die "Can't make more than one perimeter when spiral vase mode is enabled\n" + if $self->perimeters > 1; + + die "Can't make less than one perimeter when spiral vase mode is enabled\n" + if $self->perimeters < 1; + + die "Spiral vase mode is not compatible with non-zero fill density\n" + if $self->fill_density > 0; + + die "Spiral vase mode is not compatible with top solid layers\n" + if $self->top_solid_layers > 0; + + die "Spiral vase mode is not compatible with support material\n" + if $self->support_material || $self->support_material_enforce_layers > 0; + + # This should be enforce automatically only on spiral layers and + # done on the others + die "Spiral vase mode is not compatible with retraction on layer change\n" + if defined first { $_ } @{ $self->retract_layer_change }; + } + # general validation, quick and dirty - foreach my $opt_key (keys %$Options) { + foreach my $opt_key (@{$self->get_keys}) { my $opt = $Options->{$opt_key}; next unless defined $self->$opt_key; next unless defined $opt->{cli} && $opt->{cli} =~ /=(.+)$/; @@ -415,4 +442,16 @@ sub read_ini { return $ini; } +package Slic3r::Config::Print; +use parent 'Slic3r::Config'; + +package Slic3r::Config::PrintObject; +use parent 'Slic3r::Config'; + +package Slic3r::Config::PrintRegion; +use parent 'Slic3r::Config'; + +package Slic3r::Config::Full; +use parent 'Slic3r::Config'; + 1; diff --git a/lib/Slic3r/Extruder.pm b/lib/Slic3r/Extruder.pm index 9444f6916..514aec7c7 100644 --- a/lib/Slic3r/Extruder.pm +++ b/lib/Slic3r/Extruder.pm @@ -1,6 +1,12 @@ package Slic3r::Extruder; use Moo; +require Exporter; +our @ISA = qw(Exporter); +our @EXPORT_OK = qw(EXTRUDER_ROLE_PERIMETER EXTRUDER_ROLE_INFILL EXTRUDER_ROLE_SUPPORT_MATERIAL + EXTRUDER_ROLE_SUPPORT_MATERIAL_INTERFACE); +our %EXPORT_TAGS = (roles => \@EXPORT_OK); + use Slic3r::Geometry qw(PI scale); use constant OPTIONS => [qw( @@ -12,24 +18,31 @@ use constant OPTIONS => [qw( has 'id' => (is => 'rw', required => 1); has $_ => (is => 'ro', required => 1) for @{&OPTIONS}; -has 'config'=> (is => 'ro', required => 1); +has 'use_relative_e_distances' => (is => 'ro', default => sub {0}); -has 'bridge_flow' => (is => 'lazy'); has 'E' => (is => 'rw', default => sub {0} ); has 'absolute_E' => (is => 'rw', default => sub {0} ); has 'retracted' => (is => 'rw', default => sub {0} ); has 'restart_extra' => (is => 'rw', default => sub {0} ); has 'e_per_mm3' => (is => 'lazy'); has 'retract_speed_mm_min' => (is => 'lazy'); -has '_mm3_per_mm_cache' => (is => 'ro', default => sub {{}}); -sub _build_bridge_flow { - my $self = shift; +use constant EXTRUDER_ROLE_PERIMETER => 1; +use constant EXTRUDER_ROLE_INFILL => 2; +use constant EXTRUDER_ROLE_SUPPORT_MATERIAL => 3; +use constant EXTRUDER_ROLE_SUPPORT_MATERIAL_INTERFACE => 4; + +sub new_from_config { + my ($class, $config, $extruder_id) = @_; - return Slic3r::Flow::Bridge->new( - nozzle_diameter => $self->nozzle_diameter, - bridge_flow_ratio => $self->config->bridge_flow_ratio, + my %conf = ( + id => $extruder_id, + use_relative_e_distances => $config->use_relative_e_distances, ); + foreach my $opt_key (@{&OPTIONS}) { + $conf{$opt_key} = $config->get_at($opt_key, $extruder_id); + } + return $class->new(%conf); } sub _build_e_per_mm3 { @@ -42,6 +55,15 @@ sub _build_retract_speed_mm_min { return $self->retract_speed * 60; } +sub reset { + my ($self) = @_; + + $self->E(0); + $self->absolute_E(0); + $self->retracted(0); + $self->restart_extra(0); +} + sub scaled_wipe_distance { my ($self, $travel_speed) = @_; @@ -55,7 +77,7 @@ sub scaled_wipe_distance { sub extrude { my ($self, $E) = @_; - $self->E(0) if $self->config->use_relative_e_distances; + $self->E(0) if $self->use_relative_e_distances; $self->absolute_E($self->absolute_E + $E); return $self->E($self->E + $E); } @@ -65,37 +87,9 @@ sub extruded_volume { return $self->absolute_E * ($self->filament_diameter**2) * PI/4; } -sub make_flow { - my $self = shift; - return Slic3r::Flow->new(nozzle_diameter => $self->nozzle_diameter, @_); -} - -sub mm3_per_mm { - my $self = shift; - my ($s, $h) = @_; - - my $cache_key = "${s}_${h}"; - if (!exists $self->_mm3_per_mm_cache->{$cache_key}) { - my $w_threshold = $h + $self->nozzle_diameter; - my $s_threshold = $w_threshold - &Slic3r::OVERLAP_FACTOR * ($w_threshold - ($w_threshold - $h * (1 - PI/4))); - - if ($s >= $s_threshold) { - # rectangle with semicircles at the ends - my $w = $s + &Slic3r::OVERLAP_FACTOR * $h * (1 - PI/4); - $self->_mm3_per_mm_cache->{$cache_key} = $w * $h + ($h**2) / 4 * (PI - 4); - } else { - # rectangle with shrunk semicircles at the ends - my $w = ($s + $self->nozzle_diameter * &Slic3r::OVERLAP_FACTOR * (PI/4 - 1)) / (1 + &Slic3r::OVERLAP_FACTOR * (PI/4 - 1)); - $self->_mm3_per_mm_cache->{$cache_key} = $self->nozzle_diameter * $h * (1 - PI/4) + $h * $w * PI/4; - } - } - return $self->_mm3_per_mm_cache->{$cache_key}; -} - sub e_per_mm { - my $self = shift; - my ($s, $h) = @_; - return $self->mm3_per_mm($s, $h) * $self->e_per_mm3; + my ($self, $mm3_per_mm) = @_; + return $mm3_per_mm * $self->e_per_mm3; } 1; diff --git a/lib/Slic3r/ExtrusionLoop.pm b/lib/Slic3r/ExtrusionLoop.pm index 57f380eec..7123b3a77 100644 --- a/lib/Slic3r/ExtrusionLoop.pm +++ b/lib/Slic3r/ExtrusionLoop.pm @@ -8,8 +8,7 @@ sub split_at { return Slic3r::ExtrusionPath->new( polyline => $self->polygon->split_at(@_), role => $self->role, - flow_spacing => $self->flow_spacing, - height => $self->height, + mm3_per_mm => $self->mm3_per_mm, ); } diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 42cbea13f..c128d0eb3 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -1,6 +1,7 @@ package Slic3r::Fill; use Moo; +use Slic3r::ExtrusionPath ':roles'; use Slic3r::Fill::ArchimedeanChords; use Slic3r::Fill::Base; use Slic3r::Fill::Concentric; @@ -11,7 +12,7 @@ use Slic3r::Fill::Line; use Slic3r::Fill::OctagramSpiral; use Slic3r::Fill::PlanePath; use Slic3r::Fill::Rectilinear; -use Slic3r::ExtrusionPath ':roles'; +use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(X Y PI scale chained_path); use Slic3r::Geometry::Clipper qw(union union_ex diff diff_ex intersection_ex offset offset2); use Slic3r::Surface ':types'; @@ -50,7 +51,10 @@ sub make_fill { my ($layerm) = @_; Slic3r::debugf "Filling layer %d:\n", $layerm->id; - my $fill_density = $layerm->config->fill_density; + + my $fill_density = $layerm->config->fill_density; + my $infill_flow = $layerm->flow(FLOW_ROLE_INFILL); + my $solid_infill_flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL); my @surfaces = (); @@ -95,7 +99,7 @@ sub make_fill { # we are going to grow such regions by overlapping them with the void (if any) # TODO: detect and investigate whether there could be narrow regions without # any void neighbors - my $distance_between_surfaces = $layerm->solid_infill_flow->scaled_spacing * &Slic3r::INFILL_OVERLAP_OVER_SPACING; + my $distance_between_surfaces = $infill_flow->scaled_spacing * &Slic3r::INFILL_OVERLAP_OVER_SPACING; { my $collapsed = diff( [ map @{$_->expolygon}, @surfaces ], @@ -133,11 +137,10 @@ sub make_fill { my $filler = $layerm->config->fill_pattern; my $density = $fill_density; my $flow = ($surface->surface_type == S_TYPE_TOP) - ? $layerm->top_infill_flow + ? $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL) : $surface->is_solid - ? $layerm->solid_infill_flow - : $layerm->infill_flow; - my $flow_spacing = $flow->spacing; + ? $solid_infill_flow + : $infill_flow; my $is_bridge = $layerm->id > 0 && $surface->is_bridge; my $is_solid = $surface->is_solid; @@ -147,7 +150,7 @@ sub make_fill { $filler = $layerm->config->solid_fill_pattern; if ($is_bridge) { $filler = 'rectilinear'; - $flow_spacing = $layerm->extruders->{infill}->bridge_flow->spacing; + $flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL, 1); } elsif ($surface->surface_type == S_TYPE_INTERNALSOLID) { $filler = 'rectilinear'; } @@ -160,13 +163,14 @@ sub make_fill { $f->angle($layerm->config->fill_angle); my ($params, @polylines) = $f->fill_surface( $surface, - density => $density, - flow_spacing => $flow_spacing, + density => $density, + flow => $flow, ); next unless @polylines; - # ugly hack(tm) to get the right amount of flow (GCode.pm should be fixed) - $params->{flow_spacing} = $layerm->extruders->{infill}->bridge_flow->width if $is_bridge; + my $h = $surface->thickness; + $h = $layerm->height if $h == -1; + my $mm3_per_mm = $params->{flow}->mm3_per_mm($h); # save into layer push @fills, my $collection = Slic3r::ExtrusionPath::Collection->new; @@ -182,8 +186,7 @@ sub make_fill { : $is_solid ? (($surface->surface_type == S_TYPE_TOP) ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL) : EXTR_ROLE_FILL), - height => $surface->thickness, - flow_spacing => $params->{flow_spacing} || (warn "Warning: no flow_spacing was returned by the infill engine, please report this to the developer\n"), + mm3_per_mm => $mm3_per_mm, ), @polylines, ); push @fills_ordering_points, $polylines[0]->first_point; diff --git a/lib/Slic3r/Fill/Concentric.pm b/lib/Slic3r/Fill/Concentric.pm index 5649a8aa7..0bb1f5061 100644 --- a/lib/Slic3r/Fill/Concentric.pm +++ b/lib/Slic3r/Fill/Concentric.pm @@ -15,16 +15,22 @@ sub fill_surface { my $expolygon = $surface->expolygon; my $bounding_box = $expolygon->bounding_box; - my $min_spacing = scale $params{flow_spacing}; + my $flow = $params{flow}; + my $min_spacing = $flow->scaled_spacing; my $distance = $min_spacing / $params{density}; - my $flow_spacing = $params{flow_spacing}; + my $flow_spacing = $flow->spacing; if ($params{density} == 1 && !$params{dont_adjust}) { $distance = $self->adjust_solid_spacing( width => $bounding_box->size->[X], distance => $distance, ); - $flow_spacing = unscale $distance; + $flow = Slic3r::Flow->new_from_spacing( + spacing => unscale($distance), + nozzle_diameter => $flow->nozzle_diameter, + layer_height => $surface->thickness, + bridge => $flow->bridge, + ); } # compensate the overlap which is good for rectilinear but harmful for concentric @@ -48,11 +54,11 @@ sub fill_surface { } # clip the paths to avoid the extruder to get exactly on the first point of the loop - my $clip_length = scale $flow_spacing * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_SPACING; + my $clip_length = scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER; $_->clip_end($clip_length) for @paths; # TODO: return ExtrusionLoop objects to get better chained paths - return { flow_spacing => $flow_spacing, no_sort => 1 }, @paths; + return { flow => $flow, no_sort => 1 }, @paths; } 1; diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm index caaa15407..1e6df2742 100644 --- a/lib/Slic3r/Fill/Honeycomb.pm +++ b/lib/Slic3r/Fill/Honeycomb.pm @@ -17,11 +17,11 @@ sub fill_surface { my $rotate_vector = $self->infill_direction($surface); # cache hexagons math - my $cache_id = sprintf "d%s_s%s", $params{density}, $params{flow_spacing}; + my $cache_id = sprintf "d%s_s%s", $params{density}, $params{flow}->spacing; my $m; if (!($m = $self->cache->{$cache_id})) { $m = $self->cache->{$cache_id} = {}; - my $min_spacing = scale $params{flow_spacing}; + my $min_spacing = $params{flow}->scaled_spacing; $m->{distance} = $min_spacing / $params{density}; $m->{hex_side} = $m->{distance} / (sqrt(3)/2); $m->{hex_width} = $m->{distance} * 2; # $m->{hex_width} == $m->{hex_side} * sqrt(3); @@ -120,7 +120,7 @@ sub fill_surface { )}; } - return { flow_spacing => $params{flow_spacing} }, @paths; + return { flow => $params{flow} }, @paths; } 1; diff --git a/lib/Slic3r/Fill/PlanePath.pm b/lib/Slic3r/Fill/PlanePath.pm index 0797fd10e..d393f9049 100644 --- a/lib/Slic3r/Fill/PlanePath.pm +++ b/lib/Slic3r/Fill/PlanePath.pm @@ -27,7 +27,8 @@ sub fill_surface { my $rotate_vector = $self->infill_direction($surface); $self->rotate_points($expolygon, $rotate_vector); - my $distance_between_lines = scale $params{flow_spacing} / $params{density} * $self->multiplier; + my $flow = $params{flow}; + my $distance_between_lines = $flow->scaled_spacing / $params{density} * $self->multiplier; my $bounding_box = $expolygon->bounding_box; (ref $self) =~ /::([^:]+)$/; @@ -54,7 +55,7 @@ sub fill_surface { # paths must be rotated back $self->rotate_points_back(\@paths, $rotate_vector); - return { flow_spacing => $params{flow_spacing} }, @paths; + return { flow => $flow }, @paths; } 1; diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index 335bf5b23..acf7c1090 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -17,8 +17,8 @@ sub fill_surface { my $rotate_vector = $self->infill_direction($surface); $self->rotate_points($expolygon, $rotate_vector); - my $flow_spacing = $params{flow_spacing}; - my $min_spacing = scale $params{flow_spacing}; + my $flow = $params{flow} or die "No flow supplied to fill_surface()"; + my $min_spacing = $flow->scaled_spacing; my $line_spacing = $min_spacing / $params{density}; my $line_oscillation = $line_spacing - $min_spacing; my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); @@ -30,7 +30,12 @@ sub fill_surface { width => $bounding_box->size->[X], distance => $line_spacing, ); - $flow_spacing = unscale $line_spacing; + $flow = Slic3r::Flow->new_from_spacing( + spacing => unscale($line_spacing), + nozzle_diameter => $flow->nozzle_diameter, + layer_height => $surface->thickness, + bridge => $flow->bridge, + ); } else { # extend bounding box so that our pattern will be aligned with other layers $bounding_box->extents->[X][MIN] -= $bounding_box->x_min % $line_spacing; @@ -62,7 +67,7 @@ sub fill_surface { # connect lines unless ($params{dont_connect} || !@polylines) { # prevent calling leftmost_point() on empty collections - my ($expolygon_off) = @{$expolygon->offset_ex(scale $params{flow_spacing}/2)}; + my ($expolygon_off) = @{$expolygon->offset_ex($min_spacing/2)}; my $collection = Slic3r::Polyline::Collection->new(@polylines); @polylines = (); @@ -97,7 +102,7 @@ sub fill_surface { # paths must be rotated back $self->rotate_points_back(\@polylines, $rotate_vector); - return { flow_spacing => $flow_spacing }, @polylines; + return { flow => $flow }, @polylines; } 1; diff --git a/lib/Slic3r/Flow.pm b/lib/Slic3r/Flow.pm index a02f7b34e..a7c8312c5 100644 --- a/lib/Slic3r/Flow.pm +++ b/lib/Slic3r/Flow.pm @@ -1,108 +1,12 @@ package Slic3r::Flow; -use Moo; +use strict; +use warnings; -use Slic3r::Geometry qw(PI scale); +use parent qw(Exporter); -has 'nozzle_diameter' => (is => 'ro', required => 1); -has 'layer_height' => (is => 'ro', required => 1); -has 'role' => (is => 'ro', default => sub { '' }); - -has 'width' => (is => 'rwp', builder => 1); -has 'spacing' => (is => 'lazy'); -has 'scaled_width' => (is => 'lazy'); -has 'scaled_spacing' => (is => 'lazy'); - -sub BUILD { - my $self = shift; - - if ($self->width =~ /^(\d+(?:\.\d+)?)%$/) { - $self->_set_width($self->layer_height * $1 / 100); - } - $self->_set_width($self->_build_width) if $self->width == 0; # auto -} - -sub _build_width { - my $self = shift; - - # here we calculate a sane default by matching the flow speed (at the nozzle) and the feed rate - my $volume = ($self->nozzle_diameter**2) * PI/4; - my $shape_threshold = $self->nozzle_diameter * $self->layer_height + ($self->layer_height**2) * PI/4; - my $width; - if ($volume >= $shape_threshold) { - # rectangle with semicircles at the ends - $width = (($self->nozzle_diameter**2) * PI + ($self->layer_height**2) * (4 - PI)) / (4 * $self->layer_height); - } else { - # rectangle with squished semicircles at the ends - $width = $self->nozzle_diameter * ($self->nozzle_diameter/$self->layer_height - 4/PI + 1); - } - - my $min = $self->nozzle_diameter * 1.05; - my $max; - if ($self->role eq 'perimeter' || $self->role eq 'support_material') { - $min = $max = $self->nozzle_diameter; - } elsif ($self->role ne 'infill') { - # do not limit width for sparse infill so that we use full native flow for it - $max = $self->nozzle_diameter * 1.7; - } - $width = $max if defined($max) && $width > $max; - $width = $min if $width < $min; - - return $width; -} - -sub _build_spacing { - my $self = shift; - - my $min_flow_spacing; - if ($self->width >= ($self->nozzle_diameter + $self->layer_height)) { - # rectangle with semicircles at the ends - $min_flow_spacing = $self->width - $self->layer_height * (1 - PI/4); - } else { - # rectangle with shrunk semicircles at the ends - $min_flow_spacing = $self->nozzle_diameter * (1 - PI/4) + $self->width * PI/4; - } - return $self->width - &Slic3r::OVERLAP_FACTOR * ($self->width - $min_flow_spacing); -} - -sub clone { - my $self = shift; - - return (ref $self)->new( - nozzle_diameter => $self->nozzle_diameter, - layer_height => $self->layer_height, - @_, - ); -} - -sub _build_scaled_width { - my $self = shift; - return scale $self->width; -} - -sub _build_scaled_spacing { - my $self = shift; - return scale $self->spacing; -} - - -package Slic3r::Flow::Bridge; -use Moo; -extends 'Slic3r::Flow'; - -# layer_height is not required in this case -has '+layer_height' => (is => 'ro', required => 0); -has 'bridge_flow_ratio' => (is => 'ro', required => 1); - -use Slic3r::Geometry qw(PI); - -sub _build_width { - my $self = shift; - return sqrt($self->bridge_flow_ratio * ($self->nozzle_diameter**2)); -} - -sub _build_spacing { - my $self = shift; - return $self->width + 0.05; -} +our @EXPORT_OK = qw(FLOW_ROLE_PERIMETER FLOW_ROLE_INFILL FLOW_ROLE_SOLID_INFILL + FLOW_ROLE_TOP_SOLID_INFILL FLOW_ROLE_SUPPORT_MATERIAL + FLOW_ROLE_SUPPORT_MATERIAL_INTERFACE); +our %EXPORT_TAGS = (roles => \@EXPORT_OK); 1; diff --git a/lib/Slic3r/Format/AMF/Parser.pm b/lib/Slic3r/Format/AMF/Parser.pm index d67687f70..43dbf05a9 100644 --- a/lib/Slic3r/Format/AMF/Parser.pm +++ b/lib/Slic3r/Format/AMF/Parser.pm @@ -100,7 +100,7 @@ sub end_element { if ($self->{_material_metadata_type} =~ /^slic3r\.(.+)/) { my $opt_key = $1; if (exists $Slic3r::Config::Options->{$opt_key}) { - $self->{_material}->set_deserialize($opt_key, $self->{_material}->attributes->{$opt_key}); + $self->{_material}->config->set_deserialize($opt_key, $self->{_material}->attributes->{"slic3r.$opt_key"}); } } $self->{_material_metadata_type} = undef; diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index da55942f6..1d1952309 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -3,19 +3,19 @@ use Moo; use List::Util qw(min first); use Slic3r::ExtrusionPath ':roles'; +use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon points_coincide PI X Y B); use Slic3r::Geometry::Clipper qw(union_ex); use Slic3r::Surface ':types'; -has 'config' => (is => 'ro', required => 1); +has 'print_config' => (is => 'ro', default => sub { Slic3r::Config::Print->new }); has 'extra_variables' => (is => 'rw', default => sub {{}}); -has 'extruders' => (is => 'ro', required => 1); -has 'multiple_extruders' => (is => 'lazy'); has 'standby_points' => (is => 'rw'); has 'enable_loop_clipping' => (is => 'rw', default => sub {1}); -has 'enable_wipe' => (is => 'lazy'); # at least one extruder has wipe enabled +has 'enable_wipe' => (is => 'rw', default => sub {0}); # at least one extruder has wipe enabled has 'layer_count' => (is => 'ro', required => 1 ); has 'layer' => (is => 'rw'); +has 'region' => (is => 'rw'); has '_layer_islands' => (is => 'rw'); has '_upper_layer_islands' => (is => 'rw'); has '_layer_overhangs' => (is => 'ro', default => sub { Slic3r::ExPolygon::Collection->new }); @@ -23,13 +23,15 @@ has 'shift_x' => (is => 'rw', default => sub {0} ); has 'shift_y' => (is => 'rw', default => sub {0} ); has 'z' => (is => 'rw'); has 'speed' => (is => 'rw'); - +has '_extrusion_axis' => (is => 'rw'); +has '_retract_lift' => (is => 'rw'); +has 'extruders' => (is => 'ro', default => sub {{}}); +has 'extruder' => (is => 'rw'); has 'speeds' => (is => 'lazy'); # mm/min has 'external_mp' => (is => 'rw'); has 'layer_mp' => (is => 'rw'); has 'new_object' => (is => 'rw', default => sub {0}); has 'straight_once' => (is => 'rw', default => sub {1}); -has 'extruder' => (is => 'rw'); has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds has 'lifted' => (is => 'rw', default => sub {0} ); has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0) } ); @@ -38,12 +40,28 @@ has 'last_f' => (is => 'rw', default => sub {""}); has 'last_fan_speed' => (is => 'rw', default => sub {0}); has 'wipe_path' => (is => 'rw'); +sub BUILD { + my ($self) = @_; + + $self->_extrusion_axis($self->print_config->get_extrusion_axis); + $self->_retract_lift($self->print_config->retract_lift->[0]); +} + +sub set_extruders { + my ($self, $extruder_ids) = @_; + + foreach my $i (@$extruder_ids) { + $self->extruders->{$i} = my $e = Slic3r::Extruder->new_from_config($self->print_config, $i); + $self->enable_wipe(1) if $e->wipe; + } +} + sub _build_speeds { my $self = shift; return { - map { $_ => 60 * $self->config->get_value("${_}_speed") } + map { $_ => 60 * $self->print_config->get_value("${_}_speed") } qw(travel perimeter small_perimeter external_perimeter infill - solid_infill top_solid_infill support_material bridge gap_fill retract), + solid_infill top_solid_infill bridge gap_fill retract), }; } @@ -59,18 +77,12 @@ my %role_speeds = ( &EXTR_ROLE_BRIDGE => 'bridge', &EXTR_ROLE_INTERNALBRIDGE => 'solid_infill', &EXTR_ROLE_SKIRT => 'perimeter', - &EXTR_ROLE_SUPPORTMATERIAL => 'support_material', &EXTR_ROLE_GAPFILL => 'gap_fill', ); -sub _build_multiple_extruders { +sub multiple_extruders { my $self = shift; - return @{$self->extruders} > 1; -} - -sub _build_enable_wipe { - my $self = shift; - return (first { $_->wipe } @{$self->extruders}) ? 1 : 0; + return (keys %{$self->extruders}) > 1; } sub set_shift { @@ -97,29 +109,29 @@ sub change_layer { $self->_layer_islands($layer->islands); $self->_upper_layer_islands($layer->upper_layer ? $layer->upper_layer->islands : []); $self->_layer_overhangs->clear; - if ($layer->id > 0 && ($layer->config->overhangs || $self->config->start_perimeters_at_non_overhang)) { + if ($layer->id > 0 && ($self->print_config->overhangs || $self->print_config->start_perimeters_at_non_overhang)) { $self->_layer_overhangs->append( # clone ExPolygons because they come from Surface objects but will be used outside here map $_->expolygon, map @{$_->slices->filter_by_type(S_TYPE_BOTTOM)}, @{$layer->regions} ); } - if ($self->config->avoid_crossing_perimeters) { + if ($self->print_config->avoid_crossing_perimeters) { $self->layer_mp(Slic3r::GCode::MotionPlanner->new( islands => union_ex([ map @$_, @{$layer->slices} ], 1), )); } my $gcode = ""; - if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) { + if ($self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/) { $gcode .= sprintf "M73 P%s%s\n", int(99 * ($layer->id / ($self->layer_count - 1))), - ($self->config->gcode_comments ? ' ; update progress' : ''); + ($self->print_config->gcode_comments ? ' ; update progress' : ''); } - if ($self->config->first_layer_acceleration) { + if ($self->print_config->first_layer_acceleration) { if ($layer->id == 0) { - $gcode .= $self->set_acceleration($self->config->first_layer_acceleration); + $gcode .= $self->set_acceleration($self->print_config->first_layer_acceleration); } elsif ($layer->id == 1) { - $gcode .= $self->set_acceleration($self->config->default_acceleration); + $gcode .= $self->set_acceleration($self->print_config->default_acceleration); } } @@ -133,7 +145,7 @@ sub move_z { my $gcode = ""; - $z += $self->config->z_offset; + $z += $self->print_config->z_offset; my $current_z = $self->z; my $nominal_z = defined $current_z ? ($current_z - $self->lifted) : undef; @@ -181,11 +193,11 @@ sub extrude_loop { # find candidate starting points # start looking for concave vertices not being overhangs my @concave = (); - if ($self->config->start_perimeters_at_concave_points) { + if ($self->print_config->start_perimeters_at_concave_points) { @concave = $polygon->concave_points; } my @candidates = (); - if ($self->config->start_perimeters_at_non_overhang) { + if ($self->print_config->start_perimeters_at_non_overhang) { @candidates = grep !$self->_layer_overhangs->contains_point($_), @concave; } if (!@candidates) { @@ -193,7 +205,7 @@ sub extrude_loop { @candidates = @concave; if (!@candidates) { # if none, look for any non-overhang vertex - if ($self->config->start_perimeters_at_non_overhang) { + if ($self->print_config->start_perimeters_at_non_overhang) { @candidates = grep !$self->_layer_overhangs->contains_point($_), @$polygon; } if (!@candidates) { @@ -206,9 +218,9 @@ sub extrude_loop { # find the point of the loop that is closest to the current extruder position # or randomize if requested my $last_pos = $self->last_pos; - if ($self->config->randomize_start && $loop->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER) { - $last_pos = Slic3r::Point->new(scale $self->config->print_center->[X], scale $self->config->bed_size->[Y]); - $last_pos->rotate(rand(2*PI), $self->config->print_center); + if ($self->print_config->randomize_start && $loop->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER) { + $last_pos = Slic3r::Point->new(scale $self->print_config->print_center->[X], scale $self->print_config->bed_size->[Y]); + $last_pos->rotate(rand(2*PI), $self->print_config->print_center); } # split the loop at the starting point and make a path @@ -218,27 +230,25 @@ sub extrude_loop { # clip the path to avoid the extruder to get exactly on the first point of the loop; # if polyline was shorter than the clipping distance we'd get a null polyline, so # we discard it in that case - $extrusion_path->clip_end(scale $extrusion_path->flow_spacing * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_SPACING) + $extrusion_path->clip_end(scale($self->extruder->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER) if $self->enable_loop_clipping; return '' if !@{$extrusion_path->polyline}; my @paths = (); # detect overhanging/bridging perimeters - if ($self->layer->config->overhangs && $extrusion_path->is_perimeter && $self->_layer_overhangs->count > 0) { + if ($self->layer->print->config->overhangs && $extrusion_path->is_perimeter && $self->_layer_overhangs->count > 0) { # get non-overhang paths by subtracting overhangs from the loop push @paths, map $_->clone, @{$extrusion_path->subtract_expolygons($self->_layer_overhangs)}; # get overhang paths by intersecting overhangs with the loop - push @paths, - map { - $_->role(EXTR_ROLE_OVERHANG_PERIMETER); - $_->flow_spacing($self->extruder->bridge_flow->width); - $_ - } - map $_->clone, - @{$extrusion_path->intersect_expolygons($self->_layer_overhangs)}; + foreach my $path (@{$extrusion_path->intersect_expolygons($self->_layer_overhangs)}) { + $path = $path->clone; + $path->role(EXTR_ROLE_OVERHANG_PERIMETER); + $path->mm3_per_mm($self->region->flow(FLOW_ROLE_PERIMETER, -1, 1)->mm3_per_mm(-1)); + push @paths, $path; + } # reapply the nearest point search for starting point # (clone because the collection gets DESTROY'ed) @@ -259,7 +269,7 @@ sub extrude_loop { $self->wipe_path($extrusion_path->polyline->clone) if $self->enable_wipe; # make a little move inwards before leaving loop - if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->layer->object->config->perimeters > 1) { + if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->region->config->perimeters > 1) { # detect angle between last and first segment # the side depends on the original winding order of the polygon (left for contours, right for holes) my @points = $was_clockwise ? (-2, 1) : (1, -2); @@ -270,7 +280,7 @@ sub extrude_loop { # we make sure we don't exceed the segment length because we don't know # the rotation of the second segment so we might cross the object boundary my $first_segment = Slic3r::Line->new(@{$extrusion_path->polyline}[0,1]); - my $distance = min(scale $extrusion_path->flow_spacing, $first_segment->length); + my $distance = min(scale($self->extruder->nozzle_diameter), $first_segment->length); my $point = $first_segment->point_at($distance); $point->rotate($angle, $extrusion_path->first_point); @@ -299,38 +309,26 @@ sub extrude_path { # adjust acceleration my $acceleration; - if (!$self->config->first_layer_acceleration || $self->layer->id != 0) { - if ($self->config->perimeter_acceleration && $path->is_perimeter) { - $acceleration = $self->config->perimeter_acceleration; - } elsif ($self->config->infill_acceleration && $path->is_fill) { - $acceleration = $self->config->infill_acceleration; - } elsif ($self->config->infill_acceleration && $path->is_bridge) { - $acceleration = $self->config->bridge_acceleration; + if (!$self->print_config->first_layer_acceleration || $self->layer->id != 0) { + if ($self->print_config->perimeter_acceleration && $path->is_perimeter) { + $acceleration = $self->print_config->perimeter_acceleration; + } elsif ($self->print_config->infill_acceleration && $path->is_fill) { + $acceleration = $self->print_config->infill_acceleration; + } elsif ($self->print_config->infill_acceleration && $path->is_bridge) { + $acceleration = $self->print_config->bridge_acceleration; } $gcode .= $self->set_acceleration($acceleration) if $acceleration; } - my $area; # mm^3 of extrudate per mm of tool movement - if ($path->is_bridge) { - my $s = $path->flow_spacing; - $area = ($s**2) * PI/4; - } else { - my $s = $path->flow_spacing; - my $h = (defined $path->height && $path->height != -1) ? $path->height : $self->layer->height; - $area = $self->extruder->mm3_per_mm($s, $h); - } - # calculate extrusion length per distance unit - my $e = $self->extruder->e_per_mm3 * $area; - $e = 0 if !$self->config->extrusion_axis; + my $e = $self->extruder->e_per_mm3 * $path->mm3_per_mm; + $e = 0 if !$self->_extrusion_axis; # set speed $self->speed( $params{speed} || $role_speeds{$path->role} || die "Unknown role: " . $path->role ); my $F = $self->speeds->{$self->speed} // $self->speed; if ($self->layer->id == 0) { - $F = $self->config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/ - ? sprintf("%.3f", $F * $1/100) - : $self->config->first_layer_speed * 60; + $F = $self->print_config->get_abs_value_over('first_layer_speed', $F/60) * 60; } # extrude arc or line @@ -350,12 +348,12 @@ sub extrude_path { $gcode .= sprintf "G1 X%.3f Y%.3f", ($point->x * &Slic3r::SCALING_FACTOR) + $self->shift_x - $self->extruder->extruder_offset->[X], ($point->y * &Slic3r::SCALING_FACTOR) + $self->shift_y - $self->extruder->extruder_offset->[Y]; #** - $gcode .= sprintf(" %s%.5f", $self->config->extrusion_axis, $E) + $gcode .= sprintf(" %s%.5f", $self->_extrusion_axis, $E) if $E; $gcode .= " F$local_F" if $local_F; $gcode .= " ; $description" - if $self->config->gcode_comments; + if $self->print_config->gcode_comments; $gcode .= "\n"; # only include F in the first line @@ -369,19 +367,14 @@ sub extrude_path { $gcode .= ";_BRIDGE_FAN_END\n" if $path->is_bridge; $self->last_pos($path->last_point); - if ($self->config->cooling) { + if ($self->print_config->cooling) { my $path_time = $path_length / $F * 60; - if ($self->layer->id == 0) { - $path_time = $self->config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/ - ? $path_time / ($1/100) - : $path_length / $self->config->first_layer_speed * 60; - } $self->elapsed_time($self->elapsed_time + $path_time); } # reset acceleration - $gcode .= $self->set_acceleration($self->config->default_acceleration) - if $acceleration && $self->config->default_acceleration; + $gcode .= $self->set_acceleration($self->print_config->default_acceleration) + if $acceleration && $self->print_config->default_acceleration; return $gcode; } @@ -400,7 +393,7 @@ sub travel_to { # skip retraction if the travel move is contained in an island in the current layer # *and* in an island in the upper layer (so that the ooze will not be visible) if ($travel->length < scale $self->extruder->retract_before_travel - || ($self->config->only_retract_when_crossing_perimeters + || ($self->print_config->only_retract_when_crossing_perimeters && (first { $_->contains_line($travel) } @{$self->_upper_layer_islands}) && (first { $_->contains_line($travel) } @{$self->_layer_islands})) || (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && (first { $_->contains_line($travel) } @{$self->layer->support_islands})) @@ -408,7 +401,7 @@ sub travel_to { $self->straight_once(0); $self->speed('travel'); $gcode .= $self->G0($point, undef, 0, $comment || ""); - } elsif (!$self->config->avoid_crossing_perimeters || $self->straight_once) { + } elsif (!$self->print_config->avoid_crossing_perimeters || $self->straight_once) { $self->straight_once(0); $gcode .= $self->retract; $self->speed('travel'); @@ -441,7 +434,7 @@ sub _plan { my @travel = @{$mp->shortest_path($self->last_pos, $point)->lines}; # if the path is not contained in a single island we need to retract - my $need_retract = !$self->config->only_retract_when_crossing_perimeters; + my $need_retract = !$self->print_config->only_retract_when_crossing_perimeters; if (!$need_retract) { $need_retract = 1; foreach my $island (@{$self->_upper_layer_islands}) { @@ -481,14 +474,14 @@ sub retract { if ($self->extruder->wipe && $self->wipe_path) { my @points = @{$self->wipe_path}; $wipe_path = Slic3r::Polyline->new($self->last_pos, @{$self->wipe_path}[1..$#{$self->wipe_path}]); - $wipe_path->clip_end($wipe_path->length - $self->extruder->scaled_wipe_distance($self->config->travel_speed)); + $wipe_path->clip_end($wipe_path->length - $self->extruder->scaled_wipe_distance($self->print_config->travel_speed)); } # prepare moves my $retract = [undef, undef, -$length, $comment]; - my $lift = ($self->extruder->retract_lift == 0 || defined $params{move_z}) && !$self->lifted + my $lift = ($self->_retract_lift == 0 || defined $params{move_z}) && !$self->lifted ? undef - : [undef, $self->z + $self->extruder->retract_lift, 0, 'lift plate during travel']; + : [undef, $self->z + $self->_retract_lift, 0, 'lift plate during travel']; # check that we have a positive wipe length if ($wipe_path) { @@ -500,7 +493,7 @@ sub retract { my $segment_length = $line->length; # reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one # due to rounding - my $e = $retract->[2] * ($segment_length / $self->extruder->scaled_wipe_distance($self->config->travel_speed)) * 0.95; + my $e = $retract->[2] * ($segment_length / $self->extruder->scaled_wipe_distance($self->print_config->travel_speed)) * 0.95; $retracted += $e; $gcode .= $self->G1($line->b, undef, $e, $retract->[3] . ";_WIPE"); } @@ -510,7 +503,7 @@ sub retract { $self->speed('retract'); $gcode .= $self->G1(undef, undef, $retract->[2] - $retracted, $comment); } - } elsif ($self->config->use_firmware_retraction) { + } elsif ($self->print_config->use_firmware_retraction) { $gcode .= "G10 ; retract\n"; } else { $self->speed('retract'); @@ -518,23 +511,23 @@ sub retract { } if (!$self->lifted) { $self->speed('travel'); - if (defined $params{move_z} && $self->extruder->retract_lift > 0) { - my $travel = [undef, $params{move_z} + $self->extruder->retract_lift, 0, 'move to next layer (' . $self->layer->id . ') and lift']; + if (defined $params{move_z} && $self->_retract_lift > 0) { + my $travel = [undef, $params{move_z} + $self->_retract_lift, 0, 'move to next layer (' . $self->layer->id . ') and lift']; $gcode .= $self->G0(@$travel); - $self->lifted($self->extruder->retract_lift); + $self->lifted($self->_retract_lift); } elsif ($lift) { $gcode .= $self->G1(@$lift); } } $self->extruder->retracted($self->extruder->retracted + $length); $self->extruder->restart_extra($restart_extra); - $self->lifted($self->extruder->retract_lift) if $lift; + $self->lifted($self->_retract_lift) if $lift; # reset extrusion distance during retracts # this makes sure we leave sufficient precision in the firmware $gcode .= $self->reset_e; - $gcode .= "M103 ; extruder off\n" if $self->config->gcode_flavor eq 'makerware'; + $gcode .= "M103 ; extruder off\n" if $self->print_config->gcode_flavor eq 'makerware'; return $gcode; } @@ -543,7 +536,7 @@ sub unretract { my ($self) = @_; my $gcode = ""; - $gcode .= "M101 ; extruder on\n" if $self->config->gcode_flavor eq 'makerware'; + $gcode .= "M101 ; extruder on\n" if $self->print_config->gcode_flavor eq 'makerware'; if ($self->lifted) { $self->speed('travel'); @@ -554,15 +547,15 @@ sub unretract { my $to_unretract = $self->extruder->retracted + $self->extruder->restart_extra; if ($to_unretract) { $self->speed('retract'); - if ($self->config->use_firmware_retraction) { + if ($self->print_config->use_firmware_retraction) { $gcode .= "G11 ; unretract\n"; - } elsif ($self->config->extrusion_axis) { + } elsif ($self->_extrusion_axis) { # use G1 instead of G0 because G0 will blend the restart with the previous travel move $gcode .= sprintf "G1 %s%.5f F%.3f", - $self->config->extrusion_axis, + $self->_extrusion_axis, $self->extruder->extrude($to_unretract), $self->extruder->retract_speed_mm_min; - $gcode .= " ; compensate retraction" if $self->config->gcode_comments; + $gcode .= " ; compensate retraction" if $self->print_config->gcode_comments; $gcode .= "\n"; } $self->extruder->retracted(0); @@ -574,11 +567,11 @@ sub unretract { sub reset_e { my ($self) = @_; - return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerware|sailfish)$/; + return "" if $self->print_config->gcode_flavor =~ /^(?:mach3|makerware|sailfish)$/; $self->extruder->E(0) if $self->extruder; - return sprintf "G92 %s0%s\n", $self->config->extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '') - if $self->config->extrusion_axis && !$self->config->use_relative_e_distances; + return sprintf "G92 %s0%s\n", $self->_extrusion_axis, ($self->print_config->gcode_comments ? ' ; reset extrusion distance' : '') + if $self->_extrusion_axis && !$self->print_config->use_relative_e_distances; } sub set_acceleration { @@ -586,12 +579,12 @@ sub set_acceleration { return "" if !$acceleration; return sprintf "M204 S%s%s\n", - $acceleration, ($self->config->gcode_comments ? ' ; adjust acceleration' : ''); + $acceleration, ($self->print_config->gcode_comments ? ' ; adjust acceleration' : ''); } sub G0 { my $self = shift; - return $self->G1(@_) if !($self->config->g0 || $self->config->gcode_flavor eq 'mach3'); + return $self->G1(@_) if !($self->print_config->g0 || $self->print_config->gcode_flavor eq 'mach3'); return $self->_G0_G1("G0", @_); } @@ -628,23 +621,23 @@ sub _Gx { $gcode .= sprintf " F%.3f", $F; # output extrusion distance - if ($e && $self->config->extrusion_axis) { - $gcode .= sprintf " %s%.5f", $self->config->extrusion_axis, $self->extruder->extrude($e); + if ($e && $self->_extrusion_axis) { + $gcode .= sprintf " %s%.5f", $self->_extrusion_axis, $self->extruder->extrude($e); } - $gcode .= " ; $comment" if $comment && $self->config->gcode_comments; + $gcode .= " ; $comment" if $comment && $self->print_config->gcode_comments; return "$gcode\n"; } sub set_extruder { - my ($self, $extruder) = @_; + my ($self, $extruder_id) = @_; # return nothing if this extruder was already selected - return "" if (defined $self->extruder) && ($self->extruder->id == $extruder->id); + return "" if (defined $self->extruder) && ($self->extruder->id == $extruder_id); # if we are running a single-extruder setup, just set the extruder and return nothing if (!$self->multiple_extruders) { - $self->extruder($extruder); + $self->extruder($self->extruders->{$extruder_id}); return ""; } @@ -653,10 +646,10 @@ sub set_extruder { $gcode .= $self->retract(toolchange => 1) if defined $self->extruder; # append custom toolchange G-code - if (defined $self->extruder && $self->config->toolchange_gcode) { - $gcode .= sprintf "%s\n", $self->replace_variables($self->config->toolchange_gcode, { + if (defined $self->extruder && $self->print_config->toolchange_gcode) { + $gcode .= sprintf "%s\n", $self->replace_variables($self->print_config->toolchange_gcode, { previous_extruder => $self->extruder->id, - next_extruder => $extruder->id, + next_extruder => $extruder_id, }); } @@ -669,24 +662,24 @@ sub set_extruder { ? $self->extruder->first_layer_temperature : $self->extruder->temperature; # we assume that heating is always slower than cooling, so no need to block - $gcode .= $self->set_temperature($temp + $self->config->standby_temperature_delta, 0); + $gcode .= $self->set_temperature($temp + $self->print_config->standby_temperature_delta, 0); } # set the new extruder - $self->extruder($extruder); + $self->extruder($self->extruders->{$extruder_id}); $gcode .= sprintf "%s%d%s\n", - ($self->config->gcode_flavor eq 'makerware' + ($self->print_config->gcode_flavor eq 'makerware' ? 'M135 T' - : $self->config->gcode_flavor eq 'sailfish' + : $self->print_config->gcode_flavor eq 'sailfish' ? 'M108 T' : 'T'), - $extruder->id, - ($self->config->gcode_comments ? ' ; change extruder' : ''); + $extruder_id, + ($self->print_config->gcode_comments ? ' ; change extruder' : ''); $gcode .= $self->reset_e; # set the new extruder to the operating temperature - if ($self->config->ooze_prevention) { + if ($self->print_config->ooze_prevention) { my $temp = defined $self->layer && $self->layer->id == 0 ? $self->extruder->first_layer_temperature : $self->extruder->temperature; @@ -702,18 +695,18 @@ sub set_fan { if ($self->last_fan_speed != $speed || $dont_save) { $self->last_fan_speed($speed) if !$dont_save; if ($speed == 0) { - my $code = $self->config->gcode_flavor eq 'teacup' + my $code = $self->print_config->gcode_flavor eq 'teacup' ? 'M106 S0' - : $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/ + : $self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/ ? 'M127' : 'M107'; - return sprintf "$code%s\n", ($self->config->gcode_comments ? ' ; disable fan' : ''); + return sprintf "$code%s\n", ($self->print_config->gcode_comments ? ' ; disable fan' : ''); } else { - if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) { - return sprintf "M126%s\n", ($self->config->gcode_comments ? ' ; enable fan' : ''); + if ($self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/) { + return sprintf "M126%s\n", ($self->print_config->gcode_comments ? ' ; enable fan' : ''); } else { - return sprintf "M106 %s%d%s\n", ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), - (255 * $speed / 100), ($self->config->gcode_comments ? ' ; enable fan' : ''); + return sprintf "M106 %s%d%s\n", ($self->print_config->gcode_flavor eq 'mach3' ? 'P' : 'S'), + (255 * $speed / 100), ($self->print_config->gcode_comments ? ' ; enable fan' : ''); } } } @@ -723,17 +716,17 @@ sub set_fan { sub set_temperature { my ($self, $temperature, $wait, $tool) = @_; - return "" if $wait && $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/; + return "" if $wait && $self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/; - my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup') + my ($code, $comment) = ($wait && $self->print_config->gcode_flavor ne 'teacup') ? ('M109', 'wait for temperature to be reached') : ('M104', 'set temperature'); my $gcode = sprintf "$code %s%d %s; $comment\n", - ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature, - (defined $tool && ($self->multiple_extruders || $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/)) ? "T$tool " : ""; + ($self->print_config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature, + (defined $tool && ($self->multiple_extruders || $self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/)) ? "T$tool " : ""; $gcode .= "M116 ; wait for temperature to be reached\n" - if $self->config->gcode_flavor eq 'teacup' && $wait; + if $self->print_config->gcode_flavor eq 'teacup' && $wait; return $gcode; } @@ -741,21 +734,21 @@ sub set_temperature { sub set_bed_temperature { my ($self, $temperature, $wait) = @_; - my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup') - ? (($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached') + my ($code, $comment) = ($wait && $self->print_config->gcode_flavor ne 'teacup') + ? (($self->print_config->gcode_flavor =~ /^(?:makerware|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached') : ('M140', 'set bed temperature'); my $gcode = sprintf "$code %s%d ; $comment\n", - ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature; + ($self->print_config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature; $gcode .= "M116 ; wait for bed temperature to be reached\n" - if $self->config->gcode_flavor eq 'teacup' && $wait; + if $self->print_config->gcode_flavor eq 'teacup' && $wait; return $gcode; } sub replace_variables { my ($self, $string, $extra) = @_; - return $self->config->replace_options($string, { %{$self->extra_variables}, %{ $extra || {} } }); + return $self->print_config->replace_options($string, { %{$self->extra_variables}, %{ $extra || {} } }); } 1; diff --git a/lib/Slic3r/GCode/ArcFitting.pm b/lib/Slic3r/GCode/ArcFitting.pm index 52078bdc0..fe45bdb36 100644 --- a/lib/Slic3r/GCode/ArcFitting.pm +++ b/lib/Slic3r/GCode/ArcFitting.pm @@ -104,7 +104,7 @@ sub flush_path { $gcode .= sprintf " I%.3f J%.3f", map { unscale($arc_center->[$_] - $cur_path->[0][$_]) } (X,Y); my $E = 0; # TODO: compute E using $length - $gcode .= sprintf(" %s%.5f", $self->config->extrusion_axis, $E) + $gcode .= sprintf(" %s%.5f", $self->config->get_extrusion_axis, $E) if $E; my $F = 0; # TODO: extract F from original moves diff --git a/lib/Slic3r/GCode/CoolingBuffer.pm b/lib/Slic3r/GCode/CoolingBuffer.pm index d95b5fec1..91119fe3e 100644 --- a/lib/Slic3r/GCode/CoolingBuffer.pm +++ b/lib/Slic3r/GCode/CoolingBuffer.pm @@ -1,7 +1,7 @@ package Slic3r::GCode::CoolingBuffer; use Moo; -has 'config' => (is => 'ro', required => 1); +has 'config' => (is => 'ro', required => 1); # Slic3r::Config::Print has 'gcodegen' => (is => 'ro', required => 1); has 'gcode' => (is => 'rw', default => sub {""}); has 'elapsed_time' => (is => 'rw', default => sub {0}); diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index f888ddac2..08de360ec 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -4,8 +4,8 @@ use Moo; use List::Util qw(first); use Slic3r::Geometry qw(X Y unscale); -has 'print' => (is => 'ro', required => 1, handles => [qw(extruders)]); -has 'gcodegen' => (is => 'ro', required => 1); +has 'print' => (is => 'ro', required => 1); +has 'gcodegen' => (is => 'ro', required => 1, handles => [qw(extruders)]); has 'shift' => (is => 'ro', default => sub { [0,0] }); has 'spiralvase' => (is => 'lazy'); @@ -19,24 +19,24 @@ has '_last_obj_copy' => (is => 'rw'); sub _build_spiralvase { my $self = shift; - return $self->gcodegen->config->spiral_vase - ? Slic3r::GCode::SpiralVase->new(config => $self->gcodegen->config) + return $self->print->config->spiral_vase + ? Slic3r::GCode::SpiralVase->new(config => $self->print->config) : undef; } sub _build_vibration_limit { my $self = shift; - return $self->gcodegen->config->vibration_limit - ? Slic3r::GCode::VibrationLimit->new(config => $self->gcodegen->config) + return $self->print->config->vibration_limit + ? Slic3r::GCode::VibrationLimit->new(config => $self->print->config) : undef; } sub _build_arc_fitting { my $self = shift; - return $self->gcodegen->config->gcode_arcs - ? Slic3r::GCode::ArcFitting->new(config => $self->gcodegen->config) + return $self->print->config->gcode_arcs + ? Slic3r::GCode::ArcFitting->new(config => $self->print->config) : undef; } @@ -45,45 +45,49 @@ sub process_layer { my ($layer, $object_copies) = @_; my $gcode = ""; + my $object = $layer->object; + # check whether we're going to apply spiralvase logic my $spiralvase = defined $self->spiralvase - && ($layer->id > 0 || $self->gcodegen->config->brim_width == 0) - && ($layer->id >= $self->gcodegen->config->skirt_height && $self->gcodegen->config->skirt_height != -1) - && ($layer->id >= $self->gcodegen->config->bottom_solid_layers); + && ($layer->id > 0 || $self->print->config->brim_width == 0) + && ($layer->id >= $self->print->config->skirt_height && $self->print->config->skirt_height != -1) + && !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions}); # if we're going to apply spiralvase to this layer, disable loop clipping $self->gcodegen->enable_loop_clipping(!$spiralvase); if (!$self->second_layer_things_done && $layer->id == 1) { - for my $t (grep $self->extruders->[$_], 0 .. $#{$self->gcodegen->config->temperature}) { - $gcode .= $self->gcodegen->set_temperature($self->extruders->[$t]->temperature, 0, $t) - if $self->print->extruders->[$t]->temperature && $self->extruders->[$t]->temperature != $self->extruders->[$t]->first_layer_temperature; + for my $extruder_id (sort keys %{$self->extruders}) { + my $extruder = $self->extruders->{$extruder_id}; + $gcode .= $self->gcodegen->set_temperature($extruder->temperature, 0, $extruder->id) + if $extruder->temperature && $extruder->temperature != $extruder->first_layer_temperature; } - $gcode .= $self->gcodegen->set_bed_temperature($self->gcodegen->config->bed_temperature) - if $self->gcodegen->config->bed_temperature && $self->gcodegen->config->bed_temperature != $self->gcodegen->config->first_layer_bed_temperature; + $gcode .= $self->gcodegen->set_bed_temperature($self->print->config->bed_temperature) + if $self->print->config->bed_temperature && $self->print->config->bed_temperature != $self->print->config->first_layer_bed_temperature; $self->second_layer_things_done(1); } # set new layer - this will change Z and force a retraction if retract_layer_change is enabled $gcode .= $self->gcodegen->change_layer($layer); - $gcode .= $self->gcodegen->replace_variables($self->gcodegen->config->layer_gcode, { + $gcode .= $self->gcodegen->replace_variables($self->print->config->layer_gcode, { layer_num => $self->gcodegen->layer->id, - }) . "\n" if $self->gcodegen->config->layer_gcode; + }) . "\n" if $self->print->config->layer_gcode; # extrude skirt - if (((values %{$self->skirt_done}) < $self->gcodegen->config->skirt_height || $self->gcodegen->config->skirt_height == -1) + if (((values %{$self->skirt_done}) < $self->print->config->skirt_height || $self->print->config->skirt_height == -1) && !$self->skirt_done->{$layer->print_z}) { $self->gcodegen->set_shift(@{$self->shift}); - $gcode .= $self->gcodegen->set_extruder($self->extruders->[0]); + my @extruder_ids = sort keys %{$self->extruders}; + $gcode .= $self->gcodegen->set_extruder($extruder_ids[0]); # skip skirt if we have a large brim - if ($layer->id < $self->gcodegen->config->skirt_height || $self->gcodegen->config->skirt_height == -1) { + if ($layer->id < $self->print->config->skirt_height || $self->print->config->skirt_height == -1) { # distribute skirt loops across all extruders my @skirt_loops = @{$self->print->skirt}; for my $i (0 .. $#skirt_loops) { # when printing layers > 0 ignore 'min_skirt_length' and # just use the 'skirts' setting; also just use the current extruder - last if ($layer->id > 0) && ($i >= $self->gcodegen->config->skirts); - $gcode .= $self->gcodegen->set_extruder($self->extruders->[ ($i/@{$self->extruders}) % @{$self->extruders} ]) + last if ($layer->id > 0) && ($i >= $self->print->config->skirts); + $gcode .= $self->gcodegen->set_extruder(($i/@extruder_ids) % @extruder_ids) if $layer->id == 0; $gcode .= $self->gcodegen->extrude_loop($skirt_loops[$i], 'skirt'); } @@ -94,7 +98,7 @@ sub process_layer { # extrude brim if (!$self->brim_done) { - $gcode .= $self->gcodegen->set_extruder($self->extruders->[$self->gcodegen->config->support_material_extruder-1]); + $gcode .= $self->gcodegen->set_extruder($self->print->objects->[0]->config->support_material_extruder-1); $self->gcodegen->set_shift(@{$self->shift}); $gcode .= $self->gcodegen->extrude_loop($_, 'brim') for @{$self->print->brim}; $self->brim_done(1); @@ -109,15 +113,17 @@ sub process_layer { # extrude support material before other things because it might use a lower Z # and also because we avoid travelling on other things when printing it - if ($self->print->has_support_material && $layer->isa('Slic3r::Layer::Support')) { + if ($layer->isa('Slic3r::Layer::Support')) { if ($layer->support_interface_fills->count > 0) { - $gcode .= $self->gcodegen->set_extruder($self->extruders->[$self->gcodegen->config->support_material_interface_extruder-1]); - $gcode .= $self->gcodegen->extrude_path($_, 'support material interface') + $gcode .= $self->gcodegen->set_extruder($object->config->support_material_interface_extruder-1); + my %params = (speed => $object->config->support_material_speed*60); + $gcode .= $self->gcodegen->extrude_path($_, 'support material interface', %params) for @{$layer->support_interface_fills->chained_path_from($self->gcodegen->last_pos, 0)}; } if ($layer->support_fills->count > 0) { - $gcode .= $self->gcodegen->set_extruder($self->extruders->[$self->gcodegen->config->support_material_extruder-1]); - $gcode .= $self->gcodegen->extrude_path($_, 'support material') + $gcode .= $self->gcodegen->set_extruder($object->config->support_material_extruder-1); + my %params = (speed => $object->config->support_material_speed*60); + $gcode .= $self->gcodegen->extrude_path($_, 'support material', %params) for @{$layer->support_fills->chained_path_from($self->gcodegen->last_pos, 0)}; } } @@ -126,16 +132,17 @@ sub process_layer { my @region_ids = 0 .. ($self->print->regions_count-1); if ($self->gcodegen->multiple_extruders) { my $last_extruder = $self->gcodegen->extruder; - my $best_region_id = first { $self->print->regions->[$_]->extruders->{perimeter} eq $last_extruder } @region_ids; + my $best_region_id = first { $self->print->regions->[$_]->config->perimeter_extruder-1 eq $last_extruder } @region_ids; @region_ids = ($best_region_id, grep $_ != $best_region_id, @region_ids) if $best_region_id; } foreach my $region_id (@region_ids) { my $layerm = $layer->regions->[$region_id] or next; my $region = $self->print->regions->[$region_id]; + $self->gcodegen->region($region); my @islands = (); - if ($self->gcodegen->config->avoid_crossing_perimeters) { + if ($self->print->config->avoid_crossing_perimeters) { push @islands, { perimeters => [], fills => [] } for 1 .. (@{$layer->slices} || 1); # make sure we have at least one island hash to avoid failure of the -1 subscript below PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) { @@ -166,8 +173,8 @@ sub process_layer { foreach my $island (@islands) { # give priority to infill if we were already using its extruder and it wouldn't # be good for perimeters - if ($self->gcodegen->config->infill_first - || ($self->gcodegen->multiple_extruders && $region->extruders->{infill} eq $self->gcodegen->extruder) && $region->extruders->{infill} ne $region->extruders->{perimeter}) { + if ($self->print->config->infill_first + || ($self->gcodegen->multiple_extruders && $region->config->infill_extruder-1 == $self->gcodegen->extruder->id && $region->config->infill_extruder != $region->config->perimeter_extruder)) { $gcode .= $self->_extrude_infill($island, $region); $gcode .= $self->_extrude_perimeters($island, $region); } else { @@ -184,11 +191,11 @@ sub process_layer { # apply vibration limit if enabled $gcode = $self->vibration_limit->process($gcode) - if $self->gcodegen->config->vibration_limit != 0; + if $self->print->config->vibration_limit != 0; # apply arc fitting if enabled $gcode = $self->arc_fitting->process($gcode) - if $self->gcodegen->config->gcode_arcs; + if $self->print->config->gcode_arcs; return $gcode; } @@ -200,7 +207,7 @@ sub _extrude_perimeters { return "" if !@{ $island->{perimeters} }; my $gcode = ""; - $gcode .= $self->gcodegen->set_extruder($region->extruders->{perimeter}); + $gcode .= $self->gcodegen->set_extruder($region->config->perimeter_extruder-1); $gcode .= $self->gcodegen->extrude($_, 'perimeter') for @{ $island->{perimeters} }; return $gcode; } @@ -212,7 +219,7 @@ sub _extrude_infill { return "" if !@{ $island->{fills} }; my $gcode = ""; - $gcode .= $self->gcodegen->set_extruder($region->extruders->{infill}); + $gcode .= $self->gcodegen->set_extruder($region->config->infill_extruder-1); for my $fill (@{ $island->{fills} }) { if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { $gcode .= $self->gcodegen->extrude($_, 'fill') diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 4bc0a2728..a00b67da5 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -387,59 +387,65 @@ sub load_file { my $model = eval { Slic3r::Model->read_from_file($input_file) }; Slic3r::GUI::show_error($self, $@) if $@; - $self->load_model_object($_) for @{$model->objects}; + $self->load_model_objects(@{$model->objects}); $process_dialog->Destroy; $self->statusbar->SetStatusText("Loaded " . basename($input_file)); } -sub load_model_object { - my ($self, $model_object) = @_; - - my $o = $self->{model}->add_object($model_object); - - push @{ $self->{objects} }, Slic3r::GUI::Plater::Object->new( - name => basename($model_object->input_file), - ); +sub load_model_objects { + my ($self, @model_objects) = @_; my $need_arrange = 0; - if (!defined $model_object->instances) { - # if object has no defined position(s) we need to rearrange everything after loading - $need_arrange = 1; + my @obj_idx = (); + foreach my $model_object (@model_objects) { + my $o = $self->{model}->add_object($model_object); - # add a default instance and center object around origin - $o->center_around_origin; - $o->add_instance(offset => [ @{$self->{config}->print_center} ]); - } + push @{ $self->{objects} }, Slic3r::GUI::Plater::Object->new( + name => basename($model_object->input_file), + ); + push @obj_idx, $#{ $self->{objects} }; - $self->{print}->add_model_object($o); + if (!defined $model_object->instances) { + # if object has no defined position(s) we need to rearrange everything after loading + $need_arrange = 1; + + # add a default instance and center object around origin + $o->center_around_origin; + $o->add_instance(offset => [ @{$self->{config}->print_center} ]); + } + + $self->{print}->add_model_object($o); + } # if user turned autocentering off, automatic arranging would disappoint them if (!$Slic3r::GUI::Settings->{_}{autocenter}) { $need_arrange = 0; } - $self->object_loaded($#{ $self->{objects} }, no_arrange => !$need_arrange); + $self->objects_loaded(\@obj_idx, no_arrange => !$need_arrange); } -sub object_loaded { +sub objects_loaded { my $self = shift; - my ($obj_idx, %params) = @_; + my ($obj_idxs, %params) = @_; - my $object = $self->{objects}[$obj_idx]; - my $model_object = $self->{model}->objects->[$obj_idx]; - $self->{list}->InsertStringItem($obj_idx, $object->name); - $self->{list}->SetItemFont($obj_idx, Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL)) - if $self->{list}->can('SetItemFont'); # legacy code for wxPerl < 0.9918 not supporting SetItemFont() + foreach my $obj_idx (@$obj_idxs) { + my $object = $self->{objects}[$obj_idx]; + my $model_object = $self->{model}->objects->[$obj_idx]; + $self->{list}->InsertStringItem($obj_idx, $object->name); + $self->{list}->SetItemFont($obj_idx, Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL)) + if $self->{list}->can('SetItemFont'); # legacy code for wxPerl < 0.9918 not supporting SetItemFont() - $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); - $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); + $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); + $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); - $self->make_thumbnail($obj_idx); + $self->make_thumbnail($obj_idx); + } $self->arrange unless $params{no_arrange}; $self->update; $self->{list}->Update; - $self->{list}->Select($obj_idx, 1); + $self->{list}->Select($obj_idxs->[-1], 1); $self->object_list_changed; } @@ -638,7 +644,6 @@ sub split_object { input_file => $current_model_object->input_file, config => $current_model_object->config->clone, layer_height_ranges => $current_model_object->layer_height_ranges, # TODO: clone this - material_mapping => $current_model_object->material_mapping, # TODO: clone this ); $model_object->add_volume( mesh => $mesh, @@ -658,7 +663,7 @@ sub split_object { } # we need to center this single object around origin $model_object->center_around_origin; - $self->load_model_object($model_object); + $self->load_model_objects($model_object); } } @@ -755,11 +760,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 30a5dadeb..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,7 +334,12 @@ sub Closing { my $self = shift; # save mappings into the plater object - $self->model_object->material_mapping($self->{mapping}); + 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 }-1); + } + } } 1; diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index ee11147bd..371698e0a 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -88,25 +88,15 @@ sub quick_slice { my $self = shift; my %params = @_; - my $process_dialog; + my $progress_dialog; eval { # validate configuration my $config = $self->config; $config->validate; - - # confirm slicing of more than one copies - my $copies = $config->duplicate_grid->[X] * $config->duplicate_grid->[Y]; - $copies = $config->duplicate if $config->duplicate > 1; - if ($copies > 1) { - my $confirmation = Wx::MessageDialog->new($self, "Are you sure you want to slice $copies copies?", - 'Multiple Copies', wxICON_QUESTION | wxOK | wxCANCEL); - return unless $confirmation->ShowModal == wxID_OK; - } # select input file - my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; - my $input_file; + my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || ''; if (!$params{reslice}) { my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF):', $dir, "", MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { @@ -133,28 +123,23 @@ sub quick_slice { $Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file); Slic3r::GUI->save_settings; - my $print = $self->init_print; - my $model = eval { Slic3r::Model->read_from_file($input_file) }; - Slic3r::GUI::show_error($self, $@) if $@; + my $sprint = Slic3r::Print::Simple->new( + status_cb => sub { + my ($percent, $message) = @_; + return if &Wx::wxVERSION_STRING !~ / 2\.(8\.|9\.[2-9])/; + $progress_dialog->Update($percent, "$message…"); + }, + ); - if ($model->has_objects_with_no_instances) { - # apply a default position to all objects not having one - foreach my $object (@{$model->objects}) { - $object->add_instance(offset => [0,0]) if !defined $object->instances; - } - $model->arrange_objects($config->min_object_distance); - } - $model->center_instances_around_point($config->print_center); + $sprint->apply_config($config); + $sprint->set_model(Slic3r::Model->read_from_file($input_file)); - $print->add_model_object($_) for @{ $model->objects }; - $print->validate; - # select output file - my $output_file = $main::opt{output}; + my $output_file; if ($params{reslice}) { $output_file = $last_output_file if defined $last_output_file; } elsif ($params{save_as}) { - $output_file = $print->expanded_output_filepath($output_file); + $output_file = $sprint->expanded_output_filepath; $output_file =~ s/\.gcode$/.svg/i if $params{export_svg}; my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', Slic3r::GUI->output_path(dirname($output_file)), @@ -171,40 +156,32 @@ sub quick_slice { } # show processbar dialog - $process_dialog = Wx::ProgressDialog->new('Slicing…', "Processing $input_file_basename…", + $progress_dialog = Wx::ProgressDialog->new('Slicing…', "Processing $input_file_basename…", 100, $self, 0); - $process_dialog->Pulse; + $progress_dialog->Pulse; { my @warnings = (); local $SIG{__WARN__} = sub { push @warnings, $_[0] }; - my %export_params = ( - output_file => $output_file, - ); - $print->status_cb(sub { - my ($percent, $message) = @_; - if (&Wx::wxVERSION_STRING =~ / 2\.(8\.|9\.[2-9])/) { - $process_dialog->Update($percent, "$message…"); - } - }); + + $sprint->output_file($output_file); if ($params{export_svg}) { - $print->export_svg(%export_params); + $sprint->export_svg; } else { - $print->process; - $print->export_gcode(%export_params); + $sprint->export_gcode; } - $print->status_cb(undef); + $sprint->status_cb(undef); Slic3r::GUI::warning_catcher($self)->($_) for @warnings; } - $process_dialog->Destroy; - undef $process_dialog; + $progress_dialog->Destroy; + undef $progress_dialog; my $message = "$input_file_basename was successfully sliced."; &Wx::wxTheApp->notify($message); Wx::MessageDialog->new($self, $message, 'Slicing Done!', wxOK | wxICON_INFORMATION)->ShowModal; }; - Slic3r::GUI::catch_error($self, sub { $process_dialog->Destroy if $process_dialog }); + Slic3r::GUI::catch_error($self, sub { $progress_dialog->Destroy if $progress_dialog }); } sub repair_stl { diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 53f40a659..53b3834d3 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -3,6 +3,7 @@ use Moo; use List::Util qw(sum first); use Slic3r::ExtrusionPath ':roles'; +use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(PI A B scale unscale chained_path points_coincide); use Slic3r::Geometry::Clipper qw(union_ex diff_ex intersection_ex offset offset2 offset2_ex union_pt diff intersection @@ -13,14 +14,9 @@ has 'layer' => ( is => 'ro', weak_ref => 1, required => 1, - trigger => 1, - handles => [qw(id slice_z print_z height flow config)], + handles => [qw(id slice_z print_z height object print)], ); -has 'region' => (is => 'ro', required => 1, handles => [qw(extruders)]); -has 'perimeter_flow' => (is => 'rw'); -has 'infill_flow' => (is => 'rw'); -has 'solid_infill_flow' => (is => 'rw'); -has 'top_infill_flow' => (is => 'rw'); +has 'region' => (is => 'ro', required => 1, handles => [qw(config)]); has 'infill_area_threshold' => (is => 'lazy'); has 'overhang_width' => (is => 'lazy'); @@ -40,43 +36,26 @@ has 'perimeters' => (is => 'rw', default => sub { Slic3r::ExtrusionPath::Collect # ordered collection of extrusion paths to fill surfaces has 'fills' => (is => 'rw', default => sub { Slic3r::ExtrusionPath::Collection->new }); -sub BUILD { - my $self = shift; - $self->_update_flows; -} - -sub _trigger_layer { - my $self = shift; - $self->_update_flows; -} - -sub _update_flows { - my $self = shift; - return if !$self->region; - - if ($self->id == 0) { - for (qw(perimeter infill solid_infill top_infill)) { - my $method = "${_}_flow"; - $self->$method - ($self->region->first_layer_flows->{$_} || $self->region->flows->{$_}); - } - } else { - $self->perimeter_flow($self->region->flows->{perimeter}); - $self->infill_flow($self->region->flows->{infill}); - $self->solid_infill_flow($self->region->flows->{solid_infill}); - $self->top_infill_flow($self->region->flows->{top_infill}); - } -} - sub _build_overhang_width { my $self = shift; - my $threshold_rad = PI/2 - atan2($self->perimeter_flow->width / $self->height / 2, 1); + my $threshold_rad = PI/2 - atan2($self->flow(FLOW_ROLE_PERIMETER)->width / $self->height / 2, 1); return scale($self->height * ((cos $threshold_rad) / (sin $threshold_rad))); } sub _build_infill_area_threshold { my $self = shift; - return $self->solid_infill_flow->scaled_spacing ** 2; + return $self->flow(FLOW_ROLE_SOLID_INFILL)->scaled_spacing ** 2; +} + +sub flow { + my ($self, $role, $bridge, $width) = @_; + return $self->region->flow( + $role, + $self->layer->height, + $bridge // 0, + $self->layer->id == 0, + $width, + ); } # build polylines from lines @@ -145,10 +124,12 @@ sub _merge_loops { sub make_perimeters { my $self = shift; - my $pwidth = $self->perimeter_flow->scaled_width; - my $pspacing = $self->perimeter_flow->scaled_spacing; - my $ispacing = $self->solid_infill_flow->scaled_spacing; - my $gap_area_threshold = $self->perimeter_flow->scaled_width ** 2; + my $perimeter_flow = $self->flow(FLOW_ROLE_PERIMETER); + my $mm3_per_mm = $perimeter_flow->mm3_per_mm($self->height); + my $pwidth = $perimeter_flow->scaled_width; + my $pspacing = $perimeter_flow->scaled_spacing; + my $ispacing = $self->flow(FLOW_ROLE_SOLID_INFILL)->scaled_spacing; + my $gap_area_threshold = $pwidth ** 2; $self->perimeters->clear; $self->fill_surfaces->clear; @@ -188,7 +169,7 @@ sub make_perimeters { @offsets = @{offset2(\@last, -(1.5*$pspacing - 1), +(0.5*$pspacing - 1))}; # look for gaps - if ($self->config->gap_fill_speed > 0 && $self->config->fill_density > 0) { + if ($self->print->config->gap_fill_speed > 0 && $self->config->fill_density > 0) { my $diff = diff_ex( offset(\@last, -0.5*$pspacing), offset(\@offsets, +0.5*$pspacing), @@ -284,7 +265,7 @@ sub make_perimeters { push @loops, Slic3r::ExtrusionLoop->new( polygon => $polygon, role => $role, - flow_spacing => $self->perimeter_flow->spacing, + mm3_per_mm => $mm3_per_mm, ); } return @loops; @@ -297,8 +278,8 @@ sub make_perimeters { # we continue inwards after having finished the brim # TODO: add test for perimeter order @loops = reverse @loops - if $self->config->external_perimeters_first - || ($self->layer->id == 0 && $self->config->brim_width > 0); + if $self->print->config->external_perimeters_first + || ($self->layer->id == 0 && $self->print->config->brim_width > 0); # append perimeters $self->perimeters->append(@loops); @@ -310,8 +291,8 @@ sub make_perimeters { for my $p (@p) { next if $p->length <= $pspacing * 2; my %params = ( - role => EXTR_ROLE_EXTERNAL_PERIMETER, - flow_spacing => $self->perimeter_flow->spacing, + role => EXTR_ROLE_EXTERNAL_PERIMETER, + mm3_per_mm => $mm3_per_mm, ); push @paths, $p->isa('Slic3r::Polygon') ? Slic3r::ExtrusionLoop->new(polygon => $p, %params) @@ -345,10 +326,10 @@ sub _fill_gaps { # we could try with 1.5*$w for example, but that doesn't work well for zigzag fill # because it tends to create very sparse points along the gap when the infill direction # is not parallel to the gap (1.5*$w thus may only work well with a straight line) - my $w = $self->perimeter_flow->width; + my $w = $self->flow(FLOW_ROLE_PERIMETER)->width; my @widths = ($w, 0.4 * $w); # worth trying 0.2 too? foreach my $width (@widths) { - my $flow = $self->perimeter_flow->clone(width => $width); + my $flow = $self->flow(FLOW_ROLE_PERIMETER, 0, $width); # extract the gaps having this width my @this_width = map @{$_->offset_ex(+0.5*$flow->scaled_width)}, @@ -359,8 +340,8 @@ sub _fill_gaps { # fill gaps using dynamic extrusion width, by treating them like thin polygons, # thus generating the skeleton and using it to fill them my %path_args = ( - role => EXTR_ROLE_SOLIDFILL, - flow_spacing => $flow->spacing, + role => EXTR_ROLE_SOLIDFILL, + mm3_per_mm => $flow->mm3_per_mm($self->height), ); $self->thin_fills->append(map { $_->isa('Slic3r::Polygon') @@ -380,9 +361,10 @@ sub _fill_gaps { foreach my $expolygon (@infill) { my ($params, @paths) = $filler->fill_surface( Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNALSOLID), - density => 1, - flow_spacing => $flow->spacing, + density => 1, + flow => $flow, ); + my $mm3_per_mm = $params->{flow}->mm3_per_mm($self->height); # Split polylines into lines so that the chained_path() search # at the final stage has more freedom and will choose starting @@ -399,8 +381,7 @@ sub _fill_gaps { @paths = map Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@$_), role => EXTR_ROLE_GAPFILL, - height => $self->height, - flow_spacing => $params->{flow_spacing}, + mm3_per_mm => $mm3_per_mm, ), @lines; $_->simplify($flow->scaled_width/3) for @paths; @@ -499,7 +480,10 @@ sub process_external_surfaces { sub _detect_bridge_direction { my ($self, $expolygon, $lower_layer) = @_; - my $grown = $expolygon->offset_ex(+$self->perimeter_flow->scaled_width); + my $perimeter_flow = $self->flow(FLOW_ROLE_PERIMETER); + my $infill_flow = $self->flow(FLOW_ROLE_INFILL); + + my $grown = $expolygon->offset_ex(+$perimeter_flow->scaled_width); my @lower = @{$lower_layer->slices}; # expolygons # detect what edges lie on lower slices @@ -554,7 +538,7 @@ sub _detect_bridge_direction { } } elsif (@edges) { # inset the bridge expolygon; we'll use this one to clip our test lines - my $inset = $expolygon->offset_ex($self->infill_flow->scaled_width); + my $inset = $expolygon->offset_ex($infill_flow->scaled_width); # detect anchors as intersection between our bridge expolygon and the lower slices my $anchors = intersection_ex( @@ -568,7 +552,7 @@ sub _detect_bridge_direction { # endpoints within anchors my %directions = (); # angle => score my $angle_increment = PI/36; # 5° - my $line_increment = $self->infill_flow->scaled_width; + my $line_increment = $infill_flow->scaled_width; for (my $angle = 0; $angle <= PI; $angle += $angle_increment) { # rotate everything - the center point doesn't matter $_->rotate($angle, [0,0]) for @$inset, @$anchors; diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index d9b5ef463..3cbd97493 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -44,7 +44,6 @@ sub add_object { input_file => $object->input_file, config => $object->config, layer_height_ranges => $object->layer_height_ranges, # TODO: clone! - material_mapping => $object->material_mapping, # TODO: clone! ); foreach my $volume (@{$object->volumes}) { @@ -55,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}); } } @@ -324,6 +324,7 @@ use Moo; has 'model' => (is => 'ro', weak_ref => 1, required => 1); has 'attributes' => (is => 'rw', default => sub { {} }); +has 'config' => (is => 'rw', default => sub { Slic3r::Config->new }); package Slic3r::Model::Object; use Moo; @@ -338,7 +339,6 @@ has 'volumes' => (is => 'ro', default => sub { [] }); has 'instances' => (is => 'rw'); has 'config' => (is => 'rw', default => sub { Slic3r::Config->new }); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] -has 'material_mapping' => (is => 'rw', default => sub { {} }); # { material_id => region_idx } has '_bounding_box' => (is => 'rw'); sub add_volume { @@ -479,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 59d90af7d..e24070354 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -5,21 +5,22 @@ use File::Basename qw(basename fileparse); use File::Spec; use List::Util qw(min max first); use Slic3r::ExtrusionPath ':roles'; +use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points chained_path convex_hull); use Slic3r::Geometry::Clipper qw(diff_ex union_ex union_pt intersection_ex intersection offset offset2 union union_pt_chained JT_ROUND JT_SQUARE); use Slic3r::Print::State ':steps'; -has 'config' => (is => 'rw', default => sub { Slic3r::Config->new_from_defaults }, trigger => \&init_config); +has 'config' => (is => 'ro', default => sub { Slic3r::Config::Print->new }); +has 'default_object_config' => (is => 'ro', default => sub { Slic3r::Config::PrintObject->new }); +has 'default_region_config' => (is => 'ro', default => sub { Slic3r::Config::PrintRegion->new }); has 'extra_variables' => (is => 'rw', default => sub {{}}); has 'objects' => (is => 'rw', default => sub {[]}); has 'status_cb' => (is => 'rw'); -has 'extruders' => (is => 'rw', default => sub {[]}); has 'regions' => (is => 'rw', default => sub {[]}); -has 'support_material_flow' => (is => 'rw'); -has 'first_layer_support_material_flow' => (is => 'rw'); -has 'has_support_material' => (is => 'lazy'); +has 'total_used_filament' => (is => 'rw'); +has 'total_extruded_volume' => (is => 'rw'); has '_state' => (is => 'ro', default => sub { Slic3r::Print::State->new }); # ordered collection of extrusion paths to build skirt loops @@ -28,54 +29,93 @@ has 'skirt' => (is => 'rw', default => sub { Slic3r::ExtrusionPath::Collection-> # ordered collection of extrusion paths to build a brim has 'brim' => (is => 'rw', default => sub { Slic3r::ExtrusionPath::Collection->new }); -sub BUILD { - my $self = shift; - - # call this manually because the 'default' coderef doesn't trigger the trigger - $self->init_config; -} - -# this method needs to be idempotent -sub init_config { - my $self = shift; - - # legacy with existing config files - $self->config->set('first_layer_height', $self->config->layer_height) - if !$self->config->first_layer_height; - $self->config->set_ifndef('small_perimeter_speed', $self->config->perimeter_speed); - $self->config->set_ifndef('bridge_speed', $self->config->infill_speed); - $self->config->set_ifndef('solid_infill_speed', $self->config->infill_speed); - $self->config->set_ifndef('top_solid_infill_speed', $self->config->solid_infill_speed); - $self->config->set_ifndef('top_solid_layers', $self->config->solid_layers); - $self->config->set_ifndef('bottom_solid_layers', $self->config->solid_layers); - - # G-code flavors - $self->config->set('extrusion_axis', 'A') if $self->config->gcode_flavor eq 'mach3'; - $self->config->set('extrusion_axis', '') if $self->config->gcode_flavor eq 'no-extrusion'; - - # enforce some settings when spiral_vase is set - if ($self->config->spiral_vase) { - $self->config->set('perimeters', 1); - $self->config->set('fill_density', 0); - $self->config->set('top_solid_layers', 0); - $self->config->set('support_material', 0); - $self->config->set('support_material_enforce_layers', 0); - $self->config->set('retract_layer_change', [0]); # TODO: only apply this to the spiral layers - } - - # force all retraction lift values to be the same - $self->config->set('retract_lift', [ map $self->config->retract_lift->[0], @{$self->config->retract_lift} ]); -} - sub apply_config { my ($self, $config) = @_; - $self->config->apply($config); - $self->init_config; - $_->init_config for @{$self->objects}; + # 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 _build_has_support_material { +sub has_support_material { my $self = shift; return (first { $_->config->support_material } @{$self->objects}) || (first { $_->config->raft_layers > 0 } @{$self->objects}) @@ -88,30 +128,43 @@ sub add_model_object { my $self = shift; my ($object, $obj_idx) = @_; - # read the material mapping provided by the model object, if any - my %matmap = %{ $object->material_mapping || {} }; - $_-- for values %matmap; # extruders in the mapping are 1-indexed but we want 0-indexed - my %volumes = (); # region_id => [ volume_id, ... ] foreach my $volume_id (0..$#{$object->volumes}) { my $volume = $object->volumes->[$volume_id]; - # determine what region should this volume be mapped to - my $region_id; + # get the config applied to this volume: start from our global defaults + my $config = Slic3r::Config::PrintRegion->new; + $config->apply($self->default_region_config); + + # override the defaults with per-object config and then with per-material config + $config->apply_dynamic($object->config); if (defined $volume->material_id) { - if (!exists $matmap{ $volume->material_id }) { - # there's no mapping between this material and a region - $matmap{ $volume->material_id } = scalar(@{ $self->regions }); - } - $region_id = $matmap{ $volume->material_id }; - } else { - $region_id = 0; + my $material_config = $object->model->materials->{ $volume->material_id }->config; + $config->apply_dynamic($material_config); } + + # find an existing print region with the same config + my $region_id; + foreach my $i (0..$#{$self->regions}) { + my $region = $self->regions->[$i]; + if ($config->equals($region->config)) { + $region_id = $i; + last; + } + } + + # if no region exists with the same config, create a new one + if (!defined $region_id) { + push @{$self->regions}, my $r = Slic3r::Print::Region->new( + print => $self, + ); + $r->config->apply($config); + $region_id = $#{$self->regions}; + } + + # assign volume to region $volumes{$region_id} //= []; push @{ $volumes{$region_id} }, $volume_id; - - # instantiate region if it does not exist - $self->regions->[$region_id] //= Slic3r::Print::Region->new; } # initialize print object @@ -120,9 +173,14 @@ sub add_model_object { model_object => $object, region_volumes => [ map $volumes{$_}, 0..$#{$self->regions} ], copies => [ map Slic3r::Point->new_scale(@{ $_->offset }), @{ $object->instances } ], - config_overrides => $object->config, layer_height_ranges => $object->layer_height_ranges, ); + + # apply config to print object + $o->config->apply($self->default_object_config); + $o->config->apply_dynamic($object->config); + + # store print object at the given position if (defined $obj_idx) { splice @{$self->objects}, $obj_idx, 0, $o; } else { @@ -225,72 +283,32 @@ sub validate { } } +# 0-based indices of used extruders +sub extruders { + my ($self) = @_; + + # initialize all extruder(s) we need + my @used_extruders = (); + foreach my $region (@{$self->regions}) { + push @used_extruders, + map $region->config->get("${_}_extruder")-1, + qw(perimeter infill); + } + foreach my $object (@{$self->objects}) { + push @used_extruders, + map $object->config->get("${_}_extruder")-1, + qw(support_material support_material_interface); + } + + my %h = map { $_ => 1 } @used_extruders; + return [ sort keys %h ]; +} + sub init_extruders { my $self = shift; - # map regions to extruders (ghetto mapping for now) - my %extruder_mapping = map { $_ => $_ } 0..$#{$self->regions}; - - # initialize all extruder(s) we need - my @used_extruders = ( - 0, - (map $self->config->get("${_}_extruder")-1, qw(perimeter infill support_material support_material_interface)), - (values %extruder_mapping), - ); - for my $extruder_id (keys %{{ map {$_ => 1} @used_extruders }}) { - $self->extruders->[$extruder_id] = Slic3r::Extruder->new( - config => $self->config, - id => $extruder_id, - map { $_ => $self->config->get($_)->[$extruder_id] // $self->config->get($_)->[0] } #/ - @{&Slic3r::Extruder::OPTIONS} - ); - } - - # calculate regions' flows - for my $region_id (0 .. $#{$self->regions}) { - my $region = $self->regions->[$region_id]; - - # per-role extruders and flows - for (qw(perimeter infill solid_infill top_infill)) { - my $extruder_name = $_; - $extruder_name =~ s/^(?:solid|top)_//; - $region->extruders->{$_} = ($self->regions_count > 1) - ? $self->extruders->[$extruder_mapping{$region_id}] - : $self->extruders->[$self->config->get("${extruder_name}_extruder")-1]; - $region->flows->{$_} = $region->extruders->{$_}->make_flow( - layer_height => $self->config->layer_height, - width => $self->config->get("${_}_extrusion_width") || $self->config->extrusion_width, - role => $_, - ); - $region->first_layer_flows->{$_} = $region->extruders->{$_}->make_flow( - layer_height => $self->config->get_value('first_layer_height'), - width => $self->config->first_layer_extrusion_width, - role => $_, - ) if $self->config->first_layer_extrusion_width; - } - } - - # calculate support material flow - # Note: we should calculate a different flow for support material interface - # TODO: support material layers have their own variable layer heights, so we - # probably need a DynamicFlow object that calculates flow on the fly - # (or the Flow object must support a mutable layer_height) - if ($self->has_support_material) { - my $extruder = $self->extruders->[$self->config->support_material_extruder-1]; - $self->support_material_flow($extruder->make_flow( - layer_height => $self->config->layer_height, # WRONG! - width => $self->config->support_material_extrusion_width || $self->config->extrusion_width, - role => 'support_material', - )); - $self->first_layer_support_material_flow($extruder->make_flow( - layer_height => $self->config->get_value('first_layer_height'), - width => $self->config->first_layer_extrusion_width, - role => 'support_material', - )); - } - # enforce tall skirt if using ooze_prevention - # NOTE: this is not idempotent (i.e. switching ooze_prevention off will not revert skirt settings) + # FIXME: this is not idempotent (i.e. switching ooze_prevention off will not revert skirt settings) if ($self->config->ooze_prevention && @{$self->extruders} > 1) { $self->config->set('skirt_height', -1); $self->config->set('skirts', 1) if $self->config->skirts == 0; @@ -631,10 +649,20 @@ sub make_skirt { my @extruded_length = (); # for each extruder + # skirt may be printed on several layers, having distinct layer heights, + # but loops must be aligned so can't vary width/spacing # TODO: use each extruder's own flow - my $spacing = $self->objects->[0]->layers->[0]->regions->[0]->perimeter_flow->spacing; + my $first_layer_height = $self->objects->[0]->config->get_value('first_layer_height'); + my $flow = Slic3r::Flow->new_from_width( + width => ($self->config->first_layer_extrusion_width || $self->regions->[0]->config->perimeter_extrusion_width), + role => FLOW_ROLE_PERIMETER, + nozzle_diameter => $self->config->nozzle_diameter->[0], + layer_height => $first_layer_height, + bridge_flow_ratio => 0, + ); + my $spacing = $flow->spacing; + my $mm3_per_mm = $flow->mm3_per_mm($first_layer_height); - my $first_layer_height = $self->config->get_value('first_layer_height'); my @extruders_e_per_mm = (); my $extruder_idx = 0; @@ -647,13 +675,16 @@ sub make_skirt { $self->skirt->append(Slic3r::ExtrusionLoop->new( polygon => Slic3r::Polygon->new(@$loop), role => EXTR_ROLE_SKIRT, - flow_spacing => $spacing, + mm3_per_mm => $mm3_per_mm, )); if ($self->config->min_skirt_length > 0) { - $extruded_length[$extruder_idx] ||= 0; - $extruders_e_per_mm[$extruder_idx] ||= $self->extruders->[$extruder_idx]->e_per_mm($spacing, $first_layer_height); - $extruded_length[$extruder_idx] += unscale $loop->length * $extruders_e_per_mm[$extruder_idx]; + $extruded_length[$extruder_idx] ||= 0; + if (!$extruders_e_per_mm[$extruder_idx]) { + my $extruder = Slic3r::Extruder->new_from_config($self->config, $extruder_idx); + $extruders_e_per_mm[$extruder_idx] = $extruder->e_per_mm($mm3_per_mm); + } + $extruded_length[$extruder_idx] += unscale $loop->length * $extruders_e_per_mm[$extruder_idx]; $i++ if defined first { ($extruded_length[$_] // 0) < $self->config->min_skirt_length } 0 .. $#{$self->extruders}; if ($extruded_length[$extruder_idx] >= $self->config->min_skirt_length) { if ($extruder_idx < $#{$self->extruders}) { @@ -673,7 +704,16 @@ sub make_brim { $self->brim->clear; # method must be idempotent - my $flow = $self->objects->[0]->layers->[0]->regions->[0]->perimeter_flow; + # brim is only printed on first layer and uses support material extruder + my $first_layer_height = $self->objects->[0]->config->get_abs_value('first_layer_height'); + my $flow = Slic3r::Flow->new_from_width( + width => ($self->config->first_layer_extrusion_width || $self->regions->[0]->config->perimeter_extrusion_width), + role => FLOW_ROLE_PERIMETER, + nozzle_diameter => $self->config->get_at('nozzle_diameter', $self->objects->[0]->config->support_material_extruder-1), + layer_height => $first_layer_height, + bridge_flow_ratio => 0, + ); + my $mm3_per_mm = $flow->mm3_per_mm($first_layer_height); my $grow_distance = $flow->scaled_width / 2; my @islands = (); # array of polygons @@ -716,7 +756,7 @@ sub make_brim { $self->brim->append(map Slic3r::ExtrusionLoop->new( polygon => Slic3r::Polygon->new(@$_), role => EXTR_ROLE_SKIRT, - flow_spacing => $flow->spacing, + mm3_per_mm => $mm3_per_mm, ), reverse @{union_pt_chained(\@loops)}); } @@ -741,29 +781,33 @@ sub write_gcode { print $fh "; $_\n" foreach split /\R/, $self->config->notes; print $fh "\n" if $self->config->notes; - for (qw(layer_height perimeters top_solid_layers bottom_solid_layers fill_density perimeter_speed infill_speed travel_speed)) { - printf $fh "; %s = %s\n", $_, $self->config->$_; + my $layer_height = $self->objects->[0]->config->layer_height; + for my $region_id (0..$#{$self->regions}) { + printf $fh "; perimeters extrusion width = %.2fmm\n", + $self->regions->[$region_id]->flow(FLOW_ROLE_PERIMETER, $layer_height)->width; + printf $fh "; infill extrusion width = %.2fmm\n", + $self->regions->[$region_id]->flow(FLOW_ROLE_INFILL, $layer_height)->width; + printf $fh "; solid infill extrusion width = %.2fmm\n", + $self->regions->[$region_id]->flow(FLOW_ROLE_SOLID_INFILL, $layer_height)->width; + printf $fh "; top infill extrusion width = %.2fmm\n", + $self->regions->[$region_id]->flow(FLOW_ROLE_TOP_SOLID_INFILL, $layer_height)->width; + printf $fh "; support material extrusion width = %.2fmm\n", + $self->objects->[0]->support_material_flow->width + if $self->has_support_material; + printf $fh "; first layer extrusion width = %.2fmm\n", + $self->regions->[$region_id]->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 1)->width + if $self->regions->[$region_id]->config->first_layer_extrusion_width; + print $fh "\n"; } - for (qw(nozzle_diameter filament_diameter extrusion_multiplier)) { - printf $fh "; %s = %s\n", $_, $self->config->$_->[0]; - } - printf $fh "; perimeters extrusion width = %.2fmm\n", $self->regions->[0]->flows->{perimeter}->width; - printf $fh "; infill extrusion width = %.2fmm\n", $self->regions->[0]->flows->{infill}->width; - printf $fh "; solid infill extrusion width = %.2fmm\n", $self->regions->[0]->flows->{solid_infill}->width; - printf $fh "; top infill extrusion width = %.2fmm\n", $self->regions->[0]->flows->{top_infill}->width; - printf $fh "; support material extrusion width = %.2fmm\n", $self->support_material_flow->width - if $self->support_material_flow; - printf $fh "; first layer extrusion width = %.2fmm\n", $self->regions->[0]->first_layer_flows->{perimeter}->width - if $self->regions->[0]->first_layer_flows->{perimeter}; - print $fh "\n"; # set up our extruder object my $gcodegen = Slic3r::GCode->new( - config => $self->config, + print_config => $self->config, extra_variables => $self->extra_variables, - extruders => $self->extruders, # we should only pass the *used* extruders (but maintain the Tx indices right!) layer_count => $self->layer_count, ); + $gcodegen->set_extruders($self->extruders); + print $fh "G21 ; set units to millimeters\n" if $self->config->gcode_flavor ne 'makerware'; print $fh $gcodegen->set_fan(0, 1) if $self->config->cooling && $self->config->disable_fan_first_layers; @@ -777,8 +821,8 @@ sub write_gcode { my ($wait) = @_; return if $self->config->start_gcode =~ /M(?:109|104)/i; - for my $t (0 .. $#{$self->extruders}) { - my $temp = $self->extruders->[$t]->first_layer_temperature; + for my $t (@{$self->extruders}) { + my $temp = $self->config->get_at('first_layer_temperature', $t); $temp += $self->config->standby_temperature_delta if $self->config->ooze_prevention; printf $fh $gcodegen->set_temperature($temp, $wait, $t) if $temp > 0; } @@ -798,10 +842,6 @@ sub write_gcode { } } - # always start with first extruder - # TODO: make sure we select the first *used* extruder - print $fh $gcodegen->set_extruder($self->extruders->[0]); - # initialize a motion planner for object-to-object travel moves if ($self->config->avoid_crossing_perimeters) { my $distance_from_objects = 1; @@ -832,9 +872,9 @@ sub write_gcode { if (@skirt_points) { my $outer_skirt = convex_hull(\@skirt_points); my @skirts = (); - foreach my $extruder (@{$self->extruders}) { + foreach my $extruder_id (@{$self->extruders}) { push @skirts, my $s = $outer_skirt->clone; - $s->translate(map scale($_), @{$extruder->extruder_offset}); + $s->translate(map scale($_), @{$self->config->get_at('extruder_offset', $extruder_id)}); } my $convex_hull = convex_hull([ map @$_, @skirts ]); $gcodegen->standby_points([ map $_->clone, map @$_, map $_->subdivide(scale 10), @{offset([$convex_hull], scale 3)} ]); @@ -847,6 +887,9 @@ sub write_gcode { gcodegen => $gcodegen, ); + # set initial extruder only after custom start G-code + print $fh $gcodegen->set_extruder($self->extruders->[0]); + # do all objects for each layer if ($self->config->complete_objects) { # print objects from the smallest to the tallest to avoid collisions @@ -931,18 +974,22 @@ sub write_gcode { print $fh $gcodegen->set_fan(0); printf $fh "%s\n", $gcodegen->replace_variables($self->config->end_gcode); - foreach my $extruder (@{$self->extruders}) { + $self->total_used_filament(0); + $self->total_extruded_volume(0); + foreach my $extruder_id (@{$self->extruders}) { + my $extruder = $gcodegen->extruders->{$extruder_id}; + $self->total_used_filament($self->total_used_filament + $extruder->absolute_E); + $self->total_extruded_volume($self->total_extruded_volume + $extruder->extruded_volume); + printf $fh "; filament used = %.1fmm (%.1fcm3)\n", $extruder->absolute_E, $extruder->extruded_volume/1000; } - if ($self->config->gcode_comments) { - # append full config - print $fh "\n"; - foreach my $opt_key (sort @{$self->config->get_keys}) { - next if $Slic3r::Config::Options->{$opt_key}{shortcut}; - printf $fh "; %s = %s\n", $opt_key, $self->config->serialize($opt_key); - } + # append full config + print $fh "\n"; + foreach my $opt_key (sort @{$self->config->get_keys}) { + next if $Slic3r::Config::Options->{$opt_key}{shortcut}; + printf $fh "; %s = %s\n", $opt_key, $self->config->serialize($opt_key); } # close our gcode file @@ -1012,4 +1059,23 @@ sub invalidate_step { keys %Slic3r::Print::State::prereqs; } +# This method assigns extruders to the volumes having a material +# but not having extruders set in the material config. +sub auto_assign_extruders { + my ($self, $model_object) = @_; + + my $extruders = scalar @{ $self->config->nozzle_diameter }; + foreach my $i (0..$#{$model_object->volumes}) { + my $volume = $model_object->volumes->[$i]; + if (defined $volume->material_id) { + my $material = $model_object->model->materials->{ $volume->material_id }; + my $config = $material->config; + $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); + } + } +} + 1; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index d7d8f00db..bf196c100 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -2,6 +2,7 @@ package Slic3r::Print::Object; use Moo; use List::Util qw(min max sum first); +use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(X Y Z PI scale unscale deg2rad rad2deg scaled_epsilon chained_path); use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex offset offset_ex offset2 offset2_ex CLIPPER_OFFSET_SCALE JT_MITER); @@ -12,8 +13,7 @@ has 'print' => (is => 'ro', weak_ref => 1, required => 1); has 'model_object' => (is => 'ro', required => 1); has 'region_volumes' => (is => 'rw', default => sub { [] }); # by region_id has 'copies' => (is => 'ro'); # Slic3r::Point objects in scaled G-code coordinates -has 'config_overrides' => (is => 'rw', default => sub { Slic3r::Config->new }); -has 'config' => (is => 'rw'); +has 'config' => (is => 'ro', default => sub { Slic3r::Config::PrintObject->new }); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] has 'size' => (is => 'rw'); # XYZ in scaled coordinates @@ -27,8 +27,6 @@ has '_state' => (is => 'ro', default => sub { Slic3r::Print::State->n sub BUILD { my $self = shift; - $self->init_config; - # translate meshes so that we work with smaller coordinates { # compute the bounding box of the supplied meshes @@ -96,11 +94,6 @@ sub delete_all_copies { $self->_trigger_copies; } -sub init_config { - my $self = shift; - $self->config(Slic3r::Config->merge($self->print->config, $self->config_overrides)); -} - sub layer_count { my $self = shift; return scalar @{ $self->layers }; @@ -280,8 +273,8 @@ sub slice { } # simplify slices if required - if ($self->config->resolution) { - $self->_simplify_slices(scale($self->config->resolution)); + if ($self->print->config->resolution) { + $self->_simplify_slices(scale($self->print->config->resolution)); } } @@ -295,17 +288,20 @@ sub make_perimeters { # but we don't generate any extra perimeter if fill density is zero, as they would be floating # inside the object - infill_only_where_needed should be the method of choice for printing # hollow objects - if ($self->config->extra_perimeters && $self->config->perimeters > 0 && $self->config->fill_density > 0) { - for my $region_id (0 .. ($self->print->regions_count-1)) { + for my $region_id (0 .. ($self->print->regions_count-1)) { + my $region = $self->print->regions->[$region_id]; + my $region_perimeters = $region->config->perimeters; + + if ($region->config->extra_perimeters && $region_perimeters > 0 && $region->config->fill_density > 0) { for my $layer_id (0 .. $self->layer_count-2) { my $layerm = $self->layers->[$layer_id]->regions->[$region_id]; my $upper_layerm = $self->layers->[$layer_id+1]->regions->[$region_id]; - my $perimeter_spacing = $layerm->perimeter_flow->scaled_spacing; + my $perimeter_spacing = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_spacing; my $overlap = $perimeter_spacing; # one perimeter my $diff = diff( - offset([ map @{$_->expolygon}, @{$layerm->slices} ], -($self->config->perimeters * $perimeter_spacing)), + offset([ map @{$_->expolygon}, @{$layerm->slices} ], -($region_perimeters * $perimeter_spacing)), offset([ map @{$_->expolygon}, @{$upper_layerm->slices} ], -$overlap), ); next if !@$diff; @@ -329,8 +325,8 @@ sub make_perimeters { # of our slice $extra_perimeters++; my $hypothetical_perimeter = diff( - offset($slice->expolygon->arrayref, -($perimeter_spacing * ($self->config->perimeters + $extra_perimeters-1))), - offset($slice->expolygon->arrayref, -($perimeter_spacing * ($self->config->perimeters + $extra_perimeters))), + offset($slice->expolygon->arrayref, -($perimeter_spacing * ($region_perimeters + $extra_perimeters-1))), + offset($slice->expolygon->arrayref, -($perimeter_spacing * ($region_perimeters + $extra_perimeters))), ); last CYCLE if !@$hypothetical_perimeter; # no extra perimeter is possible @@ -346,7 +342,7 @@ sub make_perimeters { } Slic3r::parallelize( - threads => $self->config->threads, + threads => $self->print->config->threads, items => sub { 0 .. ($self->layer_count-1) }, thread_cb => sub { my $q = shift; @@ -383,7 +379,7 @@ sub detect_surfaces_type { ); # collapse very narrow parts (using the safety offset in the diff is not enough) - my $offset = $layerm->perimeter_flow->scaled_width / 10; + my $offset = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_width / 10; return map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type), @{ offset2_ex($diff, -$offset, +$offset) }; }; @@ -525,13 +521,16 @@ sub clip_fill_surfaces { sub bridge_over_infill { my $self = shift; - return if $self->config->fill_density == 1; - for my $layer_id (1..$#{$self->layers}) { - my $layer = $self->layers->[$layer_id]; - my $lower_layer = $self->layers->[$layer_id-1]; + for my $region_id (0..$#{$self->print->regions}) { + my $fill_density = $self->print->regions->[$region_id]->config->fill_density; + next if $fill_density == 1 || $fill_density == 0; - foreach my $layerm (@{$layer->regions}) { + for my $layer_id (1..$#{$self->layers}) { + my $layer = $self->layers->[$layer_id]; + my $layerm = $layer->regions->[$region_id]; + my $lower_layer = $self->layers->[$layer_id-1]; + # compute the areas needing bridge math my @internal_solid = @{$layerm->fill_surfaces->filter_by_type(S_TYPE_INTERNALSOLID)}; my @lower_internal = map @{$_->fill_surfaces->filter_by_type(S_TYPE_INTERNAL)}, @{$lower_layer->regions}; @@ -616,8 +615,8 @@ sub discover_horizontal_shells { for (my $i = 0; $i < $self->layer_count; $i++) { my $layerm = $self->layers->[$i]->regions->[$region_id]; - if ($self->config->solid_infill_every_layers && $self->config->fill_density > 0 - && ($i % $self->config->solid_infill_every_layers) == 0) { + if ($layerm->config->solid_infill_every_layers && $layerm->config->fill_density > 0 + && ($i % $layerm->config->solid_infill_every_layers) == 0) { $_->surface_type(S_TYPE_INTERNALSOLID) for @{$layerm->fill_surfaces->filter_by_type(S_TYPE_INTERNAL)}; } @@ -639,8 +638,8 @@ sub discover_horizontal_shells { Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == S_TYPE_TOP) ? 'top' : 'bottom'; my $solid_layers = ($type == S_TYPE_TOP) - ? $self->config->top_solid_layers - : $self->config->bottom_solid_layers; + ? $layerm->config->top_solid_layers + : $layerm->config->bottom_solid_layers; NEIGHBOR: for (my $n = ($type == S_TYPE_TOP) ? $i-1 : $i+1; abs($n - $i) <= $solid_layers-1; ($type == S_TYPE_TOP) ? $n-- : $n++) { @@ -675,7 +674,7 @@ sub discover_horizontal_shells { # get a triangle in $too_narrow; if we grow it below then the shell # would have a different shape from the external surface and we'd still # have the same angle, so the next shell would be grown even more and so on. - my $margin = 3 * $layerm->solid_infill_flow->scaled_width; # require at least this size + my $margin = 3 * $layerm->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width; # require at least this size my $too_narrow = diff( $new_internal_solid, offset2($new_internal_solid, -$margin, +$margin, CLIPPER_OFFSET_SCALE, JT_MITER, 5), @@ -684,7 +683,7 @@ sub discover_horizontal_shells { # if some parts are going to collapse, use a different strategy according to fill density if (@$too_narrow) { - if ($self->config->fill_density > 0) { + if ($layerm->config->fill_density > 0) { # if we have internal infill, grow the collapsing parts and add the extra area to # the neighbor layer as well as to our original surfaces so that we support this # additional area in the next shell too @@ -748,15 +747,18 @@ sub discover_horizontal_shells { # combine fill surfaces across layers sub combine_infill { my $self = shift; - return unless $self->config->infill_every_layers > 1 && $self->config->fill_density > 0; - my $every = $self->config->infill_every_layers; + + return unless defined first { $_->config->infill_every_layers > 1 && $_->config->fill_density > 0 } @{$self->print->regions}; my $layer_count = $self->layer_count; my @layer_heights = map $self->layers->[$_]->height, 0 .. $layer_count-1; for my $region_id (0 .. ($self->print->regions_count-1)) { + my $region = $self->print->regions->[$region_id]; + my $every = $region->config->infill_every_layers; + # limit the number of combined layers to the maximum height allowed by this regions' nozzle - my $nozzle_diameter = $self->print->regions->[$region_id]->extruders->{infill}->nozzle_diameter; + my $nozzle_diameter = $self->print->config->get_at('nozzle_diameter', $region->config->infill_extruder-1); # define the combinations my @combine = (); # layer_id => thickness in layers @@ -808,12 +810,12 @@ sub combine_infill { # so let's remove those areas from all layers my @intersection_with_clearance = map @{$_->offset( - $layerms[-1]->solid_infill_flow->scaled_width / 2 - + $layerms[-1]->perimeter_flow->scaled_width / 2 + $layerms[-1]->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width / 2 + + $layerms[-1]->flow(FLOW_ROLE_PERIMETER)->scaled_width / 2 # Because fill areas for rectilinear and honeycomb are grown # later to overlap perimeters, we need to counteract that too. - + (($type == S_TYPE_INTERNALSOLID || $self->config->fill_pattern =~ /(rectilinear|honeycomb)/) - ? $layerms[-1]->solid_infill_flow->scaled_width * &Slic3r::INFILL_OVERLAP_OVER_SPACING + + (($type == S_TYPE_INTERNALSOLID || $region->config->fill_pattern =~ /(rectilinear|honeycomb)/) + ? $layerms[-1]->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width * &Slic3r::INFILL_OVERLAP_OVER_SPACING : 0) )}, @$intersection; @@ -861,9 +863,23 @@ sub generate_support_material { return unless ($self->config->support_material || $self->config->raft_layers > 0) && $self->layer_count >= 2; - Slic3r::Print::SupportMaterial - ->new(config => $self->config, flow => $self->print->support_material_flow) - ->generate($self); + my $first_layer_flow = Slic3r::Flow->new_from_width( + width => ($self->config->first_layer_extrusion_width || $self->config->support_material_extrusion_width), + role => FLOW_ROLE_SUPPORT_MATERIAL, + nozzle_diameter => $self->print->config->nozzle_diameter->[ $self->config->support_material_extruder-1 ] + // $self->print->config->nozzle_diameter->[0], + layer_height => $self->config->get_abs_value('first_layer_height'), + bridge_flow_ratio => 0, + ); + + my $s = Slic3r::Print::SupportMaterial->new( + print_config => $self->print->config, + object_config => $self->config, + first_layer_flow => $first_layer_flow, + flow => $self->support_material_flow, + interface_flow => $self->support_material_flow(FLOW_ROLE_SUPPORT_MATERIAL_INTERFACE), + ); + $s->generate($self); } sub _simplify_slices { @@ -875,4 +891,23 @@ sub _simplify_slices { } } +sub support_material_flow { + my ($self, $role) = @_; + + $role //= FLOW_ROLE_SUPPORT_MATERIAL; + my $extruder = ($role == FLOW_ROLE_SUPPORT_MATERIAL) + ? $self->config->support_material_extruder + : $self->config->support_material_interface_extruder; + + # we use a bogus layer_height because we use the same flow for all + # support material layers + return Slic3r::Flow->new_from_width( + width => $self->config->support_material_extrusion_width, + role => $role, + nozzle_diameter => $self->print->config->nozzle_diameter->[$extruder-1] // $self->print->config->nozzle_diameter->[0], + layer_height => $self->config->layer_height, + bridge_flow_ratio => 0, + ); +} + 1; diff --git a/lib/Slic3r/Print/Region.pm b/lib/Slic3r/Print/Region.pm index 253fde2ba..657aa34d3 100644 --- a/lib/Slic3r/Print/Region.pm +++ b/lib/Slic3r/Print/Region.pm @@ -1,8 +1,60 @@ package Slic3r::Print::Region; use Moo; -has 'extruders' => (is => 'rw', default => sub { {} }); # by role -has 'flows' => (is => 'rw', default => sub { {} }); # by role -has 'first_layer_flows' => (is => 'rw', default => sub { {} }); # by role +use Slic3r::Extruder ':roles'; +use Slic3r::Flow ':roles'; + +# A Print::Region object represents a group of volumes to print +# sharing the same config (including the same assigned extruder(s)) + +has 'print' => (is => 'ro', required => 1, weak_ref => 1); +has 'config' => (is => 'ro', default => sub { Slic3r::Config::PrintRegion->new}); + +sub flow { + my ($self, $role, $layer_height, $bridge, $first_layer, $width) = @_; + + $bridge //= 0; + $first_layer //= 0; + + # use the supplied custom width, if any + my $config_width = $width; + if (!defined $config_width) { + # get extrusion width from configuration + # (might be an absolute value, or a percent value, or zero for auto) + if ($first_layer && $self->print->config->first_layer_extrusion_width) { + $config_width = $self->print->config->first_layer_extrusion_width; + } elsif ($role == FLOW_ROLE_PERIMETER) { + $config_width = $self->config->perimeter_extrusion_width; + } elsif ($role == FLOW_ROLE_INFILL) { + $config_width = $self->config->infill_extrusion_width; + } elsif ($role == FLOW_ROLE_SOLID_INFILL) { + $config_width = $self->config->solid_infill_extrusion_width; + } elsif ($role == FLOW_ROLE_TOP_SOLID_INFILL) { + $config_width = $self->config->top_infill_extrusion_width; + } else { + die "Unknown role $role"; + } + } + + # get the configured nozzle_diameter for the extruder associated + # to the flow role requested + my $extruder; # 1-based + if ($role == FLOW_ROLE_PERIMETER) { + $extruder = $self->config->perimeter_extruder; + } elsif ($role == FLOW_ROLE_INFILL || $role == FLOW_ROLE_SOLID_INFILL || $role == FLOW_ROLE_TOP_SOLID_INFILL) { + $extruder = $self->config->infill_extruder; + } else { + die "Unknown role $role"; + } + my $nozzle_diameter = $self->print->config->get_at('nozzle_diameter', $extruder-1); + + return Slic3r::Flow->new_from_width( + width => $config_width, + role => $role, + nozzle_diameter => $nozzle_diameter, + layer_height => $layer_height, + bridge_flow_ratio => ($bridge ? $self->print->config->bridge_flow_ratio : 0), + ); +} 1; diff --git a/lib/Slic3r/Print/Simple.pm b/lib/Slic3r/Print/Simple.pm new file mode 100644 index 000000000..ded003063 --- /dev/null +++ b/lib/Slic3r/Print/Simple.pm @@ -0,0 +1,112 @@ +package Slic3r::Print::Simple; +use Moo; + +use Slic3r::Geometry qw(X Y); + +has '_print' => ( + is => 'ro', + default => sub { Slic3r::Print->new }, + handles => [qw(apply_config extruders expanded_output_filepath + total_used_filament total_extruded_volume)], +); + +has 'duplicate' => ( + is => 'rw', + default => sub { 1 }, +); + +has 'scale' => ( + is => 'rw', + default => sub { 1 }, +); + +has 'rotate' => ( + is => 'rw', + default => sub { 0 }, +); + +has 'duplicate_grid' => ( + is => 'rw', + default => sub { [1,1] }, +); + +has 'status_cb' => ( + is => 'rw', + default => sub { sub {} }, +); + +has 'output_file' => ( + is => 'rw', +); + +sub set_model { + my ($self, $model) = @_; + + # make method idempotent so that the object is reusable + $self->_print->delete_all_objects; + + my $need_arrange = $model->has_objects_with_no_instances; + if ($need_arrange) { + # apply a default position to all objects not having one + foreach my $object (@{$model->objects}) { + $object->add_instance(offset => [0,0]) if !defined $object->instances; + } + } + + # apply scaling and rotation supplied from command line if any + foreach my $instance (map @{$_->instances}, @{$model->objects}) { + $instance->scaling_factor($instance->scaling_factor * $self->scale); + $instance->rotation($instance->rotation + $self->rotate); + } + + if ($self->duplicate_grid->[X] > 1 || $self->duplicate_grid->[Y] > 1) { + $model->duplicate_objects_grid($self->duplicate_grid, $self->_print->config->duplicate_distance); + } elsif ($need_arrange) { + $model->duplicate_objects($self->duplicate, $self->_print->config->min_object_distance); + } elsif ($self->duplicate > 1) { + # if all input objects have defined position(s) apply duplication to the whole model + $model->duplicate($self->duplicate, $self->_print->config->min_object_distance); + } + $model->center_instances_around_point($self->_print->config->print_center); + + foreach my $model_object (@{$model->objects}) { + $self->_print->auto_assign_extruders($model_object); + $self->_print->add_model_object($model_object); + } +} + +sub _before_export { + my ($self) = @_; + + $self->_print->status_cb($self->status_cb); + $self->_print->validate; +} + +sub _after_export { + my ($self) = @_; + + $self->_print->status_cb(undef); +} + +sub export_gcode { + my ($self) = @_; + + $self->_before_export; + + $self->_print->process; + $self->_print->export_gcode(output_file => $self->output_file); + + $self->_after_export; +} + +sub export_svg { + my ($self) = @_; + + $self->_before_export; + + $self->_print->export_svg(output_file => $self->output_file); + + $self->_after_export; +} + +1; diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index 291268bba..2c533d8d9 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -3,13 +3,17 @@ use Moo; use List::Util qw(sum min max); use Slic3r::ExtrusionPath ':roles'; +use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(scale scaled_epsilon PI rad2deg deg2rad); use Slic3r::Geometry::Clipper qw(offset diff union union_ex intersection offset_ex offset2 intersection_pl); use Slic3r::Surface ':types'; -has 'config' => (is => 'rw', required => 1); -has 'flow' => (is => 'rw', required => 1); +has 'print_config' => (is => 'rw', required => 1); +has 'object_config' => (is => 'rw', required => 1); +has 'flow' => (is => 'rw', required => 1); +has 'first_layer_flow' => (is => 'rw', required => 1); +has 'interface_flow' => (is => 'rw', required => 1); use constant DEBUG_CONTACT_ONLY => 0; @@ -73,8 +77,8 @@ sub contact_area { # if user specified a custom angle threshold, convert it to radians my $threshold_rad; - if ($self->config->support_material_threshold) { - $threshold_rad = deg2rad($self->config->support_material_threshold + 1); # +1 makes the threshold inclusive + if ($self->object_config->support_material_threshold) { + $threshold_rad = deg2rad($self->object_config->support_material_threshold + 1); # +1 makes the threshold inclusive Slic3r::debugf "Threshold angle = %d°\n", rad2deg($threshold_rad); } @@ -86,9 +90,9 @@ sub contact_area { # so $layer_id == 0 means first object layer # and $layer->id == 0 means first print layer (including raft) - if ($self->config->raft_layers == 0) { + if ($self->object_config->raft_layers == 0) { next if $layer_id == 0; - } elsif (!$self->config->support_material) { + } elsif (!$self->object_config->support_material) { # if we are only going to generate raft just check # the 'overhangs' of the first object layer last if $layer_id > 0; @@ -105,13 +109,13 @@ sub contact_area { } else { my $lower_layer = $object->layers->[$layer_id-1]; foreach my $layerm (@{$layer->regions}) { - my $fw = $layerm->perimeter_flow->scaled_width; + my $fw = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_width; my $diff; # If a threshold angle was specified, use a different logic for detecting overhangs. if (defined $threshold_rad - || $layer_id < $self->config->support_material_enforce_layers - || $self->config->raft_layers > 0) { + || $layer_id < $self->object_config->support_material_enforce_layers + || $self->object_config->raft_layers > 0) { my $d = defined $threshold_rad ? scale $lower_layer->height * ((cos $threshold_rad) / (sin $threshold_rad)) : 0; @@ -170,8 +174,8 @@ sub contact_area { # now apply the contact areas to the layer were they need to be made { # get the average nozzle diameter used on this layer - my @nozzle_diameters = map $_->nozzle_diameter, - map { $_->perimeter_flow, $_->solid_infill_flow } + my @nozzle_diameters = map $self->print_config->get_at('nozzle_diameter', $_), + map { $_->config->perimeter_extruder-1, $_->config->infill_extruder-1 } @{$layer->regions}; my $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters; @@ -179,7 +183,7 @@ sub contact_area { ###$contact_z = $layer->print_z - $layer->height; # ignore this contact area if it's too low - next if $contact_z < $self->config->get_value('first_layer_height'); + next if $contact_z < $self->object_config->get_value('first_layer_height'); $contact{$contact_z} = [ @contact ]; $overhang{$contact_z} = [ @overhang ]; @@ -242,25 +246,25 @@ sub support_layers_z { # determine layer height for any non-contact layer # we use max() to prevent many ultra-thin layers to be inserted in case # layer_height > nozzle_diameter * 0.75 - my $nozzle_diameter = $self->flow->nozzle_diameter; + my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $self->object_config->support_material_extruder-1); my $support_material_height = max($max_object_layer_height, $nozzle_diameter * 0.75); my @z = sort { $a <=> $b } @$contact_z, @$top_z, (map $_ + $nozzle_diameter, @$top_z); # enforce first layer height - my $first_layer_height = $self->config->get_value('first_layer_height'); + my $first_layer_height = $self->object_config->get_value('first_layer_height'); shift @z while @z && $z[0] <= $first_layer_height; unshift @z, $first_layer_height; # add raft layers by dividing the space between first layer and # first contact layer evenly - if ($self->config->raft_layers > 1 && @z >= 2) { + if ($self->object_config->raft_layers > 1 && @z >= 2) { # $z[1] is last raft layer (contact layer for the first layer object) - my $height = ($z[1] - $z[0]) / ($self->config->raft_layers - 1); + my $height = ($z[1] - $z[0]) / ($self->object_config->raft_layers - 1); splice @z, 1, 0, map { int($_*100)/100 } map { $z[0] + $height * $_ } - 0..($self->config->raft_layers - 1); + 0..($self->object_config->raft_layers - 1); } for (my $i = $#z; $i >= 0; $i--) { @@ -291,7 +295,7 @@ sub generate_interface_layers { # let's now generate interface layers below contact areas my %interface = (); # layer_id => [ polygons ] - my $interface_layers = $self->config->support_material_interface_layers; + my $interface_layers = $self->object_config->support_material_interface_layers; for my $layer_id (0 .. $#$support_z) { my $z = $support_z->[$layer_id]; my $this = $contact->{$z} // next; @@ -339,7 +343,7 @@ sub generate_base_layers { # in case we have no interface layers, look at upper contact # (1 interface layer means we only have contact layer, so $interface->{$i+1} is empty) my @upper_contact = (); - if ($self->config->support_material_interface_layers <= 1) { + if ($self->object_config->support_material_interface_layers <= 1) { @upper_contact = @{ $contact->{$support_z->[$i+1]} || [] }; } @@ -383,11 +387,12 @@ sub clip_with_object { sub generate_toolpaths { my ($self, $object, $overhang, $contact, $interface, $base) = @_; - my $flow = $self->flow; + my $flow = $self->flow; + my $interface_flow = $self->interface_flow; # shape of contact area my $contact_loops = 1; - my $circle_radius = 1.5 * $flow->scaled_width; + my $circle_radius = 1.5 * $interface_flow->scaled_width; my $circle_distance = 3 * $circle_radius; my $circle = Slic3r::Polygon->new(map [ $circle_radius * cos $_, $circle_radius * sin $_ ], (5*PI/3, 4*PI/3, PI, 2*PI/3, PI/3, 0)); @@ -395,8 +400,8 @@ sub generate_toolpaths { Slic3r::debugf "Generating patterns\n"; # prepare fillers - my $pattern = $self->config->support_material_pattern; - my @angles = ($self->config->support_material_angle); + my $pattern = $self->object_config->support_material_pattern; + my @angles = ($self->object_config->support_material_angle); if ($pattern eq 'rectilinear-grid') { $pattern = 'rectilinear'; push @angles, $angles[0] + 90; @@ -407,10 +412,10 @@ sub generate_toolpaths { support => $object->fill_maker->filler($pattern), ); - my $interface_angle = $self->config->support_material_angle + 90; - my $interface_spacing = $self->config->support_material_interface_spacing + $flow->spacing; - my $interface_density = $interface_spacing == 0 ? 1 : $flow->spacing / $interface_spacing; - my $support_spacing = $self->config->support_material_spacing + $flow->spacing; + my $interface_angle = $self->object_config->support_material_angle + 90; + my $interface_spacing = $self->object_config->support_material_interface_spacing + $interface_flow->spacing; + my $interface_density = $interface_spacing == 0 ? 1 : $interface_flow->spacing / $interface_spacing; + my $support_spacing = $self->object_config->support_material_spacing + $flow->spacing; my $support_density = $support_spacing == 0 ? 1 : $flow->spacing / $support_spacing; my $process_layer = sub { @@ -441,7 +446,7 @@ sub generate_toolpaths { # contact my $contact_infill = []; - if ($self->config->support_material_interface_layers == 0) { + if ($self->object_config->support_material_interface_layers == 0) { # if no interface layers were requested we treat the contact layer # exactly as a generic base layer push @$base, @$contact; @@ -450,11 +455,11 @@ sub generate_toolpaths { my @loops0 = (); { # find centerline of the external loop of the contours - my @external_loops = @{offset($contact, -$flow->scaled_width/2)}; + my @external_loops = @{offset($contact, -$interface_flow->scaled_width/2)}; # only consider the loops facing the overhang { - my $overhang_with_margin = offset($overhang, +$flow->scaled_width/2); + my $overhang_with_margin = offset($overhang, +$interface_flow->scaled_width/2); @external_loops = grep { @{intersection_pl( [ $_->split_at_first_point ], @@ -474,8 +479,8 @@ sub generate_toolpaths { # make more loops my @loops = @loops0; for my $i (2..$contact_loops) { - my $d = ($i-1) * $flow->scaled_spacing; - push @loops, @{offset2(\@loops0, -$d -0.5*$flow->scaled_spacing, +0.5*$flow->scaled_spacing)}; + my $d = ($i-1) * $interface_flow->scaled_spacing; + push @loops, @{offset2(\@loops0, -$d -0.5*$interface_flow->scaled_spacing, +0.5*$interface_flow->scaled_spacing)}; } # clip such loops to the side oriented towards the object @@ -495,10 +500,11 @@ sub generate_toolpaths { ); # transform loops into ExtrusionPath objects + my $mm3_per_mm = $interface_flow->mm3_per_mm($layer->height); @loops = map Slic3r::ExtrusionPath->new( - polyline => $_, - role => EXTR_ROLE_SUPPORTMATERIAL, - flow_spacing => $flow->spacing, + polyline => $_, + role => EXTR_ROLE_SUPPORTMATERIAL, + mm3_per_mm => $mm3_per_mm, ), @loops; $layer->support_interface_fills->append(@loops); @@ -531,16 +537,16 @@ sub generate_toolpaths { foreach my $expolygon (@{union_ex($interface)}) { my ($params, @p) = $fillers{interface}->fill_surface( Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL), - density => $interface_density, - flow_spacing => $flow->spacing, - complete => 1, + density => $interface_density, + flow => $interface_flow, + complete => 1, ); + my $mm3_per_mm = $params->{flow}->mm3_per_mm($layer->height); push @paths, map Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polyline->new(@$_), - role => EXTR_ROLE_SUPPORTMATERIAL, - height => undef, - flow_spacing => $params->{flow_spacing}, + polyline => Slic3r::Polyline->new(@$_), + role => EXTR_ROLE_SUPPORTMATERIAL, + mm3_per_mm => $mm3_per_mm, ), @p; } @@ -551,8 +557,8 @@ sub generate_toolpaths { if (@$base) { my $filler = $fillers{support}; $filler->angle($angles[ ($layer_id) % @angles ]); - my $density = $support_density; - my $flow_spacing = $flow->spacing; + my $density = $support_density; + my $base_flow = $flow; # TODO: use offset2_ex() my $to_infill = union_ex($base, 1); @@ -561,17 +567,17 @@ sub generate_toolpaths { # base flange if ($layer_id == 0) { $filler = $fillers{interface}; - $filler->angle($self->config->support_material_angle + 90); + $filler->angle($self->object_config->support_material_angle + 90); $density = 0.5; - $flow_spacing = $object->print->first_layer_support_material_flow->spacing; + $base_flow = $self->first_layer_flow; } else { # draw a perimeter all around support infill # TODO: use brim ordering algorithm + my $mm3_per_mm = $flow->mm3_per_mm($layer->height); push @paths, map Slic3r::ExtrusionPath->new( - polyline => $_->split_at_first_point, - role => EXTR_ROLE_SUPPORTMATERIAL, - height => undef, - flow_spacing => $flow->spacing, + polyline => $_->split_at_first_point, + role => EXTR_ROLE_SUPPORTMATERIAL, + mm3_per_mm => $mm3_per_mm, ), map @$_, @$to_infill; # TODO: use offset2_ex() @@ -581,16 +587,16 @@ sub generate_toolpaths { foreach my $expolygon (@$to_infill) { my ($params, @p) = $filler->fill_surface( Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL), - density => $density, - flow_spacing => $flow_spacing, - complete => 1, + density => $density, + flow => $base_flow, + complete => 1, ); + my $mm3_per_mm = $params->{flow}->mm3_per_mm($layer->height); push @paths, map Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polyline->new(@$_), - role => EXTR_ROLE_SUPPORTMATERIAL, - height => undef, - flow_spacing => $params->{flow_spacing}, + polyline => Slic3r::Polyline->new(@$_), + role => EXTR_ROLE_SUPPORTMATERIAL, + mm3_per_mm => $mm3_per_mm, ), @p; } @@ -609,7 +615,7 @@ sub generate_toolpaths { }; Slic3r::parallelize( - threads => $self->config->threads, + threads => $self->print_config->threads, items => [ 0 .. $#{$object->support_layers} ], thread_cb => sub { my $q = shift; diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 3a3944318..2e88268a7 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -99,20 +99,21 @@ sub model { sub init_print { my ($model_name, %params) = @_; - my $config = Slic3r::Config->new_from_defaults; + my $config = Slic3r::Config->new; $config->apply($params{config}) if $params{config}; $config->set('gcode_comments', 1) if $ENV{SLIC3R_TESTS_GCODE}; - my $print = Slic3r::Print->new(config => $config); + my $print = Slic3r::Print->new; + $print->apply_config($config); $model_name = [$model_name] if ref($model_name) ne 'ARRAY'; for my $model (map model($_, %params), @$model_name) { die "Unknown model in test" if !defined $model; if (defined $params{duplicate} && $params{duplicate} > 1) { - $model->duplicate($params{duplicate} // 1, $config->min_object_distance); + $model->duplicate($params{duplicate} // 1, $print->config->min_object_distance); } - $model->arrange_objects($config->min_object_distance); - $model->center_instances_around_point($config->print_center); + $model->arrange_objects($print->config->min_object_distance); + $model->center_instances_around_point($print->config->print_center); $print->add_model_object($_) for @{$model->objects}; } $print->validate; diff --git a/slic3r.pl b/slic3r.pl index c55bd19a6..e27e7d323 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -12,7 +12,6 @@ use Getopt::Long qw(:config no_auto_abbrev); use List::Util qw(first); use POSIX qw(setlocale LC_NUMERIC); use Slic3r; -use Slic3r::Geometry qw(X Y); use Time::HiRes qw(gettimeofday tv_interval); $|++; @@ -38,6 +37,11 @@ my %cli_options = (); 'merge|m' => \$opt{merge}, 'repair' => \$opt{repair}, 'info' => \$opt{info}, + + 'scale=f' => \$opt{scale}, + 'rotate=i' => \$opt{rotate}, + 'duplicate=i' => \$opt{duplicate}, + 'duplicate-grid=s' => \$opt{duplicate_grid}, ); foreach my $opt_key (keys %{$Slic3r::Config::Options}) { my $cli = $Slic3r::Config::Options->{$opt_key}->{cli} or next; @@ -122,52 +126,36 @@ if (@ARGV) { # slicing from command line $model = Slic3r::Model->read_from_file($input_file); } - my $need_arrange = $model->has_objects_with_no_instances; - if ($need_arrange) { - # apply a default position to all objects not having one - foreach my $object (@{$model->objects}) { - $object->add_instance(offset => [0,0]) if !defined $object->instances; - } - } - - # apply scaling and rotation supplied from command line if any - foreach my $instance (map @{$_->instances}, @{$model->objects}) { - $instance->scaling_factor($instance->scaling_factor * $config->scale); - $instance->rotation($instance->rotation + $config->rotate); - } - # TODO: --scale --rotate, --duplicate* shouldn't be stored in config - - if ($config->duplicate_grid->[X] > 1 || $config->duplicate_grid->[Y] > 1) { - $model->duplicate_objects_grid($config->duplicate_grid, $config->duplicate_distance); - } elsif ($need_arrange) { - $model->duplicate_objects($config->duplicate, $config->min_object_distance); - } elsif ($config->duplicate > 1) { - # if all input objects have defined position(s) apply duplication to the whole model - $model->duplicate($config->duplicate, $config->min_object_distance); - } - $model->center_instances_around_point($config->print_center); - if ($opt{info}) { $model->print_info; next; } - my $print = Slic3r::Print->new( - config => $config, - status_cb => sub { + if (defined $opt{duplicate_grid}) { + $opt{duplicate_grid} = [ split /[,x]/, $opt{duplicate_grid}, 2 ]; + } + + my $sprint = Slic3r::Print::Simple->new( + scale => $opt{scale} // 1, + rotate => $opt{rotate} // 0, + duplicate => $opt{duplicate} // 1, + duplicate_grid => $opt{duplicate_grid} // [1,1], + status_cb => sub { my ($percent, $message) = @_; printf "=> %s\n", $message; }, + output_file => $opt{output}, ); - $print->add_model_object($_) for @{$model->objects}; + + $sprint->apply_config($config); + $sprint->set_model($model); undef $model; # free memory - $print->validate; + if ($opt{export_svg}) { - $print->export_svg(output_file => $opt{output}); + $sprint->export_svg; } else { my $t0 = [gettimeofday]; - $print->process; - $print->export_gcode(output_file => $opt{output}); + $sprint->export_gcode; # output some statistics { @@ -175,9 +163,8 @@ if (@ARGV) { # slicing from command line printf "Done. Process took %d minutes and %.3f seconds\n", int($duration/60), ($duration - int($duration/60)*60); # % truncates to integer } - print map sprintf("Filament required: %.1fmm (%.1fcm3)\n", - $_->absolute_E, $_->extruded_volume/1000), - @{$print->extruders}; + printf "Filament required: %.1fmm (%.1fcm3)\n", + $sprint->total_used_filament, $sprint->total_extruded_volume/1000; } } } else { @@ -415,11 +402,11 @@ $j (mm, default: $config->{brim_width}) Transform options: - --scale Factor for scaling input object (default: $config->{scale}) - --rotate Rotation angle in degrees (0-360, default: $config->{rotate}) - --duplicate Number of items with auto-arrange (1+, default: $config->{duplicate}) + --scale Factor for scaling input object (default: 1) + --rotate Rotation angle in degrees (0-360, default: 0) + --duplicate Number of items with auto-arrange (1+, default: 1) --bed-size Bed size, only used for auto-arrange (mm, default: $config->{bed_size}->[0],$config->{bed_size}->[1]) - --duplicate-grid Number of items with grid arrangement (default: $config->{duplicate_grid}->[0],$config->{duplicate_grid}->[1]) + --duplicate-grid Number of items with grid arrangement (default: 1,1) --duplicate-distance Distance in mm between copies (default: $config->{duplicate_distance}) Sequential printing options: diff --git a/t/arcs.t b/t/arcs.t index 92f83964b..2eac2acf6 100644 --- a/t/arcs.t +++ b/t/arcs.t @@ -21,7 +21,7 @@ use Slic3r::Geometry qw(scaled_epsilon scale X Y); [306517.1,219034.23], [286979.42,248012.49], [258001.16,267550.17], [222515.14,274714.47], [187029.11,267550.17], [158050.85,248012.49], [138513.17,219034.23], [131348.87,183548.2], [86948.77,175149.09], [119825.35,100585], - ), role => EXTR_ROLE_FILL, flow_spacing => 0.5); + ), role => EXTR_ROLE_FILL, mm3_per_mm => 0.5); my @paths = $path->detect_arcs(30); @@ -42,12 +42,12 @@ use Slic3r::Geometry qw(scaled_epsilon scale X Y); my $path1 = Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@points), role => EXTR_ROLE_FILL, - flow_spacing => 0.5, + mm3_per_mm => 0.5, ); my $path2 = Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(reverse @points), role => EXTR_ROLE_FILL, - flow_spacing => 0.5, + mm3_per_mm => 0.5, ); my @paths1 = $path1->detect_arcs(10, scale 1); diff --git a/t/combineinfill.t b/t/combineinfill.t index bd80b0d30..62e22e6b2 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -11,10 +11,41 @@ use List::Util qw(first); use Slic3r; use Slic3r::Test; -plan skip_all => 'this test is currently disabled'; # needs to be adapted to the new API -plan tests => 3; +plan tests => 2; { + my $config = Slic3r::Config->new_from_defaults; + $config->set('layer_height', 0.2); + $config->set('first_layer_height', 0.2); + $config->set('nozzle_diameter', [0.5]); + $config->set('infill_every_layers', 2); + $config->set('infill_extruder', 2); + $config->set('top_solid_layers', 0); + $config->set('bottom_solid_layers', 0); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + ok my $gcode = Slic3r::Test::gcode($print), "infill_every_layers does not crash"; + + my $tool = undef; + my %layer_infill = (); # layer_z => has_infill + Slic3r::GCode::Reader->new->parse($gcode, sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd =~ /^T(\d+)/) { + $tool = $1; + } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { + $layer_infill{$self->Z} //= 0; + if ($tool == $config->infill_extruder-1) { + $layer_infill{$self->Z} = 1; + } + } + }); + my $layers_with_infill = grep $_, values %layer_infill; + $layers_with_infill--; # first layer is never combined + is $layers_with_infill, scalar(keys %layer_infill)/2, 'infill is only present in correct number of layers'; +} + +# the following needs to be adapted to the new API +if (0) { my $config = Slic3r::Config->new_from_defaults; $config->set('skirts', 0); $config->set('solid_layers', 0); diff --git a/t/cooling.t b/t/cooling.t index 87d3dff39..6fc5cf71b 100644 --- a/t/cooling.t +++ b/t/cooling.t @@ -13,10 +13,14 @@ use Slic3r; use Slic3r::Test; sub buffer { - my $config = shift || Slic3r::Config->new_from_defaults; + my $config = shift || Slic3r::Config->new; + + my $print_config = Slic3r::Config::Print->new; + $print_config->apply_dynamic($config); + my $buffer = Slic3r::GCode::CoolingBuffer->new( - config => $config, - gcodegen => Slic3r::GCode->new(config => $config, layer_count => 10, extruders => []), + config => $print_config, + gcodegen => Slic3r::GCode->new(print_config => $print_config, layer_count => 10, extruders => []), ); return $buffer; } diff --git a/t/fill.t b/t/fill.t index 2be4b87c1..8e112484a 100644 --- a/t/fill.t +++ b/t/fill.t @@ -43,9 +43,14 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } surface_type => S_TYPE_TOP, expolygon => $expolygon, ); + my $flow = Slic3r::Flow->new( + width => 0.69, + spacing => 0.69, + nozzle_diameter => 0.50, + ); foreach my $angle (0, 45) { $surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]); - my ($params, @paths) = $filler->fill_surface($surface, flow_spacing => 0.69, density => 0.4); + my ($params, @paths) = $filler->fill_surface($surface, flow => $flow, density => 0.4); is scalar @paths, 1, 'one continuous path'; } } @@ -62,14 +67,19 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } surface_type => S_TYPE_BOTTOM, expolygon => $expolygon, ); + my $flow = Slic3r::Flow->new( + width => $flow_spacing, + spacing => $flow_spacing, + nozzle_diameter => $flow_spacing, + ); my ($params, @paths) = $filler->fill_surface( $surface, - flow_spacing => $flow_spacing, + flow => $flow, density => $density // 1, ); # check whether any part was left uncovered - my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $params->{flow_spacing}/2)}, @paths; + my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $params->{flow}->spacing/2)}, @paths; my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ], 1); # ignore very small dots @@ -134,7 +144,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0), + map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([0,15], [0,18], [0,20]), Slic3r::Polyline->new([0,10], [0,8], [0,5]), ); @@ -146,7 +156,7 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0), + map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([15,0], [10,0], [4,0]), Slic3r::Polyline->new([10,5], [15,5], [20,5]), ); diff --git a/t/gcode.t b/t/gcode.t index c8db2f816..bd5677fc7 100644 --- a/t/gcode.t +++ b/t/gcode.t @@ -14,9 +14,8 @@ use Slic3r::Test; { my $gcodegen = Slic3r::GCode->new( - config => Slic3r::Config->new_from_defaults, - layer_count => 1, - extruders => [], + layer_count => 1, + extruders => [], ); $gcodegen->set_shift(10, 10); is_deeply $gcodegen->last_pos->arrayref, [scale -10, scale -10], 'last_pos is shifted correctly'; diff --git a/t/retraction.t b/t/retraction.t index 636359149..3737f63cd 100644 --- a/t/retraction.t +++ b/t/retraction.t @@ -41,8 +41,8 @@ my $test = sub { if ($info->{dist_Z}) { # lift move or lift + change layer - if (_eq($info->{dist_Z}, $print->extruders->[$tool]->retract_lift) - || (_eq($info->{dist_Z}, $conf->layer_height + $print->extruders->[$tool]->retract_lift) && $print->extruders->[$tool]->retract_lift > 0)) { + if (_eq($info->{dist_Z}, $print->config->get_at('retract_lift', $tool)) + || (_eq($info->{dist_Z}, $conf->layer_height + $print->config->get_at('retract_lift', $tool)) && $print->config->get_at('retract_lift', $tool) > 0)) { fail 'only lifting while retracted' if !$retracted[$tool] && !($conf->g0 && $info->{retracting}); fail 'double lift' if $lifted; $lifted = 1; @@ -50,8 +50,8 @@ my $test = sub { if ($info->{dist_Z} < 0) { fail 'going down only after lifting' if !$lifted; fail 'going down by the same amount of the lift or by the amount needed to get to next layer' - if !_eq($info->{dist_Z}, -$print->extruders->[$tool]->retract_lift) - && !_eq($info->{dist_Z}, -$print->extruders->[$tool]->retract_lift + $conf->layer_height); + if !_eq($info->{dist_Z}, -$print->config->get_at('retract_lift', $tool)) + && !_eq($info->{dist_Z}, -$print->config->get_at('retract_lift', $tool) + $conf->layer_height); $lifted = 0; } fail 'move Z at travel speed' if ($args->{F} // $self->F) != $conf->travel_speed * 60; @@ -59,9 +59,9 @@ my $test = sub { if ($info->{retracting}) { $retracted[$tool] = 1; $retracted_length[$tool] += -$info->{dist_E}; - if (_eq($retracted_length[$tool], $print->extruders->[$tool]->retract_length)) { + if (_eq($retracted_length[$tool], $print->config->get_at('retract_length', $tool))) { # okay - } elsif (_eq($retracted_length[$tool], $print->extruders->[$tool]->retract_length_toolchange)) { + } elsif (_eq($retracted_length[$tool], $print->config->get_at('retract_length_toolchange', $tool))) { $wait_for_toolchange = 1; } else { fail 'retracted by the correct amount'; @@ -72,9 +72,9 @@ my $test = sub { if ($info->{extruding}) { fail 'only extruding while not lifted' if $lifted; if ($retracted[$tool]) { - my $expected_amount = $retracted_length[$tool] + $print->extruders->[$tool]->retract_restart_extra; + my $expected_amount = $retracted_length[$tool] + $print->config->get_at('retract_restart_extra', $tool); if ($changed_tool && $toolchange_count[$tool] > 1) { - $expected_amount = $print->extruders->[$tool]->retract_length_toolchange + $print->extruders->[$tool]->retract_restart_extra_toolchange; + $expected_amount = $print->config->get_at('retract_length_toolchange', $tool) + $print->config->get_at('retract_restart_extra_toolchange', $tool); $changed_tool = 0; } fail 'unretracted by the correct amount' @@ -83,7 +83,7 @@ my $test = sub { $retracted_length[$tool] = 0; } } - if ($info->{travel} && $info->{dist_XY} >= $print->extruders->[$tool]->retract_before_travel) { + if ($info->{travel} && $info->{dist_XY} >= $print->config->get_at('retract_before_travel', $tool)) { fail 'retracted before long travel move' if !$retracted[$tool]; } }); diff --git a/t/shells.t b/t/shells.t index 10da90556..541c7cb75 100644 --- a/t/shells.t +++ b/t/shells.t @@ -140,6 +140,9 @@ use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; + $config->set('perimeters', 1); + $config->set('fill_density', 0); + $config->set('top_solid_layers', 0); $config->set('spiral_vase', 1); $config->set('bottom_solid_layers', 0); $config->set('skirts', 0); diff --git a/t/support.t b/t/support.t index b7d1810df..a1e7173d9 100644 --- a/t/support.t +++ b/t/support.t @@ -21,16 +21,22 @@ use Slic3r::Test; my $test = sub { my $print = Slic3r::Test::init_print('20mm_cube', config => $config); $print->init_extruders; - my $flow = $print->support_material_flow; + my $flow = $print->objects->[0]->support_material_flow; my $support_z = Slic3r::Print::SupportMaterial - ->new(config => $config, flow => $flow) + ->new( + object_config => $print->objects->[0]->config, + print_config => $print->config, + flow => $flow, + interface_flow => $flow, + first_layer_flow => $flow, + ) ->support_layers_z(\@contact_z, \@top_z, $config->layer_height); is $support_z->[0], $config->first_layer_height, 'first layer height is honored'; is scalar(grep { $support_z->[$_]-$support_z->[$_-1] <= 0 } 1..$#$support_z), 0, 'no null or negative support layers'; - is scalar(grep { $support_z->[$_]-$support_z->[$_-1] > $flow->nozzle_diameter + epsilon } 1..$#$support_z), 0, + is scalar(grep { $support_z->[$_]-$support_z->[$_-1] > $config->nozzle_diameter->[0] + epsilon } 1..$#$support_z), 0, 'no layers thicker than nozzle diameter'; my $wrong_top_spacing = 0; @@ -40,7 +46,7 @@ use Slic3r::Test; # check that first support layer above this top surface is spaced with nozzle diameter $wrong_top_spacing = 1 - if ($support_z->[$layer_id+1] - $support_z->[$layer_id]) != $flow->nozzle_diameter; + if ($support_z->[$layer_id+1] - $support_z->[$layer_id]) != $config->nozzle_diameter->[0]; } ok !$wrong_top_spacing, 'layers above top surfaces are spaced correctly'; }; diff --git a/xs/MANIFEST b/xs/MANIFEST index ef37e40cb..fc52e2497 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -22,6 +22,8 @@ src/ExtrusionEntity.cpp src/ExtrusionEntity.hpp src/ExtrusionEntityCollection.cpp src/ExtrusionEntityCollection.hpp +src/Flow.cpp +src/Flow.hpp src/Geometry.cpp src/Geometry.hpp src/Line.cpp @@ -65,6 +67,7 @@ t/12_extrusionpathcollection.t t/13_polylinecollection.t t/14_geometry.t t/15_config.t +t/16_flow.t xsp/Clipper.xsp xsp/Config.xsp xsp/ExPolygon.xsp @@ -72,6 +75,7 @@ xsp/ExPolygonCollection.xsp xsp/ExtrusionEntityCollection.xsp xsp/ExtrusionLoop.xsp xsp/ExtrusionPath.xsp +xsp/Flow.xsp xsp/Geometry.xsp xsp/Line.xsp xsp/my.map diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 9f761ba9a..489aeb0e8 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -97,8 +97,7 @@ sub new { return $class->_new( $args{polygon}, # required $args{role}, # required - $args{height} // -1, - $args{flow_spacing} // -1, + $args{mm3_per_mm} // -1, ); } @@ -108,8 +107,7 @@ sub clone { return (ref $self)->_new( $args{polygon} // $self->polygon, $args{role} // $self->role, - $args{height} // $self->height, - $args{flow_spacing} // $self->flow_spacing, + $args{mm3_per_mm} // $self->mm3_per_mm, ); } @@ -129,8 +127,7 @@ sub new { return $class->_new( $args{polyline}, # required $args{role}, # required - $args{height} // -1, - $args{flow_spacing} // -1, + $args{mm3_per_mm} // -1, ); } @@ -140,8 +137,7 @@ sub clone { return (ref $self)->_new( $args{polyline} // $self->polyline, $args{role} // $self->role, - $args{height} // $self->height, - $args{flow_spacing} // $self->flow_spacing, + $args{mm3_per_mm} // $self->mm3_per_mm, ); } @@ -150,6 +146,34 @@ our @ISA = 'Slic3r::ExtrusionPath'; sub DESTROY {} +package Slic3r::Flow; + +sub new { + my ($class, %args) = @_; + + my $self = $class->_new( + @args{qw(width spacing nozzle_diameter)}, + ); + $self->set_bridge($args{bridge} // 0); + return $self; +} + +sub new_from_width { + my ($class, %args) = @_; + + return $class->_new_from_width( + @args{qw(role width nozzle_diameter layer_height bridge_flow_ratio)}, + ); +} + +sub new_from_spacing { + my ($class, %args) = @_; + + return $class->_new_from_spacing( + @args{qw(spacing nozzle_diameter layer_height bridge)}, + ); +} + package Slic3r::Surface; sub new { diff --git a/xs/src/Config.cpp b/xs/src/Config.cpp index 15ff4340c..20be9a3c2 100644 --- a/xs/src/Config.cpp +++ b/xs/src/Config.cpp @@ -35,6 +35,14 @@ ConfigBase::serialize(const t_config_option_key opt_key) { void ConfigBase::set_deserialize(const t_config_option_key opt_key, std::string str) { + if (this->def->count(opt_key) == 0) throw "Calling set_deserialize() on unknown option"; + ConfigOptionDef* optdef = &(*this->def)[opt_key]; + if (!optdef->shortcut.empty()) { + for (std::vector::iterator it = optdef->shortcut.begin(); it != optdef->shortcut.end(); ++it) + this->set_deserialize(*it, str); + return; + } + ConfigOption* opt = this->option(opt_key, true); assert(opt != NULL); opt->deserialize(str); @@ -42,23 +50,29 @@ ConfigBase::set_deserialize(const t_config_option_key opt_key, std::string str) double ConfigBase::get_abs_value(const t_config_option_key opt_key) { - // get option definition - assert(this->def->count(opt_key) != 0); - ConfigOptionDef* def = &(*this->def)[opt_key]; - assert(def->type == coFloatOrPercent); - + ConfigOption* opt = this->option(opt_key, false); + if (ConfigOptionFloatOrPercent* optv = dynamic_cast(opt)) { + // get option definition + assert(this->def->count(opt_key) != 0); + ConfigOptionDef* def = &(*this->def)[opt_key]; + + // compute absolute value over the absolute value of the base option + return optv->get_abs_value(this->get_abs_value(def->ratio_over)); + } else if (ConfigOptionFloat* optv = dynamic_cast(opt)) { + return optv->value; + } else { + throw "Not a valid option type for get_abs_value()"; + } +} + +double +ConfigBase::get_abs_value(const t_config_option_key opt_key, double ratio_over) { // get stored option value ConfigOptionFloatOrPercent* opt = dynamic_cast(this->option(opt_key)); assert(opt != NULL); // compute absolute value - if (opt->percent) { - ConfigOptionFloat* optbase = dynamic_cast(this->option(def->ratio_over)); - if (optbase == NULL) throw "ratio_over option not found"; - return optbase->value * opt->value / 100; - } else { - return opt->value; - } + return opt->get_abs_value(ratio_over); } #ifdef SLIC3RXS @@ -108,9 +122,9 @@ ConfigBase::get(t_config_option_key opt_key) { return optv->point.to_SV_pureperl(); } else if (ConfigOptionPoints* optv = dynamic_cast(opt)) { AV* av = newAV(); - av_fill(av, optv->points.size()-1); - for (Pointfs::iterator it = optv->points.begin(); it != optv->points.end(); ++it) - av_store(av, it - optv->points.begin(), it->to_SV_pureperl()); + av_fill(av, optv->values.size()-1); + for (Pointfs::iterator it = optv->values.begin(); it != optv->values.end(); ++it) + av_store(av, it - optv->values.begin(), it->to_SV_pureperl()); return newRV_noinc((SV*)av); } else if (ConfigOptionBool* optv = dynamic_cast(opt)) { return newSViv(optv->value ? 1 : 0); @@ -126,11 +140,40 @@ ConfigBase::get(t_config_option_key opt_key) { } } +SV* +ConfigBase::get_at(t_config_option_key opt_key, size_t i) { + ConfigOption* opt = this->option(opt_key); + if (opt == NULL) return &PL_sv_undef; + + if (ConfigOptionFloats* optv = dynamic_cast(opt)) { + return newSVnv(optv->get_at(i)); + } else if (ConfigOptionInts* optv = dynamic_cast(opt)) { + return newSViv(optv->get_at(i)); + } else if (ConfigOptionStrings* optv = dynamic_cast(opt)) { + // we don't serialize() because that would escape newlines + std::string val = optv->get_at(i); + return newSVpvn(val.c_str(), val.length()); + } else if (ConfigOptionPoints* optv = dynamic_cast(opt)) { + return optv->get_at(i).to_SV_pureperl(); + } else if (ConfigOptionBools* optv = dynamic_cast(opt)) { + return newSViv(optv->get_at(i) ? 1 : 0); + } else { + return &PL_sv_undef; + } +} + void ConfigBase::set(t_config_option_key opt_key, SV* value) { ConfigOption* opt = this->option(opt_key, true); if (opt == NULL) CONFESS("Trying to set non-existing option"); + ConfigOptionDef* optdef = &(*this->def)[opt_key]; + if (!optdef->shortcut.empty()) { + for (std::vector::iterator it = optdef->shortcut.begin(); it != optdef->shortcut.end(); ++it) + this->set(*it, value); + return; + } + if (ConfigOptionFloat* optv = dynamic_cast(opt)) { optv->value = SvNV(value); } else if (ConfigOptionFloats* optv = dynamic_cast(opt)) { @@ -164,14 +207,14 @@ ConfigBase::set(t_config_option_key opt_key, SV* value) { } else if (ConfigOptionPoint* optv = dynamic_cast(opt)) { optv->point.from_SV(value); } else if (ConfigOptionPoints* optv = dynamic_cast(opt)) { - optv->points.clear(); + optv->values.clear(); AV* av = (AV*)SvRV(value); const size_t len = av_len(av)+1; for (size_t i = 0; i < len; i++) { SV** elem = av_fetch(av, i, 0); Pointf point; point.from_SV(*elem); - optv->points.push_back(point); + optv->values.push_back(point); } } else if (ConfigOptionBool* optv = dynamic_cast(opt)) { optv->value = SvTRUE(value); diff --git a/xs/src/Config.hpp b/xs/src/Config.hpp index e222651c8..71f945349 100644 --- a/xs/src/Config.hpp +++ b/xs/src/Config.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include "Point.hpp" @@ -20,10 +21,26 @@ typedef std::vector t_config_option_keys; class ConfigOption { public: virtual ~ConfigOption() {}; - virtual std::string serialize() = 0; + virtual std::string serialize() const = 0; virtual void deserialize(std::string str) = 0; }; +template +class ConfigOptionVector +{ + public: + virtual ~ConfigOptionVector() {}; + std::vector values; + + T get_at(size_t i) { + try { + return this->values.at(i); + } catch (const std::out_of_range& oor) { + return this->values.front(); + } + }; +}; + class ConfigOptionFloat : public ConfigOption { public: @@ -32,7 +49,7 @@ class ConfigOptionFloat : public ConfigOption operator double() const { return this->value; }; - std::string serialize() { + std::string serialize() const { std::ostringstream ss; ss << this->value; return ss.str(); @@ -43,12 +60,11 @@ class ConfigOptionFloat : public ConfigOption }; }; -class ConfigOptionFloats : public ConfigOption +class ConfigOptionFloats : public ConfigOption, public ConfigOptionVector { public: - std::vector values; - std::string serialize() { + std::string serialize() const { std::ostringstream ss; for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { if (it - this->values.begin() != 0) ss << ","; @@ -75,7 +91,7 @@ class ConfigOptionInt : public ConfigOption operator int() const { return this->value; }; - std::string serialize() { + std::string serialize() const { std::ostringstream ss; ss << this->value; return ss.str(); @@ -86,12 +102,11 @@ class ConfigOptionInt : public ConfigOption }; }; -class ConfigOptionInts : public ConfigOption +class ConfigOptionInts : public ConfigOption, public ConfigOptionVector { public: - std::vector values; - std::string serialize() { + std::string serialize() const { std::ostringstream ss; for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { if (it - this->values.begin() != 0) ss << ","; @@ -118,7 +133,7 @@ class ConfigOptionString : public ConfigOption operator std::string() const { return this->value; }; - std::string serialize() { + std::string serialize() const { std::string str = this->value; // s/\R/\\n/g @@ -144,12 +159,11 @@ class ConfigOptionString : public ConfigOption }; // semicolon-separated strings -class ConfigOptionStrings : public ConfigOption +class ConfigOptionStrings : public ConfigOption, public ConfigOptionVector { public: - std::vector values; - std::string serialize() { + std::string serialize() const { std::ostringstream ss; for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { if (it - this->values.begin() != 0) ss << ";"; @@ -175,7 +189,15 @@ class ConfigOptionFloatOrPercent : public ConfigOption bool percent; ConfigOptionFloatOrPercent() : value(0), percent(false) {}; - std::string serialize() { + double get_abs_value(double ratio_over) const { + if (this->percent) { + return ratio_over * this->value / 100; + } else { + return this->value; + } + }; + + std::string serialize() const { std::ostringstream ss; ss << this->value; std::string s(ss.str()); @@ -202,7 +224,7 @@ class ConfigOptionPoint : public ConfigOption operator Pointf() const { return this->point; }; - std::string serialize() { + std::string serialize() const { std::ostringstream ss; ss << this->point.x; ss << ","; @@ -215,15 +237,14 @@ class ConfigOptionPoint : public ConfigOption }; }; -class ConfigOptionPoints : public ConfigOption +class ConfigOptionPoints : public ConfigOption, public ConfigOptionVector { public: - Pointfs points; - std::string serialize() { + std::string serialize() const { std::ostringstream ss; - for (Pointfs::const_iterator it = this->points.begin(); it != this->points.end(); ++it) { - if (it - this->points.begin() != 0) ss << ","; + for (Pointfs::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { + if (it - this->values.begin() != 0) ss << ","; ss << it->x; ss << "x"; ss << it->y; @@ -232,13 +253,13 @@ class ConfigOptionPoints : public ConfigOption }; void deserialize(std::string str) { - this->points.clear(); + this->values.clear(); std::istringstream is(str); std::string point_str; while (std::getline(is, point_str, ',')) { Pointf point; sscanf(point_str.c_str(), "%lfx%lf", &point.x, &point.y); - this->points.push_back(point); + this->values.push_back(point); } }; }; @@ -251,7 +272,7 @@ class ConfigOptionBool : public ConfigOption operator bool() const { return this->value; }; - std::string serialize() { + std::string serialize() const { return std::string(this->value ? "1" : "0"); }; @@ -260,12 +281,11 @@ class ConfigOptionBool : public ConfigOption }; }; -class ConfigOptionBools : public ConfigOption +class ConfigOptionBools : public ConfigOption, public ConfigOptionVector { public: - std::vector values; - std::string serialize() { + std::string serialize() const { std::ostringstream ss; for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { if (it - this->values.begin() != 0) ss << ","; @@ -294,7 +314,7 @@ class ConfigOptionEnum : public ConfigOption operator T() const { return this->value; }; - std::string serialize() { + std::string serialize() const { t_config_enum_values enum_keys_map = ConfigOptionEnum::get_enum_values(); for (t_config_enum_values::iterator it = enum_keys_map.begin(); it != enum_keys_map.end(); ++it) { if (it->second == static_cast(this->value)) return it->first; @@ -321,7 +341,7 @@ class ConfigOptionEnumGeneric : public ConfigOption operator int() const { return this->value; }; - std::string serialize() { + std::string serialize() const { for (t_config_enum_values::iterator it = this->keys_map->begin(); it != this->keys_map->end(); ++it) { if (it->second == this->value) return it->first; } @@ -354,6 +374,7 @@ class ConfigOptionDef public: ConfigOptionType type; std::string label; + std::string full_label; std::string category; std::string tooltip; std::string sidetext; @@ -361,7 +382,6 @@ class ConfigOptionDef std::string scope; t_config_option_key ratio_over; bool multiline; - bool full_label; bool full_width; bool readonly; int height; @@ -374,7 +394,7 @@ class ConfigOptionDef std::vector enum_labels; t_config_enum_values enum_keys_map; - ConfigOptionDef() : multiline(false), full_label(false), full_width(false), readonly(false), + ConfigOptionDef() : multiline(false), full_width(false), readonly(false), height(-1), width(-1), min(INT_MIN), max(INT_MAX) {}; }; @@ -393,10 +413,12 @@ class ConfigBase std::string serialize(const t_config_option_key opt_key); void set_deserialize(const t_config_option_key opt_key, std::string str); double get_abs_value(const t_config_option_key opt_key); + double get_abs_value(const t_config_option_key opt_key, double ratio_over); #ifdef SLIC3RXS SV* as_hash(); SV* get(t_config_option_key opt_key); + SV* get_at(t_config_option_key opt_key, size_t i); void set(t_config_option_key opt_key, SV* value); #endif }; diff --git a/xs/src/ExtrusionEntity.cpp b/xs/src/ExtrusionEntity.cpp index 9c5cd5d31..c6641a716 100644 --- a/xs/src/ExtrusionEntity.cpp +++ b/xs/src/ExtrusionEntity.cpp @@ -116,8 +116,7 @@ ExtrusionLoop::split_at_index(int index) const ExtrusionPath* path = new ExtrusionPath(); path->polyline = *poly; path->role = this->role; - path->height = this->height; - path->flow_spacing = this->flow_spacing; + path->mm3_per_mm = this->mm3_per_mm; delete poly; return path; diff --git a/xs/src/ExtrusionEntity.hpp b/xs/src/ExtrusionEntity.hpp index c763ad77e..381fd1fd1 100644 --- a/xs/src/ExtrusionEntity.hpp +++ b/xs/src/ExtrusionEntity.hpp @@ -31,8 +31,7 @@ class ExtrusionEntity virtual ExtrusionEntity* clone() const = 0; virtual ~ExtrusionEntity() {}; ExtrusionRole role; - double height; // vertical thickness of the extrusion expressed in mm - double flow_spacing; + double mm3_per_mm; // mm^3 of plastic per mm of linear head motion virtual void reverse() = 0; virtual Point* first_point() const = 0; virtual Point* last_point() const = 0; diff --git a/xs/src/Flow.cpp b/xs/src/Flow.cpp new file mode 100644 index 000000000..e5e7dc2be --- /dev/null +++ b/xs/src/Flow.cpp @@ -0,0 +1,109 @@ +#include "Flow.hpp" +#include + +namespace Slic3r { + +Flow +Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio) { + float w; + if (!width.percent && width.value == 0) { + w = Flow::_width(role, nozzle_diameter, height, bridge_flow_ratio); + } else { + w = width.get_abs_value(height); + } + + Flow flow(w, Flow::_spacing(w, nozzle_diameter, height, bridge_flow_ratio), nozzle_diameter); + if (bridge_flow_ratio > 0) flow.bridge = true; + return flow; +} + +Flow +Flow::new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge) { + float w = Flow::_width_from_spacing(spacing, nozzle_diameter, height, bridge); + Flow flow(w, spacing, nozzle_diameter); + flow.bridge = bridge; + return flow; +} + +double +Flow::mm3_per_mm(float h) { + if (this->bridge) { + return (this->width * this->width) * PI/4.0; + } else if (this->width >= (this->nozzle_diameter + h)) { + // rectangle with semicircles at the ends + return this->width * h + (h*h) / 4.0 * (PI-4.0); + } else { + // rectangle with shrunk semicircles at the ends + return this->nozzle_diameter * h * (1 - PI/4.0) + h * this->width * PI/4.0; + } +} + +float +Flow::_width(FlowRole role, float nozzle_diameter, float height, float bridge_flow_ratio) { + if (bridge_flow_ratio > 0) { + return sqrt(bridge_flow_ratio * (nozzle_diameter*nozzle_diameter)); + } + + // here we calculate a sane default by matching the flow speed (at the nozzle) and the feed rate + float volume = (nozzle_diameter*nozzle_diameter) * PI/4.0; + float shape_threshold = nozzle_diameter * height + (height*height) * PI/4.0; + float width; + if (volume >= shape_threshold) { + // rectangle with semicircles at the ends + width = ((nozzle_diameter*nozzle_diameter) * PI + (height*height) * (4.0 - PI)) / (4.0 * height); + } else { + // rectangle with squished semicircles at the ends + width = nozzle_diameter * (nozzle_diameter/height - 4.0/PI + 1); + } + + float min = nozzle_diameter * 1.05; + float max = -1; + if (role == frPerimeter || role == frSupportMaterial) { + min = max = nozzle_diameter; + } else if (role != frInfill) { + // do not limit width for sparse infill so that we use full native flow for it + max = nozzle_diameter * 1.7; + } + if (max != -1 && width > max) width = max; + if (width < min) width = min; + + return width; +} + + +float +Flow::_width_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge) { + if (bridge) { + return spacing - BRIDGE_EXTRA_SPACING; + } + + float w_threshold = height + nozzle_diameter; + float s_threshold = w_threshold - OVERLAP_FACTOR * (w_threshold - (w_threshold - height * (1 - PI/4.0))); + + if (spacing >= s_threshold) { + // rectangle with semicircles at the ends + return spacing + OVERLAP_FACTOR * height * (1 - PI/4.0); + } else { + // rectangle with shrunk semicircles at the ends + return (spacing + nozzle_diameter * OVERLAP_FACTOR * (PI/4.0 - 1)) / (1 + OVERLAP_FACTOR * (PI/4.0 - 1)); + } +} + +float +Flow::_spacing(float width, float nozzle_diameter, float height, float bridge_flow_ratio) { + if (bridge_flow_ratio > 0) { + return width + BRIDGE_EXTRA_SPACING; + } + + float min_flow_spacing; + if (width >= (nozzle_diameter + height)) { + // rectangle with semicircles at the ends + min_flow_spacing = width - height * (1 - PI/4.0); + } else { + // rectangle with shrunk semicircles at the ends + min_flow_spacing = nozzle_diameter * (1 - PI/4.0) + width * PI/4.0; + } + return width - OVERLAP_FACTOR * (width - min_flow_spacing); +} + +} diff --git a/xs/src/Flow.hpp b/xs/src/Flow.hpp new file mode 100644 index 000000000..e98173dc9 --- /dev/null +++ b/xs/src/Flow.hpp @@ -0,0 +1,48 @@ +#ifndef slic3r_Flow_hpp_ +#define slic3r_Flow_hpp_ + +#include +#include "Config.hpp" +#include "ExtrusionEntity.hpp" + +namespace Slic3r { + +#define BRIDGE_EXTRA_SPACING 0.05 +#define OVERLAP_FACTOR 1.0 + +enum FlowRole { + frPerimeter, + frInfill, + frSolidInfill, + frTopSolidInfill, + frSupportMaterial, + frSupportMaterialInterface, +}; + +class Flow +{ + public: + float width; + float spacing; + float nozzle_diameter; + bool bridge; + coord_t scaled_width; + coord_t scaled_spacing; + + Flow(float _w, float _s, float _nd): width(_w), spacing(_s), nozzle_diameter(_nd), bridge(false) { + this->scaled_width = scale_(this->width); + this->scaled_spacing = scale_(this->spacing); + }; + double mm3_per_mm(float h); + static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio); + static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge); + + private: + static float _width(FlowRole role, float nozzle_diameter, float height, float bridge_flow_ratio); + static float _width_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge); + static float _spacing(float width, float nozzle_diameter, float height, float bridge_flow_ratio); +}; + +} + +#endif diff --git a/xs/src/Point.cpp b/xs/src/Point.cpp index fbb3b06c6..d35b3f0f8 100644 --- a/xs/src/Point.cpp +++ b/xs/src/Point.cpp @@ -23,8 +23,8 @@ Point::rotate(double angle, Point* center) { double cur_x = (double)this->x; double cur_y = (double)this->y; - this->x = (long)round( (double)center->x + cos(angle) * (cur_x - (double)center->x) - sin(angle) * (cur_y - (double)center->y) ); - this->y = (long)round( (double)center->y + cos(angle) * (cur_y - (double)center->y) + sin(angle) * (cur_x - (double)center->x) ); + this->x = (coord_t)round( (double)center->x + cos(angle) * (cur_x - (double)center->x) - sin(angle) * (cur_y - (double)center->y) ); + this->y = (coord_t)round( (double)center->y + cos(angle) * (cur_y - (double)center->y) + sin(angle) * (cur_x - (double)center->x) ); } bool diff --git a/xs/src/Point.hpp b/xs/src/Point.hpp index 507d082c9..385f99cdb 100644 --- a/xs/src/Point.hpp +++ b/xs/src/Point.hpp @@ -17,9 +17,9 @@ typedef std::vector Pointfs; class Point { public: - long x; - long y; - explicit Point(long _x = 0, long _y = 0): x(_x), y(_y) {}; + coord_t x; + coord_t y; + explicit Point(coord_t _x = 0, coord_t _y = 0): x(_x), y(_y) {}; void scale(double factor); void translate(double x, double y); void rotate(double angle, Point* center); 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.cpp b/xs/src/PrintConfig.cpp index 76cc9ef49..711740897 100644 --- a/xs/src/PrintConfig.cpp +++ b/xs/src/PrintConfig.cpp @@ -2,6 +2,6 @@ namespace Slic3r { -t_optiondef_map PrintConfig::PrintConfigDef = PrintConfig::build_def(); +t_optiondef_map PrintConfigDef::def = PrintConfigDef::build_def(); } diff --git a/xs/src/PrintConfig.hpp b/xs/src/PrintConfig.hpp index 4dd732150..cb44a007f 100644 --- a/xs/src/PrintConfig.hpp +++ b/xs/src/PrintConfig.hpp @@ -37,417 +37,10 @@ template<> inline t_config_enum_values ConfigOptionEnum::get_enum return keys_map; } -class PrintConfig : public StaticConfig +class PrintConfigDef { public: - static t_optiondef_map PrintConfigDef; - - ConfigOptionBool avoid_crossing_perimeters; - ConfigOptionPoint bed_size; - ConfigOptionInt bed_temperature; - ConfigOptionInt bottom_solid_layers; - ConfigOptionFloat bridge_acceleration; - ConfigOptionInt bridge_fan_speed; - ConfigOptionFloat bridge_flow_ratio; - ConfigOptionFloat bridge_speed; - ConfigOptionFloat brim_width; - ConfigOptionBool complete_objects; - ConfigOptionBool cooling; - ConfigOptionFloat default_acceleration; - ConfigOptionInt disable_fan_first_layers; - ConfigOptionInt duplicate; - ConfigOptionFloat duplicate_distance; - ConfigOptionPoint duplicate_grid; - ConfigOptionString end_gcode; - ConfigOptionFloatOrPercent external_perimeter_speed; - ConfigOptionBool external_perimeters_first; - ConfigOptionBool extra_perimeters; - ConfigOptionFloat extruder_clearance_height; - ConfigOptionFloat extruder_clearance_radius; - ConfigOptionPoints extruder_offset; - ConfigOptionString extrusion_axis; - ConfigOptionFloats extrusion_multiplier; - ConfigOptionFloatOrPercent extrusion_width; - ConfigOptionBool fan_always_on; - ConfigOptionInt fan_below_layer_time; - ConfigOptionFloats filament_diameter; - ConfigOptionInt fill_angle; - ConfigOptionFloat fill_density; - ConfigOptionEnum fill_pattern; - ConfigOptionFloat first_layer_acceleration; - ConfigOptionInt first_layer_bed_temperature; - ConfigOptionFloatOrPercent first_layer_extrusion_width; - ConfigOptionFloatOrPercent first_layer_height; - ConfigOptionFloatOrPercent first_layer_speed; - ConfigOptionInts first_layer_temperature; - ConfigOptionBool g0; - ConfigOptionFloat gap_fill_speed; - ConfigOptionBool gcode_arcs; - ConfigOptionBool gcode_comments; - ConfigOptionEnum gcode_flavor; - ConfigOptionFloat infill_acceleration; - ConfigOptionInt infill_every_layers; - ConfigOptionInt infill_extruder; - ConfigOptionFloatOrPercent infill_extrusion_width; - ConfigOptionBool infill_first; - ConfigOptionBool infill_only_where_needed; - ConfigOptionFloat infill_speed; - ConfigOptionString layer_gcode; - ConfigOptionFloat layer_height; - ConfigOptionInt max_fan_speed; - ConfigOptionInt min_fan_speed; - ConfigOptionInt min_print_speed; - ConfigOptionFloat min_skirt_length; - ConfigOptionString notes; - ConfigOptionFloats nozzle_diameter; - ConfigOptionBool only_retract_when_crossing_perimeters; - ConfigOptionBool ooze_prevention; - ConfigOptionString output_filename_format; - ConfigOptionBool overhangs; - ConfigOptionFloat perimeter_acceleration; - ConfigOptionInt perimeter_extruder; - ConfigOptionFloatOrPercent perimeter_extrusion_width; - ConfigOptionFloat perimeter_speed; - ConfigOptionInt perimeters; - ConfigOptionStrings post_process; - ConfigOptionPoint print_center; - ConfigOptionInt raft_layers; - ConfigOptionBool randomize_start; - ConfigOptionFloat resolution; - ConfigOptionFloats retract_before_travel; - ConfigOptionBools retract_layer_change; - ConfigOptionFloats retract_length; - ConfigOptionFloats retract_length_toolchange; - ConfigOptionFloats retract_lift; - ConfigOptionFloats retract_restart_extra; - ConfigOptionFloats retract_restart_extra_toolchange; - ConfigOptionInts retract_speed; - ConfigOptionInt rotate; - ConfigOptionFloat scale; - ConfigOptionFloat skirt_distance; - ConfigOptionInt skirt_height; - ConfigOptionInt skirts; - ConfigOptionInt slowdown_below_layer_time; - ConfigOptionFloatOrPercent small_perimeter_speed; - ConfigOptionEnum solid_fill_pattern; - ConfigOptionFloat solid_infill_below_area; - ConfigOptionInt solid_infill_every_layers; - ConfigOptionFloatOrPercent solid_infill_extrusion_width; - ConfigOptionFloatOrPercent solid_infill_speed; - ConfigOptionInt solid_layers; - ConfigOptionBool spiral_vase; - ConfigOptionInt standby_temperature_delta; - ConfigOptionString start_gcode; - ConfigOptionBool start_perimeters_at_concave_points; - ConfigOptionBool start_perimeters_at_non_overhang; - ConfigOptionBool support_material; - ConfigOptionInt support_material_angle; - ConfigOptionInt support_material_enforce_layers; - ConfigOptionInt support_material_extruder; - ConfigOptionFloatOrPercent support_material_extrusion_width; - ConfigOptionInt support_material_interface_extruder; - ConfigOptionInt support_material_interface_layers; - ConfigOptionFloat support_material_interface_spacing; - ConfigOptionEnum support_material_pattern; - ConfigOptionFloat support_material_spacing; - ConfigOptionFloat support_material_speed; - ConfigOptionInt support_material_threshold; - ConfigOptionInts temperature; - ConfigOptionBool thin_walls; - ConfigOptionInt threads; - ConfigOptionString toolchange_gcode; - ConfigOptionFloatOrPercent top_infill_extrusion_width; - ConfigOptionFloatOrPercent top_solid_infill_speed; - ConfigOptionInt top_solid_layers; - ConfigOptionFloat travel_speed; - ConfigOptionBool use_firmware_retraction; - ConfigOptionBool use_relative_e_distances; - ConfigOptionFloat vibration_limit; - ConfigOptionBools wipe; - ConfigOptionFloat z_offset; - - PrintConfig() { - this->def = &PrintConfig::PrintConfigDef; - - this->avoid_crossing_perimeters.value = false; - this->bed_size.point = Pointf(200,200); - this->bed_temperature.value = 0; - this->bottom_solid_layers.value = 3; - this->bridge_acceleration.value = 0; - this->bridge_fan_speed.value = 100; - this->bridge_flow_ratio.value = 1; - this->bridge_speed.value = 60; - this->brim_width.value = 0; - this->complete_objects.value = false; - this->cooling.value = true; - this->default_acceleration.value = 0; - this->disable_fan_first_layers.value = 1; - this->duplicate.value = 1; - this->duplicate_distance.value = 6; - this->duplicate_grid.point = Pointf(1,1); - this->end_gcode.value = "M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n"; - this->external_perimeter_speed.value = 70; - this->external_perimeter_speed.percent = true; - this->external_perimeters_first.value = false; - this->extra_perimeters.value = true; - this->extruder_clearance_height.value = 20; - this->extruder_clearance_radius.value = 20; - this->extruder_offset.points.resize(1); - this->extruder_offset.points[0] = Pointf(0,0); - this->extrusion_axis.value = "E"; - this->extrusion_multiplier.values.resize(1); - this->extrusion_multiplier.values[0] = 1; - this->extrusion_width.value = 0; - this->extrusion_width.percent = false; - this->fan_always_on.value = false; - this->fan_below_layer_time.value = 60; - this->filament_diameter.values.resize(1); - this->filament_diameter.values[0] = 3; - this->fill_angle.value = 45; - this->fill_density.value = 0.4; - this->fill_pattern.value = ipHoneycomb; - this->first_layer_acceleration.value = 0; - this->first_layer_bed_temperature.value = 0; - this->first_layer_extrusion_width.value = 200; - this->first_layer_extrusion_width.percent = true; - this->first_layer_height.value = 0.35; - this->first_layer_height.percent = false; - this->first_layer_speed.value = 30; - this->first_layer_speed.percent = true; - this->first_layer_temperature.values.resize(1); - this->first_layer_temperature.values[0] = 200; - this->g0.value = false; - this->gap_fill_speed.value = 20; - this->gcode_arcs.value = false; - this->gcode_comments.value = false; - this->gcode_flavor.value = gcfRepRap; - this->infill_acceleration.value = 0; - this->infill_every_layers.value = 1; - this->infill_extruder.value = 1; - this->infill_extrusion_width.value = 0; - this->infill_extrusion_width.percent = false; - this->infill_first.value = false; - this->infill_only_where_needed.value = false; - this->infill_speed.value = 60; - this->layer_gcode.value = ""; - this->layer_height.value = 0.4; - this->max_fan_speed.value = 100; - this->min_fan_speed.value = 35; - this->min_print_speed.value = 10; - this->min_skirt_length.value = 0; - this->notes.value = ""; - this->nozzle_diameter.values.resize(1); - this->nozzle_diameter.values[0] = 0.5; - this->only_retract_when_crossing_perimeters.value = true; - this->ooze_prevention.value = false; - this->output_filename_format.value = "[input_filename_base].gcode"; - this->overhangs.value = true; - this->perimeter_acceleration.value = 0; - this->perimeter_extruder.value = 1; - this->perimeter_extrusion_width.value = 0; - this->perimeter_extrusion_width.percent = false; - this->perimeter_speed.value = 30; - this->perimeters.value = 3; - this->print_center.point = Pointf(100,100); - this->raft_layers.value = 0; - this->randomize_start.value = false; - this->resolution.value = 0; - this->retract_before_travel.values.resize(1); - this->retract_before_travel.values[0] = 2; - this->retract_layer_change.values.resize(1); - this->retract_layer_change.values[0] = true; - this->retract_length.values.resize(1); - this->retract_length.values[0] = 1; - this->retract_length_toolchange.values.resize(1); - this->retract_length_toolchange.values[0] = 10; - this->retract_lift.values.resize(1); - this->retract_lift.values[0] = 0; - this->retract_restart_extra.values.resize(1); - this->retract_restart_extra.values[0] = 0; - this->retract_restart_extra_toolchange.values.resize(1); - this->retract_restart_extra_toolchange.values[0] = 0; - this->retract_speed.values.resize(1); - this->retract_speed.values[0] = 30; - this->rotate.value = 0; - this->scale.value = 1; - this->skirt_distance.value = 6; - this->skirt_height.value = 1; - this->skirts.value = 1; - this->slowdown_below_layer_time.value = 30; - this->small_perimeter_speed.value = 30; - this->small_perimeter_speed.percent = false; - this->solid_fill_pattern.value = ipRectilinear; - this->solid_infill_below_area.value = 70; - this->solid_infill_every_layers.value = 0; - this->solid_infill_extrusion_width.value = 0; - this->solid_infill_extrusion_width.percent = false; - this->solid_infill_speed.value = 60; - this->solid_infill_speed.percent = false; - this->spiral_vase.value = false; - this->standby_temperature_delta.value = -5; - this->start_gcode.value = "G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n"; - this->start_perimeters_at_concave_points.value = false; - this->start_perimeters_at_non_overhang.value = false; - this->support_material.value = false; - this->support_material_angle.value = 0; - this->support_material_enforce_layers.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 = 1; - this->support_material_interface_layers.value = 3; - this->support_material_interface_spacing.value = 0; - this->support_material_pattern.value = ipHoneycomb; - this->support_material_spacing.value = 2.5; - this->support_material_speed.value = 60; - this->support_material_threshold.value = 0; - this->temperature.values.resize(1); - this->temperature.values[0] = 200; - this->thin_walls.value = true; - this->threads.value = 2; - this->toolchange_gcode.value = ""; - this->top_infill_extrusion_width.value = 0; - this->top_infill_extrusion_width.percent = false; - this->top_solid_infill_speed.value = 50; - this->top_solid_infill_speed.percent = false; - this->top_solid_layers.value = 3; - this->travel_speed.value = 130; - this->use_firmware_retraction.value = false; - this->use_relative_e_distances.value = false; - this->vibration_limit.value = 0; - this->wipe.values.resize(1); - this->wipe.values[0] = false; - this->z_offset.value = 0; - }; - - ConfigOption* option(const t_config_option_key opt_key, bool create = false) { - if (opt_key == "avoid_crossing_perimeters") return &this->avoid_crossing_perimeters; - if (opt_key == "bed_size") return &this->bed_size; - if (opt_key == "bed_temperature") return &this->bed_temperature; - if (opt_key == "bottom_solid_layers") return &this->bottom_solid_layers; - if (opt_key == "bridge_acceleration") return &this->bridge_acceleration; - if (opt_key == "bridge_fan_speed") return &this->bridge_fan_speed; - if (opt_key == "bridge_flow_ratio") return &this->bridge_flow_ratio; - if (opt_key == "bridge_speed") return &this->bridge_speed; - if (opt_key == "brim_width") return &this->brim_width; - if (opt_key == "complete_objects") return &this->complete_objects; - if (opt_key == "cooling") return &this->cooling; - if (opt_key == "default_acceleration") return &this->default_acceleration; - if (opt_key == "disable_fan_first_layers") return &this->disable_fan_first_layers; - if (opt_key == "duplicate") return &this->duplicate; - if (opt_key == "duplicate_distance") return &this->duplicate_distance; - if (opt_key == "duplicate_grid") return &this->duplicate_grid; - if (opt_key == "end_gcode") return &this->end_gcode; - if (opt_key == "external_perimeter_speed") return &this->external_perimeter_speed; - if (opt_key == "external_perimeters_first") return &this->external_perimeters_first; - if (opt_key == "extra_perimeters") return &this->extra_perimeters; - if (opt_key == "extruder_clearance_height") return &this->extruder_clearance_height; - if (opt_key == "extruder_clearance_radius") return &this->extruder_clearance_radius; - if (opt_key == "extruder_offset") return &this->extruder_offset; - if (opt_key == "extrusion_axis") return &this->extrusion_axis; - if (opt_key == "extrusion_multiplier") return &this->extrusion_multiplier; - if (opt_key == "extrusion_width") return &this->extrusion_width; - if (opt_key == "fan_always_on") return &this->fan_always_on; - if (opt_key == "fan_below_layer_time") return &this->fan_below_layer_time; - if (opt_key == "filament_diameter") return &this->filament_diameter; - if (opt_key == "fill_angle") return &this->fill_angle; - if (opt_key == "fill_density") return &this->fill_density; - if (opt_key == "fill_pattern") return &this->fill_pattern; - if (opt_key == "first_layer_acceleration") return &this->first_layer_acceleration; - if (opt_key == "first_layer_bed_temperature") return &this->first_layer_bed_temperature; - if (opt_key == "first_layer_extrusion_width") return &this->first_layer_extrusion_width; - if (opt_key == "first_layer_height") return &this->first_layer_height; - if (opt_key == "first_layer_speed") return &this->first_layer_speed; - if (opt_key == "first_layer_temperature") return &this->first_layer_temperature; - if (opt_key == "g0") return &this->g0; - if (opt_key == "gap_fill_speed") return &this->gap_fill_speed; - if (opt_key == "gcode_arcs") return &this->gcode_arcs; - if (opt_key == "gcode_comments") return &this->gcode_comments; - if (opt_key == "gcode_flavor") return &this->gcode_flavor; - if (opt_key == "infill_acceleration") return &this->infill_acceleration; - if (opt_key == "infill_every_layers") return &this->infill_every_layers; - if (opt_key == "infill_extruder") return &this->infill_extruder; - if (opt_key == "infill_extrusion_width") return &this->infill_extrusion_width; - if (opt_key == "infill_first") return &this->infill_first; - if (opt_key == "infill_only_where_needed") return &this->infill_only_where_needed; - if (opt_key == "infill_speed") return &this->infill_speed; - if (opt_key == "layer_gcode") return &this->layer_gcode; - if (opt_key == "layer_height") return &this->layer_height; - if (opt_key == "max_fan_speed") return &this->max_fan_speed; - if (opt_key == "min_fan_speed") return &this->min_fan_speed; - if (opt_key == "min_print_speed") return &this->min_print_speed; - if (opt_key == "min_skirt_length") return &this->min_skirt_length; - if (opt_key == "notes") return &this->notes; - if (opt_key == "nozzle_diameter") return &this->nozzle_diameter; - if (opt_key == "only_retract_when_crossing_perimeters") return &this->only_retract_when_crossing_perimeters; - if (opt_key == "ooze_prevention") return &this->ooze_prevention; - if (opt_key == "output_filename_format") return &this->output_filename_format; - if (opt_key == "overhangs") return &this->overhangs; - if (opt_key == "perimeter_acceleration") return &this->perimeter_acceleration; - if (opt_key == "perimeter_extruder") return &this->perimeter_extruder; - if (opt_key == "perimeter_extrusion_width") return &this->perimeter_extrusion_width; - if (opt_key == "perimeter_speed") return &this->perimeter_speed; - if (opt_key == "perimeters") return &this->perimeters; - if (opt_key == "post_process") return &this->post_process; - if (opt_key == "print_center") return &this->print_center; - if (opt_key == "raft_layers") return &this->raft_layers; - if (opt_key == "randomize_start") return &this->randomize_start; - if (opt_key == "resolution") return &this->resolution; - if (opt_key == "retract_before_travel") return &this->retract_before_travel; - if (opt_key == "retract_layer_change") return &this->retract_layer_change; - if (opt_key == "retract_length") return &this->retract_length; - if (opt_key == "retract_length_toolchange") return &this->retract_length_toolchange; - if (opt_key == "retract_lift") return &this->retract_lift; - if (opt_key == "retract_restart_extra") return &this->retract_restart_extra; - if (opt_key == "retract_restart_extra_toolchange") return &this->retract_restart_extra_toolchange; - if (opt_key == "retract_speed") return &this->retract_speed; - if (opt_key == "rotate") return &this->rotate; - if (opt_key == "scale") return &this->scale; - if (opt_key == "skirt_distance") return &this->skirt_distance; - if (opt_key == "skirt_height") return &this->skirt_height; - if (opt_key == "skirts") return &this->skirts; - if (opt_key == "slowdown_below_layer_time") return &this->slowdown_below_layer_time; - if (opt_key == "small_perimeter_speed") return &this->small_perimeter_speed; - if (opt_key == "solid_fill_pattern") return &this->solid_fill_pattern; - if (opt_key == "solid_infill_below_area") return &this->solid_infill_below_area; - if (opt_key == "solid_infill_every_layers") return &this->solid_infill_every_layers; - if (opt_key == "solid_infill_extrusion_width") return &this->solid_infill_extrusion_width; - if (opt_key == "solid_infill_speed") return &this->solid_infill_speed; - if (opt_key == "solid_layers") return &this->solid_layers; - if (opt_key == "spiral_vase") return &this->spiral_vase; - if (opt_key == "standby_temperature_delta") return &this->standby_temperature_delta; - if (opt_key == "start_gcode") return &this->start_gcode; - if (opt_key == "start_perimeters_at_concave_points") return &this->start_perimeters_at_concave_points; - if (opt_key == "start_perimeters_at_non_overhang") return &this->start_perimeters_at_non_overhang; - if (opt_key == "support_material") return &this->support_material; - if (opt_key == "support_material_angle") return &this->support_material_angle; - if (opt_key == "support_material_enforce_layers") return &this->support_material_enforce_layers; - if (opt_key == "support_material_extruder") return &this->support_material_extruder; - if (opt_key == "support_material_extrusion_width") return &this->support_material_extrusion_width; - if (opt_key == "support_material_interface_extruder") return &this->support_material_interface_extruder; - if (opt_key == "support_material_interface_layers") return &this->support_material_interface_layers; - if (opt_key == "support_material_interface_spacing") return &this->support_material_interface_spacing; - if (opt_key == "support_material_pattern") return &this->support_material_pattern; - if (opt_key == "support_material_spacing") return &this->support_material_spacing; - if (opt_key == "support_material_speed") return &this->support_material_speed; - if (opt_key == "support_material_threshold") return &this->support_material_threshold; - if (opt_key == "temperature") return &this->temperature; - if (opt_key == "thin_walls") return &this->thin_walls; - if (opt_key == "threads") return &this->threads; - if (opt_key == "toolchange_gcode") return &this->toolchange_gcode; - if (opt_key == "top_infill_extrusion_width") return &this->top_infill_extrusion_width; - if (opt_key == "top_solid_infill_speed") return &this->top_solid_infill_speed; - if (opt_key == "top_solid_layers") return &this->top_solid_layers; - if (opt_key == "travel_speed") return &this->travel_speed; - if (opt_key == "use_firmware_retraction") return &this->use_firmware_retraction; - if (opt_key == "use_relative_e_distances") return &this->use_relative_e_distances; - if (opt_key == "vibration_limit") return &this->vibration_limit; - if (opt_key == "wipe") return &this->wipe; - if (opt_key == "z_offset") return &this->z_offset; - - if (create) throw "Attempt to create non-existing option in StaticConfig object"; - return NULL; - }; + static t_optiondef_map def; static t_optiondef_map build_def () { t_optiondef_map Options; @@ -468,7 +61,7 @@ class PrintConfig : public StaticConfig 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; @@ -477,7 +70,7 @@ class PrintConfig : public StaticConfig 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"; @@ -533,11 +126,6 @@ class PrintConfig : public StaticConfig Options["disable_fan_first_layers"].cli = "disable-fan-first-layers=i"; Options["disable_fan_first_layers"].max = 1000; - Options["duplicate"].type = coInt; - Options["duplicate"].label = "Copies (autoarrange)"; - Options["duplicate"].cli = "duplicate=i"; - Options["duplicate"].min = 1; - Options["duplicate_distance"].type = coFloat; Options["duplicate_distance"].label = "Distance between copies"; Options["duplicate_distance"].tooltip = "Distance used for the auto-arrange feature of the plater."; @@ -545,10 +133,6 @@ class PrintConfig : public StaticConfig Options["duplicate_distance"].cli = "duplicate-distance=f"; Options["duplicate_distance"].aliases.push_back("multiply_distance"); - Options["duplicate_grid"].type = coPoint; - Options["duplicate_grid"].label = "Copies (grid)"; - Options["duplicate_grid"].cli = "duplicate-grid=s"; - Options["end_gcode"].type = coString; Options["end_gcode"].label = "End G-code"; Options["end_gcode"].tooltip = "This end procedure is inserted at the end of the output file. Note that you can use placeholder variables for all Slic3r settings."; @@ -576,6 +160,14 @@ class PrintConfig : public StaticConfig Options["extra_perimeters"].cli = "extra-perimeters!"; Options["extra_perimeters"].scope = "object"; + Options["extruder"].type = coInt; + Options["extruder"].label = "Extruder"; + Options["extruder"].cli = "extruder=i"; + Options["extruder"].shortcut.push_back("perimeter_extruder"); + Options["extruder"].shortcut.push_back("infill_extruder"); + Options["extruder"].shortcut.push_back("support_material_extruder"); + Options["extruder"].shortcut.push_back("support_material_interface_extruder"); + Options["extruder_clearance_height"].type = coFloat; Options["extruder_clearance_height"].label = "Height"; Options["extruder_clearance_height"].tooltip = "Set this to the vertical distance between your nozzle tip and (usually) the X carriage rods. In other words, this is the height of the clearance cylinder around your extruder, and it represents the maximum depth the extruder can peek before colliding with other printed objects."; @@ -649,6 +241,7 @@ class PrintConfig : public StaticConfig Options["fill_pattern"].tooltip = "Fill pattern for general low-density infill."; Options["fill_pattern"].cli = "fill-pattern=s"; Options["fill_pattern"].scope = "object"; + Options["fill_pattern"].enum_keys_map = ConfigOptionEnum::get_enum_values(); Options["fill_pattern"].enum_values.push_back("rectilinear"); Options["fill_pattern"].enum_values.push_back("line"); Options["fill_pattern"].enum_values.push_back("concentric"); @@ -728,6 +321,7 @@ class PrintConfig : public StaticConfig Options["gcode_flavor"].label = "G-code flavor"; Options["gcode_flavor"].tooltip = "Some G/M-code commands, including temperature control and others, are not universal. Set this option to your printer's firmware to get a compatible output. The \"No extrusion\" flavor prevents Slic3r from exporting any extrusion value at all."; Options["gcode_flavor"].cli = "gcode-flavor=s"; + Options["gcode_flavor"].enum_keys_map = ConfigOptionEnum::get_enum_values(); Options["gcode_flavor"].enum_values.push_back("reprap"); Options["gcode_flavor"].enum_values.push_back("teacup"); Options["gcode_flavor"].enum_values.push_back("makerware"); @@ -754,7 +348,7 @@ class PrintConfig : public StaticConfig 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; @@ -983,16 +577,6 @@ class PrintConfig : public StaticConfig Options["retract_speed"].cli = "retract-speed=f@"; Options["retract_speed"].max = 1000; - Options["rotate"].type = coInt; - Options["rotate"].label = "Rotate"; - Options["rotate"].sidetext = "°"; - Options["rotate"].cli = "rotate=i"; - Options["rotate"].max = 359; - - Options["scale"].type = coFloat; - Options["scale"].label = "Scale"; - Options["scale"].cli = "scale=f"; - Options["skirt_distance"].type = coFloat; Options["skirt_distance"].label = "Distance from object"; Options["skirt_distance"].tooltip = "Distance between skirt and object(s). Set this to zero to attach the skirt to the object(s) and get a brim for better adhesion."; @@ -1031,6 +615,7 @@ class PrintConfig : public StaticConfig Options["solid_fill_pattern"].tooltip = "Fill pattern for top/bottom infill."; Options["solid_fill_pattern"].cli = "solid-fill-pattern=s"; Options["solid_fill_pattern"].scope = "object"; + Options["solid_fill_pattern"].enum_keys_map = ConfigOptionEnum::get_enum_values(); Options["solid_fill_pattern"].enum_values.push_back("rectilinear"); Options["solid_fill_pattern"].enum_values.push_back("concentric"); Options["solid_fill_pattern"].enum_values.push_back("hilbertcurve"); @@ -1133,7 +718,7 @@ class PrintConfig : public StaticConfig 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"; @@ -1207,7 +792,7 @@ class PrintConfig : public StaticConfig 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; @@ -1253,7 +838,7 @@ class PrintConfig : public StaticConfig 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"; @@ -1293,13 +878,459 @@ class PrintConfig : public StaticConfig }; }; +class PrintObjectConfig : public virtual StaticConfig +{ + public: + ConfigOptionFloatOrPercent extrusion_width; + ConfigOptionFloatOrPercent first_layer_height; + ConfigOptionBool infill_only_where_needed; + ConfigOptionFloat layer_height; + ConfigOptionInt raft_layers; + ConfigOptionBool support_material; + ConfigOptionInt support_material_angle; + ConfigOptionInt support_material_enforce_layers; + ConfigOptionInt support_material_extruder; + ConfigOptionFloatOrPercent support_material_extrusion_width; + ConfigOptionInt support_material_interface_extruder; + ConfigOptionInt support_material_interface_layers; + ConfigOptionFloat support_material_interface_spacing; + ConfigOptionEnum support_material_pattern; + ConfigOptionFloat support_material_spacing; + ConfigOptionFloat support_material_speed; + ConfigOptionInt support_material_threshold; + + PrintObjectConfig() { + this->def = &PrintConfigDef::def; + + this->extrusion_width.value = 0; + this->extrusion_width.percent = false; + this->first_layer_height.value = 0.35; + this->first_layer_height.percent = false; + this->infill_only_where_needed.value = false; + this->layer_height.value = 0.4; + this->raft_layers.value = 0; + this->support_material.value = false; + this->support_material_angle.value = 0; + this->support_material_enforce_layers.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 = 1; + this->support_material_interface_layers.value = 3; + this->support_material_interface_spacing.value = 0; + this->support_material_pattern.value = ipHoneycomb; + this->support_material_spacing.value = 2.5; + this->support_material_speed.value = 60; + this->support_material_threshold.value = 0; + }; + + ConfigOption* option(const t_config_option_key opt_key, bool create = false) { + if (opt_key == "extrusion_width") return &this->extrusion_width; + if (opt_key == "first_layer_height") return &this->first_layer_height; + if (opt_key == "infill_only_where_needed") return &this->infill_only_where_needed; + if (opt_key == "layer_height") return &this->layer_height; + if (opt_key == "raft_layers") return &this->raft_layers; + if (opt_key == "support_material") return &this->support_material; + if (opt_key == "support_material_angle") return &this->support_material_angle; + if (opt_key == "support_material_enforce_layers") return &this->support_material_enforce_layers; + if (opt_key == "support_material_extruder") return &this->support_material_extruder; + if (opt_key == "support_material_extrusion_width") return &this->support_material_extrusion_width; + if (opt_key == "support_material_interface_extruder") return &this->support_material_interface_extruder; + if (opt_key == "support_material_interface_layers") return &this->support_material_interface_layers; + if (opt_key == "support_material_interface_spacing") return &this->support_material_interface_spacing; + if (opt_key == "support_material_pattern") return &this->support_material_pattern; + if (opt_key == "support_material_spacing") return &this->support_material_spacing; + if (opt_key == "support_material_speed") return &this->support_material_speed; + if (opt_key == "support_material_threshold") return &this->support_material_threshold; + + return NULL; + }; +}; + +class PrintRegionConfig : public virtual StaticConfig +{ + public: + ConfigOptionInt bottom_solid_layers; + ConfigOptionBool extra_perimeters; + ConfigOptionInt fill_angle; + ConfigOptionFloat fill_density; + ConfigOptionEnum fill_pattern; + ConfigOptionInt infill_extruder; + ConfigOptionFloatOrPercent infill_extrusion_width; + ConfigOptionInt infill_every_layers; + ConfigOptionInt perimeter_extruder; + ConfigOptionFloatOrPercent perimeter_extrusion_width; + ConfigOptionInt perimeters; + ConfigOptionEnum solid_fill_pattern; + ConfigOptionFloat solid_infill_below_area; + ConfigOptionFloatOrPercent solid_infill_extrusion_width; + ConfigOptionInt solid_infill_every_layers; + ConfigOptionInt solid_layers; + ConfigOptionBool thin_walls; + ConfigOptionFloatOrPercent top_infill_extrusion_width; + ConfigOptionInt top_solid_layers; + + PrintRegionConfig() { + this->def = &PrintConfigDef::def; + + this->bottom_solid_layers.value = 3; + this->extra_perimeters.value = true; + this->fill_angle.value = 45; + this->fill_density.value = 0.4; + this->fill_pattern.value = ipHoneycomb; + 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 = 1; + this->perimeter_extrusion_width.value = 0; + this->perimeter_extrusion_width.percent = false; + this->perimeters.value = 3; + this->solid_fill_pattern.value = ipRectilinear; + this->solid_infill_below_area.value = 70; + this->solid_infill_extrusion_width.value = 0; + this->solid_infill_extrusion_width.percent = false; + this->solid_infill_every_layers.value = 0; + this->thin_walls.value = true; + this->top_infill_extrusion_width.value = 0; + this->top_infill_extrusion_width.percent = false; + this->top_solid_layers.value = 3; + }; + + ConfigOption* option(const t_config_option_key opt_key, bool create = false) { + if (opt_key == "bottom_solid_layers") return &this->bottom_solid_layers; + if (opt_key == "extra_perimeters") return &this->extra_perimeters; + if (opt_key == "fill_angle") return &this->fill_angle; + if (opt_key == "fill_density") return &this->fill_density; + if (opt_key == "fill_pattern") return &this->fill_pattern; + if (opt_key == "infill_extruder") return &this->infill_extruder; + if (opt_key == "infill_extrusion_width") return &this->infill_extrusion_width; + if (opt_key == "infill_every_layers") return &this->infill_every_layers; + if (opt_key == "perimeter_extruder") return &this->perimeter_extruder; + if (opt_key == "perimeter_extrusion_width") return &this->perimeter_extrusion_width; + if (opt_key == "perimeters") return &this->perimeters; + if (opt_key == "solid_fill_pattern") return &this->solid_fill_pattern; + if (opt_key == "solid_infill_below_area") return &this->solid_infill_below_area; + if (opt_key == "solid_infill_extrusion_width") return &this->solid_infill_extrusion_width; + if (opt_key == "solid_infill_every_layers") return &this->solid_infill_every_layers; + if (opt_key == "solid_layers") return &this->solid_layers; + if (opt_key == "thin_walls") return &this->thin_walls; + if (opt_key == "top_infill_extrusion_width") return &this->top_infill_extrusion_width; + if (opt_key == "top_solid_layers") return &this->top_solid_layers; + + return NULL; + }; +}; + +class PrintConfig : public virtual StaticConfig +{ + public: + ConfigOptionBool avoid_crossing_perimeters; + ConfigOptionPoint bed_size; + ConfigOptionInt bed_temperature; + ConfigOptionFloat bridge_acceleration; + ConfigOptionInt bridge_fan_speed; + ConfigOptionFloat bridge_flow_ratio; + ConfigOptionFloat bridge_speed; + ConfigOptionFloat brim_width; + ConfigOptionBool complete_objects; + ConfigOptionBool cooling; + ConfigOptionFloat default_acceleration; + ConfigOptionInt disable_fan_first_layers; + ConfigOptionFloat duplicate_distance; + ConfigOptionString end_gcode; + ConfigOptionFloatOrPercent external_perimeter_speed; + ConfigOptionBool external_perimeters_first; + ConfigOptionFloat extruder_clearance_height; + ConfigOptionFloat extruder_clearance_radius; + ConfigOptionPoints extruder_offset; + ConfigOptionString extrusion_axis; + ConfigOptionFloats extrusion_multiplier; + ConfigOptionBool fan_always_on; + ConfigOptionInt fan_below_layer_time; + ConfigOptionFloats filament_diameter; + ConfigOptionFloat first_layer_acceleration; + ConfigOptionInt first_layer_bed_temperature; + ConfigOptionFloatOrPercent first_layer_extrusion_width; + ConfigOptionFloatOrPercent first_layer_speed; + ConfigOptionInts first_layer_temperature; + ConfigOptionBool g0; + ConfigOptionFloat gap_fill_speed; + ConfigOptionBool gcode_arcs; + ConfigOptionBool gcode_comments; + ConfigOptionEnum gcode_flavor; + ConfigOptionFloat infill_acceleration; + ConfigOptionBool infill_first; + ConfigOptionFloat infill_speed; + ConfigOptionString layer_gcode; + ConfigOptionInt max_fan_speed; + ConfigOptionInt min_fan_speed; + ConfigOptionInt min_print_speed; + ConfigOptionFloat min_skirt_length; + ConfigOptionString notes; + ConfigOptionFloats nozzle_diameter; + ConfigOptionBool only_retract_when_crossing_perimeters; + ConfigOptionBool ooze_prevention; + ConfigOptionString output_filename_format; + ConfigOptionBool overhangs; + ConfigOptionFloat perimeter_acceleration; + ConfigOptionFloat perimeter_speed; + ConfigOptionStrings post_process; + ConfigOptionPoint print_center; + ConfigOptionBool randomize_start; + ConfigOptionFloat resolution; + ConfigOptionFloats retract_before_travel; + ConfigOptionBools retract_layer_change; + ConfigOptionFloats retract_length; + ConfigOptionFloats retract_length_toolchange; + ConfigOptionFloats retract_lift; + ConfigOptionFloats retract_restart_extra; + ConfigOptionFloats retract_restart_extra_toolchange; + ConfigOptionInts retract_speed; + ConfigOptionFloat skirt_distance; + ConfigOptionInt skirt_height; + ConfigOptionInt skirts; + ConfigOptionInt slowdown_below_layer_time; + ConfigOptionFloatOrPercent small_perimeter_speed; + ConfigOptionFloatOrPercent solid_infill_speed; + ConfigOptionBool spiral_vase; + ConfigOptionInt standby_temperature_delta; + ConfigOptionString start_gcode; + ConfigOptionBool start_perimeters_at_concave_points; + ConfigOptionBool start_perimeters_at_non_overhang; + ConfigOptionInts temperature; + ConfigOptionInt threads; + ConfigOptionString toolchange_gcode; + ConfigOptionFloatOrPercent top_solid_infill_speed; + ConfigOptionFloat travel_speed; + ConfigOptionBool use_firmware_retraction; + ConfigOptionBool use_relative_e_distances; + ConfigOptionFloat vibration_limit; + ConfigOptionBools wipe; + ConfigOptionFloat z_offset; + + PrintConfig() { + this->def = &PrintConfigDef::def; + + this->avoid_crossing_perimeters.value = false; + this->bed_size.point = Pointf(200,200); + this->bed_temperature.value = 0; + this->bridge_acceleration.value = 0; + this->bridge_fan_speed.value = 100; + this->bridge_flow_ratio.value = 1; + this->bridge_speed.value = 60; + this->brim_width.value = 0; + this->complete_objects.value = false; + this->cooling.value = true; + this->default_acceleration.value = 0; + this->disable_fan_first_layers.value = 1; + this->duplicate_distance.value = 6; + this->end_gcode.value = "M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n"; + this->external_perimeter_speed.value = 70; + this->external_perimeter_speed.percent = true; + this->external_perimeters_first.value = false; + this->extruder_clearance_height.value = 20; + this->extruder_clearance_radius.value = 20; + this->extruder_offset.values.resize(1); + this->extruder_offset.values[0] = Pointf(0,0); + this->extrusion_axis.value = "E"; + this->extrusion_multiplier.values.resize(1); + this->extrusion_multiplier.values[0] = 1; + this->fan_always_on.value = false; + this->fan_below_layer_time.value = 60; + this->filament_diameter.values.resize(1); + this->filament_diameter.values[0] = 3; + this->first_layer_acceleration.value = 0; + this->first_layer_bed_temperature.value = 0; + this->first_layer_extrusion_width.value = 200; + this->first_layer_extrusion_width.percent = true; + this->first_layer_speed.value = 30; + this->first_layer_speed.percent = true; + this->first_layer_temperature.values.resize(1); + this->first_layer_temperature.values[0] = 200; + this->g0.value = false; + this->gap_fill_speed.value = 20; + this->gcode_arcs.value = false; + this->gcode_comments.value = false; + this->gcode_flavor.value = gcfRepRap; + this->infill_acceleration.value = 0; + this->infill_first.value = false; + this->infill_speed.value = 60; + this->layer_gcode.value = ""; + this->max_fan_speed.value = 100; + this->min_fan_speed.value = 35; + this->min_print_speed.value = 10; + this->min_skirt_length.value = 0; + this->notes.value = ""; + this->nozzle_diameter.values.resize(1); + this->nozzle_diameter.values[0] = 0.5; + this->only_retract_when_crossing_perimeters.value = true; + this->ooze_prevention.value = false; + this->output_filename_format.value = "[input_filename_base].gcode"; + this->overhangs.value = true; + this->perimeter_acceleration.value = 0; + this->perimeter_speed.value = 30; + this->print_center.point = Pointf(100,100); + this->randomize_start.value = false; + this->resolution.value = 0; + this->retract_before_travel.values.resize(1); + this->retract_before_travel.values[0] = 2; + this->retract_layer_change.values.resize(1); + this->retract_layer_change.values[0] = true; + this->retract_length.values.resize(1); + this->retract_length.values[0] = 1; + this->retract_length_toolchange.values.resize(1); + this->retract_length_toolchange.values[0] = 10; + this->retract_lift.values.resize(1); + this->retract_lift.values[0] = 0; + this->retract_restart_extra.values.resize(1); + this->retract_restart_extra.values[0] = 0; + this->retract_restart_extra_toolchange.values.resize(1); + this->retract_restart_extra_toolchange.values[0] = 0; + this->retract_speed.values.resize(1); + this->retract_speed.values[0] = 30; + this->skirt_distance.value = 6; + this->skirt_height.value = 1; + this->skirts.value = 1; + this->slowdown_below_layer_time.value = 30; + this->small_perimeter_speed.value = 30; + this->small_perimeter_speed.percent = false; + this->solid_infill_speed.value = 60; + this->solid_infill_speed.percent = false; + this->spiral_vase.value = false; + this->standby_temperature_delta.value = -5; + this->start_gcode.value = "G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n"; + this->start_perimeters_at_concave_points.value = false; + this->start_perimeters_at_non_overhang.value = false; + this->temperature.values.resize(1); + this->temperature.values[0] = 200; + this->threads.value = 2; + this->toolchange_gcode.value = ""; + this->top_solid_infill_speed.value = 50; + this->top_solid_infill_speed.percent = false; + this->travel_speed.value = 130; + this->use_firmware_retraction.value = false; + this->use_relative_e_distances.value = false; + this->vibration_limit.value = 0; + this->wipe.values.resize(1); + this->wipe.values[0] = false; + this->z_offset.value = 0; + }; + + ConfigOption* option(const t_config_option_key opt_key, bool create = false) { + if (opt_key == "avoid_crossing_perimeters") return &this->avoid_crossing_perimeters; + if (opt_key == "bed_size") return &this->bed_size; + if (opt_key == "bed_temperature") return &this->bed_temperature; + if (opt_key == "bridge_acceleration") return &this->bridge_acceleration; + if (opt_key == "bridge_fan_speed") return &this->bridge_fan_speed; + if (opt_key == "bridge_flow_ratio") return &this->bridge_flow_ratio; + if (opt_key == "bridge_speed") return &this->bridge_speed; + if (opt_key == "brim_width") return &this->brim_width; + if (opt_key == "complete_objects") return &this->complete_objects; + if (opt_key == "cooling") return &this->cooling; + if (opt_key == "default_acceleration") return &this->default_acceleration; + if (opt_key == "disable_fan_first_layers") return &this->disable_fan_first_layers; + if (opt_key == "duplicate_distance") return &this->duplicate_distance; + if (opt_key == "end_gcode") return &this->end_gcode; + if (opt_key == "external_perimeter_speed") return &this->external_perimeter_speed; + if (opt_key == "external_perimeters_first") return &this->external_perimeters_first; + if (opt_key == "extruder_clearance_height") return &this->extruder_clearance_height; + if (opt_key == "extruder_clearance_radius") return &this->extruder_clearance_radius; + if (opt_key == "extruder_offset") return &this->extruder_offset; + if (opt_key == "extrusion_axis") return &this->extrusion_axis; + if (opt_key == "extrusion_multiplier") return &this->extrusion_multiplier; + if (opt_key == "fan_always_on") return &this->fan_always_on; + if (opt_key == "fan_below_layer_time") return &this->fan_below_layer_time; + if (opt_key == "filament_diameter") return &this->filament_diameter; + if (opt_key == "first_layer_acceleration") return &this->first_layer_acceleration; + if (opt_key == "first_layer_bed_temperature") return &this->first_layer_bed_temperature; + if (opt_key == "first_layer_extrusion_width") return &this->first_layer_extrusion_width; + if (opt_key == "first_layer_speed") return &this->first_layer_speed; + if (opt_key == "first_layer_temperature") return &this->first_layer_temperature; + if (opt_key == "g0") return &this->g0; + if (opt_key == "gap_fill_speed") return &this->gap_fill_speed; + if (opt_key == "gcode_arcs") return &this->gcode_arcs; + if (opt_key == "gcode_comments") return &this->gcode_comments; + if (opt_key == "gcode_flavor") return &this->gcode_flavor; + if (opt_key == "infill_acceleration") return &this->infill_acceleration; + if (opt_key == "infill_first") return &this->infill_first; + if (opt_key == "infill_speed") return &this->infill_speed; + if (opt_key == "layer_gcode") return &this->layer_gcode; + if (opt_key == "max_fan_speed") return &this->max_fan_speed; + if (opt_key == "min_fan_speed") return &this->min_fan_speed; + if (opt_key == "min_print_speed") return &this->min_print_speed; + if (opt_key == "min_skirt_length") return &this->min_skirt_length; + if (opt_key == "notes") return &this->notes; + if (opt_key == "nozzle_diameter") return &this->nozzle_diameter; + if (opt_key == "only_retract_when_crossing_perimeters") return &this->only_retract_when_crossing_perimeters; + if (opt_key == "ooze_prevention") return &this->ooze_prevention; + if (opt_key == "output_filename_format") return &this->output_filename_format; + if (opt_key == "overhangs") return &this->overhangs; + if (opt_key == "perimeter_acceleration") return &this->perimeter_acceleration; + if (opt_key == "perimeter_speed") return &this->perimeter_speed; + if (opt_key == "post_process") return &this->post_process; + if (opt_key == "print_center") return &this->print_center; + if (opt_key == "randomize_start") return &this->randomize_start; + if (opt_key == "resolution") return &this->resolution; + if (opt_key == "retract_before_travel") return &this->retract_before_travel; + if (opt_key == "retract_layer_change") return &this->retract_layer_change; + if (opt_key == "retract_length") return &this->retract_length; + if (opt_key == "retract_length_toolchange") return &this->retract_length_toolchange; + if (opt_key == "retract_lift") return &this->retract_lift; + if (opt_key == "retract_restart_extra") return &this->retract_restart_extra; + if (opt_key == "retract_restart_extra_toolchange") return &this->retract_restart_extra_toolchange; + if (opt_key == "retract_speed") return &this->retract_speed; + if (opt_key == "skirt_distance") return &this->skirt_distance; + if (opt_key == "skirt_height") return &this->skirt_height; + if (opt_key == "skirts") return &this->skirts; + if (opt_key == "slowdown_below_layer_time") return &this->slowdown_below_layer_time; + if (opt_key == "small_perimeter_speed") return &this->small_perimeter_speed; + if (opt_key == "solid_infill_speed") return &this->solid_infill_speed; + if (opt_key == "spiral_vase") return &this->spiral_vase; + if (opt_key == "standby_temperature_delta") return &this->standby_temperature_delta; + if (opt_key == "start_gcode") return &this->start_gcode; + if (opt_key == "start_perimeters_at_concave_points") return &this->start_perimeters_at_concave_points; + if (opt_key == "start_perimeters_at_non_overhang") return &this->start_perimeters_at_non_overhang; + if (opt_key == "temperature") return &this->temperature; + if (opt_key == "threads") return &this->threads; + if (opt_key == "toolchange_gcode") return &this->toolchange_gcode; + if (opt_key == "top_solid_infill_speed") return &this->top_solid_infill_speed; + if (opt_key == "travel_speed") return &this->travel_speed; + if (opt_key == "use_firmware_retraction") return &this->use_firmware_retraction; + if (opt_key == "use_relative_e_distances") return &this->use_relative_e_distances; + if (opt_key == "vibration_limit") return &this->vibration_limit; + if (opt_key == "wipe") return &this->wipe; + if (opt_key == "z_offset") return &this->z_offset; + + return NULL; + }; + + std::string get_extrusion_axis() { + if (this->gcode_flavor == gcfMach3) { + return std::string("A"); + } else if (this->gcode_flavor == gcfNoExtrusion) { + return std::string(""); + } + return this->extrusion_axis; + } +}; + class DynamicPrintConfig : public DynamicConfig { public: DynamicPrintConfig() { - this->def = &PrintConfig::PrintConfigDef; + this->def = &PrintConfigDef::def; }; +}; +class FullPrintConfig : public PrintObjectConfig, public PrintRegionConfig, public PrintConfig { + ConfigOption* option(const t_config_option_key opt_key, bool create = false) { + ConfigOption* opt; + if ((opt = PrintObjectConfig::option(opt_key, create)) != NULL) return opt; + if ((opt = PrintRegionConfig::option(opt_key, create)) != NULL) return opt; + if ((opt = PrintConfig::option(opt_key, create)) != NULL) return opt; + return NULL; + }; }; } diff --git a/xs/src/myinit.h b/xs/src/myinit.h index 1ef522b44..fcbaca5c0 100644 --- a/xs/src/myinit.h +++ b/xs/src/myinit.h @@ -20,8 +20,10 @@ extern "C" { #define EPSILON 1e-4 #define SCALING_FACTOR 0.000001 +#define PI 3.141592653589793238 #define scale_(val) (val / SCALING_FACTOR) #define unscale(val) (val * SCALING_FACTOR) +typedef long coord_t; namespace Slic3r {} using namespace Slic3r; diff --git a/xs/t/07_extrusionpath.t b/xs/t/07_extrusionpath.t index 78e084ccd..1c2ed3f2d 100644 --- a/xs/t/07_extrusionpath.t +++ b/xs/t/07_extrusionpath.t @@ -15,6 +15,7 @@ my $points = [ my $path = Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@$points), role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, + mm3_per_mm => 1, ); isa_ok $path->polyline, 'Slic3r::Polyline::Ref', 'path polyline'; is_deeply $path->polyline->pp, $points, 'path points roundtrip'; diff --git a/xs/t/08_extrusionloop.t b/xs/t/08_extrusionloop.t index bd729361f..54ef72ca7 100644 --- a/xs/t/08_extrusionloop.t +++ b/xs/t/08_extrusionloop.t @@ -16,6 +16,7 @@ my $square = [ my $loop = Slic3r::ExtrusionLoop->new( polygon => Slic3r::Polygon->new(@$square), role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, + mm3_per_mm => 1, ); isa_ok $loop->polygon, 'Slic3r::Polygon::Ref', 'loop polygon'; is_deeply $loop->polygon->pp, $square, 'polygon points roundtrip'; diff --git a/xs/t/12_extrusionpathcollection.t b/xs/t/12_extrusionpathcollection.t index e28c04b6d..4d5b9c589 100644 --- a/xs/t/12_extrusionpathcollection.t +++ b/xs/t/12_extrusionpathcollection.t @@ -15,11 +15,13 @@ my $points = [ my $path = Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@$points), role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, + mm3_per_mm => 1, ); my $loop = Slic3r::ExtrusionLoop->new( polygon => Slic3r::Polygon->new(@$points), role => Slic3r::ExtrusionPath::EXTR_ROLE_FILL, + mm3_per_mm => 1, ); my $collection = Slic3r::ExtrusionPath::Collection->new($path); @@ -49,7 +51,7 @@ is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated'; { my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0), + map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([0,15], [0,18], [0,20]), Slic3r::Polyline->new([0,10], [0,8], [0,5]), ); @@ -65,7 +67,7 @@ is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated'; { my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0), + map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([15,0], [10,0], [4,0]), Slic3r::Polyline->new([10,5], [15,5], [20,5]), ); diff --git a/xs/t/15_config.t b/xs/t/15_config.t index 9a9943d3a..ed60c3e6d 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -4,9 +4,9 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 78; +use Test::More tests => 89; -foreach my $config (Slic3r::Config->new, Slic3r::Config::Print->new) { +foreach my $config (Slic3r::Config->new, Slic3r::Config::Full->new) { $config->set('layer_height', 0.3); ok abs($config->get('layer_height') - 0.3) < 1e-4, 'set/get float'; is $config->serialize('layer_height'), '0.3', 'serialize float'; @@ -49,6 +49,9 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Print->new) { $config->set_deserialize('gcode_flavor', 'mach3'); is $config->get('gcode_flavor'), 'mach3', 'deserialize enum'; + $config->set_deserialize('fill_pattern', 'line'); + is $config->get('fill_pattern'), 'line', 'deserialize enum'; + $config->set('extruder_offset', [[10,20],[30,45]]); is_deeply $config->get('extruder_offset'), [[10,20],[30,45]], 'set/get points'; is $config->serialize('extruder_offset'), '10x20,30x45', 'serialize points'; @@ -72,6 +75,9 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Print->new) { $config->set('wipe', [1,0]); is_deeply $config->get('wipe'), [1,0], 'set/get bools'; + is $config->get_at('wipe', 0), 1, 'get_at bools'; + is $config->get_at('wipe', 1), 0, 'get_at bools'; + is $config->get_at('wipe', 9), 1, 'get_at bools'; is $config->serialize('wipe'), '1,0', 'serialize bools'; $config->set_deserialize('wipe', '0,1,1'); is_deeply $config->get('wipe'), [0,1,1], 'deserialize bools'; @@ -88,20 +94,35 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Print->new) { { my $config = Slic3r::Config->new; $config->set('perimeters', 2); + $config->set('solid_layers', 2); + is $config->get('top_solid_layers'), 2, 'shortcut'; # test that no crash happens when using set_deserialize() with a key that hasn't been set() yet $config->set_deserialize('filament_diameter', '3'); - my $config2 = Slic3r::Config::Print->new; + my $config2 = Slic3r::Config::Full->new; $config2->apply_dynamic($config); is $config2->get('perimeters'), 2, 'apply_dynamic'; } { - my $config = Slic3r::Config::Print->new; + my $config = Slic3r::Config::Full->new; my $config2 = Slic3r::Config->new; $config2->apply_static($config); is $config2->get('perimeters'), Slic3r::Config::print_config_def()->{perimeters}{default}, 'apply_static and print_config_def'; + + $config->set('top_solid_infill_speed', 70); + is $config->get_abs_value('top_solid_infill_speed'), 70, 'get_abs_value() works when ratio_over references a floatOrPercent option'; +} + +{ + my $config = Slic3r::Config->new; + $config->set('fill_pattern', 'line'); + + my $config2 = Slic3r::Config->new; + $config2->set('fill_pattern', 'hilbertcurve'); + + is $config->get('fill_pattern'), 'line', 'no interferences between DynamicConfig objects'; } __END__ diff --git a/xs/t/16_flow.t b/xs/t/16_flow.t new file mode 100644 index 000000000..e19430200 --- /dev/null +++ b/xs/t/16_flow.t @@ -0,0 +1,29 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Slic3r::XS; +use Test::More tests => 2; + +{ + my $flow = Slic3r::Flow->new_from_width( + role => Slic3r::Flow::FLOW_ROLE_PERIMETER, + width => '1', + nozzle_diameter => 0.5, + layer_height => 0.3, + bridge_flow_ratio => 1, + ); + isa_ok $flow, 'Slic3r::Flow', 'new_from_width'; +} + +{ + my $flow = Slic3r::Flow->new( + width => 1, + spacing => 0.95, + nozzle_diameter => 0.5, + ); + isa_ok $flow, 'Slic3r::Flow', 'new'; +} + +__END__ diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 3f053061d..461f1cd89 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -11,19 +11,19 @@ bool has(t_config_option_key opt_key); SV* as_hash(); SV* get(t_config_option_key opt_key); + SV* get_at(t_config_option_key opt_key, int i); void set(t_config_option_key opt_key, SV* value); void set_deserialize(t_config_option_key opt_key, std::string str); std::string serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); + %name{get_abs_value_over} + double get_abs_value(t_config_option_key opt_key, double ratio_over); void apply(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; - void apply_static(PrintConfig* other) + void apply_static(FullPrintConfig* other) %code{% THIS->apply(*other, true); %}; std::vector get_keys() %code{% THIS->keys(&RETVAL); %}; -%{ - -%} }; %name{Slic3r::Config::Print} class PrintConfig { @@ -32,17 +32,81 @@ bool has(t_config_option_key opt_key); SV* as_hash(); SV* get(t_config_option_key opt_key); + SV* get_at(t_config_option_key opt_key, int i); void set(t_config_option_key opt_key, SV* value); void set_deserialize(t_config_option_key opt_key, std::string str); std::string serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); + %name{get_abs_value_over} + double get_abs_value(t_config_option_key opt_key, double ratio_over); void apply_dynamic(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; std::vector get_keys() %code{% THIS->keys(&RETVAL); %}; -%{ + std::string get_extrusion_axis(); +}; -%} +%name{Slic3r::Config::PrintRegion} class PrintRegionConfig { + PrintRegionConfig(); + ~PrintRegionConfig(); + bool has(t_config_option_key opt_key); + SV* as_hash(); + SV* get(t_config_option_key opt_key); + SV* get_at(t_config_option_key opt_key, int i); + void set(t_config_option_key opt_key, SV* value); + void set_deserialize(t_config_option_key opt_key, std::string str); + std::string serialize(t_config_option_key opt_key); + double get_abs_value(t_config_option_key opt_key); + %name{get_abs_value_over} + double get_abs_value(t_config_option_key opt_key, double ratio_over); + void apply(PrintRegionConfig* other) + %code{% THIS->apply(*other, true); %}; + void apply_dynamic(DynamicPrintConfig* other) + %code{% THIS->apply(*other, true); %}; + std::vector get_keys() + %code{% THIS->keys(&RETVAL); %}; +}; + +%name{Slic3r::Config::PrintObject} class PrintObjectConfig { + PrintObjectConfig(); + ~PrintObjectConfig(); + bool has(t_config_option_key opt_key); + SV* as_hash(); + SV* get(t_config_option_key opt_key); + SV* get_at(t_config_option_key opt_key, int i); + void set(t_config_option_key opt_key, SV* value); + void set_deserialize(t_config_option_key opt_key, std::string str); + std::string serialize(t_config_option_key opt_key); + double get_abs_value(t_config_option_key opt_key); + %name{get_abs_value_over} + double get_abs_value(t_config_option_key opt_key, double ratio_over); + void apply(PrintObjectConfig* other) + %code{% THIS->apply(*other, true); %}; + void apply_dynamic(DynamicPrintConfig* other) + %code{% THIS->apply(*other, true); %}; + std::vector get_keys() + %code{% THIS->keys(&RETVAL); %}; +}; + +%name{Slic3r::Config::Full} class FullPrintConfig { + FullPrintConfig(); + ~FullPrintConfig(); + bool has(t_config_option_key opt_key); + SV* as_hash(); + SV* get(t_config_option_key opt_key); + SV* get_at(t_config_option_key opt_key, int i); + void set(t_config_option_key opt_key, SV* value); + void set_deserialize(t_config_option_key opt_key, std::string str); + std::string serialize(t_config_option_key opt_key); + double get_abs_value(t_config_option_key opt_key); + %name{get_abs_value_over} + double get_abs_value(t_config_option_key opt_key, double ratio_over); + void apply(PrintObjectConfig* other) + %code{% THIS->apply(*other, true); %}; + void apply_dynamic(DynamicPrintConfig* other) + %code{% THIS->apply(*other, true); %}; + std::vector get_keys() + %code{% THIS->keys(&RETVAL); %}; }; %package{Slic3r::Config}; @@ -53,7 +117,7 @@ PROTOTYPES: DISABLE SV* print_config_def() CODE: - PrintConfig config; + FullPrintConfig config; t_optiondef_map* def = config.def; HV* options_hv = newHV(); @@ -83,6 +147,8 @@ 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()) ); + if (!optdef->full_label.empty()) + (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()) ); @@ -90,7 +156,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/ExtrusionLoop.xsp b/xs/xsp/ExtrusionLoop.xsp index 9bef01d42..fbf4dfca3 100644 --- a/xs/xsp/ExtrusionLoop.xsp +++ b/xs/xsp/ExtrusionLoop.xsp @@ -28,18 +28,16 @@ %{ ExtrusionLoop* -_new(CLASS, polygon_sv, role, height, flow_spacing) +_new(CLASS, polygon_sv, role, mm3_per_mm) char* CLASS; SV* polygon_sv; ExtrusionRole role; - double height; - double flow_spacing; + double mm3_per_mm; CODE: RETVAL = new ExtrusionLoop (); RETVAL->polygon.from_SV_check(polygon_sv); RETVAL->role = role; - RETVAL->height = height; - RETVAL->flow_spacing = flow_spacing; + RETVAL->mm3_per_mm = mm3_per_mm; OUTPUT: RETVAL @@ -66,22 +64,12 @@ ExtrusionLoop::role(...) RETVAL double -ExtrusionLoop::height(...) +ExtrusionLoop::mm3_per_mm(...) CODE: if (items > 1) { - THIS->height = (double)SvNV(ST(1)); + THIS->mm3_per_mm = (double)SvNV(ST(1)); } - RETVAL = THIS->height; - OUTPUT: - RETVAL - -double -ExtrusionLoop::flow_spacing(...) - CODE: - if (items > 1) { - THIS->flow_spacing = (double)SvNV(ST(1)); - } - RETVAL = THIS->flow_spacing; + RETVAL = THIS->mm3_per_mm; OUTPUT: RETVAL diff --git a/xs/xsp/ExtrusionPath.xsp b/xs/xsp/ExtrusionPath.xsp index b4248214c..9d0abd70d 100644 --- a/xs/xsp/ExtrusionPath.xsp +++ b/xs/xsp/ExtrusionPath.xsp @@ -33,18 +33,16 @@ %{ ExtrusionPath* -_new(CLASS, polyline_sv, role, height, flow_spacing) +_new(CLASS, polyline_sv, role, mm3_per_mm) char* CLASS; SV* polyline_sv; ExtrusionRole role; - double height; - double flow_spacing; + double mm3_per_mm; CODE: RETVAL = new ExtrusionPath (); RETVAL->polyline.from_SV_check(polyline_sv); RETVAL->role = role; - RETVAL->height = height; - RETVAL->flow_spacing = flow_spacing; + RETVAL->mm3_per_mm = mm3_per_mm; OUTPUT: RETVAL @@ -71,22 +69,12 @@ ExtrusionPath::role(...) RETVAL double -ExtrusionPath::height(...) +ExtrusionPath::mm3_per_mm(...) CODE: if (items > 1) { - THIS->height = (double)SvNV(ST(1)); + THIS->mm3_per_mm = (double)SvNV(ST(1)); } - RETVAL = THIS->height; - OUTPUT: - RETVAL - -double -ExtrusionPath::flow_spacing(...) - CODE: - if (items > 1) { - THIS->flow_spacing = (double)SvNV(ST(1)); - } - RETVAL = THIS->flow_spacing; + RETVAL = THIS->mm3_per_mm; OUTPUT: RETVAL diff --git a/xs/xsp/Flow.xsp b/xs/xsp/Flow.xsp new file mode 100644 index 000000000..4ff5427fc --- /dev/null +++ b/xs/xsp/Flow.xsp @@ -0,0 +1,80 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "Flow.hpp" +%} + +%name{Slic3r::Flow} class Flow { + ~Flow(); + %name{_new} Flow(float width, float spacing, float nozzle_diameter); + void set_bridge(bool bridge) + %code{% THIS->bridge = bridge; %}; + Flow* clone() + %code{% const char* CLASS = "Slic3r::Flow"; RETVAL = new Flow(*THIS); %}; + + float width() + %code{% RETVAL = THIS->width; %}; + float spacing() + %code{% RETVAL = THIS->spacing; %}; + float nozzle_diameter() + %code{% RETVAL = THIS->nozzle_diameter; %}; + bool bridge() + %code{% RETVAL = THIS->bridge; %}; + long scaled_width() + %code{% RETVAL = THIS->scaled_width; %}; + long scaled_spacing() + %code{% RETVAL = THIS->scaled_spacing; %}; + + double mm3_per_mm(float height); +%{ + +Flow* +_new_from_width(CLASS, role, width, nozzle_diameter, height, bridge_flow_ratio) + char* CLASS; + FlowRole role; + std::string width; + float nozzle_diameter; + float height; + float bridge_flow_ratio; + CODE: + ConfigOptionFloatOrPercent optwidth; + optwidth.deserialize(width); + RETVAL = new Flow(Flow::new_from_config_width(role, optwidth, nozzle_diameter, height, bridge_flow_ratio)); + OUTPUT: + RETVAL + +Flow* +_new_from_spacing(CLASS, spacing, nozzle_diameter, height, bridge) + char* CLASS; + float spacing; + float nozzle_diameter; + float height; + bool bridge; + CODE: + RETVAL = new Flow(Flow::new_from_spacing(spacing, nozzle_diameter, height, bridge)); + OUTPUT: + RETVAL + +%} +}; + +%package{Slic3r::Flow}; +%{ + +IV +_constant() + ALIAS: + FLOW_ROLE_PERIMETER = frPerimeter + FLOW_ROLE_INFILL = frInfill + FLOW_ROLE_SOLID_INFILL = frSolidInfill + FLOW_ROLE_TOP_SOLID_INFILL = frTopSolidInfill + FLOW_ROLE_SUPPORT_MATERIAL = frSupportMaterial + FLOW_ROLE_SUPPORT_MATERIAL_INTERFACE = frSupportMaterialInterface + PROTOTYPE: + CODE: + RETVAL = ix; + OUTPUT: RETVAL + +%} + 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(); %{ %} diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 7c370ac10..6a02d9432 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -2,7 +2,10 @@ std::vector T_STD_VECTOR_INT t_config_option_key T_STD_STRING DynamicPrintConfig* O_OBJECT +PrintObjectConfig* O_OBJECT +PrintRegionConfig* O_OBJECT PrintConfig* O_OBJECT +FullPrintConfig* O_OBJECT ZTable* O_OBJECT TriangleMesh* O_OBJECT Point* O_OBJECT @@ -15,11 +18,13 @@ ExPolygonCollection* O_OBJECT ExtrusionEntityCollection* O_OBJECT ExtrusionPath* O_OBJECT ExtrusionLoop* O_OBJECT +Flow* O_OBJECT PrintState* O_OBJECT Surface* O_OBJECT SurfaceCollection* O_OBJECT ExtrusionRole T_UV +FlowRole T_UV PrintStep T_UV SurfaceType T_UV ClipperLib::JoinType T_UV diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index eb6302e7d..46a037526 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -9,9 +9,13 @@ %typemap{AV*}; %typemap{Point*}; %typemap{DynamicPrintConfig*}; +%typemap{PrintObjectConfig*}; +%typemap{PrintRegionConfig*}; %typemap{PrintConfig*}; +%typemap{FullPrintConfig*}; %typemap{ExPolygon*}; %typemap{ExPolygonCollection*}; +%typemap{Flow*}; %typemap{Line*}; %typemap{Polyline*}; %typemap{Polygon*}; @@ -40,6 +44,12 @@ $CVar = (ExtrusionRole)SvUV($PerlVar); %}; }; +%typemap{FlowRole}{parsed}{ + %cpp_type{FlowRole}; + %precall_code{% + $CVar = (FlowRole)SvUV($PerlVar); + %}; +}; %typemap{PrintStep}{parsed}{ %cpp_type{PrintStep}; %precall_code{%