Merge branch 'master' into sender

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

View File

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

View File

@ -58,6 +58,7 @@ use Slic3r::GCode::VibrationLimit;
use Slic3r::Geometry qw(PI);
use Slic3r::Geometry::Clipper;
use Slic3r::Layer;
use Slic3r::Layer::PerimeterGenerator;
use Slic3r::Layer::Region;
use Slic3r::Line;
use Slic3r::Model;

View File

@ -207,8 +207,11 @@ sub extrude_loop {
my $last_path_polyline = $paths[-1]->polyline;
# detect angle between last and first segment
# the side depends on the original winding order of the polygon (left for contours, right for holes)
my @points = $was_clockwise ? (-2, 1) : (1, -2);
my $angle = Slic3r::Geometry::angle3points(@$last_path_polyline[0, @points]) / 3;
my @points = ($paths[0][1], $paths[-1][-2]);
@points = reverse @points if $was_clockwise;
my $angle = $paths[0]->first_point->ccw_angle(@points) / 3;
# turn left if contour, turn right if hole
$angle *= -1 if $was_clockwise;
# create the destination point along the first segment and rotate it
@ -332,48 +335,75 @@ sub _extrude_path {
sub travel_to {
my ($self, $point, $role, $comment) = @_;
my $gcode = "";
# Define the travel move as a line between current position and the taget point.
# This is expressed in print coordinates, so it will need to be translated by
# $self->origin in order to get G-code coordinates.
my $travel = Slic3r::Line->new($self->last_pos, $point);
my $travel = Slic3r::Polyline->new($self->last_pos, $point);
# Skip retraction at all in the following cases:
# - travel length is shorter than the configured threshold
# - user has enabled "Only retract when crossing perimeters" and the travel move is
# contained in a single internal fill_surface (this includes the bottom layer when
# bottom_solid_layers == 0) or in a single internal slice (this would exclude such
# bottom layer but preserve perimeter-to-infill moves in all the other layers)
# - the path that will be extruded after this travel move is a support material
# extrusion and the travel move is contained in a single support material island
if ($travel->length < scale $self->config->get_at('retract_before_travel', $self->writer->extruder->id)
|| ($self->config->only_retract_when_crossing_perimeters
&& $self->config->fill_density > 0
&& defined($self->layer)
&& ($self->layer->any_internal_region_slice_contains_line($travel)
|| $self->layer->any_internal_region_fill_surface_contains_line($travel)))
|| (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_line($travel))
) {
# Just perform a straight travel move without any retraction.
$gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), $comment || '');
} elsif ($self->config->avoid_crossing_perimeters && !$self->avoid_crossing_perimeters->disable_once) {
# If avoid_crossing_perimeters is enabled and the disable_once flag is not set
# we need to plan a multi-segment travel move inside the configuration space.
$gcode .= $self->avoid_crossing_perimeters->travel_to($self, $point, $comment || '');
} else {
# If avoid_crossing_perimeters is disabled or the disable_once flag is set,
# perform a straight move with a retraction.
$gcode .= $self->retract;
$gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), $comment || '');
# check whether a straight travel move would need retraction
my $needs_retraction = $self->needs_retraction($travel, $role);
# if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
# multi-hop travel path inside the configuration space
if ($needs_retraction
&& $self->config->avoid_crossing_perimeters
&& !$self->avoid_crossing_perimeters->disable_once) {
$travel = $self->avoid_crossing_perimeters->travel_to($self, $point);
# check again whether the new travel path still needs a retraction
$needs_retraction = $self->needs_retraction($travel, $role);
}
# Re-allow avoid_crossing_perimeters for the next travel moves
$self->avoid_crossing_perimeters->disable_once(0);
$self->avoid_crossing_perimeters->use_external_mp_once(0);
# generate G-code for the travel move
my $gcode = "";
$gcode .= $self->retract if $needs_retraction;
# use G1 because we rely on paths being straight (G0 may make round paths)
$gcode .= $self->writer->travel_to_xy($self->point_to_gcode($_->b), $comment)
for @{$travel->lines};
return $gcode;
}
sub needs_retraction {
my ($self, $travel, $role) = @_;
if ($travel->length < scale $self->config->get_at('retract_before_travel', $self->writer->extruder->id)) {
# skip retraction if the move is shorter than the configured threshold
return 0;
}
if (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands->contains_polyline($travel)) {
# skip retraction if this is a travel move inside a support material island
return 0;
}
if ($self->config->only_retract_when_crossing_perimeters && defined $self->layer) {
if ($self->config->fill_density > 0
&& $self->layer->any_internal_region_slice_contains_polyline($travel)) {
# skip retraction if travel is contained in an internal slice *and*
# internal infill is enabled (so that stringing is entirely not visible)
return 0;
} elsif ($self->layer->any_bottom_region_slice_contains_polyline($travel)
&& defined $self->layer->upper_layer
&& $self->layer->upper_layer->slices->contains_polyline($travel)
&& ($self->config->bottom_solid_layers >= 2 || $self->config->fill_density > 0)) {
# skip retraction if travel is contained in an *infilled* bottom slice
# but only if it's also covered by an *infilled* upper layer's slice
# so that it's not visible from above (a bottom surface might not have an
# upper slice in case of a thin membrane)
return 0;
}
}
# retract if only_retract_when_crossing_perimeters is disabled or doesn't apply
return 1;
}
sub retract {
my ($self, $toolchange) = @_;
@ -568,9 +598,10 @@ has '_external_mp' => (is => 'rw');
has '_layer_mp' => (is => 'rw');
has 'use_external_mp' => (is => 'rw', default => sub {0});
has 'use_external_mp_once' => (is => 'rw', default => sub {0}); # this flag triggers the use of the external configuration space for avoid_crossing_perimeters for the next travel move
has 'disable_once' => (is => 'rw', default => sub {1}); # this flag disables avoid_crossing_perimeters just for the next travel move
use Slic3r::Geometry qw(scale);
# this flag disables avoid_crossing_perimeters just for the next travel move
# we enable it by default for the first travel move in print
has 'disable_once' => (is => 'rw', default => sub {1});
sub init_external_mp {
my ($self, $islands) = @_;
@ -583,47 +614,30 @@ sub init_layer_mp {
}
sub travel_to {
my ($self, $gcodegen, $point, $comment) = @_;
my $gcode = "";
my ($self, $gcodegen, $point) = @_;
if ($self->use_external_mp || $self->use_external_mp_once) {
$self->use_external_mp_once(0);
# get current origin set in $gcodegen
# (the one that will be used to translate the G-code coordinates by)
my $scaled_origin = Slic3r::Point->new_scale(@{$gcodegen->origin});
# represent $point in G-code coordinates
# represent last_pos in absolute G-code coordinates
my $last_pos = $gcodegen->last_pos->clone;
$last_pos->translate(@$scaled_origin);
# represent $point in absolute G-code coordinates
$point = $point->clone;
my $origin = $gcodegen->origin;
$point->translate(map scale $_, @$origin);
$point->translate(@$scaled_origin);
# calculate path
my $travel = $self->_external_mp->shortest_path($last_pos, $point);
# calculate path (external_mp uses G-code coordinates so we set a temporary null origin)
$gcodegen->set_origin(Slic3r::Pointf->new(0,0));
$gcode .= $self->_plan($gcodegen, $self->_external_mp, $point, $comment);
$gcodegen->set_origin($origin);
# translate the path back into the shifted coordinate system that $gcodegen
# is currently using for writing coordinates
$travel->translate(@{$scaled_origin->negative});
return $travel;
} else {
$gcode .= $self->_plan($gcodegen, $self->_layer_mp, $point, $comment);
return $self->_layer_mp->shortest_path($gcodegen->last_pos, $point);
}
return $gcode;
}
sub _plan {
my ($self, $gcodegen, $mp, $point, $comment) = @_;
my $gcode = "";
my $travel = $mp->shortest_path($gcodegen->last_pos, $point);
# if the path is not contained in a single island we need to retract
$gcode .= $gcodegen->retract
if !$gcodegen->config->only_retract_when_crossing_perimeters
|| !$gcodegen->layer->any_internal_region_fill_surface_contains_polyline($travel);
# append the actual path and return
# use G1 because we rely on paths being straight (G0 may make round paths)
$gcode .= join '',
map $gcodegen->writer->travel_to_xy($gcodegen->point_to_gcode($_->b), $comment),
@{$travel->lines};
return $gcode;
}
1;

View File

@ -24,7 +24,7 @@ sub BUILD {
sub process {
my $self = shift;
my ($gcode) = @_;
my ($gcode, $flush) = @_;
my $new_gcode = "";
@ -51,7 +51,7 @@ sub process {
if (abs($new_advance - $self->_advance) > 1E-5) {
my $new_E = ($self->config->use_relative_e_distances ? 0 : $reader->E) + ($new_advance - $self->_advance);
$new_gcode .= sprintf "G1 %s%.5f F%.3f ; pressure advance\n",
$self->_extrusion_axis, $new_E, $self->unretract_speed;
$self->_extrusion_axis, $new_E, $self->_unretract_speed;
$new_gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $reader->E
if !$self->config->use_relative_e_distances;
$self->_advance($new_advance);
@ -61,20 +61,33 @@ sub process {
}
} elsif (($info->{retracting} || $cmd eq 'G10') && $self->_advance != 0) {
# We need to bring pressure to zero when retracting.
my $new_E = ($self->config->use_relative_e_distances ? 0 : $reader->E) - $self->_advance;
$new_gcode .= sprintf "G1 %s%.5f F%.3f ; pressure discharge\n",
$self->_extrusion_axis, $new_E, $args->{F} // $self->unretract_speed;
$new_gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $reader->E
if !$self->config->use_relative_e_distances;
$new_gcode .= $self->_discharge($args->{F});
}
$new_gcode .= "$info->{raw}\n";
});
if ($flush) {
$new_gcode .= $self->_discharge;
}
return $new_gcode;
}
sub unretract_speed {
sub _discharge {
my ($self, $F) = @_;
my $new_E = ($self->config->use_relative_e_distances ? 0 : $self->reader->E) - $self->_advance;
my $gcode = sprintf "G1 %s%.5f F%.3f ; pressure discharge\n",
$self->_extrusion_axis, $new_E, $F // $self->_unretract_speed;
$gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $self->reader->E
if !$self->config->use_relative_e_distances;
$self->_advance(0);
return $gcode;
}
sub _unretract_speed {
my ($self) = @_;
return $self->config->get_at('retract_speed', $self->_tool) * 60;
}

View File

@ -12,7 +12,7 @@ use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 270]);
my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 300]);
$self->SetBackgroundColour(Wx::wxWHITE);
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
@ -47,7 +47,7 @@ sub new {
'<html>' .
'<body bgcolor="#ffffff" link="#808080">' .
'<font color="#808080">' .
'Copyright &copy; 2011-2014 Alessandro Ranellucci. <br />' .
'Copyright &copy; 2011-2015 Alessandro Ranellucci. <br />' .
'<a href="http://slic3r.org/">Slic3r</a> is licensed under the ' .
'<a href="http://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License, version 3</a>.' .
'<br /><br /><br />' .

View File

@ -301,7 +301,7 @@ sub Render {
foreach my $layerm (@{$layer->regions}) {
if ($object->step_done(STEP_PERIMETERS)) {
$self->color([0.7, 0, 0]);
$self->_draw($object, $print_z, $_) for @{$layerm->perimeters};
$self->_draw($object, $print_z, $_) for map @$_, @{$layerm->perimeters};
}
if ($object->step_done(STEP_INFILL)) {

View File

@ -44,7 +44,7 @@ use constant TRACKBALLSIZE => 0.8;
use constant TURNTABLE_MODE => 1;
use constant GROUND_Z => -0.02;
use constant SELECTED_COLOR => [0,1,0,1];
use constant HOVER_COLOR => [0.8,0.8,0,1];
use constant HOVER_COLOR => [0.4,0.9,0,1];
use constant COLORS => [ [1,1,0], [1,0.5,0.5], [0.5,1,0.5], [0.5,0.5,1] ];
# make OpenGL::Array thread-safe
@ -90,7 +90,7 @@ sub new {
my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta();
$zoom = max(min($zoom, 4), -4);
$zoom /= 10;
$self->_zoom($self->_zoom * (1-$zoom));
$self->_zoom($self->_zoom / (1-$zoom));
# In order to zoom around the mouse point we need to translate
# the camera target
@ -171,7 +171,7 @@ sub mouse_event {
$self->_drag_start_pos($cur_pos);
$self->_dragged(1);
$self->Refresh;
} elsif ($e->Dragging && !defined $self->_hover_volume_idx) {
} elsif ($e->Dragging) {
if ($e->LeftIsDown) {
# if dragging over blank area with left button, rotate
if (defined $self->_drag_start_pos) {
@ -208,7 +208,7 @@ sub mouse_event {
}
$self->_drag_start_xy($pos);
}
} elsif ($e->LeftUp || $e->RightUp) {
} elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) {
if ($self->on_move && defined $self->_drag_volume_idx) {
$self->on_move->($self->_drag_volume_idx) if $self->_dragged;
}
@ -629,17 +629,16 @@ sub InitGL {
glEnable(GL_MULTISAMPLE);
# ambient lighting
glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.1, 0.1, 0.1, 1);
glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.3, 0.3, 0.3, 1);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHT1);
glLightfv_p(GL_LIGHT0, GL_POSITION, 0.5, 0.5, 1, 0);
glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.5, 0.5, 0.5, 1);
glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.8, 0.8, 0.8, 1);
glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 0.5, 0);
glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.5, 0.5, 0.5, 1);
glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 1, 1, 1, 1);
# light from camera
glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0);
glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.3, 0.3, 0.3, 1);
glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 0.2, 0.2, 0.2, 1);
# Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
glShadeModel(GL_SMOOTH);
@ -681,6 +680,11 @@ sub Render {
}
glTranslatef(@{ $self->_camera_target->negative });
# light from above
glLightfv_p(GL_LIGHT0, GL_POSITION, -0.5, -0.5, 1, 0);
glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.2, 0.2, 0.2, 1);
glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.5, 0.5, 0.5, 1);
if ($self->enable_picking) {
glDisable(GL_LIGHTING);
$self->draw_volumes(1);
@ -706,6 +710,7 @@ sub Render {
# draw fixed background
if ($self->background) {
glDisable(GL_LIGHTING);
glPushMatrix();
glLoadIdentity();
@ -725,85 +730,72 @@ sub Render {
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glEnable(GL_LIGHTING);
}
# draw ground and axes
glDisable(GL_LIGHTING);
my $z0 = 0;
# draw ground
my $ground_z = GROUND_Z;
if ($self->bed_triangles) {
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(0.8, 0.6, 0.5, 0.4);
glNormal3d(0,0,1);
glVertexPointer_p(3, $self->bed_triangles);
glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
glDisableClientState(GL_VERTEX_ARRAY);
# we need depth test for grid, otherwise it would disappear when looking
# the object from below
glEnable(GL_DEPTH_TEST);
# draw grid
glLineWidth(3);
glColor4f(0.2, 0.2, 0.2, 0.4);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer_p(3, $self->bed_grid_lines);
glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
glDisableClientState(GL_VERTEX_ARRAY);
glDisable(GL_BLEND);
}
my $volumes_bb = $self->volumes_bounding_box;
{
# draw ground
my $ground_z = GROUND_Z;
if ($self->bed_triangles) {
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(0.8, 0.6, 0.5, 0.4);
glNormal3d(0,0,1);
glVertexPointer_p(3, $self->bed_triangles);
glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
glDisableClientState(GL_VERTEX_ARRAY);
glEnable(GL_DEPTH_TEST);
# draw grid
glLineWidth(3);
glColor4f(0.2, 0.2, 0.2, 0.4);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer_p(3, $self->bed_grid_lines);
glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
glDisableClientState(GL_VERTEX_ARRAY);
glDisable(GL_BLEND);
}
my $volumes_bb = $self->volumes_bounding_box;
{
# draw axes
$ground_z += 0.02;
my $origin = $self->origin;
my $axis_len = max(
0.3 * max(@{ $self->bed_bounding_box->size }),
2 * max(@{ $volumes_bb->size }),
);
glLineWidth(2);
glBegin(GL_LINES);
# draw line for x axis
glColor3f(1, 0, 0);
glVertex3f(@$origin, $ground_z);
glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,,
# draw line for y axis
glColor3f(0, 1, 0);
glVertex3f(@$origin, $ground_z);
glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++
# draw line for Z axis
glColor3f(0, 0, 1);
glVertex3f(@$origin, $ground_z);
glVertex3f(@$origin, $ground_z+$axis_len);
glEnd();
}
# draw cutting plane
if (defined $self->cutting_plane_z) {
my $plane_z = $z0 + $self->cutting_plane_z;
my $bb = $volumes_bb;
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBegin(GL_QUADS);
glColor4f(0.8, 0.8, 0.8, 0.5);
glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z);
glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z);
glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z);
glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z);
glEnd();
glEnable(GL_CULL_FACE);
glDisable(GL_BLEND);
}
# draw axes
# disable depth testing so that axes are not covered by ground
glDisable(GL_DEPTH_TEST);
my $origin = $self->origin;
my $axis_len = max(
0.3 * max(@{ $self->bed_bounding_box->size }),
2 * max(@{ $volumes_bb->size }),
);
glLineWidth(2);
glBegin(GL_LINES);
# draw line for x axis
glColor3f(1, 0, 0);
glVertex3f(@$origin, $ground_z);
glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,,
# draw line for y axis
glColor3f(0, 1, 0);
glVertex3f(@$origin, $ground_z);
glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++
glEnd();
# draw line for Z axis
# (re-enable depth test so that axis is correctly shown when objects are behind it)
glEnable(GL_DEPTH_TEST);
glBegin(GL_LINES);
glColor3f(0, 0, 1);
glVertex3f(@$origin, $ground_z);
glVertex3f(@$origin, $ground_z+$axis_len);
glEnd();
}
glEnable(GL_LIGHTING);
@ -811,6 +803,25 @@ sub Render {
# draw objects
$self->draw_volumes;
# draw cutting plane
if (defined $self->cutting_plane_z) {
my $plane_z = $self->cutting_plane_z;
my $bb = $volumes_bb;
glDisable(GL_CULL_FACE);
glDisable(GL_LIGHTING);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBegin(GL_QUADS);
glColor4f(0.8, 0.8, 0.8, 0.5);
glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z);
glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z);
glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z);
glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z);
glEnd();
glEnable(GL_CULL_FACE);
glDisable(GL_BLEND);
}
glFlush();
$self->SwapBuffers();
@ -866,9 +877,13 @@ sub draw_volumes {
glLineWidth(0);
glColor3f(@{COLORS->[0]});
glBegin(GL_QUADS);
glNormal3f((map $_/$line->length, @{$line->normal}), 0);
# We'll use this for the middle normal when using 4 quads:
#my $xy_normal = $line->normal;
#$_xynormal->scale(1/$line->length);
glNormal3f(0,0,-1);
glVertex3f((map unscale($_), @{$line->a}), $bottom_z);
glVertex3f((map unscale($_), @{$line->b}), $bottom_z);
glNormal3f(0,0,1);
glVertex3f((map unscale($_), @{$line->b}), $top_z);
glVertex3f((map unscale($_), @{$line->a}), $top_z);
glEnd();

View File

@ -928,6 +928,7 @@ sub build {
serial_port serial_speed
octoprint_host octoprint_apikey
use_firmware_retraction pressure_advance vibration_limit
use_volumetric_e
start_gcode end_gcode layer_gcode toolchange_gcode
nozzle_diameter extruder_offset
retract_length retract_lift retract_speed retract_restart_extra retract_before_travel retract_layer_change wipe
@ -1019,8 +1020,8 @@ sub build {
{
my $optgroup = $page->new_optgroup('OctoPrint upload');
# append a button to the Host line
my $octoprint_host_widget = sub {
# append two buttons to the Host line
my $octoprint_host_browse = sub {
my ($parent) = @_;
my $btn = Wx::Button->new($parent, -1, "Browse…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
@ -1032,10 +1033,7 @@ sub build {
if (!eval "use Net::Bonjour; 1") {
$btn->Disable;
}
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($btn);
EVT_BUTTON($self, $btn, sub {
my $dlg = Slic3r::GUI::BonjourBrowser->new($self);
if ($dlg->ShowModal == wxID_OK) {
@ -1047,22 +1045,51 @@ sub build {
}
});
return $sizer;
return $btn;
};
my $octoprint_host_test = sub {
my ($parent) = @_;
my $btn = $self->{octoprint_host_test_btn} = Wx::Button->new($parent, -1, "Test", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
$btn->SetFont($Slic3r::GUI::small_font);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new("$Slic3r::var/wrench.png", wxBITMAP_TYPE_PNG));
}
EVT_BUTTON($self, $btn, sub {
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
my $res = $ua->post(
"http://" . $self->{config}->octoprint_host . "/api/version",
'X-Api-Key' => $self->{config}->octoprint_apikey,
);
if ($res->is_success) {
Slic3r::GUI::show_info($self, "Connection to OctoPrint works correctly.", "Success!");
} else {
Slic3r::GUI::show_error($self,
"I wasn't able to connect to OctoPrint (" . $res->status_line . "). "
. "Check hostname and OctoPrint version (at least 1.1.0 is required).");
}
});
return $btn;
};
my $host_line = $optgroup->create_single_option_line('octoprint_host');
$host_line->append_widget($octoprint_host_widget);
$host_line->append_widget($octoprint_host_browse);
$host_line->append_widget($octoprint_host_test);
$optgroup->append_line($host_line);
$optgroup->append_single_option_line('octoprint_apikey');
}
{
my $optgroup = $page->new_optgroup('Firmware');
$optgroup->append_single_option_line('gcode_flavor');
$optgroup->append_single_option_line('use_relative_e_distances');
}
{
my $optgroup = $page->new_optgroup('Advanced');
$optgroup->append_single_option_line('use_relative_e_distances');
$optgroup->append_single_option_line('use_firmware_retraction');
$optgroup->append_single_option_line('use_volumetric_e');
$optgroup->append_single_option_line('pressure_advance');
$optgroup->append_single_option_line('vibration_limit');
}
@ -1201,6 +1228,11 @@ sub _update {
my $config = $self->{config};
$self->get_field('serial_speed')->toggle($config->get('serial_port'));
if ($config->get('octoprint_host') && eval "use LWP::UserAgent; 1") {
$self->{octoprint_host_test_btn}->Enable;
} else {
$self->{octoprint_host_test_btn}->Disable;
}
$self->get_field('octoprint_apikey')->toggle($config->get('octoprint_host'));
my $have_multiple_extruders = $self->{extruders_count} > 1;

View File

@ -0,0 +1,462 @@
package Slic3r::Layer::PerimeterGenerator;
use Moo;
use Slic3r::ExtrusionLoop ':roles';
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(scale unscale chained_path);
use Slic3r::Geometry::Clipper qw(union_ex diff diff_ex intersection_ex offset offset2
offset_ex offset2_ex union_pt intersection_ppl diff_ppl);
use Slic3r::Surface ':types';
has 'slices' => (is => 'ro', required => 1); # SurfaceCollection
has 'lower_slices' => (is => 'ro', required => 0);
has 'layer_height' => (is => 'ro', required => 1);
has 'layer_id' => (is => 'ro', required => 0, default => sub { -1 });
has 'perimeter_flow' => (is => 'ro', required => 1);
has 'ext_perimeter_flow' => (is => 'ro', required => 1);
has 'overhang_flow' => (is => 'ro', required => 1);
has 'solid_infill_flow' => (is => 'ro', required => 1);
has 'config' => (is => 'ro', default => sub { Slic3r::Config::PrintRegion->new });
has 'print_config' => (is => 'ro', default => sub { Slic3r::Config::Print->new });
has '_lower_slices_p' => (is => 'rw', default => sub { [] });
has '_holes_pt' => (is => 'rw');
has '_ext_mm3_per_mm' => (is => 'rw');
has '_mm3_per_mm' => (is => 'rw');
has '_mm3_per_mm_overhang' => (is => 'rw');
has '_thin_wall_polylines' => (is => 'rw', default => sub { [] });
# generated loops will be put here
has 'loops' => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Collection->new });
# generated gap fills will be put here
has 'gap_fill' => (is => 'ro', default => sub { Slic3r::ExtrusionPath::Collection->new });
# generated fill surfaces will be put here
has 'fill_surfaces' => (is => 'ro', default => sub { Slic3r::Surface::Collection->new });
sub BUILDARGS {
my ($class, %args) = @_;
if (my $flow = delete $args{flow}) {
$args{perimeter_flow} //= $flow;
$args{ext_perimeter_flow} //= $flow;
$args{overhang_flow} //= $flow;
$args{solid_infill_flow} //= $flow;
}
return { %args };
}
sub process {
my ($self) = @_;
# other perimeters
$self->_mm3_per_mm($self->perimeter_flow->mm3_per_mm);
my $pwidth = $self->perimeter_flow->scaled_width;
my $pspacing = $self->perimeter_flow->scaled_spacing;
# external perimeters
$self->_ext_mm3_per_mm($self->ext_perimeter_flow->mm3_per_mm);
my $ext_pwidth = $self->ext_perimeter_flow->scaled_width;
my $ext_pspacing = scale($self->ext_perimeter_flow->spacing_to($self->perimeter_flow));
# overhang perimeters
$self->_mm3_per_mm_overhang($self->overhang_flow->mm3_per_mm);
# solid infill
my $ispacing = $self->solid_infill_flow->scaled_spacing;
my $gap_area_threshold = $pwidth ** 2;
# Calculate the minimum required spacing between two adjacent traces.
# This should be equal to the nominal flow spacing but we experiment
# with some tolerance in order to avoid triggering medial axis when
# some squishing might work. Loops are still spaced by the entire
# flow spacing; this only applies to collapsing parts.
my $min_spacing = $pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
my $ext_min_spacing = $ext_pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
# prepare grown lower layer slices for overhang detection
if ($self->lower_slices && $self->config->overhangs) {
# We consider overhang any part where the entire nozzle diameter is not supported by the
# lower layer, so we take lower slices and offset them by half the nozzle diameter used
# in the current layer
my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $self->config->perimeter_extruder-1);
$self->_lower_slices_p(
offset([ map @$_, @{$self->lower_slices} ], scale +$nozzle_diameter/2)
);
}
# we need to process each island separately because we might have different
# extra perimeters for each one
foreach my $surface (@{$self->slices}) {
my @contours = (); # array of Polygons with ccw orientation
my @holes = (); # array of Polygons with cw orientation
my @thin_walls = (); # array of ExPolygons
# detect how many perimeters must be generated for this island
my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0);
my @last = @{$surface->expolygon};
my @gaps = (); # array of ExPolygons
if ($loop_number > 0) {
# we loop one time more than needed in order to find gaps after the last perimeter was applied
for my $i (1 .. ($loop_number+1)) { # outer loop is 1
my @offsets = ();
if ($i == 1) {
# the minimum thickness of a single loop is:
# ext_width/2 + ext_spacing/2 + spacing/2 + width/2
if ($self->config->thin_walls) {
@offsets = @{offset2(
\@last,
-(0.5*$ext_pwidth + 0.5*$ext_min_spacing - 1),
+(0.5*$ext_min_spacing - 1),
)};
} else {
@offsets = @{offset(
\@last,
-0.5*$ext_pwidth,
)};
}
# look for thin walls
if ($self->config->thin_walls) {
my $diff = diff_ex(
\@last,
offset(\@offsets, +0.5*$ext_pwidth),
1, # medial axis requires non-overlapping geometry
);
push @thin_walls, @$diff;
}
} else {
my $distance = ($i == 2) ? $ext_pspacing : $pspacing;
if ($self->config->thin_walls) {
@offsets = @{offset2(
\@last,
-($distance + 0.5*$min_spacing - 1),
+(0.5*$min_spacing - 1),
)};
} else {
@offsets = @{offset(
\@last,
-$distance,
)};
}
# look for gaps
if ($self->config->gap_fill_speed > 0 && $self->config->fill_density > 0) {
# not using safety offset here would "detect" very narrow gaps
# (but still long enough to escape the area threshold) that gap fill
# won't be able to fill but we'd still remove from infill area
my $diff = diff_ex(
offset(\@last, -0.5*$pspacing),
offset(\@offsets, +0.5*$pspacing + 10), # safety offset
);
push @gaps, grep abs($_->area) >= $gap_area_threshold, @$diff;
}
}
last if !@offsets;
last if $i > $loop_number; # we were only looking for gaps this time
# clone polygons because these ExPolygons will go out of scope very soon
@last = @offsets;
foreach my $polygon (@offsets) {
if ($polygon->is_counter_clockwise) {
push @contours, $polygon;
} else {
push @holes, $polygon;
}
}
}
}
# fill gaps
if (@gaps) {
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"gaps.svg",
expolygons => \@gaps,
);
}
# where $pwidth < thickness < 2*$pspacing, infill with width = 1.5*$pwidth
# where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth
my @gap_sizes = (
[ $pwidth, 2*$pspacing, unscale 1.5*$pwidth ],
[ 0.5*$pwidth, $pwidth, unscale 0.5*$pwidth ],
);
foreach my $gap_size (@gap_sizes) {
my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps);
$self->gap_fill->append($_) for @gap_fill;
# Make sure we don't infill narrow parts that are already gap-filled
# (we only consider this surface's gaps to reduce the diff() complexity).
# Growing actual extrusions ensures that gaps not filled by medial axis
# are not subtracted from fill surfaces (they might be too short gaps
# that medial axis skips but infill might join with other infill regions
# and use zigzag).
my $w = $gap_size->[2];
my @filled = map {
@{($_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline)
->grow(scale $w/2)};
} @gap_fill;
@last = @{diff(\@last, \@filled)};
}
}
# create one more offset to be used as boundary for fill
# we offset by half the perimeter spacing (to get to the actual infill boundary)
# and then we offset back and forth by half the infill spacing to only consider the
# non-collapsing regions
my $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
$self->fill_surfaces->append($_)
for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), # use a bogus surface type
@{offset2_ex(
[ map @{$_->simplify_p(&Slic3r::SCALED_RESOLUTION)}, @{union_ex(\@last)} ],
-($pspacing/2 + $min_perimeter_infill_spacing/2),
+$min_perimeter_infill_spacing/2,
)};
# process thin walls by collapsing slices to single passes
if (@thin_walls) {
# the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
# (actually, something larger than that still may exist due to mitering or other causes)
my $min_width = $pwidth / 4;
@thin_walls = @{offset2_ex([ map @$_, @thin_walls ], -$min_width/2, +$min_width/2)};
# the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
$self->_thin_wall_polylines([ map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls ]);
Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->_thin_wall_polylines}) if $Slic3r::debug;
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"medial_axis.svg",
no_arrows => 1,
expolygons => \@thin_walls,
green_polylines => [ map $_->polygon->split_at_first_point, @{$self->perimeters} ],
red_polylines => $self->_thin_wall_polylines,
);
}
}
# find nesting hierarchies separately for contours and holes
my $contours_pt = union_pt(\@contours);
$self->_holes_pt(union_pt(\@holes));
# order loops from inner to outer (in terms of object slices)
my @loops = $self->_traverse_pt($contours_pt, 0, 1);
# if brim will be printed, reverse the order of perimeters so that
# we continue inwards after having finished the brim
# TODO: add test for perimeter order
@loops = reverse @loops
if $self->config->external_perimeters_first
|| ($self->layer_id == 0 && $self->print_config->brim_width > 0);
# append perimeters for this slice as a collection
$self->loops->append(Slic3r::ExtrusionPath::Collection->new(@loops));
}
}
sub _traverse_pt {
my ($self, $polynodes, $depth, $is_contour) = @_;
# convert all polynodes to ExtrusionLoop objects
my $collection = Slic3r::ExtrusionPath::Collection->new; # temporary collection
my @children = ();
foreach my $polynode (@$polynodes) {
my $polygon = ($polynode->{outer} // $polynode->{hole})->clone;
my $role = EXTR_ROLE_PERIMETER;
my $loop_role = EXTRL_ROLE_DEFAULT;
my $root_level = $depth == 0;
my $no_children = !@{ $polynode->{children} };
my $is_external = $is_contour ? $root_level : $no_children;
my $is_internal = $is_contour ? $no_children : $root_level;
if ($is_contour && $is_internal) {
# internal perimeters are root level in case of holes
# and items with no children in case of contours
# Note that we set loop role to ContourInternalPerimeter
# also when loop is both internal and external (i.e.
# there's only one contour loop).
$loop_role = EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER;
}
if ($is_external) {
# external perimeters are root level in case of contours
# and items with no children in case of holes
$role = EXTR_ROLE_EXTERNAL_PERIMETER;
}
# detect overhanging/bridging perimeters
my @paths = ();
if ($self->config->overhangs && $self->layer_id > 0) {
# get non-overhang paths by intersecting this loop with the grown lower slices
foreach my $polyline (@{ intersection_ppl([ $polygon ], $self->_lower_slices_p) }) {
push @paths, Slic3r::ExtrusionPath->new(
polyline => $polyline,
role => $role,
mm3_per_mm => ($is_external ? $self->_ext_mm3_per_mm : $self->_mm3_per_mm),
width => ($is_external ? $self->ext_perimeter_flow->width : $self->perimeter_flow->width),
height => $self->layer_height,
);
}
# get overhang paths by checking what parts of this loop fall
# outside the grown lower slices (thus where the distance between
# the loop centerline and original lower slices is >= half nozzle diameter
foreach my $polyline (@{ diff_ppl([ $polygon ], $self->_lower_slices_p) }) {
push @paths, Slic3r::ExtrusionPath->new(
polyline => $polyline,
role => EXTR_ROLE_OVERHANG_PERIMETER,
mm3_per_mm => $self->_mm3_per_mm_overhang,
width => $self->overhang_flow->width,
height => $self->layer_height,
);
}
# reapply the nearest point search for starting point
# (clone because the collection gets DESTROY'ed)
# We allow polyline reversal because Clipper may have randomly
# reversed polylines during clipping.
my $collection = Slic3r::ExtrusionPath::Collection->new(@paths); # temporary collection
@paths = map $_->clone, @{$collection->chained_path(0)};
} else {
push @paths, Slic3r::ExtrusionPath->new(
polyline => $polygon->split_at_first_point,
role => $role,
mm3_per_mm => $self->_mm3_per_mm,
width => $self->perimeter_flow->width,
height => $self->layer_height,
);
}
my $loop = Slic3r::ExtrusionLoop->new_from_paths(@paths);
$loop->role($loop_role);
# return ccw contours and cw holes
# GCode.pm will convert all of them to ccw, but it needs to know
# what the holes are in order to compute the correct inwards move
# We do this on the final Loop object because overhang clipping
# does not keep orientation.
if ($is_contour) {
$loop->make_counter_clockwise;
} else {
$loop->make_clockwise;
}
$collection->append($loop);
# save the children
push @children, $polynode->{children};
}
# if we're handling the top-level contours, add thin walls as candidates too
# in order to include them in the nearest-neighbor search
if ($is_contour && $depth == 0) {
foreach my $polyline (@{$self->_thin_wall_polylines}) {
$collection->append(Slic3r::ExtrusionPath->new(
polyline => $polyline,
role => EXTR_ROLE_EXTERNAL_PERIMETER,
mm3_per_mm => $self->_mm3_per_mm,
width => $self->perimeter_flow->width,
height => $self->layer_height,
));
}
}
# use a nearest neighbor search to order these children
# TODO: supply second argument to chained_path() too?
# (We used to skip this chained_path() when $is_contour &&
# $depth == 0 because slices are ordered at G_code export
# time, but multiple top-level perimeters might belong to
# the same slice actually, so that was a broken optimization.)
# We supply no_reverse = false because we want to permit reversal
# of thin walls, but we rely on the fact that loops will never
# be reversed anyway.
my $sorted_collection = $collection->chained_path_indices(0);
my @orig_indices = @{$sorted_collection->orig_indices};
my @loops = ();
foreach my $loop (@$sorted_collection) {
my $orig_index = shift @orig_indices;
if ($loop->isa('Slic3r::ExtrusionPath')) {
push @loops, $loop->clone;
} else {
# if this is an external contour find all holes belonging to this contour(s)
# and prepend them
if ($is_contour && $depth == 0) {
# $loop is the outermost loop of an island
my @holes = ();
for (my $i = 0; $i <= $#{$self->_holes_pt}; $i++) {
if ($loop->polygon->contains_point($self->_holes_pt->[$i]{outer}->first_point)) {
push @holes, splice @{$self->_holes_pt}, $i, 1; # remove from candidates to reduce complexity
$i--;
}
}
# order holes efficiently
@holes = @holes[@{chained_path([ map {($_->{outer} // $_->{hole})->first_point} @holes ])}];
push @loops, reverse map $self->_traverse_pt([$_], 0, 0), @holes;
}
# traverse children and prepend them to this loop
push @loops, $self->_traverse_pt($children[$orig_index], $depth+1, $is_contour);
push @loops, $loop->clone;
}
}
return @loops;
}
sub _fill_gaps {
my ($self, $min, $max, $w, $gaps) = @_;
my $this = diff_ex(
offset2([ map @$_, @$gaps ], -$min/2, +$min/2),
offset2([ map @$_, @$gaps ], -$max/2, +$max/2),
1,
);
my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this;
return if !@polylines;
Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @$this, $w
if @$this;
#my $flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL, 0, $w);
my $flow = Slic3r::Flow->new(
width => $w,
height => $self->layer_height,
nozzle_diameter => $self->solid_infill_flow->nozzle_diameter,
);
my %path_args = (
role => EXTR_ROLE_GAPFILL,
mm3_per_mm => $flow->mm3_per_mm,
width => $flow->width,
height => $self->layer_height,
);
my @entities = ();
foreach my $polyline (@polylines) {
#if ($polylines[$i]->isa('Slic3r::Polygon')) {
# my $loop = Slic3r::ExtrusionLoop->new;
# $loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args));
# $polylines[$i] = $loop;
if ($polyline->is_valid && $polyline->first_point->coincides_with($polyline->last_point)) {
# since medial_axis() now returns only Polyline objects, detect loops here
push @entities, my $loop = Slic3r::ExtrusionLoop->new;
$loop->append(Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args));
} else {
push @entities, Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args);
}
}
return @entities;
}
1;

View File

@ -2,14 +2,11 @@ package Slic3r::Layer::Region;
use strict;
use warnings;
use List::Util qw(sum first);
use Slic3r::ExtrusionLoop ':roles';
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Flow ':roles';
use Slic3r::Geometry qw(PI A B scale unscale chained_path);
use Slic3r::Geometry::Clipper qw(union_ex diff_ex intersection_ex
offset offset_ex offset2 offset2_ex union_pt diff intersection
union diff intersection_ppl diff_ppl);
use Slic3r::Geometry qw(scale);
use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex
);
use Slic3r::Surface ':types';
@ -31,418 +28,28 @@ sub config { return $_[0]->region->config; }
sub make_perimeters {
my ($self, $slices, $fill_surfaces) = @_;
# other perimeters
my $perimeter_flow = $self->flow(FLOW_ROLE_PERIMETER);
my $mm3_per_mm = $perimeter_flow->mm3_per_mm;
my $pwidth = $perimeter_flow->scaled_width;
my $pspacing = $perimeter_flow->scaled_spacing;
# external perimeters
my $ext_perimeter_flow = $self->flow(FLOW_ROLE_EXTERNAL_PERIMETER);
my $ext_mm3_per_mm = $ext_perimeter_flow->mm3_per_mm;
my $ext_pwidth = $ext_perimeter_flow->scaled_width;
my $ext_pspacing = scale($ext_perimeter_flow->spacing_to($perimeter_flow));
# overhang perimeters
my $overhang_flow = $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object);
my $mm3_per_mm_overhang = $overhang_flow->mm3_per_mm;
# solid infill
my $solid_infill_flow = $self->flow(FLOW_ROLE_SOLID_INFILL);
my $ispacing = $solid_infill_flow->scaled_spacing;
my $gap_area_threshold = $pwidth ** 2;
# Calculate the minimum required spacing between two adjacent traces.
# This should be equal to the nominal flow spacing but we experiment
# with some tolerance in order to avoid triggering medial axis when
# some squishing might work. Loops are still spaced by the entire
# flow spacing; this only applies to collapsing parts.
my $min_spacing = $pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
my $ext_min_spacing = $ext_pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
$self->perimeters->clear;
$self->thin_fills->clear;
my @contours = (); # array of Polygons with ccw orientation
my @holes = (); # array of Polygons with cw orientation
my @thin_walls = (); # array of ExPolygons
# we need to process each island separately because we might have different
# extra perimeters for each one
foreach my $surface (@$slices) {
# detect how many perimeters must be generated for this island
my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0);
my $generator = Slic3r::Layer::PerimeterGenerator->new(
# input:
config => $self->config,
print_config => $self->layer->print->config,
layer_height => $self->height,
layer_id => $self->layer->id,
slices => $slices,
lower_slices => defined($self->layer->lower_layer) ? $self->layer->lower_layer->slices : undef,
perimeter_flow => $self->flow(FLOW_ROLE_PERIMETER),
ext_perimeter_flow => $self->flow(FLOW_ROLE_EXTERNAL_PERIMETER),
overhang_flow => $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object),
solid_infill_flow => $self->flow(FLOW_ROLE_SOLID_INFILL),
my @last = @{$surface->expolygon};
my @gaps = (); # array of ExPolygons
if ($loop_number > 0) {
# we loop one time more than needed in order to find gaps after the last perimeter was applied
for my $i (1 .. ($loop_number+1)) { # outer loop is 1
my @offsets = ();
if ($i == 1) {
# the minimum thickness of a single loop is:
# ext_width/2 + ext_spacing/2 + spacing/2 + width/2
if ($self->config->thin_walls) {
@offsets = @{offset2(
\@last,
-(0.5*$ext_pwidth + 0.5*$ext_min_spacing - 1),
+(0.5*$ext_min_spacing - 1),
)};
} else {
@offsets = @{offset(
\@last,
-0.5*$ext_pwidth,
)};
}
# look for thin walls
if ($self->config->thin_walls) {
my $diff = diff_ex(
\@last,
offset(\@offsets, +0.5*$ext_pwidth),
1, # medial axis requires non-overlapping geometry
);
push @thin_walls, @$diff;
}
} else {
my $distance = ($i == 2) ? $ext_pspacing : $pspacing;
if ($self->config->thin_walls) {
@offsets = @{offset2(
\@last,
-($distance + 0.5*$min_spacing - 1),
+(0.5*$min_spacing - 1),
)};
} else {
@offsets = @{offset(
\@last,
-$distance,
)};
}
# look for gaps
if ($self->region->config->gap_fill_speed > 0 && $self->config->fill_density > 0) {
# not using safety offset here would "detect" very narrow gaps
# (but still long enough to escape the area threshold) that gap fill
# won't be able to fill but we'd still remove from infill area
my $diff = diff_ex(
offset(\@last, -0.5*$pspacing),
offset(\@offsets, +0.5*$pspacing + 10), # safety offset
);
push @gaps, grep abs($_->area) >= $gap_area_threshold, @$diff;
}
}
last if !@offsets;
last if $i > $loop_number; # we were only looking for gaps this time
# clone polygons because these ExPolygons will go out of scope very soon
@last = @offsets;
foreach my $polygon (@offsets) {
if ($polygon->is_counter_clockwise) {
push @contours, $polygon;
} else {
push @holes, $polygon;
}
}
}
}
# fill gaps
if (@gaps) {
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"gaps.svg",
expolygons => \@gaps,
);
}
# where $pwidth < thickness < 2*$pspacing, infill with width = 1.5*$pwidth
# where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth
my @gap_sizes = (
[ $pwidth, 2*$pspacing, unscale 1.5*$pwidth ],
[ 0.5*$pwidth, $pwidth, unscale 0.5*$pwidth ],
);
foreach my $gap_size (@gap_sizes) {
my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps);
$self->thin_fills->append($_) for @gap_fill;
# Make sure we don't infill narrow parts that are already gap-filled
# (we only consider this surface's gaps to reduce the diff() complexity).
# Growing actual extrusions ensures that gaps not filled by medial axis
# are not subtracted from fill surfaces (they might be too short gaps
# that medial axis skips but infill might join with other infill regions
# and use zigzag).
my $w = $gap_size->[2];
my @filled = map {
@{($_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline)
->grow(scale $w/2)};
} @gap_fill;
@last = @{diff(\@last, \@filled)};
}
}
# create one more offset to be used as boundary for fill
# we offset by half the perimeter spacing (to get to the actual infill boundary)
# and then we offset back and forth by half the infill spacing to only consider the
# non-collapsing regions
my $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
$fill_surfaces->append($_)
for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), # use a bogus surface type
@{offset2_ex(
[ map @{$_->simplify_p(&Slic3r::SCALED_RESOLUTION)}, @{union_ex(\@last)} ],
-($pspacing/2 + $min_perimeter_infill_spacing/2),
+$min_perimeter_infill_spacing/2,
)};
}
# process thin walls by collapsing slices to single passes
my @thin_wall_polylines = ();
if (@thin_walls) {
# the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
# (actually, something larger than that still may exist due to mitering or other causes)
my $min_width = $pwidth / 4;
@thin_walls = @{offset2_ex([ map @$_, @thin_walls ], -$min_width/2, +$min_width/2)};
# the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
@thin_wall_polylines = map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls;
Slic3r::debugf " %d thin walls detected\n", scalar(@thin_wall_polylines) if $Slic3r::debug;
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"medial_axis.svg",
no_arrows => 1,
expolygons => \@thin_walls,
green_polylines => [ map $_->polygon->split_at_first_point, @{$self->perimeters} ],
red_polylines => \@thin_wall_polylines,
);
}
}
# find nesting hierarchies separately for contours and holes
my $contours_pt = union_pt(\@contours);
my $holes_pt = union_pt(\@holes);
# prepare grown lower layer slices for overhang detection
my $lower_slices = Slic3r::ExPolygon::Collection->new;
if ($self->layer->lower_layer && $self->region->config->overhangs) {
# We consider overhang any part where the entire nozzle diameter is not supported by the
# lower layer, so we take lower slices and offset them by half the nozzle diameter used
# in the current layer
my $nozzle_diameter = $self->layer->print->config->get_at('nozzle_diameter', $self->region->config->perimeter_extruder-1);
$lower_slices->append($_)
for @{offset_ex([ map @$_, @{$self->layer->lower_layer->slices} ], scale +$nozzle_diameter/2)};
}
my $lower_slices_p = $lower_slices->polygons;
# prepare a coderef for traversing the PolyTree object
# external contours are root items of $contours_pt
# internal contours are the ones next to external
my $traverse;
$traverse = sub {
my ($polynodes, $depth, $is_contour) = @_;
# convert all polynodes to ExtrusionLoop objects
my $collection = Slic3r::ExtrusionPath::Collection->new; # temporary collection
my @children = ();
foreach my $polynode (@$polynodes) {
my $polygon = ($polynode->{outer} // $polynode->{hole})->clone;
my $role = EXTR_ROLE_PERIMETER;
my $loop_role = EXTRL_ROLE_DEFAULT;
my $root_level = $depth == 0;
my $no_children = !@{ $polynode->{children} };
my $is_external = $is_contour ? $root_level : $no_children;
my $is_internal = $is_contour ? $no_children : $root_level;
if ($is_contour && $is_internal) {
# internal perimeters are root level in case of holes
# and items with no children in case of contours
# Note that we set loop role to ContourInternalPerimeter
# also when loop is both internal and external (i.e.
# there's only one contour loop).
$loop_role = EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER;
}
if ($is_external) {
# external perimeters are root level in case of contours
# and items with no children in case of holes
$role = EXTR_ROLE_EXTERNAL_PERIMETER;
}
# detect overhanging/bridging perimeters
my @paths = ();
if ($self->region->config->overhangs && $self->layer->id > 0) {
# get non-overhang paths by intersecting this loop with the grown lower slices
foreach my $polyline (@{ intersection_ppl([ $polygon ], $lower_slices_p) }) {
push @paths, Slic3r::ExtrusionPath->new(
polyline => $polyline,
role => $role,
mm3_per_mm => ($is_external ? $ext_mm3_per_mm : $mm3_per_mm),
width => ($is_external ? $ext_perimeter_flow->width : $perimeter_flow->width),
height => $self->height,
);
}
# get overhang paths by checking what parts of this loop fall
# outside the grown lower slices (thus where the distance between
# the loop centerline and original lower slices is >= half nozzle diameter
foreach my $polyline (@{ diff_ppl([ $polygon ], $lower_slices_p) }) {
push @paths, Slic3r::ExtrusionPath->new(
polyline => $polyline,
role => EXTR_ROLE_OVERHANG_PERIMETER,
mm3_per_mm => $mm3_per_mm_overhang,
width => $overhang_flow->width,
height => $self->height,
);
}
# reapply the nearest point search for starting point
# (clone because the collection gets DESTROY'ed)
# We allow polyline reversal because Clipper may have randomly
# reversed polylines during clipping.
my $collection = Slic3r::ExtrusionPath::Collection->new(@paths); # temporary collection
@paths = map $_->clone, @{$collection->chained_path(0)};
} else {
push @paths, Slic3r::ExtrusionPath->new(
polyline => $polygon->split_at_first_point,
role => $role,
mm3_per_mm => $mm3_per_mm,
width => $perimeter_flow->width,
height => $self->height,
);
}
my $loop = Slic3r::ExtrusionLoop->new_from_paths(@paths);
$loop->role($loop_role);
# return ccw contours and cw holes
# GCode.pm will convert all of them to ccw, but it needs to know
# what the holes are in order to compute the correct inwards move
# We do this on the final Loop object instead of the polygon because
# overhang clipping might have reversed its order since Clipper does
# not preserve polyline orientation.
if ($is_contour) {
$loop->make_counter_clockwise;
} else {
$loop->make_clockwise;
}
$collection->append($loop);
# save the children
push @children, $polynode->{children};
}
# if we're handling the top-level contours, add thin walls as candidates too
# in order to include them in the nearest-neighbor search
if ($is_contour && $depth == 0) {
foreach my $polyline (@thin_wall_polylines) {
$collection->append(Slic3r::ExtrusionPath->new(
polyline => $polyline,
role => EXTR_ROLE_EXTERNAL_PERIMETER,
mm3_per_mm => $mm3_per_mm,
width => $perimeter_flow->width,
height => $self->height,
));
}
}
# use a nearest neighbor search to order these children
# TODO: supply second argument to chained_path() too?
# (We used to skip this chiained_path() when $is_contour &&
# $depth == 0 because slices are ordered at G_code export
# time, but multiple top-level perimeters might belong to
# the same slice actually, so that was a broken optimization.)
my $sorted_collection = $collection->chained_path_indices(0);
my @orig_indices = @{$sorted_collection->orig_indices};
my @loops = ();
foreach my $loop (@$sorted_collection) {
my $orig_index = shift @orig_indices;
if ($loop->isa('Slic3r::ExtrusionPath')) {
push @loops, $loop->clone;
} else {
# if this is an external contour find all holes belonging to this contour(s)
# and prepend them
if ($is_contour && $depth == 0) {
# $loop is the outermost loop of an island
my @holes = ();
for (my $i = 0; $i <= $#$holes_pt; $i++) {
if ($loop->polygon->contains_point($holes_pt->[$i]{outer}->first_point)) {
push @holes, splice @$holes_pt, $i, 1; # remove from candidates to reduce complexity
$i--;
}
}
# order holes efficiently
@holes = @holes[@{chained_path([ map {($_->{outer} // $_->{hole})->first_point} @holes ])}];
push @loops, reverse map $traverse->([$_], 0, 0), @holes;
}
# traverse children and prepend them to this loop
push @loops, $traverse->($children[$orig_index], $depth+1, $is_contour);
push @loops, $loop->clone;
}
}
return @loops;
};
# order loops from inner to outer (in terms of object slices)
my @loops = $traverse->($contours_pt, 0, 1);
# if brim will be printed, reverse the order of perimeters so that
# we continue inwards after having finished the brim
# TODO: add test for perimeter order
@loops = reverse @loops
if $self->region->config->external_perimeters_first
|| ($self->layer->id == 0 && $self->print->config->brim_width > 0);
# append perimeters
$self->perimeters->append($_) for @loops;
}
sub _fill_gaps {
my ($self, $min, $max, $w, $gaps) = @_;
my $this = diff_ex(
offset2([ map @$_, @$gaps ], -$min/2, +$min/2),
offset2([ map @$_, @$gaps ], -$max/2, +$max/2),
1,
# output:
loops => $self->perimeters,
gap_fill => $self->thin_fills,
fill_surfaces => $fill_surfaces,
);
my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this;
return if !@polylines;
Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @$this, $w
if @$this;
my $flow = $self->flow(FLOW_ROLE_SOLID_INFILL, 0, $w);
my %path_args = (
role => EXTR_ROLE_GAPFILL,
mm3_per_mm => $flow->mm3_per_mm,
width => $flow->width,
height => $self->height,
);
my @entities = ();
foreach my $polyline (@polylines) {
#if ($polylines[$i]->isa('Slic3r::Polygon')) {
# my $loop = Slic3r::ExtrusionLoop->new;
# $loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args));
# $polylines[$i] = $loop;
if ($polyline->is_valid && $polyline->first_point->coincides_with($polyline->last_point)) {
# since medial_axis() now returns only Polyline objects, detect loops here
push @entities, my $loop = Slic3r::ExtrusionLoop->new;
$loop->append(Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args));
} else {
push @entities, Slic3r::ExtrusionPath->new(polyline => $polyline, %path_args);
}
}
return @entities;
$generator->process;
}
sub prepare_fill_surfaces {

View File

@ -7,6 +7,11 @@ sub new_scale {
return $class->new(map Slic3r::Geometry::scale($_), @_);
}
sub dump_perl {
my $self = shift;
return sprintf "[%s,%s]", @$self;
}
package Slic3r::Pointf;
use strict;
use warnings;

View File

@ -54,7 +54,7 @@ sub BUILD {
if $self->config->spiral_vase;
$self->_vibration_limit(Slic3r::GCode::VibrationLimit->new(config => $self->config))
if $self->config->vibration_limit > 0;
if $self->config->vibration_limit != 0;
$self->_arc_fitting(Slic3r::GCode::ArcFitting->new(config => $self->config))
if $self->config->gcode_arcs;
@ -121,23 +121,30 @@ sub export {
# initialize a motion planner for object-to-object travel moves
if ($self->config->avoid_crossing_perimeters) {
my $distance_from_objects = 1;
my $distance_from_objects = scale 1;
# compute the offsetted convex hull for each object and repeat it for each copy.
my @islands = ();
foreach my $obj_idx (0 .. ($self->print->object_count - 1)) {
my @islands_p = ();
foreach my $object (@{$self->objects}) {
# compute the convex hull of the entire object
my $convex_hull = convex_hull([
map @{$_->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers},
map @{$_->contour}, map @{$_->slices}, @{$object->layers},
]);
# discard layers only containing thin walls (offset would fail on an empty polygon)
if (@$convex_hull) {
my $expolygon = Slic3r::ExPolygon->new($convex_hull);
my @island = @{$expolygon->offset_ex(scale $distance_from_objects, 1, JT_SQUARE)};
foreach my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) {
push @islands, map { my $c = $_->clone; $c->translate(@$copy); $c } @island;
}
# discard objects only containing thin walls (offset would fail on an empty polygon)
next if !@$convex_hull;
# grow convex hull by the wanted clearance
my @obj_islands_p = @{offset([$convex_hull], $distance_from_objects, 1, JT_SQUARE)};
# translate convex hull for each object copy and append it to the islands array
foreach my $copy (@{ $object->_shifted_copies }) {
my @copy_islands_p = map $_->clone, @obj_islands_p;
$_->translate(@$copy) for @copy_islands_p;
push @islands_p, @copy_islands_p;
}
}
$gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex([ map @$_, @islands ]));
$gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex(\@islands_p));
}
# calculate wiping points if needed
@ -208,7 +215,7 @@ sub export {
}
$self->process_layer($layer, [$copy]);
}
$self->flush_cooling_buffer;
$self->flush_filters;
$finished_objects++;
}
}
@ -234,7 +241,7 @@ sub export {
}
}
}
$self->flush_cooling_buffer;
$self->flush_filters;
}
# write end commands to file
@ -357,7 +364,12 @@ sub process_layer {
}
$self->_skirt_done->{$layer->print_z} = 1;
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0);
$self->_gcodegen->avoid_crossing_perimeters->disable_once(1);
# allow a straight travel move to the first object point if this is the first layer
# (but don't in next layers)
if ($layer->id == 0) {
$self->_gcodegen->avoid_crossing_perimeters->disable_once(1);
}
}
# extrude brim
@ -369,6 +381,8 @@ sub process_layer {
for @{$self->print->brim};
$self->_brim_done(1);
$self->_gcodegen->avoid_crossing_perimeters->use_external_mp(0);
# allow a straight travel move to the first object point
$self->_gcodegen->avoid_crossing_perimeters->disable_once(1);
}
@ -413,17 +427,17 @@ sub process_layer {
# process perimeters
{
my $extruder_id = $region->config->perimeter_extruder-1;
foreach my $perimeter (@{$layerm->perimeters}) {
foreach my $perimeter_coll (@{$layerm->perimeters}) {
# init by_extruder item only if we actually use the extruder
$by_extruder{$extruder_id} //= [];
# $perimeter is an ExtrusionLoop or ExtrusionPath object
# $perimeter_coll is an ExtrusionPath::Collection object representing a single slice
for my $i (0 .. $#{$layer->slices}) {
if ($i == $#{$layer->slices}
|| $layer->slices->[$i]->contour->contains_point($perimeter->first_point)) {
|| $layer->slices->[$i]->contour->contains_point($perimeter_coll->first_point)) {
$by_extruder{$extruder_id}[$i] //= { perimeters => {} };
$by_extruder{$extruder_id}[$i]{perimeters}{$region_id} //= [];
push @{ $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} }, $perimeter;
push @{ $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} }, @$perimeter_coll;
last;
}
}
@ -532,28 +546,29 @@ sub _extrude_infill {
return $gcode;
}
sub flush_cooling_buffer {
sub flush_filters {
my ($self) = @_;
print {$self->fh} $self->filter($self->_cooling_buffer->flush);
print {$self->fh} $self->filter($self->_cooling_buffer->flush, 1);
}
sub filter {
my ($self, $gcode) = @_;
my ($self, $gcode, $flush) = @_;
# apply vibration limit if enabled;
# this injects pauses according to time (thus depends on actual speeds)
$gcode = $self->_vibration_limit->process($gcode)
if $self->print->config->vibration_limit != 0;
if defined $self->_vibration_limit;
# apply pressure regulation if enabled;
# this depends on actual speeds
$gcode = $self->_pressure_regulator->process($gcode)
if $self->print->config->pressure_advance > 0;
$gcode = $self->_pressure_regulator->process($gcode, $flush)
if defined $self->_pressure_regulator;
# apply arc fitting if enabled;
# this does not depend on speeds but changes G1 XY commands into G2/G2 IJ
$gcode = $self->_arc_fitting->process($gcode)
if $self->print->config->gcode_arcs;
if defined $self->_arc_fitting;
return $gcode;
}

View File

@ -178,7 +178,7 @@ sub contact_area {
# TODO: split_at_first_point() could split a bridge mid-way
my @overhang_perimeters =
map { $_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline->clone }
@{$layerm->perimeters};
map @$_, @{$layerm->perimeters};
# workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline()
$_->[0]->translate(1,0) for @overhang_perimeters;

View File

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

View File

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

36
t/pressure.t Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -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<Point>(const Point &item) const;
template bool ExPolygonCollection::contains<Line>(const Line &item) const;
template bool ExPolygonCollection::contains<Polyline>(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

View File

@ -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 &center);
template <class T> 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;
};
}

View File

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

View File

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

View File

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

View File

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

View File

@ -48,22 +48,32 @@ GCodeWriter::set_extruders(const std::vector<unsigned int> &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<unsigned int,Extruder>::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) {

View File

@ -129,20 +129,18 @@ Layer::any_internal_region_slice_contains(const T &item) const
}
return false;
}
template bool Layer::any_internal_region_slice_contains<Line>(const Line &item) const;
template bool Layer::any_internal_region_slice_contains<Polyline>(const Polyline &item) const;
template <class T>
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<Line>(const Line &item) const;
template bool Layer::any_internal_region_fill_surface_contains<Polyline>(const Polyline &item) const;
template bool Layer::any_bottom_region_slice_contains<Polyline>(const Polyline &item) const;
#ifdef SLIC3RXS
REGISTER_CLASS(Layer, "Layer");

View File

@ -94,8 +94,8 @@ class Layer {
void make_slices();
template <class T> bool any_internal_region_slice_contains(const T &item) const;
template <class T> bool any_internal_region_fill_surface_contains(const T &item) const;
template <class T> bool any_bottom_region_slice_contains(const T &item) const;
protected:
int _id; // sequential number of layer, 0-based
PrintObject *_object;

View File

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

View File

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

View File

@ -9,6 +9,7 @@ namespace Slic3r {
class Line;
class Linef3;
class Polyline;
typedef std::vector<Line> 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<Line> Lines;
class Linef3
{
public:

View File

@ -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<MotionPlannerGraph::neighbor>::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<double> VD;
typedef std::map<const VD::vertex_type*,size_t> t_vd_vertices;
VD vd;
t_vd_vertices vd_vertices;
typedef voronoi_diagram<double> VD;
VD vd;
// mapping between Voronoi vertices and graph nodes
typedef std::map<const VD::vertex_type*,size_t> 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<weight_t>::infinity();
std::vector<weight_t> min_distance;
std::vector<weight_t> dist;
std::vector<node_t> 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<std::pair<weight_t, node_t> > vertex_queue;
vertex_queue.insert(std::make_pair(min_distance[from], from));
while (!vertex_queue.empty())
// initialize the Q with all nodes
std::set<node_t> 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<node_t>::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<neighbor> &neighbors = this->adjacency_list[u];
for (std::vector<neighbor>::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();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,6 @@
#include "Polyline.hpp"
#include "ExPolygon.hpp"
#include "ExPolygonCollection.hpp"
#include "Line.hpp"
#include "Polygon.hpp"
#include <iostream>
@ -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 <class T>
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<ExPolygon>(const ExPolygon &area);
template void Polyline::simplify_by_visibility<ExPolygonCollection>(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);
}
}

View File

@ -7,6 +7,7 @@
namespace Slic3r {
class ExPolygon;
class Polyline;
typedef std::vector<Polyline> 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 <class T> 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;

View File

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

View File

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

View File

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

View File

@ -1,8 +1,12 @@
#include "SVG.hpp"
#include <iostream>
#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)
" <polyline fill=\"darkblue\" points=\"0,0 10,5 0,10 1,5\" />\n"
" </marker>\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,
" <line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" style=\"stroke: black; stroke-width: 2\"",
this->coordinate(line.a.x), this->coordinate(line.a.y), this->coordinate(line.b.x), this->coordinate(line.b.y)
" <line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" style=\"stroke: %s; stroke-width: 1\"",
COORD(line.a.x), COORD(line.a.y), COORD(line.b.x), COORD(line.b.y), stroke.c_str()
);
if (this->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 << " <circle cx=\"" << COORD(point.x) << "\" cy=\"" << COORD(point.y)
<< "\" r=\"" << radius << "\" "
<< "style=\"stroke: none; fill: " << fill << "\" />";
fprintf(this->f, "%s\n", svg.str().c_str());
}
void
SVG::path(const std::string &d, bool fill)
{
fprintf(
this->f,
" <path d=\"%s\" style=\"fill: %s; stroke: %s; stroke-width: %s; fill-type: evenodd\" %s />\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, "</svg>\n");
fclose(this->f);
printf("SVG file written.\n");
printf("SVG written to %s\n", this->filename.c_str());
}
}

View File

@ -2,6 +2,7 @@
#define slic3r_SVG_hpp_
#include <myinit.h>
#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;
};
}

View File

@ -77,9 +77,19 @@ SurfaceCollection::any_internal_contains(const T &item) const
}
return false;
}
template bool SurfaceCollection::any_internal_contains<Line>(const Line &item) const;
template bool SurfaceCollection::any_internal_contains<Polyline>(const Polyline &item) const;
template <class T>
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<Polyline>(const Polyline &item) const;
SurfacesPtr
SurfaceCollection::filter_by_type(SurfaceType type)
{

View File

@ -16,6 +16,7 @@ class SurfaceCollection
void simplify(double tolerance);
void group(std::vector<SurfacesPtr> *retval);
template <class T> bool any_internal_contains(const T &item) const;
template <class T> bool any_bottom_contains(const T &item) const;
SurfacesPtr filter_by_type(SurfaceType type);
void filter_by_type(SurfaceType type, Polygons* polygons);
};

View File

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

View File

@ -5,6 +5,7 @@
#include <admesh/stl.h>
#include <vector>
#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;

View File

@ -6,7 +6,7 @@
#include <iostream>
#include <sstream>
#define SLIC3R_VERSION "1.2.4"
#define SLIC3R_VERSION "1.2.5-dev"
#define EPSILON 1e-4
#define SCALING_FACTOR 0.000001

View File

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

View File

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

View File

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

View File

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