mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-09-26 11:33:14 +08:00
Merge branch 'ms_fix_bridge_anchoring' (see SPE-2031 for list of issues)
This commit is contained in:
commit
b133579126
@ -223,6 +223,7 @@ set(SLIC3R_SOURCES
|
|||||||
KDTreeIndirect.hpp
|
KDTreeIndirect.hpp
|
||||||
Layer.cpp
|
Layer.cpp
|
||||||
Layer.hpp
|
Layer.hpp
|
||||||
|
LayerRegion.hpp
|
||||||
LayerRegion.cpp
|
LayerRegion.cpp
|
||||||
libslic3r.h
|
libslic3r.h
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h"
|
"${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h"
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
#include "Flow.hpp"
|
#include "Flow.hpp"
|
||||||
#include "SurfaceCollection.hpp"
|
#include "SurfaceCollection.hpp"
|
||||||
#include "ExtrusionEntityCollection.hpp"
|
#include "ExtrusionEntityCollection.hpp"
|
||||||
|
#include "LayerRegion.hpp"
|
||||||
|
|
||||||
#include <boost/container/small_vector.hpp>
|
#include <boost/container/small_vector.hpp>
|
||||||
|
|
||||||
@ -39,42 +40,6 @@ namespace FillLightning {
|
|||||||
class Generator;
|
class Generator;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Range of indices, providing support for range based loops.
|
|
||||||
template<typename T>
|
|
||||||
class IndexRange
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {}
|
|
||||||
IndexRange() = default;
|
|
||||||
|
|
||||||
// Just a bare minimum functionality iterator required by range-for loop.
|
|
||||||
class Iterator {
|
|
||||||
public:
|
|
||||||
T operator*() const { return m_idx; }
|
|
||||||
bool operator!=(const Iterator &rhs) const { return m_idx != rhs.m_idx; }
|
|
||||||
void operator++() { ++ m_idx; }
|
|
||||||
private:
|
|
||||||
friend class IndexRange<T>;
|
|
||||||
Iterator(T idx) : m_idx(idx) {}
|
|
||||||
T m_idx;
|
|
||||||
};
|
|
||||||
|
|
||||||
Iterator begin() const { assert(m_begin <= m_end); return Iterator(m_begin); };
|
|
||||||
Iterator end() const { assert(m_begin <= m_end); return Iterator(m_end); };
|
|
||||||
|
|
||||||
bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; }
|
|
||||||
T size() const { assert(m_begin <= m_end); return m_end - m_begin; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Index of the first extrusion in LayerRegion.
|
|
||||||
T m_begin { 0 };
|
|
||||||
// Index of the last extrusion in LayerRegion.
|
|
||||||
T m_end { 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
using ExtrusionRange = IndexRange<uint32_t>;
|
|
||||||
using ExPolygonRange = IndexRange<uint32_t>;
|
|
||||||
|
|
||||||
// Range of extrusions, referencing the source region by an index.
|
// Range of extrusions, referencing the source region by an index.
|
||||||
class LayerExtrusionRange : public ExtrusionRange
|
class LayerExtrusionRange : public ExtrusionRange
|
||||||
{
|
{
|
||||||
@ -101,144 +66,6 @@ using LayerExtrusionRanges =
|
|||||||
std::vector<LayerExtrusionRange>;
|
std::vector<LayerExtrusionRange>;
|
||||||
#endif // NDEBUG
|
#endif // NDEBUG
|
||||||
|
|
||||||
class LayerRegion
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
[[nodiscard]] Layer* layer() { return m_layer; }
|
|
||||||
[[nodiscard]] const Layer* layer() const { return m_layer; }
|
|
||||||
[[nodiscard]] const PrintRegion& region() const { return *m_region; }
|
|
||||||
|
|
||||||
// collection of surfaces generated by slicing the original geometry
|
|
||||||
// divided by type top/bottom/internal
|
|
||||||
[[nodiscard]] const SurfaceCollection& slices() const { return m_slices; }
|
|
||||||
|
|
||||||
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
|
|
||||||
// and for re-starting of infills.
|
|
||||||
[[nodiscard]] const ExPolygons& fill_expolygons() const { return m_fill_expolygons; }
|
|
||||||
// and their bounding boxes
|
|
||||||
[[nodiscard]] const BoundingBoxes& fill_expolygons_bboxes() const { return m_fill_expolygons_bboxes; }
|
|
||||||
// Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands.
|
|
||||||
// Not used for a plain single material print with no infill modifiers.
|
|
||||||
[[nodiscard]] const ExPolygons& fill_expolygons_composite() const { return m_fill_expolygons_composite; }
|
|
||||||
// and their bounding boxes
|
|
||||||
[[nodiscard]] const BoundingBoxes& fill_expolygons_composite_bboxes() const { return m_fill_expolygons_composite_bboxes; }
|
|
||||||
|
|
||||||
// collection of surfaces generated by slicing the original geometry
|
|
||||||
// divided by type top/bottom/internal
|
|
||||||
[[nodiscard]] const SurfaceCollection& fill_surfaces() const { return m_fill_surfaces; }
|
|
||||||
|
|
||||||
// collection of extrusion paths/loops filling gaps
|
|
||||||
// These fills are generated by the perimeter generator.
|
|
||||||
// They are not printed on their own, but they are copied to this->fills during infill generation.
|
|
||||||
[[nodiscard]] const ExtrusionEntityCollection& thin_fills() const { return m_thin_fills; }
|
|
||||||
|
|
||||||
// collection of polylines representing the unsupported bridge edges
|
|
||||||
[[nodiscard]] const Polylines& unsupported_bridge_edges() const { return m_unsupported_bridge_edges; }
|
|
||||||
|
|
||||||
// ordered collection of extrusion paths/loops to build all perimeters
|
|
||||||
// (this collection contains only ExtrusionEntityCollection objects)
|
|
||||||
[[nodiscard]] const ExtrusionEntityCollection& perimeters() const { return m_perimeters; }
|
|
||||||
|
|
||||||
// ordered collection of extrusion paths to fill surfaces
|
|
||||||
// (this collection contains only ExtrusionEntityCollection objects)
|
|
||||||
[[nodiscard]] const ExtrusionEntityCollection& fills() const { return m_fills; }
|
|
||||||
|
|
||||||
Flow flow(FlowRole role) const;
|
|
||||||
Flow flow(FlowRole role, double layer_height) const;
|
|
||||||
Flow bridging_flow(FlowRole role, bool force_thick_bridges = false) const;
|
|
||||||
|
|
||||||
void slices_to_fill_surfaces_clipped();
|
|
||||||
void prepare_fill_surfaces();
|
|
||||||
// Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices.
|
|
||||||
void make_perimeters(
|
|
||||||
// Input slices for which the perimeters, gap fills and fill expolygons are to be generated.
|
|
||||||
const SurfaceCollection &slices,
|
|
||||||
// Ranges of perimeter extrusions and gap fill extrusions per suface, referencing
|
|
||||||
// newly created extrusions stored at this LayerRegion.
|
|
||||||
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
|
|
||||||
// All fill areas produced for all input slices above.
|
|
||||||
ExPolygons &fill_expolygons,
|
|
||||||
// Ranges of fill areas above per input slice.
|
|
||||||
std::vector<ExPolygonRange> &fill_expolygons_ranges);
|
|
||||||
void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered);
|
|
||||||
double infill_area_threshold() const;
|
|
||||||
// Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer.
|
|
||||||
void trim_surfaces(const Polygons &trimming_polygons);
|
|
||||||
// Single elephant foot compensation step, used by the elephant foor compensation at the 1st layer.
|
|
||||||
// Trim surfaces by trimming polygons (shrunk by an elephant foot compensation step), but don't shrink narrow parts so much that no perimeter would fit.
|
|
||||||
void elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons);
|
|
||||||
|
|
||||||
void export_region_slices_to_svg(const char *path) const;
|
|
||||||
void export_region_fill_surfaces_to_svg(const char *path) const;
|
|
||||||
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
|
|
||||||
void export_region_slices_to_svg_debug(const char *name) const;
|
|
||||||
void export_region_fill_surfaces_to_svg_debug(const char *name) const;
|
|
||||||
|
|
||||||
// Is there any valid extrusion assigned to this LayerRegion?
|
|
||||||
bool has_extrusions() const { return ! this->perimeters().empty() || ! this->fills().empty(); }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
friend class Layer;
|
|
||||||
friend class PrintObject;
|
|
||||||
|
|
||||||
LayerRegion(Layer *layer, const PrintRegion *region) : m_layer(layer), m_region(region) {}
|
|
||||||
~LayerRegion() = default;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Modifying m_slices
|
|
||||||
friend std::string fix_slicing_errors(LayerPtrs&, const std::function<void()>&);
|
|
||||||
template<typename ThrowOnCancel>
|
|
||||||
friend void apply_mm_segmentation(PrintObject& print_object, ThrowOnCancel throw_on_cancel);
|
|
||||||
|
|
||||||
Layer *m_layer;
|
|
||||||
const PrintRegion *m_region;
|
|
||||||
|
|
||||||
// Backed up slices before they are split into top/bottom/internal.
|
|
||||||
// Only backed up for multi-region layers or layers with elephant foot compensation.
|
|
||||||
//FIXME Review whether not to simplify the code by keeping the raw_slices all the time.
|
|
||||||
ExPolygons m_raw_slices;
|
|
||||||
|
|
||||||
//FIXME make m_slices public for unit tests
|
|
||||||
public:
|
|
||||||
// collection of surfaces generated by slicing the original geometry
|
|
||||||
// divided by type top/bottom/internal
|
|
||||||
SurfaceCollection m_slices;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
|
|
||||||
// and for re-starting of infills.
|
|
||||||
ExPolygons m_fill_expolygons;
|
|
||||||
// and their bounding boxes
|
|
||||||
BoundingBoxes m_fill_expolygons_bboxes;
|
|
||||||
// Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands.
|
|
||||||
// Not used for a plain single material print with no infill modifiers.
|
|
||||||
ExPolygons m_fill_expolygons_composite;
|
|
||||||
// and their bounding boxes
|
|
||||||
BoundingBoxes m_fill_expolygons_composite_bboxes;
|
|
||||||
|
|
||||||
// Collection of surfaces for infill generation, created by splitting m_slices by m_fill_expolygons.
|
|
||||||
SurfaceCollection m_fill_surfaces;
|
|
||||||
|
|
||||||
// Collection of extrusion paths/loops filling gaps
|
|
||||||
// These fills are generated by the perimeter generator.
|
|
||||||
// They are not printed on their own, but they are copied to this->fills during infill generation.
|
|
||||||
ExtrusionEntityCollection m_thin_fills;
|
|
||||||
|
|
||||||
// collection of polylines representing the unsupported bridge edges
|
|
||||||
Polylines m_unsupported_bridge_edges;
|
|
||||||
|
|
||||||
// ordered collection of extrusion paths/loops to build all perimeters
|
|
||||||
// (this collection contains only ExtrusionEntityCollection objects)
|
|
||||||
ExtrusionEntityCollection m_perimeters;
|
|
||||||
|
|
||||||
// ordered collection of extrusion paths to fill surfaces
|
|
||||||
// (this collection contains only ExtrusionEntityCollection objects)
|
|
||||||
ExtrusionEntityCollection m_fills;
|
|
||||||
|
|
||||||
// collection of expolygons representing the bridged areas (thus not
|
|
||||||
// needing support material)
|
|
||||||
// Polygons bridged;
|
|
||||||
};
|
|
||||||
|
|
||||||
// LayerSlice contains one or more LayerIsland objects,
|
// LayerSlice contains one or more LayerIsland objects,
|
||||||
// each LayerIsland containing a set of perimeter extrusions extruded with one particular PrintRegionConfig parameters
|
// each LayerIsland containing a set of perimeter extrusions extruded with one particular PrintRegionConfig parameters
|
||||||
|
@ -169,61 +169,16 @@ static ExPolygons fill_surfaces_extract_expolygons(Surfaces &surfaces, std::init
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params,
|
|
||||||
// detect bridges.
|
|
||||||
// Trim "shells" by the expanded bridges.
|
|
||||||
Surfaces expand_bridges_detect_orientations(
|
|
||||||
Surfaces &surfaces,
|
|
||||||
ExPolygons &shells,
|
|
||||||
const Algorithm::RegionExpansionParameters &expansion_params_into_solid_infill,
|
|
||||||
ExPolygons &sparse,
|
|
||||||
const Algorithm::RegionExpansionParameters &expansion_params_into_sparse_infill,
|
|
||||||
const float closing_radius)
|
|
||||||
{
|
|
||||||
using namespace Slic3r::Algorithm;
|
|
||||||
|
|
||||||
double thickness;
|
|
||||||
ExPolygons bridges_ex = fill_surfaces_extract_expolygons(surfaces, {stBottomBridge}, thickness);
|
|
||||||
if (bridges_ex.empty())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
// Calculate bridge anchors and their expansions in their respective shell region.
|
|
||||||
WaveSeeds bridge_anchors = wave_seeds(bridges_ex, shells, expansion_params_into_solid_infill.tiny_expansion, true);
|
|
||||||
std::vector<RegionExpansionEx> bridge_expansions = propagate_waves_ex(bridge_anchors, shells, expansion_params_into_solid_infill);
|
|
||||||
bool expanded_into_shells = ! bridge_expansions.empty();
|
|
||||||
bool expanded_into_sparse = false;
|
|
||||||
{
|
|
||||||
WaveSeeds bridge_anchors_sparse = wave_seeds(bridges_ex, sparse, expansion_params_into_sparse_infill.tiny_expansion, true);
|
|
||||||
std::vector<RegionExpansionEx> bridge_expansions_sparse = propagate_waves_ex(bridge_anchors_sparse, sparse, expansion_params_into_sparse_infill);
|
|
||||||
if (! bridge_expansions_sparse.empty()) {
|
|
||||||
expanded_into_sparse = true;
|
|
||||||
for (WaveSeed &seed : bridge_anchors_sparse)
|
|
||||||
seed.boundary += uint32_t(shells.size());
|
|
||||||
for (RegionExpansionEx &expansion : bridge_expansions_sparse)
|
|
||||||
expansion.boundary_id += uint32_t(shells.size());
|
|
||||||
append(bridge_anchors, std::move(bridge_anchors_sparse));
|
|
||||||
append(bridge_expansions, std::move(bridge_expansions_sparse));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache for detecting bridge orientation and merging regions with overlapping expansions.
|
// Cache for detecting bridge orientation and merging regions with overlapping expansions.
|
||||||
struct Bridge {
|
struct Bridge {
|
||||||
ExPolygon expolygon;
|
ExPolygon expolygon;
|
||||||
uint32_t group_id;
|
uint32_t group_id;
|
||||||
std::vector<RegionExpansionEx>::const_iterator bridge_expansion_begin;
|
std::vector<Algorithm::RegionExpansionEx>::const_iterator bridge_expansion_begin;
|
||||||
double angle = -1;
|
std::optional<double> angle{std::nullopt};
|
||||||
};
|
};
|
||||||
std::vector<Bridge> bridges;
|
|
||||||
{
|
|
||||||
bridges.reserve(bridges_ex.size());
|
|
||||||
uint32_t group_id = 0;
|
|
||||||
for (ExPolygon &ex : bridges_ex)
|
|
||||||
bridges.push_back({ std::move(ex), group_id ++, bridge_expansions.end() });
|
|
||||||
bridges_ex.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group the bridge surfaces by overlaps.
|
// Group the bridge surfaces by overlaps.
|
||||||
auto group_id = [&bridges](uint32_t src_id) {
|
uint32_t group_id(std::vector<Bridge> &bridges, uint32_t src_id) {
|
||||||
uint32_t group_id = bridges[src_id].group_id;
|
uint32_t group_id = bridges[src_id].group_id;
|
||||||
while (group_id != src_id) {
|
while (group_id != src_id) {
|
||||||
src_id = group_id;
|
src_id = group_id;
|
||||||
@ -233,107 +188,154 @@ Surfaces expand_bridges_detect_orientations(
|
|||||||
return group_id;
|
return group_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::vector<Bridge> get_grouped_bridges(
|
||||||
|
ExPolygons&& bridge_expolygons,
|
||||||
|
const std::vector<Algorithm::RegionExpansionEx>& bridge_expansions
|
||||||
|
) {
|
||||||
|
using namespace Algorithm;
|
||||||
|
|
||||||
|
std::vector<Bridge> result;
|
||||||
{
|
{
|
||||||
// Cache of bboxes per expansion boundary.
|
result.reserve(bridge_expansions.size());
|
||||||
std::vector<BoundingBox> bboxes;
|
uint32_t group_id = 0;
|
||||||
// Detect overlaps of bridge anchors inside their respective shell regions.
|
using std::move_iterator;
|
||||||
// bridge_expansions are sorted by boundary id and source id.
|
for (ExPolygon& expolygon : bridge_expolygons)
|
||||||
for (auto it = bridge_expansions.begin(); it != bridge_expansions.end();) {
|
result.push_back({ std::move(expolygon), group_id ++, bridge_expansions.end() });
|
||||||
// For each boundary region:
|
|
||||||
auto it_begin = it;
|
|
||||||
auto it_end = std::next(it_begin);
|
|
||||||
for (; it_end != bridge_expansions.end() && it_end->boundary_id == it_begin->boundary_id; ++ it_end) ;
|
|
||||||
bboxes.clear();
|
|
||||||
bboxes.reserve(it_end - it_begin);
|
|
||||||
for (auto it2 = it_begin; it2 != it_end; ++ it2)
|
|
||||||
bboxes.emplace_back(get_extents(it2->expolygon.contour));
|
|
||||||
// For each bridge anchor of the current source:
|
|
||||||
for (; it != it_end; ++ it) {
|
|
||||||
// A grup id for this bridge.
|
|
||||||
for (auto it2 = std::next(it); it2 != it_end; ++ it2)
|
|
||||||
if (it->src_id != it2->src_id &&
|
|
||||||
bboxes[it - it_begin].overlap(bboxes[it2 - it_begin]) &&
|
|
||||||
// One may ignore holes, they are irrelevant for intersection test.
|
|
||||||
! intersection(it->expolygon.contour, it2->expolygon.contour).empty()) {
|
|
||||||
// The two bridge regions intersect. Give them the same (lower) group id.
|
|
||||||
uint32_t id = group_id(it->src_id);
|
|
||||||
uint32_t id2 = group_id(it2->src_id);
|
|
||||||
if (id < id2)
|
|
||||||
bridges[id2].group_id = id;
|
|
||||||
else
|
|
||||||
bridges[id].group_id = id2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect bridge directions.
|
|
||||||
{
|
// Detect overlaps of bridge anchors inside their respective shell regions.
|
||||||
std::sort(bridge_anchors.begin(), bridge_anchors.end(), Algorithm::lower_by_src_and_boundary);
|
// bridge_expansions are sorted by boundary id and source id.
|
||||||
|
for (auto expansion_iterator = bridge_expansions.begin(); expansion_iterator != bridge_expansions.end();) {
|
||||||
|
auto boundary_region_begin = expansion_iterator;
|
||||||
|
auto boundary_region_end = std::find_if(
|
||||||
|
next(expansion_iterator),
|
||||||
|
bridge_expansions.end(),
|
||||||
|
[&](const RegionExpansionEx& expansion){
|
||||||
|
return expansion.boundary_id != expansion_iterator->boundary_id;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cache of bboxes per expansion boundary.
|
||||||
|
std::vector<BoundingBox> bounding_boxes;
|
||||||
|
bounding_boxes.reserve(std::distance(boundary_region_begin, boundary_region_end));
|
||||||
|
std::transform(
|
||||||
|
boundary_region_begin,
|
||||||
|
boundary_region_end,
|
||||||
|
std::back_inserter(bounding_boxes),
|
||||||
|
[](const RegionExpansionEx& expansion){
|
||||||
|
return get_extents(expansion.expolygon.contour);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// For each bridge anchor of the current source:
|
||||||
|
for (;expansion_iterator != boundary_region_end; ++expansion_iterator) {
|
||||||
|
auto candidate_iterator = std::next(expansion_iterator);
|
||||||
|
for (;candidate_iterator != boundary_region_end; ++candidate_iterator) {
|
||||||
|
const BoundingBox& current_bounding_box{
|
||||||
|
bounding_boxes[expansion_iterator - boundary_region_begin]
|
||||||
|
};
|
||||||
|
const BoundingBox& candidate_bounding_box{
|
||||||
|
bounding_boxes[candidate_iterator - boundary_region_begin]
|
||||||
|
};
|
||||||
|
if (
|
||||||
|
expansion_iterator->src_id != candidate_iterator->src_id
|
||||||
|
&& current_bounding_box.overlap(candidate_bounding_box)
|
||||||
|
// One may ignore holes, they are irrelevant for intersection test.
|
||||||
|
&& !intersection(expansion_iterator->expolygon.contour, candidate_iterator->expolygon.contour).empty()
|
||||||
|
) {
|
||||||
|
// The two bridge regions intersect. Give them the same (lower) group id.
|
||||||
|
uint32_t id = group_id(result, expansion_iterator->src_id);
|
||||||
|
uint32_t id2 = group_id(result, candidate_iterator->src_id);
|
||||||
|
if (id < id2)
|
||||||
|
result[id2].group_id = id;
|
||||||
|
else
|
||||||
|
result[id].group_id = id2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void detect_bridge_directions(
|
||||||
|
const Algorithm::WaveSeeds& bridge_anchors,
|
||||||
|
std::vector<Bridge>& bridges,
|
||||||
|
const std::vector<ExpansionZone>& expansion_zones
|
||||||
|
) {
|
||||||
|
if (expansion_zones.empty()) {
|
||||||
|
throw std::runtime_error("At least one expansion zone must exist!");
|
||||||
|
}
|
||||||
auto it_bridge_anchor = bridge_anchors.begin();
|
auto it_bridge_anchor = bridge_anchors.begin();
|
||||||
Lines lines;
|
|
||||||
Polygons anchor_areas;
|
|
||||||
for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) {
|
for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) {
|
||||||
Bridge &bridge = bridges[bridge_id];
|
Bridge &bridge = bridges[bridge_id];
|
||||||
// lines.clear();
|
Polygons anchor_areas;
|
||||||
anchor_areas.clear();
|
|
||||||
int32_t last_anchor_id = -1;
|
int32_t last_anchor_id = -1;
|
||||||
for (; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) {
|
for (; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) {
|
||||||
if (last_anchor_id != int(it_bridge_anchor->boundary)) {
|
if (last_anchor_id != int(it_bridge_anchor->boundary)) {
|
||||||
last_anchor_id = int(it_bridge_anchor->boundary);
|
last_anchor_id = int(it_bridge_anchor->boundary);
|
||||||
append(anchor_areas, to_polygons(last_anchor_id < int32_t(shells.size()) ? shells[last_anchor_id] : sparse[last_anchor_id - int32_t(shells.size())]));
|
|
||||||
|
unsigned start_index{};
|
||||||
|
unsigned end_index{};
|
||||||
|
for (const ExpansionZone& expansion_zone: expansion_zones) {
|
||||||
|
end_index += expansion_zone.expolygons.size();
|
||||||
|
if (last_anchor_id < static_cast<int64_t>(end_index)) {
|
||||||
|
append(anchor_areas, to_polygons(expansion_zone.expolygons[last_anchor_id - start_index]));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
// if (Points &polyline = it_bridge_anchor->path; polyline.size() >= 2) {
|
start_index += expansion_zone.expolygons.size();
|
||||||
// reserve_more_power_of_2(lines, polyline.size() - 1);
|
|
||||||
// for (size_t i = 1; i < polyline.size(); ++ i)
|
|
||||||
// lines.push_back({ polyline[i - 1], polyline[1] });
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
lines = to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON))));
|
}
|
||||||
|
}
|
||||||
|
Lines lines{to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON))))};
|
||||||
auto [bridging_dir, unsupported_dist] = detect_bridging_direction(lines, to_polygons(bridge.expolygon));
|
auto [bridging_dir, unsupported_dist] = detect_bridging_direction(lines, to_polygons(bridge.expolygon));
|
||||||
bridge.angle = M_PI + std::atan2(bridging_dir.y(), bridging_dir.x());
|
bridge.angle = M_PI + std::atan2(bridging_dir.y(), bridging_dir.x());
|
||||||
#if 0
|
|
||||||
|
if constexpr (false) {
|
||||||
coordf_t stroke_width = scale_(0.06);
|
coordf_t stroke_width = scale_(0.06);
|
||||||
BoundingBox bbox = get_extents(anchor_areas);
|
BoundingBox bbox = get_extents(anchor_areas);
|
||||||
bbox.merge(get_extents(bridge.expolygon));
|
bbox.merge(get_extents(bridge.expolygon));
|
||||||
bbox.offset(scale_(1.));
|
bbox.offset(scale_(1.));
|
||||||
::Slic3r::SVG
|
::Slic3r::SVG
|
||||||
svg(debug_out_path(("bridge" + std::to_string(bridge.angle) + "_" /* + std::to_string(this->layer()->bottom_z())*/).c_str()),
|
svg(debug_out_path(("bridge" + std::to_string(*bridge.angle) + "_" /* + std::to_string(this->layer()->bottom_z())*/).c_str()),
|
||||||
bbox);
|
bbox);
|
||||||
svg.draw(bridge.expolygon, "cyan");
|
svg.draw(bridge.expolygon, "cyan");
|
||||||
svg.draw(lines, "green", stroke_width);
|
svg.draw(lines, "green", stroke_width);
|
||||||
svg.draw(anchor_areas, "red");
|
svg.draw(anchor_areas, "red");
|
||||||
#endif
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge the groups with the same group id, produce surfaces by merging source overhangs with their newly expanded anchors.
|
Surfaces merge_bridges(
|
||||||
Surfaces out;
|
std::vector<Bridge>& bridges,
|
||||||
{
|
const std::vector<Algorithm::RegionExpansionEx>& bridge_expansions,
|
||||||
Polygons acc;
|
const float closing_radius
|
||||||
Surface templ{ stBottomBridge, {} };
|
) {
|
||||||
std::sort(bridge_expansions.begin(), bridge_expansions.end(), [](auto &l, auto &r) {
|
|
||||||
return l.src_id < r.src_id || (l.src_id == r.src_id && l.boundary_id < r.boundary_id);
|
|
||||||
});
|
|
||||||
for (auto it = bridge_expansions.begin(); it != bridge_expansions.end(); ) {
|
for (auto it = bridge_expansions.begin(); it != bridge_expansions.end(); ) {
|
||||||
bridges[it->src_id].bridge_expansion_begin = it;
|
bridges[it->src_id].bridge_expansion_begin = it;
|
||||||
uint32_t src_id = it->src_id;
|
uint32_t src_id = it->src_id;
|
||||||
for (++ it; it != bridge_expansions.end() && it->src_id == src_id; ++ it) ;
|
for (++ it; it != bridge_expansions.end() && it->src_id == src_id; ++ it) ;
|
||||||
}
|
}
|
||||||
for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id)
|
|
||||||
if (group_id(bridge_id) == bridge_id) {
|
Surfaces result;
|
||||||
|
for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) {
|
||||||
|
if (group_id(bridges, bridge_id) == bridge_id) {
|
||||||
// Head of the group.
|
// Head of the group.
|
||||||
acc.clear();
|
Polygons acc;
|
||||||
for (uint32_t bridge_id2 = bridge_id; bridge_id2 < uint32_t(bridges.size()); ++ bridge_id2)
|
for (uint32_t bridge_id2 = bridge_id; bridge_id2 < uint32_t(bridges.size()); ++ bridge_id2)
|
||||||
if (group_id(bridge_id2) == bridge_id) {
|
if (group_id(bridges, bridge_id2) == bridge_id) {
|
||||||
append(acc, to_polygons(std::move(bridges[bridge_id2].expolygon)));
|
append(acc, to_polygons(std::move(bridges[bridge_id2].expolygon)));
|
||||||
auto it_bridge_expansion = bridges[bridge_id2].bridge_expansion_begin;
|
auto it_bridge_expansion = bridges[bridge_id2].bridge_expansion_begin;
|
||||||
assert(it_bridge_expansion == bridge_expansions.end() || it_bridge_expansion->src_id == bridge_id2);
|
assert(it_bridge_expansion == bridge_expansions.end() || it_bridge_expansion->src_id == bridge_id2);
|
||||||
for (; it_bridge_expansion != bridge_expansions.end() && it_bridge_expansion->src_id == bridge_id2; ++ it_bridge_expansion)
|
for (; it_bridge_expansion != bridge_expansions.end() && it_bridge_expansion->src_id == bridge_id2; ++ it_bridge_expansion)
|
||||||
append(acc, to_polygons(std::move(it_bridge_expansion->expolygon)));
|
append(acc, to_polygons(it_bridge_expansion->expolygon));
|
||||||
}
|
}
|
||||||
//FIXME try to be smart and pick the best bridging angle for all?
|
//FIXME try to be smart and pick the best bridging angle for all?
|
||||||
templ.bridge_angle = bridges[bridge_id].angle;
|
if (!bridges[bridge_id].angle) {
|
||||||
|
assert(false && "Bridge angle must be pre-calculated!");
|
||||||
|
}
|
||||||
|
Surface templ{ stBottomBridge, {} };
|
||||||
|
templ.bridge_angle = bridges[bridge_id].angle ? *bridges[bridge_id].angle : -1;
|
||||||
//NOTE: The current regularization of the shells can create small unasigned regions in the object (E.G. benchy)
|
//NOTE: The current regularization of the shells can create small unasigned regions in the object (E.G. benchy)
|
||||||
// without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface.
|
// without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface.
|
||||||
// look for narrow_ensure_vertical_wall_thickness_region_radius filter.
|
// look for narrow_ensure_vertical_wall_thickness_region_radius filter.
|
||||||
@ -341,29 +343,105 @@ Surfaces expand_bridges_detect_orientations(
|
|||||||
// without safety offset, artifacts are generated (GH #2494)
|
// without safety offset, artifacts are generated (GH #2494)
|
||||||
// union_safety_offset_ex(acc)
|
// union_safety_offset_ex(acc)
|
||||||
for (ExPolygon &ex : final)
|
for (ExPolygon &ex : final)
|
||||||
out.emplace_back(templ, std::move(ex));
|
result.emplace_back(templ, std::move(ex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExpansionResult {
|
||||||
|
Algorithm::WaveSeeds anchors;
|
||||||
|
std::vector<Algorithm::RegionExpansionEx> expansions;
|
||||||
|
};
|
||||||
|
|
||||||
|
ExpansionResult expand_expolygons(
|
||||||
|
const ExPolygons& expolygons,
|
||||||
|
std::vector<ExpansionZone>& expansion_zones
|
||||||
|
) {
|
||||||
|
using namespace Algorithm;
|
||||||
|
WaveSeeds bridge_anchors;
|
||||||
|
std::vector<RegionExpansionEx> bridge_expansions;
|
||||||
|
|
||||||
|
unsigned processed_bridges_count = 0;
|
||||||
|
for (ExpansionZone& expansion_zone : expansion_zones) {
|
||||||
|
WaveSeeds seeds{wave_seeds(
|
||||||
|
expolygons,
|
||||||
|
expansion_zone.expolygons,
|
||||||
|
expansion_zone.parameters.tiny_expansion,
|
||||||
|
true
|
||||||
|
)};
|
||||||
|
std::vector<RegionExpansionEx> expansions{propagate_waves_ex(
|
||||||
|
seeds,
|
||||||
|
expansion_zone.expolygons,
|
||||||
|
expansion_zone.parameters
|
||||||
|
)};
|
||||||
|
|
||||||
|
for (WaveSeed &seed : seeds)
|
||||||
|
seed.boundary += processed_bridges_count;
|
||||||
|
for (RegionExpansionEx &expansion : expansions)
|
||||||
|
expansion.boundary_id += processed_bridges_count;
|
||||||
|
|
||||||
|
expansion_zone.expanded_into = ! expansions.empty();
|
||||||
|
|
||||||
|
append(bridge_anchors, std::move(seeds));
|
||||||
|
append(bridge_expansions, std::move(expansions));
|
||||||
|
|
||||||
|
processed_bridges_count += expansion_zone.expolygons.size();
|
||||||
|
}
|
||||||
|
return {bridge_anchors, bridge_expansions};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params,
|
||||||
|
// detect bridges.
|
||||||
|
// Trim "shells" by the expanded bridges.
|
||||||
|
Surfaces expand_bridges_detect_orientations(
|
||||||
|
Surfaces &surfaces,
|
||||||
|
std::vector<ExpansionZone>& expansion_zones,
|
||||||
|
const float closing_radius
|
||||||
|
)
|
||||||
|
{
|
||||||
|
using namespace Slic3r::Algorithm;
|
||||||
|
|
||||||
|
double thickness;
|
||||||
|
ExPolygons bridge_expolygons = fill_surfaces_extract_expolygons(surfaces, {stBottomBridge}, thickness);
|
||||||
|
if (bridge_expolygons.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
// Calculate bridge anchors and their expansions in their respective shell region.
|
||||||
|
ExpansionResult expansion_result{expand_expolygons(
|
||||||
|
bridge_expolygons,
|
||||||
|
expansion_zones
|
||||||
|
)};
|
||||||
|
|
||||||
|
std::vector<Bridge> bridges{get_grouped_bridges(
|
||||||
|
std::move(bridge_expolygons),
|
||||||
|
expansion_result.expansions
|
||||||
|
)};
|
||||||
|
bridge_expolygons.clear();
|
||||||
|
|
||||||
|
std::sort(expansion_result.anchors.begin(), expansion_result.anchors.end(), Algorithm::lower_by_src_and_boundary);
|
||||||
|
detect_bridge_directions(expansion_result.anchors, bridges, expansion_zones);
|
||||||
|
|
||||||
|
// Merge the groups with the same group id, produce surfaces by merging source overhangs with their newly expanded anchors.
|
||||||
|
std::sort(expansion_result.expansions.begin(), expansion_result.expansions.end(), [](auto &l, auto &r) {
|
||||||
|
return l.src_id < r.src_id || (l.src_id == r.src_id && l.boundary_id < r.boundary_id);
|
||||||
|
});
|
||||||
|
Surfaces out{merge_bridges(bridges, expansion_result.expansions, closing_radius)};
|
||||||
|
|
||||||
// Clip by the expanded bridges.
|
// Clip by the expanded bridges.
|
||||||
if (expanded_into_shells)
|
for (ExpansionZone& expansion_zone : expansion_zones)
|
||||||
shells = diff_ex(shells, out);
|
if (expansion_zone.expanded_into)
|
||||||
if (expanded_into_sparse)
|
expansion_zone.expolygons = diff_ex(expansion_zone.expolygons, out);
|
||||||
sparse = diff_ex(sparse, out);
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params.
|
Surfaces expand_merge_surfaces(
|
||||||
// Trim "shells" by the expanded bridges.
|
|
||||||
static Surfaces expand_merge_surfaces(
|
|
||||||
Surfaces &surfaces,
|
Surfaces &surfaces,
|
||||||
SurfaceType surface_type,
|
SurfaceType surface_type,
|
||||||
ExPolygons &shells,
|
std::vector<ExpansionZone>& expansion_zones,
|
||||||
const Algorithm::RegionExpansionParameters &expansion_params_into_solid_infill,
|
|
||||||
ExPolygons &sparse,
|
|
||||||
const Algorithm::RegionExpansionParameters &expansion_params_into_sparse_infill,
|
|
||||||
const float closing_radius,
|
const float closing_radius,
|
||||||
const double bridge_angle = -1.)
|
const double bridge_angle
|
||||||
|
)
|
||||||
{
|
{
|
||||||
using namespace Slic3r::Algorithm;
|
using namespace Slic3r::Algorithm;
|
||||||
|
|
||||||
@ -372,17 +450,17 @@ static Surfaces expand_merge_surfaces(
|
|||||||
if (src.empty())
|
if (src.empty())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
std::vector<RegionExpansion> expansions = propagate_waves(src, shells, expansion_params_into_solid_infill);
|
unsigned processed_expolygons_count = 0;
|
||||||
bool expanded_into_shells = !expansions.empty();
|
std::vector<RegionExpansion> expansions;
|
||||||
bool expanded_into_sparse = false;
|
for (ExpansionZone& expansion_zone : expansion_zones) {
|
||||||
{
|
std::vector<RegionExpansion> zone_expansions = propagate_waves(src, expansion_zone.expolygons, expansion_zone.parameters);
|
||||||
std::vector<RegionExpansion> expansions2 = propagate_waves(src, sparse, expansion_params_into_sparse_infill);
|
expansion_zone.expanded_into = !zone_expansions.empty();
|
||||||
if (! expansions2.empty()) {
|
|
||||||
expanded_into_sparse = true;
|
for (RegionExpansion &expansion : zone_expansions)
|
||||||
for (RegionExpansion &expansion : expansions2)
|
expansion.boundary_id += processed_expolygons_count;
|
||||||
expansion.boundary_id += uint32_t(shells.size());
|
|
||||||
append(expansions, std::move(expansions2));
|
processed_expolygons_count += expansion_zone.expolygons.size();
|
||||||
}
|
append(expansions, std::move(zone_expansions));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ExPolygon> expanded = merge_expansions_into_expolygons(std::move(src), std::move(expansions));
|
std::vector<ExPolygon> expanded = merge_expansions_into_expolygons(std::move(src), std::move(expansions));
|
||||||
@ -390,11 +468,10 @@ static Surfaces expand_merge_surfaces(
|
|||||||
// without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface.
|
// without the following closing operation, those regions will stay unfilled and cause small holes in the expanded surface.
|
||||||
// look for narrow_ensure_vertical_wall_thickness_region_radius filter.
|
// look for narrow_ensure_vertical_wall_thickness_region_radius filter.
|
||||||
expanded = closing_ex(expanded, closing_radius);
|
expanded = closing_ex(expanded, closing_radius);
|
||||||
// Trim the shells by the expanded expolygons.
|
// Trim the zones by the expanded expolygons.
|
||||||
if (expanded_into_shells)
|
for (ExpansionZone& expansion_zone : expansion_zones)
|
||||||
shells = diff_ex(shells, expanded);
|
if (expansion_zone.expanded_into)
|
||||||
if (expanded_into_sparse)
|
expansion_zone.expolygons = diff_ex(expansion_zone.expolygons, expanded);
|
||||||
sparse = diff_ex(sparse, expanded);
|
|
||||||
|
|
||||||
Surface templ{ surface_type, {} };
|
Surface templ{ surface_type, {} };
|
||||||
templ.bridge_angle = bridge_angle;
|
templ.bridge_angle = bridge_angle;
|
||||||
@ -443,16 +520,23 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
|||||||
double layer_thickness;
|
double layer_thickness;
|
||||||
ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, { stInternalSolid }, layer_thickness));
|
ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, { stInternalSolid }, layer_thickness));
|
||||||
ExPolygons sparse = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, { stInternal }, layer_thickness));
|
ExPolygons sparse = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, { stInternal }, layer_thickness));
|
||||||
|
ExPolygons top_expolygons = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, { stTop }, layer_thickness));
|
||||||
|
const auto expansion_params_into_sparse_infill = RegionExpansionParameters::build(expansion_min, expansion_step, max_nr_expansion_steps);
|
||||||
|
const auto expansion_params_into_solid_infill = RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps);
|
||||||
|
|
||||||
|
std::vector<ExpansionZone> expansion_zones{
|
||||||
|
ExpansionZone{std::move(shells), expansion_params_into_solid_infill},
|
||||||
|
ExpansionZone{std::move(sparse), expansion_params_into_sparse_infill},
|
||||||
|
ExpansionZone{std::move(top_expolygons), expansion_params_into_solid_infill},
|
||||||
|
};
|
||||||
|
|
||||||
SurfaceCollection bridges;
|
SurfaceCollection bridges;
|
||||||
const auto expansion_params_into_sparse_infill = RegionExpansionParameters::build(expansion_min, expansion_step, max_nr_expansion_steps);
|
|
||||||
{
|
{
|
||||||
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z;
|
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z;
|
||||||
const double custom_angle = this->region().config().bridge_angle.value;
|
const double custom_angle = this->region().config().bridge_angle.value;
|
||||||
const auto expansion_params_into_solid_infill = RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps);
|
|
||||||
bridges.surfaces = custom_angle > 0 ?
|
bridges.surfaces = custom_angle > 0 ?
|
||||||
expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, expansion_params_into_solid_infill, sparse, expansion_params_into_sparse_infill, closing_radius, Geometry::deg2rad(custom_angle)) :
|
expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, expansion_zones, closing_radius, Geometry::deg2rad(custom_angle)) :
|
||||||
expand_bridges_detect_orientations(m_fill_surfaces.surfaces, shells, expansion_params_into_solid_infill, sparse, expansion_params_into_sparse_infill, closing_radius);
|
expand_bridges_detect_orientations(m_fill_surfaces.surfaces, expansion_zones, closing_radius);
|
||||||
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done";
|
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done";
|
||||||
#if 0
|
#if 0
|
||||||
{
|
{
|
||||||
@ -462,25 +546,36 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Surfaces bottoms = expand_merge_surfaces(m_fill_surfaces.surfaces, stBottom, shells,
|
m_fill_surfaces.remove_types({ stTop });
|
||||||
RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps),
|
{
|
||||||
sparse, expansion_params_into_sparse_infill, closing_radius);
|
Surface top_templ(stTop, {});
|
||||||
Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, shells,
|
top_templ.thickness = layer_thickness;
|
||||||
RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps),
|
m_fill_surfaces.append(std::move(expansion_zones.back().expolygons), top_templ);
|
||||||
sparse, expansion_params_into_sparse_infill, closing_radius);
|
}
|
||||||
|
|
||||||
|
expansion_zones.pop_back();
|
||||||
|
|
||||||
|
expansion_zones.at(0).parameters = RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps);
|
||||||
|
Surfaces bottoms = expand_merge_surfaces(m_fill_surfaces.surfaces, stBottom, expansion_zones, closing_radius);
|
||||||
|
|
||||||
|
expansion_zones.at(0).parameters = RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps);
|
||||||
|
Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, expansion_zones, closing_radius);
|
||||||
|
|
||||||
// m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternal, stInternalSolid });
|
// m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternal, stInternalSolid });
|
||||||
m_fill_surfaces.clear();
|
m_fill_surfaces.clear();
|
||||||
reserve_more(m_fill_surfaces.surfaces, shells.size() + sparse.size() + bridges.size() + bottoms.size() + tops.size());
|
unsigned zones_expolygons_count = 0;
|
||||||
|
for (const ExpansionZone& zone : expansion_zones)
|
||||||
|
zones_expolygons_count += zone.expolygons.size();
|
||||||
|
reserve_more(m_fill_surfaces.surfaces, zones_expolygons_count + bridges.size() + bottoms.size() + tops.size());
|
||||||
{
|
{
|
||||||
Surface solid_templ(stInternalSolid, {});
|
Surface solid_templ(stInternalSolid, {});
|
||||||
solid_templ.thickness = layer_thickness;
|
solid_templ.thickness = layer_thickness;
|
||||||
m_fill_surfaces.append(std::move(shells), solid_templ);
|
m_fill_surfaces.append(std::move(expansion_zones[0].expolygons), solid_templ);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
Surface sparse_templ(stInternal, {});
|
Surface sparse_templ(stInternal, {});
|
||||||
sparse_templ.thickness = layer_thickness;
|
sparse_templ.thickness = layer_thickness;
|
||||||
m_fill_surfaces.append(std::move(sparse), sparse_templ);
|
m_fill_surfaces.append(std::move(expansion_zones[1].expolygons), sparse_templ);
|
||||||
}
|
}
|
||||||
m_fill_surfaces.append(std::move(bridges.surfaces));
|
m_fill_surfaces.append(std::move(bridges.surfaces));
|
||||||
m_fill_surfaces.append(std::move(bottoms));
|
m_fill_surfaces.append(std::move(bottoms));
|
||||||
|
224
src/libslic3r/LayerRegion.hpp
Normal file
224
src/libslic3r/LayerRegion.hpp
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
#ifndef slic3r_LayerRegion_hpp_
|
||||||
|
#define slic3r_LayerRegion_hpp_
|
||||||
|
|
||||||
|
#include "BoundingBox.hpp"
|
||||||
|
#include "ExtrusionEntityCollection.hpp"
|
||||||
|
#include "SurfaceCollection.hpp"
|
||||||
|
#include "libslic3r/Algorithm/RegionExpansion.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class Layer;
|
||||||
|
using LayerPtrs = std::vector<Layer*>;
|
||||||
|
class PrintRegion;
|
||||||
|
|
||||||
|
// Range of indices, providing support for range based loops.
|
||||||
|
template<typename T>
|
||||||
|
class IndexRange
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {}
|
||||||
|
IndexRange() = default;
|
||||||
|
|
||||||
|
// Just a bare minimum functionality iterator required by range-for loop.
|
||||||
|
class Iterator {
|
||||||
|
public:
|
||||||
|
T operator*() const { return m_idx; }
|
||||||
|
bool operator!=(const Iterator &rhs) const { return m_idx != rhs.m_idx; }
|
||||||
|
void operator++() { ++ m_idx; }
|
||||||
|
private:
|
||||||
|
friend class IndexRange<T>;
|
||||||
|
Iterator(T idx) : m_idx(idx) {}
|
||||||
|
T m_idx;
|
||||||
|
};
|
||||||
|
|
||||||
|
Iterator begin() const { assert(m_begin <= m_end); return Iterator(m_begin); };
|
||||||
|
Iterator end() const { assert(m_begin <= m_end); return Iterator(m_end); };
|
||||||
|
|
||||||
|
bool empty() const { assert(m_begin <= m_end); return m_begin >= m_end; }
|
||||||
|
T size() const { assert(m_begin <= m_end); return m_end - m_begin; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Index of the first extrusion in LayerRegion.
|
||||||
|
T m_begin { 0 };
|
||||||
|
// Index of the last extrusion in LayerRegion.
|
||||||
|
T m_end { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
template class IndexRange<uint32_t>;
|
||||||
|
|
||||||
|
using ExtrusionRange = IndexRange<uint32_t>;
|
||||||
|
using ExPolygonRange = IndexRange<uint32_t>;
|
||||||
|
|
||||||
|
class LayerRegion
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
[[nodiscard]] Layer* layer() { return m_layer; }
|
||||||
|
[[nodiscard]] const Layer* layer() const { return m_layer; }
|
||||||
|
[[nodiscard]] const PrintRegion& region() const { return *m_region; }
|
||||||
|
|
||||||
|
// collection of surfaces generated by slicing the original geometry
|
||||||
|
// divided by type top/bottom/internal
|
||||||
|
[[nodiscard]] const SurfaceCollection& slices() const { return m_slices; }
|
||||||
|
|
||||||
|
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
|
||||||
|
// and for re-starting of infills.
|
||||||
|
[[nodiscard]] const ExPolygons& fill_expolygons() const { return m_fill_expolygons; }
|
||||||
|
// and their bounding boxes
|
||||||
|
[[nodiscard]] const BoundingBoxes& fill_expolygons_bboxes() const { return m_fill_expolygons_bboxes; }
|
||||||
|
// Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands.
|
||||||
|
// Not used for a plain single material print with no infill modifiers.
|
||||||
|
[[nodiscard]] const ExPolygons& fill_expolygons_composite() const { return m_fill_expolygons_composite; }
|
||||||
|
// and their bounding boxes
|
||||||
|
[[nodiscard]] const BoundingBoxes& fill_expolygons_composite_bboxes() const { return m_fill_expolygons_composite_bboxes; }
|
||||||
|
|
||||||
|
// collection of surfaces generated by slicing the original geometry
|
||||||
|
// divided by type top/bottom/internal
|
||||||
|
[[nodiscard]] const SurfaceCollection& fill_surfaces() const { return m_fill_surfaces; }
|
||||||
|
|
||||||
|
// collection of extrusion paths/loops filling gaps
|
||||||
|
// These fills are generated by the perimeter generator.
|
||||||
|
// They are not printed on their own, but they are copied to this->fills during infill generation.
|
||||||
|
[[nodiscard]] const ExtrusionEntityCollection& thin_fills() const { return m_thin_fills; }
|
||||||
|
|
||||||
|
// collection of polylines representing the unsupported bridge edges
|
||||||
|
[[nodiscard]] const Polylines& unsupported_bridge_edges() const { return m_unsupported_bridge_edges; }
|
||||||
|
|
||||||
|
// ordered collection of extrusion paths/loops to build all perimeters
|
||||||
|
// (this collection contains only ExtrusionEntityCollection objects)
|
||||||
|
[[nodiscard]] const ExtrusionEntityCollection& perimeters() const { return m_perimeters; }
|
||||||
|
|
||||||
|
// ordered collection of extrusion paths to fill surfaces
|
||||||
|
// (this collection contains only ExtrusionEntityCollection objects)
|
||||||
|
[[nodiscard]] const ExtrusionEntityCollection& fills() const { return m_fills; }
|
||||||
|
|
||||||
|
Flow flow(FlowRole role) const;
|
||||||
|
Flow flow(FlowRole role, double layer_height) const;
|
||||||
|
Flow bridging_flow(FlowRole role, bool force_thick_bridges = false) const;
|
||||||
|
|
||||||
|
void slices_to_fill_surfaces_clipped();
|
||||||
|
void prepare_fill_surfaces();
|
||||||
|
// Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices.
|
||||||
|
void make_perimeters(
|
||||||
|
// Input slices for which the perimeters, gap fills and fill expolygons are to be generated.
|
||||||
|
const SurfaceCollection &slices,
|
||||||
|
// Ranges of perimeter extrusions and gap fill extrusions per suface, referencing
|
||||||
|
// newly created extrusions stored at this LayerRegion.
|
||||||
|
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
|
||||||
|
// All fill areas produced for all input slices above.
|
||||||
|
ExPolygons &fill_expolygons,
|
||||||
|
// Ranges of fill areas above per input slice.
|
||||||
|
std::vector<ExPolygonRange> &fill_expolygons_ranges);
|
||||||
|
void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered);
|
||||||
|
double infill_area_threshold() const;
|
||||||
|
// Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer.
|
||||||
|
void trim_surfaces(const Polygons &trimming_polygons);
|
||||||
|
// Single elephant foot compensation step, used by the elephant foor compensation at the 1st layer.
|
||||||
|
// Trim surfaces by trimming polygons (shrunk by an elephant foot compensation step), but don't shrink narrow parts so much that no perimeter would fit.
|
||||||
|
void elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons);
|
||||||
|
|
||||||
|
void export_region_slices_to_svg(const char *path) const;
|
||||||
|
void export_region_fill_surfaces_to_svg(const char *path) const;
|
||||||
|
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
|
||||||
|
void export_region_slices_to_svg_debug(const char *name) const;
|
||||||
|
void export_region_fill_surfaces_to_svg_debug(const char *name) const;
|
||||||
|
|
||||||
|
// Is there any valid extrusion assigned to this LayerRegion?
|
||||||
|
bool has_extrusions() const { return ! this->perimeters().empty() || ! this->fills().empty(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend class Layer;
|
||||||
|
friend class PrintObject;
|
||||||
|
|
||||||
|
LayerRegion(Layer *layer, const PrintRegion *region) : m_layer(layer), m_region(region) {}
|
||||||
|
~LayerRegion() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Modifying m_slices
|
||||||
|
friend std::string fix_slicing_errors(LayerPtrs&, const std::function<void()>&);
|
||||||
|
template<typename ThrowOnCancel>
|
||||||
|
friend void apply_mm_segmentation(PrintObject& print_object, ThrowOnCancel throw_on_cancel);
|
||||||
|
|
||||||
|
Layer *m_layer;
|
||||||
|
const PrintRegion *m_region;
|
||||||
|
|
||||||
|
// Backed up slices before they are split into top/bottom/internal.
|
||||||
|
// Only backed up for multi-region layers or layers with elephant foot compensation.
|
||||||
|
//FIXME Review whether not to simplify the code by keeping the raw_slices all the time.
|
||||||
|
ExPolygons m_raw_slices;
|
||||||
|
|
||||||
|
//FIXME make m_slices public for unit tests
|
||||||
|
public:
|
||||||
|
// collection of surfaces generated by slicing the original geometry
|
||||||
|
// divided by type top/bottom/internal
|
||||||
|
SurfaceCollection m_slices;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
|
||||||
|
// and for re-starting of infills.
|
||||||
|
ExPolygons m_fill_expolygons;
|
||||||
|
// and their bounding boxes
|
||||||
|
BoundingBoxes m_fill_expolygons_bboxes;
|
||||||
|
// Storage for fill regions produced for a single LayerIsland, of which infill splits into multiple islands.
|
||||||
|
// Not used for a plain single material print with no infill modifiers.
|
||||||
|
ExPolygons m_fill_expolygons_composite;
|
||||||
|
// and their bounding boxes
|
||||||
|
BoundingBoxes m_fill_expolygons_composite_bboxes;
|
||||||
|
|
||||||
|
// Collection of surfaces for infill generation, created by splitting m_slices by m_fill_expolygons.
|
||||||
|
SurfaceCollection m_fill_surfaces;
|
||||||
|
|
||||||
|
// Collection of extrusion paths/loops filling gaps
|
||||||
|
// These fills are generated by the perimeter generator.
|
||||||
|
// They are not printed on their own, but they are copied to this->fills during infill generation.
|
||||||
|
ExtrusionEntityCollection m_thin_fills;
|
||||||
|
|
||||||
|
// collection of polylines representing the unsupported bridge edges
|
||||||
|
Polylines m_unsupported_bridge_edges;
|
||||||
|
|
||||||
|
// ordered collection of extrusion paths/loops to build all perimeters
|
||||||
|
// (this collection contains only ExtrusionEntityCollection objects)
|
||||||
|
ExtrusionEntityCollection m_perimeters;
|
||||||
|
|
||||||
|
// ordered collection of extrusion paths to fill surfaces
|
||||||
|
// (this collection contains only ExtrusionEntityCollection objects)
|
||||||
|
ExtrusionEntityCollection m_fills;
|
||||||
|
|
||||||
|
// collection of expolygons representing the bridged areas (thus not
|
||||||
|
// needing support material)
|
||||||
|
// Polygons bridged;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExpansionZone {
|
||||||
|
ExPolygons expolygons;
|
||||||
|
Algorithm::RegionExpansionParameters parameters;
|
||||||
|
bool expanded_into = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params,
|
||||||
|
* detect bridges.
|
||||||
|
* Trim "shells" by the expanded bridges.
|
||||||
|
*/
|
||||||
|
Surfaces expand_bridges_detect_orientations(
|
||||||
|
Surfaces &surfaces,
|
||||||
|
std::vector<ExpansionZone>& expansion_zones,
|
||||||
|
const float closing_radius
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params.
|
||||||
|
* Trim "shells" by the expanded bridges.
|
||||||
|
*/
|
||||||
|
Surfaces expand_merge_surfaces(
|
||||||
|
Surfaces &surfaces,
|
||||||
|
SurfaceType surface_type,
|
||||||
|
std::vector<ExpansionZone>& expansion_zones,
|
||||||
|
const float closing_radius,
|
||||||
|
const double bridge_angle = -1
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // slic3r_LayerRegion_hpp_
|
@ -42,6 +42,7 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_anyptr.cpp
|
test_anyptr.cpp
|
||||||
test_jump_point_search.cpp
|
test_jump_point_search.cpp
|
||||||
test_support_spots_generator.cpp
|
test_support_spots_generator.cpp
|
||||||
|
test_layer_region.cpp
|
||||||
../data/prusaparts.cpp
|
../data/prusaparts.cpp
|
||||||
../data/prusaparts.hpp
|
../data/prusaparts.hpp
|
||||||
test_static_map.cpp
|
test_static_map.cpp
|
||||||
|
148
tests/libslic3r/test_layer_region.cpp
Normal file
148
tests/libslic3r/test_layer_region.cpp
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
#include "libslic3r/ClipperUtils.hpp"
|
||||||
|
#include "libslic3r/Geometry.hpp"
|
||||||
|
#include "libslic3r/Point.hpp"
|
||||||
|
#include "libslic3r/SVG.hpp"
|
||||||
|
#include <catch2/catch.hpp>
|
||||||
|
#include <libslic3r/LayerRegion.hpp>
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
using namespace Slic3r::Algorithm;
|
||||||
|
|
||||||
|
constexpr bool export_svgs = false;
|
||||||
|
|
||||||
|
ExPolygon rectangle(const Point& origin, const int width, const int height) {
|
||||||
|
return {
|
||||||
|
origin,
|
||||||
|
origin + Point{width, 0},
|
||||||
|
origin + Point{width, height},
|
||||||
|
origin + Point{0, height},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LayerRegionFixture {
|
||||||
|
Surfaces surfaces{
|
||||||
|
Surface{
|
||||||
|
stBottomBridge,
|
||||||
|
rectangle({scaled(-1.0), scaled(0.0)}, scaled(1.0), scaled(1.0))
|
||||||
|
},
|
||||||
|
Surface{
|
||||||
|
stBottomBridge,
|
||||||
|
rectangle({scaled(0.0), scaled(0.0)}, scaled(1.0), scaled(1.0))
|
||||||
|
},
|
||||||
|
Surface{
|
||||||
|
stBottomBridge,
|
||||||
|
rectangle({scaled(-3.0), scaled(0.0)}, scaled(1.0), scaled(1.0))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ExPolygons shells{{
|
||||||
|
rectangle({scaled(-1.0), scaled(1.0)}, scaled(3.0), scaled(1.0))
|
||||||
|
}};
|
||||||
|
ExPolygons sparse {{
|
||||||
|
rectangle({scaled(-2.0), scaled(-1.0)}, scaled(1.0), scaled(3.0))
|
||||||
|
}};
|
||||||
|
|
||||||
|
const float scaled_spacing{scaled(0.3)};
|
||||||
|
|
||||||
|
static constexpr const float expansion_step = scaled<float>(0.1);
|
||||||
|
static constexpr const size_t max_nr_expansion_steps = 5;
|
||||||
|
const float closing_radius = 0.55f * 0.65f * 1.05f * scaled_spacing;
|
||||||
|
const int shells_expansion_depth = scaled(0.6);
|
||||||
|
const RegionExpansionParameters expansion_params_into_solid_infill = RegionExpansionParameters::build(
|
||||||
|
shells_expansion_depth,
|
||||||
|
expansion_step,
|
||||||
|
max_nr_expansion_steps
|
||||||
|
);
|
||||||
|
const int sparse_expansion_depth = scaled(0.3);
|
||||||
|
const RegionExpansionParameters expansion_params_into_sparse_infill = RegionExpansionParameters::build(
|
||||||
|
sparse_expansion_depth,
|
||||||
|
expansion_step,
|
||||||
|
max_nr_expansion_steps
|
||||||
|
);
|
||||||
|
|
||||||
|
std::vector<ExpansionZone> expansion_zones{
|
||||||
|
ExpansionZone{
|
||||||
|
std::move(shells),
|
||||||
|
expansion_params_into_solid_infill,
|
||||||
|
},
|
||||||
|
ExpansionZone{
|
||||||
|
std::move(sparse),
|
||||||
|
expansion_params_into_sparse_infill,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(LayerRegionFixture, "test the surface expansion", "[LayerRegion]") {
|
||||||
|
const double custom_angle{1.234f};
|
||||||
|
|
||||||
|
const Surfaces result{expand_merge_surfaces(
|
||||||
|
surfaces, stBottomBridge,
|
||||||
|
expansion_zones,
|
||||||
|
closing_radius,
|
||||||
|
custom_angle
|
||||||
|
)};
|
||||||
|
|
||||||
|
if constexpr (export_svgs) {
|
||||||
|
SVG svg("general_expansion.svg", BoundingBox{
|
||||||
|
Point{scaled(-3.0), scaled(-1.0)},
|
||||||
|
Point{scaled(2.0), scaled(2.0)}
|
||||||
|
});
|
||||||
|
|
||||||
|
svg.draw(surfaces, "blue");
|
||||||
|
svg.draw(expansion_zones[0].expolygons, "green");
|
||||||
|
svg.draw(expansion_zones[1].expolygons, "red");
|
||||||
|
svg.draw_outline(result, "black", "", scale_(0.01));
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(result.size() == 2);
|
||||||
|
CHECK(result.at(0).bridge_angle == Approx(custom_angle));
|
||||||
|
CHECK(result.at(1).bridge_angle == Approx(custom_angle));
|
||||||
|
CHECK(result.at(0).expolygon.contour.size() == 22);
|
||||||
|
CHECK(result.at(1).expolygon.contour.size() == 14);
|
||||||
|
|
||||||
|
// These lines in the polygons should correspond to the expansion depth.
|
||||||
|
CHECK(result.at(0).expolygon.contour.lines().at(2).length() == shells_expansion_depth);
|
||||||
|
CHECK(result.at(1).expolygon.contour.lines().at(7).length() == sparse_expansion_depth);
|
||||||
|
CHECK(result.at(1).expolygon.contour.lines().at(11).length() == sparse_expansion_depth);
|
||||||
|
|
||||||
|
CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[0].expolygons).size() == 0);
|
||||||
|
CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[1].expolygons).size() == 0);
|
||||||
|
CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[0].expolygons).size() == 0);
|
||||||
|
CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[1].expolygons).size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(LayerRegionFixture, "test the bridge expansion with the bridge angle detection", "[LayerRegion]") {
|
||||||
|
Surfaces result{expand_bridges_detect_orientations(
|
||||||
|
surfaces,
|
||||||
|
expansion_zones,
|
||||||
|
closing_radius
|
||||||
|
)};
|
||||||
|
|
||||||
|
if constexpr (export_svgs) {
|
||||||
|
SVG svg("bridge_expansion.svg", BoundingBox{
|
||||||
|
Point{scaled(-3.0), scaled(-1.0)},
|
||||||
|
Point{scaled(2.0), scaled(2.0)}
|
||||||
|
});
|
||||||
|
|
||||||
|
svg.draw(surfaces, "blue");
|
||||||
|
svg.draw(expansion_zones[0].expolygons, "green");
|
||||||
|
svg.draw(expansion_zones[1].expolygons, "red");
|
||||||
|
svg.draw_outline(result, "black", "", scale_(0.01));
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(result.size() == 2);
|
||||||
|
CHECK(result.at(0).bridge_angle == Approx(1.5707963268));
|
||||||
|
CHECK(std::fmod(result.at(1).bridge_angle, M_PI) == Approx(0.0));
|
||||||
|
CHECK(result.at(0).expolygon.contour.size() == 22);
|
||||||
|
CHECK(result.at(1).expolygon.contour.size() == 14);
|
||||||
|
|
||||||
|
// These lines in the polygons should correspond to the expansion depth.
|
||||||
|
CHECK(result.at(0).expolygon.contour.lines().at(2).length() == shells_expansion_depth);
|
||||||
|
CHECK(result.at(1).expolygon.contour.lines().at(7).length() == sparse_expansion_depth);
|
||||||
|
CHECK(result.at(1).expolygon.contour.lines().at(11).length() == sparse_expansion_depth);
|
||||||
|
|
||||||
|
CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[0].expolygons).size() == 0);
|
||||||
|
CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[1].expolygons).size() == 0);
|
||||||
|
CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[0].expolygons).size() == 0);
|
||||||
|
CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[1].expolygons).size() == 0);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user