From ee626eb65a1a41ec7fd3e00f7b39d048c2795516 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 26 Oct 2022 18:41:39 +0200 Subject: [PATCH 01/33] WIP: Layers split into islands, islands overlapping in Z interconnected into a graph with links to the layer above / below. In addition: Members of LayerRegion were made private, public interface const only. this->m_xxx replaced with just m_xxx SurfacesPtr was made a vector of const pointers. --- src/libslic3r/Brim.cpp | 2 +- src/libslic3r/Color.cpp | 2 +- src/libslic3r/ExtrusionEntityCollection.hpp | 12 +- src/libslic3r/Fill/Fill.cpp | 34 +- src/libslic3r/Fill/FillAdaptive.cpp | 4 +- src/libslic3r/Fill/Lightning/Generator.cpp | 4 +- src/libslic3r/GCode.cpp | 45 ++- .../GCode/AvoidCrossingPerimeters.cpp | 10 +- src/libslic3r/GCode/GCodeProcessor.cpp | 6 +- src/libslic3r/GCode/PressureEqualizer.cpp | 2 +- src/libslic3r/GCode/PrintExtents.cpp | 4 +- src/libslic3r/GCode/SeamPlacer.cpp | 4 +- src/libslic3r/GCode/ToolOrdering.cpp | 14 +- src/libslic3r/Layer.cpp | 320 +++++++++++++++--- src/libslic3r/Layer.hpp | 207 +++++++++-- src/libslic3r/LayerRegion.cpp | 68 ++-- src/libslic3r/MultiMaterialSegmentation.cpp | 4 +- src/libslic3r/Preset.hpp | 2 +- src/libslic3r/PrintBase.cpp | 2 +- src/libslic3r/PrintObject.cpp | 141 ++++---- src/libslic3r/PrintObjectSlice.cpp | 43 ++- src/libslic3r/SLA/DefaultSupportTree.cpp | 2 +- src/libslic3r/SupportMaterial.cpp | 24 +- src/libslic3r/SupportSpotsGenerator.cpp | 8 +- src/libslic3r/Surface.hpp | 4 +- src/libslic3r/SurfaceCollection.cpp | 29 +- src/libslic3r/SurfaceCollection.hpp | 13 +- src/libslic3r/TriangleMesh.cpp | 10 +- src/libslic3r/TriangleMesh.hpp | 2 +- src/libslic3r/TriangleSelector.cpp | 4 +- src/slic3r/GUI/GLCanvas3D.cpp | 6 +- src/slic3r/GUI/GUI_App.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 32 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 2 +- src/slic3r/GUI/OptionsGroup.cpp | 2 +- src/slic3r/Utils/AppUpdater.cpp | 4 +- tests/fff_print/test_perimeters.cpp | 17 +- tests/fff_print/test_print.cpp | 6 +- xs/xsp/Layer.xsp | 10 +- 39 files changed, 751 insertions(+), 356 deletions(-) diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 62d886785..061cf1423 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -53,7 +53,7 @@ static ExPolygons get_print_object_bottom_layer_expolygons(const PrintObject &pr { ExPolygons ex_polygons; for (LayerRegion *region : print_object.layers().front()->regions()) - Slic3r::append(ex_polygons, closing_ex(region->slices.surfaces, float(SCALED_EPSILON))); + Slic3r::append(ex_polygons, closing_ex(region->slices().surfaces, float(SCALED_EPSILON))); return ex_polygons; } diff --git a/src/libslic3r/Color.cpp b/src/libslic3r/Color.cpp index 4d3bc6793..6d8daa00a 100644 --- a/src/libslic3r/Color.cpp +++ b/src/libslic3r/Color.cpp @@ -218,7 +218,7 @@ ColorRGBA ColorRGBA::operator * (float value) const for (size_t i = 0; i < 3; ++i) { ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f); } - ret.m_data[3] = this->m_data[3]; + ret.m_data[3] = m_data[3]; return ret; } diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index 1ecda7dd5..a344bb4a9 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -44,7 +44,14 @@ public: } ~ExtrusionEntityCollection() override { clear(); } explicit operator ExtrusionPaths() const; - + + ExtrusionEntitiesPtr::const_iterator cbegin() const { return this->entities.cbegin(); } + ExtrusionEntitiesPtr::const_iterator cend() const { return this->entities.cend(); } + ExtrusionEntitiesPtr::const_iterator begin() const { return this->entities.cbegin(); } + ExtrusionEntitiesPtr::const_iterator end() const { return this->entities.cend(); } + ExtrusionEntitiesPtr::iterator begin() { return this->entities.begin(); } + ExtrusionEntitiesPtr::iterator end() { return this->entities.end(); } + bool is_collection() const override { return true; } ExtrusionRole role() const override { ExtrusionRole out = erNone; @@ -102,6 +109,9 @@ public: { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; } + size_t size() const { return entities.size(); } + // Recursively count paths and loops contained in this collection. + // this->items_count() >= this->size() size_t items_count() const; /// Returns a flattened copy of this ExtrusionEntityCollection. That is, all of the items in its entities vector are not collections. /// You should be iterating over flatten().entities if you are interested in the underlying ExtrusionEntities (and don't care about hierarchy). diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 8278db960..5ae71616c 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -121,8 +121,8 @@ std::vector group_fills(const Layer &layer) bool has_internal_voids = false; for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) { const LayerRegion &layerm = *layer.regions()[region_id]; - region_to_surface_params[region_id].assign(layerm.fill_surfaces.size(), nullptr); - for (const Surface &surface : layerm.fill_surfaces.surfaces) + region_to_surface_params[region_id].assign(layerm.fill_surfaces().size(), nullptr); + for (const Surface &surface : layerm.fill_surfaces()) if (surface.surface_type == stInternalVoid) has_internal_voids = true; else { @@ -180,7 +180,7 @@ std::vector group_fills(const Layer &layer) auto it_params = set_surface_params.find(params); if (it_params == set_surface_params.end()) it_params = set_surface_params.insert(it_params, params); - region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()] = &(*it_params); + region_to_surface_params[region_id][&surface - &layerm.fill_surfaces().surfaces.front()] = &(*it_params); } } @@ -192,9 +192,9 @@ std::vector group_fills(const Layer &layer) for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) { const LayerRegion &layerm = *layer.regions()[region_id]; - for (const Surface &surface : layerm.fill_surfaces.surfaces) + for (const Surface &surface : layerm.fill_surfaces()) if (surface.surface_type != stInternalVoid) { - const SurfaceFillParams *params = region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()]; + const SurfaceFillParams *params = region_to_surface_params[region_id][&surface - &layerm.fill_surfaces().surfaces.front()]; if (params != nullptr) { SurfaceFill &fill = surface_fills[params->idx]; if (fill.region_id == size_t(-1)) { @@ -325,7 +325,7 @@ void export_group_fills_to_svg(const char *path, const std::vector void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) { for (LayerRegion *layerm : m_regions) - layerm->fills.clear(); + layerm->m_fills.clear(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING @@ -420,7 +420,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: } // Save into layer. ExtrusionEntityCollection* eec = nullptr; - m_regions[surface_fill.region_id]->fills.entities.push_back(eec = new ExtrusionEntityCollection()); + m_regions[surface_fill.region_id]->m_fills.entities.push_back(eec = new ExtrusionEntityCollection()); // Only concentric fills are not sorted. eec->no_sort = f->no_sort(); if (params.use_arachne) { @@ -453,16 +453,16 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection. // Why the paths are unpacked? for (LayerRegion *layerm : m_regions) - for (const ExtrusionEntity *thin_fill : layerm->thin_fills.entities) { + for (const ExtrusionEntity *thin_fill : layerm->thin_fills().entities) { ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection()); - layerm->fills.entities.push_back(&collection); + layerm->m_fills.entities.push_back(&collection); collection.entities.push_back(thin_fill->clone()); } #ifndef NDEBUG for (LayerRegion *layerm : m_regions) - for (size_t i = 0; i < layerm->fills.entities.size(); ++ i) - assert(dynamic_cast(layerm->fills.entities[i]) != nullptr); + for (const ExtrusionEntity *e : layerm->fills()) + assert(dynamic_cast(e) != nullptr); #endif } @@ -539,7 +539,7 @@ void Layer::make_ironing() double default_layer_height = this->object()->config().layer_height; for (LayerRegion *layerm : m_regions) - if (! layerm->slices.empty()) { + if (! layerm->slices().empty()) { IroningParams ironing_params; const PrintRegionConfig &config = layerm->region().config(); if (config.ironing && @@ -602,7 +602,7 @@ void Layer::make_ironing() if (iron_everything) { // Check whether there is any non-solid hole in the regions. bool internal_infill_solid = region_config.fill_density.value > 95.; - for (const Surface &surface : ironing_params.layerm->fill_surfaces.surfaces) + for (const Surface &surface : ironing_params.layerm->fill_surfaces()) if ((! internal_infill_solid && surface.surface_type == stInternal) || surface.surface_type == stInternalBridge || surface.surface_type == stInternalVoid) { // Some fill region is not quite solid. Don't iron over the whole surface. iron_completely = false; @@ -611,10 +611,10 @@ void Layer::make_ironing() } if (iron_completely) { // Iron everything. This is likely only good for solid transparent objects. - for (const Surface &surface : ironing_params.layerm->slices.surfaces) + for (const Surface &surface : ironing_params.layerm->slices()) polygons_append(polys, surface.expolygon); } else { - for (const Surface &surface : ironing_params.layerm->slices.surfaces) + for (const Surface &surface : ironing_params.layerm->slices()) if (surface.surface_type == stTop || (iron_everything && surface.surface_type == stBottom)) // stBottomBridge is not being ironed on purpose, as it would likely destroy the bridges. polygons_append(polys, surface.expolygon); @@ -622,7 +622,7 @@ void Layer::make_ironing() if (iron_everything && ! iron_completely) { // Add solid fill surfaces. This may not be ideal, as one will not iron perimeters touching these // solid fill surfaces, but it is likely better than nothing. - for (const Surface &surface : ironing_params.layerm->fill_surfaces.surfaces) + for (const Surface &surface : ironing_params.layerm->fill_surfaces()) if (surface.surface_type == stInternalSolid) polygons_append(infills, surface.expolygon); } @@ -659,7 +659,7 @@ void Layer::make_ironing() if (! polylines.empty()) { // Save into layer. ExtrusionEntityCollection *eec = nullptr; - ironing_params.layerm->fills.entities.push_back(eec = new ExtrusionEntityCollection()); + ironing_params.layerm->m_fills.entities.push_back(eec = new ExtrusionEntityCollection()); // Don't sort the ironing infill lines as they are monotonicly ordered. eec->no_sort = true; extrusion_entities_append_paths( diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 29b343db0..f935d0edf 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -316,9 +316,9 @@ std::pair adaptive_fill_line_spacing(const PrintObject &print_ob for (const Layer *layer : print_object.layers()) for (size_t region_id = 0; region_id < layer->regions().size(); ++ region_id) { RegionFillData &rd = region_fill_data[region_id]; - if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty()) + if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces().empty()) rd.has_adaptive_infill = Tristate::Yes; - if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty()) + if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces().empty()) rd.has_support_infill = Tristate::Yes; } diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index bd83bcfff..185cb60af 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -62,7 +62,7 @@ void Generator::generateInitialInternalOverhangs(const PrintObject &print_object throw_on_cancel_callback(); Polygons infill_area_here; for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions()) - for (const Surface& surface : layerm->fill_surfaces.surfaces) + for (const Surface& surface : layerm->fill_surfaces()) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) append(infill_area_here, to_polygons(surface.expolygon)); @@ -93,7 +93,7 @@ void Generator::generateTrees(const PrintObject &print_object, const std::functi for (int layer_id = int(print_object.layers().size()) - 1; layer_id >= 0; layer_id--) { throw_on_cancel_callback(); for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions()) - for (const Surface &surface : layerm->fill_surfaces.surfaces) + for (const Surface &surface : layerm->fill_surfaces()) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) append(infill_outlines[layer_id], to_polygons(surface.expolygon)); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index e2bd6ef65..c45b6daa9 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -849,7 +849,7 @@ namespace DoExport { region.config().get_abs_value("small_perimeter_speed") == 0 || region.config().get_abs_value("external_perimeter_speed") == 0 || region.config().get_abs_value("bridge_speed") == 0) - mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm()); + mm3_per_mm.push_back(layerm->perimeters().min_mm3_per_mm()); if (region.config().get_abs_value("infill_speed") == 0 || region.config().get_abs_value("solid_infill_speed") == 0 || region.config().get_abs_value("top_solid_infill_speed") == 0 || @@ -866,7 +866,7 @@ namespace DoExport { return min; }; - mm3_per_mm.push_back(min_mm3_per_mm_no_ironing(layerm->fills)); + mm3_per_mm.push_back(min_mm3_per_mm_no_ironing(layerm->fills())); } } } @@ -1502,7 +1502,7 @@ void GCode::process_layers( } }); const auto spiral_vase = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&spiral_vase = *this->m_spiral_vase](LayerResult in) -> LayerResult { + [&spiral_vase = *m_spiral_vase](LayerResult in) -> LayerResult { if (in.nop_layer_result) return in; @@ -1510,18 +1510,18 @@ void GCode::process_layers( return { spiral_vase.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush}; }); const auto pressure_equalizer = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&pressure_equalizer = *this->m_pressure_equalizer](LayerResult in) -> LayerResult { + [&pressure_equalizer = *m_pressure_equalizer](LayerResult in) -> LayerResult { return pressure_equalizer.process_layer(std::move(in)); }); const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&cooling_buffer = *this->m_cooling_buffer](LayerResult in) -> std::string { + [&cooling_buffer = *m_cooling_buffer](LayerResult in) -> std::string { if (in.nop_layer_result) return in.gcode; return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); }); const auto find_replace = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&self = *this->m_find_replace](std::string s) -> std::string { + [&self = *m_find_replace](std::string s) -> std::string { return self.process_layer(std::move(s)); }); const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, @@ -1584,24 +1584,24 @@ void GCode::process_layers( } }); const auto spiral_vase = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&spiral_vase = *this->m_spiral_vase](LayerResult in)->LayerResult { + [&spiral_vase = *m_spiral_vase](LayerResult in)->LayerResult { if (in.nop_layer_result) return in; spiral_vase.enable(in.spiral_vase_enable); return { spiral_vase.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; }); const auto pressure_equalizer = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&pressure_equalizer = *this->m_pressure_equalizer](LayerResult in) -> LayerResult { + [&pressure_equalizer = *m_pressure_equalizer](LayerResult in) -> LayerResult { return pressure_equalizer.process_layer(std::move(in)); }); const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&cooling_buffer = *this->m_cooling_buffer](LayerResult in)->std::string { + [&cooling_buffer = *m_cooling_buffer](LayerResult in)->std::string { if (in.nop_layer_result) return in.gcode; return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); }); const auto find_replace = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&self = *this->m_find_replace](std::string s) -> std::string { + [&self = *m_find_replace](std::string s) -> std::string { return self.process_layer(std::move(s)); }); const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, @@ -2108,8 +2108,8 @@ LayerResult GCode::process_layer( if (enable) { for (const LayerRegion *layer_region : layer.regions()) if (size_t(layer_region->region().config().bottom_solid_layers.value) > layer.id() || - layer_region->perimeters.items_count() > 1u || - layer_region->fills.items_count() > 0) { + layer_region->perimeters().items_count() > 1u || + layer_region->fills().items_count() > 0) { enable = false; break; } @@ -2252,20 +2252,11 @@ LayerResult GCode::process_layer( // option // (Still, we have to keep track of regions because we need to apply their config) size_t n_slices = layer.lslices.size(); - const std::vector &layer_surface_bboxes = layer.lslices_bboxes; + const LayerSlices &layer_surfaces = layer.lslices_ex; // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. - std::vector slices_test_order; - slices_test_order.reserve(n_slices); - for (size_t i = 0; i < n_slices; ++ i) - slices_test_order.emplace_back(i); - std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](size_t i, size_t j) { - const Vec2d s1 = layer_surface_bboxes[i].size().cast(); - const Vec2d s2 = layer_surface_bboxes[j].size().cast(); - return s1.x() * s1.y() < s2.x() * s2.y(); - }); - auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) { - const BoundingBox &bbox = layer_surface_bboxes[i]; + auto point_inside_surface = [&layer, &layer_surfaces](const size_t i, const Point &point) { + const BoundingBox &bbox = layer_surfaces[i].bbox; return point(0) >= bbox.min(0) && point(0) < bbox.max(0) && point(1) >= bbox.min(1) && point(1) < bbox.max(1) && layer.lslices[i].contour.contains(point); @@ -2284,7 +2275,7 @@ LayerResult GCode::process_layer( // The process is almost the same for perimeters and infills - we will do it in a cycle that repeats twice: std::vector printing_extruders; for (const ObjectByExtruder::Island::Region::Type entity_type : { ObjectByExtruder::Island::Region::INFILL, ObjectByExtruder::Island::Region::PERIMETERS }) { - for (const ExtrusionEntity *ee : (entity_type == ObjectByExtruder::Island::Region::INFILL) ? layerm->fills.entities : layerm->perimeters.entities) { + for (const ExtrusionEntity *ee : (entity_type == ObjectByExtruder::Island::Region::INFILL) ? layerm->fills() : layerm->perimeters()) { // extrusions represents infill or perimeter extrusions of a single island. assert(dynamic_cast(ee) != nullptr); const auto *extrusions = static_cast(ee); @@ -2329,7 +2320,9 @@ LayerResult GCode::process_layer( layers.size(), n_slices+1); for (size_t i = 0; i <= n_slices; ++ i) { bool last = i == n_slices; - size_t island_idx = last ? n_slices : slices_test_order[i]; + // Traverse lslices back to front: lslices are produced by traversing ClipperLib::PolyTree, emitting parent contour before its children. + // Therefore traversing layer_surfaces back to front will traverse children contours before their parents. + size_t island_idx = last ? n_slices : layer_surfaces.size() - i - 1; if (// extrusions->first_point does not fit inside any slice last || // extrusions->first_point fits inside ith slice diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 9edb35ee8..c866e13e4 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -487,7 +487,7 @@ static float get_perimeter_spacing(const Layer &layer) size_t regions_count = 0; float perimeter_spacing = 0.f; for (const LayerRegion *layer_region : layer.regions()) - if (layer_region != nullptr && !layer_region->slices.empty()) { + if (layer_region != nullptr && ! layer_region->slices().empty()) { perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); ++regions_count; } @@ -508,7 +508,7 @@ static float get_perimeter_spacing_external(const Layer &layer) for (const PrintObject *object : layer.object()->print()->objects()) if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) for (const LayerRegion *layer_region : l->regions()) - if (layer_region != nullptr && !layer_region->slices.empty()) { + if (layer_region != nullptr && ! layer_region->slices().empty()) { perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); ++ regions_count; } @@ -527,7 +527,7 @@ static float get_external_perimeter_width(const Layer &layer) size_t regions_count = 0; float perimeter_width = 0.f; for (const LayerRegion *layer_region : layer.regions()) - if (layer_region != nullptr && !layer_region->slices.empty()) { + if (layer_region != nullptr && ! layer_region->slices().empty()) { perimeter_width += float(layer_region->flow(frExternalPerimeter).scaled_width()); ++regions_count; } @@ -1070,14 +1070,14 @@ static ExPolygons get_boundary(const Layer &layer) // Collect all top layers that will not be crossed. size_t polygons_count = 0; for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) + for (const Surface &surface : layer_region->fill_surfaces()) if (surface.is_top()) ++polygons_count; if (polygons_count > 0) { ExPolygons top_layer_polygons; top_layer_polygons.reserve(polygons_count); for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) + for (const Surface &surface : layer_region->fill_surfaces()) if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); top_layer_polygons = union_ex(top_layer_polygons); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index c82fd38e5..d9492cb2a 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -2735,8 +2735,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) if (volume_extruded_filament != 0.) m_used_filaments.increase_caches(volume_extruded_filament, - this->m_extruder_id, area_filament_cross_section * this->m_parking_position, - area_filament_cross_section * this->m_extra_loading_move); + m_extruder_id, area_filament_cross_section * m_parking_position, + area_filament_cross_section * m_extra_loading_move); const EMoveType type = move_type(delta_pos); if (type == EMoveType::Extrude) { @@ -4303,7 +4303,7 @@ void GCodeProcessor::process_filaments(CustomGCode::Type code) m_used_filaments.process_color_change_cache(); if (code == CustomGCode::ToolChange) - m_used_filaments.process_extruder_cache(this->m_extruder_id); + m_used_filaments.process_extruder_cache(m_extruder_id); } void GCodeProcessor::simulate_st_synchronize(float additional_time) diff --git a/src/libslic3r/GCode/PressureEqualizer.cpp b/src/libslic3r/GCode/PressureEqualizer.cpp index 7135c0a5c..c8a9618ef 100644 --- a/src/libslic3r/GCode/PressureEqualizer.cpp +++ b/src/libslic3r/GCode/PressureEqualizer.cpp @@ -690,7 +690,7 @@ inline bool is_just_line_with_extrude_set_speed_tag(const std::string &line) void PressureEqualizer::push_line_to_output(const size_t line_idx, const float new_feedrate, const char *comment) { - const GCodeLine &line = this->m_gcode_lines[line_idx]; + const GCodeLine &line = m_gcode_lines[line_idx]; if (line_idx > 0 && output_buffer_length > 0) { const std::string prev_line_str = std::string(output_buffer.begin() + int(this->output_buffer_prev_length), output_buffer.begin() + int(this->output_buffer_length) + 1); diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index a86411519..9a01dc8a6 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -113,8 +113,8 @@ BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object break; BoundingBoxf bbox_this; for (const LayerRegion *layerm : layer->regions()) { - bbox_this.merge(extrusionentity_extents(layerm->perimeters)); - for (const ExtrusionEntity *ee : layerm->fills.entities) + bbox_this.merge(extrusionentity_extents(layerm->perimeters())); + for (const ExtrusionEntity *ee : layerm->fills()) // fill represents infill extrusions of a single island. bbox_this.merge(extrusionentity_extents(*dynamic_cast(ee))); } diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 1d7fd5b3c..c92ff8212 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -401,7 +401,7 @@ struct GlobalModelInfo { Polygons extract_perimeter_polygons(const Layer *layer, std::vector &corresponding_regions_out) { Polygons polygons; for (const LayerRegion *layer_region : layer->regions()) { - for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { + for (const ExtrusionEntity *ex_entity : layer_region->perimeters()) { if (ex_entity->is_collection()) { //collection of inner, outer, and overhang perimeters for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { ExtrusionRole role = perimeter->role(); @@ -1060,7 +1060,7 @@ void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { size_t regions_with_perimeter = 0; for (const LayerRegion *region : po->layers()[layer_idx]->regions()) { - if (region->perimeters.entities.size() > 0) { + if (region->perimeters().size() > 0) { regions_with_perimeter++; } }; diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index c5554c2fa..6c5d0667a 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -226,12 +226,12 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto for (const LayerRegion *layerm : layer->regions()) { const PrintRegion ®ion = layerm->region(); - if (! layerm->perimeters.entities.empty()) { + if (! layerm->perimeters().empty()) { bool something_nonoverriddable = true; if (m_print_config_ptr) { // in this case complete_objects is false (see ToolOrdering constructors) something_nonoverriddable = false; - for (const auto& eec : layerm->perimeters.entities) // let's check if there are nonoverriddable entities + for (const ExtrusionEntity *eec : layerm->perimeters()) // let's check if there are nonoverriddable entities if (!layer_tools.wiping_extrusions().is_overriddable_and_mark(dynamic_cast(*eec), *m_print_config_ptr, object, region)) something_nonoverriddable = true; } @@ -245,7 +245,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto bool has_infill = false; bool has_solid_infill = false; bool something_nonoverriddable = false; - for (const ExtrusionEntity *ee : layerm->fills.entities) { + for (const ExtrusionEntity *ee : layerm->fills()) { // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); ExtrusionRole role = fill->entities.empty() ? erNone : fill->entities.front()->role(); @@ -692,7 +692,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int bool wipe_into_infill_only = ! object->config().wipe_into_objects && region.config().wipe_into_infill; if (print.config().infill_first != perimeters_done || wipe_into_infill_only) { - for (const ExtrusionEntity* ee : layerm->fills.entities) { // iterate through all infill Collections + for (const ExtrusionEntity* ee : layerm->fills()) { // iterate through all infill Collections auto* fill = dynamic_cast(ee); if (!is_overriddable(*fill, print.config(), *object, region)) @@ -716,7 +716,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int // Now the same for perimeters - see comments above for explanation: if (object->config().wipe_into_objects && print.config().infill_first == perimeters_done) { - for (const ExtrusionEntity* ee : layerm->perimeters.entities) { + for (const ExtrusionEntity* ee : layerm->perimeters()) { auto* fill = dynamic_cast(ee); if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume) { set_extruder_override(fill, copy, new_extruder, num_of_copies); @@ -762,7 +762,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) if (!region.config().wipe_into_infill && !object->config().wipe_into_objects) continue; - for (const ExtrusionEntity* ee : layerm->fills.entities) { // iterate through all infill Collections + for (const ExtrusionEntity* ee : layerm->fills()) { // iterate through all infill Collections auto* fill = dynamic_cast(ee); if (!is_overriddable(*fill, print.config(), *object, region) @@ -785,7 +785,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) } // Now the same for perimeters - see comments above for explanation: - for (const ExtrusionEntity* ee : layerm->perimeters.entities) { // iterate through all perimeter Collections + for (const ExtrusionEntity* ee : layerm->perimeters()) { // iterate through all perimeter Collections auto* fill = dynamic_cast(ee); if (is_overriddable(*fill, print.config(), *object, region) && ! is_entity_overridden(fill, copy)) set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies); diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index d273fde96..3bd2566a5 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -22,7 +22,7 @@ Layer::~Layer() bool Layer::empty() const { for (const LayerRegion *layerm : m_regions) - if (layerm != nullptr && ! layerm->slices.empty()) + if (layerm != nullptr && ! layerm->slices().empty()) // Non empty layer. return false; return true; @@ -40,11 +40,11 @@ void Layer::make_slices() ExPolygons slices; if (m_regions.size() == 1) { // optimization: if we only have one region, take its slices - slices = to_expolygons(m_regions.front()->slices.surfaces); + slices = to_expolygons(m_regions.front()->slices().surfaces); } else { Polygons slices_p; for (LayerRegion *layerm : m_regions) - polygons_append(slices_p, to_polygons(layerm->slices.surfaces)); + polygons_append(slices_p, to_polygons(layerm->slices().surfaces)); slices = union_safety_offset_ex(slices_p); } @@ -65,6 +65,245 @@ void Layer::make_slices() this->lslices.emplace_back(std::move(slices[i])); } +// used by Layer::build_up_down_graph() +[[nodiscard]] static ClipperLib_Z::Paths expolygons_to_zpaths(const ExPolygons &expolygons, coord_t isrc) +{ + size_t num_paths = 0; + for (const ExPolygon &expolygon : expolygons) + num_paths += expolygon.num_contours(); + + ClipperLib_Z::Paths out; + out.reserve(num_paths); + + for (const ExPolygon &expolygon : expolygons) { + for (size_t icontour = 0; icontour < expolygon.num_contours(); ++ icontour) { + const Polygon &contour = expolygon.contour_or_hole(icontour); + out.emplace_back(); + ClipperLib_Z::Path &path = out.back(); + path.reserve(contour.size()); + for (const Point &p : contour.points) + path.push_back({ p.x(), p.y(), isrc }); + } + ++ isrc; + } + + return out; +} + +// used by Layer::build_up_down_graph() +static void connect_layer_slices( + Layer &below, + Layer &above, + const ClipperLib_Z::PolyTree &polytree, + const std::vector> &intersections, + const coord_t offset_below, + const coord_t offset_above, + const coord_t offset_end) +{ + class Visitor { + public: + Visitor(const std::vector> &intersections, + Layer &below, Layer &above, const coord_t offset_below, const coord_t offset_above, const coord_t offset_end) : + m_intersections(intersections), m_below(below), m_above(above), m_offset_below(offset_below), m_offset_above(offset_above), m_offset_end(offset_end) {} + + void visit(const ClipperLib_Z::PolyNode &polynode) + { + if (polynode.Contour.size() >= 3) { + int32_t i = 0, j = 0; + double area = 0; + for (int icontour = 0; icontour <= polynode.ChildCount(); ++ icontour) { + const ClipperLib_Z::Path &contour = icontour == 0 ? polynode.Contour : polynode.Childs[icontour - 1]->Contour; + if (contour.size() >= 3) { + area = ClipperLib_Z::Area(contour); + int32_t i = contour.front().z(); + int32_t j = i; + if (i < 0) { + std::tie(i, j) = m_intersections[-i - 1]; + } else { + for (const ClipperLib_Z::IntPoint& pt : contour) { + j = pt.z(); + if (j < 0) { + std::tie(i, j) = m_intersections[-j - 1]; + goto end; + } + else if (i != j) + goto end; + } + } + } + } + end: + bool found = false; + if (i == j) { + // The contour is completely inside another contour. + Point pt(polynode.Contour.front().x(), polynode.Contour.front().y()); + if (i < m_offset_above) { + // Index of an island below. Look-it up in the island above. + assert(i >= m_offset_below); + i -= m_offset_below; + for (int l = int(m_above.lslices_ex.size()) - 1; l >= 0; -- l) { + LayerSlice &lslice = m_above.lslices_ex[l]; + if (lslice.bbox.contains(pt) && m_above.lslices[l].contains(pt)) { + found = true; + j = l; + break; + } + } + } else { + // Index of an island above. Look-it up in the island below. + assert(j < m_offset_end); + j -= m_offset_below; + for (int l = int(m_below.lslices_ex.size()) - 1; l >= 0; -- l) { + LayerSlice &lslice = m_below.lslices_ex[l]; + if (lslice.bbox.contains(pt) && m_below.lslices[l].contains(pt)) { + found = true; + i = l; + break; + } + } + } + } else { + if (i > j) + std::swap(i, j); + assert(i >= m_offset_below); + assert(i < m_offset_above); + i -= m_offset_below; + assert(j >= m_offset_above); + assert(j < m_offset_end); + j -= m_offset_above; + found = true; + } + if (found) { + // Subtract area of holes from the area of outer contour. + for (int icontour = 0; icontour < polynode.ChildCount(); ++ icontour) + area -= ClipperLib_Z::Area(polynode.Childs[icontour]->Contour); + // Store the links and area into the contours. + LayerSlice::Links &links_below = m_below.lslices_ex[i].overlaps_above; + LayerSlice::Links &links_above = m_above.lslices_ex[i].overlaps_below; + LayerSlice::Link key{ j }; + auto it_below = std::lower_bound(links_below.begin(), links_below.end(), key, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; }); + if (it_below != links_below.end() && it_below->slice_idx == j) { + it_below->area += area; + } else { + auto it_above = std::lower_bound(links_above.begin(), links_above.end(), key, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; }); + if (it_above != links_above.end() && it_above->slice_idx == i) { + it_above->area += area; + } else { + // Insert into one of the two vectors. + bool take_below = false; + if (links_below.size() < LayerSlice::LinksStaticSize) + take_below = false; + else if (links_above.size() >= LayerSlice::LinksStaticSize) { + size_t shift_below = links_below.end() - it_below; + size_t shift_above = links_above.end() - it_above; + take_below = shift_below < shift_above; + } + if (take_below) + links_below.insert(it_below, { j, float(area) }); + else + links_above.insert(it_above, { i, float(area) }); + } + } + } + } + for (int i = 0; i < polynode.ChildCount(); ++ i) + for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++ j) + this->visit(*polynode.Childs[i]->Childs[j]); + } + + private: + const std::vector> &m_intersections; + Layer &m_below; + Layer &m_above; + const coord_t m_offset_below; + const coord_t m_offset_above; + const coord_t m_offset_end; + } visitor(intersections, below, above, offset_below, offset_above, offset_end); + + for (int i = 0; i < polytree.ChildCount(); ++ i) + visitor.visit(*polytree.Childs[i]); + +#ifndef NDEBUG + // Verify that only one directional link is stored: either from bottom slice up or from upper slice down. + for (int32_t islice = 0; islice < below.lslices_ex.size(); ++ islice) { + LayerSlice::Links &links1 = below.lslices_ex[islice].overlaps_above; + for (LayerSlice::Link &link1 : links1) { + LayerSlice::Links &links2 = above.lslices_ex[link1.slice_idx].overlaps_below; + assert(! std::binary_search(links2.begin(), links2.end(), link1, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; })); + } + } + for (int32_t islice = 0; islice < above.lslices_ex.size(); ++ islice) { + LayerSlice::Links &links1 = above.lslices_ex[islice].overlaps_above; + for (LayerSlice::Link &link1 : links1) { + LayerSlice::Links &links2 = below.lslices_ex[link1.slice_idx].overlaps_below; + assert(! std::binary_search(links2.begin(), links2.end(), link1, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; })); + } + } +#endif // NDEBUG + + // Scatter the links, but don't sort them yet. + for (int32_t islice = 0; islice < below.lslices_ex.size(); ++ islice) + for (LayerSlice::Link &link : below.lslices_ex[islice].overlaps_above) + above.lslices_ex[link.slice_idx].overlaps_below.push_back({ islice, link.area }); + for (int32_t islice = 0; islice < above.lslices_ex.size(); ++ islice) + for (LayerSlice::Link &link : above.lslices_ex[islice].overlaps_below) + below.lslices_ex[link.slice_idx].overlaps_above.push_back({ islice, link.area }); + // Sort the links. + for (LayerSlice &lslice : below.lslices_ex) + std::sort(lslice.overlaps_above.begin(), lslice.overlaps_above.end(), [](const LayerSlice::Link &l, const LayerSlice::Link &r){ return l.slice_idx < r.slice_idx; }); + for (LayerSlice &lslice : above.lslices_ex) + std::sort(lslice.overlaps_below.begin(), lslice.overlaps_below.end(), [](const LayerSlice::Link &l, const LayerSlice::Link &r){ return l.slice_idx < r.slice_idx; }); +} + +void Layer::build_up_down_graph(Layer& below, Layer& above) +{ + coord_t paths_below_offset = 0; + ClipperLib_Z::Paths paths_below = expolygons_to_zpaths(below.lslices, paths_below_offset); + coord_t paths_above_offset = paths_below_offset + coord_t(below.lslices.size()); + ClipperLib_Z::Paths paths_above = expolygons_to_zpaths(above.lslices, paths_above_offset); + coord_t paths_end = paths_above_offset + coord_t(above.lslices.size()); + + class ZFill { + public: + ZFill() = default; + void reset() { m_intersections.clear(); } + void operator()( + const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top, + const ClipperLib_Z::IntPoint& e2bot, const ClipperLib_Z::IntPoint& e2top, + ClipperLib_Z::IntPoint& pt) { + coord_t srcs[4]{ e1bot.z(), e1top.z(), e2bot.z(), e2top.z() }; + coord_t* begin = srcs; + coord_t* end = srcs + 4; + std::sort(begin, end); + end = std::unique(begin, end); + assert(begin + 2 == end); + if (begin + 1 == end) + pt.z() = *begin; + else if (begin + 2 <= end) { + // store a -1 based negative index into the "intersections" vector here. + m_intersections.emplace_back(srcs[0], srcs[1]); + pt.z() = -coord_t(m_intersections.size()); + } + } + const std::vector>& intersections() const { return m_intersections; } + + private: + std::vector> m_intersections; + } zfill; + + ClipperLib_Z::Clipper clipper; + ClipperLib_Z::PolyTree result; + clipper.ZFillFunction( + [&zfill](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, + const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) + { return zfill(e1bot, e1top, e2bot, e2top, pt); }); + clipper.AddPaths(paths_below, ClipperLib_Z::ptSubject, true); + clipper.AddPaths(paths_above, ClipperLib_Z::ptClip, true); + clipper.Execute(ClipperLib_Z::ctIntersection, result, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + + connect_layer_slices(below, above, result, zfill.intersections(), paths_below_offset, paths_above_offset, paths_end); +} + static inline bool layer_needs_raw_backup(const Layer *layer) { return ! (layer->regions().size() == 1 && (layer->id() > 0 || layer->object()->config().elefant_foot_compensation.value == 0)); @@ -74,10 +313,10 @@ void Layer::backup_untyped_slices() { if (layer_needs_raw_backup(this)) { for (LayerRegion *layerm : m_regions) - layerm->raw_slices = to_expolygons(layerm->slices.surfaces); + layerm->m_raw_slices = to_expolygons(layerm->slices().surfaces); } else { assert(m_regions.size() == 1); - m_regions.front()->raw_slices.clear(); + m_regions.front()->m_raw_slices.clear(); } } @@ -85,10 +324,10 @@ void Layer::restore_untyped_slices() { if (layer_needs_raw_backup(this)) { for (LayerRegion *layerm : m_regions) - layerm->slices.set(layerm->raw_slices, stInternal); + layerm->m_slices.set(layerm->m_raw_slices, stInternal); } else { assert(m_regions.size() == 1); - m_regions.front()->slices.set(this->lslices, stInternal); + m_regions.front()->m_slices.set(this->lslices, stInternal); } } @@ -101,13 +340,13 @@ void Layer::restore_untyped_slices_no_extra_perimeters() if (layer_needs_raw_backup(this)) { for (LayerRegion *layerm : m_regions) if (! layerm->region().config().extra_perimeters.value) - layerm->slices.set(layerm->raw_slices, stInternal); + layerm->m_slices.set(layerm->m_raw_slices, stInternal); } else { assert(m_regions.size() == 1); LayerRegion *layerm = m_regions.front(); // This optimization is correct, as extra_perimeters are only reused by prepare_infill() with multi-regions. //if (! layerm->region().config().extra_perimeters.value) - layerm->slices.set(this->lslices, stInternal); + layerm->m_slices.set(this->lslices, stInternal); } } @@ -125,7 +364,7 @@ ExPolygons Layer::merged(float offset_scaled) const const PrintRegionConfig &config = layerm->region().config(); // Our users learned to bend Slic3r to produce empty volumes to act as subtracters. Only add the region if it is non-empty. if (config.bottom_solid_layers > 0 || config.top_solid_layers > 0 || config.fill_density > 0. || config.perimeters > 0) - append(polygons, offset(layerm->slices.surfaces, offset_scaled)); + append(polygons, offset(layerm->slices().surfaces, offset_scaled)); } ExPolygons out = union_ex(polygons); if (offset_scaled2 != 0.f) @@ -143,11 +382,12 @@ void Layer::make_perimeters() // keep track of regions whose perimeters we have already generated std::vector done(m_regions.size(), false); + LayerRegionPtrs layerms; for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) - if ((*layerm)->slices.empty()) { - (*layerm)->perimeters.clear(); - (*layerm)->fills.clear(); - (*layerm)->thin_fills.clear(); + if ((*layerm)->slices().empty()) { + (*layerm)->m_perimeters.clear(); + (*layerm)->m_fills.clear(); + (*layerm)->m_thin_fills.clear(); } else { size_t region_id = layerm - m_regions.begin(); if (done[region_id]) @@ -157,10 +397,10 @@ void Layer::make_perimeters() const PrintRegionConfig &config = (*layerm)->region().config(); // find compatible regions - LayerRegionPtrs layerms; + layerms.clear(); layerms.push_back(*layerm); for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it) - if (! (*it)->slices.empty()) { + if (! (*it)->slices().empty()) { LayerRegion* other_layerm = *it; const PrintRegionConfig &other_config = other_layerm->region().config(); if (config.perimeter_extruder == other_config.perimeter_extruder @@ -178,18 +418,20 @@ void Layer::make_perimeters() && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness && config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist) { - other_layerm->perimeters.clear(); - other_layerm->fills.clear(); - other_layerm->thin_fills.clear(); + other_layerm->m_perimeters.clear(); + other_layerm->m_fills.clear(); + other_layerm->m_thin_fills.clear(); layerms.push_back(other_layerm); done[it - m_regions.begin()] = true; } } + SurfaceCollection fill_surfaces; if (layerms.size() == 1) { // optimization - (*layerm)->fill_surfaces.surfaces.clear(); - (*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces); - (*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces); + (*layerm)->m_fill_expolygons.clear(); + (*layerm)->m_fill_surfaces.clear(); + (*layerm)->make_perimeters((*layerm)->slices(), &fill_surfaces); + (*layerm)->m_fill_expolygons = to_expolygons(fill_surfaces.surfaces); } else { SurfaceCollection new_slices; // Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence. @@ -198,29 +440,25 @@ void Layer::make_perimeters() // group slices (surfaces) according to number of extra perimeters std::map slices; // extra_perimeters => [ surface, surface... ] for (LayerRegion *layerm : layerms) { - for (const Surface &surface : layerm->slices.surfaces) + for (const Surface &surface : layerm->slices()) slices[surface.extra_perimeters].emplace_back(surface); if (layerm->region().config().fill_density > layerm_config->region().config().fill_density) layerm_config = layerm; + layerm->m_fill_surfaces.clear(); + layerm->m_fill_expolygons.clear(); } // merge the surfaces assigned to each group for (std::pair &surfaces_with_extra_perimeters : slices) new_slices.append(offset_ex(surfaces_with_extra_perimeters.second, ClipperSafetyOffset), surfaces_with_extra_perimeters.second.front()); } - // make perimeters - SurfaceCollection fill_surfaces; layerm_config->make_perimeters(new_slices, &fill_surfaces); - // assign fill_surfaces to each layer - if (!fill_surfaces.surfaces.empty()) { - for (LayerRegionPtrs::iterator l = layerms.begin(); l != layerms.end(); ++l) { - // Separate the fill surfaces. - ExPolygons expp = intersection_ex(fill_surfaces.surfaces, (*l)->slices.surfaces); - (*l)->fill_expolygons = expp; - (*l)->fill_surfaces.set(std::move(expp), fill_surfaces.surfaces.front()); - } - } + if (! fill_surfaces.empty()) { + // Separate the fill surfaces. + for (LayerRegion *l : layerms) + l->m_fill_expolygons = intersection_ex(fill_surfaces.surfaces, l->slices().surfaces); + } } } BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done"; @@ -230,7 +468,7 @@ void Layer::export_region_slices_to_svg(const char *path) const { BoundingBox bbox; for (const auto *region : m_regions) - for (const auto &surface : region->slices.surfaces) + for (const auto &surface : region->slices()) bbox.merge(get_extents(surface.expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min(0), bbox.max(1)); @@ -239,7 +477,7 @@ void Layer::export_region_slices_to_svg(const char *path) const SVG svg(path, bbox); const float transparency = 0.5f; for (const auto *region : m_regions) - for (const auto &surface : region->slices.surfaces) + for (const auto &surface : region->slices()) svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency); export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); @@ -256,7 +494,7 @@ void Layer::export_region_fill_surfaces_to_svg(const char *path) const { BoundingBox bbox; for (const auto *region : m_regions) - for (const auto &surface : region->slices.surfaces) + for (const auto &surface : region->slices()) bbox.merge(get_extents(surface.expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min(0), bbox.max(1)); @@ -265,7 +503,7 @@ void Layer::export_region_fill_surfaces_to_svg(const char *path) const SVG svg(path, bbox); const float transparency = 0.5f; for (const auto *region : m_regions) - for (const auto &surface : region->slices.surfaces) + for (const auto &surface : region->slices()) svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency); export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); @@ -281,9 +519,9 @@ void Layer::export_region_fill_surfaces_to_svg_debug(const char *name) const BoundingBox get_extents(const LayerRegion &layer_region) { BoundingBox bbox; - if (!layer_region.slices.surfaces.empty()) { - bbox = get_extents(layer_region.slices.surfaces.front()); - for (auto it = layer_region.slices.surfaces.cbegin() + 1; it != layer_region.slices.surfaces.cend(); ++it) + if (! layer_region.slices().empty()) { + bbox = get_extents(layer_region.slices().surfaces.front()); + for (auto it = layer_region.slices().surfaces.cbegin() + 1; it != layer_region.slices().surfaces.cend(); ++ it) bbox.merge(get_extents(*it)); } return bbox; diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index e11c740e6..9a1a77aea 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -2,10 +2,13 @@ #define slic3r_Layer_hpp_ #include "libslic3r.h" +#include "BoundingBox.hpp" #include "Flow.hpp" #include "SurfaceCollection.hpp" #include "ExtrusionEntityCollection.hpp" +#include + namespace Slic3r { class ExPolygon; @@ -25,50 +28,38 @@ namespace FillLightning { class Generator; }; -namespace FillLightning { - class Generator; -}; - class LayerRegion { public: - Layer* layer() { return m_layer; } - const Layer* layer() const { return m_layer; } - const PrintRegion& region() const { return *m_region; } + [[nodiscard]] Layer* layer() { return m_layer; } + [[nodiscard]] const Layer* layer() const { return m_layer; } + [[nodiscard]] const PrintRegion& region() const { return *m_region; } // collection of surfaces generated by slicing the original geometry // divided by type top/bottom/internal - SurfaceCollection slices; - // Backed up slices before they are split into top/bottom/internal. - // Only backed up for multi-region layers or layers with elephant foot compensation. - //FIXME Review whether not to simplify the code by keeping the raw_slices all the time. - ExPolygons raw_slices; + [[nodiscard]] const SurfaceCollection& slices() const { return m_slices; } + + [[nodiscard]] const ExPolygons& fill_expolygons() const { return m_fill_expolygons; } + + // collection of surfaces generated by slicing the original geometry + // divided by type top/bottom/internal + [[nodiscard]] const SurfaceCollection& fill_surfaces() const { return m_fill_surfaces; } // collection of extrusion paths/loops filling gaps // These fills are generated by the perimeter generator. // They are not printed on their own, but they are copied to this->fills during infill generation. - ExtrusionEntityCollection thin_fills; - - // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") - // and for re-starting of infills. - ExPolygons fill_expolygons; - // collection of surfaces for infill generation - SurfaceCollection fill_surfaces; - - // collection of expolygons representing the bridged areas (thus not - // needing support material) -// Polygons bridged; + [[nodiscard]] const ExtrusionEntityCollection& thin_fills() const { return m_thin_fills; } // collection of polylines representing the unsupported bridge edges - Polylines unsupported_bridge_edges; + [[nodiscard]] const Polylines& unsupported_bridge_edges() const { return m_unsupported_bridge_edges; } // ordered collection of extrusion paths/loops to build all perimeters // (this collection contains only ExtrusionEntityCollection objects) - ExtrusionEntityCollection perimeters; + [[nodiscard]] const ExtrusionEntityCollection& perimeters() const { return m_perimeters; } // ordered collection of extrusion paths to fill surfaces // (this collection contains only ExtrusionEntityCollection objects) - ExtrusionEntityCollection fills; + [[nodiscard]] const ExtrusionEntityCollection& fills() const { return m_fills; } Flow flow(FlowRole role) const; Flow flow(FlowRole role, double layer_height) const; @@ -92,20 +83,165 @@ public: void export_region_fill_surfaces_to_svg_debug(const char *name) const; // Is there any valid extrusion assigned to this LayerRegion? - bool has_extrusions() const { return ! this->perimeters.entities.empty() || ! this->fills.entities.empty(); } + bool has_extrusions() const { return ! this->perimeters().empty() || ! this->fills().empty(); } protected: friend class Layer; friend class PrintObject; LayerRegion(Layer *layer, const PrintRegion *region) : m_layer(layer), m_region(region) {} - ~LayerRegion() {} + ~LayerRegion() = default; private: - Layer *m_layer; - const PrintRegion *m_region; + // Modifying m_slices + friend std::string fix_slicing_errors(LayerPtrs&, const std::function&); + template + friend void apply_mm_segmentation(PrintObject& print_object, ThrowOnCancel throw_on_cancel); + + Layer *m_layer; + const PrintRegion *m_region; + + // Backed up slices before they are split into top/bottom/internal. + // Only backed up for multi-region layers or layers with elephant foot compensation. + //FIXME Review whether not to simplify the code by keeping the raw_slices all the time. + ExPolygons m_raw_slices; + +//FIXME make m_slices public for unit tests +public: + // collection of surfaces generated by slicing the original geometry + // divided by type top/bottom/internal + SurfaceCollection m_slices; + +private: + // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") + // and for re-starting of infills. + ExPolygons m_fill_expolygons; + + // collection of surfaces for infill generation + SurfaceCollection m_fill_surfaces; + + // collection of extrusion paths/loops filling gaps + // These fills are generated by the perimeter generator. + // They are not printed on their own, but they are copied to this->fills during infill generation. + ExtrusionEntityCollection m_thin_fills; + + // collection of polylines representing the unsupported bridge edges + Polylines m_unsupported_bridge_edges; + + // ordered collection of extrusion paths/loops to build all perimeters + // (this collection contains only ExtrusionEntityCollection objects) + ExtrusionEntityCollection m_perimeters; + + // ordered collection of extrusion paths to fill surfaces + // (this collection contains only ExtrusionEntityCollection objects) + ExtrusionEntityCollection m_fills; + + // collection of expolygons representing the bridged areas (thus not + // needing support material) +// Polygons bridged; }; +// Range of two indices, providing support for range based loops. +template +class IndexRange +{ +public: + IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {} + IndexRange() = default; + + T begin() const { assert(m_begin <= m_end); return m_begin; }; + T end() const { assert(m_begin <= m_end); return m_end; }; + + bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; } + T size() const { assert(m_begin <= m_end); return m_end - m_begin; } + +private: + // Index of the first extrusion in LayerRegion. + T m_begin { 0 }; + // Index of the last extrusion in LayerRegion. + T m_end { 0 }; +}; + +class LayerExtrusionRange : public IndexRange +{ +public: + LayerExtrusionRange(uint32_t iregion, uint32_t ibegin, uint32_t iend) : m_region(iregion), IndexRange(ibegin, iend) {} + LayerExtrusionRange() = default; + + // Index of LayerRegion in Layer. + uint32_t region() const { return m_region; }; + +private: + // Index of LayerRegion in Layer. + uint32_t m_region { 0 }; +}; + +static constexpr const size_t LayerExtrusionRangesStaticSize = 1; +using LayerExtrusionRanges = +#ifdef NDEBUG + // To reduce memory allocation in release mode. + boost::container::small_vector; +#else // NDEBUG + // To ease debugging. + std::vector; +#endif // NDEBUG + +using ExPolygonRange = IndexRange(); + +// LayerSlice contains one or more LayerIsland objects, +// each LayerIsland containing a set of perimeter extrusions extruded with one particular PrintRegionConfig parameters +// and one or multiple +struct LayerIsland +{ + // Perimeter extrusions in LayerRegion belonging to this island. + LayerExtrusionRange perimeters; + // Infill + gapfill extrusions in LayerRegion belonging to this island. + LayerExtrusionRanges fills; + // Region that is to be filled with the fills above. + ExPolygonRange fill_expolygons; + // Centroid of this island used for path planning. + Point centroid; + + bool has_extrusions() const { return ! this->perimeters.empty() || ! this->fills.empty(); } +}; + +static constexpr const size_t LayerIslandsStaticSize = 1; +using LayerIslands = +#ifdef NDEBUG + // To reduce memory allocation in release mode. + boost::container::small_vector; +#else // NDEBUG + // To ease debugging. + std::vector; +#endif // NDEBUG + +// +struct LayerSlice +{ + struct Link { + int32_t slice_idx; + float area; + }; + static constexpr const size_t LinksStaticSize = 4; + using Links = +#ifdef NDEBUG + // To reduce memory allocation in release mode. + boost::container::small_vector; +#else // NDEBUG + // To ease debugging. + std::vector; +#endif // NDEBUG + + BoundingBox bbox; + Links overlaps_above; + Links overlaps_below; + LayerIslands islands; + + bool has_extrusions() const { for (const LayerIsland &island : islands) if (island.has_extrusions()) return true; return false; } +}; + +using LayerSlices = std::vector; + class Layer { public: @@ -131,8 +267,8 @@ public: // order will be applied by the G-code generator to the extrusions fitting into these lslices. // These lslices are also used to detect overhangs and overlaps between successive layers, therefore it is important // that the 1st lslice is not compensated by the Elephant foot compensation algorithm. - ExPolygons lslices; - std::vector lslices_bboxes; + ExPolygons lslices; + LayerSlices lslices_ex; size_t region_count() const { return m_regions.size(); } const LayerRegion* get_region(int idx) const { return m_regions[idx]; } @@ -142,6 +278,8 @@ public: // Test whether whether there are any slices assigned to this layer. bool empty() const; void make_slices(); + // After creating the slices on all layers, chain the islands overlapping in Z. + static void build_up_down_graph(Layer &below, Layer &above); // Backup and restore raw sliced regions if needed. //FIXME Review whether not to simplify the code by keeping the raw_slices all the time. void backup_untyped_slices(); @@ -151,11 +289,11 @@ public: // Slices merged into islands, to be used by the elephant foot compensation to trim the individual surfaces with the shrunk merged slices. ExPolygons merged(float offset) const; template bool any_internal_region_slice_contains(const T &item) const { - for (const LayerRegion *layerm : m_regions) if (layerm->slices.any_internal_contains(item)) return true; + for (const LayerRegion *layerm : m_regions) if (layerm->slices().any_internal_contains(item)) return true; return false; } template bool any_bottom_region_slice_contains(const T &item) const { - for (const LayerRegion *layerm : m_regions) if (layerm->slices.any_bottom_contains(item)) return true; + for (const LayerRegion *layerm : m_regions) if (layerm->slices().any_bottom_contains(item)) return true; return false; } void make_perimeters(); @@ -172,6 +310,7 @@ public: // Is there any valid extrusion assigned to this LayerRegion? virtual bool has_extrusions() const { for (auto layerm : m_regions) if (layerm->has_extrusions()) return true; return false; } +// virtual bool has_extrusions() const { for (const LayerSlice &lslice : lslices_ex) if (lslice.has_extrusions()) return true; return false; } protected: friend class PrintObject; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 1996a58b5..b0bfc961e 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -43,30 +43,26 @@ Flow LayerRegion::bridging_flow(FlowRole role) const } } -// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. +// Fill in layerm->fill_surfaces by trimming the layerm->slices by layerm->fill_expolygons. void LayerRegion::slices_to_fill_surfaces_clipped() { - // Note: this method should be idempotent, but fill_surfaces gets modified - // in place. However we're now only using its boundaries (which are invariant) - // so we're safe. This guarantees idempotence of prepare_infill() also in case - // that combine_infill() turns some fill_surface into VOID surfaces. // Collect polygons per surface type. - std::array by_surface; - for (Surface &surface : this->slices.surfaces) + std::array, size_t(stCount)> by_surface; + for (const Surface &surface : this->slices()) by_surface[size_t(surface.surface_type)].emplace_back(&surface); // Trim surfaces by the fill_boundaries. - this->fill_surfaces.surfaces.clear(); + m_fill_surfaces.surfaces.clear(); for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) { - const SurfacesPtr &this_surfaces = by_surface[surface_type]; + const std::vector &this_surfaces = by_surface[surface_type]; if (! this_surfaces.empty()) - this->fill_surfaces.append(intersection_ex(this_surfaces, this->fill_expolygons), SurfaceType(surface_type)); + m_fill_surfaces.append(intersection_ex(this_surfaces, this->fill_expolygons()), SurfaceType(surface_type)); } } void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces) { - this->perimeters.clear(); - this->thin_fills.clear(); + m_perimeters.clear(); + m_thin_fills.clear(); const PrintConfig &print_config = this->layer()->object()->print()->config(); const PrintRegionConfig ®ion_config = this->region().config(); @@ -87,8 +83,8 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec spiral_vase, // output: - &this->perimeters, - &this->thin_fills, + &m_perimeters, + &m_thin_fills, fill_surfaces ); @@ -131,7 +127,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly // Internal surfaces, not grown. Surfaces internal; // Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed. - Polygons fill_boundaries = to_polygons(this->fill_expolygons); + Polygons fill_boundaries = to_polygons(this->fill_expolygons()); Polygons lower_layer_covered_tmp; // Collect top surfaces and internal surfaces. @@ -141,7 +137,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly { // Voids are sparse infills if infill rate is zero. Polygons voids; - for (const Surface &surface : this->fill_surfaces.surfaces) { + for (const Surface &surface : this->fill_surfaces()) { if (surface.is_top()) { // Collect the top surfaces, inflate them and trim them by the bottom surfaces. // This gives the priority to bottom surfaces. @@ -292,7 +288,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly bridges[idx_last].bridge_angle = bd.angle; if (this->layer()->object()->has_support()) { // polygons_append(this->bridged, bd.coverage()); - append(this->unsupported_bridge_edges, bd.unsupported_edges()); + append(m_unsupported_bridge_edges, bd.unsupported_edges()); } } else if (custom_angle > 0) { // Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in @@ -365,7 +361,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly surfaces_append(new_surfaces, std::move(new_expolys), s1); } - this->fill_surfaces.surfaces = std::move(new_surfaces); + m_fill_surfaces.surfaces = std::move(new_surfaces); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final"); @@ -389,12 +385,12 @@ void LayerRegion::prepare_fill_surfaces() // For Lightning infill, infill_only_where_needed is ignored because both // do a similar thing, and their combination doesn't make much sense. if (! spiral_vase && this->region().config().top_solid_layers == 0) { - for (Surface &surface : this->fill_surfaces.surfaces) + for (Surface &surface : m_fill_surfaces) if (surface.is_top()) surface.surface_type = this->layer()->object()->config().infill_only_where_needed && this->region().config().fill_pattern != ipLightning ? stInternalVoid : stInternal; } if (this->region().config().bottom_solid_layers == 0) { - for (Surface &surface : this->fill_surfaces.surfaces) + for (Surface &surface : m_fill_surfaces) if (surface.is_bottom()) // (surface.surface_type == stBottom) surface.surface_type = stInternal; } @@ -403,7 +399,7 @@ void LayerRegion::prepare_fill_surfaces() if (! spiral_vase && this->region().config().fill_density.value > 0) { // scaling an area requires two calls! double min_area = scale_(scale_(this->region().config().solid_infill_below_area.value)); - for (Surface &surface : this->fill_surfaces.surfaces) + for (Surface &surface : m_fill_surfaces) if (surface.surface_type == stInternal && surface.area() <= min_area) surface.surface_type = stInternalSolid; } @@ -423,38 +419,38 @@ double LayerRegion::infill_area_threshold() const void LayerRegion::trim_surfaces(const Polygons &trimming_polygons) { #ifndef NDEBUG - for (const Surface &surface : this->slices.surfaces) + for (const Surface &surface : this->slices()) assert(surface.surface_type == stInternal); #endif /* NDEBUG */ - this->slices.set(intersection_ex(this->slices.surfaces, trimming_polygons), stInternal); + m_slices.set(intersection_ex(this->slices().surfaces, trimming_polygons), stInternal); } void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons) { #ifndef NDEBUG - for (const Surface &surface : this->slices.surfaces) + for (const Surface &surface : this->slices()) assert(surface.surface_type == stInternal); #endif /* NDEBUG */ - Polygons tmp = intersection(this->slices.surfaces, trimming_polygons); - append(tmp, diff(this->slices.surfaces, opening(this->slices.surfaces, elephant_foot_compensation_perimeter_step))); - this->slices.set(union_ex(tmp), stInternal); + Polygons tmp = intersection(this->slices().surfaces, trimming_polygons); + append(tmp, diff(this->slices().surfaces, opening(this->slices().surfaces, elephant_foot_compensation_perimeter_step))); + m_slices.set(union_ex(tmp), stInternal); } void LayerRegion::export_region_slices_to_svg(const char *path) const { BoundingBox bbox; - for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface) - bbox.merge(get_extents(surface->expolygon)); + for (const Surface &surface : this->slices()) + bbox.merge(get_extents(surface.expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min(0), bbox.max(1)); bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); SVG svg(path, bbox); const float transparency = 0.5f; - for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface) - svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency); - for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) - svg.draw(surface->expolygon.lines(), surface_type_to_color_name(surface->surface_type)); + for (const Surface &surface : this->slices()) + svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency); + for (const Surface &surface : this->fill_surfaces()) + svg.draw(surface.expolygon.lines(), surface_type_to_color_name(surface.surface_type)); export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); } @@ -470,15 +466,15 @@ void LayerRegion::export_region_slices_to_svg_debug(const char *name) const void LayerRegion::export_region_fill_surfaces_to_svg(const char *path) const { BoundingBox bbox; - for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) - bbox.merge(get_extents(surface->expolygon)); + for (const Surface &surface : this->fill_surfaces()) + bbox.merge(get_extents(surface.expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min(0), bbox.max(1)); bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); SVG svg(path, bbox); const float transparency = 0.5f; - for (const Surface &surface : this->fill_surfaces.surfaces) { + for (const Surface &surface : this->fill_surfaces()) { svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency); svg.draw_outline(surface.expolygon, "black", "blue", scale_(0.05)); } diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 21b53c40d..661c2d0fd 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -713,7 +713,7 @@ struct MMU_Graph [[nodiscard]] const Vec2d &point_double() const { return m_point_double; } [[nodiscard]] const Point &point() const { return m_point; } - bool operator==(const CPoint &rhs) const { return this->m_point_double == rhs.m_point_double && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; } + bool operator==(const CPoint &rhs) const { return m_point_double == rhs.m_point_double && m_contour_idx == rhs.m_contour_idx && m_point_idx == rhs.m_point_idx; } }; struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }}; typedef ClosestPointInRadiusLookup CPointLookupType; @@ -1697,7 +1697,7 @@ std::vector> multi_material_segmentation_by_painting(con throw_on_cancel_callback(); ExPolygons ex_polygons; for (LayerRegion *region : layers[layer_idx]->regions()) - for (const Surface &surface : region->slices.surfaces) + for (const Surface &surface : region->slices()) Slic3r::append(ex_polygons, offset_ex(surface.expolygon, float(10 * SCALED_EPSILON))); // All expolygons are expanded by SCALED_EPSILON, merged, and then shrunk again by SCALED_EPSILON // to ensure that very close polygons will be merged. diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index f0a81a71a..74ba2e0f5 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -418,7 +418,7 @@ public: size_t first_compatible_idx(PreferedCondition prefered_condition) const { size_t i = m_default_suppressed ? m_num_default_presets : 0; - size_t n = this->m_presets.size(); + size_t n = m_presets.size(); size_t i_compatible = n; int match_quality = -1; for (; i < n; ++ i) diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index b60401e96..b0aa7bd1e 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -103,7 +103,7 @@ std::string PrintBase::output_filepath(const std::string &path, const std::strin void PrintBase::status_update_warnings(int step, PrintStateBase::WarningLevel /* warning_level */, const std::string &message, const PrintObjectBase* print_object) { - if (this->m_status_callback) { + if (m_status_callback) { auto status = print_object ? SlicingStatus(*print_object, step) : SlicingStatus(*this, step); m_status_callback(status); } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index bdcb034ab..d3ac219b0 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -157,7 +157,7 @@ void PrintObject::make_perimeters() m_print->throw_if_canceled(); LayerRegion &layerm = *m_layers[layer_idx]->get_region(region_id); const LayerRegion &upper_layerm = *m_layers[layer_idx+1]->get_region(region_id); - const Polygons upper_layerm_polygons = to_polygons(upper_layerm.slices.surfaces); + const Polygons upper_layerm_polygons = to_polygons(upper_layerm.slices().surfaces); // Filter upper layer polygons in intersection_ppl by their bounding boxes? // my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ]; const double total_loop_length = total_length(upper_layerm_polygons); @@ -166,7 +166,8 @@ void PrintObject::make_perimeters() const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width(); const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing(); - for (Surface &slice : layerm.slices.surfaces) { + // slice is not const because slice.extra_perimeters is being incremented. + for (Surface &slice : layerm.m_slices.surfaces) { for (;;) { // compute the total thickness of perimeters const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2 @@ -418,7 +419,7 @@ void PrintObject::generate_support_spots() BOOST_LOG_TRIVIAL(debug) << "Searching support spots - start"; m_print->set_status(75, L("Searching support spots")); - if (this->m_config.support_material && !this->m_config.support_material_auto && + if (m_config.support_material && !m_config.support_material_auto && std::all_of(this->model_object()->volumes.begin(), this->model_object()->volumes.end(), [](const ModelVolume* mv){return mv->supported_facets.empty();}) ) { @@ -500,7 +501,7 @@ std::pair PrintObject::prepare m_print->throw_if_canceled(); const Layer *layer = this->layers()[idx_layer]; for (const LayerRegion *layerm : layer->regions()) - for (const Surface &surface : layerm->fill_surfaces.surfaces) + for (const Surface &surface : layerm->fill_surfaces()) if (surface.surface_type == stInternalBridge) append(out, triangulate_expolygon_3d(surface.expolygon, layer->bottom_z())); } @@ -883,13 +884,13 @@ void PrintObject::detect_surfaces_type() Surfaces top; if (upper_layer) { ExPolygons upper_slices = interface_shells ? - diff_ex(layerm->slices.surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) : - diff_ex(layerm->slices.surfaces, upper_layer->lslices, ApplySafetyOffset::Yes); + diff_ex(layerm->slices().surfaces, upper_layer->m_regions[region_id]->slices().surfaces, ApplySafetyOffset::Yes) : + diff_ex(layerm->slices().surfaces, upper_layer->lslices, ApplySafetyOffset::Yes); surfaces_append(top, opening_ex(upper_slices, offset), stTop); } else { // if no upper layer, all surfaces of this one are solid // we clone surfaces because we're going to clear the slices collection - top = layerm->slices.surfaces; + top = layerm->slices().surfaces; for (Surface &surface : top) surface.surface_type = stTop; } @@ -910,7 +911,7 @@ void PrintObject::detect_surfaces_type() surfaces_append( bottom, opening_ex( - diff_ex(layerm->slices.surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), + diff_ex(layerm->slices().surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), offset), surface_type_bottom_other); // if user requested internal shells, we need to identify surfaces @@ -922,8 +923,8 @@ void PrintObject::detect_surfaces_type() bottom, opening_ex( diff_ex( - intersection(layerm->slices.surfaces, lower_layer->lslices), // supported - lower_layer->m_regions[region_id]->slices.surfaces, + intersection(layerm->slices().surfaces, lower_layer->lslices), // supported + lower_layer->m_regions[region_id]->slices().surfaces, ApplySafetyOffset::Yes), offset), stBottom); @@ -932,7 +933,7 @@ void PrintObject::detect_surfaces_type() } else { // if no lower layer, all surfaces of this one are solid // we clone surfaces because we're going to clear the slices collection - bottom = layerm->slices.surfaces; + bottom = layerm->slices().surfaces; for (Surface &surface : bottom) surface.surface_type = stBottom; } @@ -961,13 +962,13 @@ void PrintObject::detect_surfaces_type() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // save surfaces to layer - Surfaces &surfaces_out = interface_shells ? surfaces_new[idx_layer] : layerm->slices.surfaces; + Surfaces &surfaces_out = interface_shells ? surfaces_new[idx_layer] : layerm->slices().surfaces; Surfaces surfaces_backup; if (! interface_shells) { surfaces_backup = std::move(surfaces_out); surfaces_out.clear(); } - const Surfaces &surfaces_prev = interface_shells ? layerm->slices.surfaces : surfaces_backup; + const Surfaces &surfaces_prev = interface_shells ? layerm->slices().surfaces : surfaces_backup; // find internal surfaces (difference between top/bottom surfaces and others) { @@ -993,15 +994,15 @@ void PrintObject::detect_surfaces_type() if (interface_shells) { // Move surfaces_new to layerm->slices.surfaces for (size_t idx_layer = 0; idx_layer < num_layers; ++ idx_layer) - m_layers[idx_layer]->m_regions[region_id]->slices.surfaces = std::move(surfaces_new[idx_layer]); + m_layers[idx_layer]->m_regions[region_id]->m_slices.set(std::move(surfaces_new[idx_layer])); } if (spiral_vase) { if (num_layers > 1) // Turn the last bottom layer infill to a top infill, so it will be extruded with a proper pattern. - m_layers[num_layers - 1]->m_regions[region_id]->slices.set_type(stTop); + m_layers[num_layers - 1]->m_regions[region_id]->m_slices.set_type(stTop); for (size_t i = num_layers; i < m_layers.size(); ++ i) - m_layers[i]->m_regions[region_id]->slices.set_type(stInternal); + m_layers[i]->m_regions[region_id]->m_slices.set_type(stInternal); } BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - start"; @@ -1048,7 +1049,7 @@ void PrintObject::process_external_surfaces() bool expansions = false; bool voids = false; for (const LayerRegion *layerm : layer->regions()) { - for (const Surface &surface : layerm->fill_surfaces.surfaces) { + for (const Surface &surface : layerm->fill_surfaces()) { if (surface.surface_type == stInternal) voids = true; else @@ -1073,7 +1074,7 @@ void PrintObject::process_external_surfaces() Polygons voids; for (const LayerRegion *layerm : m_layers[layer_idx]->regions()) { if (layerm->region().config().fill_density.value == 0.) - for (const Surface &surface : layerm->fill_surfaces.surfaces) + for (const Surface &surface : layerm->fill_surfaces()) // Shrink the holes, let the layer above expand slightly inside the unsupported areas. polygons_append(voids, offset(surface.expolygon, unsupported_width)); } @@ -1102,7 +1103,7 @@ void PrintObject::process_external_surfaces() m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - end"; } -} +} // void PrintObject::process_external_surfaces() void PrintObject::discover_vertical_shells() { @@ -1170,15 +1171,15 @@ void PrintObject::discover_vertical_shells() LayerRegion &layerm = *layer.m_regions[region_id]; float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f; // Top surfaces. - append(cache.top_surfaces, offset(layerm.slices.filter_by_type(stTop), min_perimeter_infill_spacing)); - append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), min_perimeter_infill_spacing)); + append(cache.top_surfaces, offset(layerm.slices().filter_by_type(stTop), min_perimeter_infill_spacing)); + append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), min_perimeter_infill_spacing)); // Bottom surfaces. - append(cache.bottom_surfaces, offset(layerm.slices.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); - append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); + append(cache.bottom_surfaces, offset(layerm.slices().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); + append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); // Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only. // First find the maxium number of perimeters per region slice. unsigned int perimeters = 0; - for (Surface &s : layerm.slices.surfaces) + for (const Surface &s : layerm.slices()) perimeters = std::max(perimeters, s.extra_perimeters); perimeters += layerm.region().config().perimeters.value; // Then calculate the infill offset. @@ -1189,7 +1190,7 @@ void PrintObject::discover_vertical_shells() 0.5f * float(extflow.scaled_width() + extflow.scaled_spacing()) + (float(perimeters) - 1.f) * flow.scaled_spacing()); perimeter_min_spacing = std::min(perimeter_min_spacing, float(std::min(extflow.scaled_spacing(), flow.scaled_spacing()))); } - polygons_append(cache.holes, to_polygons(layerm.fill_expolygons)); + polygons_append(cache.holes, to_polygons(layerm.fill_expolygons())); } // Save some computing time by reducing the number of polygons. cache.top_surfaces = union_(cache.top_surfaces); @@ -1242,15 +1243,15 @@ void PrintObject::discover_vertical_shells() float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f; // Top surfaces. auto &cache = cache_top_botom_regions[idx_layer]; - cache.top_surfaces = offset(layerm.slices.filter_by_type(stTop), min_perimeter_infill_spacing); - append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), min_perimeter_infill_spacing)); + cache.top_surfaces = offset(layerm.slices().filter_by_type(stTop), min_perimeter_infill_spacing); + append(cache.top_surfaces, offset(layerm.fill_surfaces().filter_by_type(stTop), min_perimeter_infill_spacing)); // Bottom surfaces. - cache.bottom_surfaces = offset(layerm.slices.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing); - append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); + cache.bottom_surfaces = offset(layerm.slices().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing); + append(cache.bottom_surfaces, offset(layerm.fill_surfaces().filter_by_types(surfaces_bottom, 2), min_perimeter_infill_spacing)); // Holes over all regions. Only collect them once, they are valid for all region_id iterations. if (cache.holes.empty()) { for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) - polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons)); + polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons())); } } }); @@ -1407,14 +1408,14 @@ void PrintObject::discover_vertical_shells() // Trim the shells region by the internal & internal void surfaces. const SurfaceType surfaceTypesInternal[] = { stInternal, stInternalVoid, stInternalSolid }; - const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types(surfaceTypesInternal, 3)); + const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces().filter_by_types(surfaceTypesInternal, 3)); shell = intersection(shell, polygonsInternal, ApplySafetyOffset::Yes); polygons_append(shell, diff(polygonsInternal, holes)); if (shell.empty()) continue; // Append the internal solids, so they will be merged with the new ones. - polygons_append(shell, to_polygons(layerm->fill_surfaces.filter_by_type(stInternalSolid))); + polygons_append(shell, to_polygons(layerm->fill_surfaces().filter_by_type(stInternalSolid))); // These regions will be filled by a rectilinear full infill. Currently this type of infill // only fills regions, which fit at least a single line. To avoid gaps in the sparse infill, @@ -1461,8 +1462,8 @@ void PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Trim the internal & internalvoid by the shell. - Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces.filter_by_type(stInternal), shell); - Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces.filter_by_type(stInternalVoid), shell); + Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces().filter_by_type(stInternal), shell); + Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces().filter_by_type(stInternalVoid), shell); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { @@ -1474,10 +1475,10 @@ void PrintObject::discover_vertical_shells() // Assign resulting internal surfaces to layer. const SurfaceType surfaceTypesKeep[] = { stTop, stBottom, stBottomBridge }; - layerm->fill_surfaces.keep_types(surfaceTypesKeep, sizeof(surfaceTypesKeep)/sizeof(SurfaceType)); - layerm->fill_surfaces.append(new_internal, stInternal); - layerm->fill_surfaces.append(new_internal_void, stInternalVoid); - layerm->fill_surfaces.append(new_internal_solid, stInternalSolid); + layerm->m_fill_surfaces.keep_types(surfaceTypesKeep, sizeof(surfaceTypesKeep)/sizeof(SurfaceType)); + layerm->m_fill_surfaces.append(new_internal, stInternal); + layerm->m_fill_surfaces.append(new_internal_void, stInternalVoid); + layerm->m_fill_surfaces.append(new_internal_solid, stInternalSolid); } // for each layer }); m_print->throw_if_canceled(); @@ -1491,7 +1492,7 @@ void PrintObject::discover_vertical_shells() } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } // for each region -} +} // void PrintObject::discover_vertical_shells() // This method applies bridge flow to the first internal solid layer above sparse infill. void PrintObject::bridge_over_infill() @@ -1514,7 +1515,7 @@ void PrintObject::bridge_over_infill() for (Layer *layer : m_layers) { Polygons sum; for (const LayerRegion *layerm : layer->m_regions) - layerm->fill_surfaces.filter_by_type(stInternal, &sum); + layerm->fill_surfaces().filter_by_type(stInternal, &sum); internals.emplace_back(std::move(sum)); } @@ -1531,7 +1532,7 @@ void PrintObject::bridge_over_infill() // Extract the stInternalSolid surfaces that might be transformed into bridges. ExPolygons internal_solid; - layerm->fill_surfaces.remove_type(stInternalSolid, &internal_solid); + layerm->m_fill_surfaces.remove_type(stInternalSolid, &internal_solid); if (internal_solid.empty()) // No internal solid -> no new bridges for this layer region. continue; @@ -1563,7 +1564,7 @@ void PrintObject::bridge_over_infill() if (to_bridge_pp.empty()) { // Restore internal_solid surfaces. for (ExPolygon &ex : internal_solid) - layerm->fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); + layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); continue; } // convert into ExPolygons @@ -1579,9 +1580,9 @@ void PrintObject::bridge_over_infill() to_bridge = intersection_ex(to_bridge, internal_solid, ApplySafetyOffset::Yes); // build the new collection of fill_surfaces for (ExPolygon &ex : to_bridge) - layerm->fill_surfaces.surfaces.push_back(Surface(stInternalBridge, std::move(ex))); + layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalBridge, std::move(ex))); for (ExPolygon &ex : not_to_bridge) - layerm->fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); + layerm->m_fill_surfaces.surfaces.push_back(Surface(stInternalSolid, std::move(ex))); /* # exclude infill from the layers below if needed # see discussion at https://github.com/alexrj/Slic3r/issues/240 @@ -1622,7 +1623,7 @@ void PrintObject::bridge_over_infill() m_print->throw_if_canceled(); } }); -} +} // void PrintObject::bridge_over_infill() static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders) { @@ -1829,7 +1830,7 @@ void PrintObject::clip_fill_surfaces() // Solid surfaces to be supported. Polygons overhangs; for (const LayerRegion *layerm : layer->m_regions) - for (const Surface &surface : layerm->fill_surfaces.surfaces) { + for (const Surface &surface : layerm->fill_surfaces()) { Polygons polygons = to_polygons(surface.expolygon); if (surface.is_solid()) polygons_append(overhangs, polygons); @@ -1838,7 +1839,7 @@ void PrintObject::clip_fill_surfaces() Polygons lower_layer_fill_surfaces; Polygons lower_layer_internal_surfaces; for (const LayerRegion *layerm : lower_layer->m_regions) - for (const Surface &surface : layerm->fill_surfaces.surfaces) { + for (const Surface &surface : layerm->fill_surfaces()) { Polygons polygons = to_polygons(surface.expolygon); if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) polygons_append(lower_layer_internal_surfaces, polygons); @@ -1873,12 +1874,12 @@ void PrintObject::clip_fill_surfaces() continue; SurfaceType internal_surface_types[] = { stInternal, stInternalVoid }; Polygons internal; - for (Surface &surface : layerm->fill_surfaces.surfaces) + for (Surface &surface : layerm->m_fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) polygons_append(internal, std::move(surface.expolygon)); - layerm->fill_surfaces.remove_types(internal_surface_types, 2); - layerm->fill_surfaces.append(intersection_ex(internal, upper_internal, ApplySafetyOffset::Yes), stInternal); - layerm->fill_surfaces.append(diff_ex (internal, upper_internal, ApplySafetyOffset::Yes), stInternalVoid); + layerm->m_fill_surfaces.remove_types(internal_surface_types, 2); + layerm->m_fill_surfaces.append(intersection_ex(internal, upper_internal, ApplySafetyOffset::Yes), stInternal); + layerm->m_fill_surfaces.append(diff_ex (internal, upper_internal, ApplySafetyOffset::Yes), stInternalVoid); // If there are voids it means that our internal infill is not adjacent to // perimeters. In this case it would be nice to add a loop around infill to // make it more robust and nicer. TODO. @@ -1888,7 +1889,7 @@ void PrintObject::clip_fill_surfaces() } m_print->throw_if_canceled(); } -} +} // void PrintObject::clip_fill_surfaces() void PrintObject::discover_horizontal_shells() { @@ -1904,7 +1905,7 @@ void PrintObject::discover_horizontal_shells() (i % region_config.solid_infill_every_layers) == 0) { // Insert a solid internal layer. Mark stInternal surfaces as stInternalSolid or stInternalBridge. SurfaceType type = (region_config.fill_density == 100 || region_config.solid_infill_every_layers == 1) ? stInternalSolid : stInternalBridge; - for (Surface &surface : layerm->fill_surfaces.surfaces) + for (Surface &surface : layerm->m_fill_surfaces.surfaces) if (surface.surface_type == stInternal) surface.surface_type = type; } @@ -1935,11 +1936,11 @@ void PrintObject::discover_horizontal_shells() // (not covered by a layer above / below). // This does not contain the areas covered by perimeters! Polygons solid; - for (const Surface &surface : layerm->slices.surfaces) + for (const Surface &surface : layerm->slices()) if (surface.surface_type == type) polygons_append(solid, to_polygons(surface.expolygon)); // Infill areas (slices without the perimeters). - for (const Surface &surface : layerm->fill_surfaces.surfaces) + for (const Surface &surface : layerm->fill_surfaces()) if (surface.surface_type == type) polygons_append(solid, to_polygons(surface.expolygon)); if (solid.empty()) @@ -1972,7 +1973,7 @@ void PrintObject::discover_horizontal_shells() Polygons new_internal_solid; { Polygons internal; - for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces) + for (const Surface &surface : neighbor_layerm->fill_surfaces()) if (surface.surface_type == stInternal || surface.surface_type == stInternalSolid) polygons_append(internal, to_polygons(surface.expolygon)); new_internal_solid = intersection(solid, internal, ApplySafetyOffset::Yes); @@ -2028,7 +2029,7 @@ void PrintObject::discover_horizontal_shells() // additional area in the next shell too // make sure our grown surfaces don't exceed the fill area Polygons internal; - for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces) + for (const Surface &surface : neighbor_layerm->fill_surfaces()) if (surface.is_internal() && !surface.is_bridge()) polygons_append(internal, to_polygons(surface.expolygon)); polygons_append(new_internal_solid, @@ -2047,16 +2048,16 @@ void PrintObject::discover_horizontal_shells() // internal-solid are the union of the existing internal-solid surfaces // and new ones - SurfaceCollection backup = std::move(neighbor_layerm->fill_surfaces); + SurfaceCollection backup = std::move(neighbor_layerm->m_fill_surfaces); polygons_append(new_internal_solid, to_polygons(backup.filter_by_type(stInternalSolid))); ExPolygons internal_solid = union_ex(new_internal_solid); // assign new internal-solid surfaces to layer - neighbor_layerm->fill_surfaces.set(internal_solid, stInternalSolid); + neighbor_layerm->m_fill_surfaces.set(internal_solid, stInternalSolid); // subtract intersections from layer surfaces to get resulting internal surfaces Polygons polygons_internal = to_polygons(std::move(internal_solid)); ExPolygons internal = diff_ex(backup.filter_by_type(stInternal), polygons_internal, ApplySafetyOffset::Yes); // assign resulting internal surfaces to layer - neighbor_layerm->fill_surfaces.append(internal, stInternal); + neighbor_layerm->m_fill_surfaces.append(internal, stInternal); polygons_append(polygons_internal, to_polygons(std::move(internal))); // assign top and bottom surfaces to layer SurfaceType surface_types_solid[] = { stTop, stBottom, stBottomBridge }; @@ -2064,7 +2065,7 @@ void PrintObject::discover_horizontal_shells() std::vector top_bottom_groups; backup.group(&top_bottom_groups); for (SurfacesPtr &group : top_bottom_groups) - neighbor_layerm->fill_surfaces.append( + neighbor_layerm->m_fill_surfaces.append( diff_ex(group, polygons_internal), // Use an existing surface as a template, it carries the bridge angle etc. *group.front()); @@ -2083,7 +2084,7 @@ void PrintObject::discover_horizontal_shells() } // for each layer } // for each region #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ -} +} // void PrintObject::discover_horizontal_shells() // combine fill surfaces across layers to honor the "infill every N layers" option // Idempotence of this method is guaranteed by the fact that we don't remove things from @@ -2141,10 +2142,10 @@ void PrintObject::combine_infill() layerms.emplace_back(m_layers[i]->regions()[region_id]); // We need to perform a multi-layer intersection, so let's split it in pairs. // Initialize the intersection with the candidates of the lowest layer. - ExPolygons intersection = to_expolygons(layerms.front()->fill_surfaces.filter_by_type(stInternal)); + ExPolygons intersection = to_expolygons(layerms.front()->fill_surfaces().filter_by_type(stInternal)); // Start looping from the second layer and intersect the current intersection with it. for (size_t i = 1; i < layerms.size(); ++ i) - intersection = intersection_ex(layerms[i]->fill_surfaces.filter_by_type(stInternal), intersection); + intersection = intersection_ex(layerms[i]->fill_surfaces().filter_by_type(stInternal), intersection); double area_threshold = layerms.front()->infill_area_threshold(); if (! intersection.empty() && area_threshold > 0.) intersection.erase(std::remove_if(intersection.begin(), intersection.end(), @@ -2173,9 +2174,9 @@ void PrintObject::combine_infill() for (ExPolygon &expoly : intersection) polygons_append(intersection_with_clearance, offset(expoly, clearance_offset)); for (LayerRegion *layerm : layerms) { - Polygons internal = to_polygons(std::move(layerm->fill_surfaces.filter_by_type(stInternal))); - layerm->fill_surfaces.remove_type(stInternal); - layerm->fill_surfaces.append(diff_ex(internal, intersection_with_clearance), stInternal); + Polygons internal = to_polygons(std::move(layerm->fill_surfaces().filter_by_type(stInternal))); + layerm->m_fill_surfaces.remove_type(stInternal); + layerm->m_fill_surfaces.append(diff_ex(internal, intersection_with_clearance), stInternal); if (layerm == layerms.back()) { // Apply surfaces back with adjusted depth to the uppermost layer. Surface templ(stInternal, ExPolygon()); @@ -2183,17 +2184,17 @@ void PrintObject::combine_infill() for (LayerRegion *layerm2 : layerms) templ.thickness += layerm2->layer()->height; templ.thickness_layers = (unsigned short)layerms.size(); - layerm->fill_surfaces.append(intersection, templ); + layerm->m_fill_surfaces.append(intersection, templ); } else { // Save void surfaces. - layerm->fill_surfaces.append( + layerm->m_fill_surfaces.append( intersection_ex(internal, intersection_with_clearance), stInternalVoid); } } } } -} +} // void PrintObject::combine_infill() void PrintObject::_generate_support_material() { diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 4b91714e5..f5ba3b9d7 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -432,12 +432,12 @@ std::string fix_slicing_errors(LayerPtrs &layers, const std::function &t const Surfaces *lower_surfaces = nullptr; for (size_t j = idx_layer + 1; j < layers.size(); ++ j) if (! layers[j]->slicing_errors) { - upper_surfaces = &layers[j]->regions()[region_id]->slices.surfaces; + upper_surfaces = &layers[j]->regions()[region_id]->slices().surfaces; break; } for (int j = int(idx_layer) - 1; j >= 0; -- j) if (! layers[j]->slicing_errors) { - lower_surfaces = &layers[j]->regions()[region_id]->slices.surfaces; + lower_surfaces = &layers[j]->regions()[region_id]->slices().surfaces; break; } // Collect outer contours and holes from the valid layers above & below. @@ -464,7 +464,7 @@ std::string fix_slicing_errors(LayerPtrs &layers, const std::function &t if (lower_surfaces) for (const auto &surface : *lower_surfaces) polygons_append(holes, surface.expolygon.holes); - layerm->slices.set(diff_ex(union_(outer), holes), stInternal); + layerm->m_slices.set(diff_ex(union_(outer), holes), stInternal); } // Update layer slices after repairing the single regions. layer->make_slices(); @@ -517,17 +517,26 @@ void PrintObject::slice() // Update bounding boxes, back up raw slices of complex models. tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), - [this](const tbb::blocked_range& range) { + [this](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); Layer &layer = *m_layers[layer_idx]; - layer.lslices_bboxes.clear(); - layer.lslices_bboxes.reserve(layer.lslices.size()); + layer.lslices_ex.clear(); + layer.lslices_ex.reserve(layer.lslices.size()); for (const ExPolygon &expoly : layer.lslices) - layer.lslices_bboxes.emplace_back(get_extents(expoly)); + layer.lslices_ex.push_back({ get_extents(expoly) }); layer.backup_untyped_slices(); } }); + // Interlink the lslices into a Z graph. + tbb::parallel_for( + tbb::blocked_range(1, m_layers.size()), + [this](const tbb::blocked_range &range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { + m_print->throw_if_canceled(); + Layer::build_up_down_graph(*m_layers[layer_idx - 1], *m_layers[layer_idx]); + } + }); if (m_layers.empty()) throw Slic3r::SlicingError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"); this->set_done(posSlice); @@ -579,9 +588,9 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance // layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID. auto it_painted_region = layer_range.painted_regions.begin(); for (int region_id = 0; region_id < int(layer->region_count()); ++ region_id) - if (LayerRegion &layerm = *layer->get_region(region_id); ! layerm.slices.surfaces.empty()) { + if (LayerRegion &layerm = *layer->get_region(region_id); ! layerm.slices().empty()) { assert(layerm.region().print_object_region_id() == region_id); - const BoundingBox bbox = get_extents(layerm.slices.surfaces); + const BoundingBox bbox = get_extents(layerm.slices().surfaces); assert(it_painted_region < layer_range.painted_regions.end()); // Find the first it_painted_region which overrides this region. for (; layer_range.volume_regions[it_painted_region->parent].region->print_object_region_id() < region_id; ++ it_painted_region) @@ -604,7 +613,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance } // Steal from this region. int target_region_id = it_painted_region->region->print_object_region_id(); - ExPolygons stolen = intersection_ex(layerm.slices.surfaces, segmented.expolygons); + ExPolygons stolen = intersection_ex(layerm.slices().surfaces, segmented.expolygons); if (! stolen.empty()) { ByRegion &dst = by_region[target_region_id]; if (dst.expolygons.empty()) { @@ -622,7 +631,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance } if (! self_trimmed) { // Trim slices of this LayerRegion with all the MMU regions. - Polygons mine = to_polygons(std::move(layerm.slices.surfaces)); + Polygons mine = to_polygons(std::move(layerm.slices().surfaces)); for (auto &segmented : by_extruder) if (&segmented - by_extruder.data() + 1 != self_extruder_id && segmented.bbox.defined && bbox.overlap(segmented.bbox)) { mine = diff(mine, segmented.expolygons); @@ -653,7 +662,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance if (src.needs_merge) // Multiple regions were merged into one. src.expolygons = closing_ex(src.expolygons, float(scale_(10 * EPSILON))); - layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal); + layer->get_region(region_id)->m_slices.set(std::move(src.expolygons), stInternal); } } }); @@ -693,7 +702,7 @@ void PrintObject::slice_volumes() for (size_t region_id = 0; region_id < region_slices.size(); ++ region_id) { std::vector &by_layer = region_slices[region_id]; for (size_t layer_id = 0; layer_id < by_layer.size(); ++ layer_id) - m_layers[layer_id]->regions()[region_id]->slices.append(std::move(by_layer[layer_id]), stInternal); + m_layers[layer_id]->regions()[region_id]->m_slices.append(std::move(by_layer[layer_id]), stInternal); } region_slices.clear(); @@ -754,14 +763,14 @@ void PrintObject::slice_volumes() LayerRegion *layerm = layer->m_regions.front(); if (elfoot > 0) { // Apply the elephant foot compensation and store the 1st layer slices without the Elephant foot compensation applied. - lslices_1st_layer = to_expolygons(std::move(layerm->slices.surfaces)); + lslices_1st_layer = to_expolygons(std::move(layerm->m_slices.surfaces)); float delta = xy_compensation_scaled; if (delta > elfoot) { delta -= elfoot; elfoot = 0.f; } else if (delta > 0) elfoot -= delta; - layerm->slices.set( + layerm->m_slices.set( union_ex( Slic3r::elephant_foot_compensation( (delta == 0.f) ? lslices_1st_layer : offset_ex(lslices_1st_layer, delta), @@ -771,8 +780,8 @@ void PrintObject::slice_volumes() lslices_1st_layer = offset_ex(std::move(lslices_1st_layer), xy_compensation_scaled); } else if (xy_compensation_scaled < 0.f) { // Apply the XY compensation. - layerm->slices.set( - offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), xy_compensation_scaled), + layerm->m_slices.set( + offset_ex(to_expolygons(std::move(layerm->m_slices.surfaces)), xy_compensation_scaled), stInternal); } } else { diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 8634fc3bb..9e21fca45 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -180,7 +180,7 @@ bool DefaultSupportTree::interconnect(const Pillar &pillar, Vec3d eupper = pillar.endpoint(); Vec3d elower = nextpillar.endpoint(); - double zmin = ground_level(this->m_sm) + m_sm.cfg.base_height_mm; + double zmin = ground_level(m_sm) + m_sm.cfg.base_height_mm; eupper.z() = std::max(eupper.z(), zmin); elower.z() = std::max(elower.z(), zmin); diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index c4a4efbf2..7242bee3e 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -650,14 +650,14 @@ Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_t // 1) Count the new polygons first. size_t n_polygons_new = 0; for (const LayerRegion *region : layer.regions()) - for (const Surface &surface : region->slices.surfaces) + for (const Surface &surface : region->slices()) if (surface.surface_type == surface_type) n_polygons_new += surface.expolygon.holes.size() + 1; // 2) Collect the new polygons. Polygons out; out.reserve(n_polygons_new); for (const LayerRegion *region : layer.regions()) - for (const Surface &surface : region->slices.surfaces) + for (const Surface &surface : region->slices()) if (surface.surface_type == surface_type) polygons_append(out, surface.expolygon); return out; @@ -1213,9 +1213,9 @@ namespace SupportMaterialInternal { static bool has_bridging_extrusions(const Layer &layer) { for (const LayerRegion *region : layer.regions()) { - if (SupportMaterialInternal::has_bridging_perimeters(region->perimeters)) + if (SupportMaterialInternal::has_bridging_perimeters(region->perimeters())) return true; - if (region->fill_surfaces.has(stBottomBridge) && has_bridging_fills(region->fills)) + if (region->fill_surfaces().has(stBottomBridge) && has_bridging_fills(region->fills())) return true; } return false; @@ -1287,7 +1287,7 @@ namespace SupportMaterialInternal { // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); #else - Polylines overhang_perimeters = diff_pl(layerm.perimeters.as_polylines(), lower_grown_slices); + Polylines overhang_perimeters = diff_pl(layerm.perimeters().as_polylines(), lower_grown_slices); #endif // only consider straight overhangs @@ -1311,7 +1311,7 @@ namespace SupportMaterialInternal { bool supported[2] = { false, false }; for (size_t i = 0; i < lower_layer.lslices.size() && ! (supported[0] && supported[1]); ++ i) for (int j = 0; j < 2; ++ j) - if (! supported[j] && lower_layer.lslices_bboxes[i].contains(pts[j]) && lower_layer.lslices[i].contains(pts[j])) + if (! supported[j] && lower_layer.lslices_ex[i].bbox.contains(pts[j]) && lower_layer.lslices[i].contains(pts[j])) supported[j] = true; if (supported[0] && supported[1]) // Offset a polyline into a thick line. @@ -1321,7 +1321,7 @@ namespace SupportMaterialInternal { } // remove the entire bridges and only support the unsupported edges //FIXME the brided regions are already collected as layerm.bridged. Use it? - for (const Surface &surface : layerm.fill_surfaces.surfaces) + for (const Surface &surface : layerm.fill_surfaces()) if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) polygons_append(bridges, surface.expolygon); //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? @@ -1329,14 +1329,14 @@ namespace SupportMaterialInternal { //FIXME add supports at regular intervals to support long bridges! bridges = diff(bridges, // Offset unsupported edges into polygons. - offset(layerm.unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset(layerm.unsupported_bridge_edges(), scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); // Remove bridged areas from the supported areas. contact_polygons = diff(contact_polygons, bridges, ApplySafetyOffset::Yes); #ifdef SLIC3R_DEBUG static int iRun = 0; SVG::export_expolygons(debug_out_path("support-top-contacts-remove-bridges-run%d.svg", iRun ++), - { { { union_ex(offset(layerm.unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } }, + { { { union_ex(offset(layerm.unsupported_bridge_edges(), scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } }, { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } }, { { union_ex(bridges) }, { "bridges", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ @@ -1487,7 +1487,7 @@ static inline std::tuple detect_overhangs( 0.5f * fw); // Overhang polygons for this layer and region. Polygons diff_polygons; - Polygons layerm_polygons = to_polygons(layerm->slices.surfaces); + Polygons layerm_polygons = to_polygons(layerm->slices().surfaces); if (lower_layer_offset == 0.f) { // Support everything. diff_polygons = diff(layerm_polygons, lower_layer_polygons); @@ -2858,10 +2858,10 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object( break; some_region_overlaps = true; polygons_append(polygons_trimming, - offset(region->fill_surfaces.filter_by_type(stBottomBridge), gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset(region->fill_surfaces().filter_by_type(stBottomBridge), gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (region->region().config().overhangs.value) // Add bridging perimeters. - SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming); + SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters(), gap_xy_scaled, polygons_trimming); } if (! some_region_overlaps) break; diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 09b2bc5ae..8dd4a013d 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -1039,12 +1039,12 @@ std::tuple> check_extrusions_and_build_graph(c // PREPARE BASE LAYER const Layer *layer = po->layers()[0]; for (const LayerRegion *layer_region : layer->regions()) { - for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { + for (const ExtrusionEntity *ex_entity : layer_region->perimeters()) { for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { push_lines(perimeter, layer_lines); } // perimeter } // ex_entity - for (const ExtrusionEntity *ex_entity : layer_region->fills.entities) { + for (const ExtrusionEntity *ex_entity : layer_region->fills()) { for (const ExtrusionEntity *fill : static_cast(ex_entity)->entities) { push_lines(fill, layer_lines); } // fill @@ -1083,13 +1083,13 @@ std::tuple> check_extrusions_and_build_graph(c for (size_t layer_idx = 1; layer_idx < po->layer_count(); ++layer_idx) { const Layer *layer = po->layers()[layer_idx]; for (const LayerRegion *layer_region : layer->regions()) { - for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { + for (const ExtrusionEntity *ex_entity : layer_region->perimeters()) { for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { check_extrusion_entity_stability(perimeter, layer_lines, layer->slice_z, layer_region, external_lines, issues, params); } // perimeter } // ex_entity - for (const ExtrusionEntity *ex_entity : layer_region->fills.entities) { + for (const ExtrusionEntity *ex_entity : layer_region->fills()) { for (const ExtrusionEntity *fill : static_cast(ex_entity)->entities) { if (fill->role() == ExtrusionRole::erGapFill || fill->role() == ExtrusionRole::erBridgeInfill) { diff --git a/src/libslic3r/Surface.hpp b/src/libslic3r/Surface.hpp index ef1de30e9..4afcdf6f5 100644 --- a/src/libslic3r/Surface.hpp +++ b/src/libslic3r/Surface.hpp @@ -104,7 +104,7 @@ public: }; typedef std::vector Surfaces; -typedef std::vector SurfacesPtr; +typedef std::vector SurfacesPtr; inline Polygons to_polygons(const Surface &surface) { @@ -221,6 +221,7 @@ inline void polygons_append(Polygons &dst, const SurfacesPtr &src) } } +/* inline void polygons_append(Polygons &dst, SurfacesPtr &&src) { dst.reserve(dst.size() + number_polygons(src)); @@ -230,6 +231,7 @@ inline void polygons_append(Polygons &dst, SurfacesPtr &&src) (*it)->expolygon.holes.clear(); } } +*/ // Append a vector of Surfaces at the end of another vector of polygons. inline void surfaces_append(Surfaces &dst, const ExPolygons &src, SurfaceType surfaceType) diff --git a/src/libslic3r/SurfaceCollection.cpp b/src/libslic3r/SurfaceCollection.cpp index 2ec071f7d..10a12b683 100644 --- a/src/libslic3r/SurfaceCollection.cpp +++ b/src/libslic3r/SurfaceCollection.cpp @@ -22,46 +22,45 @@ void SurfaceCollection::simplify(double tolerance) } /* group surfaces by common properties */ -void SurfaceCollection::group(std::vector *retval) +void SurfaceCollection::group(std::vector *retval) const { - for (Surfaces::iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++it) { + for (const Surface &surface : this->surfaces) { // find a group with the same properties - SurfacesPtr* group = NULL; + SurfacesPtr *group = nullptr; for (std::vector::iterator git = retval->begin(); git != retval->end(); ++git) - if (! git->empty() && surfaces_could_merge(*git->front(), *it)) { + if (! git->empty() && surfaces_could_merge(*git->front(), surface)) { group = &*git; break; } // if no group with these properties exists, add one - if (group == NULL) { + if (group == nullptr) { retval->resize(retval->size() + 1); group = &retval->back(); } // append surface to group - group->push_back(&*it); + group->push_back(&surface); } } -SurfacesPtr SurfaceCollection::filter_by_type(const SurfaceType type) +SurfacesPtr SurfaceCollection::filter_by_type(const SurfaceType type) const { SurfacesPtr ss; - for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { - if (surface->surface_type == type) ss.push_back(&*surface); - } + for (const Surface &surface : this->surfaces) + if (surface.surface_type == type) + ss.push_back(&surface); return ss; } -SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes) +SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes) const { SurfacesPtr ss; - for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { + for (const Surface &surface : this->surfaces) for (int i = 0; i < ntypes; ++ i) { - if (surface->surface_type == types[i]) { - ss.push_back(&*surface); + if (surface.surface_type == types[i]) { + ss.push_back(&surface); break; } } - } return ss; } diff --git a/src/libslic3r/SurfaceCollection.hpp b/src/libslic3r/SurfaceCollection.hpp index 8c27a507b..b81808b32 100644 --- a/src/libslic3r/SurfaceCollection.hpp +++ b/src/libslic3r/SurfaceCollection.hpp @@ -17,7 +17,7 @@ public: SurfaceCollection(Surfaces &&surfaces) : surfaces(std::move(surfaces)) {}; void simplify(double tolerance); - void group(std::vector *retval); + void group(std::vector *retval) const; template bool any_internal_contains(const T &item) const { for (const Surface &surface : this->surfaces) if (surface.is_internal() && surface.expolygon.contains(item)) return true; return false; @@ -26,8 +26,8 @@ public: for (const Surface &surface : this->surfaces) if (surface.is_bottom() && surface.expolygon.contains(item)) return true; return false; } - SurfacesPtr filter_by_type(const SurfaceType type); - SurfacesPtr filter_by_types(const SurfaceType *types, int ntypes); + SurfacesPtr filter_by_type(const SurfaceType type) const; + SurfacesPtr filter_by_types(const SurfaceType *types, int ntypes) const; void keep_type(const SurfaceType type); void keep_types(const SurfaceType *types, int ntypes); void remove_type(const SurfaceType type); @@ -48,6 +48,13 @@ public: return false; } + Surfaces::const_iterator cbegin() const { return this->surfaces.cbegin(); } + Surfaces::const_iterator cend() const { return this->surfaces.cend(); } + Surfaces::const_iterator begin() const { return this->surfaces.cbegin(); } + Surfaces::const_iterator end() const { return this->surfaces.cend(); } + Surfaces::iterator begin() { return this->surfaces.begin(); } + Surfaces::iterator end() { return this->surfaces.end(); } + void set(const SurfaceCollection &coll) { surfaces = coll.surfaces; } void set(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); } void set(const ExPolygons &src, SurfaceType surfaceType) { clear(); this->append(src, surfaceType); } diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index df820fac9..97175ea89 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -284,7 +284,7 @@ void TriangleMesh::rotate(float angle, const Axis &axis) case Z: its_rotate_z(this->its, angle); break; default: assert(false); return; } - update_bounding_box(this->its, this->m_stats); + update_bounding_box(this->its, m_stats); } } @@ -295,7 +295,7 @@ void TriangleMesh::rotate(float angle, const Vec3d& axis) Transform3d m = Transform3d::Identity(); m.rotate(Eigen::AngleAxisd(angle, axis_norm)); its_transform(its, m); - update_bounding_box(this->its, this->m_stats); + update_bounding_box(this->its, m_stats); } } @@ -334,7 +334,7 @@ void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) det = -det; } m_stats.volume *= det; - update_bounding_box(this->its, this->m_stats); + update_bounding_box(this->its, m_stats); } void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed) @@ -346,7 +346,7 @@ void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed) det = -det; } m_stats.volume *= det; - update_bounding_box(this->its, this->m_stats); + update_bounding_box(this->its, m_stats); } void TriangleMesh::flip_triangles() @@ -512,7 +512,7 @@ std::vector TriangleMesh::slice(const std::vector &z) const size_t TriangleMesh::memsize() const { - size_t memsize = 8 + this->its.memsize() + sizeof(this->m_stats); + size_t memsize = 8 + this->its.memsize() + sizeof(m_stats); return memsize; } diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index f30776307..6be4c4173 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -92,7 +92,7 @@ public: TriangleMesh(std::vector &&vertices, const std::vector &&faces); explicit TriangleMesh(const indexed_triangle_set &M); explicit TriangleMesh(indexed_triangle_set &&M, const RepairedMeshErrors& repaired_errors = RepairedMeshErrors()); - void clear() { this->its.clear(); this->m_stats.clear(); } + void clear() { this->its.clear(); m_stats.clear(); } bool ReadSTLFile(const char* input_file, bool repair = true); bool write_ascii(const char* output_file); bool write_binary(const char* output_file); diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 820c26acc..90f77d738 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -371,7 +371,7 @@ std::pair, std::vector> TriangleSelector::precompute_a { std::vector neighbors(m_triangles.size(), Vec3i(-1, -1, -1)); std::vector neighbors_propagated(m_triangles.size(), Vec3i(-1, -1, -1)); - for (int facet_idx = 0; facet_idx < this->m_orig_size_indices; ++facet_idx) { + for (int facet_idx = 0; facet_idx < m_orig_size_indices; ++facet_idx) { neighbors[facet_idx] = m_neighbors[facet_idx]; neighbors_propagated[facet_idx] = neighbors[facet_idx]; assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors[facet_idx])); @@ -1480,7 +1480,7 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i &vertices, const std::vector TriangleSelector::get_seed_fill_contour() const { std::vector edges_out; - for (int facet_idx = 0; facet_idx < this->m_orig_size_indices; ++facet_idx) { + for (int facet_idx = 0; facet_idx < m_orig_size_indices; ++facet_idx) { const Vec3i neighbors = m_neighbors[facet_idx]; assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors)); this->get_seed_fill_contour_recursive(facet_idx, neighbors, neighbors, edges_out); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f77eed369..c1e740672 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -7165,7 +7165,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c if (is_selected_separate_extruder) { bool at_least_one_has_correct_extruder = false; for (const LayerRegion* layerm : layer->regions()) { - if (layerm->slices.surfaces.empty()) + if (layerm->slices().empty()) continue; const PrintRegionConfig& cfg = layerm->region().config(); if (cfg.perimeter_extruder.value == m_selected_extruder || @@ -7208,14 +7208,14 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c } if (ctxt.has_perimeters) #if ENABLE_LEGACY_OPENGL_REMOVAL - _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, + _3DScene::extrusionentity_to_verts(layerm->perimeters(), float(layer->print_z), copy, select_geometry(idx_layer, layerm->region().config().perimeter_extruder.value, 0)); #else _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, volume(idx_layer, layerm->region().config().perimeter_extruder.value, 0)); #endif // ENABLE_LEGACY_OPENGL_REMOVAL if (ctxt.has_infill) { - for (const ExtrusionEntity *ee : layerm->fills.entities) { + for (const ExtrusionEntity *ee : layerm->fills()) { // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); if (! fill->entities.empty()) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b03010b17..c5f4d9672 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -3244,7 +3244,7 @@ void GUI_App::app_updater(bool from_user) app_data.target_path =dwnld_dlg.get_download_path(); // start download - this->plater_->get_notification_manager()->push_download_progress_notification(_utf8("Download"), std::bind(&AppUpdater::cancel_callback, this->m_app_updater.get())); + this->plater_->get_notification_manager()->push_download_progress_notification(_utf8("Download"), std::bind(&AppUpdater::cancel_callback, m_app_updater.get())); app_data.start_after = dwnld_dlg.run_after_download(); m_app_updater->set_app_data(std::move(app_data)); m_app_updater->sync_download(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 62611ac25..966c36b27 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -1158,18 +1158,18 @@ void TriangleSelectorGUI::update_render_data() #if !ENABLE_LEGACY_OPENGL_REMOVAL void GLPaintContour::render() const { - assert(this->m_contour_VBO_id != 0); - assert(this->m_contour_EBO_id != 0); + assert(m_contour_VBO_id != 0); + assert(m_contour_EBO_id != 0); glsafe(::glLineWidth(4.0f)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_contour_VBO_id)); glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr)); glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); if (this->contour_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_contour_EBO_id)); glsafe(::glDrawElements(GL_LINES, GLsizei(this->contour_indices_size), GL_UNSIGNED_INT, nullptr)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); } @@ -1181,20 +1181,20 @@ void GLPaintContour::render() const void GLPaintContour::finalize_geometry() { - assert(this->m_contour_VBO_id == 0); - assert(this->m_contour_EBO_id == 0); + assert(m_contour_VBO_id == 0); + assert(m_contour_EBO_id == 0); if (!this->contour_vertices.empty()) { - glsafe(::glGenBuffers(1, &this->m_contour_VBO_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); + glsafe(::glGenBuffers(1, &m_contour_VBO_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_contour_VBO_id)); glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_vertices.size() * sizeof(float), this->contour_vertices.data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); this->contour_vertices.clear(); } if (!this->contour_indices.empty()) { - glsafe(::glGenBuffers(1, &this->m_contour_EBO_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id)); + glsafe(::glGenBuffers(1, &m_contour_EBO_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_contour_EBO_id)); glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->contour_indices.size() * sizeof(unsigned int), this->contour_indices.data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); this->contour_indices.clear(); @@ -1203,13 +1203,13 @@ void GLPaintContour::finalize_geometry() void GLPaintContour::release_geometry() { - if (this->m_contour_VBO_id) { - glsafe(::glDeleteBuffers(1, &this->m_contour_VBO_id)); - this->m_contour_VBO_id = 0; + if (m_contour_VBO_id) { + glsafe(::glDeleteBuffers(1, &m_contour_VBO_id)); + m_contour_VBO_id = 0; } - if (this->m_contour_EBO_id) { - glsafe(::glDeleteBuffers(1, &this->m_contour_EBO_id)); - this->m_contour_EBO_id = 0; + if (m_contour_EBO_id) { + glsafe(::glDeleteBuffers(1, &m_contour_EBO_id)); + m_contour_EBO_id = 0; } this->clear(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 04f24b20d..94aca4fdb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -41,7 +41,7 @@ public: void render() const; - inline bool has_VBO() const { return this->m_contour_EBO_id != 0; } + inline bool has_VBO() const { return m_contour_EBO_id != 0; } // Release the geometry data, release OpenGL VBOs. void release_geometry(); diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 1855b6a4e..cfa2ef15a 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -101,7 +101,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co this->back_to_initial_value(opt_id); }; field->m_back_to_sys_value = [this](std::string opt_id) { - if (!this->m_disabled) + if (!m_disabled) this->back_to_sys_value(opt_id); }; diff --git a/src/slic3r/Utils/AppUpdater.cpp b/src/slic3r/Utils/AppUpdater.cpp index 27f2e34bc..a17adea8a 100644 --- a/src/slic3r/Utils/AppUpdater.cpp +++ b/src/slic3r/Utils/AppUpdater.cpp @@ -168,7 +168,7 @@ bool AppUpdater::priv::http_get_file(const std::string& url, size_t size_limit, .size_limit(size_limit) .on_progress([&, progress_fn](Http::Progress progress, bool& cancel) { // progress function returns true as success (to continue) - cancel = (this->m_cancel ? true : !progress_fn(std::move(progress))); + cancel = (m_cancel ? true : !progress_fn(std::move(progress))); if (cancel) { error_message = GUI::format("Error getting: `%1%`: Download was canceled.", //lm:typo //dk: am i blind? :) url); @@ -259,7 +259,7 @@ boost::filesystem::path AppUpdater::priv::download_file(const DownloadAppData& d ); if (!res) { - if (this->m_cancel) + if (m_cancel) { BOOST_LOG_TRIVIAL(info) << error_message; //lm:Is this an error? // dk: changed to info wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_APP_DOWNLOAD_FAILED); // FAILED with empty msg only closes progress notification diff --git a/tests/fff_print/test_perimeters.cpp b/tests/fff_print/test_perimeters.cpp index 71ab3e675..54cf37067 100644 --- a/tests/fff_print/test_perimeters.cpp +++ b/tests/fff_print/test_perimeters.cpp @@ -459,8 +459,8 @@ SCENARIO("Some weird coverage test", "[Perimeters]") object->slice(); Layer *layer = object->get_layer(1); LayerRegion *layerm = layer->get_region(0); - layerm->slices.clear(); - layerm->slices.append({ expolygon }, stInternal); + layerm->m_slices.clear(); + layerm->m_slices.append({ expolygon }, stInternal); // make perimeters layer->make_perimeters(); @@ -472,22 +472,22 @@ SCENARIO("Some weird coverage test", "[Perimeters]") Polygons covered_by_infill; { Polygons acc; - for (const ExtrusionEntity *ee : layerm->perimeters.entities) + for (const ExtrusionEntity *ee : layerm->perimeters()) for (const ExtrusionEntity *ee : dynamic_cast(ee)->entities) append(acc, offset(dynamic_cast(ee)->polygon().split_at_first_point(), float(pflow.scaled_width() / 2.f + SCALED_EPSILON))); covered_by_perimeters = union_(acc); } { Polygons acc; - for (const Surface &surface : layerm->fill_surfaces.surfaces) - append(acc, to_polygons(surface.expolygon)); - for (const ExtrusionEntity *ee : layerm->thin_fills.entities) + for (const ExPolygon &expolygon : layerm->fill_expolygons()) + append(acc, to_polygons(expolygon)); + for (const ExtrusionEntity *ee : layerm->thin_fills().entities) append(acc, offset(dynamic_cast(ee)->polyline, float(iflow.scaled_width() / 2.f + SCALED_EPSILON))); covered_by_infill = union_(acc); } // compute the non covered area - ExPolygons non_covered = diff_ex(to_polygons(layerm->slices.surfaces), union_(covered_by_perimeters, covered_by_infill)); + ExPolygons non_covered = diff_ex(to_polygons(layerm->slices().surfaces), union_(covered_by_perimeters, covered_by_infill)); /* if (0) { @@ -506,7 +506,8 @@ SCENARIO("Some weird coverage test", "[Perimeters]") } */ THEN("no gap between perimeters and infill") { - size_t num_non_convered = std::count_if(non_covered.begin(), non_covered.end(), [&iflow](const ExPolygon &ex){ return ex.area() > sqr(double(iflow.scaled_width())); }); + size_t num_non_convered = std::count_if(non_covered.begin(), non_covered.end(), + [&iflow](const ExPolygon &ex){ return ex.area() > sqr(double(iflow.scaled_width())); }); REQUIRE(num_non_convered == 0); } } diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index d8ab5a3fa..204f3f8e0 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -20,11 +20,11 @@ SCENARIO("PrintObject: Perimeter generation", "[PrintObject]") { } THEN("Every layer in region 0 has 1 island of perimeters") { for (const Layer *layer : object.layers()) - REQUIRE(layer->regions().front()->perimeters.entities.size() == 1); + REQUIRE(layer->regions().front()->perimeters().size() == 1); } THEN("Every layer in region 0 has 3 paths in its perimeters list.") { for (const Layer *layer : object.layers()) - REQUIRE(layer->regions().front()->perimeters.items_count() == 3); + REQUIRE(layer->regions().front()->perimeters().items_count() == 3); } } } @@ -66,7 +66,7 @@ SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces t // iterate over all of the regions in the layer for (const LayerRegion *region : layer.regions()) { // for each region, iterate over the fill surfaces - for (const Surface &surface : region->fill_surfaces.surfaces) + for (const Surface &surface : region->fill_surfaces()) CHECK(surface.is_solid()); } }; diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index e42486985..da2962c1e 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -13,15 +13,15 @@ %code%{ RETVAL = &THIS->region(); %}; Ref slices() - %code%{ RETVAL = &THIS->slices; %}; + %code%{ RETVAL = const_cast(&THIS->slices()); %}; Ref thin_fills() - %code%{ RETVAL = &THIS->thin_fills; %}; + %code%{ RETVAL = const_cast(&THIS->thin_fills()); %}; Ref fill_surfaces() - %code%{ RETVAL = &THIS->fill_surfaces; %}; + %code%{ RETVAL = const_cast(&THIS->fill_surfaces()); %}; Ref perimeters() - %code%{ RETVAL = &THIS->perimeters; %}; + %code%{ RETVAL = const_cast(&THIS->perimeters()); %}; Ref fills() - %code%{ RETVAL = &THIS->fills; %}; + %code%{ RETVAL = const_cast(&THIS->fills()); %}; void prepare_fill_surfaces(); void make_perimeters(SurfaceCollection* slices, SurfaceCollection* fill_surfaces) From f8c67e07a47c2e209ae56e8736f935950da2640c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 26 Oct 2022 19:05:50 +0200 Subject: [PATCH 02/33] Follow-up to ee626eb65a1a41ec7fd3e00f7b39d048c2795516 --- src/libslic3r/ExtrusionEntity.hpp | 2 +- src/libslic3r/PrintObject.cpp | 2 +- src/libslic3r/PrintObjectSlice.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 0591e8a26..47c33938a 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -186,7 +186,7 @@ public: ExtrusionEntity* clone() const override { return new ExtrusionPathOriented(*this); } // Create a new object, initialize it with this object using the move semantics. ExtrusionEntity* clone_move() override { return new ExtrusionPathOriented(std::move(*this)); } - virtual bool can_reverse() const { return false; } + virtual bool can_reverse() const override { return false; } }; typedef std::vector ExtrusionPaths; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d3ac219b0..1fc331935 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -962,7 +962,7 @@ void PrintObject::detect_surfaces_type() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // save surfaces to layer - Surfaces &surfaces_out = interface_shells ? surfaces_new[idx_layer] : layerm->slices().surfaces; + Surfaces &surfaces_out = interface_shells ? surfaces_new[idx_layer] : layerm->m_slices.surfaces; Surfaces surfaces_backup; if (! interface_shells) { surfaces_backup = std::move(surfaces_out); diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index f5ba3b9d7..81e21f305 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -543,7 +543,7 @@ void PrintObject::slice() } template -static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel) +void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel) { // Returns MMU segmentation based on painting in MMU segmentation gizmo std::vector> segmentation = multi_material_segmentation_by_painting(print_object, throw_on_cancel); From 237e56c7ce7bbe01d02b4b00c2097f42850c7aa5 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 27 Oct 2022 13:04:52 +0200 Subject: [PATCH 03/33] Follow-up to ee626eb65a1a41ec7fd3e00f7b39d048c2795516 Refactored PerimeterGenerator for functional style, better constness with the goal of calling PerimeterGenerator::process_xxx() for each surface at once to collect its fill expolygons. --- src/libslic3r/Arachne/utils/ExtrusionLine.cpp | 4 +- src/libslic3r/Fill/Fill.cpp | 3 +- src/libslic3r/LayerRegion.cpp | 57 +++-- src/libslic3r/PerimeterGenerator.cpp | 229 +++++++++--------- src/libslic3r/PerimeterGenerator.hpp | 127 ++++++---- tests/fff_print/test_perimeters.cpp | 30 ++- 6 files changed, 251 insertions(+), 199 deletions(-) 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()); From d041fa6c0ccbebfc3ee967597b3ccd1213f32d2f Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 27 Oct 2022 19:08:43 +0200 Subject: [PATCH 04/33] Refactored PerimeterGenerator to output out_fill_surfaces as ExPolygons, not SurfaceCollection. Reworked combineinfill.t, 07_extrusionpath.t, 08_extrusionloop.t to c++. Removed Layer / ExtrusionPath / ExtrusionLoop / ExtrusionEntityCollection from Perl bindings. --- lib/Slic3r/Print/Object.pm | 5 - src/libslic3r/Layer.cpp | 12 +- src/libslic3r/Layer.hpp | 2 +- src/libslic3r/LayerRegion.cpp | 6 +- src/libslic3r/PerimeterGenerator.cpp | 14 +- src/libslic3r/PerimeterGenerator.hpp | 4 +- src/libslic3r/libslic3r.h | 9 +- t/combineinfill.t | 110 --------- tests/fff_print/test_extrusion_entity.cpp | 279 +++++++++++++++++++++- tests/fff_print/test_fill.cpp | 129 +++++++++- tests/fff_print/test_perimeters.cpp | 4 +- xs/CMakeLists.txt | 4 - xs/lib/Slic3r/XS.pm | 2 - xs/src/perlglue.cpp | 2 - xs/t/07_extrusionpath.t | 36 --- xs/t/08_extrusionloop.t | 157 ------------ xs/t/12_extrusionpathcollection.t | 91 ------- xs/xsp/ExtrusionEntityCollection.xsp | 102 -------- xs/xsp/ExtrusionLoop.xsp | 65 ----- xs/xsp/ExtrusionPath.xsp | 126 ---------- xs/xsp/Layer.xsp | 72 ------ xs/xsp/Print.xsp | 7 - xs/xsp/my.map | 20 -- xs/xsp/typemap.xspt | 16 -- 24 files changed, 422 insertions(+), 852 deletions(-) delete mode 100644 t/combineinfill.t delete mode 100644 xs/t/07_extrusionpath.t delete mode 100644 xs/t/08_extrusionloop.t delete mode 100644 xs/t/12_extrusionpathcollection.t delete mode 100644 xs/xsp/ExtrusionEntityCollection.xsp delete mode 100644 xs/xsp/ExtrusionLoop.xsp delete mode 100644 xs/xsp/ExtrusionPath.xsp delete mode 100644 xs/xsp/Layer.xsp diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index b4e53245a..d5c19e4a4 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -6,9 +6,4 @@ use warnings; use List::Util qw(min max sum first); use Slic3r::Surface ':types'; -sub layers { - my $self = shift; - return [ map $self->get_layer($_), 0..($self->layer_count - 1) ]; -} - 1; diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 3bd2566a5..82c82372b 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -426,12 +426,12 @@ void Layer::make_perimeters() } } - SurfaceCollection fill_surfaces; + ExPolygons fill_expolygons; if (layerms.size() == 1) { // optimization (*layerm)->m_fill_expolygons.clear(); (*layerm)->m_fill_surfaces.clear(); - (*layerm)->make_perimeters((*layerm)->slices(), &fill_surfaces); - (*layerm)->m_fill_expolygons = to_expolygons(fill_surfaces.surfaces); + (*layerm)->make_perimeters((*layerm)->slices(), fill_expolygons); + (*layerm)->m_fill_expolygons = std::move(fill_expolygons); } else { SurfaceCollection new_slices; // Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence. @@ -452,12 +452,12 @@ void Layer::make_perimeters() new_slices.append(offset_ex(surfaces_with_extra_perimeters.second, ClipperSafetyOffset), surfaces_with_extra_perimeters.second.front()); } // make perimeters - layerm_config->make_perimeters(new_slices, &fill_surfaces); + layerm_config->make_perimeters(new_slices, fill_expolygons); // assign fill_surfaces to each layer - if (! fill_surfaces.empty()) { + if (! fill_expolygons.empty()) { // Separate the fill surfaces. for (LayerRegion *l : layerms) - l->m_fill_expolygons = intersection_ex(fill_surfaces.surfaces, l->slices().surfaces); + l->m_fill_expolygons = intersection_ex(l->slices().surfaces, fill_expolygons); } } } diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 9a1a77aea..0fda3a6b2 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -67,7 +67,7 @@ public: void slices_to_fill_surfaces_clipped(); void prepare_fill_surfaces(); - void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces); + void make_perimeters(const SurfaceCollection &slices, ExPolygons &fill_expolygons); void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered); double infill_area_threshold() const; // Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer. diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 555da2146..4b346647b 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -59,7 +59,7 @@ void LayerRegion::slices_to_fill_surfaces_clipped() } } -void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces) +void LayerRegion::make_perimeters(const SurfaceCollection &slices, ExPolygons &fill_expolygons) { m_perimeters.clear(); m_thin_fills.clear(); @@ -100,7 +100,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec // output: m_perimeters, m_thin_fills, - *fill_surfaces); + fill_expolygons); else PerimeterGenerator::process_classic( // input: @@ -111,7 +111,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec // output: m_perimeters, m_thin_fills, - *fill_surfaces); + fill_expolygons); } //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 2856323a4..27bc0e9d7 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -611,7 +611,7 @@ void PerimeterGenerator::process_arachne( // Gaps without the thin walls ExtrusionEntityCollection &out_gap_fill, // Infills without the gap fills - SurfaceCollection &out_fill_surfaces) + ExPolygons &out_fill_expolygons) { // other perimeters coord_t perimeter_spacing = params.perimeter_flow.scaled_spacing(); @@ -817,12 +817,11 @@ void PerimeterGenerator::process_arachne( // 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 - out_fill_surfaces.append( + append(out_fill_expolygons, offset2_ex( union_ex(pp), float(- min_perimeter_infill_spacing / 2.), - float(inset + min_perimeter_infill_spacing / 2.)), - stInternal); + float(inset + min_perimeter_infill_spacing / 2.))); } } @@ -839,7 +838,7 @@ void PerimeterGenerator::process_classic( // Gaps without the thin walls ExtrusionEntityCollection &out_gap_fill, // Infills without the gap fills - SurfaceCollection &out_fill_surfaces) + ExPolygons &out_fill_expolygons) { // other perimeters coord_t perimeter_width = params.perimeter_flow.scaled_width(); @@ -1094,12 +1093,11 @@ void PerimeterGenerator::process_classic( // 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 - out_fill_surfaces.append( + append(out_fill_expolygons, offset2_ex( union_ex(pp), float(- inset - min_perimeter_infill_spacing / 2.), - float(min_perimeter_infill_spacing / 2.)), - stInternal); + float(min_perimeter_infill_spacing / 2.))); } // for each island } diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index 92710c4ee..de52dd89a 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -77,7 +77,7 @@ void process_classic( // Gaps without the thin walls ExtrusionEntityCollection &out_gap_fill, // Infills without the gap fills - SurfaceCollection &out_fill_surfaces); + ExPolygons &out_fill_expolygons); void process_arachne( // Inputs: @@ -92,7 +92,7 @@ void process_arachne( // Gaps without the thin walls ExtrusionEntityCollection &out_gap_fill, // Infills without the gap fills - SurfaceCollection &out_fill_surfaces); + ExPolygons &out_fill_expolygons); ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, float tolerance, float merge_tolerance); diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 2285c29a6..cd1b41c38 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -32,12 +32,13 @@ #include "Technologies.hpp" #include "Semver.hpp" +using coord_t = #if 1 // Saves around 32% RAM after slicing step, 6.7% after G-code export (tested on PrusaSlicer 2.2.0 final). -using coord_t = int32_t; + int32_t; #else -//FIXME At least FillRectilinear2 and std::boost Voronoi require coord_t to be 32bit. -typedef int64_t coord_t; + //FIXME At least FillRectilinear2 and std::boost Voronoi require coord_t to be 32bit. + int64_t; #endif using coordf_t = double; @@ -366,4 +367,4 @@ inline IntegerOnly fast_round_up(double a) } // namespace Slic3r -#endif +#endif // _libslic3r_h_ diff --git a/t/combineinfill.t b/t/combineinfill.t deleted file mode 100644 index ebb430419..000000000 --- a/t/combineinfill.t +++ /dev/null @@ -1,110 +0,0 @@ -use Test::More; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(first); -use Slic3r; -use Slic3r::Surface ':types'; -use Slic3r::Test; - -plan tests => 8; - -{ - my $test = sub { - my ($config) = @_; - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok my $gcode = Slic3r::Test::gcode($print), "infill_every_layers does not crash"; - - my $tool = undef; - my %layers = (); # layer_z => 1 - my %layer_infill = (); # layer_z => has_infill - Slic3r::GCode::Reader->new->parse($gcode, sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0 && $tool != $config->support_material_extruder-1) { - $layer_infill{$self->Z} //= 0; - if ($tool == $config->infill_extruder-1) { - $layer_infill{$self->Z} = 1; - } - } - # Previously, all G-code commands had a fixed number of decimal points with means with redundant zeros after decimal points. - # We changed this behavior and got rid of these redundant padding zeros, which caused this test to fail - # because the position in Z-axis is compared as a string, and previously, G-code contained the following two commands: - # "G1 Z5 F5000 ; lift nozzle" - # "G1 Z5.000 F7800.000" - # That has a different Z-axis position from the view of string comparisons of floating-point numbers. - # To correct the computation of the number of printed layers, even in the case of string comparisons of floating-point numbers, - # we filtered out the G-code command with the commend 'lift nozzle'. - $layers{$args->{Z}} = 1 if $cmd eq 'G1' && $info->{dist_Z} && index($info->{comment}, 'lift nozzle') == -1; - }); - - my $layers_with_perimeters = scalar(keys %layer_infill); - my $layers_with_infill = grep $_ > 0, values %layer_infill; - is scalar(keys %layers), $layers_with_perimeters+$config->raft_layers, 'expected number of layers'; - - if ($config->raft_layers == 0) { - # first infill layer printed directly on print bed is not combined, so we don't consider it. - $layers_with_infill--; - $layers_with_perimeters--; - } - - # we expect that infill is generated for half the number of combined layers - # plus for each single layer that was not combined (remainder) - is $layers_with_infill, - int($layers_with_perimeters/$config->infill_every_layers) + ($layers_with_perimeters % $config->infill_every_layers), - 'infill is only present in correct number of layers'; - }; - - my $config = Slic3r::Config::new_from_defaults; - $config->set('layer_height', 0.2); - $config->set('first_layer_height', 0.2); - $config->set('nozzle_diameter', [0.5,0.5,0.5,0.5]); - $config->set('infill_every_layers', 2); - $config->set('perimeter_extruder', 1); - $config->set('infill_extruder', 2); - $config->set('wipe_into_infill', 0); - $config->set('support_material_extruder', 3); - $config->set('support_material_interface_extruder', 3); - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - $test->($config); - - $config->set('skirts', 0); # prevent usage of perimeter_extruder in raft layers - $config->set('raft_layers', 5); - $test->($config); -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('layer_height', 0.2); - $config->set('first_layer_height', 0.2); - $config->set('nozzle_diameter', [0.5]); - $config->set('infill_every_layers', 2); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - $print->process; - - ok defined(first { @{$_->get_region(0)->fill_surfaces->filter_by_type(S_TYPE_INTERNALVOID)} > 0 } - @{$print->print->get_object(0)->layers}), - 'infill combination produces internal void surfaces'; - - # we disable combination after infill has been generated - $config->set('infill_every_layers', 1); - $print->apply($print->print->model->clone, $config); - $print->process; - - ok !(defined first { @{$_->get_region(0)->fill_surfaces} == 0 } - @{$print->print->get_object(0)->layers}), - 'infill combination is idempotent'; -} - -__END__ diff --git a/tests/fff_print/test_extrusion_entity.cpp b/tests/fff_print/test_extrusion_entity.cpp index c98e0ec1a..56588d98a 100644 --- a/tests/fff_print/test_extrusion_entity.cpp +++ b/tests/fff_print/test_extrusion_entity.cpp @@ -35,7 +35,254 @@ static Slic3r::ExtrusionPaths random_paths(size_t count = 10, size_t length = 20 return p; } -SCENARIO("ExtrusionEntityCollection: Polygon flattening", "[ExtrusionEntity]") { +SCENARIO("ExtrusionPath", "[ExtrusionEntity]") { + GIVEN("Simple path") { + Slic3r::ExtrusionPath path{ erExternalPerimeter }; + path.polyline = { { 100, 100 }, { 200, 100 }, { 200, 200 } }; + path.mm3_per_mm = 1.; + THEN("first point") { + REQUIRE(path.first_point() == path.polyline.front()); + } + THEN("cloned") { + auto cloned = std::unique_ptr(path.clone()); + REQUIRE(cloned->role() == path.role()); + } + } +} + +static ExtrusionPath new_extrusion_path(const Polyline &polyline, ExtrusionRole role, double mm3_per_mm) +{ + ExtrusionPath path(role); + path.polyline = polyline; + path.mm3_per_mm = 1.; + return path; +} + +SCENARIO("ExtrusionLoop", "[ExtrusionEntity]") +{ + GIVEN("Square") { + Polygon square { { 100, 100 }, { 200, 100 }, { 200, 200 }, { 100, 200 } }; + + ExtrusionLoop loop; + loop.paths.emplace_back(new_extrusion_path(square.split_at_first_point(), erExternalPerimeter, 1.)); + THEN("polygon area") { + REQUIRE(loop.polygon().area() == Approx(square.area())); + } + THEN("loop length") { + REQUIRE(loop.length() == Approx(square.length())); + } + + WHEN("cloned") { + auto loop2 = std::unique_ptr(dynamic_cast(loop.clone())); + THEN("cloning worked") { + REQUIRE(loop2 != nullptr); + } + THEN("loop contains one path") { + REQUIRE(loop2->paths.size() == 1); + } + THEN("cloned role") { + REQUIRE(loop2->paths.front().role() == erExternalPerimeter); + } + } + WHEN("cloned and split") { + auto loop2 = std::unique_ptr(dynamic_cast(loop.clone())); + loop2->split_at_vertex(square.points[2]); + THEN("splitting a single-path loop results in a single path") { + REQUIRE(loop2->paths.size() == 1); + } + THEN("path has correct number of points") { + REQUIRE(loop2->paths.front().size() == 5); + } + THEN("expected point order") { + REQUIRE(loop2->paths.front().polyline[0] == square.points[2]); + REQUIRE(loop2->paths.front().polyline[1] == square.points[3]); + REQUIRE(loop2->paths.front().polyline[2] == square.points[0]); + REQUIRE(loop2->paths.front().polyline[3] == square.points[1]); + REQUIRE(loop2->paths.front().polyline[4] == square.points[2]); + } + } + } + + GIVEN("Loop with two pieces") { + Polyline polyline1 { { 100, 100 }, { 200, 100 }, { 200, 200 } }; + Polyline polyline2 { { 200, 200 }, { 100, 200 }, { 100, 100 } }; + ExtrusionLoop loop; + loop.paths.emplace_back(new_extrusion_path(polyline1, erExternalPerimeter, 1.)); + loop.paths.emplace_back(new_extrusion_path(polyline2, erOverhangPerimeter, 1.)); + + double tot_len = polyline1.length() + polyline2.length(); + THEN("length") { + REQUIRE(loop.length() == Approx(tot_len)); + } + + WHEN("splitting at intermediate point") { + auto loop2 = std::unique_ptr(dynamic_cast(loop.clone())); + loop2->split_at_vertex(polyline1.points[1]); + THEN("length after splitting is unchanged") { + REQUIRE(loop2->length() == Approx(tot_len)); + } + THEN("loop contains three paths after splitting") { + REQUIRE(loop2->paths.size() == 3); + } + THEN("expected starting point") { + REQUIRE(loop2->paths.front().polyline.front() == polyline1.points[1]); + } + THEN("expected ending point") { + REQUIRE(loop2->paths.back().polyline.back() == polyline1.points[1]); + } + THEN("paths have common point") { + REQUIRE(loop2->paths.front().polyline.back() == loop2->paths[1].polyline.front()); + REQUIRE(loop2->paths[1].polyline.back() == loop2->paths[2].polyline.front()); + } + THEN("expected order after splitting") { + REQUIRE(loop2->paths.front().role() == erExternalPerimeter); + REQUIRE(loop2->paths[1].role() == erOverhangPerimeter); + REQUIRE(loop2->paths[2].role() == erExternalPerimeter); + } + THEN("path has correct number of points") { + REQUIRE(loop2->paths.front().polyline.size() == 2); + REQUIRE(loop2->paths[1].polyline.size() == 3); + REQUIRE(loop2->paths[2].polyline.size() == 2); + } + THEN("clipped path has expected length") { + double l = loop2->length(); + ExtrusionPaths paths; + loop2->clip_end(3, &paths); + double l2 = 0; + for (const ExtrusionPath &p : paths) + l2 += p.length(); + REQUIRE(l2 == Approx(l - 3.)); + } + } + + WHEN("splitting at endpoint") { + auto loop2 = std::unique_ptr(dynamic_cast(loop.clone())); + loop2->split_at_vertex(polyline2.points.front()); + THEN("length after splitting is unchanged") { + REQUIRE(loop2->length() == Approx(tot_len)); + } + THEN("loop contains two paths after splitting") { + REQUIRE(loop2->paths.size() == 2); + } + THEN("expected starting point") { + REQUIRE(loop2->paths.front().polyline.front() == polyline2.points.front()); + } + THEN("expected ending point") { + REQUIRE(loop2->paths.back().polyline.back() == polyline2.points.front()); + } + THEN("paths have common point") { + REQUIRE(loop2->paths.front().polyline.back() == loop2->paths[1].polyline.front()); + REQUIRE(loop2->paths[1].polyline.back() == loop2->paths.front().polyline.front()); + } + THEN("expected order after splitting") { + REQUIRE(loop2->paths.front().role() == erOverhangPerimeter); + REQUIRE(loop2->paths[1].role() == erExternalPerimeter); + } + THEN("path has correct number of points") { + REQUIRE(loop2->paths.front().polyline.size() == 3); + REQUIRE(loop2->paths[1].polyline.size() == 3); + } + } + + WHEN("splitting at an edge") { + Point point(250, 150); + auto loop2 = std::unique_ptr(dynamic_cast(loop.clone())); + loop2->split_at(point, false, 0); + THEN("length after splitting is unchanged") { + REQUIRE(loop2->length() == Approx(tot_len)); + } + Point expected_start_point(200, 150); + THEN("expected starting point") { + REQUIRE(loop2->paths.front().polyline.front() == expected_start_point); + } + THEN("expected ending point") { + REQUIRE(loop2->paths.back().polyline.back() == expected_start_point); + } + } + } + + GIVEN("Loop with four pieces") { + Polyline polyline1 { { 59312736, 4821067 }, { 64321068, 4821067 }, { 64321068, 4821067 }, { 64321068, 9321068 }, { 59312736, 9321068 } }; + Polyline polyline2 { { 59312736, 9321068 }, { 9829401, 9321068 } }; + Polyline polyline3 { { 9829401, 9321068 }, { 4821067, 9321068 }, { 4821067, 4821067 }, { 9829401, 4821067 } }; + Polyline polyline4 { { 9829401, 4821067 }, { 59312736,4821067 } }; + ExtrusionLoop loop; + loop.paths.emplace_back(new_extrusion_path(polyline1, erExternalPerimeter, 1.)); + loop.paths.emplace_back(new_extrusion_path(polyline2, erOverhangPerimeter, 1.)); + loop.paths.emplace_back(new_extrusion_path(polyline3, erExternalPerimeter, 1.)); + loop.paths.emplace_back(new_extrusion_path(polyline4, erOverhangPerimeter, 1.)); + double len = loop.length(); + WHEN("splitting at vertex") { + Point point(4821067, 9321068); + if (! loop.split_at_vertex(point)) + loop.split_at(point, false, 0); + THEN("total length is preserved after splitting") { + REQUIRE(loop.length() == Approx(len)); + } + THEN("order is correctly preserved after splitting") { + REQUIRE(loop.paths.front().role() == erExternalPerimeter); + REQUIRE(loop.paths[1].role() == erOverhangPerimeter); + REQUIRE(loop.paths[2].role() == erExternalPerimeter); + REQUIRE(loop.paths[3].role() == erOverhangPerimeter); + } + } + } + + GIVEN("Some complex loop") { + ExtrusionLoop loop; + loop.paths.emplace_back(new_extrusion_path( + Polyline { { 15896783, 15868739 }, { 24842049, 12117558 }, { 33853238, 15801279 }, { 37591780, 24780128 }, { 37591780, 24844970 }, + { 33853231, 33825297 }, { 24842049, 37509013 }, { 15896798, 33757841 }, { 12211841, 24812544 }, { 15896783, 15868739 } }, + erExternalPerimeter, 1.)); + double len = loop.length(); + THEN("split_at() preserves total length") { + loop.split_at({ 15896783, 15868739 }, false, 0); + REQUIRE(loop.length() == Approx(len)); + } + } +} + +SCENARIO("ExtrusionEntityCollection: Basics", "[ExtrusionEntity]") +{ + Polyline polyline { { 100, 100 }, { 200, 100 }, { 200, 200 } }; + ExtrusionPath path = new_extrusion_path(polyline, erExternalPerimeter, 1.); + ExtrusionLoop loop; + loop.paths.emplace_back(new_extrusion_path(Polygon(polyline.points).split_at_first_point(), erInternalInfill, 1.)); + ExtrusionEntityCollection collection; + collection.append(path); + THEN("no_sort is false by default") { + REQUIRE(! collection.no_sort); + } + collection.append(collection); + THEN("append ExtrusionEntityCollection") { + REQUIRE(collection.entities.size() == 2); + } + collection.append(path); + THEN("append ExtrusionPath") { + REQUIRE(collection.entities.size() == 3); + } + collection.append(loop); + THEN("append ExtrusionLoop") { + REQUIRE(collection.entities.size() == 4); + } + THEN("appended collection was duplicated") { + REQUIRE(dynamic_cast(collection.entities[1])->entities.size() == 1); + } + WHEN("cloned") { + auto coll2 = std::unique_ptr(dynamic_cast(collection.clone())); + THEN("expected no_sort value") { + assert(! coll2->no_sort); + } + coll2->no_sort = true; + THEN("no_sort is kept after clone") { + auto coll3 = std::unique_ptr(dynamic_cast(coll2->clone())); + assert(coll3->no_sort); + } + } +} + +SCENARIO("ExtrusionEntityCollection: Polygon flattening", "[ExtrusionEntity]") +{ srand(0xDEADBEEF); // consistent seed for test reproducibility. // Generate one specific random path set and save it for later comparison @@ -101,7 +348,7 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") { { {0,20}, {0,18}, {0,15} }, { {0,10}, {0,8}, {0,5} } }, - { 0,30 } + { 0, 30 } }, { { @@ -112,7 +359,7 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") { { {20,5}, {15,5}, {10,5} }, { {15,0}, {10,0}, {4,0} } }, - { 30,0 } + { 30, 0 } }, { { @@ -123,7 +370,7 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") { { {20,5}, {15,5}, {10,5} }, { {15,0}, {10,0}, {4,0} } }, - { 30,0 } + { 30, 0 } }, }; for (const Test &test : tests) { @@ -132,12 +379,24 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") { ExtrusionEntityCollection unchained_extrusions; extrusion_entities_append_paths(unchained_extrusions.entities, test.unchained, erInternalInfill, 0., 0.4f, 0.3f); - ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point); - REQUIRE(chained_extrusions.entities.size() == test.chained.size()); - for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) { - const Points &p1 = test.chained[i].points; - const Points &p2 = dynamic_cast(chained_extrusions.entities[i])->polyline.points; - REQUIRE(p1 == p2); + THEN("Chaining works") { + ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point); + REQUIRE(chained_extrusions.entities.size() == test.chained.size()); + for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) { + const Points &p1 = test.chained[i].points; + const Points &p2 = dynamic_cast(chained_extrusions.entities[i])->polyline.points; + REQUIRE(p1 == p2); + } + } + THEN("Chaining produces no change with no_sort") { + unchained_extrusions.no_sort = true; + ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point); + REQUIRE(chained_extrusions.entities.size() == test.unchained.size()); + for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) { + const Points &p1 = test.unchained[i].points; + const Points &p2 = dynamic_cast(chained_extrusions.entities[i])->polyline.points; + REQUIRE(p1 == p2); + } } } } diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index 75eb16a97..af59fca2a 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -3,14 +3,17 @@ #include #include +#include "libslic3r/libslic3r.h" + #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Fill/Fill.hpp" #include "libslic3r/Flow.hpp" +#include "libslic3r/Layer.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/Point.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SVG.hpp" -#include "libslic3r/libslic3r.h" #include "test_data.hpp" @@ -329,6 +332,130 @@ SCENARIO("Infill only where needed", "[Fill]") } } +SCENARIO("Combine infill", "[Fill]") +{ + { + auto test = [](const DynamicPrintConfig &config) { + std::string gcode = Test::slice({ Test::TestMesh::cube_20x20x20 }, config); + THEN("infill_every_layers does not crash") { + REQUIRE(! gcode.empty()); + } + + Slic3r::GCodeReader parser; + int tool = -1; + std::set layers; // layer_z => 1 + std::map layer_infill; // layer_z => has_infill + const int infill_extruder = config.opt_int("infill_extruder"); + const int support_material_extruder = config.opt_int("support_material_extruder"); + parser.parse_buffer(gcode, + [&tool, &layers, &layer_infill, infill_extruder, support_material_extruder](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + coord_t z = line.new_Z(self) / SCALING_FACTOR; + if (boost::starts_with(line.cmd(), "T")) { + tool = atoi(line.cmd().data() + 1); + } else if (line.cmd_is("G1") && line.extruding(self) && line.dist_XY(self) > 0 && tool + 1 != support_material_extruder) { + if (tool + 1 == infill_extruder) + layer_infill[z] = true; + else if (auto it = layer_infill.find(z); it == layer_infill.end()) + layer_infill.insert(it, std::make_pair(z, false)); + } + // Previously, all G-code commands had a fixed number of decimal points with means with redundant zeros after decimal points. + // We changed this behavior and got rid of these redundant padding zeros, which caused this test to fail + // because the position in Z-axis is compared as a string, and previously, G-code contained the following two commands: + // "G1 Z5 F5000 ; lift nozzle" + // "G1 Z5.000 F7800.000" + // That has a different Z-axis position from the view of string comparisons of floating-point numbers. + // To correct the computation of the number of printed layers, even in the case of string comparisons of floating-point numbers, + // we filtered out the G-code command with the commend 'lift nozzle'. + if (line.cmd_is("G1") && line.dist_Z(self) != 0 && line.comment().find("lift nozzle") == std::string::npos) + layers.insert(z); + }); + + auto layers_with_perimeters = int(layer_infill.size()); + auto layers_with_infill = int(std::count_if(layer_infill.begin(), layer_infill.end(), [](auto &v){ return v.second; })); + THEN("expected number of layers") { + REQUIRE(layers.size() == layers_with_perimeters + config.opt_int("raft_layers")); + } + + if (config.opt_int("raft_layers") == 0) { + // first infill layer printed directly on print bed is not combined, so we don't consider it. + -- layers_with_infill; + -- layers_with_perimeters; + } + + // we expect that infill is generated for half the number of combined layers + // plus for each single layer that was not combined (remainder) + THEN("infill is only present in correct number of layers") { + int infill_every = config.opt_int("infill_every_layers"); + REQUIRE(layers_with_infill == int(layers_with_perimeters / infill_every) + (layers_with_perimeters % infill_every)); + } + }; + + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "nozzle_diameter", "0.5, 0.5, 0.5, 0.5" }, + { "layer_height", 0.2 }, + { "first_layer_height", 0.2 }, + { "infill_every_layers", 2 }, + { "perimeter_extruder", 1 }, + { "infill_extruder", 2 }, + { "wipe_into_infill", false }, + { "support_material_extruder", 3 }, + { "support_material_interface_extruder", 3 }, + { "top_solid_layers", 0 }, + { "bottom_solid_layers", 0 } + }); + + test(config); + + // Reuse the config above + config.set_deserialize_strict({ + { "skirts", 0 }, // prevent usage of perimeter_extruder in raft layers + { "raft_layers", 5 } + }); + test(config); + } + + WHEN("infill_every_layers == 2") { + Slic3r::Print print; + Slic3r::Test::init_and_process_print({ Test::TestMesh::cube_20x20x20 }, print, { + { "nozzle_diameter", "0.5" }, + { "layer_height", 0.2 }, + { "first_layer_height", 0.2 }, + { "infill_every_layers", 2 } + }); + THEN("infill combination produces internal void surfaces") { + bool has_void = false; + for (const Layer *layer : print.get_object(0)->layers()) + if (layer->get_region(0)->fill_surfaces().filter_by_type(stInternalVoid).size() > 0) { + has_void = true; + break; + } + REQUIRE(has_void); + } + } + + WHEN("infill_every_layers disabled") { + // we disable combination after infill has been generated + Slic3r::Print print; + Slic3r::Test::init_and_process_print({ Test::TestMesh::cube_20x20x20 }, print, { + { "nozzle_diameter", "0.5" }, + { "layer_height", 0.2 }, + { "first_layer_height", 0.2 }, + { "infill_every_layers", 1 } + }); + + THEN("infill combination is idempotent") { + bool has_infill_on_each_layer = true; + for (const Layer *layer : print.get_object(0)->layers()) + if (layer->get_region(0)->fill_surfaces().empty()) { + has_infill_on_each_layer = false; + break; + } + REQUIRE(has_infill_on_each_layer); + } + } +} + SCENARIO("Infill density zero", "[Fill]") { WHEN("20mm cube is sliced") { diff --git a/tests/fff_print/test_perimeters.cpp b/tests/fff_print/test_perimeters.cpp index 8fc75e744..decdb4802 100644 --- a/tests/fff_print/test_perimeters.cpp +++ b/tests/fff_print/test_perimeters.cpp @@ -44,7 +44,7 @@ SCENARIO("Perimeter nesting", "[Perimeters]") ExtrusionEntityCollection loops; ExtrusionEntityCollection gap_fill; - SurfaceCollection fill_surfaces; + ExPolygons fill_expolygons; Flow flow(1., 1., 1.); PerimeterGenerator::Parameters perimeter_generator_params( 1., // layer height @@ -67,7 +67,7 @@ SCENARIO("Perimeter nesting", "[Perimeters]") // cache: lower_layer_polygons_cache, // output: - loops, gap_fill, fill_surfaces); + loops, gap_fill, fill_expolygons); THEN("expected number of collections") { REQUIRE(loops.entities.size() == data.expolygons.size()); diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index efc07af31..237fb0a9a 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -46,11 +46,7 @@ set(XS_XSP_FILES ${XSP_DIR}/BoundingBox.xsp ${XSP_DIR}/Config.xsp ${XSP_DIR}/ExPolygon.xsp - ${XSP_DIR}/ExtrusionEntityCollection.xsp - ${XSP_DIR}/ExtrusionLoop.xsp - ${XSP_DIR}/ExtrusionPath.xsp ${XSP_DIR}/Geometry.xsp - ${XSP_DIR}/Layer.xsp ${XSP_DIR}/Line.xsp ${XSP_DIR}/Model.xsp ${XSP_DIR}/Point.xsp diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index 87fb267c5..de3ff0102 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -159,8 +159,6 @@ for my $class (qw( Slic3r::ExtrusionPath Slic3r::ExtrusionPath::Collection Slic3r::Geometry::BoundingBox - Slic3r::Layer - Slic3r::Layer::Region Slic3r::Line Slic3r::Model Slic3r::Model::Instance diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 8484b1d64..62a80d018 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -8,8 +8,6 @@ REGISTER_CLASS(ExtrusionPath, "ExtrusionPath"); REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop"); REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection"); REGISTER_CLASS(GCode, "GCode"); -REGISTER_CLASS(Layer, "Layer"); -REGISTER_CLASS(LayerRegion, "Layer::Region"); REGISTER_CLASS(Line, "Line"); REGISTER_CLASS(Polygon, "Polygon"); REGISTER_CLASS(Polyline, "Polyline"); diff --git a/xs/t/07_extrusionpath.t b/xs/t/07_extrusionpath.t deleted file mode 100644 index 084b4f03e..000000000 --- a/xs/t/07_extrusionpath.t +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 5; - -my $points = [ - [100, 100], - [200, 100], - [200, 200], -]; - -my $path = Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polyline->new(@$points), - role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, - mm3_per_mm => 1, -); - -$path->reverse; -is_deeply $path->polyline->pp, [ reverse @$points ], 'reverse path'; - -$path->append([ 150, 150 ]); -is scalar(@$path), 4, 'append to path'; - -$path->pop_back; -is scalar(@$path), 3, 'pop_back from path'; - -ok $path->first_point->coincides_with($path->polyline->[0]), 'first_point'; - -$path = $path->clone; - -is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'role'; - -__END__ diff --git a/xs/t/08_extrusionloop.t b/xs/t/08_extrusionloop.t deleted file mode 100644 index 3abfbd728..000000000 --- a/xs/t/08_extrusionloop.t +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use List::Util qw(sum); -use Slic3r::XS; -use Test::More tests => 46; - -{ - my $square = [ - [100, 100], - [200, 100], - [200, 200], - [100, 200], - ]; - my $square_p = Slic3r::Polygon->new(@$square); - - my $loop = Slic3r::ExtrusionLoop->new; - $loop->append(Slic3r::ExtrusionPath->new( - polyline => $square_p->split_at_first_point, - role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, - mm3_per_mm => 1, - )); - - isa_ok $loop, 'Slic3r::ExtrusionLoop'; - isa_ok $loop->polygon, 'Slic3r::Polygon', 'loop polygon'; - is $loop->polygon->area, $square_p->area, 'polygon area'; - is $loop->length, $square_p->length(), 'loop length'; - - $loop = $loop->clone; - - is scalar(@$loop), 1, 'loop contains one path'; - { - my $path = $loop->[0]; - is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'role'; - } - - $loop->split_at_vertex($square_p->[2]); - is scalar(@$loop), 1, 'splitting a single-path loop results in a single path'; - is scalar(@{$loop->[0]->polyline}), 5, 'path has correct number of points'; - ok $loop->[0]->polyline->[0]->coincides_with($square_p->[2]), 'expected point order'; - ok $loop->[0]->polyline->[1]->coincides_with($square_p->[3]), 'expected point order'; - ok $loop->[0]->polyline->[2]->coincides_with($square_p->[0]), 'expected point order'; - ok $loop->[0]->polyline->[3]->coincides_with($square_p->[1]), 'expected point order'; - ok $loop->[0]->polyline->[4]->coincides_with($square_p->[2]), 'expected point order'; -} - -{ - my $polyline1 = Slic3r::Polyline->new([100,100], [200,100], [200,200]); - my $polyline2 = Slic3r::Polyline->new([200,200], [100,200], [100,100]); - - my $loop = Slic3r::ExtrusionLoop->new_from_paths( - Slic3r::ExtrusionPath->new( - polyline => $polyline1, - role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, - mm3_per_mm => 1, - ), - Slic3r::ExtrusionPath->new( - polyline => $polyline2, - role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, - mm3_per_mm => 1, - ), - ); - my $tot_len = sum($polyline1->length, $polyline2->length); - is $loop->length, $tot_len, 'length'; - is scalar(@$loop), 2, 'loop contains two paths'; - - { - # check splitting at intermediate point - my $loop2 = $loop->clone; - isa_ok $loop2, 'Slic3r::ExtrusionLoop'; - $loop2->split_at_vertex($polyline1->[1]); - is $loop2->length, $tot_len, 'length after splitting is unchanged'; - is scalar(@$loop2), 3, 'loop contains three paths after splitting'; - ok $loop2->[0]->polyline->[0]->coincides_with($polyline1->[1]), 'expected starting point'; - ok $loop2->[-1]->polyline->[-1]->coincides_with($polyline1->[1]), 'expected ending point'; - ok $loop2->[0]->polyline->[-1]->coincides_with($loop2->[1]->polyline->[0]), 'paths have common point'; - ok $loop2->[1]->polyline->[-1]->coincides_with($loop2->[2]->polyline->[0]), 'paths have common point'; - is $loop2->[0]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; - is $loop2->[1]->role, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, 'expected order after splitting'; - is $loop2->[2]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; - is scalar(@{$loop2->[0]->polyline}), 2, 'path has correct number of points'; - is scalar(@{$loop2->[1]->polyline}), 3, 'path has correct number of points'; - is scalar(@{$loop2->[2]->polyline}), 2, 'path has correct number of points'; - - my @paths = @{$loop2->clip_end(3)}; - is sum(map $_->length, @paths), $loop2->length - 3, 'returned paths have expected length'; - } - - { - # check splitting at endpoint - my $loop2 = $loop->clone; - $loop2->split_at_vertex($polyline2->[0]); - is $loop2->length, $tot_len, 'length after splitting is unchanged'; - is scalar(@$loop2), 2, 'loop contains two paths after splitting'; - ok $loop2->[0]->polyline->[0]->coincides_with($polyline2->[0]), 'expected starting point'; - ok $loop2->[-1]->polyline->[-1]->coincides_with($polyline2->[0]), 'expected ending point'; - ok $loop2->[0]->polyline->[-1]->coincides_with($loop2->[1]->polyline->[0]), 'paths have common point'; - ok $loop2->[1]->polyline->[-1]->coincides_with($loop2->[0]->polyline->[0]), 'paths have common point'; - is $loop2->[0]->role, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, 'expected order after splitting'; - is $loop2->[1]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; - is scalar(@{$loop2->[0]->polyline}), 3, 'path has correct number of points'; - is scalar(@{$loop2->[1]->polyline}), 3, 'path has correct number of points'; - } - - { - my $loop2 = $loop->clone; - my $point = Slic3r::Point->new(250,150); - $loop2->split_at($point); - is $loop2->length, $tot_len, 'length after splitting is unchanged'; - is scalar(@$loop2), 3, 'loop contains three paths after splitting'; - my $expected_start_point = Slic3r::Point->new(200,150); - ok $loop2->[0]->polyline->[0]->coincides_with($expected_start_point), 'expected starting point'; - ok $loop2->[-1]->polyline->[-1]->coincides_with($expected_start_point), 'expected ending point'; - } -} - -{ - my @polylines = ( - Slic3r::Polyline->new([59312736,4821067],[64321068,4821067],[64321068,4821067],[64321068,9321068],[59312736,9321068]), - Slic3r::Polyline->new([59312736,9321068],[9829401,9321068]), - Slic3r::Polyline->new([9829401,9321068],[4821067,9321068],[4821067,4821067],[9829401,4821067]), - Slic3r::Polyline->new([9829401,4821067],[59312736,4821067]), - ); - my $loop = Slic3r::ExtrusionLoop->new; - $loop->append($_) for ( - Slic3r::ExtrusionPath->new(polyline => $polylines[0], role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1), - Slic3r::ExtrusionPath->new(polyline => $polylines[1], role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1), - Slic3r::ExtrusionPath->new(polyline => $polylines[2], role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1), - Slic3r::ExtrusionPath->new(polyline => $polylines[3], role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1), - ); - my $len = $loop->length; - my $point = Slic3r::Point->new(4821067,9321068); - $loop->split_at_vertex($point) or $loop->split_at($point); - is $loop->length, $len, 'total length is preserved after splitting'; - is_deeply [ map $_->role, @$loop ], [ - Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, - Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, - Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, - Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, - Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, - ], 'order is correctly preserved after splitting'; -} - -{ - my $loop = Slic3r::ExtrusionLoop->new; - $loop->append(Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polyline->new([15896783,15868739],[24842049,12117558],[33853238,15801279],[37591780,24780128],[37591780,24844970],[33853231,33825297],[24842049,37509013],[15896798,33757841],[12211841,24812544],[15896783,15868739]), - role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1 - )); - my $len = $loop->length; - $loop->split_at(Slic3r::Point->new(15896783,15868739)); - is $loop->length, $len, 'split_at() preserves total length'; -} - -__END__ diff --git a/xs/t/12_extrusionpathcollection.t b/xs/t/12_extrusionpathcollection.t deleted file mode 100644 index e02854245..000000000 --- a/xs/t/12_extrusionpathcollection.t +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 13; - -my $points = [ - [100, 100], - [200, 100], - [200, 200], -]; - -my $path = Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polyline->new(@$points), - role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, - mm3_per_mm => 1, -); - -my $loop = Slic3r::ExtrusionLoop->new_from_paths( - Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polygon->new(@$points)->split_at_first_point, - role => Slic3r::ExtrusionPath::EXTR_ROLE_FILL, - mm3_per_mm => 1, - ), -); - -my $collection = Slic3r::ExtrusionPath::Collection->new( - $path, -); -isa_ok $collection, 'Slic3r::ExtrusionPath::Collection', 'collection object with items in constructor'; -ok !$collection->no_sort, 'no_sort is false by default'; - -$collection->append($collection); -is scalar(@$collection), 2, 'append ExtrusionPath::Collection'; - -$collection->append($path); -is scalar(@$collection), 3, 'append ExtrusionPath'; - -$collection->append($loop); -is scalar(@$collection), 4, 'append ExtrusionLoop'; - -is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated'; - -{ - my $collection_loop = $collection->[3]; - $collection_loop->polygon->scale(2); - is_deeply $collection->[3]->polygon->pp, $collection_loop->polygon->pp, 'items are returned by reference'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), - Slic3r::Polyline->new([0,15], [0,18], [0,20]), - Slic3r::Polyline->new([0,10], [0,8], [0,5]), - ); - is_deeply - [ map $_->y, map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], - [20, 18, 15, 10, 8, 5], - 'chained_path_from'; - is_deeply - [ map $_->y, map @{$_->polyline}, @{$collection->chained_path(0)} ], - [15, 18, 20, 10, 8, 5], - 'chained_path'; -} - -{ - my $collection = Slic3r::ExtrusionPath::Collection->new( - map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), - Slic3r::Polyline->new([15,0], [10,0], [4,0]), - Slic3r::Polyline->new([10,5], [15,5], [20,5]), - ); - is_deeply - [ map $_->x, map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], - [reverse 4, 10, 15, 10, 15, 20], - 'chained_path_from'; - - $collection->no_sort(1); - my @foo = @{$collection->chained_path(0)}; - pass 'chained_path with no_sort'; -} - -{ - my $coll2 = $collection->clone; - ok !$coll2->no_sort, 'expected no_sort value'; - $coll2->no_sort(1); - ok $coll2->clone->no_sort, 'no_sort is kept after clone'; -} - -__END__ diff --git a/xs/xsp/ExtrusionEntityCollection.xsp b/xs/xsp/ExtrusionEntityCollection.xsp deleted file mode 100644 index 1c3337303..000000000 --- a/xs/xsp/ExtrusionEntityCollection.xsp +++ /dev/null @@ -1,102 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/ExtrusionEntityCollection.hpp" -%} - -%name{Slic3r::ExtrusionPath::Collection} class ExtrusionEntityCollection { - %name{_new} ExtrusionEntityCollection(); - ~ExtrusionEntityCollection(); - Clone clone() - %code{% RETVAL = (ExtrusionEntityCollection*)THIS->clone(); %}; - void reverse(); - void clear(); - ExtrusionEntityCollection* chained_path(bool no_reverse, ExtrusionRole role = erMixed) - %code{% - if (no_reverse) - croak("no_reverse must be false"); - RETVAL = new ExtrusionEntityCollection(); - *RETVAL = THIS->chained_path_from(THIS->entities.front()->first_point()); - %}; - ExtrusionEntityCollection* chained_path_from(Point* start_near, bool no_reverse, ExtrusionRole role = erMixed) - %code{% - if (no_reverse) - croak("no_reverse must be false"); - RETVAL = new ExtrusionEntityCollection(); - *RETVAL = THIS->chained_path_from(*start_near, role); - %}; - Clone first_point(); - Clone last_point(); - int count() - %code{% RETVAL = THIS->entities.size(); %}; - int items_count() - %code{% RETVAL = THIS->items_count(); %}; - ExtrusionEntityCollection* flatten() - %code{% - RETVAL = new ExtrusionEntityCollection(); - *RETVAL = THIS->flatten(); - %}; - double min_mm3_per_mm(); - bool empty() - %code{% RETVAL = THIS->entities.empty(); %}; - Polygons polygons_covered_by_width(); - Polygons polygons_covered_by_spacing(); -%{ - -SV* -ExtrusionEntityCollection::arrayref() - CODE: - AV* av = newAV(); - av_fill(av, THIS->entities.size()-1); - int i = 0; - for (ExtrusionEntitiesPtr::iterator it = THIS->entities.begin(); it != THIS->entities.end(); ++it) { - SV* sv = newSV(0); - // return our item by reference - if (ExtrusionPath* path = dynamic_cast(*it)) { - sv_setref_pv( sv, perl_class_name_ref(path), path ); - } else if (ExtrusionLoop* loop = dynamic_cast(*it)) { - sv_setref_pv( sv, perl_class_name_ref(loop), loop ); - } else if (ExtrusionEntityCollection* collection = dynamic_cast(*it)) { - sv_setref_pv( sv, perl_class_name_ref(collection), collection ); - } else { - croak("Unexpected type in ExtrusionEntityCollection"); - } - av_store(av, i++, sv); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -void -ExtrusionEntityCollection::append(...) - CODE: - for (unsigned int i = 1; i < items; i++) { - if(!sv_isobject( ST(i) ) || (SvTYPE(SvRV( ST(i) )) != SVt_PVMG)) { - croak("Argument %d is not object", i); - } - ExtrusionEntity* entity = (ExtrusionEntity *)SvIV((SV*)SvRV( ST(i) )); - // append COPIES - if (ExtrusionPath* path = dynamic_cast(entity)) { - THIS->entities.push_back( new ExtrusionPath(*path) ); - } else if (ExtrusionLoop* loop = dynamic_cast(entity)) { - THIS->entities.push_back( new ExtrusionLoop(*loop) ); - } else if(ExtrusionEntityCollection* collection = dynamic_cast(entity)) { - THIS->entities.push_back( collection->clone() ); - } else { - croak("Argument %d is of unknown type", i); - } - } - -bool -ExtrusionEntityCollection::no_sort(...) - CODE: - if (items > 1) { - THIS->no_sort = SvTRUE(ST(1)); - } - RETVAL = THIS->no_sort; - OUTPUT: - RETVAL - -%} -}; diff --git a/xs/xsp/ExtrusionLoop.xsp b/xs/xsp/ExtrusionLoop.xsp deleted file mode 100644 index ded17cb19..000000000 --- a/xs/xsp/ExtrusionLoop.xsp +++ /dev/null @@ -1,65 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/ExtrusionEntity.hpp" -%} - -%name{Slic3r::ExtrusionLoop} class ExtrusionLoop { - ExtrusionLoop(); - ~ExtrusionLoop(); - Clone clone() - %code{% RETVAL = THIS; %}; - void reverse(); - bool make_clockwise(); - bool make_counter_clockwise(); - Clone first_point(); - Clone last_point(); - Clone polygon(); - void append(ExtrusionPath* path) - %code{% THIS->paths.push_back(*path); %}; - double length(); - bool split_at_vertex(Point* point) - %code{% RETVAL = THIS->split_at_vertex(*point); %}; - void split_at(Point* point, int prefer_non_overhang = 0, double scaled_epsilon = 0.) - %code{% THIS->split_at(*point, prefer_non_overhang != 0, scaled_epsilon); %}; - ExtrusionPaths clip_end(double distance) - %code{% THIS->clip_end(distance, &RETVAL); %}; - bool has_overhang_point(Point* point) - %code{% RETVAL = THIS->has_overhang_point(*point); %}; - ExtrusionRole role() const; - ExtrusionLoopRole loop_role() const; - Polygons polygons_covered_by_width(); - Polygons polygons_covered_by_spacing(); -%{ - -SV* -ExtrusionLoop::arrayref() - CODE: - AV* av = newAV(); - av_fill(av, THIS->paths.size()-1); - for (ExtrusionPaths::iterator it = THIS->paths.begin(); it != THIS->paths.end(); ++it) { - av_store(av, it - THIS->paths.begin(), perl_to_SV_ref(*it)); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -%} -}; - -%package{Slic3r::ExtrusionLoop}; -%{ - -IV -_constant() - ALIAS: - EXTRL_ROLE_DEFAULT = elrDefault - EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER = elrContourInternalPerimeter - EXTRL_ROLE_SKIRT = elrSkirt - PROTOTYPE: - CODE: - RETVAL = ix; - OUTPUT: RETVAL - -%} diff --git a/xs/xsp/ExtrusionPath.xsp b/xs/xsp/ExtrusionPath.xsp deleted file mode 100644 index 1dbc00917..000000000 --- a/xs/xsp/ExtrusionPath.xsp +++ /dev/null @@ -1,126 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/ExtrusionEntity.hpp" -#include "libslic3r/ExtrusionEntityCollection.hpp" -%} - -%name{Slic3r::ExtrusionPath} class ExtrusionPath { - ~ExtrusionPath(); - SV* arrayref() - %code{% RETVAL = to_AV(&THIS->polyline); %}; - SV* pp() - %code{% RETVAL = to_SV_pureperl(&THIS->polyline); %}; - void pop_back() - %code{% THIS->polyline.points.pop_back(); %}; - void reverse(); - Lines lines() - %code{% RETVAL = THIS->polyline.lines(); %}; - Clone first_point(); - Clone last_point(); - void clip_end(double distance); - void simplify(double tolerance); - double length(); - ExtrusionRole role() const; - bool is_bridge() - %code{% RETVAL = is_bridge(THIS->role()); %}; - Polygons polygons_covered_by_width(); - Polygons polygons_covered_by_spacing(); -%{ - -ExtrusionPath* -_new(CLASS, polyline_sv, role, mm3_per_mm, width, height) - char* CLASS; - SV* polyline_sv; - ExtrusionRole role; - double mm3_per_mm; - float width; - float height; - CODE: - RETVAL = new ExtrusionPath (role); - from_SV_check(polyline_sv, &RETVAL->polyline); - RETVAL->mm3_per_mm = mm3_per_mm; - RETVAL->width = width; - RETVAL->height = height; - OUTPUT: - RETVAL - -Ref -ExtrusionPath::polyline(...) - CODE: - if (items > 1) { - from_SV_check(ST(1), &THIS->polyline); - } - RETVAL = &(THIS->polyline); - OUTPUT: - RETVAL - -double -ExtrusionPath::mm3_per_mm(...) - CODE: - if (items > 1) { - THIS->mm3_per_mm = (double)SvNV(ST(1)); - } - RETVAL = THIS->mm3_per_mm; - OUTPUT: - RETVAL - -float -ExtrusionPath::width(...) - CODE: - if (items > 1) { - THIS->width = (float)SvNV(ST(1)); - } - RETVAL = THIS->width; - OUTPUT: - RETVAL - -float -ExtrusionPath::height(...) - CODE: - if (items > 1) { - THIS->height = (float)SvNV(ST(1)); - } - RETVAL = THIS->height; - OUTPUT: - RETVAL - -void -ExtrusionPath::append(...) - CODE: - for (unsigned int i = 1; i < items; i++) { - Point p; - from_SV_check(ST(i), &p); - THIS->polyline.points.push_back(p); - } - -%} -}; - -%package{Slic3r::ExtrusionPath}; -%{ - -IV -_constant() - ALIAS: - EXTR_ROLE_NONE = erNone - EXTR_ROLE_PERIMETER = erPerimeter - EXTR_ROLE_EXTERNAL_PERIMETER = erExternalPerimeter - EXTR_ROLE_OVERHANG_PERIMETER = erOverhangPerimeter - EXTR_ROLE_FILL = erInternalInfill - EXTR_ROLE_SOLIDFILL = erSolidInfill - EXTR_ROLE_TOPSOLIDFILL = erTopSolidInfill - EXTR_ROLE_BRIDGE = erBridgeInfill - EXTR_ROLE_GAPFILL = erGapFill - EXTR_ROLE_SKIRT = erSkirt - EXTR_ROLE_SUPPORTMATERIAL = erSupportMaterial - EXTR_ROLE_SUPPORTMATERIAL_INTERFACE = erSupportMaterialInterface - EXTR_ROLE_MIXED = erMixed - PROTOTYPE: - CODE: - RETVAL = ix; - OUTPUT: RETVAL - -%} - diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp deleted file mode 100644 index da2962c1e..000000000 --- a/xs/xsp/Layer.xsp +++ /dev/null @@ -1,72 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/Layer.hpp" -%} - -%name{Slic3r::Layer::Region} class LayerRegion { - // owned by Layer, no constructor/destructor - - Ref layer(); - Ref region() - %code%{ RETVAL = &THIS->region(); %}; - - Ref slices() - %code%{ RETVAL = const_cast(&THIS->slices()); %}; - Ref thin_fills() - %code%{ RETVAL = const_cast(&THIS->thin_fills()); %}; - Ref fill_surfaces() - %code%{ RETVAL = const_cast(&THIS->fill_surfaces()); %}; - Ref perimeters() - %code%{ RETVAL = const_cast(&THIS->perimeters()); %}; - Ref fills() - %code%{ RETVAL = const_cast(&THIS->fills()); %}; - - void prepare_fill_surfaces(); - void make_perimeters(SurfaceCollection* slices, SurfaceCollection* fill_surfaces) - %code%{ THIS->make_perimeters(*slices, fill_surfaces); %}; - double infill_area_threshold(); - - void export_region_slices_to_svg(const char *path) const; - void export_region_fill_surfaces_to_svg(const char *path) const; - void export_region_slices_to_svg_debug(const char *name) const; - void export_region_fill_surfaces_to_svg_debug(const char *name) const; -}; - -%name{Slic3r::Layer} class Layer { - // owned by PrintObject, no constructor/destructor - - Ref as_layer() - %code%{ RETVAL = THIS; %}; - - int id(); - void set_id(int id); - Ref object(); - bool slicing_errors() - %code%{ RETVAL = THIS->slicing_errors; %}; - coordf_t slice_z() - %code%{ RETVAL = THIS->slice_z; %}; - coordf_t print_z() - %code%{ RETVAL = THIS->print_z; %}; - coordf_t height() - %code%{ RETVAL = THIS->height; %}; - - size_t region_count(); - Ref get_region(int idx); - Ref add_region(PrintRegion* print_region); - - int ptr() - %code%{ RETVAL = (int)(intptr_t)THIS; %}; - - void make_slices(); - void backup_untyped_slices(); - void restore_untyped_slices(); - void make_perimeters(); - void make_fills(); - - void export_region_slices_to_svg(const char *path); - void export_region_fill_surfaces_to_svg(const char *path); - void export_region_slices_to_svg_debug(const char *name); - void export_region_fill_surfaces_to_svg_debug(const char *name); -}; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 6b6f6e9be..9379aea20 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -21,9 +21,6 @@ %code%{ RETVAL = &THIS->config(); %}; Clone bounding_box(); - size_t layer_count(); - Ref get_layer(int idx); - void slice(); }; @@ -35,10 +32,6 @@ %code%{ RETVAL = const_cast(&THIS->model()); %}; Ref config() %code%{ RETVAL = const_cast(static_cast(&THIS->config())); %}; - Ref skirt() - %code%{ RETVAL = const_cast(&THIS->skirt()); %}; - Ref brim() - %code%{ RETVAL = const_cast(&THIS->brim()); %}; double total_used_filament() %code%{ RETVAL = THIS->print_statistics().total_used_filament; %}; double total_extruded_volume() diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 42ca74292..dcf20bdab 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -70,18 +70,6 @@ ExPolygon* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -ExtrusionEntityCollection* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -ExtrusionPath* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -ExtrusionLoop* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - Surface* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T @@ -119,12 +107,6 @@ Print* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -LayerRegion* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - -Layer* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - Axis T_UV ExtrusionLoopRole T_UV ExtrusionRole T_UV @@ -137,7 +119,6 @@ Lines T_ARRAYREF Polygons T_ARRAYREF Polylines T_ARRAYREF ExPolygons T_ARRAYREF -ExtrusionPaths T_ARRAYREF Surfaces T_ARRAYREF # we return these types whenever we want the items to be returned @@ -149,7 +130,6 @@ ModelVolumePtrs* T_PTR_ARRAYREF_PTR ModelInstancePtrs* T_PTR_ARRAYREF_PTR PrintRegionPtrs* T_PTR_ARRAYREF_PTR PrintObjectPtrs* T_PTR_ARRAYREF_PTR -LayerPtrs* T_PTR_ARRAYREF_PTR # we return these types whenever we want the items to be returned # by reference and not marked ::Ref because they're newly allocated diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index bf1e8e2ac..e5bba9812 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -53,15 +53,6 @@ %typemap{Polygon*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{ExtrusionEntityCollection*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{ExtrusionPath*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -%typemap{ExtrusionLoop*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{TriangleMesh*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; @@ -86,19 +77,12 @@ %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{LayerRegion*}; -%typemap{Ref}{simple}; - -%typemap{Layer*}; -%typemap{Ref}{simple}; - %typemap{Points}; %typemap{Pointfs}; %typemap{Lines}; %typemap{Polygons}; %typemap{Polylines}; %typemap{ExPolygons}; -%typemap{ExtrusionPaths}; %typemap{Surfaces}; %typemap{Polygons*}; %typemap{TriangleMesh*}; From 57db091612262949f8acab43c3e97c0a60b5e0b7 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 27 Oct 2022 19:43:56 +0200 Subject: [PATCH 05/33] Removed Surface and SurfaceCollection from Perl bindings. --- xs/CMakeLists.txt | 2 - xs/lib/Slic3r/XS.pm | 58 ------------------- xs/src/perlglue.cpp | 3 - xs/t/05_surface.t | 71 ----------------------- xs/xsp/Surface.xsp | 107 ----------------------------------- xs/xsp/SurfaceCollection.xsp | 70 ----------------------- xs/xsp/my.map | 8 --- xs/xsp/typemap.xspt | 8 --- 8 files changed, 327 deletions(-) delete mode 100644 xs/t/05_surface.t delete mode 100644 xs/xsp/Surface.xsp delete mode 100644 xs/xsp/SurfaceCollection.xsp diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 237fb0a9a..f8e0d0fe2 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -53,8 +53,6 @@ set(XS_XSP_FILES ${XSP_DIR}/Polygon.xsp ${XSP_DIR}/Polyline.xsp ${XSP_DIR}/Print.xsp - ${XSP_DIR}/Surface.xsp - ${XSP_DIR}/SurfaceCollection.xsp ${XSP_DIR}/TriangleMesh.xsp ${XSP_DIR}/XS.xsp ) diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index de3ff0102..dee25afb8 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -48,61 +48,6 @@ use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; -package Slic3r::ExtrusionPath::Collection; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - -sub new { - my ($class, @paths) = @_; - - my $self = $class->_new; - $self->append(@paths); - return $self; -} - -package Slic3r::ExtrusionLoop; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - -sub new_from_paths { - my ($class, @paths) = @_; - - my $loop = $class->new; - $loop->append($_) for @paths; - return $loop; -} - -package Slic3r::ExtrusionPath; -use overload - '@{}' => sub { $_[0]->arrayref }, - 'fallback' => 1; - -sub new { - my ($class, %args) = @_; - - return $class->_new( - $args{polyline}, # required - $args{role}, # required - $args{mm3_per_mm} // die("Missing required mm3_per_mm in ExtrusionPath constructor"), - $args{width} // -1, - $args{height} // -1, - ); -} - -sub clone { - my ($self, %args) = @_; - - return __PACKAGE__->_new( - $args{polyline} // $self->polyline, - $args{role} // $self->role, - $args{mm3_per_mm} // $self->mm3_per_mm, - $args{width} // $self->width, - $args{height} // $self->height, - ); -} - package Slic3r::Surface; sub new { @@ -155,9 +100,6 @@ for my $class (qw( Slic3r::Config::Print Slic3r::Config::Static Slic3r::ExPolygon - Slic3r::ExtrusionLoop - Slic3r::ExtrusionPath - Slic3r::ExtrusionPath::Collection Slic3r::Geometry::BoundingBox Slic3r::Line Slic3r::Model diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 62a80d018..3f75617dd 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -4,9 +4,6 @@ namespace Slic3r { REGISTER_CLASS(ExPolygon, "ExPolygon"); -REGISTER_CLASS(ExtrusionPath, "ExtrusionPath"); -REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop"); -REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection"); REGISTER_CLASS(GCode, "GCode"); REGISTER_CLASS(Line, "Line"); REGISTER_CLASS(Polygon, "Polygon"); diff --git a/xs/t/05_surface.t b/xs/t/05_surface.t deleted file mode 100644 index 4d9eb5b89..000000000 --- a/xs/t/05_surface.t +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use Slic3r::XS; -use Test::More tests => 11; - -my $square = [ # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], -]; -my $hole_in_square = [ # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], -]; - -my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); -my $surface = Slic3r::Surface->new( - expolygon => $expolygon, - surface_type => Slic3r::Surface::S_TYPE_INTERNAL, -); - -$surface = $surface->clone; - -is $surface->surface_type, Slic3r::Surface::S_TYPE_INTERNAL, 'surface_type'; -$surface->surface_type(Slic3r::Surface::S_TYPE_BOTTOM); -is $surface->surface_type, Slic3r::Surface::S_TYPE_BOTTOM, 'modify surface_type'; - -$surface->bridge_angle(30); -is $surface->bridge_angle, 30, 'bridge_angle'; - -$surface->extra_perimeters(2); -is $surface->extra_perimeters, 2, 'extra_perimeters'; - -{ - my $surface2 = $surface->clone; - $surface2->expolygon->scale(2); - isnt $surface2->expolygon->area, $expolygon->area, 'expolygon is returned by reference'; -} - -{ - my $collection = Slic3r::Surface::Collection->new; - $collection->append($_) for $surface, $surface->clone; - is scalar(@$collection), 2, 'collection has the right number of items'; - is_deeply $collection->[0]->expolygon->pp, [$square, $hole_in_square], - 'collection returns a correct surface expolygon'; - $collection->clear; - is scalar(@$collection), 0, 'clear collection'; - $collection->append($surface); - is scalar(@$collection), 1, 'append to collection'; - - my $item = $collection->[0]; - $item->surface_type(Slic3r::Surface::S_TYPE_INTERNAL); - is $item->surface_type, $collection->[0]->surface_type, 'collection returns items by reference'; -} - -{ - my $collection = Slic3r::Surface::Collection->new; - $collection->append($_) for - Slic3r::Surface->new(expolygon => $expolygon, surface_type => Slic3r::Surface::S_TYPE_BOTTOM), - Slic3r::Surface->new(expolygon => $expolygon, surface_type => Slic3r::Surface::S_TYPE_BOTTOM), - Slic3r::Surface->new(expolygon => $expolygon, surface_type => Slic3r::Surface::S_TYPE_TOP); - is scalar(@{$collection->group}), 2, 'group() returns correct number of groups'; -} - -__END__ diff --git a/xs/xsp/Surface.xsp b/xs/xsp/Surface.xsp deleted file mode 100644 index 3fffea9ab..000000000 --- a/xs/xsp/Surface.xsp +++ /dev/null @@ -1,107 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/Surface.hpp" -%} - -%name{Slic3r::Surface} class Surface { - ~Surface(); - Ref expolygon() - %code{% RETVAL = &(THIS->expolygon); %}; - double thickness() - %code{% RETVAL = THIS->thickness; %}; - unsigned short thickness_layers() - %code{% RETVAL = THIS->thickness_layers; %}; - double area(); - bool is_solid() const; - bool is_external() const; - bool is_internal() const; - bool is_bottom() const; - bool is_bridge() const; -%{ - -Surface* -_new(CLASS, expolygon, surface_type, thickness, thickness_layers, bridge_angle, extra_perimeters) - char* CLASS; - ExPolygon* expolygon; - SurfaceType surface_type; - double thickness; - unsigned short thickness_layers; - double bridge_angle; - unsigned short extra_perimeters; - CODE: - RETVAL = new Surface (surface_type, *expolygon); - RETVAL->thickness = thickness; - RETVAL->thickness_layers = thickness_layers; - RETVAL->bridge_angle = bridge_angle; - RETVAL->extra_perimeters = extra_perimeters; - // we don't delete expolygon here because it's referenced by a Perl SV - // whose DESTROY will take care of destruction - OUTPUT: - RETVAL - -SurfaceType -Surface::surface_type(...) - CODE: - if (items > 1) { - THIS->surface_type = (SurfaceType)SvUV(ST(1)); - } - RETVAL = THIS->surface_type; - OUTPUT: - RETVAL - -double -Surface::bridge_angle(...) - CODE: - if (items > 1) { - THIS->bridge_angle = (double)SvNV(ST(1)); - } - RETVAL = THIS->bridge_angle; - OUTPUT: - RETVAL - -unsigned short -Surface::extra_perimeters(...) - CODE: - if (items > 1) { - THIS->extra_perimeters = (double)SvUV(ST(1)); - } - RETVAL = THIS->extra_perimeters; - OUTPUT: - RETVAL - -Polygons -Surface::polygons() - CODE: - RETVAL.push_back(THIS->expolygon.contour); - for (Polygons::iterator it = THIS->expolygon.holes.begin(); it != THIS->expolygon.holes.end(); ++it) { - RETVAL.push_back((*it)); - } - OUTPUT: - RETVAL - -%} -}; - -%package{Slic3r::Surface}; -%{ - -IV -_constant() - ALIAS: - S_TYPE_TOP = stTop - S_TYPE_BOTTOM = stBottom - S_TYPE_BOTTOMBRIDGE = stBottomBridge - S_TYPE_INTERNAL = stInternal - S_TYPE_INTERNALSOLID = stInternalSolid - S_TYPE_INTERNALBRIDGE = stInternalBridge - S_TYPE_INTERNALVOID = stInternalVoid - S_TYPW_PERIMETER = stPerimeter - PROTOTYPE: - CODE: - RETVAL = ix; - OUTPUT: RETVAL - -%} - diff --git a/xs/xsp/SurfaceCollection.xsp b/xs/xsp/SurfaceCollection.xsp deleted file mode 100644 index 0d31c5ae3..000000000 --- a/xs/xsp/SurfaceCollection.xsp +++ /dev/null @@ -1,70 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/SurfaceCollection.hpp" -%} - -%name{Slic3r::Surface::Collection} class SurfaceCollection { - %name{_new} SurfaceCollection(); - ~SurfaceCollection(); - void clear() - %code{% THIS->surfaces.clear(); %}; - void append(Surface* surface) - %code{% THIS->surfaces.push_back(*surface); %}; - int count() - %code{% RETVAL = THIS->surfaces.size(); %}; - void simplify(double tolerance); -%{ - -SV* -SurfaceCollection::arrayref() - CODE: - AV* av = newAV(); - av_fill(av, THIS->surfaces.size()-1); - int i = 0; - for (Surfaces::iterator it = THIS->surfaces.begin(); it != THIS->surfaces.end(); ++it) { - av_store(av, i++, perl_to_SV_ref(*it)); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -SV* -SurfaceCollection::filter_by_type(surface_type) - SurfaceType surface_type; - CODE: - AV* av = newAV(); - for (Surfaces::iterator it = THIS->surfaces.begin(); it != THIS->surfaces.end(); ++it) { - if ((*it).surface_type == surface_type) av_push(av, perl_to_SV_ref(*it)); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -SV* -SurfaceCollection::group() - CODE: - // perform grouping - std::vector groups; - THIS->group(&groups); - - // build return arrayref - AV* av = newAV(); - av_fill(av, groups.size()-1); - size_t i = 0; - for (std::vector::iterator it = groups.begin(); it != groups.end(); ++it) { - AV* innerav = newAV(); - av_fill(innerav, it->size()-1); - size_t j = 0; - for (SurfacesPtr::iterator it_s = it->begin(); it_s != it->end(); ++it_s) { - av_store(innerav, j++, perl_to_SV_clone_ref(**it_s)); - } - av_store(av, i++, newRV_noinc((SV*)innerav)); - } - RETVAL = newRV_noinc((SV*)av); - OUTPUT: - RETVAL - -%} -}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index dcf20bdab..5c5d10781 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -70,13 +70,6 @@ ExPolygon* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -Surface* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -SurfaceCollection* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - Model* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T @@ -119,7 +112,6 @@ Lines T_ARRAYREF Polygons T_ARRAYREF Polylines T_ARRAYREF ExPolygons T_ARRAYREF -Surfaces T_ARRAYREF # we return these types whenever we want the items to be returned # by reference and marked ::Ref because they're contained in another diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index e5bba9812..7fc61fe3f 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -56,13 +56,6 @@ %typemap{TriangleMesh*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{SurfaceCollection*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; - -%typemap{Surface*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{PrintState*}; %typemap{Ref}{simple}; @@ -83,7 +76,6 @@ %typemap{Polygons}; %typemap{Polylines}; %typemap{ExPolygons}; -%typemap{Surfaces}; %typemap{Polygons*}; %typemap{TriangleMesh*}; %typemap{Model*}; From 2eb04170187d09d70e6a20c696f5a6470b13e46b Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 27 Oct 2022 20:01:55 +0200 Subject: [PATCH 06/33] Further Perl eradication --- lib/Slic3r.pm | 6 ----- lib/Slic3r/ExPolygon.pm | 12 --------- lib/Slic3r/ExtrusionLoop.pm | 12 --------- lib/Slic3r/ExtrusionPath.pm | 13 ---------- lib/Slic3r/Geometry.pm | 17 ------------ lib/Slic3r/Layer.pm | 18 ------------- lib/Slic3r/Print/Object.pm | 9 ------- lib/Slic3r/Surface.pm | 15 ----------- t/geometry.t | 12 ++++----- xs/CMakeLists.txt | 1 - xs/lib/Slic3r/XS.pm | 5 ---- xs/xsp/BoundingBox.xsp | 52 ------------------------------------- xs/xsp/Polygon.xsp | 3 --- xs/xsp/Polyline.xsp | 2 -- xs/xsp/Print.xsp | 38 --------------------------- xs/xsp/my.map | 12 --------- xs/xsp/typemap.xspt | 34 ------------------------ 17 files changed, 6 insertions(+), 255 deletions(-) delete mode 100644 lib/Slic3r/ExPolygon.pm delete mode 100644 lib/Slic3r/ExtrusionLoop.pm delete mode 100644 lib/Slic3r/ExtrusionPath.pm delete mode 100644 lib/Slic3r/Layer.pm delete mode 100644 lib/Slic3r/Print/Object.pm delete mode 100644 lib/Slic3r/Surface.pm delete mode 100644 xs/xsp/BoundingBox.xsp diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 1e94e0f48..3b0167360 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -32,18 +32,12 @@ use Moo 1.003001; use Slic3r::XS; # import all symbols (constants etc.) before they get parsed use Slic3r::Config; -use Slic3r::ExPolygon; -use Slic3r::ExtrusionLoop; -use Slic3r::ExtrusionPath; use Slic3r::GCode::Reader; -use Slic3r::Layer; use Slic3r::Line; use Slic3r::Model; use Slic3r::Point; use Slic3r::Polygon; use Slic3r::Polyline; -use Slic3r::Print::Object; -use Slic3r::Surface; our $build = eval "use Slic3r::Build; 1"; # Scaling between the float and integer coordinates. diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm deleted file mode 100644 index 1b55849eb..000000000 --- a/lib/Slic3r/ExPolygon.pm +++ /dev/null @@ -1,12 +0,0 @@ -package Slic3r::ExPolygon; -use strict; -use warnings; - -# an ExPolygon is a polygon with holes - -sub bounding_box { - my $self = shift; - return $self->contour->bounding_box; -} - -1; diff --git a/lib/Slic3r/ExtrusionLoop.pm b/lib/Slic3r/ExtrusionLoop.pm deleted file mode 100644 index f0a857785..000000000 --- a/lib/Slic3r/ExtrusionLoop.pm +++ /dev/null @@ -1,12 +0,0 @@ -package Slic3r::ExtrusionLoop; -use strict; -use warnings; - -use parent qw(Exporter); - -our @EXPORT_OK = qw(EXTRL_ROLE_DEFAULT - EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER EXTRL_ROLE_SKIRT); -our %EXPORT_TAGS = (roles => \@EXPORT_OK); - - -1; diff --git a/lib/Slic3r/ExtrusionPath.pm b/lib/Slic3r/ExtrusionPath.pm deleted file mode 100644 index 3e9e6b8c7..000000000 --- a/lib/Slic3r/ExtrusionPath.pm +++ /dev/null @@ -1,13 +0,0 @@ -package Slic3r::ExtrusionPath; -use strict; -use warnings; - -use parent qw(Exporter); - -our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER - EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_GAPFILL EXTR_ROLE_BRIDGE - EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_SUPPORTMATERIAL_INTERFACE - EXTR_ROLE_NONE); -our %EXPORT_TAGS = (roles => \@EXPORT_OK); - -1; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 2e4f5a097..6a2161d28 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -34,21 +34,4 @@ sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR } sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR } sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR } -# 2D -sub bounding_box { - my ($points) = @_; - - my @x = map $_->x, @$points; - my @y = map $_->y, @$points; #,, - my @bb = (undef, undef, undef, undef); - for (0..$#x) { - $bb[X1] = $x[$_] if !defined $bb[X1] || $x[$_] < $bb[X1]; - $bb[X2] = $x[$_] if !defined $bb[X2] || $x[$_] > $bb[X2]; - $bb[Y1] = $y[$_] if !defined $bb[Y1] || $y[$_] < $bb[Y1]; - $bb[Y2] = $y[$_] if !defined $bb[Y2] || $y[$_] > $bb[Y2]; - } - - return @bb[X1,Y1,X2,Y2]; -} - 1; diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm deleted file mode 100644 index a4146f169..000000000 --- a/lib/Slic3r/Layer.pm +++ /dev/null @@ -1,18 +0,0 @@ -# Extends the C++ class Slic3r::Layer. - -package Slic3r::Layer; -use strict; -use warnings; - -# the following two were previously generated by Moo -sub print { - my $self = shift; - return $self->object->print; -} - -sub config { - my $self = shift; - return $self->object->config; -} - -1; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm deleted file mode 100644 index d5c19e4a4..000000000 --- a/lib/Slic3r/Print/Object.pm +++ /dev/null @@ -1,9 +0,0 @@ -package Slic3r::Print::Object; -# extends c++ class Slic3r::PrintObject (Print.xsp) -use strict; -use warnings; - -use List::Util qw(min max sum first); -use Slic3r::Surface ':types'; - -1; diff --git a/lib/Slic3r/Surface.pm b/lib/Slic3r/Surface.pm deleted file mode 100644 index 0c5bb5d23..000000000 --- a/lib/Slic3r/Surface.pm +++ /dev/null @@ -1,15 +0,0 @@ -package Slic3r::Surface; -use strict; -use warnings; - -require Exporter; -our @ISA = qw(Exporter); -our @EXPORT_OK = qw(S_TYPE_TOP S_TYPE_BOTTOM S_TYPE_BOTTOMBRIDGE S_TYPE_INTERNAL S_TYPE_INTERNALSOLID S_TYPE_INTERNALBRIDGE S_TYPE_INTERNALVOID); -our %EXPORT_TAGS = (types => \@EXPORT_OK); - -sub p { - my $self = shift; - return @{$self->polygons}; -} - -1; diff --git a/t/geometry.t b/t/geometry.t index 981621ee8..83fb72eea 100644 --- a/t/geometry.t +++ b/t/geometry.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 11; +plan tests => 10; BEGIN { use FindBin; @@ -57,11 +57,11 @@ my $polygons = [ #========================================================== -{ - my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ map Slic3r::Point->new(@$_), [0, 1], [10, 2], [20, 2] ]); - $bb->scale(2); - is_deeply [ $bb->min_point->pp, $bb->max_point->pp ], [ [0,2], [40,4] ], 'bounding box is scaled correctly'; -} +#{ +# my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ map Slic3r::Point->new(@$_), [0, 1], [10, 2], [20, 2] ]); +# $bb->scale(2); +# is_deeply [ $bb->min_point->pp, $bb->max_point->pp ], [ [0,2], [40,4] ], 'bounding box is scaled correctly'; +#} #========================================================== diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index f8e0d0fe2..b16f221b9 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -43,7 +43,6 @@ set(XS_MAIN_XS ${CMAKE_CURRENT_BINARY_DIR}/main.xs) set(XSP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/xsp) #FIXME list the dependecies explicitely, add dependency on the typemap. set(XS_XSP_FILES - ${XSP_DIR}/BoundingBox.xsp ${XSP_DIR}/Config.xsp ${XSP_DIR}/ExPolygon.xsp ${XSP_DIR}/Geometry.xsp diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index dee25afb8..60c9a9316 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -100,7 +100,6 @@ for my $class (qw( Slic3r::Config::Print Slic3r::Config::Static Slic3r::ExPolygon - Slic3r::Geometry::BoundingBox Slic3r::Line Slic3r::Model Slic3r::Model::Instance @@ -115,10 +114,6 @@ for my $class (qw( Slic3r::Polyline Slic3r::Polyline::Collection Slic3r::Print - Slic3r::Print::Object - Slic3r::Print::Region - Slic3r::Surface - Slic3r::Surface::Collection Slic3r::TriangleMesh )) { diff --git a/xs/xsp/BoundingBox.xsp b/xs/xsp/BoundingBox.xsp deleted file mode 100644 index 75592e7c3..000000000 --- a/xs/xsp/BoundingBox.xsp +++ /dev/null @@ -1,52 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/BoundingBox.hpp" -#include "libslic3r/Point.hpp" -%} - -%name{Slic3r::Geometry::BoundingBox} class BoundingBox { - BoundingBox(); - ~BoundingBox(); - Clone clone() - %code{% RETVAL = THIS; %}; - void merge(BoundingBox* bb) %code{% THIS->merge(*bb); %}; - void merge_point(Point* point) %code{% THIS->merge(*point); %}; - void scale(double factor); - void translate(double x, double y); - void offset(double delta); - bool contains_point(Point* point) %code{% RETVAL = THIS->contains(*point); %}; - bool overlap(BoundingBox* bbox) %code{% RETVAL = THIS->overlap(*bbox); %}; - Clone polygon(); - Clone size(); - Clone center(); - bool empty() %code{% RETVAL = empty(*THIS); %}; - double radius(); - Clone min_point() %code{% RETVAL = THIS->min; %}; - Clone max_point() %code{% RETVAL = THIS->max; %}; - int x_min() %code{% RETVAL = THIS->min(0); %}; - int x_max() %code{% RETVAL = THIS->max(0); %}; - int y_min() %code{% RETVAL = THIS->min(1); %}; - int y_max() %code{% RETVAL = THIS->max(1); %}; - void set_x_min(double val) %code{% THIS->min(0) = val; %}; - void set_x_max(double val) %code{% THIS->max(0) = val; %}; - void set_y_min(double val) %code{% THIS->min(1) = val; %}; - void set_y_max(double val) %code{% THIS->max(1) = val; %}; - std::string serialize() %code{% char buf[2048]; sprintf(buf, "%ld,%ld;%ld,%ld", THIS->min(0), THIS->min(1), THIS->max(0), THIS->max(1)); RETVAL = buf; %}; - bool defined() %code{% RETVAL = THIS->defined; %}; - -%{ - -BoundingBox* -new_from_points(CLASS, points) - char* CLASS - Points points - CODE: - RETVAL = new BoundingBox(points); - OUTPUT: - RETVAL - -%} -}; - diff --git a/xs/xsp/Polygon.xsp b/xs/xsp/Polygon.xsp index 873c4bcc9..7b1fbb83c 100644 --- a/xs/xsp/Polygon.xsp +++ b/xs/xsp/Polygon.xsp @@ -2,9 +2,7 @@ %{ #include -#include "libslic3r/BoundingBox.hpp" #include "libslic3r/Polygon.hpp" -#include "libslic3r/BoundingBox.hpp" %} %name{Slic3r::Polygon} class Polygon { @@ -35,7 +33,6 @@ %code{% RETVAL = THIS->contains(*point); %}; Polygons simplify(double tolerance); Clone centroid(); - Clone bounding_box(); Clone first_intersection(Line* line) %code{% Point p; diff --git a/xs/xsp/Polyline.xsp b/xs/xsp/Polyline.xsp index 3534e329e..595d54ec3 100644 --- a/xs/xsp/Polyline.xsp +++ b/xs/xsp/Polyline.xsp @@ -2,7 +2,6 @@ %{ #include -#include "libslic3r/BoundingBox.hpp" #include "libslic3r/Polyline.hpp" %} @@ -31,7 +30,6 @@ void simplify(double tolerance); void split_at(Point* point, Polyline* p1, Polyline* p2) %code{% THIS->split_at(*point, p1, p2); %}; - Clone bounding_box(); %{ Polyline* diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 9379aea20..584a2c100 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -5,25 +5,6 @@ #include "libslic3r/Print.hpp" %} -%name{Slic3r::Print::Region} class PrintRegion { - // owned by Print, no constructor/destructor - - Ref config() - %code%{ RETVAL = &THIS->config(); %}; -}; - -%name{Slic3r::Print::Object} class PrintObject { - // owned by Print, no constructor/destructor - - Ref print(); - Ref model_object(); - Ref config() - %code%{ RETVAL = &THIS->config(); %}; - Clone bounding_box(); - - void slice(); -}; - %name{Slic3r::Print} class Print { Print(); ~Print(); @@ -34,25 +15,6 @@ %code%{ RETVAL = const_cast(static_cast(&THIS->config())); %}; double total_used_filament() %code%{ RETVAL = THIS->print_statistics().total_used_filament; %}; - double total_extruded_volume() - %code%{ RETVAL = THIS->print_statistics().total_extruded_volume; %}; - double total_weight() - %code%{ RETVAL = THIS->print_statistics().total_weight; %}; - double total_cost() - %code%{ RETVAL = THIS->print_statistics().total_cost; %}; - double total_wipe_tower_cost() - %code%{ RETVAL = THIS->print_statistics().total_wipe_tower_cost; %}; - double total_wipe_tower_filament() - %code%{ RETVAL = THIS->print_statistics().total_wipe_tower_filament; %}; - int wipe_tower_number_of_toolchanges() - %code%{ RETVAL = THIS->wipe_tower_data().number_of_toolchanges; %}; - PrintObjectPtrs* objects() - %code%{ RETVAL = const_cast(&THIS->objects_mutable()); %}; - Ref get_object(int idx) - %code%{ RETVAL = THIS->objects_mutable()[idx]; %}; - size_t object_count() - %code%{ RETVAL = THIS->objects().size(); %}; - void auto_assign_extruders(ModelObject* model_object); std::string output_filepath(std::string path = "") diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 5c5d10781..ba5ed6e04 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -14,10 +14,6 @@ std::vector T_STD_VECTOR_UINT std::vector T_STD_VECTOR_DOUBLE -BoundingBox* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - DynamicPrintConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T @@ -90,12 +86,6 @@ ModelInstance* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -PrintRegion* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - -PrintObject* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T - Print* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T @@ -120,8 +110,6 @@ Polygons* T_ARRAYREF_PTR ModelObjectPtrs* T_PTR_ARRAYREF_PTR ModelVolumePtrs* T_PTR_ARRAYREF_PTR ModelInstancePtrs* T_PTR_ARRAYREF_PTR -PrintRegionPtrs* T_PTR_ARRAYREF_PTR -PrintObjectPtrs* T_PTR_ARRAYREF_PTR # we return these types whenever we want the items to be returned # by reference and not marked ::Ref because they're newly allocated diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 7fc61fe3f..7b9c02319 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -27,9 +27,6 @@ %typemap{Vec3d*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{BoundingBox*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; %typemap{DynamicPrintConfig*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; @@ -57,15 +54,6 @@ %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{PrintState*}; -%typemap{Ref}{simple}; - -%typemap{PrintRegion*}; -%typemap{Ref}{simple}; - -%typemap{PrintObject*}; -%typemap{Ref}{simple}; - %typemap{Print*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; @@ -103,31 +91,9 @@ %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{PrintRegionPtrs*}; -%typemap{PrintObjectPtrs*}; -%typemap{LayerPtrs*}; - %typemap{Axis}{parsed}{ %cpp_type{Axis}; %precall_code{% $CVar = (Axis)SvUV($PerlVar); %}; }; -%typemap{SurfaceType}{parsed}{ - %cpp_type{SurfaceType}; - %precall_code{% - $CVar = (SurfaceType)SvUV($PerlVar); - %}; -}; -%typemap{ExtrusionLoopRole}{parsed}{ - %cpp_type{ExtrusionLoopRole}; - %precall_code{% - $CVar = (ExtrusionLoopRole)SvUV($PerlVar); - %}; -}; -%typemap{ExtrusionRole}{parsed}{ - %cpp_type{ExtrusionRole}; - %precall_code{% - $CVar = (ExtrusionRole)SvUV($PerlVar); - %}; -}; From 409fae6183f8fcd84ec60857bae5c8880103520f Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 2 Nov 2022 12:59:31 +0100 Subject: [PATCH 07/33] WIP Refactoring of Layers: LayerIslands filled in with perimeter extrusions, gap fill extrusions and fill regions. --- src/libslic3r/BoundingBox.hpp | 2 + src/libslic3r/ClipperUtils.cpp | 2 + src/libslic3r/ClipperUtils.hpp | 1 + src/libslic3r/ExPolygon.cpp | 12 + src/libslic3r/ExPolygon.hpp | 4 + src/libslic3r/Layer.cpp | 351 +++++++++--- src/libslic3r/Layer.hpp | 184 +++++-- src/libslic3r/LayerRegion.cpp | 70 ++- src/libslic3r/PerimeterGenerator.cpp | 794 +++++++++++++-------------- src/libslic3r/PerimeterGenerator.hpp | 4 +- src/libslic3r/Polygon.cpp | 20 + src/libslic3r/Polygon.hpp | 4 + tests/fff_print/test_perimeters.cpp | 3 +- 13 files changed, 891 insertions(+), 560 deletions(-) diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 1d6cd4ef1..5e3d3e323 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -187,6 +187,8 @@ public: friend BoundingBox get_extents_rotated(const Points &points, double angle); }; +using BoundingBoxes = std::vector; + class BoundingBox3 : public BoundingBox3Base { public: diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 650cfca15..1f83309f4 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -432,6 +432,8 @@ Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float d { return PolyTreeToExPolygons(expolygons_offset_pt(expolygons, delta, joinType, miterLimit)); } Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) { return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); } +Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit) + { return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); } Polygons offset2(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit) { diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 50d96142d..89d05260c 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -326,6 +326,7 @@ Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); } inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); } diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 7e22127cd..2224f991e 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -306,6 +306,18 @@ Lines ExPolygon::lines() const return lines; } +// Do expolygons match? If they match, they must have the same topology, +// however their contours may be rotated. +bool expolygons_match(const ExPolygon &l, const ExPolygon &r) +{ + if (l.holes.size() != r.holes.size() || ! polygons_match(l.contour, r.contour)) + return false; + for (size_t hole_idx = 0; hole_idx < l.holes.size(); ++ hole_idx) + if (! polygons_match(l.holes[hole_idx], r.holes[hole_idx])) + return false; + return true; +} + BoundingBox get_extents(const ExPolygon &expolygon) { return get_extents(expolygon.contour); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index def2be85a..a0e4f272f 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -391,6 +391,10 @@ inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double toleranc return out; } +// Do expolygons match? If they match, they must have the same topology, +// however their contours may be rotated. +bool expolygons_match(const ExPolygon &l, const ExPolygon &r); + BoundingBox get_extents(const ExPolygon &expolygon); BoundingBox get_extents(const ExPolygons &expolygons); BoundingBox get_extents_rotated(const ExPolygon &poly, double angle); diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 82c82372b..1561a2869 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -380,90 +380,279 @@ void Layer::make_perimeters() BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id(); // keep track of regions whose perimeters we have already generated - std::vector done(m_regions.size(), false); - - LayerRegionPtrs layerms; - for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) - if ((*layerm)->slices().empty()) { - (*layerm)->m_perimeters.clear(); - (*layerm)->m_fills.clear(); - (*layerm)->m_thin_fills.clear(); - } else { - size_t region_id = layerm - m_regions.begin(); - if (done[region_id]) - continue; - BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id; - done[region_id] = true; - const PrintRegionConfig &config = (*layerm)->region().config(); - - // find compatible regions - layerms.clear(); - layerms.push_back(*layerm); - for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it) - if (! (*it)->slices().empty()) { - LayerRegion* other_layerm = *it; - const PrintRegionConfig &other_config = other_layerm->region().config(); - if (config.perimeter_extruder == other_config.perimeter_extruder - && config.perimeters == other_config.perimeters - && config.perimeter_speed == other_config.perimeter_speed - && config.external_perimeter_speed == other_config.external_perimeter_speed - && (config.gap_fill_enabled ? config.gap_fill_speed.value : 0.) == - (other_config.gap_fill_enabled ? other_config.gap_fill_speed.value : 0.) - && config.overhangs == other_config.overhangs - && config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width") - && config.thin_walls == other_config.thin_walls - && config.external_perimeters_first == other_config.external_perimeters_first - && config.infill_overlap == other_config.infill_overlap - && config.fuzzy_skin == other_config.fuzzy_skin - && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness - && config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist) - { - other_layerm->m_perimeters.clear(); - other_layerm->m_fills.clear(); - other_layerm->m_thin_fills.clear(); - layerms.push_back(other_layerm); - done[it - m_regions.begin()] = true; - } - } - - ExPolygons fill_expolygons; - if (layerms.size() == 1) { // optimization - (*layerm)->m_fill_expolygons.clear(); - (*layerm)->m_fill_surfaces.clear(); - (*layerm)->make_perimeters((*layerm)->slices(), fill_expolygons); - (*layerm)->m_fill_expolygons = std::move(fill_expolygons); - } else { - SurfaceCollection new_slices; - // Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence. - LayerRegion *layerm_config = layerms.front(); - { - // group slices (surfaces) according to number of extra perimeters - std::map slices; // extra_perimeters => [ surface, surface... ] - for (LayerRegion *layerm : layerms) { - for (const Surface &surface : layerm->slices()) - slices[surface.extra_perimeters].emplace_back(surface); - if (layerm->region().config().fill_density > layerm_config->region().config().fill_density) - layerm_config = layerm; - layerm->m_fill_surfaces.clear(); - layerm->m_fill_expolygons.clear(); - } - // merge the surfaces assigned to each group - for (std::pair &surfaces_with_extra_perimeters : slices) - new_slices.append(offset_ex(surfaces_with_extra_perimeters.second, ClipperSafetyOffset), surfaces_with_extra_perimeters.second.front()); - } - // make perimeters - layerm_config->make_perimeters(new_slices, fill_expolygons); - // assign fill_surfaces to each layer - if (! fill_expolygons.empty()) { - // Separate the fill surfaces. - for (LayerRegion *l : layerms) - l->m_fill_expolygons = intersection_ex(l->slices().surfaces, fill_expolygons); - } - } - } + std::vector done(m_regions.size(), false); + std::vector layer_region_ids; + std::vector> perimeter_and_gapfill_ranges; + ExPolygons fill_expolygons; + std::vector fill_expolygons_ranges; + SurfacesPtr surfaces_to_merge; + SurfacesPtr surfaces_to_merge_temp; + + auto layer_region_reset_perimeters = [](LayerRegion &layerm) { + layerm.m_perimeters.clear(); + layerm.m_fills.clear(); + layerm.m_thin_fills.clear(); + layerm.m_fill_expolygons.clear(); + layerm.m_fill_expolygons_bboxes.clear(); + layerm.m_fill_expolygons_composite.clear(); + layerm.m_fill_expolygons_composite_bboxes.clear(); + }; + + for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) + if (size_t region_id = layerm - m_regions.begin(); ! done[region_id]) { + layer_region_reset_perimeters(**layerm); + if (! (*layerm)->slices().empty()) { + BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id; + done[region_id] = true; + const PrintRegionConfig &config = (*layerm)->region().config(); + + perimeter_and_gapfill_ranges.clear(); + fill_expolygons.clear(); + fill_expolygons_ranges.clear(); + surfaces_to_merge.clear(); + + // find compatible regions + layer_region_ids.clear(); + layer_region_ids.push_back(region_id); + for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it) + if (! (*it)->slices().empty()) { + LayerRegion* other_layerm = *it; + const PrintRegionConfig &other_config = other_layerm->region().config(); + if (config.perimeter_extruder == other_config.perimeter_extruder + && config.perimeters == other_config.perimeters + && config.perimeter_speed == other_config.perimeter_speed + && config.external_perimeter_speed == other_config.external_perimeter_speed + && (config.gap_fill_enabled ? config.gap_fill_speed.value : 0.) == + (other_config.gap_fill_enabled ? other_config.gap_fill_speed.value : 0.) + && config.overhangs == other_config.overhangs + && config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width") + && config.thin_walls == other_config.thin_walls + && config.external_perimeters_first == other_config.external_perimeters_first + && config.infill_overlap == other_config.infill_overlap + && config.fuzzy_skin == other_config.fuzzy_skin + && config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness + && config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist) + { + layer_region_reset_perimeters(*other_layerm); + layer_region_ids.push_back(it - m_regions.begin()); + done[it - m_regions.begin()] = true; + } + } + + if (layer_region_ids.size() == 1) { // optimization + (*layerm)->make_perimeters((*layerm)->slices(), perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges); + this->sort_perimeters_into_islands((*layerm)->slices(), region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); + } else { + SurfaceCollection new_slices; + // Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence. + LayerRegion *layerm_config = m_regions[layer_region_ids.front()]; + { + // Merge slices (surfaces) according to number of extra perimeters. + for (uint32_t region_id : layer_region_ids) { + LayerRegion &layerm = *m_regions[region_id]; + for (const Surface &surface : layerm.slices()) + surfaces_to_merge.emplace_back(&surface); + if (layerm.region().config().fill_density > layerm_config->region().config().fill_density) + layerm_config = &layerm; + } + std::sort(surfaces_to_merge.begin(), surfaces_to_merge.end(), [](const Surface *l, const Surface *r){ return l->extra_perimeters < r->extra_perimeters; }); + for (size_t i = 0; i < surfaces_to_merge.size();) { + size_t j = i; + const Surface &first = *surfaces_to_merge[i]; + size_t extra_perimeters = first.extra_perimeters; + for (; j < surfaces_to_merge.size() && surfaces_to_merge[j]->extra_perimeters == extra_perimeters; ++ j) ; + if (i + 1 == j) + // Nothing to merge, just copy. + new_slices.surfaces.emplace_back(*surfaces_to_merge[i]); + else { + surfaces_to_merge_temp.assign(surfaces_to_merge.begin() + i, surfaces_to_merge.begin() + j); + new_slices.append(offset_ex(surfaces_to_merge_temp, ClipperSafetyOffset), first); + } + i = j; + } + } + // make perimeters + layerm_config->make_perimeters(new_slices, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges); + this->sort_perimeters_into_islands(new_slices, region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); + } + } + } BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done"; } +void Layer::sort_perimeters_into_islands( + // Slices for which perimeters and fill_expolygons were just created. + // The slices may have been created by merging multiple source slices with the same perimeter parameters. + const SurfaceCollection &slices, + // Region where the perimeters, gap fills and fill expolygons are stored. + const uint32_t region_id, + // Perimeters and gap fills produced by the perimeter generator for the slices, + // sorted by the source slices. + const std::vector> &perimeter_and_gapfill_ranges, + // Fill expolygons produced for all source slices above. + ExPolygons &&fill_expolygons, + // Fill expolygon ranges sorted by the source slices. + const std::vector &fill_expolygons_ranges, + // If the current layer consists of multiple regions, then the fill_expolygons above are split by the source LayerRegion surfaces. + const std::vector &layer_region_ids) +{ + for (LayerSlice &lslice : this->lslices_ex) + lslice.islands.clear(); + + LayerRegion &this_layer_region = *m_regions[region_id]; + + // Bounding boxes of fill_expolygons. + BoundingBoxes fill_expolygons_bboxes; + fill_expolygons_bboxes.reserve(fill_expolygons.size()); + for (const ExPolygon &expolygon : fill_expolygons) + fill_expolygons_bboxes.emplace_back(get_extents(expolygon)); + + // Map of source fill_expolygon into region and fill_expolygon of that region. + // -1: not set + std::vector> map_expolygon_to_region_and_fill; + + // assign fill_surfaces to each layer + if (! fill_expolygons.empty()) { + if (layer_region_ids.size() == 1) { + this_layer_region.m_fill_expolygons = std::move(fill_expolygons); + this_layer_region.m_fill_expolygons_bboxes = std::move(fill_expolygons_bboxes); + } else { + // Sort the bounding boxes lexicographically. + std::vector fill_expolygons_bboxes_sorted(fill_expolygons_bboxes.size()); + std::iota(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), 0); + std::sort(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), [&fill_expolygons_bboxes](uint32_t lhs, uint32_t rhs){ + const BoundingBox &bbl = fill_expolygons_bboxes[lhs]; + const BoundingBox &bbr = fill_expolygons_bboxes[rhs]; + return bbl.min < bbr.min || (bbl.min == bbr.min && bbl.max < bbr.max); + }); + map_expolygon_to_region_and_fill.assign(fill_expolygons.size(), std::make_pair(-1, -1)); + for (uint32_t region_idx : layer_region_ids) { + LayerRegion &l = *m_regions[region_idx]; + l.m_fill_expolygons = intersection_ex(l.slices().surfaces, fill_expolygons); + l.m_fill_expolygons_bboxes.reserve(l.fill_expolygons().size()); + for (const ExPolygon &expolygon : l.fill_expolygons()) { + BoundingBox bbox = get_extents(expolygon); + l.m_fill_expolygons_bboxes.emplace_back(bbox); + auto it_bbox = std::lower_bound(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), bbox, [&fill_expolygons_bboxes](uint32_t lhs, const BoundingBox &bbr){ + const BoundingBox &bbl = fill_expolygons_bboxes[lhs]; + return bbl.min < bbr.min || (bbl.min == bbr.min && bbl.max < bbr.max); + }); + if (it_bbox != fill_expolygons_bboxes_sorted.end()) + if (uint32_t fill_id = *it_bbox; fill_expolygons_bboxes[fill_id] == bbox) { + // With a very high probability the two expolygons match exactly. Confirm that. + if (expolygons_match(expolygon, fill_expolygons[fill_id])) { + std::pair &ref = map_expolygon_to_region_and_fill[fill_id]; + // Only one expolygon produced by intersection with LayerRegion surface may match an expolygon of fill_expolygons. + assert(ref.first == -1); + ref.first = region_idx; + ref.second = int(&expolygon - l.fill_expolygons().data()); + } + } + } + } + } + } + + // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, + // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. + auto point_inside_surface = [this](const size_t lslice_idx, const Point &point) { + const BoundingBox &bbox = this->lslices_ex[lslice_idx].bbox; + return point.x() >= bbox.min.x() && point.x() < bbox.max.x() && + point.y() >= bbox.min.y() && point.y() < bbox.max.y() && + this->lslices[lslice_idx].contour.contains(point); + }; + + // Take one sample point for each source slice, to be used to sort source slices into layer slices. + // source slice index + its sample. + std::vector> perimeter_slices_queue; + perimeter_slices_queue.reserve(slices.size()); + for (uint32_t islice = 0; islice < uint32_t(slices.size()); ++ islice) { + const std::pair &extrusions = perimeter_and_gapfill_ranges[islice]; + Point sample; + bool sample_set = false; + if (! extrusions.first.empty()) { + sample = this_layer_region.perimeters().entities[*extrusions.first.begin()]->first_point(); + sample_set = true; + } else if (! extrusions.second.empty()) { + sample = this_layer_region.thin_fills().entities[*extrusions.second.begin()]->first_point(); + sample_set = true; + } else if (const ExPolygonRange &fill_expolygon_range = fill_expolygons_ranges[islice]; ! fill_expolygons.empty()) { + for (uint32_t iexpoly : fill_expolygon_range) + if (const ExPolygon &expoly = fill_expolygons[iexpoly]; ! expoly.empty()) { + sample = expoly.contour.points.front(); + sample_set = true; + break; + } + } + if (sample_set) + perimeter_slices_queue.emplace_back(islice, sample); + } + + // Sort perimeter extrusions, thin fill extrusions and fill expolygons into islands. + std::vector region_fill_sorted_last; + for (int lslice_idx = int(this->lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx) { + for (auto it_source_slice = perimeter_slices_queue.begin(); it_source_slice != perimeter_slices_queue.end(); ++ it_source_slice) + if (point_inside_surface(lslice_idx, it_source_slice->second)) { + this->lslices_ex[lslice_idx].islands.push_back({}); + LayerIsland &island = this->lslices_ex[lslice_idx].islands.back(); + const uint32_t source_slice_idx = it_source_slice->first; + island.perimeters = LayerExtrusionRange(region_id, perimeter_and_gapfill_ranges[source_slice_idx].first); + island.thin_fills = perimeter_and_gapfill_ranges[source_slice_idx].second; + if (ExPolygonRange fill_range = fill_expolygons_ranges[source_slice_idx]; ! fill_range.empty()) { + if (layer_region_ids.size() == 1) { + // Layer island is made of one fill region only. + island.fill_expolygons = fill_range; + island.fill_region_id = region_id; + } else { + // Check whether the fill expolygons of this island were split into multiple regions. + island.fill_region_id = LayerIsland::fill_region_composite_id; + for (uint32_t fill_idx : fill_range) { + const std::pair &kvp = map_expolygon_to_region_and_fill[fill_idx]; + if (kvp.first == -1 || (island.fill_region_id != -1 && island.fill_region_id != kvp.second)) { + island.fill_region_id = LayerIsland::fill_region_composite_id; + break; + } else + island.fill_region_id = kvp.second; + } + if (island.fill_expolygons_composite()) { + // They were split, thus store the unsplit "composite" expolygons into the region of perimeters. + auto begin = uint32_t(this_layer_region.fill_expolygons_composite().size()); + this_layer_region.m_fill_expolygons_composite.reserve(this_layer_region.fill_expolygons_composite().size() + fill_range.size()); + std::move(fill_expolygons.begin() + *fill_range.begin(), fill_expolygons.begin() + *fill_range.end(), std::back_inserter(this_layer_region.m_fill_expolygons_composite)); + this_layer_region.m_fill_expolygons_composite_bboxes.insert(this_layer_region.m_fill_expolygons_composite_bboxes.end(), + fill_expolygons_bboxes.begin() + *fill_range.begin(), fill_expolygons_bboxes.begin() + *fill_range.end()); + island.fill_expolygons = ExPolygonRange(begin, uint32_t(this_layer_region.fill_expolygons_composite().size())); + } else { + if (region_fill_sorted_last.empty()) + region_fill_sorted_last.assign(m_regions.size(), 0); + uint32_t &last = region_fill_sorted_last[island.fill_region_id]; + // They were not split and they belong to the same region. + // Sort the region m_fill_expolygons to a continuous span. + uint32_t begin = last; + LayerRegion &layerm = *m_regions[island.fill_region_id]; + for (uint32_t fill_id : fill_range) { + uint32_t region_fill_id = map_expolygon_to_region_and_fill[fill_id].second; + assert(region_fill_id >= last); + if (region_fill_id > last) { + std::swap(layerm.m_fill_expolygons[region_fill_id], layerm.m_fill_expolygons[last]); + std::swap(layerm.m_fill_expolygons_bboxes[region_fill_id], layerm.m_fill_expolygons_bboxes[last]); + } + ++ last; + } + island.fill_expolygons = ExPolygonRange(begin, last); + } + } + } + if (std::next(it_source_slice) != perimeter_slices_queue.end()) { + // Remove the current slice & point pair from the queue. + *it_source_slice = perimeter_slices_queue.back(); + perimeter_slices_queue.pop_back(); + } + break; + } + } +} + void Layer::export_region_slices_to_svg(const char *path) const { BoundingBox bbox; diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 0fda3a6b2..137f35e5c 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -28,6 +28,71 @@ namespace FillLightning { class Generator; }; +// Range of indices, providing support for range based loops. +template +class IndexRange +{ +private: + // Just a bare minimum functionality iterator required by range-for loop. + template + class IteratorType { + public: + T operator*() const { return m_idx; } + bool operator!=(const IteratorType &rhs) const { return m_idx != rhs.m_idx; } + void operator++() { ++ m_idx; } + private: + friend class IndexRange; + IteratorType(T idx) : m_idx(idx) {} + T m_idx; + }; + // Index of the first extrusion in LayerRegion. + T m_begin { 0 }; + // Index of the last extrusion in LayerRegion. + T m_end { 0 }; + +public: + IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {} + IndexRange() = default; + + using Iterator = IteratorType; + + Iterator begin() const { assert(m_begin <= m_end); return Iterator(m_begin); }; + Iterator end() const { assert(m_begin <= m_end); return Iterator(m_end); }; + + bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; } + T size() const { assert(m_begin <= m_end); return m_end - m_begin; } +}; + +using ExtrusionRange = IndexRange; +using ExPolygonRange = IndexRange; + +// Range of extrusions, referencing the source region by an index. +class LayerExtrusionRange : public ExtrusionRange +{ +public: + LayerExtrusionRange(uint32_t iregion, uint32_t ibegin, uint32_t iend) : m_region(iregion), ExtrusionRange(ibegin, iend) {} + LayerExtrusionRange(uint32_t iregion, ExtrusionRange extrusion_range) : m_region(iregion), ExtrusionRange(extrusion_range) {} + LayerExtrusionRange() = default; + + // Index of LayerRegion in Layer. + uint32_t region() const { return m_region; }; + +private: + // Index of LayerRegion in Layer. + uint32_t m_region { 0 }; +}; + +// Most likely one LayerIsland will be filled with maximum one fill type. +static constexpr const size_t LayerExtrusionRangesStaticSize = 1; +using LayerExtrusionRanges = +#ifdef NDEBUG + // To reduce memory allocation in release mode. + boost::container::small_vector; +#else // NDEBUG + // To ease debugging. + std::vector; +#endif // NDEBUG + class LayerRegion { public: @@ -39,7 +104,16 @@ public: // divided by type top/bottom/internal [[nodiscard]] const SurfaceCollection& slices() const { return m_slices; } + // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") + // and for re-starting of infills. [[nodiscard]] const ExPolygons& fill_expolygons() const { return m_fill_expolygons; } + // and their bounding boxes + [[nodiscard]] const BoundingBoxes& fill_expolygons_bboxes() const { return m_fill_expolygons_bboxes; } + // Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands. + // Not used for a plain single material print with no infill modifiers. + [[nodiscard]] const ExPolygons& fill_expolygons_composite() const { return m_fill_expolygons_composite; } + // and their bounding boxes + [[nodiscard]] const BoundingBoxes& fill_expolygons_composite_bboxes() const { return m_fill_expolygons_composite_bboxes; } // collection of surfaces generated by slicing the original geometry // divided by type top/bottom/internal @@ -67,7 +141,17 @@ public: void slices_to_fill_surfaces_clipped(); void prepare_fill_surfaces(); - void make_perimeters(const SurfaceCollection &slices, ExPolygons &fill_expolygons); + // Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices. + void make_perimeters( + // Input slices for which the perimeters, gap fills and fill expolygons are to be generated. + const SurfaceCollection &slices, + // Ranges of perimeter extrusions and gap fill extrusions per suface, referencing + // newly created extrusions stored at this LayerRegion. + std::vector> &perimeter_and_gapfill_ranges, + // All fill areas produced for all input slices above. + ExPolygons &fill_expolygons, + // Ranges of fill areas above per input slice. + std::vector &fill_expolygons_ranges); void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered); double infill_area_threshold() const; // Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer. @@ -116,11 +200,18 @@ private: // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") // and for re-starting of infills. ExPolygons m_fill_expolygons; + // and their bounding boxes + BoundingBoxes m_fill_expolygons_bboxes; + // Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands. + // Not used for a plain single material print with no infill modifiers. + ExPolygons m_fill_expolygons_composite; + // and their bounding boxes + BoundingBoxes m_fill_expolygons_composite_bboxes; - // collection of surfaces for infill generation + // Collection of surfaces for infill generation, created by splitting m_slices by m_fill_expolygons. SurfaceCollection m_fill_surfaces; - // collection of extrusion paths/loops filling gaps + // Collection of extrusion paths/loops filling gaps // These fills are generated by the perimeter generator. // They are not printed on their own, but they are copied to this->fills during infill generation. ExtrusionEntityCollection m_thin_fills; @@ -141,66 +232,31 @@ private: // Polygons bridged; }; -// Range of two indices, providing support for range based loops. -template -class IndexRange -{ -public: - IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {} - IndexRange() = default; - - T begin() const { assert(m_begin <= m_end); return m_begin; }; - T end() const { assert(m_begin <= m_end); return m_end; }; - - bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; } - T size() const { assert(m_begin <= m_end); return m_end - m_begin; } - -private: - // Index of the first extrusion in LayerRegion. - T m_begin { 0 }; - // Index of the last extrusion in LayerRegion. - T m_end { 0 }; -}; - -class LayerExtrusionRange : public IndexRange -{ -public: - LayerExtrusionRange(uint32_t iregion, uint32_t ibegin, uint32_t iend) : m_region(iregion), IndexRange(ibegin, iend) {} - LayerExtrusionRange() = default; - - // Index of LayerRegion in Layer. - uint32_t region() const { return m_region; }; - -private: - // Index of LayerRegion in Layer. - uint32_t m_region { 0 }; -}; - -static constexpr const size_t LayerExtrusionRangesStaticSize = 1; -using LayerExtrusionRanges = -#ifdef NDEBUG - // To reduce memory allocation in release mode. - boost::container::small_vector; -#else // NDEBUG - // To ease debugging. - std::vector; -#endif // NDEBUG - -using ExPolygonRange = IndexRange(); - // LayerSlice contains one or more LayerIsland objects, // each LayerIsland containing a set of perimeter extrusions extruded with one particular PrintRegionConfig parameters // and one or multiple struct LayerIsland { +private: + friend class Layer; + static constexpr const uint32_t fill_region_composite_id = std::numeric_limits::max(); + +public: // Perimeter extrusions in LayerRegion belonging to this island. LayerExtrusionRange perimeters; + // Thin fills of the same region as perimeters. Generated by classic perimeter generator, while Arachne puts them into perimeters. + ExtrusionRange thin_fills; // Infill + gapfill extrusions in LayerRegion belonging to this island. LayerExtrusionRanges fills; - // Region that is to be filled with the fills above. + // Region that is to be filled with the fills above (thin fills, regular fills). + // Pointing to either LayerRegion::fill_expolygons() or LayerRegion::fill_expolygons_composite() + // based on this->fill_expolygons_composite() flag. ExPolygonRange fill_expolygons; + // Index of LayerRegion with LayerRegion::fill_expolygons() if not fill_expolygons_composite(). + uint32_t fill_region_id; + bool fill_expolygons_composite() const { return this->fill_region_id == fill_region_composite_id; } // Centroid of this island used for path planning. - Point centroid; +// Point centroid; bool has_extrusions() const { return ! this->perimeters.empty() || ! this->fills.empty(); } }; @@ -215,7 +271,7 @@ using LayerIslands = std::vector; #endif // NDEBUG -// +// One connected island of a layer. LayerSlice may consist of one or more LayerIslands. struct LayerSlice { struct Link { @@ -235,6 +291,10 @@ struct LayerSlice BoundingBox bbox; Links overlaps_above; Links overlaps_below; + // One island for each region or region set that generates its own perimeters. + // For multi-material prints or prints with regions of different perimeter parameters, + // a LayerSlice may be split into multiple LayerIslands. + // For most prints there will be just one island. LayerIslands islands; bool has_extrusions() const { for (const LayerIsland &island : islands) if (island.has_extrusions()) return true; return false; } @@ -324,6 +384,22 @@ protected: virtual ~Layer(); private: + void sort_perimeters_into_islands( + // Slices for which perimeters and fill_expolygons were just created. + // The slices may have been created by merging multiple source slices with the same perimeter parameters. + const SurfaceCollection &slices, + // Region where the perimeters, gap fills and fill expolygons are stored. + const uint32_t region_id, + // Perimeters and gap fills produced by the perimeter generator for the slices, + // sorted by the source slices. + const std::vector> &perimeter_and_gapfill_ranges, + // Fill expolygons produced for all source slices above. + ExPolygons &&fill_expolygons, + // Fill expolygon ranges sorted by the source slices. + const std::vector &fill_expolygons_ranges, + // If the current layer consists of multiple regions, then the fill_expolygons above are split by the source LayerRegion surfaces. + const std::vector &layer_region_ids); + // Sequential index of layer, 0-based, offsetted by number of raft layers. size_t m_id; PrintObject *m_object; @@ -337,7 +413,7 @@ public: // Used to suppress retraction if moving for a support extrusion over these support_islands. ExPolygons support_islands; // Slightly inflated bounding boxes of the above, for faster intersection query. - std::vector support_islands_bboxes; + BoundingBoxes support_islands_bboxes; // Extrusion paths for the support base and for the support interface and contacts. ExtrusionEntityCollection support_fills; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 4b346647b..0c50fce27 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -59,11 +59,26 @@ void LayerRegion::slices_to_fill_surfaces_clipped() } } -void LayerRegion::make_perimeters(const SurfaceCollection &slices, ExPolygons &fill_expolygons) +// Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices. +void LayerRegion::make_perimeters( + // Input slices for which the perimeters, gap fills and fill expolygons are to be generated. + const SurfaceCollection &slices, + // Ranges of perimeter extrusions and gap fill extrusions per suface, referencing + // newly created extrusions stored at this LayerRegion. + std::vector> &perimeter_and_gapfill_ranges, + // All fill areas produced for all input slices above. + ExPolygons &fill_expolygons, + // Ranges of fill areas above per input slice. + std::vector &fill_expolygons_ranges) { m_perimeters.clear(); m_thin_fills.clear(); + perimeter_and_gapfill_ranges.reserve(perimeter_and_gapfill_ranges.size() + slices.size()); + // There may be more expolygons produced per slice, thus this reserve is conservative. + fill_expolygons.reserve(fill_expolygons.size() + slices.size()); + fill_expolygons_ranges.reserve(fill_expolygons_ranges.size() + slices.size()); + const PrintConfig &print_config = this->layer()->object()->print()->config(); const PrintRegionConfig ®ion_config = this->region().config(); // This needs to be in sync with PrintObject::_slice() slicing_mode_normal_below_layer! @@ -90,28 +105,37 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, ExPolygons &f // Cache for offsetted lower_slices Polygons lower_layer_polygons_cache; - if (this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase) - PerimeterGenerator::process_arachne( - // input: - params, - &slices, - lower_slices, - lower_layer_polygons_cache, - // output: - m_perimeters, - m_thin_fills, - fill_expolygons); - else - PerimeterGenerator::process_classic( - // input: - params, - &slices, - lower_slices, - lower_layer_polygons_cache, - // output: - m_perimeters, - m_thin_fills, - fill_expolygons); + for (const Surface &surface : slices) { + auto perimeters_begin = uint32_t(m_perimeters.size()); + auto gap_fills_begin = uint32_t(m_thin_fills.size()); + auto fill_expolygons_begin = uint32_t(fill_expolygons.size()); + if (this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase) + PerimeterGenerator::process_arachne( + // input: + params, + surface, + lower_slices, + lower_layer_polygons_cache, + // output: + m_perimeters, + m_thin_fills, + fill_expolygons); + else + PerimeterGenerator::process_classic( + // input: + params, + surface, + lower_slices, + lower_layer_polygons_cache, + // output: + m_perimeters, + m_thin_fills, + fill_expolygons); + perimeter_and_gapfill_ranges.emplace_back( + ExtrusionRange{ perimeters_begin, uint32_t(m_perimeters.size()) }, + ExtrusionRange{ gap_fills_begin, uint32_t(m_thin_fills.size()) }); + fill_expolygons_ranges.emplace_back(ExtrusionRange{ fill_expolygons_begin, uint32_t(fill_expolygons.size()) }); + } } //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 27bc0e9d7..92759238e 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -601,7 +601,7 @@ static void export_perimeters_to_svg(const std::string &path, const Polygons &co void PerimeterGenerator::process_arachne( // Inputs: const Parameters ¶ms, - const SurfaceCollection *slices, + const Surface &surface, const ExPolygons *lower_slices, // Cache: Polygons &lower_slices_polygons_cache, @@ -633,202 +633,200 @@ void PerimeterGenerator::process_arachne( // we need to process each island separately because we might have different // extra perimeters for each one - for (const Surface &surface : slices->surfaces) { - // detect how many perimeters must be generated for this island - 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); + // detect how many perimeters must be generated for this island + 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, params.layer_height, params.object_config, params.print_config); - std::vector perimeters = wallToolPaths.getToolPaths(); - loop_number = int(perimeters.size()) - 1; + 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; #ifdef ARACHNE_DEBUG - { - static int iRun = 0; - export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour())); - } + { + static int iRun = 0; + export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour())); + } #endif - // All closed ExtrusionLine should have the same the first and the last point. - // But in rare cases, Arachne produce ExtrusionLine marked as closed but without - // equal the first and the last point. - assert([&perimeters = std::as_const(perimeters)]() -> bool { - for (const Arachne::VariableWidthLines &perimeter : perimeters) - for (const Arachne::ExtrusionLine &el : perimeter) - if (el.is_closed && el.junctions.front().p != el.junctions.back().p) - return false; - return true; - }()); + // All closed ExtrusionLine should have the same the first and the last point. + // But in rare cases, Arachne produce ExtrusionLine marked as closed but without + // equal the first and the last point. + assert([&perimeters = std::as_const(perimeters)]() -> bool { + for (const Arachne::VariableWidthLines &perimeter : perimeters) + for (const Arachne::ExtrusionLine &el : perimeter) + if (el.is_closed && el.junctions.front().p != el.junctions.back().p) + return false; + return true; + }()); - int start_perimeter = int(perimeters.size()) - 1; - int end_perimeter = -1; - int direction = -1; + int start_perimeter = int(perimeters.size()) - 1; + int end_perimeter = -1; + int direction = -1; - if (params.config.external_perimeters_first) { - start_perimeter = 0; - end_perimeter = int(perimeters.size()); - direction = 1; - } - - std::vector all_extrusions; - for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) { - if (perimeters[perimeter_idx].empty()) - continue; - for (Arachne::ExtrusionLine &wall : perimeters[perimeter_idx]) - all_extrusions.emplace_back(&wall); - } - - // Find topological order with constraints from extrusions_constrains. - std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion. - std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion. - std::unordered_map map_extrusion_to_idx; - 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, params.config.external_perimeters_first); - for (auto [before, after] : extrusions_constrains) { - auto after_it = map_extrusion_to_idx.find(after); - ++blocked[after_it->second]; - blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second); - } - - std::vector processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed. - Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position. - std::vector ordered_extrusions; // To store our result in. At the end we'll std::swap. - ordered_extrusions.reserve(all_extrusions.size()); - - while (ordered_extrusions.size() < all_extrusions.size()) { - size_t best_candidate = 0; - double best_distance_sqr = std::numeric_limits::max(); - bool is_best_closed = false; - - std::vector available_candidates; - for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) { - if (processed[candidate] || blocked[candidate]) - continue; // Not a valid candidate. - available_candidates.push_back(candidate); - } - - std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool { - return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed; - }); - - for (const size_t candidate_path_idx : available_candidates) { - auto& path = all_extrusions[candidate_path_idx]; - - if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. - if (best_distance_sqr == std::numeric_limits::max()) { - best_candidate = candidate_path_idx; - is_best_closed = path->is_closed; - } - continue; - } - - const Point candidate_position = path->junctions.front().p; - double distance_sqr = (current_position - candidate_position).cast().norm(); - if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far. - if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits::max()) || (!path->is_closed && !is_best_closed)) { - best_candidate = candidate_path_idx; - best_distance_sqr = distance_sqr; - is_best_closed = path->is_closed; - } - } - } - - auto &best_path = all_extrusions[best_candidate]; - ordered_extrusions.push_back({best_path, best_path->is_contour(), false}); - processed[best_candidate] = true; - for (size_t unlocked_idx : blocking[best_candidate]) - blocked[unlocked_idx]--; - - if(!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then. - if(best_path->is_closed) - current_position = best_path->junctions[0].p; //We end where we started. - else - current_position = best_path->junctions.back().p; //Pick the other end from where we started. - } - } - - 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 && params.config.fuzzy_skin == FuzzySkinType::External) { - closed_loop_extrusions.emplace_back(&extrusion); - } else { - extrusion.fuzzify = true; - } - } - - 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) { - assert(cl_extrusion->extrusion->junctions.front() == cl_extrusion->extrusion->junctions.back()); - size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front(); - ClipperLib_Z::Path loop_path; - loop_path.reserve(cl_extrusion->extrusion->junctions.size() - 1); - for (auto junction_it = cl_extrusion->extrusion->junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion->junctions.end()); ++junction_it) - loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx); - loops_paths.emplace_back(loop_path); - } - - ClipperLib_Z::Clipper clipper; - clipper.AddPaths(loops_paths, ClipperLib_Z::ptSubject, true); - ClipperLib_Z::PolyTree loops_polytree; - clipper.Execute(ClipperLib_Z::ctUnion, loops_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd); - - for (const ClipperLib_Z::PolyNode *child_node : loops_polytree.Childs) { - // The whole contour must have the same index. - coord_t polygon_idx = child_node->Contour.front().z(); - bool has_same_idx = std::all_of(child_node->Contour.begin(), child_node->Contour.end(), - [&polygon_idx](const ClipperLib_Z::IntPoint &point) -> bool { return polygon_idx == point.z(); }); - if (has_same_idx) - closed_loop_extrusions[polygon_idx]->fuzzify = true; - } - } - } - - 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; - if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) - infill_contour.clear(); // Infill region is too small, so let's filter it out. - - // create one more offset to be used as boundary for fill - // we offset by half the perimeter spacing (to get to the actual infill boundary) - // and then we offset back and forth by half the infill spacing to only consider the - // non-collapsing regions - coord_t inset = - (loop_number < 0) ? 0 : - (loop_number == 0) ? - // one loop - ext_perimeter_spacing: - // two or more loops? - perimeter_spacing; - - inset = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(inset)))); - Polygons pp; - for (ExPolygon &ex : infill_contour) - 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 - append(out_fill_expolygons, - offset2_ex( - union_ex(pp), - float(- min_perimeter_infill_spacing / 2.), - float(inset + min_perimeter_infill_spacing / 2.))); + if (params.config.external_perimeters_first) { + start_perimeter = 0; + end_perimeter = int(perimeters.size()); + direction = 1; } + + std::vector all_extrusions; + for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) { + if (perimeters[perimeter_idx].empty()) + continue; + for (Arachne::ExtrusionLine &wall : perimeters[perimeter_idx]) + all_extrusions.emplace_back(&wall); + } + + // Find topological order with constraints from extrusions_constrains. + std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion. + std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion. + std::unordered_map map_extrusion_to_idx; + 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, params.config.external_perimeters_first); + for (auto [before, after] : extrusions_constrains) { + auto after_it = map_extrusion_to_idx.find(after); + ++blocked[after_it->second]; + blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second); + } + + std::vector processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed. + Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position. + std::vector ordered_extrusions; // To store our result in. At the end we'll std::swap. + ordered_extrusions.reserve(all_extrusions.size()); + + while (ordered_extrusions.size() < all_extrusions.size()) { + size_t best_candidate = 0; + double best_distance_sqr = std::numeric_limits::max(); + bool is_best_closed = false; + + std::vector available_candidates; + for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) { + if (processed[candidate] || blocked[candidate]) + continue; // Not a valid candidate. + available_candidates.push_back(candidate); + } + + std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool { + return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed; + }); + + for (const size_t candidate_path_idx : available_candidates) { + auto& path = all_extrusions[candidate_path_idx]; + + if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. + if (best_distance_sqr == std::numeric_limits::max()) { + best_candidate = candidate_path_idx; + is_best_closed = path->is_closed; + } + continue; + } + + const Point candidate_position = path->junctions.front().p; + double distance_sqr = (current_position - candidate_position).cast().norm(); + if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far. + if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits::max()) || (!path->is_closed && !is_best_closed)) { + best_candidate = candidate_path_idx; + best_distance_sqr = distance_sqr; + is_best_closed = path->is_closed; + } + } + } + + auto &best_path = all_extrusions[best_candidate]; + ordered_extrusions.push_back({best_path, best_path->is_contour(), false}); + processed[best_candidate] = true; + for (size_t unlocked_idx : blocking[best_candidate]) + blocked[unlocked_idx]--; + + if(!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then. + if(best_path->is_closed) + current_position = best_path->junctions[0].p; //We end where we started. + else + current_position = best_path->junctions.back().p; //Pick the other end from where we started. + } + } + + 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 && params.config.fuzzy_skin == FuzzySkinType::External) { + closed_loop_extrusions.emplace_back(&extrusion); + } else { + extrusion.fuzzify = true; + } + } + + 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) { + assert(cl_extrusion->extrusion->junctions.front() == cl_extrusion->extrusion->junctions.back()); + size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front(); + ClipperLib_Z::Path loop_path; + loop_path.reserve(cl_extrusion->extrusion->junctions.size() - 1); + for (auto junction_it = cl_extrusion->extrusion->junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion->junctions.end()); ++junction_it) + loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx); + loops_paths.emplace_back(loop_path); + } + + ClipperLib_Z::Clipper clipper; + clipper.AddPaths(loops_paths, ClipperLib_Z::ptSubject, true); + ClipperLib_Z::PolyTree loops_polytree; + clipper.Execute(ClipperLib_Z::ctUnion, loops_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd); + + for (const ClipperLib_Z::PolyNode *child_node : loops_polytree.Childs) { + // The whole contour must have the same index. + coord_t polygon_idx = child_node->Contour.front().z(); + bool has_same_idx = std::all_of(child_node->Contour.begin(), child_node->Contour.end(), + [&polygon_idx](const ClipperLib_Z::IntPoint &point) -> bool { return polygon_idx == point.z(); }); + if (has_same_idx) + closed_loop_extrusions[polygon_idx]->fuzzify = true; + } + } + } + + 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; + if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) + infill_contour.clear(); // Infill region is too small, so let's filter it out. + + // create one more offset to be used as boundary for fill + // we offset by half the perimeter spacing (to get to the actual infill boundary) + // and then we offset back and forth by half the infill spacing to only consider the + // non-collapsing regions + coord_t inset = + (loop_number < 0) ? 0 : + (loop_number == 0) ? + // one loop + ext_perimeter_spacing: + // two or more loops? + perimeter_spacing; + + inset = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale(inset)))); + Polygons pp; + for (ExPolygon &ex : infill_contour) + 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 + append(out_fill_expolygons, + offset2_ex( + union_ex(pp), + float(- min_perimeter_infill_spacing / 2.), + float(inset + min_perimeter_infill_spacing / 2.))); } void PerimeterGenerator::process_classic( // Inputs: const Parameters ¶ms, - const SurfaceCollection *slices, + const Surface &surface, const ExPolygons *lower_slices, // Cache: Polygons &lower_slices_polygons_cache, @@ -875,230 +873,228 @@ void PerimeterGenerator::process_classic( // we need to process each island separately because we might have different // extra perimeters for each one - for (const Surface &surface : slices->surfaces) { - // detect how many perimeters must be generated for this island - 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. - std::vector contours(loop_number+1); // depth => loops - std::vector holes(loop_number+1); // depth => loops - ThickPolylines thin_walls; - // we loop one time more than needed in order to find gaps after the last perimeter was applied - for (int i = 0;; ++ i) { // outer loop is 0 - // Calculate next onion shell of perimeters. - ExPolygons offsets; - if (i == 0) { - // the minimum thickness of a single loop is: - // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 - 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 (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_(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)), - float(min_width / 2.)); - // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop - for (ExPolygon &ex : expp) - ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); - } - if (params.spiral_vase && offsets.size() > 1) { - // Remove all but the largest area polygon. - keep_largest_contour_only(offsets); - } - } else { - //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 = 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 - // reliable gap fill algorithm. - // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than - // the original. - offset2_ex(last, - - float(distance + min_spacing / 2. - 1.), - float(min_spacing / 2. - 1.)) : - // If "detect thin walls" is not enabled, this paths will be entered, which - // leads to overflows, as in prusa3d/Slic3r GH #32 - offset_ex(last, - float(distance)); - // look for gaps - if (has_gap_fill) - // not using safety offset here would "detect" very narrow gaps - // (but still long enough to escape the area threshold) that gap fill - // won't be able to fill but we'd still remove from infill area - append(gaps, diff_ex( - offset(last, - float(0.5 * distance)), - offset(offsets, float(0.5 * distance + 10)))); // safety offset + // detect how many perimeters must be generated for this island + 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. + std::vector contours(loop_number+1); // depth => loops + std::vector holes(loop_number+1); // depth => loops + ThickPolylines thin_walls; + // we loop one time more than needed in order to find gaps after the last perimeter was applied + for (int i = 0;; ++ i) { // outer loop is 0 + // Calculate next onion shell of perimeters. + ExPolygons offsets; + if (i == 0) { + // the minimum thickness of a single loop is: + // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 + 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 (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_(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)), + float(min_width / 2.)); + // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop + for (ExPolygon &ex : expp) + ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); } - if (offsets.empty()) { - // Store the number of loops actually generated. - loop_number = i - 1; - // No region left to be filled in. - last.clear(); - break; - } else if (i > loop_number) { - // If i > loop_number, we were looking just for gaps. - break; + if (params.spiral_vase && offsets.size() > 1) { + // Remove all but the largest area polygon. + keep_largest_contour_only(offsets); } - { - 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, - // outer contour may overlap with itself. - //FIXME evaluate the overlaps, annotate each point with an overlap depth, - // compensate for the depth of intersection. - contours[i].emplace_back(expolygon.contour, i, true, fuzzify_contours); + } else { + //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 = 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 + // reliable gap fill algorithm. + // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than + // the original. + offset2_ex(last, + - float(distance + min_spacing / 2. - 1.), + float(min_spacing / 2. - 1.)) : + // If "detect thin walls" is not enabled, this paths will be entered, which + // leads to overflows, as in prusa3d/Slic3r GH #32 + offset_ex(last, - float(distance)); + // look for gaps + if (has_gap_fill) + // not using safety offset here would "detect" very narrow gaps + // (but still long enough to escape the area threshold) that gap fill + // won't be able to fill but we'd still remove from infill area + append(gaps, diff_ex( + offset(last, - float(0.5 * distance)), + offset(offsets, float(0.5 * distance + 10)))); // safety offset + } + if (offsets.empty()) { + // Store the number of loops actually generated. + loop_number = i - 1; + // No region left to be filled in. + last.clear(); + break; + } else if (i > loop_number) { + // If i > loop_number, we were looking just for gaps. + break; + } + { + 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, + // outer contour may overlap with itself. + //FIXME evaluate the overlaps, annotate each point with an overlap depth, + // compensate for the depth of intersection. + contours[i].emplace_back(expolygon.contour, i, true, fuzzify_contours); - if (! expolygon.holes.empty()) { - holes[i].reserve(holes[i].size() + expolygon.holes.size()); - for (const Polygon &hole : expolygon.holes) - holes[i].emplace_back(hole, i, false, fuzzify_holes); - } + if (! expolygon.holes.empty()) { + holes[i].reserve(holes[i].size() + expolygon.holes.size()); + for (const Polygon &hole : expolygon.holes) + holes[i].emplace_back(hole, i, false, fuzzify_holes); } } - last = std::move(offsets); - 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; - } } - - // nest loops: holes first - for (int d = 0; d <= loop_number; ++ d) { - PerimeterGeneratorLoops &holes_d = holes[d]; - // loop through all holes having depth == d - for (int i = 0; i < (int)holes_d.size(); ++ i) { - const PerimeterGeneratorLoop &loop = holes_d[i]; - // find the hole loop that contains this one, if any - for (int t = d + 1; t <= loop_number; ++ t) { - for (int j = 0; j < (int)holes[t].size(); ++ j) { - PerimeterGeneratorLoop &candidate_parent = holes[t][j]; - if (candidate_parent.polygon.contains(loop.polygon.first_point())) { - candidate_parent.children.push_back(loop); - holes_d.erase(holes_d.begin() + i); - -- i; - goto NEXT_LOOP; - } - } - } - // if no hole contains this hole, find the contour loop that contains it - for (int t = loop_number; t >= 0; -- t) { - for (int j = 0; j < (int)contours[t].size(); ++ j) { - PerimeterGeneratorLoop &candidate_parent = contours[t][j]; - if (candidate_parent.polygon.contains(loop.polygon.first_point())) { - candidate_parent.children.push_back(loop); - holes_d.erase(holes_d.begin() + i); - -- i; - goto NEXT_LOOP; - } - } - } - NEXT_LOOP: ; - } + last = std::move(offsets); + 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; } - // nest contour loops - for (int d = loop_number; d >= 1; -- d) { - PerimeterGeneratorLoops &contours_d = contours[d]; - // loop through all contours having depth == d - for (int i = 0; i < (int)contours_d.size(); ++ i) { - const PerimeterGeneratorLoop &loop = contours_d[i]; - // find the contour loop that contains it - for (int t = d - 1; t >= 0; -- t) { - for (size_t j = 0; j < contours[t].size(); ++ j) { - PerimeterGeneratorLoop &candidate_parent = contours[t][j]; - if (candidate_parent.polygon.contains(loop.polygon.first_point())) { - candidate_parent.children.push_back(loop); - contours_d.erase(contours_d.begin() + i); - -- i; - goto NEXT_CONTOUR; - } - } - } - NEXT_CONTOUR: ; - } - } - // at this point, all loops should be in contours[0] - 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 (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()) - out_loops.append(entities); - } // for each loop of an island - - // fill gaps - if (! gaps.empty()) { - // collapse - double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE); - double max = 2. * perimeter_spacing; - ExPolygons gaps_ex = diff_ex( - //FIXME offset2 would be enough and cheaper. - opening_ex(gaps, float(min / 2.)), - offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset))); - ThickPolylines polylines; - for (const ExPolygon &ex : gaps_ex) - ex.medial_axis(max, min, &polylines); - if (! polylines.empty()) { - ExtrusionEntityCollection gap_fill; - 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 - are not subtracted from fill surfaces (they might be too short gaps - that medial axis skips but infill might join with other infill regions - and use zigzag). */ - //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)); - out_gap_fill.append(std::move(gap_fill.entities)); - } } - // create one more offset to be used as boundary for fill - // we offset by half the perimeter spacing (to get to the actual infill boundary) - // and then we offset back and forth by half the infill spacing to only consider the - // non-collapsing regions - coord_t inset = - (loop_number < 0) ? 0 : - (loop_number == 0) ? - // one loop - ext_perimeter_spacing / 2 : - // two or more loops? - perimeter_spacing / 2; - // only apply infill overlap if we actually have one perimeter - if (inset > 0) - 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(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 - append(out_fill_expolygons, - offset2_ex( - union_ex(pp), - float(- inset - min_perimeter_infill_spacing / 2.), - float(min_perimeter_infill_spacing / 2.))); - } // for each island + // nest loops: holes first + for (int d = 0; d <= loop_number; ++ d) { + PerimeterGeneratorLoops &holes_d = holes[d]; + // loop through all holes having depth == d + for (int i = 0; i < (int)holes_d.size(); ++ i) { + const PerimeterGeneratorLoop &loop = holes_d[i]; + // find the hole loop that contains this one, if any + for (int t = d + 1; t <= loop_number; ++ t) { + for (int j = 0; j < (int)holes[t].size(); ++ j) { + PerimeterGeneratorLoop &candidate_parent = holes[t][j]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) { + candidate_parent.children.push_back(loop); + holes_d.erase(holes_d.begin() + i); + -- i; + goto NEXT_LOOP; + } + } + } + // if no hole contains this hole, find the contour loop that contains it + for (int t = loop_number; t >= 0; -- t) { + for (int j = 0; j < (int)contours[t].size(); ++ j) { + PerimeterGeneratorLoop &candidate_parent = contours[t][j]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) { + candidate_parent.children.push_back(loop); + holes_d.erase(holes_d.begin() + i); + -- i; + goto NEXT_LOOP; + } + } + } + NEXT_LOOP: ; + } + } + // nest contour loops + for (int d = loop_number; d >= 1; -- d) { + PerimeterGeneratorLoops &contours_d = contours[d]; + // loop through all contours having depth == d + for (int i = 0; i < (int)contours_d.size(); ++ i) { + const PerimeterGeneratorLoop &loop = contours_d[i]; + // find the contour loop that contains it + for (int t = d - 1; t >= 0; -- t) { + for (size_t j = 0; j < contours[t].size(); ++ j) { + PerimeterGeneratorLoop &candidate_parent = contours[t][j]; + if (candidate_parent.polygon.contains(loop.polygon.first_point())) { + candidate_parent.children.push_back(loop); + contours_d.erase(contours_d.begin() + i); + -- i; + goto NEXT_CONTOUR; + } + } + } + NEXT_CONTOUR: ; + } + } + // at this point, all loops should be in contours[0] + 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 (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()) + out_loops.append(entities); + } // for each loop of an island + + // fill gaps + if (! gaps.empty()) { + // collapse + double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE); + double max = 2. * perimeter_spacing; + ExPolygons gaps_ex = diff_ex( + //FIXME offset2 would be enough and cheaper. + opening_ex(gaps, float(min / 2.)), + offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset))); + ThickPolylines polylines; + for (const ExPolygon &ex : gaps_ex) + ex.medial_axis(max, min, &polylines); + if (! polylines.empty()) { + ExtrusionEntityCollection gap_fill; + 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 + are not subtracted from fill surfaces (they might be too short gaps + that medial axis skips but infill might join with other infill regions + and use zigzag). */ + //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)); + out_gap_fill.append(std::move(gap_fill.entities)); + } + } + + // create one more offset to be used as boundary for fill + // we offset by half the perimeter spacing (to get to the actual infill boundary) + // and then we offset back and forth by half the infill spacing to only consider the + // non-collapsing regions + coord_t inset = + (loop_number < 0) ? 0 : + (loop_number == 0) ? + // one loop + ext_perimeter_spacing / 2 : + // two or more loops? + perimeter_spacing / 2; + // only apply infill overlap if we actually have one perimeter + if (inset > 0) + 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(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 + append(out_fill_expolygons, + offset2_ex( + union_ex(pp), + float(- inset - min_perimeter_infill_spacing / 2.), + float(min_perimeter_infill_spacing / 2.))); } } diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index de52dd89a..33d735a3e 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -67,7 +67,7 @@ private: void process_classic( // Inputs: const Parameters ¶ms, - const SurfaceCollection *slices, + const Surface &surface, const ExPolygons *lower_slices, // Cache: Polygons &lower_slices_polygons_cache, @@ -82,7 +82,7 @@ void process_classic( void process_arachne( // Inputs: const Parameters ¶ms, - const SurfaceCollection *slices, + const Surface &surface, const ExPolygons *lower_slices, // Cache: Polygons &lower_slices_polygons_cache, diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index bf0abd4fa..361234bd2 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -516,6 +516,26 @@ void remove_collinear(Polygons &polys) remove_collinear(poly); } +// Do polygons match? If they match, they must have the same topology, +// however their contours may be rotated. +bool polygons_match(const Polygon &l, const Polygon &r) +{ + if (l.size() != r.size()) + return false; + auto it_l = std::find(l.points.begin(), l.points.end(), r.points.front()); + if (it_l == l.points.end()) + return false; + auto it_r = r.points.begin(); + for (; it_l != l.points.end(); ++ it_l, ++ it_r) + if (*it_l != *it_r) + return false; + it_l = l.points.begin(); + for (; it_r != r.points.end(); ++ it_l, ++ it_r) + if (*it_l != *it_r) + return false; + return true; +} + bool contains(const Polygons &polygons, const Point &p, bool border_result) { int poly_count_inside = 0; diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 12d457c37..a17b91188 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -250,6 +250,10 @@ inline Polygons to_polygons(std::vector &&paths) return out; } +// Do polygons match? If they match, they must have the same topology, +// however their contours may be rotated. +bool polygons_match(const Polygon &l, const Polygon &r); + // Returns true if inside. Returns border_result if on boundary. bool contains(const Polygons& polygons, const Point& p, bool border_result = true); diff --git a/tests/fff_print/test_perimeters.cpp b/tests/fff_print/test_perimeters.cpp index decdb4802..3c817be00 100644 --- a/tests/fff_print/test_perimeters.cpp +++ b/tests/fff_print/test_perimeters.cpp @@ -55,6 +55,7 @@ SCENARIO("Perimeter nesting", "[Perimeters]") static_cast(config), false); // spiral_vase Polygons lower_layer_polygons_cache; + for (const Surface &surface : slices) // FIXME Lukas H.: Disable this test for Arachne because it is failing and needs more investigation. // if (config.perimeter_generator == PerimeterGeneratorType::Arachne) // PerimeterGenerator::process_arachne(); @@ -62,7 +63,7 @@ SCENARIO("Perimeter nesting", "[Perimeters]") PerimeterGenerator::process_classic( // input: perimeter_generator_params, - &slices, + surface, nullptr, // cache: lower_layer_polygons_cache, From 386cfae54626dbee5b8c1d56965afffa125345bf Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 2 Nov 2022 17:20:23 +0100 Subject: [PATCH 08/33] WIP Refactoring of Layers: Sorting of infill extrusions into LayerIslands. FIXME: Gap fill extrusions are currently not handled! --- src/libslic3r/BoundingBox.hpp | 33 +++++++++++ src/libslic3r/ExPolygon.cpp | 20 +++++++ src/libslic3r/ExPolygon.hpp | 2 + src/libslic3r/Fill/Fill.cpp | 108 ++++++++++++++++++++++++++++++++-- src/libslic3r/Layer.hpp | 37 ++++++------ 5 files changed, 175 insertions(+), 25 deletions(-) diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 5e3d3e323..ecc36de7b 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -244,6 +244,39 @@ auto cast(const BoundingBox3Base &b) b.max.template cast()}; } +// Distance of a point to a bounding box. Zero inside and on the boundary, positive outside. +inline double bbox_point_distance(const BoundingBox &bbox, const Point &pt) +{ + if (pt.x() < bbox.min.x()) + return pt.y() < bbox.min.y() ? (bbox.min - pt).cast().norm() : + pt.y() > bbox.max.y() ? (Point(bbox.min.x(), bbox.max.y()) - pt).cast().norm() : + double(bbox.min.x() - pt.x()); + else if (pt.x() > bbox.max.x()) + return pt.y() < bbox.min.y() ? (Point(bbox.max.x(), bbox.min.y()) - pt).cast().norm() : + pt.y() > bbox.max.y() ? (bbox.max - pt).cast().norm() : + double(pt.x() - bbox.max.x()); + else + return pt.y() < bbox.min.y() ? bbox.min.y() - pt.y() : + pt.y() > bbox.max.y() ? pt.y() - bbox.max.y() : + coord_t(0); +} + +inline double bbox_point_distance_squared(const BoundingBox &bbox, const Point &pt) +{ + if (pt.x() < bbox.min.x()) + return pt.y() < bbox.min.y() ? (bbox.min - pt).cast().squaredNorm() : + pt.y() > bbox.max.y() ? (Point(bbox.min.x(), bbox.max.y()) - pt).cast().squaredNorm() : + Slic3r::sqr(double(bbox.min.x() - pt.x())); + else if (pt.x() > bbox.max.x()) + return pt.y() < bbox.min.y() ? (Point(bbox.max.x(), bbox.min.y()) - pt).cast().squaredNorm() : + pt.y() > bbox.max.y() ? (bbox.max - pt).cast().squaredNorm() : + Slic3r::sqr(pt.x() - bbox.max.x()); + else + return Slic3r::sqr(pt.y() < bbox.min.y() ? bbox.min.y() - pt.y() : + pt.y() > bbox.max.y() ? pt.y() - bbox.max.y() : + coord_t(0)); +} + } // namespace Slic3r // Serialization through the Cereal library diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 2224f991e..651be062a 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -117,6 +117,26 @@ ExPolygon::has_boundary_point(const Point &point) const return false; } +// Projection of a point onto the polygon. +Point ExPolygon::point_projection(const Point &point) const +{ + if (this->holes.empty()) { + return this->contour.point_projection(point); + } else { + double dist_min2 = std::numeric_limits::max(); + Point closest_pt_min; + for (size_t i = 0; i < this->num_contours(); ++ i) { + Point closest_pt = this->contour_or_hole(i).point_projection(point); + double d2 = (closest_pt - point).cast().squaredNorm(); + if (d2 < dist_min2) { + dist_min2 = d2; + closest_pt_min = closest_pt; + } + } + return closest_pt_min; + } +} + bool ExPolygon::overlaps(const ExPolygon &other) const { #if 0 diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index a0e4f272f..a319d003c 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -53,6 +53,8 @@ public: bool contains(const Point &point) const; bool contains_b(const Point &point) const; bool has_boundary_point(const Point &point) const; + // Projection of a point onto the polygon. + Point point_projection(const Point &point) const; // Does this expolygon overlap another expolygon? // Either the ExPolygons intersect, or one is fully inside the other, diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 252600692..0a54ae1fa 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -322,6 +322,92 @@ void export_group_fills_to_svg(const char *path, const std::vector } #endif +static void insert_fills_into_islands(Layer &layer, uint32_t fill_region_id, uint32_t fill_begin, uint32_t fill_end) +{ + if (fill_begin < fill_end) { + // Sort the extrusion range into its LayerIsland. + // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, + // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. + auto point_inside_surface = [&layer](const size_t lslice_idx, const Point &point) { + const BoundingBox &bbox = layer.lslices_ex[lslice_idx].bbox; + return point.x() >= bbox.min.x() && point.x() < bbox.max.x() && + point.y() >= bbox.min.y() && point.y() < bbox.max.y() && + layer.lslices[lslice_idx].contour.contains(point); + }; + Point point = layer.get_region(fill_region_id)->fills().entities[fill_begin]->first_point(); + int lslice_idx = int(layer.lslices_ex.size()) - 1; + for (; lslice_idx >= 0; -- lslice_idx) + if (point_inside_surface(lslice_idx, point)) + break; + assert(lslice_idx >= 0); + if (lslice_idx >= 0) { + LayerSlice &lslice = layer.lslices_ex[lslice_idx]; + // Find an island. + LayerIsland *island = nullptr; + if (lslice.islands.size() == 1) { + // Cool, just save the extrusions in there. + island = &lslice.islands.front(); + } else { + // The infill was created for one of the infills. + // In case of ironing, the infill may not fall into any of the infill expolygons either. + // In case of some numerical error, the infill may not fall into any of the infill expolygons either. + // 1) Try an exact test, it should be cheaper than a closest region test. + for (LayerIsland &li : lslice.islands) { + const BoundingBoxes &bboxes = li.fill_expolygons_composite() ? + layer.get_region(li.perimeters.region())->fill_expolygons_composite_bboxes() : + layer.get_region(li.fill_region_id)->fill_expolygons_bboxes(); + const ExPolygons &expolygons = li.fill_expolygons_composite() ? + layer.get_region(li.perimeters.region())->fill_expolygons_composite() : + layer.get_region(li.fill_region_id)->fill_expolygons(); + for (uint32_t fill_expolygon_id : li.fill_expolygons) + if (bboxes[fill_expolygon_id].contains(point) && expolygons[fill_expolygon_id].contains(point)) { + island = &li; + goto found; + } + } + // 2) Find closest fill_expolygon, branch and bound by distance to bounding box. + { + struct Island { + uint32_t island_idx; + uint32_t expolygon_idx; + double distance2; + }; + std::vector islands_sorted; + for (uint32_t island_idx = 0; island_idx < uint32_t(lslice.islands.size()); ++ island_idx) { + const LayerIsland &li = lslice.islands[island_idx]; + const BoundingBoxes &bboxes = li.fill_expolygons_composite() ? + layer.get_region(li.perimeters.region())->fill_expolygons_composite_bboxes() : + layer.get_region(li.fill_region_id)->fill_expolygons_bboxes(); + for (uint32_t fill_expolygon_id : li.fill_expolygons) + islands_sorted.push_back({ island_idx, fill_expolygon_id, bbox_point_distance_squared(bboxes[fill_expolygon_id], point) }); + } + std::sort(islands_sorted.begin(), islands_sorted.end(), [](auto &l, auto &r){ return l.distance2 < r.distance2; }); + auto dist_min2 = std::numeric_limits::max(); + for (uint32_t sorted_bbox_idx = 0; sorted_bbox_idx < uint32_t(islands_sorted.size()); ++ sorted_bbox_idx) { + const Island &isl = islands_sorted[sorted_bbox_idx]; + if (isl.distance2 > dist_min2) + // Branch & bound condition. + break; + LayerIsland &li = lslice.islands[isl.island_idx]; + const ExPolygons &expolygons = li.fill_expolygons_composite() ? + layer.get_region(li.perimeters.region())->fill_expolygons_composite() : + layer.get_region(li.fill_region_id)->fill_expolygons(); + double d2 = (expolygons[isl.expolygon_idx].point_projection(point) - point).cast().squaredNorm(); + if (d2 < dist_min2) { + dist_min2 = d2; + island = &li; + } + } + } + found:; + } + assert(island); + if (island) + island->fills.push_back(LayerExtrusionRange{ fill_region_id, { fill_begin, fill_end }}); + } + } +} + // friend to Layer void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) { @@ -382,6 +468,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // Used by the concentric infill pattern to clip the loops to create extrusion paths. f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); + LayerRegion &layerm = *m_regions[surface_fill.region_id]; + // apply half spacing using this flow's own spacing and generate infill FillParams params; params.density = float(0.01 * surface_fill.params.density); @@ -390,7 +478,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.anchor_length_max = surface_fill.params.anchor_length_max; params.resolution = resolution; params.use_arachne = perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric; - params.layer_height = m_regions[surface_fill.region_id]->layer()->height; + params.layer_height = layerm.layer()->height; for (ExPolygon &expoly : surface_fill.expolygons) { // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon. @@ -421,7 +509,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: } // Save into layer. ExtrusionEntityCollection* eec = nullptr; - m_regions[surface_fill.region_id]->m_fills.entities.push_back(eec = new ExtrusionEntityCollection()); + auto fill_begin = uint32_t(layerm.fills().size()); + layerm.m_fills.entities.push_back(eec = new ExtrusionEntityCollection()); // Only concentric fills are not sorted. eec->no_sort = f->no_sort(); if (params.use_arachne) { @@ -445,10 +534,14 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: surface_fill.params.extrusion_role, flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height()); } + insert_fills_into_islands(*this, uint32_t(surface_fill.region_id), fill_begin, uint32_t(layerm.fills().size())); } } } + //FIXME Don't copy thin fill extrusions into fills, just use these thin fill extrusions + // from the G-code export directly. +#if 0 // add thin fill regions // Unpacks the collection, creates multiple collections per path. // The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection. @@ -459,6 +552,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: layerm->m_fills.entities.push_back(&collection); collection.entities.push_back(thin_fill->clone()); } +#endif #ifndef NDEBUG for (LayerRegion *layerm : m_regions) @@ -519,7 +613,8 @@ void Layer::make_ironing() this->angle == rhs.angle; } - LayerRegion *layerm = nullptr; + LayerRegion *layerm; + uint32_t region_id; // IdeaMaker: ironing // ironing flowrate (5% percent) @@ -539,8 +634,8 @@ void Layer::make_ironing() std::vector by_extruder; double default_layer_height = this->object()->config().layer_height; - for (LayerRegion *layerm : m_regions) - if (! layerm->slices().empty()) { + for (uint32_t region_id = 0; region_id < uint32_t(this->regions().size()); ++region_id) + if (LayerRegion *layerm = this->get_region(region_id); ! layerm->slices().empty()) { IroningParams ironing_params; const PrintRegionConfig &config = layerm->region().config(); if (config.ironing && @@ -564,6 +659,7 @@ void Layer::make_ironing() ironing_params.speed = config.ironing_speed; ironing_params.angle = config.fill_angle * M_PI / 180.; ironing_params.layerm = layerm; + ironing_params.region_id = region_id; by_extruder.emplace_back(ironing_params); } } @@ -659,6 +755,7 @@ void Layer::make_ironing() } if (! polylines.empty()) { // Save into layer. + auto fill_begin = uint32_t(ironing_params.layerm->fills().size()); ExtrusionEntityCollection *eec = nullptr; ironing_params.layerm->m_fills.entities.push_back(eec = new ExtrusionEntityCollection()); // Don't sort the ironing infill lines as they are monotonicly ordered. @@ -667,6 +764,7 @@ void Layer::make_ironing() eec->entities, std::move(polylines), erIroning, flow_mm3_per_mm, extrusion_width, float(extrusion_height)); + insert_fills_into_islands(*this, ironing_params.region_id, fill_begin, uint32_t(ironing_params.layerm->fills().size())); } } diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 137f35e5c..d7d0fbffb 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -32,35 +32,33 @@ namespace FillLightning { template class IndexRange { -private: - // Just a bare minimum functionality iterator required by range-for loop. - template - class IteratorType { - public: - T operator*() const { return m_idx; } - bool operator!=(const IteratorType &rhs) const { return m_idx != rhs.m_idx; } - void operator++() { ++ m_idx; } - private: - friend class IndexRange; - IteratorType(T idx) : m_idx(idx) {} - T m_idx; - }; - // Index of the first extrusion in LayerRegion. - T m_begin { 0 }; - // Index of the last extrusion in LayerRegion. - T m_end { 0 }; - public: IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {} IndexRange() = default; - using Iterator = IteratorType; + // Just a bare minimum functionality iterator required by range-for loop. + class Iterator { + public: + T operator*() const { return m_idx; } + bool operator!=(const Iterator &rhs) const { return m_idx != rhs.m_idx; } + void operator++() { ++ m_idx; } + private: + friend class IndexRange; + Iterator(T idx) : m_idx(idx) {} + T m_idx; + }; Iterator begin() const { assert(m_begin <= m_end); return Iterator(m_begin); }; Iterator end() const { assert(m_begin <= m_end); return Iterator(m_end); }; bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; } T size() const { assert(m_begin <= m_end); return m_end - m_begin; } + +private: + // Index of the first extrusion in LayerRegion. + T m_begin { 0 }; + // Index of the last extrusion in LayerRegion. + T m_end { 0 }; }; using ExtrusionRange = IndexRange; @@ -70,7 +68,6 @@ using ExPolygonRange = IndexRange; class LayerExtrusionRange : public ExtrusionRange { public: - LayerExtrusionRange(uint32_t iregion, uint32_t ibegin, uint32_t iend) : m_region(iregion), ExtrusionRange(ibegin, iend) {} LayerExtrusionRange(uint32_t iregion, ExtrusionRange extrusion_range) : m_region(iregion), ExtrusionRange(extrusion_range) {} LayerExtrusionRange() = default; From 8858651bf46dce2ac0b3435ab9b46a4053cf7c3b Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Nov 2022 14:47:43 +0100 Subject: [PATCH 09/33] WIP Refactoring of Layers: Reworked G-code export to make use of Layer->LayerSlice->LayerIsland hierarchy. This should improve tool path ordering of multiple parts within the same object #5511. Some shells tests rewritten from Perl to C++. FIXME: Gap fill extrusions are currently not handled by the initial G-code preview! --- src/libslic3r/ExtrusionEntityCollection.hpp | 4 +- src/libslic3r/GCode.cpp | 710 ++++++++------------ src/libslic3r/GCode.hpp | 125 ++-- src/libslic3r/GCode/ToolOrdering.cpp | 45 +- src/libslic3r/GCode/ToolOrdering.hpp | 23 +- src/libslic3r/Layer.cpp | 242 ++++--- src/libslic3r/Layer.hpp | 4 +- t/shells.t | 69 +- tests/fff_print/CMakeLists.txt | 1 + tests/fff_print/test_shells.cpp | 112 +++ 10 files changed, 602 insertions(+), 733 deletions(-) create mode 100644 tests/fff_print/test_shells.cpp diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index a344bb4a9..59d4421d6 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -127,12 +127,12 @@ public: }; void collect_polylines(Polylines &dst) const override { - for (ExtrusionEntity* extrusion_entity : this->entities) + for (const ExtrusionEntity *extrusion_entity : this->entities) extrusion_entity->collect_polylines(dst); } void collect_points(Points &dst) const override { - for (ExtrusionEntity* extrusion_entity : this->entities) + for (const ExtrusionEntity *extrusion_entity : this->entities) extrusion_entity->collect_points(dst); } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c45b6daa9..24e350ac4 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -475,9 +475,9 @@ namespace Slic3r { // Collect pairs of object_layer + support_layer sorted by print_z. // object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON. -std::vector GCode::collect_layers_to_print(const PrintObject& object) +GCode::ObjectsLayerToPrint GCode::collect_layers_to_print(const PrintObject& object) { - std::vector layers_to_print; + GCode::ObjectsLayerToPrint layers_to_print; layers_to_print.reserve(object.layers().size() + object.support_layers().size()); /* @@ -501,9 +501,9 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Pair the object layers with the support layers by z. size_t idx_object_layer = 0; size_t idx_support_layer = 0; - const LayerToPrint* last_extrusion_layer = nullptr; + const ObjectLayerToPrint* last_extrusion_layer = nullptr; while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) { - LayerToPrint layer_to_print; + ObjectLayerToPrint layer_to_print; layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer++] : nullptr; layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer++] : nullptr; if (layer_to_print.object_layer && layer_to_print.support_layer) { @@ -575,8 +575,8 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z // will be printed for all objects at once. -// Return a list of items. -std::vector>> GCode::collect_layers_to_print(const Print& print) +// Return a list of items. +std::vector> GCode::collect_layers_to_print(const Print& print) { struct OrderingItem { coordf_t print_z; @@ -584,15 +584,15 @@ std::vector>> GCode::collec size_t layer_idx; }; - std::vector> per_object(print.objects().size(), std::vector()); - std::vector ordering; + std::vector per_object(print.objects().size(), ObjectsLayerToPrint()); + std::vector ordering; for (size_t i = 0; i < print.objects().size(); ++i) { per_object[i] = collect_layers_to_print(*print.objects()[i]); OrderingItem ordering_item; ordering_item.object_idx = i; ordering.reserve(ordering.size() + per_object[i].size()); - const LayerToPrint& front = per_object[i].front(); - for (const LayerToPrint& ltp : per_object[i]) { + const ObjectLayerToPrint &front = per_object[i].front(); + for (const ObjectLayerToPrint <p : per_object[i]) { ordering_item.print_z = ltp.print_z(); ordering_item.layer_idx = <p - &front; ordering.emplace_back(ordering_item); @@ -601,7 +601,7 @@ std::vector>> GCode::collec std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; }); - std::vector>> layers_to_print; + std::vector> layers_to_print; // Merge numerically very close Z values. for (size_t i = 0; i < ordering.size();) { @@ -610,10 +610,10 @@ std::vector>> GCode::collec coordf_t zmax = ordering[i].print_z + EPSILON; for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j); // Merge into layers_to_print. - std::pair> merged; + std::pair merged; // Assign an average print_z to the set of layers with nearly equal print_z. merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z); - merged.second.assign(print.objects().size(), LayerToPrint()); + merged.second.assign(print.objects().size(), ObjectLayerToPrint()); for (; i < j; ++i) { const OrderingItem& oi = ordering[i]; assert(merged.second[oi.object_idx].layer() == nullptr); @@ -1347,7 +1347,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato } else { // Sort layers by Z. // All extrusion moves with the same top layer height are extruded uninterrupted. - std::vector>> layers_to_print = collect_layers_to_print(print); + std::vector> layers_to_print = collect_layers_to_print(print); // Prusa Multi-Material wipe tower. if (has_wipe_tower && ! layers_to_print.empty()) { m_wipe_tower.reset(new WipeTowerIntegration(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get())); @@ -1475,7 +1475,7 @@ void GCode::process_layers( const Print &print, const ToolOrdering &tool_ordering, const std::vector &print_object_instances_ordering, - const std::vector>> &layers_to_print, + const std::vector> &layers_to_print, GCodeOutputStream &output_stream) { // The pipeline is variable: The vase mode filter is optional. @@ -1493,7 +1493,7 @@ void GCode::process_layers( return LayerResult::make_nop_layer_result(); } } else { - const std::pair>& layer = layers_to_print[layer_to_print_idx++]; + const std::pair &layer = layers_to_print[layer_to_print_idx++]; const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first); if (m_wipe_tower && layer_tools.has_wipe_tower) m_wipe_tower->next_layer(); @@ -1559,7 +1559,7 @@ void GCode::process_layers( void GCode::process_layers( const Print &print, const ToolOrdering &tool_ordering, - std::vector layers_to_print, + ObjectsLayerToPrint layers_to_print, const size_t single_object_idx, GCodeOutputStream &output_stream) { @@ -1578,7 +1578,7 @@ void GCode::process_layers( return LayerResult::make_nop_layer_result(); } } else { - LayerToPrint &layer = layers_to_print[layer_to_print_idx ++]; + ObjectLayerToPrint &layer = layers_to_print[layer_to_print_idx ++]; print.throw_if_canceled(); return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx); } @@ -1829,70 +1829,39 @@ void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Pr } } -inline GCode::ObjectByExtruder& object_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects) -{ - std::vector &objects_by_extruder = by_extruder[extruder_id]; - if (objects_by_extruder.empty()) - objects_by_extruder.assign(num_objects, GCode::ObjectByExtruder()); - return objects_by_extruder[object_idx]; -} - -inline std::vector& object_islands_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects, - size_t num_islands) -{ - std::vector &islands = object_by_extruder(by_extruder, extruder_id, object_idx, num_objects).islands; - if (islands.empty()) - islands.assign(num_islands, GCode::ObjectByExtruder::Island()); - return islands; -} - std::vector GCode::sort_print_object_instances( - std::vector &objects_by_extruder, - const std::vector &layers, + const std::vector &object_layers, // Ordering must be defined for normal (non-sequential print). - const std::vector *ordering, + const std::vector *ordering, // For sequential print, the instance of the object to be printing has to be defined. - const size_t single_object_instance_idx) + const size_t single_object_instance_idx) { std::vector out; if (ordering == nullptr) { // Sequential print, single object is being printed. - for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { - const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); - const PrintObject *print_object = layers[layer_id].object(); - if (print_object) - out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx); - } + assert(object_layers.size() == 1); + const Layer *layer = object_layers.front().object_layer; + assert(layer != nullptr); + out.emplace_back(0, *layer->object(), single_object_instance_idx); } else { - // Create mapping from PrintObject* to ObjectByExtruder*. - std::vector> sorted; - sorted.reserve(objects_by_extruder.size()); - for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { - const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); - const PrintObject *print_object = layers[layer_id].object(); - if (print_object) - sorted.emplace_back(print_object, &object_by_extruder); - } + // Create mapping from PrintObject* to ObjectLayerToPrint ID. + std::vector> sorted; + sorted.reserve(object_layers.size()); + for (const ObjectLayerToPrint &object : object_layers) + if (const PrintObject* print_object = object.object(); print_object) + sorted.emplace_back(print_object, &object - object_layers.data()); std::sort(sorted.begin(), sorted.end()); if (! sorted.empty()) { out.reserve(sorted.size()); for (const PrintInstance *instance : *ordering) { const PrintObject &print_object = *instance->print_object; - std::pair key(&print_object, nullptr); + std::pair key(&print_object, 0); auto it = std::lower_bound(sorted.begin(), sorted.end(), key); if (it != sorted.end() && it->first == &print_object) - // ObjectByExtruder for this PrintObject was found. - out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data()); + // ObjectLayerToPrint for this PrintObject was found. + out.emplace_back(it->second, print_object, instance - print_object.instances().data()); } } } @@ -2059,7 +2028,7 @@ namespace Skirt { LayerResult GCode::process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. - const std::vector &layers, + const ObjectsLayerToPrint &layers, const LayerTools &layer_tools, const bool last_layer, // Pairs of PrintObject index and its instance index. @@ -2076,7 +2045,7 @@ LayerResult GCode::process_layer( const Layer *object_layer = nullptr; const SupportLayer *support_layer = nullptr; const SupportLayer *raft_layer = nullptr; - for (const LayerToPrint &l : layers) { + for (const ObjectLayerToPrint &l : layers) { if (l.object_layer && ! object_layer) object_layer = l.object_layer; if (l.support_layer) { @@ -2086,7 +2055,7 @@ LayerResult GCode::process_layer( raft_layer = support_layer; } } - const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer; + const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer; LayerResult result { {}, layer.id(), false, last_layer, false}; if (layer_tools.extruders.empty()) // Nothing to extrude. @@ -2189,157 +2158,6 @@ LayerResult GCode::process_layer( Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) : Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done); - // Group extrusions by an extruder, then by an object, an island and a region. - std::map> by_extruder; - bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); - for (const LayerToPrint &layer_to_print : layers) { - if (layer_to_print.support_layer != nullptr) { - const SupportLayer &support_layer = *layer_to_print.support_layer; - const PrintObject &object = *support_layer.object(); - if (! support_layer.support_fills.entities.empty()) { - ExtrusionRole role = support_layer.support_fills.role(); - bool has_support = role == erMixed || role == erSupportMaterial; - bool has_interface = role == erMixed || role == erSupportMaterialInterface; - // Extruder ID of the support base. -1 if "don't care". - unsigned int support_extruder = object.config().support_material_extruder.value - 1; - // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes? - bool support_dontcare = object.config().support_material_extruder.value == 0; - // Extruder ID of the support interface. -1 if "don't care". - unsigned int interface_extruder = object.config().support_material_interface_extruder.value - 1; - // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes? - bool interface_dontcare = object.config().support_material_interface_extruder.value == 0; - if (support_dontcare || interface_dontcare) { - // Some support will be printed with "don't care" material, preferably non-soluble. - // Is the current extruder assigned a soluble filament? - unsigned int dontcare_extruder = first_extruder_id; - if (print.config().filament_soluble.get_at(dontcare_extruder)) { - // The last extruder printed on the previous layer extrudes soluble filament. - // Try to find a non-soluble extruder on the same layer. - for (unsigned int extruder_id : layer_tools.extruders) - if (! print.config().filament_soluble.get_at(extruder_id)) { - dontcare_extruder = extruder_id; - break; - } - } - if (support_dontcare) - support_extruder = dontcare_extruder; - if (interface_dontcare) - interface_extruder = dontcare_extruder; - } - // Both the support and the support interface are printed with the same extruder, therefore - // the interface may be interleaved with the support base. - bool single_extruder = ! has_support || support_extruder == interface_extruder; - // Assign an extruder to the base. - ObjectByExtruder &obj = object_by_extruder(by_extruder, has_support ? support_extruder : interface_extruder, &layer_to_print - layers.data(), layers.size()); - obj.support = &support_layer.support_fills; - obj.support_extrusion_role = single_extruder ? erMixed : erSupportMaterial; - if (! single_extruder && has_interface) { - ObjectByExtruder &obj_interface = object_by_extruder(by_extruder, interface_extruder, &layer_to_print - layers.data(), layers.size()); - obj_interface.support = &support_layer.support_fills; - obj_interface.support_extrusion_role = erSupportMaterialInterface; - } - } - } - if (layer_to_print.object_layer != nullptr) { - const Layer &layer = *layer_to_print.object_layer; - // We now define a strategy for building perimeters and fills. The separation - // between regions doesn't matter in terms of printing order, as we follow - // another logic instead: - // - we group all extrusions by extruder so that we minimize toolchanges - // - we start from the last used extruder - // - for each extruder, we group extrusions by island - // - for each island, we extrude perimeters first, unless user set the infill_first - // option - // (Still, we have to keep track of regions because we need to apply their config) - size_t n_slices = layer.lslices.size(); - const LayerSlices &layer_surfaces = layer.lslices_ex; - // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, - // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. - auto point_inside_surface = [&layer, &layer_surfaces](const size_t i, const Point &point) { - const BoundingBox &bbox = layer_surfaces[i].bbox; - return point(0) >= bbox.min(0) && point(0) < bbox.max(0) && - point(1) >= bbox.min(1) && point(1) < bbox.max(1) && - layer.lslices[i].contour.contains(point); - }; - - for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) { - const LayerRegion *layerm = layer.regions()[region_id]; - if (layerm == nullptr) - continue; - // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not - // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. - const PrintRegion ®ion = print.get_print_region(layerm->region().print_region_id()); - - // Now we must process perimeters and infills and create islands of extrusions in by_region std::map. - // It is also necessary to save which extrusions are part of MM wiping and which are not. - // The process is almost the same for perimeters and infills - we will do it in a cycle that repeats twice: - std::vector printing_extruders; - for (const ObjectByExtruder::Island::Region::Type entity_type : { ObjectByExtruder::Island::Region::INFILL, ObjectByExtruder::Island::Region::PERIMETERS }) { - for (const ExtrusionEntity *ee : (entity_type == ObjectByExtruder::Island::Region::INFILL) ? layerm->fills() : layerm->perimeters()) { - // extrusions represents infill or perimeter extrusions of a single island. - assert(dynamic_cast(ee) != nullptr); - const auto *extrusions = static_cast(ee); - if (extrusions->entities.empty()) // This shouldn't happen but first_point() would fail. - continue; - - // This extrusion is part of certain Region, which tells us which extruder should be used for it: - int correct_extruder_id = layer_tools.extruder(*extrusions, region); - - // Let's recover vector of extruder overrides: - const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr; - if (! layer_tools.has_extruder(correct_extruder_id)) { - // this entity is not overridden, but its extruder is not in layer_tools - we'll print it - // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools) - correct_extruder_id = layer_tools.extruders.back(); - } - printing_extruders.clear(); - if (is_anything_overridden) { - entity_overrides = const_cast(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->instances().size()); - if (entity_overrides == nullptr) { - printing_extruders.emplace_back(correct_extruder_id); - } else { - printing_extruders.reserve(entity_overrides->size()); - for (int extruder : *entity_overrides) - printing_extruders.emplace_back(extruder >= 0 ? - // at least one copy is overridden to use this extruder - extruder : - // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation) - static_cast(- extruder - 1)); - Slic3r::sort_remove_duplicates(printing_extruders); - } - } else - printing_extruders.emplace_back(correct_extruder_id); - - // Now we must add this extrusion into the by_extruder map, once for each extruder that will print it: - for (unsigned int extruder : printing_extruders) - { - std::vector &islands = object_islands_by_extruder( - by_extruder, - extruder, - &layer_to_print - layers.data(), - layers.size(), n_slices+1); - for (size_t i = 0; i <= n_slices; ++ i) { - bool last = i == n_slices; - // Traverse lslices back to front: lslices are produced by traversing ClipperLib::PolyTree, emitting parent contour before its children. - // Therefore traversing layer_surfaces back to front will traverse children contours before their parents. - size_t island_idx = last ? n_slices : layer_surfaces.size() - i - 1; - if (// extrusions->first_point does not fit inside any slice - last || - // extrusions->first_point fits inside ith slice - point_inside_surface(island_idx, extrusions->first_point())) { - if (islands[island_idx].by_region.empty()) - islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); - islands[island_idx].by_region[region.print_region_id()].append(entity_type, extrusions, entity_overrides); - break; - } - } - } - } - } - } // for regions - } - } // for objects - // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. for (unsigned int extruder_id : layer_tools.extruders) { @@ -2387,91 +2205,30 @@ LayerResult GCode::process_layer( } - auto objects_by_extruder_it = by_extruder.find(extruder_id); - if (objects_by_extruder_it == by_extruder.end()) - continue; - - std::vector instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx); + std::vector instances_to_print = sort_print_object_instances(layers, ordering, single_object_instance_idx); // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): - std::vector by_region_per_copy_cache; - for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) { - if (is_anything_overridden && print_wipe_extrusions == 0) + //FIXME const_cast + bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); + if (is_anything_overridden) { + // Extrude wipes. + size_t gcode_size_old = gcode.size(); + for (const InstanceToPrint &instance : instances_to_print) + this->process_layer_single_object( + gcode, extruder_id, instance, + layers[instance.object_layer_to_print_id], layer_tools, + is_anything_overridden, true /* print_wipe_extrusions */); + if (gcode_size_old < gcode.size()) gcode+="; PURGING FINISHED\n"; - - for (InstanceToPrint &instance_to_print : instances_to_print) { - const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id]; - // To control print speed of the 1st object layer printed over raft interface. - bool object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 && - instance_to_print.print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id(); - m_config.apply(instance_to_print.print_object.config(), true); - m_layer = layer_to_print.layer(); - m_object_layer_over_raft = object_layer_over_raft; - if (m_config.avoid_crossing_perimeters) - m_avoid_crossing_perimeters.init_layer(*m_layer); - if (this->config().gcode_label_objects) - gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; - // When starting a new object, use the external motion planner for the first travel move. - const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; - std::pair this_object_copy(&instance_to_print.print_object, offset); - if (m_last_obj_copy != this_object_copy) - m_avoid_crossing_perimeters.use_external_mp_once(); - m_last_obj_copy = this_object_copy; - this->set_origin(unscale(offset)); - if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) { - m_layer = layer_to_print.support_layer; - m_object_layer_over_raft = false; - gcode += this->extrude_support( - // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. - instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role)); - m_layer = layer_to_print.layer(); - m_object_layer_over_raft = object_layer_over_raft; - } - //FIXME order islands? - // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) - for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) { - const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region; - //FIXME the following code prints regions in the order they are defined, the path is not optimized in any way. - if (print.config().infill_first) { - gcode += this->extrude_infill(print, by_region_specific, false); - gcode += this->extrude_perimeters(print, by_region_specific); - } else { - gcode += this->extrude_perimeters(print, by_region_specific); - gcode += this->extrude_infill(print,by_region_specific, false); - } - // ironing - gcode += this->extrude_infill(print,by_region_specific, true); - } - if (this->config().gcode_label_objects) - gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; - } } + // Extrude normal extrusions. + for (const InstanceToPrint &instance : instances_to_print) + this->process_layer_single_object( + gcode, extruder_id, instance, + layers[instance.object_layer_to_print_id], layer_tools, + is_anything_overridden, false /* print_wipe_extrusions */); } -#if 0 - // 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 - // bottom non-spiral layers otherwise it will mess with positions) - // we apply spiral vase at this stage because it requires a full layer. - // Just a reminder: A spiral vase mode is allowed for a single object per layer, single material print only. - if (m_spiral_vase) - gcode = m_spiral_vase->process_layer(std::move(gcode)); - - // Apply cooling logic; this may alter speeds. - if (m_cooling_buffer) - gcode = m_cooling_buffer->process_layer(std::move(gcode), layer.id(), - // Flush the cooling buffer at each object layer or possibly at the last layer, even if it contains just supports (This should not happen). - object_layer || last_layer); - - // Apply pressure equalization if enabled; - // printf("G-code before filter:\n%s\n", gcode.c_str()); - if (m_pressure_equalizer) - gcode = m_pressure_equalizer->process(gcode.c_str(), false); - // printf("G-code after filter:\n%s\n", out.c_str()); - - file.write(gcode); -#endif - BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); @@ -2480,6 +2237,214 @@ LayerResult GCode::process_layer( return result; } +static const auto comment_perimeter = "perimeter"sv; +// Comparing string_view pointer & length for speed. +static inline bool comment_is_perimeter(const std::string_view comment) { + return comment.data() == comment_perimeter.data() && comment.size() == comment_perimeter.size(); +} + +void GCode::process_layer_single_object( + // output + std::string &gcode, + // Index of the extruder currently active. + const unsigned int extruder_id, + // What object and instance is going to be printed. + const InstanceToPrint &print_instance, + // and the object & support layer of the above. + const ObjectLayerToPrint &layer_to_print, + // Container for extruder overrides (when wiping into object or infill). + const LayerTools &layer_tools, + // Is any extrusion possibly marked as wiping extrusion? + const bool is_anything_overridden, + // Round 1 (wiping into object or infill) or round 2 (normal extrusions). + const bool print_wipe_extrusions) +{ + //FIXME what the heck ID is this? Layer ID or Object ID? More likely an Object ID. + uint32_t layer_id = 0; + bool first = true; + // Delay layer initialization as many layers may not print with all extruders. + auto init_layer_delayed = [this, &print_instance, &layer_to_print, layer_id, &first, &gcode]() { + if (first) { + first = false; + const PrintObject &print_object = print_instance.print_object; + const Print &print = *print_object.print(); + m_config.apply(print_object.config(), true); + m_layer = layer_to_print.layer(); + if (print.config().avoid_crossing_perimeters) + m_avoid_crossing_perimeters.init_layer(*m_layer); + // When starting a new object, use the external motion planner for the first travel move. + const Point &offset = print_object.instances()[print_instance.instance_id].shift; + std::pair this_object_copy(&print_object, offset); + if (m_last_obj_copy != this_object_copy) + m_avoid_crossing_perimeters.use_external_mp_once(); + m_last_obj_copy = this_object_copy; + this->set_origin(unscale(offset)); + if (this->config().gcode_label_objects) + gcode += std::string("; printing object ") + print_object.model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(print_instance.instance_id) + "\n"; + } + }; + + const PrintObject &print_object = print_instance.print_object; + const Print &print = *print_object.print(); + + if (! print_wipe_extrusions && layer_to_print.support_layer != nullptr) + if (const SupportLayer &support_layer = *layer_to_print.support_layer; ! support_layer.support_fills.entities.empty()) { + ExtrusionRole role = support_layer.support_fills.role(); + bool has_support = role == erMixed || role == erSupportMaterial; + bool has_interface = role == erMixed || role == erSupportMaterialInterface; + // Extruder ID of the support base. -1 if "don't care". + unsigned int support_extruder = print_object.config().support_material_extruder.value - 1; + // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes? + bool support_dontcare = print_object.config().support_material_extruder.value == 0; + // Extruder ID of the support interface. -1 if "don't care". + unsigned int interface_extruder = print_object.config().support_material_interface_extruder.value - 1; + // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes? + bool interface_dontcare = print_object.config().support_material_interface_extruder.value == 0; + if (support_dontcare || interface_dontcare) { + // Some support will be printed with "don't care" material, preferably non-soluble. + // Is the current extruder assigned a soluble filament? + unsigned int dontcare_extruder = layer_tools.extruders.front(); + if (print.config().filament_soluble.get_at(dontcare_extruder)) { + // The last extruder printed on the previous layer extrudes soluble filament. + // Try to find a non-soluble extruder on the same layer. + for (unsigned int extruder_id : layer_tools.extruders) + if (! print.config().filament_soluble.get_at(extruder_id)) { + dontcare_extruder = extruder_id; + break; + } + } + if (support_dontcare) + support_extruder = dontcare_extruder; + if (interface_dontcare) + interface_extruder = dontcare_extruder; + } + bool extrude_support = has_support && support_extruder == extruder_id; + bool extrude_interface = interface_extruder && interface_extruder == extruder_id; + if (extrude_support || extrude_interface) { + init_layer_delayed(); + m_layer = layer_to_print.support_layer; + m_object_layer_over_raft = false; + gcode += this->extrude_support( + // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. + support_layer.support_fills.chained_path_from(m_last_pos, has_support ? (has_interface ? erMixed : erSupportMaterial) : erSupportMaterialInterface)); + } + } + + m_layer = layer_to_print.layer(); + // To control print speed of the 1st object layer printed over raft interface. + m_object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 && + print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id(); + + // Check whether this ExtrusionEntityCollection should be printed now with extruder_id, given print_wipe_extrusions + // (wipe extrusions are printed before regular extrusions). + auto shall_print_this_extrusion_collection = [extruder_id, instance_id = print_instance.instance_id, &layer_tools, is_anything_overridden, print_wipe_extrusions](const ExtrusionEntityCollection *eec, const PrintRegion ®ion) -> bool { + assert(eec != nullptr); + if (eec->entities.empty()) + // This shouldn't happen. FIXME why? but first_point() would fail. + return false; + // This extrusion is part of certain Region, which tells us which extruder should be used for it: + int correct_extruder_id = layer_tools.extruder(*eec, region); + if (! layer_tools.has_extruder(correct_extruder_id)) { + // this entity is not overridden, but its extruder is not in layer_tools - we'll print it + // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools) + correct_extruder_id = layer_tools.extruders.back(); + } + //FIXME const_cast! + int extruder_override_id = is_anything_overridden ? const_cast(layer_tools).wiping_extrusions().get_extruder_override(eec, instance_id) : -1; + return print_wipe_extrusions ? + extruder_override_id == int(extruder_id) : + extruder_override_id < 0 && extruder_id == correct_extruder_id; + }; + + ExtrusionEntitiesPtr temp_fill_extrusions; + if (const Layer *layer = layer_to_print.object_layer; layer) + for (const LayerSlice &lslice : layer->lslices_ex) { + auto extrude_infill_range = [&](const LayerRegion &layerm, const ExtrusionEntityCollection &fills, const ExtrusionRange fill_range, bool ironing) { + // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not + // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. + const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); + temp_fill_extrusions.clear(); + for (uint32_t fill_id : fill_range) + if (auto *eec = static_cast(layerm.fills().entities[fill_id]); + (eec->role() == erIroning) == ironing && shall_print_this_extrusion_collection(eec, region)) { + if (eec->can_reverse()) + // Flatten the infill collection for better path planning. + for (auto *ee : eec->entities) + temp_fill_extrusions.emplace_back(ee); + else + temp_fill_extrusions.emplace_back(eec); + } + if (! temp_fill_extrusions.empty()) { + init_layer_delayed(); + m_config.apply(region.config()); + //FIXME The source extrusions may be reversed, thus modifying the extrusions! Is it a problem? How about the initial G-code preview? + // Will parallel access of initial G-code preview to these extrusions while reordering them at backend cause issues? + chain_and_reorder_extrusion_entities(temp_fill_extrusions, &m_last_pos); + const auto extrusion_name = ironing ? "ironing"sv : "infill"sv; + for (const ExtrusionEntity *fill : temp_fill_extrusions) + if (auto *eec = dynamic_cast(fill); eec) { + for (const ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) + gcode += this->extrude_entity(*ee, extrusion_name); + } else + gcode += this->extrude_entity(*fill, extrusion_name); + } + }; + + //FIXME order islands? + // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) + for (const LayerIsland &island : lslice.islands) { + auto process_perimeters = [&]() { + const LayerRegion &layerm = *layer->get_region(island.perimeters.region()); + // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not + // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. + const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); + bool first = true; + for (uint32_t perimeter_id : island.perimeters) + if (const auto *eec = static_cast(layerm.perimeters().entities[perimeter_id]); + shall_print_this_extrusion_collection(eec, region)) { + assert(! eec->can_reverse()); + if (first) { + first = false; + init_layer_delayed(); + m_config.apply(region.config()); + } + for (const ExtrusionEntity *ee : *eec) + gcode += this->extrude_entity(*ee, comment_perimeter, -1.); + } + }; + auto process_infill = [&]() { + for (LayerExtrusionRange fill_range : island.fills) { + const LayerRegion &layerm = *layer->get_region(fill_range.region()); + extrude_infill_range(layerm, layerm.fills(), fill_range, false /* normal extrusions, not ironing */); + } + { + const LayerRegion &layerm = *layer->get_region(island.perimeters.region()); + extrude_infill_range(layerm, layerm.thin_fills(), island.thin_fills, false); + } + }; + if (print.config().infill_first) { + process_infill(); + process_perimeters(); + } else { + process_perimeters(); + process_infill(); + } + } + // ironing + //FIXME move ironing into the loop above over LayerIslands? + // First Ironing changes extrusion rate quickly, second single ironing may be done over multiple perimeter regions. + // Ironing in a second phase is safer, but it may be less efficient. + for (const LayerIsland &island : lslice.islands) { + for (LayerExtrusionRange fill_range : island.fills) { + const LayerRegion &layerm = *layer->get_region(fill_range.region()); + extrude_infill_range(layerm, layerm.fills(), fill_range, true /* ironing, not normal extrusions */); + } + } + } + if (! first && this->config().gcode_label_objects) + gcode += std::string("; stop printing object ") + print_object.model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(print_instance.instance_id) + "\n"; +} + void GCode::apply_print_config(const PrintConfig &print_config) { m_writer.apply_print_config(print_config); @@ -2569,12 +2534,6 @@ std::string GCode::change_layer(coordf_t print_z) return gcode; } -static const auto comment_perimeter = "perimeter"sv; -// Comparing string_view pointer & length for speed. -static inline bool comment_is_perimeter(const std::string_view comment) { - return comment.data() == comment_perimeter.data() && comment.size() == comment_perimeter.size(); -} - std::string GCode::extrude_loop(ExtrusionLoop loop, const std::string_view description, double speed) { // get a copy; don't modify the orientation of the original loop object otherwise @@ -2741,49 +2700,6 @@ std::string GCode::extrude_path(ExtrusionPath path, std::string_view description return gcode; } -// Extrude perimeters: Decide where to put seams (hide or align seams). -std::string GCode::extrude_perimeters(const Print &print, const std::vector &by_region) -{ - std::string gcode; - for (const ObjectByExtruder::Island::Region ®ion : by_region) - if (! region.perimeters.empty()) { - m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); - - for (const ExtrusionEntity* ee : region.perimeters) - gcode += this->extrude_entity(*ee, comment_perimeter, -1.); - } - return gcode; -} - -// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance. -std::string GCode::extrude_infill(const Print &print, const std::vector &by_region, bool ironing) -{ - std::string gcode; - ExtrusionEntitiesPtr extrusions; - const auto extrusion_name = ironing ? "ironing"sv : "infill"sv; - for (const ObjectByExtruder::Island::Region ®ion : by_region) - if (! region.infills.empty()) { - extrusions.clear(); - extrusions.reserve(region.infills.size()); - for (ExtrusionEntity *ee : region.infills) - if ((ee->role() == erIroning) == ironing) - extrusions.emplace_back(ee); - if (! extrusions.empty()) { - m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); - chain_and_reorder_extrusion_entities(extrusions, &m_last_pos); - for (const ExtrusionEntity *fill : extrusions) { - auto *eec = dynamic_cast(fill); - if (eec) { - for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) - gcode += this->extrude_entity(*ee, extrusion_name); - } else - gcode += this->extrude_entity(*fill, extrusion_name); - } - } - } - return gcode; -} - std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fills) { static constexpr const auto support_label = "support material"sv; @@ -3302,104 +3218,4 @@ Point GCode::gcode_to_point(const Vec2d &point) const scale_(point(1) - m_origin(1) + extruder_offset(1))); } -// Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed -// during infill/perimeter wiping, or normally (depends on wiping_entities parameter) -// Fills in by_region_per_copy_cache and returns its reference. -const std::vector& GCode::ObjectByExtruder::Island::by_region_per_copy(std::vector &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities) const -{ - bool has_overrides = false; - for (const auto& reg : by_region) - if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) { - has_overrides = true; - break; - } - - // Data is cleared, but the memory is not. - by_region_per_copy_cache.clear(); - - if (! has_overrides) - // Simple case. No need to copy the regions. - return wiping_entities ? by_region_per_copy_cache : this->by_region; - - // Complex case. Some of the extrusions of some object instances are to be printed first - those are the wiping extrusions. - // Some of the extrusions of some object instances are printed later - those are the clean print extrusions. - // Filter out the extrusions based on the infill_overrides / perimeter_overrides: - - for (const auto& reg : by_region) { - by_region_per_copy_cache.emplace_back(); // creates a region in the newly created Island - - // Now we are going to iterate through perimeters and infills and pick ones that are supposed to be printed - // References are used so that we don't have to repeat the same code - for (int iter = 0; iter < 2; ++iter) { - const ExtrusionEntitiesPtr& entities = (iter ? reg.infills : reg.perimeters); - ExtrusionEntitiesPtr& target_eec = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters); - const std::vector& overrides = (iter ? reg.infills_overrides : reg.perimeters_overrides); - - // Now the most important thing - which extrusion should we print. - // See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack. - if (wiping_entities) { - // Apply overrides for this region. - for (unsigned int i = 0; i < overrides.size(); ++ i) { - const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; - // This copy (aka object instance) should be printed with this extruder, which overrides the default one. - if (this_override != nullptr && (*this_override)[copy] == int(extruder)) - target_eec.emplace_back(entities[i]); - } - } else { - // Apply normal extrusions (non-overrides) for this region. - unsigned int i = 0; - for (; i < overrides.size(); ++ i) { - const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; - // This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one. - if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1) - target_eec.emplace_back(entities[i]); - } - for (; i < entities.size(); ++ i) - target_eec.emplace_back(entities[i]); - } - } - } - return by_region_per_copy_cache; -} - -// This function takes the eec and appends its entities to either perimeters or infills of this Region (depending on the first parameter) -// It also saves pointer to ExtruderPerCopy struct (for each entity), that holds information about which extruders should be used for which copy. -void GCode::ObjectByExtruder::Island::Region::append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copies_extruder) -{ - // We are going to manipulate either perimeters or infills, exactly in the same way. Let's create pointers to the proper structure to not repeat ourselves: - ExtrusionEntitiesPtr* perimeters_or_infills; - std::vector* perimeters_or_infills_overrides; - - switch (type) { - case PERIMETERS: - perimeters_or_infills = &perimeters; - perimeters_or_infills_overrides = &perimeters_overrides; - break; - case INFILL: - perimeters_or_infills = &infills; - perimeters_or_infills_overrides = &infills_overrides; - break; - default: - throw Slic3r::InvalidArgument("Unknown parameter!"); - } - - // First we append the entities, there are eec->entities.size() of them: - size_t old_size = perimeters_or_infills->size(); - size_t new_size = old_size + (eec->can_reverse() ? eec->entities.size() : 1); - perimeters_or_infills->reserve(new_size); - if (eec->can_reverse()) { - for (auto* ee : eec->entities) - perimeters_or_infills->emplace_back(ee); - } else - perimeters_or_infills->emplace_back(const_cast(eec)); - - if (copies_extruder != nullptr) { - // Don't reallocate overrides if not needed. - // Missing overrides are implicitely considered non-overridden. - perimeters_or_infills_overrides->reserve(new_size); - perimeters_or_infills_overrides->resize(old_size, nullptr); - perimeters_or_infills_overrides->resize(new_size, copies_extruder); - } -} - } // namespace Slic3r diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 4592402e3..28290e2e5 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -188,15 +188,16 @@ public: // Object and support extrusions of the same PrintObject at the same print_z. // public, so that it could be accessed by free helper functions from GCode.cpp - struct LayerToPrint + struct ObjectLayerToPrint { - LayerToPrint() : object_layer(nullptr), support_layer(nullptr) {} + ObjectLayerToPrint() : object_layer(nullptr), support_layer(nullptr) {} const Layer* object_layer; const SupportLayer* support_layer; const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; } const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; } coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; } }; + using ObjectsLayerToPrint = std::vector; private: class GCodeOutputStream { @@ -239,13 +240,13 @@ private: }; void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb); - static std::vector collect_layers_to_print(const PrintObject &object); - static std::vector>> collect_layers_to_print(const Print &print); + static ObjectsLayerToPrint collect_layers_to_print(const PrintObject &object); + static std::vector> collect_layers_to_print(const Print &print); LayerResult process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. - const std::vector &layers, + const ObjectsLayerToPrint &layers, const LayerTools &layer_tools, const bool last_layer, // Pairs of PrintObject index and its instance index. @@ -257,18 +258,18 @@ private: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. void process_layers( - const Print &print, - const ToolOrdering &tool_ordering, - const std::vector &print_object_instances_ordering, - const std::vector>> &layers_to_print, - GCodeOutputStream &output_stream); + const Print &print, + const ToolOrdering &tool_ordering, + const std::vector &print_object_instances_ordering, + const std::vector> &layers_to_print, + GCodeOutputStream &output_stream); // Process all layers of a single object instance (sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. void process_layers( const Print &print, const ToolOrdering &tool_ordering, - std::vector layers_to_print, + ObjectsLayerToPrint layers_to_print, const size_t single_object_idx, GCodeOutputStream &output_stream); @@ -282,71 +283,43 @@ private: std::string extrude_multi_path(ExtrusionMultiPath multipath, const std::string_view description, double speed = -1.); std::string extrude_path(ExtrusionPath path, const std::string_view description, double speed = -1.); - // Extruding multiple objects with soluble / non-soluble / combined supports - // on a multi-material printer, trying to minimize tool switches. - // Following structures sort extrusions by the extruder ID, by an order of objects and object islands. - struct ObjectByExtruder + struct InstanceToPrint { - ObjectByExtruder() : support(nullptr), support_extrusion_role(erNone) {} - const ExtrusionEntityCollection *support; - // erSupportMaterial / erSupportMaterialInterface or erMixed. - ExtrusionRole support_extrusion_role; + InstanceToPrint(size_t object_layer_to_print_id, const PrintObject &print_object, size_t instance_id) : + object_layer_to_print_id(object_layer_to_print_id), print_object(print_object), instance_id(instance_id) {} - struct Island - { - struct Region { - // Non-owned references to LayerRegion::perimeters::entities - // std::vector would be better here, but there is no way in C++ to convert from std::vector std::vector without copying. - ExtrusionEntitiesPtr perimeters; - // Non-owned references to LayerRegion::fills::entities - ExtrusionEntitiesPtr infills; - - std::vector infills_overrides; - std::vector perimeters_overrides; - - enum Type { - PERIMETERS, - INFILL, - }; - - // Appends perimeter/infill entities and writes don't indices of those that are not to be extruder as part of perimeter/infill wiping - void append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copy_extruders); - }; - - - std::vector by_region; // all extrusions for this island, grouped by regions - - // Fills in by_region_per_copy_cache and returns its reference. - const std::vector& by_region_per_copy(std::vector &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities = false) const; - }; - std::vector islands; + // Index into std::vector, which contains Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. + const size_t object_layer_to_print_id; + const PrintObject &print_object; + // Instance idx of the copy of a print object. + const size_t instance_id; }; - struct InstanceToPrint - { - InstanceToPrint(ObjectByExtruder &object_by_extruder, size_t layer_id, const PrintObject &print_object, size_t instance_id) : - object_by_extruder(object_by_extruder), layer_id(layer_id), print_object(print_object), instance_id(instance_id) {} + std::vector sort_print_object_instances( + // Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. + const std::vector &layers, + // Ordering must be defined for normal (non-sequential print). + const std::vector *ordering, + // For sequential print, the instance of the object to be printing has to be defined. + const size_t single_object_instance_idx); - // Repository - ObjectByExtruder &object_by_extruder; - // Index into std::vector, which contains Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. - const size_t layer_id; - const PrintObject &print_object; - // Instance idx of the copy of a print object. - const size_t instance_id; - }; + // This function will be called for each printing extruder, possibly twice: First for wiping extrusions, second for normal extrusions. + void process_layer_single_object( + // output + std::string &gcode, + // Index of the extruder currently active. + const unsigned int extruder_id, + // What object and instance is going to be printed. + const InstanceToPrint &print_instance, + // and the object & support layer of the above. + const ObjectLayerToPrint &layer_to_print, + // Container for extruder overrides (when wiping into object or infill). + const LayerTools &layer_tools, + // Is any extrusion possibly marked as wiping extrusion? + const bool is_anything_overridden, + // Round 1 (wiping into object or infill) or round 2 (normal extrusions). + const bool print_wipe_extrusions); - std::vector sort_print_object_instances( - std::vector &objects_by_extruder, - // Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. - const std::vector &layers, - // Ordering must be defined for normal (non-sequential print). - const std::vector *ordering, - // For sequential print, the instance of the object to be printing has to be defined. - const size_t single_object_instance_idx); - - std::string extrude_perimeters(const Print &print, const std::vector &by_region); - std::string extrude_infill(const Print &print, const std::vector &by_region, bool ironing); std::string extrude_support(const ExtrusionEntityCollection &support_fills); std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); @@ -438,18 +411,6 @@ private: // To control print speed of 1st object layer over raft interface. bool object_layer_over_raft() const { return m_object_layer_over_raft; } - friend ObjectByExtruder& object_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects); - friend std::vector& object_islands_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, - size_t num_objects, - size_t num_islands); - friend class Wipe; friend class WipeTowerIntegration; friend class PressureEqualizer; diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 6c5d0667a..7f14203a9 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -103,7 +103,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude } double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height); - // Collect extruders reuqired to print the layers. + // Collect extruders required to print the layers. this->collect_extruders(object, std::vector>()); // Reorder the extruders to minimize tool switches. @@ -598,14 +598,15 @@ const LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) const // This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual) void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies) { - something_overridden = true; + m_something_overridden = true; - auto entity_map_it = (entity_map.emplace(entity, ExtruderPerCopy())).first; // (add and) return iterator + auto entity_map_it = (m_entity_map.emplace(entity, ExtruderPerCopy())).first; // (add and) return iterator ExtruderPerCopy& copies_vector = entity_map_it->second; copies_vector.resize(num_of_copies, -1); + assert(copies_vector[copy_id] == -1); if (copies_vector[copy_id] != -1) - std::cout << "ERROR: Entity extruder overriden multiple times!!!\n"; // A debugging message - this must never happen. + BOOST_LOG_TRIVIAL(error) << "ERROR: Entity extruder overriden multiple times!!!"; copies_vector[copy_id] = extruder; } @@ -649,13 +650,19 @@ bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, con // Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange // and returns volume that is left to be wiped on the wipe tower. +// Switching from old_extruder to new_extruder, trying to wipe volume_to_wipe into not yet extruded extrusions, that may change material (overridable). float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe) { const LayerTools& lt = *m_layer_tools; const float min_infill_volume = 0.f; // ignore infill with smaller volume than this - if (! this->something_overridable || volume_to_wipe <= 0. || print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder)) - return std::max(0.f, volume_to_wipe); // Soluble filament cannot be wiped in a random infill, neither the filament after it + if (! m_something_overridable || volume_to_wipe <= 0. || + // Don't wipe a soluble filament into another object. + print.config().filament_soluble.get_at(old_extruder) || + // Don't prime a soluble filament into another object. + print.config().filament_soluble.get_at(new_extruder)) + // Soluble filament cannot be wiped in a random infill, neither the filament after it + return std::max(0.f, volume_to_wipe); // we will sort objects so that dedicated for wiping are at the beginning: ConstPrintObjectPtrs object_list(print.objects().begin(), print.objects().end()); @@ -742,7 +749,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int // them again and make sure we override it. void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) { - if (! this->something_overridable) + if (! m_something_overridable) return; const LayerTools& lt = *m_layer_tools; @@ -775,8 +782,8 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) // Either way, we will now force-override it with something suitable: if (print.config().infill_first || object->config().wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely - || lt.is_extruder_order(lt.perimeter_extruder(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints - || ! lt.has_extruder(lt.infill_extruder(region)))) // we have to force override - this could violate infill_first (FIXME) + || lt.is_extruder_order(lt.perimeter_extruder(region), last_nonsoluble_extruder) // !infill_first, but perimeter is already printed when last extruder prints + || ! lt.has_extruder(lt.infill_extruder(region))) // we have to force override - this could violate infill_first (FIXME) set_extruder_override(fill, copy, (print.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies); else { // In this case we can (and should) leave it to be printed normally. @@ -795,24 +802,4 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) } } -// Following function is called from GCode::process_layer and returns pointer to vector with information about which extruders should be used for given copy of this entity. -// If this extrusion does not have any override, nullptr is returned. -// Otherwise it modifies the vector in place and changes all -1 to correct_extruder_id (at the time the overrides were created, correct extruders were not known, -// so -1 was used as "print as usual"). -// The resulting vector therefore keeps track of which extrusions are the ones that were overridden and which were not. If the extruder used is overridden, -// its number is saved as is (zero-based index). Regular extrusions are saved as -number-1 (unfortunately there is no negative zero). -const WipingExtrusions::ExtruderPerCopy* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies) -{ - ExtruderPerCopy *overrides = nullptr; - auto entity_map_it = entity_map.find(entity); - if (entity_map_it != entity_map.end()) { - overrides = &entity_map_it->second; - overrides->resize(num_of_copies, -1); - // Each -1 now means "print as usual" - we will replace it with actual extruder id (shifted it so we don't lose that information): - std::replace(overrides->begin(), overrides->end(), -1, -correct_extruder_id-1); - } - return overrides; -} - - } // namespace Slic3r diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 036730325..d043c48ad 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -24,14 +24,19 @@ class WipingExtrusions { public: bool is_anything_overridden() const { // if there are no overrides, all the agenda can be skipped - this function can tell us if that's the case - return something_overridden; + return m_something_overridden; } // When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place. typedef boost::container::small_vector ExtruderPerCopy; - // This is called from GCode::process_layer - see implementation for further comments: - const ExtruderPerCopy* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies); + // This is called from GCode::process_layer_single_object() + // Returns positive number if the extruder is overridden. + // Returns -1 if not. + int get_extruder_override(const ExtrusionEntity* entity, uint32_t instance_id) const { + auto entity_map_it = m_entity_map.find(entity); + return entity_map_it == m_entity_map.end() ? -1 : entity_map_it->second[instance_id]; + } // This function goes through all infill entities, decides which ones will be used for wiping and // marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower: @@ -42,7 +47,7 @@ public: bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const; bool is_overriddable_and_mark(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) { bool out = this->is_overriddable(ee, print_config, object, region); - this->something_overridable |= out; + m_something_overridable |= out; return out; } @@ -57,13 +62,13 @@ private: // Returns true in case that entity is not printed with its usual extruder for a given copy: bool is_entity_overridden(const ExtrusionEntity* entity, size_t copy_id) const { - auto it = entity_map.find(entity); - return it == entity_map.end() ? false : it->second[copy_id] != -1; + auto it = m_entity_map.find(entity); + return it == m_entity_map.end() ? false : it->second[copy_id] != -1; } - std::map entity_map; // to keep track of who prints what - bool something_overridable = false; - bool something_overridden = false; + std::map m_entity_map; // to keep track of who prints what + bool m_something_overridable = false; + bool m_something_overridden = false; const LayerTools* m_layer_tools = nullptr; // so we know which LayerTools object this belongs to }; diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 1561a2869..95c9cf50e 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -507,16 +507,43 @@ void Layer::sort_perimeters_into_islands( for (const ExPolygon &expolygon : fill_expolygons) fill_expolygons_bboxes.emplace_back(get_extents(expolygon)); + + // Take one sample point for each source slice, to be used to sort source slices into layer slices. + // source slice index + its sample. + std::vector> perimeter_slices_queue; + perimeter_slices_queue.reserve(slices.size()); + for (uint32_t islice = 0; islice < uint32_t(slices.size()); ++ islice) { + const std::pair &extrusions = perimeter_and_gapfill_ranges[islice]; + Point sample; + bool sample_set = false; + if (! extrusions.first.empty()) { + sample = this_layer_region.perimeters().entities[*extrusions.first.begin()]->first_point(); + sample_set = true; + } else if (! extrusions.second.empty()) { + sample = this_layer_region.thin_fills().entities[*extrusions.second.begin()]->first_point(); + sample_set = true; + } else { + for (uint32_t iexpoly : fill_expolygons_ranges[islice]) + if (const ExPolygon &expoly = fill_expolygons[iexpoly]; ! expoly.empty()) { + sample = expoly.contour.points.front(); + sample_set = true; + break; + } + } + // There may be a valid empty island. + // assert(sample_set); + if (sample_set) + perimeter_slices_queue.emplace_back(islice, sample); + } + // Map of source fill_expolygon into region and fill_expolygon of that region. // -1: not set std::vector> map_expolygon_to_region_and_fill; - + const bool has_multiple_regions = layer_region_ids.size() > 1; + assert(has_multiple_regions || layer_region_ids.size() == 1); // assign fill_surfaces to each layer if (! fill_expolygons.empty()) { - if (layer_region_ids.size() == 1) { - this_layer_region.m_fill_expolygons = std::move(fill_expolygons); - this_layer_region.m_fill_expolygons_bboxes = std::move(fill_expolygons_bboxes); - } else { + if (has_multiple_regions) { // Sort the bounding boxes lexicographically. std::vector fill_expolygons_bboxes_sorted(fill_expolygons_bboxes.size()); std::iota(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), 0); @@ -550,106 +577,133 @@ void Layer::sort_perimeters_into_islands( } } } + } else { + this_layer_region.m_fill_expolygons = std::move(fill_expolygons); + this_layer_region.m_fill_expolygons_bboxes = std::move(fill_expolygons_bboxes); } } - // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, - // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. - auto point_inside_surface = [this](const size_t lslice_idx, const Point &point) { - const BoundingBox &bbox = this->lslices_ex[lslice_idx].bbox; - return point.x() >= bbox.min.x() && point.x() < bbox.max.x() && - point.y() >= bbox.min.y() && point.y() < bbox.max.y() && - this->lslices[lslice_idx].contour.contains(point); - }; - - // Take one sample point for each source slice, to be used to sort source slices into layer slices. - // source slice index + its sample. - std::vector> perimeter_slices_queue; - perimeter_slices_queue.reserve(slices.size()); - for (uint32_t islice = 0; islice < uint32_t(slices.size()); ++ islice) { - const std::pair &extrusions = perimeter_and_gapfill_ranges[islice]; - Point sample; - bool sample_set = false; - if (! extrusions.first.empty()) { - sample = this_layer_region.perimeters().entities[*extrusions.first.begin()]->first_point(); - sample_set = true; - } else if (! extrusions.second.empty()) { - sample = this_layer_region.thin_fills().entities[*extrusions.second.begin()]->first_point(); - sample_set = true; - } else if (const ExPolygonRange &fill_expolygon_range = fill_expolygons_ranges[islice]; ! fill_expolygons.empty()) { - for (uint32_t iexpoly : fill_expolygon_range) - if (const ExPolygon &expoly = fill_expolygons[iexpoly]; ! expoly.empty()) { - sample = expoly.contour.points.front(); - sample_set = true; - break; - } - } - if (sample_set) - perimeter_slices_queue.emplace_back(islice, sample); - } - // Sort perimeter extrusions, thin fill extrusions and fill expolygons into islands. std::vector region_fill_sorted_last; - for (int lslice_idx = int(this->lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx) { + auto insert_into_island = [ + // Region where the perimeters, gap fills and fill expolygons are stored. + region_id, + // Whether there are infills with different regions generated for this LayerSlice. + has_multiple_regions, + // Perimeters and gap fills to be sorted into islands. + &perimeter_and_gapfill_ranges, + // Infill regions to be sorted into islands. + &fill_expolygons, &fill_expolygons_bboxes, &fill_expolygons_ranges, + // Mapping of fill_expolygon to region and its infill. + &map_expolygon_to_region_and_fill, + // Output + ®ions = m_regions, &lslices_ex = this->lslices_ex, + // fill_expolygons and fill_expolygons_bboxes need to be sorted into contiguous sequence by island, + // thus region_fill_sorted_last contains last fill_expolygon processed (meaning sorted). + ®ion_fill_sorted_last] + (int lslice_idx, int source_slice_idx) { + lslices_ex[lslice_idx].islands.push_back({}); + LayerIsland &island = lslices_ex[lslice_idx].islands.back(); + island.perimeters = LayerExtrusionRange(region_id, perimeter_and_gapfill_ranges[source_slice_idx].first); + island.thin_fills = perimeter_and_gapfill_ranges[source_slice_idx].second; + if (ExPolygonRange fill_range = fill_expolygons_ranges[source_slice_idx]; ! fill_range.empty()) { + if (has_multiple_regions) { + // Check whether the fill expolygons of this island were split into multiple regions. + island.fill_region_id = LayerIsland::fill_region_composite_id; + for (uint32_t fill_idx : fill_range) { + const std::pair &kvp = map_expolygon_to_region_and_fill[fill_idx]; + if (kvp.first == -1 || (island.fill_region_id != -1 && island.fill_region_id != kvp.second)) { + island.fill_region_id = LayerIsland::fill_region_composite_id; + break; + } else + island.fill_region_id = kvp.second; + } + if (island.fill_expolygons_composite()) { + // They were split, thus store the unsplit "composite" expolygons into the region of perimeters. + LayerRegion &this_layer_region = *regions[region_id]; + auto begin = uint32_t(this_layer_region.fill_expolygons_composite().size()); + this_layer_region.m_fill_expolygons_composite.reserve(this_layer_region.fill_expolygons_composite().size() + fill_range.size()); + std::move(fill_expolygons.begin() + *fill_range.begin(), fill_expolygons.begin() + *fill_range.end(), std::back_inserter(this_layer_region.m_fill_expolygons_composite)); + this_layer_region.m_fill_expolygons_composite_bboxes.insert(this_layer_region.m_fill_expolygons_composite_bboxes.end(), + fill_expolygons_bboxes.begin() + *fill_range.begin(), fill_expolygons_bboxes.begin() + *fill_range.end()); + island.fill_expolygons = ExPolygonRange(begin, uint32_t(this_layer_region.fill_expolygons_composite().size())); + } else { + if (region_fill_sorted_last.empty()) + region_fill_sorted_last.assign(regions.size(), 0); + uint32_t &last = region_fill_sorted_last[island.fill_region_id]; + // They were not split and they belong to the same region. + // Sort the region m_fill_expolygons to a continuous span. + uint32_t begin = last; + LayerRegion &layerm = *regions[island.fill_region_id]; + for (uint32_t fill_id : fill_range) { + uint32_t region_fill_id = map_expolygon_to_region_and_fill[fill_id].second; + assert(region_fill_id >= last); + if (region_fill_id > last) { + std::swap(layerm.m_fill_expolygons[region_fill_id], layerm.m_fill_expolygons[last]); + std::swap(layerm.m_fill_expolygons_bboxes[region_fill_id], layerm.m_fill_expolygons_bboxes[last]); + } + ++ last; + } + island.fill_expolygons = ExPolygonRange(begin, last); + } + } else { + // Layer island is made of one fill region only. + island.fill_expolygons = fill_range; + island.fill_region_id = region_id; + } + } + }; + + // First sort into islands using exact fit. + // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, + // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. + auto point_inside_surface = [&lslices = this->lslices, &lslices_ex = this->lslices_ex](size_t lslice_idx, const Point &point) { + const BoundingBox &bbox = lslices_ex[lslice_idx].bbox; + return point.x() >= bbox.min.x() && point.x() < bbox.max.x() && + point.y() >= bbox.min.y() && point.y() < bbox.max.y() && + // Exact match: Don't just test whether a point is inside the outer contour of an island, + // test also whether the point is not inside some hole of the same expolygon. + // This is unfortunatelly necessary because the point may be inside an expolygon of one of this expolygon's hole + // and missed due to numerical issues. + lslices[lslice_idx].contains(point); + }; + for (int lslice_idx = int(lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx) for (auto it_source_slice = perimeter_slices_queue.begin(); it_source_slice != perimeter_slices_queue.end(); ++ it_source_slice) if (point_inside_surface(lslice_idx, it_source_slice->second)) { - this->lslices_ex[lslice_idx].islands.push_back({}); - LayerIsland &island = this->lslices_ex[lslice_idx].islands.back(); - const uint32_t source_slice_idx = it_source_slice->first; - island.perimeters = LayerExtrusionRange(region_id, perimeter_and_gapfill_ranges[source_slice_idx].first); - island.thin_fills = perimeter_and_gapfill_ranges[source_slice_idx].second; - if (ExPolygonRange fill_range = fill_expolygons_ranges[source_slice_idx]; ! fill_range.empty()) { - if (layer_region_ids.size() == 1) { - // Layer island is made of one fill region only. - island.fill_expolygons = fill_range; - island.fill_region_id = region_id; - } else { - // Check whether the fill expolygons of this island were split into multiple regions. - island.fill_region_id = LayerIsland::fill_region_composite_id; - for (uint32_t fill_idx : fill_range) { - const std::pair &kvp = map_expolygon_to_region_and_fill[fill_idx]; - if (kvp.first == -1 || (island.fill_region_id != -1 && island.fill_region_id != kvp.second)) { - island.fill_region_id = LayerIsland::fill_region_composite_id; - break; - } else - island.fill_region_id = kvp.second; - } - if (island.fill_expolygons_composite()) { - // They were split, thus store the unsplit "composite" expolygons into the region of perimeters. - auto begin = uint32_t(this_layer_region.fill_expolygons_composite().size()); - this_layer_region.m_fill_expolygons_composite.reserve(this_layer_region.fill_expolygons_composite().size() + fill_range.size()); - std::move(fill_expolygons.begin() + *fill_range.begin(), fill_expolygons.begin() + *fill_range.end(), std::back_inserter(this_layer_region.m_fill_expolygons_composite)); - this_layer_region.m_fill_expolygons_composite_bboxes.insert(this_layer_region.m_fill_expolygons_composite_bboxes.end(), - fill_expolygons_bboxes.begin() + *fill_range.begin(), fill_expolygons_bboxes.begin() + *fill_range.end()); - island.fill_expolygons = ExPolygonRange(begin, uint32_t(this_layer_region.fill_expolygons_composite().size())); - } else { - if (region_fill_sorted_last.empty()) - region_fill_sorted_last.assign(m_regions.size(), 0); - uint32_t &last = region_fill_sorted_last[island.fill_region_id]; - // They were not split and they belong to the same region. - // Sort the region m_fill_expolygons to a continuous span. - uint32_t begin = last; - LayerRegion &layerm = *m_regions[island.fill_region_id]; - for (uint32_t fill_id : fill_range) { - uint32_t region_fill_id = map_expolygon_to_region_and_fill[fill_id].second; - assert(region_fill_id >= last); - if (region_fill_id > last) { - std::swap(layerm.m_fill_expolygons[region_fill_id], layerm.m_fill_expolygons[last]); - std::swap(layerm.m_fill_expolygons_bboxes[region_fill_id], layerm.m_fill_expolygons_bboxes[last]); - } - ++ last; - } - island.fill_expolygons = ExPolygonRange(begin, last); - } - } - } - if (std::next(it_source_slice) != perimeter_slices_queue.end()) { + insert_into_island(lslice_idx, it_source_slice->first); + if (std::next(it_source_slice) != perimeter_slices_queue.end()) // Remove the current slice & point pair from the queue. *it_source_slice = perimeter_slices_queue.back(); - perimeter_slices_queue.pop_back(); - } + perimeter_slices_queue.pop_back(); break; } + // If anything fails to be sorted in using exact fit, try to find a closest island. + auto point_inside_surface_dist2 = + [&lslices = this->lslices, &lslices_ex = this->lslices_ex, bbox_eps = scaled(this->object()->print()->config().gcode_resolution.value) + SCALED_EPSILON] + (const size_t lslice_idx, const Point &point) { + const BoundingBox &bbox = lslices_ex[lslice_idx].bbox; + return + point.x() < bbox.min.x() - bbox_eps || point.x() > bbox.max.x() + bbox_eps || + point.y() < bbox.min.y() - bbox_eps || point.y() > bbox.max.y() + bbox_eps ? + std::numeric_limits::max() : + (lslices[lslice_idx].point_projection(point) - point).cast().squaredNorm(); + }; + for (int lslice_idx = int(lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx) { + double d2min = std::numeric_limits::max(); + auto it_source_slice = perimeter_slices_queue.end(); + for (auto it = perimeter_slices_queue.begin(); it != perimeter_slices_queue.end(); ++ it) + if (double d2 = point_inside_surface_dist2(lslice_idx, it->second); d2 < d2min) { + d2min = d2; + it_source_slice = it; + } + assert(it_source_slice != perimeter_slices_queue.end()); + if (it_source_slice != perimeter_slices_queue.end()) { + insert_into_island(lslice_idx, it_source_slice->first); + if (std::next(it_source_slice) != perimeter_slices_queue.end()) + // Remove the current slice & point pair from the queue. + *it_source_slice = perimeter_slices_queue.back(); + perimeter_slices_queue.pop_back(); + } } } diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index d7d0fbffb..45665410d 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -79,8 +79,8 @@ private: uint32_t m_region { 0 }; }; -// Most likely one LayerIsland will be filled with maximum one fill type. -static constexpr const size_t LayerExtrusionRangesStaticSize = 1; +// One LayerIsland may be filled with solid fill, sparse fill, top / bottom fill. +static constexpr const size_t LayerExtrusionRangesStaticSize = 3; using LayerExtrusionRanges = #ifdef NDEBUG // To reduce memory allocation in release mode. diff --git a/t/shells.t b/t/shells.t index 29bc0b5f0..cd96f9cde 100644 --- a/t/shells.t +++ b/t/shells.t @@ -1,4 +1,4 @@ -use Test::More tests => 20; +use Test::More tests => 17; use strict; use warnings; @@ -13,73 +13,6 @@ use Slic3r; use Slic3r::Geometry qw(epsilon); use Slic3r::Test; -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('skirts', 0); - $config->set('perimeters', 0); - $config->set('solid_infill_speed', 99); - $config->set('top_solid_infill_speed', 99); - $config->set('bridge_speed', 72); - $config->set('first_layer_speed', '100%'); - $config->set('cooling', [ 0 ]); - - my $test = sub { - my ($conf) = @_; - $conf ||= $config; - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - - my %z = (); # Z => 1 - my %layers_with_solid_infill = (); # Z => $count - my %layers_with_bridge_infill = (); # Z => $count - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($self->Z > 0) { - $z{ $self->Z } = 1; - if ($info->{extruding} && $info->{dist_XY} > 0) { - my $F = $args->{F} // $self->F; - $layers_with_solid_infill{$self->Z} = 1 - if $F == $config->solid_infill_speed*60; - $layers_with_bridge_infill{$self->Z} = 1 - if $F == $config->bridge_speed*60; - } - } - }); - my @z = sort { $a <=> $b } keys %z; - my @shells = map $layers_with_solid_infill{$_} || $layers_with_bridge_infill{$_}, @z; - fail "insufficient number of bottom solid layers" - unless !defined(first { !$_ } @shells[0..$config->bottom_solid_layers-1]); - fail "excessive number of bottom solid layers" - unless scalar(grep $_, @shells[0 .. $#shells/2]) == $config->bottom_solid_layers; - fail "insufficient number of top solid layers" - unless !defined(first { !$_ } @shells[-$config->top_solid_layers..-1]); - fail "excessive number of top solid layers" - unless scalar(grep $_, @shells[($#shells/2)..$#shells]) == $config->top_solid_layers; - if ($config->top_solid_layers > 0) { - fail "unexpected solid infill speed in first solid layer over sparse infill" - if $layers_with_solid_infill{ $z[-$config->top_solid_layers] }; - die "bridge speed not used in first solid layer over sparse infill" - if !$layers_with_bridge_infill{ $z[-$config->top_solid_layers] }; - } - 1; - }; - - $config->set('top_solid_layers', 3); - $config->set('bottom_solid_layers', 3); - ok $test->(), "proper number of shells is applied"; - - $config->set('top_solid_layers', 0); - $config->set('bottom_solid_layers', 0); - ok $test->(), "no shells are applied when both top and bottom are set to zero"; - - $config->set('perimeters', 1); - $config->set('top_solid_layers', 3); - $config->set('bottom_solid_layers', 3); - $config->set('fill_density', 0); - ok $test->(), "proper number of shells is applied even when fill density is none"; -} - # issue #1161 { my $config = Slic3r::Config::new_from_defaults; diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 0b3b920b9..6e9a80ef8 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -21,6 +21,7 @@ add_executable(${_TEST_NAME}_tests test_print.cpp test_printgcode.cpp test_printobject.cpp + test_shells.cpp test_skirt_brim.cpp test_support_material.cpp test_thin_walls.cpp diff --git a/tests/fff_print/test_shells.cpp b/tests/fff_print/test_shells.cpp new file mode 100644 index 000000000..df0e8f6bb --- /dev/null +++ b/tests/fff_print/test_shells.cpp @@ -0,0 +1,112 @@ +#include + +#include "libslic3r/GCodeReader.hpp" + +#include "test_data.hpp" // get access to init_print, etc + +using namespace Slic3r::Test; +using namespace Slic3r; + +SCENARIO("Shells", "[Shells]") { + GIVEN("20mm box") { + auto test = [](const DynamicPrintConfig &config){ + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + + std::vector zs; + std::set layers_with_solid_infill; + std::set layers_with_bridge_infill; + const double solid_infill_speed = config.opt_float("solid_infill_speed") * 60; + const double bridge_speed = config.opt_float("bridge_speed") * 60; + GCodeReader parser; + parser.parse_buffer(gcode, + [&zs, &layers_with_solid_infill, &layers_with_bridge_infill, solid_infill_speed, bridge_speed] + (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + { + double z = line.new_Z(self); + REQUIRE(z >= 0); + if (z > 0) { + coord_t scaled_z = scaled(z); + zs.emplace_back(scaled_z); + if (line.extruding(self) && line.dist_XY(self) > 0) { + double f = line.new_F(self); + if (std::abs(f - solid_infill_speed) < EPSILON) + layers_with_solid_infill.insert(scaled_z); + if (std::abs(f - bridge_speed) < EPSILON) + layers_with_bridge_infill.insert(scaled_z); + } + } + }); + sort_remove_duplicates(zs); + + auto has_solid_infill = [&layers_with_solid_infill](coord_t z) { return layers_with_solid_infill.find(z) != layers_with_solid_infill.end(); }; + auto has_bridge_infill = [&layers_with_bridge_infill](coord_t z) { return layers_with_bridge_infill.find(z) != layers_with_bridge_infill.end(); }; + auto has_shells = [&has_solid_infill, &has_bridge_infill, &zs](int layer_idx) { coord_t z = zs[layer_idx]; return has_solid_infill(z) || has_bridge_infill(z); }; + const int bottom_solid_layers = config.opt_int("bottom_solid_layers"); + const int top_solid_layers = config.opt_int("top_solid_layers"); + THEN("correct number of bottom solid layers") { + for (int i = 0; i < bottom_solid_layers; ++ i) + REQUIRE(has_shells(i)); + for (int i = bottom_solid_layers; i < int(zs.size() / 2); ++ i) + REQUIRE(! has_shells(i)); + } + THEN("correct number of top solid layers") { + for (int i = 0; i < top_solid_layers; ++ i) + REQUIRE(has_shells(int(zs.size()) - i - 1)); + for (int i = top_solid_layers; i < int(zs.size() / 2); ++ i) + REQUIRE(! has_shells(int(zs.size()) - i - 1)); + } + if (top_solid_layers > 0) { + THEN("solid infill speed is used on solid infill") { + for (int i = 0; i < top_solid_layers - 1; ++ i) { + auto z = zs[int(zs.size()) - i - 1]; + REQUIRE(has_solid_infill(z)); + REQUIRE(! has_bridge_infill(z)); + } + } + THEN("bridge used in first solid layer over sparse infill") { + auto z = zs[int(zs.size()) - top_solid_layers]; + REQUIRE(! has_solid_infill(z)); + REQUIRE(has_bridge_infill(z)); + } + } + }; + + auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ + { "skirts", 0 }, + { "perimeters", 0 }, + { "solid_infill_speed", 99 }, + { "top_solid_infill_speed", 99 }, + { "bridge_speed", 72 }, + { "first_layer_speed", "100%" }, + { "cooling", "0" } + }); + + WHEN("three top and bottom layers") { + // proper number of shells is applied + config.set_deserialize_strict({ + { "top_solid_layers", 3 }, + { "bottom_solid_layers", 3 } + }); + test(config); + } + + WHEN("zero top and bottom layers") { + // no shells are applied when both top and bottom are set to zero + config.set_deserialize_strict({ + { "top_solid_layers", 0 }, + { "bottom_solid_layers", 0 } + }); + test(config); + } + + WHEN("three top and bottom layers, zero infill") { + // proper number of shells is applied even when fill density is none + config.set_deserialize_strict({ + { "perimeters", 1 }, + { "top_solid_layers", 3 }, + { "bottom_solid_layers", 3 } + }); + test(config); + } + } +} From 3cdacd700c5a6fd6d9a6bdb05f8f8761d79554f8 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Nov 2022 14:51:07 +0100 Subject: [PATCH 10/33] Merged with master --- src/libslic3r/GCode.cpp | 36 ++-- src/libslic3r/Measure.cpp | 252 ++++++++++++++++------- src/libslic3r/Measure.hpp | 11 + src/slic3r/GUI/3DBed.cpp | 5 +- src/slic3r/GUI/GLCanvas3D.cpp | 6 +- src/slic3r/GUI/GUI_ObjectList.cpp | 4 + src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 52 ++--- src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp | 1 + src/slic3r/GUI/MainFrame.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 24 ++- src/slic3r/GUI/Plater.hpp | 1 + src/slic3r/GUI/Search.cpp | 2 - 12 files changed, 276 insertions(+), 120 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 24e350ac4..bc3027cac 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1502,27 +1502,27 @@ void GCode::process_layers( } }); const auto spiral_vase = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&spiral_vase = *m_spiral_vase](LayerResult in) -> LayerResult { + [spiral_vase = this->m_spiral_vase.get()](LayerResult in) -> LayerResult { if (in.nop_layer_result) return in; - spiral_vase.enable(in.spiral_vase_enable); - return { spiral_vase.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush}; + spiral_vase->enable(in.spiral_vase_enable); + return { spiral_vase->process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush}; }); const auto pressure_equalizer = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&pressure_equalizer = *m_pressure_equalizer](LayerResult in) -> LayerResult { - return pressure_equalizer.process_layer(std::move(in)); + [pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult { + return pressure_equalizer->process_layer(std::move(in)); }); const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&cooling_buffer = *m_cooling_buffer](LayerResult in) -> std::string { + [cooling_buffer = this->m_cooling_buffer.get()](LayerResult in) -> std::string { if (in.nop_layer_result) return in.gcode; - return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); + return cooling_buffer->process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); }); const auto find_replace = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&self = *m_find_replace](std::string s) -> std::string { - return self.process_layer(std::move(s)); + [find_replace = this->m_find_replace.get()](std::string s) -> std::string { + return find_replace->process_layer(std::move(s)); }); const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [&output_stream](std::string s) { output_stream.write(s); } @@ -1584,25 +1584,25 @@ void GCode::process_layers( } }); const auto spiral_vase = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&spiral_vase = *m_spiral_vase](LayerResult in)->LayerResult { + [spiral_vase = this->m_spiral_vase.get()](LayerResult in)->LayerResult { if (in.nop_layer_result) return in; - spiral_vase.enable(in.spiral_vase_enable); - return { spiral_vase.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; + spiral_vase->enable(in.spiral_vase_enable); + return { spiral_vase->process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; }); const auto pressure_equalizer = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&pressure_equalizer = *m_pressure_equalizer](LayerResult in) -> LayerResult { - return pressure_equalizer.process_layer(std::move(in)); + [pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult { + return pressure_equalizer->process_layer(std::move(in)); }); const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&cooling_buffer = *m_cooling_buffer](LayerResult in)->std::string { + [cooling_buffer = this->m_cooling_buffer.get()](LayerResult in)->std::string { if (in.nop_layer_result) return in.gcode; - return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); + return cooling_buffer->process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); }); const auto find_replace = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&self = *m_find_replace](std::string s) -> std::string { - return self.process_layer(std::move(s)); + [find_replace = this->m_find_replace.get()](std::string s) -> std::string { + return find_replace->process_layer(std::move(s)); }); const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [&output_stream](std::string s) { output_stream.write(s); } diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index f64b79e22..fc27f0383 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -87,6 +87,10 @@ void MeasuringImpl::update_planes() return (std::abs(a(0) - b(0)) < 0.001 && std::abs(a(1) - b(1)) < 0.001 && std::abs(a(2) - b(2)) < 0.001); }; + // First go through all the triangles and fill in m_planes vector. For each "plane" + // detected on the model, it will contain list of facets that are part of it. + // We will also fill in m_face_to_plane, which contains index into m_planes + // for each of the source facets. while (1) { // Find next unvisited triangle: for (; seed_facet_idx < num_of_facets; ++ seed_facet_idx) @@ -117,20 +121,29 @@ void MeasuringImpl::update_planes() m_planes.back().normal = normal_ptr->cast(); std::sort(m_planes.back().facets.begin(), m_planes.back().facets.end()); } - + + // Check that each facet is part of one of the planes. assert(std::none_of(m_face_to_plane.begin(), m_face_to_plane.end(), [](size_t val) { return val == size_t(-1); })); + // Now we will walk around each of the planes and save vertices which form the border. SurfaceMesh sm(m_its); for (int plane_id=0; plane_id < int(m_planes.size()); ++plane_id) { - //int plane_id = 5; { const auto& facets = m_planes[plane_id].facets; m_planes[plane_id].borders.clear(); std::vector> visited(facets.size(), {false, false, false}); + + for (int face_id=0; face_id double { return 2.*M_PI / N; }; - constexpr double polygon_upper_threshold = N_to_angle(4.5); - constexpr double polygon_lower_threshold = N_to_angle(8.5); std::vector angles; std::vector lengths; @@ -209,31 +230,39 @@ void MeasuringImpl::extract_features() trafo.rotate(q); for (const std::vector& border : plane.borders) { - assert(border.size() > 1); + if (border.size() <= 1) + continue; + assert(border.front() == border.back()); int start_idx = -1; + std::vector edges; + // First calculate angles at all the vertices. angles.clear(); lengths.clear(); - for (int i=0; i M_PI) angle = 2*M_PI - angle; angles.push_back(angle); - lengths.push_back(v2.squaredNorm()); + lengths.push_back(v2.norm()); } assert(border.size() == angles.size()); assert(border.size() == lengths.size()); + // First go around the border and pick what might be circular segments. + // Save pair of indices to where such potential segments start and end. + // Also remember the length of these segments. bool circle = false; std::vector circles; - std::vector> circles_idxs; + std::vector> circles_idxs; + std::vector circles_lengths; for (int i=1; i<(int)angles.size(); ++i) { if (Slic3r::is_approx(lengths[i], lengths[i-1]) && Slic3r::is_approx(angles[i], angles[i-1]) @@ -245,62 +274,149 @@ void MeasuringImpl::extract_features() } } else { if (circle) { - // Add the circle and remember indices into borders. const auto& [center, radius] = get_center_and_radius(border, start_idx, i, trafo); + // Add the circle and remember indices into borders. circles_idxs.emplace_back(start_idx, i); circles.emplace_back(SurfaceFeature(SurfaceFeatureType::Circle, center, plane.normal, std::nullopt, radius)); + circles_lengths.emplace_back(std::accumulate(lengths.begin() + start_idx + 1, lengths.begin() + i + 1, 0.)); circle = false; } } } - // Some of the "circles" may actually be polygons. We want them detected as - // edges, but also to remember the center and save it into those edges. - // We will add all such edges manually and delete the detected circles, - // leaving it in circles_idxs so they are not picked again: + // At this point we might need to merge the first and last segment, if the starting + // point happened to be inside the segment. The discrimination of too small segments + // will follow, so we need a complete picture before that. + if (circles_idxs.size() > 1 + && circles_idxs.back().second == angles.size()-1 + && circles_idxs.front().first == 0) { + // Possibly the same circle. Check that the angle and length criterion holds along the combined segment. + bool same = true; + double last_len = -1.; + double last_angle = 0.; + for (int i=circles_idxs.back().first + 1; i != circles_idxs.front().second; ++i) { + if (i == angles.size()) + i = 1; + if (last_len == -1.) { + last_len = lengths[i]; + last_angle = angles[i]; + } else { + if (! Slic3r::is_approx(lengths[i], last_len) || ! Slic3r::is_approx(angles[i], last_angle)) { + same = false; + break; + } + } + } + if (same) { + // This seems to really be the same circle. Better apply ransac again. The parts can be small and inexact. + std::vector points(border.begin() + circles_idxs.back().first, border.end()); + points.insert(points.end(), border.begin(), border.begin() + circles_idxs.front().second+1); + auto [c, radius] = get_center_and_radius(points, 0, points.size()-1, trafo); + + // Now replace the first circle with the combined one, remove the last circle. + // First index of the first circle is saved negative - we are going to pick edges + // from the border later, we will need to know where the merged in segment was. + // The sign simplifies the algorithm that picks the remaining edges - see below. + circles.front() = SurfaceFeature(SurfaceFeatureType::Circle, c, plane.normal, std::nullopt, radius); + circles_idxs.front().first = - circles_idxs.back().first; + circles_lengths.front() += circles_lengths.back(); + circles.pop_back(); + circles_idxs.pop_back(); + circles_lengths.pop_back(); + } + } + + // Now throw away all circles that subtend less than 90 deg. + assert(circles.size() == circles_lengths.size()); + for (int i=0; i(circles[i].get_circle()); + if (circles_lengths[i] / r < 0.9*M_PI/2.) { + circles_lengths.erase(circles_lengths.begin() + i); + circles.erase(circles.begin() + i); + circles_idxs.erase(circles_idxs.begin() + i); + --i; + } + } + circles_lengths.clear(); // no longer needed, make it obvious + + // Some of the "circles" may actually be polygons (5-8 vertices). We want them + // detected as edges, but also to remember the center and save it into those edges. + // We will add all such edges manually and delete the detected circles, leaving it + // in circles_idxs so they are not picked again. assert(circles.size() == circles_idxs.size()); for (int i=circles.size()-1; i>=0; --i) { - assert(circles_idxs[i].first + 1 < angles.size() - 1); // Check that this is internal point of the circle, not the first, not the last. - double angle = angles[circles_idxs[i].first + 1]; - if (angle > polygon_lower_threshold) { - if (angle < polygon_upper_threshold) { - const Vec3d center = std::get<0>(circles[i].get_circle()); - for (int j=(int)circles_idxs[i].first + 1; j<=(int)circles_idxs[i].second; ++j) - plane.surface_features.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, - border[j - 1], border[j], std::make_optional(center))); - } else { - // This will be handled just like a regular edge. - circles_idxs.erase(circles_idxs.begin() + i); + if (circles_idxs[i].first == 0 && circles_idxs[i].second == border.size()-1) { + int N = circles_idxs[i].second - circles_idxs[i].first; + if (N <= 8) { + if (N >= 5) { // polygon = 5,6,7,8 vertices + const Vec3d center = std::get<0>(circles[i].get_circle()); + for (int j=(int)circles_idxs[i].first + 1; j<=(int)circles_idxs[i].second; ++j) + edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, + border[j - 1], border[j], std::make_optional(center))); + } else { + // This will be handled just like a regular edge (squares, triangles). + circles_idxs.erase(circles_idxs.begin() + i); + } + circles.erase(circles.begin() + i); } + } + } + + // Anything under 5 vertices shall not be considered a circle. + assert(circles_idxs.size() == circles.size()); + for (int i=0; i= 0 + ? end - start + (start == 0 && end == border.size()-1 ? 0 : 1) // last point is the same as first + : end + (border.size() + start); + if (N < 5) { circles.erase(circles.begin() + i); + circles_idxs.erase(circles_idxs.begin() + i); + --i; } } - - - - - // We have the circles. Now go around again and pick edges. - int cidx = 0; // index of next circle in the way + // We have the circles. Now go around again and pick edges, while jumping over circles. + // If the first index of the first circle is negative, it means that it was merged + // with a segment that was originally at the back and is no longer there. Ressurect + // its pair of indices so that edges are not picked again. + if (! circles_idxs.empty() && circles_idxs.front().first < 0) + circles_idxs.emplace_back(-circles_idxs.front().first, int(border.size())); + int cidx = 0; // index of next circle to jump over for (int i=1; i (int)circles_idxs[cidx].first) i = circles_idxs[cidx++].second; else - plane.surface_features.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[i - 1], border[i])); + edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[i - 1], border[i])); } - // FIXME Throw away / do not create edges which are parts of circles or - // which lead to circle points (unless they belong to the same plane.) + // Merge adjacent edges where needed. + assert(std::all_of(edges.begin(), edges.end(), + [](const SurfaceFeature& f) { return f.get_type() == SurfaceFeatureType::Edge; })); + for (int i=edges.size()-1; i>=0; --i) { + const auto& [first_start, first_end] = edges[i==0 ? edges.size()-1 : i-1].get_edge(); + const auto& [second_start, second_end] = edges[i].get_edge(); - // FIXME Check and merge first and last circle if needed. + if (Slic3r::is_approx(first_end, second_start) + && Slic3r::is_approx((first_end-first_start).normalized().dot((second_end-second_start).normalized()), 1.)) { + // The edges have the same direction and share a point. Merge them. + edges[i==0 ? edges.size()-1 : i-1] = SurfaceFeature(SurfaceFeatureType::Edge, first_start, second_end); + edges.erase(edges.begin() + i); + } + } - // Now move the circles into the feature list. + // Now move the circles and edges into the feature list for the plane. assert(std::all_of(circles.begin(), circles.end(), [](const SurfaceFeature& f) { return f.get_type() == SurfaceFeatureType::Circle; })); + assert(std::all_of(edges.begin(), edges.end(), [](const SurfaceFeature& f) { + return f.get_type() == SurfaceFeatureType::Edge; + })); plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(circles.begin()), - std::make_move_iterator(circles.end())); + std::make_move_iterator(circles.end())); + plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(edges.begin()), + std::make_move_iterator(edges.end())); } // The last surface feature is the plane itself. @@ -607,8 +723,8 @@ MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& if (f2.get_type() == SurfaceFeatureType::Point) { Vec3d diff = (f2.get_point() - f1.get_point()); result.distance_strict = std::make_optional(DistAndPoints{diff.norm(), f1.get_point(), f2.get_point()}); - result.distance_xyz = diff; - + result.distance_xyz = diff.cwiseAbs(); + /////////////////////////////////////////////////////////////////////////// } else if (f2.get_type() == SurfaceFeatureType::Edge) { const auto [s,e] = f2.get_edge(); @@ -659,16 +775,16 @@ MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& if (f2.get_type() == SurfaceFeatureType::Edge) { std::vector distances; -// auto add_point_edge_distance = [&distances](const Vec3d& v, const std::pair& e) { -// const MeasurementResult res = get_measurement(SurfaceFeature(v), SurfaceFeature(SurfaceFeatureType::Edge, e.first, e.second)); -// double distance = res.distance_strict->dist; -// Vec3d v2 = res.distance_strict->to; -// -// const Vec3d e1e2 = e.second - e.first; -// const Vec3d e1v2 = v2 - e.first; -// if (e1v2.dot(e1e2) >= 0.0 && e1v2.norm() < e1e2.norm()) -// distances.emplace_back(distance, v, v2); -// }; + auto add_point_edge_distance = [&distances](const Vec3d& v, const std::pair& e) { + const MeasurementResult res = get_measurement(SurfaceFeature(v), SurfaceFeature(SurfaceFeatureType::Edge, e.first, e.second)); + double distance = res.distance_strict->dist; + Vec3d v2 = res.distance_strict->to; + + const Vec3d e1e2 = e.second - e.first; + const Vec3d e1v2 = v2 - e.first; + if (e1v2.dot(e1e2) >= 0.0 && e1v2.norm() < e1e2.norm()) + distances.emplace_back(distance, v, v2); + }; std::pair e1 = f1.get_edge(); std::pair e2 = f2.get_edge(); @@ -677,10 +793,10 @@ MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& distances.emplace_back((e2.second - e1.first).norm(), e1.first, e2.second); distances.emplace_back((e2.first - e1.second).norm(), e1.second, e2.first); distances.emplace_back((e2.second - e1.second).norm(), e1.second, e2.second); -// add_point_edge_distance(e1.first, e2); -// add_point_edge_distance(e1.second, e2); -// add_point_edge_distance(e2.first, e1); -// add_point_edge_distance(e2.second, e1); + add_point_edge_distance(e1.first, e2); + add_point_edge_distance(e1.second, e2); + add_point_edge_distance(e2.first, e1); + add_point_edge_distance(e2.second, e1); auto it = std::min_element(distances.begin(), distances.end(), [](const DistAndPoints& item1, const DistAndPoints& item2) { return item1.dist < item2.dist; @@ -1037,14 +1153,6 @@ MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& else result.angle = angle_plane_plane(f1.get_plane(), f2.get_plane()); } - - // validation - if (result.distance_infinite.has_value() && result.distance_infinite->dist < EPSILON) - result.distance_infinite.reset(); - if (result.distance_strict.has_value() && result.distance_strict->dist < EPSILON) - result.distance_strict.reset(); - if (result.angle.has_value() && std::abs(result.angle->angle) < EPSILON) - result.angle.reset(); return result; } diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp index d1a0e3866..ede8c634e 100644 --- a/src/libslic3r/Measure.hpp +++ b/src/libslic3r/Measure.hpp @@ -150,6 +150,17 @@ struct MeasurementResult { bool has_any_data() const { return angle.has_value() || distance_infinite.has_value() || distance_strict.has_value() || distance_xyz.has_value(); } + + void transform(const Transform3d& trafo) { + if (angle.has_value()) + angle->transform(trafo); + if (distance_infinite.has_value()) + distance_infinite->transform(trafo); + if (distance_strict.has_value()) { + distance_strict->transform(trafo); + distance_xyz = (distance_strict->to - distance_strict->from).cwiseAbs(); + } + } }; // Returns distance/angle between two SurfaceFeatures. diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 4319a28cf..7572d4fc4 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -223,7 +223,10 @@ bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, c #if ENABLE_LEGACY_OPENGL_REMOVAL m_contour = ExPolygon(Polygon::new_scale(bed_shape)); - m_polygon = offset(m_contour.contour, (float)m_contour.contour.bounding_box().radius() * 1.7f, jtRound, scale_(0.5)).front(); + const BoundingBox bbox = m_contour.contour.bounding_box(); + if (!bbox.defined) + throw RuntimeError(std::string("Invalid bed shape")); + m_polygon = offset(m_contour.contour, (float)bbox.radius() * 1.7f, jtRound, scale_(0.5)).front(); m_triangles.reset(); m_gridlines.reset(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ec3865a40..abe5fc1e2 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3460,6 +3460,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_gizmos.get_current_type() != GLGizmosManager::FdmSupports && m_gizmos.get_current_type() != GLGizmosManager::Seam && m_gizmos.get_current_type() != GLGizmosManager::Cut && + m_gizmos.get_current_type() != GLGizmosManager::Measure && m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) { m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); m_dirty = true; @@ -3695,7 +3696,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // if right clicking on volume, propagate event through callback (shows context menu) int volume_idx = get_first_hover_volume_idx(); if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower - && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports) // disable context menu when the gizmo is open + && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && m_gizmos.get_current_type() != GLGizmosManager::Measure)) // disable context menu when the gizmo is open { // forces the selection of the volume /* m_selection.add(volume_idx); // #et_FIXME_if_needed @@ -3719,7 +3720,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (!m_mouse.dragging) { // do not post the event if the user is panning the scene // or if right click was done over the wipe tower - const bool post_right_click_event = m_hover_volume_idxs.empty() || !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower; + const bool post_right_click_event = (m_hover_volume_idxs.empty() || !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower) && + m_gizmos.get_current_type() != GLGizmosManager::Measure; if (post_right_click_event) post_event(RBtnEvent(EVT_GLCANVAS_RIGHT_CLICK, { logical_pos, m_hover_volume_idxs.empty() })); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d6bbbeba1..abb22392b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2882,6 +2882,9 @@ static bool can_add_volumes_to_object(const ModelObject* object) wxDataViewItemArray ObjectList::add_volumes_to_object_in_list(size_t obj_idx, std::function add_to_selection/* = nullptr*/) { + const bool is_prevent_list_events = m_prevent_list_events; + m_prevent_list_events = true; + wxDataViewItem object_item = m_objects_model->GetItemById(int(obj_idx)); m_objects_model->DeleteVolumeChildren(object_item); @@ -2909,6 +2912,7 @@ wxDataViewItemArray ObjectList::add_volumes_to_object_in_list(size_t obj_idx, st Expand(object_item); } + m_prevent_list_events = is_prevent_list_events; return items; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index 74be878f1..3fa8d8d6f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -303,16 +303,11 @@ bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event) if (m_selected_features != selected_features_old && m_selected_features.second.feature.has_value()) { m_measurement_result = Measure::get_measurement(*m_selected_features.first.feature, *m_selected_features.second.feature, m_measuring.get()); // transform to world coordinates - if (m_measurement_result.angle.has_value()) - m_measurement_result.angle->transform(m_volume_matrix); - if (m_measurement_result.distance_infinite.has_value()) - m_measurement_result.distance_infinite->transform(m_volume_matrix); - if (m_measurement_result.distance_strict.has_value()) - m_measurement_result.distance_strict->transform(m_volume_matrix); - if (m_measurement_result.distance_xyz.has_value()) - m_measurement_result.distance_xyz = TransformHelper::model_to_world(*m_measurement_result.distance_xyz, m_volume_matrix); + m_measurement_result.transform(m_volume_matrix); } + m_imgui->set_requires_extra_frame(); + return true; } @@ -334,7 +329,7 @@ bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event) else if (mouse_event.RightDown() && mouse_event.CmdDown()) { m_selected_features.reset(); m_selection_raycasters.clear(); - m_imgui->set_requires_extra_frame(); + m_parent.request_extra_frame(); } else if (mouse_event.Leaving()) m_mouse_left_down = false; @@ -357,7 +352,14 @@ void GLGizmoMeasure::data_changed() m_last_inv_zoom = 0.0f; m_last_plane_idx = -1; - m_selected_features.reset(); + if (m_pending_scale) { + m_measurement_result = Measure::get_measurement(*m_selected_features.first.feature, *m_selected_features.second.feature, m_measuring.get()); + // transform to world coordinates + m_measurement_result.transform(m_volume_matrix); + m_pending_scale = false; + } + else + m_selected_features.reset(); m_selection_raycasters.clear(); m_editing_distance = false; m_is_editing_distance_first_frame = true; @@ -905,7 +907,7 @@ void GLGizmoMeasure::render_dimensioning() return; auto point_point = [this, shader](const Vec3d& v1, const Vec3d& v2, float distance) { - if (v1.isApprox(v2)) + if ((v2 - v1).squaredNorm() < 0.000001 || distance < 0.001f) return; const Camera& camera = wxGetApp().plater()->get_camera(); @@ -1007,6 +1009,9 @@ void GLGizmoMeasure::render_dimensioning() selection.scale(ratio * Vec3d::Ones(), type); wxGetApp().plater()->canvas3D()->do_scale(""); // avoid storing another snapshot wxGetApp().obj_manipul()->set_dirty(); + + // update measure on next call to data_changed() + m_pending_scale = true; }; auto action_exit = [this]() { m_editing_distance = false; @@ -1271,13 +1276,6 @@ void GLGizmoMeasure::render_dimensioning() if (m_selected_features.second.feature.has_value()) { const bool has_distance = m_measurement_result.has_distance_data(); - if (has_distance) { - // Render the arrow between the points that the backend passed: - const Measure::DistAndPoints& dap = m_measurement_result.distance_infinite.has_value() - ? *m_measurement_result.distance_infinite - : *m_measurement_result.distance_strict; - point_point(dap.from, dap.to, dap.dist); - } const Measure::SurfaceFeature* f1 = &(*m_selected_features.first.feature); const Measure::SurfaceFeature* f2 = &(*m_selected_features.second.feature); @@ -1290,17 +1288,25 @@ void GLGizmoMeasure::render_dimensioning() std::swap(f1, f2); } - // Where needed, draw also the extension of the edge to where the dist is measured: - if (has_distance && ft1 == Measure::SurfaceFeatureType::Point && ft2 == Measure::SurfaceFeatureType::Edge) - point_edge(*f1, *f2); - - // Now if there is an angle to show, draw the arc: + // If there is an angle to show, draw the arc: if (ft1 == Measure::SurfaceFeatureType::Edge && ft2 == Measure::SurfaceFeatureType::Edge) arc_edge_edge(*f1, *f2); else if (ft1 == Measure::SurfaceFeatureType::Edge && ft2 == Measure::SurfaceFeatureType::Plane) arc_edge_plane(*f1, *f2); else if (ft1 == Measure::SurfaceFeatureType::Plane && ft2 == Measure::SurfaceFeatureType::Plane) arc_plane_plane(*f1, *f2); + + if (has_distance){ + // Where needed, draw the extension of the edge to where the dist is measured: + if (ft1 == Measure::SurfaceFeatureType::Point && ft2 == Measure::SurfaceFeatureType::Edge) + point_edge(*f1, *f2); + + // Render the arrow between the points that the backend passed: + const Measure::DistAndPoints& dap = m_measurement_result.distance_infinite.has_value() + ? *m_measurement_result.distance_infinite + : *m_measurement_result.distance_strict; + point_point(dap.from, dap.to, dap.dist); + } } glsafe(::glEnable(GL_DEPTH_TEST)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp index 7d20ca26a..4426457ff 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -116,6 +116,7 @@ class GLGizmoMeasure : public GLGizmoBase KeyAutoRepeatFilter m_ctrl_kar_filter; SelectedFeatures m_selected_features; + bool m_pending_scale{ false }; bool m_editing_distance{ false }; bool m_is_editing_distance_first_frame{ true }; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 96959f33b..5764b283c 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -538,7 +538,7 @@ void MainFrame::update_layout() case ESettingsLayout::GCodeViewer: { m_main_sizer->Add(m_plater, 1, wxEXPAND); - m_plater->set_bed_shape({ { 0.0, 0.0 }, { 200.0, 0.0 }, { 200.0, 200.0 }, { 0.0, 200.0 } }, 0.0, {}, {}, true); + m_plater->set_default_bed_shape(); m_plater->get_collapse_toolbar().set_enabled(false); m_plater->collapse_sidebar(true); m_plater->Show(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index dd47325b5..ccaf28ec0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5477,10 +5477,27 @@ void Plater::load_gcode(const wxString& filename) p->gcode_result = std::move(processor.extract_result()); // show results - p->preview->reload_print(false); + try + { + p->preview->reload_print(false); + } + catch (const std::exception&) + { + wxEndBusyCursor(); + p->gcode_result.reset(); + reset_gcode_toolpaths(); + set_default_bed_shape(); + p->preview->reload_print(false); + p->get_current_canvas3D()->render(); + MessageDialog(this, _L("The selected file") + ":\n" + filename + "\n" + _L("does not contain valid gcode."), + wxString(GCODEVIEWER_APP_NAME) + " - " + _L("Error while loading .gcode file"), wxOK | wxICON_WARNING | wxCENTRE).ShowModal(); + set_project_filename(wxEmptyString); + return; + } p->preview->get_canvas3d()->zoom_to_gcode(); if (p->preview->get_canvas3d()->get_gcode_layers_zs().empty()) { + wxEndBusyCursor(); //wxMessageDialog(this, _L("The selected file") + ":\n" + filename + "\n" + _L("does not contain valid gcode."), MessageDialog(this, _L("The selected file") + ":\n" + filename + "\n" + _L("does not contain valid gcode."), wxString(GCODEVIEWER_APP_NAME) + " - " + _L("Error while loading .gcode file"), wxOK | wxICON_WARNING | wxCENTRE).ShowModal(); @@ -6646,6 +6663,11 @@ void Plater::set_bed_shape(const Pointfs& shape, const double max_print_height, p->set_bed_shape(shape, max_print_height, custom_texture, custom_model, force_as_custom); } +void Plater::set_default_bed_shape() const +{ + set_bed_shape({ { 0.0, 0.0 }, { 200.0, 0.0 }, { 200.0, 200.0 }, { 0.0, 200.0 } }, 0.0, {}, {}, true); +} + void Plater::force_filament_colors_update() { bool update_scheduled = false; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 9dc9f6316..ff750d561 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -399,6 +399,7 @@ public: void set_bed_shape() const; void set_bed_shape(const Pointfs& shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const; + void set_default_bed_shape() const; NotificationManager * get_notification_manager(); const NotificationManager * get_notification_manager() const; diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 52e58193c..88d3e1376 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -594,8 +594,6 @@ void SearchDialog::ProcessSelection(wxDataViewItem selection) void SearchDialog::OnInputText(wxCommandEvent&) { - search_line->SetInsertionPointEnd(); - wxString input_string = search_line->GetValue(); if (input_string == default_string) input_string.Clear(); From f342bfae4e3d09a2c6dbd873e0ed7c821988a897 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Nov 2022 16:00:13 +0100 Subject: [PATCH 11/33] Improved const correctness of ToolOrdering. --- src/libslic3r/GCode.cpp | 6 ++---- src/libslic3r/GCode/ToolOrdering.cpp | 28 ++++++++++++++++++---------- src/libslic3r/GCode/ToolOrdering.hpp | 26 ++++++++++++-------------- src/libslic3r/Print.cpp | 4 ++-- 4 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index bc3027cac..46dcccd21 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2208,8 +2208,7 @@ LayerResult GCode::process_layer( std::vector instances_to_print = sort_print_object_instances(layers, ordering, single_object_instance_idx); // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): - //FIXME const_cast - bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); + bool is_anything_overridden = layer_tools.wiping_extrusions().is_anything_overridden(); if (is_anything_overridden) { // Extrude wipes. size_t gcode_size_old = gcode.size(); @@ -2349,8 +2348,7 @@ void GCode::process_layer_single_object( // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools) correct_extruder_id = layer_tools.extruders.back(); } - //FIXME const_cast! - int extruder_override_id = is_anything_overridden ? const_cast(layer_tools).wiping_extrusions().get_extruder_override(eec, instance_id) : -1; + int extruder_override_id = is_anything_overridden ? layer_tools.wiping_extrusions().get_extruder_override(eec, instance_id) : -1; return print_wipe_extrusions ? extruder_override_id == int(extruder_id) : extruder_override_id < 0 && extruder_id == correct_extruder_id; diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 7f14203a9..84e80a07f 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -232,12 +232,14 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto if (m_print_config_ptr) { // in this case complete_objects is false (see ToolOrdering constructors) something_nonoverriddable = false; for (const ExtrusionEntity *eec : layerm->perimeters()) // let's check if there are nonoverriddable entities - if (!layer_tools.wiping_extrusions().is_overriddable_and_mark(dynamic_cast(*eec), *m_print_config_ptr, object, region)) + if (layer_tools.wiping_extrusions().is_overriddable(dynamic_cast(*eec), *m_print_config_ptr, object, region)) + layer_tools.wiping_extrusions_nonconst().set_something_overridable(); + else something_nonoverriddable = true; } if (something_nonoverriddable) - layer_tools.extruders.emplace_back((extruder_override == 0) ? region.config().perimeter_extruder.value : extruder_override); + layer_tools.extruders.emplace_back(extruder_override == 0 ? region.config().perimeter_extruder.value : extruder_override); layer_tools.has_object = true; } @@ -255,7 +257,9 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto has_infill = true; if (m_print_config_ptr) { - if (! layer_tools.wiping_extrusions().is_overriddable_and_mark(*fill, *m_print_config_ptr, object, region)) + if (layer_tools.wiping_extrusions().is_overriddable(*fill, *m_print_config_ptr, object, region)) + layer_tools.wiping_extrusions_nonconst().set_something_overridable(); + else something_nonoverriddable = true; } } @@ -595,6 +599,12 @@ const LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) const return *it_layer_tools; } +static const LayerTools& layer_tools(const WipingExtrusions *self) +{ + char *ptr = (char*)(self) - offsetof(LayerTools, m_wiping_extrusions); + return *reinterpret_cast(ptr); +} + // This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual) void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies) { @@ -614,8 +624,7 @@ void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, size // Finds first non-soluble extruder on the layer int WipingExtrusions::first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const { - const LayerTools& lt = *m_layer_tools; - for (auto extruders_it = lt.extruders.begin(); extruders_it != lt.extruders.end(); ++extruders_it) + for (auto extruders_it = layer_tools(this).extruders.begin(); extruders_it != layer_tools(this).extruders.end(); ++extruders_it) if (!print_config.filament_soluble.get_at(*extruders_it)) return (*extruders_it); @@ -625,8 +634,7 @@ int WipingExtrusions::first_nonsoluble_extruder_on_layer(const PrintConfig& prin // Finds last non-soluble extruder on the layer int WipingExtrusions::last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const { - const LayerTools& lt = *m_layer_tools; - for (auto extruders_it = lt.extruders.rbegin(); extruders_it != lt.extruders.rend(); ++extruders_it) + for (auto extruders_it = layer_tools(this).extruders.rbegin(); extruders_it != layer_tools(this).extruders.rend(); ++extruders_it) if (!print_config.filament_soluble.get_at(*extruders_it)) return (*extruders_it); @@ -636,7 +644,7 @@ int WipingExtrusions::last_nonsoluble_extruder_on_layer(const PrintConfig& print // Decides whether this entity could be overridden bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const { - if (print_config.filament_soluble.get_at(m_layer_tools->extruder(eec, region))) + if (print_config.filament_soluble.get_at(layer_tools(this).extruder(eec, region))) return false; if (object.config().wipe_into_objects) @@ -653,7 +661,7 @@ bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, con // Switching from old_extruder to new_extruder, trying to wipe volume_to_wipe into not yet extruded extrusions, that may change material (overridable). float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe) { - const LayerTools& lt = *m_layer_tools; + const LayerTools& lt = layer_tools(this); const float min_infill_volume = 0.f; // ignore infill with smaller volume than this if (! m_something_overridable || volume_to_wipe <= 0. || @@ -752,7 +760,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) if (! m_something_overridable) return; - const LayerTools& lt = *m_layer_tools; + const LayerTools& lt = layer_tools(this); unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config()); unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config()); diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index d043c48ad..d0357baa0 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -6,6 +6,7 @@ #include "../libslic3r.h" #include +#include #include @@ -14,6 +15,7 @@ namespace Slic3r { class Print; class PrintObject; class LayerTools; +class ToolOrdering; namespace CustomGCode { struct Item; } class PrintRegion; @@ -45,13 +47,7 @@ public: void ensure_perimeters_infills_order(const Print& print); bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const; - bool is_overriddable_and_mark(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) { - bool out = this->is_overriddable(ee, print_config, object, region); - m_something_overridable |= out; - return out; - } - - void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; } + void set_something_overridable() { m_something_overridable = true; } private: int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const; @@ -69,14 +65,11 @@ private: std::map m_entity_map; // to keep track of who prints what bool m_something_overridable = false; bool m_something_overridden = false; - const LayerTools* m_layer_tools = nullptr; // so we know which LayerTools object this belongs to }; class LayerTools { public: - LayerTools(const coordf_t z) : print_z(z) {} - // Changing these operators to epsilon version can make a problem in cases where support and object layers get close to each other. // In case someone tries to do it, make sure you know what you're doing and test it properly (slice multiple objects at once with supports). bool operator< (const LayerTools &rhs) const { return print_z < rhs.print_z; } @@ -114,12 +107,17 @@ public: // Custom G-code (color change, extruder switch, pause) to be performed before this layer starts to print. const CustomGCode::Item *custom_gcode = nullptr; - WipingExtrusions& wiping_extrusions() { - m_wiping_extrusions.set_layer_tools_ptr(this); - return m_wiping_extrusions; - } + WipingExtrusions& wiping_extrusions_nonconst() { return m_wiping_extrusions; } + const WipingExtrusions& wiping_extrusions() const { return m_wiping_extrusions; } private: + // to access LayerTools private constructor + friend class ToolOrdering; + LayerTools(const coordf_t z) : print_z(z) {} + + // for calculating offset of m_wiping_extrusions in LayerTools. + friend const LayerTools& layer_tools(const WipingExtrusions *self); + // This object holds list of extrusion that will be used for extruder wiping WipingExtrusions m_wiping_extrusions; }; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 2979b3557..8f32b6c31 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1208,7 +1208,7 @@ void Print::_make_wipe_tower() volume_to_wipe -= (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); // try to assign some infills/objects for the wiping: - volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe); + volume_to_wipe = layer_tools.wiping_extrusions_nonconst().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe); // add back the minimal amount toforce on the wipe tower: volume_to_wipe += (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); @@ -1219,7 +1219,7 @@ void Print::_make_wipe_tower() current_extruder_id = extruder_id; } } - layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this); + layer_tools.wiping_extrusions_nonconst().ensure_perimeters_infills_order(*this); if (&layer_tools == &m_wipe_tower_data.tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0) break; } From fcb00680ab594aa4f66cf2f6c0cf362e5da7f309 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Nov 2022 16:09:45 +0100 Subject: [PATCH 12/33] Follow-up to f342bfae4e3d09a2c6dbd873e0ed7c821988a897: --- src/slic3r/GUI/DoubleSlider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 31b2c5c90..99b8d73d5 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -1984,7 +1984,7 @@ std::set TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extru std::set used_extruders; - auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(print_z)); + auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), print_z, [](const LayerTools &lhs, double rhs){ return lhs.print_z < rhs; }); for (; it_layer_tools != tool_ordering.end(); ++it_layer_tools) { const std::vector& extruders = it_layer_tools->extruders; for (const auto& extruder : extruders) From f24b4e86a9612fcf4cba28a5ae9f24db13f1b171 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Nov 2022 16:30:11 +0100 Subject: [PATCH 13/33] Follow-up to f342bfae4e3d09a2c6dbd873e0ed7c821988a897 as GCC did not like what MSVC was able to swallow. --- src/libslic3r/GCode.cpp | 1 - src/libslic3r/GCode/ToolOrdering.cpp | 66 ++++++++++++---------------- src/libslic3r/GCode/ToolOrdering.hpp | 11 +---- src/libslic3r/Print.cpp | 4 +- 4 files changed, 33 insertions(+), 49 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 46dcccd21..1dac959a5 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2204,7 +2204,6 @@ LayerResult GCode::process_layer( m_avoid_crossing_perimeters.disable_once(); } - std::vector instances_to_print = sort_print_object_instances(layers, ordering, single_object_instance_idx); // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 84e80a07f..99f3e9176 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -187,6 +187,21 @@ void ToolOrdering::initialize_layers(std::vector &zs) } } +// Decides whether this entity could be overridden +static [[nodiscard]] bool is_overriddable(const ExtrusionEntityCollection& eec, const LayerTools& lt, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) +{ + if (print_config.filament_soluble.get_at(lt.extruder(eec, region))) + return false; + + if (object.config().wipe_into_objects) + return true; + + if (!region.config().wipe_into_infill || eec.role() != erInternalInfill) + return false; + + return true; +} + // Collect extruders reuqired to print layers. void ToolOrdering::collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches) { @@ -232,7 +247,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto if (m_print_config_ptr) { // in this case complete_objects is false (see ToolOrdering constructors) something_nonoverriddable = false; for (const ExtrusionEntity *eec : layerm->perimeters()) // let's check if there are nonoverriddable entities - if (layer_tools.wiping_extrusions().is_overriddable(dynamic_cast(*eec), *m_print_config_ptr, object, region)) + if (is_overriddable(dynamic_cast(*eec), layer_tools, *m_print_config_ptr, object, region)) layer_tools.wiping_extrusions_nonconst().set_something_overridable(); else something_nonoverriddable = true; @@ -257,7 +272,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto has_infill = true; if (m_print_config_ptr) { - if (layer_tools.wiping_extrusions().is_overriddable(*fill, *m_print_config_ptr, object, region)) + if (is_overriddable(*fill, layer_tools, *m_print_config_ptr, object, region)) layer_tools.wiping_extrusions_nonconst().set_something_overridable(); else something_nonoverriddable = true; @@ -599,12 +614,6 @@ const LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) const return *it_layer_tools; } -static const LayerTools& layer_tools(const WipingExtrusions *self) -{ - char *ptr = (char*)(self) - offsetof(LayerTools, m_wiping_extrusions); - return *reinterpret_cast(ptr); -} - // This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual) void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies) { @@ -622,9 +631,9 @@ void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, size } // Finds first non-soluble extruder on the layer -int WipingExtrusions::first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const +static [[nodiscard]] int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config, const LayerTools& layer_tools) { - for (auto extruders_it = layer_tools(this).extruders.begin(); extruders_it != layer_tools(this).extruders.end(); ++extruders_it) + for (auto extruders_it = layer_tools.extruders.begin(); extruders_it != layer_tools.extruders.end(); ++extruders_it) if (!print_config.filament_soluble.get_at(*extruders_it)) return (*extruders_it); @@ -632,36 +641,20 @@ int WipingExtrusions::first_nonsoluble_extruder_on_layer(const PrintConfig& prin } // Finds last non-soluble extruder on the layer -int WipingExtrusions::last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const +static [[nodiscard]] int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config, const LayerTools& layer_tools) { - for (auto extruders_it = layer_tools(this).extruders.rbegin(); extruders_it != layer_tools(this).extruders.rend(); ++extruders_it) + for (auto extruders_it = layer_tools.extruders.rbegin(); extruders_it != layer_tools.extruders.rend(); ++extruders_it) if (!print_config.filament_soluble.get_at(*extruders_it)) return (*extruders_it); return (-1); } -// Decides whether this entity could be overridden -bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const -{ - if (print_config.filament_soluble.get_at(layer_tools(this).extruder(eec, region))) - return false; - - if (object.config().wipe_into_objects) - return true; - - if (!region.config().wipe_into_infill || eec.role() != erInternalInfill) - return false; - - return true; -} - // Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange // and returns volume that is left to be wiped on the wipe tower. // Switching from old_extruder to new_extruder, trying to wipe volume_to_wipe into not yet extruded extrusions, that may change material (overridable). -float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe) +float WipingExtrusions::mark_wiping_extrusions(const Print& print, const LayerTools <, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe) { - const LayerTools& lt = layer_tools(this); const float min_infill_volume = 0.f; // ignore infill with smaller volume than this if (! m_something_overridable || volume_to_wipe <= 0. || @@ -710,7 +703,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int for (const ExtrusionEntity* ee : layerm->fills()) { // iterate through all infill Collections auto* fill = dynamic_cast(ee); - if (!is_overriddable(*fill, print.config(), *object, region)) + if (!is_overriddable(*fill, lt, print.config(), *object, region)) continue; if (wipe_into_infill_only && ! print.config().infill_first) @@ -733,7 +726,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int { for (const ExtrusionEntity* ee : layerm->perimeters()) { auto* fill = dynamic_cast(ee); - if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume) { + if (is_overriddable(*fill, lt, print.config(), *object, region) && !is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume) { set_extruder_override(fill, copy, new_extruder, num_of_copies); if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f) // More material was purged already than asked for. @@ -755,14 +748,13 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int // that were not actually overridden. If they are part of a dedicated object, printing them with the extruder // they were initially assigned to might mean violating the perimeter-infill order. We will therefore go through // them again and make sure we override it. -void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) +void WipingExtrusions::ensure_perimeters_infills_order(const Print& print, const LayerTools <) { if (! m_something_overridable) return; - const LayerTools& lt = layer_tools(this); - unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config()); - unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config()); + unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config(), lt); + unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config(), lt); for (const PrintObject* object : print.objects()) { // Finds this layer: @@ -780,7 +772,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) for (const ExtrusionEntity* ee : layerm->fills()) { // iterate through all infill Collections auto* fill = dynamic_cast(ee); - if (!is_overriddable(*fill, print.config(), *object, region) + if (!is_overriddable(*fill, lt, print.config(), *object, region) || is_entity_overridden(fill, copy) ) continue; @@ -802,7 +794,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) // Now the same for perimeters - see comments above for explanation: for (const ExtrusionEntity* ee : layerm->perimeters()) { // iterate through all perimeter Collections auto* fill = dynamic_cast(ee); - if (is_overriddable(*fill, print.config(), *object, region) && ! is_entity_overridden(fill, copy)) + if (is_overriddable(*fill, lt, print.config(), *object, region) && ! is_entity_overridden(fill, copy)) set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies); } } diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index d0357baa0..4aa3dda4e 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -42,17 +42,13 @@ public: // This function goes through all infill entities, decides which ones will be used for wiping and // marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower: - float mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe); + float mark_wiping_extrusions(const Print& print, const LayerTools& lt, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe); - void ensure_perimeters_infills_order(const Print& print); + void ensure_perimeters_infills_order(const Print& print, const LayerTools& lt); - bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const; void set_something_overridable() { m_something_overridable = true; } private: - int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const; - int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const; - // This function is called from mark_wiping_extrusions and sets extruder that it should be printed with (-1 .. as usual) void set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies); @@ -115,9 +111,6 @@ private: friend class ToolOrdering; LayerTools(const coordf_t z) : print_z(z) {} - // for calculating offset of m_wiping_extrusions in LayerTools. - friend const LayerTools& layer_tools(const WipingExtrusions *self); - // This object holds list of extrusion that will be used for extruder wiping WipingExtrusions m_wiping_extrusions; }; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 8f32b6c31..611179bc0 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1208,7 +1208,7 @@ void Print::_make_wipe_tower() volume_to_wipe -= (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); // try to assign some infills/objects for the wiping: - volume_to_wipe = layer_tools.wiping_extrusions_nonconst().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe); + volume_to_wipe = layer_tools.wiping_extrusions_nonconst().mark_wiping_extrusions(*this, layer_tools, current_extruder_id, extruder_id, volume_to_wipe); // add back the minimal amount toforce on the wipe tower: volume_to_wipe += (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); @@ -1219,7 +1219,7 @@ void Print::_make_wipe_tower() current_extruder_id = extruder_id; } } - layer_tools.wiping_extrusions_nonconst().ensure_perimeters_infills_order(*this); + layer_tools.wiping_extrusions_nonconst().ensure_perimeters_infills_order(*this, layer_tools); if (&layer_tools == &m_wipe_tower_data.tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0) break; } From 28f8997dd311f0dcbfea0d1c2d8d15f4a79c16ae Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Nov 2022 17:26:48 +0100 Subject: [PATCH 14/33] Follow-up to f24b4e86a9612fcf4cba28a5ae9f24db13f1b171: Fixed compilation on OSX Fixed incorrect clear of LayerIslands. --- src/libslic3r/GCode.cpp | 3 ++- src/libslic3r/GCode/ToolOrdering.cpp | 6 +++--- src/libslic3r/Layer.cpp | 7 ++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 1dac959a5..f42ae4f83 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2399,7 +2399,8 @@ void GCode::process_layer_single_object( for (uint32_t perimeter_id : island.perimeters) if (const auto *eec = static_cast(layerm.perimeters().entities[perimeter_id]); shall_print_this_extrusion_collection(eec, region)) { - assert(! eec->can_reverse()); + // This may not apply to Arachne, but maybe the Arachne gap fill should disable reverse as well? + // assert(! eec->can_reverse()); if (first) { first = false; init_layer_delayed(); diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 99f3e9176..fc2e7ae2c 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -188,7 +188,7 @@ void ToolOrdering::initialize_layers(std::vector &zs) } // Decides whether this entity could be overridden -static [[nodiscard]] bool is_overriddable(const ExtrusionEntityCollection& eec, const LayerTools& lt, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) +[[nodiscard]] static bool is_overriddable(const ExtrusionEntityCollection& eec, const LayerTools& lt, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) { if (print_config.filament_soluble.get_at(lt.extruder(eec, region))) return false; @@ -631,7 +631,7 @@ void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, size } // Finds first non-soluble extruder on the layer -static [[nodiscard]] int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config, const LayerTools& layer_tools) +[[nodiscard]] static int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config, const LayerTools& layer_tools) { for (auto extruders_it = layer_tools.extruders.begin(); extruders_it != layer_tools.extruders.end(); ++extruders_it) if (!print_config.filament_soluble.get_at(*extruders_it)) @@ -641,7 +641,7 @@ static [[nodiscard]] int first_nonsoluble_extruder_on_layer(const PrintConfig& p } // Finds last non-soluble extruder on the layer -static [[nodiscard]] int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config, const LayerTools& layer_tools) +[[nodiscard]] static int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config, const LayerTools& layer_tools) { for (auto extruders_it = layer_tools.extruders.rbegin(); extruders_it != layer_tools.extruders.rend(); ++extruders_it) if (!print_config.filament_soluble.get_at(*extruders_it)) diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 95c9cf50e..f785757d0 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -398,6 +398,9 @@ void Layer::make_perimeters() layerm.m_fill_expolygons_composite_bboxes.clear(); }; + for (LayerSlice &lslice : this->lslices_ex) + lslice.islands.clear(); + for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) if (size_t region_id = layerm - m_regions.begin(); ! done[region_id]) { layer_region_reset_perimeters(**layerm); @@ -496,9 +499,6 @@ void Layer::sort_perimeters_into_islands( // If the current layer consists of multiple regions, then the fill_expolygons above are split by the source LayerRegion surfaces. const std::vector &layer_region_ids) { - for (LayerSlice &lslice : this->lslices_ex) - lslice.islands.clear(); - LayerRegion &this_layer_region = *m_regions[region_id]; // Bounding boxes of fill_expolygons. @@ -705,6 +705,7 @@ void Layer::sort_perimeters_into_islands( perimeter_slices_queue.pop_back(); } } + assert(perimeter_slices_queue.empty()); } void Layer::export_region_slices_to_svg(const char *path) const From 5eaec515bafe7cf20f7dacd37ece59cc154f0ddd Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Nov 2022 20:00:34 +0100 Subject: [PATCH 15/33] Follow-up to f24b4e86a9612fcf4cba28a5ae9f24db13f1b171: Refactoring of G-code export to LayerSlices / LayerIslands: Fixed some bugs --- src/libslic3r/GCode/ToolOrdering.cpp | 2 +- src/libslic3r/GCode/ToolOrdering.hpp | 7 ++++++- src/libslic3r/Layer.cpp | 25 +++++++++---------------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index fc2e7ae2c..ca4ddf4d5 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -667,7 +667,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, const LayerTo // we will sort objects so that dedicated for wiping are at the beginning: ConstPrintObjectPtrs object_list(print.objects().begin(), print.objects().end()); - std::sort(object_list.begin(), object_list.end(), [](const PrintObject* a, const PrintObject* b) { return a->config().wipe_into_objects; }); + std::sort(object_list.begin(), object_list.end(), [](const PrintObject* a, const PrintObject* b) { return a->config().wipe_into_objects && ! b->config().wipe_into_objects; }); // We will now iterate through // - first the dedicated objects to mark perimeters or infills (depending on infill_first) diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 4aa3dda4e..4fb6e56c0 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -30,7 +30,12 @@ public: } // When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place. - typedef boost::container::small_vector ExtruderPerCopy; + using ExtruderPerCopy = +#ifdef NDEBUG + boost::container::small_vector; +#else // NDEBUG + std::vector; +#endif // NDEBUG // This is called from GCode::process_layer_single_object() // Returns positive number if the extruder is overridden. diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index f785757d0..ebe3b8814 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -233,9 +233,9 @@ static void connect_layer_slices( } } for (int32_t islice = 0; islice < above.lslices_ex.size(); ++ islice) { - LayerSlice::Links &links1 = above.lslices_ex[islice].overlaps_above; + LayerSlice::Links &links1 = above.lslices_ex[islice].overlaps_below; for (LayerSlice::Link &link1 : links1) { - LayerSlice::Links &links2 = below.lslices_ex[link1.slice_idx].overlaps_below; + LayerSlice::Links &links2 = below.lslices_ex[link1.slice_idx].overlaps_above; assert(! std::binary_search(links2.begin(), links2.end(), link1, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; })); } } @@ -688,24 +688,17 @@ void Layer::sort_perimeters_into_islands( std::numeric_limits::max() : (lslices[lslice_idx].point_projection(point) - point).cast().squaredNorm(); }; - for (int lslice_idx = int(lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx) { + for (auto it_source_slice = perimeter_slices_queue.begin(); it_source_slice != perimeter_slices_queue.end(); ++ it_source_slice) { double d2min = std::numeric_limits::max(); - auto it_source_slice = perimeter_slices_queue.end(); - for (auto it = perimeter_slices_queue.begin(); it != perimeter_slices_queue.end(); ++ it) - if (double d2 = point_inside_surface_dist2(lslice_idx, it->second); d2 < d2min) { + int lslice_idx_min = -1; + for (int lslice_idx = int(lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx) + if (double d2 = point_inside_surface_dist2(lslice_idx, it_source_slice->second); d2 < d2min) { d2min = d2; - it_source_slice = it; + lslice_idx_min = lslice_idx; } - assert(it_source_slice != perimeter_slices_queue.end()); - if (it_source_slice != perimeter_slices_queue.end()) { - insert_into_island(lslice_idx, it_source_slice->first); - if (std::next(it_source_slice) != perimeter_slices_queue.end()) - // Remove the current slice & point pair from the queue. - *it_source_slice = perimeter_slices_queue.back(); - perimeter_slices_queue.pop_back(); - } + assert(lslice_idx_min != -1); + insert_into_island(lslice_idx_min, it_source_slice->first); } - assert(perimeter_slices_queue.empty()); } void Layer::export_region_slices_to_svg(const char *path) const From 9dca8403fe1ca4bd4aac67adaad5d1ef0fedd4a1 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 14 Nov 2022 15:17:04 +0100 Subject: [PATCH 16/33] ClipperLib: Optimized PointInPolygon() to calculate cross products with int64s instead of doubles. Polygon / ExPolygon: contains() reworked to use ClipperLib::PointInPolygon(). The Slic3r own implementation was not robust. Fixed test_perimeters after recent refactoring (sorting of extrusions into LayerIslands) --- src/clipper/clipper.cpp | 10 +++++----- src/clipper/clipper.hpp | 6 ++++-- src/libslic3r/ExPolygon.cpp | 18 ++++++++++-------- src/libslic3r/ExPolygon.hpp | 8 ++++---- src/libslic3r/Layer.cpp | 2 +- src/libslic3r/Polygon.cpp | 28 ++++++++++------------------ src/libslic3r/Polygon.hpp | 1 + tests/fff_print/test_perimeters.cpp | 2 ++ 8 files changed, 37 insertions(+), 38 deletions(-) diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index 2ca643882..518b4b7c3 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -225,7 +225,7 @@ int PointInPolygon(const IntPoint &pt, const Path &path) if (ipNext.x() > pt.x()) result = 1 - result; else { - double d = (double)(ip.x() - pt.x()) * (ipNext.y() - pt.y()) - (double)(ipNext.x() - pt.x()) * (ip.y() - pt.y()); + auto d = CrossProductType(ip.x() - pt.x()) * CrossProductType(ipNext.y() - pt.y()) - CrossProductType(ipNext.x() - pt.x()) * CrossProductType(ip.y() - pt.y()); if (!d) return -1; if ((d > 0) == (ipNext.y() > ip.y())) result = 1 - result; } @@ -233,7 +233,7 @@ int PointInPolygon(const IntPoint &pt, const Path &path) { if (ipNext.x() > pt.x()) { - double d = (double)(ip.x() - pt.x()) * (ipNext.y() - pt.y()) - (double)(ipNext.x() - pt.x()) * (ip.y() - pt.y()); + auto d = CrossProductType(ip.x() - pt.x()) * CrossProductType(ipNext.y() - pt.y()) - CrossProductType(ipNext.x() - pt.x()) * CrossProductType(ip.y() - pt.y()); if (!d) return -1; if ((d > 0) == (ipNext.y() > ip.y())) result = 1 - result; } @@ -246,7 +246,7 @@ int PointInPolygon(const IntPoint &pt, const Path &path) //------------------------------------------------------------------------------ // Called by Poly2ContainsPoly1() -int PointInPolygon (const IntPoint &pt, OutPt *op) +int PointInPolygon(const IntPoint &pt, OutPt *op) { //returns 0 if false, +1 if true, -1 if pt ON polygon boundary int result = 0; @@ -265,7 +265,7 @@ int PointInPolygon (const IntPoint &pt, OutPt *op) if (op->Next->Pt.x() > pt.x()) result = 1 - result; else { - double d = (double)(op->Pt.x() - pt.x()) * (op->Next->Pt.y() - pt.y()) - (double)(op->Next->Pt.x() - pt.x()) * (op->Pt.y() - pt.y()); + auto d = CrossProductType(op->Pt.x() - pt.x()) * CrossProductType(op->Next->Pt.y() - pt.y()) - CrossProductType(op->Next->Pt.x() - pt.x()) * CrossProductType(op->Pt.y() - pt.y()); if (!d) return -1; if ((d > 0) == (op->Next->Pt.y() > op->Pt.y())) result = 1 - result; } @@ -273,7 +273,7 @@ int PointInPolygon (const IntPoint &pt, OutPt *op) { if (op->Next->Pt.x() > pt.x()) { - double d = (double)(op->Pt.x() - pt.x()) * (op->Next->Pt.y() - pt.y()) - (double)(op->Next->Pt.x() - pt.x()) * (op->Pt.y() - pt.y()); + auto d = CrossProductType(op->Pt.x() - pt.x()) * CrossProductType(op->Next->Pt.y() - pt.y()) - CrossProductType(op->Next->Pt.x() - pt.x()) * CrossProductType(op->Pt.y() - pt.y()); if (!d) return -1; if ((d > 0) == (op->Next->Pt.y() > op->Pt.y())) result = 1 - result; } diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index b1dae3c24..641476c8b 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -85,9 +85,11 @@ enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; // Point coordinate type #ifdef CLIPPERLIB_INT32 // Coordinates and their differences (vectors of the coordinates) have to fit int32_t. - typedef int32_t cInt; + using cInt = int32_t; + using CrossProductType = int64_t; #else - typedef int64_t cInt; + using cInt = int64_t; + using CrossProductType = double; // Maximum cInt value to allow a cross product calculation using 32bit expressions. static constexpr cInt const loRange = 0x3FFFFFFF; // 0x3FFFFFFF = 1 073 741 823 // Maximum allowed cInt value. diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 6d9e21f28..311194102 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -100,10 +100,12 @@ bool ExPolygon::contains(const Polylines &polylines) const bool ExPolygon::contains(const Point &point) const { - if (! this->contour.contains(point)) + if (! Slic3r::contains(contour, point, true)) + // Outside the outer contour, not on the contour boundary. return false; for (const Polygon &hole : this->holes) - if (hole.contains(point)) + if (Slic3r::contains(hole, point, false)) + // Inside a hole, not on the hole boundary. return false; return true; } @@ -114,13 +116,13 @@ bool ExPolygon::contains_b(const Point &point) const return this->contains(point) || this->has_boundary_point(point); } -bool -ExPolygon::has_boundary_point(const Point &point) const +bool ExPolygon::has_boundary_point(const Point &point) const { - if (this->contour.has_boundary_point(point)) return true; - for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) { - if (h->has_boundary_point(point)) return true; - } + if (this->contour.has_boundary_point(point)) + return true; + for (const Polygon &hole : this->holes) + if (hole.has_boundary_point(point)) + return true; return false; } diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index d21b4db12..c0f3d95a4 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -373,14 +373,14 @@ inline void expolygons_append(ExPolygons &dst, ExPolygons &&src) inline void expolygons_rotate(ExPolygons &expolys, double angle) { - for (ExPolygons::iterator p = expolys.begin(); p != expolys.end(); ++p) - p->rotate(angle); + for (ExPolygon &expoly : expolys) + expoly.rotate(angle); } inline bool expolygons_contain(ExPolygons &expolys, const Point &pt) { - for (ExPolygons::iterator p = expolys.begin(); p != expolys.end(); ++p) - if (p->contains(pt)) + for (const ExPolygon &expoly : expolys) + if (expoly.contains(pt)) return true; return false; } diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index ebe3b8814..a138b6d7d 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -691,7 +691,7 @@ void Layer::sort_perimeters_into_islands( for (auto it_source_slice = perimeter_slices_queue.begin(); it_source_slice != perimeter_slices_queue.end(); ++ it_source_slice) { double d2min = std::numeric_limits::max(); int lslice_idx_min = -1; - for (int lslice_idx = int(lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx) + for (int lslice_idx = int(lslices_ex.size()) - 1; lslice_idx >= 0; -- lslice_idx) if (double d2 = point_inside_surface_dist2(lslice_idx, it_source_slice->second); d2 < d2min) { d2min = d2; lslice_idx_min = lslice_idx; diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 361234bd2..26b326b1c 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -89,26 +89,9 @@ void Polygon::douglas_peucker(double tolerance) } // Does an unoriented polygon contain a point? -// Tested by counting intersections along a horizontal line. bool Polygon::contains(const Point &p) const { - // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html - bool result = false; - Points::const_iterator i = this->points.begin(); - Points::const_iterator j = this->points.end() - 1; - for (; i != this->points.end(); j = i ++) - if (i->y() > p.y() != j->y() > p.y()) -#if 1 - if (Vec2d v = (*j - *i).cast(); - // p.x() is below the line - p.x() - i->x() < double(p.y() - i->y()) * v.x() / v.y()) -#else - // Orientation predicated relative to i-th point. - if (double orient = (double)(p.x() - i->x()) * (double)(j->y() - i->y()) - (double)(p.y() - i->y()) * (double)(j->x() - i->x()); - (i->y() > j->y()) ? (orient > 0.) : (orient < 0.)) -#endif - result = !result; - return result; + return Slic3r::contains(*this, p, true); } // this only works on CCW polygons as CW will be ripped out by Clipper's simplify_polygons() @@ -536,6 +519,15 @@ bool polygons_match(const Polygon &l, const Polygon &r) return true; } +bool contains(const Polygon &polygon, const Point &p, bool border_result) +{ + if (const int poly_count_inside = ClipperLib::PointInPolygon(p, polygon.points); + poly_count_inside == -1) + return border_result; + else + return (poly_count_inside % 2) == 1; +} + bool contains(const Polygons &polygons, const Point &p, bool border_result) { int poly_count_inside = 0; diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index a17b91188..ccfe213e8 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -255,6 +255,7 @@ inline Polygons to_polygons(std::vector &&paths) bool polygons_match(const Polygon &l, const Polygon &r); // Returns true if inside. Returns border_result if on boundary. +bool contains(const Polygon& polygon, const Point& p, bool border_result = true); bool contains(const Polygons& polygons, const Point& p, bool border_result = true); Polygon make_circle(double radius, double error); diff --git a/tests/fff_print/test_perimeters.cpp b/tests/fff_print/test_perimeters.cpp index 3c817be00..d8f0660ca 100644 --- a/tests/fff_print/test_perimeters.cpp +++ b/tests/fff_print/test_perimeters.cpp @@ -470,6 +470,8 @@ SCENARIO("Some weird coverage test", "[Perimeters]") LayerRegion *layerm = layer->get_region(0); layerm->m_slices.clear(); layerm->m_slices.append({ expolygon }, stInternal); + layer->lslices = { expolygon }; + layer->lslices_ex = { { get_extents(expolygon) } }; // make perimeters layer->make_perimeters(); From f1c0c61895b665dd14792fb97117e4f3499bf0de Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 14 Nov 2022 19:01:17 +0100 Subject: [PATCH 17/33] Refactored Point / MultiPoint / Polyline / Polygon: 1) Removed virtual methods. There was not really need for them. 2) Some of the virtual methods were using conversion to Lines, which was unnecessary and expensive. 3) Removed some nearest element search methods from Point. --- .../Arachne/SkeletalTrapezoidation.cpp | 2 +- src/libslic3r/Arachne/utils/VoronoiUtils.cpp | 2 +- src/libslic3r/Arachne/utils/linearAlg2D.hpp | 6 +- src/libslic3r/ExPolygon.cpp | 27 ++-- src/libslic3r/ExPolygon.hpp | 10 +- src/libslic3r/Fill/FillConcentric.cpp | 4 +- src/libslic3r/GCode.cpp | 3 +- src/libslic3r/Line.hpp | 4 +- src/libslic3r/MultiPoint.cpp | 67 ---------- src/libslic3r/MultiPoint.hpp | 11 -- src/libslic3r/PerimeterGenerator.cpp | 4 +- src/libslic3r/Point.cpp | 115 ++++-------------- src/libslic3r/Point.hpp | 17 ++- src/libslic3r/Polygon.cpp | 75 +++++++++++- src/libslic3r/Polygon.hpp | 26 ++-- src/libslic3r/Polyline.cpp | 26 +++- src/libslic3r/Polyline.hpp | 13 +- xs/t/03_point.t | 20 +-- xs/xsp/Point.xsp | 5 +- 19 files changed, 173 insertions(+), 264 deletions(-) diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index 04dede546..04a1042d8 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -274,7 +274,7 @@ std::vector SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_ Point right_point = VoronoiUtils::getSourcePoint(*right_cell, segments); coord_t d = (right_point - left_point).cast().norm(); Point middle = (left_point + right_point) / 2; - Point x_axis_dir = Point(right_point - left_point).rotate_90_degree_ccw(); + Point x_axis_dir = perp(Point(right_point - left_point)); coord_t x_axis_length = x_axis_dir.cast().norm(); const auto projected_x = [x_axis_dir, x_axis_length, middle](Point from) //Project a point on the edge. diff --git a/src/libslic3r/Arachne/utils/VoronoiUtils.cpp b/src/libslic3r/Arachne/utils/VoronoiUtils.cpp index 82bd79523..069e1f5ad 100644 --- a/src/libslic3r/Arachne/utils/VoronoiUtils.cpp +++ b/src/libslic3r/Arachne/utils/VoronoiUtils.cpp @@ -165,7 +165,7 @@ std::vector VoronoiUtils::discretizeParabola(const Point& p, const Segmen Line(a, b).distance_to_infinite_squared(p, &pxx); const Point ppxx = pxx - p; const coord_t d = ppxx.cast().norm(); - const PointMatrix rot = PointMatrix(ppxx.rotate_90_degree_ccw()); + const PointMatrix rot = PointMatrix(perp(ppxx)); if (d == 0) { diff --git a/src/libslic3r/Arachne/utils/linearAlg2D.hpp b/src/libslic3r/Arachne/utils/linearAlg2D.hpp index 797bae0b9..304984b1c 100644 --- a/src/libslic3r/Arachne/utils/linearAlg2D.hpp +++ b/src/libslic3r/Arachne/utils/linearAlg2D.hpp @@ -38,15 +38,11 @@ inline static bool isInsideCorner(const Point &a, const Point &b, const Point &c return (p0.cast() * int64_t(len) / _len).cast(); }; - auto rotate_90_degree_ccw = [](const Vec2d &p) -> Vec2d { - return {-p.y(), p.x()}; - }; - constexpr coord_t normal_length = 10000; //Create a normal vector of reasonable length in order to reduce rounding error. const Point ba = normal(a - b, normal_length); const Point bc = normal(c - b, normal_length); const Vec2d bq = query_point.cast() - b.cast(); - const Vec2d perpendicular = rotate_90_degree_ccw(bq); //The query projects to this perpendicular to coordinate 0. + const Vec2d perpendicular = perp(bq); //The query projects to this perpendicular to coordinate 0. const double project_a_perpendicular = ba.cast().dot(perpendicular); //Project vertex A on the perpendicular line. const double project_c_perpendicular = bc.cast().dot(perpendicular); //Project vertex C on the perpendicular line. diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 311194102..61c204044 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -98,30 +98,24 @@ bool ExPolygon::contains(const Polylines &polylines) const return pl_out.empty(); } -bool ExPolygon::contains(const Point &point) const +bool ExPolygon::contains(const Point &point, bool border_result /* = true */) const { - if (! Slic3r::contains(contour, point, true)) + if (! Slic3r::contains(contour, point, border_result)) // Outside the outer contour, not on the contour boundary. return false; for (const Polygon &hole : this->holes) - if (Slic3r::contains(hole, point, false)) + if (Slic3r::contains(hole, point, ! border_result)) // Inside a hole, not on the hole boundary. return false; return true; } -// inclusive version of contains() that also checks whether point is on boundaries -bool ExPolygon::contains_b(const Point &point) const +bool ExPolygon::on_boundary(const Point &point, double eps) const { - return this->contains(point) || this->has_boundary_point(point); -} - -bool ExPolygon::has_boundary_point(const Point &point) const -{ - if (this->contour.has_boundary_point(point)) + if (this->contour.on_boundary(point, eps)) return true; for (const Polygon &hole : this->holes) - if (hole.has_boundary_point(point)) + if (hole.on_boundary(point, eps)) return true; return false; } @@ -148,6 +142,9 @@ Point ExPolygon::point_projection(const Point &point) const bool ExPolygon::overlaps(const ExPolygon &other) const { + if (this->empty() || other.empty()) + return false; + #if 0 BoundingBox bbox = get_extents(other); bbox.merge(get_extents(*this)); @@ -164,7 +161,7 @@ bool ExPolygon::overlaps(const ExPolygon &other) const if (! pl_out.empty()) return true; //FIXME ExPolygon::overlaps() shall be commutative, it is not! - return ! other.contour.points.empty() && this->contains_b(other.contour.points.front()); + return this->contains(other.contour.points.front()); } void ExPolygon::simplify_p(double tolerance, Polygons* polygons) const @@ -241,7 +238,7 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl call, so we keep the inner point until we perform the second intersection() as well */ Point new_front = polyline.points.front(); Point new_back = polyline.points.back(); - if (polyline.endpoints.first && !this->has_boundary_point(new_front)) { + if (polyline.endpoints.first && !this->on_boundary(new_front, SCALED_EPSILON)) { Vec2d p1 = polyline.points.front().cast(); Vec2d p2 = polyline.points[1].cast(); // prevent the line from touching on the other side, otherwise intersection() might return that solution @@ -251,7 +248,7 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl p1 -= (p2 - p1).normalized() * max_width; this->contour.intersection(Line(p1.cast(), p2.cast()), &new_front); } - if (polyline.endpoints.second && !this->has_boundary_point(new_back)) { + if (polyline.endpoints.second && !this->on_boundary(new_back, SCALED_EPSILON)) { Vec2d p1 = (polyline.points.end() - 2)->cast(); Vec2d p2 = polyline.points.back().cast(); // prevent the line from touching on the other side, otherwise intersection() might return that solution diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index c0f3d95a4..58f0d33e9 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -51,9 +51,9 @@ public: bool contains(const Line &line) const; bool contains(const Polyline &polyline) const; bool contains(const Polylines &polylines) const; - bool contains(const Point &point) const; - bool contains_b(const Point &point) const; - bool has_boundary_point(const Point &point) const; + bool contains(const Point &point, bool border_result = true) const; + // Approximate on boundary test. + bool on_boundary(const Point &point, double eps) const; // Projection of a point onto the polygon. Point point_projection(const Point &point) const; @@ -377,10 +377,10 @@ inline void expolygons_rotate(ExPolygons &expolys, double angle) expoly.rotate(angle); } -inline bool expolygons_contain(ExPolygons &expolys, const Point &pt) +inline bool expolygons_contain(ExPolygons &expolys, const Point &pt, bool border_result = true) { for (const ExPolygon &expoly : expolys) - if (expoly.contains(pt)) + if (expoly.contains(pt, border_result)) return true; return false; } diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp index 69f530720..7b005ee35 100644 --- a/src/libslic3r/Fill/FillConcentric.cpp +++ b/src/libslic3r/Fill/FillConcentric.cpp @@ -40,7 +40,7 @@ void FillConcentric::_fill_surface_single( size_t iPathFirst = polylines_out.size(); Point last_pos(0, 0); for (const Polygon &loop : loops) { - polylines_out.emplace_back(loop.split_at_index(last_pos.nearest_point_index(loop.points))); + polylines_out.emplace_back(loop.split_at_index(nearest_point_index(loop.points, last_pos))); last_pos = polylines_out.back().last_point(); } @@ -100,7 +100,7 @@ void FillConcentric::_fill_surface_single(const FillParams ¶ms, 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); + int nearest_idx = nearest_point_index(thick_polyline.points, last_pos); 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()); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f42ae4f83..12342ddca 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -116,8 +116,7 @@ namespace Slic3r { Point pos = Point::new_scale(writer_pos(0), writer_pos(1)); // find standby point - Point standby_point; - pos.nearest_point(this->standby_points, &standby_point); + Point standby_point = nearest_point(this->standby_points, pos).first; /* We don't call gcodegen.travel_to() because we don't need retraction (it was already triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index f9a8977d5..f7a14c251 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -54,11 +54,11 @@ double distance_to_squared(const L &line, const Vec, Scalar> &point, V // We find projection of this point onto the line. // It falls where t = [(this-a) . (b-a)] / |b-a|^2 const double t = va.dot(v) / l2; - if (t < 0.0) { + if (t <= 0.0) { // beyond the 'a' end of the segment *nearest_point = get_a(line); return va.squaredNorm(); - } else if (t > 1.0) { + } else if (t >= 1.0) { // beyond the 'b' end of the segment *nearest_point = get_b(line); return (point - get_b(line)).template cast().squaredNorm(); diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index 5ed9eb23c..f18720bd6 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -45,16 +45,6 @@ void MultiPoint::rotate(double angle, const Point ¢er) } } -double MultiPoint::length() const -{ - const Lines& lines = this->lines(); - double len = 0; - for (auto it = lines.cbegin(); it != lines.cend(); ++it) { - len += it->length(); - } - return len; -} - int MultiPoint::find_point(const Point &point) const { for (const Point &pt : this->points) @@ -81,12 +71,6 @@ int MultiPoint::find_point(const Point &point, double scaled_epsilon) const return dist2_min < eps2 ? idx_min : -1; } -bool MultiPoint::has_boundary_point(const Point &point) const -{ - double dist = (point.projection_onto(*this) - point).cast().norm(); - return dist < SCALED_EPSILON; -} - BoundingBox MultiPoint::bounding_box() const { return BoundingBox(this->points); @@ -119,49 +103,6 @@ bool MultiPoint::remove_duplicate_points() return false; } -bool MultiPoint::intersection(const Line& line, Point* intersection) const -{ - Lines lines = this->lines(); - for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) { - if (it->intersection(line, intersection)) return true; - } - return false; -} - -bool MultiPoint::first_intersection(const Line& line, Point* intersection) const -{ - bool found = false; - double dmin = 0.; - for (const Line &l : this->lines()) { - Point ip; - if (l.intersection(line, &ip)) { - if (! found) { - found = true; - dmin = (line.a - ip).cast().norm(); - *intersection = ip; - } else { - double d = (line.a - ip).cast().norm(); - if (d < dmin) { - dmin = d; - *intersection = ip; - } - } - } - } - return found; -} - -bool MultiPoint::intersections(const Line &line, Points *intersections) const -{ - size_t intersections_size = intersections->size(); - for (const Line &polygon_line : this->lines()) { - Point intersection; - if (polygon_line.intersection(line, &intersection)) - intersections->emplace_back(std::move(intersection)); - } - return intersections->size() > intersections_size; -} - std::vector MultiPoint::_douglas_peucker(const std::vector& pts, const double tolerance) { std::vector result_pts; @@ -363,14 +304,6 @@ void MultiPoint3::translate(const Point& vector) this->translate(vector(0), vector(1)); } -double MultiPoint3::length() const -{ - double len = 0.0; - for (const Line3& line : this->lines()) - len += line.length(); - return len; -} - BoundingBox3 MultiPoint3::bounding_box() const { return BoundingBox3(points); diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index bc9cf761d..778b30c57 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -18,7 +18,6 @@ public: Points points; MultiPoint() = default; - virtual ~MultiPoint() = default; MultiPoint(const MultiPoint &other) : points(other.points) {} MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {} MultiPoint(std::initializer_list list) : points(list) {} @@ -37,11 +36,8 @@ public: const Point& front() const { return this->points.front(); } const Point& back() const { return this->points.back(); } const Point& first_point() const { return this->front(); } - virtual const Point& last_point() const = 0; - virtual Lines lines() const = 0; size_t size() const { return points.size(); } bool empty() const { return points.empty(); } - double length() const; bool is_valid() const { return this->points.size() >= 2; } // Return index of a polygon point exactly equal to point. @@ -50,7 +46,6 @@ public: // Return index of the closest point to point closer than scaled_epsilon. // Return -1 if no such point exists. int find_point(const Point &point, const double scaled_epsilon) const; - bool has_boundary_point(const Point &point) const; int closest_point_index(const Point &point) const { int idx = -1; if (! this->points.empty()) { @@ -86,10 +81,6 @@ public: } } - bool intersection(const Line& line, Point* intersection) const; - bool first_intersection(const Line& line, Point* intersection) const; - bool intersections(const Line &line, Points *intersections) const; - static Points _douglas_peucker(const Points &points, const double tolerance); static Points visivalingam(const Points& pts, const double& tolerance); @@ -110,8 +101,6 @@ public: void translate(double x, double y); void translate(const Point& vector); - virtual Lines3 lines() const = 0; - double length() const; bool is_valid() const { return this->points.size() >= 2; } BoundingBox3 bounding_box() const; diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 92759238e..e864a37ad 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -405,8 +405,8 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con Point prev(subject.front().x(), subject.front().y()); for (auto it = std::next(subject.begin()); it != subject.end(); ++it) { Point curr(it->x(), it->y()); - Point projected_pt = pt.projection_onto(Line(prev, curr)); - if (double dist_sqr = (projected_pt - pt).cast().squaredNorm(); dist_sqr < dist_sqr_min) { + Point projected_pt; + if (double dist_sqr = line_alg::distance_to_squared(Line(prev, curr), pt, &projected_pt); dist_sqr < dist_sqr_min) { dist_sqr_min = dist_sqr; projected_pt_min = projected_pt; it_min = std::prev(it); diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index 9f56f96d6..a1145a952 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -57,98 +57,6 @@ void Point::rotate(double angle, const Point ¢er) (*this)(1) = (coord_t)round( (double)center(1) + c * dy + s * dx ); } -int Point::nearest_point_index(const Points &points) const -{ - PointConstPtrs p; - p.reserve(points.size()); - for (Points::const_iterator it = points.begin(); it != points.end(); ++it) - p.push_back(&*it); - return this->nearest_point_index(p); -} - -int Point::nearest_point_index(const PointConstPtrs &points) const -{ - int idx = -1; - double distance = -1; // double because long is limited to 2147483647 on some platforms and it's not enough - - for (PointConstPtrs::const_iterator it = points.begin(); it != points.end(); ++it) { - /* If the X distance of the candidate is > than the total distance of the - best previous candidate, we know we don't want it */ - double d = sqr((*this)(0) - (*it)->x()); - if (distance != -1 && d > distance) continue; - - /* If the Y distance of the candidate is > than the total distance of the - best previous candidate, we know we don't want it */ - d += sqr((*this)(1) - (*it)->y()); - if (distance != -1 && d > distance) continue; - - idx = it - points.begin(); - distance = d; - - if (distance < EPSILON) break; - } - - return idx; -} - -int Point::nearest_point_index(const PointPtrs &points) const -{ - PointConstPtrs p; - p.reserve(points.size()); - for (PointPtrs::const_iterator it = points.begin(); it != points.end(); ++it) - p.push_back(*it); - return this->nearest_point_index(p); -} - -bool Point::nearest_point(const Points &points, Point* point) const -{ - int idx = this->nearest_point_index(points); - if (idx == -1) return false; - *point = points.at(idx); - return true; -} - -Point Point::projection_onto(const MultiPoint &poly) const -{ - Point running_projection = poly.first_point(); - double running_min = (running_projection - *this).cast().norm(); - - Lines lines = poly.lines(); - for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { - Point point_temp = this->projection_onto(*line); - if ((point_temp - *this).cast().norm() < running_min) { - running_projection = point_temp; - running_min = (running_projection - *this).cast().norm(); - } - } - return running_projection; -} - -Point Point::projection_onto(const Line &line) const -{ - if (line.a == line.b) return line.a; - - /* - (Ported from VisiLibity by Karl J. Obermeyer) - The projection of point_temp onto the line determined by - line_segment_temp can be represented as an affine combination - expressed in the form projection of - Point = theta*line_segment_temp.first + (1.0-theta)*line_segment_temp.second. - If theta is outside the interval [0,1], then one of the Line_Segment's endpoints - must be closest to calling Point. - */ - double lx = (double)(line.b(0) - line.a(0)); - double ly = (double)(line.b(1) - line.a(1)); - double theta = ( (double)(line.b(0) - (*this)(0))*lx + (double)(line.b(1)- (*this)(1))*ly ) - / ( sqr(lx) + sqr(ly) ); - - if (0.0 <= theta && theta <= 1.0) - return (theta * line.a.cast() + (1.0-theta) * line.b.cast()).cast(); - - // Else pick closest endpoint. - return ((line.a - *this).cast().squaredNorm() < (line.b - *this).cast().squaredNorm()) ? line.a : line.b; -} - bool has_duplicate_points(std::vector &&pts) { std::sort(pts.begin(), pts.end()); @@ -179,6 +87,29 @@ BoundingBoxf get_extents(const std::vector &pts) return bbox; } +int nearest_point_index(const Points &points, const Point &pt) +{ + int64_t distance = std::numeric_limits::max(); + int idx = -1; + + for (const Point &pt2 : points) { + // If the X distance of the candidate is > than the total distance of the + // best previous candidate, we know we don't want it. + int64_t d = sqr(pt2.x() - pt.x()); + if (d < distance) { + // If the Y distance of the candidate is > than the total distance of the + // best previous candidate, we know we don't want it. + d += sqr(pt2.y() - pt.y()); + if (d < distance) { + idx = &pt2 - points.data(); + distance = d; + } + } + } + + return idx; +} + std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf) { return stm << pointf(0) << "," << pointf(1); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 949ddbad1..3d0bae2c3 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -17,8 +17,6 @@ namespace Slic3r { class BoundingBox; class BoundingBoxf; -class Line; -class MultiPoint; class Point; using Vector = Point; @@ -183,13 +181,6 @@ public: Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } Point rotated(double cos_a, double sin_a) const { Point res(*this); res.rotate(cos_a, sin_a); return res; } Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; } - Point rotate_90_degree_ccw() const { return Point(-this->y(), this->x()); } - int nearest_point_index(const Points &points) const; - int nearest_point_index(const PointConstPtrs &points) const; - int nearest_point_index(const PointPtrs &points) const; - bool nearest_point(const Points &points, Point* point) const; - Point projection_onto(const MultiPoint &poly) const; - Point projection_onto(const Line &line) const; }; inline bool operator<(const Point &l, const Point &r) @@ -242,6 +233,14 @@ BoundingBox get_extents(const Points &pts); BoundingBox get_extents(const std::vector &pts); BoundingBoxf get_extents(const std::vector &pts); +int nearest_point_index(const Points &points, const Point &pt); + +inline std::pair nearest_point(const Points &points, const Point &pt) +{ + int idx = nearest_point_index(points, pt); + return idx == -1 ? std::make_pair(Point(), false) : std::make_pair(points[idx], true); +} + // Test for duplicate points in a vector of points. // The points are copied, sorted and checked for duplicates globally. bool has_duplicate_points(std::vector &&pts); diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 26b326b1c..717e25aed 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -6,6 +6,17 @@ namespace Slic3r { +double Polygon::length() const +{ + double l = 0; + if (this->points.size() > 1) { + l = (this->points.back() - this->points.front()).cast().norm(); + for (size_t i = 1; i < this->points.size(); ++ i) + l += (this->points[i] - this->points[i - 1]).cast().norm(); + } + return l; +} + Lines Polygon::lines() const { return to_lines(*this); @@ -88,12 +99,6 @@ void Polygon::douglas_peucker(double tolerance) this->points = std::move(p); } -// Does an unoriented polygon contain a point? -bool Polygon::contains(const Point &p) const -{ - return Slic3r::contains(*this, p, true); -} - // this only works on CCW polygons as CW will be ripped out by Clipper's simplify_polygons() Polygons Polygon::simplify(double tolerance) const { @@ -150,6 +155,64 @@ Point Polygon::centroid() const return Point(Vec2d(c / (3. * area_sum))); } +bool Polygon::intersection(const Line &line, Point *intersection) const +{ + if (this->points.size() < 2) + return false; + if (Line(this->points.front(), this->points.back()).intersection(line, intersection)) + return true; + for (size_t i = 1; i < this->points.size(); ++ i) + if (Line(this->points[i - 1], this->points[i]).intersection(line, intersection)) + return true; + return false; +} + +bool Polygon::first_intersection(const Line& line, Point* intersection) const +{ + if (this->points.size() < 2) + return false; + + bool found = false; + double dmin = 0.; + Line l(this->points.back(), this->points.front()); + for (size_t i = 0; i < this->points.size(); ++ i) { + l.b = this->points[i]; + Point ip; + if (l.intersection(line, &ip)) { + if (! found) { + found = true; + dmin = (line.a - ip).cast().squaredNorm(); + *intersection = ip; + } else { + double d = (line.a - ip).cast().squaredNorm(); + if (d < dmin) { + dmin = d; + *intersection = ip; + } + } + } + l.a = l.b; + } + return found; +} + +bool Polygon::intersections(const Line &line, Points *intersections) const +{ + if (this->points.size() < 2) + return false; + + size_t intersections_size = intersections->size(); + Line l(this->points.back(), this->points.front()); + for (size_t i = 0; i < this->points.size(); ++ i) { + l.b = this->points[i]; + Point intersection; + if (l.intersection(line, &intersection)) + intersections->emplace_back(std::move(intersection)); + l.a = l.b; + } + return intersections->size() > intersections_size; +} + // Filter points from poly to the output with the help of FilterFn. // filter function receives two vectors: // v1: this_point - previous_point diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index ccfe213e8..8a4a2ba0a 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -15,11 +15,14 @@ using Polygons = std::vector; using PolygonPtrs = std::vector; using ConstPolygonPtrs = std::vector; +// Returns true if inside. Returns border_result if on boundary. +bool contains(const Polygon& polygon, const Point& p, bool border_result = true); +bool contains(const Polygons& polygons, const Point& p, bool border_result = true); + class Polygon : public MultiPoint { public: Polygon() = default; - ~Polygon() override = default; explicit Polygon(const Points &points) : MultiPoint(points) {} Polygon(std::initializer_list points) : MultiPoint(points) {} Polygon(const Polygon &other) : MultiPoint(other.points) {} @@ -38,9 +41,10 @@ public: const Point& operator[](Points::size_type idx) const { return this->points[idx]; } // last point == first point for polygons - const Point& last_point() const override { return this->points.front(); } + const Point& last_point() const { return this->points.front(); } - Lines lines() const override; + double length() const; + Lines lines() const; Polyline split_at_vertex(const Point &point) const; // Split a closed polygon into an open polyline, with the split point duplicated at both ends. Polyline split_at_index(int index) const; @@ -58,13 +62,21 @@ public: void douglas_peucker(double tolerance); // Does an unoriented polygon contain a point? - // Tested by counting intersections along a horizontal line. - bool contains(const Point &point) const; + bool contains(const Point &point) const { return Slic3r::contains(*this, point, true); } + // Approximate on boundary test. + bool on_boundary(const Point &point, double eps) const + { return (this->point_projection(point) - point).cast().squaredNorm() < eps * eps; } + Polygons simplify(double tolerance) const; void simplify(double tolerance, Polygons &polygons) const; void densify(float min_length, std::vector* lengths = nullptr); void triangulate_convex(Polygons* polygons) const; Point centroid() const; + + bool intersection(const Line& line, Point* intersection) const; + bool first_intersection(const Line& line, Point* intersection) const; + bool intersections(const Line &line, Points *intersections) const; + // Considering CCW orientation of this polygon, find all convex resp. concave points // with the angle at the vertex larger than a threshold. // Zero angle_threshold means to accept all convex resp. concave points. @@ -254,10 +266,6 @@ inline Polygons to_polygons(std::vector &&paths) // however their contours may be rotated. bool polygons_match(const Polygon &l, const Polygon &r); -// Returns true if inside. Returns border_result if on boundary. -bool contains(const Polygon& polygon, const Point& p, bool border_result = true); -bool contains(const Polygons& polygons, const Point& p, bool border_result = true); - Polygon make_circle(double radius, double error); Polygon make_circle_num_segments(double radius, size_t num_segments); diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 42d89c882..e2dcabfe1 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -19,6 +19,14 @@ const Point& Polyline::leftmost_point() const return *p; } +double Polyline::length() const +{ + double l = 0; + for (size_t i = 1; i < this->points.size(); ++ i) + l += (this->points[i] - this->points[i - 1]).cast().norm(); + return l; +} + Lines Polyline::lines() const { Lines lines; @@ -146,9 +154,8 @@ void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const auto min_point_it = this->points.cbegin(); Point prev = this->points.front(); for (auto it = this->points.cbegin() + 1; it != this->points.cend(); ++ it) { - Point proj = point.projection_onto(Line(prev, *it)); - auto d2 = (proj - point).cast().squaredNorm(); - if (d2 < min_dist2) { + Point proj; + if (double d2 = line_alg::distance_to_squared(Line(prev, *it), point, &proj); d2 < min_dist2) { min_dist2 = d2; min_point_it = it; } @@ -235,9 +242,8 @@ std::pair foot_pt(const Points &polyline, const Point &pt) auto it = polyline.begin(); auto it_proj = polyline.begin(); for (++ it; it != polyline.end(); ++ it) { - Point foot_pt = pt.projection_onto(Line(prev, *it)); - double d2 = (foot_pt - pt).cast().squaredNorm(); - if (d2 < d2_min) { + Point foot_pt; + if (double d2 = line_alg::distance_to_squared(Line(prev, *it), pt, &foot_pt); d2 < d2_min) { d2_min = d2; foot_pt_min = foot_pt; it_proj = it; @@ -286,6 +292,14 @@ void ThickPolyline::clip_end(double distance) assert(this->width.size() == (this->points.size() - 1) * 2); } +double Polyline3::length() const +{ + double l = 0; + for (size_t i = 1; i < this->points.size(); ++ i) + l += (this->points[i] - this->points[i - 1]).cast().norm(); + return l; +} + Lines3 Polyline3::lines() const { Lines3 lines; diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 547664d4f..bee5e51ba 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -17,7 +17,6 @@ typedef std::vector ThickPolylines; class Polyline : public MultiPoint { public: Polyline() = default; - ~Polyline() override = default; Polyline(const Polyline &other) : MultiPoint(other.points) {} Polyline(Polyline &&other) : MultiPoint(std::move(other.points)) {} Polyline(std::initializer_list list) : MultiPoint(list) {} @@ -64,11 +63,12 @@ public: Point& operator[](Points::size_type idx) { return this->points[idx]; } const Point& operator[](Points::size_type idx) const { return this->points[idx]; } - const Point& last_point() const override { return this->points.back(); } + double length() const; + const Point& last_point() const { return this->points.back(); } const Point& leftmost_point() const; - Lines lines() const override; + Lines lines() const; - virtual void clip_end(double distance); + void clip_end(double distance); void clip_start(double distance); void extend_end(double distance); void extend_start(double distance); @@ -170,7 +170,7 @@ public: std::swap(this->endpoints.first, this->endpoints.second); } - void clip_end(double distance) override; + void clip_end(double distance); std::vector width; std::pair endpoints; @@ -191,7 +191,8 @@ inline ThickPolylines to_thick_polylines(Polylines &&polylines, const coordf_t w class Polyline3 : public MultiPoint3 { public: - virtual Lines3 lines() const; + double length() const; + Lines3 lines() const; }; typedef std::vector Polylines3; diff --git a/xs/t/03_point.t b/xs/t/03_point.t index f888349b3..389e120c7 100644 --- a/xs/t/03_point.t +++ b/xs/t/03_point.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 21; +use Test::More tests => 16; my $point = Slic3r::Point->new(10, 15); @@ -59,22 +59,4 @@ is_deeply [ @$point2 ], [30, 15], 'translate'; ok $p0->ccw($p1, $p2) < 0, 'ccw() does not overflow'; } -{ - my $point = Slic3r::Point->new(15,15); - my $line = Slic3r::Line->new([10,10], [20,10]); - is_deeply $point->projection_onto_line($line)->pp, [15,10], 'project_onto_line'; - - $point = Slic3r::Point->new(0, 15); - is_deeply $point->projection_onto_line($line)->pp, [10,10], 'project_onto_line'; - - $point = Slic3r::Point->new(25, 15); - is_deeply $point->projection_onto_line($line)->pp, [20,10], 'project_onto_line'; - - $point = Slic3r::Point->new(10,10); - is_deeply $point->projection_onto_line($line)->pp, [10,10], 'project_onto_line'; - - $point = Slic3r::Point->new(12, 10); - is_deeply $point->projection_onto_line($line)->pp, [12,10], 'project_onto_line'; -} - __END__ diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp index 2d70e0203..0d44ea364 100644 --- a/xs/xsp/Point.xsp +++ b/xs/xsp/Point.xsp @@ -29,9 +29,8 @@ %code{% (*THIS)(0) = val; %}; void set_y(int val) %code{% (*THIS)(1) = val; %}; - int nearest_point_index(Points points); Clone nearest_point(Points points) - %code{% Point p; THIS->nearest_point(points, &p); RETVAL = p; %}; + %code{% RETVAL = nearest_point(points, *THIS).first; %}; double distance_to(Point* point) %code{% RETVAL = (*point - *THIS).cast().norm(); %}; double distance_to_line(Line* line) @@ -40,8 +39,6 @@ %code{% RETVAL = line->perp_distance_to(*THIS); %}; double ccw(Point* p1, Point* p2) %code{% RETVAL = cross2((*p1 - *THIS).cast(), (*p2 - *p1).cast()); %}; - Point* projection_onto_line(Line* line) - %code{% RETVAL = new Point(THIS->projection_onto(*line)); %}; Point* negative() %code{% RETVAL = new Point(- *THIS); %}; std::string serialize() %code{% char buf[2048]; sprintf(buf, "%ld,%ld", (*THIS)(0), (*THIS)(1)); RETVAL = buf; %}; From db3f6968889c557a49ef2b9d493d4b5379ab6ccc Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 15 Nov 2022 15:32:16 +0100 Subject: [PATCH 18/33] Fixed ExPolygon::overlaps(), which was not commutative. Wrote unit tests for Clipper polyline clipping operations. Rewrote ExPolygon unit tests from Perl to C++. --- src/libslic3r/ExPolygon.cpp | 12 +- src/libslic3r/ExPolygon.hpp | 4 + src/libslic3r/SLA/SupportPointGenerator.hpp | 3 +- tests/fff_print/test_clipper.cpp | 121 +++++++++++++++++--- tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_expolygon.cpp | 67 +++++++++++ xs/t/04_expolygon.t | 69 ----------- 7 files changed, 187 insertions(+), 90 deletions(-) create mode 100644 tests/libslic3r/test_expolygon.cpp delete mode 100644 xs/t/04_expolygon.t diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 61c204044..771603548 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -154,14 +154,18 @@ bool ExPolygon::overlaps(const ExPolygon &other) const svg.draw_outline(*this); svg.draw_outline(other, "blue"); #endif + Polylines pl_out = intersection_pl(to_polylines(other), *this); + #if 0 svg.draw(pl_out, "red"); #endif - if (! pl_out.empty()) - return true; - //FIXME ExPolygon::overlaps() shall be commutative, it is not! - return this->contains(other.contour.points.front()); + + // See unit test SCENARIO("Clipper diff with polyline", "[Clipper]") + // for in which case the intersection_pl produces any intersection. + return ! pl_out.empty() || + // If *this is completely inside other, then pl_out is empty, but the expolygons overlap. Test for that situation. + other.contains(this->contour.points.front()); } void ExPolygon::simplify_p(double tolerance, Polygons* polygons) const diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 58f0d33e9..55c3e60ef 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -60,6 +60,10 @@ public: // Does this expolygon overlap another expolygon? // Either the ExPolygons intersect, or one is fully inside the other, // and it is not inside a hole of the other expolygon. + // The test may not be commutative if the two expolygons touch by a boundary only, + // see unit test SCENARIO("Clipper diff with polyline", "[Clipper]"). + // Namely expolygons touching at a vertical boundary are considered overlapping, while expolygons touching + // at a horizontal boundary are NOT considered overlapping. bool overlaps(const ExPolygon &other) const; void simplify_p(double tolerance, Polygons* polygons) const; diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index 5cb7b7069..a9e094386 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -84,8 +84,7 @@ public: float overhangs_area = 0.f; bool overlaps(const Structure &rhs) const { - //FIXME ExPolygon::overlaps() shall be commutative, it is not! - return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon)); + return this->bbox.overlap(rhs.bbox) && this->polygon->overlaps(*rhs.polygon); } float overlap_area(const Structure &rhs) const { double out = 0.; diff --git a/tests/fff_print/test_clipper.cpp b/tests/fff_print/test_clipper.cpp index dc49ce0f1..ea923b48e 100644 --- a/tests/fff_print/test_clipper.cpp +++ b/tests/fff_print/test_clipper.cpp @@ -2,25 +2,115 @@ #include "test_data.hpp" #include "clipper/clipper_z.hpp" +#include "libslic3r/clipper.hpp" using namespace Slic3r; -// Test case for an issue with duplicity vertices (same XY coordinates but differ in Z coordinates) in Clipper 6.2.9, -// (related to https://sourceforge.net/p/polyclipping/bugs/160/) that was fixed in Clipper 6.4.2. +// tests for ExPolygon::overlaps(const ExPolygon &other) +SCENARIO("Clipper intersection with polyline", "[Clipper]") +{ + struct TestData { + ClipperLib::Path subject; + ClipperLib::Path clip; + ClipperLib::Paths result; + }; + + auto run_test = [](const TestData &data) { + ClipperLib::Clipper clipper; + clipper.AddPath(data.subject, ClipperLib::ptSubject, false); + clipper.AddPath(data.clip, ClipperLib::ptClip, true); + + ClipperLib::PolyTree polytree; + ClipperLib::Paths paths; + clipper.Execute(ClipperLib::ctIntersection, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + ClipperLib::PolyTreeToPaths(polytree, paths); + + REQUIRE(paths == data.result); + }; + + WHEN("Open polyline completely inside stays inside") { + run_test({ + { { 10, 0 }, { 20, 0 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { { { 20, 0 }, { 10, 0 } } } + }); + }; + WHEN("Closed polyline completely inside stays inside") { + run_test({ + { { 10, 0 }, { 20, 0 }, { 20, 20 }, { 10, 20 }, { 10, 0 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { { { 10, 0 }, { 20, 0 }, { 20, 20 }, { 10, 20 }, { 10, 0 } } } + }); + }; + WHEN("Polyline which crosses right rectangle boundary is trimmed") { + run_test({ + { { 10, 0 }, { 2000, 0 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { { { 1000, 0 }, { 10, 0 } } } + }); + }; + WHEN("Polyline which is outside clipping region is removed") { + run_test({ + { { 1500, 0 }, { 2000, 0 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { } + }); + }; + + WHEN("Polyline on left vertical boundary is kept") { + run_test({ + { { -1000, -1000 }, { -1000, 1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { { { -1000, -1000 }, { -1000, 1000 } } } + }); + run_test({ + { { -1000, 1000 }, { -1000, -1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { { { -1000, 1000 }, { -1000, -1000 } } } + }); + }; + WHEN("Polyline on right vertical boundary is kept") { + run_test({ + { { 1000, -1000 }, { 1000, 1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { { { 1000, -1000 }, { 1000, 1000 } } } + }); + run_test({ + { { 1000, 1000 }, { 1000, -1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { { { 1000, 1000 }, { 1000, -1000 } } } + }); + }; + WHEN("Polyline on bottom horizontal boundary is removed") { + run_test({ + { { -1000, -1000 }, { 1000, -1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { } + }); + run_test({ + { { 1000, -1000 }, { -1000, -1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { } + }); + }; + WHEN("Polyline on top horizontal boundary is removed") { + run_test({ + { { -1000, 1000 }, { 1000, 1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { } + }); + run_test({ + { { 1000, 1000 }, { -1000, 1000 } }, + { { -1000, -1000 }, { -1000, 1000 }, { 1000, 1000 }, { 1000, -1000 } }, + { } + }); + }; +} + SCENARIO("Clipper Z", "[ClipperZ]") { - ClipperLib_Z::Path subject; - - subject.emplace_back(-2000, -1000, 10); - subject.emplace_back(-2000, 1000, 10); - subject.emplace_back( 2000, 1000, 10); - subject.emplace_back( 2000, -1000, 10); - - ClipperLib_Z::Path clip; - clip.emplace_back(-1000, -2000, -5); - clip.emplace_back(-1000, 2000, -5); - clip.emplace_back( 1000, 2000, -5); - clip.emplace_back( 1000, -2000, -5); + ClipperLib_Z::Path subject { { -2000, -1000, 10 }, { -2000, 1000, 10 }, { 2000, 1000, 10 }, { 2000, -1000, 10 } }; + ClipperLib_Z::Path clip{ { -1000, -2000, -5 }, { -1000, 2000, -5 }, { 1000, 2000, -5 }, { 1000, -2000, -5 } }; ClipperLib_Z::Clipper clipper; clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, @@ -40,4 +130,5 @@ SCENARIO("Clipper Z", "[ClipperZ]") REQUIRE(paths.front().size() == 2); for (const ClipperLib_Z::IntPoint &pt : paths.front()) REQUIRE(pt.z() == 1); -} \ No newline at end of file +} + diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index ed7d8a92b..1cd27c6c9 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -12,6 +12,7 @@ add_executable(${_TEST_NAME}_tests test_config.cpp test_curve_fitting.cpp test_elephant_foot_compensation.cpp + test_expolygon.cpp test_geometry.cpp test_placeholder_parser.cpp test_polygon.cpp diff --git a/tests/libslic3r/test_expolygon.cpp b/tests/libslic3r/test_expolygon.cpp new file mode 100644 index 000000000..63d763e2f --- /dev/null +++ b/tests/libslic3r/test_expolygon.cpp @@ -0,0 +1,67 @@ +#include + +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/ExPolygon.hpp" + +using namespace Slic3r; + +static inline bool points_close(const Point &p1, const Point &p2) +{ + return (p1 - p2).cast().norm() < SCALED_EPSILON; +} + +static bool polygons_close_permuted(const Polygon &poly1, const Polygon &poly2, const std::vector &permutation2) +{ + if (poly1.size() != poly2.size() || poly1.size() != permutation2.size()) + return false; + for (size_t i = 0; i < poly1.size(); ++ i) + if (poly1[i] != poly2[permutation2[i]]) + return false; + return true; +} + +SCENARIO("Basics", "[ExPolygon]") { + GIVEN("ccw_square") { + Polygon ccw_square{ { 100, 100 }, { 200, 100 }, { 200, 200 }, { 100, 200 } }; + Polygon cw_hole_in_square{ { 140, 140 }, { 140, 160 }, { 160, 160 }, { 160, 140 } }; + ExPolygon expolygon { ccw_square, cw_hole_in_square }; + THEN("expolygon is valid") { + REQUIRE(expolygon.is_valid()); + } + THEN("expolygon area") { + REQUIRE(expolygon.area() == Approx(100*100-20*20)); + } + WHEN("Expolygon scaled") { + ExPolygon expolygon2 = expolygon; + expolygon2.scale(2.5); + REQUIRE(expolygon.contour.size() == expolygon2.contour.size()); + REQUIRE(expolygon.holes.size() == 1); + REQUIRE(expolygon2.holes.size() == 1); + for (size_t i = 0; i < expolygon.contour.size(); ++ i) + REQUIRE(points_close(expolygon.contour[i] * 2.5, expolygon2.contour[i])); + for (size_t i = 0; i < expolygon.holes.front().size(); ++ i) + REQUIRE(points_close(expolygon.holes.front()[i] * 2.5, expolygon2.holes.front()[i])); + } + WHEN("Expolygon translated") { + ExPolygon expolygon2 = expolygon; + expolygon2.translate(10, -5); + REQUIRE(expolygon.contour.size() == expolygon2.contour.size()); + REQUIRE(expolygon.holes.size() == 1); + REQUIRE(expolygon2.holes.size() == 1); + for (size_t i = 0; i < expolygon.contour.size(); ++ i) + REQUIRE(points_close(expolygon.contour[i] + Point(10, -5), expolygon2.contour[i])); + for (size_t i = 0; i < expolygon.holes.front().size(); ++ i) + REQUIRE(points_close(expolygon.holes.front()[i] + Point(10, -5), expolygon2.holes.front()[i])); + } + WHEN("Expolygon rotated around point") { + ExPolygon expolygon2 = expolygon; + expolygon2.rotate(M_PI / 2, Point(150, 150)); + REQUIRE(expolygon.contour.size() == expolygon2.contour.size()); + REQUIRE(expolygon.holes.size() == 1); + REQUIRE(expolygon2.holes.size() == 1); + REQUIRE(polygons_close_permuted(expolygon2.contour, expolygon.contour, { 1, 2, 3, 0})); + REQUIRE(polygons_close_permuted(expolygon2.holes.front(), expolygon.holes.front(), { 3, 0, 1, 2})); + } + } +} diff --git a/xs/t/04_expolygon.t b/xs/t/04_expolygon.t deleted file mode 100644 index 9132c44b9..000000000 --- a/xs/t/04_expolygon.t +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -use List::Util qw(first sum); -use Slic3r::XS; -use Test::More tests => 7; - -use constant PI => 4 * atan2(1, 1); - -my $square = [ # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], -]; -my $hole_in_square = [ # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], -]; - -my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); - -ok $expolygon->is_valid, 'is_valid'; - -is_deeply $expolygon->clone->pp, [$square, $hole_in_square], 'clone'; - -is $expolygon->area, 100*100-20*20, 'area'; - -{ - my $expolygon2 = $expolygon->clone; - $expolygon2->scale(2.5); - is_deeply $expolygon2->pp, [ - [map [ 2.5*$_->[0], 2.5*$_->[1] ], @$square], - [map [ 2.5*$_->[0], 2.5*$_->[1] ], @$hole_in_square] - ], 'scale'; -} - -{ - my $expolygon2 = $expolygon->clone; - $expolygon2->translate(10, -5); - is_deeply $expolygon2->pp, [ - [map [ $_->[0]+10, $_->[1]-5 ], @$square], - [map [ $_->[0]+10, $_->[1]-5 ], @$hole_in_square] - ], 'translate'; -} - -{ - my $expolygon2 = $expolygon->clone; - $expolygon2->rotate(PI/2, Slic3r::Point->new(150,150)); - is_deeply $expolygon2->pp, [ - [ @$square[1,2,3,0] ], - [ @$hole_in_square[3,0,1,2] ] - ], 'rotate around Point'; -} - -{ - my $expolygon2 = $expolygon->clone; - $expolygon2->rotate(PI/2, [150,150]); - is_deeply $expolygon2->pp, [ - [ @$square[1,2,3,0] ], - [ @$hole_in_square[3,0,1,2] ] - ], 'rotate around pure-Perl Point'; -} - -__END__ From babc8a88a1dcdfdd7a74445968aa5c3dba444658 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 15 Nov 2022 16:54:26 +0100 Subject: [PATCH 19/33] clip_clipper_polygon_with_subject_bbox() and diff_clipped() extracted from TreeSupports to ClipperUtils to be generally available. diff_clipped() is an optimized version clipping the "clipping" polygon using clip_clipper_polygon_with_subject_bbox(). To be used with complex clipping polygons, where majority of the clipping polygons are outside of the source polygon. --- src/libslic3r/ClipperUtils.cpp | 96 ++++++++++++++++++++++++++++++++++ src/libslic3r/ClipperUtils.hpp | 20 +++++++ src/libslic3r/TreeSupport.cpp | 72 +------------------------ 3 files changed, 117 insertions(+), 71 deletions(-) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 1f83309f4..7f90fd2f0 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -48,6 +48,100 @@ err: namespace ClipperUtils { Points EmptyPathsProvider::s_empty_points; Points SinglePathProvider::s_end; + + // Clip source polygon to be used as a clipping polygon with a bouding box around the source (to be clipped) polygon. + // Useful as an optimization for expensive ClipperLib operations, for example when clipping source polygons one by one + // with a set of polygons covering the whole layer below. + template + inline void clip_clipper_polygon_with_subject_bbox_templ(const std::vector &src, const BoundingBox &bbox, std::vector &out) + { + out.clear(); + const size_t cnt = src.size(); + if (cnt < 3) + return; + + enum class Side { + Left = 1, + Right = 2, + Top = 4, + Bottom = 8 + }; + + auto sides = [bbox](const PointType &p) { + return int(p.x() < bbox.min.x()) * int(Side::Left) + + int(p.x() > bbox.max.x()) * int(Side::Right) + + int(p.y() < bbox.min.y()) * int(Side::Bottom) + + int(p.y() > bbox.max.y()) * int(Side::Top); + }; + + int sides_prev = sides(src.back()); + int sides_this = sides(src.front()); + const size_t last = cnt - 1; + for (size_t i = 0; i < last; ++ i) { + int sides_next = sides(src[i + 1]); + if (// This point is inside. Take it. + sides_this == 0 || + // Either this point is outside and previous or next is inside, or + // the edge possibly cuts corner of the bounding box. + (sides_prev & sides_this & sides_next) == 0) { + out.emplace_back(src[i]); + sides_prev = sides_this; + } else { + // All the three points (this, prev, next) are outside at the same side. + // Ignore this point. + } + sides_this = sides_next; + } + + // Never produce just a single point output polygon. + if (! out.empty()) + if (int sides_next = sides(out.front()); + // The last point is inside. Take it. + sides_this == 0 || + // Either this point is outside and previous or next is inside, or + // the edge possibly cuts corner of the bounding box. + (sides_prev & sides_this & sides_next) == 0) + out.emplace_back(src.back()); + } + + void clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox, Points &out) + { clip_clipper_polygon_with_subject_bbox_templ(src, bbox, out); } + void clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox, ZPoints &out) + { clip_clipper_polygon_with_subject_bbox_templ(src, bbox, out); } + + template + [[nodiscard]] std::vector clip_clipper_polygon_with_subject_bbox_templ(const std::vector &src, const BoundingBox &bbox) + { + std::vector out; + clip_clipper_polygon_with_subject_bbox(src, bbox, out); + return out; + } + + [[nodiscard]] Points clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox) + { return clip_clipper_polygon_with_subject_bbox_templ(src, bbox); } + [[nodiscard]] ZPoints clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox) + { return clip_clipper_polygon_with_subject_bbox_templ(src, bbox); } + + void clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, Polygon &out) + { + clip_clipper_polygon_with_subject_bbox(src.points, bbox, out.points); + } + + [[nodiscard]] Polygon clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox) + { + Polygon out; + clip_clipper_polygon_with_subject_bbox(src.points, bbox, out.points); + return out; + } + + [[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const Polygons &src, const BoundingBox &bbox) + { + Polygons out; + out.reserve(src.size()); + for (const Polygon &p : src) + out.emplace_back(clip_clipper_polygon_with_subject_bbox(p, bbox)); + return out; + } } static ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree &&polytree) @@ -537,6 +631,8 @@ Slic3r::Polygons diff(const Slic3r::Polygon &subject, const Slic3r::Polygon &cli { return _clipper(ClipperLib::ctDifference, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); } Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } +Slic3r::Polygons diff_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) + { return diff(subject, ClipperUtils::clip_clipper_polygons_with_subject_bbox(clip, get_extents(subject).inflated(SCALED_EPSILON)), do_safety_offset); } Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 89d05260c..4017a73f1 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -24,6 +24,8 @@ using Slic3r::ClipperLib::jtSquare; namespace Slic3r { +class BoundingBox; + static constexpr const float ClipperSafetyOffset = 10.f; static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter; @@ -306,6 +308,21 @@ namespace ClipperUtils { const SurfacesPtr &m_surfaces; size_t m_size; }; + + // For ClipperLib with Z coordinates. + using ZPoint = Vec3i32; + using ZPoints = std::vector; + + // Clip source polygon to be used as a clipping polygon with a bouding box around the source (to be clipped) polygon. + // Useful as an optimization for expensive ClipperLib operations, for example when clipping source polygons one by one + // with a set of polygons covering the whole layer below. + void clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox, Points &out); + void clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox, ZPoints &out); + [[nodiscard]] Points clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox); + [[nodiscard]] ZPoints clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox); + void clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, Polygon &out); + [[nodiscard]] Polygon clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox); + [[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const Polygons &src, const BoundingBox &bbox); } // offset Polygons @@ -391,6 +408,9 @@ Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &su Slic3r::Polygons diff(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); +// Optimized version clipping the "clipping" polygon using clip_clipper_polygon_with_subject_bbox(). +// To be used with complex clipping polygons, where majority of the clipping polygons are outside of the source polygon. +Slic3r::Polygons diff_clipped(const Slic3r::Polygons &src, const Slic3r::Polygons &clipping, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index b60744e32..6c5b95406 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -115,76 +115,6 @@ static inline void check_self_intersections(const ExPolygon &expoly, const std:: #endif // _WIN32 } -static inline void clip_for_diff(const Polygon &src, const BoundingBox &bbox, Polygon &out) -{ - out.clear(); - const size_t cnt = src.points.size(); - if (cnt < 3) - return; - - enum class Side { - Left = 1, - Right = 2, - Top = 4, - Bottom = 8 - }; - - auto sides = [bbox](const Point &p) { - return int(p.x() < bbox.min.x()) * int(Side::Left) + - int(p.x() > bbox.max.x()) * int(Side::Right) + - int(p.y() < bbox.min.y()) * int(Side::Bottom) + - int(p.y() > bbox.max.y()) * int(Side::Top); - }; - - int sides_prev = sides(src.points.back()); - int sides_this = sides(src.points.front()); - const size_t last = cnt - 1; - for (size_t i = 0; i < last; ++ i) { - int sides_next = sides(src.points[i + 1]); - if (// This point is inside. Take it. - sides_this == 0 || - // Either this point is outside and previous or next is inside, or - // the edge possibly cuts corner of the bounding box. - (sides_prev & sides_this & sides_next) == 0) { - out.points.emplace_back(src.points[i]); - sides_prev = sides_this; - } else { - // All the three points (this, prev, next) are outside at the same side. - // Ignore this point. - } - sides_this = sides_next; - } - // For the last point, if src is completely outside bbox, then out.points will be empty. Just use the first point instead. - int sides_next = sides(out.points.empty() ? src.points.front() : out.points.front()); - if (// The last point is inside. Take it. - sides_this == 0 || - // Either this point is outside and previous or next is inside, or - // the edge possibly cuts corner of the bounding box. - (sides_prev & sides_this & sides_next) == 0) - out.points.emplace_back(src.points.back()); -} - -[[nodiscard]] static inline Polygon clip_for_diff(const Polygon &src, const BoundingBox &bbox) -{ - Polygon out; - clip_for_diff(src, bbox, out); - return out; -} - -[[nodiscard]] static inline Polygons clip_for_diff(const Polygons &src, const BoundingBox &bbox) -{ - Polygons out; - out.reserve(src.size()); - for (const Polygon &p : src) - out.emplace_back(clip_for_diff(p, bbox)); - return out; -} - -[[nodiscard]] static inline Polygons diff_clipped(const Polygons &src, const Polygons &clipping) -{ - return diff(src, clip_for_diff(clipping, get_extents(src).inflated(SCALED_EPSILON))); -} - static constexpr const auto tiny_area_threshold = sqr(scaled(0.001)); static std::vector>> group_meshes(const Print &print, const std::vector &print_object_ids) @@ -821,7 +751,7 @@ static std::optional> polyline_sample_next_point_at_dis Polygons collision_trimmed_buffer; auto collision_trimmed = [&collision_trimmed_buffer, &collision, &ret, distance]() -> const Polygons& { if (collision_trimmed_buffer.empty() && ! collision.empty()) - collision_trimmed_buffer = clip_for_diff(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON)); + collision_trimmed_buffer = ClipperUtils::clip_clipper_polygons_with_subject_bbox(collision, get_extents(ret).inflated(std::max(0, distance) + SCALED_EPSILON)); return collision_trimmed_buffer; }; From 1307fbf2f6884061afcfeb5df9d84ddd34cf9235 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 15 Nov 2022 18:28:56 +0100 Subject: [PATCH 20/33] Fixed performance issues of PerimeterGenerator with extremely detailed chain fabric models, for example Fabric_of_Thyme_2.0_23x23 --- src/libslic3r/PerimeterGenerator.cpp | 38 +++++++++++++++++++--------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index e864a37ad..17240d71d 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -282,10 +282,13 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator::Parame 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)) { + BoundingBox bbox(polygon.points); + bbox.offset(SCALED_EPSILON); + Polygons lower_slices_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_slices_polygons_cache, bbox); // get non-overhang paths by intersecting this loop with the grown lower slices extrusion_paths_append( paths, - intersection_pl({ polygon }, lower_slices_polygons_cache), + intersection_pl({ polygon }, lower_slices_polygons_clipped), role, is_external ? params.ext_mm3_per_mm : params.mm3_per_mm, is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(), @@ -296,7 +299,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator::Parame // the loop centerline and original lower slices is >= half nozzle diameter extrusion_paths_append( paths, - diff_pl({ polygon }, lower_slices_polygons_cache), + diff_pl({ polygon }, lower_slices_polygons_clipped), erOverhangPerimeter, params.mm3_per_mm_overhang, params.overhang_flow.width(), @@ -466,17 +469,28 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P ClipperLib_Z::Path extrusion_path; extrusion_path.reserve(extrusion->size()); - for (const Arachne::ExtrusionJunction &ej : extrusion->junctions) + BoundingBox extrusion_path_bbox; + for (const Arachne::ExtrusionJunction &ej : extrusion->junctions) { extrusion_path.emplace_back(ej.p.x(), ej.p.y(), ej.w); + extrusion_path_bbox.merge(Point{ej.p.x(), ej.p.y()}); + } ClipperLib_Z::Paths lower_slices_paths; 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()); - for (const Point &pt : poly.points) - out.emplace_back(pt.x(), pt.y(), 0); + { + Points clipped; + extrusion_path_bbox.offset(SCALED_EPSILON); + for (const Polygon &poly : lower_slices_polygons_cache) { + clipped.clear(); + ClipperUtils::clip_clipper_polygon_with_subject_bbox(poly.points, extrusion_path_bbox, clipped); + if (! clipped.empty()) { + lower_slices_paths.emplace_back(); + ClipperLib_Z::Path &out = lower_slices_paths.back(); + out.reserve(clipped.size()); + for (const Point &pt : clipped) + out.emplace_back(pt.x(), pt.y(), 0); + } + } } // get non-overhang paths by intersecting this loop with the grown lower slices @@ -623,7 +637,7 @@ void PerimeterGenerator::process_arachne( coord_t solid_infill_spacing = params.solid_infill_flow.scaled_spacing(); // prepare grown lower layer slices for overhang detection - if (lower_slices != nullptr && params.config.overhangs) { + if (params.config.overhangs && lower_slices != nullptr && lower_slices_polygons_cache.empty()) { // 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 @@ -741,7 +755,7 @@ void PerimeterGenerator::process_arachne( for (size_t unlocked_idx : blocking[best_candidate]) blocked[unlocked_idx]--; - if(!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then. + if (!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then. if(best_path->is_closed) current_position = best_path->junctions[0].p; //We end where we started. else @@ -863,7 +877,7 @@ void PerimeterGenerator::process_classic( 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 (lower_slices != nullptr && params.config.overhangs) { + if (params.config.overhangs && lower_slices != nullptr && lower_slices_polygons_cache.empty()) { // 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 From 8190eb4fe61b705da2a92159776ed4614a272056 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 15 Nov 2022 18:44:38 +0100 Subject: [PATCH 21/33] Fixed some compilation errors and warnings. --- src/libslic3r/GCode/ToolOrdering.cpp | 3 ++- src/libslic3r/Layer.cpp | 36 +++++++++++++++++++++++----- src/libslic3r/Measure.cpp | 2 ++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index ca4ddf4d5..b4d563bb1 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -14,8 +14,9 @@ #include #include -#include +#include +#include namespace Slic3r { diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index a138b6d7d..957603e8b 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -1,4 +1,5 @@ #include "Layer.hpp" +#include #include "ClipperUtils.hpp" #include "Print.hpp" #include "Fill/Fill.hpp" @@ -97,14 +98,25 @@ static void connect_layer_slices( const ClipperLib_Z::PolyTree &polytree, const std::vector> &intersections, const coord_t offset_below, - const coord_t offset_above, - const coord_t offset_end) + const coord_t offset_above +#ifndef NDEBUG + , const coord_t offset_end +#endif // NDEBUG + ) { class Visitor { public: Visitor(const std::vector> &intersections, - Layer &below, Layer &above, const coord_t offset_below, const coord_t offset_above, const coord_t offset_end) : - m_intersections(intersections), m_below(below), m_above(above), m_offset_below(offset_below), m_offset_above(offset_above), m_offset_end(offset_end) {} + Layer &below, Layer &above, const coord_t offset_below, const coord_t offset_above +#ifndef NDEBUG + , const coord_t offset_end +#endif // NDEBUG + ) : + m_intersections(intersections), m_below(below), m_above(above), m_offset_below(offset_below), m_offset_above(offset_above) +#ifndef NDEBUG + , m_offset_end(offset_end) +#endif // NDEBUG + {} void visit(const ClipperLib_Z::PolyNode &polynode) { @@ -217,8 +229,14 @@ static void connect_layer_slices( Layer &m_above; const coord_t m_offset_below; const coord_t m_offset_above; +#ifndef NDEBUG const coord_t m_offset_end; - } visitor(intersections, below, above, offset_below, offset_above, offset_end); +#endif // NDEBUG + } visitor(intersections, below, above, offset_below, offset_above +#ifndef NDEBUG + , offset_end +#endif // NDEBUG + ); for (int i = 0; i < polytree.ChildCount(); ++ i) visitor.visit(*polytree.Childs[i]); @@ -261,7 +279,9 @@ void Layer::build_up_down_graph(Layer& below, Layer& above) ClipperLib_Z::Paths paths_below = expolygons_to_zpaths(below.lslices, paths_below_offset); coord_t paths_above_offset = paths_below_offset + coord_t(below.lslices.size()); ClipperLib_Z::Paths paths_above = expolygons_to_zpaths(above.lslices, paths_above_offset); +#ifndef NDEBUG coord_t paths_end = paths_above_offset + coord_t(above.lslices.size()); +#endif // NDEBUG class ZFill { public: @@ -301,7 +321,11 @@ void Layer::build_up_down_graph(Layer& below, Layer& above) clipper.AddPaths(paths_above, ClipperLib_Z::ptClip, true); clipper.Execute(ClipperLib_Z::ctIntersection, result, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); - connect_layer_slices(below, above, result, zfill.intersections(), paths_below_offset, paths_above_offset, paths_end); + connect_layer_slices(below, above, result, zfill.intersections(), paths_below_offset, paths_above_offset +#ifndef NDEBUG + , paths_end +#endif // NDEBUG + ); } static inline bool layer_needs_raw_backup(const Layer *layer) diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index fc27f0383..5d7b3daa4 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -5,6 +5,8 @@ #include "libslic3r/Geometry/Circle.hpp" #include "libslic3r/SurfaceMesh.hpp" +#include + namespace Slic3r { namespace Measure { From 0a84421ea4c60dd45bfd66c498fa8141e6664312 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 15 Nov 2022 19:20:16 +0100 Subject: [PATCH 22/33] Fixing after recent refactoring: Missing forward declaration. --- src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp index cfdbfecda..efda32383 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp @@ -10,6 +10,11 @@ #include "SkeletalTrapezoidationEdge.hpp" #include "SkeletalTrapezoidationJoint.hpp" +namespace Slic3r +{ +class Line; +}; + namespace Slic3r::Arachne { From fe51f7783975e6090a4a9397d15998e955252096 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 16 Nov 2022 12:03:31 +0100 Subject: [PATCH 23/33] Improvements in performance of Medial Axis algorithm. Fixes Slicing slows or hangs on "Generating Permiters 20%" cpu load is at 100% #8164 Fixes Slicing hangs on generating perimeters with thing:3565827 (30g) #3259 --- src/libslic3r/ExPolygon.cpp | 11 +- src/libslic3r/ExPolygon.hpp | 8 +- src/libslic3r/Geometry/MedialAxis.cpp | 248 ++++++++++------------- src/libslic3r/Geometry/MedialAxis.hpp | 39 ++-- src/libslic3r/Geometry/VoronoiOffset.hpp | 2 +- src/libslic3r/PerimeterGenerator.cpp | 4 +- tests/fff_print/test_thin_walls.cpp | 18 +- tests/libslic3r/test_voronoi.cpp | 1 - 8 files changed, 157 insertions(+), 174 deletions(-) diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 771603548..8dc0ff547 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -206,12 +206,10 @@ void ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const append(*expolygons, this->simplify(tolerance)); } -void -ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polylines) const +void ExPolygon::medial_axis(double min_width, double max_width, ThickPolylines* polylines) const { // init helper object - Slic3r::Geometry::MedialAxis ma(max_width, min_width, this); - ma.lines = this->lines(); + Slic3r::Geometry::MedialAxis ma(min_width, max_width, *this); // compute the Voronoi diagram and extract medial axis polylines ThickPolylines pp; @@ -318,11 +316,10 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl polylines->insert(polylines->end(), pp.begin(), pp.end()); } -void -ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const +void ExPolygon::medial_axis(double min_width, double max_width, Polylines* polylines) const { ThickPolylines tp; - this->medial_axis(max_width, min_width, &tp); + this->medial_axis(min_width, max_width, &tp); polylines->insert(polylines->end(), tp.begin(), tp.end()); } diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 55c3e60ef..17b5b1963 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -70,10 +70,10 @@ public: Polygons simplify_p(double tolerance) const; ExPolygons simplify(double tolerance) const; void simplify(double tolerance, ExPolygons* expolygons) const; - void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const; - void medial_axis(double max_width, double min_width, Polylines* polylines) const; - Polylines medial_axis(double max_width, double min_width) const - { Polylines out; this->medial_axis(max_width, min_width, &out); return out; } + void medial_axis(double min_width, double max_width, ThickPolylines* polylines) const; + void medial_axis(double min_width, double max_width, Polylines* polylines) const; + Polylines medial_axis(double min_width, double max_width) const + { Polylines out; this->medial_axis(min_width, max_width, &out); return out; } Lines lines() const; // Number of contours (outer contour with holes). diff --git a/src/libslic3r/Geometry/MedialAxis.cpp b/src/libslic3r/Geometry/MedialAxis.cpp index ce38a6c70..c92796f41 100644 --- a/src/libslic3r/Geometry/MedialAxis.cpp +++ b/src/libslic3r/Geometry/MedialAxis.cpp @@ -1,6 +1,7 @@ #include "MedialAxis.hpp" #include "clipper.hpp" +#include "VoronoiOffset.hpp" #ifdef SLIC3R_DEBUG namespace boost { namespace polygon { @@ -392,8 +393,7 @@ inline const typename VD::point_type retrieve_cell_point(const typename VD::cell } template -inline std::pair -measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments) +inline std::pair measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments) { typedef typename VD::coord_type T; const typename VD::point_type pa(edge.vertex0()->x(), edge.vertex0()->y()); @@ -442,15 +442,21 @@ private: const Lines &lines; }; -void -MedialAxis::build(ThickPolylines* polylines) +MedialAxis::MedialAxis(double min_width, double max_width, const ExPolygon &expolygon) : + m_expolygon(expolygon), m_lines(expolygon.lines()), m_min_width(min_width), m_max_width(max_width) +{} + +void MedialAxis::build(ThickPolylines* polylines) { - construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd); + construct_voronoi(m_lines.begin(), m_lines.end(), &m_vd); + Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines); +// static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees +// std::vector skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha); /* // DEBUG: dump all Voronoi edges { - for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { + for (VD::const_edge_iterator edge = m_vd.edges().begin(); edge != m_vd.edges().end(); ++edge) { if (edge->is_infinite()) continue; ThickPolyline polyline; @@ -463,73 +469,59 @@ MedialAxis::build(ThickPolylines* polylines) */ //typedef const VD::vertex_type vert_t; - typedef const VD::edge_type edge_t; + using edge_t = const VD::edge_type; // collect valid edges (i.e. prune those not belonging to MAT) // note: this keeps twins, so it inserts twice the number of the valid edges - this->valid_edges.clear(); - { - std::set seen_edges; - for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { - // if we only process segments representing closed loops, none if the - // infinite edges (if any) would be part of our MAT anyway - if (edge->is_secondary() || edge->is_infinite()) continue; - - // don't re-validate twins - if (seen_edges.find(&*edge) != seen_edges.end()) continue; // TODO: is this needed? - seen_edges.insert(&*edge); - seen_edges.insert(edge->twin()); - - if (!this->validate_edge(&*edge)) continue; - this->valid_edges.insert(&*edge); - this->valid_edges.insert(edge->twin()); + m_edge_data.assign(m_vd.edges().size() / 2, EdgeData{}); + for (VD::const_edge_iterator edge = m_vd.edges().begin(); edge != m_vd.edges().end(); edge += 2) + if (edge->is_primary() && edge->is_finite() && + (Voronoi::vertex_category(edge->vertex0()) == Voronoi::VertexCategory::Inside || + Voronoi::vertex_category(edge->vertex1()) == Voronoi::VertexCategory::Inside) && + this->validate_edge(&*edge)) { + // Valid skeleton edge. + this->edge_data(*edge).first.active = true; } - } - this->edges = this->valid_edges; // iterate through the valid edges to build polylines - while (!this->edges.empty()) { - const edge_t* edge = *this->edges.begin(); + ThickPolyline reverse_polyline; + for (VD::const_edge_iterator seed_edge = m_vd.edges().begin(); seed_edge != m_vd.edges().end(); seed_edge += 2) + if (EdgeData &seed_edge_data = this->edge_data(*seed_edge).first; seed_edge_data.active) { + // Mark this edge as visited. + seed_edge_data.active = false; + + // Start a polyline. + ThickPolyline polyline; + polyline.points.emplace_back(seed_edge->vertex0()->x(), seed_edge->vertex0()->y()); + polyline.points.emplace_back(seed_edge->vertex1()->x(), seed_edge->vertex1()->y()); + polyline.width.emplace_back(seed_edge_data.width_start); + polyline.width.emplace_back(seed_edge_data.width_end); + // Grow the polyline in a forward direction. + this->process_edge_neighbors(&*seed_edge, &polyline); - // start a polyline - ThickPolyline polyline; - polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() )); - polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() )); - polyline.width.push_back(this->thickness[edge].first); - polyline.width.push_back(this->thickness[edge].second); + // Grow the polyline in a backward direction. + reverse_polyline.clear(); + this->process_edge_neighbors(seed_edge->twin(), &reverse_polyline); + polyline.points.insert(polyline.points.begin(), reverse_polyline.points.rbegin(), reverse_polyline.points.rend()); + polyline.width.insert(polyline.width.begin(), reverse_polyline.width.rbegin(), reverse_polyline.width.rend()); + polyline.endpoints.first = reverse_polyline.endpoints.second; - // remove this edge and its twin from the available edges - (void)this->edges.erase(edge); - (void)this->edges.erase(edge->twin()); + assert(polyline.width.size() == polyline.points.size() * 2 - 2); - // get next points - this->process_edge_neighbors(edge, &polyline); - - // get previous points - { - ThickPolyline rpolyline; - this->process_edge_neighbors(edge->twin(), &rpolyline); - polyline.points.insert(polyline.points.begin(), rpolyline.points.rbegin(), rpolyline.points.rend()); - polyline.width.insert(polyline.width.begin(), rpolyline.width.rbegin(), rpolyline.width.rend()); - polyline.endpoints.first = rpolyline.endpoints.second; + // Prevent loop endpoints from being extended. + if (polyline.first_point() == polyline.last_point()) { + polyline.endpoints.first = false; + polyline.endpoints.second = false; + } + + // Append polyline to result. + polylines->emplace_back(std::move(polyline)); } - - assert(polyline.width.size() == polyline.points.size()*2 - 2); - - // prevent loop endpoints from being extended - if (polyline.first_point() == polyline.last_point()) { - polyline.endpoints.first = false; - polyline.endpoints.second = false; - } - - // append polyline to result - polylines->push_back(polyline); - } #ifdef SLIC3R_DEBUG { static int iRun = 0; - dump_voronoi_to_svg(this->lines, this->vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str()); + dump_voronoi_to_svg(m_lines, m_vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str()); printf("Thick lines: "); for (ThickPolylines::const_iterator it = polylines->begin(); it != polylines->end(); ++ it) { ThickLines lines = it->thicklines(); @@ -542,56 +534,66 @@ MedialAxis::build(ThickPolylines* polylines) #endif /* SLIC3R_DEBUG */ } -void -MedialAxis::build(Polylines* polylines) +void MedialAxis::build(Polylines* polylines) { ThickPolylines tp; this->build(&tp); polylines->insert(polylines->end(), tp.begin(), tp.end()); } -void -MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline) +void MedialAxis::process_edge_neighbors(const VD::edge_type *edge, ThickPolyline* polyline) { - while (true) { + for (;;) { // Since rot_next() works on the edge starting point but we want // to find neighbors on the ending point, we just swap edge with // its twin. - const VD::edge_type* twin = edge->twin(); + const VD::edge_type *twin = edge->twin(); // count neighbors for this edge - std::vector neighbors; - for (const VD::edge_type* neighbor = twin->rot_next(); neighbor != twin; - neighbor = neighbor->rot_next()) { - if (this->valid_edges.count(neighbor) > 0) neighbors.push_back(neighbor); - } + size_t num_neighbors = 0; + const VD::edge_type *first_neighbor = nullptr; + for (const VD::edge_type *neighbor = twin->rot_next(); neighbor != twin; neighbor = neighbor->rot_next()) + if (this->edge_data(*neighbor).first.active) { + if (num_neighbors == 0) + first_neighbor = neighbor; + ++ num_neighbors; + } // if we have a single neighbor then we can continue recursively - if (neighbors.size() == 1) { - const VD::edge_type* neighbor = neighbors.front(); - - // break if this is a closed loop - if (this->edges.count(neighbor) == 0) return; - - Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y()); - polyline->points.push_back(new_point); - polyline->width.push_back(this->thickness[neighbor].first); - polyline->width.push_back(this->thickness[neighbor].second); - (void)this->edges.erase(neighbor); - (void)this->edges.erase(neighbor->twin()); - edge = neighbor; - } else if (neighbors.size() == 0) { + if (num_neighbors == 1) { + if (std::pair neighbor_data = this->edge_data(*first_neighbor); + neighbor_data.first.active) { + neighbor_data.first.active = false; + polyline->points.emplace_back(first_neighbor->vertex1()->x(), first_neighbor->vertex1()->y()); + if (neighbor_data.second) { + polyline->width.push_back(neighbor_data.first.width_end); + polyline->width.push_back(neighbor_data.first.width_start); + } else { + polyline->width.push_back(neighbor_data.first.width_start); + polyline->width.push_back(neighbor_data.first.width_end); + } + edge = first_neighbor; + // Continue chaining. + continue; + } + } else if (num_neighbors == 0) { polyline->endpoints.second = true; - return; } else { - // T-shaped or star-shaped joint - return; + // T-shaped or star-shaped joint } + // Stop chaining. + break; } } bool MedialAxis::validate_edge(const VD::edge_type* edge) { + auto retrieve_segment = [this](const VD::cell_type* cell) -> const Line& { return m_lines[cell->source_index()]; }; + auto retrieve_endpoint = [retrieve_segment](const VD::cell_type* cell) -> const Point& { + const Line &line = retrieve_segment(cell); + return cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT ? line.a : line.b; + }; + // prevent overflows and detect almost-infinite edges #ifndef CLIPPERLIB_INT32 if (std::abs(edge->vertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) || @@ -602,32 +604,18 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge) #endif // CLIPPERLIB_INT32 // construct the line representing this edge of the Voronoi diagram - const Line line( - Point( edge->vertex0()->x(), edge->vertex0()->y() ), - Point( edge->vertex1()->x(), edge->vertex1()->y() ) - ); - - // discard edge if it lies outside the supplied shape - // this could maybe be optimized (checking inclusion of the endpoints - // might give false positives as they might belong to the contour itself) - if (this->expolygon != NULL) { - if (line.a == line.b) { - // in this case, contains(line) returns a false positive - if (!this->expolygon->contains(line.a)) return false; - } else { - if (!this->expolygon->contains(line)) return false; - } - } + const Line line({ edge->vertex0()->x(), edge->vertex0()->y() }, + { edge->vertex1()->x(), edge->vertex1()->y() }); // retrieve the original line segments which generated the edge we're checking const VD::cell_type* cell_l = edge->cell(); const VD::cell_type* cell_r = edge->twin()->cell(); - const Line &segment_l = this->retrieve_segment(cell_l); - const Line &segment_r = this->retrieve_segment(cell_r); + const Line &segment_l = retrieve_segment(cell_l); + const Line &segment_r = retrieve_segment(cell_r); /* SVG svg("edge.svg"); - svg.draw(*this->expolygon); + svg.draw(m_expolygon); svg.draw(line); svg.draw(segment_l, "red"); svg.draw(segment_r, "blue"); @@ -651,30 +639,30 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge) coordf_t w0 = cell_r->contains_segment() ? segment_r.distance_to(line.a)*2 - : (this->retrieve_endpoint(cell_r) - line.a).cast().norm()*2; + : (retrieve_endpoint(cell_r) - line.a).cast().norm()*2; coordf_t w1 = cell_l->contains_segment() ? segment_l.distance_to(line.b)*2 - : (this->retrieve_endpoint(cell_l) - line.b).cast().norm()*2; + : (retrieve_endpoint(cell_l) - line.b).cast().norm()*2; if (cell_l->contains_segment() && cell_r->contains_segment()) { // calculate the relative angle between the two boundary segments double angle = fabs(segment_r.orientation() - segment_l.orientation()); - if (angle > PI) angle = 2*PI - angle; + if (angle > PI) + angle = 2. * PI - angle; assert(angle >= 0 && angle <= PI); - + // fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction) // we're interested only in segments close to the second case (facing segments) // so we allow some tolerance. // this filter ensures that we're dealing with a narrow/oriented area (longer than thick) // we don't run it on edges not generated by two segments (thus generated by one segment // and the endpoint of another segment), since their orientation would not be meaningful - if (PI - angle > PI/8) { + if (PI - angle > PI / 8.) { // angle is not narrow enough - // only apply this filter to segments that are not too short otherwise their // angle could possibly be not meaningful - if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width) + if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= m_min_width) return false; } } else { @@ -682,31 +670,17 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge) return false; } - if (w0 < this->min_width && w1 < this->min_width) - return false; - - if (w0 > this->max_width && w1 > this->max_width) - return false; - - this->thickness[edge] = std::make_pair(w0, w1); - this->thickness[edge->twin()] = std::make_pair(w1, w0); - - return true; -} - -const Line& MedialAxis::retrieve_segment(const VD::cell_type* cell) const -{ - return this->lines[cell->source_index()]; -} - -const Point& MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const -{ - const Line& line = this->retrieve_segment(cell); - if (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) { - return line.a; - } else { - return line.b; + if ((w0 >= m_min_width || w1 >= m_min_width) && + (w0 <= m_max_width || w1 <= m_max_width)) { + std::pair ed = this->edge_data(*edge); + if (ed.second) + std::swap(w0, w1); + ed.first.width_start = w0; + ed.first.width_end = w1; + return true; } + + return false; } } } // namespace Slicer::Geometry diff --git a/src/libslic3r/Geometry/MedialAxis.hpp b/src/libslic3r/Geometry/MedialAxis.hpp index cfbb5f080..b1354ddb2 100644 --- a/src/libslic3r/Geometry/MedialAxis.hpp +++ b/src/libslic3r/Geometry/MedialAxis.hpp @@ -4,30 +4,43 @@ #include "Voronoi.hpp" #include "../ExPolygon.hpp" -namespace Slic3r { namespace Geometry { +namespace Slic3r::Geometry { class MedialAxis { public: - Lines lines; - const ExPolygon* expolygon; - double max_width; - double min_width; - MedialAxis(double _max_width, double _min_width, const ExPolygon* _expolygon = NULL) - : expolygon(_expolygon), max_width(_max_width), min_width(_min_width) {}; + MedialAxis(double min_width, double max_width, const ExPolygon &expolygon); void build(ThickPolylines* polylines); void build(Polylines* polylines); private: + // Input + const ExPolygon &m_expolygon; + Lines m_lines; + // for filtering of the skeleton edges + double m_min_width; + double m_max_width; + + // Voronoi Diagram. using VD = VoronoiDiagram; - VD vd; - std::set edges, valid_edges; - std::map > thickness; + VD m_vd; + + // Annotations of the VD skeleton edges. + struct EdgeData { + bool active { false }; + double width_start { 0 }; + double width_end { 0 }; + }; + // Returns a reference to EdgeData and a "reversed" boolean. + std::pair edge_data(const VD::edge_type &edge) { + size_t edge_id = &edge - &m_vd.edges().front(); + return { m_edge_data[edge_id / 2], (edge_id & 1) != 0 }; + } + std::vector m_edge_data; + void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline); bool validate_edge(const VD::edge_type* edge); - const Line& retrieve_segment(const VD::cell_type* cell) const; - const Point& retrieve_endpoint(const VD::cell_type* cell) const; }; -} } // namespace Slicer::Geometry +} // namespace Slicer::Geometry #endif // slic3r_Geometry_MedialAxis_hpp_ diff --git a/src/libslic3r/Geometry/VoronoiOffset.hpp b/src/libslic3r/Geometry/VoronoiOffset.hpp index 359fe010c..fa72ae2bc 100644 --- a/src/libslic3r/Geometry/VoronoiOffset.hpp +++ b/src/libslic3r/Geometry/VoronoiOffset.hpp @@ -1,4 +1,4 @@ -// Polygon offsetting using Voronoi diagram prodiced by boost::polygon. +// Polygon offsetting using Voronoi diagram produced by boost::polygon. #ifndef slic3r_VoronoiOffset_hpp_ #define slic3r_VoronoiOffset_hpp_ diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 17240d71d..a3540ff09 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -920,7 +920,7 @@ void PerimeterGenerator::process_classic( float(min_width / 2.)); // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop for (ExPolygon &ex : expp) - ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); + ex.medial_axis(min_width, ext_perimeter_width + ext_perimeter_spacing2, &thin_walls); } if (params.spiral_vase && offsets.size() > 1) { // Remove all but the largest area polygon. @@ -1066,7 +1066,7 @@ void PerimeterGenerator::process_classic( offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset))); ThickPolylines polylines; for (const ExPolygon &ex : gaps_ex) - ex.medial_axis(max, min, &polylines); + ex.medial_axis(min, max, &polylines); if (! polylines.empty()) { ExtrusionEntityCollection gap_fill; variable_width(polylines, erGapFill, params.solid_infill_flow, gap_fill.entities); diff --git a/tests/fff_print/test_thin_walls.cpp b/tests/fff_print/test_thin_walls.cpp index cd80f3bd6..59fb6b080 100644 --- a/tests/fff_print/test_thin_walls.cpp +++ b/tests/fff_print/test_thin_walls.cpp @@ -16,7 +16,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { auto hole_in_square = Polygon::new_scale({ {140, 140}, {140, 160}, {160, 160}, {160, 140} }); ExPolygon expolygon{ square, hole_in_square }; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(scaled(40.), scaled(0.5)); + Polylines res = expolygon.medial_axis(scaled(0.5), scaled(40.)); THEN("medial axis of a square shape is a single path") { REQUIRE(res.size() == 1); } @@ -32,7 +32,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { GIVEN("narrow rectangle") { ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 200}, {100, 200} }) }; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(scaled(20.), scaled(0.5)); + Polylines res = expolygon.medial_axis(scaled(0.5), scaled(20.)); THEN("medial axis of a narrow rectangle is a single line") { REQUIRE(res.size() == 1); } @@ -50,7 +50,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { {100, 200} })}; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(scaled(1.), scaled(0.5)); + Polylines res = expolygon.medial_axis(scaled(0.5), scaled(1.)); THEN("medial axis of a narrow rectangle with an extra vertex is still a single line") { REQUIRE(res.size() == 1); } @@ -81,7 +81,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { {1687689,4235755},{1218962,3499999},{827499,2748020},{482284,1920196},{219954,1088186},{31126,236479},{0,0},{1005754,0} }}; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(scaled(1.324888), scaled(0.25)); + Polylines res = expolygon.medial_axis(scaled(0.25), scaled(1.324888)); THEN("medial axis of a semicircumference is a single line") { REQUIRE(res.size() == 1); } @@ -103,7 +103,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { GIVEN("narrow trapezoid") { ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {112, 200}, {108, 200} }) }; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(scaled(20.), scaled(0.5)); + Polylines res = expolygon.medial_axis(scaled(0.5), scaled(20.)); THEN("medial axis of a narrow trapezoid is a single line") { REQUIRE(res.size() == 1); } @@ -115,7 +115,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { GIVEN("L shape") { ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 180}, {200, 180}, {200, 200}, {100, 200}, }) }; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(scaled(20.), scaled(0.5)); + Polylines res = expolygon.medial_axis(scaled(0.5), scaled(20.)); THEN("medial axis of an L shape is a single line") { REQUIRE(res.size() == 1); } @@ -134,7 +134,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { {-220815482,-37738966},{-221117540,-37738966},{-221117540,-51762024},{-203064906,-51762024}, }}; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(819998., 102499.75); + Polylines res = expolygon.medial_axis(102499.75, 819998.); THEN("medial axis is a single line") { REQUIRE(res.size() == 1); } @@ -147,7 +147,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { GIVEN("narrow triangle") { ExPolygon expolygon{ Polygon::new_scale({ {50, 100}, {1000, 102}, {50, 104} }) }; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(scaled(4.), scaled(0.5)); + Polylines res = expolygon.medial_axis(scaled(0.5), scaled(4.)); THEN("medial axis of a narrow triangle is a single line") { REQUIRE(res.size() == 1); } @@ -159,7 +159,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") { GIVEN("GH #2474") { ExPolygon expolygon{{ {91294454,31032190},{11294481,31032190},{11294481,29967810},{44969182,29967810},{89909960,29967808},{91294454,29967808} }}; WHEN("Medial axis is extracted") { - Polylines res = expolygon.medial_axis(1871238, 500000); + Polylines res = expolygon.medial_axis(500000, 1871238); THEN("medial axis is a single line") { REQUIRE(res.size() == 1); } diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index fb36541dd..967cb0e96 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -1918,7 +1918,6 @@ TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]") Lines lines = to_lines(poly); construct_voronoi(lines.begin(), lines.end(), &vd); Slic3r::Voronoi::annotate_inside_outside(vd, lines); - Slic3r::Voronoi::annotate_inside_outside(vd, lines); static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees std::vector skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha); From 70b1b4dfbf9d4a0a2061c01f1b21223eac30ec99 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 16 Nov 2022 15:27:17 +0100 Subject: [PATCH 24/33] Fixed extrusion of gap fill of classic perimeter generator after recent refactoring / sorting of extrusions into LayerIslands. --- src/libslic3r/Fill/Fill.cpp | 44 +++++++++++++++-------- src/libslic3r/GCode.cpp | 53 +++++++++++++++++----------- src/libslic3r/GCode/ToolOrdering.cpp | 2 ++ src/libslic3r/PerimeterGenerator.cpp | 14 ++++---- 4 files changed, 71 insertions(+), 42 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 0a54ae1fa..e7f6b1a76 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -539,20 +539,36 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: } } - //FIXME Don't copy thin fill extrusions into fills, just use these thin fill extrusions - // from the G-code export directly. -#if 0 - // add thin fill regions - // Unpacks the collection, creates multiple collections per path. - // The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection. - // Why the paths are unpacked? - for (LayerRegion *layerm : m_regions) - for (const ExtrusionEntity *thin_fill : layerm->thin_fills().entities) { - ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection()); - layerm->m_fills.entities.push_back(&collection); - collection.entities.push_back(thin_fill->clone()); - } -#endif + for (LayerSlice &lslice : this->lslices_ex) + for (LayerIsland &island : lslice.islands) { + if (! island.thin_fills.empty()) { + // Copy thin fills into fills packed as a collection. + // Fills are always stored as collections, the rest of the pipeline (wipe into infill, G-code generator) relies on it. + LayerRegion &layerm = *this->get_region(island.perimeters.region()); + ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection()); + layerm.m_fills.entities.push_back(&collection); + collection.entities.reserve(island.thin_fills.size()); + for (uint32_t fill_id : island.thin_fills) + collection.entities.push_back(layerm.thin_fills().entities[fill_id]->clone()); + island.fills.push_back({ island.perimeters.region(), { uint32_t(layerm.m_fills.entities.size() - 1), uint32_t(layerm.m_fills.entities.size()) } }); + } + // Sort the fills by region ID. + std::sort(island.fills.begin(), island.fills.end(), [](auto &l, auto &r){ return l.region() < r.region() || (l.region() == r.region() && *l.begin() < *r.begin()); }); + // Compress continuous fill ranges of the same region. + { + size_t k = 0; + for (size_t i = 0; i < island.fills.size(); ++ i) { + uint32_t region_id = island.fills[i].region(); + uint32_t begin = *island.fills[i].begin(); + uint32_t end = *island.fills[i].end(); + size_t j = i + 1; + for (; j < island.fills.size() && island.fills[j].region() == region_id && *island.fills[j].begin() == end; ++ j) + end = *island.fills[j].end(); + island.fills[k ++] = { region_id, { begin, end } }; + } + island.fills.erase(island.fills.begin() + k, island.fills.end()); + } + } #ifndef NDEBUG for (LayerRegion *layerm : m_regions) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 12342ddca..9aef29660 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2355,21 +2355,28 @@ void GCode::process_layer_single_object( ExtrusionEntitiesPtr temp_fill_extrusions; if (const Layer *layer = layer_to_print.object_layer; layer) for (const LayerSlice &lslice : layer->lslices_ex) { - auto extrude_infill_range = [&](const LayerRegion &layerm, const ExtrusionEntityCollection &fills, const ExtrusionRange fill_range, bool ironing) { + auto extrude_infill_range = [&]( + const LayerRegion &layerm, const ExtrusionEntityCollection &fills, + LayerExtrusionRanges::const_iterator it_fill_ranges_begin, LayerExtrusionRanges::const_iterator it_fill_ranges_end, bool ironing) { // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); temp_fill_extrusions.clear(); - for (uint32_t fill_id : fill_range) - if (auto *eec = static_cast(layerm.fills().entities[fill_id]); - (eec->role() == erIroning) == ironing && shall_print_this_extrusion_collection(eec, region)) { - if (eec->can_reverse()) - // Flatten the infill collection for better path planning. - for (auto *ee : eec->entities) - temp_fill_extrusions.emplace_back(ee); - else - temp_fill_extrusions.emplace_back(eec); + for (auto it_fill_range = it_fill_ranges_begin; it_fill_range != it_fill_ranges_end; ++ it_fill_range) { + assert(it_fill_range->region() == it_fill_ranges_begin->region()); + for (uint32_t fill_id : *it_fill_range) { + assert(dynamic_cast(fills.entities[fill_id])); + if (auto *eec = static_cast(fills.entities[fill_id]); + (eec->role() == erIroning) == ironing && shall_print_this_extrusion_collection(eec, region)) { + if (eec->can_reverse()) + // Flatten the infill collection for better path planning. + for (auto *ee : eec->entities) + temp_fill_extrusions.emplace_back(ee); + else + temp_fill_extrusions.emplace_back(eec); + } } + } if (! temp_fill_extrusions.empty()) { init_layer_delayed(); m_config.apply(region.config()); @@ -2395,7 +2402,8 @@ void GCode::process_layer_single_object( // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); bool first = true; - for (uint32_t perimeter_id : island.perimeters) + for (uint32_t perimeter_id : island.perimeters) { + assert(dynamic_cast(layerm.perimeters().entities[perimeter_id])); if (const auto *eec = static_cast(layerm.perimeters().entities[perimeter_id]); shall_print_this_extrusion_collection(eec, region)) { // This may not apply to Arachne, but maybe the Arachne gap fill should disable reverse as well? @@ -2408,15 +2416,15 @@ void GCode::process_layer_single_object( for (const ExtrusionEntity *ee : *eec) gcode += this->extrude_entity(*ee, comment_perimeter, -1.); } + } }; auto process_infill = [&]() { - for (LayerExtrusionRange fill_range : island.fills) { - const LayerRegion &layerm = *layer->get_region(fill_range.region()); - extrude_infill_range(layerm, layerm.fills(), fill_range, false /* normal extrusions, not ironing */); - } - { - const LayerRegion &layerm = *layer->get_region(island.perimeters.region()); - extrude_infill_range(layerm, layerm.thin_fills(), island.thin_fills, false); + for (auto it = island.fills.begin(); it != island.fills.end(); ++ it) { + // Gather range of fill ranges with the same region. + auto it_end = it; + for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ; + const LayerRegion &layerm = *layer->get_region(it->region()); + extrude_infill_range(layerm, layerm.fills(), it, it_end, false /* normal extrusions, not ironing */); } }; if (print.config().infill_first) { @@ -2432,9 +2440,12 @@ void GCode::process_layer_single_object( // First Ironing changes extrusion rate quickly, second single ironing may be done over multiple perimeter regions. // Ironing in a second phase is safer, but it may be less efficient. for (const LayerIsland &island : lslice.islands) { - for (LayerExtrusionRange fill_range : island.fills) { - const LayerRegion &layerm = *layer->get_region(fill_range.region()); - extrude_infill_range(layerm, layerm.fills(), fill_range, true /* ironing, not normal extrusions */); + for (auto it = island.fills.begin(); it != island.fills.end(); ++ it) { + // Gather range of fill ranges with the same region. + auto it_end = it; + for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ; + const LayerRegion &layerm = *layer->get_region(it->region()); + extrude_infill_range(layerm, layerm.fills(), it, it_end, true /* ironing, not normal extrusions */); } } } diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index b4d563bb1..5e14035d7 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -772,6 +772,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print, const for (const ExtrusionEntity* ee : layerm->fills()) { // iterate through all infill Collections auto* fill = dynamic_cast(ee); + assert(fill); if (!is_overriddable(*fill, lt, print.config(), *object, region) || is_entity_overridden(fill, copy) ) @@ -795,6 +796,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print, const // Now the same for perimeters - see comments above for explanation: for (const ExtrusionEntity* ee : layerm->perimeters()) { // iterate through all perimeter Collections auto* fill = dynamic_cast(ee); + assert(fill); if (is_overriddable(*fill, lt, print.config(), *object, region) && ! is_entity_overridden(fill, copy)) set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies); } diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index a3540ff09..8ff53f96f 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -114,7 +114,7 @@ ExtrusionMultiPath PerimeterGenerator::thick_polyline_to_multi_path(const ThickP return multi_path; } -static void variable_width(const ThickPolylines &polylines, ExtrusionRole role, const Flow &flow, std::vector &out) +static void variable_width_classic(const ThickPolylines &polylines, ExtrusionRole role, const Flow &flow, std::vector &out) { // This value determines granularity of adaptive width, as G-code does not allow // variable extrusion within a single move; this value shall only affect the amount @@ -251,7 +251,7 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy using PerimeterGeneratorLoops = std::vector; -static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) +static ExtrusionEntityCollection traverse_loops_classic(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 @@ -322,7 +322,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator::Parame // Append thin walls to the nearest-neighbor search (only for first iteration) if (! thin_walls.empty()) { - variable_width(thin_walls, erExternalPerimeter, params.ext_perimeter_flow, coll.entities); + variable_width_classic(thin_walls, erExternalPerimeter, params.ext_perimeter_flow, coll.entities); thin_walls.clear(); } @@ -342,7 +342,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator::Parame } else { const PerimeterGeneratorLoop &loop = loops[idx.first]; assert(thin_walls.empty()); - ExtrusionEntityCollection children = traverse_loops(params, lower_slices_polygons_cache, loop.children, thin_walls); + ExtrusionEntityCollection children = traverse_loops_classic(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; @@ -623,7 +623,7 @@ void PerimeterGenerator::process_arachne( // Loops with the external thin walls ExtrusionEntityCollection &out_loops, // Gaps without the thin walls - ExtrusionEntityCollection &out_gap_fill, + ExtrusionEntityCollection & /* out_gap_fill */, // Infills without the gap fills ExPolygons &out_fill_expolygons) { @@ -1043,7 +1043,7 @@ void PerimeterGenerator::process_classic( } } // at this point, all loops should be in contours[0] - ExtrusionEntityCollection entities = traverse_loops(params, lower_slices_polygons_cache, contours.front(), thin_walls); + ExtrusionEntityCollection entities = traverse_loops_classic(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 @@ -1069,7 +1069,7 @@ void PerimeterGenerator::process_classic( ex.medial_axis(min, max, &polylines); if (! polylines.empty()) { ExtrusionEntityCollection gap_fill; - variable_width(polylines, erGapFill, params.solid_infill_flow, gap_fill.entities); + variable_width_classic(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 From 30fbdd123525e1ea04d4af7593efc20a4b223147 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 16 Nov 2022 18:14:42 +0100 Subject: [PATCH 25/33] Fixed Z chaining of layer islands. --- src/libslic3r/Layer.cpp | 57 ++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 957603e8b..c85194543 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -120,28 +120,45 @@ static void connect_layer_slices( void visit(const ClipperLib_Z::PolyNode &polynode) { +#ifndef NDEBUG + auto assert_intersection_valid = [this](int i, int j) { + assert(i != j); + if (i > j) + std::swap(i, j); + assert(i >= m_offset_below); + assert(i < m_offset_above); + assert(j >= m_offset_above); + assert(j < m_offset_end); + return true; + }; +#endif // NDEBUG if (polynode.Contour.size() >= 3) { + // If there is an intersection point, it should indicate which contours (one from layer below, the other from layer above) intersect. + // Otherwise the contour is fully inside another contour. int32_t i = 0, j = 0; - double area = 0; for (int icontour = 0; icontour <= polynode.ChildCount(); ++ icontour) { - const ClipperLib_Z::Path &contour = icontour == 0 ? polynode.Contour : polynode.Childs[icontour - 1]->Contour; + const bool first = icontour == 0; + const ClipperLib_Z::Path &contour = first ? polynode.Contour : polynode.Childs[icontour - 1]->Contour; if (contour.size() >= 3) { - area = ClipperLib_Z::Area(contour); - int32_t i = contour.front().z(); - int32_t j = i; - if (i < 0) { - std::tie(i, j) = m_intersections[-i - 1]; - } else { - for (const ClipperLib_Z::IntPoint& pt : contour) { - j = pt.z(); - if (j < 0) { - std::tie(i, j) = m_intersections[-j - 1]; - goto end; - } - else if (i != j) - goto end; + if (first) { + i = contour.front().z(); + j = i; + if (i < 0) { + std::tie(i, j) = m_intersections[-i - 1]; + assert(assert_intersection_valid(i, j)); + goto end; } } + for (const ClipperLib_Z::IntPoint& pt : contour) { + j = pt.z(); + if (j < 0) { + std::tie(i, j) = m_intersections[-j - 1]; + assert(assert_intersection_valid(i, j)); + goto end; + } + else if (i != j) + goto end; + } } } end: @@ -175,23 +192,21 @@ static void connect_layer_slices( } } } else { + assert(assert_intersection_valid(i, j)); if (i > j) std::swap(i, j); - assert(i >= m_offset_below); - assert(i < m_offset_above); i -= m_offset_below; - assert(j >= m_offset_above); - assert(j < m_offset_end); j -= m_offset_above; found = true; } if (found) { // Subtract area of holes from the area of outer contour. + double area = ClipperLib_Z::Area(polynode.Contour); for (int icontour = 0; icontour < polynode.ChildCount(); ++ icontour) area -= ClipperLib_Z::Area(polynode.Childs[icontour]->Contour); // Store the links and area into the contours. LayerSlice::Links &links_below = m_below.lslices_ex[i].overlaps_above; - LayerSlice::Links &links_above = m_above.lslices_ex[i].overlaps_below; + LayerSlice::Links &links_above = m_above.lslices_ex[j].overlaps_below; LayerSlice::Link key{ j }; auto it_below = std::lower_bound(links_below.begin(), links_below.end(), key, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; }); if (it_below != links_below.end() && it_below->slice_idx == j) { From 3713f09a8e461435d66cb375ddd41fb5f22bf7ef Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 16 Nov 2022 18:41:48 +0100 Subject: [PATCH 26/33] Follow-up to 30fbdd123525e1ea04d4af7593efc20a4b223147 Fixed one more bug in Z chaining of layer islands. --- src/libslic3r/Layer.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index c85194543..97c98a0e8 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -175,18 +175,22 @@ static void connect_layer_slices( if (lslice.bbox.contains(pt) && m_above.lslices[l].contains(pt)) { found = true; j = l; + assert(i >= 0 && i < m_below.lslices_ex.size()); + assert(j >= 0 && j < m_above.lslices_ex.size()); break; } } } else { // Index of an island above. Look-it up in the island below. assert(j < m_offset_end); - j -= m_offset_below; + j -= m_offset_above; for (int l = int(m_below.lslices_ex.size()) - 1; l >= 0; -- l) { LayerSlice &lslice = m_below.lslices_ex[l]; if (lslice.bbox.contains(pt) && m_below.lslices[l].contains(pt)) { found = true; i = l; + assert(i >= 0 && i < m_below.lslices_ex.size()); + assert(j >= 0 && j < m_above.lslices_ex.size()); break; } } @@ -197,6 +201,8 @@ static void connect_layer_slices( std::swap(i, j); i -= m_offset_below; j -= m_offset_above; + assert(i >= 0 && i < m_below.lslices_ex.size()); + assert(j >= 0 && j < m_above.lslices_ex.size()); found = true; } if (found) { From 19e7c55a2af1b7e1abb371ab50199a058408b46b Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 18 Nov 2022 12:27:23 +0100 Subject: [PATCH 27/33] TreeSupports: Debugging the collision caches --- src/libslic3r/TreeModelVolumes.cpp | 46 ++++++++++++++++++++++++++++++ src/libslic3r/TreeModelVolumes.hpp | 3 ++ src/libslic3r/TreeSupport.cpp | 4 +-- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/TreeModelVolumes.cpp b/src/libslic3r/TreeModelVolumes.cpp index e098636fd..b416db634 100644 --- a/src/libslic3r/TreeModelVolumes.cpp +++ b/src/libslic3r/TreeModelVolumes.cpp @@ -291,6 +291,42 @@ void TreeModelVolumes::precalculate(const coord_t max_layer) // m_precalculated = true; BOOST_LOG_TRIVIAL(info) << "Precalculating collision took" << dur_col << " ms. Precalculating avoidance took " << dur_avo << " ms."; + +#if 0 + // Paint caches into SVGs: + auto paint_cache_into_SVGs = [this](const RadiusLayerPolygonCache &cache, std::string_view name) { + const std::vector>> sorted = cache.sorted(); + static constexpr const std::string_view colors[] = { + "red", "green", "blue", "magenta", "orange" + }; + static constexpr const size_t num_colors = sizeof(colors) / sizeof(colors[0]); + for (size_t i = 0; i < sorted.size();) { + // Find range of cache items with the same layer index. + size_t j = i; + for (++ j; j < sorted.size() && sorted[i].first.second == sorted[j].first.second; ++ j) ; + // Collect expolygons in reverse order (largest to smallest). + std::vector> expolygons_with_attributes; + for (int k = int(j - 1); k >= int(i); -- k) { + std::string legend = format("radius-%1%", unscaled(sorted[k].first.first)); + expolygons_with_attributes.push_back({ union_ex(sorted[k].second), SVG::ExPolygonAttributes(legend, std::string(colors[(k - int(i)) % num_colors]), 1.) }); + } + // Render the range of per radius collision polygons into a common SVG. + SVG::export_expolygons(debug_out_path("treesupport_cache-%s-%d.svg", name.data(), sorted[i].first.second), expolygons_with_attributes); + i = j; + } + }; + paint_cache_into_SVGs(m_collision_cache, "collision_cache"); + paint_cache_into_SVGs(m_collision_cache_holefree, "collision_cache_holefree"); + paint_cache_into_SVGs(m_avoidance_cache, "avoidance_cache"); + paint_cache_into_SVGs(m_avoidance_cache_slow, "avoidance_cache_slow"); + paint_cache_into_SVGs(m_avoidance_cache_to_model, "avoidance_cache_to_model"); + paint_cache_into_SVGs(m_avoidance_cache_to_model_slow, "avoidance_cache_to_model_slow"); + paint_cache_into_SVGs(m_placeable_areas_cache, "placable_areas_cache"); + paint_cache_into_SVGs(m_avoidance_cache_holefree, "avoidance_cache_holefree"); + paint_cache_into_SVGs(m_avoidance_cache_holefree_to_model, "avoidance_cache_holefree_to_model"); + paint_cache_into_SVGs(m_wall_restrictions_cache, "wall_restrictions_cache"); + paint_cache_into_SVGs(m_wall_restrictions_cache_min, "wall_restrictions_cache_min"); +#endif } const Polygons& TreeModelVolumes::getCollision(const coord_t orig_radius, LayerIndex layer_idx, bool min_xy_dist) const @@ -783,4 +819,14 @@ coord_t TreeModelVolumes::ceilRadius(const coord_t radius) const return out; } +// For debugging purposes, sorted by layer index, then by radius. +std::vector>> TreeModelVolumes::RadiusLayerPolygonCache::sorted() const +{ + std::vector>> out; + for (auto it = this->data.begin(); it != this->data.end(); ++ it) + out.emplace_back(it->first, it->second); + std::sort(out.begin(), out.end(), [](auto &l, auto &r){ return l.first.second < r.first.second || (l.first.second == r.first.second) && l.first.first < r.first.first; }); + return out; +} + } // namespace Slic3r::FFFTreeSupport diff --git a/src/libslic3r/TreeModelVolumes.hpp b/src/libslic3r/TreeModelVolumes.hpp index eea271bd4..0ced2f422 100644 --- a/src/libslic3r/TreeModelVolumes.hpp +++ b/src/libslic3r/TreeModelVolumes.hpp @@ -366,6 +366,9 @@ private: return max_layer; } + // For debugging purposes, sorted by layer index, then by radius. + [[nodiscard]] std::vector>> sorted() const; + private: RadiusLayerPolygonCacheData data; mutable std::mutex mutex; diff --git a/src/libslic3r/TreeSupport.cpp b/src/libslic3r/TreeSupport.cpp index 6c5b95406..1f0aa00b2 100644 --- a/src/libslic3r/TreeSupport.cpp +++ b/src/libslic3r/TreeSupport.cpp @@ -3728,10 +3728,10 @@ static void generate_support_areas(Print &print, const BuildVolume &build_volume for (size_t layer_idx = 0; layer_idx < move_bounds.size(); ++layer_idx) { Polygons polys; for (auto& area : move_bounds[layer_idx]) - append(polys, area->influence_area); + append(polys, area.influence_area); if (auto begin = move_bounds[layer_idx].begin(); begin != move_bounds[layer_idx].end()) SVG::export_expolygons(debug_out_path("treesupport-initial_areas-%d.svg", layer_idx), - { { { union_ex(volumes.getWallRestriction(config.getCollisionRadius((*begin)->state), layer_idx, (*begin)->state.use_min_xy_dist)) }, + { { { union_ex(volumes.getWallRestriction(config.getCollisionRadius(begin->state), layer_idx, begin->state.use_min_xy_dist)) }, { "wall_restricrictions", "gray", 0.5f } }, { { union_ex(polys) }, { "parent", "red", "black", "", scaled(0.1f), 0.5f } } }); } From da00cedc843a444fdf7b1da0ab2ec76242d2113d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 18 Nov 2022 17:16:47 +0100 Subject: [PATCH 28/33] Fixed polygons_simplify() to correctly handle holes. This fixes some missing tree / organic supports in the middle of an object surrounded by walls all around. --- src/libslic3r/ClipperUtils.cpp | 2 +- src/libslic3r/Polygon.cpp | 32 +++++++++++++++++++++-------- src/libslic3r/Polygon.hpp | 37 ++++++++++++++++------------------ 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 7f90fd2f0..ea5015798 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -931,7 +931,7 @@ ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear if (! preserve_collinear) return union_ex(simplify_polygons(subject, false)); - ClipperLib::PolyTree polytree; + ClipperLib::PolyTree polytree; ClipperLib::Clipper c; c.PreserveCollinear(true); c.StrictlySimple(true); diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 717e25aed..d342f3d91 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -99,9 +99,11 @@ void Polygon::douglas_peucker(double tolerance) this->points = std::move(p); } -// this only works on CCW polygons as CW will be ripped out by Clipper's simplify_polygons() Polygons Polygon::simplify(double tolerance) const { + // Works on CCW polygons only, CW contour will be reoriented to CCW by Clipper's simplify_polygons()! + assert(this->is_counter_clockwise()); + // repeat first point at the end in order to apply Douglas-Peucker // on the whole polygon Points points = this->points; @@ -114,13 +116,6 @@ Polygons Polygon::simplify(double tolerance) const return simplify_polygons(pp); } -void Polygon::simplify(double tolerance, Polygons &polygons) const -{ - Polygons pp = this->simplify(tolerance); - polygons.reserve(polygons.size() + pp.size()); - polygons.insert(polygons.end(), pp.begin(), pp.end()); -} - // Only call this on convex polygons or it will return invalid results void Polygon::triangulate_convex(Polygons* polygons) const { @@ -562,6 +557,27 @@ void remove_collinear(Polygons &polys) remove_collinear(poly); } +Polygons polygons_simplify(const Polygons &source_polygons, double tolerance) +{ + Polygons out; + out.reserve(source_polygons.size()); + for (const Polygon &source_polygon : source_polygons) { + // Run Douglas / Peucker simplification algorithm on an open polyline (by repeating the first point at the end of the polyline), + Points simplified = MultiPoint::_douglas_peucker(to_polyline(source_polygon).points, tolerance); + // then remove the last (repeated) point. + simplified.pop_back(); + // Simplify the decimated contour by ClipperLib. + bool ccw = ClipperLib::Area(simplified) > 0.; + for (Points &path : ClipperLib::SimplifyPolygons(ClipperUtils::SinglePathProvider(simplified), ClipperLib::pftNonZero)) { + if (! ccw) + // ClipperLib likely reoriented negative area contours to become positive. Reverse holes back to CW. + std::reverse(path.begin(), path.end()); + out.emplace_back(std::move(path)); + } + } + return out; +} + // Do polygons match? If they match, they must have the same topology, // however their contours may be rotated. bool polygons_match(const Polygon &l, const Polygon &r) diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 8a4a2ba0a..b0d33db53 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -67,8 +67,8 @@ public: bool on_boundary(const Point &point, double eps) const { return (this->point_projection(point) - point).cast().squaredNorm() < eps * eps; } + // Works on CCW polygons only, CW contour will be reoriented to CCW by Clipper's simplify_polygons()! Polygons simplify(double tolerance) const; - void simplify(double tolerance, Polygons &polygons) const; void densify(float min_length, std::vector* lengths = nullptr); void triangulate_convex(Polygons* polygons) const; Point centroid() const; @@ -148,14 +148,7 @@ inline void polygons_append(Polygons &dst, Polygons &&src) } } -inline Polygons polygons_simplify(const Polygons &polys, double tolerance) -{ - Polygons out; - out.reserve(polys.size()); - for (const Polygon &p : polys) - polygons_append(out, p.simplify(tolerance)); - return out; -} +Polygons polygons_simplify(const Polygons &polys, double tolerance); inline void polygons_rotate(Polygons &polys, double angle) { @@ -216,18 +209,22 @@ inline Lines to_lines(const Polygons &polys) return lines; } -inline Polylines to_polylines(const Polygons &polys) +inline Polyline to_polyline(const Polygon &polygon) { - Polylines polylines; - polylines.assign(polys.size(), Polyline()); - size_t idx = 0; - for (Polygons::const_iterator it = polys.begin(); it != polys.end(); ++ it) { - Polyline &pl = polylines[idx ++]; - pl.points = it->points; - pl.points.push_back(it->points.front()); - } - assert(idx == polylines.size()); - return polylines; + Polyline out; + out.points.reserve(polygon.size() + 1); + out.points.assign(polygon.points.begin(), polygon.points.end()); + out.points.push_back(polygon.points.front()); + return out; +} + +inline Polylines to_polylines(const Polygons &polygons) +{ + Polylines out; + out.reserve(polygons.size()); + for (const Polygon &polygon : polygons) + out.emplace_back(to_polyline(polygon)); + return out; } inline Polylines to_polylines(Polygons &&polys) From 423503a6c5c2c3d9b1edec6b8145fbed6c845236 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 22 Nov 2022 09:27:11 +0100 Subject: [PATCH 29/33] Follow-up to 3713f09a8e461435d66cb375ddd41fb5f22bf7ef 30fbdd123525e1ea04d4af7593efc20a4b223147 Fixed duplication of infills. --- src/libslic3r/Fill/Fill.cpp | 7 ++++--- src/libslic3r/GCode.cpp | 6 ++++-- src/libslic3r/GCodeReader.cpp | 2 +- src/libslic3r/Layer.hpp | 8 ++++++++ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index e7f6b1a76..9fd20cf9f 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -403,7 +403,7 @@ static void insert_fills_into_islands(Layer &layer, uint32_t fill_region_id, uin } assert(island); if (island) - island->fills.push_back(LayerExtrusionRange{ fill_region_id, { fill_begin, fill_end }}); + island->add_fill_range(LayerExtrusionRange{ fill_region_id, { fill_begin, fill_end }}); } } } @@ -550,14 +550,14 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: collection.entities.reserve(island.thin_fills.size()); for (uint32_t fill_id : island.thin_fills) collection.entities.push_back(layerm.thin_fills().entities[fill_id]->clone()); - island.fills.push_back({ island.perimeters.region(), { uint32_t(layerm.m_fills.entities.size() - 1), uint32_t(layerm.m_fills.entities.size()) } }); + island.add_fill_range({ island.perimeters.region(), { uint32_t(layerm.m_fills.entities.size() - 1), uint32_t(layerm.m_fills.entities.size()) } }); } // Sort the fills by region ID. std::sort(island.fills.begin(), island.fills.end(), [](auto &l, auto &r){ return l.region() < r.region() || (l.region() == r.region() && *l.begin() < *r.begin()); }); // Compress continuous fill ranges of the same region. { size_t k = 0; - for (size_t i = 0; i < island.fills.size(); ++ i) { + for (size_t i = 0; i < island.fills.size();) { uint32_t region_id = island.fills[i].region(); uint32_t begin = *island.fills[i].begin(); uint32_t end = *island.fills[i].end(); @@ -565,6 +565,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: for (; j < island.fills.size() && island.fills[j].region() == region_id && *island.fills[j].begin() == end; ++ j) end = *island.fills[j].end(); island.fills[k ++] = { region_id, { begin, end } }; + i = j; } island.fills.erase(island.fills.begin() + k, island.fills.end()); } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 5776541ae..06801b0e4 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2430,12 +2430,13 @@ void GCode::process_layer_single_object( } }; auto process_infill = [&]() { - for (auto it = island.fills.begin(); it != island.fills.end(); ++ it) { + for (auto it = island.fills.begin(); it != island.fills.end();) { // Gather range of fill ranges with the same region. auto it_end = it; for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ; const LayerRegion &layerm = *layer->get_region(it->region()); extrude_infill_range(layerm, layerm.fills(), it, it_end, false /* normal extrusions, not ironing */); + it = it_end; } }; if (print.config().infill_first) { @@ -2451,12 +2452,13 @@ void GCode::process_layer_single_object( // First Ironing changes extrusion rate quickly, second single ironing may be done over multiple perimeter regions. // Ironing in a second phase is safer, but it may be less efficient. for (const LayerIsland &island : lslice.islands) { - for (auto it = island.fills.begin(); it != island.fills.end(); ++ it) { + for (auto it = island.fills.begin(); it != island.fills.end();) { // Gather range of fill ranges with the same region. auto it_end = it; for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ; const LayerRegion &layerm = *layer->get_region(it->region()); extrude_infill_range(layerm, layerm.fills(), it, it_end, true /* ironing, not normal extrusions */); + it = it_end; } } } diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index cd6cedf51..a45ea8439 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -70,7 +70,7 @@ const char* GCodeReader::parse_line_internal(const char *ptr, const char *end, G if (axis != NUM_AXES_WITH_UNKNOWN) { // Try to parse the numeric value. double v; - c = skip_whitespaces(++c); + c = skip_whitespaces(++ c); auto [pend, ec] = fast_float::from_chars(c, end, v); if (pend != c && is_end_of_word(*pend)) { // The axis value has been parsed correctly. diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 869004cd5..0aa4c2aa3 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -256,6 +256,14 @@ public: // Point centroid; bool has_extrusions() const { return ! this->perimeters.empty() || ! this->fills.empty(); } + + void add_fill_range(const LayerExtrusionRange &new_fill_range) { + // Compress ranges. + if (! this->fills.empty() && this->fills.back().region() == new_fill_range.region() && *this->fills.back().end() == *new_fill_range.begin()) + this->fills.back() = { new_fill_range.region(), { *this->fills.back().begin(), *new_fill_range.end() } }; + else + this->fills.push_back(new_fill_range); + } }; static constexpr const size_t LayerIslandsStaticSize = 1; From af6b022878b93adc989d64513fe139de8276ea62 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 22 Nov 2022 09:28:08 +0100 Subject: [PATCH 30/33] Optimization of Cooling buffer by replacing atof() and atoi() with std::from_chars() and fast_float::from_chars() --- src/libslic3r/GCode/CoolingBuffer.cpp | 43 +++++++++++++++++---------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index ef0d63c93..f8e1dc6d7 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -14,6 +14,8 @@ #include +#include + namespace Slic3r { CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0) @@ -339,12 +341,13 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: // for a sequence of extrusion moves. size_t active_speed_modifier = size_t(-1); + std::vector new_pos; for (; *line_start != 0; line_start = line_end) { while (*line_end != '\n' && *line_end != 0) ++ line_end; // sline will not contain the trailing '\n'. - std::string sline(line_start, line_end); + std::string_view sline(line_start, line_end - line_start); // CoolingLine will contain the trailing '\n'. if (*line_end == '\n') ++ line_end; @@ -358,20 +361,18 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: if (line.type) { // G0, G1 or G92 // Parse the G-code line. - std::vector new_pos(current_pos); - const char *c = sline.data() + 3; - for (;;) { + new_pos = current_pos; + for (auto c = sline.begin() + 3;;) { // Skip whitespaces. - for (; *c == ' ' || *c == '\t'; ++ c); - if (*c == 0 || *c == ';') + for (; c != sline.end() && (*c == ' ' || *c == '\t'); ++ c); + if (c == sline.end() || *c == ';') break; - assert(is_decimal_separator_point()); // for atof // Parse the axis. size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1); if (axis != size_t(-1)) { - new_pos[axis] = float(atof(++c)); + auto [pend, ec] = fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]); if (axis == 4) { // Convert mm/min to mm/sec. new_pos[4] /= 60.f; @@ -381,7 +382,7 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: } } // Skip this word. - for (; *c != ' ' && *c != '\t' && *c != 0; ++ c); + for (; c != sline.end() && *c != ' ' && *c != '\t'; ++ c); } bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER"); bool wipe = boost::contains(sline, ";_WIPE"); @@ -460,7 +461,8 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: } active_speed_modifier = size_t(-1); } else if (boost::starts_with(sline, m_toolchange_prefix)) { - unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + m_toolchange_prefix.size()); + unsigned int new_extruder; + auto res = std::from_chars(sline.data() + m_toolchange_prefix.size(), sline.data() + sline.size(), new_extruder); // Only change extruder in case the number is meaningful. User could provide an out-of-range index through custom gcodes - those shall be ignored. if (new_extruder < map_extruder_to_per_extruder_adjustment.size()) { if (new_extruder != current_extruder) { @@ -485,10 +487,15 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: line.type = CoolingLine::TYPE_G4; size_t pos_S = sline.find('S', 3); size_t pos_P = sline.find('P', 3); - assert(is_decimal_separator_point()); // for atof - line.time = line.time_max = float( - (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) : - (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.); + bool has_S = pos_S > 0; + bool has_P = pos_P > 0; + if (has_S || has_P) { + auto [pend, ec] = fast_float::from_chars(sline.data() + (has_S ? pos_S : pos_P) + 1, sline.data() + sline.size(), line.time); + if (has_P) + line.time *= 0.001f; + } else + line.time = 0; + line.time_max = line.time; } if (line.type != 0) adjustment->lines.emplace_back(std::move(line)); @@ -778,7 +785,8 @@ std::string CoolingBuffer::apply_layer_cooldown( if (line_start > pos) new_gcode.append(pos, line_start - pos); if (line->type & CoolingLine::TYPE_SET_TOOL) { - unsigned int new_extruder = (unsigned int)atoi(line_start + m_toolchange_prefix.size()); + unsigned int new_extruder; + auto res = std::from_chars(line_start + m_toolchange_prefix.size(), line_end, new_extruder); if (new_extruder != m_current_extruder) { m_current_extruder = new_extruder; change_extruder_set_fan(); @@ -804,7 +812,10 @@ std::string CoolingBuffer::apply_layer_cooldown( // Remove the F word from the current G-code line. bool remove = false; assert(fpos != nullptr); - new_feedrate = line->slowdown ? int(floor(60. * line->feedrate + 0.5)) : atoi(fpos); + if (line->slowdown) + new_feedrate = int(floor(60. * line->feedrate + 0.5)); + else + auto res = std::from_chars(fpos, line_end, new_feedrate); if (new_feedrate == current_feedrate) { // No need to change the F value. if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_ADJUSTABLE_EMPTY | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) From e6d10fc74737430caac862d7bbe9a06fe9cdeb80 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 28 Nov 2022 10:07:22 +0100 Subject: [PATCH 31/33] Fixed crash on reslicing after infill invalidation. The crash was introduced with sorting the extrusions into islands. --- src/libslic3r/Fill/Fill.cpp | 14 ++++++++++---- src/libslic3r/Layer.cpp | 1 + src/libslic3r/Layer.hpp | 2 ++ src/libslic3r/PrintObject.cpp | 1 + 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 9fd20cf9f..c962bbeb0 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -408,12 +408,18 @@ static void insert_fills_into_islands(Layer &layer, uint32_t fill_region_id, uin } } -// friend to Layer +void Layer::clear_fills() +{ + for (LayerRegion *layerm : m_regions) + layerm->m_fills.clear(); + for (LayerSlice &lslice : lslices_ex) + for (LayerIsland &island : lslice.islands) + island.fills.clear(); +} + void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) { - for (LayerRegion *layerm : m_regions) - layerm->m_fills.clear(); - + this->clear_fills(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING // this->export_region_fill_surfaces_to_svg_debug("10_fill-initial"); diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 97c98a0e8..2fa0c5c3c 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -443,6 +443,7 @@ void Layer::make_perimeters() layerm.m_fill_expolygons_composite_bboxes.clear(); }; + // Remove layer islands, remove references to perimeters and fills from these layer islands to LayerRegion ExtrusionEntities. for (LayerSlice &lslice : this->lslices_ex) lslice.islands.clear(); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 0aa4c2aa3..024ed41a4 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -390,6 +390,8 @@ protected: slice_z(slice_z), print_z(print_z), height(height), m_id(id), m_object(object) {} virtual ~Layer(); + // Clear fill extrusions, remove them from layer islands. + void clear_fills(); private: void sort_perimeters_into_islands( diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index e7f2abe1c..1b3bf5d44 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -131,6 +131,7 @@ void PrintObject::make_perimeters() // Revert the typed slices into untyped slices. if (m_typed_slices) { for (Layer *layer : m_layers) { + layer->clear_fills(); layer->restore_untyped_slices(); m_print->throw_if_canceled(); } From 3fa16155181f2d887cc100ade143067f482930aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Miku=C5=A1?= Date: Mon, 28 Nov 2022 10:47:04 +0100 Subject: [PATCH 32/33] Refactoring FDM support spots generator to use Z-Graph (#11) * import updates from curling avoidance branch * fix compilation issues * Refactoring FDM support spots generator to use the new Z-graph built during slicing * fix local issues bugs * fix bugs, add new filter for too short extrusions * fix bugs with nonexistent weakest area * Use links of Z graph after fix, format the code * remove unnecesary includes --- src/libslic3r/PrintObject.cpp | 2 +- src/libslic3r/SupportSpotsGenerator.cpp | 1337 +++++++++-------------- src/libslic3r/SupportSpotsGenerator.hpp | 9 +- 3 files changed, 520 insertions(+), 828 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1b3bf5d44..ab50c105d 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -425,7 +425,7 @@ void PrintObject::generate_support_spots() [](const ModelVolume* mv){return mv->supported_facets.empty();}) ) { SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values}; - auto [issues, malformations] = SupportSpotsGenerator::full_search(this, params); + SupportSpotsGenerator::Issues issues = SupportSpotsGenerator::full_search(this, params); auto obj_transform = this->trafo_centered(); for (ModelVolume *model_volume : this->model_object()->volumes) { diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index abaaabfbe..66c6de7a9 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -6,6 +6,8 @@ #include "Line.hpp" #include "Point.hpp" #include "Polygon.hpp" +#include "Print.hpp" +#include "Tesselate.hpp" #include "libslic3r.h" #include "tbb/parallel_for.h" #include "tbb/blocked_range.h" @@ -18,6 +20,7 @@ #include #include #include +#include #include "AABBTreeLines.hpp" #include "KDTreeIndirect.hpp" @@ -26,7 +29,7 @@ #include "Geometry/ConvexHull.hpp" // #define DETAILED_DEBUG_LOGS -//#define DEBUG_FILES +// #define DEBUG_FILES #ifdef DEBUG_FILES #include @@ -38,164 +41,74 @@ namespace Slic3r { class ExtrusionLine { public: - ExtrusionLine() : - a(Vec2f::Zero()), b(Vec2f::Zero()), len(0.0f), origin_entity(nullptr) { - } - ExtrusionLine(const Vec2f &a, const Vec2f &b, const ExtrusionEntity *origin_entity) : - a(a), b(b), len((a - b).norm()), origin_entity(origin_entity) { - } + ExtrusionLine() : a(Vec2f::Zero()), b(Vec2f::Zero()), len(0.0f), origin_entity(nullptr) {} + ExtrusionLine(const Vec2f &a, const Vec2f &b, const ExtrusionEntity *origin_entity) + : a(a), b(b), len((a - b).norm()), origin_entity(origin_entity) + {} - float length() { - return (a - b).norm(); - } + float length() { return (a - b).norm(); } - bool is_external_perimeter() const { + bool is_external_perimeter() const + { assert(origin_entity != nullptr); return origin_entity->role() == erExternalPerimeter || origin_entity->role() == erOverhangPerimeter; } - Vec2f a; - Vec2f b; - float len; + Vec2f a; + Vec2f b; + float len; const ExtrusionEntity *origin_entity; - bool support_point_generated = false; - float malformation = 0.0f; + bool support_point_generated = false; + float malformation = 0.0f; static const constexpr int Dim = 2; - using Scalar = Vec2f::Scalar; + using Scalar = Vec2f::Scalar; }; -auto get_a(ExtrusionLine &&l) { - return l.a; -} -auto get_b(ExtrusionLine &&l) { - return l.b; -} +auto get_a(ExtrusionLine &&l) { return l.a; } +auto get_b(ExtrusionLine &&l) { return l.b; } namespace SupportSpotsGenerator { -SupportPoint::SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec3f &direction) : - position(position), force(force), spot_radius(spot_radius), direction(direction) { -} - -static const size_t NULL_ISLAND = std::numeric_limits::max(); +SupportPoint::SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec2f &direction) + : position(position), force(force), spot_radius(spot_radius), direction(direction) +{} using LD = AABBTreeLines::LinesDistancer; -class PixelGrid { - Vec2f pixel_size; - Vec2f origin; - Vec2f size; - Vec2i pixel_count; - - std::vector pixels { }; - -public: - PixelGrid(const PrintObject *po, float resolution) { - pixel_size = Vec2f(resolution, resolution); - - Vec2crd size_half = po->size().head<2>().cwiseQuotient(Vec2crd(2, 2)) + Vec2crd::Ones(); - Vec2f min = unscale(Vec2crd(-size_half.x(), -size_half.y())).cast(); - Vec2f max = unscale(Vec2crd(size_half.x(), size_half.y())).cast(); - - origin = min; - size = max - min; - pixel_count = size.cwiseQuotient(pixel_size).cast() + Vec2i::Ones(); - - pixels.resize(pixel_count.y() * pixel_count.x()); - clear(); - } - - void distribute_edge(const Vec2f &p1, const Vec2f &p2, size_t value) { - Vec2f dir = (p2 - p1); - float length = dir.norm(); - if (length < 0.1) { - return; - } - float step_size = this->pixel_size.x() / 2.0; - - float distributed_length = 0; - while (distributed_length < length) { - float next_len = std::min(length, distributed_length + step_size); - Vec2f location = p1 + ((next_len / length) * dir); - this->access_pixel(location) = value; - - distributed_length = next_len; - } - } - - void clear() { - for (size_t &val : pixels) { - val = NULL_ISLAND; - } - } - - float pixel_area() const { - return this->pixel_size.x() * this->pixel_size.y(); - } - - size_t get_pixel(const Vec2i &coords) const { - return pixels[this->to_pixel_index(coords)]; - } - - Vec2i get_pixel_count() { - return pixel_count; - } - - Vec2f get_pixel_center(const Vec2i &coords) const { - return origin + coords.cast().cwiseProduct(this->pixel_size) - + this->pixel_size.cwiseQuotient(Vec2f(2.0f, 2.0f)); - } - -private: - Vec2i to_pixel_coords(const Vec2f &position) const { - Vec2i pixel_coords = (position - this->origin).cwiseQuotient(this->pixel_size).cast(); - return pixel_coords; - } - - size_t to_pixel_index(const Vec2i &pixel_coords) const { - assert(pixel_coords.x() >= 0); - assert(pixel_coords.x() < pixel_count.x()); - assert(pixel_coords.y() >= 0); - assert(pixel_coords.y() < pixel_count.y()); - - return pixel_coords.y() * pixel_count.x() + pixel_coords.x(); - } - - size_t& access_pixel(const Vec2f &position) { - return pixels[this->to_pixel_index(this->to_pixel_coords(position))]; - } -}; - -struct SupportGridFilter { +struct SupportGridFilter +{ private: Vec3f cell_size; Vec3f origin; Vec3f size; Vec3i cell_count; - std::unordered_set taken_cells { }; + std::unordered_set taken_cells{}; public: - SupportGridFilter(const PrintObject *po, float voxel_size) { + SupportGridFilter(const PrintObject *po, float voxel_size) + { cell_size = Vec3f(voxel_size, voxel_size, voxel_size); Vec2crd size_half = po->size().head<2>().cwiseQuotient(Vec2crd(2, 2)) + Vec2crd::Ones(); - Vec3f min = unscale(Vec3crd(-size_half.x(), -size_half.y(), 0)).cast() - cell_size; - Vec3f max = unscale(Vec3crd(size_half.x(), size_half.y(), po->height())).cast() + cell_size; + Vec3f min = unscale(Vec3crd(-size_half.x(), -size_half.y(), 0)).cast() - cell_size; + Vec3f max = unscale(Vec3crd(size_half.x(), size_half.y(), po->height())).cast() + cell_size; - origin = min; - size = max - min; + origin = min; + size = max - min; cell_count = size.cwiseQuotient(cell_size).cast() + Vec3i::Ones(); } - Vec3i to_cell_coords(const Vec3f &position) const { + Vec3i to_cell_coords(const Vec3f &position) const + { Vec3i cell_coords = (position - this->origin).cwiseQuotient(this->cell_size).cast(); return cell_coords; } - size_t to_cell_index(const Vec3i &cell_coords) const { + size_t to_cell_index(const Vec3i &cell_coords) const + { assert(cell_coords.x() >= 0); assert(cell_coords.x() < cell_count.x()); assert(cell_coords.y() >= 0); @@ -203,43 +116,41 @@ public: assert(cell_coords.z() >= 0); assert(cell_coords.z() < cell_count.z()); - return cell_coords.z() * cell_count.x() * cell_count.y() - + cell_coords.y() * cell_count.x() - + cell_coords.x(); + return cell_coords.z() * cell_count.x() * cell_count.y() + cell_coords.y() * cell_count.x() + cell_coords.x(); } - Vec3f get_cell_center(const Vec3i &cell_coords) const { - return origin + cell_coords.cast().cwiseProduct(this->cell_size) - + this->cell_size.cwiseQuotient(Vec3f(2.0f, 2.0f, 2.0)); + Vec3f get_cell_center(const Vec3i &cell_coords) const + { + return origin + cell_coords.cast().cwiseProduct(this->cell_size) + this->cell_size.cwiseQuotient(Vec3f(2.0f, 2.0f, 2.0)); } - void take_position(const Vec3f &position) { - taken_cells.insert(to_cell_index(to_cell_coords(position))); - } + void take_position(const Vec3f &position) { taken_cells.insert(to_cell_index(to_cell_coords(position))); } - bool position_taken(const Vec3f &position) const { + bool position_taken(const Vec3f &position) const + { return taken_cells.find(to_cell_index(to_cell_coords(position))) != taken_cells.end(); } - }; -struct IslandConnection { - float area { }; - Vec3f centroid_accumulator = Vec3f::Zero(); +struct SliceConnection +{ + float area{}; + Vec3f centroid_accumulator = Vec3f::Zero(); Vec2f second_moment_of_area_accumulator = Vec2f::Zero(); - float second_moment_of_area_covariance_accumulator { }; + float second_moment_of_area_covariance_accumulator{}; - void add(const IslandConnection &other) { + void add(const SliceConnection &other) + { this->area += other.area; this->centroid_accumulator += other.centroid_accumulator; this->second_moment_of_area_accumulator += other.second_moment_of_area_accumulator; this->second_moment_of_area_covariance_accumulator += other.second_moment_of_area_covariance_accumulator; } - void print_info(const std::string &tag) { - Vec3f centroid = centroid_accumulator / area; - Vec2f variance = - (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>())); + void print_info(const std::string &tag) + { + Vec3f centroid = centroid_accumulator / area; + Vec2f variance = (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>())); float covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y(); std::cout << tag << std::endl; std::cout << "area: " << area << std::endl; @@ -249,93 +160,59 @@ struct IslandConnection { } }; -struct Island { - std::unordered_map connected_islands { }; - float volume { }; - Vec3f volume_centroid_accumulator = Vec3f::Zero(); - float sticking_area { }; // for support points present on this layer (or bed extrusions) - Vec3f sticking_centroid_accumulator = Vec3f::Zero(); - Vec2f sticking_second_moment_of_area_accumulator = Vec2f::Zero(); - float sticking_second_moment_of_area_covariance_accumulator { }; - - std::vector external_lines; -}; - -struct LayerIslands { - std::vector islands; - float layer_z; -}; - -float get_flow_width(const LayerRegion *region, ExtrusionRole role) { +float get_flow_width(const LayerRegion *region, ExtrusionRole role) +{ switch (role) { - case ExtrusionRole::erBridgeInfill: - return region->flow(FlowRole::frExternalPerimeter).width(); - case ExtrusionRole::erExternalPerimeter: - return region->flow(FlowRole::frExternalPerimeter).width(); - case ExtrusionRole::erGapFill: - return region->flow(FlowRole::frInfill).width(); - case ExtrusionRole::erPerimeter: - return region->flow(FlowRole::frPerimeter).width(); - case ExtrusionRole::erSolidInfill: - return region->flow(FlowRole::frSolidInfill).width(); - case ExtrusionRole::erInternalInfill: - return region->flow(FlowRole::frInfill).width(); - case ExtrusionRole::erTopSolidInfill: - return region->flow(FlowRole::frTopSolidInfill).width(); - default: - return region->flow(FlowRole::frPerimeter).width(); + case ExtrusionRole::erBridgeInfill: return region->flow(FlowRole::frExternalPerimeter).width(); + case ExtrusionRole::erExternalPerimeter: return region->flow(FlowRole::frExternalPerimeter).width(); + case ExtrusionRole::erGapFill: return region->flow(FlowRole::frInfill).width(); + case ExtrusionRole::erPerimeter: return region->flow(FlowRole::frPerimeter).width(); + case ExtrusionRole::erSolidInfill: return region->flow(FlowRole::frSolidInfill).width(); + case ExtrusionRole::erInternalInfill: return region->flow(FlowRole::frInfill).width(); + case ExtrusionRole::erTopSolidInfill: return region->flow(FlowRole::frTopSolidInfill).width(); + default: return region->flow(FlowRole::frPerimeter).width(); } } // Accumulator of current extrusion path properties // It remembers unsuported distance and maximum accumulated curvature over that distance. // Used to determine local stability issues (too long bridges, extrusion curves into air) -struct ExtrusionPropertiesAccumulator { - float distance = 0; //accumulated distance - float curvature = 0; //accumulated signed ccw angles - float max_curvature = 0; //max absolute accumulated value +struct ExtrusionPropertiesAccumulator +{ + float distance = 0; // accumulated distance + float curvature = 0; // accumulated signed ccw angles + float max_curvature = 0; // max absolute accumulated value - void add_distance(float dist) { - distance += dist; - } + void add_distance(float dist) { distance += dist; } - void add_angle(float ccw_angle) { + void add_angle(float ccw_angle) + { curvature += ccw_angle; max_curvature = std::max(max_curvature, std::abs(curvature)); } - void reset() { - distance = 0; - curvature = 0; + void reset() + { + distance = 0; + curvature = 0; max_curvature = 0; } }; // base function: ((e^(((1)/(x^(2)+1)))-1)/(e-1)) // checkout e.g. here: https://www.geogebra.org/calculator -float gauss(float value, float mean_x_coord, float mean_value, float falloff_speed) { - float shifted = value - mean_x_coord; - float denominator = falloff_speed * shifted * shifted + 1.0f; - float exponent = 1.0f / denominator; - return mean_value * (std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f); -} - -void push_lines(const ExtrusionEntity *e, std::vector& destination) +float gauss(float value, float mean_x_coord, float mean_value, float falloff_speed) { - assert(!e->is_collection()); - Polyline pl = e->as_polyline(); - for (int point_idx = 0; point_idx < int(pl.points.size() - 1); ++point_idx) { - Vec2f start = unscaled(pl.points[point_idx]).cast(); - Vec2f next = unscaled(pl.points[point_idx + 1]).cast(); - ExtrusionLine line{start, next, e}; - destination.push_back(line); - } + float shifted = value - mean_x_coord; + float denominator = falloff_speed * shifted * shifted + 1.0f; + float exponent = 1.0f / denominator; + return mean_value * (std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f); } std::vector to_short_lines(const ExtrusionEntity *e, float length_limit) { assert(!e->is_collection()); - Polyline pl = e->as_polyline(); + Polyline pl = e->as_polyline(); std::vector lines; lines.reserve(pl.points.size() * 1.5f); lines.emplace_back(unscaled(pl.points[0]).cast(), unscaled(pl.points[0]).cast(), e); @@ -356,33 +233,30 @@ std::vector to_short_lines(const ExtrusionEntity *e, float length return lines; } -void check_extrusion_entity_stability(const ExtrusionEntity *entity, - std::vector &checked_lines_out, - float layer_z, - const LayerRegion *layer_region, - const LD &prev_layer_lines, - Issues &issues, - const Params ¶ms) { - +std::vector check_extrusion_entity_stability(const ExtrusionEntity *entity, + const LayerRegion *layer_region, + const LD &prev_layer_lines, + const Params ¶ms) +{ if (entity->is_collection()) { - for (const auto *e : static_cast(entity)->entities) { - check_extrusion_entity_stability(e, checked_lines_out, layer_z, layer_region, prev_layer_lines, - issues, params); + std::vector checked_lines_out; + checked_lines_out.reserve(prev_layer_lines.get_lines().size() / 3); + for (const auto *e : static_cast(entity)->entities) { + auto tmp = check_extrusion_entity_stability(e, layer_region, prev_layer_lines, params); + checked_lines_out.insert(checked_lines_out.end(), tmp.begin(), tmp.end()); } - } else { //single extrusion path, with possible varying parameters - const auto to_vec3f = [layer_z](const Vec2f &point) { - return Vec3f(point.x(), point.y(), layer_z); - }; + return checked_lines_out; + } else { // single extrusion path, with possible varying parameters + if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) { return {}; } + std::vector lines = to_short_lines(entity, params.bridge_distance); - if (lines.empty()) return; - ExtrusionPropertiesAccumulator bridging_acc { }; - ExtrusionPropertiesAccumulator malformation_acc { }; + ExtrusionPropertiesAccumulator bridging_acc{}; + ExtrusionPropertiesAccumulator malformation_acc{}; bridging_acc.add_distance(params.bridge_distance + 1.0f); - const float flow_width = get_flow_width(layer_region, entity->role()); - float min_malformation_dist = flow_width - params.malformation_overlap_factor.first * flow_width; - float max_malformation_dist = flow_width - params.malformation_overlap_factor.second * flow_width; - + const float flow_width = get_flow_width(layer_region, entity->role()); + float min_malformation_dist = flow_width - params.malformation_overlap_factor.first * flow_width; + float max_malformation_dist = flow_width - params.malformation_overlap_factor.second * flow_width; for (size_t line_idx = 0; line_idx < lines.size(); ++line_idx) { ExtrusionLine ¤t_line = lines[line_idx]; @@ -393,443 +267,304 @@ void check_extrusion_entity_stability(const ExtrusionEntity *entity, if (line_idx + 1 < lines.size()) { const Vec2f v1 = current_line.b - current_line.a; const Vec2f v2 = lines[line_idx + 1].b - lines[line_idx + 1].a; - curr_angle = angle(v1, v2); + curr_angle = angle(v1, v2); } bridging_acc.add_angle(curr_angle); // malformation in concave angles does not happen malformation_acc.add_angle(std::max(0.0f, curr_angle)); - if (curr_angle < -20.0 * PI / 180.0) { - malformation_acc.reset(); - } + if (curr_angle < -20.0 * PI / 180.0) { malformation_acc.reset(); } auto [dist_from_prev_layer, nearest_line_idx, nearest_point] = prev_layer_lines.signed_distance_from_lines_extra(current_line.b); - if (fabs(dist_from_prev_layer) < flow_width) { + if (dist_from_prev_layer < flow_width) { bridging_acc.reset(); } else { bridging_acc.add_distance(current_line.len); // if unsupported distance is larger than bridge distance linearly decreased by curvature, enforce supports. - bool in_layer_dist_condition = bridging_acc.distance - > params.bridge_distance / (1.0f + (bridging_acc.max_curvature - * params.bridge_distance_decrease_by_curvature_factor / PI)); - bool between_layers_condition = fabs(dist_from_prev_layer) > flow_width || - prev_layer_lines.get_line(nearest_line_idx).malformation > 3.0f * layer_region->layer()->height; - + bool in_layer_dist_condition = bridging_acc.distance > + params.bridge_distance / (1.0f + (bridging_acc.max_curvature * + params.bridge_distance_decrease_by_curvature_factor / PI)); + bool between_layers_condition = dist_from_prev_layer > max_malformation_dist; if (in_layer_dist_condition && between_layers_condition) { - issues.support_points.emplace_back(to_vec3f(current_line.b), 0.0f, params.support_points_interface_radius, Vec3f(0.f, 0.0f, -1.0f)); current_line.support_point_generated = true; bridging_acc.reset(); } } - //malformation + // malformation propagation from below if (fabs(dist_from_prev_layer) < 2.0f * flow_width) { const ExtrusionLine &nearest_line = prev_layer_lines.get_line(nearest_line_idx); current_line.malformation += 0.85 * nearest_line.malformation; } + // current line maformation if (dist_from_prev_layer > min_malformation_dist && dist_from_prev_layer < max_malformation_dist) { float factor = std::abs(dist_from_prev_layer - (max_malformation_dist + min_malformation_dist) * 0.5) / (max_malformation_dist - min_malformation_dist); malformation_acc.add_distance(current_line.len); current_line.malformation += layer_region->layer()->height * factor * (2.0f + 3.0f * (malformation_acc.max_curvature / PI)); - current_line.malformation = std::min(current_line.malformation, float(layer_region->layer()->height * params.max_malformation_factor)); + current_line.malformation = std::min(current_line.malformation, + float(layer_region->layer()->height * params.max_malformation_factor)); } else { malformation_acc.reset(); } } - checked_lines_out.insert(checked_lines_out.end(), lines.begin(), lines.end()); + return lines; } } -std::tuple reckon_islands( - const Layer *layer, bool first_layer, - size_t prev_layer_islands_count, - const PixelGrid &prev_layer_grid, - const std::vector &layer_lines, - const Params ¶ms) { +// returns triangle area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance +// none of the values is divided/normalized by area. +// The function computes intgeral over the area of the triangle, with function f(x,y) = x for first moments of area (y is analogous) +// f(x,y) = x^2 for second moment of area +// and f(x,y) = x*y for second moment of area covariance +std::tuple compute_triangle_moments_of_area(const Vec2f &a, const Vec2f &b, const Vec2f &c) +{ + // based on the following guide: + // Denote the vertices of S by a, b, c. Then the map + // g:(u,v)↦a+u(b−a)+v(c−a) , + // which in coordinates appears as + // g:(u,v)↦{x(u,v)y(u,v)=a1+u(b1−a1)+v(c1−a1)=a2+u(b2−a2)+v(c2−a2) ,(1) + // obviously maps S′ bijectively onto S. Therefore the transformation formula for multiple integrals steps into action, and we obtain + // ∫Sf(x,y)d(x,y)=∫S′f(x(u,v),y(u,v))∣∣Jg(u,v)∣∣ d(u,v) . + // In the case at hand the Jacobian determinant is a constant: From (1) we obtain + // Jg(u,v)=det[xuyuxvyv]=(b1−a1)(c2−a2)−(c1−a1)(b2−a2) . + // Therefore we can write + // ∫Sf(x,y)d(x,y)=∣∣Jg∣∣∫10∫1−u0f~(u,v) dv du , + // where f~ denotes the pullback of f to S′: + // f~(u,v):=f(x(u,v),y(u,v)) . + // Don't forget taking the absolute value of Jg! - //extract extrusions (connected paths from multiple lines) from the layer_lines. Grouping by the same polyline is determined by common origin_entity ptr. - // result is a vector of [start, end) index pairs into the layer_lines vector - std::vector> extrusions; //start and end idx (one beyond last extrusion) [start,end) - const ExtrusionEntity *current_ex = nullptr; - for (size_t lidx = 0; lidx < layer_lines.size(); ++lidx) { - const ExtrusionLine &line = layer_lines[lidx]; - if (line.origin_entity == current_ex) { - extrusions.back().second = lidx + 1; - } else { - extrusions.emplace_back(lidx, lidx + 1); - current_ex = line.origin_entity; - } - } + float jacobian_determinant_abs = std::abs((b.x() - a.x()) * (c.y() - a.y()) - (c.x() - a.x()) * (b.y() - a.y())); - std::vector> islands; // these search trees will be used to determine to which island does the extrusion belong. - for (const ExPolygon& island : layer->lslices) { - islands.emplace_back(to_lines(island)); - } + // coordinate transform: gx(u,v) = a.x + u * (b.x - a.x) + v * (c.x - a.x) + // coordinate transform: gy(u,v) = a.y + u * (b.y - a.y) + v * (c.y - a.y) + // second moment of area for x: f(x, y) = x^2; + // f(gx(u,v), gy(u,v)) = gx(u,v)^2 = ... (long expanded form) - std::vector> island_extrusions(islands.size(), - std::vector{}); // final assigment of each extrusion to an island. - for (size_t extrusion_idx = 0; extrusion_idx < extrusions.size(); extrusion_idx++) { - Point second_point = Point::new_scale(layer_lines[extrusions[extrusion_idx].first].b); - for (size_t island_idx = 0; island_idx < islands.size(); island_idx++) { - if (islands[island_idx].signed_distance_from_lines(second_point) <= 0.0) { - island_extrusions[island_idx].push_back(extrusion_idx); - } - } - } + // result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du + // integral_0^1 integral_0^(1 - u) (a + u (b - a) + v (c - a))^2 dv du = 1/12 (a^2 + a (b + c) + b^2 + b c + c^2) - float flow_width = get_flow_width(layer->regions()[0], erExternalPerimeter); - // after filtering the layer lines into islands, build the result LayerIslands structure. - LayerIslands result { }; - result.layer_z = layer->slice_z; - std::vector line_to_island_mapping(layer_lines.size(), NULL_ISLAND); - for (const std::vector &island_ex : island_extrusions) { - if (island_ex.empty()) { - continue; - } + Vec2f second_moment_of_area_xy = jacobian_determinant_abs * + (a.cwiseProduct(a) + b.cwiseProduct(b) + b.cwiseProduct(c) + c.cwiseProduct(c) + + a.cwiseProduct(b + c)) / + 12.0f; + // second moment of area covariance : f(x, y) = x*y; + // f(gx(u,v), gy(u,v)) = gx(u,v)*gy(u,v) = ... (long expanded form) + //(a_1 + u * (b_1 - a_1) + v * (c_1 - a_1)) * (a_2 + u * (b_2 - a_2) + v * (c_2 - a_2)) + // == (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) - Island island { }; - for (size_t extrusion_idx : island_ex) { - - if (layer_lines[extrusions[extrusion_idx].first].is_external_perimeter()) { - island.external_lines.insert(island.external_lines.end(), - layer_lines.begin() + extrusions[extrusion_idx].first, - layer_lines.begin() + extrusions[extrusion_idx].second); - } + // intermediate result: integral_0^(1 - u) (a_1 + u (b_1 - a_1) + v (c_1 - a_1)) (a_2 + u (b_2 - a_2) + v (c_2 - a_2)) dv = + // 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 + // b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) result = integral_0^1 1/6 (u - 1) (-c_1 (u - 1) (a_2 (u - 1) - 3 b_2 u) - c_2 (u - + // 1) (a_1 (u - 1) - 3 b_1 u + 2 c_1 (u - 1)) + 3 b_1 u (a_2 (u - 1) - 2 b_2 u) + a_1 (u - 1) (3 b_2 u - 2 a_2 (u - 1))) du = + // 1/24 (a_2 (b_1 + c_1) + a_1 (2 a_2 + b_2 + c_2) + b_2 c_1 + b_1 c_2 + 2 b_1 b_2 + 2 c_1 c_2) + // result is Int_T func = jacobian_determinant_abs * Int_0^1 Int_0^1-u func(gx(u,v), gy(u,v)) dv du + float second_moment_of_area_covariance = jacobian_determinant_abs * (1.0f / 24.0f) * + (a.y() * (b.x() + c.x()) + a.x() * (2.0f * a.y() + b.y() + c.y()) + b.y() * c.x() + + b.x() * c.y() + 2.0f * b.x() * b.y() + 2.0f * c.x() * c.y()); - for (size_t lidx = extrusions[extrusion_idx].first; lidx < extrusions[extrusion_idx].second; ++lidx) { - line_to_island_mapping[lidx] = result.islands.size(); - const ExtrusionLine &line = layer_lines[lidx]; - float volume = line.len * layer->height * flow_width * PI / 4.0f; - island.volume += volume; - island.volume_centroid_accumulator += to_3d(Vec2f((line.a + line.b) / 2.0f), float(layer->slice_z)) - * volume; + float area = jacobian_determinant_abs * 0.5f; - if (first_layer) { - float sticking_area = line.len * flow_width; - island.sticking_area += sticking_area; - Vec2f middle = Vec2f((line.a + line.b) / 2.0f); - island.sticking_centroid_accumulator += sticking_area * to_3d(middle, float(layer->slice_z)); - // Bottom infill lines can be quite long, and algined, so the middle approximaton used above does not work - Vec2f dir = (line.b - line.a).normalized(); - float segment_length = flow_width; // segments of size flow_width - for (float segment_middle_dist = std::min(line.len, segment_length * 0.5f); - segment_middle_dist < line.len; - segment_middle_dist += segment_length) { - Vec2f segment_middle = line.a + segment_middle_dist * dir; - island.sticking_second_moment_of_area_accumulator += segment_length * flow_width - * segment_middle.cwiseProduct(segment_middle); - island.sticking_second_moment_of_area_covariance_accumulator += segment_length * flow_width - * segment_middle.x() - * segment_middle.y(); - } - } else if (layer_lines[lidx].support_point_generated) { - float sticking_area = line.len * flow_width; - island.sticking_area += sticking_area; - island.sticking_centroid_accumulator += sticking_area * to_3d(line.b, float(layer->slice_z)); - island.sticking_second_moment_of_area_accumulator += sticking_area * line.b.cwiseProduct(line.b); - island.sticking_second_moment_of_area_covariance_accumulator += sticking_area * line.b.x() - * line.b.y(); - } - } - } - result.islands.push_back(island); - } + Vec2f first_moment_of_area_xy = jacobian_determinant_abs * (a + b + c) / 6.0f; - //LayerIslands structure built. Now determine connections and their areas to the previous layer using rasterization. - PixelGrid current_layer_grid = prev_layer_grid; - current_layer_grid.clear(); - // build index image of current layer - tbb::parallel_for(tbb::blocked_range(0, layer_lines.size()), - [&layer_lines, ¤t_layer_grid, &line_to_island_mapping]( - tbb::blocked_range r) { - for (size_t i = r.begin(); i < r.end(); ++i) { - size_t island = line_to_island_mapping[i]; - const ExtrusionLine &line = layer_lines[i]; - current_layer_grid.distribute_edge(line.a, line.b, island); - } - }); - - //compare the image of previous layer with the current layer. For each pair of overlapping valid pixels, add pixel area to the respective island connection - for (size_t x = 0; x < size_t(current_layer_grid.get_pixel_count().x()); ++x) { - for (size_t y = 0; y < size_t(current_layer_grid.get_pixel_count().y()); ++y) { - Vec2i coords = Vec2i(x, y); - if (current_layer_grid.get_pixel(coords) != NULL_ISLAND - && prev_layer_grid.get_pixel(coords) != NULL_ISLAND) { - IslandConnection &connection = result.islands[current_layer_grid.get_pixel(coords)] - .connected_islands[prev_layer_grid.get_pixel(coords)]; - Vec2f current_coords = current_layer_grid.get_pixel_center(coords); - connection.area += current_layer_grid.pixel_area(); - connection.centroid_accumulator += to_3d(current_coords, result.layer_z) - * current_layer_grid.pixel_area(); - connection.second_moment_of_area_accumulator += current_coords.cwiseProduct(current_coords) - * current_layer_grid.pixel_area(); - connection.second_moment_of_area_covariance_accumulator += current_coords.x() * current_coords.y() - * current_layer_grid.pixel_area(); - } - } - } - - // filter out very small connection areas, they brake the graph building - for (Island &island : result.islands) { - std::vector conns_to_remove; - for (const auto &conn : island.connected_islands) { - if (conn.second.area < params.connections_min_considerable_area) { conns_to_remove.push_back(conn.first); } - } - for (size_t conn : conns_to_remove) { island.connected_islands.erase(conn); } - } - - return {result, current_layer_grid}; -} - -struct CoordinateFunctor { - const std::vector *coordinates; - CoordinateFunctor(const std::vector *coords) : - coordinates(coords) { - } - CoordinateFunctor() : - coordinates(nullptr) { - } - - const float& operator()(size_t idx, size_t dim) const { - return coordinates->operator [](idx)[dim]; - } + return {area, first_moment_of_area_xy, second_moment_of_area_xy, second_moment_of_area_covariance}; }; -class ObjectPart { - float volume { }; - Vec3f volume_centroid_accumulator = Vec3f::Zero(); - float sticking_area { }; - Vec3f sticking_centroid_accumulator = Vec3f::Zero(); - Vec2f sticking_second_moment_of_area_accumulator = Vec2f::Zero(); - float sticking_second_moment_of_area_covariance_accumulator { }; +SliceConnection estimate_slice_connection(size_t slice_idx, const Layer *layer) +{ + SliceConnection connection; + const LayerSlice &slice = layer->lslices_ex[slice_idx]; + ExPolygon slice_poly = layer->lslices[slice_idx]; + const Layer *lower_layer = layer->lower_layer; + + ExPolygons below_polys{}; + for (const auto &link : slice.overlaps_below) { below_polys.push_back(lower_layer->lslices[link.slice_idx]); } + ExPolygons overlap = intersection_ex({slice_poly}, below_polys); + + std::vector triangles = triangulate_expolygons_2f(overlap); + for (size_t idx = 0; idx < triangles.size(); idx += 3) { + auto [area, first_moment_of_area, second_moment_area, + second_moment_of_area_covariance] = compute_triangle_moments_of_area(triangles[idx], triangles[idx + 1], triangles[idx + 2]); + connection.area += area; + connection.centroid_accumulator += Vec3f(first_moment_of_area.x(), first_moment_of_area.y(), layer->print_z * area); + connection.second_moment_of_area_accumulator += second_moment_area; + connection.second_moment_of_area_covariance_accumulator += second_moment_of_area_covariance; + } + + return connection; +}; + +class ObjectPart +{ public: + float volume{}; + Vec3f volume_centroid_accumulator = Vec3f::Zero(); + float sticking_area{}; + Vec3f sticking_centroid_accumulator = Vec3f::Zero(); + Vec2f sticking_second_moment_of_area_accumulator = Vec2f::Zero(); + float sticking_second_moment_of_area_covariance_accumulator{}; + ObjectPart() = default; - ObjectPart(const Island &island) { - this->volume = island.volume; - this->volume_centroid_accumulator = island.volume_centroid_accumulator; - this->sticking_area = island.sticking_area; - this->sticking_centroid_accumulator = island.sticking_centroid_accumulator; - this->sticking_second_moment_of_area_accumulator = island.sticking_second_moment_of_area_accumulator; - this->sticking_second_moment_of_area_covariance_accumulator = - island.sticking_second_moment_of_area_covariance_accumulator; - } - - float get_volume() const { - return volume; - } - - void add(const ObjectPart &other) { + void add(const ObjectPart &other) + { this->volume_centroid_accumulator += other.volume_centroid_accumulator; this->volume += other.volume; this->sticking_area += other.sticking_area; this->sticking_centroid_accumulator += other.sticking_centroid_accumulator; this->sticking_second_moment_of_area_accumulator += other.sticking_second_moment_of_area_accumulator; - this->sticking_second_moment_of_area_covariance_accumulator += - other.sticking_second_moment_of_area_covariance_accumulator; + this->sticking_second_moment_of_area_covariance_accumulator += other.sticking_second_moment_of_area_covariance_accumulator; } - void add_support_point(const Vec3f &position, float sticking_area) { + void add_support_point(const Vec3f &position, float sticking_area) + { this->sticking_area += sticking_area; this->sticking_centroid_accumulator += sticking_area * position; - this->sticking_second_moment_of_area_accumulator += sticking_area - * position.head<2>().cwiseProduct(position.head<2>()); - this->sticking_second_moment_of_area_covariance_accumulator += sticking_area - * position.x() * position.y(); + this->sticking_second_moment_of_area_accumulator += sticking_area * position.head<2>().cwiseProduct(position.head<2>()); + this->sticking_second_moment_of_area_covariance_accumulator += sticking_area * position.x() * position.y(); } - float compute_directional_xy_variance( - const Vec2f &line_dir, - const Vec3f ¢roid_accumulator, - const Vec2f &second_moment_of_area_accumulator, - const float &second_moment_of_area_covariance_accumulator, - const float &area) const { + float compute_directional_xy_variance(const Vec2f &line_dir, + const Vec3f ¢roid_accumulator, + const Vec2f &second_moment_of_area_accumulator, + const float &second_moment_of_area_covariance_accumulator, + const float &area) const + { assert(area > 0); - Vec3f centroid = centroid_accumulator / area; - Vec2f variance = (second_moment_of_area_accumulator / area - - centroid.head<2>().cwiseProduct(centroid.head<2>())); + Vec3f centroid = centroid_accumulator / area; + Vec2f variance = (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>())); float covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y(); // Var(aX+bY)=a^2*Var(X)+b^2*Var(Y)+2*a*b*Cov(X,Y) - float directional_xy_variance = line_dir.x() * line_dir.x() * variance.x() - + line_dir.y() * line_dir.y() * variance.y() + - 2.0f * line_dir.x() * line_dir.y() * covariance; + float directional_xy_variance = line_dir.x() * line_dir.x() * variance.x() + line_dir.y() * line_dir.y() * variance.y() + + 2.0f * line_dir.x() * line_dir.y() * covariance; #ifdef DETAILED_DEBUG_LOGS - BOOST_LOG_TRIVIAL(debug) - << "centroid: " << centroid.x() << " " << centroid.y() << " " << centroid.z(); - BOOST_LOG_TRIVIAL(debug) - << "variance: " << variance.x() << " " << variance.y(); - BOOST_LOG_TRIVIAL(debug) - << "covariance: " << covariance; - BOOST_LOG_TRIVIAL(debug) - << "directional_xy_variance: " << directional_xy_variance; + BOOST_LOG_TRIVIAL(debug) << "centroid: " << centroid.x() << " " << centroid.y() << " " << centroid.z(); + BOOST_LOG_TRIVIAL(debug) << "variance: " << variance.x() << " " << variance.y(); + BOOST_LOG_TRIVIAL(debug) << "covariance: " << covariance; + BOOST_LOG_TRIVIAL(debug) << "directional_xy_variance: " << directional_xy_variance; #endif return directional_xy_variance; } - float compute_elastic_section_modulus( - const Vec2f &line_dir, - const Vec3f &extreme_point, - const Vec3f ¢roid_accumulator, - const Vec2f &second_moment_of_area_accumulator, - const float &second_moment_of_area_covariance_accumulator, - const float &area) const { - - float directional_xy_variance = compute_directional_xy_variance( - line_dir, - centroid_accumulator, - second_moment_of_area_accumulator, - second_moment_of_area_covariance_accumulator, - area); - if (directional_xy_variance < EPSILON) { - return 0.0f; - } - Vec3f centroid = centroid_accumulator / area; - float extreme_fiber_dist = line_alg::distance_to( - Linef(centroid.head<2>().cast(), - (centroid.head<2>() + Vec2f(line_dir.y(), -line_dir.x())).cast()), - extreme_point.head<2>().cast()); + float compute_elastic_section_modulus(const Vec2f &line_dir, + const Vec3f &extreme_point, + const Vec3f ¢roid_accumulator, + const Vec2f &second_moment_of_area_accumulator, + const float &second_moment_of_area_covariance_accumulator, + const float &area) const + { + float directional_xy_variance = compute_directional_xy_variance(line_dir, centroid_accumulator, second_moment_of_area_accumulator, + second_moment_of_area_covariance_accumulator, area); + if (directional_xy_variance < EPSILON) { return 0.0f; } + Vec3f centroid = centroid_accumulator / area; + float extreme_fiber_dist = line_alg::distance_to(Linef(centroid.head<2>().cast(), + (centroid.head<2>() + Vec2f(line_dir.y(), -line_dir.x())).cast()), + extreme_point.head<2>().cast()); float elastic_section_modulus = area * directional_xy_variance / extreme_fiber_dist; #ifdef DETAILED_DEBUG_LOGS - BOOST_LOG_TRIVIAL(debug) - << "extreme_fiber_dist: " << extreme_fiber_dist; - BOOST_LOG_TRIVIAL(debug) - << "elastic_section_modulus: " << elastic_section_modulus; + BOOST_LOG_TRIVIAL(debug) << "extreme_fiber_dist: " << extreme_fiber_dist; + BOOST_LOG_TRIVIAL(debug) << "elastic_section_modulus: " << elastic_section_modulus; #endif return elastic_section_modulus; } - float is_stable_while_extruding( - const IslandConnection &connection, - const ExtrusionLine &extruded_line, - const Vec3f &extreme_point, - float layer_z, - const Params ¶ms) const { - - Vec2f line_dir = (extruded_line.b - extruded_line.a).normalized(); + float is_stable_while_extruding(const SliceConnection &connection, + const ExtrusionLine &extruded_line, + const Vec3f &extreme_point, + float layer_z, + const Params ¶ms) const + { + Vec2f line_dir = (extruded_line.b - extruded_line.a).normalized(); const Vec3f &mass_centroid = this->volume_centroid_accumulator / this->volume; - float mass = this->volume * params.filament_density; - float weight = mass * params.gravity_constant; + float mass = this->volume * params.filament_density; + float weight = mass * params.gravity_constant; float movement_force = params.max_acceleration * mass; float extruder_conflict_force = params.standard_extruder_conflict_force + - std::min(extruded_line.malformation, 1.0f) * params.malformations_additive_conflict_extruder_force; + std::min(extruded_line.malformation, 1.0f) * params.malformations_additive_conflict_extruder_force; // section for bed calculations { - if (this->sticking_area < EPSILON) - return 1.0f; + if (this->sticking_area < EPSILON) return 1.0f; - Vec3f bed_centroid = this->sticking_centroid_accumulator / this->sticking_area; - float bed_yield_torque = -compute_elastic_section_modulus( - line_dir, - extreme_point, - this->sticking_centroid_accumulator, - this->sticking_second_moment_of_area_accumulator, - this->sticking_second_moment_of_area_covariance_accumulator, - this->sticking_area) - * params.get_bed_adhesion_yield_strength(); + Vec3f bed_centroid = this->sticking_centroid_accumulator / this->sticking_area; + float bed_yield_torque = -compute_elastic_section_modulus(line_dir, extreme_point, this->sticking_centroid_accumulator, + this->sticking_second_moment_of_area_accumulator, + this->sticking_second_moment_of_area_covariance_accumulator, + this->sticking_area) * + params.get_bed_adhesion_yield_strength(); - Vec2f bed_weight_arm = (mass_centroid.head<2>() - bed_centroid.head<2>()); - float bed_weight_arm_len = bed_weight_arm.norm(); - float bed_weight_dir_xy_variance = compute_directional_xy_variance(bed_weight_arm, - this->sticking_centroid_accumulator, - this->sticking_second_moment_of_area_accumulator, - this->sticking_second_moment_of_area_covariance_accumulator, - this->sticking_area); - float bed_weight_sign = bed_weight_arm_len < 2.0f * sqrt(bed_weight_dir_xy_variance) ? -1.0f : 1.0f; - float bed_weight_torque = bed_weight_sign * bed_weight_arm_len * weight; + Vec2f bed_weight_arm = (mass_centroid.head<2>() - bed_centroid.head<2>()); + float bed_weight_arm_len = bed_weight_arm.norm(); + float bed_weight_dir_xy_variance = compute_directional_xy_variance(bed_weight_arm, this->sticking_centroid_accumulator, + this->sticking_second_moment_of_area_accumulator, + this->sticking_second_moment_of_area_covariance_accumulator, + this->sticking_area); + float bed_weight_sign = bed_weight_arm_len < 2.0f * sqrt(bed_weight_dir_xy_variance) ? -1.0f : 1.0f; + float bed_weight_torque = bed_weight_sign * bed_weight_arm_len * weight; - float bed_movement_arm = std::max(0.0f, mass_centroid.z() - bed_centroid.z()); + float bed_movement_arm = std::max(0.0f, mass_centroid.z() - bed_centroid.z()); float bed_movement_torque = movement_force * bed_movement_arm; - float bed_conflict_torque_arm = layer_z - bed_centroid.z(); + float bed_conflict_torque_arm = layer_z - bed_centroid.z(); float bed_extruder_conflict_torque = extruder_conflict_force * bed_conflict_torque_arm; - float bed_total_torque = bed_movement_torque + bed_extruder_conflict_torque + bed_weight_torque - + bed_yield_torque; + float bed_total_torque = bed_movement_torque + bed_extruder_conflict_torque + bed_weight_torque + bed_yield_torque; #ifdef DETAILED_DEBUG_LOGS - BOOST_LOG_TRIVIAL(debug) - << "bed_centroid: " << bed_centroid.x() << " " << bed_centroid.y() << " " << bed_centroid.z(); - BOOST_LOG_TRIVIAL(debug) - << "SSG: bed_yield_torque: " << bed_yield_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: bed_weight_arm: " << bed_weight_arm; - BOOST_LOG_TRIVIAL(debug) - << "SSG: bed_weight_torque: " << bed_weight_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: bed_movement_arm: " << bed_movement_arm; - BOOST_LOG_TRIVIAL(debug) - << "SSG: bed_movement_torque: " << bed_movement_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: bed_conflict_torque_arm: " << bed_conflict_torque_arm; - BOOST_LOG_TRIVIAL(debug) - << "SSG: extruded_line.malformation: " << extruded_line.malformation; - BOOST_LOG_TRIVIAL(debug) - << "SSG: extruder_conflict_force: " << extruder_conflict_force; - BOOST_LOG_TRIVIAL(debug) - << "SSG: bed_extruder_conflict_torque: " << bed_extruder_conflict_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: total_torque: " << bed_total_torque << " layer_z: " << layer_z; + BOOST_LOG_TRIVIAL(debug) << "bed_centroid: " << bed_centroid.x() << " " << bed_centroid.y() << " " << bed_centroid.z(); + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_yield_torque: " << bed_yield_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_arm: " << bed_weight_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_torque: " << bed_weight_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_arm: " << bed_movement_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_torque: " << bed_movement_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_conflict_torque_arm: " << bed_conflict_torque_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: extruded_line.malformation: " << extruded_line.malformation; + BOOST_LOG_TRIVIAL(debug) << "SSG: extruder_conflict_force: " << extruder_conflict_force; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_extruder_conflict_torque: " << bed_extruder_conflict_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << bed_total_torque << " layer_z: " << layer_z; #endif - if (bed_total_torque > 0) - return bed_total_torque / bed_conflict_torque_arm; + if (bed_total_torque > 0) return bed_total_torque / bed_conflict_torque_arm; } - //section for weak connection calculations + // section for weak connection calculations { - if (connection.area < EPSILON) - return 1.0f; + if (connection.area < EPSILON) return 1.0f; Vec3f conn_centroid = connection.centroid_accumulator / connection.area; - if (layer_z - conn_centroid.z() < 3.0f) { - return -1.0f; - } - float conn_yield_torque = compute_elastic_section_modulus( - line_dir, - extreme_point, - connection.centroid_accumulator, - connection.second_moment_of_area_accumulator, - connection.second_moment_of_area_covariance_accumulator, - connection.area) * params.material_yield_strength; + if (layer_z - conn_centroid.z() < 3.0f) { return -1.0f; } + float conn_yield_torque = compute_elastic_section_modulus(line_dir, extreme_point, connection.centroid_accumulator, + connection.second_moment_of_area_accumulator, + connection.second_moment_of_area_covariance_accumulator, + connection.area) * + params.material_yield_strength; - float conn_weight_arm = (conn_centroid.head<2>() - mass_centroid.head<2>()).norm(); + float conn_weight_arm = (conn_centroid.head<2>() - mass_centroid.head<2>()).norm(); float conn_weight_torque = conn_weight_arm * weight * (conn_centroid.z() / layer_z); - float conn_movement_arm = std::max(0.0f, mass_centroid.z() - conn_centroid.z()); + float conn_movement_arm = std::max(0.0f, mass_centroid.z() - conn_centroid.z()); float conn_movement_torque = movement_force * conn_movement_arm; - float conn_conflict_torque_arm = layer_z - conn_centroid.z(); + float conn_conflict_torque_arm = layer_z - conn_centroid.z(); float conn_extruder_conflict_torque = extruder_conflict_force * conn_conflict_torque_arm; - float conn_total_torque = conn_movement_torque + conn_extruder_conflict_torque + conn_weight_torque - - conn_yield_torque; + float conn_total_torque = conn_movement_torque + conn_extruder_conflict_torque + conn_weight_torque - conn_yield_torque; #ifdef DETAILED_DEBUG_LOGS - BOOST_LOG_TRIVIAL(debug) - << "bed_centroid: " << conn_centroid.x() << " " << conn_centroid.y() << " " << conn_centroid.z(); - BOOST_LOG_TRIVIAL(debug) - << "SSG: conn_yield_torque: " << conn_yield_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: conn_weight_arm: " << conn_weight_arm; - BOOST_LOG_TRIVIAL(debug) - << "SSG: conn_weight_torque: " << conn_weight_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: conn_movement_arm: " << conn_movement_arm; - BOOST_LOG_TRIVIAL(debug) - << "SSG: conn_movement_torque: " << conn_movement_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: conn_conflict_torque_arm: " << conn_conflict_torque_arm; - BOOST_LOG_TRIVIAL(debug) - << "SSG: conn_extruder_conflict_torque: " << conn_extruder_conflict_torque; - BOOST_LOG_TRIVIAL(debug) - << "SSG: total_torque: " << conn_total_torque << " layer_z: " << layer_z; + BOOST_LOG_TRIVIAL(debug) << "bed_centroid: " << conn_centroid.x() << " " << conn_centroid.y() << " " << conn_centroid.z(); + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_yield_torque: " << conn_yield_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_weight_arm: " << conn_weight_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_weight_torque: " << conn_weight_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_movement_arm: " << conn_movement_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_movement_torque: " << conn_movement_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_conflict_torque_arm: " << conn_conflict_torque_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_extruder_conflict_torque: " << conn_extruder_conflict_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << conn_total_torque << " layer_z: " << layer_z; #endif return conn_total_torque / conn_conflict_torque_arm; @@ -837,57 +572,102 @@ public: } }; -#ifdef DETAILED_DEBUG_LOGS -void debug_print_graph(const std::vector &islands_graph) { - std::cout << "BUILT ISLANDS GRAPH:" << std::endl; - for (size_t layer_idx = 0; layer_idx < islands_graph.size(); ++layer_idx) { - std::cout << "ISLANDS AT LAYER: " << layer_idx << " AT HEIGHT: " << islands_graph[layer_idx].layer_z - << std::endl; - for (size_t island_idx = 0; island_idx < islands_graph[layer_idx].islands.size(); ++island_idx) { - const Island &island = islands_graph[layer_idx].islands[island_idx]; - std::cout << " ISLAND " << island_idx << std::endl; - std::cout << " volume: " << island.volume << std::endl; - std::cout << " sticking_area: " << island.sticking_area << std::endl; - std::cout << " connected_islands count: " << island.connected_islands.size() << std::endl; - std::cout << " lines count: " << island.external_lines.size() << std::endl; +// return new object part and actual area covered by extrusions +std::tuple build_object_part_from_slice(const LayerSlice &slice, const Layer *layer) +{ + ObjectPart new_object_part; + float area_covered_by_extrusions = 0; + + auto add_extrusions_to_object = [&new_object_part, &area_covered_by_extrusions](const ExtrusionEntity *e, const LayerRegion *region) { + float flow_width = get_flow_width(region, e->role()); + const Layer *l = region->layer(); + float slice_z = l->slice_z; + float height = l->height; + std::vector lines = to_short_lines(e, 5.0); + for (const ExtrusionLine &line : lines) { + float volume = line.len * height * flow_width * PI / 4.0f; + area_covered_by_extrusions += line.len * flow_width; + new_object_part.volume += volume; + new_object_part.volume_centroid_accumulator += to_3d(Vec2f((line.a + line.b) / 2.0f), slice_z) * volume; + + if (l->id() == 0) { // first layer + float sticking_area = line.len * flow_width; + new_object_part.sticking_area += sticking_area; + Vec2f middle = Vec2f((line.a + line.b) / 2.0f); + new_object_part.sticking_centroid_accumulator += sticking_area * to_3d(middle, slice_z); + // Bottom infill lines can be quite long, and algined, so the middle approximaton used above does not work + Vec2f dir = (line.b - line.a).normalized(); + float segment_length = flow_width; // segments of size flow_width + for (float segment_middle_dist = std::min(line.len, segment_length * 0.5f); segment_middle_dist < line.len; + segment_middle_dist += segment_length) { + Vec2f segment_middle = line.a + segment_middle_dist * dir; + new_object_part.sticking_second_moment_of_area_accumulator += segment_length * flow_width * + segment_middle.cwiseProduct(segment_middle); + new_object_part.sticking_second_moment_of_area_covariance_accumulator += segment_length * flow_width * + segment_middle.x() * segment_middle.y(); + } + } + } + }; + + for (const auto &island : slice.islands) { + const LayerRegion *perimeter_region = layer->get_region(island.perimeters.region()); + for (const auto &perimeter_idx : island.perimeters) { + for (const ExtrusionEntity *perimeter : + static_cast(perimeter_region->perimeters().entities[perimeter_idx])->entities) { + add_extrusions_to_object(perimeter, perimeter_region); + } + } + for (const LayerExtrusionRange &fill_range : island.fills) { + const LayerRegion *fill_region = layer->get_region(fill_range.region()); + for (const auto &fill_idx : fill_range) { + for (const ExtrusionEntity *fill : + static_cast(fill_region->fills().entities[fill_idx])->entities) { + add_extrusions_to_object(fill, fill_region); + } + } + } + const LayerRegion *thin_fill_region = layer->get_region(island.fill_region_id); + for (const auto &thin_fill_idx : island.thin_fills) { + add_extrusions_to_object(thin_fill_region->thin_fills().entities[thin_fill_idx], perimeter_region); } } - std::cout << "END OF GRAPH" << std::endl; -} -#endif -class ActiveObjectParts { - size_t next_part_idx = 0; + return {new_object_part, area_covered_by_extrusions}; +} + +class ActiveObjectParts +{ + size_t next_part_idx = 0; std::unordered_map active_object_parts; - std::unordered_map active_object_parts_id_mapping; + std::unordered_map active_object_parts_id_mapping; public: - size_t get_flat_id(size_t id) { + size_t get_flat_id(size_t id) + { size_t index = active_object_parts_id_mapping.at(id); - while (index != active_object_parts_id_mapping.at(index)) { - index = active_object_parts_id_mapping.at(index); - } + while (index != active_object_parts_id_mapping.at(index)) { index = active_object_parts_id_mapping.at(index); } size_t i = id; while (index != active_object_parts_id_mapping.at(i)) { - size_t next = active_object_parts_id_mapping[i]; + size_t next = active_object_parts_id_mapping[i]; active_object_parts_id_mapping[i] = index; - i = next; + i = next; } return index; } - ObjectPart& access(size_t id) { - return this->active_object_parts.at(this->get_flat_id(id)); - } + ObjectPart &access(size_t id) { return this->active_object_parts.at(this->get_flat_id(id)); } - size_t insert(const Island &island) { - this->active_object_parts.emplace(next_part_idx, ObjectPart(island)); + size_t insert(const ObjectPart &new_part) + { + this->active_object_parts.emplace(next_part_idx, new_part); this->active_object_parts_id_mapping.emplace(next_part_idx, next_part_idx); return next_part_idx++; } - void merge(size_t from, size_t to) { - size_t to_flat = this->get_flat_id(to); + void merge(size_t from, size_t to) + { + size_t to_flat = this->get_flat_id(to); size_t from_flat = this->get_flat_id(from); active_object_parts.at(to_flat).add(active_object_parts.at(from_flat)); active_object_parts.erase(from_flat); @@ -895,282 +675,199 @@ public: } }; -Issues check_global_stability(SupportGridFilter supports_presence_grid, - const std::vector &islands_graph, const Params ¶ms) { -#ifdef DETAILED_DEBUG_LOGS - debug_print_graph(islands_graph); -#endif +Issues check_stability(const PrintObject *po, const Params ¶ms) +{ + Issues issues{}; + SupportGridFilter supports_presence_grid(po, params.min_distance_between_support_points); + ActiveObjectParts active_object_parts{}; + LD prev_layer_ext_perim_lines({}); - Issues issues { }; - ActiveObjectParts active_object_parts { }; - std::unordered_map prev_island_to_object_part_mapping; - std::unordered_map next_island_to_object_part_mapping; + std::unordered_map prev_slice_idx_to_object_part_mapping; + std::unordered_map next_slice_idx_to_object_part_mapping; + std::unordered_map prev_slice_idx_to_weakest_connection; + std::unordered_map next_slice_idx_to_weakest_connection; - std::unordered_map prev_island_weakest_connection; - std::unordered_map next_island_weakest_connection; + for (size_t layer_idx = 0; layer_idx < po->layer_count(); ++layer_idx) { + const Layer *layer = po->get_layer(layer_idx); + float bottom_z = layer->bottom_z(); + auto create_support_point_position = [bottom_z](const Vec2f &layer_pos) { return Vec3f{layer_pos.x(), layer_pos.y(), bottom_z}; }; - for (size_t layer_idx = 0; layer_idx < islands_graph.size(); ++layer_idx) { - float layer_z = islands_graph[layer_idx].layer_z; + for (size_t slice_idx = 0; slice_idx < layer->lslices_ex.size(); ++slice_idx) { + const LayerSlice &slice = layer->lslices_ex.at(slice_idx); + auto [new_part, covered_area] = build_object_part_from_slice(slice, layer); + SliceConnection connection_to_below = estimate_slice_connection(slice_idx, layer); #ifdef DETAILED_DEBUG_LOGS - for (const auto &m : prev_island_to_object_part_mapping) { - std::cout << "island " << m.first << " maps to part " << m.second << std::endl; - prev_island_weakest_connection[m.first].print_info("connection info:"); - } + std::cout << "SLICE IDX: " << slice_idx << std::endl; + for (const auto &link : slice.overlaps_below) { + std::cout << "connected to slice below: " << link.slice_idx << " by area : " << link.area << std::endl; + } + connection_to_below.print_info("CONNECTION TO BELOW"); #endif - for (size_t island_idx = 0; island_idx < islands_graph[layer_idx].islands.size(); ++island_idx) { - const Island &island = islands_graph[layer_idx].islands[island_idx]; - if (island.connected_islands.empty()) { //new object part emerging - size_t part_id = active_object_parts.insert(island); - next_island_to_object_part_mapping.emplace(island_idx, part_id); - next_island_weakest_connection.emplace(island_idx, - IslandConnection { 1.0f, Vec3f::Zero(), Vec2f { INFINITY, INFINITY } }); + if (connection_to_below.area < EPSILON) { // new object part emerging + size_t part_id = active_object_parts.insert(new_part); + next_slice_idx_to_object_part_mapping.emplace(slice_idx, part_id); + next_slice_idx_to_weakest_connection.emplace(slice_idx, connection_to_below); } else { - size_t final_part_id { }; - IslandConnection transfered_weakest_connection { }; - IslandConnection new_weakest_connection { }; + size_t final_part_id{}; + SliceConnection transfered_weakest_connection{}; // MERGE parts { std::unordered_set parts_ids; - for (const auto &connection : island.connected_islands) { - size_t part_id = active_object_parts.get_flat_id( - prev_island_to_object_part_mapping.at(connection.first)); + for (const auto &link : slice.overlaps_below) { + size_t part_id = active_object_parts.get_flat_id(prev_slice_idx_to_object_part_mapping.at(link.slice_idx)); parts_ids.insert(part_id); - transfered_weakest_connection.add(prev_island_weakest_connection.at(connection.first)); - new_weakest_connection.add(connection.second); + transfered_weakest_connection.add(prev_slice_idx_to_weakest_connection.at(link.slice_idx)); } + final_part_id = *parts_ids.begin(); for (size_t part_id : parts_ids) { - if (final_part_id != part_id) { - active_object_parts.merge(part_id, final_part_id); - } + if (final_part_id != part_id) { active_object_parts.merge(part_id, final_part_id); } } } - auto estimate_conn_strength = [layer_z](const IslandConnection &conn) { - Vec3f centroid = conn.centroid_accumulator / conn.area; - Vec2f variance = (conn.second_moment_of_area_accumulator / conn.area - - centroid.head<2>().cwiseProduct(centroid.head<2>())); - float xy_variance = variance.x() + variance.y(); - float arm_len_estimate = std::max(1.0f, layer_z - (conn.centroid_accumulator.z() / conn.area)); + auto estimate_conn_strength = [bottom_z](const SliceConnection &conn) { + if (conn.area < EPSILON) { // connection is empty, does not exists. Return max strength so that it is not picked as the + // weakest connection. + return INFINITY; + } + Vec3f centroid = conn.centroid_accumulator / conn.area; + Vec2f variance = (conn.second_moment_of_area_accumulator / conn.area - + centroid.head<2>().cwiseProduct(centroid.head<2>())); + float xy_variance = variance.x() + variance.y(); + float arm_len_estimate = std::max(1.0f, bottom_z - (conn.centroid_accumulator.z() / conn.area)); return conn.area * sqrt(xy_variance) / arm_len_estimate; }; #ifdef DETAILED_DEBUG_LOGS - new_weakest_connection.print_info("new_weakest_connection"); + connection_to_below.print_info("new_weakest_connection"); transfered_weakest_connection.print_info("transfered_weakest_connection"); #endif - if (estimate_conn_strength(transfered_weakest_connection) - > estimate_conn_strength(new_weakest_connection)) { - transfered_weakest_connection = new_weakest_connection; + if (estimate_conn_strength(transfered_weakest_connection) > estimate_conn_strength(connection_to_below)) { + transfered_weakest_connection = connection_to_below; } - next_island_weakest_connection.emplace(island_idx, transfered_weakest_connection); - next_island_to_object_part_mapping.emplace(island_idx, final_part_id); + next_slice_idx_to_weakest_connection.emplace(slice_idx, transfered_weakest_connection); + next_slice_idx_to_object_part_mapping.emplace(slice_idx, final_part_id); ObjectPart &part = active_object_parts.access(final_part_id); - part.add(ObjectPart(island)); + part.add(new_part); } } - prev_island_to_object_part_mapping = next_island_to_object_part_mapping; - next_island_to_object_part_mapping.clear(); - prev_island_weakest_connection = next_island_weakest_connection; - next_island_weakest_connection.clear(); + prev_slice_idx_to_object_part_mapping = next_slice_idx_to_object_part_mapping; + next_slice_idx_to_object_part_mapping.clear(); + prev_slice_idx_to_weakest_connection = next_slice_idx_to_weakest_connection; + next_slice_idx_to_weakest_connection.clear(); - // All object parts updated, inactive parts removed and weakest point of each island updated as well. - // Now compute the stability of each active object part, adding supports where necessary, and also - // check each island whether the weakest point is strong enough. If not, add supports as well. - - for (size_t island_idx = 0; island_idx < islands_graph[layer_idx].islands.size(); ++island_idx) { - const Island &island = islands_graph[layer_idx].islands[island_idx]; - ObjectPart &part = active_object_parts.access(prev_island_to_object_part_mapping[island_idx]); - - IslandConnection &weakest_conn = prev_island_weakest_connection[island_idx]; + std::vector current_layer_ext_perims_lines{}; + current_layer_ext_perims_lines.reserve(prev_layer_ext_perim_lines.get_lines().size()); + // All object parts updated, and for each slice we have coresponding weakest connection. + // We can now check each slice and its corresponding weakest connection and object part for stability. + for (size_t slice_idx = 0; slice_idx < layer->lslices_ex.size(); ++slice_idx) { + const LayerSlice &slice = layer->lslices_ex.at(slice_idx); + ObjectPart &part = active_object_parts.access(prev_slice_idx_to_object_part_mapping[slice_idx]); + SliceConnection &weakest_conn = prev_slice_idx_to_weakest_connection[slice_idx]; + std::vector current_slice_ext_perims_lines{}; + current_slice_ext_perims_lines.reserve(prev_layer_ext_perim_lines.get_lines().size() / layer->lslices_ex.size()); #ifdef DETAILED_DEBUG_LOGS weakest_conn.print_info("weakest connection info: "); #endif - LD island_lines_dist(island.external_lines); - float unchecked_dist = params.min_distance_between_support_points + 1.0f; + // Function that is used when new support point is generated. It will update the ObjectPart stability, weakest conneciton info, + // and the support presence grid and add the point to the issues. + auto reckon_new_support_point = [&part, &weakest_conn, &issues, &supports_presence_grid, ¶ms, + &layer_idx](const Vec3f &support_point, float force, const Vec2f &dir) { + if (supports_presence_grid.position_taken(support_point) || layer_idx <= 1) { return; } + float area = params.support_points_interface_radius * params.support_points_interface_radius * float(PI); + part.add_support_point(support_point, area); - for (const ExtrusionLine &line : island.external_lines) { - if ((unchecked_dist + line.len < params.min_distance_between_support_points - && line.malformation < 0.3f) || line.len == 0) { - unchecked_dist += line.len; - } else { - unchecked_dist = line.len; - Vec3f pivot_site_search_point = to_3d(Vec2f(line.b + (line.b - line.a).normalized() * 300.0f), - layer_z); - auto [dist, nidx, target_point] = island_lines_dist.signed_distance_from_lines_extra(pivot_site_search_point.head<2>()); - Vec3f support_point = to_3d(target_point, layer_z); - auto force = part.is_stable_while_extruding(weakest_conn, line, support_point, layer_z, params); - if (force > 0) { - if (!supports_presence_grid.position_taken(support_point)) { - float orig_area = params.support_points_interface_radius * params.support_points_interface_radius * float(PI); - // artifically lower the area for materials that have strong bed adhesion, as this adhesion does not apply for support points - float altered_area = orig_area * params.get_support_spots_adhesion_strength() / params.get_bed_adhesion_yield_strength(); - part.add_support_point(support_point, altered_area); + float radius = params.support_points_interface_radius; + issues.support_points.emplace_back(support_point, force, radius, dir); + supports_presence_grid.take_position(support_point); - float radius = part.get_volume() < params.small_parts_threshold ? params.small_parts_support_points_interface_radius : params.support_points_interface_radius; - issues.support_points.emplace_back(support_point, force, radius, to_3d(Vec2f(line.b - line.a).normalized(), 0.0f)); - supports_presence_grid.take_position(support_point); + if (weakest_conn.area > EPSILON) { // Do not add it to the weakest connection if it is not valid - does not exist + weakest_conn.area += area; + weakest_conn.centroid_accumulator += support_point * area; + weakest_conn.second_moment_of_area_accumulator += area * support_point.head<2>().cwiseProduct(support_point.head<2>()); + weakest_conn.second_moment_of_area_covariance_accumulator += area * support_point.x() * support_point.y(); + } + }; - weakest_conn.area += altered_area; - weakest_conn.centroid_accumulator += support_point * altered_area; - weakest_conn.second_moment_of_area_accumulator += altered_area * - support_point.head<2>().cwiseProduct(support_point.head<2>()); - weakest_conn.second_moment_of_area_covariance_accumulator += altered_area * support_point.x() * - support_point.y(); + // first we will check local extrusion stability of bridges, then of perimeters. Perimeters are more important, they + // account for most of the curling and possible crashes, so on them we will run also global stability check + for (const auto &island : slice.islands) { + // Support bridges where needed. + for (const LayerExtrusionRange &fill_range : island.fills) { + const LayerRegion *fill_region = layer->get_region(fill_range.region()); + for (const auto &fill_idx : fill_range) { + const ExtrusionEntity *entity = fill_region->fills().entities[fill_idx]; + if (entity->role() == erBridgeInfill) { + for (const ExtrusionLine &bridge : + check_extrusion_entity_stability(entity, fill_region, prev_layer_ext_perim_lines, params)) { + if (bridge.support_point_generated) { + reckon_new_support_point(create_support_point_position(bridge.b), -EPSILON, Vec2f::Zero()); + } + } } } } + + const LayerRegion *perimeter_region = layer->get_region(island.perimeters.region()); + for (const auto &perimeter_idx : island.perimeters) { + const ExtrusionEntity *entity = perimeter_region->perimeters().entities[perimeter_idx]; + std::vector perims = check_extrusion_entity_stability(entity, perimeter_region, + prev_layer_ext_perim_lines, params); + for (const ExtrusionLine &perim : perims) { + if (perim.support_point_generated) { + reckon_new_support_point(create_support_point_position(perim.b), -EPSILON, Vec2f::Zero()); + } + if (perim.is_external_perimeter()) { current_slice_ext_perims_lines.push_back(perim); } + } + } } - } - //end of iteration over layer - } + + LD current_slice_lines_distancer(current_slice_ext_perims_lines); + float unchecked_dist = params.min_distance_between_support_points + 1.0f; + + for (const ExtrusionLine &line : current_slice_ext_perims_lines) { + if ((unchecked_dist + line.len < params.min_distance_between_support_points && line.malformation < 0.3f) || line.len == 0) { + unchecked_dist += line.len; + } else { + unchecked_dist = line.len; + Vec2f pivot_site_search_point = Vec2f(line.b + (line.b - line.a).normalized() * 300.0f); + auto [dist, nidx, + nearest_point] = current_slice_lines_distancer.signed_distance_from_lines_extra(pivot_site_search_point); + Vec3f support_point = create_support_point_position(nearest_point); + auto force = part.is_stable_while_extruding(weakest_conn, line, support_point, bottom_z, params); + if (force > 0) { reckon_new_support_point(support_point, force, (line.b - line.a).normalized()); } + } + } + current_layer_ext_perims_lines.insert(current_layer_ext_perims_lines.end(), current_slice_ext_perims_lines.begin(), + current_slice_ext_perims_lines.end()); + } // slice iterations + prev_layer_ext_perim_lines = LD(current_layer_ext_perims_lines); + } // layer iterations return issues; } -std::tuple> check_extrusions_and_build_graph(const PrintObject *po, - const Params ¶ms) { #ifdef DEBUG_FILES - FILE *segmentation_f = boost::nowide::fopen(debug_out_path("segmentation.obj").c_str(), "w"); - FILE *malform_f = boost::nowide::fopen(debug_out_path("malformations.obj").c_str(), "w"); -#endif - - Issues issues { }; - Malformations malformations{}; - std::vector islands_graph; - std::vector layer_lines; - float flow_width = get_flow_width(po->layers()[po->layer_count() - 1]->regions()[0], erExternalPerimeter); - PixelGrid prev_layer_grid(po, flow_width*2.0f); - -// PREPARE BASE LAYER - const Layer *layer = po->layers()[0]; - malformations.layers.push_back({}); // no malformations to be expected at first layer - for (const LayerRegion *layer_region : layer->regions()) { - for (const ExtrusionEntity *ex_entity : layer_region->perimeters()) { - for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { - push_lines(perimeter, layer_lines); - } // perimeter - } // ex_entity - for (const ExtrusionEntity *ex_entity : layer_region->fills()) { - for (const ExtrusionEntity *fill : static_cast(ex_entity)->entities) { - push_lines(fill, layer_lines); - } // fill - } // ex_entity - } // region - - auto [layer_islands, layer_grid] = reckon_islands(layer, true, 0, prev_layer_grid, - layer_lines, params); - islands_graph.push_back(std::move(layer_islands)); -#ifdef DEBUG_FILES - for (size_t x = 0; x < size_t(layer_grid.get_pixel_count().x()); ++x) { - for (size_t y = 0; y < size_t(layer_grid.get_pixel_count().y()); ++y) { - Vec2i coords = Vec2i(x, y); - size_t island_idx = layer_grid.get_pixel(coords); - if (layer_grid.get_pixel(coords) != NULL_ISLAND) { - Vec2f pos = layer_grid.get_pixel_center(coords); - size_t pseudornd = ((island_idx + 127) * 33331 + 6907) % 23; - Vec3f color = value_to_rgbf(0.0f, float(23), float(pseudornd)); - fprintf(segmentation_f, "v %f %f %f %f %f %f\n", pos[0], - pos[1], layer->slice_z, color[0], color[1], color[2]); - } - } - } - for (const auto &line : layer_lines) { - if (line.malformation > 0.0f) { - Vec3f color = value_to_rgbf(-EPSILON, 1.0f, line.malformation); - fprintf(malform_f, "v %f %f %f %f %f %f\n", line.b[0], - line.b[1], layer->slice_z, color[0], color[1], color[2]); - } - } -#endif - LD external_lines(layer_lines); - layer_lines.clear(); - prev_layer_grid = layer_grid; - - for (size_t layer_idx = 1; layer_idx < po->layer_count(); ++layer_idx) { - const Layer *layer = po->layers()[layer_idx]; - for (const LayerRegion *layer_region : layer->regions()) { - for (const ExtrusionEntity *ex_entity : layer_region->perimeters()) { - for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { - check_extrusion_entity_stability(perimeter, layer_lines, layer->slice_z, layer_region, - external_lines, issues, params); - } // perimeter - } // ex_entity - for (const ExtrusionEntity *ex_entity : layer_region->fills()) { - for (const ExtrusionEntity *fill : static_cast(ex_entity)->entities) { - if (fill->role() == ExtrusionRole::erGapFill - || fill->role() == ExtrusionRole::erBridgeInfill) { - check_extrusion_entity_stability(fill, layer_lines, layer->slice_z, layer_region, - external_lines, issues, params); - } else { - push_lines(fill, layer_lines); - } - } // fill - } // ex_entity - } // region - - auto [layer_islands, layer_grid] = reckon_islands(layer, false, 0, prev_layer_grid, - layer_lines, params); - islands_graph.push_back(std::move(layer_islands)); - - Lines malformed_lines{}; - for (const auto &line : layer_lines) { - if (line.malformation > 0.3f) { malformed_lines.push_back(Line{Point::new_scale(line.a), Point::new_scale(line.b)}); } - } - malformations.layers.push_back(malformed_lines); - -#ifdef DEBUG_FILES - for (size_t x = 0; x < size_t(layer_grid.get_pixel_count().x()); ++x) { - for (size_t y = 0; y < size_t(layer_grid.get_pixel_count().y()); ++y) { - Vec2i coords = Vec2i(x, y); - size_t island_idx = layer_grid.get_pixel(coords); - if (layer_grid.get_pixel(coords) != NULL_ISLAND) { - Vec2f pos = layer_grid.get_pixel_center(coords); - size_t pseudornd = ((island_idx + 127) * 33331 + 6907) % 23; - Vec3f color = value_to_rgbf(0.0f, float(23), float(pseudornd)); - fprintf(segmentation_f, "v %f %f %f %f %f %f\n", pos[0], - pos[1], layer->slice_z, color[0], color[1], color[2]); - } - } - } - for (const auto &line : layer_lines) { - if (line.malformation > 0.0f) { - Vec3f color = value_to_rgbf(-EPSILON, layer->height*params.max_malformation_factor, line.malformation); - fprintf(malform_f, "v %f %f %f %f %f %f\n", line.b[0], - line.b[1], layer->slice_z, color[0], color[1], color[2]); - } - } -#endif - external_lines = LD(layer_lines); - layer_lines.clear(); - prev_layer_grid = layer_grid; - } - -#ifdef DEBUG_FILES - fclose(segmentation_f); - fclose(malform_f); -#endif - - return {issues, malformations, islands_graph}; -} - -#ifdef DEBUG_FILES -void debug_export(Issues issues, std::string file_name) { +void debug_export(Issues issues, std::string file_name) +{ Slic3r::CNumericLocalesSetter locales_setter; { FILE *fp = boost::nowide::fopen(debug_out_path((file_name + "_supports.obj").c_str()).c_str(), "w"); if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "Debug files: Couldn't open " << file_name << " for writing"; + BOOST_LOG_TRIVIAL(error) << "Debug files: Couldn't open " << file_name << " for writing"; return; } for (size_t i = 0; i < issues.support_points.size(); ++i) { - fprintf(fp, "v %f %f %f %f %f %f\n", issues.support_points[i].position(0), - issues.support_points[i].position(1), - issues.support_points[i].position(2), 1.0, 0.0, 1.0); + if (issues.support_points[i].force <= 0) { + fprintf(fp, "v %f %f %f %f %f %f\n", issues.support_points[i].position(0), issues.support_points[i].position(1), + issues.support_points[i].position(2), 0.0, 1.0, 0.0); + } else { + fprintf(fp, "v %f %f %f %f %f %f\n", issues.support_points[i].position(0), issues.support_points[i].position(1), + issues.support_points[i].position(2), 1.0, 0.0, 0.0); + } } fclose(fp); @@ -1181,21 +878,17 @@ void debug_export(Issues issues, std::string file_name) { // std::vector quick_search(const PrintObject *po, const Params ¶ms) { // return {}; // } - -std::tuple full_search(const PrintObject *po, const Params ¶ms) { - auto [local_issues, malformations, graph] = check_extrusions_and_build_graph(po, params); - Issues global_issues = check_global_stability( { po, params.min_distance_between_support_points }, graph, params); +Issues full_search(const PrintObject *po, const Params ¶ms) +{ + Issues issues = check_stability(po, params); #ifdef DEBUG_FILES - debug_export(local_issues, "local_issues"); - debug_export(global_issues, "global_issues"); + debug_export(issues, "issues"); #endif - global_issues.support_points.insert(global_issues.support_points.end(), - local_issues.support_points.begin(), local_issues.support_points.end()); - - return {global_issues, malformations}; + return issues; } + struct LayerCurlingEstimator { LD prev_layer_lines = LD({}); diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index 6a549bd36..b0cbab57d 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -32,8 +32,7 @@ struct Params { const float min_distance_between_support_points = 3.0f; //mm const float support_points_interface_radius = 1.5f; // mm const float connections_min_considerable_area = 1.5f; //mm^2 - const float small_parts_threshold = 5.0f; //mm^3 - const float small_parts_support_points_interface_radius = 3.0f; // mm + const float min_distance_to_allow_local_supports = 2.0f; //mm std::string filament_type; const float gravity_constant = 9806.65f; // mm/s^2; gravity acceleration on Earth's surface, algorithm assumes that printer is in upwards position. @@ -61,11 +60,11 @@ struct Params { }; struct SupportPoint { - SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec3f &direction); + SupportPoint(const Vec3f &position, float force, float spot_radius, const Vec2f &direction); Vec3f position; float force; float spot_radius; - Vec3f direction; + Vec2f direction; }; struct Issues { @@ -77,7 +76,7 @@ struct Malformations { }; // std::vector quick_search(const PrintObject *po, const Params ¶ms); -std::tuple full_search(const PrintObject *po, const Params ¶ms); +Issues full_search(const PrintObject *po, const Params ¶ms); void estimate_supports_malformations(SupportLayerPtrs &layers, float supports_flow_width, const Params ¶ms); void estimate_malformations(LayerPtrs &layers, const Params ¶ms); From 9323e347f0d19382e177f7e0b7fa048154b7c18a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 29 Nov 2022 12:59:05 +0100 Subject: [PATCH 33/33] ObjectList: Delete last volume from the object even if this volume is text --- src/slic3r/GUI/ObjectDataViewModel.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index cfc78f9f8..fe57d7d5a 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -772,11 +772,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) // get index of the last VolumeItem in CildrenList size_t vol_idx = GetItemIndexForFirstVolume(node_parent); - ObjectDataViewModelNode *last_child_node = node_parent->GetNthChild(vol_idx); - // if last volume is text then don't delete it - if (last_child_node->is_text_volume()) - return parent; // delete this last volume DeleteSettings(wxDataViewItem(last_child_node));