diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp index 75e4d5338..de5251639 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp @@ -268,13 +268,13 @@ void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extr { for (const ClipperLib_Z::Path &extrusion_path : extrusion_paths) { ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion_path); - Slic3r::append(dst, thick_polyline_to_multi_path(thick_polyline, role, flow, scaled(0.05), float(SCALED_EPSILON)).paths); + Slic3r::append(dst, PerimeterGenerator::thick_polyline_to_multi_path(thick_polyline, role, flow, scaled(0.05), float(SCALED_EPSILON)).paths); } } void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &extrusion, const ExtrusionRole role, const Flow &flow) { ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion); - Slic3r::append(dst, thick_polyline_to_multi_path(thick_polyline, role, flow, scaled(0.05), float(SCALED_EPSILON)).paths); + Slic3r::append(dst, PerimeterGenerator::thick_polyline_to_multi_path(thick_polyline, role, flow, scaled(0.05), float(SCALED_EPSILON)).paths); } } // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 5ae71616c..252600692 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -8,6 +8,7 @@ #include "../Print.hpp" #include "../PrintConfig.hpp" #include "../Surface.hpp" +// for Arachne based infills #include "../PerimeterGenerator.hpp" #include "FillBase.hpp" @@ -427,7 +428,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: for (const ThickPolyline &thick_polyline : thick_polylines) { Flow new_flow = surface_fill.params.flow.with_spacing(float(f->spacing)); - ExtrusionMultiPath multi_path = thick_polyline_to_multi_path(thick_polyline, surface_fill.params.extrusion_role, new_flow, scaled(0.05), float(SCALED_EPSILON)); + ExtrusionMultiPath multi_path = PerimeterGenerator::thick_polyline_to_multi_path(thick_polyline, surface_fill.params.extrusion_role, new_flow, scaled(0.05), float(SCALED_EPSILON)); // Append paths to collection. if (!multi_path.empty()) { if (multi_path.paths.front().first_point() == multi_path.paths.back().last_point()) diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index b0bfc961e..555da2146 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -72,35 +72,46 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec (this->layer()->id() >= size_t(region_config.bottom_solid_layers.value) && this->layer()->print_z >= region_config.bottom_solid_min_thickness - EPSILON); - PerimeterGenerator g( - // input: - &slices, + PerimeterGenerator::Parameters params( this->layer()->height, + int(this->layer()->id()), this->flow(frPerimeter), - ®ion_config, - &this->layer()->object()->config(), - &print_config, - spiral_vase, - - // output: - &m_perimeters, - &m_thin_fills, - fill_surfaces + this->flow(frExternalPerimeter), + this->bridging_flow(frPerimeter), + this->flow(frSolidInfill), + region_config, + this->layer()->object()->config(), + print_config, + spiral_vase ); - - if (this->layer()->lower_layer != nullptr) - // Cummulative sum of polygons over all the regions. - g.lower_slices = &this->layer()->lower_layer->lslices; - - g.layer_id = (int)this->layer()->id(); - g.ext_perimeter_flow = this->flow(frExternalPerimeter); - g.overhang_flow = this->bridging_flow(frPerimeter); - g.solid_infill_flow = this->flow(frSolidInfill); + + // Cummulative sum of polygons over all the regions. + const ExPolygons *lower_slices = this->layer()->lower_layer ? &this->layer()->lower_layer->lslices : nullptr; + // Cache for offsetted lower_slices + Polygons lower_layer_polygons_cache; if (this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase) - g.process_arachne(); + PerimeterGenerator::process_arachne( + // input: + params, + &slices, + lower_slices, + lower_layer_polygons_cache, + // output: + m_perimeters, + m_thin_fills, + *fill_surfaces); else - g.process_classic(); + PerimeterGenerator::process_classic( + // input: + params, + &slices, + lower_slices, + lower_layer_polygons_cache, + // output: + m_perimeters, + m_thin_fills, + *fill_surfaces); } //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 6456457db..2856323a4 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -21,7 +21,7 @@ namespace Slic3r { -ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance) +ExtrusionMultiPath PerimeterGenerator::thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance, const float merge_tolerance) { ExtrusionMultiPath multi_path; ExtrusionPath path(role); @@ -121,7 +121,7 @@ static void variable_width(const ThickPolylines &polylines, ExtrusionRole role, // of segments, and any pruning shall be performed before we apply this tolerance. const auto tolerance = float(scale_(0.05)); for (const ThickPolyline &p : polylines) { - ExtrusionMultiPath multi_path = thick_polyline_to_multi_path(p, role, flow, tolerance, tolerance); + ExtrusionMultiPath multi_path = PerimeterGenerator::thick_polyline_to_multi_path(p, role, flow, tolerance, tolerance); // Append paths to collection. if (!multi_path.paths.empty()) { for (auto it = std::next(multi_path.paths.begin()); it != multi_path.paths.end(); ++it) { @@ -157,7 +157,15 @@ public: // External perimeter. It may be CCW or CW oriented (outer contour or hole contour). bool is_external() const { return this->depth == 0; } // An island, which may have holes, but it does not have another internal island. - bool is_internal_contour() const; + bool is_internal_contour() const { + // An internal contour is a contour containing no other contours + if (! this->is_contour) + return false; + for (const PerimeterGeneratorLoop &loop : this->children) + if (loop.is_contour) + return false; + return true; + } }; // Thanks Cura developers for this function. @@ -243,7 +251,7 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy using PerimeterGeneratorLoops = std::vector; -static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) +static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) { // loops is an arrayref of ::Loop objects // turn each one into an ExtrusionLoop object @@ -269,30 +277,30 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime const Polygon &polygon = loop.fuzzify ? fuzzified : loop.polygon; if (loop.fuzzify) { fuzzified = loop.polygon; - fuzzy_polygon(fuzzified, scaled(perimeter_generator.config->fuzzy_skin_thickness.value), scaled(perimeter_generator.config->fuzzy_skin_point_dist.value)); + fuzzy_polygon(fuzzified, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value)); } - if (perimeter_generator.config->overhangs && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers - && ! ((perimeter_generator.object_config->support_material || perimeter_generator.object_config->support_material_enforce_layers > 0) && - perimeter_generator.object_config->support_material_contact_distance.value == 0)) { + if (params.config.overhangs && params.layer_id > params.object_config.raft_layers + && ! ((params.object_config.support_material || params.object_config.support_material_enforce_layers > 0) && + params.object_config.support_material_contact_distance.value == 0)) { // get non-overhang paths by intersecting this loop with the grown lower slices extrusion_paths_append( paths, - intersection_pl({ polygon }, perimeter_generator.lower_slices_polygons()), + intersection_pl({ polygon }, lower_slices_polygons_cache), role, - is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(), - is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width(), - (float)perimeter_generator.layer_height); + is_external ? params.ext_mm3_per_mm : params.mm3_per_mm, + is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(), + float(params.layer_height)); // get overhang paths by checking what parts of this loop fall // outside the grown lower slices (thus where the distance between // the loop centerline and original lower slices is >= half nozzle diameter extrusion_paths_append( paths, - diff_pl({ polygon }, perimeter_generator.lower_slices_polygons()), + diff_pl({ polygon }, lower_slices_polygons_cache), erOverhangPerimeter, - perimeter_generator.mm3_per_mm_overhang(), - perimeter_generator.overhang_flow.width(), - perimeter_generator.overhang_flow.height()); + params.mm3_per_mm_overhang, + params.overhang_flow.width(), + params.overhang_flow.height()); // Reapply the nearest point search for starting point. // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. @@ -300,9 +308,9 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime } else { ExtrusionPath path(role); path.polyline = polygon.split_at_first_point(); - path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(); - path.width = is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width(); - path.height = (float)perimeter_generator.layer_height; + path.mm3_per_mm = is_external ? params.ext_mm3_per_mm : params.mm3_per_mm; + path.width = is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(); + path.height = float(params.layer_height); paths.push_back(path); } @@ -311,7 +319,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime // Append thin walls to the nearest-neighbor search (only for first iteration) if (! thin_walls.empty()) { - variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow, coll.entities); + variable_width(thin_walls, erExternalPerimeter, params.ext_perimeter_flow, coll.entities); thin_walls.clear(); } @@ -331,7 +339,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime } else { const PerimeterGeneratorLoop &loop = loops[idx.first]; assert(thin_walls.empty()); - ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls); + ExtrusionEntityCollection children = traverse_loops(params, lower_slices_polygons_cache, loop.children, thin_walls); out.entities.reserve(out.entities.size() + children.entities.size() + 1); ExtrusionLoop *eloop = static_cast(coll.entities[idx.first]); coll.entities[idx.first] = nullptr; @@ -436,7 +444,7 @@ struct PerimeterGeneratorArachneExtrusion bool fuzzify = false; }; -static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator &perimeter_generator, std::vector &pg_extrusions) +static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, std::vector &pg_extrusions) { ExtrusionEntityCollection extrusion_coll; for (PerimeterGeneratorArachneExtrusion &pg_extrusion : pg_extrusions) { @@ -448,13 +456,13 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator &p ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter; if (pg_extrusion.fuzzify) - fuzzy_extrusion_line(*extrusion, scaled(perimeter_generator.config->fuzzy_skin_thickness.value), scaled(perimeter_generator.config->fuzzy_skin_point_dist.value)); + fuzzy_extrusion_line(*extrusion, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value)); ExtrusionPaths paths; // detect overhanging/bridging perimeters - if (perimeter_generator.config->overhangs && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers - && ! ((perimeter_generator.object_config->support_material || perimeter_generator.object_config->support_material_enforce_layers > 0) && - perimeter_generator.object_config->support_material_contact_distance.value == 0)) { + if (params.config.overhangs && params.layer_id > params.object_config.raft_layers + && ! ((params.object_config.support_material || params.object_config.support_material_enforce_layers > 0) && + params.object_config.support_material_contact_distance.value == 0)) { ClipperLib_Z::Path extrusion_path; extrusion_path.reserve(extrusion->size()); @@ -462,8 +470,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator &p extrusion_path.emplace_back(ej.p.x(), ej.p.y(), ej.w); ClipperLib_Z::Paths lower_slices_paths; - lower_slices_paths.reserve(perimeter_generator.lower_slices_polygons().size()); - for (const Polygon &poly : perimeter_generator.lower_slices_polygons()) { + lower_slices_paths.reserve(lower_slices_polygons_cache.size()); + for (const Polygon &poly : lower_slices_polygons_cache) { lower_slices_paths.emplace_back(); ClipperLib_Z::Path &out = lower_slices_paths.back(); out.reserve(poly.points.size()); @@ -473,13 +481,13 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator &p // get non-overhang paths by intersecting this loop with the grown lower slices extrusion_paths_append(paths, clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctIntersection), role, - is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); + is_external ? params.ext_perimeter_flow : params.perimeter_flow); // get overhang paths by checking what parts of this loop fall // outside the grown lower slices (thus where the distance between // the loop centerline and original lower slices is >= half nozzle diameter extrusion_paths_append(paths, clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctDifference), erOverhangPerimeter, - perimeter_generator.overhang_flow); + params.overhang_flow); // Reapply the nearest point search for starting point. // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. @@ -519,7 +527,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator &p chain_and_reorder_extrusion_paths(paths, &start_point); } } else { - extrusion_paths_append(paths, *extrusion, role, is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow); + extrusion_paths_append(paths, *extrusion, role, is_external ? params.ext_perimeter_flow : params.perimeter_flow); } // Append paths to collection. @@ -590,42 +598,48 @@ static void export_perimeters_to_svg(const std::string &path, const Polygons &co // Thanks, Cura developers, for implementing an algorithm for generating perimeters with variable width (Arachne) that is based on the paper // "A framework for adaptive width control of dense contour-parallel toolpaths in fused deposition modeling" -void PerimeterGenerator::process_arachne() +void PerimeterGenerator::process_arachne( + // Inputs: + const Parameters ¶ms, + const SurfaceCollection *slices, + const ExPolygons *lower_slices, + // Cache: + Polygons &lower_slices_polygons_cache, + // Output: + // Loops with the external thin walls + ExtrusionEntityCollection &out_loops, + // Gaps without the thin walls + ExtrusionEntityCollection &out_gap_fill, + // Infills without the gap fills + SurfaceCollection &out_fill_surfaces) { // other perimeters - m_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); - coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); - + coord_t perimeter_spacing = params.perimeter_flow.scaled_spacing(); // external perimeters - m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); - coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); - coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); - coord_t ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); - - // overhang perimeters - m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); - + coord_t ext_perimeter_width = params.ext_perimeter_flow.scaled_width(); + coord_t ext_perimeter_spacing = params.ext_perimeter_flow.scaled_spacing(); + coord_t ext_perimeter_spacing2 = scaled(0.5f * (params.ext_perimeter_flow.spacing() + params.perimeter_flow.spacing())); // solid infill - coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); + coord_t solid_infill_spacing = params.solid_infill_flow.scaled_spacing(); // prepare grown lower layer slices for overhang detection - if (this->lower_slices != nullptr && this->config->overhangs) { + if (lower_slices != nullptr && params.config.overhangs) { // We consider overhang any part where the entire nozzle diameter is not supported by the // lower layer, so we take lower slices and offset them by half the nozzle diameter used // in the current layer - double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1); - m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter/2))); + double nozzle_diameter = params.print_config.nozzle_diameter.get_at(params.config.perimeter_extruder-1); + lower_slices_polygons_cache = offset(*lower_slices, float(scale_(+nozzle_diameter/2))); } // we need to process each island separately because we might have different // extra perimeters for each one - for (const Surface &surface : this->slices->surfaces) { + for (const Surface &surface : slices->surfaces) { // detect how many perimeters must be generated for this island - int loop_number = this->config->perimeters + surface.extra_perimeters - 1; // 0-indexed loops - ExPolygons last = offset_ex(surface.expolygon.simplify_p(m_scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); + int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops + ExPolygons last = offset_ex(surface.expolygon.simplify_p(params.scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); Polygons last_p = to_polygons(last); - Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, layer_height, *this->object_config, *this->print_config); + Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, params.layer_height, params.object_config, params.print_config); std::vector perimeters = wallToolPaths.getToolPaths(); loop_number = int(perimeters.size()) - 1; @@ -651,7 +665,7 @@ void PerimeterGenerator::process_arachne() int end_perimeter = -1; int direction = -1; - if (this->config->external_perimeters_first) { + if (params.config.external_perimeters_first) { start_perimeter = 0; end_perimeter = int(perimeters.size()); direction = 1; @@ -672,7 +686,7 @@ void PerimeterGenerator::process_arachne() for (size_t idx = 0; idx < all_extrusions.size(); idx++) map_extrusion_to_idx.emplace(all_extrusions[idx], idx); - auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, this->config->external_perimeters_first); + auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, params.config.external_perimeters_first); for (auto [before, after] : extrusions_constrains) { auto after_it = map_extrusion_to_idx.find(after); ++blocked[after_it->second]; @@ -736,18 +750,18 @@ void PerimeterGenerator::process_arachne() } } - if (this->layer_id > 0 && this->config->fuzzy_skin != FuzzySkinType::None) { + if (params.layer_id > 0 && params.config.fuzzy_skin != FuzzySkinType::None) { std::vector closed_loop_extrusions; for (PerimeterGeneratorArachneExtrusion &extrusion : ordered_extrusions) if (extrusion.extrusion->inset_idx == 0) { - if (extrusion.extrusion->is_closed && this->config->fuzzy_skin == FuzzySkinType::External) { + if (extrusion.extrusion->is_closed && params.config.fuzzy_skin == FuzzySkinType::External) { closed_loop_extrusions.emplace_back(&extrusion); } else { extrusion.fuzzify = true; } } - if (this->config->fuzzy_skin == FuzzySkinType::External) { + if (params.config.fuzzy_skin == FuzzySkinType::External) { ClipperLib_Z::Paths loops_paths; loops_paths.reserve(closed_loop_extrusions.size()); for (const auto &cl_extrusion : closed_loop_extrusions) { @@ -776,8 +790,8 @@ void PerimeterGenerator::process_arachne() } } - if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(*this, ordered_extrusions); !extrusion_coll.empty()) - this->loops->append(extrusion_coll); + if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(params, lower_slices_polygons_cache, ordered_extrusions); !extrusion_coll.empty()) + out_loops.append(extrusion_coll); ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour()); const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; @@ -796,14 +810,14 @@ void PerimeterGenerator::process_arachne() // two or more loops? perimeter_spacing; - inset = coord_t(scale_(this->config->get_abs_value("infill_overlap", unscale(inset)))); + inset = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(inset)))); Polygons pp; for (ExPolygon &ex : infill_contour) - ex.simplify_p(m_scaled_resolution, &pp); + ex.simplify_p(params.scaled_resolution, &pp); // collapse too narrow infill areas const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); // append infill areas to fill_surfaces - this->fill_surfaces->append( + out_fill_surfaces.append( offset2_ex( union_ex(pp), float(- min_perimeter_infill_spacing / 2.), @@ -812,24 +826,30 @@ void PerimeterGenerator::process_arachne() } } -void PerimeterGenerator::process_classic() +void PerimeterGenerator::process_classic( + // Inputs: + const Parameters ¶ms, + const SurfaceCollection *slices, + const ExPolygons *lower_slices, + // Cache: + Polygons &lower_slices_polygons_cache, + // Output: + // Loops with the external thin walls + ExtrusionEntityCollection &out_loops, + // Gaps without the thin walls + ExtrusionEntityCollection &out_gap_fill, + // Infills without the gap fills + SurfaceCollection &out_fill_surfaces) { // other perimeters - m_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); - coord_t perimeter_width = this->perimeter_flow.scaled_width(); - coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); - + coord_t perimeter_width = params.perimeter_flow.scaled_width(); + coord_t perimeter_spacing = params.perimeter_flow.scaled_spacing(); // external perimeters - m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); - coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); - coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); - coord_t ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); - - // overhang perimeters - m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); - + coord_t ext_perimeter_width = params.ext_perimeter_flow.scaled_width(); + coord_t ext_perimeter_spacing = params.ext_perimeter_flow.scaled_spacing(); + coord_t ext_perimeter_spacing2 = scaled(0.5f * (params.ext_perimeter_flow.spacing() + params.perimeter_flow.spacing())); // solid infill - coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); + coord_t solid_infill_spacing = params.solid_infill_flow.scaled_spacing(); // Calculate the minimum required spacing between two adjacent traces. // This should be equal to the nominal flow spacing but we experiment @@ -843,23 +863,23 @@ void PerimeterGenerator::process_classic() // internal flow which is unrelated. coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE)); coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE)); - bool has_gap_fill = this->config->gap_fill_enabled.value && this->config->gap_fill_speed.value > 0; + bool has_gap_fill = params.config.gap_fill_enabled.value && params.config.gap_fill_speed.value > 0; // prepare grown lower layer slices for overhang detection - if (this->lower_slices != NULL && this->config->overhangs) { + if (lower_slices != nullptr && params.config.overhangs) { // We consider overhang any part where the entire nozzle diameter is not supported by the // lower layer, so we take lower slices and offset them by half the nozzle diameter used // in the current layer - double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1); - m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter/2))); + double nozzle_diameter = params.print_config.nozzle_diameter.get_at(params.config.perimeter_extruder-1); + lower_slices_polygons_cache = offset(*lower_slices, float(scale_(+nozzle_diameter/2))); } // we need to process each island separately because we might have different // extra perimeters for each one - for (const Surface &surface : this->slices->surfaces) { + for (const Surface &surface : slices->surfaces) { // detect how many perimeters must be generated for this island - int loop_number = this->config->perimeters + surface.extra_perimeters - 1; // 0-indexed loops - ExPolygons last = union_ex(surface.expolygon.simplify_p(m_scaled_resolution)); + int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops + ExPolygons last = union_ex(surface.expolygon.simplify_p(params.scaled_resolution)); ExPolygons gaps; if (loop_number >= 0) { // In case no perimeters are to be generated, loop_number will equal to -1. @@ -873,17 +893,17 @@ void PerimeterGenerator::process_classic() if (i == 0) { // the minimum thickness of a single loop is: // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 - offsets = this->config->thin_walls ? + offsets = params.config.thin_walls ? offset2_ex( last, - float(ext_perimeter_width / 2. + ext_min_spacing / 2. - 1), + float(ext_min_spacing / 2. - 1)) : offset_ex(last, - float(ext_perimeter_width / 2.)); // look for thin walls - if (this->config->thin_walls) { + if (params.config.thin_walls) { // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width // (actually, something larger than that still may exist due to mitering or other causes) - coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3)); + coord_t min_width = coord_t(scale_(params.ext_perimeter_flow.nozzle_diameter() / 3)); ExPolygons expp = opening_ex( // medial axis requires non-overlapping geometry diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)), @@ -892,7 +912,7 @@ void PerimeterGenerator::process_classic() for (ExPolygon &ex : expp) ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); } - if (m_spiral_vase && offsets.size() > 1) { + if (params.spiral_vase && offsets.size() > 1) { // Remove all but the largest area polygon. keep_largest_contour_only(offsets); } @@ -900,7 +920,7 @@ void PerimeterGenerator::process_classic() //FIXME Is this offset correct if the line width of the inner perimeters differs // from the line width of the infill? coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing; - offsets = this->config->thin_walls ? + offsets = params.config.thin_walls ? // This path will ensure, that the perimeters do not overfill, as in // prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters // excessively, creating gaps, which then need to be filled in by the not very @@ -933,8 +953,8 @@ void PerimeterGenerator::process_classic() break; } { - const bool fuzzify_contours = this->config->fuzzy_skin != FuzzySkinType::None && i == 0 && this->layer_id > 0; - const bool fuzzify_holes = fuzzify_contours && this->config->fuzzy_skin == FuzzySkinType::All; + const bool fuzzify_contours = params.config.fuzzy_skin != FuzzySkinType::None && i == 0 && params.layer_id > 0; + const bool fuzzify_holes = fuzzify_contours && params.config.fuzzy_skin == FuzzySkinType::All; for (const ExPolygon &expolygon : offsets) { // Outer contour may overlap with an inner contour, // inner contour may overlap with another inner contour, @@ -951,7 +971,7 @@ void PerimeterGenerator::process_classic() } } last = std::move(offsets); - if (i == loop_number && (! has_gap_fill || this->config->fill_density.value == 0)) { + if (i == loop_number && (! has_gap_fill || params.config.fill_density.value == 0)) { // The last run of this loop is executed to collect gaps for gap fill. // As the gap fill is either disabled or not break; @@ -1013,16 +1033,16 @@ void PerimeterGenerator::process_classic() } } // at this point, all loops should be in contours[0] - ExtrusionEntityCollection entities = traverse_loops(*this, contours.front(), thin_walls); + ExtrusionEntityCollection entities = traverse_loops(params, lower_slices_polygons_cache, contours.front(), thin_walls); // if brim will be printed, reverse the order of perimeters so that // we continue inwards after having finished the brim // TODO: add test for perimeter order - if (this->config->external_perimeters_first || - (this->layer_id == 0 && this->object_config->brim_width.value > 0)) + if (params.config.external_perimeters_first || + (params.layer_id == 0 && params.object_config.brim_width.value > 0)) entities.reverse(); // append perimeters for this slice as a collection if (! entities.empty()) - this->loops->append(entities); + out_loops.append(entities); } // for each loop of an island // fill gaps @@ -1039,7 +1059,7 @@ void PerimeterGenerator::process_classic() ex.medial_axis(max, min, &polylines); if (! polylines.empty()) { ExtrusionEntityCollection gap_fill; - variable_width(polylines, erGapFill, this->solid_infill_flow, gap_fill.entities); + variable_width(polylines, erGapFill, params.solid_infill_flow, gap_fill.entities); /* Make sure we don't infill narrow parts that are already gap-filled (we only consider this surface's gaps to reduce the diff() complexity). Growing actual extrusions ensures that gaps not filled by medial axis @@ -1049,7 +1069,7 @@ void PerimeterGenerator::process_classic() //FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing, // therefore it may cover the area, but no the volume. last = diff_ex(last, gap_fill.polygons_covered_by_width(10.f)); - this->gap_fill->append(std::move(gap_fill.entities)); + out_gap_fill.append(std::move(gap_fill.entities)); } } @@ -1066,15 +1086,15 @@ void PerimeterGenerator::process_classic() perimeter_spacing / 2; // only apply infill overlap if we actually have one perimeter if (inset > 0) - inset -= coord_t(scale_(this->config->get_abs_value("infill_overlap", unscale(inset + solid_infill_spacing / 2)))); + inset -= coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(inset + solid_infill_spacing / 2)))); // simplify infill contours according to resolution Polygons pp; for (ExPolygon &ex : last) - ex.simplify_p(m_scaled_resolution, &pp); + ex.simplify_p(params.scaled_resolution, &pp); // collapse too narrow infill areas coord_t min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); // append infill areas to fill_surfaces - this->fill_surfaces->append( + out_fill_surfaces.append( offset2_ex( union_ex(pp), float(- inset - min_perimeter_infill_spacing / 2.), @@ -1083,15 +1103,4 @@ void PerimeterGenerator::process_classic() } // for each island } -bool PerimeterGeneratorLoop::is_internal_contour() const -{ - // An internal contour is a contour containing no other contours - if (! this->is_contour) - return false; - for (const PerimeterGeneratorLoop &loop : this->children) - if (loop.is_contour) - return false; - return true; -} - } diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index ecf09c593..92710c4ee 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -10,70 +10,93 @@ namespace Slic3r { -class PerimeterGenerator { -public: - // Inputs: - const SurfaceCollection *slices; - const ExPolygons *lower_slices; +namespace PerimeterGenerator +{ + +struct Parameters { + Parameters( + double layer_height, + int layer_id, + Flow perimeter_flow, + Flow ext_perimeter_flow, + Flow overhang_flow, + Flow solid_infill_flow, + const PrintRegionConfig &config, + const PrintObjectConfig &object_config, + const PrintConfig &print_config, + const bool spiral_vase) : + layer_height(layer_height), + layer_id(layer_id), + perimeter_flow(perimeter_flow), + ext_perimeter_flow(ext_perimeter_flow), + overhang_flow(overhang_flow), + solid_infill_flow(solid_infill_flow), + config(config), + object_config(object_config), + print_config(print_config), + spiral_vase(spiral_vase), + scaled_resolution(scaled(print_config.gcode_resolution.value)), + mm3_per_mm(perimeter_flow.mm3_per_mm()), + ext_mm3_per_mm(ext_perimeter_flow.mm3_per_mm()), + mm3_per_mm_overhang(overhang_flow.mm3_per_mm()) + { + } + + // Input parameters double layer_height; int layer_id; Flow perimeter_flow; Flow ext_perimeter_flow; Flow overhang_flow; Flow solid_infill_flow; - const PrintRegionConfig *config; - const PrintObjectConfig *object_config; - const PrintConfig *print_config; - // Outputs: - ExtrusionEntityCollection *loops; - ExtrusionEntityCollection *gap_fill; - SurfaceCollection *fill_surfaces; - - PerimeterGenerator( - // Input: - const SurfaceCollection* slices, - double layer_height, - Flow flow, - const PrintRegionConfig* config, - const PrintObjectConfig* object_config, - const PrintConfig* print_config, - const bool spiral_vase, - // Output: - // Loops with the external thin walls - ExtrusionEntityCollection* loops, - // Gaps without the thin walls - ExtrusionEntityCollection* gap_fill, - // Infills without the gap fills - SurfaceCollection* fill_surfaces) - : slices(slices), lower_slices(nullptr), layer_height(layer_height), - layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow), - overhang_flow(flow), solid_infill_flow(flow), - config(config), object_config(object_config), print_config(print_config), - m_spiral_vase(spiral_vase), - m_scaled_resolution(scaled(print_config->gcode_resolution.value)), - loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces), - m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1) - {} + const PrintRegionConfig &config; + const PrintObjectConfig &object_config; + const PrintConfig &print_config; - void process_classic(); - void process_arachne(); - - double ext_mm3_per_mm() const { return m_ext_mm3_per_mm; } - double mm3_per_mm() const { return m_mm3_per_mm; } - double mm3_per_mm_overhang() const { return m_mm3_per_mm_overhang; } - Polygons lower_slices_polygons() const { return m_lower_slices_polygons; } + // Derived parameters + bool spiral_vase; + double scaled_resolution; + double ext_mm3_per_mm; + double mm3_per_mm; + double mm3_per_mm_overhang; private: - bool m_spiral_vase; - double m_scaled_resolution; - double m_ext_mm3_per_mm; - double m_mm3_per_mm; - double m_mm3_per_mm_overhang; - Polygons m_lower_slices_polygons; + Parameters() = delete; }; +void process_classic( + // Inputs: + const Parameters ¶ms, + const SurfaceCollection *slices, + const ExPolygons *lower_slices, + // Cache: + Polygons &lower_slices_polygons_cache, + // Output: + // Loops with the external thin walls + ExtrusionEntityCollection &out_loops, + // Gaps without the thin walls + ExtrusionEntityCollection &out_gap_fill, + // Infills without the gap fills + SurfaceCollection &out_fill_surfaces); + +void process_arachne( + // Inputs: + const Parameters ¶ms, + const SurfaceCollection *slices, + const ExPolygons *lower_slices, + // Cache: + Polygons &lower_slices_polygons_cache, + // Output: + // Loops with the external thin walls + ExtrusionEntityCollection &out_loops, + // Gaps without the thin walls + ExtrusionEntityCollection &out_gap_fill, + // Infills without the gap fills + SurfaceCollection &out_fill_surfaces); + ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, float tolerance, float merge_tolerance); -} +} // namespace PerimeterGenerator +} // namespace Slic3r #endif diff --git a/tests/fff_print/test_perimeters.cpp b/tests/fff_print/test_perimeters.cpp index 54cf37067..8fc75e744 100644 --- a/tests/fff_print/test_perimeters.cpp +++ b/tests/fff_print/test_perimeters.cpp @@ -45,21 +45,29 @@ SCENARIO("Perimeter nesting", "[Perimeters]") ExtrusionEntityCollection loops; ExtrusionEntityCollection gap_fill; SurfaceCollection fill_surfaces; - PerimeterGenerator perimeter_generator( - &slices, + Flow flow(1., 1., 1.); + PerimeterGenerator::Parameters perimeter_generator_params( 1., // layer height - Flow(1., 1., 1.), - static_cast(&config), - static_cast(&config), - static_cast(&config), - false, // spiral_vase - // output: - &loops, &gap_fill, &fill_surfaces); + -1, // layer ID + flow, flow, flow, flow, + static_cast(config), + static_cast(config), + static_cast(config), + false); // spiral_vase + Polygons lower_layer_polygons_cache; // FIXME Lukas H.: Disable this test for Arachne because it is failing and needs more investigation. // if (config.perimeter_generator == PerimeterGeneratorType::Arachne) -// perimeter_generator.process_arachne(); +// PerimeterGenerator::process_arachne(); // else - perimeter_generator.process_classic(); + PerimeterGenerator::process_classic( + // input: + perimeter_generator_params, + &slices, + nullptr, + // cache: + lower_layer_polygons_cache, + // output: + loops, gap_fill, fill_surfaces); THEN("expected number of collections") { REQUIRE(loops.entities.size() == data.expolygons.size());