OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::PerimeterGenerator Class Reference

#include </home/runner/work/OrcaSlicer/OrcaSlicer/src/libslic3r/PerimeterGenerator.hpp>

Collaboration diagram for Slic3r::PerimeterGenerator:

Public Member Functions

 PerimeterGenerator (const SurfaceCollection *slices, const LayerRegionPtrs *compatible_regions, double layer_height, coordf_t slice_z, Flow flow, const PrintRegionConfig *config, const PrintObjectConfig *object_config, const PrintConfig *print_config, const bool spiral_mode, const double model_rotation_rad, ExtrusionEntityCollection *loops, ExtrusionEntityCollection *gap_fill, SurfaceCollection *fill_surfaces, ExPolygons *fill_no_overlap)
void process_classic ()
void process_arachne ()
void add_infill_contour_for_arachne (ExPolygons infill_contour, int loops, coord_t ext_perimeter_spacing, coord_t perimeter_spacing, coord_t min_perimeter_infill_spacing, coord_t spacing, bool is_inner_part)
double ext_mm3_per_mm () const
double mm3_per_mm () const
double mm3_per_mm_overhang () const
double smaller_width_ext_mm3_per_mm () const
Polygons lower_slices_polygons () const
ExPolygons printable_slices (const ExPolygons &slices) const
bool outer_walls_first_for_brim () const

Public Attributes

const SurfaceCollection * slices
const LayerRegionPtrs * compatible_regions
const ExPolygons * upper_slices
const SurfaceCollection * upper_slices_same_region
const ExPolygons * lower_slices
double layer_height
int layer_id
coordf_t slice_z
Flow perimeter_flow
Flow ext_perimeter_flow
Flow overhang_flow
Flow solid_infill_flow
const PrintRegionConfig * config
const PrintObjectConfig * object_config
const PrintConfig * print_config
ExtrusionEntityCollection * loops
ExtrusionEntityCollection * gap_fill
SurfaceCollection * fill_surfaces
ExPolygons * fill_no_overlap
Flow smaller_ext_perimeter_flow
std::vector< Polygons > m_lower_polygons_series
std::vector< Polygons > m_external_lower_polygons_series
std::vector< Polygons > m_smaller_external_lower_polygons_series
bool has_fuzzy_skin = false
bool has_fuzzy_hole = false
std::vector< std::pair< FuzzySkinConfig, ExPolygons > > regions_by_fuzzify
std::optional< ExPolygons > fuzzy_supported_area

Private Member Functions

std::vector< Polygons > generate_lower_polygons_series (float width)
void split_top_surfaces (const ExPolygons &orig_polygons, ExPolygons &top_fills, ExPolygons &non_top_polygons, ExPolygons &fill_clip) const
void apply_extra_perimeters (ExPolygons &infill_area)
void process_no_bridge (Surfaces &all_surfaces, coord_t perimeter_spacing, coord_t ext_perimeter_width)

Private Attributes

bool m_spiral_vase
double m_scaled_resolution
double m_model_rotation_rad
double m_ext_mm3_per_mm
double m_mm3_per_mm
double m_mm3_per_mm_overhang
double m_ext_mm3_per_mm_smaller_width
Polygons m_lower_slices_polygons

Detailed Description

Constructor & Destructor Documentation

◆ PerimeterGenerator()

Slic3r::PerimeterGenerator::PerimeterGenerator ( const SurfaceCollection * slices,
const LayerRegionPtrs * compatible_regions,
double layer_height,
coordf_t slice_z,
Flow flow,
const PrintRegionConfig * config,
const PrintObjectConfig * object_config,
const PrintConfig * print_config,
const bool spiral_mode,
const double model_rotation_rad,
ExtrusionEntityCollection * loops,
ExtrusionEntityCollection * gap_fill,
SurfaceCollection * fill_surfaces,
ExPolygons * fill_no_overlap )
inline
145 overhang_flow(flow), solid_infill_flow(flow),
147 m_spiral_vase(spiral_mode),
148 m_scaled_resolution(scaled<double>(print_config->resolution.value > EPSILON ? print_config->resolution.value : EPSILON)),
149 m_model_rotation_rad(model_rotation_rad),
152 {}
#define EPSILON
Definition ColorUtils.cpp:64
Flow overhang_flow
Definition PerimeterGenerator.hpp:97
double layer_height
Definition PerimeterGenerator.hpp:92
ExtrusionEntityCollection * gap_fill
Definition PerimeterGenerator.hpp:104
int layer_id
Definition PerimeterGenerator.hpp:93
const PrintConfig * print_config
Definition PerimeterGenerator.hpp:101
ExPolygons * fill_no_overlap
Definition PerimeterGenerator.hpp:107
double m_model_rotation_rad
Definition PerimeterGenerator.hpp:179
const LayerRegionPtrs * compatible_regions
Definition PerimeterGenerator.hpp:88
const PrintRegionConfig * config
Definition PerimeterGenerator.hpp:99
double m_mm3_per_mm
Definition PerimeterGenerator.hpp:181
Flow perimeter_flow
Definition PerimeterGenerator.hpp:95
Flow solid_infill_flow
Definition PerimeterGenerator.hpp:98
coordf_t slice_z
Definition PerimeterGenerator.hpp:94
const SurfaceCollection * slices
Definition PerimeterGenerator.hpp:87
double m_ext_mm3_per_mm_smaller_width
Definition PerimeterGenerator.hpp:184
Flow ext_perimeter_flow
Definition PerimeterGenerator.hpp:96
double m_mm3_per_mm_overhang
Definition PerimeterGenerator.hpp:182
bool m_spiral_vase
Definition PerimeterGenerator.hpp:177
const PrintObjectConfig * object_config
Definition PerimeterGenerator.hpp:100
const ExPolygons * lower_slices
Definition PerimeterGenerator.hpp:91
double m_ext_mm3_per_mm
Definition PerimeterGenerator.hpp:180
SurfaceCollection * fill_surfaces
Definition PerimeterGenerator.hpp:105
const ExPolygons * upper_slices
Definition PerimeterGenerator.hpp:89
double m_scaled_resolution
Definition PerimeterGenerator.hpp:178
ExtrusionEntityCollection * loops
Definition PerimeterGenerator.hpp:103
BoundingBox scaled(const BoundingBoxf &bb)
Definition BoundingBox.hpp:284

References compatible_regions, config, EPSILON, ext_perimeter_flow, fill_no_overlap, fill_surfaces, gap_fill, layer_height, layer_id, loops, lower_slices, m_ext_mm3_per_mm, m_ext_mm3_per_mm_smaller_width, m_mm3_per_mm, m_mm3_per_mm_overhang, m_model_rotation_rad, m_scaled_resolution, m_spiral_vase, object_config, overhang_flow, perimeter_flow, print_config, Slic3r::PrintObjectConfig, Slic3r::PrintRegionConfig, Slic3r::resolution(), Slic3r::scaled(), slice_z, slices, solid_infill_flow, and upper_slices.

Here is the call graph for this function:

Member Function Documentation

◆ add_infill_contour_for_arachne()

void Slic3r::PerimeterGenerator::add_infill_contour_for_arachne ( ExPolygons infill_contour,
int loops,
coord_t ext_perimeter_spacing,
coord_t perimeter_spacing,
coord_t min_perimeter_infill_spacing,
coord_t spacing,
bool is_inner_part )
2112{
2113 if( offset_ex(infill_contour, -float(spacing / 2.)).empty() )
2114 {
2115 infill_contour.clear(); // Infill region is too small, so let's filter it out.
2116 }
2117
2118 // create one more offset to be used as boundary for fill
2119 // we offset by half the perimeter spacing (to get to the actual infill boundary)
2120 // and then we offset back and forth by half the infill spacing to only consider the
2121 // non-collapsing regions
2122 coord_t insert = (loops < 0) ? 0: ext_perimeter_spacing;
2123 if (is_inner_part || loops > 0)
2124 insert = perimeter_spacing;
2125
2126 insert = coord_t(scale_(this->config->infill_wall_overlap.get_abs_value(unscale<double>(insert))));
2127
2128 Polygons inner_pp;
2129 for (ExPolygon &ex : infill_contour)
2130 ex.simplify_p(m_scaled_resolution, &inner_pp);
2131
2132 this->fill_surfaces->append(offset2_ex(union_ex(inner_pp), float(-min_perimeter_infill_spacing / 2.), float(insert + min_perimeter_infill_spacing / 2.)), stInternal);
2133
2134 append(*this->fill_no_overlap, offset2_ex(union_ex(inner_pp), float(-min_perimeter_infill_spacing / 2.), float(+min_perimeter_infill_spacing / 2.)));
2135}
#define scale_(val)
Definition libslic3r.h:106
int64_t coord_t
Definition libslic3r.h:48
std::vector< Polygon, PointsAllocator< Polygon > > Polygons
Definition Polygon.hpp:22
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, JoinType joinType, double miterLimit)
Definition ClipperUtils.cpp:605
T unscale(Q v)
Definition libslic3r.h:138
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, PolyFillType fill_type)
Definition ClipperUtils.cpp:1011
@ stInternal
Definition Surface.hpp:26
void append(std::vector< T, Alloc > &dest, const std::vector< T, Alloc2 > &src)
Definition libslic3r.h:157
ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1, const float delta2, JoinType joinType, double miterLimit)
Definition ClipperUtils.cpp:652
bool empty(const BoundingBoxBase< PointType, PointsType > &bb)
Definition BoundingBox.hpp:273

References add_infill_contour_for_arachne(), Slic3r::append(), config, Slic3r::empty(), fill_no_overlap, fill_surfaces, loops, m_scaled_resolution, Slic3r::offset2_ex(), Slic3r::offset_ex(), scale_, Slic3r::stInternal, Slic3r::union_ex(), and Slic3r::unscale().

Referenced by add_infill_contour_for_arachne().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ apply_extra_perimeters()

void Slic3r::PerimeterGenerator::apply_extra_perimeters ( ExPolygons & infill_area)
private
1334{
1335 if (!m_spiral_vase && this->lower_slices != nullptr && this->config->detect_overhang_wall && this->config->extra_perimeters_on_overhangs &&
1336 this->config->wall_loops > 0 && this->layer_id > this->object_config->raft_layers) {
1337 // Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material
1338 auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_area, this->lower_slices_polygons(),
1339 this->config->wall_loops, this->overhang_flow,
1340 this->m_scaled_resolution, *this->object_config,
1341 *this->print_config);
1342 if (!extra_perimeters.empty()) {
1343 ExtrusionEntityCollection *this_islands_perimeters = static_cast<ExtrusionEntityCollection *>(this->loops->entities.back());
1344 ExtrusionEntityCollection new_perimeters{};
1345 new_perimeters.no_sort = this_islands_perimeters->no_sort;
1346 for (const ExtrusionPaths &paths : extra_perimeters) {
1347 new_perimeters.append(paths);
1348 }
1349 new_perimeters.append(this_islands_perimeters->entities);
1350 this_islands_perimeters->swap(new_perimeters);
1351
1352 SurfaceCollection orig_surfaces = *this->fill_surfaces;
1353 this->fill_surfaces->clear();
1354 for (const auto &surface : orig_surfaces.surfaces) {
1355 auto new_surfaces = diff_ex({surface.expolygon}, filled_area);
1356 this->fill_surfaces->append(new_surfaces, surface);
1357 }
1358 }
1359 }
1360}
Polygons lower_slices_polygons() const
Definition PerimeterGenerator.hpp:164
std::tuple< std::vector< ExtrusionPaths >, Polygons > generate_extra_perimeters_over_overhangs(ExPolygons infill_area, const Polygons &lower_slices_polygons, int perimeter_count, const Flow &overhang_flow, double scaled_resolution, const PrintObjectConfig &object_config, const PrintConfig &print_config)
Definition PerimeterGenerator.cpp:1131
Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
Definition ClipperUtils.cpp:831
std::vector< ExtrusionPath > ExtrusionPaths
Definition ExtrusionEntity.hpp:395

References Slic3r::ExtrusionEntityCollection::append(), config, Slic3r::diff_ex(), Slic3r::ExtrusionEntityCollection::entities, fill_surfaces, Slic3r::generate_extra_perimeters_over_overhangs(), loops, lower_slices, lower_slices_polygons(), m_spiral_vase, Slic3r::ExtrusionEntityCollection::no_sort, Slic3r::SurfaceCollection::surfaces, and Slic3r::ExtrusionEntityCollection::swap().

Referenced by process_classic().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ ext_mm3_per_mm()

double Slic3r::PerimeterGenerator::ext_mm3_per_mm ( ) const
inline
159{ return m_ext_mm3_per_mm; }

References m_ext_mm3_per_mm.

Referenced by Slic3r::traverse_loops().

Here is the caller graph for this function:

◆ generate_lower_polygons_series()

std::vector< Polygons > Slic3r::PerimeterGenerator::generate_lower_polygons_series ( float width)
private
3113{
3114 float nozzle_diameter = print_config->nozzle_diameter.get_at(config->outer_wall_filament_id - 1);
3115 float start_offset = -0.5 * width;
3116 float end_offset = 0.5 * nozzle_diameter;
3117
3118 assert(overhang_sampling_number >= 3);
3119 // generate offsets
3120 std::vector<float> offset_series;
3121 offset_series.reserve(2);
3122
3123 offset_series.push_back(start_offset + 0.5 * (end_offset - start_offset) / (overhang_sampling_number - 1));
3124 offset_series.push_back(end_offset);
3125 std::vector<Polygons> lower_polygons_series;
3126 if (this->lower_slices == NULL) {
3127 return lower_polygons_series;
3128 }
3129
3130 // offset expolygon to generate series of polygons
3131 for (int i = 0; i < offset_series.size(); i++) {
3132 lower_polygons_series.emplace_back(offset(*this->lower_slices, float(scale_(offset_series[i]))));
3133 }
3134 return lower_polygons_series;
3135}
static const int overhang_sampling_number
Definition PerimeterGenerator.cpp:44
coord_t width(const BoundingBox &box)
Definition Arrange.cpp:1184
zaa_minimize_perimeter_height nozzle_diameter((ConfigOptionBool, reduce_infill_retraction))((ConfigOptionBool
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, JoinType joinType, double miterLimit)
Definition ClipperUtils.cpp:582

References config, generate_lower_polygons_series(), lower_slices, Slic3r::nozzle_diameter(), Slic3r::offset(), overhang_sampling_number, print_config, and scale_.

Referenced by generate_lower_polygons_series(), and process_classic().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ lower_slices_polygons()

Polygons Slic3r::PerimeterGenerator::lower_slices_polygons ( ) const
inline
164{ return m_lower_slices_polygons; }
Polygons m_lower_slices_polygons
Definition PerimeterGenerator.hpp:185

References m_lower_slices_polygons.

Referenced by apply_extra_perimeters(), and Slic3r::traverse_extrusions().

Here is the caller graph for this function:

◆ mm3_per_mm()

double Slic3r::PerimeterGenerator::mm3_per_mm ( ) const
inline
160{ return m_mm3_per_mm; }

References m_mm3_per_mm.

Referenced by Slic3r::traverse_loops().

Here is the caller graph for this function:

◆ mm3_per_mm_overhang()

double Slic3r::PerimeterGenerator::mm3_per_mm_overhang ( ) const
inline
161{ return m_mm3_per_mm_overhang; }

References m_mm3_per_mm_overhang.

Referenced by Slic3r::traverse_loops().

Here is the caller graph for this function:

◆ outer_walls_first_for_brim()

bool Slic3r::PerimeterGenerator::outer_walls_first_for_brim ( ) const
3103{
3104 return layer_id == 0 && brim_puts_outer_walls_first(object_config->brim_type.value, object_config->brim_width.value);
3105}
bool brim_puts_outer_walls_first(BrimType brim_type, double brim_width)
Definition PerimeterGenerator.cpp:3107

References Slic3r::brim_puts_outer_walls_first(), layer_id, object_config, and outer_walls_first_for_brim().

Referenced by outer_walls_first_for_brim(), process_classic(), and Slic3r::traverse_extrusions().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ printable_slices()

ExPolygons Slic3r::PerimeterGenerator::printable_slices ( const ExPolygons & slices) const
3091{
3092 double min_width = *std::min_element(print_config->nozzle_diameter.values.begin(),
3093 print_config->nozzle_diameter.values.end()) / 3.;
3094 if (object_config->wall_generator.value == PerimeterGeneratorType::Arachne) {
3096 // Spiral vase can put a classic layer under an Arachne one, so there both limits apply.
3097 min_width = print_config->spiral_mode ? std::min(min_width, min_feature_size) : min_feature_size;
3098 }
3099 return min_width > EPSILON ? opening_ex(slices, float(scale_(min_width / 2.))) : slices;
3100}
WallToolPathsParams make_paths_params(const int layer_id, const PrintObjectConfig &print_object_config, const PrintConfig &print_config)
Definition WallToolPaths.cpp:47
float min_feature_size
Definition WallToolPaths.hpp:32
Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, JoinType joinType=DefaultJoinType, double miterLimit=DefaultMiterLimit)
Definition ClipperUtils.hpp:447
@ Arachne
Definition PrintConfig.hpp:487

References Slic3r::Arachne, EPSILON, layer_id, Slic3r::Arachne::make_paths_params(), object_config, Slic3r::opening_ex(), print_config, printable_slices(), scale_, and slices.

Referenced by Slic3r::LayerRegion::make_perimeters(), and printable_slices().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ process_arachne()

