From ad666beac0e0c5301312aaec320d614d942ca318 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 19 Mar 2017 01:40:18 +0100 Subject: [PATCH] Ported GCodeReader and SpiralVase to C++ --- lib/Slic3r.pm | 1 - lib/Slic3r/GCode/SpiralVase.pm | 88 ------------------------- lib/Slic3r/Print/GCode.pm | 4 +- src/CMakeLists.txt | 2 + xs/MANIFEST | 2 + xs/src/libslic3r/GCode/SpiralVase.cpp | 95 +++++++++++++++++++++++++++ xs/src/libslic3r/GCode/SpiralVase.hpp | 29 ++++++++ xs/src/libslic3r/GCodeReader.cpp | 91 +++++++++++++++++++++++++ xs/src/libslic3r/GCodeReader.hpp | 64 ++++++++++++++++++ xs/src/perlglue.cpp | 1 + xs/xsp/GCode.xsp | 13 ++++ xs/xsp/my.map | 4 ++ 12 files changed, 303 insertions(+), 91 deletions(-) delete mode 100644 lib/Slic3r/GCode/SpiralVase.pm create mode 100644 xs/src/libslic3r/GCode/SpiralVase.cpp create mode 100644 xs/src/libslic3r/GCode/SpiralVase.hpp create mode 100644 xs/src/libslic3r/GCodeReader.cpp create mode 100644 xs/src/libslic3r/GCodeReader.hpp diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 294b623ec..ca09d71f9 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -56,7 +56,6 @@ use Slic3r::GCode::ArcFitting; use Slic3r::GCode::MotionPlanner; use Slic3r::GCode::PressureRegulator; use Slic3r::GCode::Reader; -use Slic3r::GCode::SpiralVase; use Slic3r::GCode::VibrationLimit; use Slic3r::Geometry qw(PI); use Slic3r::Geometry::Clipper; diff --git a/lib/Slic3r/GCode/SpiralVase.pm b/lib/Slic3r/GCode/SpiralVase.pm deleted file mode 100644 index d35e29e71..000000000 --- a/lib/Slic3r/GCode/SpiralVase.pm +++ /dev/null @@ -1,88 +0,0 @@ -package Slic3r::GCode::SpiralVase; -use Moo; - -has 'config' => (is => 'ro', required => 1); -has 'enable' => (is => 'rw', default => sub { 0 }); -has 'reader' => (is => 'ro', default => sub { Slic3r::GCode::Reader->new }); - -use Slic3r::Geometry qw(unscale); - -sub BUILD { - my ($self) = @_; - - $self->reader->Z($self->config->z_offset); - $self->reader->apply_print_config($self->config); -} - -sub process_layer { - my $self = shift; - my ($gcode) = @_; - - # This post-processor relies on several assumptions: - # - all layers are processed through it, including those that are not supposed - # to be transformed, in order to update the reader with the XY positions - # - each call to this method includes a full layer, with a single Z move - # at the beginning - # - each layer is composed by suitable geometry (i.e. a single complete loop) - # - loops were not clipped before calling this method - - # if we're not going to modify G-code, just feed it to the reader - # in order to update positions - if (!$self->enable) { - $self->reader->parse($gcode, sub {}); - return $gcode; - } - - # get total XY length for this layer by summing all extrusion moves - my $total_layer_length = 0; - my $layer_height = 0; - my $z = undef; - $self->reader->clone->parse($gcode, sub { - my ($reader, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1') { - if ($info->{extruding}) { - $total_layer_length += $info->{dist_XY}; - } elsif (exists $args->{Z}) { - $layer_height += $info->{dist_Z}; - $z //= $args->{Z}; - } - } - }); - - #use XXX; XXX [ $gcode, $layer_height, $z, $total_layer_length ]; - # remove layer height from initial Z - $z -= $layer_height; - - my $new_gcode = ""; - $self->reader->parse($gcode, sub { - my ($reader, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1' && exists $args->{Z}) { - # if this is the initial Z move of the layer, replace it with a - # (redundant) move to the last Z of previous layer - my $line = $info->{raw}; - $line =~ s/ Z[.0-9]+/ Z$z/; - $new_gcode .= "$line\n"; - } elsif ($cmd eq 'G1' && !exists($args->{Z}) && $info->{dist_XY}) { - # horizontal move - my $line = $info->{raw}; - if ($info->{extruding}) { - $z += $info->{dist_XY} * $layer_height / $total_layer_length; - $line =~ s/^G1 /sprintf 'G1 Z%.3f ', $z/e; - $new_gcode .= "$line\n"; - } - # skip travel moves: the move to first perimeter point will - # cause a visible seam when loops are not aligned in XY; by skipping - # it we blend the first loop move in the XY plane (although the smoothness - # of such blend depend on how long the first segment is; maybe we should - # enforce some minimum length?) - } else { - $new_gcode .= "$info->{raw}\n"; - } - }); - - return $new_gcode; -} - -1; diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index 261769716..9a653e89d 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -48,7 +48,7 @@ sub BUILD { $self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new($self->_gcodegen)); - $self->_spiral_vase(Slic3r::GCode::SpiralVase->new(config => $self->config)) + $self->_spiral_vase(Slic3r::GCode::SpiralVase->new($self->config)) if $self->config->spiral_vase; $self->_vibration_limit(Slic3r::GCode::VibrationLimit->new(config => $self->config)) @@ -323,7 +323,7 @@ sub process_layer { # check whether we're going to apply spiralvase logic if (defined $self->_spiral_vase) { - $self->_spiral_vase->enable( + $self->_spiral_vase->set_enable( $layer->id > 0 && ($self->print->config->skirts == 0 || ($layer->id >= $self->print->config->skirt_height && !$self->print->has_infinite_skirt)) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f3d4f4629..85e98471e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -55,6 +55,8 @@ add_library(libslic3r STATIC ${LIBDIR}/libslic3r/Flow.cpp ${LIBDIR}/libslic3r/GCode.cpp ${LIBDIR}/libslic3r/GCode/CoolingBuffer.cpp + ${LIBDIR}/libslic3r/GCode/SpiralVase.cpp + ${LIBDIR}/libslic3r/GCodeReader.cpp ${LIBDIR}/libslic3r/GCodeSender.cpp ${LIBDIR}/libslic3r/GCodeWriter.cpp ${LIBDIR}/libslic3r/Geometry.cpp diff --git a/xs/MANIFEST b/xs/MANIFEST index 3b89458d3..a2ffbba5a 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -66,6 +66,8 @@ src/libslic3r/GCode.cpp src/libslic3r/GCode.hpp src/libslic3r/GCode/CoolingBuffer.cpp src/libslic3r/GCode/CoolingBuffer.hpp +src/libslic3r/GCode/SpiralVase.cpp +src/libslic3r/GCode/SpiralVase.hpp src/libslic3r/GCodeSender.cpp src/libslic3r/GCodeSender.hpp src/libslic3r/GCodeWriter.cpp diff --git a/xs/src/libslic3r/GCode/SpiralVase.cpp b/xs/src/libslic3r/GCode/SpiralVase.cpp new file mode 100644 index 000000000..b56c9e58b --- /dev/null +++ b/xs/src/libslic3r/GCode/SpiralVase.cpp @@ -0,0 +1,95 @@ +#include "SpiralVase.hpp" +#include + +namespace Slic3r { + +std::string +_format_z(float z) +{ + std::ostringstream ss; + ss << std::fixed << std::setprecision(3) + << z; + return ss.str(); +} + +std::string +SpiralVase::process_layer(const std::string &gcode) +{ + /* This post-processor relies on several assumptions: + - all layers are processed through it, including those that are not supposed + to be transformed, in order to update the reader with the XY positions + - each call to this method includes a full layer, with a single Z move + at the beginning + - each layer is composed by suitable geometry (i.e. a single complete loop) + - loops were not clipped before calling this method */ + + // If we're not going to modify G-code, just feed it to the reader + // in order to update positions. + if (!this->enable) { + this->_reader.parse(gcode, {}); + return gcode; + } + + // Get total XY length for this layer by summing all extrusion moves. + float total_layer_length = 0; + float layer_height = 0; + float z; + bool set_z = false; + + { + GCodeReader r = this->_reader; // clone + r.parse(gcode, [&total_layer_length, &layer_height, &z, &set_z] + (GCodeReader &, GCodeReader::GCodeLine &line) { + if (line.cmd == "G1") { + if (line.extruding()) { + total_layer_length += line.dist_XY(); + } else if (line.has('Z')) { + layer_height += line.dist_Z(); + if (!set_z) { + z = line.new_Z(); + set_z = true; + } + } + } + }); + } + + // Remove layer height from initial Z. + z -= layer_height; + + std::string new_gcode; + this->_reader.parse(gcode, [&new_gcode, &z, &layer_height, &total_layer_length] + (GCodeReader &, GCodeReader::GCodeLine line) { + if (line.cmd == "G1") { + if (line.has('Z')) { + // If this is the initial Z move of the layer, replace it with a + // (redundant) move to the last Z of previous layer. + line.set('Z', _format_z(z)); + new_gcode += line.raw + '\n'; + return; + } else { + float dist_XY = line.dist_XY(); + if (dist_XY > 0) { + // horizontal move + if (line.extruding()) { + z += dist_XY * layer_height / total_layer_length; + line.set('Z', _format_z(z)); + new_gcode += line.raw + '\n'; + } + return; + + /* Skip travel moves: the move to first perimeter point will + cause a visible seam when loops are not aligned in XY; by skipping + it we blend the first loop move in the XY plane (although the smoothness + of such blend depend on how long the first segment is; maybe we should + enforce some minimum length?). */ + } + } + } + new_gcode += line.raw + '\n'; + }); + + return new_gcode; +} + +} diff --git a/xs/src/libslic3r/GCode/SpiralVase.hpp b/xs/src/libslic3r/GCode/SpiralVase.hpp new file mode 100644 index 000000000..f14d15879 --- /dev/null +++ b/xs/src/libslic3r/GCode/SpiralVase.hpp @@ -0,0 +1,29 @@ +#ifndef slic3r_SpiralVase_hpp_ +#define slic3r_SpiralVase_hpp_ + +#include "libslic3r.h" +#include "GCode.hpp" +#include "GCodeReader.hpp" + +namespace Slic3r { + +class SpiralVase { + public: + bool enable; + + SpiralVase(const PrintConfig &config) + : enable(false), _config(&config) + { + this->_reader.Z = this->_config->z_offset; + this->_reader.apply_config(*this->_config); + }; + std::string process_layer(const std::string &gcode); + + private: + const PrintConfig* _config; + GCodeReader _reader; +}; + +} + +#endif diff --git a/xs/src/libslic3r/GCodeReader.cpp b/xs/src/libslic3r/GCodeReader.cpp new file mode 100644 index 000000000..fcaaea9be --- /dev/null +++ b/xs/src/libslic3r/GCodeReader.cpp @@ -0,0 +1,91 @@ +#include "GCodeReader.hpp" +#include +#include +#include + +namespace Slic3r { + +void +GCodeReader::apply_config(const PrintConfigBase &config) +{ + this->_config.apply(config, true); + this->_extrusion_axis = this->_config.get_extrusion_axis()[0]; +} + +void +GCodeReader::parse(const std::string &gcode, callback_t callback) +{ + std::istringstream ss(gcode); + std::string line; + while (std::getline(ss, line)) { + GCodeLine gline(this); + gline.raw = line; + if (this->verbose) + std::cout << line << std::endl; + + // strip comment + { + size_t pos = line.find(';'); + if (pos != std::string::npos) { + gline.comment = line.substr(pos+1); + line.erase(pos); + } + } + + // command and args + { + std::vector args; + boost::split(args, line, boost::is_any_of(" ")); + + // first one is cmd + gline.cmd = args.front(); + args.erase(args.begin()); + + for (std::string &arg : args) { + if (arg.size() < 2) continue; + gline.args.insert(std::make_pair(arg[0], arg.substr(1))); + } + } + + // convert extrusion axis + if (this->_extrusion_axis != 'E') { + const auto it = gline.args.find(this->_extrusion_axis); + if (it != gline.args.end()) { + std::swap(gline.args['E'], it->second); + gline.args.erase(it); + } + } + + if (callback) callback(*this, gline); + + // update coordinates + if (gline.cmd == "G0" || gline.cmd == "G1" || gline.cmd == "G92") { + this->X = gline.new_X(); + this->Y = gline.new_Y(); + this->Z = gline.new_Z(); + this->E = gline.new_E(); + this->F = gline.new_F(); + } + } +} + +void +GCodeReader::GCodeLine::set(char arg, std::string value) +{ + const std::string space(" "); + if (this->has(arg)) { + size_t pos = this->raw.find(space + arg)+2; + size_t end = this->raw.find(' ', pos+1); + this->raw = this->raw.replace(pos, end-pos, value); + } else { + size_t pos = this->raw.find(' '); + if (pos == std::string::npos) { + this->raw += space + arg + value; + } else { + this->raw = this->raw.replace(pos, 0, space + arg + value); + } + } + this->args[arg] = value; +} + +} diff --git a/xs/src/libslic3r/GCodeReader.hpp b/xs/src/libslic3r/GCodeReader.hpp new file mode 100644 index 000000000..267ac0e18 --- /dev/null +++ b/xs/src/libslic3r/GCodeReader.hpp @@ -0,0 +1,64 @@ +#ifndef slic3r_GCodeReader_hpp_ +#define slic3r_GCodeReader_hpp_ + +#include "libslic3r.h" +#include +#include +#include +#include +#include "PrintConfig.hpp" + +namespace Slic3r { + +class GCodeReader; +class GCodeReader { + public: + + class GCodeLine { + public: + GCodeReader* reader; + std::string raw; + std::string cmd; + std::string comment; + std::map args; + + GCodeLine(GCodeReader* _reader) : reader(_reader) {}; + + bool has(char arg) const { return this->args.count(arg) > 0; }; + float new_X() const { return this->has('X') ? atof(this->args.at('X').c_str()) : this->reader->X; }; + float new_Y() const { return this->has('Y') ? atof(this->args.at('Y').c_str()) : this->reader->Y; }; + float new_Z() const { return this->has('Z') ? atof(this->args.at('Z').c_str()) : this->reader->Z; }; + float new_E() const { return this->has('E') ? atof(this->args.at('E').c_str()) : this->reader->E; }; + float new_F() const { return this->has('F') ? atof(this->args.at('F').c_str()) : this->reader->F; }; + float dist_X() const { return this->new_X() - this->reader->X; }; + float dist_Y() const { return this->new_Y() - this->reader->Y; }; + float dist_Z() const { return this->new_Z() - this->reader->Z; }; + float dist_E() const { return this->new_E() - this->reader->E; }; + float dist_XY() const { + float x = this->dist_X(); + float y = this->dist_Y(); + return sqrt(x*x + y*y); + }; + bool extruding() const { return this->cmd == "G1" && this->dist_E() > 0; }; + bool retracting() const { return this->cmd == "G1" && this->dist_E() < 0; }; + bool travel() const { return this->cmd == "G1" && !this->has('E'); }; + void set(char arg, std::string value); + }; + typedef std::function callback_t; + + float X, Y, Z, E, F; + bool verbose; + callback_t callback; + + GCodeReader() : X(0), Y(0), Z(0), E(0), F(0), verbose(false), _extrusion_axis('E') {}; + void apply_config(const PrintConfigBase &config); + void parse(const std::string &gcode, callback_t callback); + + private: + GCodeConfig _config; + char _extrusion_axis; +}; + +} /* namespace Slic3r */ + +#endif /* slic3r_GCodeReader_hpp_ */ diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 3c08d6144..9ce012a36 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -15,6 +15,7 @@ REGISTER_CLASS(Filler, "Filler"); REGISTER_CLASS(AvoidCrossingPerimeters, "GCode::AvoidCrossingPerimeters"); REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer"); REGISTER_CLASS(OozePrevention, "GCode::OozePrevention"); +REGISTER_CLASS(SpiralVase, "GCode::SpiralVase"); REGISTER_CLASS(Wipe, "GCode::Wipe"); REGISTER_CLASS(GCode, "GCode"); REGISTER_CLASS(GCodeSender, "GCode::Sender"); diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 579951c73..f73ca4442 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -4,6 +4,7 @@ #include #include "libslic3r/GCode.hpp" #include "libslic3r/GCode/CoolingBuffer.hpp" +#include "libslic3r/GCode/SpiralVase.hpp" %} %name{Slic3r::GCode::AvoidCrossingPerimeters} class AvoidCrossingPerimeters { @@ -81,6 +82,18 @@ std::string flush(); }; +%name{Slic3r::GCode::SpiralVase} class SpiralVase { + SpiralVase(StaticPrintConfig* config) + %code{% RETVAL = new SpiralVase(*dynamic_cast(config)); %}; + ~SpiralVase(); + + bool enable() + %code{% RETVAL = THIS->enable; %}; + void set_enable(bool enable) + %code{% THIS->enable = enable; %}; + std::string process_layer(std::string gcode); +}; + %name{Slic3r::GCode} class GCode { GCode(); ~GCode(); diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 1ce5d719f..c47d79e00 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -198,6 +198,10 @@ CoolingBuffer* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +SpiralVase* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + GCode* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T