Merge branch 'master' into sender
Conflicts: Build.PL lib/Slic3r.pm xs/MANIFEST xs/src/libslic3r/PrintConfig.hpp
This commit is contained in:
commit
9b21ac877a
93 changed files with 3339 additions and 2050 deletions
|
@ -192,14 +192,6 @@ sub save {
|
|||
__PACKAGE__->write_ini($file, $self->as_ini);
|
||||
}
|
||||
|
||||
sub setenv {
|
||||
my $self = shift;
|
||||
|
||||
foreach my $opt_key (@{$self->get_keys}) {
|
||||
$ENV{"SLIC3R_" . uc $opt_key} = $self->serialize($opt_key);
|
||||
}
|
||||
}
|
||||
|
||||
# this method is idempotent by design and only applies to ::DynamicConfig or ::Full
|
||||
# objects because it performs cross checks
|
||||
sub validate {
|
||||
|
|
|
@ -6,7 +6,8 @@ use parent qw(Exporter);
|
|||
|
||||
our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER
|
||||
EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_GAPFILL EXTR_ROLE_BRIDGE
|
||||
EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_SUPPORTMATERIAL_INTERFACE);
|
||||
EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_SUPPORTMATERIAL_INTERFACE
|
||||
EXTR_ROLE_NONE);
|
||||
our %EXPORT_TAGS = (roles => \@EXPORT_OK);
|
||||
|
||||
1;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package Slic3r::Fill;
|
||||
use Moo;
|
||||
|
||||
use List::Util qw(max);
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Fill::3DHoneycomb;
|
||||
use Slic3r::Fill::Base;
|
||||
|
@ -20,6 +21,7 @@ has 'fillers' => (is => 'rw', default => sub { {} });
|
|||
our %FillTypes = (
|
||||
archimedeanchords => 'Slic3r::Fill::ArchimedeanChords',
|
||||
rectilinear => 'Slic3r::Fill::Rectilinear',
|
||||
grid => 'Slic3r::Fill::Grid',
|
||||
flowsnake => 'Slic3r::Fill::Flowsnake',
|
||||
octagramspiral => 'Slic3r::Fill::OctagramSpiral',
|
||||
hilbertcurve => 'Slic3r::Fill::HilbertCurve',
|
||||
|
@ -47,9 +49,9 @@ sub make_fill {
|
|||
my $self = shift;
|
||||
my ($layerm) = @_;
|
||||
|
||||
Slic3r::debugf "Filling layer %d:\n", $layerm->id;
|
||||
Slic3r::debugf "Filling layer %d:\n", $layerm->layer->id;
|
||||
|
||||
my $fill_density = $layerm->config->fill_density;
|
||||
my $fill_density = $layerm->region->config->fill_density;
|
||||
my $infill_flow = $layerm->flow(FLOW_ROLE_INFILL);
|
||||
my $solid_infill_flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL);
|
||||
my $top_solid_infill_flow = $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL);
|
||||
|
@ -73,13 +75,13 @@ sub make_fill {
|
|||
for (my $i = 0; $i <= $#groups; $i++) {
|
||||
# we can only merge solid non-bridge surfaces, so discard
|
||||
# non-solid surfaces
|
||||
if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->id == 0)) {
|
||||
if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->layer->id == 0)) {
|
||||
$is_solid[$i] = 1;
|
||||
$fw[$i] = ($groups[$i][0]->surface_type == S_TYPE_TOP)
|
||||
? $top_solid_infill_flow->width
|
||||
: $solid_infill_flow->width;
|
||||
$pattern[$i] = $groups[$i][0]->is_external
|
||||
? $layerm->config->external_fill_pattern
|
||||
? $layerm->region->config->external_fill_pattern
|
||||
: 'rectilinear';
|
||||
} else {
|
||||
$is_solid[$i] = 0;
|
||||
|
@ -143,8 +145,12 @@ 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 = $infill_flow->scaled_spacing;
|
||||
{
|
||||
my $distance_between_surfaces = max(
|
||||
$infill_flow->scaled_spacing,
|
||||
$solid_infill_flow->scaled_spacing,
|
||||
$top_solid_infill_flow->scaled_spacing,
|
||||
);
|
||||
my $collapsed = diff(
|
||||
[ map @{$_->expolygon}, @surfaces ],
|
||||
offset2([ map @{$_->expolygon}, @surfaces ], -$distance_between_surfaces/2, +$distance_between_surfaces/2),
|
||||
|
@ -163,9 +169,6 @@ sub make_fill {
|
|||
)};
|
||||
}
|
||||
|
||||
# add spacing between surfaces
|
||||
@surfaces = map @{$_->offset(-$distance_between_surfaces / 2)}, @surfaces;
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output("fill_" . $layerm->print_z . ".svg",
|
||||
|
@ -177,19 +180,19 @@ sub make_fill {
|
|||
my @fills = ();
|
||||
SURFACE: foreach my $surface (@surfaces) {
|
||||
next if $surface->surface_type == S_TYPE_INTERNALVOID;
|
||||
my $filler = $layerm->config->fill_pattern;
|
||||
my $filler = $layerm->region->config->fill_pattern;
|
||||
my $density = $fill_density;
|
||||
my $role = ($surface->surface_type == S_TYPE_TOP) ? FLOW_ROLE_TOP_SOLID_INFILL
|
||||
: $surface->is_solid ? FLOW_ROLE_SOLID_INFILL
|
||||
: FLOW_ROLE_INFILL;
|
||||
my $is_bridge = $layerm->id > 0 && $surface->is_bridge;
|
||||
my $is_bridge = $layerm->layer->id > 0 && $surface->is_bridge;
|
||||
my $is_solid = $surface->is_solid;
|
||||
|
||||
if ($surface->is_solid) {
|
||||
$density = 100;
|
||||
$filler = 'rectilinear';
|
||||
if ($surface->is_external && !$is_bridge) {
|
||||
$filler = $layerm->config->external_fill_pattern;
|
||||
$filler = $layerm->region->config->external_fill_pattern;
|
||||
}
|
||||
} else {
|
||||
next SURFACE unless $density > 0;
|
||||
|
@ -199,14 +202,14 @@ sub make_fill {
|
|||
my $f = $self->filler($filler);
|
||||
|
||||
# calculate the actual flow we'll be using for this infill
|
||||
my $h = $surface->thickness == -1 ? $layerm->height : $surface->thickness;
|
||||
my $h = $surface->thickness == -1 ? $layerm->layer->height : $surface->thickness;
|
||||
my $flow = $layerm->region->flow(
|
||||
$role,
|
||||
$h,
|
||||
$is_bridge || $f->use_bridge_flow,
|
||||
$layerm->id == 0,
|
||||
$layerm->layer->id == 0,
|
||||
-1,
|
||||
$layerm->object,
|
||||
$layerm->layer->object,
|
||||
);
|
||||
|
||||
# calculate flow spacing for infill pattern generation
|
||||
|
@ -218,11 +221,11 @@ sub make_fill {
|
|||
# layer height
|
||||
my $internal_flow = $layerm->region->flow(
|
||||
FLOW_ROLE_INFILL,
|
||||
$layerm->object->config->layer_height, # TODO: handle infill_every_layers?
|
||||
$layerm->layer->object->config->layer_height, # TODO: handle infill_every_layers?
|
||||
0, # no bridge
|
||||
0, # no first layer
|
||||
-1, # auto width
|
||||
$layerm->object,
|
||||
$layerm->layer->object,
|
||||
);
|
||||
$f->spacing($internal_flow->spacing);
|
||||
$using_internal_flow = 1;
|
||||
|
@ -230,15 +233,18 @@ sub make_fill {
|
|||
$f->spacing($flow->spacing);
|
||||
}
|
||||
|
||||
$f->layer_id($layerm->id);
|
||||
$f->z($layerm->print_z);
|
||||
$f->angle(deg2rad($layerm->config->fill_angle));
|
||||
$f->layer_id($layerm->layer->id);
|
||||
$f->z($layerm->layer->print_z);
|
||||
$f->angle(deg2rad($layerm->region->config->fill_angle));
|
||||
$f->loop_clipping(scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
|
||||
my @polylines = $f->fill_surface(
|
||||
$surface,
|
||||
|
||||
# apply half spacing using this flow's own spacing and generate infill
|
||||
my @polylines = map $f->fill_surface(
|
||||
$_,
|
||||
density => $density/100,
|
||||
layer_height => $h,
|
||||
);
|
||||
), @{ $surface->offset(-scale($f->spacing)/2) };
|
||||
|
||||
next unless @polylines;
|
||||
|
||||
# calculate actual flow from spacing (which might have been adjusted by the infill
|
||||
|
|
|
@ -12,6 +12,8 @@ has '_line_oscillation' => (is => 'rw');
|
|||
use Slic3r::Geometry qw(scale unscale scaled_epsilon);
|
||||
use Slic3r::Geometry::Clipper qw(intersection_pl);
|
||||
|
||||
sub horizontal_lines { 0 }
|
||||
|
||||
sub fill_surface {
|
||||
my $self = shift;
|
||||
my ($surface, %params) = @_;
|
||||
|
@ -44,9 +46,18 @@ sub fill_surface {
|
|||
|
||||
# generate the basic pattern
|
||||
my $x_max = $bounding_box->x_max + scaled_epsilon;
|
||||
my @vertical_lines = ();
|
||||
my @lines = ();
|
||||
for (my $x = $bounding_box->x_min; $x <= $x_max; $x += $self->_line_spacing) {
|
||||
push @vertical_lines, $self->_line($#vertical_lines, $x, $bounding_box->y_min, $bounding_box->y_max);
|
||||
push @lines, $self->_line($#lines, $x, $bounding_box->y_min, $bounding_box->y_max);
|
||||
}
|
||||
if ($self->horizontal_lines) {
|
||||
my $y_max = $bounding_box->y_max + scaled_epsilon;
|
||||
for (my $y = $bounding_box->y_min; $y <= $y_max; $y += $self->_line_spacing) {
|
||||
push @lines, Slic3r::Polyline->new(
|
||||
[$bounding_box->x_min, $y],
|
||||
[$bounding_box->x_max, $y],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# clip paths against a slightly larger expolygon, so that the first and last paths
|
||||
|
@ -54,7 +65,7 @@ sub fill_surface {
|
|||
# the minimum offset for preventing edge lines from being clipped is scaled_epsilon;
|
||||
# however we use a larger offset to support expolygons with slightly skewed sides and
|
||||
# not perfectly straight
|
||||
my @polylines = @{intersection_pl(\@vertical_lines, $expolygon->offset(+scale 0.02))};
|
||||
my @polylines = @{intersection_pl(\@lines, $expolygon->offset(+scale 0.02))};
|
||||
|
||||
my $extra = $self->_min_spacing * &Slic3r::INFILL_OVERLAP_OVER_SPACING;
|
||||
foreach my $polyline (@polylines) {
|
||||
|
@ -146,4 +157,12 @@ sub _can_connect {
|
|||
&& $dist_Y <= $self->_diagonal_distance;
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::Fill::Grid;
|
||||
use Moo;
|
||||
extends 'Slic3r::Fill::Rectilinear';
|
||||
|
||||
sub angles () { [0] }
|
||||
sub horizontal_lines { 1 }
|
||||
|
||||
1;
|
||||
|
|
|
@ -36,7 +36,9 @@ sub write_file {
|
|||
printf $fh qq{<amf unit="millimeter">\n};
|
||||
printf $fh qq{ <metadata type="cad">Slic3r %s</metadata>\n}, $Slic3r::VERSION;
|
||||
for my $material_id (sort @{ $model->material_names }) {
|
||||
next if $material_id eq '';
|
||||
my $material = $model->get_material($material_id);
|
||||
# note that material-id must never be 0 since it's reserved by the AMF spec
|
||||
printf $fh qq{ <material id="%s">\n}, $material_id;
|
||||
for (keys %{$material->attributes}) {
|
||||
printf $fh qq{ <metadata type=\"%s\">%s</metadata>\n}, $_, $material->attributes->{$_};
|
||||
|
|
|
@ -1,649 +0,0 @@
|
|||
package Slic3r::GCode;
|
||||
use Moo;
|
||||
|
||||
use List::Util qw(min max first);
|
||||
use Slic3r::ExtrusionLoop ':roles';
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Geometry qw(epsilon scale unscale PI X Y B);
|
||||
use Slic3r::Geometry::Clipper qw(union_ex);
|
||||
|
||||
# Origin of print coordinates expressed in unscaled G-code coordinates.
|
||||
# This affects the input arguments supplied to the extrude*() and travel_to()
|
||||
# methods.
|
||||
has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf->new });
|
||||
|
||||
has 'config' => (is => 'ro', default => sub { Slic3r::Config::Full->new });
|
||||
has 'writer' => (is => 'ro', default => sub { Slic3r::GCode::Writer->new });
|
||||
has 'placeholder_parser' => (is => 'rw', default => sub { Slic3r::GCode::PlaceholderParser->new });
|
||||
has 'ooze_prevention' => (is => 'rw', default => sub { Slic3r::GCode::OozePrevention->new });
|
||||
has 'wipe' => (is => 'rw', default => sub { Slic3r::GCode::Wipe->new });
|
||||
has 'avoid_crossing_perimeters' => (is => 'rw', default => sub { Slic3r::GCode::AvoidCrossingPerimeters->new });
|
||||
has 'enable_loop_clipping' => (is => 'rw', default => sub {1});
|
||||
has 'enable_cooling_markers' => (is =>'rw', default => sub {0});
|
||||
has 'layer_count' => (is => 'ro');
|
||||
has 'layer_index' => (is => 'rw', default => sub {-1}); # just a counter
|
||||
has 'layer' => (is => 'rw');
|
||||
has '_seam_position' => (is => 'ro', default => sub { {} }); # $object => pos
|
||||
has 'first_layer' => (is => 'rw', default => sub {0}); # this flag triggers first layer speeds
|
||||
has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds
|
||||
has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0) } );
|
||||
|
||||
sub apply_print_config {
|
||||
my ($self, $print_config) = @_;
|
||||
|
||||
$self->writer->apply_print_config($print_config);
|
||||
$self->config->apply_print_config($print_config);
|
||||
}
|
||||
|
||||
sub set_extruders {
|
||||
my ($self, $extruder_ids) = @_;
|
||||
|
||||
$self->writer->set_extruders($extruder_ids);
|
||||
|
||||
# enable wipe path generation if any extruder has wipe enabled
|
||||
$self->wipe->enable(defined first { $self->config->get_at('wipe', $_) } @$extruder_ids);
|
||||
}
|
||||
|
||||
sub set_origin {
|
||||
my ($self, $pointf) = @_;
|
||||
|
||||
# if origin increases (goes towards right), last_pos decreases because it goes towards left
|
||||
my @translate = (
|
||||
scale ($self->origin->x - $pointf->x),
|
||||
scale ($self->origin->y - $pointf->y), #-
|
||||
);
|
||||
$self->last_pos->translate(@translate);
|
||||
$self->wipe->path->translate(@translate) if $self->wipe->path;
|
||||
|
||||
$self->origin($pointf);
|
||||
}
|
||||
|
||||
sub preamble {
|
||||
my ($self) = @_;
|
||||
|
||||
my $gcode = $self->writer->preamble;
|
||||
|
||||
# Perform a *silent* move to z_offset: we need this to initialize the Z
|
||||
# position of our writer object so that any initial lift taking place
|
||||
# before the first layer change will raise the extruder from the correct
|
||||
# initial Z instead of 0.
|
||||
$self->writer->travel_to_z($self->config->z_offset, '');
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub change_layer {
|
||||
my ($self, $layer) = @_;
|
||||
|
||||
$self->layer($layer);
|
||||
$self->layer_index($self->layer_index + 1);
|
||||
$self->first_layer($layer->id == 0);
|
||||
|
||||
# avoid computing islands and overhangs if they're not needed
|
||||
if ($self->config->avoid_crossing_perimeters) {
|
||||
$self->avoid_crossing_perimeters->init_layer_mp(
|
||||
union_ex([ map @$_, @{$layer->slices} ], 1),
|
||||
);
|
||||
}
|
||||
|
||||
my $gcode = "";
|
||||
if (defined $self->layer_count) {
|
||||
$gcode .= $self->writer->update_progress($self->layer_index, $self->layer_count);
|
||||
}
|
||||
|
||||
my $z = $layer->print_z + $self->config->z_offset; # in unscaled coordinates
|
||||
if ($self->config->get_at('retract_layer_change', $self->writer->extruder->id) && $self->writer->will_move_z($z)) {
|
||||
$gcode .= $self->retract;
|
||||
}
|
||||
$gcode .= $self->writer->travel_to_z($z, 'move to next layer (' . $self->layer_index . ')');
|
||||
|
||||
# forget last wiping path as wiping after raising Z is pointless
|
||||
$self->wipe->path(undef);
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub extrude {
|
||||
my $self = shift;
|
||||
|
||||
$_[0]->isa('Slic3r::ExtrusionLoop')
|
||||
? $self->extrude_loop(@_)
|
||||
: $self->extrude_path(@_);
|
||||
}
|
||||
|
||||
sub extrude_loop {
|
||||
my ($self, $loop, $description, $speed) = @_;
|
||||
|
||||
# make a copy; don't modify the orientation of the original loop object otherwise
|
||||
# next copies (if any) would not detect the correct orientation
|
||||
$loop = $loop->clone;
|
||||
|
||||
# extrude all loops ccw
|
||||
my $was_clockwise = $loop->make_counter_clockwise;
|
||||
|
||||
# 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->spiral_vase) {
|
||||
$loop->split_at($last_pos);
|
||||
} elsif ($self->config->seam_position eq 'nearest' || $self->config->seam_position eq 'aligned') {
|
||||
# simplify polygon in order to skip false positives in concave/convex detection
|
||||
my $polygon = $loop->polygon;
|
||||
my @simplified = @{$polygon->simplify(scale $self->config->get_at('nozzle_diameter', $self->writer->extruder->id)/2)};
|
||||
|
||||
# concave vertices have priority
|
||||
my @candidates = map @{$_->concave_points(PI*4/3)}, @simplified;
|
||||
|
||||
# if no concave points were found, look for convex vertices
|
||||
@candidates = map @{$_->convex_points(PI*2/3)}, @simplified if !@candidates;
|
||||
|
||||
# retrieve the last start position for this object
|
||||
my $obj_ptr = 0;
|
||||
if (defined $self->layer) {
|
||||
$obj_ptr = $self->layer->object->ptr;
|
||||
if (defined $self->_seam_position->{$obj_ptr}) {
|
||||
$last_pos = $self->_seam_position->{$obj_ptr};
|
||||
}
|
||||
}
|
||||
|
||||
my $point;
|
||||
if ($self->config->seam_position eq 'nearest') {
|
||||
@candidates = @$polygon if !@candidates;
|
||||
$point = $last_pos->nearest_point(\@candidates);
|
||||
if (!$loop->split_at_vertex($point)) {
|
||||
# On 32-bit Linux, Clipper will change some point coordinates by 1 unit
|
||||
# while performing simplify_polygons(), thus split_at_vertex() won't
|
||||
# find them anymore.
|
||||
$loop->split_at($point);
|
||||
}
|
||||
} elsif (@candidates) {
|
||||
my @non_overhang = grep !$loop->has_overhang_point($_), @candidates;
|
||||
@candidates = @non_overhang if @non_overhang;
|
||||
$point = $last_pos->nearest_point(\@candidates);
|
||||
if (!$loop->split_at_vertex($point)) {
|
||||
$loop->split_at($point);
|
||||
}
|
||||
} else {
|
||||
$point = $last_pos->projection_onto_polygon($polygon);
|
||||
$loop->split_at($point);
|
||||
}
|
||||
$self->_seam_position->{$obj_ptr} = $point;
|
||||
} elsif ($self->config->seam_position eq 'random') {
|
||||
if ($loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) {
|
||||
my $polygon = $loop->polygon;
|
||||
my $centroid = $polygon->centroid;
|
||||
$last_pos = Slic3r::Point->new($polygon->bounding_box->x_max, $centroid->y); #))
|
||||
$last_pos->rotate(rand(2*PI), $centroid);
|
||||
}
|
||||
$loop->split_at($last_pos);
|
||||
}
|
||||
|
||||
# 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
|
||||
my $clip_length = $self->enable_loop_clipping
|
||||
? scale($self->config->get_at('nozzle_diameter', $self->writer->extruder->id)) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER
|
||||
: 0;
|
||||
|
||||
# get paths
|
||||
my @paths = @{$loop->clip_end($clip_length)};
|
||||
return '' if !@paths;
|
||||
|
||||
# apply the small perimeter speed
|
||||
if ($paths[0]->is_perimeter && $loop->length <= &Slic3r::SMALL_PERIMETER_LENGTH) {
|
||||
$speed //= $self->config->get_abs_value('small_perimeter_speed');
|
||||
}
|
||||
|
||||
# extrude along the path
|
||||
my $gcode = join '', map $self->_extrude_path($_, $description, $speed), @paths;
|
||||
|
||||
# reset acceleration
|
||||
$gcode .= $self->writer->set_acceleration($self->config->default_acceleration);
|
||||
|
||||
$self->wipe->path($paths[0]->polyline->clone) if $self->wipe->enable; # TODO: don't limit wipe to last path
|
||||
|
||||
# make a little move inwards before leaving loop
|
||||
if ($paths[-1]->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->config->perimeters > 1) {
|
||||
my $last_path_polyline = $paths[-1]->polyline;
|
||||
# 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 = ($paths[0][1], $paths[-1][-2]);
|
||||
@points = reverse @points if $was_clockwise;
|
||||
my $angle = $paths[0]->first_point->ccw_angle(@points) / 3;
|
||||
|
||||
# turn left if contour, turn right if hole
|
||||
$angle *= -1 if $was_clockwise;
|
||||
|
||||
# create the destination point along the first segment and rotate it
|
||||
# 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(@{$paths[0]->polyline}[0,1]);
|
||||
my $distance = min(scale($self->config->get_at('nozzle_diameter', $self->writer->extruder->id)), $first_segment->length);
|
||||
my $point = $first_segment->point_at($distance);
|
||||
$point->rotate($angle, $first_segment->a);
|
||||
|
||||
# generate the travel move
|
||||
$gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), "move inwards before travel");
|
||||
}
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub extrude_path {
|
||||
my ($self, $path, $description, $speed) = @_;
|
||||
|
||||
my $gcode = $self->_extrude_path($path, $description, $speed);
|
||||
|
||||
# reset acceleration
|
||||
$gcode .= $self->writer->set_acceleration($self->config->default_acceleration);
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub _extrude_path {
|
||||
my ($self, $path, $description, $speed) = @_;
|
||||
|
||||
$path->simplify(&Slic3r::SCALED_RESOLUTION);
|
||||
|
||||
# go to first point of extrusion path
|
||||
my $gcode = "";
|
||||
{
|
||||
my $first_point = $path->first_point;
|
||||
$gcode .= $self->travel_to($first_point, $path->role, "move to first $description point")
|
||||
if !defined $self->last_pos || !$self->last_pos->coincides_with($first_point);
|
||||
}
|
||||
|
||||
# compensate retraction
|
||||
$gcode .= $self->unretract;
|
||||
|
||||
# adjust acceleration
|
||||
{
|
||||
my $acceleration;
|
||||
if ($self->config->first_layer_acceleration && $self->first_layer) {
|
||||
$acceleration = $self->config->first_layer_acceleration;
|
||||
} elsif ($self->config->perimeter_acceleration && $path->is_perimeter) {
|
||||
$acceleration = $self->config->perimeter_acceleration;
|
||||
} elsif ($self->config->bridge_acceleration && $path->is_bridge) {
|
||||
$acceleration = $self->config->bridge_acceleration;
|
||||
} elsif ($self->config->infill_acceleration && $path->is_infill) {
|
||||
$acceleration = $self->config->infill_acceleration;
|
||||
} else {
|
||||
$acceleration = $self->config->default_acceleration;
|
||||
}
|
||||
$gcode .= $self->writer->set_acceleration($acceleration);
|
||||
}
|
||||
|
||||
# calculate extrusion length per distance unit
|
||||
my $e_per_mm = $self->writer->extruder->e_per_mm3 * $path->mm3_per_mm;
|
||||
$e_per_mm = 0 if !$self->writer->extrusion_axis;
|
||||
|
||||
# set speed
|
||||
$speed //= -1;
|
||||
if ($speed == -1) {
|
||||
if ($path->role == EXTR_ROLE_PERIMETER) {
|
||||
$speed = $self->config->get_abs_value('perimeter_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_EXTERNAL_PERIMETER) {
|
||||
$speed = $self->config->get_abs_value('external_perimeter_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_OVERHANG_PERIMETER || $path->role == EXTR_ROLE_BRIDGE) {
|
||||
$speed = $self->config->get_abs_value('bridge_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_FILL) {
|
||||
$speed = $self->config->get_abs_value('infill_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_SOLIDFILL) {
|
||||
$speed = $self->config->get_abs_value('solid_infill_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_TOPSOLIDFILL) {
|
||||
$speed = $self->config->get_abs_value('top_solid_infill_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_GAPFILL) {
|
||||
$speed = $self->config->get_abs_value('gap_fill_speed');
|
||||
} else {
|
||||
die "Invalid speed";
|
||||
}
|
||||
}
|
||||
my $F = $speed * 60; # convert mm/sec to mm/min
|
||||
|
||||
if ($self->first_layer) {
|
||||
$F = $self->config->get_abs_value_over('first_layer_speed', $F/60) * 60;
|
||||
}
|
||||
|
||||
# extrude arc or line
|
||||
$gcode .= ";_BRIDGE_FAN_START\n" if $path->is_bridge && $self->enable_cooling_markers;
|
||||
my $path_length = unscale $path->length;
|
||||
{
|
||||
my $extruder_offset = $self->config->get_at('extruder_offset', $self->writer->extruder->id);
|
||||
$gcode .= $path->gcode($self->writer->extruder, $e_per_mm, $F,
|
||||
$self->origin->x - $extruder_offset->x,
|
||||
$self->origin->y - $extruder_offset->y, #-
|
||||
$self->writer->extrusion_axis,
|
||||
$self->config->gcode_comments ? " ; $description" : "");
|
||||
|
||||
if ($self->wipe->enable) {
|
||||
$self->wipe->path($path->polyline->clone);
|
||||
$self->wipe->path->reverse;
|
||||
}
|
||||
}
|
||||
$gcode .= ";_BRIDGE_FAN_END\n" if $path->is_bridge && $self->enable_cooling_markers;
|
||||
$self->last_pos($path->last_point);
|
||||
|
||||
if ($self->config->cooling) {
|
||||
my $path_time = $path_length / $F * 60;
|
||||
$self->elapsed_time($self->elapsed_time + $path_time);
|
||||
}
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
# This method accepts $point in print coordinates.
|
||||
sub travel_to {
|
||||
my ($self, $point, $role, $comment) = @_;
|
||||
|
||||
# Define the travel move as a line between current position and the taget point.
|
||||
# This is expressed in print coordinates, so it will need to be translated by
|
||||
# $self->origin in order to get G-code coordinates.
|
||||
my $travel = Slic3r::Polyline->new($self->last_pos, $point);
|
||||
|
||||
# check whether a straight travel move would need retraction
|
||||
my $needs_retraction = $self->needs_retraction($travel, $role);
|
||||
|
||||
# if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
|
||||
# multi-hop travel path inside the configuration space
|
||||
if ($needs_retraction
|
||||
&& $self->config->avoid_crossing_perimeters
|
||||
&& !$self->avoid_crossing_perimeters->disable_once) {
|
||||
$travel = $self->avoid_crossing_perimeters->travel_to($self, $point);
|
||||
|
||||
# check again whether the new travel path still needs a retraction
|
||||
$needs_retraction = $self->needs_retraction($travel, $role);
|
||||
}
|
||||
|
||||
# Re-allow avoid_crossing_perimeters for the next travel moves
|
||||
$self->avoid_crossing_perimeters->disable_once(0);
|
||||
$self->avoid_crossing_perimeters->use_external_mp_once(0);
|
||||
|
||||
# generate G-code for the travel move
|
||||
my $gcode = "";
|
||||
$gcode .= $self->retract if $needs_retraction;
|
||||
|
||||
# use G1 because we rely on paths being straight (G0 may make round paths)
|
||||
$gcode .= $self->writer->travel_to_xy($self->point_to_gcode($_->b), $comment)
|
||||
for @{$travel->lines};
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub needs_retraction {
|
||||
my ($self, $travel, $role) = @_;
|
||||
|
||||
if ($travel->length < scale $self->config->get_at('retract_before_travel', $self->writer->extruder->id)) {
|
||||
# skip retraction if the move is shorter than the configured threshold
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_polyline($travel)) {
|
||||
# skip retraction if this is a travel move inside a support material island
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($self->config->only_retract_when_crossing_perimeters && defined $self->layer) {
|
||||
if ($self->config->fill_density > 0
|
||||
&& $self->layer->any_internal_region_slice_contains_polyline($travel)) {
|
||||
# skip retraction if travel is contained in an internal slice *and*
|
||||
# internal infill is enabled (so that stringing is entirely not visible)
|
||||
return 0;
|
||||
} elsif ($self->layer->any_bottom_region_slice_contains_polyline($travel)
|
||||
&& defined $self->layer->upper_layer
|
||||
&& $self->layer->upper_layer->slices->contains_polyline($travel)
|
||||
&& ($self->config->bottom_solid_layers >= 2 || $self->config->fill_density > 0)) {
|
||||
# skip retraction if travel is contained in an *infilled* bottom slice
|
||||
# but only if it's also covered by an *infilled* upper layer's slice
|
||||
# so that it's not visible from above (a bottom surface might not have an
|
||||
# upper slice in case of a thin membrane)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
# retract if only_retract_when_crossing_perimeters is disabled or doesn't apply
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub retract {
|
||||
my ($self, $toolchange) = @_;
|
||||
|
||||
return "" if !defined $self->writer->extruder;
|
||||
|
||||
my $gcode = "";
|
||||
|
||||
# wipe (if it's enabled for this extruder and we have a stored wipe path)
|
||||
if ($self->config->get_at('wipe', $self->writer->extruder->id) && $self->wipe->path) {
|
||||
$gcode .= $self->wipe->wipe($self, $toolchange);
|
||||
}
|
||||
|
||||
# The parent class will decide whether we need to perform an actual retraction
|
||||
# (the extruder might be already retracted fully or partially). We call these
|
||||
# methods even if we performed wipe, since this will ensure the entire retraction
|
||||
# length is honored in case wipe path was too short.p
|
||||
$gcode .= $toolchange ? $self->writer->retract_for_toolchange : $self->writer->retract;
|
||||
|
||||
$gcode .= $self->writer->reset_e;
|
||||
$gcode .= $self->writer->lift
|
||||
if $self->writer->extruder->retract_length > 0 || $self->config->use_firmware_retraction;
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub unretract {
|
||||
my ($self) = @_;
|
||||
|
||||
my $gcode = "";
|
||||
$gcode .= $self->writer->unlift;
|
||||
$gcode .= $self->writer->unretract;
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
# convert a model-space scaled point into G-code coordinates
|
||||
sub point_to_gcode {
|
||||
my ($self, $point) = @_;
|
||||
|
||||
my $extruder_offset = $self->config->get_at('extruder_offset', $self->writer->extruder->id);
|
||||
return Slic3r::Pointf->new(
|
||||
($point->x * &Slic3r::SCALING_FACTOR) + $self->origin->x - $extruder_offset->x,
|
||||
($point->y * &Slic3r::SCALING_FACTOR) + $self->origin->y - $extruder_offset->y, #**
|
||||
);
|
||||
}
|
||||
|
||||
sub set_extruder {
|
||||
my ($self, $extruder_id) = @_;
|
||||
|
||||
return "" if !$self->writer->need_toolchange($extruder_id);
|
||||
|
||||
# if we are running a single-extruder setup, just set the extruder and return nothing
|
||||
if (!$self->writer->multiple_extruders) {
|
||||
return $self->writer->toolchange($extruder_id);
|
||||
}
|
||||
|
||||
# prepend retraction on the current extruder
|
||||
my $gcode = $self->retract(1);
|
||||
|
||||
# append custom toolchange G-code
|
||||
if (defined $self->writer->extruder && $self->config->toolchange_gcode) {
|
||||
$gcode .= sprintf "%s\n", $self->placeholder_parser->process($self->config->toolchange_gcode, {
|
||||
previous_extruder => $self->writer->extruder->id,
|
||||
next_extruder => $extruder_id,
|
||||
});
|
||||
}
|
||||
|
||||
# if ooze prevention is enabled, park current extruder in the nearest
|
||||
# standby point and set it to the standby temperature
|
||||
$gcode .= $self->ooze_prevention->pre_toolchange($self)
|
||||
if $self->ooze_prevention->enable && defined $self->writer->extruder;
|
||||
|
||||
# append the toolchange command
|
||||
$gcode .= $self->writer->toolchange($extruder_id);
|
||||
|
||||
# set the new extruder to the operating temperature
|
||||
$gcode .= $self->ooze_prevention->post_toolchange($self)
|
||||
if $self->ooze_prevention->enable;
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
package Slic3r::GCode::OozePrevention;
|
||||
use Moo;
|
||||
|
||||
use Slic3r::Geometry qw(scale);
|
||||
|
||||
has 'enable' => (is => 'rw', default => sub { 0 });
|
||||
has 'standby_points' => (is => 'rw');
|
||||
|
||||
sub pre_toolchange {
|
||||
my ($self, $gcodegen) = @_;
|
||||
|
||||
my $gcode = "";
|
||||
|
||||
# move to the nearest standby point
|
||||
if (@{$self->standby_points}) {
|
||||
# get current position in print coordinates
|
||||
my $pos = Slic3r::Point->new_scale(@{$gcodegen->writer->get_position}[0,1]);
|
||||
|
||||
my $standby_point = Slic3r::Pointf->new_unscale(@{$pos->nearest_point($self->standby_points)});
|
||||
# We don't call $gcodegen->travel_to() because we don't need retraction (it was already
|
||||
# triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
|
||||
# of the destination point must not be transformed by origin nor current extruder offset.
|
||||
$gcode .= $gcodegen->writer->travel_to_xy($standby_point, 'move to standby position');
|
||||
}
|
||||
|
||||
if ($gcodegen->config->standby_temperature_delta != 0) {
|
||||
my $temp = defined $gcodegen->layer && $gcodegen->layer->id == 0
|
||||
? $gcodegen->config->get_at('first_layer_temperature', $gcodegen->writer->extruder->id)
|
||||
: $gcodegen->config->get_at('temperature', $gcodegen->writer->extruder->id);
|
||||
# we assume that heating is always slower than cooling, so no need to block
|
||||
$gcode .= $gcodegen->writer->set_temperature($temp + $gcodegen->config->standby_temperature_delta, 0);
|
||||
}
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub post_toolchange {
|
||||
my ($self, $gcodegen) = @_;
|
||||
|
||||
my $gcode = "";
|
||||
|
||||
if ($gcodegen->config->standby_temperature_delta != 0) {
|
||||
my $temp = defined $gcodegen->layer && $gcodegen->layer->id == 0
|
||||
? $gcodegen->config->get_at('first_layer_temperature', $gcodegen->writer->extruder->id)
|
||||
: $gcodegen->config->get_at('temperature', $gcodegen->writer->extruder->id);
|
||||
$gcode .= $gcodegen->writer->set_temperature($temp, 1);
|
||||
}
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
package Slic3r::GCode::Wipe;
|
||||
use Moo;
|
||||
|
||||
use Slic3r::Geometry qw(scale);
|
||||
|
||||
has 'enable' => (is => 'rw', default => sub { 0 });
|
||||
has 'path' => (is => 'rw');
|
||||
|
||||
sub wipe {
|
||||
my ($self, $gcodegen, $toolchange) = @_;
|
||||
|
||||
my $gcode = "";
|
||||
|
||||
# Reduce feedrate a bit; travel speed is often too high to move on existing material.
|
||||
# Too fast = ripping of existing material; too slow = short wipe path, thus more blob.
|
||||
my $wipe_speed = $gcodegen->writer->config->get('travel_speed') * 0.8;
|
||||
|
||||
# get the retraction length
|
||||
my $length = $toolchange
|
||||
? $gcodegen->writer->extruder->retract_length_toolchange
|
||||
: $gcodegen->writer->extruder->retract_length;
|
||||
|
||||
if ($length) {
|
||||
# Calculate how long we need to travel in order to consume the required
|
||||
# amount of retraction. In other words, how far do we move in XY at $wipe_speed
|
||||
# for the time needed to consume retract_length at retract_speed?
|
||||
my $wipe_dist = scale($length / $gcodegen->writer->extruder->retract_speed * $wipe_speed);
|
||||
|
||||
# Take the stored wipe path and replace first point with the current actual position
|
||||
# (they might be different, for example, in case of loop clipping).
|
||||
my $wipe_path = Slic3r::Polyline->new(
|
||||
$gcodegen->last_pos,
|
||||
@{$self->path}[1..$#{$self->path}],
|
||||
);
|
||||
#
|
||||
$wipe_path->clip_end($wipe_path->length - $wipe_dist);
|
||||
|
||||
# subdivide the retraction in segments
|
||||
my $retracted = 0;
|
||||
foreach my $line (@{$wipe_path->lines}) {
|
||||
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 (TODO: test and/or better math for this)
|
||||
my $dE = $length * ($segment_length / $wipe_dist) * 0.95;
|
||||
$gcode .= $gcodegen->writer->set_speed($wipe_speed*60);
|
||||
$gcode .= $gcodegen->writer->extrude_to_xy(
|
||||
$gcodegen->point_to_gcode($line->b),
|
||||
-$dE,
|
||||
'wipe and retract' . ($gcodegen->enable_cooling_markers ? ';_WIPE' : ''),
|
||||
);
|
||||
$retracted += $dE;
|
||||
}
|
||||
$gcodegen->writer->extruder->set_retracted($gcodegen->writer->extruder->retracted + $retracted);
|
||||
|
||||
# prevent wiping again on same path
|
||||
$self->path(undef);
|
||||
}
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
package Slic3r::GCode::AvoidCrossingPerimeters;
|
||||
use Moo;
|
||||
|
||||
has '_external_mp' => (is => 'rw');
|
||||
has '_layer_mp' => (is => 'rw');
|
||||
has 'use_external_mp' => (is => 'rw', default => sub {0});
|
||||
has 'use_external_mp_once' => (is => 'rw', default => sub {0}); # this flag triggers the use of the external configuration space for avoid_crossing_perimeters for the next travel move
|
||||
|
||||
# this flag disables avoid_crossing_perimeters just for the next travel move
|
||||
# we enable it by default for the first travel move in print
|
||||
has 'disable_once' => (is => 'rw', default => sub {1});
|
||||
|
||||
sub init_external_mp {
|
||||
my ($self, $islands) = @_;
|
||||
$self->_external_mp(Slic3r::MotionPlanner->new($islands));
|
||||
}
|
||||
|
||||
sub init_layer_mp {
|
||||
my ($self, $islands) = @_;
|
||||
$self->_layer_mp(Slic3r::MotionPlanner->new($islands));
|
||||
}
|
||||
|
||||
sub travel_to {
|
||||
my ($self, $gcodegen, $point) = @_;
|
||||
|
||||
if ($self->use_external_mp || $self->use_external_mp_once) {
|
||||
# get current origin set in $gcodegen
|
||||
# (the one that will be used to translate the G-code coordinates by)
|
||||
my $scaled_origin = Slic3r::Point->new_scale(@{$gcodegen->origin});
|
||||
|
||||
# represent last_pos in absolute G-code coordinates
|
||||
my $last_pos = $gcodegen->last_pos->clone;
|
||||
$last_pos->translate(@$scaled_origin);
|
||||
|
||||
# represent $point in absolute G-code coordinates
|
||||
$point = $point->clone;
|
||||
$point->translate(@$scaled_origin);
|
||||
# calculate path
|
||||
my $travel = $self->_external_mp->shortest_path($last_pos, $point);
|
||||
|
||||
# translate the path back into the shifted coordinate system that $gcodegen
|
||||
# is currently using for writing coordinates
|
||||
$travel->translate(@{$scaled_origin->negative});
|
||||
return $travel;
|
||||
} else {
|
||||
return $self->_layer_mp->shortest_path($gcodegen->last_pos, $point);
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
|
@ -27,7 +27,7 @@ sub append {
|
|||
$self->last_z->{$obj_id} = $print_z;
|
||||
$self->gcode($self->gcode . $gcode);
|
||||
$self->elapsed_time($self->elapsed_time + $self->gcodegen->elapsed_time);
|
||||
$self->gcodegen->elapsed_time(0);
|
||||
$self->gcodegen->set_elapsed_time(0);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
package Slic3r::GCode::PlaceholderParser;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub new {
|
||||
# TODO: move this code to C++ constructor, remove this method
|
||||
my ($class) = @_;
|
||||
|
||||
my $self = $class->_new;
|
||||
$self->apply_env_variables;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub apply_env_variables {
|
||||
my ($self) = @_;
|
||||
$self->_single_set($_, $ENV{$_}) for grep /^SLIC3R_/, keys %ENV;
|
||||
}
|
||||
|
||||
sub process {
|
||||
my ($self, $string, $extra) = @_;
|
||||
|
||||
# extra variables have priority over the stored ones
|
||||
if ($extra) {
|
||||
my $regex = join '|', keys %$extra;
|
||||
$string =~ s/\[($regex)\]/$extra->{$1}/eg;
|
||||
}
|
||||
{
|
||||
my $regex = join '|', @{$self->_single_keys};
|
||||
$string =~ s/\[($regex)\]/$self->_single_get("$1")/eg;
|
||||
}
|
||||
{
|
||||
my $regex = join '|', @{$self->_multiple_keys};
|
||||
$string =~ s/\[($regex)\]/$self->_multiple_get("$1")/egx;
|
||||
|
||||
# unhandled indices are populated using the first value
|
||||
$string =~ s/\[($regex)_\d+\]/$self->_multiple_get("$1")/egx;
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
1;
|
|
@ -43,7 +43,7 @@ sub process {
|
|||
my $rel_flow_rate = $info->{dist_E} / $info->{dist_XY};
|
||||
|
||||
# Then calculate absolute flow rate (mm/sec of feedstock)
|
||||
my $flow_rate = $rel_flow_rate * $args->{F} / 60;
|
||||
my $flow_rate = $rel_flow_rate * $F / 60;
|
||||
|
||||
# And finally calculate advance by using the user-configured K factor.
|
||||
my $new_advance = $self->config->pressure_advance * ($flow_rate**2);
|
||||
|
|
|
@ -3,8 +3,8 @@ use strict;
|
|||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(:font :html :misc :dialog :sizer :systemsettings);
|
||||
use Wx::Event qw(EVT_HTML_LINK_CLICKED);
|
||||
use Wx qw(:font :html :misc :dialog :sizer :systemsettings :frame :id);
|
||||
use Wx::Event qw(EVT_HTML_LINK_CLICKED EVT_LEFT_DOWN EVT_BUTTON);
|
||||
use Wx::Print;
|
||||
use Wx::Html;
|
||||
use base 'Wx::Dialog';
|
||||
|
@ -12,7 +12,7 @@ use base 'Wx::Dialog';
|
|||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 300], &Wx::wxCLOSE_BOX);
|
||||
my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 340], wxCAPTION);
|
||||
|
||||
$self->SetBackgroundColour(Wx::wxWHITE);
|
||||
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
|
@ -65,7 +65,18 @@ sub new {
|
|||
$html->SetPage($text);
|
||||
$vsizer->Add($html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 20);
|
||||
EVT_HTML_LINK_CLICKED($self, $html, \&link_clicked);
|
||||
|
||||
|
||||
my $buttons = $self->CreateStdDialogButtonSizer(wxCLOSE);
|
||||
$self->SetEscapeId(wxID_CLOSE);
|
||||
EVT_BUTTON($self, wxID_CLOSE, sub {
|
||||
$self->EndModal(wxID_CLOSE);
|
||||
$self->Close;
|
||||
});
|
||||
$vsizer->Add($buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
|
||||
|
||||
EVT_LEFT_DOWN($self, sub { $self->Close });
|
||||
EVT_LEFT_DOWN($logo, sub { $self->Close });
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ use Wx qw(:frame :bitmap :id :misc :notebook :panel :sizer :menu :dialog :filedi
|
|||
use Wx::Event qw(EVT_CLOSE EVT_MENU);
|
||||
use base 'Wx::Frame';
|
||||
|
||||
our $last_input_file;
|
||||
our $last_output_file;
|
||||
our $qs_last_input_file;
|
||||
our $qs_last_output_file;
|
||||
our $last_config;
|
||||
|
||||
sub new {
|
||||
|
@ -197,7 +197,8 @@ sub _init_menubar {
|
|||
$self->repair_stl;
|
||||
}, undef, 'wrench.png');
|
||||
$fileMenu->AppendSeparator();
|
||||
$self->_append_menu_item($fileMenu, "Preferences…", 'Application preferences', sub {
|
||||
# Cmd+, is standard on OS X - what about other operating systems?
|
||||
$self->_append_menu_item($fileMenu, "Preferences…\tCtrl+,", 'Application preferences', sub {
|
||||
Slic3r::GUI::Preferences->new($self)->ShowModal;
|
||||
}, wxID_PREFERENCES);
|
||||
$fileMenu->AppendSeparator();
|
||||
|
@ -321,19 +322,19 @@ sub quick_slice {
|
|||
}
|
||||
$input_file = Slic3r::decode_path($dialog->GetPaths);
|
||||
$dialog->Destroy;
|
||||
$last_input_file = $input_file unless $params{export_svg};
|
||||
$qs_last_input_file = $input_file unless $params{export_svg};
|
||||
} else {
|
||||
if (!defined $last_input_file) {
|
||||
if (!defined $qs_last_input_file) {
|
||||
Wx::MessageDialog->new($self, "No previously sliced file.",
|
||||
'Error', wxICON_ERROR | wxOK)->ShowModal();
|
||||
return;
|
||||
}
|
||||
if (! -e $last_input_file) {
|
||||
Wx::MessageDialog->new($self, "Previously sliced file ($last_input_file) not found.",
|
||||
if (! -e $qs_last_input_file) {
|
||||
Wx::MessageDialog->new($self, "Previously sliced file ($qs_last_input_file) not found.",
|
||||
'File Not Found', wxICON_ERROR | wxOK)->ShowModal();
|
||||
return;
|
||||
}
|
||||
$input_file = $last_input_file;
|
||||
$input_file = $qs_last_input_file;
|
||||
}
|
||||
my $input_file_basename = basename($input_file);
|
||||
$Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file);
|
||||
|
@ -368,19 +369,19 @@ sub quick_slice {
|
|||
# select output file
|
||||
my $output_file;
|
||||
if ($params{reslice}) {
|
||||
$output_file = $last_output_file if defined $last_output_file;
|
||||
$output_file = $qs_last_output_file if defined $qs_last_output_file;
|
||||
} elsif ($params{save_as}) {
|
||||
$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:',
|
||||
wxTheApp->output_path(dirname($output_file)),
|
||||
basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE);
|
||||
basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
if ($dlg->ShowModal != wxID_OK) {
|
||||
$dlg->Destroy;
|
||||
return;
|
||||
}
|
||||
$output_file = Slic3r::decode_path($dlg->GetPath);
|
||||
$last_output_file = $output_file unless $params{export_svg};
|
||||
$qs_last_output_file = $output_file unless $params{export_svg};
|
||||
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($output_file);
|
||||
wxTheApp->save_settings;
|
||||
$dlg->Destroy;
|
||||
|
|
|
@ -137,6 +137,7 @@ sub BUILD {
|
|||
$self->wxWindow($field);
|
||||
|
||||
EVT_SPINCTRL($self->parent, $field, sub {
|
||||
$self->tmp_value(undef);
|
||||
$self->_on_change($self->option->opt_id);
|
||||
});
|
||||
EVT_TEXT($self->parent, $field, sub {
|
||||
|
@ -147,11 +148,14 @@ sub BUILD {
|
|||
# gets the old one, and on_kill_focus resets the control to the old value.
|
||||
# As a workaround, we get the new value from $event->GetString and store
|
||||
# here temporarily so that we can return it from $self->get_value
|
||||
$self->tmp_value($event->GetString);
|
||||
$self->tmp_value($event->GetString) if $event->GetString =~ /^\d+$/;
|
||||
$self->_on_change($self->option->opt_id);
|
||||
$self->tmp_value(undef);
|
||||
# We don't reset tmp_value here because _on_change might put callbacks
|
||||
# in the CallAfter queue, and we want the tmp value to be available from
|
||||
# them as well.
|
||||
});
|
||||
EVT_KILL_FOCUS($field, sub {
|
||||
$self->tmp_value(undef);
|
||||
$self->_on_kill_focus($self->option->opt_id, @_);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -337,7 +337,13 @@ sub new {
|
|||
$text->SetFont($Slic3r::GUI::small_font);
|
||||
my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY);
|
||||
$self->{preset_choosers}{$group} = [$choice];
|
||||
EVT_COMBOBOX($choice, $choice, sub { $self->_on_select_preset($group, @_) });
|
||||
# setup the listener
|
||||
EVT_COMBOBOX($choice, $choice, sub {
|
||||
my ($choice) = @_;
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->_on_select_preset($group, $choice);
|
||||
});
|
||||
});
|
||||
$presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
|
||||
$presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 0);
|
||||
}
|
||||
|
@ -431,7 +437,9 @@ sub _on_select_preset {
|
|||
wxTheApp->save_settings;
|
||||
return;
|
||||
}
|
||||
$self->{on_select_preset}->($group, $choice->GetSelection)
|
||||
|
||||
# call GetSelection() in scalar context as it's context-aware
|
||||
$self->{on_select_preset}->($group, scalar $choice->GetSelection)
|
||||
if $self->{on_select_preset};
|
||||
|
||||
# get new config and generate on_config_change() event for updating plater and other things
|
||||
|
@ -445,10 +453,16 @@ sub GetFrame {
|
|||
|
||||
sub update_presets {
|
||||
my $self = shift;
|
||||
my ($group, $presets, $selected) = @_;
|
||||
my ($group, $presets, $selected, $is_dirty) = @_;
|
||||
|
||||
foreach my $choice (@{ $self->{preset_choosers}{$group} }) {
|
||||
my $sel = $choice->GetSelection;
|
||||
my @choosers = @{ $self->{preset_choosers}{$group} };
|
||||
foreach my $choice (@choosers) {
|
||||
if ($group eq 'filament' && @choosers > 1) {
|
||||
# if we have more than one filament chooser, keep our selection
|
||||
# instead of importing the one from the tab
|
||||
$selected = $choice->GetSelection;
|
||||
$is_dirty = 0;
|
||||
}
|
||||
$choice->Clear;
|
||||
foreach my $preset (@$presets) {
|
||||
my $bitmap;
|
||||
|
@ -471,15 +485,23 @@ sub update_presets {
|
|||
}
|
||||
$choice->AppendString($preset->name, $bitmap);
|
||||
}
|
||||
$choice->SetSelection($sel) if $sel <= $#$presets;
|
||||
|
||||
if ($selected <= $#$presets) {
|
||||
if ($is_dirty) {
|
||||
$choice->SetString($selected, $choice->GetString($selected) . " (modified)");
|
||||
}
|
||||
# call SetSelection() only after SetString() otherwise the new string
|
||||
# won't be picked up as the visible string
|
||||
$choice->SetSelection($selected);
|
||||
}
|
||||
}
|
||||
$self->{preset_choosers}{$group}[0]->SetSelection($selected);
|
||||
}
|
||||
|
||||
sub filament_presets {
|
||||
my $self = shift;
|
||||
|
||||
return map $_->GetSelection, @{ $self->{preset_choosers}{filament} };
|
||||
# force scalar context for GetSelection() as it's context-aware
|
||||
return map scalar($_->GetSelection), @{ $self->{preset_choosers}{filament} };
|
||||
}
|
||||
|
||||
sub add {
|
||||
|
@ -1083,7 +1105,7 @@ sub export_gcode {
|
|||
} else {
|
||||
my $default_output_file = $self->{print}->expanded_output_filepath($main::opt{output});
|
||||
my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', wxTheApp->output_path(dirname($default_output_file)),
|
||||
basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE);
|
||||
basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
if ($dlg->ShowModal != wxID_OK) {
|
||||
$dlg->Destroy;
|
||||
return;
|
||||
|
@ -1091,7 +1113,7 @@ sub export_gcode {
|
|||
my $path = Slic3r::decode_path($dlg->GetPath);
|
||||
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($path);
|
||||
wxTheApp->save_settings;
|
||||
$self->{export_gcode_output_file} = $Slic3r::GUI::MainFrame::last_output_file = $path;
|
||||
$self->{export_gcode_output_file} = $path;
|
||||
$dlg->Destroy;
|
||||
}
|
||||
|
||||
|
@ -1239,13 +1261,15 @@ sub send_gcode {
|
|||
my $ua = LWP::UserAgent->new;
|
||||
$ua->timeout(180);
|
||||
|
||||
my $path = Slic3r::encode_path($self->{send_gcode_file});
|
||||
my $res = $ua->post(
|
||||
"http://" . $self->{config}->octoprint_host . "/api/files/local",
|
||||
Content_Type => 'form-data',
|
||||
'X-Api-Key' => $self->{config}->octoprint_apikey,
|
||||
Content => [
|
||||
# OctoPrint doesn't like Windows paths
|
||||
file => [ $self->{send_gcode_file}, basename($self->{send_gcode_file}) ],
|
||||
# OctoPrint doesn't like Windows paths so we use basename()
|
||||
# Also, since we need to read from filesystem we process it through encode_path()
|
||||
file => [ $path, basename($path) ],
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -1315,7 +1339,7 @@ sub _get_export_file {
|
|||
$dlg->Destroy;
|
||||
return undef;
|
||||
}
|
||||
$output_file = $Slic3r::GUI::MainFrame::last_output_file = Slic3r::decode_path($dlg->GetPath);
|
||||
$output_file = Slic3r::decode_path($dlg->GetPath);
|
||||
$dlg->Destroy;
|
||||
}
|
||||
return $output_file;
|
||||
|
@ -1383,14 +1407,34 @@ sub on_extruders_change {
|
|||
|
||||
my $choices = $self->{preset_choosers}{filament};
|
||||
while (@$choices < $num_extruders) {
|
||||
# copy strings from first choice
|
||||
my @presets = $choices->[0]->GetStrings;
|
||||
push @$choices, Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY);
|
||||
|
||||
# initialize new choice
|
||||
my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY);
|
||||
push @$choices, $choice;
|
||||
|
||||
# copy icons from first choice
|
||||
$choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets;
|
||||
|
||||
# insert new choice into sizer
|
||||
$self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0);
|
||||
$self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choices->[-1], 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING);
|
||||
EVT_COMBOBOX($choices->[-1], $choices->[-1], sub { $self->_on_select_preset('filament', @_) });
|
||||
my $i = first { $choices->[-1]->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets;
|
||||
$choices->[-1]->SetSelection($i || 0);
|
||||
$self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING);
|
||||
|
||||
# setup the listener
|
||||
EVT_COMBOBOX($choice, $choice, sub {
|
||||
my ($choice) = @_;
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->_on_select_preset('filament', $choice);
|
||||
});
|
||||
});
|
||||
|
||||
# initialize selection
|
||||
my $i = first { $choice->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets;
|
||||
$choice->SetSelection($i || 0);
|
||||
}
|
||||
|
||||
# remove unused choices if any
|
||||
while (@$choices > $num_extruders) {
|
||||
$self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # label
|
||||
$self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # wxChoice
|
||||
|
|
|
@ -164,6 +164,8 @@ sub new {
|
|||
EVT_MOUSEWHEEL($self, sub {
|
||||
my ($self, $e) = @_;
|
||||
|
||||
return if !$self->GetParent->enabled;
|
||||
|
||||
my $old_zoom = $self->_zoom;
|
||||
|
||||
# Calculate the zoom delta and apply it to the current zoom factor
|
||||
|
@ -205,6 +207,8 @@ sub new {
|
|||
sub mouse_event {
|
||||
my ($self, $e) = @_;
|
||||
|
||||
return if !$self->GetParent->enabled;
|
||||
|
||||
my $pos = Slic3r::Pointf->new($e->GetPositionXY);
|
||||
if ($e->Entering && &Wx::wxMSW) {
|
||||
# wxMSW needs focus in order to catch mouse wheel events
|
||||
|
@ -262,8 +266,9 @@ sub set_z {
|
|||
}
|
||||
}
|
||||
|
||||
# reverse layers so that we draw the lowermost (i.e. current) on top
|
||||
$self->z($z);
|
||||
$self->layers([ @layers ]);
|
||||
$self->layers([ reverse @layers ]);
|
||||
$self->Refresh;
|
||||
}
|
||||
|
||||
|
|
|
@ -107,12 +107,17 @@ sub new {
|
|||
$grid->SetCellValue($event->GetRow, $event->GetCol, $value);
|
||||
|
||||
# if there's no empty row, let's append one
|
||||
for my $i (0 .. $grid->GetNumberRows-1) {
|
||||
for my $i (0 .. $grid->GetNumberRows) {
|
||||
if ($i == $grid->GetNumberRows) {
|
||||
# if we're here then we found no empty row
|
||||
$grid->AppendRows(1);
|
||||
last;
|
||||
}
|
||||
if (!grep $grid->GetCellValue($i, $_), 0..2) {
|
||||
return;
|
||||
# exit loop if this row is empty
|
||||
last;
|
||||
}
|
||||
}
|
||||
$grid->AppendRows(1);
|
||||
|
||||
$self->{layers_changed} = 1;
|
||||
});
|
||||
|
|
|
@ -65,7 +65,9 @@ sub new {
|
|||
$self->{treectrl}->AddRoot("root");
|
||||
$self->{pages} = [];
|
||||
$self->{treectrl}->SetIndent(0);
|
||||
$self->{disable_tree_sel_changed_event} = 0;
|
||||
EVT_TREE_SEL_CHANGED($parent, $self->{treectrl}, sub {
|
||||
return if $self->{disable_tree_sel_changed_event};
|
||||
my $page = first { $_->{title} eq $self->{treectrl}->GetItemText($self->{treectrl}->GetSelection) } @{$self->{pages}}
|
||||
or return;
|
||||
$_->Hide for @{$self->{pages}};
|
||||
|
@ -106,6 +108,7 @@ sub new {
|
|||
|
||||
$self->{config} = Slic3r::Config->new;
|
||||
$self->build;
|
||||
$self->update_tree;
|
||||
$self->_update;
|
||||
if ($self->hidden_options) {
|
||||
$self->{config}->apply(Slic3r::Config->new_from_defaults($self->hidden_options));
|
||||
|
@ -177,8 +180,11 @@ sub _update {}
|
|||
sub _on_presets_changed {
|
||||
my $self = shift;
|
||||
|
||||
$self->{on_presets_changed}->($self->{presets}, $self->{presets_choice}->GetSelection)
|
||||
if $self->{on_presets_changed};
|
||||
$self->{on_presets_changed}->(
|
||||
$self->{presets},
|
||||
scalar($self->{presets_choice}->GetSelection),
|
||||
$self->is_dirty,
|
||||
) if $self->{on_presets_changed};
|
||||
}
|
||||
|
||||
sub on_preset_loaded {}
|
||||
|
@ -198,6 +204,8 @@ sub select_preset {
|
|||
|
||||
sub select_preset_by_name {
|
||||
my ($self, $name) = @_;
|
||||
|
||||
$name = Unicode::Normalize::NFC($name);
|
||||
$self->select_preset(first { $self->{presets}[$_]->name eq $name } 0 .. $#{$self->{presets}});
|
||||
}
|
||||
|
||||
|
@ -223,6 +231,10 @@ sub on_select_preset {
|
|||
'Unsaved Changes', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
|
||||
if ($confirm->ShowModal == wxID_NO) {
|
||||
$self->{presets_choice}->SetSelection($self->current_preset);
|
||||
|
||||
# trigger the on_presets_changed event so that we also restore the previous value
|
||||
# in the plater selector
|
||||
$self->_on_presets_changed;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -282,7 +294,6 @@ sub add_options_page {
|
|||
$page->Hide;
|
||||
$self->{sizer}->Add($page, 1, wxEXPAND | wxLEFT, 5);
|
||||
push @{$self->{pages}}, $page;
|
||||
$self->update_tree;
|
||||
return $page;
|
||||
}
|
||||
|
||||
|
@ -303,12 +314,15 @@ sub update_tree {
|
|||
foreach my $page (@{$self->{pages}}) {
|
||||
my $itemId = $self->{treectrl}->AppendItem($rootItem, $page->{title}, $page->{iconID});
|
||||
if ($page->{title} eq $selected) {
|
||||
$self->{disable_tree_sel_changed_event} = 1;
|
||||
$self->{treectrl}->SelectItem($itemId);
|
||||
$self->{disable_tree_sel_changed_event} = 0;
|
||||
$have_selection = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$have_selection) {
|
||||
# this is triggered on first load, so we don't disable the sel change event
|
||||
$self->{treectrl}->SelectItem($self->{treectrl}->GetFirstChild($rootItem));
|
||||
}
|
||||
}
|
||||
|
@ -431,6 +445,7 @@ sub set_value {
|
|||
package Slic3r::GUI::Tab::Print;
|
||||
use base 'Slic3r::GUI::Tab';
|
||||
|
||||
use List::Util qw(first);
|
||||
use Wx qw(:icon :dialog :id);
|
||||
|
||||
sub name { 'print' }
|
||||
|
@ -449,6 +464,7 @@ sub build {
|
|||
infill_every_layers infill_only_where_needed
|
||||
solid_infill_every_layers fill_angle solid_infill_below_area
|
||||
only_retract_when_crossing_perimeters infill_first
|
||||
max_print_speed max_volumetric_speed
|
||||
perimeter_speed small_perimeter_speed external_perimeter_speed infill_speed
|
||||
solid_infill_speed top_solid_infill_speed support_material_speed
|
||||
support_material_interface_speed bridge_speed gap_fill_speed
|
||||
|
@ -606,6 +622,11 @@ sub build {
|
|||
$optgroup->append_single_option_line('first_layer_acceleration');
|
||||
$optgroup->append_single_option_line('default_acceleration');
|
||||
}
|
||||
{
|
||||
my $optgroup = $page->new_optgroup('Autospeed (advanced)');
|
||||
$optgroup->append_single_option_line('max_print_speed');
|
||||
$optgroup->append_single_option_line('max_volumetric_speed');
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -738,6 +759,22 @@ sub _update {
|
|||
}
|
||||
}
|
||||
|
||||
if ($config->fill_density == 100
|
||||
&& !first { $_ eq $config->fill_pattern } @{$Slic3r::Config::Options->{external_fill_pattern}{values}}) {
|
||||
my $dialog = Wx::MessageDialog->new($self,
|
||||
"The " . $config->fill_pattern . " infill pattern is not supposed to work at 100% density.\n"
|
||||
. "\nShall I switch to rectilinear fill pattern?",
|
||||
'Infill', wxICON_WARNING | wxYES | wxNO);
|
||||
|
||||
my $new_conf = Slic3r::Config->new;
|
||||
if ($dialog->ShowModal() == wxID_YES) {
|
||||
$new_conf->set("fill_pattern", 1);
|
||||
} else {
|
||||
$new_conf->set("fill_density", 40);
|
||||
}
|
||||
$self->load_config($new_conf);
|
||||
}
|
||||
|
||||
my $have_perimeters = $config->perimeters > 0;
|
||||
$self->get_field($_)->toggle($have_perimeters)
|
||||
for qw(extra_perimeters thin_walls overhangs seam_position external_perimeters_first
|
||||
|
@ -1016,8 +1053,10 @@ sub build {
|
|||
$optgroup->on_change(sub {
|
||||
my ($opt_id) = @_;
|
||||
if ($opt_id eq 'extruders_count') {
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->_extruders_count_changed($optgroup->get_value('extruders_count'));
|
||||
});
|
||||
$self->update_dirty;
|
||||
$self->_extruders_count_changed($optgroup->get_value('extruders_count'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1231,8 +1270,6 @@ sub _build_extruder_pages {
|
|||
$optgroup->append_single_option_line($_, $extruder_idx)
|
||||
for qw(retract_length_toolchange retract_restart_extra_toolchange);
|
||||
}
|
||||
|
||||
$self->{extruder_pages}[$extruder_idx]{disabled} = 0;
|
||||
}
|
||||
|
||||
# remove extra pages
|
||||
|
@ -1325,7 +1362,7 @@ sub load_config_file {
|
|||
}
|
||||
|
||||
package Slic3r::GUI::Tab::Page;
|
||||
use Wx qw(:misc :panel :sizer);
|
||||
use Wx qw(wxTheApp :misc :panel :sizer);
|
||||
use base 'Wx::ScrolledWindow';
|
||||
|
||||
sub new {
|
||||
|
@ -1353,8 +1390,11 @@ sub new_optgroup {
|
|||
config => $self->GetParent->{config},
|
||||
label_width => $params{label_width} // 200,
|
||||
on_change => sub {
|
||||
$self->GetParent->update_dirty;
|
||||
$self->GetParent->_on_value_change(@_);
|
||||
my ($opt_key, $value) = @_;
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->GetParent->update_dirty;
|
||||
$self->GetParent->_on_value_change($opt_key, $value);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -1454,7 +1494,7 @@ sub config {
|
|||
if ($self->default) {
|
||||
return Slic3r::Config->new_from_defaults(@$keys);
|
||||
} else {
|
||||
if (!-e $self->file) {
|
||||
if (!-e Slic3r::encode_path($self->file)) {
|
||||
Slic3r::GUI::show_error(undef, "The selected preset does not exist anymore (" . $self->file . ").");
|
||||
return undef;
|
||||
}
|
||||
|
|
|
@ -41,24 +41,25 @@ sub make_perimeters {
|
|||
|
||||
for my $region_id (0..$#{$self->regions}) {
|
||||
next if $done{$region_id};
|
||||
my $layerm = $self->regions->[$region_id];
|
||||
my $layerm = $self->get_region($region_id);
|
||||
my $config = $layerm->region->config;
|
||||
$done{$region_id} = 1;
|
||||
|
||||
# find compatible regions
|
||||
my @layerms = ($layerm);
|
||||
for my $i (($region_id+1)..$#{$self->regions}) {
|
||||
my $config = $self->regions->[$i]->config;
|
||||
my $layerm_config = $layerm->config;
|
||||
my $other_layerm = $self->get_region($i);
|
||||
my $other_config = $other_layerm->region->config;
|
||||
|
||||
if ($config->perimeter_extruder == $layerm_config->perimeter_extruder
|
||||
&& $config->perimeters == $layerm_config->perimeters
|
||||
&& $config->perimeter_speed == $layerm_config->perimeter_speed
|
||||
&& $config->gap_fill_speed == $layerm_config->gap_fill_speed
|
||||
&& $config->overhangs == $layerm_config->overhangs
|
||||
&& $config->perimeter_extrusion_width == $layerm_config->perimeter_extrusion_width
|
||||
&& $config->thin_walls == $layerm_config->thin_walls
|
||||
&& $config->external_perimeters_first == $layerm_config->external_perimeters_first) {
|
||||
push @layerms, $self->regions->[$i];
|
||||
if ($config->perimeter_extruder == $other_config->perimeter_extruder
|
||||
&& $config->perimeters == $other_config->perimeters
|
||||
&& $config->perimeter_speed == $other_config->perimeter_speed
|
||||
&& $config->gap_fill_speed == $other_config->gap_fill_speed
|
||||
&& $config->overhangs == $other_config->overhangs
|
||||
&& $config->perimeter_extrusion_width == $other_config->perimeter_extrusion_width
|
||||
&& $config->thin_walls == $other_config->thin_walls
|
||||
&& $config->external_perimeters_first == $other_config->external_perimeters_first) {
|
||||
push @layerms, $other_layerm;
|
||||
$done{$i} = 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,532 +0,0 @@
|
|||
package Slic3r::Layer::PerimeterGenerator;
|
||||
use Moo;
|
||||
|
||||
use Slic3r::ExtrusionLoop ':roles';
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Geometry qw(scale unscale chained_path);
|
||||
use Slic3r::Geometry::Clipper qw(union_ex diff diff_ex intersection_ex offset offset2
|
||||
offset_ex offset2_ex intersection_ppl diff_ppl);
|
||||
use Slic3r::Surface ':types';
|
||||
|
||||
has 'slices' => (is => 'ro', required => 1); # SurfaceCollection
|
||||
has 'lower_slices' => (is => 'ro', required => 0);
|
||||
has 'layer_height' => (is => 'ro', required => 1);
|
||||
has 'layer_id' => (is => 'ro', required => 0, default => sub { -1 });
|
||||
has 'perimeter_flow' => (is => 'ro', required => 1);
|
||||
has 'ext_perimeter_flow' => (is => 'ro', required => 1);
|
||||
has 'overhang_flow' => (is => 'ro', required => 1);
|
||||
has 'solid_infill_flow' => (is => 'ro', required => 1);
|
||||
has 'config' => (is => 'ro', default => sub { Slic3r::Config::PrintRegion->new });
|
||||
has 'object_config' => (is => 'ro', default => sub { Slic3r::Config::PrintObject->new });
|
||||
has 'print_config' => (is => 'ro', default => sub { Slic3r::Config::Print->new });
|
||||
has '_lower_slices_p' => (is => 'rw', default => sub { [] });
|
||||
has '_holes_pt' => (is => 'rw');
|
||||
has '_ext_mm3_per_mm' => (is => 'rw');
|
||||
has '_mm3_per_mm' => (is => 'rw');
|
||||
has '_mm3_per_mm_overhang' => (is => 'rw');
|
||||
has '_thin_wall_polylines' => (is => 'rw', default => sub { [] });
|
||||
|
||||
# generated loops will be put here
|
||||
has 'loops' => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Collection->new });
|
||||
|
||||
# generated gap fills will be put here
|
||||
has 'gap_fill' => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Collection->new });
|
||||
|
||||
# generated fill surfaces will be put here
|
||||
has 'fill_surfaces' => (is => 'ro', default => sub { Slic3r::Surface::Collection->new });
|
||||
|
||||
sub BUILDARGS {
|
||||
my ($class, %args) = @_;
|
||||
|
||||
if (my $flow = delete $args{flow}) {
|
||||
$args{perimeter_flow} //= $flow;
|
||||
$args{ext_perimeter_flow} //= $flow;
|
||||
$args{overhang_flow} //= $flow;
|
||||
$args{solid_infill_flow} //= $flow;
|
||||
}
|
||||
|
||||
return { %args };
|
||||
}
|
||||
|
||||
sub process {
|
||||
my ($self) = @_;
|
||||
|
||||
# other perimeters
|
||||
$self->_mm3_per_mm($self->perimeter_flow->mm3_per_mm);
|
||||
my $pwidth = $self->perimeter_flow->scaled_width;
|
||||
my $pspacing = $self->perimeter_flow->scaled_spacing;
|
||||
|
||||
# external perimeters
|
||||
$self->_ext_mm3_per_mm($self->ext_perimeter_flow->mm3_per_mm);
|
||||
my $ext_pwidth = $self->ext_perimeter_flow->scaled_width;
|
||||
my $ext_pspacing = scale($self->ext_perimeter_flow->spacing_to($self->perimeter_flow));
|
||||
|
||||
# overhang perimeters
|
||||
$self->_mm3_per_mm_overhang($self->overhang_flow->mm3_per_mm);
|
||||
|
||||
# solid infill
|
||||
my $ispacing = $self->solid_infill_flow->scaled_spacing;
|
||||
my $gap_area_threshold = $pwidth ** 2;
|
||||
|
||||
# Calculate the minimum required spacing between two adjacent traces.
|
||||
# This should be equal to the nominal flow spacing but we experiment
|
||||
# with some tolerance in order to avoid triggering medial axis when
|
||||
# some squishing might work. Loops are still spaced by the entire
|
||||
# flow spacing; this only applies to collapsing parts.
|
||||
my $min_spacing = $pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
|
||||
my $ext_min_spacing = $ext_pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
|
||||
|
||||
# prepare grown lower layer slices for overhang detection
|
||||
if ($self->lower_slices && $self->config->overhangs) {
|
||||
# We consider overhang any part where the entire nozzle diameter is not supported by the
|
||||
# lower layer, so we take lower slices and offset them by half the nozzle diameter used
|
||||
# in the current layer
|
||||
my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $self->config->perimeter_extruder-1);
|
||||
|
||||
$self->_lower_slices_p(
|
||||
offset([ map @$_, @{$self->lower_slices} ], scale +$nozzle_diameter/2)
|
||||
);
|
||||
}
|
||||
|
||||
# we need to process each island separately because we might have different
|
||||
# extra perimeters for each one
|
||||
foreach my $surface (@{$self->slices}) {
|
||||
# detect how many perimeters must be generated for this island
|
||||
my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0);
|
||||
$loop_number--; # 0-indexed loops
|
||||
|
||||
my @gaps = (); # Polygons
|
||||
|
||||
my @last = @{$surface->expolygon->simplify_p(&Slic3r::SCALED_RESOLUTION)};
|
||||
if ($loop_number >= 0) { # no loops = -1
|
||||
|
||||
my @contours = (); # depth => [ Polygon, Polygon ... ]
|
||||
my @holes = (); # depth => [ Polygon, Polygon ... ]
|
||||
my @thin_walls = (); # Polylines
|
||||
|
||||
# we loop one time more than needed in order to find gaps after the last perimeter was applied
|
||||
for my $i (0..($loop_number+1)) { # outer loop is 0
|
||||
my @offsets = ();
|
||||
if ($i == 0) {
|
||||
# the minimum thickness of a single loop is:
|
||||
# ext_width/2 + ext_spacing/2 + spacing/2 + width/2
|
||||
if ($self->config->thin_walls) {
|
||||
@offsets = @{offset2(
|
||||
\@last,
|
||||
-(0.5*$ext_pwidth + 0.5*$ext_min_spacing - 1),
|
||||
+(0.5*$ext_min_spacing - 1),
|
||||
)};
|
||||
} else {
|
||||
@offsets = @{offset(
|
||||
\@last,
|
||||
-0.5*$ext_pwidth,
|
||||
)};
|
||||
}
|
||||
|
||||
# look for thin walls
|
||||
if ($self->config->thin_walls) {
|
||||
my $diff = diff(
|
||||
\@last,
|
||||
offset(\@offsets, +0.5*$ext_pwidth),
|
||||
1, # medial axis requires non-overlapping geometry
|
||||
);
|
||||
|
||||
# the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
|
||||
# (actually, something larger than that still may exist due to mitering or other causes)
|
||||
my $min_width = $ext_pwidth / 4;
|
||||
@thin_walls = @{offset2_ex($diff, -$min_width/2, +$min_width/2)};
|
||||
|
||||
# the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
|
||||
@thin_walls = grep $_->length > $ext_pwidth*2,
|
||||
map @{$_->medial_axis($ext_pwidth + $ext_pspacing, $min_width)}, @thin_walls;
|
||||
Slic3r::debugf " %d thin walls detected\n", scalar(@thin_walls) if $Slic3r::debug;
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"medial_axis.svg",
|
||||
no_arrows => 1,
|
||||
expolygons => union_ex($diff),
|
||||
green_polylines => [ map $_->polygon->split_at_first_point, @{$self->perimeters} ],
|
||||
red_polylines => $self->_thin_wall_polylines,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
my $distance = ($i == 1) ? $ext_pspacing : $pspacing;
|
||||
|
||||
if ($self->config->thin_walls) {
|
||||
@offsets = @{offset2(
|
||||
\@last,
|
||||
-($distance + 0.5*$min_spacing - 1),
|
||||
+(0.5*$min_spacing - 1),
|
||||
)};
|
||||
} else {
|
||||
@offsets = @{offset(
|
||||
\@last,
|
||||
-$distance,
|
||||
)};
|
||||
}
|
||||
|
||||
# look for gaps
|
||||
if ($self->config->gap_fill_speed > 0 && $self->config->fill_density > 0) {
|
||||
# not using safety offset here would "detect" very narrow gaps
|
||||
# (but still long enough to escape the area threshold) that gap fill
|
||||
# won't be able to fill but we'd still remove from infill area
|
||||
my $diff = diff_ex(
|
||||
offset(\@last, -0.5*$distance),
|
||||
offset(\@offsets, +0.5*$distance + 10), # safety offset
|
||||
);
|
||||
push @gaps, map $_->clone, map @$_, grep abs($_->area) >= $gap_area_threshold, @$diff;
|
||||
}
|
||||
}
|
||||
|
||||
last if !@offsets;
|
||||
last if $i > $loop_number; # we were only looking for gaps this time
|
||||
|
||||
@last = @offsets;
|
||||
|
||||
$contours[$i] = [];
|
||||
$holes[$i] = [];
|
||||
foreach my $polygon (@offsets) {
|
||||
my $loop = Slic3r::Layer::PerimeterGenerator::Loop->new(
|
||||
polygon => $polygon,
|
||||
is_contour => $polygon->is_counter_clockwise,
|
||||
depth => $i,
|
||||
);
|
||||
if ($loop->is_contour) {
|
||||
push @{$contours[$i]}, $loop;
|
||||
} else {
|
||||
push @{$holes[$i]}, $loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# nest loops: holes first
|
||||
for my $d (0..$loop_number) {
|
||||
# loop through all holes having depth $d
|
||||
LOOP: for (my $i = 0; $i <= $#{$holes[$d]}; ++$i) {
|
||||
my $loop = $holes[$d][$i];
|
||||
|
||||
# find the hole loop that contains this one, if any
|
||||
for my $t (($d+1)..$loop_number) {
|
||||
for (my $j = 0; $j <= $#{$holes[$t]}; ++$j) {
|
||||
my $candidate_parent = $holes[$t][$j];
|
||||
if ($candidate_parent->polygon->contains_point($loop->polygon->first_point)) {
|
||||
$candidate_parent->add_child($loop);
|
||||
splice @{$holes[$d]}, $i, 1;
|
||||
--$i;
|
||||
next LOOP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# if no hole contains this hole, find the contour loop that contains it
|
||||
for my $t (reverse 0..$loop_number) {
|
||||
for (my $j = 0; $j <= $#{$contours[$t]}; ++$j) {
|
||||
my $candidate_parent = $contours[$t][$j];
|
||||
if ($candidate_parent->polygon->contains_point($loop->polygon->first_point)) {
|
||||
$candidate_parent->add_child($loop);
|
||||
splice @{$holes[$d]}, $i, 1;
|
||||
--$i;
|
||||
next LOOP;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# nest contour loops
|
||||
for my $d (reverse 1..$loop_number) {
|
||||
# loop through all contours having depth $d
|
||||
LOOP: for (my $i = 0; $i <= $#{$contours[$d]}; ++$i) {
|
||||
my $loop = $contours[$d][$i];
|
||||
|
||||
# find the contour loop that contains it
|
||||
for my $t (reverse 0..($d-1)) {
|
||||
for (my $j = 0; $j <= $#{$contours[$t]}; ++$j) {
|
||||
my $candidate_parent = $contours[$t][$j];
|
||||
if ($candidate_parent->polygon->contains_point($loop->polygon->first_point)) {
|
||||
$candidate_parent->add_child($loop);
|
||||
splice @{$contours[$d]}, $i, 1;
|
||||
--$i;
|
||||
next LOOP;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# at this point, all loops should be in $contours[0]
|
||||
my @entities = $self->_traverse_loops($contours[0], \@thin_walls);
|
||||
|
||||
# if brim will be printed, reverse the order of perimeters so that
|
||||
# we continue inwards after having finished the brim
|
||||
# TODO: add test for perimeter order
|
||||
@entities = reverse @entities
|
||||
if $self->config->external_perimeters_first
|
||||
|| ($self->layer_id == 0 && $self->print_config->brim_width > 0);
|
||||
|
||||
# append perimeters for this slice as a collection
|
||||
$self->loops->append(Slic3r::ExtrusionPath::Collection->new(@entities))
|
||||
if @entities;
|
||||
}
|
||||
|
||||
# fill gaps
|
||||
if (@gaps) {
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"gaps.svg",
|
||||
expolygons => union_ex(\@gaps),
|
||||
);
|
||||
}
|
||||
|
||||
# where $pwidth < thickness < 2*$pspacing, infill with width = 2*$pwidth
|
||||
# where 0.1*$pwidth < thickness < $pwidth, infill with width = 1*$pwidth
|
||||
my @gap_sizes = (
|
||||
[ $pwidth, 2*$pspacing, unscale 2*$pwidth ],
|
||||
[ 0.1*$pwidth, $pwidth, unscale 1*$pwidth ],
|
||||
);
|
||||
foreach my $gap_size (@gap_sizes) {
|
||||
my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps);
|
||||
$self->gap_fill->append($_) for @gap_fill;
|
||||
|
||||
# Make sure we don't infill narrow parts that are already gap-filled
|
||||
# (we only consider this surface's gaps to reduce the diff() complexity).
|
||||
# Growing actual extrusions ensures that gaps not filled by medial axis
|
||||
# are not subtracted from fill surfaces (they might be too short gaps
|
||||
# that medial axis skips but infill might join with other infill regions
|
||||
# and use zigzag).
|
||||
my $w = $gap_size->[2];
|
||||
my @filled = map {
|
||||
@{($_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline)
|
||||
->grow(scale $w/2)};
|
||||
} @gap_fill;
|
||||
@last = @{diff(\@last, \@filled)};
|
||||
@gaps = @{diff(\@gaps, \@filled)}; # prevent more gap fill here
|
||||
}
|
||||
}
|
||||
|
||||
# create one more offset to be used as boundary for fill
|
||||
# we offset by half the perimeter spacing (to get to the actual infill boundary)
|
||||
# and then we offset back and forth by half the infill spacing to only consider the
|
||||
# non-collapsing regions
|
||||
my $inset = 0;
|
||||
if ($loop_number == 0) {
|
||||
# one loop
|
||||
$inset += $ext_pspacing/2;
|
||||
} elsif ($loop_number > 0) {
|
||||
# two or more loops
|
||||
$inset += $pspacing/2;
|
||||
}
|
||||
$inset -= $self->config->get_abs_value_over('infill_overlap', $pwidth);
|
||||
|
||||
my $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
|
||||
$self->fill_surfaces->append($_)
|
||||
for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), # use a bogus surface type
|
||||
@{offset2_ex(
|
||||
[ map @{$_->simplify_p(&Slic3r::SCALED_RESOLUTION)}, @{union_ex(\@last)} ],
|
||||
-$inset -$min_perimeter_infill_spacing/2,
|
||||
+$min_perimeter_infill_spacing/2,
|
||||
)};
|
||||
}
|
||||
}
|
||||
|
||||
sub _traverse_loops {
|
||||
my ($self, $loops, $thin_walls) = @_;
|
||||
|
||||
# loops is an arrayref of ::Loop objects
|
||||
# turn each one into an ExtrusionLoop object
|
||||
my $coll = Slic3r::ExtrusionPath::Collection->new;
|
||||
foreach my $loop (@$loops) {
|
||||
my $is_external = $loop->is_external;
|
||||
|
||||
my ($role, $loop_role);
|
||||
if ($is_external) {
|
||||
$role = EXTR_ROLE_EXTERNAL_PERIMETER;
|
||||
} else {
|
||||
$role = EXTR_ROLE_PERIMETER;
|
||||
}
|
||||
if ($loop->is_internal_contour) {
|
||||
# Note that we set loop role to ContourInternalPerimeter
|
||||
# also when loop is both internal and external (i.e.
|
||||
# there's only one contour loop).
|
||||
$loop_role = EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER;
|
||||
} else {
|
||||
$loop_role = EXTR_ROLE_PERIMETER;
|
||||
}
|
||||
|
||||
# detect overhanging/bridging perimeters
|
||||
my @paths = ();
|
||||
if ($self->config->overhangs && $self->layer_id > 0
|
||||
&& !($self->object_config->support_material && $self->object_config->support_material_contact_distance == 0)) {
|
||||
# get non-overhang paths by intersecting this loop with the grown lower slices
|
||||
foreach my $polyline (@{ intersection_ppl([ $loop->polygon ], $self->_lower_slices_p) }) {
|
||||
push @paths, Slic3r::ExtrusionPath->new(
|
||||
polyline => $polyline,
|
||||
role => $role,
|
||||
mm3_per_mm => ($is_external ? $self->_ext_mm3_per_mm : $self->_mm3_per_mm),
|
||||
width => ($is_external ? $self->ext_perimeter_flow->width : $self->perimeter_flow->width),
|
||||
height => $self->layer_height,
|
||||
);
|
||||
}
|
||||
|
||||
# get overhang paths by checking what parts of this loop fall
|
||||
# outside the grown lower slices (thus where the distance between
|
||||
# the loop centerline and original lower slices is >= half nozzle diameter
|
||||
foreach my $polyline (@{ diff_ppl([ $loop->polygon ], $self->_lower_slices_p) }) {
|
||||
push @paths, Slic3r::ExtrusionPath->new(
|
||||
polyline => $polyline,
|
||||
role => EXTR_ROLE_OVERHANG_PERIMETER,
|
||||
mm3_per_mm => $self->_mm3_per_mm_overhang,
|
||||
width => $self->overhang_flow->width,
|
||||
height => $self->overhang_flow->height,
|
||||
);
|
||||
}
|
||||
|
||||
# reapply the nearest point search for starting point
|
||||
# (clone because the collection gets DESTROY'ed)
|
||||
# We allow polyline reversal because Clipper may have randomly
|
||||
# reversed polylines during clipping.
|
||||
my $collection = Slic3r::ExtrusionPath::Collection->new(@paths); # temporary collection
|
||||
@paths = map $_->clone, @{$collection->chained_path(0)};
|
||||
} else {
|
||||
push @paths, Slic3r::ExtrusionPath->new(
|
||||
polyline => $loop->polygon->split_at_first_point,
|
||||
role => $role,
|
||||
mm3_per_mm => ($is_external ? $self->_ext_mm3_per_mm : $self->_mm3_per_mm),
|
||||
width => ($is_external ? $self->ext_perimeter_flow->width : $self->perimeter_flow->width),
|
||||
height => $self->layer_height,
|
||||
);
|
||||
}
|
||||
my $eloop = Slic3r::ExtrusionLoop->new_from_paths(@paths);
|
||||
$eloop->role($loop_role);
|
||||
$coll->append($eloop);
|
||||
}
|
||||
|
||||
# append thin walls to the nearest-neighbor search (only for first iteration)
|
||||
if (@$thin_walls) {
|
||||
foreach my $polyline (@$thin_walls) {
|
||||
$coll->append(Slic3r::ExtrusionPath->new(
|
||||
polyline => $polyline,
|
||||
role => EXTR_ROLE_EXTERNAL_PERIMETER,
|
||||
mm3_per_mm => $self->_mm3_per_mm,
|
||||
width => $self->perimeter_flow->width,
|
||||
height => $self->layer_height,
|
||||
));
|
||||
}
|
||||
|
||||
@$thin_walls = ();
|
||||
}
|
||||
|
||||
# sort entities
|
||||
my $sorted_coll = $coll->chained_path_indices(0);
|
||||
my @indices = @{$sorted_coll->orig_indices};
|
||||
|
||||
# traverse children
|
||||
my @entities = ();
|
||||
for my $i (0..$#indices) {
|
||||
my $idx = $indices[$i];
|
||||
if ($idx > $#$loops) {
|
||||
# this is a thin wall
|
||||
# let's get it from the sorted collection as it might have been reversed
|
||||
push @entities, $sorted_coll->[$i]->clone;
|
||||
} else {
|
||||
my $loop = $loops->[$idx];
|
||||
my $eloop = $coll->[$idx]->clone;
|
||||
|
||||
my @children = $self->_traverse_loops($loop->children, $thin_walls);
|
||||
if ($loop->is_contour) {
|
||||
$eloop->make_counter_clockwise;
|
||||
push @entities, @children, $eloop;
|
||||
} else {
|
||||
$eloop->make_clockwise;
|
||||
push @entities, $eloop, @children;
|
||||
}
|
||||
}
|
||||
}
|
||||
return @entities;
|
||||
}
|
||||
|
||||
sub _fill_gaps {
|
||||
my ($self, $min, $max, $w, $gaps) = @_;
|
||||
|
||||
$min *= (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
|
||||
|
||||
my $this = diff_ex(
|
||||
offset2($gaps, -$min/2, +$min/2),
|
||||
offset2($gaps, -$max/2, +$max/2),
|
||||
1,
|
||||
);
|
||||
|
||||
my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this;
|
||||
return if !@polylines;
|
||||
|
||||
Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @$this, $w
|
||||
if @$this;
|
||||
|
||||
#my $flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL, 0, $w);
|
||||
my $flow = Slic3r::Flow->new(
|
||||
width => $w,
|
||||
height => $self->layer_height,
|
||||
nozzle_diameter => $self->solid_infill_flow->nozzle_diameter,
|
||||
);
|
||||
|
||||
my %path_args = (
|
||||
role => EXTR_ROLE_GAPFILL,
|
||||
mm3_per_mm => $flow->mm3_per_mm,
|
||||
width => $flow->width,
|
||||
height => $self->layer_height,
|
||||
);
|
||||
|
||||
my @entities = ();
|
||||
foreach my $polyline (@polylines) {
|
||||
#if ($polylines[$i]->isa('Slic3r::Polygon')) {
|
||||
# my $loop = Slic3r::ExtrusionLoop->new;
|
||||
# $loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args));
|
||||
# $polylines[$i] = $loop;
|
||||
if ($polyline->is_valid && $polyline->first_point->coincides_with($polyline->last_point)) {
|
||||
# since medial_axis() now returns only Polyline objects, detect loops here
|
||||
push @entities, my $loop = Slic3r::ExtrusionLoop->new;
|
||||
$loop->append(Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args));
|
||||
} else {
|
||||
push @entities, Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args);
|
||||
}
|
||||
}
|
||||
|
||||
return @entities;
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::Layer::PerimeterGenerator::Loop;
|
||||
use Moo;
|
||||
|
||||
has 'polygon' => (is => 'ro', required => 1);
|
||||
has 'is_contour' => (is => 'ro', required => 1);
|
||||
has 'depth' => (is => 'ro', required => 1);
|
||||
has 'children' => (is => 'ro', default => sub { [] });
|
||||
|
||||
use List::Util qw(first);
|
||||
|
||||
sub add_child {
|
||||
my ($self, $child) = @_;
|
||||
push @{$self->children}, $child;
|
||||
}
|
||||
|
||||
sub is_external {
|
||||
my ($self) = @_;
|
||||
return $self->depth == 0;
|
||||
}
|
||||
|
||||
sub is_internal_contour {
|
||||
my ($self) = @_;
|
||||
|
||||
if ($self->is_contour) {
|
||||
# an internal contour is a contour containing no other contours
|
||||
return !defined first { $_->is_contour } @{$self->children};
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,131 +0,0 @@
|
|||
package Slic3r::Layer::Region;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Flow ':roles';
|
||||
use Slic3r::Geometry qw(scale);
|
||||
use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex
|
||||
);
|
||||
use Slic3r::Surface ':types';
|
||||
|
||||
|
||||
# TODO: lazy
|
||||
sub infill_area_threshold {
|
||||
my $self = shift;
|
||||
return $self->flow(FLOW_ROLE_SOLID_INFILL)->scaled_spacing ** 2;
|
||||
}
|
||||
|
||||
sub id { return $_[0]->layer->id; }
|
||||
sub slice_z { return $_[0]->layer->slice_z; }
|
||||
sub print_z { return $_[0]->layer->print_z; }
|
||||
sub height { return $_[0]->layer->height; }
|
||||
sub object { return $_[0]->layer->object; }
|
||||
sub print { return $_[0]->layer->print; }
|
||||
|
||||
sub config { return $_[0]->region->config; }
|
||||
|
||||
sub make_perimeters {
|
||||
my ($self, $slices, $fill_surfaces) = @_;
|
||||
|
||||
$self->perimeters->clear;
|
||||
$self->thin_fills->clear;
|
||||
|
||||
my $generator = Slic3r::Layer::PerimeterGenerator->new(
|
||||
# input:
|
||||
config => $self->config,
|
||||
object_config => $self->layer->object->config,
|
||||
print_config => $self->layer->print->config,
|
||||
layer_height => $self->height,
|
||||
layer_id => $self->layer->id,
|
||||
slices => $slices,
|
||||
lower_slices => defined($self->layer->lower_layer) ? $self->layer->lower_layer->slices : undef,
|
||||
perimeter_flow => $self->flow(FLOW_ROLE_PERIMETER),
|
||||
ext_perimeter_flow => $self->flow(FLOW_ROLE_EXTERNAL_PERIMETER),
|
||||
overhang_flow => $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object),
|
||||
solid_infill_flow => $self->flow(FLOW_ROLE_SOLID_INFILL),
|
||||
|
||||
# output:
|
||||
loops => $self->perimeters,
|
||||
gap_fill => $self->thin_fills,
|
||||
fill_surfaces => $fill_surfaces,
|
||||
);
|
||||
$generator->process;
|
||||
}
|
||||
|
||||
sub process_external_surfaces {
|
||||
my ($self, $lower_layer) = @_;
|
||||
|
||||
my @surfaces = @{$self->fill_surfaces};
|
||||
my $margin = scale &Slic3r::EXTERNAL_INFILL_MARGIN;
|
||||
|
||||
my @bottom = ();
|
||||
foreach my $surface (grep $_->is_bottom, @surfaces) {
|
||||
my $grown = $surface->expolygon->offset_ex(+$margin);
|
||||
|
||||
# detect bridge direction before merging grown surfaces otherwise adjacent bridges
|
||||
# would get merged into a single one while they need different directions
|
||||
# also, supply the original expolygon instead of the grown one, because in case
|
||||
# of very thin (but still working) anchors, the grown expolygon would go beyond them
|
||||
my $angle;
|
||||
if ($lower_layer) {
|
||||
my $bridge_detector = Slic3r::BridgeDetector->new(
|
||||
$surface->expolygon,
|
||||
$lower_layer->slices,
|
||||
$self->flow(FLOW_ROLE_INFILL, $self->height, 1)->scaled_width,
|
||||
);
|
||||
Slic3r::debugf "Processing bridge at layer %d:\n", $self->id;
|
||||
$bridge_detector->detect_angle;
|
||||
$angle = $bridge_detector->angle;
|
||||
|
||||
if (defined $angle && $self->object->config->support_material) {
|
||||
$self->bridged->append(Slic3r::ExPolygon->new($_))
|
||||
for @{ $bridge_detector->coverage_by_angle($angle) };
|
||||
$self->unsupported_bridge_edges->append($_) for @{ $bridge_detector->unsupported_edges };
|
||||
}
|
||||
}
|
||||
|
||||
push @bottom, map $surface->clone(expolygon => $_, bridge_angle => $angle), @$grown;
|
||||
}
|
||||
|
||||
my @top = ();
|
||||
foreach my $surface (grep $_->surface_type == S_TYPE_TOP, @surfaces) {
|
||||
# give priority to bottom surfaces
|
||||
my $grown = diff_ex(
|
||||
$surface->expolygon->offset(+$margin),
|
||||
[ map $_->p, @bottom ],
|
||||
);
|
||||
push @top, map $surface->clone(expolygon => $_), @$grown;
|
||||
}
|
||||
|
||||
# if we're slicing with no infill, we can't extend external surfaces
|
||||
# over non-existent infill
|
||||
my @fill_boundaries = $self->config->fill_density > 0
|
||||
? @surfaces
|
||||
: grep $_->surface_type != S_TYPE_INTERNAL, @surfaces;
|
||||
|
||||
# intersect the grown surfaces with the actual fill boundaries
|
||||
my @new_surfaces = ();
|
||||
foreach my $group (@{Slic3r::Surface::Collection->new(@top, @bottom)->group}) {
|
||||
push @new_surfaces,
|
||||
map $group->[0]->clone(expolygon => $_),
|
||||
@{intersection_ex(
|
||||
[ map $_->p, @$group ],
|
||||
[ map $_->p, @fill_boundaries ],
|
||||
1, # to ensure adjacent expolygons are unified
|
||||
)};
|
||||
}
|
||||
|
||||
# subtract the new top surfaces from the other non-top surfaces and re-add them
|
||||
my @other = grep $_->surface_type != S_TYPE_TOP && !$_->is_bottom, @surfaces;
|
||||
foreach my $group (@{Slic3r::Surface::Collection->new(@other)->group}) {
|
||||
push @new_surfaces, map $group->[0]->clone(expolygon => $_), @{diff_ex(
|
||||
[ map $_->p, @$group ],
|
||||
[ map $_->p, @new_surfaces ],
|
||||
)};
|
||||
}
|
||||
$self->fill_surfaces->clear;
|
||||
$self->fill_surfaces->append($_) for @new_surfaces;
|
||||
}
|
||||
|
||||
1;
|
|
@ -14,16 +14,6 @@ use Slic3r::Print::State ':steps';
|
|||
|
||||
our $status_cb;
|
||||
|
||||
sub new {
|
||||
# TODO: port PlaceholderParser methods to C++, then its own constructor
|
||||
# can call them and no need for this new() method at all
|
||||
my ($class) = @_;
|
||||
my $self = $class->_new;
|
||||
$self->placeholder_parser->apply_env_variables;
|
||||
$self->placeholder_parser->update_timestamp;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub set_status_cb {
|
||||
my ($class, $cb) = @_;
|
||||
$status_cb = $cb;
|
||||
|
|
|
@ -15,7 +15,8 @@ has '_brim_done' => (is => 'rw');
|
|||
has '_second_layer_things_done' => (is => 'rw');
|
||||
has '_last_obj_copy' => (is => 'rw');
|
||||
|
||||
use List::Util qw(first sum);
|
||||
use List::Util qw(first sum min max);
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Flow ':roles';
|
||||
use Slic3r::Geometry qw(X Y scale unscale chained_path convex_hull);
|
||||
use Slic3r::Geometry::Clipper qw(JT_SQUARE union_ex offset);
|
||||
|
@ -35,14 +36,65 @@ sub BUILD {
|
|||
}
|
||||
|
||||
# set up our helper object
|
||||
my $gcodegen = Slic3r::GCode->new(
|
||||
placeholder_parser => $self->placeholder_parser,
|
||||
layer_count => $layer_count,
|
||||
enable_cooling_markers => 1,
|
||||
);
|
||||
my $gcodegen = Slic3r::GCode->new;
|
||||
$self->_gcodegen($gcodegen);
|
||||
$gcodegen->set_placeholder_parser($self->placeholder_parser);
|
||||
$gcodegen->set_layer_count($layer_count);
|
||||
$gcodegen->set_enable_cooling_markers(1);
|
||||
$gcodegen->apply_print_config($self->config);
|
||||
$gcodegen->set_extruders($self->print->extruders);
|
||||
$self->_gcodegen($gcodegen);
|
||||
|
||||
# initialize autospeed
|
||||
{
|
||||
# get the minimum cross-section used in the print
|
||||
my @mm3_per_mm = ();
|
||||
foreach my $object (@{$self->print->objects}) {
|
||||
foreach my $region_id (0..$#{$self->print->regions}) {
|
||||
my $region = $self->print->get_region($region_id);
|
||||
foreach my $layer (@{$object->layers}) {
|
||||
my $layerm = $layer->get_region($region_id);
|
||||
if ($region->config->get_abs_value('perimeter_speed') == 0
|
||||
|| $region->config->get_abs_value('small_perimeter_speed') == 0
|
||||
|| $region->config->get_abs_value('external_perimeter_speed') == 0
|
||||
|| $region->config->get_abs_value('bridge_speed') == 0) {
|
||||
push @mm3_per_mm, $layerm->perimeters->min_mm3_per_mm;
|
||||
}
|
||||
if ($region->config->get_abs_value('infill_speed') == 0
|
||||
|| $region->config->get_abs_value('solid_infill_speed') == 0
|
||||
|| $region->config->get_abs_value('top_solid_infill_speed') == 0
|
||||
|| $region->config->get_abs_value('bridge_speed') == 0) {
|
||||
push @mm3_per_mm, $layerm->fills->min_mm3_per_mm;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($object->config->get_abs_value('support_material_speed') == 0
|
||||
|| $object->config->get_abs_value('support_material_interface_speed') == 0) {
|
||||
foreach my $layer (@{$object->support_layers}) {
|
||||
push @mm3_per_mm, $layer->support_fills->min_mm3_per_mm;
|
||||
push @mm3_per_mm, $layer->support_interface_fills->min_mm3_per_mm;
|
||||
}
|
||||
}
|
||||
}
|
||||
@mm3_per_mm = grep $_ != 0, @mm3_per_mm;
|
||||
if (@mm3_per_mm) {
|
||||
my $min_mm3_per_mm = min(@mm3_per_mm);
|
||||
# In order to honor max_print_speed we need to find a target volumetric
|
||||
# speed that we can use throughout the print. So we define this target
|
||||
# volumetric speed as the volumetric speed produced by printing the
|
||||
# smallest cross-section at the maximum speed: any larger cross-section
|
||||
# will need slower feedrates.
|
||||
my $volumetric_speed = $min_mm3_per_mm * $self->config->max_print_speed;
|
||||
|
||||
# limit such volumetric speed with max_volumetric_speed if set
|
||||
if ($self->config->max_volumetric_speed > 0) {
|
||||
$volumetric_speed = min(
|
||||
$volumetric_speed,
|
||||
$self->config->max_volumetric_speed,
|
||||
);
|
||||
}
|
||||
$gcodegen->set_volumetric_speed($volumetric_speed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new(
|
||||
|
@ -160,8 +212,8 @@ sub export {
|
|||
}
|
||||
my $convex_hull = convex_hull([ map @$_, @skirts ]);
|
||||
|
||||
$gcodegen->ooze_prevention->enable(1);
|
||||
$gcodegen->ooze_prevention->standby_points(
|
||||
$gcodegen->ooze_prevention->set_enable(1);
|
||||
$gcodegen->ooze_prevention->set_standby_points(
|
||||
[ map @{$_->equally_spaced_points(scale 10)}, @{offset([$convex_hull], scale 3)} ]
|
||||
);
|
||||
|
||||
|
@ -195,18 +247,18 @@ sub export {
|
|||
# no collision happens hopefully.
|
||||
if ($finished_objects > 0) {
|
||||
$gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y));
|
||||
$gcodegen->enable_cooling_markers(0); # we're not filtering these moves through CoolingBuffer
|
||||
$gcodegen->avoid_crossing_perimeters->use_external_mp_once(1);
|
||||
$gcodegen->set_enable_cooling_markers(0); # we're not filtering these moves through CoolingBuffer
|
||||
$gcodegen->avoid_crossing_perimeters->set_use_external_mp_once(1);
|
||||
print $fh $gcodegen->retract;
|
||||
print $fh $gcodegen->travel_to(
|
||||
Slic3r::Point->new(0,0),
|
||||
undef,
|
||||
EXTR_ROLE_NONE,
|
||||
'move to origin position for next object',
|
||||
);
|
||||
$gcodegen->enable_cooling_markers(1);
|
||||
$gcodegen->set_enable_cooling_markers(1);
|
||||
|
||||
# disable motion planner when traveling to first object point
|
||||
$gcodegen->avoid_crossing_perimeters->disable_once(1);
|
||||
$gcodegen->avoid_crossing_perimeters->set_disable_once(1);
|
||||
}
|
||||
|
||||
my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers};
|
||||
|
@ -308,14 +360,14 @@ sub process_layer {
|
|||
$self->_spiral_vase->enable(
|
||||
($layer->id > 0 || $self->print->config->brim_width == 0)
|
||||
&& ($layer->id >= $self->print->config->skirt_height && !$self->print->has_infinite_skirt)
|
||||
&& !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions})
|
||||
&& !defined(first { $_->region->config->bottom_solid_layers > $layer->id } @{$layer->regions})
|
||||
&& !defined(first { $_->perimeters->items_count > 1 } @{$layer->regions})
|
||||
&& !defined(first { $_->fills->items_count > 0 } @{$layer->regions})
|
||||
);
|
||||
}
|
||||
|
||||
# if we're going to apply spiralvase to this layer, disable loop clipping
|
||||
$self->_gcodegen->enable_loop_clipping(!defined $self->_spiral_vase || !$self->_spiral_vase->enable);
|
||||
$self->_gcodegen->set_enable_loop_clipping(!defined $self->_spiral_vase || !$self->_spiral_vase->enable);
|
||||
|
||||
if (!$self->_second_layer_things_done && $layer->id == 1) {
|
||||
for my $extruder (@{$self->_gcodegen->writer->extruders}) {
|
||||
|
@ -329,15 +381,19 @@ sub process_layer {
|
|||
}
|
||||
|
||||
# set new layer - this will change Z and force a retraction if retract_layer_change is enabled
|
||||
$gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->before_layer_gcode, {
|
||||
layer_num => $self->_gcodegen->layer_index + 1,
|
||||
layer_z => $layer->print_z,
|
||||
}) . "\n" if $self->print->config->before_layer_gcode;
|
||||
$gcode .= $self->_gcodegen->change_layer($layer); # this will increase $self->_gcodegen->layer_index
|
||||
$gcode .= $self->_gcodegen->placeholder_parser->process($self->print->config->layer_gcode, {
|
||||
layer_num => $self->_gcodegen->layer_index,
|
||||
layer_z => $layer->print_z,
|
||||
}) . "\n" if $self->print->config->layer_gcode;
|
||||
if ($self->print->config->before_layer_gcode) {
|
||||
my $pp = $self->_gcodegen->placeholder_parser->clone;
|
||||
$pp->set('layer_num' => $self->_gcodegen->layer_index + 1);
|
||||
$pp->set('layer_z' => $layer->print_z);
|
||||
$gcode .= $pp->process($self->print->config->before_layer_gcode) . "\n";
|
||||
}
|
||||
$gcode .= $self->_gcodegen->change_layer($layer->as_layer); # this will increase $self->_gcodegen->layer_index
|
||||
if ($self->print->config->layer_gcode) {
|
||||
my $pp = $self->_gcodegen->placeholder_parser->clone;
|
||||
$pp->set('layer_num' => $self->_gcodegen->layer_index);
|
||||
$pp->set('layer_z' => $layer->print_z);
|
||||
$gcode .= $pp->process($self->print->config->layer_gcode) . "\n";
|
||||
}
|
||||
|
||||
# extrude skirt along raft layers and normal object layers
|
||||
# (not along interlaced support material layers)
|
||||
|
@ -345,7 +401,7 @@ sub process_layer {
|
|||
&& !$self->_skirt_done->{$layer->print_z}
|
||||
&& (!$layer->isa('Slic3r::Layer::Support') || $layer->id < $object->config->raft_layers)) {
|
||||
$self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0));
|
||||
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1);
|
||||
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(1);
|
||||
my @extruder_ids = map { $_->id } @{$self->_gcodegen->writer->extruders};
|
||||
$gcode .= $self->_gcodegen->set_extruder($extruder_ids[0]);
|
||||
# skip skirt if we have a large brim
|
||||
|
@ -378,12 +434,12 @@ sub process_layer {
|
|||
}
|
||||
}
|
||||
$self->_skirt_done->{$layer->print_z} = 1;
|
||||
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0);
|
||||
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(0);
|
||||
|
||||
# allow a straight travel move to the first object point if this is the first layer
|
||||
# (but don't in next layers)
|
||||
if ($layer->id == 0) {
|
||||
$self->_gcodegen->avoid_crossing_perimeters->disable_once(1);
|
||||
$self->_gcodegen->avoid_crossing_perimeters->set_disable_once(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -391,19 +447,19 @@ sub process_layer {
|
|||
if (!$self->_brim_done) {
|
||||
$gcode .= $self->_gcodegen->set_extruder($self->print->regions->[0]->config->perimeter_extruder-1);
|
||||
$self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0));
|
||||
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(1);
|
||||
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(1);
|
||||
$gcode .= $self->_gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed)
|
||||
for @{$self->print->brim};
|
||||
$self->_brim_done(1);
|
||||
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0);
|
||||
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(0);
|
||||
|
||||
# allow a straight travel move to the first object point
|
||||
$self->_gcodegen->avoid_crossing_perimeters->disable_once(1);
|
||||
$self->_gcodegen->avoid_crossing_perimeters->set_disable_once(1);
|
||||
}
|
||||
|
||||
for my $copy (@$object_copies) {
|
||||
# when starting a new object, use the external motion planner for the first travel move
|
||||
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp_once(1) if ($self->_last_obj_copy // '') ne "$copy";
|
||||
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp_once(1) if ($self->_last_obj_copy // '') ne "$copy";
|
||||
$self->_last_obj_copy("$copy");
|
||||
|
||||
$self->_gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y));
|
||||
|
@ -539,7 +595,7 @@ sub _extrude_perimeters {
|
|||
my $gcode = "";
|
||||
foreach my $region_id (sort keys %$entities_by_region) {
|
||||
$self->_gcodegen->config->apply_region_config($self->print->get_region($region_id)->config);
|
||||
$gcode .= $self->_gcodegen->extrude($_, 'perimeter')
|
||||
$gcode .= $self->_gcodegen->extrude($_, 'perimeter', -1)
|
||||
for @{ $entities_by_region->{$region_id} };
|
||||
}
|
||||
return $gcode;
|
||||
|
@ -555,10 +611,10 @@ sub _extrude_infill {
|
|||
my $collection = Slic3r::ExtrusionPath::Collection->new(@{ $entities_by_region->{$region_id} });
|
||||
for my $fill (@{$collection->chained_path_from($self->_gcodegen->last_pos, 0)}) {
|
||||
if ($fill->isa('Slic3r::ExtrusionPath::Collection')) {
|
||||
$gcode .= $self->_gcodegen->extrude($_, 'infill')
|
||||
$gcode .= $self->_gcodegen->extrude($_, 'infill', -1)
|
||||
for @{$fill->chained_path_from($self->_gcodegen->last_pos, 0)};
|
||||
} else {
|
||||
$gcode .= $self->_gcodegen->extrude($fill, 'infill') ;
|
||||
$gcode .= $self->_gcodegen->extrude($fill, 'infill', -1) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,8 +45,9 @@ sub slice {
|
|||
$self->clear_layers;
|
||||
|
||||
# make layers taking custom heights into account
|
||||
my $print_z = my $slice_z = my $height = my $id = 0;
|
||||
my $first_object_layer_height = -1;
|
||||
my $id = 0;
|
||||
my $print_z = 0;
|
||||
my $first_object_layer_height = -1;
|
||||
my $first_object_layer_distance = -1;
|
||||
|
||||
# add raft layers
|
||||
|
@ -63,8 +64,8 @@ sub slice {
|
|||
{
|
||||
my @nozzle_diameters = (
|
||||
map $self->print->config->get_at('nozzle_diameter', $_),
|
||||
$self->config->support_material_extruder,
|
||||
$self->config->support_material_interface_extruder,
|
||||
$self->config->support_material_extruder-1,
|
||||
$self->config->support_material_interface_extruder-1,
|
||||
);
|
||||
$support_material_layer_height = 0.75 * min(@nozzle_diameters);
|
||||
}
|
||||
|
@ -78,20 +79,17 @@ sub slice {
|
|||
);
|
||||
$nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters;
|
||||
}
|
||||
my $distance = $self->_support_material->contact_distance($self->config->layer_height, $nozzle_diameter);
|
||||
$first_object_layer_distance = $self->_support_material->contact_distance($self->config->layer_height, $nozzle_diameter);
|
||||
|
||||
# force first layer print_z according to the contact distance
|
||||
# (the loop below will raise print_z by such height)
|
||||
if ($self->config->support_material_contact_distance == 0) {
|
||||
$first_object_layer_height = $distance;
|
||||
} else {
|
||||
$first_object_layer_height = $nozzle_diameter;
|
||||
}
|
||||
$first_object_layer_distance = $distance;
|
||||
$first_object_layer_height = $first_object_layer_distance - $self->config->support_material_contact_distance;
|
||||
}
|
||||
|
||||
# loop until we have at least one layer and the max slice_z reaches the object height
|
||||
my $max_z = unscale($self->size->z);
|
||||
my $slice_z = 0;
|
||||
my $height = 0;
|
||||
my $max_z = unscale($self->size->z);
|
||||
while (($slice_z - $height) <= $max_z) {
|
||||
# assign the default height to the layer according to the general settings
|
||||
$height = ($id == 0)
|
||||
|
@ -439,7 +437,7 @@ sub make_perimeters {
|
|||
$slice->extra_perimeters($slice->extra_perimeters + 1);
|
||||
}
|
||||
Slic3r::debugf " adding %d more perimeter(s) at layer %d\n",
|
||||
$slice->extra_perimeters, $layerm->id
|
||||
$slice->extra_perimeters, $layerm->layer->id
|
||||
if $slice->extra_perimeters > 0;
|
||||
}
|
||||
}
|
||||
|
@ -832,17 +830,6 @@ sub clip_fill_surfaces {
|
|||
}
|
||||
}
|
||||
|
||||
sub process_external_surfaces {
|
||||
my ($self) = @_;
|
||||
|
||||
for my $region_id (0 .. ($self->print->region_count-1)) {
|
||||
$self->get_layer(0)->regions->[$region_id]->process_external_surfaces(undef);
|
||||
for my $i (1 .. ($self->layer_count - 1)) {
|
||||
$self->get_layer($i)->regions->[$region_id]->process_external_surfaces($self->get_layer($i-1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub discover_horizontal_shells {
|
||||
my $self = shift;
|
||||
|
||||
|
@ -852,8 +839,8 @@ sub discover_horizontal_shells {
|
|||
for (my $i = 0; $i < $self->layer_count; $i++) {
|
||||
my $layerm = $self->get_layer($i)->regions->[$region_id];
|
||||
|
||||
if ($layerm->config->solid_infill_every_layers && $layerm->config->fill_density > 0
|
||||
&& ($i % $layerm->config->solid_infill_every_layers) == 0) {
|
||||
if ($layerm->region->config->solid_infill_every_layers && $layerm->region->config->fill_density > 0
|
||||
&& ($i % $layerm->region->config->solid_infill_every_layers) == 0) {
|
||||
$_->surface_type(S_TYPE_INTERNALSOLID) for @{$layerm->fill_surfaces->filter_by_type(S_TYPE_INTERNAL)};
|
||||
}
|
||||
|
||||
|
@ -875,8 +862,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)
|
||||
? $layerm->config->top_solid_layers
|
||||
: $layerm->config->bottom_solid_layers;
|
||||
? $layerm->region->config->top_solid_layers
|
||||
: $layerm->region->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++) {
|
||||
|
@ -904,7 +891,7 @@ sub discover_horizontal_shells {
|
|||
);
|
||||
next EXTERNAL if !@$new_internal_solid;
|
||||
|
||||
if ($layerm->config->fill_density == 0) {
|
||||
if ($layerm->region->config->fill_density == 0) {
|
||||
# if we're printing a hollow object we discard any solid shell thinner
|
||||
# than a perimeter width, since it's probably just crossing a sloping wall
|
||||
# and it's not wanted in a hollow print even if it would make sense when
|
||||
|
@ -944,7 +931,12 @@ sub discover_horizontal_shells {
|
|||
# make sure our grown surfaces don't exceed the fill area
|
||||
my @grown = @{intersection(
|
||||
offset($too_narrow, +$margin),
|
||||
[ map $_->p, @neighbor_fill_surfaces ],
|
||||
# Discard bridges as they are grown for anchoring and we can't
|
||||
# remove such anchors. (This may happen when a bridge is being
|
||||
# anchored onto a wall where little space remains after the bridge
|
||||
# is grown, and that little space is an internal solid shell so
|
||||
# it triggers this too_narrow logic.)
|
||||
[ map $_->p, grep { $_->is_internal && !$_->is_bridge } @neighbor_fill_surfaces ],
|
||||
)};
|
||||
$new_internal_solid = $solid = [ @grown, @$new_internal_solid ];
|
||||
}
|
||||
|
@ -1080,7 +1072,7 @@ sub combine_infill {
|
|||
+ $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 || $region->config->fill_pattern =~ /(rectilinear|honeycomb)/)
|
||||
+ (($type == S_TYPE_INTERNALSOLID || $region->config->fill_pattern =~ /(rectilinear|grid|line|honeycomb)/)
|
||||
? $layerms[-1]->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width
|
||||
: 0)
|
||||
)}, @$intersection;
|
||||
|
@ -1097,12 +1089,12 @@ sub combine_infill {
|
|||
)};
|
||||
|
||||
# apply surfaces back with adjusted depth to the uppermost layer
|
||||
if ($layerm->id == $self->get_layer($layer_idx)->id) {
|
||||
if ($layerm->layer->id == $self->get_layer($layer_idx)->id) {
|
||||
push @new_this_type,
|
||||
map Slic3r::Surface->new(
|
||||
expolygon => $_,
|
||||
surface_type => $type,
|
||||
thickness => sum(map $_->height, @layerms),
|
||||
thickness => sum(map $_->layer->height, @layerms),
|
||||
thickness_layers => scalar(@layerms),
|
||||
),
|
||||
@$intersection;
|
||||
|
|
|
@ -4,7 +4,7 @@ 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 convex_hull);
|
||||
use Slic3r::Geometry qw(epsilon scale scaled_epsilon PI rad2deg deg2rad convex_hull);
|
||||
use Slic3r::Geometry::Clipper qw(offset diff union union_ex intersection offset_ex offset2
|
||||
intersection_pl offset2_ex diff_pl);
|
||||
use Slic3r::Surface ':types';
|
||||
|
@ -235,7 +235,7 @@ sub contact_area {
|
|||
# just remove bridged areas
|
||||
$diff = diff(
|
||||
$diff,
|
||||
[ map @$_, @{$layerm->bridged} ],
|
||||
$layerm->bridged,
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
@ -267,13 +267,13 @@ sub contact_area {
|
|||
# get the average nozzle diameter used on this layer
|
||||
my @nozzle_diameters = map $self->print_config->get_at('nozzle_diameter', $_),
|
||||
map { $_->config->perimeter_extruder-1, $_->config->infill_extruder-1, $_->config->solid_infill_extruder-1 }
|
||||
@{$layer->regions};
|
||||
map $_->region, @{$layer->regions};
|
||||
my $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters;
|
||||
|
||||
my $contact_z = $layer->print_z - $self->contact_distance($layer->height, $nozzle_diameter);
|
||||
|
||||
# ignore this contact area if it's too low
|
||||
next if $contact_z < $self->object_config->get_value('first_layer_height');
|
||||
next if $contact_z < $self->object_config->get_value('first_layer_height') - epsilon;
|
||||
|
||||
$contact{$contact_z} = [ @contact ];
|
||||
$overhang{$contact_z} = [ @overhang ];
|
||||
|
@ -356,13 +356,16 @@ sub support_layers_z {
|
|||
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->object_config->raft_layers - 1);
|
||||
# since we already have two raft layers ($z[0] and $z[1]) we need to insert
|
||||
# raft_layers-2 more
|
||||
splice @z, 1, 0,
|
||||
map { sprintf "%.2f", $_ }
|
||||
map { $z[0] + $height * $_ }
|
||||
0..($self->object_config->raft_layers - 1);
|
||||
1..($self->object_config->raft_layers - 2);
|
||||
}
|
||||
|
||||
for (my $i = $#z; $i >= 0; $i--) {
|
||||
# create other layers (skip raft layers as they're already done and use thicker layers)
|
||||
for (my $i = $#z; $i >= $self->object_config->raft_layers; $i--) {
|
||||
my $target_height = $support_material_height;
|
||||
if ($i > 0 && $top{ $z[$i-1] }) {
|
||||
$target_height = $nozzle_diameter;
|
||||
|
|
|
@ -34,6 +34,13 @@ sub mesh {
|
|||
$facets = [
|
||||
[0,1,2],[2,1,3],[1,0,4],[5,1,4],[6,7,4],[8,2,9],[0,2,8],[10,8,9],[0,8,6],[0,6,4],[4,7,9],[7,10,9],[2,3,9],[9,3,11],[12,1,5],[13,3,12],[14,12,5],[3,1,12],[11,3,13],[11,15,5],[11,13,15],[15,14,5],[5,4,9],[11,5,9],[8,13,12],[6,8,12],[10,15,13],[8,10,13],[15,10,14],[14,10,7],[14,7,12],[12,7,6]
|
||||
],
|
||||
} elsif ($name eq 'cube_with_concave_hole') {
|
||||
$vertices = [
|
||||
[-10,-10,-5],[-10,-10,5],[-10,10,-5],[-10,10,5],[10,-10,-5],[10,-10,5],[-5,-5,-5],[5,-5,-5],[5,5,-5],[5,10,-5],[-5,5,-5],[3.06161699911402e-16,5,-5],[5,0,-5],[0,0,-5],[10,5,-5],[5,10,5],[-5,-5,5],[5,0,5],[5,-5,5],[-5,5,5],[10,5,5],[5,5,5],[3.06161699911402e-16,5,5],[0,0,5]
|
||||
];
|
||||
$facets = [
|
||||
[0,1,2],[2,1,3],[1,0,4],[5,1,4],[6,7,4],[8,2,9],[10,2,11],[11,12,13],[0,2,10],[0,10,6],[0,6,4],[11,2,8],[4,7,12],[4,12,8],[12,11,8],[14,4,8],[2,3,9],[9,3,15],[16,1,5],[17,18,5],[19,3,16],[20,21,5],[18,16,5],[3,1,16],[22,3,19],[21,3,22],[21,17,5],[21,22,17],[21,15,3],[23,17,22],[5,4,14],[20,5,14],[20,14,21],[21,14,8],[9,15,21],[8,9,21],[10,19,16],[6,10,16],[11,22,19],[10,11,19],[13,23,11],[11,23,22],[23,13,12],[17,23,12],[17,12,18],[18,12,7],[18,7,16],[16,7,6]
|
||||
],
|
||||
} elsif ($name eq 'V') {
|
||||
$vertices = [
|
||||
[-14,0,20],[-14,15,20],[0,0,0],[0,15,0],[-4,0,20],[-4,15,20],[5,0,7.14286],[10,0,0],[24,0,20],[14,0,20],[10,15,0],[5,15,7.14286],[14,15,20],[24,15,20]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue