#include "Layer.hpp" #include "BridgeDetector.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" #include "PerimeterGenerator.hpp" #include "Print.hpp" #include "Surface.hpp" #include "BoundingBox.hpp" #include "SVG.hpp" #include "RegionExpansion.hpp" #include #include #include namespace Slic3r { Flow LayerRegion::flow(FlowRole role) const { return this->flow(role, m_layer->height); } Flow LayerRegion::flow(FlowRole role, double layer_height) const { return m_region->flow(*m_layer->object(), role, layer_height, m_layer->id() == 0); } Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge) const { const PrintRegion ®ion = this->region(); const PrintRegionConfig ®ion_config = region.config(); const PrintObject &print_object = *this->layer()->object(); if (thick_bridge) { // The old Slic3r way (different from all other slicers): Use rounded extrusions. // Get the configured nozzle_diameter for the extruder associated to the flow role requested. // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. auto nozzle_diameter = float(print_object.print()->config().nozzle_diameter.get_at(region.extruder(role) - 1)); // Applies default bridge spacing. return Flow::bridging_flow(float(sqrt(region_config.bridge_flow)) * nozzle_diameter, nozzle_diameter); } else { // The same way as other slicers: Use normal extrusions. Apply bridge_flow while maintaining the original spacing. return this->flow(role).with_flow_ratio(region_config.bridge_flow); } } // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. void LayerRegion::slices_to_fill_surfaces_clipped() { // Note: this method should be idempotent, but fill_surfaces gets modified // in place. However we're now only using its boundaries (which are invariant) // so we're safe. This guarantees idempotence of prepare_infill() also in case // that combine_infill() turns some fill_surface into VOID surfaces. // Collect polygons per surface type. std::array by_surface; for (Surface &surface : this->slices.surfaces) by_surface[size_t(surface.surface_type)].emplace_back(&surface); // Trim surfaces by the fill_boundaries. this->fill_surfaces.surfaces.clear(); for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) { const SurfacesPtr &this_surfaces = by_surface[surface_type]; if (! this_surfaces.empty()) this->fill_surfaces.append(intersection_ex(this_surfaces, this->fill_expolygons), SurfaceType(surface_type)); } } void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap) { this->perimeters.clear(); this->thin_fills.clear(); const PrintConfig &print_config = this->layer()->object()->print()->config(); const PrintRegionConfig ®ion_config = this->region().config(); const PrintObjectConfig& object_config = this->layer()->object()->config(); // This needs to be in sync with PrintObject::_slice() slicing_mode_normal_below_layer! bool spiral_mode = print_config.spiral_mode && //FIXME account for raft layers. (this->layer()->id() >= size_t(region_config.bottom_shell_layers.value) && this->layer()->print_z >= region_config.bottom_shell_thickness - EPSILON); PerimeterGenerator g( // input: &slices, this->layer()->height, this->flow(frPerimeter), ®ion_config, &this->layer()->object()->config(), &print_config, spiral_mode, // output: &this->perimeters, &this->thin_fills, fill_surfaces, //BBS fill_no_overlap ); if (this->layer()->lower_layer != nullptr) // Cummulative sum of polygons over all the regions. g.lower_slices = &this->layer()->lower_layer->lslices; if (this->layer()->upper_layer != NULL) g.upper_slices = &this->layer()->upper_layer->lslices; g.layer_id = (int)this->layer()->id(); g.ext_perimeter_flow = this->flow(frExternalPerimeter); g.overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges); g.solid_infill_flow = this->flow(frSolidInfill); if (this->layer()->object()->config().wall_generator.value == PerimeterGeneratorType::Arachne && !spiral_mode) g.process_arachne(); else g.process_classic(); } #if 1 // Extract surfaces of given type from surfaces, extract fill (layer) thickness of one of the surfaces. static ExPolygons fill_surfaces_extract_expolygons(Surfaces &surfaces, std::initializer_list surface_types, double &thickness) { size_t cnt = 0; for (const Surface &surface : surfaces) if (std::find(surface_types.begin(), surface_types.end(), surface.surface_type) != surface_types.end()) { ++cnt; thickness = surface.thickness; } if (cnt == 0) return {}; ExPolygons out; out.reserve(cnt); for (Surface &surface : surfaces) if (std::find(surface_types.begin(), surface_types.end(), surface.surface_type) != surface_types.end()) out.emplace_back(std::move(surface.expolygon)); return out; } // Cache for detecting bridge orientation and merging regions with overlapping expansions. struct Bridge { ExPolygon expolygon; uint32_t group_id; std::vector::const_iterator bridge_expansion_begin; std::optional angle{std::nullopt}; }; // Group the bridge surfaces by overlaps. uint32_t group_id(std::vector &bridges, uint32_t src_id) { uint32_t group_id = bridges[src_id].group_id; while (group_id != src_id) { src_id = group_id; group_id = bridges[src_id].group_id; } bridges[src_id].group_id = group_id; return group_id; }; std::vector get_grouped_bridges( ExPolygons&& bridge_expolygons, const std::vector& bridge_expansions ) { using namespace Algorithm; std::vector result; { result.reserve(bridge_expansions.size()); uint32_t group_id = 0; using std::move_iterator; for (ExPolygon& expolygon : bridge_expolygons) result.push_back({ std::move(expolygon), group_id ++, bridge_expansions.end() }); } // Detect overlaps of bridge anchors inside their respective shell regions. // bridge_expansions are sorted by boundary id and source id. for (auto expansion_iterator = bridge_expansions.begin(); expansion_iterator != bridge_expansions.end();) { auto boundary_region_begin = expansion_iterator; auto boundary_region_end = std::find_if( next(expansion_iterator), bridge_expansions.end(), [&](const RegionExpansionEx& expansion){ return expansion.boundary_id != expansion_iterator->boundary_id; } ); // Cache of bboxes per expansion boundary. std::vector bounding_boxes; bounding_boxes.reserve(std::distance(boundary_region_begin, boundary_region_end)); std::transform( boundary_region_begin, boundary_region_end, std::back_inserter(bounding_boxes), [](const RegionExpansionEx& expansion){ return get_extents(expansion.expolygon.contour); } ); // For each bridge anchor of the current source: for (;expansion_iterator != boundary_region_end; ++expansion_iterator) { auto candidate_iterator = std::next(expansion_iterator); for (;candidate_iterator != boundary_region_end; ++candidate_iterator) { const BoundingBox& current_bounding_box{ bounding_boxes[expansion_iterator - boundary_region_begin] }; const BoundingBox& candidate_bounding_box{ bounding_boxes[candidate_iterator - boundary_region_begin] }; if ( expansion_iterator->src_id != candidate_iterator->src_id && current_bounding_box.overlap(candidate_bounding_box) // One may ignore holes, they are irrelevant for intersection test. && !intersection(expansion_iterator->expolygon.contour, candidate_iterator->expolygon.contour).empty() ) { // The two bridge regions intersect. Give them the same (lower) group id. uint32_t id = group_id(result, expansion_iterator->src_id); uint32_t id2 = group_id(result, candidate_iterator->src_id); if (id < id2) result[id2].group_id = id; else result[id].group_id = id2; } } } } return result; } void detect_bridge_directions( const Algorithm::WaveSeeds& bridge_anchors, std::vector& bridges, const std::vector& expansion_zones ) { if (expansion_zones.empty()) { throw std::runtime_error("At least one expansion zone must exist!"); } auto it_bridge_anchor = bridge_anchors.begin(); for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) { Bridge &bridge = bridges[bridge_id]; Polygons anchor_areas; int32_t last_anchor_id = -1; for (; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) { if (last_anchor_id != int(it_bridge_anchor->boundary)) { last_anchor_id = int(it_bridge_anchor->boundary); unsigned start_index{}; unsigned end_index{}; for (const ExpansionZone& expansion_zone: expansion_zones) { end_index += expansion_zone.expolygons.size(); if (last_anchor_id < static_cast(end_index)) { append(anchor_areas, to_polygons(expansion_zone.expolygons[last_anchor_id - start_index])); break; } start_index += expansion_zone.expolygons.size(); } } } Lines lines{to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON))))}; auto [bridging_dir, unsupported_dist] = detect_bridging_direction(lines, to_polygons(bridge.expolygon)); bridge.angle = M_PI + std::atan2(bridging_dir.y(), bridging_dir.x()); if constexpr (false) { coordf_t stroke_width = scale_(0.06); BoundingBox bbox = get_extents(anchor_areas); bbox.merge(get_extents(bridge.expolygon)); bbox.offset(scale_(1.)); ::Slic3r::SVG svg(debug_out_path(("bridge" + std::to_string(*bridge.angle) + "_" /* + std::to_string(this->layer()->bottom_z())*/).c_str()), bbox); svg.draw(bridge.expolygon, "cyan"); svg.draw(lines, "green", stroke_width); svg.draw(anchor_areas, "red"); } } } Surfaces merge_bridges( std::vector& bridges, const std::vector& bridge_expansions, const float closing_radius ) { for (auto it = bridge_expansions.begin(); it != bridge_expansions.end(); ) { bridges[it->src_id].bridge_expansion_begin = it; uint32_t src_id = it->src_id; for (++ it; it != bridge_expansions.end() && it->src_id == src_id; ++ it) ; } Surfaces result; for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) { if (group_id(bridges, bridge_id) == bridge_id) { // Head of the group. Polygons acc; for (uint32_t bridge_id2 = bridge_id; bridge_id2 < uint32_t(bridges.size()); ++ bridge_id2) if (group_id(bridges, bridge_id2) == bridge_id) { append(acc, to_polygons(std::move(bridges[bridge_id2].expolygon))); auto it_bridge_expansion = bridges[bridge_id2].bridge_expansion_begin; assert(it_bridge_expansion == bridge_expansions.end() || it_bridge_expansion->src_id == bridge_id2); for (; it_bridge_expansion != bridge_expansions.end() && it_bridge_expansion->src_id == bridge_id2; ++ it_bridge_expansion) append(acc, to_polygons(it_bridge_expansion->expolygon)); } //FIXME try to be smart and pick the best bridging angle for all? if (!bridges[bridge_id].angle) { assert(false && "Bridge angle must be pre-calculated!"); } Surface templ{ stBottomBridge, {} }; templ.bridge_angle = bridges[bridge_id].angle ? *bridges[bridge_id].angle : -1; //NOTE: The current regularization of the shells can create small unasigned regions in the object (E.G. benchy) // without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface. // look for narrow_ensure_vertical_wall_thickness_region_radius filter. ExPolygons final = closing_ex(acc, closing_radius); // without safety offset, artifacts are generated (GH #2494) // union_safety_offset_ex(acc) for (ExPolygon &ex : final) result.emplace_back(templ, std::move(ex)); } } return result; } struct ExpansionResult { Algorithm::WaveSeeds anchors; std::vector expansions; }; ExpansionResult expand_expolygons( const ExPolygons& expolygons, std::vector& expansion_zones ) { using namespace Algorithm; WaveSeeds bridge_anchors; std::vector bridge_expansions; unsigned processed_bridges_count = 0; for (ExpansionZone& expansion_zone : expansion_zones) { WaveSeeds seeds{wave_seeds( expolygons, expansion_zone.expolygons, expansion_zone.parameters.tiny_expansion, true )}; std::vector expansions{propagate_waves_ex( seeds, expansion_zone.expolygons, expansion_zone.parameters )}; for (WaveSeed &seed : seeds) seed.boundary += processed_bridges_count; for (RegionExpansionEx &expansion : expansions) expansion.boundary_id += processed_bridges_count; expansion_zone.expanded_into = ! expansions.empty(); append(bridge_anchors, std::move(seeds)); append(bridge_expansions, std::move(expansions)); processed_bridges_count += expansion_zone.expolygons.size(); } return {bridge_anchors, bridge_expansions}; } // Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params, // detect bridges. // Trim "shells" by the expanded bridges. Surfaces expand_bridges_detect_orientations( Surfaces &surfaces, std::vector& expansion_zones, const float closing_radius ) { using namespace Slic3r::Algorithm; double thickness; ExPolygons bridge_expolygons = fill_surfaces_extract_expolygons(surfaces, {stBottomBridge}, thickness); if (bridge_expolygons.empty()) return {}; // Calculate bridge anchors and their expansions in their respective shell region. ExpansionResult expansion_result{expand_expolygons( bridge_expolygons, expansion_zones )}; std::vector bridges{get_grouped_bridges( std::move(bridge_expolygons), expansion_result.expansions )}; bridge_expolygons.clear(); std::sort(expansion_result.anchors.begin(), expansion_result.anchors.end(), Algorithm::lower_by_src_and_boundary); detect_bridge_directions(expansion_result.anchors, bridges, expansion_zones); // Merge the groups with the same group id, produce surfaces by merging source overhangs with their newly expanded anchors. std::sort(expansion_result.expansions.begin(), expansion_result.expansions.end(), [](auto &l, auto &r) { return l.src_id < r.src_id || (l.src_id == r.src_id && l.boundary_id < r.boundary_id); }); Surfaces out{merge_bridges(bridges, expansion_result.expansions, closing_radius)}; // Clip by the expanded bridges. for (ExpansionZone& expansion_zone : expansion_zones) if (expansion_zone.expanded_into) expansion_zone.expolygons = diff_ex(expansion_zone.expolygons, out); return out; } Surfaces expand_merge_surfaces( Surfaces &surfaces, SurfaceType surface_type, std::vector& expansion_zones, const float closing_radius, const double bridge_angle ) { using namespace Slic3r::Algorithm; double thickness; ExPolygons src = fill_surfaces_extract_expolygons(surfaces, {surface_type}, thickness); if (src.empty()) return {}; unsigned processed_expolygons_count = 0; std::vector expansions; for (ExpansionZone& expansion_zone : expansion_zones) { std::vector zone_expansions = propagate_waves(src, expansion_zone.expolygons, expansion_zone.parameters); expansion_zone.expanded_into = !zone_expansions.empty(); for (RegionExpansion &expansion : zone_expansions) expansion.boundary_id += processed_expolygons_count; processed_expolygons_count += expansion_zone.expolygons.size(); append(expansions, std::move(zone_expansions)); } std::vector expanded = merge_expansions_into_expolygons(std::move(src), std::move(expansions)); //NOTE: The current regularization of the shells can create small unasigned regions in the object (E.G. benchy) // without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface. // look for narrow_ensure_vertical_wall_thickness_region_radius filter. expanded = closing_ex(expanded, closing_radius); // Trim the zones by the expanded expolygons. for (ExpansionZone& expansion_zone : expansion_zones) if (expansion_zone.expanded_into) expansion_zone.expolygons = diff_ex(expansion_zone.expolygons, expanded); Surface templ{ surface_type, {} }; templ.bridge_angle = bridge_angle; Surfaces out; out.reserve(expanded.size()); for (auto &expoly : expanded) out.emplace_back(templ, std::move(expoly)); return out; } void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered) { using namespace Slic3r::Algorithm; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Width of the perimeters. float shell_width = 0; float expansion_min = 0; if (int num_perimeters = this->region().config().wall_loops; num_perimeters > 0) { Flow external_perimeter_flow = this->flow(frExternalPerimeter); Flow perimeter_flow = this->flow(frPerimeter); shell_width = 0.5f * external_perimeter_flow.scaled_width() + external_perimeter_flow.scaled_spacing(); shell_width += perimeter_flow.scaled_spacing() * (num_perimeters - 1); expansion_min = perimeter_flow.scaled_spacing(); } else { // TODO: Maybe there is better solution when printing with zero perimeters, but this works reasonably well, given the situation shell_width = float(SCALED_EPSILON); expansion_min = float(SCALED_EPSILON);; } // Scaled expansions of the respective external surfaces. float expansion_top = shell_width * sqrt(2.); float expansion_bottom = expansion_top; float expansion_bottom_bridge = expansion_top; // Expand by waves of expansion_step size (expansion_step is scaled), but with no more steps than max_nr_expansion_steps. static constexpr const float expansion_step = scaled(0.1); // Don't take more than max_nr_steps for small expansion_step. static constexpr const size_t max_nr_expansion_steps = 5; // Radius (with added epsilon) to absorb empty regions emering from regularization of ensuring, viz const float narrow_ensure_vertical_wall_thickness_region_radius = 0.5f * 0.65f * min_perimeter_infill_spacing; const float closing_radius = 0.55f * 0.65f * 1.05f * this->flow(frSolidInfill).scaled_spacing(); // Expand the top / bottom / bridge surfaces into the shell thickness solid infills. double layer_thickness; ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(fill_surfaces.surfaces, { stInternalSolid }, layer_thickness)); ExPolygons sparse = union_ex(fill_surfaces_extract_expolygons(fill_surfaces.surfaces, { stInternal }, layer_thickness)); ExPolygons top_expolygons = union_ex(fill_surfaces_extract_expolygons(fill_surfaces.surfaces, { stTop }, layer_thickness)); const auto expansion_params_into_sparse_infill = RegionExpansionParameters::build(expansion_min, expansion_step, max_nr_expansion_steps); const auto expansion_params_into_solid_infill = RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps); std::vector expansion_zones{ ExpansionZone{std::move(shells), expansion_params_into_solid_infill}, ExpansionZone{std::move(sparse), expansion_params_into_sparse_infill}, ExpansionZone{std::move(top_expolygons), expansion_params_into_solid_infill}, }; SurfaceCollection bridges; { BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z; const double custom_angle = this->region().config().bridge_angle.value; bridges.surfaces = custom_angle > 0 ? expand_merge_surfaces(fill_surfaces.surfaces, stBottomBridge, expansion_zones, closing_radius, Geometry::deg2rad(custom_angle)) : expand_bridges_detect_orientations(fill_surfaces.surfaces, expansion_zones, closing_radius); BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; #if 0 { static int iRun = 0; bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun++), true); } #endif } fill_surfaces.remove_types({ stTop }); { Surface top_templ(stTop, {}); top_templ.thickness = layer_thickness; fill_surfaces.append(std::move(expansion_zones.back().expolygons), top_templ); } expansion_zones.pop_back(); expansion_zones.at(0).parameters = RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps); Surfaces bottoms = expand_merge_surfaces(fill_surfaces.surfaces, stBottom, expansion_zones, closing_radius); expansion_zones.at(0).parameters = RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps); Surfaces tops = expand_merge_surfaces(fill_surfaces.surfaces, stTop, expansion_zones, closing_radius); // m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternal, stInternalSolid }); fill_surfaces.clear(); unsigned zones_expolygons_count = 0; for (const ExpansionZone& zone : expansion_zones) zones_expolygons_count += zone.expolygons.size(); reserve_more(fill_surfaces.surfaces, zones_expolygons_count + bridges.size() + bottoms.size() + tops.size()); { Surface solid_templ(stInternalSolid, {}); solid_templ.thickness = layer_thickness; fill_surfaces.append(std::move(expansion_zones[0].expolygons), solid_templ); } { Surface sparse_templ(stInternal, {}); sparse_templ.thickness = layer_thickness; fill_surfaces.append(std::move(expansion_zones[1].expolygons), sparse_templ); } fill_surfaces.append(std::move(bridges.surfaces)); fill_surfaces.append(std::move(bottoms)); fill_surfaces.append(std::move(tops)); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } #else //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 #define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered) { const bool has_infill = this->region().config().sparse_infill_density.value > 0.; //BBS auto nozzle_diameter = this->region().nozzle_dmr_avg(this->layer()->object()->print()->config()); const float margin = float(scale_(EXTERNAL_INFILL_MARGIN)); const float bridge_margin = std::min(float(scale_(BRIDGE_INFILL_MARGIN)), float(scale_(nozzle_diameter * BRIDGE_INFILL_MARGIN / 0.4))); // BBS const PrintObjectConfig& object_config = this->layer()->object()->config(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset // for better anchoring. // Bottom surfaces, grown. Surfaces bottom; // Bridge surfaces, initialy not grown. Surfaces bridges; // Top surfaces, grown. Surfaces top; // Internal surfaces, not grown. Surfaces internal; // Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed. Polygons fill_boundaries = to_polygons(this->fill_expolygons); Polygons lower_layer_covered_tmp; // Collect top surfaces and internal surfaces. // Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill. // This loop destroys the surfaces (aliasing this->fill_surfaces.surfaces) by moving into top/internal/fill_boundaries! { // Voids are sparse infills if infill rate is zero. Polygons voids; double max_grid_area = -1; if (this->layer()->lower_layer != nullptr) max_grid_area = this->layer()->lower_layer->get_sparse_infill_max_void_area(); for (const Surface &surface : this->fill_surfaces.surfaces) { if (surface.is_top()) { // Collect the top surfaces, inflate them and trim them by the bottom surfaces. // This gives the priority to bottom surfaces. if (max_grid_area < 0 || surface.expolygon.area() < max_grid_area) surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); else //BBS: Don't need to expand too much in this situation. Expand 3mm to eliminate hole and 1mm for contour surfaces_append(top, intersection_ex(offset(surface.expolygon.contour, margin / 3.0, EXTERNAL_SURFACES_OFFSET_PARAMETERS), offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS)), surface); } else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == nullptr)) { // Grown by 3mm. surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); } else if (surface.surface_type == stBottomBridge) { if (! surface.empty()) bridges.emplace_back(surface); } if (surface.is_internal()) { assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid); if (! has_infill && lower_layer != nullptr) polygons_append(voids, surface.expolygon); internal.emplace_back(std::move(surface)); } } if (! has_infill && lower_layer != nullptr && ! voids.empty()) { // Remove voids from fill_boundaries, that are not supported by the layer below. if (lower_layer_covered == nullptr) { lower_layer_covered = &lower_layer_covered_tmp; lower_layer_covered_tmp = to_polygons(lower_layer->lslices); } if (! lower_layer_covered->empty()) voids = diff(voids, *lower_layer_covered); fill_boundaries = diff(fill_boundaries, voids); } } #if 0 { static int iRun = 0; bridges.export_to_svg(debug_out_path("bridges-before-grouping-%d.svg", iRun ++), true); } #endif if (bridges.empty()) { fill_boundaries = union_safety_offset(fill_boundaries); } else { // 1) Calculate the inflated bridge regions, each constrained to its island. ExPolygons fill_boundaries_ex = union_safety_offset_ex(fill_boundaries); std::vector bridges_grown; std::vector bridge_bboxes; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { static int iRun = 0; SVG svg(debug_out_path("3_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex)); svg.draw(fill_boundaries_ex); svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05)); svg.Close(); } // export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ { // Bridge expolygons, grown, to be tested for intersection with other bridge regions. std::vector fill_boundaries_ex_bboxes = get_extents_vector(fill_boundaries_ex); bridges_grown.reserve(bridges.size()); bridge_bboxes.reserve(bridges.size()); for (size_t i = 0; i < bridges.size(); ++ i) { // Find the island of this bridge. const Point pt = bridges[i].expolygon.contour.points.front(); int idx_island = -1; for (int j = 0; j < int(fill_boundaries_ex.size()); ++ j) if (fill_boundaries_ex_bboxes[j].contains(pt) && fill_boundaries_ex[j].contains(pt)) { idx_island = j; break; } // Grown by 3mm. //BBS: eliminate too narrow area to avoid generating bridge on top layer when wall loop is 1 //Polygons polys = offset(bridges[i].expolygon, bridge_margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS); Polygons polys = offset2({ bridges[i].expolygon }, -scale_(nozzle_diameter * 0.1), bridge_margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS); if (idx_island == -1) { BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!"; } else { // Found an island, to which this bridge region belongs. Trim it, polys = intersection(polys, fill_boundaries_ex[idx_island]); } bridge_bboxes.push_back(get_extents(polys)); bridges_grown.push_back(std::move(polys)); } } // 2) Group the bridge surfaces by overlaps. std::vector bridge_group(bridges.size(), (size_t)-1); size_t n_groups = 0; for (size_t i = 0; i < bridges.size(); ++ i) { // A grup id for this bridge. size_t group_id = (bridge_group[i] == size_t(-1)) ? (n_groups ++) : bridge_group[i]; bridge_group[i] = group_id; // For all possibly overlaping bridges: for (size_t j = i + 1; j < bridges.size(); ++ j) { if (! bridge_bboxes[i].overlap(bridge_bboxes[j])) continue; if (intersection(bridges_grown[i], bridges_grown[j]).empty()) continue; // The two bridge regions intersect. Give them the same group id. if (bridge_group[j] != size_t(-1)) { // The j'th bridge has been merged with some other bridge before. size_t group_id_new = bridge_group[j]; for (size_t k = 0; k < j; ++ k) if (bridge_group[k] == group_id) bridge_group[k] = group_id_new; group_id = group_id_new; } bridge_group[j] = group_id; } } // 3) Merge the groups with the same group id, detect bridges. { BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z << ", bridge groups: " << n_groups; for (size_t group_id = 0; group_id < n_groups; ++ group_id) { size_t n_bridges_merged = 0; size_t idx_last = (size_t)-1; for (size_t i = 0; i < bridges.size(); ++ i) { if (bridge_group[i] == group_id) { ++ n_bridges_merged; idx_last = i; } } if (n_bridges_merged == 0) // This group has no regions assigned as these were moved into another group. continue; // Collect the initial ungrown regions and the grown polygons. ExPolygons initial; Polygons grown; for (size_t i = 0; i < bridges.size(); ++ i) { if (bridge_group[i] != group_id) continue; initial.push_back(std::move(bridges[i].expolygon)); polygons_append(grown, bridges_grown[i]); } // detect bridge direction before merging grown surfaces otherwise adjacent bridges // would get merged into a single one while they need different directions // also, supply the original expolygon instead of the grown one, because in case // of very thin (but still working) anchors, the grown expolygon would go beyond them double custom_angle = Geometry::deg2rad(this->region().config().bridge_angle.value); if (custom_angle > 0.0) { bridges[idx_last].bridge_angle = custom_angle; } else { auto [bridging_dir, unsupported_dist] = detect_bridging_direction(to_polygons(initial), to_polygons(lower_layer->lslices)); bridges[idx_last].bridge_angle = PI + std::atan2(bridging_dir.y(), bridging_dir.x()); } /* BridgeDetector bd(initial, lower_layer->lslices, this->bridging_flow(frInfill, object_config.thick_bridges).scaled_width()); #ifdef SLIC3R_DEBUG printf("Processing bridge at layer %zu:\n", this->layer()->id()); #endif double custom_angle = Geometry::deg2rad(this->region().config().bridge_angle.value); if (bd.detect_angle(custom_angle)) { bridges[idx_last].bridge_angle = bd.angle; if (this->layer()->object()->has_support()) { // polygons_append(this->bridged, bd.coverage()); append(this->unsupported_bridge_edges, bd.unsupported_edges()); } } else if (custom_angle > 0) { // Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in // using a bridging flow, therefore it makes sense to respect the custom bridging direction. bridges[idx_last].bridge_angle = custom_angle; } */ // without safety offset, artifacts are generated (GH #2494) surfaces_append(bottom, union_safety_offset_ex(grown), bridges[idx_last]); } fill_boundaries = to_polygons(fill_boundaries_ex); BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; } #if 0 { static int iRun = 0; bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun ++), true); } #endif } Surfaces new_surfaces; { // Intersect the grown surfaces with the actual fill boundaries. Polygons bottom_polygons = to_polygons(bottom); // Merge top and bottom in a single collection. surfaces_append(top, std::move(bottom)); for (size_t i = 0; i < top.size(); ++ i) { Surface &s1 = top[i]; if (s1.empty()) continue; Polygons polys; polygons_append(polys, to_polygons(std::move(s1))); for (size_t j = i + 1; j < top.size(); ++ j) { Surface &s2 = top[j]; if (! s2.empty() && surfaces_could_merge(s1, s2)) { polygons_append(polys, to_polygons(std::move(s2))); s2.clear(); } } if (s1.is_top()) // Trim the top surfaces by the bottom surfaces. This gives the priority to the bottom surfaces. polys = diff(polys, bottom_polygons); surfaces_append( new_surfaces, // Don't use a safety offset as fill_boundaries were already united using the safety offset. intersection_ex(polys, fill_boundaries), s1); } } // Subtract the new top surfaces from the other non-top surfaces and re-add them. Polygons new_polygons = to_polygons(new_surfaces); for (size_t i = 0; i < internal.size(); ++ i) { Surface &s1 = internal[i]; if (s1.empty()) continue; Polygons polys; polygons_append(polys, to_polygons(std::move(s1))); for (size_t j = i + 1; j < internal.size(); ++ j) { Surface &s2 = internal[j]; if (! s2.empty() && surfaces_could_merge(s1, s2)) { polygons_append(polys, to_polygons(std::move(s2))); s2.clear(); } } ExPolygons new_expolys = diff_ex(polys, new_polygons); polygons_append(new_polygons, to_polygons(new_expolys)); surfaces_append(new_surfaces, std::move(new_expolys), s1); } this->fill_surfaces.surfaces = std::move(new_surfaces); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } #endif void LayerRegion::prepare_fill_surfaces() { #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial"); export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ /* Note: in order to make the psPrepareInfill step idempotent, we should never alter fill_surfaces boundaries on which our idempotency relies since that's the only meaningful information returned by psPerimeters. */ bool spiral_mode = this->layer()->object()->print()->config().spiral_mode; #if 0 // if no solid layers are requested, turn top/bottom surfaces to internal if (! spiral_mode && this->region().config().top_shell_layers == 0) { for (Surface &surface : this->fill_surfaces.surfaces) if (surface.is_top()) //BBS //surface.surface_type = this->layer()->object()->config().infill_only_where_needed ? stInternalVoid : stInternal; surface.surface_type = PrintObject::infill_only_where_needed ? stInternalVoid : stInternal; } if (this->region().config().bottom_shell_layers == 0) { for (Surface &surface : this->fill_surfaces.surfaces) if (surface.is_bottom()) // (surface.surface_type == stBottom) surface.surface_type = stInternal; } #endif // turn too small internal regions into solid regions according to the user setting if (! spiral_mode && this->region().config().sparse_infill_density.value > 0) { // scaling an area requires two calls! double min_area = scale_(scale_(this->region().config().minimum_sparse_infill_area.value)); for (Surface &surface : this->fill_surfaces.surfaces) if (surface.surface_type == stInternal && surface.area() <= min_area) surface.surface_type = stInternalSolid; } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_slices_to_svg_debug("2_prepare_fill_surfaces-final"); export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } double LayerRegion::infill_area_threshold() const { double ss = this->flow(frSolidInfill).scaled_spacing(); return ss*ss; } void LayerRegion::trim_surfaces(const Polygons &trimming_polygons) { #ifndef NDEBUG for (const Surface &surface : this->slices.surfaces) assert(surface.surface_type == stInternal); #endif /* NDEBUG */ this->slices.set(intersection_ex(this->slices.surfaces, trimming_polygons), stInternal); } void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons) { #ifndef NDEBUG for (const Surface &surface : this->slices.surfaces) assert(surface.surface_type == stInternal); #endif /* NDEBUG */ Polygons tmp = intersection(this->slices.surfaces, trimming_polygons); append(tmp, diff(this->slices.surfaces, opening(this->slices.surfaces, elephant_foot_compensation_perimeter_step))); this->slices.set(union_ex(tmp), stInternal); } void LayerRegion::export_region_slices_to_svg(const char *path) const { BoundingBox bbox; for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface) bbox.merge(get_extents(surface->expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min(0), bbox.max(1)); bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); SVG svg(path, bbox); const float transparency = 0.5f; for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface) svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency); for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) svg.draw(surface->expolygon.lines(), surface_type_to_color_name(surface->surface_type)); export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); } // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. void LayerRegion::export_region_slices_to_svg_debug(const char *name) const { static std::map idx_map; size_t &idx = idx_map[name]; this->export_region_slices_to_svg(debug_out_path("LayerRegion-slices-%s-%d.svg", name, idx ++).c_str()); } void LayerRegion::export_region_fill_surfaces_to_svg(const char *path) const { BoundingBox bbox; for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) bbox.merge(get_extents(surface->expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min(0), bbox.max(1)); bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1))); SVG svg(path, bbox); const float transparency = 0.5f; for (const Surface &surface : this->fill_surfaces.surfaces) { svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency); svg.draw_outline(surface.expolygon, "black", "blue", scale_(0.05)); } export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); } // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. void LayerRegion::export_region_fill_surfaces_to_svg_debug(const char *name) const { static std::map idx_map; size_t &idx = idx_map[name]; this->export_region_fill_surfaces_to_svg(debug_out_path("LayerRegion-fill_surfaces-%s-%d.svg", name, idx ++).c_str()); } void LayerRegion::simplify_entity_collection(ExtrusionEntityCollection* entity_collection) { for (size_t i = 0; i < entity_collection->entities.size(); i++) { if (ExtrusionEntityCollection* collection = dynamic_cast(entity_collection->entities[i])) this->simplify_entity_collection(collection); else if (ExtrusionPath* path = dynamic_cast(entity_collection->entities[i])) this->simplify_path(path); else if (ExtrusionMultiPath* multipath = dynamic_cast(entity_collection->entities[i])) this->simplify_multi_path(multipath); else if (ExtrusionLoop* loop = dynamic_cast(entity_collection->entities[i])) this->simplify_loop(loop); else throw Slic3r::InvalidArgument("Invalid extrusion entity supplied to simplify_entity_collection()"); } } void LayerRegion::simplify_path(ExtrusionPath* path) { const auto print_config = this->layer()->object()->print()->config(); const bool spiral_mode = print_config.spiral_mode; const bool enable_arc_fitting = print_config.enable_arc_fitting; const auto scaled_resolution = scaled(print_config.resolution.value); if (enable_arc_fitting && !spiral_mode) { if (path->role() == erInternalInfill) path->simplify_by_fitting_arc(SCALED_SPARSE_INFILL_RESOLUTION); else path->simplify_by_fitting_arc(scaled_resolution); } else { path->simplify(scaled_resolution); } } void LayerRegion::simplify_multi_path(ExtrusionMultiPath* multipath) { const auto print_config = this->layer()->object()->print()->config(); const bool spiral_mode = print_config.spiral_mode; const bool enable_arc_fitting = print_config.enable_arc_fitting; const auto scaled_resolution = scaled(print_config.resolution.value); for (size_t i = 0; i < multipath->paths.size(); ++i) { if (enable_arc_fitting && !spiral_mode) { if (multipath->paths[i].role() == erInternalInfill) multipath->paths[i].simplify_by_fitting_arc(SCALED_SPARSE_INFILL_RESOLUTION); else multipath->paths[i].simplify_by_fitting_arc(scaled_resolution); } else { multipath->paths[i].simplify(scaled_resolution); } } } void LayerRegion::simplify_loop(ExtrusionLoop* loop) { const auto print_config = this->layer()->object()->print()->config(); const bool spiral_mode = print_config.spiral_mode; const bool enable_arc_fitting = print_config.enable_arc_fitting; const auto scaled_resolution = scaled(print_config.resolution.value); for (size_t i = 0; i < loop->paths.size(); ++i) { if (enable_arc_fitting && !spiral_mode) { if (loop->paths[i].role() == erInternalInfill) loop->paths[i].simplify_by_fitting_arc(SCALED_SPARSE_INFILL_RESOLUTION); else loop->paths[i].simplify_by_fitting_arc(scaled_resolution); } else { loop->paths[i].simplify(scaled_resolution); } } } }