mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-07-31 04:01:59 +08:00
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:
parent
efd95c1c66
commit
ed2cdfec61
574
src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp
Normal file
574
src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp
Normal 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 ®ion_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 ®ion_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
|
@ -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_
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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";
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user