void Slic3r::PerimeterGenerator::process_arachne ( )
2550{
2552
2553 // other perimeters
2554 m_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
2555 coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing();
2556
2557 // external perimeters
2558 m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm();
2559 coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
2560 coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
2561 coord_t ext_perimeter_spacing2 = scaled<coord_t>(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing()));
2562 // overhang perimeters
2563 m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm();
2564
2565 // solid infill
2566 coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing();
2567
2568 // prepare grown lower layer slices for overhang detection
2569 if (this->lower_slices != nullptr && this->config->detect_overhang_wall) {
2570 // We consider overhang any part where the entire nozzle diameter is not supported by the
2571 // lower layer, so we take lower slices and offset them by half the nozzle diameter used
2572 // in the current layer
2573 double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->outer_wall_filament_id - 1);
2575 }
2576
2577 Surfaces all_surfaces = this->slices->surfaces;
2578
2579 process_no_bridge(all_surfaces, perimeter_spacing, ext_perimeter_width);
2580 // BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled
2581 double surface_simplify_resolution = (print_config->enable_arc_fitting.value == ArcFittingMode::Enabled && !this->has_fuzzy_skin) ? 0.2 * m_scaled_resolution : m_scaled_resolution;
2582 // ORCA: neither one-wall option has a surface to act on without the shell behind it, see
2583 // has_top_shell_layers() / has_bottom_shell_layers(). Gated here so every use below - including the
2584 // topmost and first layers - sees the same answer.
2585 const bool only_one_wall_top = this->config->only_one_wall_top && has_top_shell_layers(*this->config);
2586 const bool only_one_wall_first_layer = this->config->only_one_wall_first_layer && has_bottom_shell_layers(*this->config);
2587 // we need to process each island separately because we might have different
2588 // extra perimeters for each one
2589 // Each island is independent up to its outputs, so they are generated in parallel - a layer split into thousands
2590 // of islands (e.g. by colour painting) otherwise ran on one thread - and the outputs are then committed in the
2591 // original island order, which is what the extra overhang perimeters (applied to the last island's loops and to
2592 // all fill surfaces so far) depend on.
2593 struct ArachneSurfaceResult
2594 {
2595 ExtrusionEntityCollection loops;
2596 bool has_loops = false;
2597 ExPolygons infill;
2598 ExPolygons no_overlap;
2599 };
2600 std::vector<ArachneSurfaceResult> results(all_surfaces.size());
2601 tbb::parallel_for(tbb::blocked_range<size_t>(0, all_surfaces.size()), [&](const tbb::blocked_range<size_t> &range) {
2602 for (size_t surface_idx = range.begin(); surface_idx < range.end(); ++surface_idx) {
2603 const Surface &surface = all_surfaces[surface_idx];
2604 ArachneSurfaceResult &result = results[surface_idx];
2605 coord_t bead_width_0 = ext_perimeter_spacing;
2606 // detect how many perimeters must be generated for this island
2607 int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops
2608 int sparse_infill_density = this->config->sparse_infill_density.value;
2609 if (this->config->alternate_extra_wall && this->layer_id % 2 == 1 && !m_spiral_vase && sparse_infill_density > 0) // add alternating extra wall
2610 loop_number++;
2611
2612 // Set the bottommost layer to be one wall
2613 const bool is_bottom_layer = (this->layer_id == object_config->raft_layers) ? true : false;
2614 if (is_bottom_layer && only_one_wall_first_layer)
2615 loop_number = 0;
2616
2617 // Orca: set the topmost layer to be one wall according to the config
2618 const bool is_topmost_layer = (this->upper_slices == nullptr) ? true : false;
2619 if (is_topmost_layer && loop_number > 0 && only_one_wall_top)
2620 loop_number = 0;
2621
2622 auto apply_precise_outer_wall = config->precise_outer_wall && config->wall_sequence == WallSequence::InnerOuter;
2623 // Orca: properly adjust offset for the outer wall if precise_outer_wall is enabled.
2624 ExPolygons last = offset_ex(surface.expolygon.simplify_p(surface_simplify_resolution),
2625 apply_precise_outer_wall? -float(ext_perimeter_width - ext_perimeter_spacing )
2626 : -float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.));
2627
2628 Arachne::WallToolPathsParams input_params = Arachne::make_paths_params(this->layer_id, *object_config, *print_config);
2629 // Set params is_top_or_bottom_layer for adjusting short-wall removal sensitivity.
2630 input_params.is_top_or_bottom_layer = (is_bottom_layer || is_topmost_layer) ? true : false;
2631
2632 coord_t wall_0_inset = 0;
2633 if (apply_precise_outer_wall)
2634 wall_0_inset = -coord_t(ext_perimeter_width / 2 - ext_perimeter_spacing / 2);
2635
2636 //PS: One wall top surface for Arachne
2637 ExPolygons top_expolygons;
2638 // Calculate how many inner loops remain when TopSurfaces is selected.
2639 const int inner_loop_number = (only_one_wall_top && upper_slices != nullptr) ? loop_number - 1 : -1;
2640
2641 // Set one perimeter when TopSurfaces is selected.
2642 if (only_one_wall_top && loop_number > 0)
2643 loop_number = 0;
2644
2645 Arachne::WallToolPathsParams input_params_tmp = input_params;
2646
2647 Polygons last_p = to_polygons(last);
2648 Arachne::WallToolPaths wallToolPaths(last_p, bead_width_0, perimeter_spacing, coord_t(loop_number + 1),
2649 wall_0_inset, layer_height, input_params_tmp);
2650 std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
2651 ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour());
2652
2653 // Check if there are some remaining perimeters to generate (the number of perimeters
2654 // is greater than one together with enabled the single perimeter on top surface feature).
2655 if (inner_loop_number >= 0) {
2656 assert(upper_slices != nullptr);
2657
2658 coord_t perimeter_width = this->perimeter_flow.scaled_width();
2659
2660 // Filter out areas that are too thin and expand top surface polygons a bit to hide the wall line.
2661 // ORCA: skip if the top surface area is smaller than "min_width_top_surface"
2662 const float top_surface_min_width = std::max<float>(float(ext_perimeter_spacing) / 4.f + scaled<float>(0.00001), float(scale_(config->min_width_top_surface.get_abs_value(unscale_(perimeter_width)))) / 4.f);
2663
2664 // Get top ExPolygons from the given contour. uncovered reports whether the upper layer leaves any of the
2665 // contour uncovered, before bridges and too thin areas are filtered out.
2666 auto get_top_expolygons = [&](const ExPolygons &contour, bool &uncovered) {
2667 // Contour bounding box.
2668 BoundingBox contour_bbox = get_extents(contour);
2669 contour_bbox.offset(SCALED_EPSILON);
2670
2671 Polygons upper_slices_clipped;
2672 if (object_config->interface_shells) {
2673 auto upper_slicer_same_region = to_expolygons(this->upper_slices_same_region->surfaces);
2674 upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(upper_slicer_same_region, contour_bbox);
2675 } else
2676 upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*upper_slices, contour_bbox);
2677
2678 ExPolygons top = diff_ex(contour, upper_slices_clipped);
2679 uncovered = !top.empty();
2680 if (top.empty())
2681 return top;
2682
2683 if (lower_slices != nullptr) {
2684 const float bridge_offset = float(std::max<coord_t>(ext_perimeter_spacing, perimeter_width));
2685 const Polygons lower_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*lower_slices, contour_bbox);
2686 const ExPolygons current_slices_bridges = offset_ex(diff_ex(top, lower_slices_clipped), bridge_offset);
2687
2688 // Remove bridges from top surface polygons.
2689 top = diff_ex(top, current_slices_bridges);
2690 }
2691
2692 // Shrink the polygon to remove the small areas, then expand it back out plus a maragin to hide the wall line a little.
2693 // ORCA: Expand the polygon with half the perimeter width in addition to the contracted amount,
2694 // not the full perimeter width as PS does, to enable thin lettering to print on the top surface without nozzle collisions
2695 // due to thin lines being generated
2696 top = offset2_ex(top, -top_surface_min_width, top_surface_min_width + float(perimeter_width * 0.85));
2697
2698 // Get final top ExPolygons (bridges were excluded above, so they stay walled).
2699 return intersection_ex(top, contour);
2700 };
2701
2702 // Walls with the full count, as generated when the single perimeter feature is disabled. Generated on first use.
2703 std::vector<Arachne::VariableWidthLines> full_perimeters;
2704 Polygons full_inner_contour;
2705 bool full_perimeters_generated = false;
2706 auto generate_full_perimeters = [&]() {
2707 if (full_perimeters_generated)
2708 return;
2709 Arachne::WallToolPaths full_tool_paths(last_p, bead_width_0, perimeter_spacing, coord_t(inner_loop_number + 2), wall_0_inset, layer_height, input_params_tmp);
2710 full_perimeters = full_tool_paths.getToolPaths();
2711 full_inner_contour = full_tool_paths.getInnerContour();
2712 full_perimeters_generated = true;
2713 };
2714
2715 // ORCA: the single wall pass allows Arachne 2 beads across a wall, so it fills a wall narrower than 3 outer wall
2716 // widths by widening both, where the full pass adds a middle bead. Over the top surface that is the intent;
2717 // anywhere else it leaves no room for the inner walls. When the single wall pass's outer walls run away from
2718 // the full pass's outside the top surface, take the full pass's outer walls and the area inside them instead.
2719 // Walls closer than outer_wall_tolerance count as the same wall: a widened bead's centerline moves by half
2720 // the width added, and only beads widened by more than twice the tolerance are looked for.
2721 const coord_t outer_wall_tolerance = bead_width_0 / 10;
2722 if (widest_bead(perimeters) > bead_width_0 + 2 * outer_wall_tolerance) {
2723 // The single wall pass's inner contour where it widens no bead: inside nominal width outer walls.
2724 const ExPolygons nominal_infill_contour = offset_ex(last, -float(bead_width_0 + wall_0_inset));
2725 bool nominal_uncovered = false;
2726 // Grown by an outer wall width to take in the outer walls bordering the top surface.
2727 const ExPolygons top_zone = offset_ex(get_top_expolygons(nominal_infill_contour, nominal_uncovered), float(bead_width_0));
2728 if (nominal_uncovered) {
2729 generate_full_perimeters();
2730 if (! full_perimeters.empty() && ! full_perimeters.front().empty() &&
2731 length_off_reference(perimeters, full_perimeters.front(), top_zone, outer_wall_tolerance) > double(perimeter_width)) {
2732 perimeters = { full_perimeters.front() };
2733 infill_contour = diff_ex(nominal_infill_contour, walls_footprint(full_perimeters.front()), ApplySafetyOffset::Yes);
2734 }
2735 }
2736 }
2737
2738 bool uncovered = false;
2739 top_expolygons = get_top_expolygons(infill_contour, uncovered);
2740
2741 if (uncovered) {
2742 // ORCA: onion the real region (inside the outer wall) so the remaining walls follow the actual
2743 // geometry, then cut away the parts over the top surface. Re-onioning the non-top complement
2744 // instead - the fallback when there is no top fill - walls the top/non-top interface and rings
2745 // top-surface islands with inner walls that don't exist when the feature is disabled.
2746 const bool clip_walls_over_top = top_fill_replaces_inner_walls(*this->config);
2747 const Polygons inner_region = to_polygons(offset_ex(clip_walls_over_top ? infill_contour
2748 : diff_ex(infill_contour, top_expolygons),
2749 wall_0_inset));
2750 Arachne::WallToolPaths inner_wall_tool_paths(inner_region, perimeter_spacing, perimeter_spacing, coord_t(inner_loop_number + 1), 0, layer_height, input_params_tmp);
2751 std::vector<Arachne::VariableWidthLines> inner_perimeters = inner_wall_tool_paths.getToolPaths();
2752
2753 if (clip_walls_over_top) {
2754 Polygons kept_over_top;
2755 clip_inner_walls_over_top(inner_perimeters, top_expolygons, perimeter_width, kept_over_top);
2756 // Route the top fill around the walls kept despite grazing the top.
2757 if (! kept_over_top.empty())
2758 top_expolygons = diff_ex(top_expolygons, kept_over_top);
2759 }
2760
2761 // Recalculate indexes of inner perimeters before merging them: they come after the single outer wall.
2762 if (!perimeters.empty())
2763 for (Arachne::VariableWidthLines &inner_perimeter : inner_perimeters)
2764 for (Arachne::ExtrusionLine &el : inner_perimeter)
2765 ++el.inset_idx;
2766
2767 perimeters.insert(perimeters.end(), inner_perimeters.begin(), inner_perimeters.end());
2768 infill_contour = union_ex(top_expolygons, inner_wall_tool_paths.getInnerContour());
2769 } else {
2770 // There is no top surface ExPolygon, so use the walls generated like when the single perimeter
2771 // feature is disabled.
2772 generate_full_perimeters();
2773 perimeters = std::move(full_perimeters);
2774 infill_contour = union_ex(full_inner_contour);
2775 }
2776 }
2777 //PS
2778
2779 loop_number = int(perimeters.size()) - 1;
2780
2781 #ifdef ARACHNE_DEBUG
2782 {
2783 static int iRun = 0;
2784 export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour()));
2785 }
2786 #endif
2787
2788 // All closed ExtrusionLine should have the same the first and the last point.
2789 // But in rare cases, Arachne produce ExtrusionLine marked as closed but without
2790 // equal the first and the last point.
2791 assert([&perimeters = std::as_const(perimeters)]() -> bool {
2792 for (const Arachne::VariableWidthLines& perimeter : perimeters)
2793 for (const Arachne::ExtrusionLine& el : perimeter)
2794 if (el.is_closed && el.junctions.front().p != el.junctions.back().p)
2795 return false;
2796 return true;
2797 }());
2798
2799 int start_perimeter = int(perimeters.size()) - 1;
2800 int end_perimeter = -1;
2801 int direction = -1;
2802
2803 bool is_outer_wall_first =
2804 this->config->wall_sequence == WallSequence::OuterInner ||
2805 this->config->wall_sequence == WallSequence::InnerOuterInner;
2806
2807 if (layer_id == 0){ // disable inner outer inner algorithm after the first layer
2808 is_outer_wall_first =
2809 this->config->wall_sequence == WallSequence::OuterInner || this->outer_walls_first_for_brim();
2810 }
2811 if (is_outer_wall_first) {
2812 start_perimeter = 0;
2813 end_perimeter = int(perimeters.size());
2814 direction = 1;
2815 }
2816
2817 std::vector<Arachne::ExtrusionLine*> all_extrusions;
2818 for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) {
2819 if (perimeters[perimeter_idx].empty())
2820 continue;
2821 for (Arachne::ExtrusionLine& wall : perimeters[perimeter_idx])
2822 all_extrusions.emplace_back(&wall);
2823 }
2824
2825 // Find topological order with constraints from extrusions_constrains.
2826 std::vector<size_t> blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion.
2827 std::vector<std::vector<size_t>> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion.
2828 std::unordered_map<const Arachne::ExtrusionLine*, size_t> map_extrusion_to_idx;
2829 for (size_t idx = 0; idx < all_extrusions.size(); idx++)
2830 map_extrusion_to_idx.emplace(all_extrusions[idx], idx);
2831
2832 auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, is_outer_wall_first);
2833 for (auto [before, after] : extrusions_constrains) {
2834 auto after_it = map_extrusion_to_idx.find(after);
2835 ++blocked[after_it->second];
2836 blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second);
2837 }
2838
2839 std::vector<bool> processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed.
2840 Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position.
2841 std::vector<PerimeterGeneratorArachneExtrusion> ordered_extrusions; // To store our result in. At the end we'll std::swap.
2842 ordered_extrusions.reserve(all_extrusions.size());
2843
2844 while (ordered_extrusions.size() < all_extrusions.size()) {
2845 size_t best_candidate = 0;
2846 double best_distance_sqr = std::numeric_limits<double>::max();
2847 bool is_best_closed = false;
2848
2849 std::vector<size_t> available_candidates;
2850 for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) {
2851 if (processed[candidate] || blocked[candidate])
2852 continue; // Not a valid candidate.
2853 available_candidates.push_back(candidate);
2854 }
2855
2856 std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool {
2857 return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed;
2858 });
2859
2860 for (const size_t candidate_path_idx : available_candidates) {
2861 auto& path = all_extrusions[candidate_path_idx];
2862
2863 if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end.
2864 if (best_distance_sqr == std::numeric_limits<double>::max()) {
2865 best_candidate = candidate_path_idx;
2866 is_best_closed = path->is_closed;
2867 }
2868 continue;
2869 }
2870
2871 const Point candidate_position = path->junctions.front().p;
2872 double distance_sqr = (current_position - candidate_position).cast<double>().norm();
2873 if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far.
2874 if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits<double>::max()) || (!path->is_closed && !is_best_closed)) {
2875 best_candidate = candidate_path_idx;
2876 best_distance_sqr = distance_sqr;
2877 is_best_closed = path->is_closed;
2878 }
2879 }
2880 }
2881
2882 auto& best_path = all_extrusions[best_candidate];
2883 ordered_extrusions.push_back({ best_path, best_path->is_contour() });
2884 processed[best_candidate] = true;
2885 for (size_t unlocked_idx : blocking[best_candidate])
2886 blocked[unlocked_idx]--;
2887
2888 if (!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then.
2889 if (best_path->is_closed)
2890 current_position = best_path->junctions[0].p; //We end where we started.
2891 else
2892 current_position = best_path->junctions.back().p; //Pick the other end from where we started.
2893 }
2894 }
2895
2896 // printf("New Layer: Layer ID %d\n",layer_id); //debug - new layer
2897 if (this->config->wall_sequence == WallSequence::InnerOuterInner && layer_id > 0) { // only enable inner outer inner algorithm after first layer
2898 if (ordered_extrusions.size() > 2) { // 3 walls minimum needed to do inner outer inner ordering
2899 int position = 0; // index to run the re-ordering for multiple external perimeters in a single island.
2900 int arr_i, arr_j = 0; // indexes to run through the walls in the for loops
2901 int outer, first_internal, second_internal, max_internal, current_perimeter; // allocate index values
2902
2903 // To address any remaining scenarios where the outer perimeter contour is not first on the list as arachne sometimes reorders the perimeters when clustering
2904 // for OI mode that is used the basis for IOI
2905 bringContoursToFront(ordered_extrusions);
2906 std::vector<PerimeterGeneratorArachneExtrusion> reordered_extrusions;
2907
2908 // Debug statement to print spacing values:
2909 //printf("External threshold - Ext perimeter: %d Ext spacing: %d Int perimeter: %d Int spacing: %d\n", this->ext_perimeter_flow.scaled_width(),this->ext_perimeter_flow.scaled_spacing(),this->perimeter_flow.scaled_width(), this->perimeter_flow.scaled_spacing());
2910
2911 // Get searching thresholds. For an external perimeter we take the external perimeter spacing/2 plus the internal perimeter spacing/2 and expand by the factor
2912 // rounding errors. When precise wall is enabled, the external perimeter full spacing is used.
2913 coord_t threshold_external = (apply_precise_outer_wall)
2914 // Precise outer wall: use the full external spacing
2915 ? ( this->ext_perimeter_flow.scaled_spacing()
2916 + this->perimeter_flow.scaled_spacing()/2.0 )
2917 // Normal: half ext spacing plus half int spacing
2918 : ( this->ext_perimeter_flow.scaled_spacing()/2.0
2919 + this->perimeter_flow.scaled_spacing()/2.0 );
2920
2921 // For the intenal perimeter threshold, the distance is the internal perimeter spacing expanded by the factor to cover rounding errors.
2922 coord_t threshold_internal = this->perimeter_flow.scaled_spacing();
2923
2924 // Re-order extrusions based on distance
2925 // Alorithm will aggresively optimise for the appearance of the outermost perimeter
2926 ordered_extrusions = reorderPerimetersByProximity(ordered_extrusions,threshold_external,threshold_internal );
2927 reordered_extrusions = ordered_extrusions; // copy them into the reordered extrusions vector to allow for IOI operations to be performed below without altering the base ordered extrusions list.
2928
2929 // Now start the sandwich mode wall re-ordering using the reordered_extrusions as the basis
2930 // scan to find the external perimeter, first internal, second internal and last perimeter in the island.
2931 // We then advance the position index to move to the second island and continue until there are no more
2932 // perimeters left.
2933 while (position < reordered_extrusions.size()) {
2934 outer = first_internal = second_internal = current_perimeter = -1; // initialise all index values to -1
2935 max_internal = reordered_extrusions.size()-1; // initialise the maximum internal perimeter to the last perimeter on the extrusion list
2936 // run through the walls to get the index values that need re-ordering until the first one for each
2937 // is found. Start at "position" index to enable the for loop to iterate for multiple external
2938 // perimeters in a single island
2939 // printf("Reorder Loop. Position %d, extrusion list size: %d, Outer index %d, inner index %d, second inner index %d\n", position, reordered_extrusions.size(),outer,first_internal,second_internal);
2940 for (arr_i = position; arr_i < reordered_extrusions.size(); ++arr_i) {
2941 // printf("Perimeter: extrusion inset index %d, ordered extrusions array position %d\n",reordered_extrusions[arr_i].extrusion->inset_idx, arr_i);
2942 switch (reordered_extrusions[arr_i].extrusion->inset_idx) {
2943 case 0: // external perimeter
2944 if (outer == -1)
2945 outer = arr_i;
2946 break;
2947 case 1: // first internal wall
2948 if (first_internal==-1 && arr_i>outer && outer!=-1){
2949 first_internal = arr_i;
2950 }
2951 break;
2952 case 2: // second internal wall
2953 if (second_internal == -1 && arr_i > first_internal && outer!=-1){
2954 second_internal = arr_i;
2955 }
2956 break;
2957 }
2958 if(outer >-1 && first_internal>-1 && reordered_extrusions[arr_i].extrusion->inset_idx == 0){ // found a new external perimeter after we've found at least a first internal perimeter to re-order.
2959 // This means we entered a new island.
2960 arr_i=arr_i-1; //step back one perimeter
2961 max_internal = arr_i; // new maximum internal perimeter is now this as we have found a new external perimeter, hence a new island.
2962 break; // exit the for loop
2963 }
2964 }
2965
2966 // printf("Layer ID %d, Outer index %d, inner index %d, second inner index %d, maximum internal perimeter %d \n",layer_id,outer,first_internal,second_internal, max_internal);
2967 if (outer > -1 && first_internal > -1 && second_internal > -1) { // found all three perimeters to re-order? If not the perimeters will be processed outside in.
2968 std::vector<PerimeterGeneratorArachneExtrusion> inner_outer_extrusions; // temporary array to hold extrusions for reordering
2969 inner_outer_extrusions.resize(max_internal - position + 1); // reserve array containing the number of perimeters before a new island. Variables are array indexes hence need to add +1 to convert to position allocations
2970 // printf("Allocated array size %d, max_internal index %d, start position index %d \n",max_internal-position+1,max_internal,position);
2971
2972 for (arr_j = max_internal; arr_j >=position; --arr_j){ // go inside out towards the external perimeter (perimeters in reverse order) and store all internal perimeters until the first one identified with inset index 2
2973 if(arr_j >= second_internal){
2974 //printf("Inside out loop: Mapped perimeter index %d to array position %d\n", arr_j, max_internal-arr_j);
2975 inner_outer_extrusions[max_internal-arr_j] = reordered_extrusions[arr_j];
2976 current_perimeter++;
2977 }
2978 }
2979
2980 for (arr_j = position; arr_j < second_internal; ++arr_j){ // go outside in and map the remaining perimeters (external and first internal wall(s)) using the outside in wall order
2981 // printf("Outside in loop: Mapped perimeter index %d to array position %d\n", arr_j, current_perimeter+1);
2982 inner_outer_extrusions[++current_perimeter] = reordered_extrusions[arr_j];
2983 }
2984
2985 for(arr_j = position; arr_j <= max_internal; ++arr_j) // replace perimeter array with the new re-ordered array
2986 ordered_extrusions[arr_j] = inner_outer_extrusions[arr_j-position];
2987 }
2988 // go to the next perimeter from the current position to continue scanning for external walls in the same island
2989 position = arr_i + 1;
2990 }
2991 }
2992 }
2993
2994 bool steep_overhang_contour = false;
2995 bool steep_overhang_hole = false;
2996 if (!config->overhang_reverse) {
2997 // Skip steep overhang detection no reverse is specified
2998 steep_overhang_contour = true;
2999 steep_overhang_hole = true;
3000 }
3001 if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(*this, ordered_extrusions, steep_overhang_contour, steep_overhang_hole); !extrusion_coll.empty()) {
3002 if (config->overhang_reverse) {
3003 reorient_perimeters(extrusion_coll, steep_overhang_contour, steep_overhang_hole,
3004 this->config->overhang_reverse_internal_only);
3005 }
3006 defer_unsupported_loops(*this, extrusion_coll);
3007 result.loops = std::move(extrusion_coll);
3008 result.has_loops = true;
3009 }
3010
3011 const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
3012
3013 if (offset_ex(infill_contour, -float(spacing / 2.)).empty())
3014 infill_contour.clear(); // Infill region is too small, so let's filter it out.
3015
3016 // create one more offset to be used as boundary for fill
3017 // we offset by half the perimeter spacing (to get to the actual infill boundary)
3018 // and then we offset back and forth by half the infill spacing to only consider the
3019 // non-collapsing regions
3020 coord_t inset =
3021 (loop_number < 0) ? 0 :
3022 (loop_number == 0) ?
3023 // one loop
3024 ext_perimeter_spacing :
3025 // two or more loops?
3026 perimeter_spacing;
3027 coord_t top_inset = inset;
3028
3029 top_inset = coord_t(scale_(this->config->top_bottom_infill_wall_overlap.get_abs_value(unscale<double>(inset))));
3030 if(is_topmost_layer || is_bottom_layer)
3031 inset = coord_t(scale_(this->config->top_bottom_infill_wall_overlap.get_abs_value(unscale<double>(inset))));
3032 else
3033 inset = coord_t(scale_(this->config->infill_wall_overlap.get_abs_value(unscale<double>(inset))));
3034
3035 // simplify infill contours according to resolution
3036 Polygons pp;
3037 for (ExPolygon& ex : infill_contour)
3038 ex.simplify_p(m_scaled_resolution, &pp);
3039 ExPolygons not_filled_exp = union_ex(pp);
3040 // collapse too narrow infill areas
3041 const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
3042
3043 ExPolygons infill_exp = offset2_ex(
3044 not_filled_exp,
3045 float(-min_perimeter_infill_spacing / 2.),
3046 float(inset + min_perimeter_infill_spacing / 2.));
3047 // append infill areas to fill_surfaces
3048 if (!top_expolygons.empty()) {
3049 infill_exp = union_ex(infill_exp, offset_ex(top_expolygons, double(top_inset)));
3050 }
3051 result.infill = std::move(infill_exp);
3052
3053 // BBS: get the no-overlap infill expolygons
3054 {
3055 ExPolygons polyWithoutOverlap;
3056 polyWithoutOverlap = offset2_ex(
3057 not_filled_exp,
3058 float(-min_perimeter_infill_spacing / 2.),
3059 float(+min_perimeter_infill_spacing / 2.));
3060 if (!top_expolygons.empty())
3061 polyWithoutOverlap = union_ex(polyWithoutOverlap, top_expolygons);
3062 result.no_overlap = std::move(polyWithoutOverlap);
3063 }
3064 }
3065 });
3066 for (ArachneSurfaceResult &result : results) {
3067 if (result.has_loops)
3068 // Moved, not copied: append(const ExtrusionEntity &) clones the whole wall tree of the island.
3069 this->loops->append(std::move(result.loops));
3070 this->fill_surfaces->append(result.infill, stInternal);
3071 apply_extra_perimeters(result.infill);
3072 append(*this->fill_no_overlap, std::move(result.no_overlap));
3073 }
3074}
bool has_fuzzy_skin
Definition PerimeterGenerator.hpp:115
void apply_extra_perimeters(ExPolygons &infill_area)
Definition PerimeterGenerator.cpp:1333
void process_no_bridge(Surfaces &all_surfaces, coord_t perimeter_spacing, coord_t ext_perimeter_width)
Definition PerimeterGenerator.cpp:2138
void group_region_by_fuzzify(PerimeterGenerator &g)
Definition FuzzySkin.cpp:430
static bool has_bottom_shell_layers(const PrintRegionConfig &config)
Definition PerimeterGenerator.cpp:628
std::vector< ExPolygon > ExPolygons
Definition ExPolygon.hpp:20
@ Enabled
Definition PrintConfig.hpp:240
auto range(Cont &&cont)
Definition libslic3r.h:405
static bool has_top_shell_layers(const PrintRegionConfig &config)
Definition PerimeterGenerator.cpp:619
std::vector< Surface > Surfaces
Definition Surface.hpp:123

