diff --git a/resources/icons/fuzzy_skin_painting.svg b/resources/icons/fuzzy_skin_painting.svg
new file mode 100644
index 0000000000..ebbfc6d870
--- /dev/null
+++ b/resources/icons/fuzzy_skin_painting.svg
@@ -0,0 +1,6 @@
+
+
diff --git a/resources/icons/fuzzy_skin_painting_.svg b/resources/icons/fuzzy_skin_painting_.svg
new file mode 100644
index 0000000000..bb956b6c8b
--- /dev/null
+++ b/resources/icons/fuzzy_skin_painting_.svg
@@ -0,0 +1,6 @@
+
+
diff --git a/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp
new file mode 100644
index 0000000000..4b41a624c9
--- /dev/null
+++ b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp
@@ -0,0 +1,574 @@
+#include
+#include
+#include
+
+#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(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;
+
+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(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();
+ const Vec2d query_vec = (query_pt - line_from_pt).template cast();
+ const double line_length_sqr = line_vec.squaredNorm();
+
+ if (line_length_sqr <= 0.) {
+ return { std::numeric_limits::max(), std::numeric_limits::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::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::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 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().squaredNorm();
+ const double curr_dist = (curr_pt - subject_pt).cast().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::max() || end_t == std::numeric_limits::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 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_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_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_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_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 to_expolygons_clips(const PerimeterRegions &perimeter_regions_clips)
+{
+ std::vector 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
diff --git a/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp
new file mode 100644
index 0000000000..5e53e0add0
--- /dev/null
+++ b/src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp
@@ -0,0 +1,69 @@
+#ifndef libslic3r_LineSegmentation_hpp_
+#define libslic3r_LineSegmentation_hpp_
+
+#include
+
+#include "libslic3r/Arachne/utils/ExtrusionLine.hpp"
+
+namespace Slic3r {
+class ExPolygon;
+class Polyline;
+class Polygon;
+class PrintRegionConfig;
+
+struct PerimeterRegion;
+
+using ExPolygons = std::vector;
+using PerimeterRegions = std::vector;
+} // 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;
+using ExtrusionSegments = std::vector;
+using PolylineRegionSegments = std::vector;
+using ExtrusionRegionSegments = std::vector;
+
+PolylineSegments polyline_segmentation(const Polyline &subject, const std::vector &expolygons_clips, size_t default_clip_idx = 0);
+PolylineSegments polygon_segmentation(const Polygon &subject, const std::vector &expolygons_clips, size_t default_clip_idx = 0);
+ExtrusionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const std::vector &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_
diff --git a/src/libslic3r/Arachne/PerimeterOrder.hpp b/src/libslic3r/Arachne/PerimeterOrder.hpp
index 20d8a3da58..f8469d917f 100644
--- a/src/libslic3r/Arachne/PerimeterOrder.hpp
+++ b/src/libslic3r/Arachne/PerimeterOrder.hpp
@@ -32,9 +32,6 @@ struct PerimeterExtrusion
size_t depth = std::numeric_limits::max();
PerimeterExtrusion *nearest_external_perimeter = nullptr;
- // Should this extrusion be fuzzyfied during path generation?
- bool fuzzify = false;
-
// Returns if ExtrusionLine is a contour or a hole.
bool is_contour() const { return extrusion.is_contour(); }
diff --git a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp
index 49f721b5cc..d0139b054a 100644
--- a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp
+++ b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp
@@ -55,7 +55,8 @@ inline const Point& make_point(const ExtrusionJunction& ej)
return ej.p;
}
-using LineJunctions = std::vector; //; //;
}
#endif // UTILS_EXTRUSION_JUNCTION_H
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index ff22f71ab9..de31aaba16 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -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
@@ -87,6 +89,8 @@ set(SLIC3R_SOURCES
ExtrusionSimulator.cpp
ExtrusionSimulator.hpp
FileParserError.hpp
+ Feature/FuzzySkin/FuzzySkin.cpp
+ Feature/FuzzySkin/FuzzySkin.hpp
Fill/Fill.cpp
Fill/Fill3DHoneycomb.cpp
Fill/Fill3DHoneycomb.hpp
diff --git a/src/libslic3r/ClipperZUtils.hpp b/src/libslic3r/ClipperZUtils.hpp
index f6b249b47f..2c71f4bfd5 100644
--- a/src/libslic3r/ClipperZUtils.hpp
+++ b/src/libslic3r/ClipperZUtils.hpp
@@ -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
+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(expoly.contour.points, z));
+ for (const Polygon &hole : expoly.holes) {
+ out.emplace_back(to_zpath(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
diff --git a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp
new file mode 100644
index 0000000000..9d2b9d3178
--- /dev/null
+++ b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp
@@ -0,0 +1,230 @@
+#include
+
+#include "libslic3r/Algorithm/LineSegmentation/LineSegmentation.hpp"
+#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp"
+#include "libslic3r/Arachne/utils/ExtrusionLine.hpp"
+#include "libslic3r/PerimeterGenerator.hpp"
+#include "libslic3r/Point.hpp"
+#include "libslic3r/Polygon.hpp"
+#include "libslic3r/PrintConfig.hpp"
+
+#include "FuzzySkin.hpp"
+
+using namespace Slic3r;
+
+namespace Slic3r::Feature::FuzzySkin {
+
+// Produces a random value between 0 and 1. Thread-safe.
+static double random_value()
+{
+ thread_local std::random_device rd;
+ // Hash thread ID for random number seed if no hardware rng seed is available
+ thread_local std::mt19937 gen(rd.entropy() > 0 ? rd() : std::hash()(std::this_thread::get_id()));
+ thread_local std::uniform_real_distribution dist(0.0, 1.0);
+ return dist(gen);
+}
+
+void fuzzy_polyline(Points &poly, const bool closed, const double fuzzy_skin_thickness, const double fuzzy_skin_point_distance)
+{
+ const double min_dist_between_points = fuzzy_skin_point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value
+ const double range_random_point_dist = fuzzy_skin_point_distance / 2.;
+ double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point
+
+ Points out;
+ out.reserve(poly.size());
+
+ // Skip the first point for open polyline.
+ Point *p0 = closed ? &poly.back() : &poly.front();
+ for (auto it_pt1 = closed ? poly.begin() : std::next(poly.begin()); it_pt1 != poly.end(); ++it_pt1) {
+ Point &p1 = *it_pt1;
+
+ // 'a' is the (next) new point between p0 and p1
+ Vec2d p0p1 = (p1 - *p0).cast();
+ double p0p1_size = p0p1.norm();
+ double p0pa_dist = dist_left_over;
+ for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) {
+ double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness;
+ out.emplace_back(*p0 + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast());
+ }
+
+ dist_left_over = p0pa_dist - p0p1_size;
+ p0 = &p1;
+ }
+
+ while (out.size() < 3) {
+ size_t point_idx = poly.size() - 2;
+ out.emplace_back(poly[point_idx]);
+ if (point_idx == 0) {
+ break;
+ }
+
+ --point_idx;
+ }
+
+ if (out.size() >= 3) {
+ poly = std::move(out);
+ }
+}
+
+void fuzzy_polygon(Polygon &polygon, double fuzzy_skin_thickness, double fuzzy_skin_point_distance)
+{
+ fuzzy_polyline(polygon.points, true, fuzzy_skin_thickness, fuzzy_skin_point_distance);
+}
+
+void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, const double fuzzy_skin_thickness, const double fuzzy_skin_point_distance)
+{
+ const double min_dist_between_points = fuzzy_skin_point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value
+ const double range_random_point_dist = fuzzy_skin_point_distance / 2.;
+ double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point
+
+ Arachne::ExtrusionJunction *p0 = &ext_lines.front();
+ Arachne::ExtrusionJunctions out;
+ out.reserve(ext_lines.size());
+ for (auto &p1 : ext_lines) {
+ if (p0->p == p1.p) {
+ // Copy the first point.
+ out.emplace_back(p1.p, p1.w, p1.perimeter_index);
+ continue;
+ }
+
+ // 'a' is the (next) new point between p0 and p1
+ Vec2d p0p1 = (p1.p - p0->p).cast();
+ double p0p1_size = p0p1.norm();
+ double p0pa_dist = dist_left_over;
+ for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) {
+ double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness;
+ out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast(), p1.w, p1.perimeter_index);
+ }
+
+ dist_left_over = p0pa_dist - p0p1_size;
+ p0 = &p1;
+ }
+
+ while (out.size() < 3) {
+ size_t point_idx = ext_lines.size() - 2;
+ out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index);
+ if (point_idx == 0) {
+ break;
+ }
+
+ --point_idx;
+ }
+
+ if (ext_lines.back().p == ext_lines.front().p) {
+ // Connect endpoints.
+ out.front().p = out.back().p;
+ }
+
+ if (out.size() >= 3) {
+ ext_lines.junctions = std::move(out);
+ }
+}
+
+bool should_fuzzify(const PrintRegionConfig &config, const size_t layer_idx, const size_t perimeter_idx, const bool is_contour)
+{
+ const FuzzySkinType fuzzy_skin_type = config.fuzzy_skin.value;
+
+ if (fuzzy_skin_type == FuzzySkinType::None || layer_idx <= 0) {
+ return false;
+ }
+
+ const bool fuzzify_contours = perimeter_idx == 0;
+ const bool fuzzify_holes = fuzzify_contours && fuzzy_skin_type == FuzzySkinType::All;
+
+ return is_contour ? fuzzify_contours : fuzzify_holes;
+}
+
+Polygon apply_fuzzy_skin(const Polygon &polygon, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, const size_t layer_idx, const size_t perimeter_idx, const bool is_contour)
+{
+ using namespace Slic3r::Algorithm::LineSegmentation;
+
+ auto apply_fuzzy_skin_on_polygon = [&layer_idx, &perimeter_idx, &is_contour](const Polygon &polygon, const PrintRegionConfig &config) -> Polygon {
+ if (should_fuzzify(config, layer_idx, perimeter_idx, is_contour)) {
+ Polygon fuzzified_polygon = polygon;
+ fuzzy_polygon(fuzzified_polygon, scaled(config.fuzzy_skin_thickness.value), scaled(config.fuzzy_skin_point_dist.value));
+
+ return fuzzified_polygon;
+ } else {
+ return polygon;
+ }
+ };
+
+ if (perimeter_regions.empty()) {
+ return apply_fuzzy_skin_on_polygon(polygon, base_config);
+ }
+
+ PolylineRegionSegments segments = polygon_segmentation(polygon, base_config, perimeter_regions);
+ if (segments.size() == 1) {
+ const PrintRegionConfig &config = segments.front().config;
+ return apply_fuzzy_skin_on_polygon(polygon, config);
+ }
+
+ Polygon fuzzified_polygon;
+ for (PolylineRegionSegment &segment : segments) {
+ const PrintRegionConfig &config = segment.config;
+ if (should_fuzzify(config, layer_idx, perimeter_idx, is_contour)) {
+ fuzzy_polyline(segment.polyline.points, false, scaled(config.fuzzy_skin_thickness.value), scaled(config.fuzzy_skin_point_dist.value));
+ }
+
+ assert(!segment.polyline.empty());
+ if (segment.polyline.empty()) {
+ continue;
+ } else if (!fuzzified_polygon.empty() && fuzzified_polygon.back() == segment.polyline.front()) {
+ // Remove the last point to avoid duplicate points.
+ fuzzified_polygon.points.pop_back();
+ }
+
+ Slic3r::append(fuzzified_polygon.points, std::move(segment.polyline.points));
+ }
+
+ assert(!fuzzified_polygon.empty());
+ if (fuzzified_polygon.front() == fuzzified_polygon.back()) {
+ // Remove the last point to avoid duplicity between the first and the last point.
+ fuzzified_polygon.points.pop_back();
+ }
+
+ return fuzzified_polygon;
+}
+
+Arachne::ExtrusionLine apply_fuzzy_skin(const Arachne::ExtrusionLine &extrusion, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, const size_t layer_idx, const size_t perimeter_idx, const bool is_contour)
+{
+ using namespace Slic3r::Algorithm::LineSegmentation;
+ using namespace Slic3r::Arachne;
+
+ if (perimeter_regions.empty()) {
+ if (should_fuzzify(base_config, layer_idx, perimeter_idx, is_contour)) {
+ ExtrusionLine fuzzified_extrusion = extrusion;
+ fuzzy_extrusion_line(fuzzified_extrusion, scaled(base_config.fuzzy_skin_thickness.value), scaled(base_config.fuzzy_skin_point_dist.value));
+
+ return fuzzified_extrusion;
+ } else {
+ return extrusion;
+ }
+ }
+
+ ExtrusionRegionSegments segments = extrusion_segmentation(extrusion, base_config, perimeter_regions);
+ ExtrusionLine fuzzified_extrusion;
+
+ for (ExtrusionRegionSegment &segment : segments) {
+ const PrintRegionConfig &config = segment.config;
+ if (should_fuzzify(config, layer_idx, perimeter_idx, is_contour)) {
+ fuzzy_extrusion_line(segment.extrusion, scaled(config.fuzzy_skin_thickness.value), scaled(config.fuzzy_skin_point_dist.value));
+ }
+
+ assert(!segment.extrusion.empty());
+ if (segment.extrusion.empty()) {
+ continue;
+ } else if (!fuzzified_extrusion.empty() && fuzzified_extrusion.back().p == segment.extrusion.front().p) {
+ // Remove the last point to avoid duplicate points (We don't care if the width of both points is different.).
+ fuzzified_extrusion.junctions.pop_back();
+ }
+
+ Slic3r::append(fuzzified_extrusion.junctions, std::move(segment.extrusion.junctions));
+ }
+
+ assert(!fuzzified_extrusion.empty());
+
+ return fuzzified_extrusion;
+}
+
+} // namespace Slic3r::Feature::FuzzySkin
diff --git a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp
new file mode 100644
index 0000000000..fb5db546f3
--- /dev/null
+++ b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp
@@ -0,0 +1,26 @@
+#ifndef libslic3r_FuzzySkin_hpp_
+#define libslic3r_FuzzySkin_hpp_
+
+namespace Slic3r::Arachne {
+struct ExtrusionLine;
+} // namespace Slic3r::Arachne
+
+namespace Slic3r::PerimeterGenerator {
+struct Parameters;
+} // namespace Slic3r::PerimeterGenerator
+
+namespace Slic3r::Feature::FuzzySkin {
+
+void fuzzy_polygon(Polygon &polygon, double fuzzy_skin_thickness, double fuzzy_skin_point_distance);
+
+void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy_skin_thickness, double fuzzy_skin_point_dist);
+
+bool should_fuzzify(const PrintRegionConfig &config, size_t layer_idx, size_t perimeter_idx, bool is_contour);
+
+Polygon apply_fuzzy_skin(const Polygon &polygon, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, size_t layer_idx, size_t perimeter_idx, bool is_contour);
+
+Arachne::ExtrusionLine apply_fuzzy_skin(const Arachne::ExtrusionLine &extrusion, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions, size_t layer_idx, size_t perimeter_idx, bool is_contour);
+
+} // namespace Slic3r::Feature::FuzzySkin
+
+#endif // libslic3r_FuzzySkin_hpp_
diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp
index 6cf688b3fb..3dc98acf8a 100644
--- a/src/libslic3r/Format/3mf.cpp
+++ b/src/libslic3r/Format/3mf.cpp
@@ -133,6 +133,7 @@ static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count";
static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports";
static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam";
static constexpr const char* MM_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation";
+static constexpr const char* FUZZY_SKIN_ATTR = "slic3rpe:fuzzy_skin";
static constexpr const char* KEY_ATTR = "key";
static constexpr const char* VALUE_ATTR = "value";
@@ -374,6 +375,7 @@ namespace Slic3r {
std::vector custom_supports;
std::vector custom_seam;
std::vector mm_segmentation;
+ std::vector fuzzy_skin;
bool empty() { return vertices.empty() || triangles.empty(); }
@@ -383,6 +385,7 @@ namespace Slic3r {
custom_supports.clear();
custom_seam.clear();
mm_segmentation.clear();
+ fuzzy_skin.clear();
}
};
@@ -2075,6 +2078,7 @@ namespace Slic3r {
m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR));
m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
+ m_curr_object.geometry.fuzzy_skin.push_back(get_attribute_value_string(attributes, num_attributes, FUZZY_SKIN_ATTR));
// Now load MM segmentation data. Unfortunately, BambuStudio has changed the attribute name after they forked us,
// leading to https://github.com/prusa3d/PrusaSlicer/issues/12502. Let's try to load both keys if the usual
@@ -2579,10 +2583,11 @@ namespace Slic3r {
if (has_transform)
volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object);
- // recreate custom supports, seam and mm segmentation from previously loaded attribute
+ // recreate custom supports, seam, mm segmentation and fuzzy skin from previously loaded attribute
volume->supported_facets.reserve(triangles_count);
volume->seam_facets.reserve(triangles_count);
volume->mm_segmentation_facets.reserve(triangles_count);
+ volume->fuzzy_skin_facets.reserve(triangles_count);
for (size_t i=0; isupported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]);
volume->mm_segmentation_facets.set_triangle_from_string(i, geometry.mm_segmentation[index]);
+ volume->fuzzy_skin_facets.set_triangle_from_string(i, geometry.fuzzy_skin[index]);
}
volume->supported_facets.shrink_to_fit();
volume->seam_facets.shrink_to_fit();
volume->mm_segmentation_facets.shrink_to_fit();
+ volume->fuzzy_skin_facets.shrink_to_fit();
if (auto &es = volume_data.shape_configuration; es.has_value())
volume->emboss_shape = std::move(es);
@@ -3278,6 +3285,15 @@ namespace Slic3r {
output_buffer += "\"";
}
+ std::string fuzzy_skin_data_string = volume->fuzzy_skin_facets.get_triangle_as_string(i);
+ if (!fuzzy_skin_data_string.empty()) {
+ output_buffer += " ";
+ output_buffer += FUZZY_SKIN_ATTR;
+ output_buffer += "=\"";
+ output_buffer += fuzzy_skin_data_string;
+ output_buffer += "\"";
+ }
+
output_buffer += "/>\n";
if (! flush())
diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp
index 2a8ce5d7be..1cf9e1882d 100644
--- a/src/libslic3r/Layer.cpp
+++ b/src/libslic3r/Layer.cpp
@@ -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";
}
diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp
index 89e27cd8bc..c913082636 100644
--- a/src/libslic3r/LayerRegion.cpp
+++ b/src/libslic3r/LayerRegion.cpp
@@ -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> &perimeter_and_gapfill_ranges,
@@ -123,6 +125,7 @@ void LayerRegion::make_perimeters(
region_config,
this->layer()->object()->config(),
print_config,
+ perimeter_regions,
spiral_vase
);
diff --git a/src/libslic3r/LayerRegion.hpp b/src/libslic3r/LayerRegion.hpp
index 7f6038900c..30e9f18e8d 100644
--- a/src/libslic3r/LayerRegion.hpp
+++ b/src/libslic3r/LayerRegion.hpp
@@ -29,6 +29,9 @@ class PrintObject;
using LayerPtrs = std::vector;
class PrintRegion;
+struct PerimeterRegion;
+using PerimeterRegions = std::vector;
+
// Range of indices, providing support for range based loops.
template
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> &perimeter_and_gapfill_ranges,
@@ -152,9 +157,10 @@ protected:
private:
// Modifying m_slices
- friend std::string fix_slicing_errors(LayerPtrs&, const std::function&);
template
- friend void apply_mm_segmentation(PrintObject& print_object, ThrowOnCancel throw_on_cancel);
+ friend void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel);
+ template
+ friend void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel);
Layer *m_layer;
const PrintRegion *m_region;
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 6d0a416275..3fdfee91be 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -648,6 +648,11 @@ bool Model::is_mm_painted() const
return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_mm_painted(); });
}
+bool Model::is_fuzzy_skin_painted() const
+{
+ return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_fuzzy_skin_painted(); });
+}
+
ModelObject::~ModelObject()
{
this->clear_volumes();
@@ -831,6 +836,11 @@ bool ModelObject::is_mm_painted() const
return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
}
+bool ModelObject::is_fuzzy_skin_painted() const
+{
+ return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_fuzzy_skin_painted(); });
+}
+
bool ModelObject::is_text() const
{
return this->volumes.size() == 1 && this->volumes[0]->is_text();
@@ -1248,6 +1258,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
vol->supported_facets.assign(volume->supported_facets);
vol->seam_facets.assign(volume->seam_facets);
vol->mm_segmentation_facets.assign(volume->mm_segmentation_facets);
+ vol->fuzzy_skin_facets.assign(volume->fuzzy_skin_facets);
// Perform conversion only if the target "imperial" state is different from the current one.
// This check supports conversion of "mixed" set of volumes, each with different "imperial" state.
@@ -1360,6 +1371,7 @@ void ModelVolume::reset_extra_facets()
this->supported_facets.reset();
this->seam_facets.reset();
this->mm_segmentation_facets.reset();
+ this->fuzzy_skin_facets.reset();
}
@@ -1926,6 +1938,7 @@ void ModelVolume::assign_new_unique_ids_recursive()
supported_facets.set_new_unique_id();
seam_facets.set_new_unique_id();
mm_segmentation_facets.set_new_unique_id();
+ fuzzy_skin_facets.set_new_unique_id();
}
void ModelVolume::rotate(double angle, Axis axis)
@@ -2273,6 +2286,13 @@ bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObjec
[](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mm_segmentation_facets.timestamp_matches(mv_new.mm_segmentation_facets); });
}
+bool model_fuzzy_skin_data_changed(const ModelObject &mo, const ModelObject &mo_new)
+{
+ return model_property_changed(mo, mo_new,
+ [](const ModelVolumeType t) { return t == ModelVolumeType::MODEL_PART; },
+ [](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.fuzzy_skin_facets.timestamp_matches(mv_new.fuzzy_skin_facets); });
+}
+
bool model_has_parameter_modifiers_in_objects(const Model &model)
{
for (const auto& model_object : model.objects)
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index 820617f6a9..95dc96a36f 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -393,6 +393,8 @@ public:
bool is_seam_painted() const;
// Checks if any of object volume is painted using the multi-material painting gizmo.
bool is_mm_painted() const;
+ // Checks if any of object volume is painted using the fuzzy skin painting gizmo.
+ bool is_fuzzy_skin_painted() const;
// Checks if object contains just one volume and it's a text
bool is_text() const;
// This object may have a varying layer height by painting or by a table.
@@ -802,6 +804,9 @@ public:
// List of mesh facets painted for MM segmentation.
FacetsAnnotation mm_segmentation_facets;
+ // List of mesh facets painted for fuzzy skin.
+ FacetsAnnotation fuzzy_skin_facets;
+
// Is set only when volume is Embossed Text type
// Contain information how to re-create volume
std::optional text_configuration;
@@ -906,11 +911,13 @@ public:
this->supported_facets.set_new_unique_id();
this->seam_facets.set_new_unique_id();
this->mm_segmentation_facets.set_new_unique_id();
+ this->fuzzy_skin_facets.set_new_unique_id();
}
bool is_fdm_support_painted() const { return !this->supported_facets.empty(); }
bool is_seam_painted() const { return !this->seam_facets.empty(); }
bool is_mm_painted() const { return !this->mm_segmentation_facets.empty(); }
+ bool is_fuzzy_skin_painted() const { return !this->fuzzy_skin_facets.empty(); }
// Returns 0-based indices of extruders painted by multi-material painting gizmo.
std::vector get_extruders_from_multi_material_painting() const;
@@ -958,10 +965,12 @@ private:
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mm_segmentation_facets.id().valid());
+ assert(this->fuzzy_skin_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mm_segmentation_facets.id());
+ assert(this->id() != this->fuzzy_skin_facets.id());
return true;
}
@@ -988,13 +997,14 @@ private:
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
supported_facets(other.supported_facets), seam_facets(other.seam_facets), mm_segmentation_facets(other.mm_segmentation_facets),
- cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
+ fuzzy_skin_facets(other.fuzzy_skin_facets), cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
{
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mm_segmentation_facets.id().valid());
+ assert(this->fuzzy_skin_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
@@ -1004,6 +1014,7 @@ private:
assert(this->supported_facets.id() == other.supported_facets.id());
assert(this->seam_facets.id() == other.seam_facets.id());
assert(this->mm_segmentation_facets.id() == other.mm_segmentation_facets.id());
+ assert(this->fuzzy_skin_facets.id() == other.fuzzy_skin_facets.id());
this->set_material_id(other.material_id());
}
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
@@ -1016,10 +1027,12 @@ private:
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mm_segmentation_facets.id().valid());
+ assert(this->fuzzy_skin_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mm_segmentation_facets.id());
+ assert(this->id() != this->fuzzy_skin_facets.id());
assert(this->id() != other.id());
assert(this->config.id() == other.config.id());
this->set_material_id(other.material_id());
@@ -1031,10 +1044,12 @@ private:
assert(this->supported_facets.id() != other.supported_facets.id());
assert(this->seam_facets.id() != other.seam_facets.id());
assert(this->mm_segmentation_facets.id() != other.mm_segmentation_facets.id());
+ assert(this->fuzzy_skin_facets.id() != other.fuzzy_skin_facets.id());
assert(this->id() != this->config.id());
assert(this->supported_facets.empty());
assert(this->seam_facets.empty());
assert(this->mm_segmentation_facets.empty());
+ assert(this->fuzzy_skin_facets.empty());
}
ModelVolume& operator=(ModelVolume &rhs) = delete;
@@ -1042,12 +1057,13 @@ private:
friend class cereal::access;
friend class UndoRedo::StackImpl;
// Used for deserialization, therefore no IDs are allocated.
- ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mm_segmentation_facets(-1), object(nullptr) {
+ ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mm_segmentation_facets(-1), fuzzy_skin_facets(-1), object(nullptr) {
assert(this->id().invalid());
assert(this->config.id().invalid());
assert(this->supported_facets.id().invalid());
assert(this->seam_facets.id().invalid());
assert(this->mm_segmentation_facets.id().invalid());
+ assert(this->fuzzy_skin_facets.id().invalid());
}
template void load(Archive &ar) {
bool has_convex_hull;
@@ -1055,6 +1071,7 @@ private:
cereal::load_by_value(ar, supported_facets);
cereal::load_by_value(ar, seam_facets);
cereal::load_by_value(ar, mm_segmentation_facets);
+ cereal::load_by_value(ar, fuzzy_skin_facets);
cereal::load_by_value(ar, config);
cereal::load(ar, text_configuration);
cereal::load(ar, emboss_shape);
@@ -1073,6 +1090,7 @@ private:
cereal::save_by_value(ar, supported_facets);
cereal::save_by_value(ar, seam_facets);
cereal::save_by_value(ar, mm_segmentation_facets);
+ cereal::save_by_value(ar, fuzzy_skin_facets);
cereal::save_by_value(ar, config);
cereal::save(ar, text_configuration);
cereal::save(ar, emboss_shape);
@@ -1337,6 +1355,8 @@ public:
bool is_seam_painted() const;
// Checks if any of objects is painted using the multi-material painting gizmo.
bool is_mm_painted() const;
+ // Checks if any of objects is painted using the fuzzy skin painting gizmo.
+ bool is_fuzzy_skin_painted() const;
private:
explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }
@@ -1381,6 +1401,10 @@ bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo
// The function assumes that volumes list is synchronized.
extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new);
+// Test whether the now ModelObject has newer fuzzy skin data than the old one.
+// The function assumes that volumes list is synchronized.
+extern bool model_fuzzy_skin_data_changed(const ModelObject &mo, const ModelObject &mo_new);
+
// If the model has object(s) which contains a modofoer, then it is currently not supported by the SLA mode.
// Either the model cannot be loaded, or a SLA printer has to be activated.
bool model_has_parameter_modifiers_in_objects(const Model& model);
diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp
index 8e93d1cd6d..b913e17242 100644
--- a/src/libslic3r/MultiMaterialSegmentation.cpp
+++ b/src/libslic3r/MultiMaterialSegmentation.cpp
@@ -524,7 +524,7 @@ static void remove_multiple_edges_in_vertex(const VD::vertex_type &vertex) {
// It iterates through all nodes on the border between two different colors, and from this point,
// start selection always left most edges for every node to construct CCW polygons.
static std::vector extract_colored_segments(const std::vector &colored_polygons,
- const size_t num_extruders,
+ const size_t num_facets_states,
const size_t layer_idx)
{
const ColoredLines colored_lines = to_lines(colored_polygons);
@@ -606,7 +606,7 @@ static std::vector extract_colored_segments(const std::vector segmented_expolygons_per_extruder(num_extruders + 1);
+ std::vector segmented_expolygons_per_extruder(num_facets_states);
for (const Voronoi::VD::cell_type &cell : vd.cells()) {
if (cell.is_degenerate() || !cell.contains_segment())
continue;
@@ -657,7 +657,7 @@ static void cut_segmented_layers(const std::vector &input_exp
const float interlocking_depth,
const std::function &throw_on_cancel_callback)
{
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Cutting segmented layers in parallel - Begin";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Cutting segmented layers in parallel - Begin";
const float interlocking_cut_width = interlocking_depth > 0.f ? std::max(cut_width - interlocking_depth, 0.f) : 0.f;
tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &interlocking_cut_width, &throw_on_cancel_callback](const tbb::blocked_range& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
@@ -673,7 +673,7 @@ static void cut_segmented_layers(const std::vector &input_exp
}
}
}); // end of parallel_for
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Cutting segmented layers in parallel - End";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Cutting segmented layers in parallel - End";
}
static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d &trafo) {
@@ -686,7 +686,8 @@ static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d
return false;
}
-static inline ExPolygons trim_by_top_or_bottom_layer(ExPolygons expolygons_to_trim, const size_t top_or_bottom_layer_idx, const std::vector> &top_or_bottom_raw_by_extruder) {
+static inline ExPolygons trim_by_top_or_bottom_layer(ExPolygons expolygons_to_trim, const size_t top_or_bottom_layer_idx, const std::vector> &top_or_bottom_raw_by_extruder)
+{
for (const std::vector &top_or_bottom_raw : top_or_bottom_raw_by_extruder) {
if (top_or_bottom_raw.empty())
continue;
@@ -699,15 +700,16 @@ static inline ExPolygons trim_by_top_or_bottom_layer(ExPolygons expolygons_to_tr
return expolygons_to_trim;
}
-// Returns MM segmentation of top and bottom layers based on painting in MM segmentation gizmo
-static inline std::vector> mm_segmentation_top_and_bottom_layers(const PrintObject &print_object,
- const std::vector &input_expolygons,
- const std::function &throw_on_cancel_callback)
+// Returns segmentation of top and bottom layers based on painting in segmentation gizmos.
+static inline std::vector> segmentation_top_and_bottom_layers(const PrintObject &print_object,
+ const std::vector &input_expolygons,
+ const std::function &extract_facets_info,
+ const size_t num_facets_states,
+ const std::function &throw_on_cancel_callback)
{
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Segmentation of top and bottom layers in parallel - Begin";
- const size_t num_extruders = print_object.print()->config().nozzle_diameter.size() + 1;
- const size_t num_layers = input_expolygons.size();
- const SpanOfConstPtrs layers = print_object.layers();
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Segmentation of top and bottom layers in parallel - Begin";
+ const size_t num_layers = input_expolygons.size();
+ const SpanOfConstPtrs layers = print_object.layers();
// Maximum number of top / bottom layers accounts for maximum overlap of one thread group into a neighbor thread group.
int max_top_layers = 0;
@@ -722,7 +724,7 @@ static inline std::vector> mm_segmentation_top_and_botto
// Project upwards pointing painted triangles over top surfaces,
// project downards pointing painted triangles over bottom surfaces.
- std::vector> top_raw(num_extruders), bottom_raw(num_extruders);
+ std::vector> top_raw(num_facets_states), bottom_raw(num_facets_states);
std::vector zs = zs_from_layers(layers);
Transform3d object_trafo = print_object.trafo_centered();
@@ -730,8 +732,8 @@ static inline std::vector> mm_segmentation_top_and_botto
for (const ModelVolume *mv : print_object.model_object()->volumes)
if (mv->is_model_part()) {
const Transform3d volume_trafo = object_trafo * mv->get_matrix();
- for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) {
- const indexed_triangle_set painted = mv->mm_segmentation_facets.get_facets_strict(*mv, TriangleStateType(extruder_idx));
+ for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) {
+ const indexed_triangle_set painted = extract_facets_info(*mv).facets_annotation.get_facets_strict(*mv, TriangleStateType(extruder_idx));
if constexpr (MM_SEGMENTATION_DEBUG_TOP_BOTTOM) {
its_write_obj(painted, debug_out_path("mm-painted-patch-%d.obj", extruder_idx).c_str());
@@ -779,8 +781,8 @@ static inline std::vector> mm_segmentation_top_and_botto
}
}
- auto filter_out_small_polygons = [&num_extruders, &num_layers](std::vector> &raw_surfaces, double min_area) -> void {
- for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) {
+ auto filter_out_small_polygons = [&num_facets_states, &num_layers](std::vector> &raw_surfaces, double min_area) -> void {
+ for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) {
if (raw_surfaces[extruder_idx].empty())
continue;
@@ -798,7 +800,7 @@ static inline std::vector> mm_segmentation_top_and_botto
filter_out_small_polygons(bottom_raw, Slic3r::sqr(POLYGON_FILTER_MIN_AREA_SCALED));
// Remove top and bottom surfaces that are covered by the previous or next sliced layer.
- for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) {
+ for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) {
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
const bool has_top_surface = !top_raw[extruder_idx].empty() && !top_raw[extruder_idx][layer_idx].empty();
const bool has_bottom_surface = !bottom_raw[extruder_idx].empty() && !bottom_raw[extruder_idx][layer_idx].empty();
@@ -817,7 +819,7 @@ static inline std::vector> mm_segmentation_top_and_botto
const std::vector colors = {"aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "yellow"};
for (size_t layer_id = 0; layer_id < zs.size(); ++layer_id) {
std::vector> svg;
- for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) {
+ for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) {
if (!top_raw[extruder_idx].empty() && !top_raw[extruder_idx][layer_id].empty()) {
if (ExPolygons expoly = union_ex(top_raw[extruder_idx][layer_id]); !expoly.empty()) {
const std::string &color = colors[extruder_idx];
@@ -837,10 +839,10 @@ static inline std::vector> mm_segmentation_top_and_botto
}
}
- std::vector> triangles_by_color_bottom(num_extruders);
- std::vector> triangles_by_color_top(num_extruders);
- triangles_by_color_bottom.assign(num_extruders, std::vector(num_layers * 2));
- triangles_by_color_top.assign(num_extruders, std::vector(num_layers * 2));
+ std::vector> triangles_by_color_bottom(num_facets_states);
+ std::vector> triangles_by_color_top(num_facets_states);
+ triangles_by_color_bottom.assign(num_facets_states, std::vector(num_layers * 2));
+ triangles_by_color_top.assign(num_facets_states, std::vector(num_layers * 2));
struct LayerColorStat {
// Number of regions for a queried color.
@@ -878,12 +880,12 @@ static inline std::vector> mm_segmentation_top_and_botto
return out;
};
- tbb::parallel_for(tbb::blocked_range(0, num_layers, granularity), [&granularity, &num_layers, &num_extruders, &layer_color_stat, &top_raw, &triangles_by_color_top,
+ tbb::parallel_for(tbb::blocked_range(0, num_layers, granularity), [&granularity, &num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top,
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom](const tbb::blocked_range &range) {
size_t group_idx = range.begin() / granularity;
size_t layer_idx_offset = (group_idx & 1) * num_layers;
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
- for (size_t color_idx = 0; color_idx < num_extruders; ++ color_idx) {
+ for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) {
throw_on_cancel_callback();
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
if (std::vector &top = top_raw[color_idx]; !top.empty() && !top[layer_idx].empty()) {
@@ -949,8 +951,8 @@ static inline std::vector> mm_segmentation_top_and_botto
}
});
- std::vector> triangles_by_color_merged(num_extruders);
- triangles_by_color_merged.assign(num_extruders, std::vector(num_layers));
+ std::vector> triangles_by_color_merged(num_facets_states);
+ triangles_by_color_merged.assign(num_facets_states, std::vector(num_layers));
tbb::parallel_for(tbb::blocked_range(0, num_layers), [&triangles_by_color_merged, &triangles_by_color_bottom, &triangles_by_color_top, &num_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
throw_on_cancel_callback();
@@ -968,39 +970,42 @@ static inline std::vector> mm_segmentation_top_and_botto
triangles_by_color_merged[color_idx - 1][layer_idx]);
}
});
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Segmentation of top and bottom layers in parallel - End";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Segmentation of top and bottom layers in parallel - End";
return triangles_by_color_merged;
}
-static std::vector> merge_segmented_layers(
- const std::vector> &segmented_regions,
- std::vector> &&top_and_bottom_layers,
- const size_t num_extruders,
- const std::function &throw_on_cancel_callback)
+static std::vector> merge_segmented_layers(const std::vector> &segmented_regions,
+ std::vector> &&top_and_bottom_layers,
+ const size_t num_facets_states,
+ const std::function &throw_on_cancel_callback)
{
const size_t num_layers = segmented_regions.size();
std::vector> segmented_regions_merged(num_layers);
- segmented_regions_merged.assign(num_layers, std::vector(num_extruders));
- assert(num_extruders + 1 == top_and_bottom_layers.size());
+ segmented_regions_merged.assign(num_layers, std::vector(num_facets_states - 1));
+ assert(!top_and_bottom_layers.size() || num_facets_states == top_and_bottom_layers.size());
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Merging segmented layers in parallel - Begin";
- tbb::parallel_for(tbb::blocked_range(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range &range) {
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin";
+ tbb::parallel_for(tbb::blocked_range(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
- assert(segmented_regions[layer_idx].size() == num_extruders + 1);
+ assert(segmented_regions[layer_idx].size() == num_facets_states);
// Zero is skipped because it is the default color of the volume
- for (size_t extruder_id = 1; extruder_id < num_extruders + 1; ++extruder_id) {
+ for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) {
throw_on_cancel_callback();
if (!segmented_regions[layer_idx][extruder_id].empty()) {
ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id];
- for (const std::vector &top_and_bottom_by_extruder : top_and_bottom_layers)
- if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty())
- segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]);
+ if (!top_and_bottom_layers.empty()) {
+ for (const std::vector &top_and_bottom_by_extruder : top_and_bottom_layers) {
+ if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty()) {
+ segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]);
+ }
+ }
+ }
segmented_regions_merged[layer_idx][extruder_id - 1] = std::move(segmented_regions_trimmed);
}
- if (!top_and_bottom_layers[extruder_id][layer_idx].empty()) {
+ if (!top_and_bottom_layers.empty() && !top_and_bottom_layers[extruder_id][layer_idx].empty()) {
bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty();
append(segmented_regions_merged[layer_idx][extruder_id - 1], top_and_bottom_layers[extruder_id][layer_idx]);
@@ -1011,7 +1016,7 @@ static std::vector> merge_segmented_layers(
}
}
}); // end of parallel_for
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Merging segmented layers in parallel - End";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - End";
return segmented_regions_merged;
}
@@ -1602,25 +1607,30 @@ static void update_color_changes_using_color_projection_ranges(std::vector= 0) {
- const TriangleMesh &mesh = volume.mesh();
- return {mesh.its.indices, mesh.its.vertices, std::vector(mesh.its.indices.size(), uint8_t(volume_extruder_id))};
- }
-
- return volume.mm_segmentation_facets.get_all_facets_strict_with_colors(volume);
-}
-
-static std::vector slice_model_volume_with_color(const ModelVolume &model_volume, const std::vector &layer_zs, const PrintObject &print_object)
+static std::vector slice_model_volume_with_color(const ModelVolume &model_volume,
+ const std::function &extract_facets_info,
+ const std::vector &layer_zs,
+ const PrintObject &print_object)
{
- const indexed_triangle_set_with_color mesh_with_color = extract_mesh_with_color(model_volume);
+ const ModelVolumeFacetsInfo facets_info = extract_facets_info(model_volume);
+
+ const auto extract_mesh_with_color = [&model_volume, &facets_info]() -> indexed_triangle_set_with_color {
+ if (const int volume_extruder_id = model_volume.extruder_id(); facets_info.replace_default_extruder && !facets_info.is_painted && volume_extruder_id >= 0) {
+ const TriangleMesh &mesh = model_volume.mesh();
+ return {mesh.its.indices, mesh.its.vertices, std::vector(mesh.its.indices.size(), uint8_t(volume_extruder_id))};
+ }
+
+ return facets_info.facets_annotation.get_all_facets_strict_with_colors(model_volume);
+ };
+
+ const indexed_triangle_set_with_color mesh_with_color = extract_mesh_with_color();
const Transform3d trafo = print_object.trafo_centered() * model_volume.get_matrix();
const MeshSlicingParams slicing_params{trafo};
std::vector color_polygons_per_layer = slice_mesh(mesh_with_color, layer_zs, slicing_params);
// Replace default painted color (TriangleStateType::NONE) with volume extruder.
- if (const int volume_extruder_id = model_volume.extruder_id(); volume_extruder_id > 0 && model_volume.is_mm_painted()) {
+ if (const int volume_extruder_id = model_volume.extruder_id(); facets_info.replace_default_extruder && facets_info.is_painted && volume_extruder_id > 0) {
for (ColorPolygons &color_polygons : color_polygons_per_layer) {
for (ColorPolygon &color_polygon : color_polygons) {
std::replace(color_polygon.colors.begin(), color_polygon.colors.end(), static_cast(TriangleStateType::NONE), static_cast(volume_extruder_id));
@@ -1631,9 +1641,14 @@ static std::vector slice_model_volume_with_color(const ModelVolum
return color_polygons_per_layer;
}
-std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback)
+std::vector> segmentation_by_painting(const PrintObject &print_object,
+ const std::function &extract_facets_info,
+ const size_t num_facets_states,
+ const float segmentation_max_width,
+ const float segmentation_interlocking_depth,
+ const IncludeTopAndBottomLayers include_top_and_bottom_layers,
+ const std::function &throw_on_cancel_callback)
{
- const size_t num_extruders = print_object.print()->config().nozzle_diameter.size();
const size_t num_layers = print_object.layers().size();
const SpanOfConstPtrs layers = print_object.layers();
@@ -1642,7 +1657,7 @@ std::vector> multi_material_segmentation_by_painting(con
std::vector> color_polygons_lines_layers(num_layers);
// Merge all regions and remove small holes
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Slices preprocessing in parallel - Begin";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Slices preprocessing in parallel - Begin";
tbb::parallel_for(tbb::blocked_range(0, num_layers), [&layers, &input_expolygons, &input_polygons_projection_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
@@ -1674,12 +1689,12 @@ std::vector> multi_material_segmentation_by_painting(con
}
}
}); // end of parallel_for
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Slices preprocessing in parallel - End";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Slices preprocessing in parallel - End";
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Slicing painted triangles - Begin";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Slicing painted triangles - Begin";
const std::vector layer_zs = get_print_object_layers_zs(layers);
for (const ModelVolume *mv : print_object.model_object()->volumes) {
- std::vector color_polygons_per_layer = slice_model_volume_with_color(*mv, layer_zs, print_object);
+ std::vector color_polygons_per_layer = slice_model_volume_with_color(*mv, extract_facets_info, layer_zs, print_object);
tbb::parallel_for(tbb::blocked_range(0, num_layers), [&color_polygons_per_layer, &color_polygons_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
@@ -1710,7 +1725,7 @@ std::vector> multi_material_segmentation_by_painting(con
}
}); // end of parallel_for
}
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Slicing painted triangles - End";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Slicing painted triangles - End";
if constexpr (MM_SEGMENTATION_DEBUG_FILTERED_COLOR_LINES) {
for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) {
@@ -1719,7 +1734,7 @@ std::vector> multi_material_segmentation_by_painting(con
}
// Project sliced ColorPolygons on sliced layers (input_expolygons).
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Projection of painted triangles - Begin";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Projection of painted triangles - Begin";
tbb::parallel_for(tbb::blocked_range(0, num_layers), [&color_polygons_lines_layers, &input_polygons_projection_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
@@ -1766,12 +1781,12 @@ std::vector> multi_material_segmentation_by_painting(con
BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Projection of painted triangles - End";
std::vector> segmented_regions(num_layers);
- segmented_regions.assign(num_layers, std::vector(num_extruders + 1));
+ segmented_regions.assign(num_layers, std::vector(num_facets_states));
// Be aware that after the projection of the ColorPolygons and its postprocessing isn't
// ensured that consistency of the color_prev. So, only color_next can be used.
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Layers segmentation in parallel - Begin";
- tbb::parallel_for(tbb::blocked_range(0, num_layers), [&input_polygons_projection_lines_layers, &segmented_regions, &input_expolygons, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range &range) {
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Layers segmentation in parallel - Begin";
+ tbb::parallel_for(tbb::blocked_range(0, num_layers), [&input_polygons_projection_lines_layers, &segmented_regions, &input_expolygons, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
@@ -1797,7 +1812,7 @@ std::vector> multi_material_segmentation_by_painting(con
assert(!colored_polygons.front().empty());
segmented_regions[layer_idx][size_t(colored_polygons.front().front().color)] = input_expolygons[layer_idx];
} else {
- segmented_regions[layer_idx] = extract_colored_segments(colored_polygons, num_extruders, layer_idx);
+ segmented_regions[layer_idx] = extract_colored_segments(colored_polygons, num_facets_states, layer_idx);
}
if constexpr (MM_SEGMENTATION_DEBUG_REGIONS) {
@@ -1805,19 +1820,22 @@ std::vector> multi_material_segmentation_by_painting(con
}
}
}); // end of parallel_for
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Layers segmentation in parallel - End";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Layers segmentation in parallel - End";
throw_on_cancel_callback();
// The first index is extruder number (includes default extruder), and the second one is layer number
- std::vector> top_and_bottom_layers = mm_segmentation_top_and_bottom_layers(print_object, input_expolygons, throw_on_cancel_callback);
- throw_on_cancel_callback();
-
- if (auto max_width = print_object.config().mmu_segmented_region_max_width, interlocking_depth = print_object.config().mmu_segmented_region_interlocking_depth; max_width > 0.f) {
- cut_segmented_layers(input_expolygons, segmented_regions, float(scale_(max_width)), float(scale_(interlocking_depth)), throw_on_cancel_callback);
+ std::vector> top_and_bottom_layers;
+ if (include_top_and_bottom_layers == IncludeTopAndBottomLayers::Yes) {
+ top_and_bottom_layers = segmentation_top_and_bottom_layers(print_object, input_expolygons, extract_facets_info, num_facets_states, throw_on_cancel_callback);
throw_on_cancel_callback();
}
- std::vector> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_extruders, throw_on_cancel_callback);
+ if (segmentation_max_width > 0.f) {
+ cut_segmented_layers(input_expolygons, segmented_regions, scaled(segmentation_max_width), scaled(segmentation_interlocking_depth), throw_on_cancel_callback);
+ throw_on_cancel_callback();
+ }
+
+ std::vector> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback);
throw_on_cancel_callback();
if constexpr (MM_SEGMENTATION_DEBUG_REGIONS) {
@@ -1829,4 +1847,37 @@ std::vector> multi_material_segmentation_by_painting(con
return segmented_regions_merged;
}
+// Returns multi-material segmentation based on painting in multi-material segmentation gizmo
+std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback) {
+ const size_t num_facets_states = print_object.print()->config().nozzle_diameter.size() + 1;
+ const float max_width = float(print_object.config().mmu_segmented_region_max_width.value);
+ const float interlocking_depth = float(print_object.config().mmu_segmented_region_interlocking_depth.value);
+
+ const auto extract_facets_info = [](const ModelVolume &mv) -> ModelVolumeFacetsInfo {
+ return {mv.mm_segmentation_facets, mv.is_mm_painted(), true};
+ };
+
+ return segmentation_by_painting(print_object, extract_facets_info, num_facets_states, max_width, interlocking_depth, IncludeTopAndBottomLayers::Yes, throw_on_cancel_callback);
+}
+
+// Returns fuzzy skin segmentation based on painting in fuzzy skin segmentation gizmo
+std::vector> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback) {
+ const size_t num_facets_states = 2; // Unpainted facets and facets painted with fuzzy skin.
+
+ const auto extract_facets_info = [](const ModelVolume &mv) -> ModelVolumeFacetsInfo {
+ return {mv.fuzzy_skin_facets, mv.is_fuzzy_skin_painted(), false};
+ };
+
+ // Because we apply fuzzy skin just on external perimeters, we limit the depth of fuzzy skin
+ // by the maximal extrusion width of external perimeters.
+ float max_external_perimeter_width = 0.;
+ for (size_t region_idx = 0; region_idx < print_object.num_printing_regions(); ++region_idx) {
+ const PrintRegion ®ion = print_object.printing_region(region_idx);
+ max_external_perimeter_width = std::max(max_external_perimeter_width, region.flow(print_object, frExternalPerimeter, print_object.config().layer_height).width());
+ }
+
+ return segmentation_by_painting(print_object, extract_facets_info, num_facets_states, max_external_perimeter_width, 0.f, IncludeTopAndBottomLayers::No, throw_on_cancel_callback);
+}
+
+
} // namespace Slic3r
\ No newline at end of file
diff --git a/src/libslic3r/MultiMaterialSegmentation.hpp b/src/libslic3r/MultiMaterialSegmentation.hpp
index 8de0963af3..6e846e7733 100644
--- a/src/libslic3r/MultiMaterialSegmentation.hpp
+++ b/src/libslic3r/MultiMaterialSegmentation.hpp
@@ -17,8 +17,10 @@
namespace Slic3r {
-class PrintObject;
class ExPolygon;
+class ModelVolume;
+class PrintObject;
+class FacetsAnnotation;
using ExPolygons = std::vector;
@@ -32,11 +34,36 @@ struct ColoredLine
using ColoredLines = std::vector;
-// Returns MMU segmentation based on painting in MMU segmentation gizmo
-std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback);
+enum class IncludeTopAndBottomLayers {
+ Yes,
+ No
+};
+
+struct ModelVolumeFacetsInfo {
+ const FacetsAnnotation &facets_annotation;
+ // Indicate if model volume is painted.
+ const bool is_painted;
+ // Indicate if the default extruder (TriangleStateType::NONE) should be replaced with the volume extruder.
+ const bool replace_default_extruder;
+};
BoundingBox get_extents(const std::vector &colored_polygons);
+// Returns segmentation based on painting in segmentation gizmos.
+std::vector> segmentation_by_painting(const PrintObject &print_object,
+ const std::function &extract_facets_info,
+ size_t num_facets_states,
+ float segmentation_max_width,
+ float segmentation_interlocking_depth,
+ IncludeTopAndBottomLayers include_top_and_bottom_layers,
+ const std::function &throw_on_cancel_callback);
+
+// Returns multi-material segmentation based on painting in multi-material segmentation gizmo
+std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback);
+
+// Returns fuzzy skin segmentation based on painting in fuzzy skin segmentation gizmo
+std::vector> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback);
+
} // namespace Slic3r
namespace boost::polygon {
diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp
index f5cb1d127f..fa0e11b5a1 100644
--- a/src/libslic3r/PerimeterGenerator.cpp
+++ b/src/libslic3r/PerimeterGenerator.cpp
@@ -27,6 +27,7 @@
#include "ExPolygon.hpp"
#include "ExtrusionEntity.hpp"
#include "ExtrusionEntityCollection.hpp"
+#include "Feature/FuzzySkin/FuzzySkin.hpp"
#include "Point.hpp"
#include "Polygon.hpp"
#include "Polyline.hpp"
@@ -41,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
@@ -176,13 +179,11 @@ public:
bool is_contour;
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
unsigned short depth;
- // Should this contur be fuzzyfied on path generation?
- bool fuzzify;
// Children contour, may be both CCW and CW oriented (outer contours or holes).
std::vector children;
-
- PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool fuzzify) :
- polygon(polygon), is_contour(is_contour), depth(depth), fuzzify(fuzzify) {}
+
+ PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour) :
+ polygon(polygon), is_contour(is_contour), depth(depth) {}
// External perimeter. It may be CCW or CW oriented (outer contour or hole contour).
bool is_external() const { return this->depth == 0; }
// An island, which may have holes, but it does not have another internal island.
@@ -197,95 +198,15 @@ public:
}
};
-// Thanks Cura developers for this function.
-static void fuzzy_polygon(Polygon &poly, double fuzzy_skin_thickness, double fuzzy_skin_point_dist)
-{
- const double min_dist_between_points = fuzzy_skin_point_dist * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value
- const double range_random_point_dist = fuzzy_skin_point_dist / 2.;
- double dist_left_over = double(rand()) * (min_dist_between_points / 2) / double(RAND_MAX); // the distance to be traversed on the line before making the first new point
- Point* p0 = &poly.points.back();
- Points out;
- out.reserve(poly.points.size());
- for (Point &p1 : poly.points)
- { // 'a' is the (next) new point between p0 and p1
- Vec2d p0p1 = (p1 - *p0).cast();
- double p0p1_size = p0p1.norm();
- // so that p0p1_size - dist_last_point evaulates to dist_left_over - p0p1_size
- double dist_last_point = dist_left_over + p0p1_size * 2.;
- for (double p0pa_dist = dist_left_over; p0pa_dist < p0p1_size;
- p0pa_dist += min_dist_between_points + double(rand()) * range_random_point_dist / double(RAND_MAX))
- {
- double r = double(rand()) * (fuzzy_skin_thickness * 2.) / double(RAND_MAX) - fuzzy_skin_thickness;
- out.emplace_back(*p0 + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast());
- dist_last_point = p0pa_dist;
- }
- dist_left_over = p0p1_size - dist_last_point;
- p0 = &p1;
- }
- while (out.size() < 3) {
- size_t point_idx = poly.size() - 2;
- out.emplace_back(poly[point_idx]);
- if (point_idx == 0)
- break;
- -- point_idx;
- }
- if (out.size() >= 3)
- poly.points = std::move(out);
-}
-
-// Thanks Cura developers for this function.
-static void fuzzy_extrusion_line(Arachne::ExtrusionLine &ext_lines, double fuzzy_skin_thickness, double fuzzy_skin_point_dist)
-{
- const double min_dist_between_points = fuzzy_skin_point_dist * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value
- const double range_random_point_dist = fuzzy_skin_point_dist / 2.;
- double dist_left_over = double(rand()) * (min_dist_between_points / 2) / double(RAND_MAX); // the distance to be traversed on the line before making the first new point
-
- auto *p0 = &ext_lines.front();
- std::vector out;
- out.reserve(ext_lines.size());
- for (auto &p1 : ext_lines) {
- if (p0->p == p1.p) { // Connect endpoints.
- out.emplace_back(p1.p, p1.w, p1.perimeter_index);
- continue;
- }
-
- // 'a' is the (next) new point between p0 and p1
- Vec2d p0p1 = (p1.p - p0->p).cast();
- double p0p1_size = p0p1.norm();
- // so that p0p1_size - dist_last_point evaulates to dist_left_over - p0p1_size
- double dist_last_point = dist_left_over + p0p1_size * 2.;
- for (double p0pa_dist = dist_left_over; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + double(rand()) * range_random_point_dist / double(RAND_MAX)) {
- double r = double(rand()) * (fuzzy_skin_thickness * 2.) / double(RAND_MAX) - fuzzy_skin_thickness;
- out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast().normalized() * r).cast(), p1.w, p1.perimeter_index);
- dist_last_point = p0pa_dist;
- }
- dist_left_over = p0p1_size - dist_last_point;
- p0 = &p1;
- }
-
- while (out.size() < 3) {
- size_t point_idx = ext_lines.size() - 2;
- out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index);
- if (point_idx == 0)
- break;
- -- point_idx;
- }
-
- if (ext_lines.back().p == ext_lines.front().p) // Connect endpoints.
- out.front().p = out.back().p;
-
- if (out.size() >= 3)
- ext_lines.junctions = std::move(out);
-}
-
using PerimeterGeneratorLoops = std::vector;
static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls)
{
+ using namespace Slic3r::Feature::FuzzySkin;
+
// loops is an arrayref of ::Loop objects
// turn each one into an ExtrusionLoop object
- ExtrusionEntityCollection coll;
- Polygon fuzzified;
+ ExtrusionEntityCollection coll;
for (const PerimeterGeneratorLoop &loop : loops) {
bool is_external = loop.is_external();
@@ -300,17 +221,15 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator
} else {
loop_role = elrDefault;
}
-
- // detect overhanging/bridging perimeters
+
+ // Apply fuzzy skin if it is enabled for at least some part of the polygon.
+ const Polygon polygon = apply_fuzzy_skin(loop.polygon, params.config, params.perimeter_regions, params.layer_id, loop.depth, loop.is_contour);
+
ExtrusionPaths paths;
- const Polygon &polygon = loop.fuzzify ? fuzzified : loop.polygon;
- if (loop.fuzzify) {
- fuzzified = loop.polygon;
- fuzzy_polygon(fuzzified, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value));
- }
- if (params.config.overhangs && params.layer_id > params.object_config.raft_layers
- && ! ((params.object_config.support_material || params.object_config.support_material_enforce_layers > 0) &&
- params.object_config.support_material_contact_distance.value == 0)) {
+ if (params.config.overhangs && params.layer_id > params.object_config.raft_layers &&
+ !((params.object_config.support_material || params.object_config.support_material_enforce_layers > 0) &&
+ params.object_config.support_material_contact_distance.value == 0)) {
+ // Detect overhanging/bridging perimeters.
BoundingBox bbox(polygon.points);
bbox.offset(SCALED_EPSILON);
Polygons lower_slices_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_slices_polygons_cache, bbox);
@@ -335,7 +254,11 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator
role_overhang,
ExtrusionFlow{ params.mm3_per_mm_overhang, params.overhang_flow.width(), params.overhang_flow.height() }
});
-
+
+ if (paths.empty()) {
+ continue;
+ }
+
// Reapply the nearest point search for starting point.
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
chain_and_reorder_extrusion_paths(paths, &paths.front().first_point());
@@ -493,9 +416,11 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con
static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, Arachne::PerimeterOrder::PerimeterExtrusions &pg_extrusions)
{
+ using namespace Slic3r::Feature::FuzzySkin;
+
ExtrusionEntityCollection extrusion_coll;
for (Arachne::PerimeterOrder::PerimeterExtrusion &pg_extrusion : pg_extrusions) {
- Arachne::ExtrusionLine &extrusion = pg_extrusion.extrusion;
+ Arachne::ExtrusionLine extrusion = pg_extrusion.extrusion;
if (extrusion.empty())
continue;
@@ -503,8 +428,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P
ExtrusionRole role_normal = is_external ? ExtrusionRole::ExternalPerimeter : ExtrusionRole::Perimeter;
ExtrusionRole role_overhang = role_normal | ExtrusionRoleModifier::Bridge;
- if (pg_extrusion.fuzzify)
- fuzzy_extrusion_line(extrusion, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value));
+ // Apply fuzzy skin if it is enabled for at least some part of the ExtrusionLine.
+ extrusion = apply_fuzzy_skin(extrusion, params.config, params.perimeter_regions, params.layer_id, pg_extrusion.extrusion.inset_idx, !pg_extrusion.extrusion.is_closed || pg_extrusion.is_contour());
ExtrusionPaths paths;
// detect overhanging/bridging perimeters
@@ -1193,46 +1118,6 @@ void PerimeterGenerator::process_arachne(
Arachne::PerimeterOrder::PerimeterExtrusions ordered_extrusions = Arachne::PerimeterOrder::ordered_perimeter_extrusions(perimeters, params.config.external_perimeters_first);
- if (params.layer_id > 0 && params.config.fuzzy_skin != FuzzySkinType::None) {
- std::vector closed_loop_extrusions;
- for (Arachne::PerimeterOrder::PerimeterExtrusion &extrusion : ordered_extrusions)
- if (extrusion.extrusion.inset_idx == 0) {
- if (extrusion.extrusion.is_closed && params.config.fuzzy_skin == FuzzySkinType::External) {
- closed_loop_extrusions.emplace_back(&extrusion);
- } else {
- extrusion.fuzzify = true;
- }
- }
-
- if (params.config.fuzzy_skin == FuzzySkinType::External) {
- ClipperLib_Z::Paths loops_paths;
- loops_paths.reserve(closed_loop_extrusions.size());
- for (const auto &cl_extrusion : closed_loop_extrusions) {
- assert(cl_extrusion->extrusion.junctions.front() == cl_extrusion->extrusion.junctions.back());
- size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front();
- ClipperLib_Z::Path loop_path;
- loop_path.reserve(cl_extrusion->extrusion.junctions.size() - 1);
- for (auto junction_it = cl_extrusion->extrusion.junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion.junctions.end()); ++junction_it)
- loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx);
- loops_paths.emplace_back(loop_path);
- }
-
- ClipperLib_Z::Clipper clipper;
- clipper.AddPaths(loops_paths, ClipperLib_Z::ptSubject, true);
- ClipperLib_Z::PolyTree loops_polytree;
- clipper.Execute(ClipperLib_Z::ctUnion, loops_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd);
-
- for (const ClipperLib_Z::PolyNode *child_node : loops_polytree.Childs) {
- // The whole contour must have the same index.
- coord_t polygon_idx = child_node->Contour.front().z();
- bool has_same_idx = std::all_of(child_node->Contour.begin(), child_node->Contour.end(),
- [&polygon_idx](const ClipperLib_Z::IntPoint &point) -> bool { return polygon_idx == point.z(); });
- if (has_same_idx)
- closed_loop_extrusions[polygon_idx]->fuzzify = true;
- }
- }
- }
-
if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(params, lower_slices_polygons_cache, ordered_extrusions); !extrusion_coll.empty())
out_loops.append(extrusion_coll);
@@ -1421,20 +1306,18 @@ void PerimeterGenerator::process_classic(
break;
}
{
- const bool fuzzify_contours = params.config.fuzzy_skin != FuzzySkinType::None && i == 0 && params.layer_id > 0;
- const bool fuzzify_holes = fuzzify_contours && params.config.fuzzy_skin == FuzzySkinType::All;
for (const ExPolygon &expolygon : offsets) {
// Outer contour may overlap with an inner contour,
// inner contour may overlap with another inner contour,
// outer contour may overlap with itself.
//FIXME evaluate the overlaps, annotate each point with an overlap depth,
// compensate for the depth of intersection.
- contours[i].emplace_back(expolygon.contour, i, true, fuzzify_contours);
+ contours[i].emplace_back(expolygon.contour, i, true);
if (! expolygon.holes.empty()) {
holes[i].reserve(holes[i].size() + expolygon.holes.size());
for (const Polygon &hole : expolygon.holes)
- holes[i].emplace_back(hole, i, false, fuzzify_holes);
+ holes[i].emplace_back(hole, i, false);
}
}
}
@@ -1657,4 +1540,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;
+}
+
}
diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp
index 6df0923635..1e699abb79 100644
--- a/src/libslic3r/PerimeterGenerator.hpp
+++ b/src/libslic3r/PerimeterGenerator.hpp
@@ -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 &perimeter_regions);
+};
+
+using PerimeterRegions = std::vector;
+
+} // 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(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
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
index 4da9807adc..4e14649e53 100644
--- a/src/libslic3r/Print.cpp
+++ b/src/libslic3r/Print.cpp
@@ -1762,5 +1762,21 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co
return final_path;
}
+PrintRegion *PrintObjectRegions::FuzzySkinPaintedRegion::parent_print_object_region(const LayerRangeRegions &layer_range) const
+{
+ using FuzzySkinParentType = PrintObjectRegions::FuzzySkinPaintedRegion::ParentType;
+
+ if (this->parent_type == FuzzySkinParentType::PaintedRegion) {
+ return layer_range.painted_regions[this->parent].region;
+ }
+
+ assert(this->parent_type == FuzzySkinParentType::VolumeRegion);
+ return layer_range.volume_regions[this->parent].region;
+}
+
+int PrintObjectRegions::FuzzySkinPaintedRegion::parent_print_object_region_id(const LayerRangeRegions &layer_range) const
+{
+ return this->parent_print_object_region(layer_range)->print_object_region_id();
+}
} // namespace Slic3r
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
index 6c5947c423..61d4714633 100644
--- a/src/libslic3r/Print.hpp
+++ b/src/libslic3r/Print.hpp
@@ -150,8 +150,6 @@ using SpanOfConstPtrs = tcb::span;
using LayerPtrs = std::vector;
using SupportLayerPtrs = std::vector;
-class BoundingBoxf3; // TODO: for temporary constructor parameter
-
// Single instance of a PrintObject.
// As multiple PrintObjects may be generated for a single ModelObject (their instances differ in rotation around Z),
// ModelObject's instancess will be distributed among these multiple PrintObjects.
@@ -204,6 +202,22 @@ public:
PrintRegion *region { nullptr };
};
+ struct LayerRangeRegions;
+
+ struct FuzzySkinPaintedRegion
+ {
+ enum class ParentType { VolumeRegion, PaintedRegion };
+
+ ParentType parent_type { ParentType::VolumeRegion };
+ // Index of a parent VolumeRegion or PaintedRegion.
+ int parent { -1 };
+ // Pointer to PrintObjectRegions::all_regions.
+ PrintRegion *region { nullptr };
+
+ PrintRegion *parent_print_object_region(const LayerRangeRegions &layer_range) const;
+ int parent_print_object_region_id(const LayerRangeRegions &layer_range) const;
+ };
+
// One slice over the PrintObject (possibly the whole PrintObject) and a list of ModelVolumes and their bounding boxes
// possibly clipped by the layer_height_range.
struct LayerRangeRegions
@@ -216,8 +230,9 @@ public:
std::vector volumes;
// Sorted in the order of their source ModelVolumes, thus reflecting the order of region clipping, modifier overrides etc.
- std::vector volume_regions;
- std::vector painted_regions;
+ std::vector volume_regions;
+ std::vector painted_regions;
+ std::vector fuzzy_skin_painted_regions;
bool has_volume(const ObjectID id) const {
auto it = lower_bound_by_predicate(this->volumes.begin(), this->volumes.end(), [id](const VolumeExtents &l) { return l.volume_id < id; });
@@ -340,6 +355,8 @@ public:
bool has_support_material() const { return this->has_support() || this->has_raft(); }
// Checks if the model object is painted using the multi-material painting gizmo.
bool is_mm_painted() const { return this->model_object()->is_mm_painted(); }
+ // Checks if the model object is painted using the fuzzy skin painting gizmo.
+ bool is_fuzzy_skin_painted() const { return this->model_object()->is_fuzzy_skin_painted(); }
// returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions)
std::vector object_extruders() const;
diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp
index 9a5ba3169f..493331e70f 100644
--- a/src/libslic3r/PrintApply.cpp
+++ b/src/libslic3r/PrintApply.cpp
@@ -105,6 +105,8 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst,
mv_dst.seam_facets.assign(mv_src.seam_facets);
assert(mv_dst.mm_segmentation_facets.id() == mv_src.mm_segmentation_facets.id());
mv_dst.mm_segmentation_facets.assign(mv_src.mm_segmentation_facets);
+ assert(mv_dst.fuzzy_skin_facets.id() == mv_src.fuzzy_skin_facets.id());
+ mv_dst.fuzzy_skin_facets.assign(mv_src.fuzzy_skin_facets);
//FIXME what to do with the materials?
// mv_dst.m_material_id = mv_src.m_material_id;
++ i_src;
@@ -681,7 +683,6 @@ bool verify_update_print_object_regions(
ModelVolumePtrs model_volumes,
const PrintRegionConfig &default_region_config,
size_t num_extruders,
- const std::vector &painting_extruders,
PrintObjectRegions &print_object_regions,
const std::function &callback_invalidate)
{
@@ -755,7 +756,7 @@ bool verify_update_print_object_regions(
}
}
- // Verify and / or update PrintRegions produced by color painting.
+ // Verify and / or update PrintRegions produced by color painting.
for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges)
for (const PrintObjectRegions::PaintedRegion ®ion : layer_range.painted_regions) {
const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[region.parent];
@@ -779,6 +780,29 @@ bool verify_update_print_object_regions(
print_region_ref_inc(*region.region);
}
+ // Verify and / or update PrintRegions produced by fuzzy skin painting.
+ for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) {
+ for (const PrintObjectRegions::FuzzySkinPaintedRegion ®ion : layer_range.fuzzy_skin_painted_regions) {
+ const PrintRegion &parent_print_region = *region.parent_print_object_region(layer_range);
+ PrintRegionConfig cfg = parent_print_region.config();
+ cfg.fuzzy_skin.value = FuzzySkinType::All;
+ if (cfg != region.region->config()) {
+ // Region configuration changed.
+ if (print_region_ref_cnt(*region.region) == 0) {
+ // Region is referenced for the first time. Just change its parameters.
+ // Stop the background process before assigning new configuration to the regions.
+ t_config_option_keys diff = region.region->config().diff(cfg);
+ callback_invalidate(region.region->config(), cfg, diff);
+ region.region->config_apply_only(cfg, diff, false);
+ } else {
+ // Region is referenced multiple times, thus the region is being split. We need to reslice.
+ return false;
+ }
+ }
+ print_region_ref_inc(*region.region);
+ }
+ }
+
// Lastly verify, whether some regions were not merged.
{
std::vector regions;
@@ -882,7 +906,8 @@ static PrintObjectRegions* generate_print_object_regions(
const Transform3d &trafo,
size_t num_extruders,
const float xy_size_compensation,
- const std::vector &painting_extruders)
+ const std::vector &painting_extruders,
+ const bool has_painted_fuzzy_skin)
{
// Reuse the old object or generate a new one.
auto out = print_object_regions_old ? std::unique_ptr(print_object_regions_old) : std::make_unique();
@@ -904,6 +929,7 @@ static PrintObjectRegions* generate_print_object_regions(
r.config = range.config;
r.volume_regions.clear();
r.painted_regions.clear();
+ r.fuzzy_skin_painted_regions.clear();
}
} else {
out->trafo_bboxes = trafo;
@@ -985,13 +1011,42 @@ static PrintObjectRegions* generate_print_object_regions(
cfg.infill_extruder.value = painted_extruder_id;
layer_range.painted_regions.push_back({ painted_extruder_id, parent_region_id, get_create_region(std::move(cfg))});
}
- // Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MMU segmentation.
+ // Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MM segmentation.
std::sort(layer_range.painted_regions.begin(), layer_range.painted_regions.end(), [&layer_range](auto &l, auto &r) {
int lid = layer_range.volume_regions[l.parent].region->print_object_region_id();
int rid = layer_range.volume_regions[r.parent].region->print_object_region_id();
return lid < rid || (lid == rid && l.extruder_id < r.extruder_id); });
}
+ if (has_painted_fuzzy_skin) {
+ using FuzzySkinParentType = PrintObjectRegions::FuzzySkinPaintedRegion::ParentType;
+
+ for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions) {
+ // FuzzySkinPaintedRegion can override different parts of the Layer than PaintedRegions,
+ // so FuzzySkinPaintedRegion has to point to both VolumeRegion and PaintedRegion.
+ for (int parent_volume_region_id = 0; parent_volume_region_id < int(layer_range.volume_regions.size()); ++parent_volume_region_id) {
+ if (const PrintObjectRegions::VolumeRegion &parent_volume_region = layer_range.volume_regions[parent_volume_region_id]; parent_volume_region.model_volume->is_model_part() || parent_volume_region.model_volume->is_modifier()) {
+ PrintRegionConfig cfg = parent_volume_region.region->config();
+ cfg.fuzzy_skin.value = FuzzySkinType::All;
+ layer_range.fuzzy_skin_painted_regions.push_back({FuzzySkinParentType::VolumeRegion, parent_volume_region_id, get_create_region(std::move(cfg))});
+ }
+ }
+
+ for (int parent_painted_regions_id = 0; parent_painted_regions_id < int(layer_range.painted_regions.size()); ++parent_painted_regions_id) {
+ const PrintObjectRegions::PaintedRegion &parent_painted_region = layer_range.painted_regions[parent_painted_regions_id];
+
+ PrintRegionConfig cfg = parent_painted_region.region->config();
+ cfg.fuzzy_skin.value = FuzzySkinType::All;
+ layer_range.fuzzy_skin_painted_regions.push_back({FuzzySkinParentType::PaintedRegion, parent_painted_regions_id, get_create_region(std::move(cfg))});
+ }
+
+ // Sort the regions by parent region::print_object_region_id() to help the slicing algorithm when applying fuzzy skin segmentation.
+ std::sort(layer_range.fuzzy_skin_painted_regions.begin(), layer_range.fuzzy_skin_painted_regions.end(), [&layer_range](auto &l, auto &r) {
+ return l.parent_print_object_region_id(layer_range) < r.parent_print_object_region_id(layer_range);
+ });
+ }
+ }
+
return out.release();
}
@@ -1196,7 +1251,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked.
bool solid_or_modifier_differ = model_volume_list_changed(model_object, model_object_new, solid_or_modifier_types) ||
model_mmu_segmentation_data_changed(model_object, model_object_new) ||
- (model_object_new.is_mm_painted() && num_extruders_changed);
+ (model_object_new.is_mm_painted() && num_extruders_changed) ||
+ model_fuzzy_skin_data_changed(model_object, model_object_new);
bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) ||
model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER);
bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty());
@@ -1447,7 +1503,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
print_object.model_object()->volumes,
m_default_region_config,
num_extruders,
- painting_extruders,
*print_object_regions,
[it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) {
for (auto it = it_print_object; it != it_print_object_end; ++it)
@@ -1474,7 +1529,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
model_object_status.print_instances.front().trafo,
num_extruders,
print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_size_compensation.value),
- painting_extruders);
+ painting_extruders,
+ print_object.is_fuzzy_skin_painted());
}
for (auto it = it_print_object; it != it_print_object_end; ++it)
if ((*it)->m_shared_regions) {
@@ -1537,7 +1593,7 @@ void Print::cleanup()
auto this_objects = SpanOfConstPtrs(const_cast(&(*it_begin)), it - it_begin);
if (! Print::is_shared_print_object_step_valid_unguarded(this_objects, posSupportSpotsSearch))
shared_regions->generated_support_points.reset();
- }
+ }
}
bool Print::is_shared_print_object_step_valid_unguarded(SpanOfConstPtrs print_objects, PrintObjectStep print_object_step)
diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp
index 754a2d1ef0..1e819876f3 100644
--- a/src/libslic3r/PrintObjectSlice.cpp
+++ b/src/libslic3r/PrintObjectSlice.cpp
@@ -39,7 +39,6 @@
#include "libslic3r/TriangleMeshSlicer.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/libslic3r.h"
-#include "tcbspan/span.hpp"
namespace Slic3r {
@@ -580,34 +579,36 @@ void PrintObject::slice()
template
void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel)
{
- // Returns MMU segmentation based on painting in MMU segmentation gizmo
+ // Returns MM segmentation based on painting in MM segmentation gizmo
std::vector> segmentation = multi_material_segmentation_by_painting(print_object, throw_on_cancel);
assert(segmentation.size() == print_object.layer_count());
tbb::parallel_for(
tbb::blocked_range(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))),
[&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range &range) {
const auto &layer_ranges = print_object.shared_regions()->layer_ranges;
- double z = print_object.get_layer(range.begin())->slice_z;
+ double z = print_object.get_layer(int(range.begin()))->slice_z;
auto it_layer_range = layer_range_first(layer_ranges, z);
const size_t num_extruders = print_object.print()->config().nozzle_diameter.size();
+
struct ByExtruder {
ExPolygons expolygons;
BoundingBox bbox;
};
+
std::vector