Merge branch 'master' into sender

Conflicts:
	lib/Slic3r/GUI/Tab.pm
This commit is contained in:
Alessandro Ranellucci 2015-01-08 22:47:43 +01:00
commit 9ec7b43ca1
53 changed files with 1535 additions and 838 deletions

View file

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

View file

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

View file

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

View file

@ -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 &copy; 2011-2014 Alessandro Ranellucci. <br />' .
'Copyright &copy; 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 />' .

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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