References config, Slic3r::Enabled, ext_perimeter_flow, Slic3r::Feature::FuzzySkin::group_region_by_fuzzify(), Slic3r::has_bottom_shell_layers(), has_fuzzy_skin, Slic3r::has_top_shell_layers(), loops, lower_slices, m_ext_mm3_per_mm, m_lower_slices_polygons, m_mm3_per_mm, m_mm3_per_mm_overhang, m_scaled_resolution, Slic3r::nozzle_diameter(), Slic3r::offset(), overhang_flow, perimeter_flow, print_config, process_arachne(), process_no_bridge(), Slic3r::range(), scale_, Slic3r::scaled(), slices, and solid_infill_flow.

Referenced by Slic3r::LayerRegion::make_perimeters(), and process_arachne().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ process_classic()

void Slic3r::PerimeterGenerator::process_classic ( )
1458{
1460
1461 // other perimeters
1462 m_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
1463 coord_t perimeter_width = this->perimeter_flow.scaled_width();
1464 coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing();
1465
1466 // external perimeters
1467 m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm();
1468 coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
1469
1470 coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
1471 coord_t ext_perimeter_spacing2;
1472 // Orca: ignore precise_outer_wall if wall_sequence is not InnerOuter
1473 if(config->precise_outer_wall && config->wall_sequence == WallSequence::InnerOuter)
1474 ext_perimeter_spacing2 = scaled<coord_t>(0.5f * (this->ext_perimeter_flow.width() + this->perimeter_flow.width()));
1475 else
1476 ext_perimeter_spacing2 = scaled<coord_t>(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing()));
1477
1478 // overhang perimeters
1479 m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm();
1480
1481 // solid infill
1482 coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing();
1483
1484 // prepare grown lower layer slices for overhang detection
1485 if (this->lower_slices != nullptr && this->config->detect_overhang_wall) {
1486 // We consider overhang any part where the entire nozzle diameter is not supported by the
1487 // lower layer, so we take lower slices and offset them by half the nozzle diameter used
1488 // in the current layer
1489 double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->outer_wall_filament_id - 1);
1491 }
1492
1493 // Calculate the minimum required spacing between two adjacent traces.
1494 // This should be equal to the nominal flow spacing but we experiment
1495 // with some tolerance in order to avoid triggering medial axis when
1496 // some squishing might work. Loops are still spaced by the entire
1497 // flow spacing; this only applies to collapsing parts.
1498 // For ext_min_spacing we use the ext_perimeter_spacing calculated for two adjacent
1499 // external loops (which is the correct way) instead of using ext_perimeter_spacing2
1500 // which is the spacing between external and internal, which is not correct
1501 // and would make the collapsing (thus the details resolution) dependent on
1502 // internal flow which is unrelated.
1503 coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
1504 coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
1505 bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->outer_wall_filament_id - 1)) > 0;
1506 // ORCA: Use the smaller width as the lower bound to avoid overestimating safe overlap
1507 const double gap_fill_min_width = 0.2 * std::min(perimeter_width, ext_perimeter_width) * (1 - INSET_OVERLAP_TOLERANCE);
1508
1509 // BBS: this flow is for smaller external perimeter for small area
1510 coord_t ext_min_spacing_smaller = coord_t(ext_perimeter_spacing * (1 - SMALLER_EXT_INSET_OVERLAP_TOLERANCE));
1512 // BBS: to be checked
1514 (ext_perimeter_width - 0.5 * SMALLER_EXT_INSET_OVERLAP_TOLERANCE * ext_perimeter_spacing));
1516
1517 // prepare grown lower layer slices for overhang detection
1519 if (ext_perimeter_width == perimeter_width){
1521 } else {
1523 }
1525 // we need to process each island separately because we might have different
1526 // extra perimeters for each one
1527 Surfaces all_surfaces = this->slices->surfaces;
1528
1529 process_no_bridge(all_surfaces, perimeter_spacing, ext_perimeter_width);
1530 // BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled
1531 double surface_simplify_resolution = (print_config->enable_arc_fitting.value == ArcFittingMode::Enabled && !this->has_fuzzy_skin) ? 0.2 * m_scaled_resolution : m_scaled_resolution;
1532 //BBS: reorder the surface to reduce the travel time
1533 ExPolygons surface_exp;
1534 for (const Surface &surface : all_surfaces)
1535 surface_exp.push_back(surface.expolygon);
1536 std::vector<size_t> surface_order = chain_expolygons(surface_exp);
1537 // ORCA: neither one-wall option has a surface to act on without the shell behind it, see
1538 // has_top_shell_layers() / has_bottom_shell_layers(). Gated here so every use below - including the
1539 // topmost and first layers - sees the same answer.
1540 const bool only_one_wall_top = this->config->only_one_wall_top && has_top_shell_layers(*this->config);
1541 const bool only_one_wall_first_layer = this->config->only_one_wall_first_layer && has_bottom_shell_layers(*this->config);
1542 for (size_t order_idx = 0; order_idx < surface_order.size(); order_idx++) {
1543 const Surface &surface = all_surfaces[surface_order[order_idx]];
1544 // detect how many perimeters must be generated for this island
1545 int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops
1546 int sparse_infill_density = this->config->sparse_infill_density.value;
1547 if (this->config->alternate_extra_wall && this->layer_id % 2 == 1 && !m_spiral_vase && sparse_infill_density > 0) // add alternating extra wall
1548 loop_number++;
1549 if (this->layer_id == object_config->raft_layers && only_one_wall_first_layer)
1550 loop_number = 0;
1551 // Set the topmost layer to be one wall
1552 if (loop_number > 0 && only_one_wall_top && this->upper_slices == nullptr)
1553 loop_number = 0;
1554
1555 ExPolygons last = union_ex(surface.expolygon.simplify_p(surface_simplify_resolution));
1556 ExPolygons gaps;
1557 ExPolygons top_fills;
1558 ExPolygons fill_clip;
1559 // ORCA: only_one_wall_top, all empty unless this island has a top surface on this layer. See the
1560 // post-onion reduction below: the region to keep clear of inner walls, the space freed by the dropped
1561 // walls (goes to infill, not left as a void) and the space held by the kept ones (withheld from the fill).
1562 ExPolygons one_wall_top_region;
1563 ExPolygons one_wall_top_reclaimed;
1564 Polygons one_wall_top_kept_bands;
1565 bool apply_one_wall_top = false;
1566 if (loop_number >= 0) {
1567 // In case no perimeters are to be generated, loop_number will equal to -1.
1568 std::vector<PerimeterGeneratorLoops> contours(loop_number+1); // depth => loops
1569 std::vector<PerimeterGeneratorLoops> holes(loop_number+1); // depth => loops
1570 ThickPolylines thin_walls;
1571 // we loop one time more than needed in order to find gaps after the last perimeter was applied
1572 for (int i = 0;; ++ i) { // outer loop is 0
1573 // Calculate next onion shell of perimeters.
1574 ExPolygons offsets;
1575 ExPolygons offsets_with_smaller_width;
1576 if (i == 0) {
1577 // look for thin walls
1578 if (this->config->detect_thin_wall) {
1579 // the minimum thickness of a single loop is:
1580 // ext_width/2 + ext_spacing/2 + spacing/2 + width/2
1581 offsets = offset2_ex(last,
1582 -float(ext_perimeter_width / 2. + ext_min_spacing / 2. - 1),
1583 +float(ext_min_spacing / 2. - 1));
1584 // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
1585 // (actually, something larger than that still may exist due to mitering or other causes)
1586 coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3));
1587 ExPolygons expp = opening_ex(
1588 // medial axis requires non-overlapping geometry
1589 diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)),
1590 float(min_width / 2.));
1591 // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
1592 for (ExPolygon &ex : expp)
1593 ex.medial_axis(min_width, ext_perimeter_width + ext_perimeter_spacing2, &thin_walls);
1594 } else {
1595 coord_t ext_perimeter_smaller_width = this->smaller_ext_perimeter_flow.scaled_width();
1596 for (const ExPolygon& expolygon : last) {
1597 // BBS: judge whether it's narrow but not too long island which is hard to place two line
1598 ExPolygons expolys;
1599 expolys.push_back(expolygon);
1600 ExPolygons offset_result = offset2_ex(expolys,
1601 -float(ext_perimeter_width / 2. + ext_min_spacing_smaller / 2.),
1602 +float(ext_min_spacing_smaller / 2.));
1603 if (offset_result.empty() &&
1604 expolygon.area() < (double)(ext_perimeter_width + ext_min_spacing_smaller) * scale_(narrow_loop_length_threshold)) {
1605 // BBS: for narrow external loop, use smaller line width
1606 ExPolygons temp_result = offset_ex(expolygon, -float(ext_perimeter_smaller_width / 2.));
1607 offsets_with_smaller_width.insert(offsets_with_smaller_width.end(), temp_result.begin(), temp_result.end());
1608 }
1609 else {
1610 //BBS: for not narrow loop, use normal external perimeter line width
1611 ExPolygons temp_result = offset_ex(expolygon, -float(ext_perimeter_width / 2.));
1612 offsets.insert(offsets.end(), temp_result.begin(), temp_result.end());
1613 }
1614 }
1615 }
1616 if (m_spiral_vase && (offsets.size() > 1 || offsets_with_smaller_width.size() > 1)) {
1617 // Remove all but the largest area polygon.
1619 //BBS
1620 if (offsets.empty())
1621 //BBS: only have small width loop, then keep the largest in spiral vase mode
1622 keep_largest_contour_only(offsets_with_smaller_width);
1623 else
1624 //BBS: have large area, clean the small width loop
1625 offsets_with_smaller_width.clear();
1626 }
1627 } else {
1628 //FIXME Is this offset correct if the line width of the inner perimeters differs
1629 // from the line width of the infill?
1630 coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
1631 //BBS
1632 //offsets = this->config->thin_walls ?
1633 // This path will ensure, that the perimeters do not overfill, as in
1634 // prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters
1635 // excessively, creating gaps, which then need to be filled in by the not very
1636 // reliable gap fill algorithm.
1637 // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than
1638 // the original.
1639 //offset2_ex(last,
1640 // - float(distance + min_spacing / 2. - 1.),
1641 // float(min_spacing / 2. - 1.)) :
1642 // If "detect thin walls" is not enabled, this paths will be entered, which
1643 // leads to overflows, as in prusa3d/Slic3r GH #32
1644 //offset_ex(last, - float(distance));
1645
1646 //BBS: For internal perimeter, we should "enable" thin wall strategy in which offset2 is used to
1647 // remove too closed line, so that gap fill can be used for such internal narrow area in following
1648 // handling.
1649 offsets = offset2_ex(last,
1650 -float(distance + min_spacing / 2. - 1.),
1651 float(min_spacing / 2. - 1.));
1652 // look for gaps
1653 if (has_gap_fill)
1654 // not using safety offset here would "detect" very narrow gaps
1655 // (but still long enough to escape the area threshold) that gap fill
1656 // won't be able to fill but we'd still remove from infill area
1657 append(gaps, diff_ex(
1658 offset(last, - float(0.5 * distance)),
1659 offset(offsets, float(0.5 * distance + 10)))); // safety offset
1660 }
1661 if (offsets.empty() && offsets_with_smaller_width.empty()) {
1662 // Store the number of loops actually generated.
1663 loop_number = i - 1;
1664 // No region left to be filled in.
1665 last.clear();
1666 break;
1667 } else if (i > loop_number) {
1668 // If i > loop_number, we were looking just for gaps.
1669 break;
1670 }
1671 {
1672 for (const ExPolygon& expolygon : offsets) {
1673 // Outer contour may overlap with an inner contour,
1674 // inner contour may overlap with another inner contour,
1675 // outer contour may overlap with itself.
1676 //FIXME evaluate the overlaps, annotate each point with an overlap depth,
1677 // compensate for the depth of intersection.
1678 contours[i].emplace_back(expolygon.contour, i, true);
1679
1680 if (!expolygon.holes.empty()) {
1681 holes[i].reserve(holes[i].size() + expolygon.holes.size());
1682 for (const Polygon& hole : expolygon.holes)
1683 holes[i].emplace_back(hole, i, false);
1684 }
1685 }
1686
1687 //BBS: save perimeter loop which use smaller width
1688 if (i == 0) {
1689 for (const ExPolygon& expolygon : offsets_with_smaller_width) {
1690 contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, true));
1691 if (!expolygon.holes.empty()) {
1692 holes[i].reserve(holes[i].size() + expolygon.holes.size());
1693 for (const Polygon& hole : expolygon.holes)
1694 holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false, true));
1695 }
1696 }
1697 }
1698 }
1699
1700 last = std::move(offsets);
1701
1702 // ORCA: a thin wall hole has no room for gap fill or an inner wall anywhere beside its outer wall, a
1703 // narrow wall hole none for an inner wall, the room the next loop is offset by below.
1704 if (i == 0 && ! holes[0].empty()) {
1705 auto reach = [&last](double room) {
1706 return to_polygons(offset2_ex(last, -float(room), float(room) + float(SCALED_EPSILON)));
1707 };
1708 auto out_of_reach = [](const Polygon &hole, const Polygons &reach) {
1709 return intersection_pl(Polylines{ hole.split_at_first_point() },
1711 };
1712 // Only Inner/Outer/Inner orders narrow wall holes apart.
1713 const bool narrow_holes_ordered = this->config->wall_sequence == WallSequence::InnerOuterInner;
1714 const Polygons gap_fill_reach = reach(0.5 * (ext_perimeter_spacing2 + gap_fill_min_width));
1715 const Polygons inner_wall_reach = narrow_holes_ordered ? reach(ext_perimeter_spacing2 + min_spacing / 2.) : Polygons();
1716 for (PerimeterGeneratorLoop &hole : holes[0]) {
1717 hole.is_thin_wall_hole = out_of_reach(hole.polygon, gap_fill_reach);
1718 hole.is_narrow_wall_hole = narrow_holes_ordered && out_of_reach(hole.polygon, inner_wall_reach);
1719 }
1720 }
1721
1722 //BBS: refer to superslicer
1723 //store surface for top infill if only_one_wall_top
1724 if (i == 0 && i!=loop_number && only_one_wall_top && !surface.is_bridge() && this->upper_slices != NULL) {
1726 // ORCA: take the top fill and the keep-out region but leave `last` as the real geometry,
1727 // so the onion follows it and the walls over the top are reduced in one step below.
1728 ExPolygons non_top_polygons;
1729 this->split_top_surfaces(last, top_fills, non_top_polygons, fill_clip);
1730 apply_one_wall_top = !top_fills.empty();
1731 if (apply_one_wall_top)
1732 one_wall_top_region = diff_ex(last, non_top_polygons);
1733 } else {
1734 // Onion the not-top region only, so the remaining walls stop at the top boundary.
1735 this->split_top_surfaces(last, top_fills, last, fill_clip);
1736 }
1737 }
1738
1739 if (i == loop_number && (! has_gap_fill || this->config->sparse_infill_density.value == 0)) {
1740 // The last run of this loop is executed to collect gaps for gap fill.
1741 // As the gap fill is either disabled or not
1742 break;
1743 }
1744 }
1745
1746 // ORCA: only_one_wall_top reduction - drop the inner walls (depth > 0) running over the top surface and
1747 // take that space back from the gaps, leaving the top with the outer wall and the top infill. Classic
1748 // perimeters are closed loops, so a wall can only be kept or dropped whole; one that merely grazes the
1749 // top (same tolerance as the Arachne clip) is kept and withheld from the top fill instead.
1750 if (apply_one_wall_top) {
1751 const BoundingBox top_region_bbox = get_extents(one_wall_top_region).inflated(SCALED_EPSILON);
1752 const double grazing_tolerance = 2. * double(perimeter_width);
1753 // The band a wall covers, taken around its centerline so the orientation of holes does not matter.
1754 auto wall_band = [perimeter_spacing](const Polygon &poly) {
1755 Polygon centerline = poly;
1756 centerline.make_counter_clockwise();
1757 return diff(offset(centerline, float(perimeter_spacing) / 2.f),
1758 offset(centerline, -float(perimeter_spacing) / 2.f));
1759 };
1760 Polygons dropped_wall_bands;
1761 auto reduce_over_top = [&](PerimeterGeneratorLoops &loops) {
1762 loops.erase(std::remove_if(loops.begin(), loops.end(), [&](const PerimeterGeneratorLoop &loop) {
1763 const TopOverlap overlap = classify_over_top(loop.polygon.points, one_wall_top_region, top_region_bbox);
1764 if (overlap == TopOverlap::None)
1765 return false;
1766 // Only a wall straddling the boundary is worth measuring; a wall wholly over the top goes.
1767 if (overlap == TopOverlap::Partial &&
1768 total_length(intersection_pl(Polylines{ loop.polygon.split_at_first_point() }, one_wall_top_region)) < grazing_tolerance) {
1769 append(one_wall_top_kept_bands, wall_band(loop.polygon));
1770 return false;
1771 }
1772 append(dropped_wall_bands, wall_band(loop.polygon));
1773 return true;
1774 }), loops.end());
1775 };
1776 for (int d = 1; d <= loop_number; ++ d) {
1777 reduce_over_top(contours[d]);
1778 reduce_over_top(holes[d]);
1779 }
1780 if (! gaps.empty())
1781 gaps = diff_ex(gaps, one_wall_top_region);
1782 if (! dropped_wall_bands.empty())
1783 one_wall_top_reclaimed = diff_ex(dropped_wall_bands, one_wall_top_region);
1784 }
1785
1786 // nest loops: holes first
1787 for (int d = 0; d <= loop_number; ++ d) {
1788 PerimeterGeneratorLoops &holes_d = holes[d];
1789 // loop through all holes having depth == d
1790 for (int i = 0; i < (int)holes_d.size(); ++ i) {
1791 const PerimeterGeneratorLoop &loop = holes_d[i];
1792 // find the hole loop that contains this one, if any
1793 for (int t = d + 1; t <= loop_number; ++ t) {
1794 for (int j = 0; j < (int)holes[t].size(); ++ j) {
1795 PerimeterGeneratorLoop &candidate_parent = holes[t][j];
1796 if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
1797 candidate_parent.children.push_back(loop);
1798 holes_d.erase(holes_d.begin() + i);
1799 -- i;
1800 goto NEXT_LOOP;
1801 }
1802 }
1803 }
1804 // if no hole contains this hole, find the contour loop that contains it
1805 for (int t = loop_number; t >= 0; -- t) {
1806 for (int j = 0; j < (int)contours[t].size(); ++ j) {
1807 PerimeterGeneratorLoop &candidate_parent = contours[t][j];
1808 if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
1809 candidate_parent.children.push_back(loop);
1810 holes_d.erase(holes_d.begin() + i);
1811 -- i;
1812 goto NEXT_LOOP;
1813 }
1814 }
1815 }
1816 NEXT_LOOP: ;
1817 }
1818 }
1819 // nest contour loops
1820 for (int d = loop_number; d >= 1; -- d) {
1821 PerimeterGeneratorLoops &contours_d = contours[d];
1822 // loop through all contours having depth == d
1823 for (int i = 0; i < (int)contours_d.size(); ++ i) {
1824 const PerimeterGeneratorLoop &loop = contours_d[i];
1825 // find the contour loop that contains it
1826 for (int t = d - 1; t >= 0; -- t) {
1827 for (size_t j = 0; j < contours[t].size(); ++ j) {
1828 PerimeterGeneratorLoop &candidate_parent = contours[t][j];
1829 if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
1830 candidate_parent.children.push_back(loop);
1831 contours_d.erase(contours_d.begin() + i);
1832 -- i;
1833 goto NEXT_CONTOUR;
1834 }
1835 }
1836 }
1837 NEXT_CONTOUR: ;
1838 }
1839 }
1840 // at this point, all loops should be in contours[0]
1841 bool steep_overhang_contour = false;
1842 bool steep_overhang_hole = false;
1843 if (!config->overhang_reverse) {
1844 // Skip steep overhang detection no reverse is specified
1845 steep_overhang_contour = true;
1846 steep_overhang_hole = true;
1847 }
1848 ExtrusionEntityCollection entities = traverse_loops(*this, contours.front(), thin_walls, steep_overhang_contour, steep_overhang_hole);
1849 // All walls are counter-clockwise initially, so we don't need to reorient it if that's what we want
1850 if (config->overhang_reverse) {
1851 reorient_perimeters(entities, steep_overhang_contour, steep_overhang_hole,
1852 // Reverse internal only if the wall direction is auto
1853 this->config->overhang_reverse_internal_only);
1854 }
1855
1856 // if brim will be printed, reverse the order of perimeters so that
1857 // we continue inwards after having finished the brim
1858 bool is_outer_wall_first = this->config->wall_sequence == WallSequence::OuterInner;
1859 // Orca: a contour with only a narrow wall hole beside it has nothing to sandwich, and prints the hole first,
1860 // then the contour, then the gap fill between them, as Arachne does.
1861 const PerimeterGeneratorLoops &island_loops = contours.front();
1862 const bool narrow_wall_hole_island = island_loops.size() == 1 && island_loops.front().children.size() == 1 &&
1863 island_loops.front().children.front().is_narrow_wall_hole;
1864 if (is_outer_wall_first || this->outer_walls_first_for_brim())
1865 entities.reverse();
1866 // Orca: sandwich mode. Apply after 1st layer.
1867 else if ((this->config->wall_sequence == WallSequence::InnerOuterInner) && layer_id > 0 && !narrow_wall_hole_island){
1868 entities.reverse(); // reverse all entities - order them from external to internal
1869 if(entities.entities.size()>2){ // 3 walls minimum needed to do inner outer inner ordering
1870 int position = 0; // index to run the re-ordering for multiple external perimeters in a single island.
1871 int arr_i, arr_j = 0; // indexes to run through the walls in the for loops
1872 int outer, first_internal, second_internal, max_internal, current_perimeter; // allocate index values
1873
1874 // Initiate reorder sequence to bring any index 1 (first internal) perimeters ahead of any second internal perimeters
1875 // Leaving these out of order will result in print defects on the external wall as they will be extruded prior to any
1876 // external wall. To do the re-ordering, we are creating two extrusion arrays - reordered_extrusions which will contain
1877 // the reordered extrusions and skipped_extrusions will contain the ones that were skipped in the scan
1878 ExtrusionEntityCollection reordered_extrusions, skipped_extrusions;
1879 bool found_second_internal = false; // helper variable to indicate the start of a new island
1880
1881 for(auto extrusion_to_reorder : entities.entities){ //scan the perimeters to reorder
1882 switch (extrusion_to_reorder->inset_idx) {
1883 case 0: // external perimeter
1884 if(found_second_internal){ //new island - move skipped extrusions to reordered array
1885 for(auto extrusion_skipped : skipped_extrusions)
1886 reordered_extrusions.append(*extrusion_skipped);
1887 skipped_extrusions.clear();
1888 }
1889 reordered_extrusions.append(*extrusion_to_reorder);
1890 break;
1891 case 1: // first internal perimeter
1892 reordered_extrusions.append(*extrusion_to_reorder);
1893 break;
1894 default: // second internal+ perimeter -> put them in the skipped extrusions array
1895 skipped_extrusions.append(*extrusion_to_reorder);
1896 found_second_internal = true;
1897 break;
1898 }
1899 }
1900 if(entities.entities.size()>reordered_extrusions.size()){
1901 // we didnt find any more islands, so lets move the remaining skipped perimeters to the reordered extrusions list.
1902 for(auto extrusion_skipped : skipped_extrusions)
1903 reordered_extrusions.append(*extrusion_skipped);
1904 skipped_extrusions.clear();
1905 }
1906
1907 // Now start the sandwich mode wall re-ordering using the reordered_extrusions as the basis
1908 // scan to find the external perimeter, first internal, second internal and last perimeter in the island.
1909 // We then advance the position index to move to the second "island" and continue until there are no more
1910 // perimeters left.
1911 while (position < reordered_extrusions.size()) {
1912 outer = first_internal = second_internal = current_perimeter = -1; // initialise all index values to -1
1913 max_internal = reordered_extrusions.size()-1; // initialise the maximum internal perimeter to the last perimeter on the extrusion list
1914 // run through the walls to get the index values that need re-ordering until the first one for each
1915 // is found. Start at "position" index to enable the for loop to iterate for multiple external
1916 // perimeters in a single island
1917 for (arr_i = position; arr_i < reordered_extrusions.size(); ++arr_i) {
1918 switch (reordered_extrusions.entities[arr_i]->inset_idx) {
1919 case 0: // external perimeter
1920 if (outer == -1)
1921 outer = arr_i;
1922 break;
1923 case 1: // first internal wall
1924 if (first_internal==-1 && arr_i>outer && outer!=-1){
1925 first_internal = arr_i;
1926 }
1927 break;
1928 case 2: // second internal wall
1929 if (second_internal == -1 && arr_i > first_internal && outer!=-1){
1930 second_internal = arr_i;
1931 }
1932 break;
1933 }
1934 if(outer >-1 && first_internal>-1 && second_internal>-1 && reordered_extrusions.entities[arr_i]->inset_idx == 0){ // found a new external perimeter after we've found all three perimeters to re-order -> this means we entered a new island.
1935 arr_i=arr_i-1; //step back one perimeter
1936 max_internal = arr_i; // new maximum internal perimeter is now this as we have found a new external perimeter, hence a new island.
1937 break; // exit the for loop
1938 }
1939 }
1940
1941 if (outer > -1 && first_internal > -1 && second_internal > -1) { // found perimeters to re-order?
1942 ExtrusionEntityCollection inner_outer_extrusions; // temporary collection to hold extrusions for reordering
1943
1944 for (arr_j = max_internal; arr_j >=position; --arr_j){ // go inside out towards the external perimeter (perimeters in reverse order) and store all internal perimeters until the first one identified with inset index 2
1945 if(arr_j >= second_internal){
1946 inner_outer_extrusions.append(*reordered_extrusions.entities[arr_j]);
1947 current_perimeter++;
1948 }
1949 }
1950
1951 for (arr_j = position; arr_j < second_internal; ++arr_j){ // go outside in and map the remaining perimeters (external and first internal wall(s)) using the outside in wall order
1952 inner_outer_extrusions.append(*reordered_extrusions.entities[arr_j]);
1953 }
1954
1955 for(arr_j = position; arr_j <= max_internal; ++arr_j) // replace perimeter array with the new re-ordered array
1956 entities.replace(arr_j, *inner_outer_extrusions.entities[arr_j-position]);
1957 } else
1958 break;
1959 // go to the next perimeter from the current position to continue scanning for external walls in the same island
1960 position = arr_i + 1;
1961 }
1962 }
1963 }
1964
1965 defer_unsupported_loops(*this, entities);
1966
1967 // append perimeters for this slice as a collection
1968 if (! entities.empty())
1969 this->loops->append(entities);
1970
1971 } // for each loop of an island
1972
1973 // fill gaps
1974 if (! gaps.empty()) { // collapse
1975 double min = gap_fill_min_width;
1976 double max = 2. * perimeter_spacing;
1977 ExPolygons gaps_ex = diff_ex(
1978 //FIXME offset2 would be enough and cheaper.
1979 opening_ex(gaps, float(min / 2.)),
1980 offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset)));
1981 ThickPolylines polylines;
1982 for (ExPolygon& ex : gaps_ex) {
1983 //BBS: Use DP simplify to avoid duplicated points and accelerate medial-axis calculation as well.
1984 ex.douglas_peucker(surface_simplify_resolution);
1985 ex.medial_axis(min, max, &polylines);
1986 }
1987
1988#ifdef GAPS_OF_PERIMETER_DEBUG_TO_SVG
1989 {
1990 static int irun = 0;
1991 BoundingBox bbox_svg;
1992 bbox_svg.merge(get_extents(gaps_ex));
1993 {
1994 std::stringstream stri;
1995 stri << "debug_gaps_ex_" << irun << ".svg";
1996 SVG svg(stri.str(), bbox_svg);
1997 svg.draw(to_polylines(gaps_ex), "blue", 0.5);
1998 svg.Close();
1999 }
2000 ++ irun;
2001 }
2002#endif
2003 // SoftFever: filter out tiny gap fills
2004 polylines.erase(std::remove_if(polylines.begin(), polylines.end(),
2005 [&](const ThickPolyline& p) {
2006 return p.length() < scale_(config->filter_out_gap_fill.value);
2007 }), polylines.end());
2008
2009
2010 if (! polylines.empty()) {
2011 ExtrusionEntityCollection gap_fill;
2012 variable_width(polylines, erGapFill, this->solid_infill_flow, gap_fill.entities);
2013 /* Make sure we don't infill narrow parts that are already gap-filled
2014 (we only consider this surface's gaps to reduce the diff() complexity).
2015 Growing actual extrusions ensures that gaps not filled by medial axis
2016 are not subtracted from fill surfaces (they might be too short gaps
2017 that medial axis skips but infill might join with other infill regions
2018 and use zigzag). */
2019 //FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing,
2020 // therefore it may cover the area, but no the volume.
2021 Polygons gap_fill_covered = gap_fill.polygons_covered_by_width(10.f);
2022 last = diff_ex(last, gap_fill_covered);
2023 if (! one_wall_top_reclaimed.empty())
2024 one_wall_top_reclaimed = diff_ex(one_wall_top_reclaimed, gap_fill_covered);
2025 this->gap_fill->append(std::move(gap_fill.entities));
2026
2027 }
2028 }
2029
2030 // create one more offset to be used as boundary for fill
2031 // we offset by half the perimeter spacing (to get to the actual infill boundary)
2032 // and then we offset back and forth by half the infill spacing to only consider the
2033 // non-collapsing regions
2034 coord_t inset =
2035 (loop_number < 0) ? 0 :
2036 (loop_number == 0) ?
2037 // one loop
2038 ext_perimeter_spacing / 2 :
2039 // two or more loops?
2040 perimeter_spacing / 2;
2041
2042 // only apply infill overlap if we actually have one perimeter
2043 coord_t infill_peri_overlap = 0;
2044 coord_t top_infill_peri_overlap = 0;
2045 if (inset > 0) {
2046 if(this->layer_id == 0 || this->upper_slices == nullptr){
2047 infill_peri_overlap = coord_t(scale_(this->config->top_bottom_infill_wall_overlap.get_abs_value(unscale<double>(inset + solid_infill_spacing / 2))));
2048 }else{
2049 infill_peri_overlap = coord_t(scale_(this->config->infill_wall_overlap.get_abs_value(unscale<double>(inset + solid_infill_spacing / 2))));
2050 top_infill_peri_overlap = coord_t(scale_(this->config->top_bottom_infill_wall_overlap.get_abs_value(unscale<double>(inset + solid_infill_spacing / 2))));
2051 }
2052 inset -= infill_peri_overlap;
2053 }
2054 // simplify infill contours according to resolution
2055 Polygons pp;
2056 for (ExPolygon &ex : last)
2057 ex.simplify_p(m_scaled_resolution, &pp);
2058 ExPolygons not_filled_exp = union_ex(pp);
2059 // collapse too narrow infill areas
2060 coord_t min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
2061
2062 ExPolygons infill_exp = offset2_ex(
2063 not_filled_exp,
2064 float(-inset - min_perimeter_infill_spacing / 2.),
2065 float(min_perimeter_infill_spacing / 2.));
2066 // append infill areas to fill_surfaces
2067 //if any top_fills, grow them by ext_perimeter_spacing/2 to have the real un-anchored fill
2068 ExPolygons top_infill_exp = intersection_ex(fill_clip, offset_ex(top_fills, double(ext_perimeter_spacing / 2)));
2069 // ORCA: only_one_wall_top - route the top fill around the walls kept despite grazing the top.
2070 if (!one_wall_top_kept_bands.empty())
2071 top_infill_exp = diff_ex(top_infill_exp, one_wall_top_kept_bands);
2072 if (!top_fills.empty()) {
2073 infill_exp = union_ex(infill_exp, offset_ex(top_infill_exp, double(top_infill_peri_overlap)));
2074 }
2075 // ORCA: only_one_wall_top - what the top fill does not cover of the dropped walls goes to infill.
2076 if (!one_wall_top_reclaimed.empty())
2077 infill_exp = union_ex(infill_exp, one_wall_top_reclaimed);
2078 this->fill_surfaces->append(infill_exp, stInternal);
2079
2080 apply_extra_perimeters(infill_exp);
2081
2082 // BBS: get the no-overlap infill expolygons
2083 {
2084 ExPolygons polyWithoutOverlap;
2085 if (min_perimeter_infill_spacing / 2 > infill_peri_overlap)
2086 polyWithoutOverlap = offset2_ex(
2087 not_filled_exp,
2088 float(-inset - min_perimeter_infill_spacing / 2.),
2089 float(min_perimeter_infill_spacing / 2 - infill_peri_overlap));
2090 else
2091 polyWithoutOverlap = offset_ex(
2092 not_filled_exp,
2093 double(-inset - infill_peri_overlap));
2094 if (!top_fills.empty())
2095 polyWithoutOverlap = union_ex(polyWithoutOverlap, top_infill_exp);
2096 if (!one_wall_top_reclaimed.empty())
2097 polyWithoutOverlap = union_ex(polyWithoutOverlap, one_wall_top_reclaimed);
2098 this->fill_no_overlap->insert(this->fill_no_overlap->end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end());
2099 }
2100
2101 } // for each island
2102}
static const double narrow_loop_length_threshold
Definition PerimeterGenerator.cpp:45
static constexpr double SMALLER_EXT_INSET_OVERLAP_TOLERANCE
Definition PerimeterGenerator.cpp:49
BoundingBox inflated(coordf_t delta) const noexcept
Definition BoundingBox.hpp:228
std::vector< Polygons > m_lower_polygons_series
Definition PerimeterGenerator.hpp:111
std::vector< Polygons > m_smaller_external_lower_polygons_series
Definition PerimeterGenerator.hpp:113
void split_top_surfaces(const ExPolygons &orig_polygons, ExPolygons &top_fills, ExPolygons &non_top_polygons, ExPolygons &fill_clip) const
Definition PerimeterGenerator.cpp:819
bool outer_walls_first_for_brim() const
Definition PerimeterGenerator.cpp:3102
Flow smaller_ext_perimeter_flow
Definition PerimeterGenerator.hpp:110
std::vector< Polygons > generate_lower_polygons_series(float width)
Definition PerimeterGenerator.cpp:3112
std::vector< Polygons > m_external_lower_polygons_series
Definition PerimeterGenerator.hpp:112
double SCALING_FACTOR
Definition libslic3r.cpp:3
#define SCALED_EPSILON
Definition libslic3r.h:109
static constexpr double INSET_OVERLAP_TOLERANCE
Definition libslic3r.h:95
Polygons clip_clipper_polygons_with_subject_bbox(const Polygons &src, const BoundingBox &bbox)
Definition ClipperUtils.cpp:132
void poly(std::vector< std::pair< Vec2d, Vec2d > > &out, std::initializer_list< Vec2d > p)
Definition DesignSketchTool.cpp:7751
float distance(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b)
Definition SpiralVase.cpp:19
BoundingBox inset(BoundingBox box, const Point &by)
Definition IMEXArrange.cpp:38
std::vector< PerimeterGeneratorLoop > PerimeterGeneratorLoops
Definition PerimeterGenerator.cpp:84
static void reorient_perimeters(ExtrusionEntityCollection &entities, bool steep_overhang_contour, bool steep_overhang_hole, bool reverse_internal_only)
Definition PerimeterGenerator.cpp:1363
Polylines to_polylines(const ExPolygon &src)
Definition ExPolygon.hpp:235
size_t get_extruder_index(const GCodeConfig &config, unsigned int filament_id)
Definition PrintConfig.cpp:140
void variable_width(const ThickPolylines &polylines, ExtrusionRole role, const Flow &flow, std::vector< ExtrusionEntity * > &out)
Definition VariableWidth.cpp:228
static bool top_fill_replaces_inner_walls(const PrintRegionConfig &config)
Definition PerimeterGenerator.cpp:637
std::vector< Polyline > Polylines
Definition Polyline.hpp:22
BoundingBox get_extents(const ExPolygonsWithIds &e)
Definition Emboss.cpp:1374
sparse_infill_density((ConfigOptionEnum< InfillPattern >, sparse_infill_pattern))((ConfigOptionPercent
@ erGapFill
Definition ExtrusionEntity.hpp:38
Polygons to_polygons(const ExPolygon &src)
Definition ExPolygon.hpp:307
Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
Definition ClipperUtils.cpp:878
void keep_largest_contour_only(ExPolygons &polygons)
Definition ExPolygon.cpp:540
@ OuterInner
Definition PrintConfig.hpp:247
@ InnerOuter
Definition PrintConfig.hpp:246
@ InnerOuterInner
Definition PrintConfig.hpp:248
std::vector< size_t > chain_expolygons(const ExPolygons &input_exploy)
Definition ShortestPath.cpp:1555
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip)
Definition ClipperUtils.cpp:1111
static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls, bool &steep_overhang_contour, bool &steep_overhang_hole)
Definition PerimeterGenerator.cpp:129
std::vector< ThickPolyline > ThickPolylines
Definition Polyline.hpp:23
Slic3r::Polygons diff(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset)
Definition ClipperUtils.cpp:731
static constexpr const float ClipperSafetyOffset
Definition ClipperUtils.hpp:30
static void defer_unsupported_loops(const PerimeterGenerator &perimeter_generator, ExtrusionEntityCollection &entities)
Definition PerimeterGenerator.cpp:1405
const Polygons & holes(const ExPolygon &p)
Definition AGGRaster.hpp:31
Slic3r::Polygon Polygon
Definition Emboss.cpp:101

