Merge branch 'region-config'

Conflicts:
	lib/Slic3r/Format/AMF/Parser.pm
This commit is contained in:
Alessandro Ranellucci 2014-01-05 14:59:36 +01:00
commit 0bdea60b53
62 changed files with 2164 additions and 1432 deletions

View file

@ -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 {};

View file

@ -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;

View file

@ -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;

View file

@ -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,
);
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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});

View file

@ -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')

View file

@ -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;
{

View file

@ -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;

View file

@ -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 {

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

112
lib/Slic3r/Print/Simple.pm Normal file
View file

@ -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;

View file

@ -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;

View file

@ -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;