From 454e6496ceb06880fd4c986e1c0d50be77e627cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 24 May 2022 12:28:14 +0200 Subject: [PATCH] Added concentric infill generated using Arachne. --- src/libslic3r/Fill/Fill.cpp | 64 +++++++++++++++----- src/libslic3r/Fill/FillBase.cpp | 20 ++++--- src/libslic3r/Fill/FillBase.hpp | 16 ++++- src/libslic3r/Fill/FillConcentric.cpp | 85 ++++++++++++++++++++++++--- src/libslic3r/Fill/FillConcentric.hpp | 13 +++- src/libslic3r/PerimeterGenerator.cpp | 4 +- src/libslic3r/PerimeterGenerator.hpp | 2 + src/libslic3r/Polyline.cpp | 28 +++++++++ src/libslic3r/Polyline.hpp | 16 ++++- src/libslic3r/SupportMaterial.cpp | 1 + tests/fff_print/test_fill.cpp | 8 ++- 11 files changed, 217 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index e6b820188..e68d517b4 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -8,10 +8,12 @@ #include "../Print.hpp" #include "../PrintConfig.hpp" #include "../Surface.hpp" +#include "../PerimeterGenerator.hpp" #include "FillBase.hpp" #include "FillRectilinear.hpp" #include "FillLightning.hpp" +#include "FillConcentric.hpp" namespace Slic3r { @@ -329,9 +331,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // this->export_region_fill_surfaces_to_svg_debug("10_fill-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - std::vector surface_fills = group_fills(*this); - const Slic3r::BoundingBox bbox = this->object()->bounding_box(); - const auto resolution = this->object()->print()->config().gcode_resolution.value; + std::vector surface_fills = group_fills(*this); + const Slic3r::BoundingBox bbox = this->object()->bounding_box(); + const auto resolution = this->object()->print()->config().gcode_resolution.value; + const auto slicing_engine = this->object()->config().slicing_engine; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { @@ -352,6 +355,13 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: if (surface_fill.params.pattern == ipLightning) dynamic_cast(f.get())->generator = lightning_generator; + if (object()->config().slicing_engine.value == SlicingEngine::Arachne && surface_fill.params.pattern == ipConcentric) { + FillConcentric *fill_concentric = dynamic_cast(f.get()); + assert(fill_concentric != nullptr); + fill_concentric->print_config = &this->object()->print()->config(); + fill_concentric->print_object_config = &this->object()->config(); + } + // calculate flow spacing for infill pattern generation bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge; double link_max_length = 0.; @@ -372,23 +382,28 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // apply half spacing using this flow's own spacing and generate infill FillParams params; - params.density = float(0.01 * surface_fill.params.density); - params.dont_adjust = false; // surface_fill.params.dont_adjust; + params.density = float(0.01 * surface_fill.params.density); + params.dont_adjust = false; // surface_fill.params.dont_adjust; params.anchor_length = surface_fill.params.anchor_length; - params.anchor_length_max = surface_fill.params.anchor_length_max; - params.resolution = resolution; + params.anchor_length_max = surface_fill.params.anchor_length_max; + params.resolution = resolution; + params.use_arachne = slicing_engine == SlicingEngine::Arachne && surface_fill.params.pattern == ipConcentric; for (ExPolygon &expoly : surface_fill.expolygons) { // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon. f->spacing = surface_fill.params.spacing; surface_fill.surface.expolygon = std::move(expoly); - Polylines polylines; + Polylines polylines; + ThickPolylines thick_polylines; try { - polylines = f->fill_surface(&surface_fill.surface, params); + if (params.use_arachne) + thick_polylines = f->fill_surface_arachne(&surface_fill.surface, params); + else + polylines = f->fill_surface(&surface_fill.surface, params); } catch (InfillFailedException &) { } - if (! polylines.empty()) { - // calculate actual flow from spacing (which might have been adjusted by the infill + if (!polylines.empty() || !thick_polylines.empty()) { + // calculate actual flow from spacing (which might have been adjusted by the infill // pattern generator) double flow_mm3_per_mm = surface_fill.params.flow.mm3_per_mm(); double flow_width = surface_fill.params.flow.width(); @@ -406,10 +421,28 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: m_regions[surface_fill.region_id]->fills.entities.push_back(eec = new ExtrusionEntityCollection()); // Only concentric fills are not sorted. eec->no_sort = f->no_sort(); - extrusion_entities_append_paths( - eec->entities, std::move(polylines), - surface_fill.params.extrusion_role, - flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height()); + if (params.use_arachne) { + for (const ThickPolyline &thick_polyline : thick_polylines) { + Flow new_flow = surface_fill.params.flow.with_spacing(float(f->spacing)); + + ExtrusionPaths paths = thick_polyline_to_extrusion_paths(thick_polyline, surface_fill.params.extrusion_role, new_flow, scaled(0.05), 0); + // Append paths to collection. + if (!paths.empty()) { + if (paths.front().first_point() == paths.back().last_point()) + eec->entities.emplace_back(new ExtrusionLoop(std::move(paths))); + else + for (ExtrusionPath &path : paths) + eec->entities.emplace_back(new ExtrusionPath(std::move(path))); + } + } + + thick_polylines.clear(); + } else { + extrusion_entities_append_paths( + eec->entities, std::move(polylines), + surface_fill.params.extrusion_role, + flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height()); + } } } } @@ -618,6 +651,7 @@ void Layer::make_ironing() surface_fill.expolygon = std::move(expoly); Polylines polylines; try { + assert(!fill_params.use_arachne); polylines = fill.fill_surface(&surface_fill, fill_params); } catch (InfillFailedException &) { } diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index c4f8d6f08..a8d5d20e2 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -82,16 +82,22 @@ Polylines Fill::fill_surface(const Surface *surface, const FillParams ¶ms) Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(scale_(this->overlap - 0.5 * this->spacing))); // Create the infills for each of the regions. Polylines polylines_out; - for (size_t i = 0; i < expp.size(); ++ i) - _fill_surface_single( - params, - surface->thickness_layers, - _infill_direction(surface), - std::move(expp[i]), - polylines_out); + for (ExPolygon &expoly : expp) + _fill_surface_single(params, surface->thickness_layers, _infill_direction(surface), std::move(expoly), polylines_out); return polylines_out; } +ThickPolylines Fill::fill_surface_arachne(const Surface *surface, const FillParams ¶ms) +{ + // Perform offset. + Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(scale_(this->overlap - 0.5 * this->spacing))); + // Create the infills for each of the regions. + ThickPolylines thick_polylines_out; + for (ExPolygon &expoly : expp) + _fill_surface_single(params, surface->thickness_layers, _infill_direction(surface), std::move(expoly), thick_polylines_out); + return thick_polylines_out; +} + // Calculate a new spacing to fill width with possibly integer number of lines, // the first and last line being centered at the interval ends. // This function possibly increases the spacing, never decreases, diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 6c57a64bf..b07cca25e 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -14,6 +14,7 @@ #include "../Exception.hpp" #include "../Utils.hpp" #include "../ExPolygon.hpp" +#include "../PrintConfig.hpp" namespace Slic3r { @@ -57,6 +58,9 @@ struct FillParams // we were requested to complete each loop; // in this case we don't try to make more continuous paths bool complete { false }; + + // For Concentric infill, to switch between Classic and Arachne. + bool use_arachne { false }; }; static_assert(IsTriviallyCopyable::value, "FillParams class is not POD (and it should be - see constructor)."); @@ -103,6 +107,7 @@ public: // Perform the fill. virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); + virtual ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams ¶ms); protected: Fill() : @@ -121,12 +126,19 @@ protected: // The expolygon may be modified by the method to avoid a copy. virtual void _fill_surface_single( - const FillParams & /* params */, + const FillParams & /* params */, unsigned int /* thickness_layers */, - const std::pair & /* direction */, + const std::pair & /* direction */, ExPolygon /* expolygon */, Polylines & /* polylines_out */) {}; + // Used for concentric infill to generate ThickPolylines using Arachne. + virtual void _fill_surface_single(const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon expolygon, + ThickPolylines &thick_polylines_out) {} + virtual float _layer_angle(size_t idx) const { return (idx & 1) ? float(M_PI/2.) : 0; } virtual std::pair _infill_direction(const Surface *surface) const; diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp index d5997552b..f692babc6 100644 --- a/src/libslic3r/Fill/FillConcentric.cpp +++ b/src/libslic3r/Fill/FillConcentric.cpp @@ -1,26 +1,27 @@ #include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" #include "../Surface.hpp" +#include "Arachne/WallToolPaths.hpp" #include "FillConcentric.hpp" namespace Slic3r { void FillConcentric::_fill_surface_single( - const FillParams ¶ms, + const FillParams ¶ms, unsigned int thickness_layers, - const std::pair &direction, + const std::pair &direction, ExPolygon expolygon, Polylines &polylines_out) { // no rotation is supported for this infill pattern BoundingBox bounding_box = expolygon.contour.bounding_box(); - - coord_t min_spacing = scale_(this->spacing); - coord_t distance = coord_t(min_spacing / params.density); - + + coord_t min_spacing = scaled(this->spacing); + coord_t distance = coord_t(min_spacing / params.density); + if (params.density > 0.9999f && !params.dont_adjust) { - distance = this->_adjust_solid_spacing(bounding_box.size()(0), distance); + distance = Slic3r::FillConcentric::_adjust_solid_spacing(bounding_box.size()(0), distance); this->spacing = unscale(distance); } @@ -34,7 +35,7 @@ void FillConcentric::_fill_surface_single( // generate paths from the outermost to the innermost, to avoid // adhesion problems of the first central tiny loops loops = union_pt_chained_outside_in(loops); - + // split paths using a nearest neighbor search size_t iPathFirst = polylines_out.size(); Point last_pos(0, 0); @@ -55,10 +56,76 @@ void FillConcentric::_fill_surface_single( } } if (j < polylines_out.size()) - polylines_out.erase(polylines_out.begin() + j, polylines_out.end()); + polylines_out.erase(polylines_out.begin() + int(j), polylines_out.end()); //TODO: return ExtrusionLoop objects to get better chained paths, // otherwise the outermost loop starts at the closest point to (0, 0). // We want the loops to be split inside the G-code generator to get optimum path planning. } +void FillConcentric::_fill_surface_single(const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon expolygon, + ThickPolylines &thick_polylines_out) +{ + assert(params.use_arachne); + assert(this->print_config != nullptr && this->print_object_config != nullptr); + + // no rotation is supported for this infill pattern + Point bbox_size = expolygon.contour.bounding_box().size(); + coord_t min_spacing = scaled(this->spacing); + + if (params.density > 0.9999f && !params.dont_adjust) { + coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / min_spacing + 1; + Polygons polygons = offset(expolygon, min_spacing / 2); + Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, *this->print_object_config, *this->print_config); + + std::vector loops = wallToolPaths.getToolPaths(); + std::vector all_extrusions; + for (Arachne::VariableWidthLines &loop : loops) { + if (loop.empty()) + continue; + for (const Arachne::ExtrusionLine &wall : loop) + all_extrusions.emplace_back(&wall); + } + + // Split paths using a nearest neighbor search. + size_t firts_poly_idx = thick_polylines_out.size(); + Point last_pos(0, 0); + for (const Arachne::ExtrusionLine *extrusion : all_extrusions) { + if (extrusion->empty()) + continue; + + ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion); + if (extrusion->is_closed && thick_polyline.points.front() == thick_polyline.points.back() && thick_polyline.width.front() == thick_polyline.width.back()) { + thick_polyline.points.pop_back(); + assert(thick_polyline.points.size() * 2 == thick_polyline.width.size()); + int nearest_idx = last_pos.nearest_point_index(thick_polyline.points); + std::rotate(thick_polyline.points.begin(), thick_polyline.points.begin() + nearest_idx, thick_polyline.points.end()); + std::rotate(thick_polyline.width.begin(), thick_polyline.width.begin() + 2 * nearest_idx, thick_polyline.width.end()); + thick_polyline.points.emplace_back(thick_polyline.points.front()); + } + thick_polylines_out.emplace_back(std::move(thick_polyline)); + } + + // clip the paths to prevent the extruder from getting exactly on the first point of the loop + // Keep valid paths only. + size_t j = firts_poly_idx; + for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) { + thick_polylines_out[i].clip_end(this->loop_clipping); + if (thick_polylines_out[i].is_valid()) { + if (j < i) + thick_polylines_out[j] = std::move(thick_polylines_out[i]); + ++j; + } + } + if (j < thick_polylines_out.size()) + thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end()); + } else { + Polylines polylines; + this->_fill_surface_single(params, thickness_layers, direction, expolygon, polylines); + append(thick_polylines_out, to_thick_polylines(std::move(polylines), min_spacing)); + } +} + } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillConcentric.hpp b/src/libslic3r/Fill/FillConcentric.hpp index 8bf01d11d..405b7238b 100644 --- a/src/libslic3r/Fill/FillConcentric.hpp +++ b/src/libslic3r/Fill/FillConcentric.hpp @@ -19,7 +19,18 @@ protected: ExPolygon expolygon, Polylines &polylines_out) override; - bool no_sort() const override { return true; } + void _fill_surface_single(const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon expolygon, + ThickPolylines &thick_polylines_out) override; + + bool no_sort() const override { return true; } + + const PrintConfig *print_config = nullptr; + const PrintObjectConfig *print_object_config = nullptr; + + friend class Layer; }; } // namespace Slic3r diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 00b1f1abf..1c77688eb 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -10,7 +10,7 @@ namespace Slic3r { -static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance) +ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance) { ExtrusionPaths paths; ExtrusionPath path(role); @@ -24,7 +24,7 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi double thickness_delta = fabs(line.a_width - line.b_width); if (thickness_delta > tolerance) { - const unsigned int segments = (unsigned int)ceil(thickness_delta / tolerance); + const auto segments = (unsigned int)ceil(thickness_delta / tolerance); const coordf_t seg_len = line_len / segments; Points pp; std::vector width; diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index 842f73097..d38f6b8fa 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -72,6 +72,8 @@ private: Polygons m_lower_slices_polygons; }; +ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance); + } #endif diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 1255d5473..45ead8643 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -235,6 +235,34 @@ ThickLines ThickPolyline::thicklines() const return lines; } +// Removes the given distance from the end of the ThickPolyline +void ThickPolyline::clip_end(double distance) +{ + while (distance > 0) { + Vec2d last_point = this->last_point().cast(); + coordf_t last_width = this->width.back(); + this->points.pop_back(); + this->width.pop_back(); + if (this->points.empty()) + break; + + Vec2d vec = this->last_point().cast() - last_point; + coordf_t width_diff = this->width.back() - last_width; + double vec_length_sqr = vec.squaredNorm(); + if (vec_length_sqr > distance * distance) { + double t = (distance / std::sqrt(vec_length_sqr)); + this->points.emplace_back((last_point + vec * t).cast()); + this->width.emplace_back(last_width + width_diff * t); + assert(this->width.size() == (this->points.size() - 1) * 2); + return; + } else + this->width.pop_back(); + + distance -= std::sqrt(vec_length_sqr); + } + assert(this->width.size() == (this->points.size() - 1) * 2); +} + Lines3 Polyline3::lines() const { Lines3 lines; diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 31e0b88d3..3277ac7d6 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -64,7 +64,7 @@ public: const Point& leftmost_point() const; Lines lines() const override; - void clip_end(double distance); + virtual void clip_end(double distance); void clip_start(double distance); void extend_end(double distance); void extend_start(double distance); @@ -172,10 +172,24 @@ public: std::swap(this->endpoints.first, this->endpoints.second); } + void clip_end(double distance) override; + std::vector width; std::pair endpoints; }; +inline ThickPolylines to_thick_polylines(Polylines &&polylines, const coordf_t width) +{ + ThickPolylines out; + out.reserve(polylines.size()); + for (Polyline &polyline : polylines) { + out.emplace_back(); + out.back().width.assign((polyline.points.size() - 1) * 2, width); + out.back().points = std::move(polyline.points); + } + return out; +} + class Polyline3 : public MultiPoint3 { public: diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 67bd2639b..92b627a95 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -3243,6 +3243,7 @@ static inline void fill_expolygon_generate_paths( Surface surface(stInternal, std::move(expolygon)); Polylines polylines; try { + assert(!fill_params.use_arachne); polylines = filler->fill_surface(&surface, fill_params); } catch (InfillFailedException &) { } diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index 8e311282e..8ebbbacd4 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -131,7 +131,7 @@ TEST_CASE("Fill: Pattern Path Length", "[Fill]") { FillParams fill_params; fill_params.density = 1.0; filler->spacing = flow.spacing(); - + REQUIRE(!fill_params.use_arachne); // Make this test fail when Arachne is used because this test is not ready for it. for (auto angle : { 0.0, 45.0}) { surface.expolygon.rotate(angle, Point(0,0)); Polylines paths = filler->fill_surface(&surface, fill_params); @@ -442,8 +442,10 @@ bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacin fill_params.density = float(density); fill_params.dont_adjust = false; - Surface surface(stBottom, expolygon); - Slic3r::Polylines paths = filler->fill_surface(&surface, fill_params); + Surface surface(stBottom, expolygon); + if (fill_params.use_arachne) // Make this test fail when Arachne is used because this test is not ready for it. + return false; + Slic3r::Polylines paths = filler->fill_surface(&surface, fill_params); // check whether any part was left uncovered Polygons grown_paths;