From 3e739b87da9f49b61a6dfc9aa99a06ba28ddfe39 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 2 Jul 2015 20:24:16 +0200 Subject: [PATCH] Finished porting Slic3r::GCode to XS (speed boost!) --- lib/Slic3r.pm | 2 - lib/Slic3r/GCode.pm | 143 --------------------- lib/Slic3r/GCode/PressureRegulator.pm | 2 +- lib/Slic3r/Print/GCode.pm | 8 +- xs/src/libslic3r/GCode.cpp | 171 +++++++++++++++++++++++++- xs/src/libslic3r/GCode.hpp | 8 +- xs/src/libslic3r/Layer.cpp | 6 + xs/src/libslic3r/Layer.hpp | 1 + xs/src/libslic3r/libslic3r.h | 2 + xs/xsp/GCode.xsp | 29 +++-- xs/xsp/Layer.xsp | 3 + 11 files changed, 208 insertions(+), 167 deletions(-) delete mode 100644 lib/Slic3r/GCode.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 54d1e524f..e052240a1 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -44,7 +44,6 @@ use Slic3r::Flow; use Slic3r::Format::AMF; use Slic3r::Format::OBJ; use Slic3r::Format::STL; -use Slic3r::GCode; use Slic3r::GCode::ArcFitting; use Slic3r::GCode::CoolingBuffer; use Slic3r::GCode::MotionPlanner; @@ -77,7 +76,6 @@ use Unicode::Normalize; use constant SCALING_FACTOR => 0.000001; use constant RESOLUTION => 0.0125; use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR; -use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI; use constant LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER => 0.15; use constant INFILL_OVERLAP_OVER_SPACING => 0.3; use constant EXTERNAL_INFILL_MARGIN => 3; diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm deleted file mode 100644 index 9e344fbd6..000000000 --- a/lib/Slic3r/GCode.pm +++ /dev/null @@ -1,143 +0,0 @@ -package Slic3r::GCode; -use strict; -use warnings; - -use List::Util qw(min max first); -use Slic3r::ExtrusionLoop ':roles'; -use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(epsilon scale unscale PI X Y B); -use Slic3r::Geometry::Clipper qw(union_ex); - -sub extrude { - my $self = shift; - - $_[0]->isa('Slic3r::ExtrusionLoop') - ? $self->extrude_loop(@_) - : $self->extrude_path(@_); -} - -sub extrude_loop { - my ($self, $loop, $description, $speed) = @_; - - # make a copy; don't modify the orientation of the original loop object otherwise - # next copies (if any) would not detect the correct orientation - $loop = $loop->clone; - - # extrude all loops ccw - my $was_clockwise = $loop->make_counter_clockwise; - - # find the point of the loop that is closest to the current extruder position - # or randomize if requested - my $last_pos = $self->last_pos; - if ($self->config->spiral_vase) { - $loop->split_at($last_pos); - } elsif ($self->config->seam_position eq 'nearest' || $self->config->seam_position eq 'aligned') { - # simplify polygon in order to skip false positives in concave/convex detection - # ($loop is always ccw as $polygon->simplify only works on ccw polygons) - my $polygon = $loop->polygon; - my @simplified = @{$polygon->simplify(scale $self->config->get_at('nozzle_diameter', $self->writer->extruder->id)/2)}; - - # restore original winding order so that concave and convex detection always happens - # on the right/outer side of the polygon - if ($was_clockwise) { - $_->reverse for @simplified; - } - - # concave vertices have priority - my @candidates = map @{$_->concave_points(PI*4/3)}, @simplified; - - # if no concave points were found, look for convex vertices - @candidates = map @{$_->convex_points(PI*2/3)}, @simplified if !@candidates; - - # retrieve the last start position for this object - if ($self->has_layer) { - if ($self->_has_seam_position($self->layer->object)) { - $last_pos = $self->_seam_position($self->layer->object); - } - } - - my $point; - if ($self->config->seam_position eq 'nearest') { - @candidates = @$polygon if !@candidates; - $point = $last_pos->nearest_point(\@candidates); - if (!$loop->split_at_vertex($point)) { - # On 32-bit Linux, Clipper will change some point coordinates by 1 unit - # while performing simplify_polygons(), thus split_at_vertex() won't - # find them anymore. - $loop->split_at($point); - } - } elsif (@candidates) { - my @non_overhang = grep !$loop->has_overhang_point($_), @candidates; - @candidates = @non_overhang if @non_overhang; - $point = $last_pos->nearest_point(\@candidates); - if (!$loop->split_at_vertex($point)) { - $loop->split_at($point); - } - } else { - $point = $last_pos->projection_onto_polygon($polygon); - $loop->split_at($point); - } - $self->_set_seam_position($self->layer->object, $point) - if $self->has_layer; - } elsif ($self->config->seam_position eq 'random') { - if ($loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) { - my $polygon = $loop->polygon; - my $centroid = $polygon->centroid; - $last_pos = Slic3r::Point->new($polygon->bounding_box->x_max, $centroid->y); #)) - $last_pos->rotate(rand(2*PI), $centroid); - } - $loop->split_at($last_pos); - } - - # clip the path to avoid the extruder to get exactly on the first point of the loop; - # if polyline was shorter than the clipping distance we'd get a null polyline, so - # we discard it in that case - my $clip_length = $self->enable_loop_clipping - ? scale($self->config->get_at('nozzle_diameter', $self->writer->extruder->id)) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER - : 0; - - # get paths - my @paths = @{$loop->clip_end($clip_length)}; - return '' if !@paths; - - # apply the small perimeter speed - if ($paths[0]->is_perimeter && $loop->length <= &Slic3r::SMALL_PERIMETER_LENGTH) { - $speed //= $self->config->get_abs_value('small_perimeter_speed'); - } - - # extrude along the path - my $gcode = join '', map $self->_extrude_path($_, $description // "", $speed // -1), @paths; - - # reset acceleration - $gcode .= $self->writer->set_acceleration($self->config->default_acceleration); - - $self->wipe->set_path($paths[0]->polyline->clone) if $self->wipe->enable; # TODO: don't limit wipe to last path - - # make a little move inwards before leaving loop - if ($paths[-1]->role == EXTR_ROLE_EXTERNAL_PERIMETER && $self->has_layer && $self->config->perimeters > 1) { - 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 = ($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 - # we make sure we don't exceed the segment length because we don't know - # the rotation of the second segment so we might cross the object boundary - my $first_segment = Slic3r::Line->new(@{$paths[0]->polyline}[0,1]); - my $distance = min(scale($self->config->get_at('nozzle_diameter', $self->writer->extruder->id)), $first_segment->length); - my $point = $first_segment->point_at($distance); - $point->rotate($angle, $first_segment->a); - - # generate the travel move - $gcode .= $self->writer->travel_to_xy($self->point_to_gcode($point), "move inwards before travel"); - } - - return $gcode; -} - -1; diff --git a/lib/Slic3r/GCode/PressureRegulator.pm b/lib/Slic3r/GCode/PressureRegulator.pm index 8d1cd6525..6074b9486 100644 --- a/lib/Slic3r/GCode/PressureRegulator.pm +++ b/lib/Slic3r/GCode/PressureRegulator.pm @@ -43,7 +43,7 @@ sub process { my $rel_flow_rate = $info->{dist_E} / $info->{dist_XY}; # Then calculate absolute flow rate (mm/sec of feedstock) - my $flow_rate = $rel_flow_rate * $args->{F} / 60; + my $flow_rate = $rel_flow_rate * $F / 60; # And finally calculate advance by using the user-configured K factor. my $new_advance = $self->config->pressure_advance * ($flow_rate**2); diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index c5d0f5fac..db900d6fc 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -384,7 +384,7 @@ sub process_layer { $pp->set('layer_z' => $layer->print_z); $gcode .= $pp->process($self->print->config->before_layer_gcode) . "\n"; } - $gcode .= $self->_gcodegen->change_layer($layer); # this will increase $self->_gcodegen->layer_index + $gcode .= $self->_gcodegen->change_layer($layer->as_layer); # this will increase $self->_gcodegen->layer_index if ($self->print->config->layer_gcode) { my $pp = $self->_gcodegen->placeholder_parser->clone; $pp->set('layer_num' => $self->_gcodegen->layer_index); @@ -592,7 +592,7 @@ sub _extrude_perimeters { my $gcode = ""; foreach my $region_id (sort keys %$entities_by_region) { $self->_gcodegen->config->apply_region_config($self->print->get_region($region_id)->config); - $gcode .= $self->_gcodegen->extrude($_, 'perimeter') + $gcode .= $self->_gcodegen->extrude($_, 'perimeter', -1) for @{ $entities_by_region->{$region_id} }; } return $gcode; @@ -608,10 +608,10 @@ sub _extrude_infill { my $collection = Slic3r::ExtrusionPath::Collection->new(@{ $entities_by_region->{$region_id} }); for my $fill (@{$collection->chained_path_from($self->_gcodegen->last_pos, 0)}) { if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { - $gcode .= $self->_gcodegen->extrude($_, 'infill') + $gcode .= $self->_gcodegen->extrude($_, 'infill', -1) for @{$fill->chained_path_from($self->_gcodegen->last_pos, 0)}; } else { - $gcode .= $self->_gcodegen->extrude($fill, 'infill') ; + $gcode .= $self->_gcodegen->extrude($fill, 'infill', -1) ; } } } diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index f153b96f7..51093667a 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -1,6 +1,7 @@ #include "GCode.hpp" #include "ExtrusionEntity.hpp" #include +#include namespace Slic3r { @@ -320,9 +321,173 @@ GCode::change_layer(const Layer &layer) } std::string -GCode::extrude_path(const ExtrusionPath &path, std::string description, double speed) +GCode::extrude(ExtrusionLoop loop, std::string description, double speed) { - std::string gcode = this->_extrude_path(path, description, speed); + // get a copy; don't modify the orientation of the original loop object otherwise + // next copies (if any) would not detect the correct orientation + + // extrude all loops ccw + bool was_clockwise = loop.make_counter_clockwise(); + + // find the point of the loop that is closest to the current extruder position + // or randomize if requested + Point last_pos = this->last_pos(); + if (this->config.spiral_vase) { + loop.split_at(last_pos); + } else if (this->config.seam_position == spNearest || this->config.seam_position == spAligned) { + Polygon polygon = loop.polygon(); + + // simplify polygon in order to skip false positives in concave/convex detection + // (loop is always ccw as polygon.simplify() only works on ccw polygons) + Polygons simplified = polygon.simplify(scale_(EXTRUDER_CONFIG(nozzle_diameter))/2); + + // restore original winding order so that concave and convex detection always happens + // on the right/outer side of the polygon + if (was_clockwise) { + for (Polygons::iterator p = simplified.begin(); p != simplified.end(); ++p) + p->reverse(); + } + + // concave vertices have priority + Points candidates; + for (Polygons::const_iterator p = simplified.begin(); p != simplified.end(); ++p) { + Points concave = p->concave_points(PI*4/3); + candidates.insert(candidates.end(), concave.begin(), concave.end()); + } + + // if no concave points were found, look for convex vertices + if (candidates.empty()) { + for (Polygons::const_iterator p = simplified.begin(); p != simplified.end(); ++p) { + Points convex = p->convex_points(PI*2/3); + candidates.insert(candidates.end(), convex.begin(), convex.end()); + } + } + + // retrieve the last start position for this object + if (this->layer != NULL && this->_seam_position.count(this->layer->object()) > 0) { + last_pos = this->_seam_position[this->layer->object()]; + } + + Point point; + if (this->config.seam_position == spNearest) { + if (candidates.empty()) candidates = polygon.points; + last_pos.nearest_point(candidates, &point); + + // On 32-bit Linux, Clipper will change some point coordinates by 1 unit + // while performing simplify_polygons(), thus split_at_vertex() won't + // find them anymore. + if (!loop.split_at_vertex(point)) loop.split_at(point); + } else if (!candidates.empty()) { + Points non_overhang; + for (Points::const_iterator p = candidates.begin(); p != candidates.end(); ++p) { + if (!loop.has_overhang_point(*p)) + non_overhang.push_back(*p); + } + + if (!non_overhang.empty()) + candidates = non_overhang; + + last_pos.nearest_point(candidates, &point); + if (!loop.split_at_vertex(point)) loop.split_at(point); // see note above + } else { + point = last_pos.projection_onto(polygon); + loop.split_at(point); + } + if (this->layer != NULL) + this->_seam_position[this->layer->object()] = point; + } else if (this->config.seam_position == spRandom) { + if (loop.role == elrContourInternalPerimeter) { + Polygon polygon = loop.polygon(); + Point centroid = polygon.centroid(); + last_pos = Point(polygon.bounding_box().max.x, centroid.y); + last_pos.rotate(rand() % 2*PI, centroid); + } + loop.split_at(last_pos); + } + + // clip the path to avoid the extruder to get exactly on the first point of the loop; + // if polyline was shorter than the clipping distance we'd get a null polyline, so + // we discard it in that case + double clip_length = this->enable_loop_clipping + ? scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER + : 0; + + // get paths + ExtrusionPaths paths; + loop.clip_end(clip_length, &paths); + if (paths.empty()) return ""; + + // apply the small perimeter speed + if (paths.front().is_perimeter() && loop.length() <= SMALL_PERIMETER_LENGTH) { + if (speed == -1) speed = this->config.get_abs_value("small_perimeter_speed"); + } + + // extrude along the path + std::string gcode; + for (ExtrusionPaths::const_iterator path = paths.begin(); path != paths.end(); ++path) + gcode += this->_extrude(*path, description, speed); + + // reset acceleration + gcode += this->writer.set_acceleration(this->config.default_acceleration.value); + + if (this->wipe.enable) + this->wipe.path = paths.front().polyline; // TODO: don't limit wipe to last path + + // make a little move inwards before leaving loop + if (paths.back().role == erExternalPerimeter && this->layer != NULL && this->config.perimeters > 1) { + Polyline &last_path_polyline = paths.back().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) + Point a = paths.front().polyline.points[1]; // second point + Point b = *(paths.back().polyline.points.end()-3); // second to last point + if (was_clockwise) { + // swap points + Point c = a; a = b; b = c; + } + + double angle = paths.front().first_point().ccw_angle(a, b) / 3; + + // turn left if contour, turn right if hole + if (was_clockwise) angle *= -1; + + // create the destination point along the first segment and rotate it + // we make sure we don't exceed the segment length because we don't know + // the rotation of the second segment so we might cross the object boundary + Line first_segment( + paths.front().polyline.points[0], + paths.front().polyline.points[1] + ); + double distance = std::min( + scale_(EXTRUDER_CONFIG(nozzle_diameter)), + first_segment.length() + ); + Point point = first_segment.point_at(distance); + point.rotate(angle, first_segment.a); + + // generate the travel move + gcode += this->writer.travel_to_xy(this->point_to_gcode(point), "move inwards before travel"); + } + + return gcode; +} + +std::string +GCode::extrude(const ExtrusionEntity &entity, std::string description, double speed) +{ + if (const ExtrusionPath* path = dynamic_cast(&entity)) { + return this->extrude(*path, description, speed); + } else if (const ExtrusionLoop* loop = dynamic_cast(&entity)) { + return this->extrude(*loop, description, speed); + } else { + CONFESS("Invalid argument supplied to extrude()"); + return ""; + } +} + +std::string +GCode::extrude(const ExtrusionPath &path, std::string description, double speed) +{ + std::string gcode = this->_extrude(path, description, speed); // reset acceleration gcode += this->writer.set_acceleration(this->config.default_acceleration.value); @@ -331,7 +496,7 @@ GCode::extrude_path(const ExtrusionPath &path, std::string description, double s } std::string -GCode::_extrude_path(ExtrusionPath path, std::string description, double speed) +GCode::_extrude(ExtrusionPath path, std::string description, double speed) { path.simplify(SCALED_RESOLUTION); diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index de391e491..f477c779e 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -80,7 +80,7 @@ class GCode { size_t layer_count; int layer_index; // just a counter const Layer* layer; - std::map _seam_position; + std::map _seam_position; bool first_layer; // this flag triggers first layer speeds unsigned int elapsed_time; // seconds double volumetric_speed; @@ -94,8 +94,9 @@ class GCode { void set_origin(const Pointf &pointf); std::string preamble(); std::string change_layer(const Layer &layer); - std::string extrude_path(const ExtrusionPath &path, std::string description = "", double speed = -1); - std::string _extrude_path(ExtrusionPath path, std::string description = "", double speed = -1); + std::string extrude(const ExtrusionEntity &entity, std::string description = "", double speed = -1); + std::string extrude(ExtrusionLoop loop, std::string description = "", double speed = -1); + std::string extrude(const ExtrusionPath &path, std::string description = "", double speed = -1); std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); bool needs_retraction(const Polyline &travel, ExtrusionRole role = erNone); std::string retract(bool toolchange = false); @@ -106,6 +107,7 @@ class GCode { private: Point _last_pos; bool _last_pos_defined; + std::string _extrude(ExtrusionPath path, std::string description = "", double speed = -1); }; } diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp index 250572fb7..4d4c28805 100644 --- a/xs/src/libslic3r/Layer.cpp +++ b/xs/src/libslic3r/Layer.cpp @@ -53,6 +53,12 @@ Layer::object() return this->_object; } +const PrintObject* +Layer::object() const +{ + return this->_object; +} + size_t Layer::region_count() diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index d34abec36..85f4844cb 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -76,6 +76,7 @@ class Layer { size_t id() const; void set_id(size_t id); PrintObject* object(); + const PrintObject* object() const; Layer *upper_layer; Layer *lower_layer; diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 470dbe015..fcf90f133 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -13,6 +13,8 @@ #define RESOLUTION 0.0125 #define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR) #define PI 3.141592653589793238 +#define LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER 0.15 +#define SMALL_PERIMETER_LENGTH (6.5 / SCALING_FACTOR) * 2 * PI #define scale_(val) (val / SCALING_FACTOR) #define unscale(val) (val * SCALING_FACTOR) #define SCALED_EPSILON scale_(EPSILON) diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 63cccfc22..4ab816bb8 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -124,13 +124,6 @@ void set_layer(Layer* ptr) %code{% THIS->layer = ptr; %}; - bool _has_seam_position(PrintObject* ptr) - %code{% RETVAL = THIS->_seam_position.count(ptr) > 0; %}; - Clone _seam_position(PrintObject* ptr) - %code{% RETVAL = THIS->_seam_position[ptr]; %}; - void _set_seam_position(PrintObject* ptr, Point* pos) - %code{% THIS->_seam_position[ptr] = *pos; %}; - bool first_layer() %code{% RETVAL = THIS->first_layer; %}; void set_first_layer(bool value) @@ -160,10 +153,10 @@ std::string preamble(); std::string change_layer(Layer* layer) %code{% RETVAL = THIS->change_layer(*layer); %}; - std::string extrude_path(ExtrusionPath* path, std::string description = "", double speed = -1) - %code{% RETVAL = THIS->extrude_path(*path, description, speed); %}; - std::string _extrude_path(ExtrusionPath* path, std::string description = "", double speed = -1) - %code{% RETVAL = THIS->_extrude_path(*path, description, speed); %}; + %name{extrude_loop} std::string extrude(ExtrusionLoop* loop, std::string description = "", double speed = -1) + %code{% RETVAL = THIS->extrude(*loop, description, speed); %}; + %name{extrude_path} std::string extrude(ExtrusionPath* path, std::string description = "", double speed = -1) + %code{% RETVAL = THIS->extrude(*path, description, speed); %}; std::string travel_to(Point* point, ExtrusionRole role, std::string comment) %code{% RETVAL = THIS->travel_to(*point, role, comment); %}; bool needs_retraction(Polyline* travel, ExtrusionRole role = erNone) @@ -173,4 +166,18 @@ std::string set_extruder(unsigned int extruder_id); Clone point_to_gcode(Point* point) %code{% RETVAL = THIS->point_to_gcode(*point); %}; + +%{ +std::string +GCode::extrude(entity, description, speed) + SV* entity + std::string description; + double speed; + CODE: + ExtrusionEntity* e = (ExtrusionEntity *)SvIV((SV*)SvRV( entity )); + RETVAL = THIS->extrude(*e, description, speed); + OUTPUT: + RETVAL +%} + }; diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index c7795b453..d8444f092 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -34,6 +34,9 @@ %name{Slic3r::Layer} class Layer { // owned by PrintObject, no constructor/destructor + + Ref as_layer() + %code%{ RETVAL = THIS; %}; int id(); void set_id(int id);