References Slic3r::append(), Slic3r::ExtrusionEntityCollection::append(), apply_extra_perimeters(), Slic3r::chain_expolygons(), Slic3r::PerimeterGeneratorLoop::children, Slic3r::ClipperUtils::clip_clipper_polygons_with_subject_bbox(), Slic3r::ClipperSafetyOffset, Slic3r::SVG::Close(), config, Slic3r::Polygon::contains(), Slic3r::defer_unsupported_loops(), Slic3r::diff(), Slic3r::diff_ex(), Slic3r::SVG::draw(), Slic3r::empty(), Slic3r::ExtrusionEntityCollection::empty(), Slic3r::Enabled, Slic3r::ExtrusionEntityCollection::entities, Slic3r::erGapFill, Slic3r::Surface::expolygon, ext_perimeter_flow, Slic3r::Surface::extra_perimeters, fill_no_overlap, fill_surfaces, Slic3r::MultiPoint::first_point(), gap_fill, generate_lower_polygons_series(), Slic3r::get_extents(), Slic3r::get_extruder_index(), Slic3r::Feature::FuzzySkin::group_region_by_fuzzify(), Slic3r::has_bottom_shell_layers(), has_fuzzy_skin, Slic3r::has_top_shell_layers(), Slic3r::holes(), Slic3r::BoundingBox::inflated(), Slic3r::InnerOuter, Slic3r::InnerOuterInner, INSET_OVERLAP_TOLERANCE, Slic3r::intersection_ex(), Slic3r::intersection_pl(), Slic3r::Surface::is_bridge(), Slic3r::keep_largest_contour_only(), layer_id, loops, lower_slices, m_ext_mm3_per_mm, m_ext_mm3_per_mm_smaller_width, m_external_lower_polygons_series, m_lower_polygons_series, m_lower_slices_polygons, m_mm3_per_mm, m_mm3_per_mm_overhang, m_scaled_resolution, m_smaller_external_lower_polygons_series, m_spiral_vase, Slic3r::Polygon::make_counter_clockwise(), Slic3r::BoundingBoxBase< PointType, APointsType >::merge(), narrow_loop_length_threshold, Slic3r::nozzle_diameter(), object_config, Slic3r::offset(), Slic3r::offset2_ex(), Slic3r::offset_ex(), Slic3r::opening_ex(), outer_walls_first_for_brim(), Slic3r::OuterInner, overhang_flow, perimeter_flow, Slic3r::PerimeterGeneratorLoop::polygon, print_config, process_no_bridge(), Slic3r::reorient_perimeters(), Slic3r::ExtrusionEntityCollection::replace(), Slic3r::ExtrusionEntityCollection::reverse(), scale_, Slic3r::scaled(), SCALED_EPSILON, SCALING_FACTOR, Slic3r::ExPolygon::simplify_p(), Slic3r::ExtrusionEntityCollection::size(), slices, SMALLER_EXT_INSET_OVERLAP_TOLERANCE, smaller_ext_perimeter_flow, solid_infill_flow, Slic3r::sparse_infill_density(), Slic3r::Polygon::split_at_first_point(), split_top_surfaces(), Slic3r::stInternal, Slic3r::to_polygons(), Slic3r::to_polylines(), Slic3r::top_fill_replaces_inner_walls(), Slic3r::traverse_loops(), Slic3r::union_ex(), Slic3r::unscale(), upper_slices, and Slic3r::variable_width().

