diff --git a/README.md b/README.md
index 653e40246f..70ec89d1d6 100644
--- a/README.md
+++ b/README.md
@@ -134,6 +134,7 @@ The author of the Silk icon set is Mark James.
default: reprap)
--use-relative-e-distances Enable this to get relative E values (default: no)
--use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no)
+ --use-volumetric-e Express E in cubic millimeters and prepend M200 (default: no)
--gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported
by all firmwares)
--gcode-comments Make G-code verbose by adding comments (default: no)
diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm
index 67c464f954..2ddb1050f8 100644
--- a/lib/Slic3r.pm
+++ b/lib/Slic3r.pm
@@ -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;
diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm
index 0a8f3951c3..c3bc2dfb4c 100644
--- a/lib/Slic3r/GCode.pm
+++ b/lib/Slic3r/GCode.pm
@@ -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;
diff --git a/lib/Slic3r/GCode/PressureRegulator.pm b/lib/Slic3r/GCode/PressureRegulator.pm
index 6acbf0e098..8d1cd65257 100644
--- a/lib/Slic3r/GCode/PressureRegulator.pm
+++ b/lib/Slic3r/GCode/PressureRegulator.pm
@@ -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;
}
diff --git a/lib/Slic3r/GUI/AboutDialog.pm b/lib/Slic3r/GUI/AboutDialog.pm
index 3f946bb4c7..b64bbea408 100644
--- a/lib/Slic3r/GUI/AboutDialog.pm
+++ b/lib/Slic3r/GUI/AboutDialog.pm
@@ -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 {
'' .
'
' .
'' .
- 'Copyright © 2011-2014 Alessandro Ranellucci.
' .
+ 'Copyright © 2011-2015 Alessandro Ranellucci.
' .
'Slic3r is licensed under the ' .
'GNU Affero General Public License, version 3.' .
'
' .
diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm
index a0721acd0f..3e6f09b27d 100644
--- a/lib/Slic3r/GUI/Plater/2DToolpaths.pm
+++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm
@@ -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)) {
diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm
index ffa7c61b79..6872af9898 100644
--- a/lib/Slic3r/GUI/PreviewCanvas.pm
+++ b/lib/Slic3r/GUI/PreviewCanvas.pm
@@ -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();
diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm
index d46f06d6e6..a306131973 100644
--- a/lib/Slic3r/GUI/Tab.pm
+++ b/lib/Slic3r/GUI/Tab.pm
@@ -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;
diff --git a/lib/Slic3r/Layer/PerimeterGenerator.pm b/lib/Slic3r/Layer/PerimeterGenerator.pm
new file mode 100644
index 0000000000..90893d5357
--- /dev/null
+++ b/lib/Slic3r/Layer/PerimeterGenerator.pm
@@ -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;
diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm
index 8e3db00e52..88f0db01f8 100644
--- a/lib/Slic3r/Layer/Region.pm
+++ b/lib/Slic3r/Layer/Region.pm
@@ -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 {
diff --git a/lib/Slic3r/Point.pm b/lib/Slic3r/Point.pm
index abb7a5edfd..6d9e0891a7 100644
--- a/lib/Slic3r/Point.pm
+++ b/lib/Slic3r/Point.pm
@@ -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;
diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm
index 12f6093b3c..be90a79983 100644
--- a/lib/Slic3r/Print/GCode.pm
+++ b/lib/Slic3r/Print/GCode.pm
@@ -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;
}
diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm
index 7e23a75c7b..e431f3fb52 100644
--- a/lib/Slic3r/Print/SupportMaterial.pm
+++ b/lib/Slic3r/Print/SupportMaterial.pm
@@ -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;
diff --git a/slic3r.pl b/slic3r.pl
index debc5ff79a..bea11ce1b0 100755
--- a/slic3r.pl
+++ b/slic3r.pl
@@ -286,6 +286,7 @@ $j
default: $config->{gcode_flavor})
--use-relative-e-distances Enable this to get relative E values (default: no)
--use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no)
+ --use-volumetric-e Express E in cubic millimeters and prepend M200 (default: no)
--gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported
by all firmwares)
--gcode-comments Make G-code verbose by adding comments (default: no)
diff --git a/t/perimeters.t b/t/perimeters.t
index 49217fd826..e2a5bc3443 100644
--- a/t/perimeters.t
+++ b/t/perimeters.t
@@ -1,4 +1,4 @@
-use Test::More tests => 11;
+use Test::More tests => 29;
use strict;
use warnings;
@@ -7,6 +7,8 @@ BEGIN {
use lib "$FindBin::Bin/../lib";
}
+use Slic3r::ExtrusionLoop ':roles';
+use Slic3r::ExtrusionPath ':roles';
use List::Util qw(first);
use Slic3r;
use Slic3r::Flow ':roles';
@@ -188,7 +190,7 @@ use Slic3r::Test;
my $pflow = $layerm->flow(FLOW_ROLE_PERIMETER);
my $iflow = $layerm->flow(FLOW_ROLE_INFILL);
my $covered_by_perimeters = union_ex([
- (map @{$_->polygon->split_at_first_point->grow($pflow->scaled_width/2)}, @{$layerm->perimeters}),
+ (map @{$_->polygon->split_at_first_point->grow($pflow->scaled_width/2)}, map @$_, @{$layerm->perimeters}),
]);
my $covered_by_infill = union_ex([
(map $_->p, @{$layerm->fill_surfaces}),
@@ -285,4 +287,89 @@ use Slic3r::Test;
$test->('small_dorito');
}
+{
+ my $flow = Slic3r::Flow->new(
+ width => 1,
+ height => 1,
+ nozzle_diameter => 1,
+ );
+
+ my $config = Slic3r::Config->new;
+ my $test = sub {
+ my ($expolygons, %expected) = @_;
+
+ my $slices = Slic3r::Surface::Collection->new;
+ $slices->append(Slic3r::Surface->new(
+ surface_type => S_TYPE_INTERNAL,
+ expolygon => $_,
+ )) for @$expolygons;
+
+ my $g = Slic3r::Layer::PerimeterGenerator->new(
+ # input:
+ layer_height => 1,
+ slices => $slices,
+ flow => $flow,
+ );
+ $g->config->apply_dynamic($config);
+ $g->process;
+
+ is scalar(@{$g->loops}),
+ scalar(@$expolygons), 'expected number of collections';
+ ok !defined(first { !$_->isa('Slic3r::ExtrusionPath::Collection') } @{$g->loops}),
+ 'everything is returned as collections';
+ is scalar(map @$_, @{$g->loops}),
+ $expected{total}, 'expected number of loops';
+ is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, map @$_, @{$g->loops}),
+ $expected{external}, 'expected number of external loops';
+ is scalar(grep $_->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, map @$_, @{$g->loops}),
+ $expected{cinternal}, 'expected number of internal contour loops';
+ is scalar(grep $_->polygon->is_counter_clockwise, map @$_, @{$g->loops}),
+ $expected{ccw}, 'expected number of ccw loops';
+
+ return $g;
+ };
+
+ $config->set('perimeters', 3);
+ $test->(
+ [
+ Slic3r::ExPolygon->new(
+ Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]),
+ ),
+ ],
+ total => 3,
+ external => 1,
+ cinternal => 1,
+ ccw => 3,
+ );
+ $test->(
+ [
+ Slic3r::ExPolygon->new(
+ Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]),
+ Slic3r::Polygon->new_scale([40,40], [40,60], [60,60], [60,40]),
+ ),
+ ],
+ total => 6,
+ external => 2,
+ cinternal => 1,
+ ccw => 3,
+ );
+ $test->(
+ [
+ Slic3r::ExPolygon->new(
+ Slic3r::Polygon->new_scale([0,0], [200,0], [200,200], [0,200]),
+ Slic3r::Polygon->new_scale([20,20], [20,180], [180,180], [180,20]),
+ ),
+ # nested:
+ Slic3r::ExPolygon->new(
+ Slic3r::Polygon->new_scale([50,50], [150,50], [150,150], [50,150]),
+ Slic3r::Polygon->new_scale([80,80], [80,120], [120,120], [120,80]),
+ ),
+ ],
+ total => 4*3,
+ external => 4,
+ cinternal => 2,
+ ccw => 2*3,
+ );
+}
+
__END__
diff --git a/t/pressure.t b/t/pressure.t
new file mode 100644
index 0000000000..fd9045c826
--- /dev/null
+++ b/t/pressure.t
@@ -0,0 +1,36 @@
+use Test::More tests => 1;
+use strict;
+use warnings;
+
+BEGIN {
+ use FindBin;
+ use lib "$FindBin::Bin/../lib";
+}
+
+use List::Util qw();
+use Slic3r;
+use Slic3r::Geometry qw(epsilon);
+use Slic3r::Test;
+
+{
+ my $config = Slic3r::Config->new_from_defaults;
+ $config->set('pressure_advance', 10);
+ $config->set('retract_length', [1]);
+
+ my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2);
+ my $retracted = $config->retract_length->[0];
+ Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
+ my ($self, $cmd, $args, $info) = @_;
+
+ if ($info->{extruding} && !$info->{dist_XY}) {
+ $retracted += $info->{dist_E};
+ } elsif ($info->{retracting}) {
+ $retracted += $info->{dist_E};
+ }
+ });
+
+ ok abs($retracted) < epsilon, 'all retractions are compensated';
+}
+
+
+__END__
diff --git a/t/retraction.t b/t/retraction.t
index f1656bc7fa..4a8993ae59 100644
--- a/t/retraction.t
+++ b/t/retraction.t
@@ -1,4 +1,4 @@
-use Test::More tests => 19;
+use Test::More tests => 18;
use strict;
use warnings;
@@ -200,29 +200,4 @@ use Slic3r::Test qw(_eq);
ok $retracted, 'retracting also when --retract-length is 0 but --use-firmware-retraction is enabled';
}
-{
- my $config = Slic3r::Config->new_from_defaults;
- $config->set('only_retract_when_crossing_perimeters', 1);
- $config->set('fill_density', 0);
-
- my $print = Slic3r::Test::init_print('cube_with_hole', config => $config);
- my $retracted = 0;
- my $traveling_without_retraction = 0;
- Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
- my ($self, $cmd, $args, $info) = @_;
-
- if ($info->{retracting}) {
- $retracted = 1;
- } elsif ($info->{extruding} && $retracted) {
- $retracted = 0;
- } elsif ($info->{travel} && !$retracted) {
- if ($info->{dist_XY} > $config->retract_before_travel->[0]) {
- $traveling_without_retraction = 1;
- }
- }
- });
-
- ok !$traveling_without_retraction, 'always retract when using only_retract_when_crossing_perimeters and fill_density = 0';
-}
-
__END__
diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp
index 34b75940be..2c5246aadb 100644
--- a/xs/src/libslic3r/ExPolygon.cpp
+++ b/xs/src/libslic3r/ExPolygon.cpp
@@ -105,6 +105,23 @@ ExPolygon::contains(const Point &point) const
return true;
}
+// inclusive version of contains() that also checks whether point is on boundaries
+bool
+ExPolygon::contains_b(const Point &point) const
+{
+ return this->contains(point) || this->has_boundary_point(point);
+}
+
+bool
+ExPolygon::has_boundary_point(const Point &point) const
+{
+ if (this->contour.has_boundary_point(point)) return true;
+ for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) {
+ if (h->has_boundary_point(point)) return true;
+ }
+ return false;
+}
+
Polygons
ExPolygon::simplify_p(double tolerance) const
{
@@ -364,6 +381,16 @@ ExPolygon::triangulate_p2t(Polygons* polygons) const
}
}
+Lines
+ExPolygon::lines() const
+{
+ Lines lines;
+ this->contour.lines(&lines);
+ for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h)
+ h->lines(&lines);
+ return lines;
+}
+
#ifdef SLIC3RXS
REGISTER_CLASS(ExPolygon, "ExPolygon");
diff --git a/xs/src/libslic3r/ExPolygon.hpp b/xs/src/libslic3r/ExPolygon.hpp
index 9d65fcb2af..3d7cd35409 100644
--- a/xs/src/libslic3r/ExPolygon.hpp
+++ b/xs/src/libslic3r/ExPolygon.hpp
@@ -25,6 +25,8 @@ class ExPolygon
bool contains(const Line &line) const;
bool contains(const Polyline &polyline) const;
bool contains(const Point &point) const;
+ bool contains_b(const Point &point) const;
+ bool has_boundary_point(const Point &point) const;
Polygons simplify_p(double tolerance) const;
ExPolygons simplify(double tolerance) const;
void simplify(double tolerance, ExPolygons &expolygons) const;
@@ -36,6 +38,7 @@ class ExPolygon
void triangulate(Polygons* polygons) const;
void triangulate_pp(Polygons* polygons) const;
void triangulate_p2t(Polygons* polygons) const;
+ Lines lines() const;
#ifdef SLIC3RXS
void from_SV(SV* poly_sv);
diff --git a/xs/src/libslic3r/ExPolygonCollection.cpp b/xs/src/libslic3r/ExPolygonCollection.cpp
index 6f29dbb0d0..aaa747d8d5 100644
--- a/xs/src/libslic3r/ExPolygonCollection.cpp
+++ b/xs/src/libslic3r/ExPolygonCollection.cpp
@@ -3,6 +3,11 @@
namespace Slic3r {
+ExPolygonCollection::ExPolygonCollection(const ExPolygon &expolygon)
+{
+ this->expolygons.push_back(expolygon);
+}
+
ExPolygonCollection::operator Points() const
{
Points points;
@@ -68,6 +73,15 @@ template bool ExPolygonCollection::contains(const Point &item) const;
template bool ExPolygonCollection::contains(const Line &item) const;
template bool ExPolygonCollection::contains(const Polyline &item) const;
+bool
+ExPolygonCollection::contains_b(const Point &point) const
+{
+ for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
+ if (it->contains_b(point)) return true;
+ }
+ return false;
+}
+
void
ExPolygonCollection::simplify(double tolerance)
{
@@ -87,6 +101,17 @@ ExPolygonCollection::convex_hull(Polygon* hull) const
Slic3r::Geometry::convex_hull(pp, hull);
}
+Lines
+ExPolygonCollection::lines() const
+{
+ Lines lines;
+ for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
+ Lines ex_lines = it->lines();
+ lines.insert(lines.end(), ex_lines.begin(), ex_lines.end());
+ }
+ return lines;
+}
+
#ifdef SLIC3RXS
REGISTER_CLASS(ExPolygonCollection, "ExPolygon::Collection");
#endif
diff --git a/xs/src/libslic3r/ExPolygonCollection.hpp b/xs/src/libslic3r/ExPolygonCollection.hpp
index 6aa5b0a567..cca6064d51 100644
--- a/xs/src/libslic3r/ExPolygonCollection.hpp
+++ b/xs/src/libslic3r/ExPolygonCollection.hpp
@@ -17,6 +17,7 @@ class ExPolygonCollection
ExPolygons expolygons;
ExPolygonCollection() {};
+ ExPolygonCollection(const ExPolygon &expolygon);
ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {};
operator Points() const;
operator Polygons() const;
@@ -25,8 +26,10 @@ class ExPolygonCollection
void translate(double x, double y);
void rotate(double angle, const Point ¢er);
template bool contains(const T &item) const;
+ bool contains_b(const Point &point) const;
void simplify(double tolerance);
void convex_hull(Polygon* hull) const;
+ Lines lines() const;
};
}
diff --git a/xs/src/libslic3r/Extruder.cpp b/xs/src/libslic3r/Extruder.cpp
index d0934851e6..8cbd00b738 100644
--- a/xs/src/libslic3r/Extruder.cpp
+++ b/xs/src/libslic3r/Extruder.cpp
@@ -9,8 +9,12 @@ Extruder::Extruder(int id, GCodeConfig *config)
reset();
// cache values that are going to be called often
- this->e_per_mm3 = this->extrusion_multiplier()
- * (4 / ((this->filament_diameter() * this->filament_diameter()) * PI));
+ if (config->use_volumetric_e) {
+ this->e_per_mm3 = this->extrusion_multiplier();
+ } else {
+ this->e_per_mm3 = this->extrusion_multiplier()
+ * (4 / ((this->filament_diameter() * this->filament_diameter()) * PI));
+ }
this->retract_speed_mm_min = this->retract_speed() * 60;
}
@@ -80,12 +84,22 @@ Extruder::e_per_mm(double mm3_per_mm) const
double
Extruder::extruded_volume() const
{
+ if (this->config->use_volumetric_e) {
+ // Any current amount of retraction should not affect used filament, since
+ // it represents empty volume in the nozzle. We add it back to E.
+ return this->absolute_E + this->retracted;
+ }
+
return this->used_filament() * (this->filament_diameter() * this->filament_diameter()) * PI/4;
}
double
Extruder::used_filament() const
{
+ if (this->config->use_volumetric_e) {
+ return this->extruded_volume() / (this->filament_diameter() * this->filament_diameter() * PI/4);
+ }
+
// Any current amount of retraction should not affect used filament, since
// it represents empty volume in the nozzle. We add it back to E.
return this->absolute_E + this->retracted;
diff --git a/xs/src/libslic3r/ExtrusionEntity.cpp b/xs/src/libslic3r/ExtrusionEntity.cpp
index ee49e33578..843dfda293 100644
--- a/xs/src/libslic3r/ExtrusionEntity.cpp
+++ b/xs/src/libslic3r/ExtrusionEntity.cpp
@@ -278,7 +278,7 @@ ExtrusionLoop::split_at(const Point &point)
{
if (this->paths.empty()) return;
- // find the closest path and closest point
+ // find the closest path and closest point belonging to that path
size_t path_idx = 0;
Point p = this->paths.front().first_point();
double min = point.distance_to(p);
diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp
index 65a1aba599..c402911906 100644
--- a/xs/src/libslic3r/ExtrusionEntity.hpp
+++ b/xs/src/libslic3r/ExtrusionEntity.hpp
@@ -35,6 +35,9 @@ enum ExtrusionLoopRole {
class ExtrusionEntity
{
public:
+ virtual bool is_loop() const {
+ return false;
+ };
virtual ExtrusionEntity* clone() const = 0;
virtual ~ExtrusionEntity() {};
virtual void reverse() = 0;
@@ -84,6 +87,9 @@ class ExtrusionLoop : public ExtrusionEntity
ExtrusionLoopRole role;
ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : role(role) {};
+ bool is_loop() const {
+ return true;
+ };
operator Polygon() const;
ExtrusionLoop* clone() const;
bool make_clockwise();
diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.cpp b/xs/src/libslic3r/ExtrusionEntityCollection.cpp
index a958e53cfc..4e3c596cf6 100644
--- a/xs/src/libslic3r/ExtrusionEntityCollection.cpp
+++ b/xs/src/libslic3r/ExtrusionEntityCollection.cpp
@@ -37,7 +37,9 @@ void
ExtrusionEntityCollection::reverse()
{
for (ExtrusionEntitiesPtr::iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
- (*it)->reverse();
+ // Don't reverse it if it's a loop, as it doesn't change anything in terms of elements ordering
+ // and caller might rely on winding order
+ if (!(*it)->is_loop()) (*it)->reverse();
}
std::reverse(this->entities.begin(), this->entities.end());
}
@@ -96,7 +98,8 @@ ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCo
int start_index = start_near.nearest_point_index(endpoints);
int path_index = start_index/2;
ExtrusionEntity* entity = my_paths.at(path_index);
- if (start_index % 2 && !no_reverse) {
+ // never reverse loops, since it's pointless for chained path and callers might depend on orientation
+ if (start_index % 2 && !no_reverse && !entity->is_loop()) {
entity->reverse();
}
retval->entities.push_back(my_paths.at(path_index));
diff --git a/xs/src/libslic3r/GCodeWriter.cpp b/xs/src/libslic3r/GCodeWriter.cpp
index bb8035ac3c..0fe15809e4 100644
--- a/xs/src/libslic3r/GCodeWriter.cpp
+++ b/xs/src/libslic3r/GCodeWriter.cpp
@@ -48,22 +48,32 @@ GCodeWriter::set_extruders(const std::vector &extruder_ids)
std::string
GCodeWriter::preamble()
{
- std::string gcode;
+ std::ostringstream gcode;
if (FLAVOR_IS_NOT(gcfMakerWare)) {
- gcode += "G21 ; set units to millimeters\n";
- gcode += "G90 ; use absolute coordinates\n";
+ gcode << "G21 ; set units to millimeters\n";
+ gcode << "G90 ; use absolute coordinates\n";
}
if (FLAVOR_IS(gcfRepRap) || FLAVOR_IS(gcfTeacup)) {
if (this->config.use_relative_e_distances) {
- gcode += "M83 ; use relative distances for extrusion\n";
+ gcode << "M83 ; use relative distances for extrusion\n";
} else {
- gcode += "M82 ; use absolute distances for extrusion\n";
+ gcode << "M82 ; use absolute distances for extrusion\n";
}
- gcode += this->reset_e(true);
+ if (this->config.use_volumetric_e && this->config.start_gcode.value.find("M200") == std::string::npos) {
+ for (std::map::const_iterator it = this->extruders.begin(); it != this->extruders.end(); ++it) {
+ unsigned int extruder_id = it->first;
+ gcode << "M200 D" << E_NUM(this->config.filament_diameter.get_at(extruder_id));
+ if (this->multiple_extruders || extruder_id != 0) {
+ gcode << " T" << extruder_id;
+ }
+ gcode << " ; set filament diameter\n";
+ }
+ }
+ gcode << this->reset_e(true);
}
- return gcode;
+ return gcode.str();
}
std::string
@@ -423,6 +433,14 @@ GCodeWriter::_retract(double length, double restart_extra, const std::string &co
might be 0, in which case the retraction logic gets skipped. */
if (this->config.use_firmware_retraction) length = 1;
+ // If we use volumetric E values we turn lengths into volumes */
+ if (this->config.use_volumetric_e) {
+ double d = this->_extruder->filament_diameter();
+ double area = d * d * PI/4;
+ length = length * area;
+ restart_extra = restart_extra * area;
+ }
+
double dE = this->_extruder->retract(length, restart_extra);
if (dE != 0) {
if (this->config.use_firmware_retraction) {
diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp
index 47bef81019..38935fcedc 100644
--- a/xs/src/libslic3r/Layer.cpp
+++ b/xs/src/libslic3r/Layer.cpp
@@ -129,20 +129,18 @@ Layer::any_internal_region_slice_contains(const T &item) const
}
return false;
}
-template bool Layer::any_internal_region_slice_contains(const Line &item) const;
+template bool Layer::any_internal_region_slice_contains(const Polyline &item) const;
template
bool
-Layer::any_internal_region_fill_surface_contains(const T &item) const
+Layer::any_bottom_region_slice_contains(const T &item) const
{
FOREACH_LAYERREGION(this, layerm) {
- if ((*layerm)->fill_surfaces.any_internal_contains(item)) return true;
+ if ((*layerm)->slices.any_bottom_contains(item)) return true;
}
return false;
}
-template bool Layer::any_internal_region_fill_surface_contains(const Line &item) const;
-template bool Layer::any_internal_region_fill_surface_contains(const Polyline &item) const;
-
+template bool Layer::any_bottom_region_slice_contains(const Polyline &item) const;
#ifdef SLIC3RXS
REGISTER_CLASS(Layer, "Layer");
diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp
index 6a4e905c3b..5f6a6baa04 100644
--- a/xs/src/libslic3r/Layer.hpp
+++ b/xs/src/libslic3r/Layer.hpp
@@ -94,8 +94,8 @@ class Layer {
void make_slices();
template bool any_internal_region_slice_contains(const T &item) const;
- template bool any_internal_region_fill_surface_contains(const T &item) const;
-
+ template bool any_bottom_region_slice_contains(const T &item) const;
+
protected:
int _id; // sequential number of layer, 0-based
PrintObject *_object;
diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp
index f068cf24f4..7b8e77f5a5 100644
--- a/xs/src/libslic3r/LayerRegion.cpp
+++ b/xs/src/libslic3r/LayerRegion.cpp
@@ -44,7 +44,8 @@ void
LayerRegion::merge_slices()
{
ExPolygons expp;
- union_(this->slices, &expp);
+ // without safety offset, artifacts are generated (GH #2494)
+ union_(this->slices, &expp, true);
this->slices.surfaces.clear();
this->slices.surfaces.reserve(expp.size());
diff --git a/xs/src/libslic3r/Line.cpp b/xs/src/libslic3r/Line.cpp
index ef8598adaa..3cce6c9713 100644
--- a/xs/src/libslic3r/Line.cpp
+++ b/xs/src/libslic3r/Line.cpp
@@ -16,6 +16,13 @@ Line::wkt() const
return ss.str();
}
+Line::operator Lines() const
+{
+ Lines lines;
+ lines.push_back(*this);
+ return lines;
+}
+
Line::operator Polyline() const
{
Polyline pl;
diff --git a/xs/src/libslic3r/Line.hpp b/xs/src/libslic3r/Line.hpp
index 52f3ef77f3..76c385ce60 100644
--- a/xs/src/libslic3r/Line.hpp
+++ b/xs/src/libslic3r/Line.hpp
@@ -9,6 +9,7 @@ namespace Slic3r {
class Line;
class Linef3;
class Polyline;
+typedef std::vector Lines;
class Line
{
@@ -18,6 +19,7 @@ class Line
Line() {};
explicit Line(Point _a, Point _b): a(_a), b(_b) {};
std::string wkt() const;
+ operator Lines() const;
operator Polyline() const;
void scale(double factor);
void translate(double x, double y);
@@ -45,8 +47,6 @@ class Line
#endif
};
-typedef std::vector Lines;
-
class Linef3
{
public:
diff --git a/xs/src/libslic3r/MotionPlanner.cpp b/xs/src/libslic3r/MotionPlanner.cpp
index 88b69ef820..47e058497f 100644
--- a/xs/src/libslic3r/MotionPlanner.cpp
+++ b/xs/src/libslic3r/MotionPlanner.cpp
@@ -72,11 +72,23 @@ MotionPlanner::initialize()
this->initialized = true;
}
+ExPolygonCollection
+MotionPlanner::get_env(size_t island_idx) const
+{
+ if (island_idx == -1) {
+ return ExPolygonCollection(this->outer);
+ } else {
+ return this->inner[island_idx];
+ }
+}
+
void
MotionPlanner::shortest_path(const Point &from, const Point &to, Polyline* polyline)
{
+ // lazy generation of configuration space
if (!this->initialized) this->initialize();
+ // if we have an empty configuration space, return a straight move
if (this->islands.empty()) {
polyline->points.push_back(from);
polyline->points.push_back(to);
@@ -99,28 +111,28 @@ MotionPlanner::shortest_path(const Point &from, const Point &to, Polyline* polyl
}
}
+ // get environment
+ ExPolygonCollection env = this->get_env(island_idx);
+ if (env.expolygons.empty()) {
+ // if this environment is empty (probably because it's too small), perform straight move
+ // and avoid running the algorithms on empty dataset
+ polyline->points.push_back(from);
+ polyline->points.push_back(to);
+ return; // bye bye
+ }
+
// Now check whether points are inside the environment.
Point inner_from = from;
Point inner_to = to;
bool from_is_inside, to_is_inside;
- if (island_idx == -1) {
- if (!(from_is_inside = this->outer.contains(from))) {
- // Find the closest inner point to start from.
- from.nearest_point(this->outer, &inner_from);
- }
- if (!(to_is_inside = this->outer.contains(to))) {
- // Find the closest inner point to start from.
- to.nearest_point(this->outer, &inner_to);
- }
- } else {
- if (!(from_is_inside = this->inner[island_idx].contains(from))) {
- // Find the closest inner point to start from.
- from.nearest_point(this->inner[island_idx], &inner_from);
- }
- if (!(to_is_inside = this->inner[island_idx].contains(to))) {
- // Find the closest inner point to start from.
- to.nearest_point(this->inner[island_idx], &inner_to);
- }
+
+ if (!(from_is_inside = env.contains(from))) {
+ // Find the closest inner point to start from.
+ inner_from = this->nearest_env_point(env, from, to);
+ }
+ if (!(to_is_inside = env.contains(to))) {
+ // Find the closest inner point to start from.
+ inner_to = this->nearest_env_point(env, to, inner_from);
}
// perform actual path search
@@ -129,85 +141,147 @@ MotionPlanner::shortest_path(const Point &from, const Point &to, Polyline* polyl
polyline->points.insert(polyline->points.begin(), from);
polyline->points.push_back(to);
+
+ {
+ // grow our environment slightly in order for simplify_by_visibility()
+ // to work best by considering moves on boundaries valid as well
+ ExPolygonCollection grown_env;
+ offset(env, &grown_env.expolygons, +SCALED_EPSILON);
+
+ // remove unnecessary vertices
+ polyline->simplify_by_visibility(grown_env);
+ }
+
+ /*
+ SVG svg("shortest_path.svg");
+ svg.draw(this->outer);
+ svg.arrows = false;
+ for (MotionPlannerGraph::adjacency_list_t::const_iterator it = graph->adjacency_list.begin(); it != graph->adjacency_list.end(); ++it) {
+ Point a = graph->nodes[it - graph->adjacency_list.begin()];
+ for (std::vector::const_iterator n = it->begin(); n != it->end(); ++n) {
+ Point b = graph->nodes[n->target];
+ svg.draw(Line(a, b));
+ }
+ }
+ svg.arrows = true;
+ svg.draw(from);
+ svg.draw(inner_from, "red");
+ svg.draw(to);
+ svg.draw(inner_to, "red");
+ svg.draw(*polyline, "red");
+ svg.Close();
+ */
+}
+
+Point
+MotionPlanner::nearest_env_point(const ExPolygonCollection &env, const Point &from, const Point &to) const
+{
+ /* In order to ensure that the move between 'from' and the initial env point does
+ not violate any of the configuration space boundaries, we limit our search to
+ the points that satisfy this condition. */
+
+ /* Assume that this method is never called when 'env' contains 'from';
+ so 'from' is either inside a hole or outside all contours */
+
+ // get the points of the hole containing 'from', if any
+ Points pp;
+ for (ExPolygons::const_iterator ex = env.expolygons.begin(); ex != env.expolygons.end(); ++ex) {
+ for (Polygons::const_iterator h = ex->holes.begin(); h != ex->holes.end(); ++h) {
+ if (h->contains(from)) {
+ pp = *h;
+ }
+ }
+ if (!pp.empty()) break;
+ }
+
+ /* If 'from' is not inside a hole, it's outside of all contours, so take all
+ contours' points */
+ if (pp.empty()) {
+ for (ExPolygons::const_iterator ex = env.expolygons.begin(); ex != env.expolygons.end(); ++ex) {
+ Points contour_pp = ex->contour;
+ pp.insert(pp.end(), contour_pp.begin(), contour_pp.end());
+ }
+ }
+
+ /* Find the candidate result and check that it doesn't cross any boundary.
+ (We could skip all of the above polygon finding logic and directly test all points
+ in env, but this way we probably reduce complexity). */
+ Polygons env_pp = env;
+ while (pp.size() >= 2) {
+ // find the point in pp that is closest to both 'from' and 'to'
+ size_t result = from.nearest_waypoint_index(pp, to);
+
+ if (intersects((Lines)Line(from, pp[result]), env_pp)) {
+ // discard result
+ pp.erase(pp.begin() + result);
+ } else {
+ return pp[result];
+ }
+ }
+
+ // if we're here, return last point if any (better than nothing)
+ if (!pp.empty()) return pp.front();
+
+ // if we have no points at all, then we have an empty environment and we
+ // make this method behave as a no-op (we shouldn't get here by the way)
+ return from;
}
MotionPlannerGraph*
MotionPlanner::init_graph(int island_idx)
{
if (this->graphs[island_idx + 1] == NULL) {
- Polygons pp;
- if (island_idx == -1) {
- pp = this->outer;
- } else {
- pp = this->inner[island_idx];
- }
-
+ // if this graph doesn't exist, initialize it
MotionPlannerGraph* graph = this->graphs[island_idx + 1] = new MotionPlannerGraph();
- // add polygon boundaries as edges
- size_t node_idx = 0;
- Lines lines;
- for (Polygons::const_iterator polygon = pp.begin(); polygon != pp.end(); ++polygon) {
- graph->nodes.push_back(polygon->points.back());
- node_idx++;
- for (Points::const_iterator p = polygon->points.begin(); p != polygon->points.end(); ++p) {
- graph->nodes.push_back(*p);
- double dist = graph->nodes[node_idx-1].distance_to(*p);
- graph->add_edge(node_idx-1, node_idx, dist);
- graph->add_edge(node_idx, node_idx-1, dist);
- node_idx++;
- }
- polygon->lines(&lines);
- }
+ /* We don't add polygon boundaries as graph edges, because we'd need to connect
+ them to the Voronoi-generated edges by recognizing coinciding nodes. */
- // add Voronoi edges as internal edges
- {
- typedef voronoi_diagram VD;
- typedef std::map t_vd_vertices;
- VD vd;
- t_vd_vertices vd_vertices;
+ typedef voronoi_diagram VD;
+ VD vd;
+
+ // mapping between Voronoi vertices and graph nodes
+ typedef std::map t_vd_vertices;
+ t_vd_vertices vd_vertices;
+
+ // get boundaries as lines
+ ExPolygonCollection env = this->get_env(island_idx);
+ Lines lines = env.lines();
+ boost::polygon::construct_voronoi(lines.begin(), lines.end(), &vd);
+
+ // traverse the Voronoi diagram and generate graph nodes and edges
+ for (VD::const_edge_iterator edge = vd.edges().begin(); edge != vd.edges().end(); ++edge) {
+ if (edge->is_infinite()) continue;
- boost::polygon::construct_voronoi(lines.begin(), lines.end(), &vd);
- for (VD::const_edge_iterator edge = vd.edges().begin(); edge != vd.edges().end(); ++edge) {
- if (edge->is_infinite()) continue;
-
- const VD::vertex_type* v0 = edge->vertex0();
- const VD::vertex_type* v1 = edge->vertex1();
- Point p0 = Point(v0->x(), v0->y());
- Point p1 = Point(v1->x(), v1->y());
- // contains() should probably be faster than contains(),
- // and should it fail on any boundary points it's not a big problem
- if (island_idx == -1) {
- if (!this->outer.contains(p0) || !this->outer.contains(p1)) continue;
- } else {
- if (!this->inner[island_idx].contains(p0) || !this->inner[island_idx].contains(p1)) continue;
- }
-
- t_vd_vertices::const_iterator i_v0 = vd_vertices.find(v0);
- size_t v0_idx;
- if (i_v0 == vd_vertices.end()) {
- graph->nodes.push_back(p0);
- v0_idx = node_idx;
- vd_vertices[v0] = node_idx;
- node_idx++;
- } else {
- v0_idx = i_v0->second;
- }
-
- t_vd_vertices::const_iterator i_v1 = vd_vertices.find(v1);
- size_t v1_idx;
- if (i_v1 == vd_vertices.end()) {
- graph->nodes.push_back(p1);
- v1_idx = node_idx;
- vd_vertices[v1] = node_idx;
- node_idx++;
- } else {
- v1_idx = i_v1->second;
- }
-
- double dist = graph->nodes[v0_idx].distance_to(graph->nodes[v1_idx]);
- graph->add_edge(v0_idx, v1_idx, dist);
+ const VD::vertex_type* v0 = edge->vertex0();
+ const VD::vertex_type* v1 = edge->vertex1();
+ Point p0 = Point(v0->x(), v0->y());
+ Point p1 = Point(v1->x(), v1->y());
+
+ // skip edge if any of its endpoints is outside our configuration space
+ if (!env.contains_b(p0) || !env.contains_b(p1)) continue;
+
+ t_vd_vertices::const_iterator i_v0 = vd_vertices.find(v0);
+ size_t v0_idx;
+ if (i_v0 == vd_vertices.end()) {
+ graph->nodes.push_back(p0);
+ vd_vertices[v0] = v0_idx = graph->nodes.size()-1;
+ } else {
+ v0_idx = i_v0->second;
}
+
+ t_vd_vertices::const_iterator i_v1 = vd_vertices.find(v1);
+ size_t v1_idx;
+ if (i_v1 == vd_vertices.end()) {
+ graph->nodes.push_back(p1);
+ vd_vertices[v1] = v1_idx = graph->nodes.size()-1;
+ } else {
+ v1_idx = i_v1->second;
+ }
+
+ // Euclidean distance is used as weight for the graph edge
+ double dist = graph->nodes[v0_idx].distance_to(graph->nodes[v1_idx]);
+ graph->add_edge(v0_idx, v1_idx, dist);
}
return graph;
@@ -244,38 +318,61 @@ MotionPlannerGraph::shortest_path(size_t from, size_t to, Polyline* polyline)
const weight_t max_weight = std::numeric_limits::infinity();
- std::vector min_distance;
+ std::vector dist;
std::vector previous;
{
- int n = this->adjacency_list.size();
- min_distance.clear();
- min_distance.resize(n, max_weight);
- min_distance[from] = 0;
+ // number of nodes
+ size_t n = this->adjacency_list.size();
+
+ // initialize dist and previous
+ dist.clear();
+ dist.resize(n, max_weight);
+ dist[from] = 0; // distance from 'from' to itself
previous.clear();
previous.resize(n, -1);
- std::set > vertex_queue;
- vertex_queue.insert(std::make_pair(min_distance[from], from));
-
- while (!vertex_queue.empty())
+
+ // initialize the Q with all nodes
+ std::set Q;
+ for (node_t i = 0; i < n; ++i) Q.insert(i);
+
+ while (!Q.empty())
{
- weight_t dist = vertex_queue.begin()->first;
- node_t u = vertex_queue.begin()->second;
- vertex_queue.erase(vertex_queue.begin());
-
- // Visit each edge exiting u
+ // get node in Q having the minimum dist ('from' in the first loop)
+ node_t u;
+ {
+ double min_dist = -1;
+ for (std::set::const_iterator n = Q.begin(); n != Q.end(); ++n) {
+ if (dist[*n] < min_dist || min_dist == -1) {
+ u = *n;
+ min_dist = dist[*n];
+ }
+ }
+ }
+ Q.erase(u);
+
+ // stop searching if we reached our destination
+ if (u == to) break;
+
+ // Visit each edge starting from node u
const std::vector &neighbors = this->adjacency_list[u];
for (std::vector::const_iterator neighbor_iter = neighbors.begin();
neighbor_iter != neighbors.end();
neighbor_iter++)
{
+ // neighbor node is v
node_t v = neighbor_iter->target;
- weight_t weight = neighbor_iter->weight;
- weight_t distance_through_u = dist + weight;
- if (distance_through_u < min_distance[v]) {
- vertex_queue.erase(std::make_pair(min_distance[v], v));
- min_distance[v] = distance_through_u;
+
+ // skip if we already visited this
+ if (Q.find(v) == Q.end()) continue;
+
+ // calculate total distance
+ weight_t alt = dist[u] + neighbor_iter->weight;
+
+ // if total distance through u is shorter than the previous
+ // distance (if any) between 'from' and 'v', replace it
+ if (alt < dist[v]) {
+ dist[v] = alt;
previous[v] = u;
- vertex_queue.insert(std::make_pair(min_distance[v], v));
}
}
@@ -284,6 +381,7 @@ MotionPlannerGraph::shortest_path(size_t from, size_t to, Polyline* polyline)
for (node_t vertex = to; vertex != -1; vertex = previous[vertex])
polyline->points.push_back(this->nodes[vertex]);
+ polyline->points.push_back(this->nodes[from]);
polyline->reverse();
}
diff --git a/xs/src/libslic3r/MotionPlanner.hpp b/xs/src/libslic3r/MotionPlanner.hpp
index b40ed2ef7e..c1617e184f 100644
--- a/xs/src/libslic3r/MotionPlanner.hpp
+++ b/xs/src/libslic3r/MotionPlanner.hpp
@@ -33,10 +33,14 @@ class MotionPlanner
void initialize();
MotionPlannerGraph* init_graph(int island_idx);
+ ExPolygonCollection get_env(size_t island_idx) const;
+ Point nearest_env_point(const ExPolygonCollection &env, const Point &from, const Point &to) const;
};
class MotionPlannerGraph
{
+ friend class MotionPlanner;
+
private:
typedef size_t node_t;
typedef double weight_t;
diff --git a/xs/src/libslic3r/MultiPoint.cpp b/xs/src/libslic3r/MultiPoint.cpp
index e4944edca5..6ed430cf79 100644
--- a/xs/src/libslic3r/MultiPoint.cpp
+++ b/xs/src/libslic3r/MultiPoint.cpp
@@ -76,6 +76,13 @@ MultiPoint::find_point(const Point &point) const
return -1; // not found
}
+bool
+MultiPoint::has_boundary_point(const Point &point) const
+{
+ double dist = point.distance_to(point.projection_onto(*this));
+ return dist < SCALED_EPSILON;
+}
+
void
MultiPoint::bounding_box(BoundingBox* bb) const
{
diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp
index eaca9b8eb7..96c2876f5e 100644
--- a/xs/src/libslic3r/MultiPoint.hpp
+++ b/xs/src/libslic3r/MultiPoint.hpp
@@ -30,6 +30,7 @@ class MultiPoint
double length() const;
bool is_valid() const;
int find_point(const Point &point) const;
+ bool has_boundary_point(const Point &point) const;
void bounding_box(BoundingBox* bb) const;
static Points _douglas_peucker(const Points &points, const double tolerance);
diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp
index d40a8efc75..9a564f8795 100644
--- a/xs/src/libslic3r/Point.cpp
+++ b/xs/src/libslic3r/Point.cpp
@@ -104,6 +104,32 @@ Point::nearest_point_index(const PointConstPtrs &points) const
return idx;
}
+/* This method finds the point that is closest to both this point and the supplied one */
+size_t
+Point::nearest_waypoint_index(const Points &points, const Point &dest) const
+{
+ size_t idx = -1;
+ double distance = -1; // double because long is limited to 2147483647 on some platforms and it's not enough
+
+ for (Points::const_iterator p = points.begin(); p != points.end(); ++p) {
+ // distance from this to candidate
+ double d = pow(this->x - p->x, 2) + pow(this->y - p->y, 2);
+
+ // distance from candidate to dest
+ d += pow(p->x - dest.x, 2) + pow(p->y - dest.y, 2);
+
+ // if the total distance is greater than current min distance, ignore it
+ if (distance != -1 && d > distance) continue;
+
+ idx = p - points.begin();
+ distance = d;
+
+ if (distance < EPSILON) break;
+ }
+
+ return idx;
+}
+
int
Point::nearest_point_index(const PointPtrs &points) const
{
@@ -123,6 +149,15 @@ Point::nearest_point(const Points &points, Point* point) const
return true;
}
+bool
+Point::nearest_waypoint(const Points &points, const Point &dest, Point* point) const
+{
+ int idx = this->nearest_waypoint_index(points, dest);
+ if (idx == -1) return false;
+ *point = points.at(idx);
+ return true;
+}
+
double
Point::distance_to(const Point &point) const
{
diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp
index 6fa2164058..a3283c2600 100644
--- a/xs/src/libslic3r/Point.hpp
+++ b/xs/src/libslic3r/Point.hpp
@@ -45,7 +45,9 @@ class Point
int nearest_point_index(const Points &points) const;
int nearest_point_index(const PointConstPtrs &points) const;
int nearest_point_index(const PointPtrs &points) const;
+ size_t nearest_waypoint_index(const Points &points, const Point &point) const;
bool nearest_point(const Points &points, Point* point) const;
+ bool nearest_waypoint(const Points &points, const Point &dest, Point* point) const;
double distance_to(const Point &point) const;
double distance_to(const Line &line) const;
double perp_distance_to(const Line &line) const;
diff --git a/xs/src/libslic3r/Polyline.cpp b/xs/src/libslic3r/Polyline.cpp
index 1ae5055ba0..f56dd03b94 100644
--- a/xs/src/libslic3r/Polyline.cpp
+++ b/xs/src/libslic3r/Polyline.cpp
@@ -1,4 +1,6 @@
#include "Polyline.hpp"
+#include "ExPolygon.hpp"
+#include "ExPolygonCollection.hpp"
#include "Line.hpp"
#include "Polygon.hpp"
#include
@@ -127,6 +129,37 @@ Polyline::simplify(double tolerance)
this->points = MultiPoint::_douglas_peucker(this->points, tolerance);
}
+/* This method simplifies all *lines* contained in the supplied area */
+template
+void
+Polyline::simplify_by_visibility(const T &area)
+{
+ Points &pp = this->points;
+
+ // find first point in area
+ size_t start = 0;
+ while (start < pp.size() && !area.contains(pp[start])) {
+ start++;
+ }
+
+ for (size_t s = start; s < pp.size() && !pp.empty(); ++s) {
+ // find the farthest point to which we can build
+ // a line that is contained in the supplied area
+ // a binary search would be more efficient for this
+ for (size_t e = pp.size()-1; e > (s + 1); --e) {
+ if (area.contains(Line(pp[s], pp[e]))) {
+ // we can suppress points between s and e
+ pp.erase(pp.begin() + s + 1, pp.begin() + e);
+
+ // repeat recursively until no further simplification is possible
+ return this->simplify_by_visibility(area);
+ }
+ }
+ }
+}
+template void Polyline::simplify_by_visibility(const ExPolygon &area);
+template void Polyline::simplify_by_visibility(const ExPolygonCollection &area);
+
void
Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const
{
@@ -159,7 +192,7 @@ Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const
p2->points.clear();
p2->points.push_back(point);
for (Lines::const_iterator line = lines.begin() + line_idx; line != lines.end(); ++line) {
- if (!line->b.coincides_with(p)) p2->points.push_back(line->b);
+ p2->points.push_back(line->b);
}
}
diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp
index 2a9a790325..3fe89f26e9 100644
--- a/xs/src/libslic3r/Polyline.hpp
+++ b/xs/src/libslic3r/Polyline.hpp
@@ -7,6 +7,7 @@
namespace Slic3r {
+class ExPolygon;
class Polyline;
typedef std::vector Polylines;
@@ -23,6 +24,7 @@ class Polyline : public MultiPoint {
void extend_start(double distance);
void equally_spaced_points(double distance, Points* points) const;
void simplify(double tolerance);
+ template void simplify_by_visibility(const T &area);
void split_at(const Point &point, Polyline* p1, Polyline* p2) const;
bool is_straight() const;
std::string wkt() const;
diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp
index b87d1c8cf7..47faf2a137 100644
--- a/xs/src/libslic3r/Print.cpp
+++ b/xs/src/libslic3r/Print.cpp
@@ -561,25 +561,26 @@ Print::validate() const
FOREACH_OBJECT(this, i_object) {
PrintObject* object = *i_object;
- // get convex hulls of all meshes assigned to this print object
- Polygons mesh_convex_hulls;
- for (size_t i = 0; i < this->regions.size(); ++i) {
- for (std::vector::const_iterator it = object->region_volumes[i].begin(); it != object->region_volumes[i].end(); ++it) {
- Polygon hull;
- object->model_object()->volumes[*it]->mesh.convex_hull(&hull);
- mesh_convex_hulls.push_back(hull);
- }
- }
-
- // make a single convex hull for all of them
+ /* get convex hull of all meshes assigned to this print object
+ (this is the same as model_object()->raw_mesh.convex_hull()
+ but probably more efficient */
Polygon convex_hull;
- Slic3r::Geometry::convex_hull(mesh_convex_hulls, &convex_hull);
+ {
+ Polygons mesh_convex_hulls;
+ for (size_t i = 0; i < this->regions.size(); ++i) {
+ for (std::vector::const_iterator it = object->region_volumes[i].begin(); it != object->region_volumes[i].end(); ++it) {
+ Polygon hull;
+ object->model_object()->volumes[*it]->mesh.convex_hull(&hull);
+ mesh_convex_hulls.push_back(hull);
+ }
+ }
+
+ // make a single convex hull for all of them
+ Slic3r::Geometry::convex_hull(mesh_convex_hulls, &convex_hull);
+ }
// apply the same transformations we apply to the actual meshes when slicing them
object->model_object()->instances.front()->transform_polygon(&convex_hull);
-
- // align object to Z = 0 and apply XY shift
- convex_hull.translate(object->_copies_shift);
// grow convex hull with the clearance margin
{
diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp
index b462a6ee95..81ed94f0b2 100644
--- a/xs/src/libslic3r/PrintConfig.cpp
+++ b/xs/src/libslic3r/PrintConfig.cpp
@@ -964,6 +964,11 @@ PrintConfigDef::build_def() {
Options["use_relative_e_distances"].tooltip = "If your firmware requires relative E values, check this, otherwise leave it unchecked. Most firmwares use absolute values.";
Options["use_relative_e_distances"].cli = "use-relative-e-distances!";
+ Options["use_volumetric_e"].type = coBool;
+ Options["use_volumetric_e"].label = "Use volumetric E";
+ Options["use_volumetric_e"].tooltip = "This experimental setting uses outputs the E values in cubic millimeters instead of linear millimeters. The M200 command is prepended to the generated G-code, unless it is found in the configured start G-code. This is only supported in recent Marlin.";
+ Options["use_volumetric_e"].cli = "use-volumetric-e!";
+
Options["vibration_limit"].type = coFloat;
Options["vibration_limit"].label = "Vibration limit (deprecated)";
Options["vibration_limit"].tooltip = "This experimental option will slow down those moves hitting the configured frequency limit. The purpose of limiting vibrations is to avoid mechanical resonance. Set zero to disable.";
diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp
index f219a9de18..4f7160b822 100644
--- a/xs/src/libslic3r/PrintConfig.hpp
+++ b/xs/src/libslic3r/PrintConfig.hpp
@@ -321,11 +321,13 @@ class PrintRegionConfig : public virtual StaticPrintConfig
class GCodeConfig : public virtual StaticPrintConfig
{
public:
+ ConfigOptionString end_gcode;
ConfigOptionString extrusion_axis;
ConfigOptionFloats extrusion_multiplier;
ConfigOptionFloats filament_diameter;
ConfigOptionBool gcode_comments;
ConfigOptionEnum gcode_flavor;
+ ConfigOptionString layer_gcode;
ConfigOptionFloat pressure_advance;
ConfigOptionFloats retract_length;
ConfigOptionFloats retract_length_toolchange;
@@ -333,11 +335,15 @@ class GCodeConfig : public virtual StaticPrintConfig
ConfigOptionFloats retract_restart_extra;
ConfigOptionFloats retract_restart_extra_toolchange;
ConfigOptionInts retract_speed;
+ ConfigOptionString start_gcode;
+ ConfigOptionString toolchange_gcode;
ConfigOptionFloat travel_speed;
ConfigOptionBool use_firmware_retraction;
ConfigOptionBool use_relative_e_distances;
+ ConfigOptionBool use_volumetric_e;
GCodeConfig() : StaticPrintConfig() {
+ this->end_gcode.value = "M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n";
this->extrusion_axis.value = "E";
this->extrusion_multiplier.values.resize(1);
this->extrusion_multiplier.values[0] = 1;
@@ -345,6 +351,7 @@ class GCodeConfig : public virtual StaticPrintConfig
this->filament_diameter.values[0] = 3;
this->gcode_comments.value = false;
this->gcode_flavor.value = gcfRepRap;
+ this->layer_gcode.value = "";
this->pressure_advance.value = 0;
this->retract_length.values.resize(1);
this->retract_length.values[0] = 1;
@@ -358,17 +365,22 @@ class GCodeConfig : public virtual StaticPrintConfig
this->retract_restart_extra_toolchange.values[0] = 0;
this->retract_speed.values.resize(1);
this->retract_speed.values[0] = 30;
+ this->start_gcode.value = "G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n";
+ this->toolchange_gcode.value = "";
this->travel_speed.value = 130;
this->use_firmware_retraction.value = false;
this->use_relative_e_distances.value = false;
+ this->use_volumetric_e.value = false;
};
ConfigOption* option(const t_config_option_key opt_key, bool create = false) {
+ if (opt_key == "end_gcode") return &this->end_gcode;
if (opt_key == "extrusion_axis") return &this->extrusion_axis;
if (opt_key == "extrusion_multiplier") return &this->extrusion_multiplier;
if (opt_key == "filament_diameter") return &this->filament_diameter;
if (opt_key == "gcode_comments") return &this->gcode_comments;
if (opt_key == "gcode_flavor") return &this->gcode_flavor;
+ if (opt_key == "layer_gcode") return &this->layer_gcode;
if (opt_key == "pressure_advance") return &this->pressure_advance;
if (opt_key == "retract_length") return &this->retract_length;
if (opt_key == "retract_length_toolchange") return &this->retract_length_toolchange;
@@ -376,9 +388,12 @@ class GCodeConfig : public virtual StaticPrintConfig
if (opt_key == "retract_restart_extra") return &this->retract_restart_extra;
if (opt_key == "retract_restart_extra_toolchange") return &this->retract_restart_extra_toolchange;
if (opt_key == "retract_speed") return &this->retract_speed;
+ if (opt_key == "start_gcode") return &this->start_gcode;
+ if (opt_key == "toolchange_gcode") return &this->toolchange_gcode;
if (opt_key == "travel_speed") return &this->travel_speed;
if (opt_key == "use_firmware_retraction") return &this->use_firmware_retraction;
if (opt_key == "use_relative_e_distances") return &this->use_relative_e_distances;
+ if (opt_key == "use_volumetric_e") return &this->use_volumetric_e;
return NULL;
};
@@ -409,7 +424,6 @@ class PrintConfig : public GCodeConfig
ConfigOptionFloat default_acceleration;
ConfigOptionInt disable_fan_first_layers;
ConfigOptionFloat duplicate_distance;
- ConfigOptionString end_gcode;
ConfigOptionFloat extruder_clearance_height;
ConfigOptionFloat extruder_clearance_radius;
ConfigOptionPoints extruder_offset;
@@ -423,7 +437,6 @@ class PrintConfig : public GCodeConfig
ConfigOptionBool gcode_arcs;
ConfigOptionFloat infill_acceleration;
ConfigOptionBool infill_first;
- ConfigOptionString layer_gcode;
ConfigOptionInt max_fan_speed;
ConfigOptionInt min_fan_speed;
ConfigOptionInt min_print_speed;
@@ -444,10 +457,8 @@ class PrintConfig : public GCodeConfig
ConfigOptionInt slowdown_below_layer_time;
ConfigOptionBool spiral_vase;
ConfigOptionInt standby_temperature_delta;
- ConfigOptionString start_gcode;
ConfigOptionInts temperature;
ConfigOptionInt threads;
- ConfigOptionString toolchange_gcode;
ConfigOptionFloat vibration_limit;
ConfigOptionBools wipe;
ConfigOptionFloat z_offset;
@@ -467,7 +478,6 @@ class PrintConfig : public GCodeConfig
this->default_acceleration.value = 0;
this->disable_fan_first_layers.value = 1;
this->duplicate_distance.value = 6;
- this->end_gcode.value = "M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n";
this->extruder_clearance_height.value = 20;
this->extruder_clearance_radius.value = 20;
this->extruder_offset.values.resize(1);
@@ -485,7 +495,6 @@ class PrintConfig : public GCodeConfig
this->gcode_arcs.value = false;
this->infill_acceleration.value = 0;
this->infill_first.value = false;
- this->layer_gcode.value = "";
this->max_fan_speed.value = 100;
this->min_fan_speed.value = 35;
this->min_print_speed.value = 10;
@@ -508,11 +517,9 @@ class PrintConfig : public GCodeConfig
this->slowdown_below_layer_time.value = 30;
this->spiral_vase.value = false;
this->standby_temperature_delta.value = -5;
- this->start_gcode.value = "G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n";
this->temperature.values.resize(1);
this->temperature.values[0] = 200;
this->threads.value = 2;
- this->toolchange_gcode.value = "";
this->vibration_limit.value = 0;
this->wipe.values.resize(1);
this->wipe.values[0] = false;
@@ -531,7 +538,6 @@ class PrintConfig : public GCodeConfig
if (opt_key == "default_acceleration") return &this->default_acceleration;
if (opt_key == "disable_fan_first_layers") return &this->disable_fan_first_layers;
if (opt_key == "duplicate_distance") return &this->duplicate_distance;
- if (opt_key == "end_gcode") return &this->end_gcode;
if (opt_key == "extruder_clearance_height") return &this->extruder_clearance_height;
if (opt_key == "extruder_clearance_radius") return &this->extruder_clearance_radius;
if (opt_key == "extruder_offset") return &this->extruder_offset;
@@ -545,7 +551,6 @@ class PrintConfig : public GCodeConfig
if (opt_key == "gcode_arcs") return &this->gcode_arcs;
if (opt_key == "infill_acceleration") return &this->infill_acceleration;
if (opt_key == "infill_first") return &this->infill_first;
- if (opt_key == "layer_gcode") return &this->layer_gcode;
if (opt_key == "max_fan_speed") return &this->max_fan_speed;
if (opt_key == "min_fan_speed") return &this->min_fan_speed;
if (opt_key == "min_print_speed") return &this->min_print_speed;
@@ -566,10 +571,8 @@ class PrintConfig : public GCodeConfig
if (opt_key == "slowdown_below_layer_time") return &this->slowdown_below_layer_time;
if (opt_key == "spiral_vase") return &this->spiral_vase;
if (opt_key == "standby_temperature_delta") return &this->standby_temperature_delta;
- if (opt_key == "start_gcode") return &this->start_gcode;
if (opt_key == "temperature") return &this->temperature;
if (opt_key == "threads") return &this->threads;
- if (opt_key == "toolchange_gcode") return &this->toolchange_gcode;
if (opt_key == "vibration_limit") return &this->vibration_limit;
if (opt_key == "wipe") return &this->wipe;
if (opt_key == "z_offset") return &this->z_offset;
diff --git a/xs/src/libslic3r/SVG.cpp b/xs/src/libslic3r/SVG.cpp
index db5ec72937..328d5421aa 100644
--- a/xs/src/libslic3r/SVG.cpp
+++ b/xs/src/libslic3r/SVG.cpp
@@ -1,8 +1,12 @@
#include "SVG.hpp"
+#include
+
+#define COORD(x) ((float)unscale(x)*10)
namespace Slic3r {
SVG::SVG(const char* filename)
+ : arrows(true), filename(filename), fill("grey"), stroke("black")
{
this->f = fopen(filename, "w");
fprintf(this->f,
@@ -13,21 +17,14 @@ SVG::SVG(const char* filename)
" \n"
" \n"
);
- this->arrows = true;
-}
-
-float
-SVG::coordinate(long c)
-{
- return (float)unscale(c)*10;
}
void
-SVG::AddLine(const Line &line)
+SVG::draw(const Line &line, std::string stroke)
{
fprintf(this->f,
- " coordinate(line.a.x), this->coordinate(line.a.y), this->coordinate(line.b.x), this->coordinate(line.b.y)
+ " arrows)
fprintf(this->f, " marker-end=\"url(#endArrow)\"");
@@ -37,7 +34,72 @@ SVG::AddLine(const Line &line)
void
SVG::AddLine(const IntersectionLine &line)
{
- this->AddLine(Line(line.a, line.b));
+ this->draw(Line(line.a, line.b));
+}
+
+void
+SVG::draw(const ExPolygon &expolygon, std::string fill)
+{
+ this->fill = fill;
+
+ std::string d;
+ Polygons pp = expolygon;
+ for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) {
+ d += this->get_path_d(*p, true) + " ";
+ }
+ this->path(d, true);
+}
+
+void
+SVG::draw(const Polygon &polygon, std::string fill)
+{
+ this->fill = fill;
+ this->path(this->get_path_d(polygon, true), true);
+}
+
+void
+SVG::draw(const Polyline &polyline, std::string stroke)
+{
+ this->stroke = stroke;
+ this->path(this->get_path_d(polyline, false), false);
+}
+
+void
+SVG::draw(const Point &point, std::string fill, unsigned int radius)
+{
+ std::ostringstream svg;
+ svg << " ";
+
+ fprintf(this->f, "%s\n", svg.str().c_str());
+}
+
+void
+SVG::path(const std::string &d, bool fill)
+{
+ fprintf(
+ this->f,
+ " \n",
+ d.c_str(),
+ fill ? this->fill.c_str() : "none",
+ this->stroke.c_str(),
+ fill ? "0" : "2",
+ (this->arrows && !fill) ? " marker-end=\"url(#endArrow)\"" : ""
+ );
+}
+
+std::string
+SVG::get_path_d(const MultiPoint &mp, bool closed) const
+{
+ std::ostringstream d;
+ d << "M ";
+ for (Points::const_iterator p = mp.points.begin(); p != mp.points.end(); ++p) {
+ d << COORD(p->x) << " ";
+ d << COORD(p->y) << " ";
+ }
+ if (closed) d << "z";
+ return d.str();
}
void
@@ -45,7 +107,7 @@ SVG::Close()
{
fprintf(this->f, "\n");
fclose(this->f);
- printf("SVG file written.\n");
+ printf("SVG written to %s\n", this->filename.c_str());
}
}
diff --git a/xs/src/libslic3r/SVG.hpp b/xs/src/libslic3r/SVG.hpp
index 5d4cfd56e5..bb0d5adcf1 100644
--- a/xs/src/libslic3r/SVG.hpp
+++ b/xs/src/libslic3r/SVG.hpp
@@ -2,6 +2,7 @@
#define slic3r_SVG_hpp_
#include
+#include "ExPolygon.hpp"
#include "Line.hpp"
#include "TriangleMesh.hpp"
@@ -9,15 +10,25 @@ namespace Slic3r {
class SVG
{
- private:
- FILE* f;
- float coordinate(long c);
public:
bool arrows;
+ std::string fill, stroke;
+
SVG(const char* filename);
- void AddLine(const Line &line);
void AddLine(const IntersectionLine &line);
+ void draw(const Line &line, std::string stroke = "black");
+ void draw(const ExPolygon &expolygon, std::string fill = "grey");
+ void draw(const Polygon &polygon, std::string fill = "grey");
+ void draw(const Polyline &polyline, std::string stroke = "black");
+ void draw(const Point &point, std::string fill = "black", unsigned int radius = 3);
void Close();
+
+ private:
+ std::string filename;
+ FILE* f;
+
+ void path(const std::string &d, bool fill);
+ std::string get_path_d(const MultiPoint &mp, bool closed = false) const;
};
}
diff --git a/xs/src/libslic3r/SurfaceCollection.cpp b/xs/src/libslic3r/SurfaceCollection.cpp
index ccf1eaa4e1..38c77ffd6e 100644
--- a/xs/src/libslic3r/SurfaceCollection.cpp
+++ b/xs/src/libslic3r/SurfaceCollection.cpp
@@ -77,9 +77,19 @@ SurfaceCollection::any_internal_contains(const T &item) const
}
return false;
}
-template bool SurfaceCollection::any_internal_contains(const Line &item) const;
template bool SurfaceCollection::any_internal_contains(const Polyline &item) const;
+template
+bool
+SurfaceCollection::any_bottom_contains(const T &item) const
+{
+ for (Surfaces::const_iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) {
+ if (surface->is_bottom() && surface->expolygon.contains(item)) return true;
+ }
+ return false;
+}
+template bool SurfaceCollection::any_bottom_contains(const Polyline &item) const;
+
SurfacesPtr
SurfaceCollection::filter_by_type(SurfaceType type)
{
diff --git a/xs/src/libslic3r/SurfaceCollection.hpp b/xs/src/libslic3r/SurfaceCollection.hpp
index 09a46449e5..e2ced7f0e9 100644
--- a/xs/src/libslic3r/SurfaceCollection.hpp
+++ b/xs/src/libslic3r/SurfaceCollection.hpp
@@ -16,6 +16,7 @@ class SurfaceCollection
void simplify(double tolerance);
void group(std::vector *retval);
template bool any_internal_contains(const T &item) const;
+ template bool any_bottom_contains(const T &item) const;
SurfacesPtr filter_by_type(SurfaceType type);
void filter_by_type(SurfaceType type, Polygons* polygons);
};
diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp
index 94e5668dd8..7c2a0a3447 100644
--- a/xs/src/libslic3r/TriangleMesh.cpp
+++ b/xs/src/libslic3r/TriangleMesh.cpp
@@ -614,10 +614,8 @@ TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int
if (!points.empty()) {
assert(points.size() == 2); // facets must intersect each plane 0 or 2 times
IntersectionLine line;
- line.a.x = points[1].x;
- line.a.y = points[1].y;
- line.b.x = points[0].x;
- line.b.y = points[0].y;
+ line.a = (Point)points[1];
+ line.b = (Point)points[0];
line.a_id = points[1].point_id;
line.b_id = points[0].point_id;
line.edge_a_id = points[1].edge_id;
diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp
index afde8a65d6..227bceae0f 100644
--- a/xs/src/libslic3r/TriangleMesh.hpp
+++ b/xs/src/libslic3r/TriangleMesh.hpp
@@ -5,6 +5,7 @@
#include
#include
#include "BoundingBox.hpp"
+#include "Line.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
#include "ExPolygon.hpp"
@@ -71,11 +72,9 @@ class IntersectionPoint : public Point
IntersectionPoint() : point_id(-1), edge_id(-1) {};
};
-class IntersectionLine
+class IntersectionLine : public Line
{
public:
- Point a;
- Point b;
int a_id;
int b_id;
int edge_a_id;
diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h
index 4a02ac2cd0..f700fd35a6 100644
--- a/xs/src/libslic3r/libslic3r.h
+++ b/xs/src/libslic3r/libslic3r.h
@@ -6,7 +6,7 @@
#include
#include
-#define SLIC3R_VERSION "1.2.4"
+#define SLIC3R_VERSION "1.2.5-dev"
#define EPSILON 1e-4
#define SCALING_FACTOR 0.000001
diff --git a/xs/t/08_extrusionloop.t b/xs/t/08_extrusionloop.t
index 0657766c8f..92d720dc37 100644
--- a/xs/t/08_extrusionloop.t
+++ b/xs/t/08_extrusionloop.t
@@ -5,7 +5,7 @@ use warnings;
use List::Util qw(sum);
use Slic3r::XS;
-use Test::More tests => 46;
+use Test::More tests => 48;
{
my $square = [
@@ -133,8 +133,10 @@ use Test::More tests => 46;
Slic3r::ExtrusionPath->new(polyline => $polylines[2], role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1),
Slic3r::ExtrusionPath->new(polyline => $polylines[3], role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1),
);
+ my $len = $loop->length;
my $point = Slic3r::Point->new(4821067,9321068);
$loop->split_at_vertex($point) or $loop->split_at($point);
+ is $loop->length, $len, 'total length is preserved after splitting';
is_deeply [ map $_->role, @$loop ], [
Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER,
Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER,
@@ -144,4 +146,15 @@ use Test::More tests => 46;
], 'order is correctly preserved after splitting';
}
+{
+ my $loop = Slic3r::ExtrusionLoop->new;
+ $loop->append(Slic3r::ExtrusionPath->new(
+ polyline => Slic3r::Polyline->new([15896783,15868739],[24842049,12117558],[33853238,15801279],[37591780,24780128],[37591780,24844970],[33853231,33825297],[24842049,37509013],[15896798,33757841],[12211841,24812544],[15896783,15868739]),
+ role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1
+ ));
+ my $len = $loop->length;
+ $loop->split_at(Slic3r::Point->new(15896783,15868739));
+ is $loop->length, $len, 'split_at() preserves total length';
+}
+
__END__
diff --git a/xs/t/09_polyline.t b/xs/t/09_polyline.t
index 99077aad36..fa5a3c48b4 100644
--- a/xs/t/09_polyline.t
+++ b/xs/t/09_polyline.t
@@ -4,7 +4,7 @@ use strict;
use warnings;
use Slic3r::XS;
-use Test::More tests => 16;
+use Test::More tests => 21;
my $points = [
[100, 100],
@@ -79,4 +79,49 @@ is_deeply $polyline->pp, [ @$points, @$points ], 'append_polyline';
ok $p2->first_point->coincides_with($point), 'split_at';
}
+{
+ my $polyline = Slic3r::Polyline->new(@$points[0,1,2,0]);
+ my $p1 = Slic3r::Polyline->new;
+ my $p2 = Slic3r::Polyline->new;
+ $polyline->split_at($polyline->first_point, $p1, $p2);
+ is scalar(@$p1), 1, 'split_at';
+ is scalar(@$p2), 4, 'split_at';
+}
+
+{
+ my $polyline = Slic3r::Polyline->new(
+ map [$_,10], (0,10,20,30,40,50,60)
+ );
+ {
+ my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new(
+ [25,0], [55,0], [55,30], [25,30],
+ ));
+ my $p = $polyline->clone;
+ $p->simplify_by_visibility($expolygon);
+ is_deeply $p->pp, [
+ map [$_,10], (0,10,20,30,50,60)
+ ], 'simplify_by_visibility()';
+ }
+ {
+ my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new(
+ [-15,0], [75,0], [75,30], [-15,30],
+ ));
+ my $p = $polyline->clone;
+ $p->simplify_by_visibility($expolygon);
+ is_deeply $p->pp, [
+ map [$_,10], (0,60)
+ ], 'simplify_by_visibility()';
+ }
+ {
+ my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new(
+ [-15,0], [25,0], [25,30], [-15,30],
+ ));
+ my $p = $polyline->clone;
+ $p->simplify_by_visibility($expolygon);
+ is_deeply $p->pp, [
+ map [$_,10], (0,20,30,40,50,60)
+ ], 'simplify_by_visibility()';
+ }
+}
+
__END__
diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp
index cc38b08472..ffc9f9af80 100644
--- a/xs/xsp/Layer.xsp
+++ b/xs/xsp/Layer.xsp
@@ -69,12 +69,10 @@
%code%{ RETVAL = (int)(intptr_t)THIS; %};
void make_slices();
- bool any_internal_region_slice_contains_line(Line* line)
- %code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %};
- bool any_internal_region_fill_surface_contains_line(Line* line)
- %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*line); %};
- bool any_internal_region_fill_surface_contains_polyline(Polyline* polyline)
- %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*polyline); %};
+ bool any_internal_region_slice_contains_polyline(Polyline* polyline)
+ %code%{ RETVAL = THIS->any_internal_region_slice_contains(*polyline); %};
+ bool any_bottom_region_slice_contains_polyline(Polyline* polyline)
+ %code%{ RETVAL = THIS->any_bottom_region_slice_contains(*polyline); %};
};
%name{Slic3r::Layer::Support} class SupportLayer {
@@ -121,10 +119,8 @@
Ref slices()
%code%{ RETVAL = &THIS->slices; %};
- bool any_internal_region_slice_contains_line(Line* line)
- %code%{ RETVAL = THIS->any_internal_region_slice_contains(*line); %};
- bool any_internal_region_fill_surface_contains_line(Line* line)
- %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*line); %};
- bool any_internal_region_fill_surface_contains_polyline(Polyline* polyline)
- %code%{ RETVAL = THIS->any_internal_region_fill_surface_contains(*polyline); %};
+ bool any_internal_region_slice_contains_polyline(Polyline* polyline)
+ %code%{ RETVAL = THIS->any_internal_region_slice_contains(*polyline); %};
+ bool any_bottom_region_slice_contains_polyline(Polyline* polyline)
+ %code%{ RETVAL = THIS->any_bottom_region_slice_contains(*polyline); %};
};
diff --git a/xs/xsp/Polyline.xsp b/xs/xsp/Polyline.xsp
index 2df0d17c1e..a8eb527d5d 100644
--- a/xs/xsp/Polyline.xsp
+++ b/xs/xsp/Polyline.xsp
@@ -32,6 +32,8 @@
void extend_end(double distance);
void extend_start(double distance);
void simplify(double tolerance);
+ void simplify_by_visibility(ExPolygon* expolygon)
+ %code{% THIS->simplify_by_visibility(*expolygon); %};
void split_at(Point* point, Polyline* p1, Polyline* p2)
%code{% THIS->split_at(*point, p1, p2); %};
bool is_straight();