diff --git a/docs/HLSD/separated-infills.md b/docs/HLSD/separated-infills.md index b7f12f4199..10742eb042 100644 --- a/docs/HLSD/separated-infills.md +++ b/docs/HLSD/separated-infills.md @@ -79,6 +79,13 @@ the same `infill_body()`. The octree of the whole object is built only for an object of a single body, or when some body received no triangles, which then uses it. +The line spacing of an octree comes from the density, line width and multiline +count of a region, so a modifier or a part with its own density needs octrees of +its own. `adaptive_fill_line_spacing()` gives the spacing of each region, and +`FillAdaptive::RegionOctrees` holds one set of octrees per distinct spacing, +shared by the regions that have it. A set is built only for the bodies its +regions fill. The fill takes the set of its region, then the octree of its body. + ## Patterns left out Lightning grows its trees over the whole object, so moving a reference point diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 3dadb20253..2f23917be6 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -1320,7 +1320,7 @@ void export_group_fills_to_svg(const char *path, const std::vector #endif // friend to Layer -void Layer::make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const FillAdaptive::Octrees* support_fill_octrees, FillLightning::Generator* lightning_generator) +void Layer::make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLightning::Generator* lightning_generator) { for (LayerRegion *layerm : m_regions) layerm->fills.clear(); @@ -1353,7 +1353,7 @@ void Layer::make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const f->z = this->print_z; f->angle = surface_fill.params.angle; f->fixed_angle = surface_fill.params.fixed_angle; - const FillAdaptive::Octrees *octrees = surface_fill.params.pattern == ipSupportCubic ? support_fill_octrees : adaptive_fill_octrees; + const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr; f->print_config = &this->object()->print()->config(); f->print_object_config = &this->object()->config(); if (surface_fill.params.pattern == ipConcentricInternal) { @@ -1516,7 +1516,7 @@ void Layer::make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const * - For lightning/adaptive patterns, the respective generators are wired so their * polylines match the final infill layout. */ -Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::Octrees* adaptive_fill_octrees, const FillAdaptive::Octrees* support_fill_octrees, FillLightning::Generator* lightning_generator) const +Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::RegionOctrees* fill_octrees, FillLightning::Generator* lightning_generator) const { LockRegionParam skin_inner_param; std::vector surface_fills = group_fills(*this, skin_inner_param); @@ -1574,7 +1574,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdapti f->z = this->print_z; f->angle = surface_fill.params.angle; f->fixed_angle = surface_fill.params.fixed_angle; - const FillAdaptive::Octrees *octrees = surface_fill.params.pattern == ipSupportCubic ? support_fill_octrees : adaptive_fill_octrees; + const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr; f->print_config = &this->object()->print()->config(); f->print_object_config = &this->object()->config(); diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 210394ea51..b693ae7cfa 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -301,88 +301,25 @@ void OctreeDeleter::operator()(Octree *p) { delete p; } -std::pair adaptive_fill_line_spacing(const PrintObject &print_object) +std::vector adaptive_fill_line_spacing(const PrintObject &print_object) { - // Output, spacing for icAdaptiveCubic and icSupportCubic - double adaptive_line_spacing = 0.; - double support_line_spacing = 0.; - - enum class Tristate { - Yes, - No, - Maybe - }; - struct RegionFillData { - Tristate has_adaptive_infill; - Tristate has_support_infill; - double density; - double extrusion_width; - }; - std::vector region_fill_data; - region_fill_data.reserve(print_object.num_printing_regions()); - bool build_octree = false; + std::vector line_spacing(print_object.num_printing_regions(), 0.); const std::vector &nozzle_diameters = print_object.print()->config().nozzle_diameter.values; double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()); double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, float(max_nozzle_diameter)); - for (size_t region_id = 0; region_id < print_object.num_printing_regions(); ++ region_id) { - const PrintRegionConfig &config = print_object.printing_region(region_id).config(); - bool nonempty = config.sparse_infill_density > 0; - bool has_adaptive_infill = nonempty && config.sparse_infill_pattern == ipAdaptiveCubic; - bool has_support_infill = nonempty && config.sparse_infill_pattern == ipSupportCubic; - double sparse_infill_line_width = config.sparse_infill_line_width.get_abs_value(max_nozzle_diameter); - region_fill_data.push_back(RegionFillData({ - has_adaptive_infill ? Tristate::Maybe : Tristate::No, - has_support_infill ? Tristate::Maybe : Tristate::No, - config.sparse_infill_density, - sparse_infill_line_width != 0. ? sparse_infill_line_width : default_infill_extrusion_width - })); - build_octree |= has_adaptive_infill || has_support_infill; + for (size_t region_id = 0; region_id < line_spacing.size(); ++ region_id) { + const PrintRegionConfig &config = print_object.printing_region(region_id).config(); + if (config.sparse_infill_density <= 0 || ! is_octree_infill_pattern(config.sparse_infill_pattern) || + std::none_of(print_object.layers().begin(), print_object.layers().end(), [region_id](const Layer *layer) { + return region_id < layer->regions().size() && ! layer->regions()[region_id]->fill_surfaces.empty(); + })) + continue; + double extrusion_width = config.sparse_infill_line_width.get_abs_value(max_nozzle_diameter); + if (extrusion_width == 0.) + extrusion_width = default_infill_extrusion_width; + line_spacing[region_id] = extrusion_width / ((config.sparse_infill_density / 100.0f) * 0.333333333f) * config.fill_multiline.value; } - - if (build_octree) { - // Compute the average of above parameters over all layers - 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()) - rd.has_adaptive_infill = Tristate::Yes; - if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty()) - rd.has_support_infill = Tristate::Yes; - } - - double adaptive_fill_density = 0.; - double adaptive_infill_extrusion_width = 0.; - int adaptive_cnt = 0; - double support_fill_density = 0.; - double support_infill_extrusion_width = 0.; - int support_cnt = 0; - - for (const RegionFillData &rd : region_fill_data) { - if (rd.has_adaptive_infill == Tristate::Yes) { - adaptive_fill_density += rd.density; - adaptive_infill_extrusion_width += rd.extrusion_width; - ++ adaptive_cnt; - } else if (rd.has_support_infill == Tristate::Yes) { - support_fill_density += rd.density; - support_infill_extrusion_width += rd.extrusion_width; - ++ support_cnt; - } - } - - auto to_line_spacing = [](int cnt, double density, double extrusion_width) { - if (cnt) { - density /= double(cnt); - extrusion_width /= double(cnt); - return extrusion_width / ((density / 100.0f) * 0.333333333f); - } else - return 0.; - }; - const int n_multiline = print_object.printing_region(0).config().fill_multiline.value; - adaptive_line_spacing = to_line_spacing(adaptive_cnt, adaptive_fill_density, adaptive_infill_extrusion_width) * n_multiline; - support_line_spacing = to_line_spacing(support_cnt, support_fill_density, support_infill_extrusion_width) * n_multiline; - } - - return std::make_pair(adaptive_line_spacing, support_line_spacing); + return line_spacing; } // Context used by generate_infill_lines() when recursively traversing an octree in a DDA fashion diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 57cbe713f1..47b6f62e28 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -59,11 +59,22 @@ struct Octrees } }; -// Calculate line spacing for -// 1) adaptive cubic infill -// 2) adaptive internal support cubic infill -// Returns zero for a particular infill type if no such infill is to be generated. -std::pair adaptive_fill_line_spacing(const PrintObject &print_object); +// Orca: The octrees of each line spacing the regions of an object fill with. +struct RegionOctrees +{ + std::vector sets; + // Index into sets for each region, -1 for a region without adaptive or support cubic infill. + std::vector region_set; + + const Octrees *region(size_t region_id) const + { + return region_id < region_set.size() && region_set[region_id] >= 0 ? &sets[region_set[region_id]] : nullptr; + } +}; + +// Line spacing of the adaptive or support cubic infill of each region of the object, +// zero for a region that generates no such infill. +std::vector adaptive_fill_line_spacing(const PrintObject &print_object); // Rotation of the octree to stand on one of its corners. Eigen::Quaterniond transform_to_world(); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 74df3e370c..d691a1da9d 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -33,7 +33,7 @@ class PrintObject; class Print; namespace FillAdaptive { - struct Octrees; + struct RegionOctrees; }; namespace FillLightning { @@ -207,10 +207,9 @@ public: static bool is_perimeter_compatible(const Print& print, const PrintRegion& a, const PrintRegion& b); void make_perimeters(); // Phony version of make_fills() without parameters for Perl integration only. - void make_fills() { this->make_fills(nullptr, nullptr); } - void make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const FillAdaptive::Octrees* support_fill_octrees, FillLightning::Generator* lightning_generator = nullptr); - Polylines generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::Octrees *adaptive_fill_octrees, - const FillAdaptive::Octrees *support_fill_octrees, + void make_fills() { this->make_fills(nullptr); } + void make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLightning::Generator* lightning_generator = nullptr); + Polylines generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::RegionOctrees *fill_octrees, FillLightning::Generator* lightning_generator) const; void make_ironing(); // Returns the filament id (1-based) the region is ironed with, or -1 when the diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 25d08316b6..5e85bb040c 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -583,7 +583,7 @@ private: void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); - std::pair prepare_adaptive_infill_data( + FillAdaptive::RegionOctrees prepare_adaptive_infill_data( const std::vector>& surfaces_w_layer) const; FillLightning::GeneratorPtr prepare_lightning_infill_data(); @@ -616,7 +616,7 @@ private: // so that next call to make_perimeters() performs a union() before computing loops bool m_typed_slices = false; - std::pair m_adaptive_fill_octrees; + FillAdaptive::RegionOctrees m_adaptive_fill_octrees; std::vector m_separated_body_bboxes; FillLightning::GeneratorPtr m_lightning_generator; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index e65314a68a..4f4d823951 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -848,7 +848,7 @@ void PrintObject::infill() [this](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); - m_layers[layer_idx]->make_fills(&m_adaptive_fill_octrees.first, &m_adaptive_fill_octrees.second, this->m_lightning_generator.get()); + m_layers[layer_idx]->make_fills(&m_adaptive_fill_octrees, this->m_lightning_generator.get()); } } ); @@ -1168,14 +1168,27 @@ static std::vector split_mesh_by_body(const PrintObject &o return bodies; } -std::pair PrintObject::prepare_adaptive_infill_data( +FillAdaptive::RegionOctrees PrintObject::prepare_adaptive_infill_data( const std::vector> &surfaces_w_layer) const { using namespace FillAdaptive; - auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this); - if ((adaptive_line_spacing == 0. && support_line_spacing == 0.) || this->layers().empty()) + // Orca: Each region fills with the octrees of its own line spacing, shared by the regions of equal spacing. + const std::vector line_spacing = adaptive_fill_line_spacing(*this); + std::vector> spacings; // Line spacing, support cubic. + RegionOctrees octrees; + octrees.region_set.assign(line_spacing.size(), -1); + for (size_t region_id = 0; region_id < line_spacing.size(); ++ region_id) + if (line_spacing[region_id] > 0.) { + const std::pair spacing(line_spacing[region_id], this->printing_region(region_id).config().sparse_infill_pattern == ipSupportCubic); + const auto it = std::find(spacings.begin(), spacings.end(), spacing); + octrees.region_set[region_id] = int(it - spacings.begin()); + if (it == spacings.end()) + spacings.push_back(spacing); + } + if (spacings.empty() || this->layers().empty()) return {}; + octrees.sets.resize(spacings.size()); indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set(); // Rotate mesh and build octree on it with axis-aligned (standart base) cubes. @@ -1198,44 +1211,50 @@ std::pair PrintObject::prepare_ada } }); - // Orca: Each body gets the octree it has when sliced on its own, from its own triangles. - std::pair octrees; - const size_t num_bodies = m_separated_body_bboxes.size(); - bool need_object = num_bodies <= 1; + // Orca: Each body gets the octree it has when sliced on its own, from its own triangles, for each line spacing + // its regions fill with. Body num_bodies stands for the whole object, which serves an object of a single body + // and the surfaces of bodies that have no octree of their own. + const size_t num_bodies = m_separated_body_bboxes.size(); + std::vector> to_build; // Set, body. + std::vector body_meshes; + std::vector> body_overhangs(num_bodies); if (num_bodies > 1) { - const std::vector body_meshes = split_mesh_by_body(*this, mesh, num_bodies); - need_object = std::any_of(body_meshes.begin(), body_meshes.end(), [](const indexed_triangle_set &its) { return its.indices.empty(); }); - std::vector> body_overhangs(num_bodies); + body_meshes = split_mesh_by_body(*this, mesh, num_bodies); for (size_t i = 0; i < surfaces_w_layer.size(); ++ i) if (const int body = separated_body_at(*surfaces_w_layer[i].second, surfaces_w_layer[i].first->expolygon.contour.points.front()); body >= 0) append(body_overhangs[body], overhangs[i]); - if (adaptive_line_spacing) - octrees.first.bodies.resize(num_bodies); - if (support_line_spacing) - octrees.second.bodies.resize(num_bodies); - tbb::parallel_for(tbb::blocked_range(0, num_bodies), [&, adaptive_spacing = adaptive_line_spacing, support_spacing = support_line_spacing]( - const tbb::blocked_range &range) { - for (size_t body = range.begin(); body < range.end(); ++ body) { - m_print->throw_if_canceled(); - if (body_meshes[body].indices.empty()) - continue; - if (adaptive_spacing) - octrees.first.bodies[body] = build_octree(body_meshes[body], body_overhangs[body], adaptive_spacing, false); - if (support_spacing) - octrees.second.bodies[body] = build_octree(body_meshes[body], body_overhangs[body], support_spacing, true); - } - }); - } + std::vector> fills(spacings.size(), std::vector(num_bodies + 1, false)); + for (const Layer *layer : m_layers) + for (size_t region_id = 0; region_id < layer->regions().size() && region_id < octrees.region_set.size(); ++ region_id) + if (const int set = octrees.region_set[region_id]; set >= 0) + for (const Surface &surface : layer->regions()[region_id]->fill_surfaces) { + const int body = separated_body_at(*layer, surface.expolygon.contour.points.front()); + fills[set][body >= 0 && ! body_meshes[body].indices.empty() ? size_t(body) : num_bodies] = true; + } + for (size_t set = 0; set < spacings.size(); ++ set) { + octrees.sets[set].bodies.resize(num_bodies); + for (size_t body = 0; body <= num_bodies; ++ body) + if (fills[set][body]) + to_build.emplace_back(set, body); + } + } else + for (size_t set = 0; set < spacings.size(); ++ set) + to_build.emplace_back(set, num_bodies); // and gather them. for (size_t i = 1; i < overhangs.size(); ++ i) append(overhangs.front(), std::move(overhangs[i])); - // Orca: The object's octree only serves bodies that have none of their own. - if (need_object && adaptive_line_spacing) - octrees.first.object = build_octree(mesh, overhangs.front(), adaptive_line_spacing, false); - if (need_object && support_line_spacing) - octrees.second.object = build_octree(mesh, overhangs.front(), support_line_spacing, true); + tbb::parallel_for(tbb::blocked_range(0, to_build.size()), [&](const tbb::blocked_range &range) { + for (size_t i = range.begin(); i < range.end(); ++ i) { + m_print->throw_if_canceled(); + const auto [set, body] = to_build[i]; + const bool object = body == num_bodies; + (object ? octrees.sets[set].object : octrees.sets[set].bodies[body]) = + build_octree(object ? mesh : body_meshes[body], object ? overhangs.front() : body_overhangs[body], spacings[set].first, + spacings[set].second); + } + }); return octrees; } @@ -3077,8 +3096,7 @@ void PrintObject::bridge_over_infill() for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) { size_t lidx = layers_to_generate_infill[job_idx]; infill_lines.at( - lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(&po->m_adaptive_fill_octrees.first, - &po->m_adaptive_fill_octrees.second, + lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(&po->m_adaptive_fill_octrees, po->m_lightning_generator.get()); } }); diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index 19ad1711b8..e1d93c5e15 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -1780,7 +1780,7 @@ TEST_CASE("Sparse plane-path anchors match the printed infill", "[Fill][Internal const AABBTreeLines::LinesDistancer printed_tree(to_lines(printed)); // Orca: Exclude perimeter connections: anchoring and extrusion can trim those differently. - const Polylines anchors = intersection_pl(layer.generate_sparse_infill_polylines_for_anchoring(nullptr, nullptr, nullptr), + const Polylines anchors = intersection_pl(layer.generate_sparse_infill_polylines_for_anchoring(nullptr, nullptr), shrink(to_polygons(layer.lslices), scale_(3.))); REQUIRE_FALSE(anchors.empty()); double max_distance = 0.; @@ -1792,8 +1792,9 @@ TEST_CASE("Sparse plane-path anchors match the printed infill", "[Fill][Internal CHECK(unscale(max_distance) <= config.opt_float("resolution")); } -// Orca: Slices the meshes as the parts of one object, where they are. -static Print &slice_parts(Print &print, DynamicPrintConfig config, const std::vector &parts) +// Orca: Slices the meshes as the parts of one object, where they are, with modifiers of their own config. +static Print &slice_parts(Print &print, DynamicPrintConfig config, const std::vector &parts, + const std::vector> &modifiers = {}) { config.set_deserialize_strict({{"layer_height", 0.2}, {"initial_layer_print_height", 0.2}, @@ -1804,6 +1805,8 @@ static Print &slice_parts(Print &print, DynamicPrintConfig config, const std::ve Slic3r::Test::init_print({parts.front()}, print, model, config, nullptr, false); for (size_t i = 1; i < parts.size(); ++ i) model.objects.front()->add_volume(TriangleMesh(parts[i]), ModelVolumeType::MODEL_PART, false); + for (const auto &[mesh, modifier_config] : modifiers) + model.objects.front()->add_volume(TriangleMesh(mesh), ModelVolumeType::PARAMETER_MODIFIER, false)->config.apply(modifier_config); print.apply(model, config); print.process(); return print; @@ -1988,3 +1991,33 @@ TEST_CASE("Adaptive infill fills each body like the body sliced alone", "[Fill][ CHECK(unmatched.first < 0.02); CHECK(unmatched.second < 0.02); } + +TEST_CASE("Adaptive infill of a modifier leaves the density of the other regions", "[Fill][Regression]") +{ + const std::string pattern = GENERATE("adaptivecubic", "supportcubic"); + CAPTURE(pattern); + auto config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({{"sparse_infill_pattern", pattern}, + {"sparse_infill_density", "15%"}, + {"top_shell_layers", 0}, + {"bottom_shell_layers", 0}}); + TriangleMesh bodies = make_cube(30, 30, 6), second = make_cube(30, 30, 6); + second.translate(40, 0, 0); + bodies.merge(second); + // Orca: A denser modifier over the right half of the second body. + TriangleMesh modifier = make_cube(20, 40, 10); + modifier.translate(55, -5, -2); + DynamicPrintConfig dense = config; + dense.set_deserialize_strict({{"sparse_infill_density", "60%"}}); + Print print, print_sparse, print_dense; + slice_parts(print, config, {bodies}, {{modifier, dense}}); + slice_parts(print_sparse, config, {bodies}); + slice_parts(print_dense, dense, {bodies}); + + // Orca: Bed regions 3 mm inside the walls and the modifier, away from the links along them. + auto rect = [](double x0, double y0, double x1, double y1) { + return Polygon({Point::new_scale(x0, y0), Point::new_scale(x1, y0), Point::new_scale(x1, y1), Point::new_scale(x0, y1)}); + }; + CHECK(unmatched_between_prints(print, print_sparse, erInternalInfill, {rect(3, 3, 27, 27), rect(43, 3, 52, 27)}) < 0.02); + CHECK(unmatched_between_prints(print, print_dense, erInternalInfill, {rect(58, 3, 67, 27)}) < 0.02); +}