mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-08-07 22:14:00 -06:00
Merge branch 'master' into sender
Conflicts: lib/Slic3r/GUI/Tab.pm
This commit is contained in:
commit
9ec7b43ca1
53 changed files with 1535 additions and 838 deletions
|
@ -58,6 +58,7 @@ use Slic3r::GCode::VibrationLimit;
|
|||
use Slic3r::Geometry qw(PI);
|
||||
use Slic3r::Geometry::Clipper;
|
||||
use Slic3r::Layer;
|
||||
use Slic3r::Layer::PerimeterGenerator;
|
||||
use Slic3r::Layer::Region;
|
||||
use Slic3r::Line;
|
||||
use Slic3r::Model;
|
||||
|
|
|
@ -207,8 +207,11 @@ sub extrude_loop {
|
|||
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 = $was_clockwise ? (-2, 1) : (1, -2);
|
||||
my $angle = Slic3r::Geometry::angle3points(@$last_path_polyline[0, @points]) / 3;
|
||||
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
|
||||
|
@ -332,48 +335,75 @@ sub _extrude_path {
|
|||
sub travel_to {
|
||||
my ($self, $point, $role, $comment) = @_;
|
||||
|
||||
my $gcode = "";
|
||||
|
||||
# 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::Line->new($self->last_pos, $point);
|
||||
my $travel = Slic3r::Polyline->new($self->last_pos, $point);
|
||||
|
||||
# Skip retraction at all in the following cases:
|
||||
# - travel length is shorter than the configured threshold
|
||||
# - user has enabled "Only retract when crossing perimeters" and the travel move is
|
||||
# contained in a single internal fill_surface (this includes the bottom layer when
|
||||
# bottom_solid_layers == 0) or in a single internal slice (this would exclude such
|
||||
# bottom layer but preserve perimeter-to-infill moves in all the other layers)
|
||||
# - the path that will be extruded after this travel move is a support material
|
||||
# extrusion and the travel move is contained in a single support material island
|
||||
if ($travel->length < scale $self->config->get_at('retract_before_travel', $self->writer->extruder->id)
|
||||
|| ($self->config->only_retract_when_crossing_perimeters
|
||||
&& $self->config->fill_density > 0
|
||||
&& defined($self->layer)
|
||||
&& ($self->layer->any_internal_region_slice_contains_line($travel)
|
||||
|| $self->layer->any_internal_region_fill_surface_contains_line($travel)))
|
||||
|| (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_line($travel))
|
||||
) {
|
||||
# Just perform a straight travel move without any retraction.
|
||||
$gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), $comment || '');
|
||||
} elsif ($self->config->avoid_crossing_perimeters && !$self->avoid_crossing_perimeters->disable_once) {
|
||||
# If avoid_crossing_perimeters is enabled and the disable_once flag is not set
|
||||
# we need to plan a multi-segment travel move inside the configuration space.
|
||||
$gcode .= $self->avoid_crossing_perimeters->travel_to($self, $point, $comment || '');
|
||||
} else {
|
||||
# If avoid_crossing_perimeters is disabled or the disable_once flag is set,
|
||||
# perform a straight move with a retraction.
|
||||
$gcode .= $self->retract;
|
||||
$gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), $comment || '');
|
||||
# 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) = @_;
|
||||
|
||||
|
@ -568,9 +598,10 @@ 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
|
||||
has 'disable_once' => (is => 'rw', default => sub {1}); # this flag disables avoid_crossing_perimeters just for the next travel move
|
||||
|
||||
use Slic3r::Geometry qw(scale);
|
||||
# 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) = @_;
|
||||
|
@ -583,47 +614,30 @@ sub init_layer_mp {
|
|||
}
|
||||
|
||||
sub travel_to {
|
||||
my ($self, $gcodegen, $point, $comment) = @_;
|
||||
|
||||
my $gcode = "";
|
||||
my ($self, $gcodegen, $point) = @_;
|
||||
|
||||
if ($self->use_external_mp || $self->use_external_mp_once) {
|
||||
$self->use_external_mp_once(0);
|
||||
# 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 $point in G-code coordinates
|
||||
# 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;
|
||||
my $origin = $gcodegen->origin;
|
||||
$point->translate(map scale $_, @$origin);
|
||||
$point->translate(@$scaled_origin);
|
||||
# calculate path
|
||||
my $travel = $self->_external_mp->shortest_path($last_pos, $point);
|
||||
|
||||
# calculate path (external_mp uses G-code coordinates so we set a temporary null origin)
|
||||
$gcodegen->set_origin(Slic3r::Pointf->new(0,0));
|
||||
$gcode .= $self->_plan($gcodegen, $self->_external_mp, $point, $comment);
|
||||
$gcodegen->set_origin($origin);
|
||||
# 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 {
|
||||
$gcode .= $self->_plan($gcodegen, $self->_layer_mp, $point, $comment);
|
||||
return $self->_layer_mp->shortest_path($gcodegen->last_pos, $point);
|
||||
}
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub _plan {
|
||||
my ($self, $gcodegen, $mp, $point, $comment) = @_;
|
||||
|
||||
my $gcode = "";
|
||||
my $travel = $mp->shortest_path($gcodegen->last_pos, $point);
|
||||
|
||||
# if the path is not contained in a single island we need to retract
|
||||
$gcode .= $gcodegen->retract
|
||||
if !$gcodegen->config->only_retract_when_crossing_perimeters
|
||||
|| !$gcodegen->layer->any_internal_region_fill_surface_contains_polyline($travel);
|
||||
|
||||
# append the actual path and return
|
||||
# use G1 because we rely on paths being straight (G0 may make round paths)
|
||||
$gcode .= join '',
|
||||
map $gcodegen->writer->travel_to_xy($gcodegen->point_to_gcode($_->b), $comment),
|
||||
@{$travel->lines};
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -24,7 +24,7 @@ sub BUILD {
|
|||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my ($gcode) = @_;
|
||||
my ($gcode, $flush) = @_;
|
||||
|
||||
my $new_gcode = "";
|
||||
|
||||
|
@ -51,7 +51,7 @@ sub process {
|
|||
if (abs($new_advance - $self->_advance) > 1E-5) {
|
||||
my $new_E = ($self->config->use_relative_e_distances ? 0 : $reader->E) + ($new_advance - $self->_advance);
|
||||
$new_gcode .= sprintf "G1 %s%.5f F%.3f ; pressure advance\n",
|
||||
$self->_extrusion_axis, $new_E, $self->unretract_speed;
|
||||
$self->_extrusion_axis, $new_E, $self->_unretract_speed;
|
||||
$new_gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $reader->E
|
||||
if !$self->config->use_relative_e_distances;
|
||||
$self->_advance($new_advance);
|
||||
|
@ -61,20 +61,33 @@ sub process {
|
|||
}
|
||||
} elsif (($info->{retracting} || $cmd eq 'G10') && $self->_advance != 0) {
|
||||
# We need to bring pressure to zero when retracting.
|
||||
my $new_E = ($self->config->use_relative_e_distances ? 0 : $reader->E) - $self->_advance;
|
||||
$new_gcode .= sprintf "G1 %s%.5f F%.3f ; pressure discharge\n",
|
||||
$self->_extrusion_axis, $new_E, $args->{F} // $self->unretract_speed;
|
||||
$new_gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $reader->E
|
||||
if !$self->config->use_relative_e_distances;
|
||||
$new_gcode .= $self->_discharge($args->{F});
|
||||
}
|
||||
|
||||
$new_gcode .= "$info->{raw}\n";
|
||||
});
|
||||
|
||||
if ($flush) {
|
||||
$new_gcode .= $self->_discharge;
|
||||
}
|
||||
|
||||
return $new_gcode;
|
||||
}
|
||||
|
||||
sub unretract_speed {
|
||||
sub _discharge {
|
||||
my ($self, $F) = @_;
|
||||
|
||||
my $new_E = ($self->config->use_relative_e_distances ? 0 : $self->reader->E) - $self->_advance;
|
||||
my $gcode = sprintf "G1 %s%.5f F%.3f ; pressure discharge\n",
|
||||
$self->_extrusion_axis, $new_E, $F // $self->_unretract_speed;
|
||||
$gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $self->reader->E
|
||||
if !$self->config->use_relative_e_distances;
|
||||
$self->_advance(0);
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub _unretract_speed {
|
||||
my ($self) = @_;
|
||||
return $self->config->get_at('retract_speed', $self->_tool) * 60;
|
||||
}
|
||||
|
|
|
@ -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, 270]);
|
||||
my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 300]);
|
||||
|
||||
$self->SetBackgroundColour(Wx::wxWHITE);
|
||||
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
|
@ -47,7 +47,7 @@ sub new {
|
|||
'<html>' .
|
||||
'<body bgcolor="#ffffff" link="#808080">' .
|
||||
'<font color="#808080">' .
|
||||
'Copyright © 2011-2014 Alessandro Ranellucci. <br />' .
|
||||
'Copyright © 2011-2015 Alessandro Ranellucci. <br />' .
|
||||
'<a href="http://slic3r.org/">Slic3r</a> is licensed under the ' .
|
||||
'<a href="http://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License, version 3</a>.' .
|
||||
'<br /><br /><br />' .
|
||||
|
|
|
@ -301,7 +301,7 @@ sub Render {
|
|||
foreach my $layerm (@{$layer->regions}) {
|
||||
if ($object->step_done(STEP_PERIMETERS)) {
|
||||
$self->color([0.7, 0, 0]);
|
||||
$self->_draw($object, $print_z, $_) for @{$layerm->perimeters};
|
||||
$self->_draw($object, $print_z, $_) for map @$_, @{$layerm->perimeters};
|
||||
}
|
||||
|
||||
if ($object->step_done(STEP_INFILL)) {
|
||||
|
|
|
@ -44,7 +44,7 @@ use constant TRACKBALLSIZE => 0.8;
|
|||
use constant TURNTABLE_MODE => 1;
|
||||
use constant GROUND_Z => -0.02;
|
||||
use constant SELECTED_COLOR => [0,1,0,1];
|
||||
use constant HOVER_COLOR => [0.8,0.8,0,1];
|
||||
use constant HOVER_COLOR => [0.4,0.9,0,1];
|
||||
use constant COLORS => [ [1,1,0], [1,0.5,0.5], [0.5,1,0.5], [0.5,0.5,1] ];
|
||||
|
||||
# make OpenGL::Array thread-safe
|
||||
|
@ -90,7 +90,7 @@ sub new {
|
|||
my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta();
|
||||
$zoom = max(min($zoom, 4), -4);
|
||||
$zoom /= 10;
|
||||
$self->_zoom($self->_zoom * (1-$zoom));
|
||||
$self->_zoom($self->_zoom / (1-$zoom));
|
||||
|
||||
# In order to zoom around the mouse point we need to translate
|
||||
# the camera target
|
||||
|
@ -171,7 +171,7 @@ sub mouse_event {
|
|||
$self->_drag_start_pos($cur_pos);
|
||||
$self->_dragged(1);
|
||||
$self->Refresh;
|
||||
} elsif ($e->Dragging && !defined $self->_hover_volume_idx) {
|
||||
} elsif ($e->Dragging) {
|
||||
if ($e->LeftIsDown) {
|
||||
# if dragging over blank area with left button, rotate
|
||||
if (defined $self->_drag_start_pos) {
|
||||
|
@ -208,7 +208,7 @@ sub mouse_event {
|
|||
}
|
||||
$self->_drag_start_xy($pos);
|
||||
}
|
||||
} elsif ($e->LeftUp || $e->RightUp) {
|
||||
} elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) {
|
||||
if ($self->on_move && defined $self->_drag_volume_idx) {
|
||||
$self->on_move->($self->_drag_volume_idx) if $self->_dragged;
|
||||
}
|
||||
|
@ -629,17 +629,16 @@ sub InitGL {
|
|||
glEnable(GL_MULTISAMPLE);
|
||||
|
||||
# ambient lighting
|
||||
glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.1, 0.1, 0.1, 1);
|
||||
glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.3, 0.3, 0.3, 1);
|
||||
|
||||
glEnable(GL_LIGHTING);
|
||||
glEnable(GL_LIGHT0);
|
||||
glEnable(GL_LIGHT1);
|
||||
glLightfv_p(GL_LIGHT0, GL_POSITION, 0.5, 0.5, 1, 0);
|
||||
glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.5, 0.5, 0.5, 1);
|
||||
glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.8, 0.8, 0.8, 1);
|
||||
glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 0.5, 0);
|
||||
glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.5, 0.5, 0.5, 1);
|
||||
glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 1, 1, 1, 1);
|
||||
|
||||
# light from camera
|
||||
glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0);
|
||||
glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.3, 0.3, 0.3, 1);
|
||||
glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 0.2, 0.2, 0.2, 1);
|
||||
|
||||
# Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
|
||||
glShadeModel(GL_SMOOTH);
|
||||
|
@ -681,6 +680,11 @@ sub Render {
|
|||
}
|
||||
glTranslatef(@{ $self->_camera_target->negative });
|
||||
|
||||
# light from above
|
||||
glLightfv_p(GL_LIGHT0, GL_POSITION, -0.5, -0.5, 1, 0);
|
||||
glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.2, 0.2, 0.2, 1);
|
||||
glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.5, 0.5, 0.5, 1);
|
||||
|
||||
if ($self->enable_picking) {
|
||||
glDisable(GL_LIGHTING);
|
||||
$self->draw_volumes(1);
|
||||
|
@ -706,6 +710,7 @@ sub Render {
|
|||
|
||||
# draw fixed background
|
||||
if ($self->background) {
|
||||
glDisable(GL_LIGHTING);
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
|
||||
|
@ -725,85 +730,72 @@ sub Render {
|
|||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glPopMatrix();
|
||||
glEnable(GL_LIGHTING);
|
||||
}
|
||||
|
||||
# draw ground and axes
|
||||
glDisable(GL_LIGHTING);
|
||||
my $z0 = 0;
|
||||
|
||||
# draw ground
|
||||
my $ground_z = GROUND_Z;
|
||||
if ($self->bed_triangles) {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glColor4f(0.8, 0.6, 0.5, 0.4);
|
||||
glNormal3d(0,0,1);
|
||||
glVertexPointer_p(3, $self->bed_triangles);
|
||||
glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
|
||||
# we need depth test for grid, otherwise it would disappear when looking
|
||||
# the object from below
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
# draw grid
|
||||
glLineWidth(3);
|
||||
glColor4f(0.2, 0.2, 0.2, 0.4);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glVertexPointer_p(3, $self->bed_grid_lines);
|
||||
glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
my $volumes_bb = $self->volumes_bounding_box;
|
||||
|
||||
{
|
||||
# draw ground
|
||||
my $ground_z = GROUND_Z;
|
||||
if ($self->bed_triangles) {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glColor4f(0.8, 0.6, 0.5, 0.4);
|
||||
glNormal3d(0,0,1);
|
||||
glVertexPointer_p(3, $self->bed_triangles);
|
||||
glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
# draw grid
|
||||
glLineWidth(3);
|
||||
glColor4f(0.2, 0.2, 0.2, 0.4);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glVertexPointer_p(3, $self->bed_grid_lines);
|
||||
glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
my $volumes_bb = $self->volumes_bounding_box;
|
||||
|
||||
{
|
||||
# draw axes
|
||||
$ground_z += 0.02;
|
||||
my $origin = $self->origin;
|
||||
my $axis_len = max(
|
||||
0.3 * max(@{ $self->bed_bounding_box->size }),
|
||||
2 * max(@{ $volumes_bb->size }),
|
||||
);
|
||||
glLineWidth(2);
|
||||
glBegin(GL_LINES);
|
||||
# draw line for x axis
|
||||
glColor3f(1, 0, 0);
|
||||
glVertex3f(@$origin, $ground_z);
|
||||
glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,,
|
||||
# draw line for y axis
|
||||
glColor3f(0, 1, 0);
|
||||
glVertex3f(@$origin, $ground_z);
|
||||
glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++
|
||||
# draw line for Z axis
|
||||
glColor3f(0, 0, 1);
|
||||
glVertex3f(@$origin, $ground_z);
|
||||
glVertex3f(@$origin, $ground_z+$axis_len);
|
||||
glEnd();
|
||||
}
|
||||
|
||||
# draw cutting plane
|
||||
if (defined $self->cutting_plane_z) {
|
||||
my $plane_z = $z0 + $self->cutting_plane_z;
|
||||
my $bb = $volumes_bb;
|
||||
glDisable(GL_CULL_FACE);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBegin(GL_QUADS);
|
||||
glColor4f(0.8, 0.8, 0.8, 0.5);
|
||||
glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z);
|
||||
glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z);
|
||||
glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z);
|
||||
glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z);
|
||||
glEnd();
|
||||
glEnable(GL_CULL_FACE);
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
# draw axes
|
||||
# disable depth testing so that axes are not covered by ground
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
my $origin = $self->origin;
|
||||
my $axis_len = max(
|
||||
0.3 * max(@{ $self->bed_bounding_box->size }),
|
||||
2 * max(@{ $volumes_bb->size }),
|
||||
);
|
||||
glLineWidth(2);
|
||||
glBegin(GL_LINES);
|
||||
# draw line for x axis
|
||||
glColor3f(1, 0, 0);
|
||||
glVertex3f(@$origin, $ground_z);
|
||||
glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,,
|
||||
# draw line for y axis
|
||||
glColor3f(0, 1, 0);
|
||||
glVertex3f(@$origin, $ground_z);
|
||||
glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++
|
||||
glEnd();
|
||||
# draw line for Z axis
|
||||
# (re-enable depth test so that axis is correctly shown when objects are behind it)
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glBegin(GL_LINES);
|
||||
glColor3f(0, 0, 1);
|
||||
glVertex3f(@$origin, $ground_z);
|
||||
glVertex3f(@$origin, $ground_z+$axis_len);
|
||||
glEnd();
|
||||
}
|
||||
|
||||
glEnable(GL_LIGHTING);
|
||||
|
@ -811,6 +803,25 @@ sub Render {
|
|||
# draw objects
|
||||
$self->draw_volumes;
|
||||
|
||||
# draw cutting plane
|
||||
if (defined $self->cutting_plane_z) {
|
||||
my $plane_z = $self->cutting_plane_z;
|
||||
my $bb = $volumes_bb;
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDisable(GL_LIGHTING);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBegin(GL_QUADS);
|
||||
glColor4f(0.8, 0.8, 0.8, 0.5);
|
||||
glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z);
|
||||
glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z);
|
||||
glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z);
|
||||
glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z);
|
||||
glEnd();
|
||||
glEnable(GL_CULL_FACE);
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
glFlush();
|
||||
|
||||
$self->SwapBuffers();
|
||||
|
@ -866,9 +877,13 @@ sub draw_volumes {
|
|||
glLineWidth(0);
|
||||
glColor3f(@{COLORS->[0]});
|
||||
glBegin(GL_QUADS);
|
||||
glNormal3f((map $_/$line->length, @{$line->normal}), 0);
|
||||
# We'll use this for the middle normal when using 4 quads:
|
||||
#my $xy_normal = $line->normal;
|
||||
#$_xynormal->scale(1/$line->length);
|
||||
glNormal3f(0,0,-1);
|
||||
glVertex3f((map unscale($_), @{$line->a}), $bottom_z);
|
||||
glVertex3f((map unscale($_), @{$line->b}), $bottom_z);
|
||||
glNormal3f(0,0,1);
|
||||
glVertex3f((map unscale($_), @{$line->b}), $top_z);
|
||||
glVertex3f((map unscale($_), @{$line->a}), $top_z);
|
||||
glEnd();
|
||||
|
|
|
@ -928,6 +928,7 @@ sub build {
|
|||
serial_port serial_speed
|
||||
octoprint_host octoprint_apikey
|
||||
use_firmware_retraction pressure_advance vibration_limit
|
||||
use_volumetric_e
|
||||
start_gcode end_gcode layer_gcode toolchange_gcode
|
||||
nozzle_diameter extruder_offset
|
||||
retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_layer_change wipe
|
||||
|
@ -1019,8 +1020,8 @@ sub build {
|
|||
{
|
||||
my $optgroup = $page->new_optgroup('OctoPrint upload');
|
||||
|
||||
# append a button to the Host line
|
||||
my $octoprint_host_widget = sub {
|
||||
# append two buttons to the Host line
|
||||
my $octoprint_host_browse = sub {
|
||||
my ($parent) = @_;
|
||||
|
||||
my $btn = Wx::Button->new($parent, -1, "Browse…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
|
@ -1032,10 +1033,7 @@ sub build {
|
|||
if (!eval "use Net::Bonjour; 1") {
|
||||
$btn->Disable;
|
||||
}
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$sizer->Add($btn);
|
||||
|
||||
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
my $dlg = Slic3r::GUI::BonjourBrowser->new($self);
|
||||
if ($dlg->ShowModal == wxID_OK) {
|
||||
|
@ -1047,22 +1045,51 @@ sub build {
|
|||
}
|
||||
});
|
||||
|
||||
return $sizer;
|
||||
return $btn;
|
||||
};
|
||||
my $octoprint_host_test = sub {
|
||||
my ($parent) = @_;
|
||||
|
||||
my $btn = $self->{octoprint_host_test_btn} = Wx::Button->new($parent, -1, "Test", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
$btn->SetFont($Slic3r::GUI::small_font);
|
||||
if ($Slic3r::GUI::have_button_icons) {
|
||||
$btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/wrench.png", wxBITMAP_TYPE_PNG));
|
||||
}
|
||||
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
my $ua = LWP::UserAgent->new;
|
||||
$ua->timeout(10);
|
||||
|
||||
my $res = $ua->post(
|
||||
"http://" . $self->{config}->octoprint_host . "/api/version",
|
||||
'X-Api-Key' => $self->{config}->octoprint_apikey,
|
||||
);
|
||||
if ($res->is_success) {
|
||||
Slic3r::GUI::show_info($self, "Connection to OctoPrint works correctly.", "Success!");
|
||||
} else {
|
||||
Slic3r::GUI::show_error($self,
|
||||
"I wasn't able to connect to OctoPrint (" . $res->status_line . "). "
|
||||
. "Check hostname and OctoPrint version (at least 1.1.0 is required).");
|
||||
}
|
||||
});
|
||||
return $btn;
|
||||
};
|
||||
|
||||
my $host_line = $optgroup->create_single_option_line('octoprint_host');
|
||||
$host_line->append_widget($octoprint_host_widget);
|
||||
$host_line->append_widget($octoprint_host_browse);
|
||||
$host_line->append_widget($octoprint_host_test);
|
||||
$optgroup->append_line($host_line);
|
||||
$optgroup->append_single_option_line('octoprint_apikey');
|
||||
}
|
||||
{
|
||||
my $optgroup = $page->new_optgroup('Firmware');
|
||||
$optgroup->append_single_option_line('gcode_flavor');
|
||||
$optgroup->append_single_option_line('use_relative_e_distances');
|
||||
}
|
||||
{
|
||||
my $optgroup = $page->new_optgroup('Advanced');
|
||||
$optgroup->append_single_option_line('use_relative_e_distances');
|
||||
$optgroup->append_single_option_line('use_firmware_retraction');
|
||||
$optgroup->append_single_option_line('use_volumetric_e');
|
||||
$optgroup->append_single_option_line('pressure_advance');
|
||||
$optgroup->append_single_option_line('vibration_limit');
|
||||
}
|
||||
|
@ -1201,6 +1228,11 @@ sub _update {
|
|||
my $config = $self->{config};
|
||||
|
||||
$self->get_field('serial_speed')->toggle($config->get('serial_port'));
|
||||
if ($config->get('octoprint_host') && eval "use LWP::UserAgent; 1") {
|
||||
$self->{octoprint_host_test_btn}->Enable;
|
||||
} else {
|
||||
$self->{octoprint_host_test_btn}->Disable;
|
||||
}
|
||||
$self->get_field('octoprint_apikey')->toggle($config->get('octoprint_host'));
|
||||
|
||||
my $have_multiple_extruders = $self->{extruders_count} > 1;
|
||||
|
|
462
lib/Slic3r/Layer/PerimeterGenerator.pm
Normal file
462
lib/Slic3r/Layer/PerimeterGenerator.pm
Normal file
|
@ -0,0 +1,462 @@
|
|||
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 union_pt 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 '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}) {
|
||||
my @contours = (); # array of Polygons with ccw orientation
|
||||
my @holes = (); # array of Polygons with cw orientation
|
||||
my @thin_walls = (); # array of ExPolygons
|
||||
|
||||
# detect how many perimeters must be generated for this island
|
||||
my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0);
|
||||
|
||||
my @last = @{$surface->expolygon};
|
||||
my @gaps = (); # array of ExPolygons
|
||||
if ($loop_number > 0) {
|
||||
# we loop one time more than needed in order to find gaps after the last perimeter was applied
|
||||
for my $i (1 .. ($loop_number+1)) { # outer loop is 1
|
||||
my @offsets = ();
|
||||
if ($i == 1) {
|
||||
# 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_ex(
|
||||
\@last,
|
||||
offset(\@offsets, +0.5*$ext_pwidth),
|
||||
1, # medial axis requires non-overlapping geometry
|
||||
);
|
||||
push @thin_walls, @$diff;
|
||||
}
|
||||
} else {
|
||||
my $distance = ($i == 2) ? $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*$pspacing),
|
||||
offset(\@offsets, +0.5*$pspacing + 10), # safety offset
|
||||
);
|
||||
push @gaps, grep abs($_->area) >= $gap_area_threshold, @$diff;
|
||||
}
|
||||
}
|
||||
|
||||
last if !@offsets;
|
||||
last if $i > $loop_number; # we were only looking for gaps this time
|
||||
|
||||
# clone polygons because these ExPolygons will go out of scope very soon
|
||||
@last = @offsets;
|
||||
foreach my $polygon (@offsets) {
|
||||
if ($polygon->is_counter_clockwise) {
|
||||
push @contours, $polygon;
|
||||
} else {
|
||||
push @holes, $polygon;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# fill gaps
|
||||
if (@gaps) {
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"gaps.svg",
|
||||
expolygons => \@gaps,
|
||||
);
|
||||
}
|
||||
|
||||
# where $pwidth < thickness < 2*$pspacing, infill with width = 1.5*$pwidth
|
||||
# where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth
|
||||
my @gap_sizes = (
|
||||
[ $pwidth, 2*$pspacing, unscale 1.5*$pwidth ],
|
||||
[ 0.5*$pwidth, $pwidth, unscale 0.5*$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)};
|
||||
}
|
||||
}
|
||||
|
||||
# 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 $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)} ],
|
||||
-($pspacing/2 + $min_perimeter_infill_spacing/2),
|
||||
+$min_perimeter_infill_spacing/2,
|
||||
)};
|
||||
|
||||
|
||||
# process thin walls by collapsing slices to single passes
|
||||
if (@thin_walls) {
|
||||
# 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 = $pwidth / 4;
|
||||
@thin_walls = @{offset2_ex([ map @$_, @thin_walls ], -$min_width/2, +$min_width/2)};
|
||||
|
||||
# the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
|
||||
$self->_thin_wall_polylines([ map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls ]);
|
||||
Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->_thin_wall_polylines}) if $Slic3r::debug;
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"medial_axis.svg",
|
||||
no_arrows => 1,
|
||||
expolygons => \@thin_walls,
|
||||
green_polylines => [ map $_->polygon->split_at_first_point, @{$self->perimeters} ],
|
||||
red_polylines => $self->_thin_wall_polylines,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# find nesting hierarchies separately for contours and holes
|
||||
my $contours_pt = union_pt(\@contours);
|
||||
$self->_holes_pt(union_pt(\@holes));
|
||||
|
||||
# order loops from inner to outer (in terms of object slices)
|
||||
my @loops = $self->_traverse_pt($contours_pt, 0, 1);
|
||||
|
||||
# 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
|
||||
@loops = reverse @loops
|
||||
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(@loops));
|
||||
}
|
||||
}
|
||||
|
||||
sub _traverse_pt {
|
||||
my ($self, $polynodes, $depth, $is_contour) = @_;
|
||||
|
||||
# convert all polynodes to ExtrusionLoop objects
|
||||
my $collection = Slic3r::ExtrusionPath::Collection->new; # temporary collection
|
||||
my @children = ();
|
||||
foreach my $polynode (@$polynodes) {
|
||||
my $polygon = ($polynode->{outer} // $polynode->{hole})->clone;
|
||||
|
||||
my $role = EXTR_ROLE_PERIMETER;
|
||||
my $loop_role = EXTRL_ROLE_DEFAULT;
|
||||
|
||||
my $root_level = $depth == 0;
|
||||
my $no_children = !@{ $polynode->{children} };
|
||||
my $is_external = $is_contour ? $root_level : $no_children;
|
||||
my $is_internal = $is_contour ? $no_children : $root_level;
|
||||
if ($is_contour && $is_internal) {
|
||||
# internal perimeters are root level in case of holes
|
||||
# and items with no children in case of contours
|
||||
# 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;
|
||||
}
|
||||
if ($is_external) {
|
||||
# external perimeters are root level in case of contours
|
||||
# and items with no children in case of holes
|
||||
$role = EXTR_ROLE_EXTERNAL_PERIMETER;
|
||||
}
|
||||
|
||||
# detect overhanging/bridging perimeters
|
||||
my @paths = ();
|
||||
if ($self->config->overhangs && $self->layer_id > 0) {
|
||||
# get non-overhang paths by intersecting this loop with the grown lower slices
|
||||
foreach my $polyline (@{ intersection_ppl([ $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([ $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->layer_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 => $polygon->split_at_first_point,
|
||||
role => $role,
|
||||
mm3_per_mm => $self->_mm3_per_mm,
|
||||
width => $self->perimeter_flow->width,
|
||||
height => $self->layer_height,
|
||||
);
|
||||
}
|
||||
my $loop = Slic3r::ExtrusionLoop->new_from_paths(@paths);
|
||||
$loop->role($loop_role);
|
||||
|
||||
# return ccw contours and cw holes
|
||||
# GCode.pm will convert all of them to ccw, but it needs to know
|
||||
# what the holes are in order to compute the correct inwards move
|
||||
# We do this on the final Loop object because overhang clipping
|
||||
# does not keep orientation.
|
||||
if ($is_contour) {
|
||||
$loop->make_counter_clockwise;
|
||||
} else {
|
||||
$loop->make_clockwise;
|
||||
}
|
||||
$collection->append($loop);
|
||||
|
||||
# save the children
|
||||
push @children, $polynode->{children};
|
||||
}
|
||||
|
||||
# if we're handling the top-level contours, add thin walls as candidates too
|
||||
# in order to include them in the nearest-neighbor search
|
||||
if ($is_contour && $depth == 0) {
|
||||
foreach my $polyline (@{$self->_thin_wall_polylines}) {
|
||||
$collection->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,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
# use a nearest neighbor search to order these children
|
||||
# TODO: supply second argument to chained_path() too?
|
||||
# (We used to skip this chained_path() when $is_contour &&
|
||||
# $depth == 0 because slices are ordered at G_code export
|
||||
# time, but multiple top-level perimeters might belong to
|
||||
# the same slice actually, so that was a broken optimization.)
|
||||
# We supply no_reverse = false because we want to permit reversal
|
||||
# of thin walls, but we rely on the fact that loops will never
|
||||
# be reversed anyway.
|
||||
my $sorted_collection = $collection->chained_path_indices(0);
|
||||
my @orig_indices = @{$sorted_collection->orig_indices};
|
||||
|
||||
my @loops = ();
|
||||
foreach my $loop (@$sorted_collection) {
|
||||
my $orig_index = shift @orig_indices;
|
||||
|
||||
if ($loop->isa('Slic3r::ExtrusionPath')) {
|
||||
push @loops, $loop->clone;
|
||||
} else {
|
||||
# if this is an external contour find all holes belonging to this contour(s)
|
||||
# and prepend them
|
||||
if ($is_contour && $depth == 0) {
|
||||
# $loop is the outermost loop of an island
|
||||
my @holes = ();
|
||||
for (my $i = 0; $i <= $#{$self->_holes_pt}; $i++) {
|
||||
if ($loop->polygon->contains_point($self->_holes_pt->[$i]{outer}->first_point)) {
|
||||
push @holes, splice @{$self->_holes_pt}, $i, 1; # remove from candidates to reduce complexity
|
||||
$i--;
|
||||
}
|
||||
}
|
||||
|
||||
# order holes efficiently
|
||||
@holes = @holes[@{chained_path([ map {($_->{outer} // $_->{hole})->first_point} @holes ])}];
|
||||
|
||||
push @loops, reverse map $self->_traverse_pt([$_], 0, 0), @holes;
|
||||
}
|
||||
|
||||
# traverse children and prepend them to this loop
|
||||
push @loops, $self->_traverse_pt($children[$orig_index], $depth+1, $is_contour);
|
||||
push @loops, $loop->clone;
|
||||
}
|
||||
}
|
||||
return @loops;
|
||||
}
|
||||
|
||||
sub _fill_gaps {
|
||||
my ($self, $min, $max, $w, $gaps) = @_;
|
||||
|
||||
my $this = diff_ex(
|
||||
offset2([ map @$_, @$gaps ], -$min/2, +$min/2),
|
||||
offset2([ map @$_, @$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;
|
||||
}
|
||||
|
||||
1;
|
|
@ -2,14 +2,11 @@ package Slic3r::Layer::Region;
|
|||
use strict;
|
||||
use warnings;
|
||||
|
||||
use List::Util qw(sum first);
|
||||
use Slic3r::ExtrusionLoop ':roles';
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Flow ':roles';
|
||||
use Slic3r::Geometry qw(PI A B scale unscale chained_path);
|
||||
use Slic3r::Geometry::Clipper qw(union_ex diff_ex intersection_ex
|
||||
offset offset_ex offset2 offset2_ex union_pt diff intersection
|
||||
union diff intersection_ppl diff_ppl);
|
||||
use Slic3r::Geometry qw(scale);
|
||||
use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex
|
||||
);
|
||||
use Slic3r::Surface ':types';
|
||||
|
||||
|
||||
|
@ -31,418 +28,28 @@ sub config { return $_[0]->region->config; }
|
|||
sub make_perimeters {
|
||||
my ($self, $slices, $fill_surfaces) = @_;
|
||||
|
||||
# other perimeters
|
||||
my $perimeter_flow = $self->flow(FLOW_ROLE_PERIMETER);
|
||||
my $mm3_per_mm = $perimeter_flow->mm3_per_mm;
|
||||
my $pwidth = $perimeter_flow->scaled_width;
|
||||
my $pspacing = $perimeter_flow->scaled_spacing;
|
||||
|
||||
# external perimeters
|
||||
my $ext_perimeter_flow = $self->flow(FLOW_ROLE_EXTERNAL_PERIMETER);
|
||||
my $ext_mm3_per_mm = $ext_perimeter_flow->mm3_per_mm;
|
||||
my $ext_pwidth = $ext_perimeter_flow->scaled_width;
|
||||
my $ext_pspacing = scale($ext_perimeter_flow->spacing_to($perimeter_flow));
|
||||
|
||||
# overhang perimeters
|
||||
my $overhang_flow = $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object);
|
||||
my $mm3_per_mm_overhang = $overhang_flow->mm3_per_mm;
|
||||
|
||||
# solid infill
|
||||
my $solid_infill_flow = $self->flow(FLOW_ROLE_SOLID_INFILL);
|
||||
my $ispacing = $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);
|
||||
|
||||
$self->perimeters->clear;
|
||||
$self->thin_fills->clear;
|
||||
|
||||
my @contours = (); # array of Polygons with ccw orientation
|
||||
my @holes = (); # array of Polygons with cw orientation
|
||||
my @thin_walls = (); # array of ExPolygons
|
||||
|
||||
# we need to process each island separately because we might have different
|
||||
# extra perimeters for each one
|
||||
foreach my $surface (@$slices) {
|
||||
# detect how many perimeters must be generated for this island
|
||||
my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0);
|
||||
my $generator = Slic3r::Layer::PerimeterGenerator->new(
|
||||
# input:
|
||||
config => $self->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),
|
||||
|
||||
my @last = @{$surface->expolygon};
|
||||
my @gaps = (); # array of ExPolygons
|
||||
if ($loop_number > 0) {
|
||||
# we loop one time more than needed in order to find gaps after the last perimeter was applied
|
||||
for my $i (1 .. ($loop_number+1)) { # outer loop is 1
|
||||
my @offsets = ();
|
||||
if ($i == 1) {
|
||||
# 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_ex(
|
||||
\@last,
|
||||
offset(\@offsets, +0.5*$ext_pwidth),
|
||||
1, # medial axis requires non-overlapping geometry
|
||||
);
|
||||
push @thin_walls, @$diff;
|
||||
}
|
||||
} else {
|
||||
my $distance = ($i == 2) ? $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->region->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*$pspacing),
|
||||
offset(\@offsets, +0.5*$pspacing + 10), # safety offset
|
||||
);
|
||||
push @gaps, grep abs($_->area) >= $gap_area_threshold, @$diff;
|
||||
}
|
||||
}
|
||||
|
||||
last if !@offsets;
|
||||
last if $i > $loop_number; # we were only looking for gaps this time
|
||||
|
||||
# clone polygons because these ExPolygons will go out of scope very soon
|
||||
@last = @offsets;
|
||||
foreach my $polygon (@offsets) {
|
||||
if ($polygon->is_counter_clockwise) {
|
||||
push @contours, $polygon;
|
||||
} else {
|
||||
push @holes, $polygon;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# fill gaps
|
||||
if (@gaps) {
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"gaps.svg",
|
||||
expolygons => \@gaps,
|
||||
);
|
||||
}
|
||||
|
||||
# where $pwidth < thickness < 2*$pspacing, infill with width = 1.5*$pwidth
|
||||
# where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth
|
||||
my @gap_sizes = (
|
||||
[ $pwidth, 2*$pspacing, unscale 1.5*$pwidth ],
|
||||
[ 0.5*$pwidth, $pwidth, unscale 0.5*$pwidth ],
|
||||
);
|
||||
foreach my $gap_size (@gap_sizes) {
|
||||
my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps);
|
||||
$self->thin_fills->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)};
|
||||
}
|
||||
}
|
||||
|
||||
# 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 $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
|
||||
$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)} ],
|
||||
-($pspacing/2 + $min_perimeter_infill_spacing/2),
|
||||
+$min_perimeter_infill_spacing/2,
|
||||
)};
|
||||
}
|
||||
|
||||
|
||||
# process thin walls by collapsing slices to single passes
|
||||
my @thin_wall_polylines = ();
|
||||
if (@thin_walls) {
|
||||
# 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 = $pwidth / 4;
|
||||
@thin_walls = @{offset2_ex([ map @$_, @thin_walls ], -$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_wall_polylines = map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls;
|
||||
Slic3r::debugf " %d thin walls detected\n", scalar(@thin_wall_polylines) if $Slic3r::debug;
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"medial_axis.svg",
|
||||
no_arrows => 1,
|
||||
expolygons => \@thin_walls,
|
||||
green_polylines => [ map $_->polygon->split_at_first_point, @{$self->perimeters} ],
|
||||
red_polylines => \@thin_wall_polylines,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# find nesting hierarchies separately for contours and holes
|
||||
my $contours_pt = union_pt(\@contours);
|
||||
my $holes_pt = union_pt(\@holes);
|
||||
|
||||
# prepare grown lower layer slices for overhang detection
|
||||
my $lower_slices = Slic3r::ExPolygon::Collection->new;
|
||||
if ($self->layer->lower_layer && $self->region->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->layer->print->config->get_at('nozzle_diameter', $self->region->config->perimeter_extruder-1);
|
||||
$lower_slices->append($_)
|
||||
for @{offset_ex([ map @$_, @{$self->layer->lower_layer->slices} ], scale +$nozzle_diameter/2)};
|
||||
}
|
||||
my $lower_slices_p = $lower_slices->polygons;
|
||||
|
||||
# prepare a coderef for traversing the PolyTree object
|
||||
# external contours are root items of $contours_pt
|
||||
# internal contours are the ones next to external
|
||||
my $traverse;
|
||||
$traverse = sub {
|
||||
my ($polynodes, $depth, $is_contour) = @_;
|
||||
|
||||
# convert all polynodes to ExtrusionLoop objects
|
||||
my $collection = Slic3r::ExtrusionPath::Collection->new; # temporary collection
|
||||
my @children = ();
|
||||
foreach my $polynode (@$polynodes) {
|
||||
my $polygon = ($polynode->{outer} // $polynode->{hole})->clone;
|
||||
|
||||
my $role = EXTR_ROLE_PERIMETER;
|
||||
my $loop_role = EXTRL_ROLE_DEFAULT;
|
||||
|
||||
my $root_level = $depth == 0;
|
||||
my $no_children = !@{ $polynode->{children} };
|
||||
my $is_external = $is_contour ? $root_level : $no_children;
|
||||
my $is_internal = $is_contour ? $no_children : $root_level;
|
||||
if ($is_contour && $is_internal) {
|
||||
# internal perimeters are root level in case of holes
|
||||
# and items with no children in case of contours
|
||||
# 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;
|
||||
}
|
||||
if ($is_external) {
|
||||
# external perimeters are root level in case of contours
|
||||
# and items with no children in case of holes
|
||||
$role = EXTR_ROLE_EXTERNAL_PERIMETER;
|
||||
}
|
||||
|
||||
# detect overhanging/bridging perimeters
|
||||
my @paths = ();
|
||||
if ($self->region->config->overhangs && $self->layer->id > 0) {
|
||||
# get non-overhang paths by intersecting this loop with the grown lower slices
|
||||
foreach my $polyline (@{ intersection_ppl([ $polygon ], $lower_slices_p) }) {
|
||||
push @paths, Slic3r::ExtrusionPath->new(
|
||||
polyline => $polyline,
|
||||
role => $role,
|
||||
mm3_per_mm => ($is_external ? $ext_mm3_per_mm : $mm3_per_mm),
|
||||
width => ($is_external ? $ext_perimeter_flow->width : $perimeter_flow->width),
|
||||
height => $self->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([ $polygon ], $lower_slices_p) }) {
|
||||
push @paths, Slic3r::ExtrusionPath->new(
|
||||
polyline => $polyline,
|
||||
role => EXTR_ROLE_OVERHANG_PERIMETER,
|
||||
mm3_per_mm => $mm3_per_mm_overhang,
|
||||
width => $overhang_flow->width,
|
||||
height => $self->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 => $polygon->split_at_first_point,
|
||||
role => $role,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
width => $perimeter_flow->width,
|
||||
height => $self->height,
|
||||
);
|
||||
}
|
||||
my $loop = Slic3r::ExtrusionLoop->new_from_paths(@paths);
|
||||
$loop->role($loop_role);
|
||||
|
||||
# return ccw contours and cw holes
|
||||
# GCode.pm will convert all of them to ccw, but it needs to know
|
||||
# what the holes are in order to compute the correct inwards move
|
||||
# We do this on the final Loop object instead of the polygon because
|
||||
# overhang clipping might have reversed its order since Clipper does
|
||||
# not preserve polyline orientation.
|
||||
if ($is_contour) {
|
||||
$loop->make_counter_clockwise;
|
||||
} else {
|
||||
$loop->make_clockwise;
|
||||
}
|
||||
$collection->append($loop);
|
||||
|
||||
# save the children
|
||||
push @children, $polynode->{children};
|
||||
}
|
||||
|
||||
# if we're handling the top-level contours, add thin walls as candidates too
|
||||
# in order to include them in the nearest-neighbor search
|
||||
if ($is_contour && $depth == 0) {
|
||||
foreach my $polyline (@thin_wall_polylines) {
|
||||
$collection->append(Slic3r::ExtrusionPath->new(
|
||||
polyline => $polyline,
|
||||
role => EXTR_ROLE_EXTERNAL_PERIMETER,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
width => $perimeter_flow->width,
|
||||
height => $self->height,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
# use a nearest neighbor search to order these children
|
||||
# TODO: supply second argument to chained_path() too?
|
||||
# (We used to skip this chiained_path() when $is_contour &&
|
||||
# $depth == 0 because slices are ordered at G_code export
|
||||
# time, but multiple top-level perimeters might belong to
|
||||
# the same slice actually, so that was a broken optimization.)
|
||||
my $sorted_collection = $collection->chained_path_indices(0);
|
||||
my @orig_indices = @{$sorted_collection->orig_indices};
|
||||
|
||||
my @loops = ();
|
||||
foreach my $loop (@$sorted_collection) {
|
||||
my $orig_index = shift @orig_indices;
|
||||
|
||||
if ($loop->isa('Slic3r::ExtrusionPath')) {
|
||||
push @loops, $loop->clone;
|
||||
} else {
|
||||
# if this is an external contour find all holes belonging to this contour(s)
|
||||
# and prepend them
|
||||
if ($is_contour && $depth == 0) {
|
||||
# $loop is the outermost loop of an island
|
||||
my @holes = ();
|
||||
for (my $i = 0; $i <= $#$holes_pt; $i++) {
|
||||
if ($loop->polygon->contains_point($holes_pt->[$i]{outer}->first_point)) {
|
||||
push @holes, splice @$holes_pt, $i, 1; # remove from candidates to reduce complexity
|
||||
$i--;
|
||||
}
|
||||
}
|
||||
|
||||
# order holes efficiently
|
||||
@holes = @holes[@{chained_path([ map {($_->{outer} // $_->{hole})->first_point} @holes ])}];
|
||||
|
||||
push @loops, reverse map $traverse->([$_], 0, 0), @holes;
|
||||
}
|
||||
|
||||
# traverse children and prepend them to this loop
|
||||
push @loops, $traverse->($children[$orig_index], $depth+1, $is_contour);
|
||||
push @loops, $loop->clone;
|
||||
}
|
||||
}
|
||||
return @loops;
|
||||
};
|
||||
|
||||
# order loops from inner to outer (in terms of object slices)
|
||||
my @loops = $traverse->($contours_pt, 0, 1);
|
||||
|
||||
# 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
|
||||
@loops = reverse @loops
|
||||
if $self->region->config->external_perimeters_first
|
||||
|| ($self->layer->id == 0 && $self->print->config->brim_width > 0);
|
||||
|
||||
# append perimeters
|
||||
$self->perimeters->append($_) for @loops;
|
||||
}
|
||||
|
||||
sub _fill_gaps {
|
||||
my ($self, $min, $max, $w, $gaps) = @_;
|
||||
|
||||
my $this = diff_ex(
|
||||
offset2([ map @$_, @$gaps ], -$min/2, +$min/2),
|
||||
offset2([ map @$_, @$gaps ], -$max/2, +$max/2),
|
||||
1,
|
||||
# output:
|
||||
loops => $self->perimeters,
|
||||
gap_fill => $self->thin_fills,
|
||||
fill_surfaces => $fill_surfaces,
|
||||
);
|
||||
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 = $self->flow(FLOW_ROLE_SOLID_INFILL, 0, $w);
|
||||
my %path_args = (
|
||||
role => EXTR_ROLE_GAPFILL,
|
||||
mm3_per_mm => $flow->mm3_per_mm,
|
||||
width => $flow->width,
|
||||
height => $self->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;
|
||||
$generator->process;
|
||||
}
|
||||
|
||||
sub prepare_fill_surfaces {
|
||||
|
|
|
@ -7,6 +7,11 @@ sub new_scale {
|
|||
return $class->new(map Slic3r::Geometry::scale($_), @_);
|
||||
}
|
||||
|
||||
sub dump_perl {
|
||||
my $self = shift;
|
||||
return sprintf "[%s,%s]", @$self;
|
||||
}
|
||||
|
||||
package Slic3r::Pointf;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
|
|
@ -54,7 +54,7 @@ sub BUILD {
|
|||
if $self->config->spiral_vase;
|
||||
|
||||
$self->_vibration_limit(Slic3r::GCode::VibrationLimit->new(config => $self->config))
|
||||
if $self->config->vibration_limit > 0;
|
||||
if $self->config->vibration_limit != 0;
|
||||
|
||||
$self->_arc_fitting(Slic3r::GCode::ArcFitting->new(config => $self->config))
|
||||
if $self->config->gcode_arcs;
|
||||
|
@ -121,23 +121,30 @@ sub export {
|
|||
|
||||
# initialize a motion planner for object-to-object travel moves
|
||||
if ($self->config->avoid_crossing_perimeters) {
|
||||
my $distance_from_objects = 1;
|
||||
my $distance_from_objects = scale 1;
|
||||
|
||||
# compute the offsetted convex hull for each object and repeat it for each copy.
|
||||
my @islands = ();
|
||||
foreach my $obj_idx (0 .. ($self->print->object_count - 1)) {
|
||||
my @islands_p = ();
|
||||
foreach my $object (@{$self->objects}) {
|
||||
# compute the convex hull of the entire object
|
||||
my $convex_hull = convex_hull([
|
||||
map @{$_->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers},
|
||||
map @{$_->contour}, map @{$_->slices}, @{$object->layers},
|
||||
]);
|
||||
# discard layers only containing thin walls (offset would fail on an empty polygon)
|
||||
if (@$convex_hull) {
|
||||
my $expolygon = Slic3r::ExPolygon->new($convex_hull);
|
||||
my @island = @{$expolygon->offset_ex(scale $distance_from_objects, 1, JT_SQUARE)};
|
||||
foreach my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) {
|
||||
push @islands, map { my $c = $_->clone; $c->translate(@$copy); $c } @island;
|
||||
}
|
||||
|
||||
# discard objects only containing thin walls (offset would fail on an empty polygon)
|
||||
next if !@$convex_hull;
|
||||
|
||||
# grow convex hull by the wanted clearance
|
||||
my @obj_islands_p = @{offset([$convex_hull], $distance_from_objects, 1, JT_SQUARE)};
|
||||
|
||||
# translate convex hull for each object copy and append it to the islands array
|
||||
foreach my $copy (@{ $object->_shifted_copies }) {
|
||||
my @copy_islands_p = map $_->clone, @obj_islands_p;
|
||||
$_->translate(@$copy) for @copy_islands_p;
|
||||
push @islands_p, @copy_islands_p;
|
||||
}
|
||||
}
|
||||
$gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex([ map @$_, @islands ]));
|
||||
$gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex(\@islands_p));
|
||||
}
|
||||
|
||||
# calculate wiping points if needed
|
||||
|
@ -208,7 +215,7 @@ sub export {
|
|||
}
|
||||
$self->process_layer($layer, [$copy]);
|
||||
}
|
||||
$self->flush_cooling_buffer;
|
||||
$self->flush_filters;
|
||||
$finished_objects++;
|
||||
}
|
||||
}
|
||||
|
@ -234,7 +241,7 @@ sub export {
|
|||
}
|
||||
}
|
||||
}
|
||||
$self->flush_cooling_buffer;
|
||||
$self->flush_filters;
|
||||
}
|
||||
|
||||
# write end commands to file
|
||||
|
@ -357,7 +364,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->disable_once(1);
|
||||
|
||||
# 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);
|
||||
}
|
||||
}
|
||||
|
||||
# extrude brim
|
||||
|
@ -369,6 +381,8 @@ sub process_layer {
|
|||
for @{$self->print->brim};
|
||||
$self->_brim_done(1);
|
||||
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0);
|
||||
|
||||
# allow a straight travel move to the first object point
|
||||
$self->_gcodegen->avoid_crossing_perimeters->disable_once(1);
|
||||
}
|
||||
|
||||
|
@ -413,17 +427,17 @@ sub process_layer {
|
|||
# process perimeters
|
||||
{
|
||||
my $extruder_id = $region->config->perimeter_extruder-1;
|
||||
foreach my $perimeter (@{$layerm->perimeters}) {
|
||||
foreach my $perimeter_coll (@{$layerm->perimeters}) {
|
||||
# init by_extruder item only if we actually use the extruder
|
||||
$by_extruder{$extruder_id} //= [];
|
||||
|
||||
# $perimeter is an ExtrusionLoop or ExtrusionPath object
|
||||
# $perimeter_coll is an ExtrusionPath::Collection object representing a single slice
|
||||
for my $i (0 .. $#{$layer->slices}) {
|
||||
if ($i == $#{$layer->slices}
|
||||
|| $layer->slices->[$i]->contour->contains_point($perimeter->first_point)) {
|
||||
|| $layer->slices->[$i]->contour->contains_point($perimeter_coll->first_point)) {
|
||||
$by_extruder{$extruder_id}[$i] //= { perimeters => {} };
|
||||
$by_extruder{$extruder_id}[$i]{perimeters}{$region_id} //= [];
|
||||
push @{ $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} }, $perimeter;
|
||||
push @{ $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} }, @$perimeter_coll;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
@ -532,28 +546,29 @@ sub _extrude_infill {
|
|||
return $gcode;
|
||||
}
|
||||
|
||||
sub flush_cooling_buffer {
|
||||
sub flush_filters {
|
||||
my ($self) = @_;
|
||||
print {$self->fh} $self->filter($self->_cooling_buffer->flush);
|
||||
|
||||
print {$self->fh} $self->filter($self->_cooling_buffer->flush, 1);
|
||||
}
|
||||
|
||||
sub filter {
|
||||
my ($self, $gcode) = @_;
|
||||
my ($self, $gcode, $flush) = @_;
|
||||
|
||||
# apply vibration limit if enabled;
|
||||
# this injects pauses according to time (thus depends on actual speeds)
|
||||
$gcode = $self->_vibration_limit->process($gcode)
|
||||
if $self->print->config->vibration_limit != 0;
|
||||
if defined $self->_vibration_limit;
|
||||
|
||||
# apply pressure regulation if enabled;
|
||||
# this depends on actual speeds
|
||||
$gcode = $self->_pressure_regulator->process($gcode)
|
||||
if $self->print->config->pressure_advance > 0;
|
||||
$gcode = $self->_pressure_regulator->process($gcode, $flush)
|
||||
if defined $self->_pressure_regulator;
|
||||
|
||||
# apply arc fitting if enabled;
|
||||
# this does not depend on speeds but changes G1 XY commands into G2/G2 IJ
|
||||
$gcode = $self->_arc_fitting->process($gcode)
|
||||
if $self->print->config->gcode_arcs;
|
||||
if defined $self->_arc_fitting;
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
|
|
@ -178,7 +178,7 @@ sub contact_area {
|
|||
# TODO: split_at_first_point() could split a bridge mid-way
|
||||
my @overhang_perimeters =
|
||||
map { $_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline->clone }
|
||||
@{$layerm->perimeters};
|
||||
map @$_, @{$layerm->perimeters};
|
||||
|
||||
# workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline()
|
||||
$_->[0]->translate(1,0) for @overhang_perimeters;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue