SPE-2486: Implement support for PerimeterRegions that will apply configurations affecting just perimeters only to parts of perimeters instead of creating different LayerRegion.

This commit is contained in:
Lukáš Hejl 2024-10-08 18:09:36 +02:00 committed by Lukas Matena
parent efd95c1c66
commit ed2cdfec61
10 changed files with 868 additions and 94 deletions

View File

@ -0,0 +1,574 @@
#include <cassert>
#include <limits>
#include <vector>
#include "clipper/clipper_z.hpp"
#include "libslic3r/Arachne/utils/ExtrusionLine.hpp"
#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp"
#include "libslic3r/ClipperZUtils.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/PerimeterGenerator.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Polyline.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/libslic3r.h"
#include "LineSegmentation.hpp"
namespace Slic3r::Algorithm::LineSegmentation {
const constexpr coord_t POINT_IS_ON_LINE_THRESHOLD_SQR = Slic3r::sqr(scaled<coord_t>(EPSILON));
struct ZAttributes
{
bool is_clip_point = false;
bool is_new_point = false;
uint32_t point_index = 0;
ZAttributes() = default;
explicit ZAttributes(const uint32_t clipper_coord) :
is_clip_point((clipper_coord >> 31) & 0x1), is_new_point((clipper_coord >> 30) & 0x1), point_index(clipper_coord & 0x3FFFFFFF) {}
explicit ZAttributes(const ClipperLib_Z::IntPoint &clipper_pt) : ZAttributes(clipper_pt.z()) {}
ZAttributes(const bool is_clip_point, const bool is_new_point, const uint32_t point_index) :
is_clip_point(is_clip_point), is_new_point(is_new_point), point_index(point_index)
{
assert(this->point_index < (1u << 30) && "point_index exceeds 30 bits!");
}
// Encode the structure to uint32_t.
constexpr uint32_t encode() const
{
assert(this->point_index < (1u << 30) && "point_index exceeds 30 bits!");
return (this->is_clip_point << 31) | (this->is_new_point << 30) | (this->point_index & 0x3FFFFFFF);
}
// Decode the uint32_t to the structure.
static ZAttributes decode(const uint32_t clipper_coord)
{
return { bool((clipper_coord >> 31) & 0x1), bool((clipper_coord >> 30) & 0x1), clipper_coord & 0x3FFFFFFF };
}
static ZAttributes decode(const ClipperLib_Z::IntPoint &clipper_pt) { return ZAttributes::decode(clipper_pt.z()); }
};
struct LineRegionRange
{
size_t begin_idx; // Index of the line on which the region begins.
double begin_t; // Scalar position on the begin_idx line in which the region begins. The value is from range <0., 1.>.
size_t end_idx; // Index of the line on which the region ends.
double end_t; // Scalar position on the end_idx line in which the region ends. The value is from range <0., 1.>.
size_t clip_idx; // Index of clipping ExPolygons to identified which ExPolygons group contains this line.
LineRegionRange(size_t begin_idx, double begin_t, size_t end_idx, double end_t, size_t clip_idx)
: begin_idx(begin_idx), begin_t(begin_t), end_idx(end_idx), end_t(end_t), clip_idx(clip_idx) {}
// Check if 'other' overlaps with this LineRegionRange.
bool is_overlap(const LineRegionRange &other) const
{
if (this->end_idx < other.begin_idx || this->begin_idx > other.end_idx) {
return false;
} else if (this->end_idx == other.begin_idx && this->end_t <= other.begin_t) {
return false;
} else if (this->begin_idx == other.end_idx && this->begin_t >= other.end_t) {
return false;
}
return true;
}
// Check if 'inner' is whole inside this LineRegionRange.
bool is_inside(const LineRegionRange &inner) const
{
if (!this->is_overlap(inner)) {
return false;
}
const bool starts_after = (this->begin_idx < inner.begin_idx) || (this->begin_idx == inner.begin_idx && this->begin_t <= inner.begin_t);
const bool ends_before = (this->end_idx > inner.end_idx) || (this->end_idx == inner.end_idx && this->end_t >= inner.end_t);
return starts_after && ends_before;
}
bool is_zero_length() const { return this->begin_idx == this->end_idx && this->begin_t == this->end_t; }
bool operator<(const LineRegionRange &rhs) const
{
return this->begin_idx < rhs.begin_idx || (this->begin_idx == rhs.begin_idx && this->begin_t < rhs.begin_t);
}
};
using LineRegionRanges = std::vector<LineRegionRange>;
inline Point make_point(const ClipperLib_Z::IntPoint &clipper_pt) { return { clipper_pt.x(), clipper_pt.y() }; }
inline ClipperLib_Z::Paths to_clip_zpaths(const ExPolygons &clips) { return ClipperZUtils::expolygons_to_zpaths_with_same_z<false>(clips, coord_t(ZAttributes(true, false, 0).encode())); }
static ClipperLib_Z::Path subject_to_zpath(const Points &subject, const bool is_closed)
{
ZAttributes z_attributes(false, false, 0);
ClipperLib_Z::Path out;
if (!subject.empty()) {
out.reserve((subject.size() + is_closed) ? 1 : 0);
for (const Point &p : subject) {
out.emplace_back(p.x(), p.y(), z_attributes.encode());
++z_attributes.point_index;
}
if (is_closed) {
// If it is closed, then duplicate the first point at the end to make a closed path open.
out.emplace_back(subject.front().x(), subject.front().y(), z_attributes.encode());
}
}
return out;
}
static ClipperLib_Z::Path subject_to_zpath(const Arachne::ExtrusionLine &subject)
{
// Closed Arachne::ExtrusionLine already has duplicated the last point.
ZAttributes z_attributes(false, false, 0);
ClipperLib_Z::Path out;
if (!subject.empty()) {
out.reserve(subject.size());
for (const Arachne::ExtrusionJunction &junction : subject) {
out.emplace_back(junction.p.x(), junction.p.y(), z_attributes.encode());
++z_attributes.point_index;
}
}
return out;
}
static ClipperLib_Z::Path subject_to_zpath(const Polyline &subject) { return subject_to_zpath(subject.points, false); }
[[maybe_unused]] static ClipperLib_Z::Path subject_to_zpath(const Polygon &subject) { return subject_to_zpath(subject.points, true); }
struct ProjectionInfo
{
double projected_t;
double distance_sqr;
};
static ProjectionInfo project_point_on_line(const Point &line_from_pt, const Point &line_to_pt, const Point &query_pt)
{
const Vec2d line_vec = (line_to_pt - line_from_pt).template cast<double>();
const Vec2d query_vec = (query_pt - line_from_pt).template cast<double>();
const double line_length_sqr = line_vec.squaredNorm();
if (line_length_sqr <= 0.) {
return { std::numeric_limits<double>::max(), std::numeric_limits<double>::max() };
}
const double projected_t = query_vec.dot(line_vec);
const double projected_t_normalized = std::clamp(projected_t / line_length_sqr, 0., 1.);
// Projected point have to line on the line.
if (projected_t < 0. || projected_t > line_length_sqr) {
return { projected_t_normalized, std::numeric_limits<double>::max() };
}
const Vec2d projected_vec = projected_t_normalized * line_vec;
const double distance_sqr = (projected_vec - query_vec).squaredNorm();
return { projected_t_normalized, distance_sqr };
}
static int32_t find_closest_line_to_point(const ClipperLib_Z::Path &subject, const ClipperLib_Z::IntPoint &query)
{
auto it_min = subject.end();
double distance_sqr_min = std::numeric_limits<double>::max();
const Point query_pt = make_point(query);
Point prev_pt = make_point(subject.front());
for (auto it_curr = std::next(subject.begin()); it_curr != subject.end(); ++it_curr) {
const Point curr_pt = make_point(*it_curr);
const double distance_sqr = project_point_on_line(prev_pt, curr_pt, query_pt).distance_sqr;
if (distance_sqr <= POINT_IS_ON_LINE_THRESHOLD_SQR) {
return int32_t(std::distance(subject.begin(), std::prev(it_curr)));
}
if (distance_sqr < distance_sqr_min) {
distance_sqr_min = distance_sqr;
it_min = std::prev(it_curr);
}
prev_pt = curr_pt;
}
if (it_min != subject.end()) {
return int32_t(std::distance(subject.begin(), it_min));
}
return -1;
}
std::optional<LineRegionRange> create_line_region_range(ClipperLib_Z::Path &&intersection, const ClipperLib_Z::Path &subject, const size_t region_idx)
{
if (intersection.size() < 2) {
return std::nullopt;
}
auto need_reverse = [&subject](const ClipperLib_Z::Path &intersection) -> bool {
for (size_t curr_idx = 1; curr_idx < intersection.size(); ++curr_idx) {
ZAttributes prev_z(intersection[curr_idx - 1]);
ZAttributes curr_z(intersection[curr_idx]);
if (!prev_z.is_clip_point && !curr_z.is_clip_point) {
if (prev_z.point_index > curr_z.point_index) {
return true;
} else if (curr_z.point_index == prev_z.point_index) {
assert(curr_z.point_index < subject.size());
const Point subject_pt = make_point(subject[curr_z.point_index]);
const Point prev_pt = make_point(intersection[curr_idx - 1]);
const Point curr_pt = make_point(intersection[curr_idx]);
const double prev_dist = (prev_pt - subject_pt).cast<double>().squaredNorm();
const double curr_dist = (curr_pt - subject_pt).cast<double>().squaredNorm();
if (prev_dist > curr_dist) {
return true;
}
}
}
}
return false;
};
for (ClipperLib_Z::IntPoint &clipper_pt : intersection) {
const ZAttributes clipper_pt_z(clipper_pt);
if (!clipper_pt_z.is_clip_point) {
continue;
}
// FIXME @hejllukas: We could save searing for the source line in some cases using other intersection points,
// but in reality, the clip point will be inside the intersection in very rare cases.
if (int32_t subject_line_idx = find_closest_line_to_point(subject, clipper_pt); subject_line_idx != -1) {
clipper_pt.z() = coord_t(ZAttributes(false, true, subject_line_idx).encode());
}
assert(!ZAttributes(clipper_pt).is_clip_point);
if (ZAttributes(clipper_pt).is_clip_point) {
return std::nullopt;
}
}
// Ensure that indices of source input are ordered in increasing order.
if (need_reverse(intersection)) {
std::reverse(intersection.begin(), intersection.end());
}
ZAttributes begin_z(intersection.front());
ZAttributes end_z(intersection.back());
assert(begin_z.point_index <= subject.size() && end_z.point_index <= subject.size());
const size_t begin_idx = begin_z.point_index;
const size_t end_idx = end_z.point_index;
const double begin_t = begin_z.is_new_point ? project_point_on_line(make_point(subject[begin_idx]), make_point(subject[begin_idx + 1]), make_point(intersection.front())).projected_t : 0.;
const double end_t = end_z.is_new_point ? project_point_on_line(make_point(subject[end_idx]), make_point(subject[end_idx + 1]), make_point(intersection.back())).projected_t : 0.;
if (begin_t == std::numeric_limits<double>::max() || end_t == std::numeric_limits<double>::max()) {
return std::nullopt;
}
return LineRegionRange{ begin_idx, begin_t, end_idx, end_t, region_idx };
}
LineRegionRanges intersection_with_region(const ClipperLib_Z::Path &subject, const ClipperLib_Z::Paths &clips, const size_t region_config_idx)
{
ClipperLib_Z::Clipper clipper;
clipper.PreserveCollinear(true); // Especially with Arachne, we don't want to remove collinear edges.
clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top,
const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top,
ClipperLib_Z::IntPoint &new_pt) {
const ZAttributes e1bot_z(e1bot), e1top_z(e1top), e2bot_z(e2bot), e2top_z(e2top);
assert(e1bot_z.is_clip_point == e1top_z.is_clip_point);
assert(e2bot_z.is_clip_point == e2top_z.is_clip_point);
if (!e1bot_z.is_clip_point && !e1top_z.is_clip_point) {
assert(e1bot_z.point_index + 1 == e1top_z.point_index || e1bot_z.point_index == e1top_z.point_index + 1);
new_pt.z() = coord_t(ZAttributes(false, true, std::min(e1bot_z.point_index, e1top_z.point_index)).encode());
} else if (!e2bot_z.is_clip_point && !e2top_z.is_clip_point) {
assert(e2bot_z.point_index + 1 == e2top_z.point_index || e2bot_z.point_index == e2top_z.point_index + 1);
new_pt.z() = coord_t(ZAttributes(false, true, std::min(e2bot_z.point_index, e2top_z.point_index)).encode());
} else {
assert(false && "At least one of the conditions above has to be met.");
}
});
clipper.AddPath(subject, ClipperLib_Z::ptSubject, false);
clipper.AddPaths(clips, ClipperLib_Z::ptClip, true);
ClipperLib_Z::Paths intersections;
{
ClipperLib_Z::PolyTree clipped_polytree;
clipper.Execute(ClipperLib_Z::ctIntersection, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(std::move(clipped_polytree), intersections);
}
LineRegionRanges line_region_ranges;
line_region_ranges.reserve(intersections.size());
for (ClipperLib_Z::Path &intersection : intersections) {
if (std::optional<LineRegionRange> region_range = create_line_region_range(std::move(intersection), subject, region_config_idx); region_range.has_value()) {
line_region_ranges.emplace_back(*region_range);
}
}
return line_region_ranges;
}
LineRegionRanges create_continues_line_region_ranges(LineRegionRanges &&line_region_ranges, const size_t default_clip_idx, const size_t total_lines_cnt)
{
if (line_region_ranges.empty()) {
return line_region_ranges;
}
std::sort(line_region_ranges.begin(), line_region_ranges.end());
// Resolve overlapping regions if it happens, but it should never happen.
for (size_t region_range_idx = 1; region_range_idx < line_region_ranges.size(); ++region_range_idx) {
LineRegionRange &prev_range = line_region_ranges[region_range_idx - 1];
LineRegionRange &curr_range = line_region_ranges[region_range_idx];
assert(!prev_range.is_overlap(curr_range));
if (prev_range.is_inside(curr_range)) {
// Make the previous range zero length to remove it later.
curr_range = prev_range;
prev_range.begin_idx = curr_range.begin_idx;
prev_range.begin_t = curr_range.begin_t;
prev_range.end_idx = curr_range.begin_idx;
prev_range.end_t = curr_range.begin_t;
} else if (prev_range.is_overlap(curr_range)) {
curr_range.begin_idx = prev_range.end_idx;
curr_range.begin_t = prev_range.end_t;
}
}
// Fill all gaps between regions with the default region.
LineRegionRanges line_region_ranges_out;
size_t prev_line_idx = 0.;
double prev_t = 0.;
for (const LineRegionRange &curr_line_region : line_region_ranges) {
if (curr_line_region.is_zero_length()) {
continue;
}
assert(prev_line_idx < curr_line_region.begin_idx || (prev_line_idx == curr_line_region.begin_idx && prev_t <= curr_line_region.begin_t));
// Fill the gap if it is necessary.
if (prev_line_idx != curr_line_region.begin_idx || prev_t != curr_line_region.begin_t) {
line_region_ranges_out.emplace_back(prev_line_idx, prev_t, curr_line_region.begin_idx, curr_line_region.begin_t, default_clip_idx);
}
// Add the current region.
line_region_ranges_out.emplace_back(curr_line_region);
prev_line_idx = curr_line_region.end_idx;
prev_t = curr_line_region.end_t;
}
// Fill the last remaining gap if it exists.
const size_t last_line_idx = total_lines_cnt - 1;
if ((prev_line_idx == last_line_idx && prev_t == 1.) || ((prev_line_idx == total_lines_cnt && prev_t == 0.))) {
// There is no gap at the end.
return line_region_ranges_out;
}
// Fill the last remaining gap.
line_region_ranges_out.emplace_back(prev_line_idx, prev_t, last_line_idx, 1., default_clip_idx);
return line_region_ranges_out;
}
LineRegionRanges subject_segmentation(const ClipperLib_Z::Path &subject, const std::vector<ExPolygons> &expolygons_clips, const size_t default_clip_idx = 0)
{
LineRegionRanges line_region_ranges;
for (const ExPolygons &expolygons_clip : expolygons_clips) {
const size_t expolygons_clip_idx = &expolygons_clip - expolygons_clips.data();
const ClipperLib_Z::Paths clips = to_clip_zpaths(expolygons_clip);
Slic3r::append(line_region_ranges, intersection_with_region(subject, clips, expolygons_clip_idx + default_clip_idx + 1));
}
return create_continues_line_region_ranges(std::move(line_region_ranges), default_clip_idx, subject.size() - 1);
}
PolylineSegment create_polyline_segment(const LineRegionRange &line_region_range, const Polyline &subject)
{
Polyline polyline_out;
if (line_region_range.begin_t == 0.) {
polyline_out.points.emplace_back(subject[line_region_range.begin_idx]);
} else {
assert(line_region_range.begin_idx <= subject.size());
Point interpolated_start_pt = lerp(subject[line_region_range.begin_idx], subject[line_region_range.begin_idx + 1], line_region_range.begin_t);
polyline_out.points.emplace_back(interpolated_start_pt);
}
for (size_t line_idx = line_region_range.begin_idx + 1; line_idx <= line_region_range.end_idx; ++line_idx) {
polyline_out.points.emplace_back(subject[line_idx]);
}
if (line_region_range.end_t == 0.) {
polyline_out.points.emplace_back(subject[line_region_range.end_idx]);
} else if (line_region_range.end_t == 1.) {
assert(line_region_range.end_idx <= subject.size());
polyline_out.points.emplace_back(subject[line_region_range.end_idx + 1]);
} else {
assert(line_region_range.end_idx <= subject.size());
Point interpolated_end_pt = lerp(subject[line_region_range.end_idx], subject[line_region_range.end_idx + 1], line_region_range.end_t);
polyline_out.points.emplace_back(interpolated_end_pt);
}
return { polyline_out, line_region_range.clip_idx };
}
PolylineSegments create_polyline_segments(const LineRegionRanges &line_region_ranges, const Polyline &subject)
{
PolylineSegments polyline_segments;
polyline_segments.reserve(line_region_ranges.size());
for (const LineRegionRange &region_range : line_region_ranges) {
polyline_segments.emplace_back(create_polyline_segment(region_range, subject));
}
return polyline_segments;
}
ExtrusionSegment create_extrusion_segment(const LineRegionRange &line_region_range, const Arachne::ExtrusionLine &subject)
{
// When we call this function, we split ExtrusionLine into at least two segments, so none of those segments are closed.
Arachne::ExtrusionLine extrusion_out(subject.inset_idx, subject.is_odd);
if (line_region_range.begin_t == 0.) {
extrusion_out.junctions.emplace_back(subject[line_region_range.begin_idx]);
} else {
assert(line_region_range.begin_idx <= subject.size());
const Arachne::ExtrusionJunction &junction_from = subject[line_region_range.begin_idx];
const Arachne::ExtrusionJunction &junction_to = subject[line_region_range.begin_idx + 1];
const Point interpolated_start_pt = lerp(junction_from.p, junction_to.p, line_region_range.begin_t);
const coord_t interpolated_start_w = lerp(junction_from.w, junction_to.w, line_region_range.begin_t);
assert(junction_from.perimeter_index == junction_to.perimeter_index);
extrusion_out.junctions.emplace_back(interpolated_start_pt, interpolated_start_w, junction_from.perimeter_index);
}
for (size_t line_idx = line_region_range.begin_idx + 1; line_idx <= line_region_range.end_idx; ++line_idx) {
extrusion_out.junctions.emplace_back(subject[line_idx]);
}
if (line_region_range.end_t == 0.) {
extrusion_out.junctions.emplace_back(subject[line_region_range.end_idx]);
} else if (line_region_range.end_t == 1.) {
assert(line_region_range.end_idx <= subject.size());
extrusion_out.junctions.emplace_back(subject[line_region_range.end_idx + 1]);
} else {
assert(line_region_range.end_idx <= subject.size());
const Arachne::ExtrusionJunction &junction_from = subject[line_region_range.end_idx];
const Arachne::ExtrusionJunction &junction_to = subject[line_region_range.end_idx + 1];
const Point interpolated_end_pt = lerp(junction_from.p, junction_to.p, line_region_range.end_t);
const coord_t interpolated_end_w = lerp(junction_from.w, junction_to.w, line_region_range.end_t);
assert(junction_from.perimeter_index == junction_to.perimeter_index);
extrusion_out.junctions.emplace_back(interpolated_end_pt, interpolated_end_w, junction_from.perimeter_index);
}
return { extrusion_out, line_region_range.clip_idx };
}
ExtrusionSegments create_extrusion_segments(const LineRegionRanges &line_region_ranges, const Arachne::ExtrusionLine &subject)
{
ExtrusionSegments extrusion_segments;
extrusion_segments.reserve(line_region_ranges.size());
for (const LineRegionRange &region_range : line_region_ranges) {
extrusion_segments.emplace_back(create_extrusion_segment(region_range, subject));
}
return extrusion_segments;
}
PolylineSegments polyline_segmentation(const Polyline &subject, const std::vector<ExPolygons> &expolygons_clips, const size_t default_clip_idx)
{
const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), expolygons_clips, default_clip_idx);
if (line_region_ranges.empty()) {
return { PolylineSegment{subject, default_clip_idx} };
} else if (line_region_ranges.size() == 1) {
return { PolylineSegment{subject, line_region_ranges.front().clip_idx} };
}
return create_polyline_segments(line_region_ranges, subject);
}
PolylineSegments polygon_segmentation(const Polygon &subject, const std::vector<ExPolygons> &expolygons_clips, const size_t default_clip_idx)
{
return polyline_segmentation(to_polyline(subject), expolygons_clips, default_clip_idx);
}
ExtrusionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const std::vector<ExPolygons> &expolygons_clips, const size_t default_clip_idx)
{
const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), expolygons_clips, default_clip_idx);
if (line_region_ranges.empty()) {
return { ExtrusionSegment{subject, default_clip_idx} };
} else if (line_region_ranges.size() == 1) {
return { ExtrusionSegment{subject, line_region_ranges.front().clip_idx} };
}
return create_extrusion_segments(line_region_ranges, subject);
}
inline std::vector<ExPolygons> to_expolygons_clips(const PerimeterRegions &perimeter_regions_clips)
{
std::vector<ExPolygons> expolygons_clips;
expolygons_clips.reserve(perimeter_regions_clips.size());
for (const PerimeterRegion &perimeter_region_clip : perimeter_regions_clips) {
expolygons_clips.emplace_back(perimeter_region_clip.expolygons);
}
return expolygons_clips;
}
PolylineRegionSegments polyline_segmentation(const Polyline &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips)
{
const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), to_expolygons_clips(perimeter_regions_clips));
if (line_region_ranges.empty()) {
return { PolylineRegionSegment{subject, base_config} };
} else if (line_region_ranges.size() == 1) {
return { PolylineRegionSegment{subject, perimeter_regions_clips[line_region_ranges.front().clip_idx - 1].region->config()} };
}
PolylineRegionSegments segments_out;
for (PolylineSegment &segment : create_polyline_segments(line_region_ranges, subject)) {
const PrintRegionConfig &config = segment.clip_idx == 0 ? base_config : perimeter_regions_clips[segment.clip_idx - 1].region->config();
segments_out.emplace_back(std::move(segment.polyline), config);
}
return segments_out;
}
PolylineRegionSegments polygon_segmentation(const Polygon &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips)
{
return polyline_segmentation(to_polyline(subject), base_config, perimeter_regions_clips);
}
ExtrusionRegionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips)
{
const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), to_expolygons_clips(perimeter_regions_clips));
if (line_region_ranges.empty()) {
return { ExtrusionRegionSegment{subject, base_config} };
} else if (line_region_ranges.size() == 1) {
return { ExtrusionRegionSegment{subject, perimeter_regions_clips[line_region_ranges.front().clip_idx - 1].region->config()} };
}
ExtrusionRegionSegments segments_out;
for (ExtrusionSegment &segment : create_extrusion_segments(line_region_ranges, subject)) {
const PrintRegionConfig &config = segment.clip_idx == 0 ? base_config : perimeter_regions_clips[segment.clip_idx - 1].region->config();
segments_out.emplace_back(std::move(segment.extrusion), config);
}
return segments_out;
}
} // namespace Slic3r::Algorithm::LineSegmentation

View File

@ -0,0 +1,69 @@
#ifndef libslic3r_LineSegmentation_hpp_
#define libslic3r_LineSegmentation_hpp_
#include <vector>
#include "libslic3r/Arachne/utils/ExtrusionLine.hpp"
namespace Slic3r {
class ExPolygon;
class Polyline;
class Polygon;
class PrintRegionConfig;
struct PerimeterRegion;
using ExPolygons = std::vector<ExPolygon>;
using PerimeterRegions = std::vector<PerimeterRegion>;
} // namespace Slic3r
namespace Slic3r::Arachne {
struct ExtrusionLine;
}
namespace Slic3r::Algorithm::LineSegmentation {
struct PolylineSegment
{
Polyline polyline;
size_t clip_idx;
};
struct PolylineRegionSegment
{
Polyline polyline;
const PrintRegionConfig &config;
PolylineRegionSegment(const Polyline &polyline, const PrintRegionConfig &config) : polyline(polyline), config(config) {}
};
struct ExtrusionSegment
{
Arachne::ExtrusionLine extrusion;
size_t clip_idx;
};
struct ExtrusionRegionSegment
{
Arachne::ExtrusionLine extrusion;
const PrintRegionConfig &config;
ExtrusionRegionSegment(const Arachne::ExtrusionLine &extrusion, const PrintRegionConfig &config) : extrusion(extrusion), config(config) {}
};
using PolylineSegments = std::vector<PolylineSegment>;
using ExtrusionSegments = std::vector<ExtrusionSegment>;
using PolylineRegionSegments = std::vector<PolylineRegionSegment>;
using ExtrusionRegionSegments = std::vector<ExtrusionRegionSegment>;
PolylineSegments polyline_segmentation(const Polyline &subject, const std::vector<ExPolygons> &expolygons_clips, size_t default_clip_idx = 0);
PolylineSegments polygon_segmentation(const Polygon &subject, const std::vector<ExPolygons> &expolygons_clips, size_t default_clip_idx = 0);
ExtrusionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const std::vector<ExPolygons> &expolygons_clips, size_t default_clip_idx = 0);
PolylineRegionSegments polyline_segmentation(const Polyline &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips);
PolylineRegionSegments polygon_segmentation(const Polygon &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips);
ExtrusionRegionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips);
} // namespace Slic3r::Algorithm::LineSegmentation
#endif // libslic3r_LineSegmentation_hpp_

View File

@ -34,6 +34,8 @@ set(SLIC3R_SOURCES
AABBTreeLines.hpp
AABBMesh.hpp
AABBMesh.cpp
Algorithm/LineSegmentation/LineSegmentation.cpp
Algorithm/LineSegmentation/LineSegmentation.hpp
Algorithm/PathSorting.hpp
Algorithm/RegionExpansion.hpp
Algorithm/RegionExpansion.cpp

View File

@ -71,6 +71,24 @@ inline ZPaths expolygons_to_zpaths(const ExPolygons &src, coord_t &base_idx)
return out;
}
// Convert multiple expolygons into z-paths with a given Z coordinate.
// If Open, then duplicate the first point of each path at its end.
template<bool Open>
inline ZPaths expolygons_to_zpaths_with_same_z(const ExPolygons &src, const coord_t z)
{
ZPaths out;
out.reserve(std::accumulate(src.begin(), src.end(), size_t(0),
[](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); }));
for (const ExPolygon &expoly : src) {
out.emplace_back(to_zpath<Open>(expoly.contour.points, z));
for (const Polygon &hole : expoly.holes) {
out.emplace_back(to_zpath<Open>(hole.points, z));
}
}
return out;
}
// Convert a single path to path with a given Z coordinate.
// If Open, then duplicate the first point at the end.
template<bool Open = false>

View File

@ -29,6 +29,7 @@
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/ExtrusionEntityCollection.hpp"
#include "libslic3r/LayerRegion.hpp"
#include "libslic3r/PerimeterGenerator.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Surface.hpp"
#include "libslic3r/SurfaceCollection.hpp"
@ -632,6 +633,36 @@ ExPolygons Layer::merged(float offset_scaled) const
return out;
}
// If there is any incompatibility, separate LayerRegions have to be created.
inline bool has_compatible_dynamic_overhang_speed(const PrintRegionConfig &config, const PrintRegionConfig &other_config)
{
bool dynamic_overhang_speed_compatibility = config.enable_dynamic_overhang_speeds == other_config.enable_dynamic_overhang_speeds;
if (dynamic_overhang_speed_compatibility && config.enable_dynamic_overhang_speeds) {
dynamic_overhang_speed_compatibility = config.overhang_speed_0 == other_config.overhang_speed_0 &&
config.overhang_speed_1 == other_config.overhang_speed_1 &&
config.overhang_speed_2 == other_config.overhang_speed_2 &&
config.overhang_speed_3 == other_config.overhang_speed_3;
}
return dynamic_overhang_speed_compatibility;
}
// If there is any incompatibility, separate LayerRegions have to be created.
inline bool has_compatible_layer_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config)
{
return config.perimeter_extruder == other_config.perimeter_extruder &&
config.perimeters == other_config.perimeters &&
config.perimeter_speed == other_config.perimeter_speed &&
config.external_perimeter_speed == other_config.external_perimeter_speed &&
(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") &&
config.thin_walls == other_config.thin_walls &&
config.external_perimeters_first == other_config.external_perimeters_first &&
config.infill_overlap == other_config.infill_overlap &&
has_compatible_dynamic_overhang_speed(config, other_config);
}
// Here the perimeters are created cummulatively for all layer regions sharing the same parameters influencing the perimeters.
// The perimeter paths and the thin fills (ExtrusionEntityCollection) are assigned to the first compatible layer region.
// The resulting fill surface is split back among the originating regions.
@ -662,98 +693,105 @@ void Layer::make_perimeters()
for (LayerSlice &lslice : this->lslices_ex)
lslice.islands.clear();
for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm)
if (size_t region_id = layerm - m_regions.begin(); ! done[region_id]) {
layer_region_reset_perimeters(**layerm);
if (! (*layerm)->slices().empty()) {
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id;
done[region_id] = true;
const PrintRegionConfig &config = (*layerm)->region().config();
perimeter_and_gapfill_ranges.clear();
fill_expolygons.clear();
fill_expolygons_ranges.clear();
surfaces_to_merge.clear();
// find compatible regions
layer_region_ids.clear();
layer_region_ids.push_back(region_id);
for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it)
if (! (*it)->slices().empty()) {
LayerRegion *other_layerm = *it;
const PrintRegionConfig &other_config = other_layerm->region().config();
bool dynamic_overhang_speed_compatibility = config.enable_dynamic_overhang_speeds ==
other_config.enable_dynamic_overhang_speeds;
if (dynamic_overhang_speed_compatibility && config.enable_dynamic_overhang_speeds) {
dynamic_overhang_speed_compatibility = config.overhang_speed_0 == other_config.overhang_speed_0 &&
config.overhang_speed_1 == other_config.overhang_speed_1 &&
config.overhang_speed_2 == other_config.overhang_speed_2 &&
config.overhang_speed_3 == other_config.overhang_speed_3;
}
if (config.perimeter_extruder == other_config.perimeter_extruder
&& config.perimeters == other_config.perimeters
&& config.perimeter_speed == other_config.perimeter_speed
&& config.external_perimeter_speed == other_config.external_perimeter_speed
&& dynamic_overhang_speed_compatibility
&& (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")
&& config.thin_walls == other_config.thin_walls
&& config.external_perimeters_first == other_config.external_perimeters_first
&& config.infill_overlap == other_config.infill_overlap
&& config.fuzzy_skin == other_config.fuzzy_skin
&& config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness
&& config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist)
{
layer_region_reset_perimeters(*other_layerm);
layer_region_ids.push_back(it - m_regions.begin());
done[it - m_regions.begin()] = true;
}
}
if (layer_region_ids.size() == 1) { // optimization
(*layerm)->make_perimeters((*layerm)->slices(), perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges);
this->sort_perimeters_into_islands((*layerm)->slices(), region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids);
} else {
SurfaceCollection new_slices;
// Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence.
uint32_t region_id_config = layer_region_ids.front();
LayerRegion* layerm_config = m_regions[region_id_config];
{
// Merge slices (surfaces) according to number of extra perimeters.
for (uint32_t region_id : layer_region_ids) {
LayerRegion &layerm = *m_regions[region_id];
for (const Surface &surface : layerm.slices())
surfaces_to_merge.emplace_back(&surface);
if (layerm.region().config().fill_density > layerm_config->region().config().fill_density) {
region_id_config = region_id;
layerm_config = &layerm;
}
}
std::sort(surfaces_to_merge.begin(), surfaces_to_merge.end(), [](const Surface *l, const Surface *r){ return l->extra_perimeters < r->extra_perimeters; });
for (size_t i = 0; i < surfaces_to_merge.size();) {
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_config, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids);
}
}
for (auto it_curr_region = m_regions.cbegin(); it_curr_region != m_regions.cend(); ++it_curr_region) {
const size_t curr_region_id = std::distance(m_regions.cbegin(), it_curr_region);
if (done[curr_region_id]) {
continue;
}
LayerRegion &curr_region = **it_curr_region;
layer_region_reset_perimeters(curr_region);
if (curr_region.slices().empty()) {
continue;
}
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << curr_region_id;
done[curr_region_id] = true;
const PrintRegionConfig &curr_config = curr_region.region().config();
perimeter_and_gapfill_ranges.clear();
fill_expolygons.clear();
fill_expolygons_ranges.clear();
surfaces_to_merge.clear();
// Find compatible regions.
layer_region_ids.clear();
layer_region_ids.push_back(curr_region_id);
PerimeterRegions perimeter_regions;
for (auto it_next_region = std::next(it_curr_region); it_next_region != m_regions.cend(); ++it_next_region) {
const size_t next_region_id = std::distance(m_regions.cbegin(), it_next_region);
LayerRegion &next_region = **it_next_region;
const PrintRegionConfig &next_config = next_region.region().config();
if (next_region.slices().empty()) {
continue;
}
if (!has_compatible_layer_regions(curr_config, next_config)) {
continue;
}
// Now, we are sure that we want to merge LayerRegions in any case.
layer_region_reset_perimeters(next_region);
layer_region_ids.push_back(next_region_id);
done[next_region_id] = true;
// If any parameters affecting just perimeters are incompatible, then we also create PerimeterRegion.
if (!PerimeterRegion::has_compatible_perimeter_regions(curr_config, next_config)) {
perimeter_regions.emplace_back(next_region);
}
}
if (layer_region_ids.size() == 1) { // Optimization.
curr_region.make_perimeters(curr_region.slices(), perimeter_regions, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges);
this->sort_perimeters_into_islands(curr_region.slices(), curr_region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids);
} else {
SurfaceCollection new_slices;
// Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence.
uint32_t region_id_config = layer_region_ids.front();
LayerRegion *layerm_config = m_regions[region_id_config];
{
// Merge slices (surfaces) according to number of extra perimeters.
for (uint32_t region_id : layer_region_ids) {
LayerRegion &layerm = *m_regions[region_id];
for (const Surface &surface : layerm.slices())
surfaces_to_merge.emplace_back(&surface);
if (layerm.region().config().fill_density > layerm_config->region().config().fill_density) {
region_id_config = region_id;
layerm_config = &layerm;
}
}
std::sort(surfaces_to_merge.begin(), surfaces_to_merge.end(), [](const Surface *l, const Surface *r) { return l->extra_perimeters < r->extra_perimeters; });
for (size_t i = 0; i < surfaces_to_merge.size();) {
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;
}
}
// Try to merge compatible PerimeterRegions.
if (perimeter_regions.size() > 1) {
PerimeterRegion::merge_compatible_perimeter_regions(perimeter_regions);
}
// Make perimeters.
layerm_config->make_perimeters(new_slices, perimeter_regions, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges);
this->sort_perimeters_into_islands(new_slices, region_id_config, 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";
}

View File

@ -89,6 +89,8 @@ void LayerRegion::slices_to_fill_surfaces_clipped()
void LayerRegion::make_perimeters(
// Input slices for which the perimeters, gap fills and fill expolygons are to be generated.
const SurfaceCollection &slices,
// Configuration regions that will be applied to parts of created perimeters.
const PerimeterRegions &perimeter_regions,
// 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,
@ -123,6 +125,7 @@ void LayerRegion::make_perimeters(
region_config,
this->layer()->object()->config(),
print_config,
perimeter_regions,
spiral_vase
);

View File

@ -29,6 +29,9 @@ class PrintObject;
using LayerPtrs = std::vector<Layer*>;
class PrintRegion;
struct PerimeterRegion;
using PerimeterRegions = std::vector<PerimeterRegion>;
// Range of indices, providing support for range based loops.
template<typename T>
class IndexRange
@ -119,6 +122,8 @@ public:
void make_perimeters(
// Input slices for which the perimeters, gap fills and fill expolygons are to be generated.
const SurfaceCollection &slices,
// Configuration regions that will be applied to parts of created perimeters.
const PerimeterRegions &perimeter_regions,
// 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,

View File

@ -42,7 +42,9 @@
#include "Arachne/utils/ExtrusionJunction.hpp"
#include "libslic3r.h"
#include "libslic3r/Flow.hpp"
#include "libslic3r/LayerRegion.hpp"
#include "libslic3r/Line.hpp"
#include "libslic3r/Print.hpp"
//#define ARACHNE_DEBUG
@ -1544,4 +1546,43 @@ void PerimeterGenerator::process_classic(
append(out_fill_expolygons, std::move(infill_areas));
}
PerimeterRegion::PerimeterRegion(const LayerRegion &layer_region) : region(&layer_region.region())
{
this->expolygons = to_expolygons(layer_region.slices().surfaces);
this->bbox = get_extents(this->expolygons);
}
bool PerimeterRegion::has_compatible_perimeter_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config)
{
return config.fuzzy_skin == other_config.fuzzy_skin &&
config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness &&
config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist;
}
void PerimeterRegion::merge_compatible_perimeter_regions(PerimeterRegions &perimeter_regions)
{
if (perimeter_regions.size() <= 1) {
return;
}
PerimeterRegions perimeter_regions_merged;
for (auto it_curr_region = perimeter_regions.begin(); it_curr_region != perimeter_regions.end();) {
PerimeterRegion current_merge = *it_curr_region;
auto it_next_region = std::next(it_curr_region);
for (; it_next_region != perimeter_regions.end() && has_compatible_perimeter_regions(it_next_region->region->config(), it_curr_region->region->config()); ++it_next_region) {
Slic3r::append(current_merge.expolygons, std::move(it_next_region->expolygons));
current_merge.bbox.merge(it_next_region->bbox);
}
if (std::distance(it_curr_region, it_next_region) > 1) {
current_merge.expolygons = union_ex(current_merge.expolygons);
}
perimeter_regions_merged.emplace_back(std::move(current_merge));
it_curr_region = it_next_region;
}
perimeter_regions = perimeter_regions_merged;
}
}

View File

@ -22,11 +22,31 @@
namespace Slic3r {
class ExtrusionEntityCollection;
class LayerRegion;
class Surface;
class PrintRegion;
struct ThickPolyline;
namespace PerimeterGenerator
struct PerimeterRegion
{
const PrintRegion *region;
ExPolygons expolygons;
BoundingBox bbox;
explicit PerimeterRegion(const LayerRegion &layer_region);
// If there is any incompatibility, we don't need to create separate LayerRegions.
// Because it is enough to split perimeters by PerimeterRegions.
static bool has_compatible_perimeter_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config);
static void merge_compatible_perimeter_regions(std::vector<PerimeterRegion> &perimeter_regions);
};
using PerimeterRegions = std::vector<PerimeterRegion>;
} // namespace Slic3r
namespace Slic3r::PerimeterGenerator {
struct Parameters {
Parameters(
@ -39,6 +59,7 @@ struct Parameters {
const PrintRegionConfig &config,
const PrintObjectConfig &object_config,
const PrintConfig &print_config,
const PerimeterRegions &perimeter_regions,
const bool spiral_vase) :
layer_height(layer_height),
layer_id(layer_id),
@ -49,6 +70,7 @@ struct Parameters {
config(config),
object_config(object_config),
print_config(print_config),
perimeter_regions(perimeter_regions),
spiral_vase(spiral_vase),
scaled_resolution(scaled<double>(print_config.gcode_resolution.value)),
mm3_per_mm(perimeter_flow.mm3_per_mm()),
@ -67,6 +89,7 @@ struct Parameters {
const PrintRegionConfig &config;
const PrintObjectConfig &object_config;
const PrintConfig &print_config;
const PerimeterRegions &perimeter_regions;
// Derived parameters
bool spiral_vase;
@ -113,7 +136,6 @@ void process_arachne(
ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, float tolerance, float merge_tolerance);
} // namespace PerimeterGenerator
} // namespace Slic3r
} // namespace Slic3r::PerimeterGenerator
#endif

View File

@ -46,6 +46,7 @@ SCENARIO("Perimeter nesting", "[Perimeters]")
ExtrusionEntityCollection gap_fill;
ExPolygons fill_expolygons;
Flow flow(1., 1., 1.);
PerimeterRegions perimeter_regions;
PerimeterGenerator::Parameters perimeter_generator_params(
1., // layer height
-1, // layer ID
@ -53,6 +54,7 @@ SCENARIO("Perimeter nesting", "[Perimeters]")
static_cast<const PrintRegionConfig&>(config),
static_cast<const PrintObjectConfig&>(config),
static_cast<const PrintConfig&>(config),
perimeter_regions,
false); // spiral_vase
Polygons lower_layer_polygons_cache;
for (const Surface &surface : slices)