diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index efa0f2eb33..64c1f18008 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -178,8 +178,6 @@ set(lisbslic3r_sources Geometry/VoronoiUtilsCgal.hpp Geometry/VoronoiVisualUtils.hpp Int128.hpp - InternalBridgeDetector.cpp - InternalBridgeDetector.hpp KDTreeIndirect.hpp Layer.cpp Layer.hpp diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 30b81f4cde..f05ef935a7 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -1,3 +1,12 @@ +///|/ Copyright (c) Prusa Research 2016 - 2023 Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Pavel Mikuš @Godrak, Lukáš Hejl @hejllukas +///|/ Copyright (c) SuperSlicer 2023 Remi Durand @supermerill +///|/ Copyright (c) 2016 Sakari Kapanen @Flannelhead +///|/ Copyright (c) Slic3r 2011 - 2015 Alessandro Ranellucci @alranel +///|/ Copyright (c) 2013 Mark Hindess +///|/ Copyright (c) 2011 Michael Moon +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include #include #include @@ -46,8 +55,6 @@ struct SurfaceFillParams // 1000mm is roughly the maximum length line that fits into a 32bit coord_t. float anchor_length = 1000.f; float anchor_length_max = 1000.f; - //BBS - bool with_loop = false; // width, height of extrusion, nozzle diameter, is bridge // For the output, for fill generator. @@ -79,7 +86,6 @@ struct SurfaceFillParams // RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust); RETURN_COMPARE_NON_EQUAL(anchor_length); RETURN_COMPARE_NON_EQUAL(anchor_length_max); - RETURN_COMPARE_NON_EQUAL(with_loop); RETURN_COMPARE_NON_EQUAL(flow.width()); RETURN_COMPARE_NON_EQUAL(flow.height()); RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter()); @@ -100,7 +106,6 @@ struct SurfaceFillParams // this->dont_adjust == rhs.dont_adjust && this->anchor_length == rhs.anchor_length && this->anchor_length_max == rhs.anchor_length_max && - this->with_loop == rhs.with_loop && this->flow == rhs.flow && this->extrusion_role == rhs.extrusion_role; } @@ -151,8 +156,6 @@ std::vector group_fills(const Layer &layer) params.extruder = layerm.region().extruder(extrusion_role); params.pattern = region_config.sparse_infill_pattern.value; params.density = float(region_config.sparse_infill_density); - //BBS - params.with_loop = surface.surface_type == stInternalWithLoop; if (surface.is_solid()) { params.density = 100.f; @@ -501,7 +504,6 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.extrusion_role = surface_fill.params.extrusion_role; params.using_internal_flow = using_internal_flow; params.no_extrusion_overlap = surface_fill.params.overlap; - params.with_loop = surface_fill.params.with_loop; params.config = &layerm->region().config(); if (surface_fill.params.pattern == ipGrid) params.can_reverse = false; @@ -540,6 +542,101 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: #endif } +Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) const +{ + std::vector surface_fills = group_fills(*this); + const Slic3r::BoundingBox bbox = this->object()->bounding_box(); + const auto resolution = this->object()->print()->config().resolution.value; + + Polylines sparse_infill_polylines{}; + + for (SurfaceFill &surface_fill : surface_fills) { + if (surface_fill.surface.surface_type != stInternal) { + continue; + } + + switch (surface_fill.params.pattern) { + case ipCount: continue; break; + case ipSupportBase: continue; break; + //TODO: case ipEnsuring: continue; break; + case ipLightning: + case ipAdaptiveCubic: + case ipSupportCubic: + case ipRectilinear: + case ipMonotonic: + case ipMonotonicLine: + case ipAlignedRectilinear: + case ipGrid: + case ipTriangles: + case ipStars: + case ipCubic: + case ipLine: + case ipConcentric: + case ipConcentricInternal: + case ipHoneycomb: + case ip3DHoneycomb: + case ipGyroid: + case ipHilbertCurve: + case ipArchimedeanChords: + case ipOctagramSpiral: break; + } + + // Create the filler object. + std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern)); + f->set_bounding_box(bbox); + f->layer_id = this->id() - this->object()->get_layer(0)->id(); // We need to subtract raft layers. + f->z = this->print_z; + f->angle = surface_fill.params.angle; + // f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; + // TODO: f->print_config = &this->object()->print()->config(); + // TODO: f->print_object_config = &this->object()->config(); + + if (surface_fill.params.pattern == ipLightning) + dynamic_cast(f.get())->generator = lightning_generator; + + // calculate flow spacing for infill pattern generation + double link_max_length = 0.; + if (!surface_fill.params.bridge) { +#if 0 + link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing()); +// printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length); +#else + if (surface_fill.params.density > 80.) // 80% + link_max_length = 3. * f->spacing; +#endif + } + + LayerRegion &layerm = *m_regions[surface_fill.region_id]; + + // Maximum length of the perimeter segment linking two infill lines. + f->link_max_length = (coord_t) scale_(link_max_length); + // Used by the concentric infill pattern to clip the loops to create extrusion paths. + f->loop_clipping = coord_t(scale_(layerm.region().config().seam_gap.get_abs_value(surface_fill.params.flow.nozzle_diameter()))); + + // apply half spacing using this flow's own spacing and generate infill + FillParams params; + params.density = float(0.01 * surface_fill.params.density); + params.dont_adjust = false; // surface_fill.params.dont_adjust; + params.anchor_length = surface_fill.params.anchor_length; + params.anchor_length_max = surface_fill.params.anchor_length_max; + params.resolution = resolution; + params.use_arachne = false; + 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. + f->spacing = surface_fill.params.spacing; + surface_fill.surface.expolygon = std::move(expoly); + try { + Polylines polylines = f->fill_surface(&surface_fill.surface, params); + sparse_infill_polylines.insert(sparse_infill_polylines.end(), polylines.begin(), polylines.end()); + } catch (InfillFailedException &) {} + } + } + + return sparse_infill_polylines; +} + // Create ironing extrusions over top surfaces. void Layer::make_ironing() { diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 9c5c40348f..2858770949 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -120,57 +120,13 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para { Polylines polylines; ThickPolylines thick_polylines; - if (!params.with_loop) { - try { - if (params.use_arachne) - thick_polylines = this->fill_surface_arachne(surface, params); - else - polylines = this->fill_surface(surface, params); - } - catch (InfillFailedException&) {} - } - //BBS: add handling for infill pattern with loop - else { - Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(scale_(this->overlap - 0.5 * this->spacing))); - Polylines loop_polylines = to_polylines(expp); - { - //BBS: clip the loop - size_t j = 0; - for (size_t i = 0; i < loop_polylines.size(); ++i) { - loop_polylines[i].clip_end(this->loop_clipping); - if (loop_polylines[i].is_valid()) { - if (j < i) - loop_polylines[j] = std::move(loop_polylines[i]); - ++j; - } - } - if (j < loop_polylines.size()) - loop_polylines.erase(loop_polylines.begin() + int(j), loop_polylines.end()); - } - - if (!loop_polylines.empty()) { - if (params.use_arachne) - append(thick_polylines, to_thick_polylines(std::move(loop_polylines), scaled(this->spacing))); - else - append(polylines, std::move(loop_polylines)); - expp = offset_ex(expp, float(scale_(0 - 0.5 * this->spacing))); - } else { - //BBS: the area is too narrow to place a loop, return to original expolygon - expp = { surface->expolygon }; - } - - Surface temp_surface = *surface; - for (ExPolygon& ex : expp) { - temp_surface.expolygon = ex; - try { - if (params.use_arachne) - append(thick_polylines, std::move(this->fill_surface_arachne(&temp_surface, params))); - else - append(polylines, std::move(this->fill_surface(&temp_surface, params))); - } - catch (InfillFailedException&) {} - } + try { + if (params.use_arachne) + thick_polylines = this->fill_surface_arachne(surface, params); + else + polylines = this->fill_surface(surface, params); } + catch (InfillFailedException&) {} if (!polylines.empty() || !thick_polylines.empty()) { // calculate actual flow from spacing (which might have been adjusted by the infill diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index ddb6073e92..687dfb188a 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -67,8 +67,6 @@ struct FillParams bool use_arachne{ false }; // Layer height for Concentric infill with Arachne. coordf_t layer_height { 0.f }; - //BBS - bool with_loop { false }; // BBS Flow flow; diff --git a/src/libslic3r/InternalBridgeDetector.cpp b/src/libslic3r/InternalBridgeDetector.cpp deleted file mode 100644 index ac105a86bf..0000000000 --- a/src/libslic3r/InternalBridgeDetector.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "InternalBridgeDetector.hpp" -#include "ClipperUtils.hpp" -#include "Geometry.hpp" -#include - -namespace Slic3r { - -InternalBridgeDetector::InternalBridgeDetector( - ExPolygon _internal_bridge, const ExPolygons& _fill_no_overlap, coord_t _spacing) : - fill_no_overlap(_fill_no_overlap), - spacing(_spacing) -{ - this->internal_bridge_infill.push_back(std::move(_internal_bridge)); - initialize(); -} - -//#define INTERNAL_BRIDGE_DETECTOR_DEBUG_TO_SVG - -void InternalBridgeDetector::initialize() -{ - Polygons grown = offset(this->internal_bridge_infill, float(this->spacing)); - this->m_anchor_regions = diff_ex(grown, offset(this->fill_no_overlap, 10.f)); - -#ifdef INTERNAL_BRIDGE_DETECTOR_DEBUG_TO_SVG - static int irun = 0; - BoundingBox bbox_svg; - - bbox_svg.merge(get_extents(this->internal_bridge_infill)); - bbox_svg.merge(get_extents(this->fill_no_overlap)); - bbox_svg.merge(get_extents(this->m_anchor_regions)); - { - std::stringstream stri; - stri << "InternalBridgeDetector_" << irun << ".svg"; - SVG svg(stri.str(), bbox_svg); - svg.draw(to_polylines(this->internal_bridge_infill), "blue"); - svg.draw(to_polylines(this->fill_no_overlap), "yellow"); - svg.draw(to_polylines(m_anchor_regions), "red"); - svg.Close(); - } - ++ irun; -#endif -} - -bool InternalBridgeDetector::detect_angle() -{ - if (this->m_anchor_regions.empty()) - return false; - - std::vector candidates; - std::vector angles = bridge_direction_candidates(); - candidates.reserve(angles.size()); - for (size_t i = 0; i < angles.size(); ++ i) - candidates.emplace_back(InternalBridgeDirection(angles[i])); - - Polygons clip_area = offset(this->internal_bridge_infill, 0.5f * float(this->spacing)); - - bool have_coverage = false; - for (size_t i_angle = 0; i_angle < candidates.size(); ++ i_angle) - { - const double angle = candidates[i_angle].angle; - - Lines lines; - { - BoundingBox bbox = get_extents_rotated(this->m_anchor_regions, - angle); - // Cover the region with line segments. - lines.reserve((bbox.max(1) - bbox.min(1) + this->spacing) / this->spacing); - double s = sin(angle); - double c = cos(angle); - - for (coord_t y = bbox.min(1); y <= bbox.max(1); y += this->spacing) - lines.push_back(Line( - Point((coord_t)round(c * bbox.min(0) - s * y), (coord_t)round(c * y + s * bbox.min(0))), - Point((coord_t)round(c * bbox.max(0) - s * y), (coord_t)round(c * y + s * bbox.max(0))))); - } - - double total_length = 0; - double anchored_length = 0; - double max_length = 0; - { - Lines clipped_lines = intersection_ln(lines, clip_area); - for (size_t i = 0; i < clipped_lines.size(); ++i) { - const Line &line = clipped_lines[i]; - double len = line.length(); - total_length += len; - if (expolygons_contain(this->m_anchor_regions, line.a) && expolygons_contain(this->m_anchor_regions, line.b)) { - // This line could be anchored. - anchored_length += len; - max_length = std::max(max_length, len); - } - } - } - if (anchored_length == 0.) - continue; - - have_coverage = true; - - candidates[i_angle].coverage = anchored_length/total_length; - candidates[i_angle].max_length = max_length; - } - - if (! have_coverage) - return false; - - std::sort(candidates.begin(), candidates.end()); - size_t i_best = 0; - this->angle = candidates[i_best].angle; - if (this->angle >= PI) - this->angle -= PI; - - return true; -} - -std::vector InternalBridgeDetector::bridge_direction_candidates() const -{ - std::vector angles; - for (int i = 0; i <= PI/this->resolution; ++i) - angles.push_back(i * this->resolution); - - // we also test angles of each bridge contour - { - Lines lines = to_lines(this->internal_bridge_infill); - for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) - angles.push_back(line->direction()); - } - - // remove duplicates - double min_resolution = PI/180.0; - std::sort(angles.begin(), angles.end()); - for (size_t i = 1; i < angles.size(); ++i) { - if (Slic3r::Geometry::directions_parallel(angles[i], angles[i-1], min_resolution)) { - angles.erase(angles.begin() + i); - --i; - } - } - - if (Slic3r::Geometry::directions_parallel(angles.front(), angles.back(), min_resolution)) - angles.pop_back(); - - return angles; -} - - - -} \ No newline at end of file diff --git a/src/libslic3r/InternalBridgeDetector.hpp b/src/libslic3r/InternalBridgeDetector.hpp deleted file mode 100644 index b63e42bfee..0000000000 --- a/src/libslic3r/InternalBridgeDetector.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef slic3r_InternalBridgeDetector_hpp_ -#define slic3r_InternalBridgeDetector_hpp_ - -#include "libslic3r.h" -#include "ExPolygon.hpp" -#include - -namespace Slic3r { - -// BBS: InternalBridgeDetector is used to detect bridge angle for internal bridge. -// this step may enlarge internal bridge area for a little(only occupy sparse infill area) for better anchoring -class InternalBridgeDetector { -public: - // input: all fill area in LayerRegion without overlap with perimeter. - ExPolygons fill_no_overlap; - // input: internal bridge infill area. - ExPolygons internal_bridge_infill; - // input: scaled extrusion width of the infill. - coord_t spacing; - // output: the final optimal angle. - double angle = -1.; - - InternalBridgeDetector(ExPolygon _internal_bridge, const ExPolygons &_fill_no_overlap, coord_t _spacing); - bool detect_angle(); - -private: - void initialize(); - std::vector bridge_direction_candidates() const; - - struct InternalBridgeDirection { - InternalBridgeDirection(double a = -1.) : angle(a), coverage(0.), max_length(0.) {} - // the best direction is the one causing most lines to be bridged and the span is short - bool operator<(const InternalBridgeDirection &other) const { - double delta = this->coverage - other.coverage; - if (delta > 0.001) - return true; - else if (delta < -0.001) - return false; - else - // coverage is almost same, then compare span - return this->max_length < other.max_length; - }; - double angle; - double coverage; - double max_length; - }; - - double resolution = PI/36.0; - ExPolygons m_anchor_regions; -}; - -} - -#endif diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index a72d427692..84fb520f2e 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -182,6 +182,9 @@ public: // Phony version of make_fills() without parameters for Perl integration only. void make_fills() { this->make_fills(nullptr, nullptr); } void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator = nullptr); + Polylines generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree *adaptive_fill_octree, + FillAdaptive::Octree *support_fill_octree, + FillLightning::Generator* lightning_generator) const; void make_ironing(); void export_region_slices_to_svg(const char *path) const; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 7568a015f0..08ccb659f8 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -752,7 +752,7 @@ static std::vector s_Preset_print_options { "gcode_add_line_number", "enable_arc_fitting", "infill_combination", /*"adaptive_layer_height",*/ "support_bottom_interface_spacing", "enable_overhang_speed", "slowdown_for_curled_perimeters", "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed", "initial_layer_infill_speed", "only_one_wall_top", - "timelapse_type", "internal_bridge_support_thickness", + "timelapse_type", "wall_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", "wall_distribution_count", "min_feature_size", "min_bead_width", "post_process", "small_perimeter_speed", "small_perimeter_threshold","bridge_angle", "filter_out_gap_fill", "travel_acceleration","inner_wall_acceleration", "min_width_top_surface", diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 00ad8de36a..102f93ed0e 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_Print_hpp_ #define slic3r_Print_hpp_ +#include "Fill/FillAdaptive.hpp" +#include "Fill/FillLightning.hpp" #include "PrintBase.hpp" #include "BoundingBox.hpp" @@ -487,7 +489,8 @@ private: void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); - std::pair prepare_adaptive_infill_data(); + std::pair prepare_adaptive_infill_data( + const std::vector>& surfaces_w_bottom_z) const; FillLightning::GeneratorPtr prepare_lightning_infill_data(); // BBS @@ -517,6 +520,10 @@ private: // this is set to true when LayerRegion->slices is split in top/internal/bottom // so that next call to make_perimeters() performs a union() before computing loops bool m_typed_slices = false; + + std::pair m_adaptive_fill_octrees; + FillLightning::GeneratorPtr m_lightning_generator; + std::vector < VolumeSlices > firstLayerObjSliceByVolume; std::vector firstLayerObjSliceByGroups; // BBS: per object skirt diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 7f24f32175..026f28786d 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1097,18 +1097,6 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(true)); - def = this->add("internal_bridge_support_thickness", coFloat); - def->label = L("Internal bridge support thickness"); - def->category = L("Strength"); - def->tooltip = L("If enabled, support loops will be generated under the contours of internal bridges." - "These support loops could prevent internal bridges from extruding over the air and improve the top surface quality, especially when the sparse infill density is low." - "This value determines the thickness of the support loops. 0 means disable this feature"); - def->sidetext = L("mm"); - def->min = 0; - def->max = 2; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0)); - auto def_top_fill_pattern = def = this->add("top_surface_pattern", coEnum); def->label = L("Top surface pattern"); def->category = L("Strength"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 57b82efd47..b33ae8aa6d 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -711,7 +711,6 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionBool, detect_narrow_internal_solid_infill)) // ((ConfigOptionBool, adaptive_layer_height)) ((ConfigOptionFloat, support_bottom_interface_spacing)) - ((ConfigOptionFloat, internal_bridge_support_thickness)) ((ConfigOptionEnum, wall_generator)) ((ConfigOptionPercent, wall_transition_length)) ((ConfigOptionPercent, wall_transition_filter_deviation)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 0d473bdc54..00cf809d9c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -28,10 +28,12 @@ #include "Fill/FillAdaptive.hpp" #include "Fill/FillLightning.hpp" #include "Format/STL.hpp" -#include "InternalBridgeDetector.hpp" #include "TreeSupport.hpp" #include +#include +#include +#include #include #include @@ -47,6 +49,20 @@ using namespace std::literals; //! return same string #define L(s) Slic3r::I18N::translate(s) +// #define PRINT_OBJECT_TIMING + +#ifdef PRINT_OBJECT_TIMING + // time limit for one ClipperLib operation (union / diff / offset), in ms + #define PRINT_OBJECT_TIME_LIMIT_DEFAULT 50 + #include + #include "Timer.hpp" + #define PRINT_OBJECT_TIME_LIMIT_SECONDS(limit) Timing::TimeLimitAlarm time_limit_alarm(uint64_t(limit) * 1000000000l, BOOST_CURRENT_FUNCTION) + #define PRINT_OBJECT_TIME_LIMIT_MILLIS(limit) Timing::TimeLimitAlarm time_limit_alarm(uint64_t(limit) * 1000000l, BOOST_CURRENT_FUNCTION) +#else + #define PRINT_OBJECT_TIME_LIMIT_SECONDS(limit) do {} while(false) + #define PRINT_OBJECT_TIME_LIMIT_MILLIS(limit) do {} while(false) +#endif // PRINT_OBJECT_TIMING + #ifdef SLIC3R_DEBUG_SLICE_PROCESSING #define SLIC3R_DEBUG #endif @@ -377,18 +393,16 @@ void PrintObject::infill() if (this->set_started(posInfill)) { m_print->set_status(35, L("Generating infill toolpath")); - - auto [adaptive_fill_octree, support_fill_octree] = this->prepare_adaptive_infill_data(); - auto lightning_generator = this->prepare_lightning_infill_data(); - + const auto& adaptive_fill_octree = this->m_adaptive_fill_octrees.first; + const auto& support_fill_octree = this->m_adaptive_fill_octrees.second; BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), - [this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree, &lightning_generator](const tbb::blocked_range& range) { + [this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](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(adaptive_fill_octree.get(), support_fill_octree.get(), lightning_generator.get()); + m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), this->m_lightning_generator.get()); } } ); @@ -585,7 +599,8 @@ void PrintObject::simplify_extrusion_path() } } -std::pair PrintObject::prepare_adaptive_infill_data() +std::pair PrintObject::prepare_adaptive_infill_data( + const std::vector> &surfaces_w_bottom_z) const { using namespace FillAdaptive; @@ -599,21 +614,19 @@ std::pair PrintObject::prepare its_transform(mesh, to_octree * this->trafo_centered(), true); // Triangulate internal bridging surfaces. - std::vector> overhangs(this->layers().size()); - tbb::parallel_for( - tbb::blocked_range(0, int(m_layers.size()) - 1), - [this, &to_octree, &overhangs](const tbb::blocked_range &range) { - std::vector &out = overhangs[range.begin()]; - for (int idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { + std::vector> overhangs(std::max(surfaces_w_bottom_z.size(), size_t(1))); + // ^ make sure vector is not empty, even with no briding surfaces we still want to build the adaptive trees later, some continue normally + tbb::parallel_for(tbb::blocked_range(0, surfaces_w_bottom_z.size()), + [this, &to_octree, &overhangs, &surfaces_w_bottom_z](const tbb::blocked_range &range) { + PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); + for (int surface_idx = range.begin(); surface_idx < range.end(); ++surface_idx) { + std::vector &out = overhangs[surface_idx]; 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) - if (surface.surface_type == stInternalBridge) - append(out, triangulate_expolygon_3d(surface.expolygon, layer->bottom_z())); + append(out, triangulate_expolygon_3d(surfaces_w_bottom_z[surface_idx].first->expolygon, + surfaces_w_bottom_z[surface_idx].second)); + for (Vec3d &p : out) + p = (to_octree * p).eval(); } - for (Vec3d &p : out) - p = (to_octree * p).eval(); }); // and gather them. for (size_t i = 1; i < overhangs.size(); ++ i) @@ -892,7 +905,6 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "ensure_vertical_shell_thickness" || opt_key == "bridge_angle" //BBS - || opt_key == "internal_bridge_support_thickness" || opt_key == "bridge_density") { steps.emplace_back(posPrepareInfill); } else if ( @@ -1384,7 +1396,7 @@ void PrintObject::discover_vertical_shells() tbb::parallel_for( tbb::blocked_range(0, num_layers, grain_size), [this, &cache_top_botom_regions](const tbb::blocked_range& range) { - const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; + const std::initializer_list surfaces_bottom { stBottom, stBottomBridge }; const size_t num_regions = this->num_printing_regions(); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { m_print->throw_if_canceled(); @@ -1404,8 +1416,8 @@ void PrintObject::discover_vertical_shells() 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), min_perimeter_infill_spacing)); + append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), 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; @@ -1467,7 +1479,7 @@ void PrintObject::discover_vertical_shells() tbb::parallel_for( tbb::blocked_range(0, num_layers, grain_size), [this, region_id, &cache_top_botom_regions](const tbb::blocked_range& range) { - const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; + const std::initializer_list surfaces_bottom { stBottom, stBottomBridge }; for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { m_print->throw_if_canceled(); Layer &layer = *m_layers[idx_layer]; @@ -1478,8 +1490,8 @@ void PrintObject::discover_vertical_shells() 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), min_perimeter_infill_spacing); + append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), 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) @@ -1646,8 +1658,7 @@ void PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // 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({ stInternal, stInternalVoid, stInternalSolid })); shell = intersection(shell, polygonsInternal, ApplySafetyOffset::Yes); polygons_append(shell, diff(polygonsInternal, holes)); if (shell.empty()) @@ -1713,8 +1724,7 @@ void PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // 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.keep_types({ stTop, stBottom, stBottomBridge }); layerm->fill_surfaces.append(new_internal, stInternal); layerm->fill_surfaces.append(new_internal_void, stInternalVoid); layerm->fill_surfaces.append(new_internal_solid, stInternalSolid); @@ -1737,186 +1747,855 @@ void PrintObject::discover_vertical_shells() // PROFILE_OUTPUT(debug_out_path("discover_vertical_shells-profile.txt").c_str()); } +// #define DEBUG_BRIDGE_OVER_INFILL +#ifdef DEBUG_BRIDGE_OVER_INFILL +template void debug_draw(std::string name, const T& a, const T& b, const T& c, const T& d) +{ + std::vector colors = {"red", "green", "blue", "orange"}; + BoundingBox bbox = get_extents(a); + bbox.merge(get_extents(b)); + bbox.merge(get_extents(c)); + bbox.merge(get_extents(d)); + bbox.offset(scale_(1.)); + ::Slic3r::SVG svg(debug_out_path(name.c_str()).c_str(), bbox); + svg.draw(a, colors[0], scale_(0.3)); + svg.draw(b, colors[1], scale_(0.23)); + svg.draw(c, colors[2], scale_(0.16)); + svg.draw(d, colors[3], scale_(0.10)); + svg.Close(); +} +#endif + // This method applies bridge flow to the first internal solid layer above sparse infill. void PrintObject::bridge_over_infill() { - BOOST_LOG_TRIVIAL(info) << "Bridge over infill..." << log_memory_info(); + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Start" << log_memory_info(); - std::vector sparse_infill_regions; - for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) - if (const PrintRegion ®ion = this->printing_region(region_id); region.config().sparse_infill_density.value < 100) - sparse_infill_regions.emplace_back(region_id); - if (this->layer_count() < 2 || sparse_infill_regions.empty()) - return; + struct CandidateSurface + { + CandidateSurface(const Surface *original_surface, + int layer_index, + Polygons new_polys, + const LayerRegion *region, + double bridge_angle) + : original_surface(original_surface) + , layer_index(layer_index) + , new_polys(new_polys) + , region(region) + , bridge_angle(bridge_angle) + {} + const Surface *original_surface; + int layer_index; + Polygons new_polys; + const LayerRegion *region; + double bridge_angle; + }; - // Collect sum of all internal (sparse infill) regions, because - // 1) layerm->fill_surfaces.will be modified in parallel. - // 2) the parallel loop works on a sum of surfaces over regions anyways, thus collecting the sparse infill surfaces - // up front is an optimization. - std::vector internals; - internals.reserve(this->layer_count()); - for (Layer *layer : m_layers) { - Polygons sum; - for (const LayerRegion *layerm : layer->m_regions) - layerm->fill_surfaces.filter_by_type(stInternal, &sum); - internals.emplace_back(std::move(sum)); - } + std::map> surfaces_by_layer; - // Process all regions and layers in parallel. - tbb::parallel_for(tbb::blocked_range(0, sparse_infill_regions.size() * (this->layer_count() - 1), sparse_infill_regions.size()), - [this, &sparse_infill_regions, &internals] - (const tbb::blocked_range &range) { - for (size_t task_id = range.begin(); task_id != range.end(); ++ task_id) { - const size_t layer_id = (task_id / sparse_infill_regions.size()) + 1; - const size_t region_id = sparse_infill_regions[task_id % sparse_infill_regions.size()]; - Layer *layer = this->get_layer(layer_id); - LayerRegion *layerm = layer->m_regions[region_id]; - const PrintObjectConfig& object_config = layer->object()->config(); - //BBS: enable thick bridge for internal bridge only - Flow bridge_flow = layerm->bridging_flow(frSolidInfill, true); - - // Extract the stInternalSolid surfaces that might be transformed into bridges. - ExPolygons internal_solid; - layerm->fill_surfaces.remove_type(stInternalSolid, &internal_solid); - if (internal_solid.empty()) - // No internal solid -> no new bridges for this layer region. - continue; - - // check whether the lower area is deep enough for absorbing the extra flow - // (for obvious physical reasons but also for preventing the bridge extrudates - // from overflowing in 3D preview) - ExPolygons to_bridge; - { - Polygons to_bridge_pp = to_polygons(internal_solid); - // Iterate through lower layers spanned by bridge_flow. - double bottom_z = layer->print_z - bridge_flow.height() - EPSILON; - for (auto i = int(layer_id) - 1; i >= 0; -- i) { - // Stop iterating if layer is lower than bottom_z. - if (m_layers[i]->print_z < bottom_z) - break; - // Intersect lower sparse infills with the candidate solid surfaces. - to_bridge_pp = intersection(to_bridge_pp, internals[i]); - } - - // BBS: expand to make avoid gap between bridge and inner wall - to_bridge_pp = expand(to_bridge_pp, bridge_flow.scaled_width()); - to_bridge_pp = intersection(to_bridge_pp, to_polygons(internal_solid)); - - // there's no point in bridging too thin/short regions - //FIXME Vojtech: The offset2 function is not a geometric offset, - // therefore it may create 1) gaps, and 2) sharp corners, which are outside the original contour. - // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer. - { - float min_width = float(bridge_flow.scaled_width()) * 3.f; - to_bridge_pp = opening(to_bridge_pp, min_width); - } - - 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))); + // SECTION to gather and filter surfaces for expanding, and then cluster them by layer + { + tbb::concurrent_vector candidate_surfaces; + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = static_cast(this), + &candidate_surfaces](tbb::blocked_range r) { + PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + const Layer *layer = po->get_layer(lidx); + if (layer->lower_layer == nullptr) { continue; } + double spacing = layer->regions().front()->flow(frSolidInfill).scaled_spacing(); + // unsupported area will serve as a filter for polygons worth bridging. + Polygons unsupported_area; + Polygons lower_layer_solids; + for (const LayerRegion *region : layer->lower_layer->regions()) { + Polygons fill_polys = to_polygons(region->fill_expolygons); + // initially consider the whole layer unsupported, but also gather solid layers to later cut off supported parts + unsupported_area.insert(unsupported_area.end(), fill_polys.begin(), fill_polys.end()); + for (const Surface &surface : region->fill_surfaces) { + if (surface.surface_type != stInternal || region->region().config().sparse_infill_density.value == 100) { + Polygons p = to_polygons(surface.expolygon); + lower_layer_solids.insert(lower_layer_solids.end(), p.begin(), p.end()); + } + } + } + unsupported_area = closing(unsupported_area, float(SCALED_EPSILON)); + // By expanding the lower layer solids, we avoid making bridges from the tiny internal overhangs that are (very likely) supported by previous layer solids + // NOTE that we cannot filter out polygons worth bridging by their area, because sometimes there is a very small internal island that will grow into large hole + lower_layer_solids = shrink(lower_layer_solids, 1 * spacing); // first remove thin regions that will not support anything + lower_layer_solids = expand(lower_layer_solids, (1 + 3) * spacing); // then expand back (opening), and further for parts supported by internal solids + // By shrinking the unsupported area, we avoid making bridges from narrow ensuring region along perimeters. + unsupported_area = shrink(unsupported_area, 3 * spacing); + unsupported_area = diff(unsupported_area, lower_layer_solids); - // convert into ExPolygons - to_bridge = union_ex(to_bridge_pp); + for (const LayerRegion *region : layer->regions()) { + SurfacesPtr region_internal_solids = region->fill_surfaces.filter_by_type(stInternalSolid); + for (const Surface *s : region_internal_solids) { + Polygons unsupported = intersection(to_polygons(s->expolygon), unsupported_area); + // The following flag marks those surfaces, which overlap with unuspported area, but at least part of them is supported. + // These regions can be filtered by area, because they for sure are touching solids on lower layers, and it does not make sense to bridge their tiny overhangs + bool partially_supported = area(unsupported) < area(to_polygons(s->expolygon)) - EPSILON; + if (!unsupported.empty() && (!partially_supported || area(unsupported) > 3 * 3 * spacing * spacing)) { + Polygons worth_bridging = intersection(to_polygons(s->expolygon), expand(unsupported, 4 * spacing)); + // after we extracted the part worth briding, we go over the leftovers and merge the tiny ones back, to not brake the surface too much + for (const Polygon& p : diff(to_polygons(s->expolygon), expand(worth_bridging, spacing))) { + double area = p.area(); + if (area < spacing * scale_(12.0) && area > spacing * spacing) { + worth_bridging.push_back(p); + } + } + worth_bridging = intersection(closing(worth_bridging, float(SCALED_EPSILON)), s->expolygon); + candidate_surfaces.push_back(CandidateSurface(s, lidx, worth_bridging, region, 0)); + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw(std::to_string(lidx) + "_candidate_surface_" + std::to_string(area(s->expolygon)), + to_lines(region->layer()->lslices), to_lines(s->expolygon), to_lines(worth_bridging), + to_lines(unsupported_area)); +#endif +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw(std::to_string(lidx) + "_candidate_processing_" + std::to_string(area(unsupported)), + to_lines(unsupported), to_lines(intersection(to_polygons(s->expolygon), expand(unsupported, 5 * spacing))), + to_lines(diff(to_polygons(s->expolygon), expand(worth_bridging, spacing))), + to_lines(unsupported_area)); +#endif + } + } + } } + }); - #ifdef SLIC3R_DEBUG - printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id()); - #endif + for (const CandidateSurface &c : candidate_surfaces) { + surfaces_by_layer[c.layer_index].push_back(c); + } + } - // compute the remaning internal solid surfaces as difference - ExPolygons not_to_bridge = diff_ex(internal_solid, to_bridge, ApplySafetyOffset::Yes); - 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, ex)); - // BBS: detect angle for internal bridge infill - InternalBridgeDetector ibd(ex, layerm->fill_no_overlap_expolygons, bridge_flow.scaled_spacing()); - if (ibd.detect_angle()) { - (layerm->fill_surfaces.surfaces.end() - 1)->bridge_angle = ibd.angle; + // LIGHTNING INFILL SECTION - If lightning infill is used somewhere, we check the areas that are going to be bridges, and those that rely on the + // lightning infill under them get expanded. This somewhat helps to ensure that most of the extrusions are anchored to the lightning infill at the ends. + // It requires modifying this instance of print object in a specific way, so that we do not invalidate the pointers in our surfaces_by_layer structure. + bool has_lightning_infill = false; + for (size_t i = 0; i < this->num_printing_regions(); i++) { + if (this->printing_region(i).config().sparse_infill_pattern == ipLightning) { + has_lightning_infill = true; + break; + } + } + if (has_lightning_infill) { + // Prepare backup data for the Layer Region infills. Before modfiyng the layer region, we backup its fill surfaces by moving! them into this map. + // then a copy is created, modifiyed and passed to lightning infill generator. After generator is created, we restore the original state of the fills + // again by moving the data from this map back to the layer regions. This ensures that pointers to surfaces stay valid. + std::map> backup_surfaces; + for (size_t lidx = 0; lidx < this->layer_count(); lidx++) { + backup_surfaces[lidx] = {}; + } + + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, &backup_surfaces, + &surfaces_by_layer](tbb::blocked_range r) { + PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end()) + continue; + + Layer *layer = po->get_layer(lidx); + const Layer *lower_layer = layer->lower_layer; + if (lower_layer == nullptr) + continue; + + Polygons lightning_fill; + for (const LayerRegion *region : lower_layer->regions()) { + if (region->region().config().sparse_infill_pattern == ipLightning) { + Polygons lf = to_polygons(region->fill_surfaces.filter_by_type(stInternal)); + lightning_fill.insert(lightning_fill.end(), lf.begin(), lf.end()); + } + } + + if (lightning_fill.empty()) + continue; + + for (LayerRegion *region : layer->regions()) { + backup_surfaces[lidx][region] = std::move( + region->fill_surfaces); // Make backup copy by move!! so that pointers in candidate surfaces stay valid + // Copy the surfaces back, this will make copy, but we will later discard it anyway + region->fill_surfaces = backup_surfaces[lidx][region]; + } + + for (LayerRegion *region : layer->regions()) { + ExPolygons sparse_infill = to_expolygons(region->fill_surfaces.filter_by_type(stInternal)); + ExPolygons solid_infill = to_expolygons(region->fill_surfaces.filter_by_type(stInternalSolid)); + + if (sparse_infill.empty()) { + break; + } + for (const auto &surface : surfaces_by_layer[lidx]) { + if (surface.region != region) + continue; + ExPolygons expansion = intersection_ex(sparse_infill, expand(surface.new_polys, scaled(3.0))); + solid_infill.insert(solid_infill.end(), expansion.begin(), expansion.end()); + } + + solid_infill = union_safety_offset_ex(solid_infill); + sparse_infill = diff_ex(sparse_infill, solid_infill); + + region->fill_surfaces.remove_types({stInternalSolid, stInternal}); + for (const ExPolygon &ep : solid_infill) { + region->fill_surfaces.surfaces.emplace_back(stInternalSolid, ep); + } + for (const ExPolygon &ep : sparse_infill) { + region->fill_surfaces.surfaces.emplace_back(stInternal, ep); + } + } + } + }); + + // Use the modified surfaces to generate expanded lightning anchors + this->m_lightning_generator = this->prepare_lightning_infill_data(); + + // And now restore carefully the original surfaces, again using move to avoid reallocation and preserving the validity of the + // pointers in surface candidates + for (size_t lidx = 0; lidx < this->layer_count(); lidx++) { + Layer *layer = this->get_layer(lidx); + for (LayerRegion *region : layer->regions()) { + if (backup_surfaces[lidx].find(region) != backup_surfaces[lidx].end()) { + region->fill_surfaces = std::move(backup_surfaces[lidx][region]); + } + } + } + } + + std::map infill_lines; + // SECTION to generate infill polylines + { + std::vector> surfaces_w_bottom_z; + for (const auto &pair : surfaces_by_layer) { + for (const CandidateSurface &c : pair.second) { + surfaces_w_bottom_z.emplace_back(c.original_surface, c.region->m_layer->bottom_z()); + } + } + + this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_bottom_z); + + std::vector layers_to_generate_infill; + for (const auto &pair : surfaces_by_layer) { + assert(pair.first > 0); + infill_lines[pair.first - 1] = {}; + layers_to_generate_infill.push_back(pair.first - 1); + } + + tbb::parallel_for(tbb::blocked_range(0, layers_to_generate_infill.size()), [po = static_cast(this), + &layers_to_generate_infill, + &infill_lines](tbb::blocked_range r) { + PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); + 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.get(), + po->m_adaptive_fill_octrees.second.get(), + po->m_lightning_generator.get()); + } + }); +#ifdef DEBUG_BRIDGE_OVER_INFILL + for (const auto &il : infill_lines) { + debug_draw(std::to_string(il.first) + "_infill_lines", to_lines(get_layer(il.first)->lslices), to_lines(il.second), {}, {}); + } +#endif + } + + // cluster layers by depth needed for thick bridges. Each cluster is to be processed by single thread sequentially, so that bridges cannot appear one on another + std::vector> clustered_layers_for_threads; + float target_flow_height_factor = 0.9f; + { + std::vector layers_with_candidates; + std::map layer_area_covered_by_candidates; + for (const auto& pair : surfaces_by_layer) { + layers_with_candidates.push_back(pair.first); + layer_area_covered_by_candidates[pair.first] = {}; + } + + // prepare inflated filter for each candidate on each layer. layers will be put into single thread cluster if they are close to each other (z-axis-wise) + // and if the inflated AABB polygons overlap somewhere + tbb::parallel_for(tbb::blocked_range(0, layers_with_candidates.size()), [&layers_with_candidates, &surfaces_by_layer, + &layer_area_covered_by_candidates]( + tbb::blocked_range r) { + PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); + for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) { + size_t lidx = layers_with_candidates[job_idx]; + for (const auto &candidate : surfaces_by_layer.at(lidx)) { + Polygon candiate_inflated_aabb = get_extents(candidate.new_polys).inflated(scale_(7)).polygon(); + layer_area_covered_by_candidates.at(lidx) = union_(layer_area_covered_by_candidates.at(lidx), + Polygons{candiate_inflated_aabb}); + } + } + }); + + // note: surfaces_by_layer is ordered map + for (auto pair : surfaces_by_layer) { + if (clustered_layers_for_threads.empty() || + this->get_layer(clustered_layers_for_threads.back().back())->print_z < + this->get_layer(pair.first)->print_z - + this->get_layer(pair.first)->regions()[0]->bridging_flow(frSolidInfill, true).height() * target_flow_height_factor - + EPSILON || + intersection(layer_area_covered_by_candidates[clustered_layers_for_threads.back().back()], + layer_area_covered_by_candidates[pair.first]) + .empty()) { + clustered_layers_for_threads.push_back({pair.first}); + } else { + clustered_layers_for_threads.back().push_back(pair.first); + } + } + +#ifdef DEBUG_BRIDGE_OVER_INFILL + std::cout << "BRIDGE OVER INFILL CLUSTERED LAYERS FOR SINGLE THREAD" << std::endl; + for (auto cluster : clustered_layers_for_threads) { + std::cout << "CLUSTER: "; + for (auto l : cluster) { + std::cout << l << " "; + } + std::cout << std::endl; + } +#endif + } + + // LAMBDA to gather areas with sparse infill deep enough that we can fit thick bridges there. + auto gather_areas_w_depth = [target_flow_height_factor](const PrintObject *po, int lidx, float target_flow_height) { + // Gather layers sparse infill areas, to depth defined by used bridge flow + ExPolygons layers_sparse_infill{}; + ExPolygons not_sparse_infill{}; + double bottom_z = po->get_layer(lidx)->print_z - target_flow_height * target_flow_height_factor - EPSILON; + for (int i = int(lidx) - 1; i >= 0; --i) { + // Stop iterating if layer is lower than bottom_z and at least one iteration was made + const Layer *layer = po->get_layer(i); + if (layer->print_z < bottom_z && i < int(lidx) - 1) + break; + + for (const LayerRegion *region : layer->regions()) { + bool has_low_density = region->region().config().sparse_infill_density.value < 100; + for (const Surface &surface : region->fill_surfaces) { + if ((surface.surface_type == stInternal && has_low_density) || surface.surface_type == stInternalVoid ) { + layers_sparse_infill.push_back(surface.expolygon); + } else { + not_sparse_infill.push_back(surface.expolygon); + } + } + } + } + layers_sparse_infill = union_ex(layers_sparse_infill); + layers_sparse_infill = closing_ex(layers_sparse_infill, float(SCALED_EPSILON)); + not_sparse_infill = union_ex(not_sparse_infill); + not_sparse_infill = closing_ex(not_sparse_infill, float(SCALED_EPSILON)); + return diff(layers_sparse_infill, not_sparse_infill); + }; + + // LAMBDA do determine optimal bridging angle + auto determine_bridging_angle = [](const Polygons &bridged_area, const Lines &anchors, InfillPattern dominant_pattern) { + AABBTreeLines::LinesDistancer lines_tree(anchors); + + std::map counted_directions; + for (const Polygon &p : bridged_area) { + double acc_distance = 0; + for (int point_idx = 0; point_idx < int(p.points.size()) - 1; ++point_idx) { + Vec2d start = p.points[point_idx].cast(); + Vec2d next = p.points[point_idx + 1].cast(); + Vec2d v = next - start; // vector from next to current + double dist_to_next = v.norm(); + acc_distance += dist_to_next; + if (acc_distance > scaled(2.0)) { + acc_distance = 0.0; + v.normalize(); + int lines_count = int(std::ceil(dist_to_next / scaled(2.0))); + float step_size = dist_to_next / lines_count; + for (int i = 0; i < lines_count; ++i) { + Point a = (start + v * (i * step_size)).cast(); + auto [distance, index, p] = lines_tree.distance_from_lines_extra(a); + double angle = lines_tree.get_line(index).orientation(); + if (angle > PI) { + angle -= PI; + } + angle += PI * 0.5; + counted_directions[angle]++; + } + } + } + } + + std::pair best_dir{0, 0}; + // sliding window accumulation + for (const auto &dir : counted_directions) { + int score_acc = 0; + double dir_acc = 0; + double window_start_angle = dir.first - PI * 0.1; + double window_end_angle = dir.first + PI * 0.1; + for (auto dirs_window = counted_directions.lower_bound(window_start_angle); + dirs_window != counted_directions.upper_bound(window_end_angle); dirs_window++) { + dir_acc += dirs_window->first * dirs_window->second; + score_acc += dirs_window->second; + } + // current span of directions is 0.5 PI to 1.5 PI (due to the aproach.). Edge values should also account for the + // opposite direction. + if (window_start_angle < 0.5 * PI) { + for (auto dirs_window = counted_directions.lower_bound(1.5 * PI - (0.5 * PI - window_start_angle)); + dirs_window != counted_directions.end(); dirs_window++) { + dir_acc += (dirs_window->first - PI) * dirs_window->second; + score_acc += dirs_window->second; + } + } + if (window_start_angle > 1.5 * PI) { + for (auto dirs_window = counted_directions.begin(); + dirs_window != counted_directions.upper_bound(window_start_angle - 1.5 * PI); dirs_window++) { + dir_acc += (dirs_window->first + PI) * dirs_window->second; + score_acc += dirs_window->second; } } - for (ExPolygon &ex : not_to_bridge) - layerm->fill_surfaces.surfaces.push_back(Surface(stInternalSolid, ex)); + if (score_acc > best_dir.second) { + best_dir = {dir_acc / score_acc, score_acc}; + } + } + double bridging_angle = best_dir.first; + if (bridging_angle == 0) { + bridging_angle = 0.001; + } + switch (dominant_pattern) { + case ipHilbertCurve: bridging_angle += 0.25 * PI; break; + case ipOctagramSpiral: bridging_angle += (1.0 / 16.0) * PI; break; + default: break; + } - //BBS: modify stInternal to be stInternalWithLoop to give better support to internal bridge - if (!to_bridge.empty()){ - float internal_loop_thickness = object_config.internal_bridge_support_thickness.value; - double bottom_z = layer->print_z - layer->height - internal_loop_thickness + EPSILON; - //BBS: lighting infill doesn't support this feature. Don't need to add loop when infill density is high than 50% - if (layerm->region().config().sparse_infill_pattern != InfillPattern::ipLightning && layerm->region().config().sparse_infill_density.value < 50) - for (int i = layer_id - 1; i >= 0; --i) { - const Layer* lower_layer = m_layers[i]; + return bridging_angle; + }; - if (lower_layer->print_z < bottom_z) break; + // LAMBDA that will fill given polygons with lines, exapand the lines to the nearest anchor, and reconstruct polygons from the newly + // generated lines + auto construct_anchored_polygon = [](Polygons bridged_area, Lines anchors, const Flow &bridging_flow, double bridging_angle) { + auto lines_rotate = [](Lines &lines, double cos_angle, double sin_angle) { + for (Line &l : lines) { + double ax = double(l.a.x()); + double ay = double(l.a.y()); + l.a.x() = coord_t(round(cos_angle * ax - sin_angle * ay)); + l.a.y() = coord_t(round(cos_angle * ay + sin_angle * ax)); + double bx = double(l.b.x()); + double by = double(l.b.y()); + l.b.x() = coord_t(round(cos_angle * bx - sin_angle * by)); + l.b.y() = coord_t(round(cos_angle * by + sin_angle * bx)); + } + }; - for (LayerRegion* lower_layerm : lower_layer->m_regions) { - Polygons lower_internal; - lower_layerm->fill_surfaces.filter_by_type(stInternal, &lower_internal); - ExPolygons internal_with_loop = intersection_ex(lower_internal, to_bridge); - ExPolygons internal = diff_ex(lower_internal, to_bridge); - if (internal_with_loop.empty()) { - //BBS: don't need to do anything - } - else if (internal.empty()) { - lower_layerm->fill_surfaces.change_to_new_type(stInternal, stInternalWithLoop); - } - else { - lower_layerm->fill_surfaces.remove_type(stInternal); - for (ExPolygon& ex : internal_with_loop) - lower_layerm->fill_surfaces.surfaces.push_back(Surface(stInternalWithLoop, ex)); - for (ExPolygon& ex : internal) - lower_layerm->fill_surfaces.surfaces.push_back(Surface(stInternal, ex)); - } - } - } + auto segments_overlap = [](coord_t alow, coord_t ahigh, coord_t blow, coord_t bhigh) { + return (alow >= blow && alow <= bhigh) || (ahigh >= blow && ahigh <= bhigh) || (blow >= alow && blow <= ahigh) || + (bhigh >= alow && bhigh <= ahigh); + }; + + Polygons expanded_bridged_area{}; + double aligning_angle = -bridging_angle + PI * 0.5; + { + polygons_rotate(bridged_area, aligning_angle); + lines_rotate(anchors, cos(aligning_angle), sin(aligning_angle)); + BoundingBox bb_x = get_extents(bridged_area); + BoundingBox bb_y = get_extents(anchors); + + const size_t n_vlines = (bb_x.max.x() - bb_x.min.x() + bridging_flow.scaled_spacing() - 1) / bridging_flow.scaled_spacing(); + std::vector vertical_lines(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + // Orca: Make sure the line is placed in the middle of the extrusion + // coord_t x = bb_x.min.x() + i * bridging_flow.scaled_spacing(); + coord_t x = bb_x.min.x() + (i + 0.5) * bridging_flow.scaled_spacing(); + coord_t y_min = bb_y.min.y() - bridging_flow.scaled_spacing(); + coord_t y_max = bb_y.max.y() + bridging_flow.scaled_spacing(); + vertical_lines[i].a = Point{x, y_min}; + vertical_lines[i].b = Point{x, y_max}; } - /* - # exclude infill from the layers below if needed - # see discussion at https://github.com/alexrj/Slic3r/issues/240 - # Update: do not exclude any infill. Sparse infill is able to absorb the excess material. - if (0) { - my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height; - for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) { - Slic3r::debugf " skipping infill below those areas at layer %d\n", $i; - foreach my $lower_layerm (@{$self->get_layer($i)->regions}) { - my @new_surfaces = (); - # subtract the area from all types of surfaces - foreach my $group (@{$lower_layerm->fill_surfaces->group}) { - push @new_surfaces, map $group->[0]->clone(expolygon => $_), - @{diff_ex( - [ map $_->p, @$group ], - [ map @$_, @$to_bridge ], - )}; - push @new_surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => stInternalVoid, - ), @{intersection_ex( - [ map $_->p, @$group ], - [ map @$_, @$to_bridge ], - )}; - } - $lower_layerm->fill_surfaces->clear; - $lower_layerm->fill_surfaces->append($_) for @new_surfaces; + auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer{std::move(anchors)}; + auto bridged_area_tree = AABBTreeLines::LinesDistancer{to_lines(bridged_area)}; + + std::vector> polygon_sections(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + auto area_intersections = bridged_area_tree.intersections_with_line(vertical_lines[i]); + for (int intersection_idx = 0; intersection_idx < int(area_intersections.size()) - 1; intersection_idx++) { + if (bridged_area_tree.outside( + (area_intersections[intersection_idx].first + area_intersections[intersection_idx + 1].first) / 2) < 0) { + polygon_sections[i].emplace_back(area_intersections[intersection_idx].first, + area_intersections[intersection_idx + 1].first); + } + } + auto anchors_intersections = anchors_and_walls_tree.intersections_with_line(vertical_lines[i]); + + for (Line §ion : polygon_sections[i]) { + auto maybe_below_anchor = std::upper_bound(anchors_intersections.rbegin(), anchors_intersections.rend(), section.a, + [](const Point &a, const std::pair &b) { + return a.y() > b.first.y(); + }); + if (maybe_below_anchor != anchors_intersections.rend()) { + section.a = maybe_below_anchor->first; + section.a.y() -= bridging_flow.scaled_width() * (0.5 + 0.5); } - $excess -= $self->get_layer($i)->height; + auto maybe_upper_anchor = std::upper_bound(anchors_intersections.begin(), anchors_intersections.end(), section.b, + [](const Point &a, const std::pair &b) { + return a.y() < b.first.y(); + }); + if (maybe_upper_anchor != anchors_intersections.end()) { + section.b = maybe_upper_anchor->first; + section.b.y() += bridging_flow.scaled_width() * (0.5 + 0.5); + } + } + + for (int section_idx = 0; section_idx < int(polygon_sections[i].size()) - 1; section_idx++) { + Line §ion_a = polygon_sections[i][section_idx]; + Line §ion_b = polygon_sections[i][section_idx + 1]; + if (segments_overlap(section_a.a.y(), section_a.b.y(), section_b.a.y(), section_b.b.y())) { + section_b.a = section_a.a.y() < section_b.a.y() ? section_a.a : section_b.a; + section_b.b = section_a.b.y() < section_b.b.y() ? section_b.b : section_a.b; + section_a.a = section_a.b; + } + } + + polygon_sections[i].erase(std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), + [](const Line &s) { return s.a == s.b; }), + polygon_sections[i].end()); + std::sort(polygon_sections[i].begin(), polygon_sections[i].end(), + [](const Line &a, const Line &b) { return a.a.y() < b.b.y(); }); + } + + // reconstruct polygon from polygon sections + struct TracedPoly + { + Points lows; + Points highs; + }; + + std::vector current_traced_polys; + for (const auto &polygon_slice : polygon_sections) { + std::unordered_set used_segments; + for (TracedPoly &traced_poly : current_traced_polys) { + auto candidates_begin = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.lows.back(), + [](const Point &low, const Line &seg) { return seg.b.y() > low.y(); }); + auto candidates_end = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.highs.back(), + [](const Point &high, const Line &seg) { return seg.a.y() > high.y(); }); + + bool segment_added = false; + for (auto candidate = candidates_begin; candidate != candidates_end && !segment_added; candidate++) { + if (used_segments.find(&(*candidate)) != used_segments.end()) { + continue; + } + + if ((traced_poly.lows.back() - candidate->a).cast().squaredNorm() < + 36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) { + traced_poly.lows.push_back(candidate->a); + } else { + traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(candidate->a - Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.lows.push_back(candidate->a); + } + + if ((traced_poly.highs.back() - candidate->b).cast().squaredNorm() < + 36.0 * double(bridging_flow.scaled_spacing()) * bridging_flow.scaled_spacing()) { + traced_poly.highs.push_back(candidate->b); + } else { + traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(candidate->b - Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(candidate->b); + } + segment_added = true; + used_segments.insert(&(*candidate)); + } + + if (!segment_added) { + // Zero overlapping segments, we just close this polygon + traced_poly.lows.push_back(traced_poly.lows.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); + traced_poly.highs.push_back(traced_poly.highs.back() + Point{bridging_flow.scaled_spacing() / 2, 0}); + Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); + new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); + traced_poly.lows.clear(); + traced_poly.highs.clear(); + } + } + + current_traced_polys.erase(std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), + [](const TracedPoly &tp) { return tp.lows.empty(); }), + current_traced_polys.end()); + + for (const auto &segment : polygon_slice) { + if (used_segments.find(&segment) == used_segments.end()) { + TracedPoly &new_tp = current_traced_polys.emplace_back(); + new_tp.lows.push_back(segment.a - Point{bridging_flow.scaled_spacing() / 2, 0}); + new_tp.lows.push_back(segment.a); + new_tp.highs.push_back(segment.b - Point{bridging_flow.scaled_spacing() / 2, 0}); + new_tp.highs.push_back(segment.b); + } } } - */ -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - layerm->export_region_slices_to_svg_debug("7_bridge_over_infill"); - layerm->export_region_fill_surfaces_to_svg_debug("7_bridge_over_infill"); -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - m_print->throw_if_canceled(); + // add not closed polys + for (TracedPoly &traced_poly : current_traced_polys) { + Polygon &new_poly = expanded_bridged_area.emplace_back(std::move(traced_poly.lows)); + new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); + } + expanded_bridged_area = union_safety_offset(expanded_bridged_area); + } + + polygons_rotate(expanded_bridged_area, -aligning_angle); + return expanded_bridged_area; + }; + + tbb::parallel_for(tbb::blocked_range(0, clustered_layers_for_threads.size()), [po = static_cast(this), + target_flow_height_factor, &surfaces_by_layer, + &clustered_layers_for_threads, + gather_areas_w_depth, &infill_lines, + determine_bridging_angle, + construct_anchored_polygon]( + tbb::blocked_range r) { + PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); + for (size_t cluster_idx = r.begin(); cluster_idx < r.end(); cluster_idx++) { + for (size_t job_idx = 0; job_idx < clustered_layers_for_threads[cluster_idx].size(); job_idx++) { + size_t lidx = clustered_layers_for_threads[cluster_idx][job_idx]; + const Layer *layer = po->get_layer(lidx); + // this thread has exclusive access to all surfaces in layers enumerated in + // clustered_layers_for_threads[cluster_idx] + + // Presort the candidate polygons. This will help choose the same angle for neighbournig surfaces, that + // would otherwise compete over anchoring sparse infill lines, leaving one area unachored + std::sort(surfaces_by_layer[lidx].begin(), surfaces_by_layer[lidx].end(), + [](const CandidateSurface &left, const CandidateSurface &right) { + auto a = get_extents(left.new_polys); + auto b = get_extents(right.new_polys); + + if (a.min.x() == b.min.x()) { + return a.min.y() < b.min.y(); + }; + return a.min.x() < b.min.x(); + }); + if (surfaces_by_layer[lidx].size() > 2) { + Vec2d origin = get_extents(surfaces_by_layer[lidx].front().new_polys).max.cast(); + std::stable_sort(surfaces_by_layer[lidx].begin() + 1, surfaces_by_layer[lidx].end(), + [origin](const CandidateSurface &left, const CandidateSurface &right) { + auto a = get_extents(left.new_polys); + auto b = get_extents(right.new_polys); + + return (origin - a.min.cast()).squaredNorm() < + (origin - b.min.cast()).squaredNorm(); + }); + } + + // Gather deep infill areas, where thick bridges fit + coordf_t spacing = surfaces_by_layer[lidx].front().region->bridging_flow(frSolidInfill, true).scaled_spacing(); + coordf_t target_flow_height = surfaces_by_layer[lidx].front().region->bridging_flow(frSolidInfill, true).height() * + target_flow_height_factor; + Polygons deep_infill_area = gather_areas_w_depth(po, lidx, target_flow_height); + + { + // Now also remove area that has been already filled on lower layers by bridging expansion - For this + // reason we did the clustering of layers per thread. + Polygons filled_polyons_on_lower_layers; + double bottom_z = layer->print_z - target_flow_height - EPSILON; + if (job_idx > 0) { + for (int lower_job_idx = job_idx - 1; lower_job_idx >= 0; lower_job_idx--) { + size_t lower_layer_idx = clustered_layers_for_threads[cluster_idx][lower_job_idx]; + const Layer *lower_layer = po->get_layer(lower_layer_idx); + if (lower_layer->print_z >= bottom_z) { + for (const auto &c : surfaces_by_layer[lower_layer_idx]) { + filled_polyons_on_lower_layers.insert(filled_polyons_on_lower_layers.end(), c.new_polys.begin(), + c.new_polys.end()); + } + } else { + break; + } + } + } + deep_infill_area = diff(deep_infill_area, filled_polyons_on_lower_layers); + } + + deep_infill_area = expand(deep_infill_area, spacing * 1.5); + + // Now gather expansion polygons - internal infill on current layer, from which we can cut off anchors + Polygons lightning_area; + Polygons expansion_area; + Polygons total_fill_area; + for (const LayerRegion *region : layer->regions()) { + Polygons internal_polys = to_polygons(region->fill_surfaces.filter_by_types({stInternal, stInternalSolid})); + expansion_area.insert(expansion_area.end(), internal_polys.begin(), internal_polys.end()); + Polygons fill_polys = to_polygons(region->fill_expolygons); + total_fill_area.insert(total_fill_area.end(), fill_polys.begin(), fill_polys.end()); + if (region->region().config().sparse_infill_pattern == ipLightning) { + Polygons l = to_polygons(region->fill_surfaces.filter_by_type(stInternal)); + lightning_area.insert(lightning_area.end(), l.begin(), l.end()); + } + } + total_fill_area = closing(total_fill_area, float(SCALED_EPSILON)); + expansion_area = closing(expansion_area, float(SCALED_EPSILON)); + expansion_area = intersection(expansion_area, deep_infill_area); + Polylines anchors = intersection_pl(infill_lines[lidx - 1], shrink(expansion_area, spacing)); + Polygons internal_unsupported_area = shrink(deep_infill_area, spacing * 4.5); + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + "_total_area", + to_lines(total_fill_area), to_lines(expansion_area), to_lines(deep_infill_area), to_lines(anchors)); +#endif + + std::vector expanded_surfaces; + expanded_surfaces.reserve(surfaces_by_layer[lidx].size()); + for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) { + const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true); + Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing()); + area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area); + + area_to_be_bridge.erase(std::remove_if(area_to_be_bridge.begin(), area_to_be_bridge.end(), + [internal_unsupported_area](const Polygon &p) { + return intersection({p}, internal_unsupported_area).empty(); + }), + area_to_be_bridge.end()); + + Polygons limiting_area = union_(area_to_be_bridge, expansion_area); + + if (area_to_be_bridge.empty()) + continue; + + Polylines boundary_plines = to_polylines(expand(total_fill_area, 1.3 * flow.scaled_spacing())); + { + Polylines limiting_plines = to_polylines(expand(limiting_area, 0.3*flow.spacing())); + boundary_plines.insert(boundary_plines.end(), limiting_plines.begin(), limiting_plines.end()); + } + +#ifdef DEBUG_BRIDGE_OVER_INFILL + int r = rand(); + debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + + "_anchors_" + std::to_string(r), + to_lines(area_to_be_bridge), to_lines(boundary_plines), to_lines(anchors), to_lines(expansion_area)); +#endif + + double bridging_angle = 0; + if (!anchors.empty()) { + bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(anchors), + candidate.region->region().config().sparse_infill_pattern.value); + } else { + // use expansion boundaries as anchors. + // Also, use Infill pattern that is neutral for angle determination, since there are no infill lines. + bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(boundary_plines), InfillPattern::ipLine); + } + + boundary_plines.insert(boundary_plines.end(), anchors.begin(), anchors.end()); + if (!lightning_area.empty() && !intersection(area_to_be_bridge, lightning_area).empty()) { + boundary_plines = intersection_pl(boundary_plines, expand(area_to_be_bridge, scale_(10))); + } + Polygons bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle); + + // Check collision with other expanded surfaces + { + bool reconstruct = false; + Polygons tmp_expanded_area = expand(bridging_area, 3.0 * flow.scaled_spacing()); + for (const CandidateSurface &s : expanded_surfaces) { + if (!intersection(s.new_polys, tmp_expanded_area).empty()) { + bridging_angle = s.bridge_angle; + reconstruct = true; + break; + } + } + if (reconstruct) { + bridging_area = construct_anchored_polygon(area_to_be_bridge, to_lines(boundary_plines), flow, bridging_angle); + } + } + + // Orca: Keep fine details for better anchoring + // bridging_area = opening(bridging_area, flow.scaled_spacing()); + bridging_area = opening(bridging_area, flow.scaled_spacing() * 0.75); + bridging_area = closing(bridging_area, flow.scaled_spacing()); + bridging_area = intersection(bridging_area, limiting_area); + bridging_area = intersection(bridging_area, total_fill_area); + expansion_area = diff(expansion_area, bridging_area); + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + "_expanded_bridging" + std::to_string(r), + to_lines(layer->lslices), to_lines(boundary_plines), to_lines(candidate.new_polys), to_lines(bridging_area)); +#endif + + expanded_surfaces.push_back(CandidateSurface(candidate.original_surface, candidate.layer_index, bridging_area, + candidate.region, bridging_angle)); + } + surfaces_by_layer[lidx].swap(expanded_surfaces); + expanded_surfaces.clear(); + } } }); -} + + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - Directions and expanded surfaces computed" << log_memory_info(); + + tbb::parallel_for(tbb::blocked_range(0, this->layers().size()), [po = this, &surfaces_by_layer](tbb::blocked_range r) { + PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); + for (size_t lidx = r.begin(); lidx < r.end(); lidx++) { + if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end() && surfaces_by_layer.find(lidx + 1) == surfaces_by_layer.end()) + continue; + Layer *layer = po->get_layer(lidx); + + Polygons cut_from_infill{}; + if (surfaces_by_layer.find(lidx) != surfaces_by_layer.end()) { + for (const auto &surface : surfaces_by_layer.at(lidx)) { + cut_from_infill.insert(cut_from_infill.end(), surface.new_polys.begin(), surface.new_polys.end()); + } + } + + Polygons additional_ensuring_areas{}; + if (surfaces_by_layer.find(lidx + 1) != surfaces_by_layer.end()) { + for (const auto &surface : surfaces_by_layer.at(lidx + 1)) { + auto additional_area = diff(surface.new_polys, + shrink(surface.new_polys, surface.region->flow(frSolidInfill).scaled_spacing())); + additional_ensuring_areas.insert(additional_ensuring_areas.end(), additional_area.begin(), additional_area.end()); + } + } + + for (LayerRegion *region : layer->regions()) { + Surfaces new_surfaces; + + Polygons near_perimeters = to_polygons(union_safety_offset_ex(to_polygons(region->fill_surfaces.surfaces))); + near_perimeters = diff(near_perimeters, shrink(near_perimeters, region->flow(frSolidInfill).scaled_spacing())); + ExPolygons additional_ensuring = intersection_ex(additional_ensuring_areas, near_perimeters); + + SurfacesPtr internal_infills = region->fill_surfaces.filter_by_type(stInternal); + ExPolygons new_internal_infills = diff_ex(internal_infills, cut_from_infill); + new_internal_infills = diff_ex(new_internal_infills, additional_ensuring); + for (const ExPolygon &ep : new_internal_infills) { + new_surfaces.emplace_back(stInternal, ep); + } + + SurfacesPtr internal_solids = region->fill_surfaces.filter_by_type(stInternalSolid); + if (surfaces_by_layer.find(lidx) != surfaces_by_layer.end()) { + for (const CandidateSurface &cs : surfaces_by_layer.at(lidx)) { + for (const Surface *surface : internal_solids) { + if (cs.original_surface == surface) { + Surface tmp{*surface, {}}; + tmp.surface_type = stInternalBridge; + tmp.bridge_angle = cs.bridge_angle; + for (const ExPolygon &ep : union_ex(cs.new_polys)) { + new_surfaces.emplace_back(tmp, ep); + } + break; + } + } + } + } + ExPolygons new_internal_solids = to_expolygons(internal_solids); + new_internal_solids.insert(new_internal_solids.end(), additional_ensuring.begin(), additional_ensuring.end()); + new_internal_solids = diff_ex(new_internal_solids, cut_from_infill); + new_internal_solids = union_safety_offset_ex(new_internal_solids); + for (const ExPolygon &ep : new_internal_solids) { + new_surfaces.emplace_back(stInternalSolid, ep); + } + +#ifdef DEBUG_BRIDGE_OVER_INFILL + debug_draw("Aensuring_" + std::to_string(reinterpret_cast(®ion)), to_polylines(additional_ensuring), + to_polylines(near_perimeters), to_polylines(to_polygons(internal_infills)), + to_polylines(to_polygons(internal_solids))); + debug_draw("Aensuring_" + std::to_string(reinterpret_cast(®ion)) + "_new", to_polylines(additional_ensuring), + to_polylines(near_perimeters), to_polylines(to_polygons(new_internal_infills)), + to_polylines(to_polygons(new_internal_solids))); +#endif + + region->fill_surfaces.remove_types({stInternalSolid, stInternal}); + region->fill_surfaces.append(new_surfaces); + } + } + }); + + BOOST_LOG_TRIVIAL(info) << "Bridge over infill - End" << log_memory_info(); + +} // void PrintObject::bridge_over_infill() static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders) { @@ -2219,12 +2898,11 @@ void PrintObject::clip_fill_surfaces() for (LayerRegion *layerm : lower_layer->m_regions) { if (layerm->region().config().sparse_infill_density.value == 0) continue; - SurfaceType internal_surface_types[] = { stInternal, stInternalVoid }; Polygons internal; for (Surface &surface : layerm->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.remove_types({ stInternal, stInternalVoid }); layerm->fill_surfaces.append(intersection_ex(internal, upper_internal, ApplySafetyOffset::Yes), stInternal); layerm->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 @@ -2407,8 +3085,7 @@ void PrintObject::discover_horizontal_shells() neighbor_layerm->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 }; - backup.keep_types(surface_types_solid, 3); + backup.keep_types({ stTop, stBottom, stBottomBridge }); std::vector top_bottom_groups; backup.group(&top_bottom_groups); for (SurfacesPtr &group : top_bottom_groups) diff --git a/src/libslic3r/Surface.hpp b/src/libslic3r/Surface.hpp index 42bf67e6de..4fd0350602 100644 --- a/src/libslic3r/Surface.hpp +++ b/src/libslic3r/Surface.hpp @@ -15,8 +15,6 @@ enum SurfaceType { stBottomBridge, // Normal sparse infill. stInternal, - // Normal sparse infill. - stInternalWithLoop, // Full infill, supporting the top surfaces and/or defining the verticall wall thickness. stInternalSolid, // 1st layer of dense infill over sparse infill, printed with a bridging extrusion flow. @@ -112,7 +110,7 @@ public: }; typedef std::vector Surfaces; -typedef std::vector SurfacesPtr; +typedef std::vector SurfacesPtr; inline Polygons to_polygons(const Surface &surface) { @@ -229,6 +227,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)); @@ -238,6 +237,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 f0af8dcd2d..83015c3e6a 100644 --- a/src/libslic3r/SurfaceCollection.cpp +++ b/src/libslic3r/SurfaceCollection.cpp @@ -42,26 +42,21 @@ void SurfaceCollection::group(std::vector *retval) } } -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(std::initializer_list types) const { SurfacesPtr ss; - for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { - for (int i = 0; i < ntypes; ++ i) { - if (surface->surface_type == types[i]) { - ss.push_back(&*surface); - break; - } - } - } + for (const Surface &surface : this->surfaces) + if (std::find(types.begin(), types.end(), surface.surface_type) != types.end()) + ss.push_back(&surface); return ss; } @@ -86,23 +81,15 @@ void SurfaceCollection::keep_type(const SurfaceType type) surfaces.erase(surfaces.begin() + j, surfaces.end()); } -void SurfaceCollection::keep_types(const SurfaceType *types, int ntypes) +void SurfaceCollection::keep_types(std::initializer_list types) { size_t j = 0; - for (size_t i = 0; i < surfaces.size(); ++ i) { - bool keep = false; - for (int k = 0; k < ntypes; ++ k) { - if (surfaces[i].surface_type == types[k]) { - keep = true; - break; - } - } - if (keep) { + for (size_t i = 0; i < surfaces.size(); ++ i) + if (std::find(types.begin(), types.end(), surfaces[i].surface_type) != types.end()) { if (j < i) std::swap(surfaces[i], surfaces[j]); ++ j; } - } if (j < surfaces.size()) surfaces.erase(surfaces.begin() + j, surfaces.end()); } @@ -137,23 +124,15 @@ void SurfaceCollection::remove_type(const SurfaceType type, ExPolygons *polygons surfaces.erase(surfaces.begin() + j, surfaces.end()); } -void SurfaceCollection::remove_types(const SurfaceType *types, int ntypes) +void SurfaceCollection::remove_types(std::initializer_list types) { size_t j = 0; - for (size_t i = 0; i < surfaces.size(); ++ i) { - bool remove = false; - for (int k = 0; k < ntypes; ++ k) { - if (surfaces[i].surface_type == types[k]) { - remove = true; - break; - } - } - if (! remove) { + for (size_t i = 0; i < surfaces.size(); ++ i) + if (std::find(types.begin(), types.end(), surfaces[i].surface_type) == types.end()) { if (j < i) std::swap(surfaces[i], surfaces[j]); ++ j; } - } if (j < surfaces.size()) surfaces.erase(surfaces.begin() + j, surfaces.end()); } diff --git a/src/libslic3r/SurfaceCollection.hpp b/src/libslic3r/SurfaceCollection.hpp index 50c71f011f..1895516aa2 100644 --- a/src/libslic3r/SurfaceCollection.hpp +++ b/src/libslic3r/SurfaceCollection.hpp @@ -3,6 +3,7 @@ #include "libslic3r.h" #include "Surface.hpp" +#include #include namespace Slic3r { @@ -26,12 +27,12 @@ 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(std::initializer_list types) const; void keep_type(const SurfaceType type); - void keep_types(const SurfaceType *types, int ntypes); + void keep_types(std::initializer_list types); void remove_type(const SurfaceType type); - void remove_types(const SurfaceType *types, int ntypes); + void remove_types(std::initializer_list types); void filter_by_type(SurfaceType type, Polygons* polygons) const; void remove_type(const SurfaceType type, ExPolygons *polygons); void set_type(SurfaceType type) { @@ -54,6 +55,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/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 370fcf2bc9..9838efd945 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1935,7 +1935,6 @@ void TabPrint::build() optgroup->append_single_option_line("infill_combination"); optgroup->append_single_option_line("detect_narrow_internal_solid_infill"); optgroup->append_single_option_line("ensure_vertical_shell_thickness"); - optgroup->append_single_option_line("internal_bridge_support_thickness"); page = add_options_page(L("Speed"), "empty"); optgroup = page->new_optgroup(L("Initial layer speed"), L"param_speed_first", 15);