WIP Refactoring of Layers: LayerIslands filled in with perimeter
extrusions, gap fill extrusions and fill regions.
This commit is contained in:
parent
2eb0417018
commit
409fae6183
@ -187,6 +187,8 @@ public:
|
|||||||
friend BoundingBox get_extents_rotated(const Points &points, double angle);
|
friend BoundingBox get_extents_rotated(const Points &points, double angle);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using BoundingBoxes = std::vector<BoundingBox>;
|
||||||
|
|
||||||
class BoundingBox3 : public BoundingBox3Base<Vec3crd>
|
class BoundingBox3 : public BoundingBox3Base<Vec3crd>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -432,6 +432,8 @@ Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float d
|
|||||||
{ return PolyTreeToExPolygons(expolygons_offset_pt(expolygons, delta, joinType, miterLimit)); }
|
{ return PolyTreeToExPolygons(expolygons_offset_pt(expolygons, delta, joinType, miterLimit)); }
|
||||||
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||||
{ return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); }
|
{ return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); }
|
||||||
|
Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||||
|
{ return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); }
|
||||||
|
|
||||||
Polygons offset2(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
|
Polygons offset2(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
|
||||||
{
|
{
|
||||||
|
@ -326,6 +326,7 @@ Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta
|
|||||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||||
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||||
|
Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||||
|
|
||||||
inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); }
|
inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); }
|
||||||
inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); }
|
inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); }
|
||||||
|
@ -306,6 +306,18 @@ Lines ExPolygon::lines() const
|
|||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do expolygons match? If they match, they must have the same topology,
|
||||||
|
// however their contours may be rotated.
|
||||||
|
bool expolygons_match(const ExPolygon &l, const ExPolygon &r)
|
||||||
|
{
|
||||||
|
if (l.holes.size() != r.holes.size() || ! polygons_match(l.contour, r.contour))
|
||||||
|
return false;
|
||||||
|
for (size_t hole_idx = 0; hole_idx < l.holes.size(); ++ hole_idx)
|
||||||
|
if (! polygons_match(l.holes[hole_idx], r.holes[hole_idx]))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
BoundingBox get_extents(const ExPolygon &expolygon)
|
BoundingBox get_extents(const ExPolygon &expolygon)
|
||||||
{
|
{
|
||||||
return get_extents(expolygon.contour);
|
return get_extents(expolygon.contour);
|
||||||
|
@ -391,6 +391,10 @@ inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double toleranc
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do expolygons match? If they match, they must have the same topology,
|
||||||
|
// however their contours may be rotated.
|
||||||
|
bool expolygons_match(const ExPolygon &l, const ExPolygon &r);
|
||||||
|
|
||||||
BoundingBox get_extents(const ExPolygon &expolygon);
|
BoundingBox get_extents(const ExPolygon &expolygon);
|
||||||
BoundingBox get_extents(const ExPolygons &expolygons);
|
BoundingBox get_extents(const ExPolygons &expolygons);
|
||||||
BoundingBox get_extents_rotated(const ExPolygon &poly, double angle);
|
BoundingBox get_extents_rotated(const ExPolygon &poly, double angle);
|
||||||
|
@ -380,90 +380,279 @@ void Layer::make_perimeters()
|
|||||||
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id();
|
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id();
|
||||||
|
|
||||||
// keep track of regions whose perimeters we have already generated
|
// keep track of regions whose perimeters we have already generated
|
||||||
std::vector<unsigned char> done(m_regions.size(), false);
|
std::vector<unsigned char> done(m_regions.size(), false);
|
||||||
|
std::vector<uint32_t> layer_region_ids;
|
||||||
LayerRegionPtrs layerms;
|
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> perimeter_and_gapfill_ranges;
|
||||||
for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm)
|
ExPolygons fill_expolygons;
|
||||||
if ((*layerm)->slices().empty()) {
|
std::vector<ExPolygonRange> fill_expolygons_ranges;
|
||||||
(*layerm)->m_perimeters.clear();
|
SurfacesPtr surfaces_to_merge;
|
||||||
(*layerm)->m_fills.clear();
|
SurfacesPtr surfaces_to_merge_temp;
|
||||||
(*layerm)->m_thin_fills.clear();
|
|
||||||
} else {
|
auto layer_region_reset_perimeters = [](LayerRegion &layerm) {
|
||||||
size_t region_id = layerm - m_regions.begin();
|
layerm.m_perimeters.clear();
|
||||||
if (done[region_id])
|
layerm.m_fills.clear();
|
||||||
continue;
|
layerm.m_thin_fills.clear();
|
||||||
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id;
|
layerm.m_fill_expolygons.clear();
|
||||||
done[region_id] = true;
|
layerm.m_fill_expolygons_bboxes.clear();
|
||||||
const PrintRegionConfig &config = (*layerm)->region().config();
|
layerm.m_fill_expolygons_composite.clear();
|
||||||
|
layerm.m_fill_expolygons_composite_bboxes.clear();
|
||||||
// find compatible regions
|
};
|
||||||
layerms.clear();
|
|
||||||
layerms.push_back(*layerm);
|
for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm)
|
||||||
for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it)
|
if (size_t region_id = layerm - m_regions.begin(); ! done[region_id]) {
|
||||||
if (! (*it)->slices().empty()) {
|
layer_region_reset_perimeters(**layerm);
|
||||||
LayerRegion* other_layerm = *it;
|
if (! (*layerm)->slices().empty()) {
|
||||||
const PrintRegionConfig &other_config = other_layerm->region().config();
|
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id;
|
||||||
if (config.perimeter_extruder == other_config.perimeter_extruder
|
done[region_id] = true;
|
||||||
&& config.perimeters == other_config.perimeters
|
const PrintRegionConfig &config = (*layerm)->region().config();
|
||||||
&& config.perimeter_speed == other_config.perimeter_speed
|
|
||||||
&& config.external_perimeter_speed == other_config.external_perimeter_speed
|
perimeter_and_gapfill_ranges.clear();
|
||||||
&& (config.gap_fill_enabled ? config.gap_fill_speed.value : 0.) ==
|
fill_expolygons.clear();
|
||||||
(other_config.gap_fill_enabled ? other_config.gap_fill_speed.value : 0.)
|
fill_expolygons_ranges.clear();
|
||||||
&& config.overhangs == other_config.overhangs
|
surfaces_to_merge.clear();
|
||||||
&& config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width")
|
|
||||||
&& config.thin_walls == other_config.thin_walls
|
// find compatible regions
|
||||||
&& config.external_perimeters_first == other_config.external_perimeters_first
|
layer_region_ids.clear();
|
||||||
&& config.infill_overlap == other_config.infill_overlap
|
layer_region_ids.push_back(region_id);
|
||||||
&& config.fuzzy_skin == other_config.fuzzy_skin
|
for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it)
|
||||||
&& config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness
|
if (! (*it)->slices().empty()) {
|
||||||
&& config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist)
|
LayerRegion* other_layerm = *it;
|
||||||
{
|
const PrintRegionConfig &other_config = other_layerm->region().config();
|
||||||
other_layerm->m_perimeters.clear();
|
if (config.perimeter_extruder == other_config.perimeter_extruder
|
||||||
other_layerm->m_fills.clear();
|
&& config.perimeters == other_config.perimeters
|
||||||
other_layerm->m_thin_fills.clear();
|
&& config.perimeter_speed == other_config.perimeter_speed
|
||||||
layerms.push_back(other_layerm);
|
&& config.external_perimeter_speed == other_config.external_perimeter_speed
|
||||||
done[it - m_regions.begin()] = true;
|
&& (config.gap_fill_enabled ? config.gap_fill_speed.value : 0.) ==
|
||||||
}
|
(other_config.gap_fill_enabled ? other_config.gap_fill_speed.value : 0.)
|
||||||
}
|
&& config.overhangs == other_config.overhangs
|
||||||
|
&& config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width")
|
||||||
ExPolygons fill_expolygons;
|
&& config.thin_walls == other_config.thin_walls
|
||||||
if (layerms.size() == 1) { // optimization
|
&& config.external_perimeters_first == other_config.external_perimeters_first
|
||||||
(*layerm)->m_fill_expolygons.clear();
|
&& config.infill_overlap == other_config.infill_overlap
|
||||||
(*layerm)->m_fill_surfaces.clear();
|
&& config.fuzzy_skin == other_config.fuzzy_skin
|
||||||
(*layerm)->make_perimeters((*layerm)->slices(), fill_expolygons);
|
&& config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness
|
||||||
(*layerm)->m_fill_expolygons = std::move(fill_expolygons);
|
&& config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist)
|
||||||
} else {
|
{
|
||||||
SurfaceCollection new_slices;
|
layer_region_reset_perimeters(*other_layerm);
|
||||||
// Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence.
|
layer_region_ids.push_back(it - m_regions.begin());
|
||||||
LayerRegion *layerm_config = layerms.front();
|
done[it - m_regions.begin()] = true;
|
||||||
{
|
}
|
||||||
// group slices (surfaces) according to number of extra perimeters
|
}
|
||||||
std::map<unsigned short, Surfaces> slices; // extra_perimeters => [ surface, surface... ]
|
|
||||||
for (LayerRegion *layerm : layerms) {
|
if (layer_region_ids.size() == 1) { // optimization
|
||||||
for (const Surface &surface : layerm->slices())
|
(*layerm)->make_perimeters((*layerm)->slices(), perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges);
|
||||||
slices[surface.extra_perimeters].emplace_back(surface);
|
this->sort_perimeters_into_islands((*layerm)->slices(), region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids);
|
||||||
if (layerm->region().config().fill_density > layerm_config->region().config().fill_density)
|
} else {
|
||||||
layerm_config = layerm;
|
SurfaceCollection new_slices;
|
||||||
layerm->m_fill_surfaces.clear();
|
// Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence.
|
||||||
layerm->m_fill_expolygons.clear();
|
LayerRegion *layerm_config = m_regions[layer_region_ids.front()];
|
||||||
}
|
{
|
||||||
// merge the surfaces assigned to each group
|
// Merge slices (surfaces) according to number of extra perimeters.
|
||||||
for (std::pair<const unsigned short,Surfaces> &surfaces_with_extra_perimeters : slices)
|
for (uint32_t region_id : layer_region_ids) {
|
||||||
new_slices.append(offset_ex(surfaces_with_extra_perimeters.second, ClipperSafetyOffset), surfaces_with_extra_perimeters.second.front());
|
LayerRegion &layerm = *m_regions[region_id];
|
||||||
}
|
for (const Surface &surface : layerm.slices())
|
||||||
// make perimeters
|
surfaces_to_merge.emplace_back(&surface);
|
||||||
layerm_config->make_perimeters(new_slices, fill_expolygons);
|
if (layerm.region().config().fill_density > layerm_config->region().config().fill_density)
|
||||||
// assign fill_surfaces to each layer
|
layerm_config = &layerm;
|
||||||
if (! fill_expolygons.empty()) {
|
}
|
||||||
// Separate the fill surfaces.
|
std::sort(surfaces_to_merge.begin(), surfaces_to_merge.end(), [](const Surface *l, const Surface *r){ return l->extra_perimeters < r->extra_perimeters; });
|
||||||
for (LayerRegion *l : layerms)
|
for (size_t i = 0; i < surfaces_to_merge.size();) {
|
||||||
l->m_fill_expolygons = intersection_ex(l->slices().surfaces, fill_expolygons);
|
size_t j = i;
|
||||||
}
|
const Surface &first = *surfaces_to_merge[i];
|
||||||
}
|
size_t extra_perimeters = first.extra_perimeters;
|
||||||
}
|
for (; j < surfaces_to_merge.size() && surfaces_to_merge[j]->extra_perimeters == extra_perimeters; ++ j) ;
|
||||||
|
if (i + 1 == j)
|
||||||
|
// Nothing to merge, just copy.
|
||||||
|
new_slices.surfaces.emplace_back(*surfaces_to_merge[i]);
|
||||||
|
else {
|
||||||
|
surfaces_to_merge_temp.assign(surfaces_to_merge.begin() + i, surfaces_to_merge.begin() + j);
|
||||||
|
new_slices.append(offset_ex(surfaces_to_merge_temp, ClipperSafetyOffset), first);
|
||||||
|
}
|
||||||
|
i = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// make perimeters
|
||||||
|
layerm_config->make_perimeters(new_slices, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges);
|
||||||
|
this->sort_perimeters_into_islands(new_slices, region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done";
|
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Layer::sort_perimeters_into_islands(
|
||||||
|
// Slices for which perimeters and fill_expolygons were just created.
|
||||||
|
// The slices may have been created by merging multiple source slices with the same perimeter parameters.
|
||||||
|
const SurfaceCollection &slices,
|
||||||
|
// Region where the perimeters, gap fills and fill expolygons are stored.
|
||||||
|
const uint32_t region_id,
|
||||||
|
// Perimeters and gap fills produced by the perimeter generator for the slices,
|
||||||
|
// sorted by the source slices.
|
||||||
|
const std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
|
||||||
|
// Fill expolygons produced for all source slices above.
|
||||||
|
ExPolygons &&fill_expolygons,
|
||||||
|
// Fill expolygon ranges sorted by the source slices.
|
||||||
|
const std::vector<ExPolygonRange> &fill_expolygons_ranges,
|
||||||
|
// If the current layer consists of multiple regions, then the fill_expolygons above are split by the source LayerRegion surfaces.
|
||||||
|
const std::vector<uint32_t> &layer_region_ids)
|
||||||
|
{
|
||||||
|
for (LayerSlice &lslice : this->lslices_ex)
|
||||||
|
lslice.islands.clear();
|
||||||
|
|
||||||
|
LayerRegion &this_layer_region = *m_regions[region_id];
|
||||||
|
|
||||||
|
// Bounding boxes of fill_expolygons.
|
||||||
|
BoundingBoxes fill_expolygons_bboxes;
|
||||||
|
fill_expolygons_bboxes.reserve(fill_expolygons.size());
|
||||||
|
for (const ExPolygon &expolygon : fill_expolygons)
|
||||||
|
fill_expolygons_bboxes.emplace_back(get_extents(expolygon));
|
||||||
|
|
||||||
|
// Map of source fill_expolygon into region and fill_expolygon of that region.
|
||||||
|
// -1: not set
|
||||||
|
std::vector<std::pair<int, int>> map_expolygon_to_region_and_fill;
|
||||||
|
|
||||||
|
// assign fill_surfaces to each layer
|
||||||
|
if (! fill_expolygons.empty()) {
|
||||||
|
if (layer_region_ids.size() == 1) {
|
||||||
|
this_layer_region.m_fill_expolygons = std::move(fill_expolygons);
|
||||||
|
this_layer_region.m_fill_expolygons_bboxes = std::move(fill_expolygons_bboxes);
|
||||||
|
} else {
|
||||||
|
// Sort the bounding boxes lexicographically.
|
||||||
|
std::vector<uint32_t> fill_expolygons_bboxes_sorted(fill_expolygons_bboxes.size());
|
||||||
|
std::iota(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), 0);
|
||||||
|
std::sort(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), [&fill_expolygons_bboxes](uint32_t lhs, uint32_t rhs){
|
||||||
|
const BoundingBox &bbl = fill_expolygons_bboxes[lhs];
|
||||||
|
const BoundingBox &bbr = fill_expolygons_bboxes[rhs];
|
||||||
|
return bbl.min < bbr.min || (bbl.min == bbr.min && bbl.max < bbr.max);
|
||||||
|
});
|
||||||
|
map_expolygon_to_region_and_fill.assign(fill_expolygons.size(), std::make_pair(-1, -1));
|
||||||
|
for (uint32_t region_idx : layer_region_ids) {
|
||||||
|
LayerRegion &l = *m_regions[region_idx];
|
||||||
|
l.m_fill_expolygons = intersection_ex(l.slices().surfaces, fill_expolygons);
|
||||||
|
l.m_fill_expolygons_bboxes.reserve(l.fill_expolygons().size());
|
||||||
|
for (const ExPolygon &expolygon : l.fill_expolygons()) {
|
||||||
|
BoundingBox bbox = get_extents(expolygon);
|
||||||
|
l.m_fill_expolygons_bboxes.emplace_back(bbox);
|
||||||
|
auto it_bbox = std::lower_bound(fill_expolygons_bboxes_sorted.begin(), fill_expolygons_bboxes_sorted.end(), bbox, [&fill_expolygons_bboxes](uint32_t lhs, const BoundingBox &bbr){
|
||||||
|
const BoundingBox &bbl = fill_expolygons_bboxes[lhs];
|
||||||
|
return bbl.min < bbr.min || (bbl.min == bbr.min && bbl.max < bbr.max);
|
||||||
|
});
|
||||||
|
if (it_bbox != fill_expolygons_bboxes_sorted.end())
|
||||||
|
if (uint32_t fill_id = *it_bbox; fill_expolygons_bboxes[fill_id] == bbox) {
|
||||||
|
// With a very high probability the two expolygons match exactly. Confirm that.
|
||||||
|
if (expolygons_match(expolygon, fill_expolygons[fill_id])) {
|
||||||
|
std::pair<int, int> &ref = map_expolygon_to_region_and_fill[fill_id];
|
||||||
|
// Only one expolygon produced by intersection with LayerRegion surface may match an expolygon of fill_expolygons.
|
||||||
|
assert(ref.first == -1);
|
||||||
|
ref.first = region_idx;
|
||||||
|
ref.second = int(&expolygon - l.fill_expolygons().data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first,
|
||||||
|
// so we can just test a point inside ExPolygon::contour and we may skip testing the holes.
|
||||||
|
auto point_inside_surface = [this](const size_t lslice_idx, const Point &point) {
|
||||||
|
const BoundingBox &bbox = this->lslices_ex[lslice_idx].bbox;
|
||||||
|
return point.x() >= bbox.min.x() && point.x() < bbox.max.x() &&
|
||||||
|
point.y() >= bbox.min.y() && point.y() < bbox.max.y() &&
|
||||||
|
this->lslices[lslice_idx].contour.contains(point);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Take one sample point for each source slice, to be used to sort source slices into layer slices.
|
||||||
|
// source slice index + its sample.
|
||||||
|
std::vector<std::pair<uint32_t, Point>> perimeter_slices_queue;
|
||||||
|
perimeter_slices_queue.reserve(slices.size());
|
||||||
|
for (uint32_t islice = 0; islice < uint32_t(slices.size()); ++ islice) {
|
||||||
|
const std::pair<ExtrusionRange, ExtrusionRange> &extrusions = perimeter_and_gapfill_ranges[islice];
|
||||||
|
Point sample;
|
||||||
|
bool sample_set = false;
|
||||||
|
if (! extrusions.first.empty()) {
|
||||||
|
sample = this_layer_region.perimeters().entities[*extrusions.first.begin()]->first_point();
|
||||||
|
sample_set = true;
|
||||||
|
} else if (! extrusions.second.empty()) {
|
||||||
|
sample = this_layer_region.thin_fills().entities[*extrusions.second.begin()]->first_point();
|
||||||
|
sample_set = true;
|
||||||
|
} else if (const ExPolygonRange &fill_expolygon_range = fill_expolygons_ranges[islice]; ! fill_expolygons.empty()) {
|
||||||
|
for (uint32_t iexpoly : fill_expolygon_range)
|
||||||
|
if (const ExPolygon &expoly = fill_expolygons[iexpoly]; ! expoly.empty()) {
|
||||||
|
sample = expoly.contour.points.front();
|
||||||
|
sample_set = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sample_set)
|
||||||
|
perimeter_slices_queue.emplace_back(islice, sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort perimeter extrusions, thin fill extrusions and fill expolygons into islands.
|
||||||
|
std::vector<uint32_t> region_fill_sorted_last;
|
||||||
|
for (int lslice_idx = int(this->lslices_ex.size()) - 1; lslice_idx >= 0 && ! perimeter_slices_queue.empty(); -- lslice_idx) {
|
||||||
|
for (auto it_source_slice = perimeter_slices_queue.begin(); it_source_slice != perimeter_slices_queue.end(); ++ it_source_slice)
|
||||||
|
if (point_inside_surface(lslice_idx, it_source_slice->second)) {
|
||||||
|
this->lslices_ex[lslice_idx].islands.push_back({});
|
||||||
|
LayerIsland &island = this->lslices_ex[lslice_idx].islands.back();
|
||||||
|
const uint32_t source_slice_idx = it_source_slice->first;
|
||||||
|
island.perimeters = LayerExtrusionRange(region_id, perimeter_and_gapfill_ranges[source_slice_idx].first);
|
||||||
|
island.thin_fills = perimeter_and_gapfill_ranges[source_slice_idx].second;
|
||||||
|
if (ExPolygonRange fill_range = fill_expolygons_ranges[source_slice_idx]; ! fill_range.empty()) {
|
||||||
|
if (layer_region_ids.size() == 1) {
|
||||||
|
// Layer island is made of one fill region only.
|
||||||
|
island.fill_expolygons = fill_range;
|
||||||
|
island.fill_region_id = region_id;
|
||||||
|
} else {
|
||||||
|
// Check whether the fill expolygons of this island were split into multiple regions.
|
||||||
|
island.fill_region_id = LayerIsland::fill_region_composite_id;
|
||||||
|
for (uint32_t fill_idx : fill_range) {
|
||||||
|
const std::pair<int, int> &kvp = map_expolygon_to_region_and_fill[fill_idx];
|
||||||
|
if (kvp.first == -1 || (island.fill_region_id != -1 && island.fill_region_id != kvp.second)) {
|
||||||
|
island.fill_region_id = LayerIsland::fill_region_composite_id;
|
||||||
|
break;
|
||||||
|
} else
|
||||||
|
island.fill_region_id = kvp.second;
|
||||||
|
}
|
||||||
|
if (island.fill_expolygons_composite()) {
|
||||||
|
// They were split, thus store the unsplit "composite" expolygons into the region of perimeters.
|
||||||
|
auto begin = uint32_t(this_layer_region.fill_expolygons_composite().size());
|
||||||
|
this_layer_region.m_fill_expolygons_composite.reserve(this_layer_region.fill_expolygons_composite().size() + fill_range.size());
|
||||||
|
std::move(fill_expolygons.begin() + *fill_range.begin(), fill_expolygons.begin() + *fill_range.end(), std::back_inserter(this_layer_region.m_fill_expolygons_composite));
|
||||||
|
this_layer_region.m_fill_expolygons_composite_bboxes.insert(this_layer_region.m_fill_expolygons_composite_bboxes.end(),
|
||||||
|
fill_expolygons_bboxes.begin() + *fill_range.begin(), fill_expolygons_bboxes.begin() + *fill_range.end());
|
||||||
|
island.fill_expolygons = ExPolygonRange(begin, uint32_t(this_layer_region.fill_expolygons_composite().size()));
|
||||||
|
} else {
|
||||||
|
if (region_fill_sorted_last.empty())
|
||||||
|
region_fill_sorted_last.assign(m_regions.size(), 0);
|
||||||
|
uint32_t &last = region_fill_sorted_last[island.fill_region_id];
|
||||||
|
// They were not split and they belong to the same region.
|
||||||
|
// Sort the region m_fill_expolygons to a continuous span.
|
||||||
|
uint32_t begin = last;
|
||||||
|
LayerRegion &layerm = *m_regions[island.fill_region_id];
|
||||||
|
for (uint32_t fill_id : fill_range) {
|
||||||
|
uint32_t region_fill_id = map_expolygon_to_region_and_fill[fill_id].second;
|
||||||
|
assert(region_fill_id >= last);
|
||||||
|
if (region_fill_id > last) {
|
||||||
|
std::swap(layerm.m_fill_expolygons[region_fill_id], layerm.m_fill_expolygons[last]);
|
||||||
|
std::swap(layerm.m_fill_expolygons_bboxes[region_fill_id], layerm.m_fill_expolygons_bboxes[last]);
|
||||||
|
}
|
||||||
|
++ last;
|
||||||
|
}
|
||||||
|
island.fill_expolygons = ExPolygonRange(begin, last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (std::next(it_source_slice) != perimeter_slices_queue.end()) {
|
||||||
|
// Remove the current slice & point pair from the queue.
|
||||||
|
*it_source_slice = perimeter_slices_queue.back();
|
||||||
|
perimeter_slices_queue.pop_back();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Layer::export_region_slices_to_svg(const char *path) const
|
void Layer::export_region_slices_to_svg(const char *path) const
|
||||||
{
|
{
|
||||||
BoundingBox bbox;
|
BoundingBox bbox;
|
||||||
|
@ -28,6 +28,71 @@ namespace FillLightning {
|
|||||||
class Generator;
|
class Generator;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Range of indices, providing support for range based loops.
|
||||||
|
template<typename T>
|
||||||
|
class IndexRange
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// Just a bare minimum functionality iterator required by range-for loop.
|
||||||
|
template<typename T>
|
||||||
|
class IteratorType {
|
||||||
|
public:
|
||||||
|
T operator*() const { return m_idx; }
|
||||||
|
bool operator!=(const IteratorType &rhs) const { return m_idx != rhs.m_idx; }
|
||||||
|
void operator++() { ++ m_idx; }
|
||||||
|
private:
|
||||||
|
friend class IndexRange<T>;
|
||||||
|
IteratorType(T idx) : m_idx(idx) {}
|
||||||
|
T m_idx;
|
||||||
|
};
|
||||||
|
// Index of the first extrusion in LayerRegion.
|
||||||
|
T m_begin { 0 };
|
||||||
|
// Index of the last extrusion in LayerRegion.
|
||||||
|
T m_end { 0 };
|
||||||
|
|
||||||
|
public:
|
||||||
|
IndexRange(T ibegin, T iend) : m_begin(ibegin), m_end(iend) {}
|
||||||
|
IndexRange() = default;
|
||||||
|
|
||||||
|
using Iterator = IteratorType<T>;
|
||||||
|
|
||||||
|
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; }
|
||||||
|
};
|
||||||
|
|
||||||
|
using ExtrusionRange = IndexRange<uint32_t>;
|
||||||
|
using ExPolygonRange = IndexRange<uint32_t>;
|
||||||
|
|
||||||
|
// Range of extrusions, referencing the source region by an index.
|
||||||
|
class LayerExtrusionRange : public ExtrusionRange
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LayerExtrusionRange(uint32_t iregion, uint32_t ibegin, uint32_t iend) : m_region(iregion), ExtrusionRange(ibegin, iend) {}
|
||||||
|
LayerExtrusionRange(uint32_t iregion, ExtrusionRange extrusion_range) : m_region(iregion), ExtrusionRange(extrusion_range) {}
|
||||||
|
LayerExtrusionRange() = default;
|
||||||
|
|
||||||
|
// Index of LayerRegion in Layer.
|
||||||
|
uint32_t region() const { return m_region; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Index of LayerRegion in Layer.
|
||||||
|
uint32_t m_region { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Most likely one LayerIsland will be filled with maximum one fill type.
|
||||||
|
static constexpr const size_t LayerExtrusionRangesStaticSize = 1;
|
||||||
|
using LayerExtrusionRanges =
|
||||||
|
#ifdef NDEBUG
|
||||||
|
// To reduce memory allocation in release mode.
|
||||||
|
boost::container::small_vector<LayerExtrusionRange, LayerExtrusionRangesStaticSize>;
|
||||||
|
#else // NDEBUG
|
||||||
|
// To ease debugging.
|
||||||
|
std::vector<LayerExtrusionRange>;
|
||||||
|
#endif // NDEBUG
|
||||||
|
|
||||||
class LayerRegion
|
class LayerRegion
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -39,7 +104,16 @@ public:
|
|||||||
// divided by type top/bottom/internal
|
// divided by type top/bottom/internal
|
||||||
[[nodiscard]] const SurfaceCollection& slices() const { return m_slices; }
|
[[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; }
|
[[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
|
// collection of surfaces generated by slicing the original geometry
|
||||||
// divided by type top/bottom/internal
|
// divided by type top/bottom/internal
|
||||||
@ -67,7 +141,17 @@ public:
|
|||||||
|
|
||||||
void slices_to_fill_surfaces_clipped();
|
void slices_to_fill_surfaces_clipped();
|
||||||
void prepare_fill_surfaces();
|
void prepare_fill_surfaces();
|
||||||
void make_perimeters(const SurfaceCollection &slices, ExPolygons &fill_expolygons);
|
// 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);
|
void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered);
|
||||||
double infill_area_threshold() const;
|
double infill_area_threshold() const;
|
||||||
// Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer.
|
// Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer.
|
||||||
@ -116,11 +200,18 @@ private:
|
|||||||
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
|
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
|
||||||
// and for re-starting of infills.
|
// and for re-starting of infills.
|
||||||
ExPolygons m_fill_expolygons;
|
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
|
// Collection of surfaces for infill generation, created by splitting m_slices by m_fill_expolygons.
|
||||||
SurfaceCollection m_fill_surfaces;
|
SurfaceCollection m_fill_surfaces;
|
||||||
|
|
||||||
// collection of extrusion paths/loops filling gaps
|
// Collection of extrusion paths/loops filling gaps
|
||||||
// These fills are generated by the perimeter generator.
|
// 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.
|
// They are not printed on their own, but they are copied to this->fills during infill generation.
|
||||||
ExtrusionEntityCollection m_thin_fills;
|
ExtrusionEntityCollection m_thin_fills;
|
||||||
@ -141,66 +232,31 @@ private:
|
|||||||
// Polygons bridged;
|
// Polygons bridged;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Range of two 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;
|
|
||||||
|
|
||||||
T begin() const { assert(m_begin <= m_end); return m_begin; };
|
|
||||||
T end() const { assert(m_begin <= m_end); return 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 };
|
|
||||||
};
|
|
||||||
|
|
||||||
class LayerExtrusionRange : public IndexRange<uint32_t>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LayerExtrusionRange(uint32_t iregion, uint32_t ibegin, uint32_t iend) : m_region(iregion), IndexRange<uint32_t>(ibegin, iend) {}
|
|
||||||
LayerExtrusionRange() = default;
|
|
||||||
|
|
||||||
// Index of LayerRegion in Layer.
|
|
||||||
uint32_t region() const { return m_region; };
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Index of LayerRegion in Layer.
|
|
||||||
uint32_t m_region { 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr const size_t LayerExtrusionRangesStaticSize = 1;
|
|
||||||
using LayerExtrusionRanges =
|
|
||||||
#ifdef NDEBUG
|
|
||||||
// To reduce memory allocation in release mode.
|
|
||||||
boost::container::small_vector<LayerExtrusionRange, LayerExtrusionRangesStaticSize>;
|
|
||||||
#else // NDEBUG
|
|
||||||
// To ease debugging.
|
|
||||||
std::vector<LayerExtrusionRange>;
|
|
||||||
#endif // NDEBUG
|
|
||||||
|
|
||||||
using ExPolygonRange = IndexRange<uint32_t>();
|
|
||||||
|
|
||||||
// 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
|
||||||
// and one or multiple
|
// and one or multiple
|
||||||
struct LayerIsland
|
struct LayerIsland
|
||||||
{
|
{
|
||||||
|
private:
|
||||||
|
friend class Layer;
|
||||||
|
static constexpr const uint32_t fill_region_composite_id = std::numeric_limits<uint32_t>::max();
|
||||||
|
|
||||||
|
public:
|
||||||
// Perimeter extrusions in LayerRegion belonging to this island.
|
// Perimeter extrusions in LayerRegion belonging to this island.
|
||||||
LayerExtrusionRange perimeters;
|
LayerExtrusionRange perimeters;
|
||||||
|
// Thin fills of the same region as perimeters. Generated by classic perimeter generator, while Arachne puts them into perimeters.
|
||||||
|
ExtrusionRange thin_fills;
|
||||||
// Infill + gapfill extrusions in LayerRegion belonging to this island.
|
// Infill + gapfill extrusions in LayerRegion belonging to this island.
|
||||||
LayerExtrusionRanges fills;
|
LayerExtrusionRanges fills;
|
||||||
// Region that is to be filled with the fills above.
|
// Region that is to be filled with the fills above (thin fills, regular fills).
|
||||||
|
// Pointing to either LayerRegion::fill_expolygons() or LayerRegion::fill_expolygons_composite()
|
||||||
|
// based on this->fill_expolygons_composite() flag.
|
||||||
ExPolygonRange fill_expolygons;
|
ExPolygonRange fill_expolygons;
|
||||||
|
// Index of LayerRegion with LayerRegion::fill_expolygons() if not fill_expolygons_composite().
|
||||||
|
uint32_t fill_region_id;
|
||||||
|
bool fill_expolygons_composite() const { return this->fill_region_id == fill_region_composite_id; }
|
||||||
// Centroid of this island used for path planning.
|
// Centroid of this island used for path planning.
|
||||||
Point centroid;
|
// Point centroid;
|
||||||
|
|
||||||
bool has_extrusions() const { return ! this->perimeters.empty() || ! this->fills.empty(); }
|
bool has_extrusions() const { return ! this->perimeters.empty() || ! this->fills.empty(); }
|
||||||
};
|
};
|
||||||
@ -215,7 +271,7 @@ using LayerIslands =
|
|||||||
std::vector<LayerIsland>;
|
std::vector<LayerIsland>;
|
||||||
#endif // NDEBUG
|
#endif // NDEBUG
|
||||||
|
|
||||||
//
|
// One connected island of a layer. LayerSlice may consist of one or more LayerIslands.
|
||||||
struct LayerSlice
|
struct LayerSlice
|
||||||
{
|
{
|
||||||
struct Link {
|
struct Link {
|
||||||
@ -235,6 +291,10 @@ struct LayerSlice
|
|||||||
BoundingBox bbox;
|
BoundingBox bbox;
|
||||||
Links overlaps_above;
|
Links overlaps_above;
|
||||||
Links overlaps_below;
|
Links overlaps_below;
|
||||||
|
// One island for each region or region set that generates its own perimeters.
|
||||||
|
// For multi-material prints or prints with regions of different perimeter parameters,
|
||||||
|
// a LayerSlice may be split into multiple LayerIslands.
|
||||||
|
// For most prints there will be just one island.
|
||||||
LayerIslands islands;
|
LayerIslands islands;
|
||||||
|
|
||||||
bool has_extrusions() const { for (const LayerIsland &island : islands) if (island.has_extrusions()) return true; return false; }
|
bool has_extrusions() const { for (const LayerIsland &island : islands) if (island.has_extrusions()) return true; return false; }
|
||||||
@ -324,6 +384,22 @@ protected:
|
|||||||
virtual ~Layer();
|
virtual ~Layer();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void sort_perimeters_into_islands(
|
||||||
|
// Slices for which perimeters and fill_expolygons were just created.
|
||||||
|
// The slices may have been created by merging multiple source slices with the same perimeter parameters.
|
||||||
|
const SurfaceCollection &slices,
|
||||||
|
// Region where the perimeters, gap fills and fill expolygons are stored.
|
||||||
|
const uint32_t region_id,
|
||||||
|
// Perimeters and gap fills produced by the perimeter generator for the slices,
|
||||||
|
// sorted by the source slices.
|
||||||
|
const std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
|
||||||
|
// Fill expolygons produced for all source slices above.
|
||||||
|
ExPolygons &&fill_expolygons,
|
||||||
|
// Fill expolygon ranges sorted by the source slices.
|
||||||
|
const std::vector<ExPolygonRange> &fill_expolygons_ranges,
|
||||||
|
// If the current layer consists of multiple regions, then the fill_expolygons above are split by the source LayerRegion surfaces.
|
||||||
|
const std::vector<uint32_t> &layer_region_ids);
|
||||||
|
|
||||||
// Sequential index of layer, 0-based, offsetted by number of raft layers.
|
// Sequential index of layer, 0-based, offsetted by number of raft layers.
|
||||||
size_t m_id;
|
size_t m_id;
|
||||||
PrintObject *m_object;
|
PrintObject *m_object;
|
||||||
@ -337,7 +413,7 @@ public:
|
|||||||
// Used to suppress retraction if moving for a support extrusion over these support_islands.
|
// Used to suppress retraction if moving for a support extrusion over these support_islands.
|
||||||
ExPolygons support_islands;
|
ExPolygons support_islands;
|
||||||
// Slightly inflated bounding boxes of the above, for faster intersection query.
|
// Slightly inflated bounding boxes of the above, for faster intersection query.
|
||||||
std::vector<BoundingBox> support_islands_bboxes;
|
BoundingBoxes support_islands_bboxes;
|
||||||
// Extrusion paths for the support base and for the support interface and contacts.
|
// Extrusion paths for the support base and for the support interface and contacts.
|
||||||
ExtrusionEntityCollection support_fills;
|
ExtrusionEntityCollection support_fills;
|
||||||
|
|
||||||
|
@ -59,11 +59,26 @@ void LayerRegion::slices_to_fill_surfaces_clipped()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LayerRegion::make_perimeters(const SurfaceCollection &slices, ExPolygons &fill_expolygons)
|
// Produce perimeter extrusions, gap fill extrusions and fill polygons for input slices.
|
||||||
|
void LayerRegion::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)
|
||||||
{
|
{
|
||||||
m_perimeters.clear();
|
m_perimeters.clear();
|
||||||
m_thin_fills.clear();
|
m_thin_fills.clear();
|
||||||
|
|
||||||
|
perimeter_and_gapfill_ranges.reserve(perimeter_and_gapfill_ranges.size() + slices.size());
|
||||||
|
// There may be more expolygons produced per slice, thus this reserve is conservative.
|
||||||
|
fill_expolygons.reserve(fill_expolygons.size() + slices.size());
|
||||||
|
fill_expolygons_ranges.reserve(fill_expolygons_ranges.size() + slices.size());
|
||||||
|
|
||||||
const PrintConfig &print_config = this->layer()->object()->print()->config();
|
const PrintConfig &print_config = this->layer()->object()->print()->config();
|
||||||
const PrintRegionConfig ®ion_config = this->region().config();
|
const PrintRegionConfig ®ion_config = this->region().config();
|
||||||
// This needs to be in sync with PrintObject::_slice() slicing_mode_normal_below_layer!
|
// This needs to be in sync with PrintObject::_slice() slicing_mode_normal_below_layer!
|
||||||
@ -90,28 +105,37 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, ExPolygons &f
|
|||||||
// Cache for offsetted lower_slices
|
// Cache for offsetted lower_slices
|
||||||
Polygons lower_layer_polygons_cache;
|
Polygons lower_layer_polygons_cache;
|
||||||
|
|
||||||
if (this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase)
|
for (const Surface &surface : slices) {
|
||||||
PerimeterGenerator::process_arachne(
|
auto perimeters_begin = uint32_t(m_perimeters.size());
|
||||||
// input:
|
auto gap_fills_begin = uint32_t(m_thin_fills.size());
|
||||||
params,
|
auto fill_expolygons_begin = uint32_t(fill_expolygons.size());
|
||||||
&slices,
|
if (this->layer()->object()->config().perimeter_generator.value == PerimeterGeneratorType::Arachne && !spiral_vase)
|
||||||
lower_slices,
|
PerimeterGenerator::process_arachne(
|
||||||
lower_layer_polygons_cache,
|
// input:
|
||||||
// output:
|
params,
|
||||||
m_perimeters,
|
surface,
|
||||||
m_thin_fills,
|
lower_slices,
|
||||||
fill_expolygons);
|
lower_layer_polygons_cache,
|
||||||
else
|
// output:
|
||||||
PerimeterGenerator::process_classic(
|
m_perimeters,
|
||||||
// input:
|
m_thin_fills,
|
||||||
params,
|
fill_expolygons);
|
||||||
&slices,
|
else
|
||||||
lower_slices,
|
PerimeterGenerator::process_classic(
|
||||||
lower_layer_polygons_cache,
|
// input:
|
||||||
// output:
|
params,
|
||||||
m_perimeters,
|
surface,
|
||||||
m_thin_fills,
|
lower_slices,
|
||||||
fill_expolygons);
|
lower_layer_polygons_cache,
|
||||||
|
// output:
|
||||||
|
m_perimeters,
|
||||||
|
m_thin_fills,
|
||||||
|
fill_expolygons);
|
||||||
|
perimeter_and_gapfill_ranges.emplace_back(
|
||||||
|
ExtrusionRange{ perimeters_begin, uint32_t(m_perimeters.size()) },
|
||||||
|
ExtrusionRange{ gap_fills_begin, uint32_t(m_thin_fills.size()) });
|
||||||
|
fill_expolygons_ranges.emplace_back(ExtrusionRange{ fill_expolygons_begin, uint32_t(fill_expolygons.size()) });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3.
|
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3.
|
||||||
|
@ -601,7 +601,7 @@ static void export_perimeters_to_svg(const std::string &path, const Polygons &co
|
|||||||
void PerimeterGenerator::process_arachne(
|
void PerimeterGenerator::process_arachne(
|
||||||
// Inputs:
|
// Inputs:
|
||||||
const Parameters ¶ms,
|
const Parameters ¶ms,
|
||||||
const SurfaceCollection *slices,
|
const Surface &surface,
|
||||||
const ExPolygons *lower_slices,
|
const ExPolygons *lower_slices,
|
||||||
// Cache:
|
// Cache:
|
||||||
Polygons &lower_slices_polygons_cache,
|
Polygons &lower_slices_polygons_cache,
|
||||||
@ -633,202 +633,200 @@ void PerimeterGenerator::process_arachne(
|
|||||||
|
|
||||||
// we need to process each island separately because we might have different
|
// we need to process each island separately because we might have different
|
||||||
// extra perimeters for each one
|
// extra perimeters for each one
|
||||||
for (const Surface &surface : slices->surfaces) {
|
// detect how many perimeters must be generated for this island
|
||||||
// detect how many perimeters must be generated for this island
|
int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops
|
||||||
int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops
|
ExPolygons last = offset_ex(surface.expolygon.simplify_p(params.scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.));
|
||||||
ExPolygons last = offset_ex(surface.expolygon.simplify_p(params.scaled_resolution), - float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.));
|
Polygons last_p = to_polygons(last);
|
||||||
Polygons last_p = to_polygons(last);
|
|
||||||
|
|
||||||
Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, params.layer_height, params.object_config, params.print_config);
|
Arachne::WallToolPaths wallToolPaths(last_p, ext_perimeter_spacing, perimeter_spacing, coord_t(loop_number + 1), 0, params.layer_height, params.object_config, params.print_config);
|
||||||
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
|
std::vector<Arachne::VariableWidthLines> perimeters = wallToolPaths.getToolPaths();
|
||||||
loop_number = int(perimeters.size()) - 1;
|
loop_number = int(perimeters.size()) - 1;
|
||||||
|
|
||||||
#ifdef ARACHNE_DEBUG
|
#ifdef ARACHNE_DEBUG
|
||||||
{
|
{
|
||||||
static int iRun = 0;
|
static int iRun = 0;
|
||||||
export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour()));
|
export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour()));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// All closed ExtrusionLine should have the same the first and the last point.
|
// All closed ExtrusionLine should have the same the first and the last point.
|
||||||
// But in rare cases, Arachne produce ExtrusionLine marked as closed but without
|
// But in rare cases, Arachne produce ExtrusionLine marked as closed but without
|
||||||
// equal the first and the last point.
|
// equal the first and the last point.
|
||||||
assert([&perimeters = std::as_const(perimeters)]() -> bool {
|
assert([&perimeters = std::as_const(perimeters)]() -> bool {
|
||||||
for (const Arachne::VariableWidthLines &perimeter : perimeters)
|
for (const Arachne::VariableWidthLines &perimeter : perimeters)
|
||||||
for (const Arachne::ExtrusionLine &el : perimeter)
|
for (const Arachne::ExtrusionLine &el : perimeter)
|
||||||
if (el.is_closed && el.junctions.front().p != el.junctions.back().p)
|
if (el.is_closed && el.junctions.front().p != el.junctions.back().p)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
|
|
||||||
int start_perimeter = int(perimeters.size()) - 1;
|
int start_perimeter = int(perimeters.size()) - 1;
|
||||||
int end_perimeter = -1;
|
int end_perimeter = -1;
|
||||||
int direction = -1;
|
int direction = -1;
|
||||||
|
|
||||||
if (params.config.external_perimeters_first) {
|
if (params.config.external_perimeters_first) {
|
||||||
start_perimeter = 0;
|
start_perimeter = 0;
|
||||||
end_perimeter = int(perimeters.size());
|
end_perimeter = int(perimeters.size());
|
||||||
direction = 1;
|
direction = 1;
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Arachne::ExtrusionLine *> all_extrusions;
|
|
||||||
for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) {
|
|
||||||
if (perimeters[perimeter_idx].empty())
|
|
||||||
continue;
|
|
||||||
for (Arachne::ExtrusionLine &wall : perimeters[perimeter_idx])
|
|
||||||
all_extrusions.emplace_back(&wall);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find topological order with constraints from extrusions_constrains.
|
|
||||||
std::vector<size_t> blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion.
|
|
||||||
std::vector<std::vector<size_t>> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion.
|
|
||||||
std::unordered_map<const Arachne::ExtrusionLine *, size_t> map_extrusion_to_idx;
|
|
||||||
for (size_t idx = 0; idx < all_extrusions.size(); idx++)
|
|
||||||
map_extrusion_to_idx.emplace(all_extrusions[idx], idx);
|
|
||||||
|
|
||||||
auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, params.config.external_perimeters_first);
|
|
||||||
for (auto [before, after] : extrusions_constrains) {
|
|
||||||
auto after_it = map_extrusion_to_idx.find(after);
|
|
||||||
++blocked[after_it->second];
|
|
||||||
blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<bool> processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed.
|
|
||||||
Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position.
|
|
||||||
std::vector<PerimeterGeneratorArachneExtrusion> ordered_extrusions; // To store our result in. At the end we'll std::swap.
|
|
||||||
ordered_extrusions.reserve(all_extrusions.size());
|
|
||||||
|
|
||||||
while (ordered_extrusions.size() < all_extrusions.size()) {
|
|
||||||
size_t best_candidate = 0;
|
|
||||||
double best_distance_sqr = std::numeric_limits<double>::max();
|
|
||||||
bool is_best_closed = false;
|
|
||||||
|
|
||||||
std::vector<size_t> available_candidates;
|
|
||||||
for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) {
|
|
||||||
if (processed[candidate] || blocked[candidate])
|
|
||||||
continue; // Not a valid candidate.
|
|
||||||
available_candidates.push_back(candidate);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool {
|
|
||||||
return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const size_t candidate_path_idx : available_candidates) {
|
|
||||||
auto& path = all_extrusions[candidate_path_idx];
|
|
||||||
|
|
||||||
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.
|
|
||||||
if (best_distance_sqr == std::numeric_limits<double>::max()) {
|
|
||||||
best_candidate = candidate_path_idx;
|
|
||||||
is_best_closed = path->is_closed;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Point candidate_position = path->junctions.front().p;
|
|
||||||
double distance_sqr = (current_position - candidate_position).cast<double>().norm();
|
|
||||||
if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far.
|
|
||||||
if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits<double>::max()) || (!path->is_closed && !is_best_closed)) {
|
|
||||||
best_candidate = candidate_path_idx;
|
|
||||||
best_distance_sqr = distance_sqr;
|
|
||||||
is_best_closed = path->is_closed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto &best_path = all_extrusions[best_candidate];
|
|
||||||
ordered_extrusions.push_back({best_path, best_path->is_contour(), false});
|
|
||||||
processed[best_candidate] = true;
|
|
||||||
for (size_t unlocked_idx : blocking[best_candidate])
|
|
||||||
blocked[unlocked_idx]--;
|
|
||||||
|
|
||||||
if(!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then.
|
|
||||||
if(best_path->is_closed)
|
|
||||||
current_position = best_path->junctions[0].p; //We end where we started.
|
|
||||||
else
|
|
||||||
current_position = best_path->junctions.back().p; //Pick the other end from where we started.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.layer_id > 0 && params.config.fuzzy_skin != FuzzySkinType::None) {
|
|
||||||
std::vector<PerimeterGeneratorArachneExtrusion *> closed_loop_extrusions;
|
|
||||||
for (PerimeterGeneratorArachneExtrusion &extrusion : ordered_extrusions)
|
|
||||||
if (extrusion.extrusion->inset_idx == 0) {
|
|
||||||
if (extrusion.extrusion->is_closed && params.config.fuzzy_skin == FuzzySkinType::External) {
|
|
||||||
closed_loop_extrusions.emplace_back(&extrusion);
|
|
||||||
} else {
|
|
||||||
extrusion.fuzzify = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.config.fuzzy_skin == FuzzySkinType::External) {
|
|
||||||
ClipperLib_Z::Paths loops_paths;
|
|
||||||
loops_paths.reserve(closed_loop_extrusions.size());
|
|
||||||
for (const auto &cl_extrusion : closed_loop_extrusions) {
|
|
||||||
assert(cl_extrusion->extrusion->junctions.front() == cl_extrusion->extrusion->junctions.back());
|
|
||||||
size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front();
|
|
||||||
ClipperLib_Z::Path loop_path;
|
|
||||||
loop_path.reserve(cl_extrusion->extrusion->junctions.size() - 1);
|
|
||||||
for (auto junction_it = cl_extrusion->extrusion->junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion->junctions.end()); ++junction_it)
|
|
||||||
loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx);
|
|
||||||
loops_paths.emplace_back(loop_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
ClipperLib_Z::Clipper clipper;
|
|
||||||
clipper.AddPaths(loops_paths, ClipperLib_Z::ptSubject, true);
|
|
||||||
ClipperLib_Z::PolyTree loops_polytree;
|
|
||||||
clipper.Execute(ClipperLib_Z::ctUnion, loops_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd);
|
|
||||||
|
|
||||||
for (const ClipperLib_Z::PolyNode *child_node : loops_polytree.Childs) {
|
|
||||||
// The whole contour must have the same index.
|
|
||||||
coord_t polygon_idx = child_node->Contour.front().z();
|
|
||||||
bool has_same_idx = std::all_of(child_node->Contour.begin(), child_node->Contour.end(),
|
|
||||||
[&polygon_idx](const ClipperLib_Z::IntPoint &point) -> bool { return polygon_idx == point.z(); });
|
|
||||||
if (has_same_idx)
|
|
||||||
closed_loop_extrusions[polygon_idx]->fuzzify = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(params, lower_slices_polygons_cache, ordered_extrusions); !extrusion_coll.empty())
|
|
||||||
out_loops.append(extrusion_coll);
|
|
||||||
|
|
||||||
ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour());
|
|
||||||
const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
|
|
||||||
if (offset_ex(infill_contour, -float(spacing / 2.)).empty())
|
|
||||||
infill_contour.clear(); // Infill region is too small, so let's filter it out.
|
|
||||||
|
|
||||||
// create one more offset to be used as boundary for fill
|
|
||||||
// we offset by half the perimeter spacing (to get to the actual infill boundary)
|
|
||||||
// and then we offset back and forth by half the infill spacing to only consider the
|
|
||||||
// non-collapsing regions
|
|
||||||
coord_t inset =
|
|
||||||
(loop_number < 0) ? 0 :
|
|
||||||
(loop_number == 0) ?
|
|
||||||
// one loop
|
|
||||||
ext_perimeter_spacing:
|
|
||||||
// two or more loops?
|
|
||||||
perimeter_spacing;
|
|
||||||
|
|
||||||
inset = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale<double>(inset))));
|
|
||||||
Polygons pp;
|
|
||||||
for (ExPolygon &ex : infill_contour)
|
|
||||||
ex.simplify_p(params.scaled_resolution, &pp);
|
|
||||||
// collapse too narrow infill areas
|
|
||||||
const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
|
|
||||||
// append infill areas to fill_surfaces
|
|
||||||
append(out_fill_expolygons,
|
|
||||||
offset2_ex(
|
|
||||||
union_ex(pp),
|
|
||||||
float(- min_perimeter_infill_spacing / 2.),
|
|
||||||
float(inset + min_perimeter_infill_spacing / 2.)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<Arachne::ExtrusionLine *> all_extrusions;
|
||||||
|
for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) {
|
||||||
|
if (perimeters[perimeter_idx].empty())
|
||||||
|
continue;
|
||||||
|
for (Arachne::ExtrusionLine &wall : perimeters[perimeter_idx])
|
||||||
|
all_extrusions.emplace_back(&wall);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find topological order with constraints from extrusions_constrains.
|
||||||
|
std::vector<size_t> blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion.
|
||||||
|
std::vector<std::vector<size_t>> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion.
|
||||||
|
std::unordered_map<const Arachne::ExtrusionLine *, size_t> map_extrusion_to_idx;
|
||||||
|
for (size_t idx = 0; idx < all_extrusions.size(); idx++)
|
||||||
|
map_extrusion_to_idx.emplace(all_extrusions[idx], idx);
|
||||||
|
|
||||||
|
auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, params.config.external_perimeters_first);
|
||||||
|
for (auto [before, after] : extrusions_constrains) {
|
||||||
|
auto after_it = map_extrusion_to_idx.find(after);
|
||||||
|
++blocked[after_it->second];
|
||||||
|
blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<bool> processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed.
|
||||||
|
Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position.
|
||||||
|
std::vector<PerimeterGeneratorArachneExtrusion> ordered_extrusions; // To store our result in. At the end we'll std::swap.
|
||||||
|
ordered_extrusions.reserve(all_extrusions.size());
|
||||||
|
|
||||||
|
while (ordered_extrusions.size() < all_extrusions.size()) {
|
||||||
|
size_t best_candidate = 0;
|
||||||
|
double best_distance_sqr = std::numeric_limits<double>::max();
|
||||||
|
bool is_best_closed = false;
|
||||||
|
|
||||||
|
std::vector<size_t> available_candidates;
|
||||||
|
for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) {
|
||||||
|
if (processed[candidate] || blocked[candidate])
|
||||||
|
continue; // Not a valid candidate.
|
||||||
|
available_candidates.push_back(candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool {
|
||||||
|
return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const size_t candidate_path_idx : available_candidates) {
|
||||||
|
auto& path = all_extrusions[candidate_path_idx];
|
||||||
|
|
||||||
|
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.
|
||||||
|
if (best_distance_sqr == std::numeric_limits<double>::max()) {
|
||||||
|
best_candidate = candidate_path_idx;
|
||||||
|
is_best_closed = path->is_closed;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Point candidate_position = path->junctions.front().p;
|
||||||
|
double distance_sqr = (current_position - candidate_position).cast<double>().norm();
|
||||||
|
if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far.
|
||||||
|
if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits<double>::max()) || (!path->is_closed && !is_best_closed)) {
|
||||||
|
best_candidate = candidate_path_idx;
|
||||||
|
best_distance_sqr = distance_sqr;
|
||||||
|
is_best_closed = path->is_closed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &best_path = all_extrusions[best_candidate];
|
||||||
|
ordered_extrusions.push_back({best_path, best_path->is_contour(), false});
|
||||||
|
processed[best_candidate] = true;
|
||||||
|
for (size_t unlocked_idx : blocking[best_candidate])
|
||||||
|
blocked[unlocked_idx]--;
|
||||||
|
|
||||||
|
if(!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then.
|
||||||
|
if(best_path->is_closed)
|
||||||
|
current_position = best_path->junctions[0].p; //We end where we started.
|
||||||
|
else
|
||||||
|
current_position = best_path->junctions.back().p; //Pick the other end from where we started.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.layer_id > 0 && params.config.fuzzy_skin != FuzzySkinType::None) {
|
||||||
|
std::vector<PerimeterGeneratorArachneExtrusion *> closed_loop_extrusions;
|
||||||
|
for (PerimeterGeneratorArachneExtrusion &extrusion : ordered_extrusions)
|
||||||
|
if (extrusion.extrusion->inset_idx == 0) {
|
||||||
|
if (extrusion.extrusion->is_closed && params.config.fuzzy_skin == FuzzySkinType::External) {
|
||||||
|
closed_loop_extrusions.emplace_back(&extrusion);
|
||||||
|
} else {
|
||||||
|
extrusion.fuzzify = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.config.fuzzy_skin == FuzzySkinType::External) {
|
||||||
|
ClipperLib_Z::Paths loops_paths;
|
||||||
|
loops_paths.reserve(closed_loop_extrusions.size());
|
||||||
|
for (const auto &cl_extrusion : closed_loop_extrusions) {
|
||||||
|
assert(cl_extrusion->extrusion->junctions.front() == cl_extrusion->extrusion->junctions.back());
|
||||||
|
size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front();
|
||||||
|
ClipperLib_Z::Path loop_path;
|
||||||
|
loop_path.reserve(cl_extrusion->extrusion->junctions.size() - 1);
|
||||||
|
for (auto junction_it = cl_extrusion->extrusion->junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion->junctions.end()); ++junction_it)
|
||||||
|
loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx);
|
||||||
|
loops_paths.emplace_back(loop_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipperLib_Z::Clipper clipper;
|
||||||
|
clipper.AddPaths(loops_paths, ClipperLib_Z::ptSubject, true);
|
||||||
|
ClipperLib_Z::PolyTree loops_polytree;
|
||||||
|
clipper.Execute(ClipperLib_Z::ctUnion, loops_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd);
|
||||||
|
|
||||||
|
for (const ClipperLib_Z::PolyNode *child_node : loops_polytree.Childs) {
|
||||||
|
// The whole contour must have the same index.
|
||||||
|
coord_t polygon_idx = child_node->Contour.front().z();
|
||||||
|
bool has_same_idx = std::all_of(child_node->Contour.begin(), child_node->Contour.end(),
|
||||||
|
[&polygon_idx](const ClipperLib_Z::IntPoint &point) -> bool { return polygon_idx == point.z(); });
|
||||||
|
if (has_same_idx)
|
||||||
|
closed_loop_extrusions[polygon_idx]->fuzzify = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(params, lower_slices_polygons_cache, ordered_extrusions); !extrusion_coll.empty())
|
||||||
|
out_loops.append(extrusion_coll);
|
||||||
|
|
||||||
|
ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour());
|
||||||
|
const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
|
||||||
|
if (offset_ex(infill_contour, -float(spacing / 2.)).empty())
|
||||||
|
infill_contour.clear(); // Infill region is too small, so let's filter it out.
|
||||||
|
|
||||||
|
// create one more offset to be used as boundary for fill
|
||||||
|
// we offset by half the perimeter spacing (to get to the actual infill boundary)
|
||||||
|
// and then we offset back and forth by half the infill spacing to only consider the
|
||||||
|
// non-collapsing regions
|
||||||
|
coord_t inset =
|
||||||
|
(loop_number < 0) ? 0 :
|
||||||
|
(loop_number == 0) ?
|
||||||
|
// one loop
|
||||||
|
ext_perimeter_spacing:
|
||||||
|
// two or more loops?
|
||||||
|
perimeter_spacing;
|
||||||
|
|
||||||
|
inset = coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale<double>(inset))));
|
||||||
|
Polygons pp;
|
||||||
|
for (ExPolygon &ex : infill_contour)
|
||||||
|
ex.simplify_p(params.scaled_resolution, &pp);
|
||||||
|
// collapse too narrow infill areas
|
||||||
|
const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
|
||||||
|
// append infill areas to fill_surfaces
|
||||||
|
append(out_fill_expolygons,
|
||||||
|
offset2_ex(
|
||||||
|
union_ex(pp),
|
||||||
|
float(- min_perimeter_infill_spacing / 2.),
|
||||||
|
float(inset + min_perimeter_infill_spacing / 2.)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PerimeterGenerator::process_classic(
|
void PerimeterGenerator::process_classic(
|
||||||
// Inputs:
|
// Inputs:
|
||||||
const Parameters ¶ms,
|
const Parameters ¶ms,
|
||||||
const SurfaceCollection *slices,
|
const Surface &surface,
|
||||||
const ExPolygons *lower_slices,
|
const ExPolygons *lower_slices,
|
||||||
// Cache:
|
// Cache:
|
||||||
Polygons &lower_slices_polygons_cache,
|
Polygons &lower_slices_polygons_cache,
|
||||||
@ -875,230 +873,228 @@ void PerimeterGenerator::process_classic(
|
|||||||
|
|
||||||
// we need to process each island separately because we might have different
|
// we need to process each island separately because we might have different
|
||||||
// extra perimeters for each one
|
// extra perimeters for each one
|
||||||
for (const Surface &surface : slices->surfaces) {
|
// detect how many perimeters must be generated for this island
|
||||||
// detect how many perimeters must be generated for this island
|
int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops
|
||||||
int loop_number = params.config.perimeters + surface.extra_perimeters - 1; // 0-indexed loops
|
ExPolygons last = union_ex(surface.expolygon.simplify_p(params.scaled_resolution));
|
||||||
ExPolygons last = union_ex(surface.expolygon.simplify_p(params.scaled_resolution));
|
ExPolygons gaps;
|
||||||
ExPolygons gaps;
|
if (loop_number >= 0) {
|
||||||
if (loop_number >= 0) {
|
// In case no perimeters are to be generated, loop_number will equal to -1.
|
||||||
// In case no perimeters are to be generated, loop_number will equal to -1.
|
std::vector<PerimeterGeneratorLoops> contours(loop_number+1); // depth => loops
|
||||||
std::vector<PerimeterGeneratorLoops> contours(loop_number+1); // depth => loops
|
std::vector<PerimeterGeneratorLoops> holes(loop_number+1); // depth => loops
|
||||||
std::vector<PerimeterGeneratorLoops> holes(loop_number+1); // depth => loops
|
ThickPolylines thin_walls;
|
||||||
ThickPolylines thin_walls;
|
// we loop one time more than needed in order to find gaps after the last perimeter was applied
|
||||||
// we loop one time more than needed in order to find gaps after the last perimeter was applied
|
for (int i = 0;; ++ i) { // outer loop is 0
|
||||||
for (int i = 0;; ++ i) { // outer loop is 0
|
// Calculate next onion shell of perimeters.
|
||||||
// Calculate next onion shell of perimeters.
|
ExPolygons offsets;
|
||||||
ExPolygons offsets;
|
if (i == 0) {
|
||||||
if (i == 0) {
|
// the minimum thickness of a single loop is:
|
||||||
// the minimum thickness of a single loop is:
|
// ext_width/2 + ext_spacing/2 + spacing/2 + width/2
|
||||||
// ext_width/2 + ext_spacing/2 + spacing/2 + width/2
|
offsets = params.config.thin_walls ?
|
||||||
offsets = params.config.thin_walls ?
|
offset2_ex(
|
||||||
offset2_ex(
|
last,
|
||||||
last,
|
- float(ext_perimeter_width / 2. + ext_min_spacing / 2. - 1),
|
||||||
- float(ext_perimeter_width / 2. + ext_min_spacing / 2. - 1),
|
+ float(ext_min_spacing / 2. - 1)) :
|
||||||
+ float(ext_min_spacing / 2. - 1)) :
|
offset_ex(last, - float(ext_perimeter_width / 2.));
|
||||||
offset_ex(last, - float(ext_perimeter_width / 2.));
|
// look for thin walls
|
||||||
// look for thin walls
|
if (params.config.thin_walls) {
|
||||||
if (params.config.thin_walls) {
|
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
|
||||||
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
|
// (actually, something larger than that still may exist due to mitering or other causes)
|
||||||
// (actually, something larger than that still may exist due to mitering or other causes)
|
coord_t min_width = coord_t(scale_(params.ext_perimeter_flow.nozzle_diameter() / 3));
|
||||||
coord_t min_width = coord_t(scale_(params.ext_perimeter_flow.nozzle_diameter() / 3));
|
ExPolygons expp = opening_ex(
|
||||||
ExPolygons expp = opening_ex(
|
// medial axis requires non-overlapping geometry
|
||||||
// medial axis requires non-overlapping geometry
|
diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)),
|
||||||
diff_ex(last, offset(offsets, float(ext_perimeter_width / 2.) + ClipperSafetyOffset)),
|
float(min_width / 2.));
|
||||||
float(min_width / 2.));
|
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
|
||||||
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
|
for (ExPolygon &ex : expp)
|
||||||
for (ExPolygon &ex : expp)
|
ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls);
|
||||||
ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls);
|
|
||||||
}
|
|
||||||
if (params.spiral_vase && offsets.size() > 1) {
|
|
||||||
// Remove all but the largest area polygon.
|
|
||||||
keep_largest_contour_only(offsets);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//FIXME Is this offset correct if the line width of the inner perimeters differs
|
|
||||||
// from the line width of the infill?
|
|
||||||
coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
|
|
||||||
offsets = params.config.thin_walls ?
|
|
||||||
// This path will ensure, that the perimeters do not overfill, as in
|
|
||||||
// prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters
|
|
||||||
// excessively, creating gaps, which then need to be filled in by the not very
|
|
||||||
// reliable gap fill algorithm.
|
|
||||||
// Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than
|
|
||||||
// the original.
|
|
||||||
offset2_ex(last,
|
|
||||||
- float(distance + min_spacing / 2. - 1.),
|
|
||||||
float(min_spacing / 2. - 1.)) :
|
|
||||||
// If "detect thin walls" is not enabled, this paths will be entered, which
|
|
||||||
// leads to overflows, as in prusa3d/Slic3r GH #32
|
|
||||||
offset_ex(last, - float(distance));
|
|
||||||
// look for gaps
|
|
||||||
if (has_gap_fill)
|
|
||||||
// not using safety offset here would "detect" very narrow gaps
|
|
||||||
// (but still long enough to escape the area threshold) that gap fill
|
|
||||||
// won't be able to fill but we'd still remove from infill area
|
|
||||||
append(gaps, diff_ex(
|
|
||||||
offset(last, - float(0.5 * distance)),
|
|
||||||
offset(offsets, float(0.5 * distance + 10)))); // safety offset
|
|
||||||
}
|
}
|
||||||
if (offsets.empty()) {
|
if (params.spiral_vase && offsets.size() > 1) {
|
||||||
// Store the number of loops actually generated.
|
// Remove all but the largest area polygon.
|
||||||
loop_number = i - 1;
|
keep_largest_contour_only(offsets);
|
||||||
// No region left to be filled in.
|
|
||||||
last.clear();
|
|
||||||
break;
|
|
||||||
} else if (i > loop_number) {
|
|
||||||
// If i > loop_number, we were looking just for gaps.
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
{
|
} else {
|
||||||
const bool fuzzify_contours = params.config.fuzzy_skin != FuzzySkinType::None && i == 0 && params.layer_id > 0;
|
//FIXME Is this offset correct if the line width of the inner perimeters differs
|
||||||
const bool fuzzify_holes = fuzzify_contours && params.config.fuzzy_skin == FuzzySkinType::All;
|
// from the line width of the infill?
|
||||||
for (const ExPolygon &expolygon : offsets) {
|
coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
|
||||||
// Outer contour may overlap with an inner contour,
|
offsets = params.config.thin_walls ?
|
||||||
// inner contour may overlap with another inner contour,
|
// This path will ensure, that the perimeters do not overfill, as in
|
||||||
// outer contour may overlap with itself.
|
// prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters
|
||||||
//FIXME evaluate the overlaps, annotate each point with an overlap depth,
|
// excessively, creating gaps, which then need to be filled in by the not very
|
||||||
// compensate for the depth of intersection.
|
// reliable gap fill algorithm.
|
||||||
contours[i].emplace_back(expolygon.contour, i, true, fuzzify_contours);
|
// Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than
|
||||||
|
// the original.
|
||||||
|
offset2_ex(last,
|
||||||
|
- float(distance + min_spacing / 2. - 1.),
|
||||||
|
float(min_spacing / 2. - 1.)) :
|
||||||
|
// If "detect thin walls" is not enabled, this paths will be entered, which
|
||||||
|
// leads to overflows, as in prusa3d/Slic3r GH #32
|
||||||
|
offset_ex(last, - float(distance));
|
||||||
|
// look for gaps
|
||||||
|
if (has_gap_fill)
|
||||||
|
// not using safety offset here would "detect" very narrow gaps
|
||||||
|
// (but still long enough to escape the area threshold) that gap fill
|
||||||
|
// won't be able to fill but we'd still remove from infill area
|
||||||
|
append(gaps, diff_ex(
|
||||||
|
offset(last, - float(0.5 * distance)),
|
||||||
|
offset(offsets, float(0.5 * distance + 10)))); // safety offset
|
||||||
|
}
|
||||||
|
if (offsets.empty()) {
|
||||||
|
// Store the number of loops actually generated.
|
||||||
|
loop_number = i - 1;
|
||||||
|
// No region left to be filled in.
|
||||||
|
last.clear();
|
||||||
|
break;
|
||||||
|
} else if (i > loop_number) {
|
||||||
|
// If i > loop_number, we were looking just for gaps.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const bool fuzzify_contours = params.config.fuzzy_skin != FuzzySkinType::None && i == 0 && params.layer_id > 0;
|
||||||
|
const bool fuzzify_holes = fuzzify_contours && params.config.fuzzy_skin == FuzzySkinType::All;
|
||||||
|
for (const ExPolygon &expolygon : offsets) {
|
||||||
|
// Outer contour may overlap with an inner contour,
|
||||||
|
// inner contour may overlap with another inner contour,
|
||||||
|
// outer contour may overlap with itself.
|
||||||
|
//FIXME evaluate the overlaps, annotate each point with an overlap depth,
|
||||||
|
// compensate for the depth of intersection.
|
||||||
|
contours[i].emplace_back(expolygon.contour, i, true, fuzzify_contours);
|
||||||
|
|
||||||
if (! expolygon.holes.empty()) {
|
if (! expolygon.holes.empty()) {
|
||||||
holes[i].reserve(holes[i].size() + expolygon.holes.size());
|
holes[i].reserve(holes[i].size() + expolygon.holes.size());
|
||||||
for (const Polygon &hole : expolygon.holes)
|
for (const Polygon &hole : expolygon.holes)
|
||||||
holes[i].emplace_back(hole, i, false, fuzzify_holes);
|
holes[i].emplace_back(hole, i, false, fuzzify_holes);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last = std::move(offsets);
|
|
||||||
if (i == loop_number && (! has_gap_fill || params.config.fill_density.value == 0)) {
|
|
||||||
// The last run of this loop is executed to collect gaps for gap fill.
|
|
||||||
// As the gap fill is either disabled or not
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
last = std::move(offsets);
|
||||||
// nest loops: holes first
|
if (i == loop_number && (! has_gap_fill || params.config.fill_density.value == 0)) {
|
||||||
for (int d = 0; d <= loop_number; ++ d) {
|
// The last run of this loop is executed to collect gaps for gap fill.
|
||||||
PerimeterGeneratorLoops &holes_d = holes[d];
|
// As the gap fill is either disabled or not
|
||||||
// loop through all holes having depth == d
|
break;
|
||||||
for (int i = 0; i < (int)holes_d.size(); ++ i) {
|
|
||||||
const PerimeterGeneratorLoop &loop = holes_d[i];
|
|
||||||
// find the hole loop that contains this one, if any
|
|
||||||
for (int t = d + 1; t <= loop_number; ++ t) {
|
|
||||||
for (int j = 0; j < (int)holes[t].size(); ++ j) {
|
|
||||||
PerimeterGeneratorLoop &candidate_parent = holes[t][j];
|
|
||||||
if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
|
|
||||||
candidate_parent.children.push_back(loop);
|
|
||||||
holes_d.erase(holes_d.begin() + i);
|
|
||||||
-- i;
|
|
||||||
goto NEXT_LOOP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if no hole contains this hole, find the contour loop that contains it
|
|
||||||
for (int t = loop_number; t >= 0; -- t) {
|
|
||||||
for (int j = 0; j < (int)contours[t].size(); ++ j) {
|
|
||||||
PerimeterGeneratorLoop &candidate_parent = contours[t][j];
|
|
||||||
if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
|
|
||||||
candidate_parent.children.push_back(loop);
|
|
||||||
holes_d.erase(holes_d.begin() + i);
|
|
||||||
-- i;
|
|
||||||
goto NEXT_LOOP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NEXT_LOOP: ;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// nest contour loops
|
|
||||||
for (int d = loop_number; d >= 1; -- d) {
|
|
||||||
PerimeterGeneratorLoops &contours_d = contours[d];
|
|
||||||
// loop through all contours having depth == d
|
|
||||||
for (int i = 0; i < (int)contours_d.size(); ++ i) {
|
|
||||||
const PerimeterGeneratorLoop &loop = contours_d[i];
|
|
||||||
// find the contour loop that contains it
|
|
||||||
for (int t = d - 1; t >= 0; -- t) {
|
|
||||||
for (size_t j = 0; j < contours[t].size(); ++ j) {
|
|
||||||
PerimeterGeneratorLoop &candidate_parent = contours[t][j];
|
|
||||||
if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
|
|
||||||
candidate_parent.children.push_back(loop);
|
|
||||||
contours_d.erase(contours_d.begin() + i);
|
|
||||||
-- i;
|
|
||||||
goto NEXT_CONTOUR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NEXT_CONTOUR: ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// at this point, all loops should be in contours[0]
|
|
||||||
ExtrusionEntityCollection entities = traverse_loops(params, lower_slices_polygons_cache, contours.front(), thin_walls);
|
|
||||||
// if brim will be printed, reverse the order of perimeters so that
|
|
||||||
// we continue inwards after having finished the brim
|
|
||||||
// TODO: add test for perimeter order
|
|
||||||
if (params.config.external_perimeters_first ||
|
|
||||||
(params.layer_id == 0 && params.object_config.brim_width.value > 0))
|
|
||||||
entities.reverse();
|
|
||||||
// append perimeters for this slice as a collection
|
|
||||||
if (! entities.empty())
|
|
||||||
out_loops.append(entities);
|
|
||||||
} // for each loop of an island
|
|
||||||
|
|
||||||
// fill gaps
|
|
||||||
if (! gaps.empty()) {
|
|
||||||
// collapse
|
|
||||||
double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE);
|
|
||||||
double max = 2. * perimeter_spacing;
|
|
||||||
ExPolygons gaps_ex = diff_ex(
|
|
||||||
//FIXME offset2 would be enough and cheaper.
|
|
||||||
opening_ex(gaps, float(min / 2.)),
|
|
||||||
offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset)));
|
|
||||||
ThickPolylines polylines;
|
|
||||||
for (const ExPolygon &ex : gaps_ex)
|
|
||||||
ex.medial_axis(max, min, &polylines);
|
|
||||||
if (! polylines.empty()) {
|
|
||||||
ExtrusionEntityCollection gap_fill;
|
|
||||||
variable_width(polylines, erGapFill, params.solid_infill_flow, gap_fill.entities);
|
|
||||||
/* Make sure we don't infill narrow parts that are already gap-filled
|
|
||||||
(we only consider this surface's gaps to reduce the diff() complexity).
|
|
||||||
Growing actual extrusions ensures that gaps not filled by medial axis
|
|
||||||
are not subtracted from fill surfaces (they might be too short gaps
|
|
||||||
that medial axis skips but infill might join with other infill regions
|
|
||||||
and use zigzag). */
|
|
||||||
//FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing,
|
|
||||||
// therefore it may cover the area, but no the volume.
|
|
||||||
last = diff_ex(last, gap_fill.polygons_covered_by_width(10.f));
|
|
||||||
out_gap_fill.append(std::move(gap_fill.entities));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create one more offset to be used as boundary for fill
|
// nest loops: holes first
|
||||||
// we offset by half the perimeter spacing (to get to the actual infill boundary)
|
for (int d = 0; d <= loop_number; ++ d) {
|
||||||
// and then we offset back and forth by half the infill spacing to only consider the
|
PerimeterGeneratorLoops &holes_d = holes[d];
|
||||||
// non-collapsing regions
|
// loop through all holes having depth == d
|
||||||
coord_t inset =
|
for (int i = 0; i < (int)holes_d.size(); ++ i) {
|
||||||
(loop_number < 0) ? 0 :
|
const PerimeterGeneratorLoop &loop = holes_d[i];
|
||||||
(loop_number == 0) ?
|
// find the hole loop that contains this one, if any
|
||||||
// one loop
|
for (int t = d + 1; t <= loop_number; ++ t) {
|
||||||
ext_perimeter_spacing / 2 :
|
for (int j = 0; j < (int)holes[t].size(); ++ j) {
|
||||||
// two or more loops?
|
PerimeterGeneratorLoop &candidate_parent = holes[t][j];
|
||||||
perimeter_spacing / 2;
|
if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
|
||||||
// only apply infill overlap if we actually have one perimeter
|
candidate_parent.children.push_back(loop);
|
||||||
if (inset > 0)
|
holes_d.erase(holes_d.begin() + i);
|
||||||
inset -= coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale<double>(inset + solid_infill_spacing / 2))));
|
-- i;
|
||||||
// simplify infill contours according to resolution
|
goto NEXT_LOOP;
|
||||||
Polygons pp;
|
}
|
||||||
for (ExPolygon &ex : last)
|
}
|
||||||
ex.simplify_p(params.scaled_resolution, &pp);
|
}
|
||||||
// collapse too narrow infill areas
|
// if no hole contains this hole, find the contour loop that contains it
|
||||||
coord_t min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
|
for (int t = loop_number; t >= 0; -- t) {
|
||||||
// append infill areas to fill_surfaces
|
for (int j = 0; j < (int)contours[t].size(); ++ j) {
|
||||||
append(out_fill_expolygons,
|
PerimeterGeneratorLoop &candidate_parent = contours[t][j];
|
||||||
offset2_ex(
|
if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
|
||||||
union_ex(pp),
|
candidate_parent.children.push_back(loop);
|
||||||
float(- inset - min_perimeter_infill_spacing / 2.),
|
holes_d.erase(holes_d.begin() + i);
|
||||||
float(min_perimeter_infill_spacing / 2.)));
|
-- i;
|
||||||
} // for each island
|
goto NEXT_LOOP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NEXT_LOOP: ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// nest contour loops
|
||||||
|
for (int d = loop_number; d >= 1; -- d) {
|
||||||
|
PerimeterGeneratorLoops &contours_d = contours[d];
|
||||||
|
// loop through all contours having depth == d
|
||||||
|
for (int i = 0; i < (int)contours_d.size(); ++ i) {
|
||||||
|
const PerimeterGeneratorLoop &loop = contours_d[i];
|
||||||
|
// find the contour loop that contains it
|
||||||
|
for (int t = d - 1; t >= 0; -- t) {
|
||||||
|
for (size_t j = 0; j < contours[t].size(); ++ j) {
|
||||||
|
PerimeterGeneratorLoop &candidate_parent = contours[t][j];
|
||||||
|
if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
|
||||||
|
candidate_parent.children.push_back(loop);
|
||||||
|
contours_d.erase(contours_d.begin() + i);
|
||||||
|
-- i;
|
||||||
|
goto NEXT_CONTOUR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NEXT_CONTOUR: ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// at this point, all loops should be in contours[0]
|
||||||
|
ExtrusionEntityCollection entities = traverse_loops(params, lower_slices_polygons_cache, contours.front(), thin_walls);
|
||||||
|
// if brim will be printed, reverse the order of perimeters so that
|
||||||
|
// we continue inwards after having finished the brim
|
||||||
|
// TODO: add test for perimeter order
|
||||||
|
if (params.config.external_perimeters_first ||
|
||||||
|
(params.layer_id == 0 && params.object_config.brim_width.value > 0))
|
||||||
|
entities.reverse();
|
||||||
|
// append perimeters for this slice as a collection
|
||||||
|
if (! entities.empty())
|
||||||
|
out_loops.append(entities);
|
||||||
|
} // for each loop of an island
|
||||||
|
|
||||||
|
// fill gaps
|
||||||
|
if (! gaps.empty()) {
|
||||||
|
// collapse
|
||||||
|
double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE);
|
||||||
|
double max = 2. * perimeter_spacing;
|
||||||
|
ExPolygons gaps_ex = diff_ex(
|
||||||
|
//FIXME offset2 would be enough and cheaper.
|
||||||
|
opening_ex(gaps, float(min / 2.)),
|
||||||
|
offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset)));
|
||||||
|
ThickPolylines polylines;
|
||||||
|
for (const ExPolygon &ex : gaps_ex)
|
||||||
|
ex.medial_axis(max, min, &polylines);
|
||||||
|
if (! polylines.empty()) {
|
||||||
|
ExtrusionEntityCollection gap_fill;
|
||||||
|
variable_width(polylines, erGapFill, params.solid_infill_flow, gap_fill.entities);
|
||||||
|
/* Make sure we don't infill narrow parts that are already gap-filled
|
||||||
|
(we only consider this surface's gaps to reduce the diff() complexity).
|
||||||
|
Growing actual extrusions ensures that gaps not filled by medial axis
|
||||||
|
are not subtracted from fill surfaces (they might be too short gaps
|
||||||
|
that medial axis skips but infill might join with other infill regions
|
||||||
|
and use zigzag). */
|
||||||
|
//FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing,
|
||||||
|
// therefore it may cover the area, but no the volume.
|
||||||
|
last = diff_ex(last, gap_fill.polygons_covered_by_width(10.f));
|
||||||
|
out_gap_fill.append(std::move(gap_fill.entities));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create one more offset to be used as boundary for fill
|
||||||
|
// we offset by half the perimeter spacing (to get to the actual infill boundary)
|
||||||
|
// and then we offset back and forth by half the infill spacing to only consider the
|
||||||
|
// non-collapsing regions
|
||||||
|
coord_t inset =
|
||||||
|
(loop_number < 0) ? 0 :
|
||||||
|
(loop_number == 0) ?
|
||||||
|
// one loop
|
||||||
|
ext_perimeter_spacing / 2 :
|
||||||
|
// two or more loops?
|
||||||
|
perimeter_spacing / 2;
|
||||||
|
// only apply infill overlap if we actually have one perimeter
|
||||||
|
if (inset > 0)
|
||||||
|
inset -= coord_t(scale_(params.config.get_abs_value("infill_overlap", unscale<double>(inset + solid_infill_spacing / 2))));
|
||||||
|
// simplify infill contours according to resolution
|
||||||
|
Polygons pp;
|
||||||
|
for (ExPolygon &ex : last)
|
||||||
|
ex.simplify_p(params.scaled_resolution, &pp);
|
||||||
|
// collapse too narrow infill areas
|
||||||
|
coord_t min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
|
||||||
|
// append infill areas to fill_surfaces
|
||||||
|
append(out_fill_expolygons,
|
||||||
|
offset2_ex(
|
||||||
|
union_ex(pp),
|
||||||
|
float(- inset - min_perimeter_infill_spacing / 2.),
|
||||||
|
float(min_perimeter_infill_spacing / 2.)));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ private:
|
|||||||
void process_classic(
|
void process_classic(
|
||||||
// Inputs:
|
// Inputs:
|
||||||
const Parameters ¶ms,
|
const Parameters ¶ms,
|
||||||
const SurfaceCollection *slices,
|
const Surface &surface,
|
||||||
const ExPolygons *lower_slices,
|
const ExPolygons *lower_slices,
|
||||||
// Cache:
|
// Cache:
|
||||||
Polygons &lower_slices_polygons_cache,
|
Polygons &lower_slices_polygons_cache,
|
||||||
@ -82,7 +82,7 @@ void process_classic(
|
|||||||
void process_arachne(
|
void process_arachne(
|
||||||
// Inputs:
|
// Inputs:
|
||||||
const Parameters ¶ms,
|
const Parameters ¶ms,
|
||||||
const SurfaceCollection *slices,
|
const Surface &surface,
|
||||||
const ExPolygons *lower_slices,
|
const ExPolygons *lower_slices,
|
||||||
// Cache:
|
// Cache:
|
||||||
Polygons &lower_slices_polygons_cache,
|
Polygons &lower_slices_polygons_cache,
|
||||||
|
@ -516,6 +516,26 @@ void remove_collinear(Polygons &polys)
|
|||||||
remove_collinear(poly);
|
remove_collinear(poly);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do polygons match? If they match, they must have the same topology,
|
||||||
|
// however their contours may be rotated.
|
||||||
|
bool polygons_match(const Polygon &l, const Polygon &r)
|
||||||
|
{
|
||||||
|
if (l.size() != r.size())
|
||||||
|
return false;
|
||||||
|
auto it_l = std::find(l.points.begin(), l.points.end(), r.points.front());
|
||||||
|
if (it_l == l.points.end())
|
||||||
|
return false;
|
||||||
|
auto it_r = r.points.begin();
|
||||||
|
for (; it_l != l.points.end(); ++ it_l, ++ it_r)
|
||||||
|
if (*it_l != *it_r)
|
||||||
|
return false;
|
||||||
|
it_l = l.points.begin();
|
||||||
|
for (; it_r != r.points.end(); ++ it_l, ++ it_r)
|
||||||
|
if (*it_l != *it_r)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool contains(const Polygons &polygons, const Point &p, bool border_result)
|
bool contains(const Polygons &polygons, const Point &p, bool border_result)
|
||||||
{
|
{
|
||||||
int poly_count_inside = 0;
|
int poly_count_inside = 0;
|
||||||
|
@ -250,6 +250,10 @@ inline Polygons to_polygons(std::vector<Points> &&paths)
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do polygons match? If they match, they must have the same topology,
|
||||||
|
// however their contours may be rotated.
|
||||||
|
bool polygons_match(const Polygon &l, const Polygon &r);
|
||||||
|
|
||||||
// Returns true if inside. Returns border_result if on boundary.
|
// Returns true if inside. Returns border_result if on boundary.
|
||||||
bool contains(const Polygons& polygons, const Point& p, bool border_result = true);
|
bool contains(const Polygons& polygons, const Point& p, bool border_result = true);
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ SCENARIO("Perimeter nesting", "[Perimeters]")
|
|||||||
static_cast<const PrintConfig&>(config),
|
static_cast<const PrintConfig&>(config),
|
||||||
false); // spiral_vase
|
false); // spiral_vase
|
||||||
Polygons lower_layer_polygons_cache;
|
Polygons lower_layer_polygons_cache;
|
||||||
|
for (const Surface &surface : slices)
|
||||||
// FIXME Lukas H.: Disable this test for Arachne because it is failing and needs more investigation.
|
// FIXME Lukas H.: Disable this test for Arachne because it is failing and needs more investigation.
|
||||||
// if (config.perimeter_generator == PerimeterGeneratorType::Arachne)
|
// if (config.perimeter_generator == PerimeterGeneratorType::Arachne)
|
||||||
// PerimeterGenerator::process_arachne();
|
// PerimeterGenerator::process_arachne();
|
||||||
@ -62,7 +63,7 @@ SCENARIO("Perimeter nesting", "[Perimeters]")
|
|||||||
PerimeterGenerator::process_classic(
|
PerimeterGenerator::process_classic(
|
||||||
// input:
|
// input:
|
||||||
perimeter_generator_params,
|
perimeter_generator_params,
|
||||||
&slices,
|
surface,
|
||||||
nullptr,
|
nullptr,
|
||||||
// cache:
|
// cache:
|
||||||
lower_layer_polygons_cache,
|
lower_layer_polygons_cache,
|
||||||
|
Loading…
Reference in New Issue
Block a user