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