Referenced by Slic3r::LayerRegion::make_perimeters().

Here is the caller graph for this function:

◆ process_no_bridge()

void Slic3r::PerimeterGenerator::process_no_bridge ( Surfaces & all_surfaces,
coord_t perimeter_spacing,
coord_t ext_perimeter_width )
private
2139{
2140
2141 if (this->config->counterbore_hole_bridging == chbNone)
2142 return; // return if counterbore hole is not enabled
2143
2144 //store surface for bridge infill to avoid unsupported perimeters (but the first one, this one is always good)
2145 if (this->lower_slices != NULL && !this->lower_slices->empty()) {
2146 const coordf_t bridged_infill_margin = scale_(BRIDGE_INFILL_MARGIN);
2147
2148 for (size_t surface_idx = 0; surface_idx < all_surfaces.size(); surface_idx++) {
2149 Surface* surface = &all_surfaces[surface_idx];
2150 ExPolygons last = { surface->expolygon };
2151 //compute our unsupported surface
2152 ExPolygons unsupported = diff_ex(last, *this->lower_slices, ApplySafetyOffset::Yes);
2153 if (!unsupported.empty()) {
2154 //remove small overhangs
2155 ExPolygons unsupported_filtered = opening_ex(unsupported, perimeter_spacing);
2156
2157 if (!unsupported_filtered.empty()) {
2158 //to_draw.insert(to_draw.end(), last.begin(), last.end());
2159 //extract only the useful part of the lower layer. The safety offset is really needed here.
2160 ExPolygons support = diff_ex(last, unsupported, ApplySafetyOffset::Yes);
2161 if (!unsupported.empty()) {
2162 //only consider the part that can be bridged (really, by the bridge algorithm)
2163 //first, separate into islands (ie, each ExPlolygon)
2164 int numploy = 0;
2165 //only consider the bottom layer that intersect unsupported, to be sure it's only on our island.
2166 ExPolygonCollection lower_island(support);
2167 //a detector per island
2168 ExPolygons bridgeable;
2169 for (ExPolygon unsupported : unsupported_filtered) {
2170 BridgeDetector detector{ unsupported,
2171 lower_island.expolygons,
2172 perimeter_spacing / 4}; // Use a finer BridgeDetector. This affects coverage resolution, not extrusion spacing.
2173 // ORCA: Relative/Align Bridge Angle
2174 const double custom_angle_deg = this->config->bridge_angle.value;
2175 const bool relative_angle = this->config->relative_bridge_angle.value;
2176 const double detect_angle_rad = (custom_angle_deg > 0.0 && !relative_angle)
2177 ? Geometry::deg2rad(custom_angle_deg) +
2178 (this->config->align_infill_direction_to_model ? this->m_model_rotation_rad : 0.0)
2179 : 0.0;
2180 if (detector.detect_angle(detect_angle_rad))
2181 expolygons_append(bridgeable, union_ex(detector.coverage(-1, true)));
2182 }
2183 if (!bridgeable.empty() && !surface->expolygon.holes.empty()) { // keep out if cannot be bridged or no holes to bridge
2184 const coordf_t bridge_anchor_offset = std::min({bridged_infill_margin, coordf_t(perimeter_spacing), coordf_t(ext_perimeter_width)});
2185
2186 // Handle filled vs partial counterbore bridging modes.
2187 if (this->config->counterbore_hole_bridging.value == chbFilled) {
2188 unsupported_filtered = offset_ex(unsupported_filtered, -perimeter_spacing); // shrink it to survive the strict bridge-candidate filter
2189
2190 //we bridge everything, even the not-bridgeable bits
2191 for (size_t i = 0; i < unsupported_filtered.size();) {
2192 ExPolygon& poly_unsupp = *(unsupported_filtered.begin() + i);
2193 Polygons contour_simplified = poly_unsupp.contour.simplify(perimeter_spacing);
2194 ExPolygon poly_unsupp_bigger = poly_unsupp;
2195 Polygons contour_bigger = offset(poly_unsupp_bigger.contour, bridged_infill_margin);
2196 if (contour_bigger.size() == 1) poly_unsupp_bigger.contour = contour_bigger[0];
2197
2198 //check convex, has some bridge, not overhang
2199 if (contour_simplified.size() == 1 && contour_bigger.size() == 1 && contour_simplified[0].concave_points().size() == 0
2200 && intersection_ex(bridgeable, ExPolygons{ poly_unsupp }).size() > 0
2201 && diff_ex(ExPolygons{ poly_unsupp_bigger }, union_ex(last, offset_ex(bridgeable, bridged_infill_margin + perimeter_spacing / 2)), ApplySafetyOffset::Yes).size() == 0
2202 ) {
2203 //ok, keep it
2204 i++;
2205 } else {
2206 unsupported_filtered.erase(unsupported_filtered.begin() + i);
2207 }
2208 }
2209
2210 unsupported_filtered = offset_ex(unsupported_filtered, perimeter_spacing + bridge_anchor_offset); // restore it back to its original size and add anchor
2211 unsupported_filtered = intersection_ex(last, unsupported_filtered); // clamp to the original surface, to avoid creating new unsupported areas
2212
2213 for (ExPolygon& expol : unsupported_filtered) {
2214 // Remove holes that need sacrificial fill, but keep holes
2215 // whose wall is already supported by the lower layer.
2216 const float hole_wall_width = float(ext_perimeter_width / 2);
2217 for (size_t hole_idx = 0; hole_idx < expol.holes.size();) {
2218 Polygon hole_area_contour = expol.holes[hole_idx];
2219 hole_area_contour.make_counter_clockwise();
2220
2221 const ExPolygons hole_area = { ExPolygon(hole_area_contour) };
2222 ExPolygons hole_wall_area = diff_ex(
2223 offset_ex(hole_area_contour, hole_wall_width),
2224 hole_area,
2226 hole_wall_area = intersection_ex(hole_wall_area, ExPolygons{ expol }, ApplySafetyOffset::Yes);
2227 if (!hole_wall_area.empty() &&
2228 intersection_ex(hole_wall_area, *this->lower_slices, ApplySafetyOffset::Yes).empty())
2229 expol.holes.erase(expol.holes.begin() + hole_idx);
2230 // After erase(), the next hole shifts into the same index. So hole_idx
2231 // must not be incremented, otherwise the next hole would be skipped.
2232 else
2233 ++hole_idx; // keep this hole, it won't be bridged, so we need to keep it as a hole
2234 }
2235
2236 //detect inside volume
2237 for (size_t surface_idx_other = 0; surface_idx_other < all_surfaces.size(); surface_idx_other++) {
2238 if (surface_idx == surface_idx_other) continue;
2239 if (intersection_ex(ExPolygons() = { expol }, ExPolygons() = { all_surfaces[surface_idx_other].expolygon }).size() > 0) {
2240 //this means that other_surf was inside an expol holes
2241 //as we removed them, we need to add a new one
2242 ExPolygons new_poly = offset2_ex(ExPolygons{ all_surfaces[surface_idx_other].expolygon }, double(-bridged_infill_margin - perimeter_spacing), double(perimeter_spacing));
2243 if (new_poly.size() == 1) {
2244 all_surfaces[surface_idx_other].expolygon = new_poly[0];
2245 expol.holes.push_back(new_poly[0].contour);
2246 expol.holes.back().make_clockwise();
2247 } else {
2248 for (size_t idx = 0; idx < new_poly.size(); idx++) {
2249 Surface new_surf = all_surfaces[surface_idx_other];
2250 new_surf.expolygon = new_poly[idx];
2251 all_surfaces.push_back(new_surf);
2252 expol.holes.push_back(new_poly[idx].contour);
2253 expol.holes.back().make_clockwise();
2254 }
2255 all_surfaces.erase(all_surfaces.begin() + surface_idx_other);
2256 if (surface_idx_other < surface_idx) {
2257 surface_idx--;
2258 surface = &all_surfaces[surface_idx];
2259 }
2260 surface_idx_other--;
2261 }
2262 }
2263 }
2264 }
2265 //TODO: add other polys as holes inside this one (-margin)
2266 } else { // if(this->config->counterbore_hole_bridging.value == chbBridges)
2267 // Orca: Partial counterbore bridging is mask-based. Preserve the supported
2268 // remainder and use simplified BridgeDetector coverage to derive the
2269 // bridgeable counterbore span. The span is grown from supported material,
2270 // shrunk back, stripped from the remaining normal surface, and expanded back.
2271 // It is then prevented from intruding deeper into it than the explicit anchor overlap.
2272 // Finally, add the allowed anchor band from it then remove the
2273 // narrow hole-side wall contact, which must remain unbridgeable.
2274
2275 const ExPolygons remaining = diff_ex(last, unsupported_filtered, ApplySafetyOffset::Yes);
2276
2277 ExPolygons bridgeable_filtered;
2278
2279 for (ExPolygon& poly : bridgeable) {
2280 poly.simplify(perimeter_spacing, &bridgeable_filtered);
2281 }
2282 bridgeable_filtered = opening_ex(bridgeable_filtered, ext_perimeter_width);
2283
2284 // Get rid of coarseness of the resulted bridgeable area by using the original supported area as reference.
2285 // This is to avoid keeping tiny bridgeable areas that are far from the supported area, or protrude into it.
2286 bridgeable_filtered = union_ex(offset_ex(remaining, perimeter_spacing), bridgeable_filtered);
2287 bridgeable_filtered = offset_ex(bridgeable_filtered, -perimeter_spacing);
2288 bridgeable_filtered = diff_ex(bridgeable_filtered, remaining, ApplySafetyOffset::Yes);
2289 bridgeable_filtered = opening_ex(bridgeable_filtered, ext_perimeter_width / 2); // filter noise from the diff_ex
2290 bridgeable_filtered = offset_ex(bridgeable_filtered, perimeter_spacing); // restore the size to the original bridgeable area
2291 // Safety measure: Keep the bridge mask from intruding deeper into the
2292 // supported anchor region than the explicit anchor overlap.
2293 bridgeable_filtered = diff_ex(bridgeable_filtered, offset_ex(remaining, -bridge_anchor_offset));
2294
2295 ExPolygons bridge_anchor_areas = intersection_ex(remaining, offset_ex(unsupported_filtered, bridge_anchor_offset));
2296 unsupported_filtered = union_ex(bridgeable_filtered, bridge_anchor_areas); // add bridge anchor
2297 unsupported_filtered = opening_ex(unsupported_filtered, bridge_anchor_offset); // remove anchor area from hole-side walls, it must remain unbridgeable
2298
2299 // update 'last' only if we have a valid bridgeable area, otherwise we will lose the original unsupported area
2300 if (!unsupported_filtered.empty())
2301 last = remaining;
2302 // TODO: Fix the case with thin outer walls around the bridge (1~2 walls) where classic wall
2303 // might generate two walls in a tiny space or non at all if "Detect thin walls" is not activated
2304 }
2305 } else {
2306 unsupported_filtered.clear();
2307 }
2308 }
2309
2310 if (!unsupported_filtered.empty()) {
2311
2312 //add this directly to the infill list.
2313 // this will avoid to throw wrong offsets into a good polygons
2314 this->fill_surfaces->append(
2315 unsupported_filtered,
2316 stInternal);
2317
2318 // store the results
2319 last = diff_ex(last, unsupported_filtered, ApplySafetyOffset::Yes);
2320 //remove "thin air" polygons (note: it assumes that all polygons below will be extruded)
2321 for (int i = 0; i < last.size(); i++) {
2322 if (intersection_ex(support, ExPolygons() = { last[i] }).empty()) {
2323 this->fill_surfaces->append(
2324 ExPolygons() = { last[i] },
2325 stInternal);
2326 last.erase(last.begin() + i);
2327 i--;
2328 }
2329 }
2330 }
2331 }
2332 }
2333 if (last.size() == 0) {
2334 all_surfaces.erase(all_surfaces.begin() + surface_idx);
2335 surface_idx--;
2336 } else {
2337 surface->expolygon = last[0];
2338 for (size_t idx = 1; idx < last.size(); idx++) {
2339 all_surfaces.emplace_back(*surface, last[idx]);
2340 }
2341 }
2342 }
2343 }
2344}
bool make_counter_clockwise()
Definition Polygon.cpp:94
static constexpr double BRIDGE_INFILL_MARGIN
Definition libslic3r.h:99
constexpr T deg2rad(const T angle)
Definition Geometry.hpp:312
double coordf_t
Definition GUI_ObjectList.hpp:46
@ chbNone
Definition PrintConfig.hpp:602
@ chbFilled
Definition PrintConfig.hpp:602
@ Yes
Definition ClipperUtils.hpp:51
void expolygons_append(ExPolygons &dst, const ExPolygons &src)
Definition ExPolygon.hpp:447
const Polygon & contour(const ExPolygon &p)
Definition AGGRaster.hpp:30

References BRIDGE_INFILL_MARGIN, Slic3r::chbFilled, Slic3r::chbNone, config, Slic3r::contour(), Slic3r::ExPolygon::contour, Slic3r::BridgeDetector::coverage(), Slic3r::Geometry::deg2rad(), Slic3r::BridgeDetector::detect_angle(), Slic3r::diff_ex(), Slic3r::empty(), Slic3r::Surface::expolygon, Slic3r::ExPolygonCollection::expolygons, Slic3r::expolygons_append(), fill_surfaces, Slic3r::ExPolygon::holes, Slic3r::intersection_ex(), lower_slices, m_model_rotation_rad, Slic3r::Polygon::make_counter_clockwise(), Slic3r::offset(), Slic3r::offset2_ex(), Slic3r::offset_ex(), Slic3r::opening_ex(), process_no_bridge(), scale_, Slic3r::Polygon::simplify(), Slic3r::stInternal, Slic3r::union_ex(), and Slic3r::Yes.

Referenced by process_arachne(), process_classic(), and process_no_bridge().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ smaller_width_ext_mm3_per_mm()

double Slic3r::PerimeterGenerator::smaller_width_ext_mm3_per_mm ( ) const
inline

References m_ext_mm3_per_mm_smaller_width.

Referenced by Slic3r::traverse_loops().

Here is the caller graph for this function:

◆ split_top_surfaces()

void Slic3r::PerimeterGenerator::split_top_surfaces ( const ExPolygons & orig_polygons,
ExPolygons & top_fills,
ExPolygons & non_top_polygons,
ExPolygons & fill_clip ) const
private
820 {
821 // other perimeters
822 coord_t perimeter_width = this->perimeter_flow.scaled_width();
823 coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing();
824
825 // external perimeters
826 coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
827 coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
828
829 bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->outer_wall_filament_id - 1)) > 0;
830
831 // split the polygons with top/not_top
832 // get the offset from solid surface anchor
833 coord_t offset_top_surface =
834 scale_(0.9 * (config->wall_loops.value == 0
835 ? 0.
836 : unscaled(double(ext_perimeter_width +
837 perimeter_spacing * int(int(config->wall_loops.value) - int(1))))));
838 // if possible, try to not push the extra perimeters inside the sparse infill
839 if (offset_top_surface >
840 0.9 * (config->wall_loops.value <= 1 ? 0. : (perimeter_spacing * (config->wall_loops.value - 1))))
841 offset_top_surface -=
842 coord_t(0.9 * (config->wall_loops.value <= 1 ? 0. : (perimeter_spacing * (config->wall_loops.value - 1))));
843 else
844 offset_top_surface = 0;
845 // don't takes into account too thin areas
846 // get boungding box of last
847 BoundingBox last_box = get_extents(orig_polygons);
848 last_box.offset(SCALED_EPSILON);
849
850 // skip if the exposed area is smaller than "min_width_top_surface"
851 double min_width_top_surface = std::max(double(ext_perimeter_spacing / 2. + 10), scale_(config->min_width_top_surface.get_abs_value(unscale_(perimeter_width))));
852
853 // get the Polygons upper the polygon this layer
854 Polygons upper_polygons_series_clipped;
855 if (object_config->interface_shells) {
856 auto upper_slicer_same_region = to_expolygons(this->upper_slices_same_region->surfaces);
857 upper_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(upper_slicer_same_region, last_box);
858 } else
859 upper_polygons_series_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*this->upper_slices, last_box);
860
861 upper_polygons_series_clipped = offset(upper_polygons_series_clipped, min_width_top_surface);
862
863 // set the clip to a virtual "second perimeter"
864 fill_clip = offset_ex(orig_polygons, -double(ext_perimeter_spacing));
865 // get the real top surface
866 ExPolygons grown_lower_slices;
867 ExPolygons bridge_checker;
868 auto nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->outer_wall_filament_id - 1);
869 // Check whether surface be bridge or not
870 if (this->lower_slices != NULL) {
871 // BBS: get the Polygons below the polygon this layer
872 Polygons lower_polygons_series_clipped =
874 double bridge_offset = std::max(double(ext_perimeter_spacing), (double(perimeter_width)));
875 // SoftFever: improve bridging
876 const float bridge_margin =
877 std::min(float(scale_(BRIDGE_INFILL_MARGIN)), float(scale_(nozzle_diameter * BRIDGE_INFILL_MARGIN / 0.4)));
878 bridge_checker = offset_ex(diff_ex(orig_polygons, lower_polygons_series_clipped, ApplySafetyOffset::Yes),
879 1.5 * bridge_offset + bridge_margin + perimeter_spacing / 2.);
880 }
881 ExPolygons delete_bridge = diff_ex(orig_polygons, bridge_checker, ApplySafetyOffset::Yes);
882
883 ExPolygons top_polygons = diff_ex(delete_bridge, upper_polygons_series_clipped, ApplySafetyOffset::Yes);
884
885 // get the not-top surface, from the "real top" but enlarged by external_infill_margin (and the
886 // min_width_top_surface we removed a bit before)
887 ExPolygons temp_gap = diff_ex(top_polygons, fill_clip);
888 ExPolygons inner_polygons =
889 diff_ex(orig_polygons,
890 offset_ex(top_polygons, offset_top_surface + min_width_top_surface - double(ext_perimeter_spacing / 2.)),
892 // get the enlarged top surface, by using inner_polygons instead of upper_slices, and clip it for it to be exactly
893 // the polygons to fill.
894 top_polygons = diff_ex(fill_clip, inner_polygons, ApplySafetyOffset::Yes);
895 // increase by half peri the inner space to fill the frontier between last and stored.
896 top_fills = union_ex(top_fills, top_polygons);
897 //set the clip to the external wall but go back inside by infill_extrusion_width/2 to be sure the extrusion won't go outside even with a 100% overlap.
898 double infill_spacing_unscaled = this->config->sparse_infill_line_width.get_abs_value(nozzle_diameter);
899 if (infill_spacing_unscaled == 0) infill_spacing_unscaled = Flow::auto_extrusion_width(frInfill, nozzle_diameter);
900 fill_clip = offset_ex(orig_polygons, double(ext_perimeter_spacing / 2.) - scale_(infill_spacing_unscaled / 2.));
901 // ExPolygons oldLast = last;
902
903 non_top_polygons = intersection_ex(inner_polygons, orig_polygons);
904 if (has_gap_fill)
905 non_top_polygons = union_ex(non_top_polygons, temp_gap);
906 //{
907 // std::stringstream stri;
908 // stri << this->layer_id << "_1_"<< i <<"_only_one_peri"<< ".svg";
909 // SVG svg(stri.str());
910 // svg.draw(to_polylines(top_fills), "green");
911 // svg.draw(to_polylines(inner_polygons), "yellow");
912 // svg.draw(to_polylines(top_polygons), "cyan");
913 // svg.draw(to_polylines(oldLast), "orange");
914 // svg.draw(to_polylines(last), "red");
915 // svg.Close();
916 //}
917}
static float auto_extrusion_width(FlowRole role, float nozzle_diameter)
Definition Flow.cpp:27
const SurfaceCollection * upper_slices_same_region
Definition PerimeterGenerator.hpp:90
#define unscale_(val)
Definition libslic3r.h:107
@ frInfill
Definition Flow.hpp:21
ExPolygons to_expolygons(const Polygons &polys)
Definition ExPolygon.hpp:373
min_width_top_surface((ConfigOptionBool, only_one_wall_first_layer))((ConfigOptionFloat
constexpr Eigen::Matrix< Tout, 2, EigenArgs... > unscaled(const Slic3r::Point &v) noexcept
Definition Arrange.cpp:81

References Slic3r::Flow::auto_extrusion_width(), BRIDGE_INFILL_MARGIN, Slic3r::ClipperUtils::clip_clipper_polygons_with_subject_bbox(), config, Slic3r::diff_ex(), ext_perimeter_flow, Slic3r::frInfill, Slic3r::get_extents(), Slic3r::get_extruder_index(), Slic3r::intersection_ex(), lower_slices, Slic3r::min_width_top_surface(), Slic3r::nozzle_diameter(), object_config, Slic3r::BoundingBoxBase< PointType, APointsType >::offset(), Slic3r::offset(), Slic3r::offset_ex(), perimeter_flow, print_config, scale_, SCALED_EPSILON, Slic3r::to_expolygons(), Slic3r::union_ex(), unscale_, Slic3r::unscaled(), upper_slices, upper_slices_same_region, and Slic3r::Yes.

Referenced by process_classic().

Here is the call graph for this function:
Here is the caller graph for this function:

Member Data Documentation

◆ compatible_regions

const LayerRegionPtrs* Slic3r::PerimeterGenerator::compatible_regions

◆ config

◆ ext_perimeter_flow

◆ fill_no_overlap

ExPolygons* Slic3r::PerimeterGenerator::fill_no_overlap

◆ fill_surfaces

◆ fuzzy_supported_area

◆ gap_fill

ExtrusionEntityCollection* Slic3r::PerimeterGenerator::gap_fill

◆ has_fuzzy_hole

bool Slic3r::PerimeterGenerator::has_fuzzy_hole = false

◆ has_fuzzy_skin

◆ layer_height

double Slic3r::PerimeterGenerator::layer_height

◆ layer_id

◆ loops

◆ lower_slices

◆ m_ext_mm3_per_mm

double Slic3r::PerimeterGenerator::m_ext_mm3_per_mm
private

◆ m_ext_mm3_per_mm_smaller_width

double Slic3r::PerimeterGenerator::m_ext_mm3_per_mm_smaller_width
private

◆ m_external_lower_polygons_series

std::vector<Polygons> Slic3r::PerimeterGenerator::m_external_lower_polygons_series

◆ m_lower_polygons_series

std::vector<Polygons> Slic3r::PerimeterGenerator::m_lower_polygons_series

◆ m_lower_slices_polygons

Polygons Slic3r::PerimeterGenerator::m_lower_slices_polygons
private

◆ m_mm3_per_mm

double Slic3r::PerimeterGenerator::m_mm3_per_mm
private

◆ m_mm3_per_mm_overhang

double Slic3r::PerimeterGenerator::m_mm3_per_mm_overhang
private

◆ m_model_rotation_rad

double Slic3r::PerimeterGenerator::m_model_rotation_rad
private

◆ m_scaled_resolution

double Slic3r::PerimeterGenerator::m_scaled_resolution
private

◆ m_smaller_external_lower_polygons_series

std::vector<Polygons> Slic3r::PerimeterGenerator::m_smaller_external_lower_polygons_series

◆ m_spiral_vase

bool Slic3r::PerimeterGenerator::m_spiral_vase
private

◆ object_config

◆ overhang_flow

◆ perimeter_flow

◆ print_config

const PrintConfig* Slic3r::PerimeterGenerator::print_config

◆ regions_by_fuzzify

◆ slice_z

◆ slices

◆ smaller_ext_perimeter_flow

Flow Slic3r::PerimeterGenerator::smaller_ext_perimeter_flow

◆ solid_infill_flow

Flow Slic3r::PerimeterGenerator::solid_infill_flow

◆ upper_slices

const ExPolygons* Slic3r::PerimeterGenerator::upper_slices

◆ upper_slices_same_region

const SurfaceCollection* Slic3r::PerimeterGenerator::upper_slices_same_region

The documentation for this class was generated from the following files: