From f518e0675c5672215c9543dae36a068f7c56b4c4 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 12 Sep 2016 16:25:15 +0200 Subject: [PATCH] Initial commit of the new Pressure Equalizer, the EdgeGrid signed distance field structure. The EdgeGrid is used to avoid placing the seams on overhangs. --- lib/Slic3r/Print/GCode.pm | 18 +- slic3r.sublime-project | 26 +- xs/Build.PL | 16 +- xs/MANIFEST | 3 + xs/lib/Slic3r/XS.pm | 13 + xs/src/libslic3r/EdgeGrid.cpp | 1025 ++++++++++++++++++ xs/src/libslic3r/EdgeGrid.hpp | 81 ++ xs/src/libslic3r/GCode.cpp | 429 +++++++- xs/src/libslic3r/GCode.hpp | 10 + xs/src/libslic3r/GCode/PressureEqualizer.cpp | 608 +++++++++++ xs/src/libslic3r/GCode/PressureEqualizer.hpp | 209 ++++ xs/src/libslic3r/PerimeterGenerator.cpp | 4 +- xs/src/perlglue.cpp | 1 + xs/xsp/Filler.xsp | 1 + xs/xsp/GCode.xsp | 5 + xs/xsp/GCodePressureEqualizer.xsp | 32 + xs/xsp/my.map | 4 + xs/xsp/typemap.xspt | 3 + 18 files changed, 2417 insertions(+), 71 deletions(-) create mode 100644 xs/src/libslic3r/EdgeGrid.cpp create mode 100644 xs/src/libslic3r/EdgeGrid.hpp create mode 100644 xs/src/libslic3r/GCode/PressureEqualizer.cpp create mode 100644 xs/src/libslic3r/GCode/PressureEqualizer.hpp create mode 100644 xs/xsp/GCodePressureEqualizer.xsp diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index 02b9d4d74..62e3f3ae1 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -10,6 +10,7 @@ has '_spiral_vase' => (is => 'rw'); has '_vibration_limit' => (is => 'rw'); has '_arc_fitting' => (is => 'rw'); has '_pressure_regulator' => (is => 'rw'); +has '_pressure_equalizer' => (is => 'rw'); has '_skirt_done' => (is => 'rw', default => sub { {} }); # print_z => 1 has '_brim_done' => (is => 'rw'); has '_second_layer_things_done' => (is => 'rw'); @@ -114,6 +115,11 @@ sub BUILD { $self->_pressure_regulator(Slic3r::GCode::PressureRegulator->new(config => $self->config)) if $self->config->pressure_advance > 0; + + $self->_pressure_equalizer(Slic3r::GCode::PressureEqualizer->new($self->config)) + if defined($ENV{"SLIC3R_PRESSURE_EQUALIZER"}) && $ENV{'SLIC3R_PRESSURE_EQUALIZER'} == 1; + + $self->_gcodegen->set_enable_extrusion_role_markers(defined $self->_pressure_equalizer); } # Export a G-code for the complete print. @@ -560,7 +566,7 @@ sub process_layer { } } } - } + } # for regions # tweak extruder ordering to save toolchanges my @extruders = sort keys %by_extruder; @@ -586,7 +592,7 @@ sub process_layer { } } } - } + } # for object copies # apply spiral vase post-processing if this layer contains suitable geometry # (we must feed all the G-code into the post-processor, including the first @@ -658,7 +664,13 @@ sub filter { # this depends on actual speeds $gcode = $self->_pressure_regulator->process($gcode, $flush) if defined $self->_pressure_regulator; - + + # apply pressure equalization if enabled; +# print "G-code before filter:\n", $gcode; + $gcode = $self->_pressure_equalizer->process($gcode, $flush) + if defined $self->_pressure_equalizer; +# print "G-code after filter:\n", $gcode; + # 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) diff --git a/slic3r.sublime-project b/slic3r.sublime-project index 9deb0e273..bd3b41d17 100644 --- a/slic3r.sublime-project +++ b/slic3r.sublime-project @@ -11,30 +11,44 @@ "name": "Run", "working_dir": "$project_path", "file_regex": " at (.*?) line ([0-9]*)", - "shell_cmd": "perl slic3r.pl --gui \"..\\Slic3r-tests\\gap fill torture 20 -rt.stl\"" + "shell_cmd": "chdir & perl slic3r.pl --DataDir \"C:\\Users\\Public\\Documents\\Prusa3D\\Slic3r settings MK2\" --gui \"..\\Slic3r-tests\\gap fill torture 20 -rt.stl\"" }, { "name": "full", "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$", - "shell_cmd": "perl Build.pl" + "shell_cmd": "chdir & perl Build.pl" }, { "name": "xs", "working_dir": "$project_path/xs", - "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$", - "shell_cmd": "perl Build install" + // for Visual Studio: + "file_regex": "^(..[^:]*)\\(([0-9]+)\\)(.*)$", + // For GCC: +// "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$", + "shell_cmd": "chdir & perl Build install", + "env": { +// "PATH": "C:\\Program Files (x86)\\MSBuild\\12.0\\bin\\amd64;C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\BIN\\amd64;C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\Common7\\IDE;C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\Common7\\Tools;%PATH%;c:\\wperl64d\\site\\bin;c:\\wperl64d\\bin", +// "PERL_CPANM_HOME": "c:\\wperl64d\\cpanm", +// "WXDIR": "D:\\src-perl\\wxWidgets-3.0.3-beta1", +// "BOOST_DIR": "D:\\src-perl\\boost_1_61_0", +// "BOOST_INCLUDEDIR": "D:\\src-perl\\boost_1_61_0", +// "BOOST_LIBRARYDIR": "D:\\src-perl\\boost_1_61_0\\stage\\x64\\lib", +// "SLIC3R_STATIC": "1" + } }, { "name": "xs & run", "working_dir": "$project_path/xs", "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$", - "shell_cmd": "perl Build install & cd .. & perl slic3r.pl --gui \"..\\Slic3r-tests\\star3-big2.stl\"" + "shell_cmd": "chdir & perl Build install & cd .. & perl slic3r.pl --gui \"..\\Slic3r-tests\\star3-big2.stl\"" } ], "folders": [ { - "path": "." + "path": ".", +// "folder_exclude_patterns": [".svn", "._d", ".metadata", ".settings"], + "file_exclude_patterns": ["XS.c"] } ], diff --git a/xs/Build.PL b/xs/Build.PL index 5413952bb..7c9465020 100644 --- a/xs/Build.PL +++ b/xs/Build.PL @@ -25,8 +25,22 @@ if ($mswin) { push @cflags, qw(-DNOMINMAX -D_USE_MATH_DEFINES); } +my @early_includes = (); my @INC = qw(-Isrc/libslic3r); my @LIBS = $cpp_guess->is_msvc ? qw(-LIBPATH:src/libslic3r) : qw(-Lsrc/libslic3r); + +#if ($ENV{SLIC3R_GUI}) +{ + print "Slic3r will be built with GUI support\n"; + require Alien::wxWidgets; + Alien::wxWidgets->load; + push @INC, Alien::wxWidgets->include_path; + push @cflags, qw(-DSLIC3R_GUI -DUNICODE), Alien::wxWidgets->defines, Alien::wxWidgets->c_flags; + my $alienwx_libraries = Alien::wxWidgets->libraries(qw(gl html)); + $alienwx_libraries =~ s/-L/-LIBPATH:/g if ($cpp_guess->is_msvc); + push @ldflags, Alien::wxWidgets->link_flags, $alienwx_libraries; +# push @early_includes, qw(slic3r/GUI/wxinit.h); +} # search for Boost in a number of places my @boost_include = (); @@ -195,7 +209,7 @@ my $build = Module::Build::WithXSpp->new( ostream sstream libslic3r/GCodeSender.hpp - )] + ), @early_includes] ); $build->create_build_script; diff --git a/xs/MANIFEST b/xs/MANIFEST index 2fbd9f0ce..9ba08d735 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -52,6 +52,8 @@ src/libslic3r/GCodeSender.cpp src/libslic3r/GCodeSender.hpp src/libslic3r/GCodeWriter.cpp src/libslic3r/GCodeWriter.hpp +src/libslic3r/GCode/PressureEqualizer.cpp +src/libslic3r/GCode/PressureEqualizer.hpp src/libslic3r/Geometry.cpp src/libslic3r/Geometry.hpp src/libslic3r/Layer.cpp @@ -151,6 +153,7 @@ xsp/Flow.xsp xsp/GCode.xsp xsp/GCodeSender.xsp xsp/GCodeWriter.xsp +xsp/GCodePressureEqualizer.xsp xsp/Geometry.xsp xsp/GUI.xsp xsp/GUI_3DScene.xsp diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 44ac57cdb..8fb933181 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -4,6 +4,19 @@ use strict; our $VERSION = '0.01'; +# We have to load these modules in order to have Wx.pm find the correct paths +# for wxWidgets dlls on MSW. +# We avoid loading these on OS X because Wx::Load() initializes a Wx App +# automatically and it steals focus even when we're not running Slic3r in GUI mode. +# TODO: only load these when compiling with GUI support +BEGIN { + if ($^O eq 'MSWin32') { + eval "use Wx"; +# eval "use Wx::Html"; + eval "use Wx::Print"; # because of some Wx bug, thread creation fails if we don't have this (looks like Wx::Printout is hard-coded in some thread cleanup code) + } +} + use Carp qw(); use XSLoader; XSLoader::load(__PACKAGE__, $VERSION); diff --git a/xs/src/libslic3r/EdgeGrid.cpp b/xs/src/libslic3r/EdgeGrid.cpp new file mode 100644 index 000000000..6b3505a29 --- /dev/null +++ b/xs/src/libslic3r/EdgeGrid.cpp @@ -0,0 +1,1025 @@ +#include +#include + +#include + +#include "libslic3r.h" +#include "EdgeGrid.hpp" + +#if 0 +// Enable debugging and assert in this file. +#define DEBUG +#define _DEBUG +#undef NDEBUG +#endif + +#include + +namespace Slic3r { + +EdgeGrid::Grid::Grid() : + m_rows(0), m_cols(0) +{ +} + +EdgeGrid::Grid::~Grid() +{ + m_contours.clear(); + m_cell_data.clear(); + m_cells.clear(); +} + +void EdgeGrid::Grid::create(const Polygons &polygons, coord_t resolution) +{ + // Count the contours. + size_t ncontours = 0; + for (size_t j = 0; j < polygons.size(); ++ j) + if (! polygons[j].points.empty()) + ++ ncontours; + + // Collect the contours. + m_contours.assign(ncontours, NULL); + ncontours = 0; + for (size_t j = 0; j < polygons.size(); ++ j) + if (! polygons[j].points.empty()) + m_contours[ncontours++] = &polygons[j].points; + + create_from_m_contours(resolution); +} + +void EdgeGrid::Grid::create(const ExPolygon &expoly, coord_t resolution) +{ + // Count the contours. + size_t ncontours = 0; + if (! expoly.contour.points.empty()) + ++ ncontours; + for (size_t j = 0; j < expoly.holes.size(); ++ j) + if (! expoly.holes[j].points.empty()) + ++ ncontours; + + // Collect the contours. + m_contours.assign(ncontours, NULL); + ncontours = 0; + if (! expoly.contour.points.empty()) + m_contours[ncontours++] = &expoly.contour.points; + for (size_t j = 0; j < expoly.holes.size(); ++ j) + if (! expoly.holes[j].points.empty()) + m_contours[ncontours++] = &expoly.holes[j].points; + + create_from_m_contours(resolution); +} + +void EdgeGrid::Grid::create(const ExPolygons &expolygons, coord_t resolution) +{ + // Count the contours. + size_t ncontours = 0; + for (size_t i = 0; i < expolygons.size(); ++ i) { + const ExPolygon &expoly = expolygons[i]; + if (! expoly.contour.points.empty()) + ++ ncontours; + for (size_t j = 0; j < expoly.holes.size(); ++ j) + if (! expoly.holes[j].points.empty()) + ++ ncontours; + } + + // Collect the contours. + m_contours.assign(ncontours, NULL); + ncontours = 0; + for (size_t i = 0; i < expolygons.size(); ++ i) { + const ExPolygon &expoly = expolygons[i]; + if (! expoly.contour.points.empty()) + m_contours[ncontours++] = &expoly.contour.points; + for (size_t j = 0; j < expoly.holes.size(); ++ j) + if (! expoly.holes[j].points.empty()) + m_contours[ncontours++] = &expoly.holes[j].points; + } + + create_from_m_contours(resolution); +} + +void EdgeGrid::Grid::create(const ExPolygonCollection &expolygons, coord_t resolution) +{ + create(expolygons.expolygons, resolution); +} + +// m_contours has been initialized. Now fill in the edge grid. +void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) +{ + // 1) Measure the bounding box. + m_bbox.defined = false; + for (size_t i = 0; i < m_contours.size(); ++ i) { + const Slic3r::Points &pts = *m_contours[i]; + for (size_t j = 0; j < pts.size(); ++ j) + m_bbox.merge(pts[j]); + } + coord_t eps = 16; + m_bbox.min.x -= eps; + m_bbox.min.y -= eps; + m_bbox.max.x += eps; + m_bbox.max.y += eps; + + // 2) Initialize the edge grid. + m_resolution = resolution; + m_cols = (m_bbox.max.x - m_bbox.min.x + m_resolution - 1) / m_resolution; + m_rows = (m_bbox.max.y - m_bbox.min.y + m_resolution - 1) / m_resolution; + m_cells.assign(m_rows * m_cols, Cell()); + + // 3) First round of contour rasterization, count the edges per grid cell. + for (size_t i = 0; i < m_contours.size(); ++ i) { + const Slic3r::Points &pts = *m_contours[i]; + for (size_t j = 0; j < pts.size(); ++ j) { + // End points of the line segment. + Slic3r::Point p1(pts[j]); + Slic3r::Point p2 = pts[(j + 1 == pts.size()) ? 0 : j + 1]; + p1.x -= m_bbox.min.x; + p1.y -= m_bbox.min.y; + p2.x -= m_bbox.min.x; + p2.y -= m_bbox.min.y; + // Get the cells of the end points. + coord_t ix = p1.x / m_resolution; + coord_t iy = p1.y / m_resolution; + coord_t ixb = p2.x / m_resolution; + coord_t iyb = p2.y / m_resolution; + assert(ix >= 0 && ix < m_cols); + assert(iy >= 0 && iy < m_rows); + assert(ixb >= 0 && ixb < m_cols); + assert(iyb >= 0 && iyb < m_rows); + // Account for the end points. + ++ m_cells[iy*m_cols+ix].end; + if (ix == ixb && iy == iyb) + // Both ends fall into the same cell. + continue; + // Raster the centeral part of the line. + coord_t dx = std::abs(p2.x - p1.x); + coord_t dy = std::abs(p2.y - p1.y); + if (p1.x < p2.x) { + int64_t ex = int64_t((ix + 1)*m_resolution - p1.x) * int64_t(dy); + if (p1.y < p2.y) { + int64_t ey = int64_t((iy + 1)*m_resolution - p1.y) * int64_t(dx); + do { + assert(ix <= ixb && iy <= iyb); + if (ex < ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix += 1; + } + else if (ex == ey) { + ex = int64_t(dy) * m_resolution; + ey = int64_t(dx) * m_resolution; + ix += 1; + iy += 1; + } + else { + assert(ex > ey); + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy += 1; + } + ++m_cells[iy*m_cols + ix].end; + } while (ix != ixb || iy != iyb); + } + else { + int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); + do { + assert(ix <= ixb && iy >= iyb); + if (ex <= ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix += 1; + } + else { + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy -= 1; + } + ++m_cells[iy*m_cols + ix].end; + } while (ix != ixb || iy != iyb); + } + } + else { + int64_t ex = int64_t(p1.x - ix*m_resolution) * int64_t(dy); + if (p1.y < p2.y) { + int64_t ey = int64_t((iy + 1)*m_resolution - p1.y) * int64_t(dx); + do { + assert(ix >= ixb && iy <= iyb); + if (ex < ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix -= 1; + } + else { + assert(ex >= ey); + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy += 1; + } + ++m_cells[iy*m_cols + ix].end; + } while (ix != ixb || iy != iyb); + } + else { + int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); + do { + assert(ix >= ixb && iy >= iyb); + if (ex < ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix -= 1; + } + else if (ex == ey) { + ex = int64_t(dy) * m_resolution; + ey = int64_t(dx) * m_resolution; + ix -= 1; + iy -= 1; + } + else { + assert(ex > ey); + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy -= 1; + } + ++m_cells[iy*m_cols + ix].end; + } while (ix != ixb || iy != iyb); + } + } + } + } + + // 4) Prefix sum the numbers of hits per cells to get an index into m_cell_data. + size_t cnt = m_cells.front().end; + for (size_t i = 1; i < m_cells.size(); ++ i) { + m_cells[i].begin = cnt; + cnt += m_cells[i].end; + m_cells[i].end = cnt; + } + + // 5) Allocate the cell data. + m_cell_data.assign(cnt, std::pair(size_t(-1), size_t(-1))); + + // 6) Finally fill in m_cell_data by rasterizing the lines once again. + for (size_t i = 0; i < m_cells.size(); ++i) + m_cells[i].end = m_cells[i].begin; + for (size_t i = 0; i < m_contours.size(); ++i) { + const Slic3r::Points &pts = *m_contours[i]; + for (size_t j = 0; j < pts.size(); ++j) { + // End points of the line segment. + Slic3r::Point p1(pts[j]); + Slic3r::Point p2 = pts[(j + 1 == pts.size()) ? 0 : j + 1]; + p1.x -= m_bbox.min.x; + p1.y -= m_bbox.min.y; + p2.x -= m_bbox.min.x; + p2.y -= m_bbox.min.y; + // Get the cells of the end points. + coord_t ix = p1.x / m_resolution; + coord_t iy = p1.y / m_resolution; + coord_t ixb = p2.x / m_resolution; + coord_t iyb = p2.y / m_resolution; + assert(ix >= 0 && ix < m_cols); + assert(iy >= 0 && iy < m_rows); + assert(ixb >= 0 && ixb < m_cols); + assert(iyb >= 0 && iyb < m_rows); + // Account for the end points. + m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); + if (ix == ixb && iy == iyb) + // Both ends fall into the same cell. + continue; + // Raster the centeral part of the line. + coord_t dx = std::abs(p2.x - p1.x); + coord_t dy = std::abs(p2.y - p1.y); + if (p1.x < p2.x) { + int64_t ex = int64_t((ix + 1)*m_resolution - p1.x) * int64_t(dy); + if (p1.y < p2.y) { + int64_t ey = int64_t((iy + 1)*m_resolution - p1.y) * int64_t(dx); + do { + assert(ix <= ixb && iy <= iyb); + if (ex < ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix += 1; + } + else if (ex == ey) { + ex = int64_t(dy) * m_resolution; + ey = int64_t(dx) * m_resolution; + ix += 1; + iy += 1; + } + else { + assert(ex > ey); + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy += 1; + } + m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); + } while (ix != ixb || iy != iyb); + } + else { + int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); + do { + assert(ix <= ixb && iy >= iyb); + if (ex <= ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix += 1; + } + else { + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy -= 1; + } + m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); + } while (ix != ixb || iy != iyb); + } + } + else { + int64_t ex = int64_t(p1.x - ix*m_resolution) * int64_t(dy); + if (p1.y < p2.y) { + int64_t ey = int64_t((iy + 1)*m_resolution - p1.y) * int64_t(dx); + do { + assert(ix >= ixb && iy <= iyb); + if (ex < ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix -= 1; + } + else { + assert(ex >= ey); + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy += 1; + } + m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); + } while (ix != ixb || iy != iyb); + } + else { + int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); + do { + assert(ix >= ixb && iy >= iyb); + if (ex < ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix -= 1; + } + else if (ex == ey) { + ex = int64_t(dy) * m_resolution; + ey = int64_t(dx) * m_resolution; + ix -= 1; + iy -= 1; + } + else { + assert(ex > ey); + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy -= 1; + } + m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); + } while (ix != ixb || iy != iyb); + } + } + } + } +} + +template +struct PropagateDanielssonSingleStep { + PropagateDanielssonSingleStep(float *aL, unsigned char *asigns, size_t astride, coord_t aresolution) : + L(aL), signs(asigns), stride(astride), resolution(aresolution) {} + inline void operator()(int r, int c, int addr_delta) { + size_t addr = r * stride + c; + if ((signs[addr] & 2) == 0) { + float *v = &L[addr << 1]; + float l = v[0] * v[0] + v[1] * v[1]; + float *v2s = v + (addr_delta << 1); + float v2[2] = { + v2s[0] + INCX * resolution, + v2s[1] + INCY * resolution + }; + float l2 = v2[0] * v2[0] + v2[1] * v2[1]; + if (l2 < l) { + v[0] = v2[0]; + v[1] = v2[1]; + } + } + } + float *L; + unsigned char *signs; + size_t stride; + coord_t resolution; +}; + +struct PropagateDanielssonSingleVStep3 { + PropagateDanielssonSingleVStep3(float *aL, unsigned char *asigns, size_t astride, coord_t aresolution) : + L(aL), signs(asigns), stride(astride), resolution(aresolution) {} + inline void operator()(int r, int c, int addr_delta, bool has_l, bool has_r) { + size_t addr = r * stride + c; + if ((signs[addr] & 2) == 0) { + float *v = &L[addr<<1]; + float l = v[0]*v[0]+v[1]*v[1]; + float *v2s = v+(addr_delta<<1); + float v2[2] = { + v2s[0], + v2s[1] + resolution + }; + float l2 = v2[0]*v2[0]+v2[1]*v2[1]; + if (l2 < l) { + v[0] = v2[0]; + v[1] = v2[1]; + } + if (has_l) { + float *v2sl = v2s - 1; + v2[0] = v2sl[0] + resolution; + v2[1] = v2sl[1] + resolution; + l2 = v2[0]*v2[0]+v2[1]*v2[1]; + if (l2 < l) { + v[0] = v2[0]; + v[1] = v2[1]; + } + } + if (has_r) { + float *v2sr = v2s + 1; + v2[0] = v2sr[0] + resolution; + v2[1] = v2sr[1] + resolution; + l2 = v2[0]*v2[0]+v2[1]*v2[1]; + if (l2 < l) { + v[0] = v2[0]; + v[1] = v2[1]; + } + } + } + } + float *L; + unsigned char *signs; + size_t stride; + coord_t resolution; +}; + +void EdgeGrid::Grid::calculate_sdf() +{ + // 1) Initialize a signum and an unsigned vector to a zero iso surface. + size_t nrows = m_rows + 1; + size_t ncols = m_cols + 1; + // Unsigned vectors towards the closest point on the surface. + std::vector L(nrows * ncols * 2, FLT_MAX); + // Bit 0 set - negative. + // Bit 1 set - original value, the distance value shall not be changed by the Danielsson propagation. + // Bit 2 set - signum not propagated yet. + std::vector signs(nrows * ncols, 4); + // SDF will be initially filled with unsigned DF. +// m_signed_distance_field.assign(nrows * ncols, FLT_MAX); + float search_radius = float(m_resolution<<1); + m_signed_distance_field.assign(nrows * ncols, search_radius); + // For each cell: + for (size_t r = 0; r < m_rows; ++ r) { + for (size_t c = 0; c < m_cols; ++ c) { + const Cell &cell = m_cells[r * m_cols + c]; + // For each segment in the cell: + for (size_t i = cell.begin; i != cell.end; ++ i) { + const Slic3r::Points &pts = *m_contours[m_cell_data[i].first]; + size_t ipt = m_cell_data[i].second; + // End points of the line segment. + const Slic3r::Point &p1 = pts[ipt]; + const Slic3r::Point &p2 = pts[(ipt + 1 == pts.size()) ? 0 : ipt + 1]; + // Segment vector + const Slic3r::Point v_seg = p1.vector_to(p2); + // l2 of v_seg + const int64_t l2_seg = int64_t(v_seg.x) * int64_t(v_seg.x) + int64_t(v_seg.y) * int64_t(v_seg.y); + // For each corner of this cell and its 1 ring neighbours: + for (int corner_y = -1; corner_y < 3; ++ corner_y) { + coord_t corner_r = r + corner_y; + if (corner_r < 0 || corner_r >= nrows) + continue; + for (int corner_x = -1; corner_x < 3; ++ corner_x) { + coord_t corner_c = c + corner_x; + if (corner_c < 0 || corner_c >= ncols) + continue; + float &d_min = m_signed_distance_field[corner_r * ncols + corner_c]; + Slic3r::Point pt(m_bbox.min.x + corner_c * m_resolution, m_bbox.min.y + corner_r * m_resolution); + Slic3r::Point v_pt = p1.vector_to(pt); + // dot(p2-p1, pt-p1) + int64_t t_pt = int64_t(v_seg.x) * int64_t(v_pt.x) + int64_t(v_seg.y) * int64_t(v_pt.y); + if (t_pt < 0) { + // Closest to p1. + double dabs = sqrt(int64_t(v_pt.x) * int64_t(v_pt.x) + int64_t(v_pt.y) * int64_t(v_pt.y)); + if (dabs < d_min) { + // Previous point. + const Slic3r::Point &p0 = pts[(ipt == 0) ? (pts.size() - 1) : ipt - 1]; + Slic3r::Point v_seg_prev = p0.vector_to(p1); + int64_t t2_pt = int64_t(v_seg_prev.x) * int64_t(v_pt.x) + int64_t(v_seg_prev.y) * int64_t(v_pt.y); + if (t2_pt > 0) { + // Inside the wedge between the previous and the next segment. + // Set the signum depending on whether the vertex is convex or reflex. + int64_t det = int64_t(v_seg_prev.x) * int64_t(v_seg.y) - int64_t(v_seg_prev.y) * int64_t(v_seg.x); + assert(det != 0); + d_min = dabs; + // Fill in an unsigned vector towards the zero iso surface. + float *l = &L[(corner_r * ncols + corner_c) << 1]; + l[0] = std::abs(v_pt.x); + l[1] = std::abs(v_pt.y); + #ifdef _DEBUG + double dabs2 = sqrt(l[0]*l[0]+l[1]*l[1]); + assert(std::abs(dabs-dabs2) < 1e-4 * std::max(dabs, dabs2)); + #endif /* _DEBUG */ + signs[corner_r * ncols + corner_c] = ((det < 0) ? 1 : 0) | 2; + } + } + } + else if (t_pt > l2_seg) { + // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the same cell. + continue; + } else { + // Closest to the segment. + assert(t_pt >= 0 && t_pt <= l2_seg); + int64_t d_seg = int64_t(v_seg.y) * int64_t(v_pt.x) - int64_t(v_seg.x) * int64_t(v_pt.y); + double d = double(d_seg) / sqrt(double(l2_seg)); + double dabs = std::abs(d); + if (dabs < d_min) { + d_min = dabs; + // Fill in an unsigned vector towards the zero iso surface. + float *l = &L[(corner_r * ncols + corner_c) << 1]; + float linv = float(d_seg) / float(l2_seg); + l[0] = std::abs(float(v_seg.y) * linv); + l[1] = std::abs(float(v_seg.x) * linv); + #ifdef _DEBUG + double dabs2 = sqrt(l[0]*l[0]+l[1]*l[1]); + assert(std::abs(dabs-dabs2) <= 1e-4 * std::max(dabs, dabs2)); + #endif /* _DEBUG */ + signs[corner_r * ncols + corner_c] = ((d_seg < 0) ? 1 : 0) | 2; + } + } + } + } + } + } + } + +#if 1 + { + wxImage img(ncols, nrows); + unsigned char *data = img.GetData(); + memset(data, 0, ncols * nrows * 3); + for (coord_t r = 0; r < nrows; ++r) { + for (coord_t c = 0; c < ncols; ++c) { + unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3; + float d = m_signed_distance_field[r * ncols + c]; + if (d != search_radius) { + float s = 255 * d / search_radius; + int is = std::max(0, std::min(255, int(floor(s + 0.5f)))); + pxl[0] = 255; + pxl[1] = 255 - is; + pxl[2] = 255 - is; + } + else { + pxl[0] = 0; + pxl[1] = 255; + pxl[2] = 0; + } + } + } + img.SaveFile("out\\unsigned_df.png", wxBITMAP_TYPE_PNG); + } + { + wxImage img(ncols, nrows); + unsigned char *data = img.GetData(); + memset(data, 0, ncols * nrows * 3); + for (coord_t r = 0; r < nrows; ++r) { + for (coord_t c = 0; c < ncols; ++c) { + unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3; + float d = m_signed_distance_field[r * ncols + c]; + if (d != search_radius) { + float s = 255 * d / search_radius; + int is = std::max(0, std::min(255, int(floor(s + 0.5f)))); + if ((signs[r * ncols + c] & 1) == 0) { + // Positive + pxl[0] = 255; + pxl[1] = 255 - is; + pxl[2] = 255 - is; + } + else { + // Negative + pxl[0] = 255 - is; + pxl[1] = 255 - is; + pxl[2] = 255; + } + } + else { + pxl[0] = 0; + pxl[1] = 255; + pxl[2] = 0; + } + } + } + img.SaveFile("out\\signed_df.png", wxBITMAP_TYPE_PNG); + } +#endif + + // 2) Propagate the signum. + #define PROPAGATE_SIGNUM_SINGLE_STEP(DELTA) do { \ + size_t addr = r * ncols + c; \ + unsigned char &cur_val = signs[addr]; \ + if (cur_val & 4) { \ + unsigned char old_val = signs[addr + (DELTA)]; \ + if ((old_val & 4) == 0) \ + cur_val = old_val & 1; \ + } \ + } while (0); + // Top to bottom propagation. + for (size_t r = 0; r < nrows; ++ r) { + if (r > 0) + for (size_t c = 0; c < ncols; ++ c) + PROPAGATE_SIGNUM_SINGLE_STEP(- int(ncols)); + for (size_t c = 1; c < ncols; ++ c) + PROPAGATE_SIGNUM_SINGLE_STEP(- 1); + for (int c = int(ncols) - 2; c >= 0; -- c) + PROPAGATE_SIGNUM_SINGLE_STEP(+ 1); + } + // Bottom to top propagation. + for (int r = int(nrows) - 2; r >= 0; -- r) { + for (size_t c = 0; c < ncols; ++ c) + PROPAGATE_SIGNUM_SINGLE_STEP(+ ncols); + for (size_t c = 1; c < ncols; ++ c) + PROPAGATE_SIGNUM_SINGLE_STEP(- 1); + for (int c = int(ncols) - 2; c >= 0; -- c) + PROPAGATE_SIGNUM_SINGLE_STEP(+ 1); + } + #undef PROPAGATE_SIGNUM_SINGLE_STEP + + // 3) Propagate the distance by the Danielsson chamfer metric. + // Top to bottom propagation. + PropagateDanielssonSingleStep<1, 0> danielsson_hstep(L.data(), signs.data(), ncols, m_resolution); + PropagateDanielssonSingleStep<0, 1> danielsson_vstep(L.data(), signs.data(), ncols, m_resolution); + PropagateDanielssonSingleVStep3 danielsson_vstep3(L.data(), signs.data(), ncols, m_resolution); + // Top to bottom propagation. + for (size_t r = 0; r < nrows; ++ r) { + if (r > 0) + for (size_t c = 0; c < ncols; ++ c) + danielsson_vstep(r, c, -int(ncols)); +// PROPAGATE_DANIELSSON_SINGLE_VSTEP3(-int(ncols), c != 0, c + 1 != ncols); + for (size_t c = 1; c < ncols; ++ c) + danielsson_hstep(r, c, -1); + for (int c = int(ncols) - 2; c >= 0; -- c) + danielsson_hstep(r, c, +1); + } + // Bottom to top propagation. + for (int r = int(nrows) - 2; r >= 0; -- r) { + for (size_t c = 0; c < ncols; ++ c) + danielsson_vstep(r, c, +ncols); +// PROPAGATE_DANIELSSON_SINGLE_VSTEP3(+int(ncols), c != 0, c + 1 != ncols); + for (size_t c = 1; c < ncols; ++ c) + danielsson_hstep(r, c, -1); + for (int c = int(ncols) - 2; c >= 0; -- c) + danielsson_hstep(r, c, +1); + } + + // Update signed distance field from absolte vectors to the iso-surface. + for (size_t r = 0; r < nrows; ++ r) { + for (size_t c = 0; c < ncols; ++ c) { + size_t addr = r * ncols + c; + float *v = &L[addr<<1]; + float d = sqrt(v[0]*v[0]+v[1]*v[1]); + if (signs[addr] & 1) + d = -d; + m_signed_distance_field[addr] = d; + } + } + +#if 1 + { + wxImage img(ncols, nrows); + unsigned char *data = img.GetData(); + memset(data, 0, ncols * nrows * 3); + float search_radius = float(m_resolution * 5); + for (coord_t r = 0; r < nrows; ++r) { + for (coord_t c = 0; c < ncols; ++c) { + unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3; + unsigned char sign = signs[r * ncols + c]; + switch (sign) { + case 0: + // Positive, outside of a narrow band. + pxl[0] = 0; + pxl[1] = 0; + pxl[2] = 255; + break; + case 1: + // Negative, outside of a narrow band. + pxl[0] = 255; + pxl[1] = 0; + pxl[2] = 0; + break; + case 2: + // Positive, outside of a narrow band. + pxl[0] = 100; + pxl[1] = 100; + pxl[2] = 255; + break; + case 3: + // Negative, outside of a narrow band. + pxl[0] = 255; + pxl[1] = 100; + pxl[2] = 100; + break; + case 4: + // This shall not happen. Undefined signum. + pxl[0] = 0; + pxl[1] = 255; + pxl[2] = 0; + break; + default: + // This shall not happen. Invalid signum value. + pxl[0] = 255; + pxl[1] = 255; + pxl[2] = 255; + break; + } + } + } + img.SaveFile("out\\signed_df-signs.png", wxBITMAP_TYPE_PNG); + } +#endif + +#if 1 + { + wxImage img(ncols, nrows); + unsigned char *data = img.GetData(); + memset(data, 0, ncols * nrows * 3); + float search_radius = float(m_resolution * 5); + for (coord_t r = 0; r < nrows; ++r) { + for (coord_t c = 0; c < ncols; ++c) { + unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3; + float d = m_signed_distance_field[r * ncols + c]; + float s = 255.f * fabs(d) / search_radius; + int is = std::max(0, std::min(255, int(floor(s + 0.5f)))); + if (d < 0.f) { + pxl[0] = 255; + pxl[1] = 255 - is; + pxl[2] = 255 - is; + } + else { + pxl[0] = 255 - is; + pxl[1] = 255 - is; + pxl[2] = 255; + } + } + } + img.SaveFile("out\\signed_df2.png", wxBITMAP_TYPE_PNG); + } +#endif +} + +float EdgeGrid::Grid::signed_distance_bilinear(const Point &pt) const +{ + coord_t x = pt.x - m_bbox.min.x; + coord_t y = pt.y - m_bbox.min.y; + coord_t w = m_resolution * m_cols; + coord_t h = m_resolution * m_rows; + bool clamped = false; + coord_t xcl = x; + coord_t ycl = y; + if (x < 0) { + xcl = 0; + clamped = true; + } else if (x >= w) { + xcl = w - 1; + clamped = true; + } + if (y < 0) { + ycl = 0; + clamped = true; + } else if (y >= h) { + ycl = h - 1; + clamped = true; + } + + coord_t cell_c = coord_t(floor(xcl / m_resolution)); + coord_t cell_r = coord_t(floor(ycl / m_resolution)); + float tx = float(xcl - cell_c * m_resolution) / float(m_resolution); + assert(tx >= -1e-5 && tx < 1.f + 1e-5); + float ty = float(ycl - cell_r * m_resolution) / float(m_resolution); + assert(ty >= -1e-5 && ty < 1.f + 1e-5); + size_t addr = cell_r * (m_cols + 1) + cell_c; + float f00 = m_signed_distance_field[addr]; + float f01 = m_signed_distance_field[addr+1]; + addr += m_cols + 1; + float f10 = m_signed_distance_field[addr]; + float f11 = m_signed_distance_field[addr+1]; + float f0 = (1.f - tx) * f00 + tx * f01; + float f1 = (1.f - tx) * f10 + tx * f11; + float f = (1.f - ty) * f0 + ty * f1; + + if (clamped) { + if (f > 0) { + if (x < 0) + f += -x; + else if (x >= w) + f += x - w + 1; + if (y < 0) + f += -y; + else if (y >= h) + f += y - h + 1; + } else { + if (x < 0) + f -= -x; + else if (x >= w) + f -= x - w + 1; + if (y < 0) + f -= -y; + else if (y >= h) + f -= y - h + 1; + } + } + + return f; +} + +bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment) const { + BoundingBox bbox; + bbox.min = bbox.max = Point(pt.x - m_bbox.min.x, pt.y - m_bbox.min.y); + bbox.defined = true; + // Upper boundary, round to grid and test validity. + bbox.max.x += search_radius; + bbox.max.y += search_radius; + if (bbox.max.x < 0 || bbox.max.y < 0) + return false; + bbox.max.x /= m_resolution; + bbox.max.y /= m_resolution; + if (bbox.max.x >= m_cols) + bbox.max.x = m_cols - 1; + if (bbox.max.y >= m_rows) + bbox.max.y = m_rows - 1; + // Lower boundary, round to grid and test validity. + bbox.min.x -= search_radius; + bbox.min.y -= search_radius; + if (bbox.min.x < 0) + bbox.min.x = 0; + if (bbox.min.y < 0) + bbox.min.y = 0; + bbox.min.x /= m_resolution; + bbox.min.y /= m_resolution; + // Is the interval empty? + if (bbox.min.x > bbox.max.x || + bbox.min.y > bbox.max.y) + return false; + // Traverse all cells in the bounding box. + float d_min = search_radius; + // Signum of the distance field at pt. + int sign_min = 0; + bool on_segment = false; + for (int r = bbox.min.y; r <= bbox.max.y; ++ r) { + for (int c = bbox.min.x; c <= bbox.max.x; ++ c) { + const Cell &cell = m_cells[r * m_cols + c]; + for (size_t i = cell.begin; i < cell.end; ++ i) { + const Slic3r::Points &pts = *m_contours[m_cell_data[i].first]; + size_t ipt = m_cell_data[i].second; + // End points of the line segment. + const Slic3r::Point &p1 = pts[ipt]; + const Slic3r::Point &p2 = pts[(ipt + 1 == pts.size()) ? 0 : ipt + 1]; + Slic3r::Point v_seg = p1.vector_to(p2); + Slic3r::Point v_pt = p1.vector_to(pt); + // dot(p2-p1, pt-p1) + int64_t t_pt = int64_t(v_seg.x) * int64_t(v_pt.x) + int64_t(v_seg.y) * int64_t(v_pt.y); + // l2 of seg + int64_t l2_seg = int64_t(v_seg.x) * int64_t(v_seg.x) + int64_t(v_seg.y) * int64_t(v_seg.y); + if (t_pt < 0) { + // Closest to p1. + double dabs = sqrt(int64_t(v_pt.x) * int64_t(v_pt.x) + int64_t(v_pt.y) * int64_t(v_pt.y)); + if (dabs < d_min) { + // Previous point. + const Slic3r::Point &p0 = pts[(ipt == 0) ? (pts.size() - 1) : ipt - 1]; + Slic3r::Point v_seg_prev = p0.vector_to(p1); + int64_t t2_pt = int64_t(v_seg_prev.x) * int64_t(v_pt.x) + int64_t(v_seg_prev.y) * int64_t(v_pt.y); + if (t2_pt > 0) { + // Inside the wedge between the previous and the next segment. + d_min = dabs; + // Set the signum depending on whether the vertex is convex or reflex. + int64_t det = int64_t(v_seg_prev.x) * int64_t(v_seg.y) - int64_t(v_seg_prev.y) * int64_t(v_seg.x); + assert(det != 0); + sign_min = (det > 0) ? 1 : -1; + on_segment = false; + } + } + } + else if (t_pt > l2_seg) { + // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the same cell. + continue; + } else { + // Closest to the segment. + assert(t_pt >= 0 && t_pt <= l2_seg); + int64_t d_seg = int64_t(v_seg.y) * int64_t(v_pt.x) - int64_t(v_seg.x) * int64_t(v_pt.y); + double d = double(d_seg) / sqrt(double(l2_seg)); + double dabs = std::abs(d); + if (dabs < d_min) { + d_min = dabs; + sign_min = (d_seg < 0) ? -1 : ((d_seg == 0) ? 0 : 1); + on_segment = true; + } + } + } + } + } + if (d_min >= search_radius) + return false; + result_min_dist = d_min * sign_min; + if (pon_segment != NULL) + *pon_segment = on_segment; + return true; +} + +bool EdgeGrid::Grid::signed_distance(const Point &pt, coord_t search_radius, coordf_t &result_min_dist) const +{ + if (signed_distance_edges(pt, search_radius, result_min_dist)) + return true; + if (m_signed_distance_field.empty()) + return false; + result_min_dist = signed_distance_bilinear(pt); + return true; +} + +void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path) +{ + unsigned int w = (bbox.max.x - bbox.min.x + resolution - 1) / resolution; + unsigned int h = (bbox.max.y - bbox.min.y + resolution - 1) / resolution; + wxImage img(w, h); + unsigned char *data = img.GetData(); + memset(data, 0, w * h * 3); + + static int iRun = 0; + ++iRun; + + const coord_t search_radius = grid.resolution() * 2; + const coord_t display_blend_radius = grid.resolution() * 5; + for (coord_t r = 0; r < h; ++r) { + for (coord_t c = 0; c < w; ++ c) { + unsigned char *pxl = data + (((h - r - 1) * w) + c) * 3; + Point pt(c * resolution + bbox.min.x, r * resolution + bbox.min.y); + coordf_t min_dist; + bool on_segment; +// if (grid.signed_distance_edges(pt, search_radius, min_dist, &on_segment)) { + if (grid.signed_distance(pt, search_radius, min_dist)) { + //FIXME + on_segment = true; + float s = 255 * std::abs(min_dist) / float(display_blend_radius); + int is = std::max(0, std::min(255, int(floor(s + 0.5f)))); + if (min_dist < 0) { + if (on_segment) { + pxl[0] = 255; + pxl[1] = 255 - is; + pxl[2] = 255 - is; + } else { + pxl[0] = 128; + pxl[1] = 128; + pxl[2] = 255 - is; + } + } + else { + if (on_segment) { + pxl[0] = 255 - is; + pxl[1] = 255 - is; + pxl[2] = 255; + } else { + pxl[0] = 255 - is; + pxl[1] = 0; + pxl[2] = 255; + } + } + } else { + pxl[0] = 0; + pxl[1] = 255; + pxl[2] = 0; + } + + float gridx = float(pt.x - grid.bbox().min.x) / float(grid.resolution()); + float gridy = float(pt.y - grid.bbox().min.y) / float(grid.resolution()); + if (gridx >= -0.4f && gridy >= -0.4f && gridx <= grid.cols() + 0.4f && gridy <= grid.rows() + 0.4f) { + int ix = int(floor(gridx + 0.5f)); + int iy = int(floor(gridy + 0.5f)); + float dx = gridx - float(ix); + float dy = gridy - float(iy); + float d = sqrt(dx*dx + dy*dy) * float(grid.resolution()) / float(resolution); + if (d < 1.f) { + // Less than 1 pixel from the grid point. + float t = 0.5f + 0.5f * d; + pxl[0] = (unsigned char)(t * pxl[0]); + pxl[1] = (unsigned char)(t * pxl[1]); + pxl[2] = (unsigned char)(t * pxl[2]); + } + } + + float dgrid = fabs(min_dist) / float(grid.resolution()); + float igrid = floor(dgrid + 0.5f); + dgrid = std::abs(dgrid - igrid) * float(grid.resolution()) / float(resolution); + if (dgrid < 1.f) { + // Less than 1 pixel from the grid point. + float t = 0.5f + 0.5f * dgrid; + pxl[0] = (unsigned char)(t * pxl[0]); + pxl[1] = (unsigned char)(t * pxl[1]); + pxl[2] = (unsigned char)(t * pxl[2]); + if (igrid > 0.f) { + // Other than zero iso contour. + int g = pxl[1] + 255.f * (1.f - t); + pxl[1] = std::min(g, 255); + } + } + } + } + + img.SaveFile(path, wxBITMAP_TYPE_PNG); +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/EdgeGrid.hpp b/xs/src/libslic3r/EdgeGrid.hpp new file mode 100644 index 000000000..4ea85e5b9 --- /dev/null +++ b/xs/src/libslic3r/EdgeGrid.hpp @@ -0,0 +1,81 @@ +#ifndef slic3r_EdgeGrid_hpp_ +#define slic3r_EdgeGrid_hpp_ + +#include +#include + +#include "Point.hpp" +#include "BoundingBox.hpp" +#include "ExPolygon.hpp" +#include "ExPolygonCollection.hpp" + +namespace Slic3r { +namespace EdgeGrid { + +struct Grid +{ + Grid(); + ~Grid(); + + void create(const Polygons &polygons, coord_t resolution); + void create(const ExPolygon &expoly, coord_t resolution); + void create(const ExPolygons &expolygons, coord_t resolution); + void create(const ExPolygonCollection &expolygons, coord_t resolution); + + // Fill in a rough m_signed_distance_field from the edge grid. + // The rough SDF is used by signed_distance() for distances outside of the search_radius. + void calculate_sdf(); + + // Return an estimate of the signed distance based on m_signed_distance_field grid. + float signed_distance_bilinear(const Point &pt) const; + + // Calculate a signed distance to the contours in search_radius from the point. + bool signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment = NULL) const; + + // Calculate a signed distance to the contours in search_radius from the point. If no edge is found in search_radius, + // return an interpolated value from m_signed_distance_field, if it exists. + bool signed_distance(const Point &pt, coord_t search_radius, coordf_t &result_min_dist) const; + + const BoundingBox& bbox() const { return m_bbox; } + const coord_t resolution() const { return m_resolution; } + const size_t rows() const { return m_rows; } + const size_t cols() const { return m_cols; } + +protected: + void create_from_m_contours(coord_t resolution); + + // Bounding box around the contours. + BoundingBox m_bbox; + // Grid dimensions. + coord_t m_resolution; + size_t m_rows; + size_t m_cols; + + // Referencing the source contours. + // This format allows one to work with any Slic3r fixed point contour format + // (Polygon, ExPolygon, ExPolygonCollection etc). + std::vector m_contours; + + // Referencing a contour and a line segment of m_contours. + std::vector> m_cell_data; + + struct Cell { + Cell() : begin(0), end(0) {} + size_t begin; + size_t end; + }; + // Full grid of cells. + std::vector m_cells; + + // Distance field derived from the edge grid, seed filled by the Danielsson chamfer metric. + // May be empty. + std::vector m_signed_distance_field; +}; + +// Debugging utility. Save the signed distance field. +extern void save_png(const Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path); + +} // namespace EdgeGrid +} // namespace Slic3r + +#endif /* slic3r_EdgeGrid_hpp_ */ diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 0b62382b3..befb00d03 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -1,9 +1,21 @@ #include "GCode.hpp" #include "ExtrusionEntity.hpp" +#include "EdgeGrid.hpp" #include #include #include +#include "SVG.hpp" + +#if 0 +// Enable debugging and asserts, even in the release build. +#define DEBUG +#define _DEBUG +#undef NDEBUG +#endif + +#include + namespace Slic3r { AvoidCrossingPerimeters::AvoidCrossingPerimeters() @@ -198,12 +210,22 @@ Wipe::wipe(GCode &gcodegen, bool toolchange) #define EXTRUDER_CONFIG(OPT) this->config.OPT.get_at(this->writer.extruder()->id) GCode::GCode() - : placeholder_parser(NULL), enable_loop_clipping(true), enable_cooling_markers(false), layer_count(0), + : placeholder_parser(NULL), enable_loop_clipping(true), + enable_cooling_markers(false), enable_extrusion_role_markers(false), + layer_count(0), layer_index(-1), layer(NULL), first_layer(false), elapsed_time(0.0), volumetric_speed(0), - _last_pos_defined(false) + _last_pos_defined(false), + _lower_layer_edge_grid(NULL), + _last_extrusion_role(erNone) { } +GCode::~GCode() +{ + delete _lower_layer_edge_grid; + _lower_layer_edge_grid = NULL; +} + const Point& GCode::last_pos() const { @@ -279,6 +301,8 @@ GCode::change_layer(const Layer &layer) this->layer = &layer; this->layer_index++; this->first_layer = (layer.id() == 0); + delete this->_lower_layer_edge_grid; + this->_lower_layer_edge_grid = NULL; // avoid computing islands and overhangs if they're not needed if (this->config.avoid_crossing_perimeters) { @@ -308,12 +332,230 @@ GCode::change_layer(const Layer &layer) return gcode; } +static inline const char* ExtrusionRole2String(const ExtrusionRole role) +{ + switch (role) { + case erNone: return "erNone"; + case erPerimeter: return "erPerimeter"; + case erExternalPerimeter: return "erExternalPerimeter"; + case erOverhangPerimeter: return "erOverhangPerimeter"; + case erInternalInfill: return "erInternalInfill"; + case erSolidInfill: return "erSolidInfill"; + case erTopSolidInfill: return "erTopSolidInfill"; + case erBridgeInfill: return "erBridgeInfill"; + case erGapFill: return "erGapFill"; + case erSkirt: return "erSkirt"; + case erSupportMaterial: return "erSupportMaterial"; + case erSupportMaterialInterface: return "erSupportMaterialInterface"; + default: return "erInvalid"; + }; +} + +static inline const char* ExtrusionLoopRole2String(const ExtrusionLoopRole role) +{ + switch (role) { + case elrDefault: return "elrDefault"; + case elrContourInternalPerimeter: return "elrContourInternalPerimeter"; + case elrSkirt: return "elrSkirt"; + default: return "elrInvalid"; + } +}; + +// Return a value in <0, 1> of a cubic B-spline kernel centered around zero. +// The B-spline is re-scaled so it has value 1 at zero. +static inline float bspline_kernel(float x) +{ + x = std::abs(x); + if (x < 1.f) { + return 1.f - (3. / 2.) * x * x + (3.f / 4.f) * x * x * x; + } + else if (x < 2.f) { + x -= 1.f; + float x2 = x * x; + float x3 = x2 * x; + return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3; + } + else + return 0; +} + +static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance) +{ + // The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve. + // Solved by sympy package: +/* +from sympy import * +(x,a,b,c,d,r,z)=symbols('x a b c d r z') +p = a + b*x + c*x*x + d*x*x*x +p2 = p.subs(solve([p.subs(x, -r), p.diff(x).subs(x, -r), p.diff(x,x).subs(x, -r), p.subs(x, 0)-z], [a, b, c, d])) +from sympy.plotting import plot +plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400) +*/ + if (overlap_distance < - nozzle_r) { + // The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty. + return 0.f; + } else { + float x = overlap_distance / nozzle_r; + float x2 = x * x; + float x3 = x2 * x; + return weight_zero * (1.f + 3.f * x + 3.f * x2 + x3); + } +} + +static Points::iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps) +{ + assert(polygon.points.size() >= 2); + if (polygon.points.size() <= 1) + if (polygon.points.size() == 1) + return polygon.points.begin(); + + Point pt_min; + double d_min = std::numeric_limits::max(); + size_t i_min = size_t(-1); + + for (size_t i = 0; i < polygon.points.size(); ++ i) { + size_t j = i + 1; + if (j == polygon.points.size()) + j = 0; + const Point &p1 = polygon.points[i]; + const Point &p2 = polygon.points[j]; + const Slic3r::Point v_seg = p1.vector_to(p2); + const Slic3r::Point v_pt = p1.vector_to(pt); + const int64_t l2_seg = int64_t(v_seg.x) * int64_t(v_seg.x) + int64_t(v_seg.y) * int64_t(v_seg.y); + int64_t t_pt = int64_t(v_seg.x) * int64_t(v_pt.x) + int64_t(v_seg.y) * int64_t(v_pt.y); + if (t_pt < 0) { + // Closest to p1. + double dabs = sqrt(int64_t(v_pt.x) * int64_t(v_pt.x) + int64_t(v_pt.y) * int64_t(v_pt.y)); + if (dabs < d_min) { + d_min = dabs; + i_min = i; + pt_min = p1; + } + } + else if (t_pt > l2_seg) { + // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the next step. + continue; + } else { + // Closest to the segment. + assert(t_pt >= 0 && t_pt <= l2_seg); + int64_t d_seg = int64_t(v_seg.y) * int64_t(v_pt.x) - int64_t(v_seg.x) * int64_t(v_pt.y); + double d = double(d_seg) / sqrt(double(l2_seg)); + double dabs = std::abs(d); + if (dabs < d_min) { + d_min = dabs; + i_min = i; + // Evaluate the foot point. + pt_min = p1; + double linv = double(d_seg) / double(l2_seg); + pt_min.x = pt.x - coord_t(floor(double(v_seg.y) * linv + 0.5)); + pt_min.y = pt.y + coord_t(floor(double(v_seg.x) * linv + 0.5)); + assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5)); + } + } + } + + assert(i_min != size_t(-1)); + if (pt_min.distance_to(polygon.points[i_min]) > eps) { + // Insert a new point on the segment i_min, i_min+1. + return polygon.points.insert(polygon.points.begin() + (i_min + 1), pt_min); + } + return polygon.points.begin() + i_min; +} + +std::vector polygon_parameter_by_length(const Polygon &polygon) +{ + // Parametrize the polygon by its length. + std::vector lengths(polygon.points.size()+1, 0.); + for (size_t i = 1; i < polygon.points.size(); ++ i) + lengths[i] = lengths[i-1] + polygon.points[i].distance_to(polygon.points[i-1]); + lengths.back() = lengths[lengths.size()-2] + polygon.points.front().distance_to(polygon.points.back()); + return lengths; +} + +std::vector polygon_angles_at_vertices(const Polygon &polygon, const std::vector &lengths, float min_arm_length) +{ + assert(polygon.points.size() + 1 == lengths.size()); + if (min_arm_length > 0.25f * lengths.back()) + min_arm_length = 0.25f * lengths.back(); + + // Find the initial prev / next point span. + size_t idx_prev = polygon.points.size(); + size_t idx_curr = 0; + size_t idx_next = 1; + while (idx_prev > idx_curr && lengths.back() - lengths[idx_prev] < min_arm_length) + -- idx_prev; + while (idx_next < idx_prev && lengths[idx_next] < min_arm_length) + ++ idx_next; + + std::vector angles(polygon.points.size(), 0.f); + for (; idx_curr < polygon.points.size(); ++ idx_curr) { + // Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length. + if (idx_prev >= idx_curr) { + while (idx_prev < polygon.points.size() && lengths.back() - lengths[idx_prev] + lengths[idx_curr] > min_arm_length) + ++ idx_prev; + if (idx_prev == polygon.points.size()) + idx_prev = 0; + } + while (idx_prev < idx_curr && lengths[idx_curr] - lengths[idx_prev] > min_arm_length) + ++ idx_prev; + // Move idx_prev one step back. + if (idx_prev == 0) + idx_prev = polygon.points.size() - 1; + else + -- idx_prev; + // Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length. + if (idx_curr <= idx_next) { + while (idx_next < polygon.points.size() && lengths[idx_next] - lengths[idx_curr] < min_arm_length) + ++ idx_next; + if (idx_next == polygon.points.size()) + idx_next = 0; + } + while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length) + ++ idx_next; + // Calculate angle between idx_prev, idx_curr, idx_next. + const Point &p0 = polygon.points[idx_prev]; + const Point &p1 = polygon.points[idx_curr]; + const Point &p2 = polygon.points[idx_next]; + const Point v1 = p0.vector_to(p1); + const Point v2 = p1.vector_to(p2); + int64_t dot = int64_t(v1.x)*int64_t(v2.x) + int64_t(v1.y)*int64_t(v2.y); + int64_t cross = int64_t(v1.x)*int64_t(v2.y) - int64_t(v1.y)*int64_t(v2.x); + float angle = float(atan2(double(cross), double(dot))); + angles[idx_curr] = angle; + } + + return angles; +} + std::string GCode::extrude(ExtrusionLoop loop, std::string description, double 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 - + + if (this->layer->lower_layer != NULL) { + if (this->_lower_layer_edge_grid == NULL) { + // Create the distance field for a layer below. + const coord_t distance_field_resolution = scale_(1.f); + this->_lower_layer_edge_grid = new EdgeGrid::Grid(); + this->_lower_layer_edge_grid->create(this->layer->lower_layer->slices, distance_field_resolution); + this->_lower_layer_edge_grid->calculate_sdf(); + #if 0 + { + static int iRun = 0; + char path[2048]; + sprintf(path, "out\\GCode_extrude_loop_edge_grid-%d.png", iRun++); + BoundingBox bbox = this->_lower_layer_edge_grid->bbox(); + bbox.min.x -= scale_(5.f); + bbox.min.y -= scale_(5.f); + bbox.max.x += scale_(5.f); + bbox.max.y += scale_(5.f); + EdgeGrid::save_png(*this->_lower_layer_edge_grid, bbox, scale_(0.1f), path); + } + #endif + } + } + // extrude all loops ccw bool was_clockwise = loop.make_counter_clockwise(); @@ -326,68 +568,126 @@ GCode::extrude(ExtrusionLoop loop, std::string description, double speed) if (this->config.spiral_vase) { loop.split_at(last_pos); } else if (seam_position == spNearest || seam_position == spAligned) { - const 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) { + Polygon polygon = loop.polygon(); + const coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter); + const coord_t nozzle_r = scale_(0.5*nozzle_dmr); + + // Retrieve the last start position for this object. + float last_pos_weight = 1.f; + if (seam_position == spAligned && this->layer != NULL && this->_seam_position.count(this->layer->object()) > 0) { last_pos = this->_seam_position[this->layer->object()]; + last_pos_weight = 5.f; } - - Point point; - if (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); + + // Insert a projection of last_pos into the polygon. + size_t last_pos_proj_idx; + { + auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); + last_pos_proj_idx = it - polygon.points.begin(); + } + Point last_pos_proj = polygon.points[last_pos_proj_idx]; + // Parametrize the polygon by its length. + std::vector lengths = polygon_parameter_by_length(polygon); + + // For each polygon point, store a penalty. + // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r. + std::vector penalties = polygon_angles_at_vertices(polygon, lengths, nozzle_r); + // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces. + const float penaltyConvexVertex = 1.f; + const float penaltyFlatSurface = 5.f; + const float penaltySeam = 1.3f; + const float penaltyOverhangHalf = 10.f; + // Penalty for visible seams. + for (size_t i = 0; i < polygon.points.size(); ++ i) { + float ccwAngle = penalties[i]; + if (was_clockwise) + ccwAngle = - ccwAngle; + float penalty = 0; +// if (ccwAngle <- float(PI/3.)) + if (ccwAngle <- float(0.6 * PI)) + // Sharp reflex vertex. We love that, it hides the seam perfectly. + penalty = 0.f; +// else if (ccwAngle > float(PI/3.)) + else if (ccwAngle > float(0.6 * PI)) + // Seams on sharp convex vertices are more visible than on reflex vertices. + penalty = penaltyConvexVertex; + else if (ccwAngle < 0.f) { + // Interpolate penalty between maximum and zero. + penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * (PI * 2. / 3.)); + } else { + assert(ccwAngle >= 0.f); + // Interpolate penalty between maximum and the penalty for a convex vertex. + penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * (PI * 2. / 3.)); } - - 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); + // Give a negative penalty for points close to the last point or the prefered seam location. + //float dist_to_last_pos_proj = last_pos_proj.distance_to(polygon.points[i]); + float dist_to_last_pos_proj = (i < last_pos_proj_idx) ? + std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) : + std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]); + float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr + penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max); + penalties[i] = std::max(0.f, penalty); } - if (this->layer != NULL) - this->_seam_position[this->layer->object()] = point; + + // Penalty for overhangs. + if (this->_lower_layer_edge_grid) { + // Use the edge grid distance field structure over the lower layer to calculate overhangs. + coord_t nozzle_r = scale_(0.5*nozzle_dmr); + coord_t search_r = scale_(0.8*nozzle_dmr); + for (size_t i = 0; i < polygon.points.size(); ++ i) { + const Point &p = polygon.points[i]; + coordf_t dist; + // Signed distance is positive outside the object, negative inside the object. + // The point is considered at an overhang, if it is more than nozzle radius + // outside of the lower layer contour. + bool found = this->_lower_layer_edge_grid->signed_distance(p, search_r, dist); + // If the approximate Signed Distance Field was initialized over this->_lower_layer_edge_grid, + // then the signed distnace shall always be known. + assert(found); + penalties[i] += extrudate_overlap_penalty(nozzle_r, penaltyOverhangHalf, dist); + } + } + + // Find a point with a minimum penalty. + size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); + + // Export the contour into a SVG file. + #if 0 + { + static int iRun = 0; + char path[2048]; + sprintf(path, "out\\GCode_extrude_loop-%d.svg", iRun ++); + SVG svg(path); + if (this->layer->lower_layer != NULL) + svg.draw(this->layer->lower_layer->slices.expolygons); + for (size_t i = 0; i < loop.paths.size(); ++ i) + svg.draw(loop.paths[i].as_polyline(), "red"); + Polylines polylines; + for (size_t i = 0; i < loop.paths.size(); ++ i) + polylines.push_back(loop.paths[i].as_polyline()); + Slic3r::Polygons polygons; + coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter); + coord_t delta = scale_(0.5*nozzle_dmr); + Slic3r::offset(polylines, &polygons, delta); +// for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue"); + svg.draw(last_pos, "green", 3); + svg.draw(polygon.points[idx_min], "yellow", 3); + svg.Close(); + } + #endif + + // Split the loop at the point with a minium penalty. + if (!loop.split_at_vertex(polygon.points[idx_min])) + // The point is not in the original loop. Insert it. + loop.split_at(polygon.points[idx_min]); + } else if (seam_position == spRandom) { if (loop.role == elrContourInternalPerimeter) { + // This loop does not contain any other loop. Set a random position. + // The other loops will get a seam close to the random point chosen + // on the inner most contour. + //FIXME This works correctly for inner contours first only. + //FIXME Better parametrize the loop by its length. Polygon polygon = loop.polygon(); Point centroid = polygon.centroid(); last_pos = Point(polygon.bounding_box().max.x, centroid.y); @@ -416,6 +716,8 @@ GCode::extrude(ExtrusionLoop loop, std::string description, double speed) // extrude along the path std::string gcode; for (ExtrusionPaths::const_iterator path = paths.begin(); path != paths.end(); ++path) +// description += ExtrusionLoopRole2String(loop.role); +// description += ExtrusionRole2String(path->role); gcode += this->_extrude(*path, description, speed); // reset acceleration @@ -477,6 +779,7 @@ GCode::extrude(const ExtrusionEntity &entity, std::string description, double sp std::string GCode::extrude(const ExtrusionPath &path, std::string description, double speed) { +// description += ExtrusionRole2String(path.role); std::string gcode = this->_extrude(path, description, speed); // reset acceleration @@ -561,6 +864,14 @@ GCode::_extrude(ExtrusionPath path, std::string description, double speed) double F = speed * 60; // convert mm/sec to mm/min // extrude arc or line + if (this->enable_extrusion_role_markers) { + if (path.role != this->_last_extrusion_role) { + this->_last_extrusion_role = path.role; + char buf[32]; + sprintf(buf, ";_EXTRUSION_ROLE:%d\n", int(path.role)); + gcode += buf; + } + } if (path.is_bridge() && this->enable_cooling_markers) gcode += ";_BRIDGE_FAN_START\n"; gcode += this->writer.set_speed(F, "", this->enable_cooling_markers ? ";_EXTRUDE_SET_SPEED" : ""); diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index ebeb50e78..d6d661b5c 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -14,7 +14,9 @@ namespace Slic3r { +// Forward declarations. class GCode; +namespace EdgeGrid { class Grid; } class AvoidCrossingPerimeters { public: @@ -77,15 +79,23 @@ class GCode { AvoidCrossingPerimeters avoid_crossing_perimeters; bool enable_loop_clipping; bool enable_cooling_markers; + // Markers for the Pressure Equalizer to recognize the extrusion type. + // The Pressure Equalizer removes the markers from the final G-code. + bool enable_extrusion_role_markers; size_t layer_count; int layer_index; // just a counter const Layer* layer; std::map _seam_position; + // Distance Field structure to + EdgeGrid::Grid *_lower_layer_edge_grid; bool first_layer; // this flag triggers first layer speeds float elapsed_time; // seconds double volumetric_speed; + // Support for the extrusion role markers. Which marker is active? + ExtrusionRole _last_extrusion_role; GCode(); + ~GCode(); const Point& last_pos() const; void set_last_pos(const Point &pos); bool last_pos_defined() const; diff --git a/xs/src/libslic3r/GCode/PressureEqualizer.cpp b/xs/src/libslic3r/GCode/PressureEqualizer.cpp new file mode 100644 index 000000000..2067b0dd5 --- /dev/null +++ b/xs/src/libslic3r/GCode/PressureEqualizer.cpp @@ -0,0 +1,608 @@ +#include +#include + +#include "../libslic3r.h" +#include "../PrintConfig.hpp" + +#include "PressureEqualizer.hpp" + +namespace Slic3r { + +GCodePressureEqualizer::GCodePressureEqualizer(const Slic3r::GCodeConfig *config) : + m_config(config) +{ + reset(); +} + +GCodePressureEqualizer::~GCodePressureEqualizer() +{ +} + +void GCodePressureEqualizer::reset() +{ + circular_buffer_pos = 0; + circular_buffer_size = 100; + circular_buffer_items = 0; + circular_buffer.assign(circular_buffer_size, GCodeLine()); + + output_buffer.clear(); + output_buffer_length = 0; + + m_current_extruder = 0; + // Zero the position of the XYZE axes + the current feed + memset(m_current_pos, 0, sizeof(float) * 5); + m_current_extrusion_role = erNone; + // Expect the first command to fill the nozzle (deretract). + m_retracted = true; + + // Calculate filamet crossections for the multiple extruders. + m_filament_crossections.clear(); + for (size_t i = 0; i < m_config->filament_diameter.values.size(); ++ i) { + double r = m_config->filament_diameter.values[i]; + double a = 0.25f*M_PI*r*r; + m_filament_crossections.push_back(float(a)); + } + + m_max_segment_length = 20.f; + // Volumetric rate of a 0.45mm x 0.2mm extrusion at 60mm/min XY movement: 0.45*0.2*60*60=5.4*60 = 324 mm^3/min + // Volumetric rate of a 0.45mm x 0.2mm extrusion at 20mm/min XY movement: 0.45*0.2*20*60=1.8*60 = 108 mm^3/min + // Slope of the volumetric rate, changing from 20mm^3/min to 60mm^3/min over 2 seconds: (5.4-1.8)*60*60/2=60*60*1.8 = 6480 mm^3/min^2 + m_max_volumetric_extrusion_rate_slope_positive = 6480.f; + m_max_volumetric_extrusion_rate_slope_negative = 6480.f; + + for (size_t i = 0; i < numExtrusionRoles; ++ i) { + m_max_volumetric_extrusion_rate_slopes[i].negative = m_max_volumetric_extrusion_rate_slope_negative; + m_max_volumetric_extrusion_rate_slopes[i].positive = m_max_volumetric_extrusion_rate_slope_positive; + } + + // Don't regulate the pressure in infill. + m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].negative = 0; + m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].positive = 0; + // Don't regulate the pressure in gap fill. + m_max_volumetric_extrusion_rate_slopes[erGapFill].negative = 0; + m_max_volumetric_extrusion_rate_slopes[erGapFill].positive = 0; + + m_stat.reset(); +} + +const char* GCodePressureEqualizer::process(const char *szGCode, bool flush) +{ + // Reset length of the output_buffer. + output_buffer_length = 0; + + if (szGCode != 0) { + const char *p = szGCode; + while (*p != 0) { + // Find end of the line. + const char *endl = p; + // Slic3r always generates end of lines in a Unix style. + for (; *endl != 0 && *endl != '\n'; ++ endl) ; + if (circular_buffer_items == circular_buffer_size) + // Buffer is full. Push out the oldest line. + output_gcode_line(circular_buffer[circular_buffer_pos]); + else + ++ circular_buffer_items; + // Process a G-code line, store it into the provided GCodeLine object. + size_t idx_tail = circular_buffer_pos; + circular_buffer_pos = circular_buffer_idx_next(circular_buffer_pos); + if (! process_line(p, endl - p, circular_buffer[idx_tail])) { + // The line has to be forgotten. It contains comment marks, which shall be + // filtered out of the target g-code. + circular_buffer_pos = idx_tail; + -- circular_buffer_items; + } + p = endl; + if (*p == '\n') + ++ p; + } + } + + if (flush) { + // Flush the remaining valid lines of the circular buffer. + for (size_t idx = circular_buffer_idx_head(); circular_buffer_items > 0; -- circular_buffer_items) { + output_gcode_line(circular_buffer[idx]); + if (++ idx == circular_buffer_size) + idx = 0; + } + // Reset the index pointer. + assert(circular_buffer_items == 0); + circular_buffer_pos = 0; + +#if 1 + printf("Statistics: \n"); + printf("Minimum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_min); + printf("Maximum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_max); + if (m_stat.extrusion_length > 0) + m_stat.volumetric_extrusion_rate_avg /= m_stat.extrusion_length; + printf("Average volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_avg); + m_stat.reset(); +#endif + } + + return output_buffer.data(); +} + +// Is a white space? +static inline bool is_ws(const char c) { return c == ' ' || c == '\t'; } +// Is it an end of line? Consider a comment to be an end of line as well. +static inline bool is_eol(const char c) { return c == 0 || c == '\r' || c == '\n' || c == ';'; }; +// Is it a white space or end of line? +static inline bool is_ws_or_eol(const char c) { return is_ws(c) || is_eol(c); }; + +// Eat whitespaces. +static void eatws(const char *&line) +{ + while (is_ws(*line)) + ++ line; +} + +// Parse an int starting at the current position of a line. +// If succeeded, the line pointer is advanced. +static inline int parse_int(const char *&line) +{ + char *endptr = NULL; + long result = strtol(line, &endptr, 10); + if (endptr == NULL || !is_ws_or_eol(*endptr)) + throw std::runtime_error("GCodePressureEqualizer: Error parsing an int"); + line = endptr; + return int(result); +}; + +// Parse an int starting at the current position of a line. +// If succeeded, the line pointer is advanced. +static inline float parse_float(const char *&line) +{ + char *endptr = NULL; + float result = strtof(line, &endptr); + if (endptr == NULL || !is_ws_or_eol(*endptr)) + throw std::runtime_error("GCodePressureEqualizer: Error parsing a float"); + line = endptr; + return result; +}; + +#define EXTRUSION_ROLE_TAG ";_EXTRUSION_ROLE:" +bool GCodePressureEqualizer::process_line(const char *line, const size_t len, GCodeLine &buf) +{ + if (strncmp(line, EXTRUSION_ROLE_TAG, strlen(EXTRUSION_ROLE_TAG)) == 0) { + line += strlen(EXTRUSION_ROLE_TAG); + int role = atoi(line); + this->m_current_extrusion_role = ExtrusionRole(role); + return false; + } + + // Set the type, copy the line to the buffer. + buf.type = GCODELINETYPE_OTHER; + buf.modified = false; + if (buf.raw.size() < len + 1) + buf.raw.assign(line, line + len + 1); + else + memcpy(buf.raw.data(), line, len); + buf.raw[len] = 0; + buf.raw_length = len; + + memcpy(buf.pos_start, m_current_pos, sizeof(float)*5); + memcpy(buf.pos_end, m_current_pos, sizeof(float)*5); + memset(buf.pos_provided, 0, 5); + + buf.volumetric_extrusion_rate = 0.f; + buf.volumetric_extrusion_rate_start = 0.f; + buf.volumetric_extrusion_rate_end = 0.f; + buf.max_volumetric_extrusion_rate_slope_positive = 0.f; + buf.max_volumetric_extrusion_rate_slope_negative = 0.f; + buf.extrusion_role = m_current_extrusion_role; + + // Parse the G-code line, store the result into the buf. + switch (toupper(*line ++)) { + case 'G': { + int gcode = parse_int(line); + eatws(line); + switch (gcode) { + case 0: + case 1: + { + // G0, G1: A FFF 3D printer does not make a difference between the two. + float new_pos[5]; + memcpy(new_pos, m_current_pos, sizeof(float)*5); + bool changed[5] = { false, false, false, false, false }; + while (!is_eol(*line)) { + char axis = toupper(*line++); + int i = -1; + switch (axis) { + case 'X': + case 'Y': + case 'Z': + i = axis - 'X'; + break; + case 'E': + i = 3; + break; + case 'F': + i = 4; + break; + default: + assert(false); + } + if (i == -1) + throw std::runtime_error(std::string("GCodePressureEqualizer: Invalid axis for G0/G1: ") + axis); + buf.pos_provided[i] = true; + new_pos[i] = parse_float(line); + if (i == 3 && m_config->use_relative_e_distances.value) + new_pos[i] += m_current_pos[i]; + changed[i] = new_pos[i] != m_current_pos[i]; + eatws(line); + } + if (changed[3]) { + // Extrusion, retract or unretract. + float diff = new_pos[3] - m_current_pos[3]; + if (diff < 0) { + buf.type = GCODELINETYPE_RETRACT; + m_retracted = true; + } else if (! changed[0] && ! changed[1] && ! changed[2]) { + // assert(m_retracted); + buf.type = GCODELINETYPE_UNRETRACT; + m_retracted = false; + } else { + assert(changed[0] || changed[1]); + // Moving in XY plane. + buf.type = GCODELINETYPE_EXTRUDE; + // Calculate the volumetric extrusion rate. + float diff[4]; + for (size_t i = 0; i < 4; ++ i) + diff[i] = new_pos[i] - m_current_pos[i]; + // volumetric extrusion rate = A_filament * F_xyz * L_e / L_xyz [mm^3/min] + float len2 = diff[0]*diff[0]+diff[1]*diff[1]+diff[2]*diff[2]; + float rate = m_filament_crossections[m_current_extruder] * new_pos[4] * sqrt((diff[3]*diff[3])/len2); + buf.volumetric_extrusion_rate = rate; + buf.volumetric_extrusion_rate_start = rate; + buf.volumetric_extrusion_rate_end = rate; + m_stat.update(rate, sqrt(len2)); + if (rate < 10.f) { + printf("Extremely low flow rate: %f\n", rate); + } + } + } else if (changed[0] || changed[1] || changed[2]) { + // Moving without extrusion. + buf.type = GCODELINETYPE_MOVE; + } + memcpy(m_current_pos, new_pos, sizeof(float) * 5); + break; + } + case 92: + { + // G92 : Set Position + // Set a logical coordinate position to a new value without actually moving the machine motors. + // Which axes to set? + bool set = false; + while (!is_eol(*line)) { + char axis = toupper(*line++); + switch (axis) { + case 'X': + case 'Y': + case 'Z': + m_current_pos[axis - 'X'] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f; + set = true; + break; + case 'E': + m_current_pos[3] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f; + set = true; + break; + default: + throw std::runtime_error(std::string("GCodePressureEqualizer: Incorrect axis in a G92 G-code: ") + axis); + } + eatws(line); + } + assert(set); + break; + } + case 10: + case 22: + // Firmware retract. + buf.type = GCODELINETYPE_RETRACT; + m_retracted = true; + break; + case 11: + case 23: + // Firmware unretract. + buf.type = GCODELINETYPE_UNRETRACT; + m_retracted = false; + break; + default: + // Ignore the rest. + break; + } + break; + } + case 'M': { + int mcode = parse_int(line); + eatws(line); + switch (mcode) { + default: + // Ignore the rest of the M-codes. + break; + } + break; + } + case 'T': + { + // Activate an extruder head. + int new_extruder = parse_int(line); + if (new_extruder != m_current_extruder) { + m_current_extruder = new_extruder; + m_retracted = true; + buf.type = GCODELINETYPE_TOOL_CHANGE; + } else { + buf.type = GCODELINETYPE_NOOP; + } + break; + } + } + + buf.extruder_id = m_current_extruder; + memcpy(buf.pos_end, m_current_pos, sizeof(float)*5); + + adjust_volumetric_rate(); + return true; +} + +void GCodePressureEqualizer::output_gcode_line(GCodeLine &line) +{ + if (! line.modified) { + push_to_output(line.raw.data(), line.raw_length, true); + return; + } + + // The line was modified. + // Find the comment. + const char *comment = line.raw.data(); + while (*comment != ';' && *comment != 0) ++comment; + if (*comment != ';') + comment = NULL; + + // Emit the line with lowered extrusion rates. + float l2 = line.dist_xyz2(); + float l = sqrt(l2); + size_t nSegments = size_t(ceil(l / m_max_segment_length)); + char text[2048]; + if (nSegments == 1) { + // Just update this segment. + push_line_to_output(line, line.feedrate() * line.volumetric_correction_avg(), comment); + } else { + bool accelerating = line.volumetric_extrusion_rate_start < line.volumetric_extrusion_rate_end; + // Update the initial and final feed rate values. + line.pos_start[4] = line.volumetric_extrusion_rate_start * line.pos_end[4] / line.volumetric_extrusion_rate; + line.pos_end [4] = line.volumetric_extrusion_rate_end * line.pos_end[4] / line.volumetric_extrusion_rate; + float feed_avg = 0.5f * (line.pos_start[4] + line.pos_end[4]); + // Limiting volumetric extrusion rate slope for this segment. + float max_volumetric_extrusion_rate_slope = accelerating ? + line.max_volumetric_extrusion_rate_slope_positive : line.max_volumetric_extrusion_rate_slope_negative; + // Total time for the segment, corrected for the possibly lowered volumetric feed rate, + // if accelerating / decelerating over the complete segment. + float t_total = line.dist_xyz() / feed_avg; + // Time of the acceleration / deceleration part of the segment, if accelerating / decelerating + // with the maximum volumetric extrusion rate slope. + float t_acc = 0.5f * (line.volumetric_extrusion_rate_start + line.volumetric_extrusion_rate_end) / max_volumetric_extrusion_rate_slope; + float l_acc = l; + float l_steady = 0.f; + if (t_acc < t_total) { + // One may achieve higher print speeds if part of the segment is not speed limited. + float l_acc = t_acc * feed_avg; + float l_steady = l - l_acc; + if (l_steady < 0.5f * m_max_segment_length) { + l_acc = l; + l_steady = 0.f; + } else + nSegments = size_t(ceil(l_acc / m_max_segment_length)); + } + float pos_start[5]; + float pos_end [5]; + float pos_end2 [4]; + memcpy(pos_start, line.pos_start, sizeof(float)*5); + memcpy(pos_end , line.pos_end , sizeof(float)*5); + if (l_steady > 0.f) { + // There will be a steady feed segment emitted. + if (accelerating) { + // Prepare the final steady feed rate segment. + memcpy(pos_end2, pos_end, sizeof(float)*4); + float t = l_acc / l; + for (int i = 0; i < 4; ++ i) { + pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t; + line.pos_provided[i] = true; + } + } else { + // Emit the steady feed rate segment. + float t = l_steady / l; + for (int i = 0; i < 4; ++ i) { + line.pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t; + line.pos_provided[i] = true; + } + push_line_to_output(line, pos_start[4], comment); + comment = NULL; + memcpy(line.pos_start, line.pos_end, sizeof(float)*5); + memcpy(pos_start, line.pos_end, sizeof(float)*5); + } + } + // Split the segment into pieces. + for (size_t i = 1; i < nSegments; ++ i) { + float t = float(i) / float(nSegments); + for (size_t j = 0; j < 4; ++ j) { + line.pos_end[j] = pos_start[j] + (pos_end[j] - pos_start[j]) * t; + line.pos_provided[j] = true; + } + // Interpolate the feed rate at the center of the segment. + push_line_to_output(line, pos_start[4] + (pos_end[4] - pos_start[4]) * (float(i) - 0.5f) / float(nSegments), comment); + comment = NULL; + memcpy(line.pos_start, line.pos_end, sizeof(float)*5); + } + if (l_steady > 0.f && accelerating) { + for (int i = 0; i < 4; ++ i) { + line.pos_end[i] = pos_end2[i]; + line.pos_provided[i] = true; + } + push_line_to_output(line, pos_end[4], comment); + } + } +} + +void GCodePressureEqualizer::adjust_volumetric_rate() +{ + if (circular_buffer_items < 2) + return; + + // Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes. + const size_t idx_head = circular_buffer_idx_head(); + const size_t idx_tail = circular_buffer_idx_prev(circular_buffer_idx_tail()); + size_t idx = idx_tail; + if (idx == idx_head || ! circular_buffer[idx].extruding()) + // Nothing to do, the last move is not extruding. + return; + + float feedrate_per_extrusion_role[numExtrusionRoles]; + for (size_t i = 0; i < numExtrusionRoles; ++ i) + feedrate_per_extrusion_role[i] = FLT_MAX; + feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_start; + + bool modified = true; + while (modified && idx != idx_head) { + size_t idx_prev = circular_buffer_idx_prev(idx); + for (; ! circular_buffer[idx_prev].extruding() && idx_prev != idx_head; idx_prev = circular_buffer_idx_prev(idx_prev)) ; + if (! circular_buffer[idx_prev].extruding()) + break; + float rate_succ = circular_buffer[idx].volumetric_extrusion_rate_start; + // What is the gradient of the extrusion rate between idx_prev and idx? + idx = idx_prev; + GCodeLine &line = circular_buffer[idx]; + for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) { + float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].negative; + if (rate_slope == 0) + // The negative rate is unlimited. + continue; + float rate_end = feedrate_per_extrusion_role[iRole]; + if (iRole == line.extrusion_role && rate_succ < rate_end) + rate_end = rate_succ; + if (line.volumetric_extrusion_rate_end > rate_end) { + line.volumetric_extrusion_rate_end = rate_end; + line.modified = true; + } else if (iRole == line.extrusion_role) { + rate_end = line.volumetric_extrusion_rate_end; + } else if (rate_end == FLT_MAX) { + // The rate for ExtrusionRole iRole is unlimited. + continue; + } else { + // Use the original, 'floating' extrusion rate as a starting point for the limiter. + } +// modified = false; + float rate_start = rate_end + rate_slope * line.time_corrected(); + if (rate_start < line.volumetric_extrusion_rate_start) { + // Limit the volumetric extrusion rate at the start of this segment due to a segment + // of ExtrusionType iRole, which will be extruded in the future. + line.volumetric_extrusion_rate_start = rate_start; + line.max_volumetric_extrusion_rate_slope_negative = rate_slope; + line.modified = true; +// modified = true; + } + feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_start : rate_start; + } + } + + // Go forward and adjust the feedrate to decrease the slope of the extrusion rate changes. + for (size_t i = 0; i < numExtrusionRoles; ++ i) + feedrate_per_extrusion_role[i] = FLT_MAX; + feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_end; + + assert(circular_buffer[idx].extruding()); + while (idx != idx_tail) { + size_t idx_next = circular_buffer_idx_next(idx); + for (; ! circular_buffer[idx_next].extruding() && idx_next != idx_tail; idx_next = circular_buffer_idx_next(idx_next)) ; + if (! circular_buffer[idx_next].extruding()) + break; + float rate_prec = circular_buffer[idx].volumetric_extrusion_rate_end; + // What is the gradient of the extrusion rate between idx_prev and idx? + idx = idx_next; + GCodeLine &line = circular_buffer[idx]; + for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) { + float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].positive; + if (rate_slope == 0) + // The positive rate is unlimited. + continue; + float rate_start = feedrate_per_extrusion_role[iRole]; + if (iRole == line.extrusion_role && rate_prec < rate_start) + rate_start = rate_prec; + if (line.volumetric_extrusion_rate_start > rate_start) { + line.volumetric_extrusion_rate_start = rate_start; + line.modified = true; + } else if (iRole == line.extrusion_role) { + rate_start = line.volumetric_extrusion_rate_start; + } else if (rate_start == FLT_MAX) { + // The rate for ExtrusionRole iRole is unlimited. + continue; + } else { + // Use the original, 'floating' extrusion rate as a starting point for the limiter. + } + float rate_end = (rate_slope == 0) ? FLT_MAX : rate_start + rate_slope * line.time_corrected(); + if (rate_end < line.volumetric_extrusion_rate_end) { + // Limit the volumetric extrusion rate at the start of this segment due to a segment + // of ExtrusionType iRole, which was extruded before. + line.volumetric_extrusion_rate_end = rate_end; + line.max_volumetric_extrusion_rate_slope_positive = rate_slope; + line.modified = true; + } + feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_end : rate_end; + } + } +} + +void GCodePressureEqualizer::push_axis_to_output(const char axis, const float value, bool add_eol) +{ + char buf[2048]; + int len = sprintf(buf, + (axis == 'E') ? " %c%.3f" : " %c%.5f", + axis, value); + push_to_output(buf, len, add_eol); +} + +void GCodePressureEqualizer::push_to_output(const char *text, const size_t len, bool add_eol) +{ + // New length of the output buffer content. + size_t len_new = output_buffer_length + len + 1; + if (add_eol) + ++ len_new; + + // Resize the output buffer to a power of 2 higher than the required memory. + if (output_buffer.size() < len_new) { + size_t v = len_new; + // Compute the next highest power of 2 of 32-bit v + // http://graphics.stanford.edu/~seander/bithacks.html + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + output_buffer.resize(v); + } + + // Copy the text to the output. + if (len != 0) { + memcpy(output_buffer.data() + output_buffer_length, text, len); + output_buffer_length += len; + } + if (add_eol) + output_buffer[output_buffer_length ++] = '\n'; + output_buffer[output_buffer_length] = 0; +} + +void GCodePressureEqualizer::push_line_to_output(const GCodeLine &line, const float new_feedrate, const char *comment) +{ + push_to_output("G1", 2, false); + for (size_t i = 0; i < 3; ++ i) + if (line.pos_provided[i]) + push_axis_to_output('X'+i, line.pos_end[i]); + push_axis_to_output('E', m_config->use_relative_e_distances.value ? (line.pos_end[3] - line.pos_start[3]) : line.pos_end[3]); +// if (line.pos_provided[4] || fabs(line.feedrate() - new_feedrate) > 1e-5) + push_axis_to_output('F', new_feedrate); + // output comment and EOL + push_to_output(comment, (comment == NULL) ? 0 : strlen(comment), true); +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/GCode/PressureEqualizer.hpp b/xs/src/libslic3r/GCode/PressureEqualizer.hpp new file mode 100644 index 000000000..d774148e7 --- /dev/null +++ b/xs/src/libslic3r/GCode/PressureEqualizer.hpp @@ -0,0 +1,209 @@ +#ifndef slic3r_GCode_PressureEqualizer_hpp_ +#define slic3r_GCode_PressureEqualizer_hpp_ + +#include "../libslic3r.h" +#include "../PrintConfig.hpp" +#include "../ExtrusionEntity.hpp" + +namespace Slic3r { + +// Processes a G-code. Finds changes in the volumetric extrusion speed and adjusts the transitions +// between these paths to limit fast changes in the volumetric extrusion speed. +class GCodePressureEqualizer +{ +public: + GCodePressureEqualizer(const Slic3r::GCodeConfig *config); + ~GCodePressureEqualizer(); + + void reset(); + + // Process a next batch of G-code lines. Flush the internal buffers if asked for. + const char* process(const char *szGCode, bool flush); + + size_t get_output_buffer_length() const { return output_buffer_length; } + +private: + struct Statistics + { + void reset() { + volumetric_extrusion_rate_min = std::numeric_limits::max(); + volumetric_extrusion_rate_max = 0.f; + volumetric_extrusion_rate_avg = 0.f; + extrusion_length = 0.f; + } + void update(float volumetric_extrusion_rate, float length) { + volumetric_extrusion_rate_min = std::min(volumetric_extrusion_rate_min, volumetric_extrusion_rate); + volumetric_extrusion_rate_max = std::max(volumetric_extrusion_rate_max, volumetric_extrusion_rate); + volumetric_extrusion_rate_avg += volumetric_extrusion_rate * length; + extrusion_length += length; + } + float volumetric_extrusion_rate_min; + float volumetric_extrusion_rate_max; + float volumetric_extrusion_rate_avg; + float extrusion_length; + }; + + struct Statistics m_stat; + + // Keeps the reference, does not own the config. + const Slic3r::GCodeConfig *m_config; + + // Private configuration values + // How fast could the volumetric extrusion rate increase / decrase? mm^3/sec^2 + struct ExtrusionRateSlope { + float positive; + float negative; + }; + enum { numExtrusionRoles = erSupportMaterialInterface + 1 }; + ExtrusionRateSlope m_max_volumetric_extrusion_rate_slopes[numExtrusionRoles]; + float m_max_volumetric_extrusion_rate_slope_positive; + float m_max_volumetric_extrusion_rate_slope_negative; + // Maximum segment length to split a long segment, if the initial and the final flow rate differ. + float m_max_segment_length; + + // Configuration extracted from config. + // Area of the crossestion of each filament. Necessary to calculate the volumetric flow rate. + std::vector m_filament_crossections; + + // Internal data. + // X,Y,Z,E,F + float m_current_pos[5]; + size_t m_current_extruder; + ExtrusionRole m_current_extrusion_role; + bool m_retracted; + + enum GCodeLineType + { + GCODELINETYPE_INVALID, + GCODELINETYPE_NOOP, + GCODELINETYPE_OTHER, + GCODELINETYPE_RETRACT, + GCODELINETYPE_UNRETRACT, + GCODELINETYPE_TOOL_CHANGE, + GCODELINETYPE_MOVE, + GCODELINETYPE_EXTRUDE, + }; + + struct GCodeLine + { + GCodeLine() : + type(GCODELINETYPE_INVALID), + raw_length(0), + modified(false), + extruder_id(0), + volumetric_extrusion_rate(0.f), + volumetric_extrusion_rate_start(0.f), + volumetric_extrusion_rate_end(0.f) + {} + + bool moving_xy() const { return fabs(pos_end[0] - pos_start[0]) > 0.f || fabs(pos_end[1] - pos_start[1]) > 0.f; } + bool moving_z () const { return fabs(pos_end[2] - pos_start[2]) > 0.f; } + bool extruding() const { return moving_xy() && pos_end[3] > pos_start[3]; } + bool retracting() const { return pos_end[3] < pos_start[3]; } + bool deretracting() const { return ! moving_xy() && pos_end[3] > pos_start[3]; } + + float dist_xy2() const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]); } + float dist_xyz2() const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]) + (pos_end[2] - pos_start[2]) * (pos_end[2] - pos_start[2]); } + float dist_xy() const { return sqrt(dist_xy2()); } + float dist_xyz() const { return sqrt(dist_xyz2()); } + float dist_e() const { return fabs(pos_end[3] - pos_start[3]); } + + float feedrate() const { return pos_end[4]; } + float time() const { return dist_xyz() / feedrate(); } + float time_inv() const { return feedrate() / dist_xyz(); } + float volumetric_correction_avg() const { + float avg_correction = 0.5f * (volumetric_extrusion_rate_start + volumetric_extrusion_rate_end) / volumetric_extrusion_rate; + assert(avg_correction > 0.f); + assert(avg_correction <= 1.00000001f); + return avg_correction; + } + float time_corrected() const { return time() * volumetric_correction_avg(); } + + GCodeLineType type; + + // We try to keep the string buffer once it has been allocated, so it will not be reallocated over and over. + std::vector raw; + size_t raw_length; + // If modified, the raw text has to be adapted by the new extrusion rate, + // or maybe the line needs to be split into multiple lines. + bool modified; + + // float timeStart; + // float timeEnd; + // X,Y,Z,E,F. Storing the state of the currently active extruder only. + float pos_start[5]; + float pos_end[5]; + // Was the axis found on the G-code line? X,Y,Z,F + bool pos_provided[5]; + + // Index of the active extruder. + size_t extruder_id; + // Extrusion role of this segment. + ExtrusionRole extrusion_role; + + // Current volumetric extrusion rate. + float volumetric_extrusion_rate; + // Volumetric extrusion rate at the start of this segment. + float volumetric_extrusion_rate_start; + // Volumetric extrusion rate at the end of this segment. + float volumetric_extrusion_rate_end; + + // Volumetric extrusion rate slope limiting this segment. + // If set to zero, the slope is unlimited. + float max_volumetric_extrusion_rate_slope_positive; + float max_volumetric_extrusion_rate_slope_negative; + }; + + // Circular buffer of GCode lines. The circular buffer size will be limited to circular_buffer_size. + std::vector circular_buffer; + // Current position of the circular buffer (index, where to write the next line to, the line has to be pushed out before it is overwritten). + size_t circular_buffer_pos; + // Circular buffer size, configuration value. + size_t circular_buffer_size; + // Number of valid lines in the circular buffer. Lower or equal to circular_buffer_size. + size_t circular_buffer_items; + + // Output buffer will only grow. It will not be reallocated over and over. + std::vector output_buffer; + size_t output_buffer_length; + + bool process_line(const char *line, const size_t len, GCodeLine &buf); + void output_gcode_line(GCodeLine &buf); + + // Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes. + // Then go forward and adjust the feedrate to decrease the slope of the extrusion rate changes. + void adjust_volumetric_rate(); + + // Push the text to the end of the output_buffer. + void push_to_output(const char *text, const size_t len, bool add_eol = true); + // Push an axis assignment to the end of the output buffer. + void push_axis_to_output(const char axis, const float value, bool add_eol = false); + // Push a G-code line to the output, + void push_line_to_output(const GCodeLine &line, const float new_feedrate, const char *comment); + + size_t circular_buffer_idx_head() const { + size_t idx = circular_buffer_pos + circular_buffer_size - circular_buffer_items; + if (idx >= circular_buffer_size) + idx -= circular_buffer_size; + return idx; + } + + size_t circular_buffer_idx_tail() const { return circular_buffer_pos; } + + size_t circular_buffer_idx_prev(size_t idx) const { + idx += circular_buffer_size - 1; + if (idx >= circular_buffer_size) + idx -= circular_buffer_size; + return idx; + } + + size_t circular_buffer_idx_next(size_t idx) const { + if (++ idx >= circular_buffer_size) + idx -= circular_buffer_size; + return idx; + } +}; + +} // namespace Slic3r + +#endif /* slic3r_GCode_PressureEqualizer_hpp_ */ diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index 7bb207e00..e9153923f 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -235,7 +235,7 @@ PerimeterGenerator::process() // append perimeters for this slice as a collection if (!entities.empty()) this->loops->append(entities); - } + } // for each loop of an island // fill gaps if (!gaps.empty()) { @@ -311,7 +311,7 @@ PerimeterGenerator::process() for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) this->fill_surfaces->surfaces.push_back(Surface(stInternal, *ex)); // use a bogus surface type } - } + } // for each island } ExtrusionEntityCollection diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index d3f6c48e5..945be6513 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -19,6 +19,7 @@ REGISTER_CLASS(Wipe, "GCode::Wipe"); REGISTER_CLASS(GCode, "GCode"); REGISTER_CLASS(GCodeSender, "GCode::Sender"); REGISTER_CLASS(GCodeWriter, "GCode::Writer"); +REGISTER_CLASS(GCodePressureEqualizer, "GCode::PressureEqualizer"); REGISTER_CLASS(Layer, "Layer"); REGISTER_CLASS(SupportLayer, "Layer::Support"); REGISTER_CLASS(LayerRegion, "Layer::Region"); diff --git a/xs/xsp/Filler.xsp b/xs/xsp/Filler.xsp index b7871664f..d03113bb1 100644 --- a/xs/xsp/Filler.xsp +++ b/xs/xsp/Filler.xsp @@ -3,6 +3,7 @@ %{ #include #include "libslic3r/Fill/FillBase.hpp" +#include "libslic3r/PolylineCollection.hpp" %} %name{Slic3r::Filler} class Filler { diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 7f17a3a12..4ecee70a9 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -107,6 +107,11 @@ void set_enable_cooling_markers(bool value) %code{% THIS->enable_cooling_markers = value; %}; + bool enable_extrusion_role_markers() + %code{% RETVAL = THIS->enable_extrusion_role_markers; %}; + void set_enable_extrusion_role_markers(bool value) + %code{% THIS->enable_extrusion_role_markers = value; %}; + int layer_count() %code{% RETVAL = THIS->layer_count; %}; void set_layer_count(int value) diff --git a/xs/xsp/GCodePressureEqualizer.xsp b/xs/xsp/GCodePressureEqualizer.xsp new file mode 100644 index 000000000..39372e0e0 --- /dev/null +++ b/xs/xsp/GCodePressureEqualizer.xsp @@ -0,0 +1,32 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "libslic3r/GCode/PressureEqualizer.hpp" +%} + +%name{Slic3r::GCode::PressureEqualizer} class GCodePressureEqualizer { + GCodePressureEqualizer(StaticPrintConfig* config) + %code%{ RETVAL = new GCodePressureEqualizer(dynamic_cast(config)); %}; + ~GCodePressureEqualizer(); + + void reset(); + + // Process a next batch of G-code lines. Flush the internal buffers if asked for. +// const char* process(const char *szGCode, bool flush); +// std::string process(const char *szGCode, bool flush) +// %code{% const char *out = THIS->process(szGCode, flush); RETVAL = (out == NULL) ? "" : std::string(out); %}; + +%{ + +SV* +GCodePressureEqualizer::process(const char *szGCode, bool flush) + CODE: + const char *out = THIS->process(szGCode, flush); + RETVAL = newSVpv(out, THIS->get_output_buffer_length()); + OUTPUT: + RETVAL + +%} + +}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 4d4d5d997..cca5649bf 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -209,6 +209,10 @@ GCodeWriter* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +GCodePressureEqualizer* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + BridgeDetector* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 3453f0067..1c7d27a95 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -102,6 +102,9 @@ %typemap{GCodeSender*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; +%typemap{GCodePressureEqualizer*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; %typemap{BridgeDetector*}; %typemap{Ref}{simple}; %typemap{Clone}{simple};