Merge branch 'lh_fuzzy_skin'

This commit is contained in:
Lukas Matena 2024-11-12 15:23:11 +01:00
commit 4500de008b
42 changed files with 2156 additions and 467 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="128px" height="128px" viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
<path fill="#ED6B21" d="m 88,38.93 c -0.83,0 -1.5,0.67 -1.5,1.5 V 70.5 h -5 V 45.14 c 0,-0.83 -0.67,-1.5 -1.5,-1.5 -0.83,0 -1.5,0.67 -1.5,1.5 V 70.5 h -5 V 49.8 c 0,-0.83 -0.67,-1.5 -1.5,-1.5 -0.83,0 -1.5,0.67 -1.5,1.5 v 20.7 h -5 V 53.84 c 0,-0.83 -0.67,-1.5 -1.5,-1.5 -0.83,0 -1.5,0.67 -1.5,1.5 V 70.5 h -5 V 49.8 c 0,-0.83 -0.67,-1.5 -1.5,-1.5 -0.83,0 -1.5,0.67 -1.5,1.5 v 20.7 h -5 V 45.14 c 0,-0.83 -0.67,-1.5 -1.5,-1.5 -0.83,0 -1.5,0.67 -1.5,1.5 V 70.5 h -5 V 40.43 c 0,-0.83 -0.67,-1.5 -1.5,-1.5 -0.83,0 -1.5,0.67 -1.5,1.5 V 72 82.99 c 0,3.59 2.92,6.51 6.51,6.51 h 2.98 c 0.67,0.01 6.51,0.24 6.51,6.5 v 16 c 0,3.29 1.99,9.5 9.5,9.5 7.51,0 9.5,-6.21 9.5,-9.5 V 96 c 0,-6.26 5.84,-6.49 6.5,-6.5 h 3 c 3.59,0 6.5,-2.92 6.5,-6.5 V 72 40.43 c 0,-0.83 -0.67,-1.5 -1.5,-1.5 z M 86.5,83 c 0,1.93 -1.57,3.5 -3.5,3.5 h -3 c -3.29,0 -9.5,1.99 -9.5,9.5 v 15.99 c -0.01,0.67 -0.24,6.51 -6.5,6.51 -6.26,0 -6.49,-5.84 -6.5,-6.5 V 96 c 0,-7.51 -6.21,-9.5 -9.5,-9.5 h -2.99 c -1.94,0 -3.51,-1.57 -3.51,-3.51 V 73.5 h 45 z"/>
<path fill="#FFFFFF" d="m 109.62313,6.5793356 c -4.05489,0 -7.35886,2.5891328 -7.50904,5.8406014 -9.836863,0.301062 -15.018101,6.442725 -15.018101,12.223115 l -7.509041,0.06022 c -9.461393,0 -14.64263,5.840601 -15.018083,11.500567 -0.07509,0 -0.07509,0 -0.15018,0 h -0.675814 c 0,0 -0.07509,0 -0.150181,0 C 63.217238,30.423447 57.960909,24.70327 48.574608,24.70327 h -7.509042 c 0.225272,0 -0.150181,-0.120425 -0.300361,-0.180637 0.375451,-5.720177 -4.805787,-11.922053 -14.56754,-12.10269 -0.375453,-3.0708319 -3.604341,-5.6599647 -7.509042,-5.6599647 -4.129973,0 -7.509041,2.5289204 -7.509041,5.8406017 0,4.455716 2.327803,7.165274 4.280154,8.670584 4.280153,3.251469 9.686663,3.251469 10.737929,3.191257 0,8.670583 8.785578,12.042478 14.64263,12.283327 h 7.809403 c 0,4.154655 2.252712,6.743788 4.129972,8.188885 4.355245,3.371894 10.137207,3.371894 11.263562,3.311682 h 0.525633 c 0.825994,0 6.607956,0.06022 10.963201,-3.311682 1.87726,-1.445097 4.129972,-4.094442 4.129972,-8.249098 h 7.509041 c 6.232505,-0.180636 15.018101,-3.612743 15.018101,-12.10269 0.97617,-0.120424 6.45777,-0.120424 10.66284,-3.371892 1.95235,-1.505311 4.28015,-4.214868 4.28015,-8.670585 0,-3.2514693 -3.37907,-5.9610268 -7.50904,-5.9610268 z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="128px" height="128px" viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
<path fill="#ED6B21" d="m 88,38.93 c -0.83,0 -1.5,0.67 -1.5,1.5 V 70.5 h -5 V 45.14 c 0,-0.83 -0.67,-1.5 -1.5,-1.5 -0.83,0 -1.5,0.67 -1.5,1.5 V 70.5 h -5 V 49.8 c 0,-0.83 -0.67,-1.5 -1.5,-1.5 -0.83,0 -1.5,0.67 -1.5,1.5 v 20.7 h -5 V 53.84 c 0,-0.83 -0.67,-1.5 -1.5,-1.5 -0.83,0 -1.5,0.67 -1.5,1.5 V 70.5 h -5 V 49.8 c 0,-0.83 -0.67,-1.5 -1.5,-1.5 -0.83,0 -1.5,0.67 -1.5,1.5 v 20.7 h -5 V 45.14 c 0,-0.83 -0.67,-1.5 -1.5,-1.5 -0.83,0 -1.5,0.67 -1.5,1.5 V 70.5 h -5 V 40.43 c 0,-0.83 -0.67,-1.5 -1.5,-1.5 -0.83,0 -1.5,0.67 -1.5,1.5 V 72 82.99 c 0,3.59 2.92,6.51 6.51,6.51 h 2.98 c 0.67,0.01 6.51,0.24 6.51,6.5 v 16 c 0,3.29 1.99,9.5 9.5,9.5 7.51,0 9.5,-6.21 9.5,-9.5 V 96 c 0,-6.26 5.84,-6.49 6.5,-6.5 h 3 c 3.59,0 6.5,-2.92 6.5,-6.5 V 72 40.43 c 0,-0.83 -0.67,-1.5 -1.5,-1.5 z M 86.5,83 c 0,1.93 -1.57,3.5 -3.5,3.5 h -3 c -3.29,0 -9.5,1.99 -9.5,9.5 v 15.99 c -0.01,0.67 -0.24,6.51 -6.5,6.51 -6.26,0 -6.49,-5.84 -6.5,-6.5 V 96 c 0,-7.51 -6.21,-9.5 -9.5,-9.5 h -2.99 c -1.94,0 -3.51,-1.57 -3.51,-3.51 V 73.5 h 45 z"/>
<path fill="#808080" d="m 109.62313,6.5793356 c -4.05489,0 -7.35886,2.5891328 -7.50904,5.8406014 -9.836863,0.301062 -15.018101,6.442725 -15.018101,12.223115 l -7.509041,0.06022 c -9.461393,0 -14.64263,5.840601 -15.018083,11.500567 -0.07509,0 -0.07509,0 -0.15018,0 h -0.675814 c 0,0 -0.07509,0 -0.150181,0 C 63.217238,30.423447 57.960909,24.70327 48.574608,24.70327 h -7.509042 c 0.225272,0 -0.150181,-0.120425 -0.300361,-0.180637 0.375451,-5.720177 -4.805787,-11.922053 -14.56754,-12.10269 -0.375453,-3.0708319 -3.604341,-5.6599647 -7.509042,-5.6599647 -4.129973,0 -7.509041,2.5289204 -7.509041,5.8406017 0,4.455716 2.327803,7.165274 4.280154,8.670584 4.280153,3.251469 9.686663,3.251469 10.737929,3.191257 0,8.670583 8.785578,12.042478 14.64263,12.283327 h 7.809403 c 0,4.154655 2.252712,6.743788 4.129972,8.188885 4.355245,3.371894 10.137207,3.371894 11.263562,3.311682 h 0.525633 c 0.825994,0 6.607956,0.06022 10.963201,-3.311682 1.87726,-1.445097 4.129972,-4.094442 4.129972,-8.249098 h 7.509041 c 6.232505,-0.180636 15.018101,-3.612743 15.018101,-12.10269 0.97617,-0.120424 6.45777,-0.120424 10.66284,-3.371892 1.95235,-1.505311 4.28015,-4.214868 4.28015,-8.670585 0,-3.2514693 -3.37907,-5.9610268 -7.50904,-5.9610268 z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

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

View File

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

View File

@ -32,9 +32,6 @@ struct PerimeterExtrusion
size_t depth = std::numeric_limits<size_t>::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(); }

View File

@ -55,7 +55,8 @@ inline const Point& make_point(const ExtrusionJunction& ej)
return ej.p;
}
using LineJunctions = std::vector<ExtrusionJunction>; //<! The junctions along a line without further information. See \ref ExtrusionLine for a more extensive class.
using LineJunctions = std::vector<ExtrusionJunction>; //<! The junctions along a line without further information. See \ref ExtrusionLine for a more extensive class.
using ExtrusionJunctions = std::vector<ExtrusionJunction>;
}
#endif // UTILS_EXTRUSION_JUNCTION_H

View File

@ -34,6 +34,8 @@ set(SLIC3R_SOURCES
AABBTreeLines.hpp
AABBMesh.hpp
AABBMesh.cpp
Algorithm/LineSegmentation/LineSegmentation.cpp
Algorithm/LineSegmentation/LineSegmentation.hpp
Algorithm/PathSorting.hpp
Algorithm/RegionExpansion.hpp
Algorithm/RegionExpansion.cpp
@ -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

View File

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

View File

@ -0,0 +1,230 @@
#include <random>
#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::thread::id>()(std::this_thread::get_id()));
thread_local std::uniform_real_distribution<double> 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>();
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<double>().normalized() * r).cast<coord_t>());
}
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>();
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<double>().normalized() * r).cast<coord_t>(), 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<double>(config.fuzzy_skin_thickness.value), scaled<double>(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<double>(config.fuzzy_skin_thickness.value), scaled<double>(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<double>(base_config.fuzzy_skin_thickness.value), scaled<double>(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<double>(config.fuzzy_skin_thickness.value), scaled<double>(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

View File

@ -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_

View File

@ -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<std::string> custom_supports;
std::vector<std::string> custom_seam;
std::vector<std::string> mm_segmentation;
std::vector<std::string> 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; i<triangles_count; ++i) {
size_t index = volume_data.first_triangle_id + i;
assert(index < geometry.custom_supports.size());
@ -2592,10 +2597,12 @@ namespace Slic3r {
volume->supported_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())

View File

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

View File

@ -89,6 +89,8 @@ void LayerRegion::slices_to_fill_surfaces_clipped()
void LayerRegion::make_perimeters(
// Input slices for which the perimeters, gap fills and fill expolygons are to be generated.
const SurfaceCollection &slices,
// Configuration regions that will be applied to parts of created perimeters.
const PerimeterRegions &perimeter_regions,
// Ranges of perimeter extrusions and gap fill extrusions per suface, referencing
// newly created extrusions stored at this LayerRegion.
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
@ -123,6 +125,7 @@ void LayerRegion::make_perimeters(
region_config,
this->layer()->object()->config(),
print_config,
perimeter_regions,
spiral_vase
);

View File

@ -29,6 +29,9 @@ class PrintObject;
using LayerPtrs = std::vector<Layer*>;
class PrintRegion;
struct PerimeterRegion;
using PerimeterRegions = std::vector<PerimeterRegion>;
// Range of indices, providing support for range based loops.
template<typename T>
class IndexRange
@ -119,6 +122,8 @@ public:
void make_perimeters(
// Input slices for which the perimeters, gap fills and fill expolygons are to be generated.
const SurfaceCollection &slices,
// Configuration regions that will be applied to parts of created perimeters.
const PerimeterRegions &perimeter_regions,
// Ranges of perimeter extrusions and gap fill extrusions per suface, referencing
// newly created extrusions stored at this LayerRegion.
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
@ -152,9 +157,10 @@ protected:
private:
// Modifying m_slices
friend std::string fix_slicing_errors(LayerPtrs&, const std::function<void()>&);
template<typename ThrowOnCancel>
friend void apply_mm_segmentation(PrintObject& print_object, ThrowOnCancel throw_on_cancel);
friend void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel);
template<typename ThrowOnCancel>
friend void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel);
Layer *m_layer;
const PrintRegion *m_region;

View File

@ -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)

View File

@ -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<TextConfiguration> 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<size_t> 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<class Archive> 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);

View File

@ -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<ExPolygons> extract_colored_segments(const std::vector<ColoredLines> &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<ExPolygons> extract_colored_segments(const std::vector<Colore
}
// Sixth, extract the colored segments from the annotated Voronoi diagram.
std::vector<ExPolygons> segmented_expolygons_per_extruder(num_extruders + 1);
std::vector<ExPolygons> 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<ExPolygons> &input_exp
const float interlocking_depth,
const std::function<void()> &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<size_t>(0, segmented_regions.size()),[&segmented_regions, &input_expolygons, &cut_width, &interlocking_cut_width, &throw_on_cancel_callback](const tbb::blocked_range<size_t>& 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<ExPolygons> &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<std::vector<Polygons>> &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<std::vector<Polygons>> &top_or_bottom_raw_by_extruder)
{
for (const std::vector<Polygons> &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<std::vector<ExPolygons>> mm_segmentation_top_and_bottom_layers(const PrintObject &print_object,
const std::vector<ExPolygons> &input_expolygons,
const std::function<void()> &throw_on_cancel_callback)
// Returns segmentation of top and bottom layers based on painting in segmentation gizmos.
static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_layers(const PrintObject &print_object,
const std::vector<ExPolygons> &input_expolygons,
const std::function<ModelVolumeFacetsInfo(const ModelVolume &)> &extract_facets_info,
const size_t num_facets_states,
const std::function<void()> &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<Layer> 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<Layer> 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<std::vector<ExPolygons>> mm_segmentation_top_and_botto
// Project upwards pointing painted triangles over top surfaces,
// project downards pointing painted triangles over bottom surfaces.
std::vector<std::vector<Polygons>> top_raw(num_extruders), bottom_raw(num_extruders);
std::vector<std::vector<Polygons>> top_raw(num_facets_states), bottom_raw(num_facets_states);
std::vector<float> zs = zs_from_layers(layers);
Transform3d object_trafo = print_object.trafo_centered();
@ -730,8 +732,8 @@ static inline std::vector<std::vector<ExPolygons>> 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<std::vector<ExPolygons>> mm_segmentation_top_and_botto
}
}
auto filter_out_small_polygons = [&num_extruders, &num_layers](std::vector<std::vector<Polygons>> &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<std::vector<Polygons>> &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<std::vector<ExPolygons>> 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<std::vector<ExPolygons>> mm_segmentation_top_and_botto
const std::vector<std::string> 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<std::pair<Slic3r::ExPolygons, SVG::ExPolygonAttributes>> 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<std::vector<ExPolygons>> mm_segmentation_top_and_botto
}
}
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_extruders);
std::vector<std::vector<ExPolygons>> triangles_by_color_top(num_extruders);
triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_facets_states);
std::vector<std::vector<ExPolygons>> triangles_by_color_top(num_facets_states);
triangles_by_color_bottom.assign(num_facets_states, std::vector<ExPolygons>(num_layers * 2));
triangles_by_color_top.assign(num_facets_states, std::vector<ExPolygons>(num_layers * 2));
struct LayerColorStat {
// Number of regions for a queried color.
@ -878,12 +880,12 @@ static inline std::vector<std::vector<ExPolygons>> mm_segmentation_top_and_botto
return out;
};
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers, granularity), [&granularity, &num_layers, &num_extruders, &layer_color_stat, &top_raw, &triangles_by_color_top,
tbb::parallel_for(tbb::blocked_range<size_t>(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<size_t> &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<Polygons> &top = top_raw[color_idx]; !top.empty() && !top[layer_idx].empty()) {
@ -949,8 +951,8 @@ static inline std::vector<std::vector<ExPolygons>> mm_segmentation_top_and_botto
}
});
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_extruders);
triangles_by_color_merged.assign(num_extruders, std::vector<ExPolygons>(num_layers));
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_facets_states);
triangles_by_color_merged.assign(num_facets_states, std::vector<ExPolygons>(num_layers));
tbb::parallel_for(tbb::blocked_range<size_t>(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<size_t> &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<std::vector<ExPolygons>> 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<std::vector<ExPolygons>> merge_segmented_layers(
const std::vector<std::vector<ExPolygons>> &segmented_regions,
std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers,
const size_t num_extruders,
const std::function<void()> &throw_on_cancel_callback)
static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::vector<std::vector<ExPolygons>> &segmented_regions,
std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers,
const size_t num_facets_states,
const std::function<void()> &throw_on_cancel_callback)
{
const size_t num_layers = segmented_regions.size();
std::vector<std::vector<ExPolygons>> segmented_regions_merged(num_layers);
segmented_regions_merged.assign(num_layers, std::vector<ExPolygons>(num_extruders));
assert(num_extruders + 1 == top_and_bottom_layers.size());
segmented_regions_merged.assign(num_layers, std::vector<ExPolygons>(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<size_t>(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &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<ExPolygons> &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<ExPolygons> &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<std::vector<ExPolygons>> 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<Color
}
}
static indexed_triangle_set_with_color extract_mesh_with_color(const ModelVolume &volume) {
if (const int volume_extruder_id = volume.extruder_id(); !volume.is_mm_painted() && volume_extruder_id >= 0) {
const TriangleMesh &mesh = volume.mesh();
return {mesh.its.indices, mesh.its.vertices, std::vector<uint8_t>(mesh.its.indices.size(), uint8_t(volume_extruder_id))};
}
return volume.mm_segmentation_facets.get_all_facets_strict_with_colors(volume);
}
static std::vector<ColorPolygons> slice_model_volume_with_color(const ModelVolume &model_volume, const std::vector<float> &layer_zs, const PrintObject &print_object)
static std::vector<ColorPolygons> slice_model_volume_with_color(const ModelVolume &model_volume,
const std::function<ModelVolumeFacetsInfo(const ModelVolume &)> &extract_facets_info,
const std::vector<float> &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<uint8_t>(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<ColorPolygons> 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<uint8_t>(TriangleStateType::NONE), static_cast<uint8_t>(volume_extruder_id));
@ -1631,9 +1641,14 @@ static std::vector<ColorPolygons> slice_model_volume_with_color(const ModelVolum
return color_polygons_per_layer;
}
std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback)
std::vector<std::vector<ExPolygons>> segmentation_by_painting(const PrintObject &print_object,
const std::function<ModelVolumeFacetsInfo(const ModelVolume &)> &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<void()> &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<Layer> layers = print_object.layers();
@ -1642,7 +1657,7 @@ std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
std::vector<std::vector<ColorLines>> 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<size_t>(0, num_layers), [&layers, &input_expolygons, &input_polygons_projection_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
@ -1674,12 +1689,12 @@ std::vector<std::vector<ExPolygons>> 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<float> layer_zs = get_print_object_layers_zs(layers);
for (const ModelVolume *mv : print_object.model_object()->volumes) {
std::vector<ColorPolygons> color_polygons_per_layer = slice_model_volume_with_color(*mv, layer_zs, print_object);
std::vector<ColorPolygons> color_polygons_per_layer = slice_model_volume_with_color(*mv, extract_facets_info, layer_zs, print_object);
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&color_polygons_per_layer, &color_polygons_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
@ -1710,7 +1725,7 @@ std::vector<std::vector<ExPolygons>> 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<std::vector<ExPolygons>> 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<size_t>(0, num_layers), [&color_polygons_lines_layers, &input_polygons_projection_lines_layers, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
@ -1766,12 +1781,12 @@ std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Projection of painted triangles - End";
std::vector<std::vector<ExPolygons>> segmented_regions(num_layers);
segmented_regions.assign(num_layers, std::vector<ExPolygons>(num_extruders + 1));
segmented_regions.assign(num_layers, std::vector<ExPolygons>(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<size_t>(0, num_layers), [&input_polygons_projection_lines_layers, &segmented_regions, &input_expolygons, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Layers segmentation in parallel - Begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&input_polygons_projection_lines_layers, &segmented_regions, &input_expolygons, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
@ -1797,7 +1812,7 @@ std::vector<std::vector<ExPolygons>> 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<std::vector<ExPolygons>> 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<std::vector<ExPolygons>> 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<std::vector<ExPolygons>> 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<std::vector<ExPolygons>> 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<float>(segmentation_max_width), scaled<float>(segmentation_interlocking_depth), throw_on_cancel_callback);
throw_on_cancel_callback();
}
std::vector<std::vector<ExPolygons>> 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<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
return segmented_regions_merged;
}
// Returns multi-material segmentation based on painting in multi-material segmentation gizmo
std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &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<std::vector<ExPolygons>> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &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 &region = print_object.printing_region(region_idx);
max_external_perimeter_width = std::max<float>(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

View File

@ -17,8 +17,10 @@
namespace Slic3r {
class PrintObject;
class ExPolygon;
class ModelVolume;
class PrintObject;
class FacetsAnnotation;
using ExPolygons = std::vector<ExPolygon>;
@ -32,11 +34,36 @@ struct ColoredLine
using ColoredLines = std::vector<ColoredLine>;
// Returns MMU segmentation based on painting in MMU segmentation gizmo
std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &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<ColoredLines> &colored_polygons);
// Returns segmentation based on painting in segmentation gizmos.
std::vector<std::vector<ExPolygons>> segmentation_by_painting(const PrintObject &print_object,
const std::function<ModelVolumeFacetsInfo(const ModelVolume &)> &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<void()> &throw_on_cancel_callback);
// Returns multi-material segmentation based on painting in multi-material segmentation gizmo
std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback);
// Returns fuzzy skin segmentation based on painting in fuzzy skin segmentation gizmo
std::vector<std::vector<ExPolygons>> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback);
} // namespace Slic3r
namespace boost::polygon {

View File

@ -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<PerimeterGeneratorLoop> 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>();
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<double>().normalized() * r).cast<coord_t>());
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<Arachne::ExtrusionJunction> 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>();
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<double>().normalized() * r).cast<coord_t>(), 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<PerimeterGeneratorLoop>;
static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator::Parameters &params, 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<float>(params.config.fuzzy_skin_thickness.value), scaled<float>(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 &params, 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<float>(params.config.fuzzy_skin_thickness.value), scaled<float>(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<Arachne::PerimeterOrder::PerimeterExtrusion *> 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;
}
}

View File

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

View File

@ -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

View File

@ -150,8 +150,6 @@ using SpanOfConstPtrs = tcb::span<const T* const>;
using LayerPtrs = std::vector<Layer*>;
using SupportLayerPtrs = std::vector<SupportLayer*>;
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<VolumeExtents> volumes;
// Sorted in the order of their source ModelVolumes, thus reflecting the order of region clipping, modifier overrides etc.
std::vector<VolumeRegion> volume_regions;
std::vector<PaintedRegion> painted_regions;
std::vector<VolumeRegion> volume_regions;
std::vector<PaintedRegion> painted_regions;
std::vector<FuzzySkinPaintedRegion> 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<unsigned int> object_extruders() const;

View File

@ -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<unsigned int> &painting_extruders,
PrintObjectRegions &print_object_regions,
const std::function<void(const PrintRegionConfig&, const PrintRegionConfig&, const t_config_option_keys&)> &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 &region : 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 &region : 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<const PrintRegion*> 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<unsigned int> &painting_extruders)
const std::vector<unsigned int> &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<PrintObjectRegions>(print_object_regions_old) : std::make_unique<PrintObjectRegions>();
@ -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<PrintObject>(const_cast<const PrintObject* const* const>(&(*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<PrintObject> print_objects, PrintObjectStep print_object_step)

View File

@ -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<typename ThrowOnCancel>
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<std::vector<ExPolygons>> segmentation = multi_material_segmentation_by_painting(print_object, throw_on_cancel);
assert(segmentation.size() == print_object.layer_count());
tbb::parallel_for(
tbb::blocked_range<size_t>(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))),
[&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range<size_t> &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<ByExtruder> by_extruder;
struct ByRegion {
ExPolygons expolygons;
bool needs_merge { false };
};
std::vector<ByRegion> by_region;
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) {
throw_on_cancel();
Layer *layer = print_object.get_layer(layer_id);
it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer->slice_z);
Layer &layer = *print_object.get_layer(int(layer_id));
it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z);
const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range;
// Gather per extruder expolygons.
by_extruder.assign(num_extruders, ByExtruder());
by_region.assign(layer->region_count(), ByRegion());
by_region.assign(layer.region_count(), ByRegion());
bool layer_split = false;
for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) {
ByExtruder &region = by_extruder[extruder_id];
@ -617,92 +618,227 @@ void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_can
layer_split = true;
}
}
if (! layer_split)
if (!layer_split)
continue;
// Split LayerRegions by by_extruder regions.
// layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID.
auto it_painted_region = layer_range.painted_regions.begin();
for (int region_id = 0; region_id < int(layer->region_count()); ++ region_id)
if (LayerRegion &layerm = *layer->get_region(region_id); ! layerm.slices().empty()) {
assert(layerm.region().print_object_region_id() == region_id);
const BoundingBox bbox = get_extents(layerm.slices().surfaces);
assert(it_painted_region < layer_range.painted_regions.end());
// Find the first it_painted_region which overrides this region.
for (; layer_range.volume_regions[it_painted_region->parent].region->print_object_region_id() < region_id; ++ it_painted_region)
assert(it_painted_region != layer_range.painted_regions.end());
assert(it_painted_region != layer_range.painted_regions.end());
assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region());
// 1-based extruder ID
bool self_trimmed = false;
int self_extruder_id = -1;
for (int extruder_id = 1; extruder_id <= int(by_extruder.size()); ++ extruder_id)
if (ByExtruder &segmented = by_extruder[extruder_id - 1]; segmented.bbox.defined && bbox.overlap(segmented.bbox)) {
// Find the target region.
for (; int(it_painted_region->extruder_id) < extruder_id; ++ it_painted_region)
assert(it_painted_region != layer_range.painted_regions.end());
assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region() && int(it_painted_region->extruder_id) == extruder_id);
//FIXME Don't trim by self, it is not reliable.
if (&layerm.region() == it_painted_region->region) {
self_extruder_id = extruder_id;
continue;
}
// Steal from this region.
int target_region_id = it_painted_region->region->print_object_region_id();
ExPolygons stolen = intersection_ex(layerm.slices().surfaces, segmented.expolygons);
if (! stolen.empty()) {
ByRegion &dst = by_region[target_region_id];
if (dst.expolygons.empty()) {
dst.expolygons = std::move(stolen);
} else {
append(dst.expolygons, std::move(stolen));
dst.needs_merge = true;
}
}
#if 0
if (&layerm.region() == it_painted_region->region)
// Slices of this LayerRegion were trimmed by a MMU region of the same PrintRegion.
self_trimmed = true;
#endif
}
if (! self_trimmed) {
// Trim slices of this LayerRegion with all the MMU regions.
Polygons mine = to_polygons(std::move(layerm.slices().surfaces));
for (auto &segmented : by_extruder)
if (&segmented - by_extruder.data() + 1 != self_extruder_id && segmented.bbox.defined && bbox.overlap(segmented.bbox)) {
mine = diff(mine, segmented.expolygons);
if (mine.empty())
break;
}
// Filter out unprintable polygons produced by subtraction multi-material painted regions from layerm.region().
// ExPolygon returned from multi-material segmentation does not precisely match ExPolygons in layerm.region()
// (because of preprocessing of the input regions in multi-material segmentation). Therefore, subtraction from
// layerm.region() could produce a huge number of small unprintable regions for the model's base extruder.
// This could, on some models, produce bulges with the model's base color (#7109).
if (! mine.empty())
mine = opening(union_ex(mine), float(scale_(5 * EPSILON)), float(scale_(5 * EPSILON)));
if (! mine.empty()) {
ByRegion &dst = by_region[layerm.region().print_object_region_id()];
if (dst.expolygons.empty()) {
dst.expolygons = union_ex(mine);
} else {
append(dst.expolygons, union_ex(mine));
dst.needs_merge = true;
}
auto it_painted_region_begin = layer_range.painted_regions.cbegin();
for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) {
if (it_painted_region_begin == layer_range.painted_regions.cend())
continue;
const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx);
const PrintRegion &parent_print_region = parent_layer_region.region();
assert(parent_print_region.print_object_region_id() == parent_layer_region_idx);
if (parent_layer_region.slices().empty())
continue;
// Find the first PaintedRegion, which overrides the parent PrintRegion.
auto it_first_painted_region = std::find_if(it_painted_region_begin, layer_range.painted_regions.cend(), [&layer_range, &parent_print_region](const auto &painted_region) {
return layer_range.volume_regions[painted_region.parent].region->print_object_region_id() == parent_print_region.print_object_region_id();
});
if (it_first_painted_region == layer_range.painted_regions.cend())
continue; // This LayerRegion isn't overrides by any PaintedRegion.
assert(&parent_print_region == layer_range.volume_regions[it_first_painted_region->parent].region);
// Find the first PaintedRegion with different parent PrintRegion.
auto it_last_painted_region = std::find_if(it_first_painted_region, layer_range.painted_regions.cend(), [&it_first_painted_region](const auto &painted_region) {
return painted_region.parent != it_first_painted_region->parent;
});
// Update the beginning PaintedRegion iterator for the next iteration.
it_painted_region_begin = it_last_painted_region;
const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices().surfaces);
bool self_trimmed = false;
int self_extruder_id = -1; // 1-based extruder ID
for (auto it_painted_region = it_first_painted_region; it_painted_region != it_last_painted_region; ++it_painted_region) {
const int extruder_id = int(it_painted_region->extruder_id); // 1-based extruder ID
assert(extruder_id > 0 && (extruder_id - 1) < int(by_extruder.size()));
assert(layer_range.volume_regions[it_painted_region->parent].region == &parent_print_region);
const ByExtruder &segmented = by_extruder[extruder_id - 1];
if (!segmented.bbox.defined || !parent_layer_region_bbox.overlap(segmented.bbox))
continue;
// FIXME: Don't trim by self, it is not reliable.
if (it_painted_region->region == &parent_print_region) {
self_extruder_id = extruder_id;
continue;
}
// Steal from this region.
int target_region_id = it_painted_region->region->print_object_region_id();
ExPolygons stolen = intersection_ex(parent_layer_region.slices().surfaces, segmented.expolygons);
if (!stolen.empty()) {
ByRegion &dst = by_region[target_region_id];
if (dst.expolygons.empty()) {
dst.expolygons = std::move(stolen);
} else {
append(dst.expolygons, std::move(stolen));
dst.needs_merge = true;
}
}
}
if (!self_trimmed) {
// Trim slices of this LayerRegion with all the MM regions.
Polygons mine = to_polygons(parent_layer_region.slices().surfaces);
for (auto &segmented : by_extruder) {
if (&segmented - by_extruder.data() + 1 != self_extruder_id && segmented.bbox.defined && parent_layer_region_bbox.overlap(segmented.bbox)) {
mine = diff(mine, segmented.expolygons);
if (mine.empty())
break;
}
}
// Filter out unprintable polygons produced by subtraction multi-material painted regions from layerm.region().
// ExPolygon returned from multi-material segmentation does not precisely match ExPolygons in layerm.region()
// (because of preprocessing of the input regions in multi-material segmentation). Therefore, subtraction from
// layerm.region() could produce a huge number of small unprintable regions for the model's base extruder.
// This could, on some models, produce bulges with the model's base color (#7109).
if (!mine.empty()) {
mine = opening(union_ex(mine), scaled<float>(5. * EPSILON), scaled<float>(5. * EPSILON));
}
if (!mine.empty()) {
ByRegion &dst = by_region[parent_print_region.print_object_region_id()];
if (dst.expolygons.empty()) {
dst.expolygons = union_ex(mine);
} else {
append(dst.expolygons, union_ex(mine));
dst.needs_merge = true;
}
}
}
}
// Re-create Surfaces of LayerRegions.
for (size_t region_id = 0; region_id < layer->region_count(); ++ region_id) {
for (int region_id = 0; region_id < layer.region_count(); ++region_id) {
ByRegion &src = by_region[region_id];
if (src.needs_merge)
if (src.needs_merge) {
// Multiple regions were merged into one.
src.expolygons = closing_ex(src.expolygons, float(scale_(10 * EPSILON)));
layer->get_region(region_id)->m_slices.set(std::move(src.expolygons), stInternal);
src.expolygons = closing_ex(src.expolygons, scaled<float>(10. * EPSILON));
}
layer.get_region(region_id)->m_slices.set(std::move(src.expolygons), stInternal);
}
}
});
}
template<typename ThrowOnCancel>
void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel)
{
// Returns fuzzy skin segmentation based on painting in the fuzzy skin painting gizmo.
std::vector<std::vector<ExPolygons>> segmentation = fuzzy_skin_segmentation_by_painting(print_object, throw_on_cancel);
assert(segmentation.size() == print_object.layer_count());
struct ByRegion
{
ExPolygons expolygons;
bool needs_merge { false };
};
tbb::parallel_for(tbb::blocked_range<size_t>(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))), [&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range<size_t> &range) {
const auto &layer_ranges = print_object.shared_regions()->layer_ranges;
auto it_layer_range = layer_range_first(layer_ranges, print_object.get_layer(int(range.begin()))->slice_z);
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel();
Layer &layer = *print_object.get_layer(int(layer_idx));
it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z);
const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range;
assert(segmentation[layer_idx].size() == 1);
const ExPolygons &fuzzy_skin_segmentation = segmentation[layer_idx][0];
const BoundingBox fuzzy_skin_segmentation_bbox = get_extents(fuzzy_skin_segmentation);
if (fuzzy_skin_segmentation.empty())
continue;
// Split LayerRegions by painted fuzzy skin regions.
// layer_range.fuzzy_skin_painted_regions are sorted by parent PrintObject region ID.
std::vector<ByRegion> by_region(layer.region_count());
auto it_fuzzy_skin_region_begin = layer_range.fuzzy_skin_painted_regions.cbegin();
for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) {
if (it_fuzzy_skin_region_begin == layer_range.fuzzy_skin_painted_regions.cend())
continue;
const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx);
const PrintRegion &parent_print_region = parent_layer_region.region();
assert(parent_print_region.print_object_region_id() == parent_layer_region_idx);
if (parent_layer_region.slices().empty())
continue;
// Find the first FuzzySkinPaintedRegion, which overrides the parent PrintRegion.
auto it_fuzzy_skin_region = std::find_if(it_fuzzy_skin_region_begin, layer_range.fuzzy_skin_painted_regions.cend(), [&layer_range, &parent_print_region](const auto &fuzzy_skin_region) {
return fuzzy_skin_region.parent_print_object_region_id(layer_range) == parent_print_region.print_object_region_id();
});
if (it_fuzzy_skin_region == layer_range.fuzzy_skin_painted_regions.cend())
continue; // This LayerRegion isn't overrides by any FuzzySkinPaintedRegion.
assert(it_fuzzy_skin_region->parent_print_object_region(layer_range) == &parent_print_region);
// Update the beginning FuzzySkinPaintedRegion iterator for the next iteration.
it_fuzzy_skin_region_begin = std::next(it_fuzzy_skin_region);
const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices().surfaces);
Polygons layer_region_remaining_polygons = to_polygons(parent_layer_region.slices().surfaces);
// Don't trim by self, it is not reliable.
if (parent_layer_region_bbox.overlap(fuzzy_skin_segmentation_bbox) && it_fuzzy_skin_region->region != &parent_print_region) {
// Steal from this region.
const int target_region_id = it_fuzzy_skin_region->region->print_object_region_id();
ExPolygons stolen = intersection_ex(parent_layer_region.slices().surfaces, fuzzy_skin_segmentation);
if (!stolen.empty()) {
ByRegion &dst = by_region[target_region_id];
if (dst.expolygons.empty()) {
dst.expolygons = std::move(stolen);
} else {
append(dst.expolygons, std::move(stolen));
dst.needs_merge = true;
}
}
// Trim slices of this LayerRegion by the fuzzy skin region.
layer_region_remaining_polygons = diff(layer_region_remaining_polygons, fuzzy_skin_segmentation);
// Filter out unprintable polygons. Detailed explanation is inside apply_mm_segmentation.
if (!layer_region_remaining_polygons.empty()) {
layer_region_remaining_polygons = opening(union_ex(layer_region_remaining_polygons), scaled<float>(5. * EPSILON), scaled<float>(5. * EPSILON));
}
}
if (!layer_region_remaining_polygons.empty()) {
ByRegion &dst = by_region[parent_print_region.print_object_region_id()];
if (dst.expolygons.empty()) {
dst.expolygons = union_ex(layer_region_remaining_polygons);
} else {
append(dst.expolygons, union_ex(layer_region_remaining_polygons));
dst.needs_merge = true;
}
}
}
// Re-create Surfaces of LayerRegions.
for (int region_id = 0; region_id < layer.region_count(); ++region_id) {
ByRegion &src = by_region[region_id];
if (src.needs_merge) {
// Multiple regions were merged into one.
src.expolygons = closing_ex(src.expolygons, scaled<float>(10. * EPSILON));
}
layer.get_region(region_id)->m_slices.set(std::move(src.expolygons), stInternal);
}
}
}); // end of parallel_for
}
// 1) Decides Z positions of the layers,
// 2) Initializes layers and their regions
// 3) Slices the object meshes
@ -752,7 +888,7 @@ void PrintObject::slice_volumes()
m_layers.back()->upper_layer = nullptr;
m_print->throw_if_canceled();
// Is any ModelVolume MMU painted?
// Is any ModelVolume multi-material painted?
if (m_print->config().nozzle_diameter.size() > 1 && this->model_object()->is_mm_painted()) {
// If XY Size compensation is also enabled, notify the user that XY Size compensation
// would not be used because the object is multi-material painted.
@ -768,6 +904,21 @@ void PrintObject::slice_volumes()
apply_mm_segmentation(*this, [print]() { print->throw_if_canceled(); });
}
// Is any ModelVolume fuzzy skin painted?
if (this->model_object()->is_fuzzy_skin_painted()) {
// If XY Size compensation is also enabled, notify the user that XY Size compensation
// would not be used because the object has custom fuzzy skin painted.
if (m_config.xy_size_compensation.value != 0.f) {
this->active_step_add_warning(
PrintStateBase::WarningLevel::CRITICAL,
_u8L("An object has enabled XY Size compensation which will not be used because it is also fuzzy skin painted.\nXY Size "
"compensation cannot be combined with fuzzy skin painting.") +
"\n" + (_u8L("Object name")) + ": " + this->model_object()->name);
}
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - Fuzzy skin segmentation";
apply_fuzzy_skin_segmentation(*this, [print]() { print->throw_if_canceled(); });
}
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin";
{

View File

@ -37,6 +37,8 @@ enum class TriangleStateType : int8_t {
NONE = 0,
ENFORCER = 1,
BLOCKER = 2,
// For the fuzzy skin, we use just two values (NONE and FUZZY_SKIN).
FUZZY_SKIN = ENFORCER,
// Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code.
Extruder1 = ENFORCER,
Extruder2 = BLOCKER,

View File

@ -81,6 +81,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoSlaSupports.hpp
GUI/Gizmos/GLGizmoFdmSupports.cpp
GUI/Gizmos/GLGizmoFdmSupports.hpp
GUI/Gizmos/GLGizmoFuzzySkin.cpp
GUI/Gizmos/GLGizmoFuzzySkin.hpp
GUI/Gizmos/GLGizmoFlatten.cpp
GUI/Gizmos/GLGizmoFlatten.hpp
GUI/Gizmos/GLGizmoCut.cpp

View File

@ -1555,11 +1555,12 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
auto gizmo_type = gm.get_current_type();
if ( (gizmo_type == GLGizmosManager::FdmSupports
|| gizmo_type == GLGizmosManager::Seam
|| gizmo_type == GLGizmosManager::Cut)
|| gizmo_type == GLGizmosManager::Cut
|| gizmo_type == GLGizmosManager::FuzzySkin)
&& !vol->is_modifier) {
vol->force_neutral_color = true;
}
else if (gizmo_type == GLGizmosManager::MmuSegmentation)
else if (gizmo_type == GLGizmosManager::MmSegmentation)
vol->is_active = false;
else
vol->force_native_color = true;
@ -3149,8 +3150,9 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
const GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
if (keyCode == WXK_ALT && (gizmo_type == GLGizmosManager::FdmSupports ||
gizmo_type == GLGizmosManager::Seam ||
gizmo_type == GLGizmosManager::MmuSegmentation)) {
// Prevents focusing on the menu bar when ALT is pressed in painting gizmos (FdmSupports, Seam, and MmuSegmentation).
gizmo_type == GLGizmosManager::MmSegmentation ||
gizmo_type == GLGizmosManager::FuzzySkin)) {
// Prevents focusing on the menu bar when ALT is pressed in painting gizmos (FdmSupports, Seam, MmSegmentation, and FuzzySkin).
evt.Skip(false);
} else if (keyCode != WXK_TAB
&& keyCode != WXK_LEFT
@ -3359,7 +3361,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_dirty = true;
// do not return if dragging or tooltip not empty to allow for tooltip update
// also, do not return if the mouse is moving and also is inside MM gizmo to allow update seed fill selection
if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation || !evt.Moving()))
if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation || !evt.Moving()))
return;
}
@ -3540,7 +3542,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_gizmos.get_current_type() != GLGizmosManager::Seam &&
m_gizmos.get_current_type() != GLGizmosManager::Cut &&
m_gizmos.get_current_type() != GLGizmosManager::Measure &&
m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) {
m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation &&
m_gizmos.get_current_type() != GLGizmosManager::FuzzySkin) {
m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect);
m_dirty = true;
}
@ -5797,7 +5800,8 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d&
&& m_gizmos.get_current_type() != GLGizmosManager::SlaSupports
&& m_gizmos.get_current_type() != GLGizmosManager::Hollow
&& m_gizmos.get_current_type() != GLGizmosManager::Seam
&& m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation);
&& m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation
&& m_gizmos.get_current_type() != GLGizmosManager::FuzzySkin);
m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_texture);
}
@ -5950,12 +5954,13 @@ void GLCanvas3D::_render_sequential_clearance()
{
case GLGizmosManager::EType::Flatten:
case GLGizmosManager::EType::Cut:
case GLGizmosManager::EType::MmuSegmentation:
case GLGizmosManager::EType::MmSegmentation:
case GLGizmosManager::EType::Measure:
case GLGizmosManager::EType::Emboss:
case GLGizmosManager::EType::Simplify:
case GLGizmosManager::EType::FdmSupports:
case GLGizmosManager::EType::Seam: { return; }
case GLGizmosManager::EType::Seam:
case GLGizmosManager::EType::FuzzySkin: { return; }
default: { break; }
}

View File

@ -1921,13 +1921,20 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
}
break;
case InfoItemType::MmuSegmentation:
case InfoItemType::MmSegmentation:
cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, _L("Remove Multi Material painting"));
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->mm_segmentation_facets.reset();
break;
case InfoItemType::FuzzySkin:
cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, _L("Remove paint-on fuzzy skin"));
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->fuzzy_skin_facets.reset();
break;
case InfoItemType::Sinking:
Plater::TakeSnapshot(plater, _L("Shift objects to bed"));
(*m_objects)[obj_idx]->ensure_on_bed();
@ -2132,8 +2139,8 @@ void ObjectList::split()
take_snapshot(_(L("Split to Parts")));
// Before splitting volume we have to remove all custom supports, seams, and multimaterial painting.
wxGetApp().plater()->clear_before_change_mesh(obj_idx, _u8L("Custom supports, seams and multimaterial painting were "
// Before splitting volume we have to remove all custom supports, seams, fuzzy skin and multi-material painting.
wxGetApp().plater()->clear_before_change_mesh(obj_idx, _u8L("Custom supports, seams, fuzzy skin and multi-material painting were "
"removed after splitting the object."));
volume->split(nozzle_dmrs_cnt);
@ -2148,8 +2155,8 @@ void ObjectList::split()
// update printable state for new volumes on canvas3D
wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object(obj_idx);
// After removing custom supports, seams, and multimaterial painting, we have to update info about the object to remove information about
// custom supports, seams, and multimaterial painting in the right panel.
// After removing custom supports, seams, fuzzy skin, and multi-material painting, we have to update info about the object to remove information about
// custom supports, seams, fuzzy skin, and multi-material painting in the right panel.
wxGetApp().obj_list()->update_info_items(obj_idx);
}
@ -2727,11 +2734,13 @@ void ObjectList::part_selection_changed()
}
case InfoItemType::CustomSupports:
case InfoItemType::CustomSeam:
case InfoItemType::MmuSegmentation:
case InfoItemType::MmSegmentation:
case InfoItemType::FuzzySkin:
{
GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports :
info_type == InfoItemType::CustomSeam ? GLGizmosManager::EType::Seam :
GLGizmosManager::EType::MmuSegmentation;
info_type == InfoItemType::FuzzySkin ? GLGizmosManager::EType::FuzzySkin :
GLGizmosManager::EType::MmSegmentation;
if (gizmos_mgr.get_current_type() != gizmo_type)
gizmos_mgr.open_gizmo(gizmo_type);
break;
@ -2901,7 +2910,8 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio
for (InfoItemType type : {InfoItemType::CustomSupports,
InfoItemType::CustomSeam,
InfoItemType::CutConnectors,
InfoItemType::MmuSegmentation,
InfoItemType::MmSegmentation,
InfoItemType::FuzzySkin,
InfoItemType::Sinking,
InfoItemType::VariableLayerHeight}) {
wxDataViewItem item = m_objects_model->GetInfoItemByType(item_obj, type);
@ -2911,12 +2921,14 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio
switch (type) {
case InfoItemType::CustomSupports :
case InfoItemType::CustomSeam :
case InfoItemType::MmuSegmentation :
case InfoItemType::MmSegmentation :
case InfoItemType::FuzzySkin :
should_show = printer_technology() == ptFFF
&& std::any_of(model_object->volumes.begin(), model_object->volumes.end(),
[type](const ModelVolume *mv) {
return !(type == InfoItemType::CustomSupports ? mv->supported_facets.empty() :
type == InfoItemType::CustomSeam ? mv->seam_facets.empty() :
type == InfoItemType::FuzzySkin ? mv->fuzzy_skin_facets.empty() :
mv->mm_segmentation_facets.empty());
});
break;
@ -4642,7 +4654,7 @@ void ObjectList::fix_through_winsdk()
msg += "\n";
}
plater->clear_before_change_mesh(obj_idx, _u8L("Custom supports, seams and multimaterial painting were "
plater->clear_before_change_mesh(obj_idx, _u8L("Custom supports, seams, fuzzy skin and multimaterial painting were "
"removed after repairing the mesh."));
std::string res;
if (!fix_model_by_win10_sdk_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res))
@ -4984,10 +4996,5 @@ ModelObject* ObjectList::object(const int obj_idx) const
return (*m_objects)[obj_idx];
}
bool ObjectList::has_paint_on_segmentation()
{
return m_objects_model->HasInfoItem(InfoItemType::MmuSegmentation);
}
} //namespace GUI
} //namespace Slic3r

View File

@ -410,7 +410,6 @@ public:
void set_extruder_for_selected_items(const int extruder) const ;
wxDataViewItemArray reorder_volumes_and_get_selection(size_t obj_idx, std::function<bool(const ModelVolume*)> add_to_selection = nullptr);
void apply_volumes_order();
bool has_paint_on_segmentation();
bool is_editing() const { return m_is_editing_started; }

View File

@ -0,0 +1,311 @@
#include "GLGizmoFuzzySkin.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/Print.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include <GL/glew.h>
#include <algorithm>
namespace Slic3r::GUI {
void GLGizmoFuzzySkin::on_shutdown()
{
m_parent.use_slope(false);
m_parent.toggle_model_objects_visibility(true);
}
std::string GLGizmoFuzzySkin::on_get_name() const
{
return _u8L("Paint-on fuzzy skin");
}
bool GLGizmoFuzzySkin::on_init()
{
m_shortcut_key = WXK_CONTROL_H;
m_desc["clipping_of_view"] = _u8L("Clipping of view") + ": ";
m_desc["reset_direction"] = _u8L("Reset direction");
m_desc["cursor_size"] = _u8L("Brush size") + ": ";
m_desc["cursor_type"] = _u8L("Brush shape") + ": ";
m_desc["add_fuzzy_skin_caption"] = _u8L("Left mouse button") + ": ";
m_desc["add_fuzzy_skin"] = _u8L("Add fuzzy skin");
m_desc["remove_fuzzy_skin_caption"] = _u8L("Shift + Left mouse button") + ": ";
m_desc["remove_fuzzy_skin"] = _u8L("Remove fuzzy skin");
m_desc["remove_all"] = _u8L("Remove all selection");
m_desc["circle"] = _u8L("Circle");
m_desc["sphere"] = _u8L("Sphere");
m_desc["pointer"] = _u8L("Triangles");
m_desc["tool_type"] = _u8L("Tool type") + ": ";
m_desc["tool_brush"] = _u8L("Brush");
m_desc["tool_smart_fill"] = _u8L("Smart fill");
m_desc["smart_fill_angle"] = _u8L("Smart fill angle");
m_desc["split_triangles"] = _u8L("Split triangles");
return true;
}
void GLGizmoFuzzySkin::render_painter_gizmo()
{
const Selection &selection = m_parent.get_selection();
glsafe(::glEnable(GL_BLEND));
glsafe(::glEnable(GL_DEPTH_TEST));
render_triangles(selection);
m_c->object_clipper()->render_cut();
m_c->instances_hider()->render_cut();
render_cursor();
glsafe(::glDisable(GL_BLEND));
}
void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_limit)
{
if (!m_c->selection_info()->model_object())
return;
const float approx_height = m_imgui->scaled(22.f);
y = std::min(y, bottom_limit - approx_height);
ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always);
ImGuiPureWrap::begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float clipping_slider_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x,
ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
const float cursor_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
const float smart_fill_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f);
const float cursor_type_radio_circle = ImGuiPureWrap::calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_sphere = ImGuiPureWrap::calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_pointer = ImGuiPureWrap::calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f);
const float button_width = ImGuiPureWrap::calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
const float buttons_width = m_imgui->scaled(0.5f);
const float minimal_slider_width = m_imgui->scaled(4.f);
const float tool_type_radio_left = ImGuiPureWrap::calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f);
const float tool_type_radio_brush = ImGuiPureWrap::calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_smart_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f);
const float split_triangles_checkbox_width = ImGuiPureWrap::calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f);
float caption_max = 0.f;
float total_text_max = 0.f;
for (const std::string t : {"add_fuzzy_skin", "remove_fuzzy_skin"}) {
caption_max = std::max(caption_max, ImGuiPureWrap::calc_text_size(m_desc[t + "_caption"]).x);
total_text_max = std::max(total_text_max, ImGuiPureWrap::calc_text_size(m_desc[t]).x);
}
total_text_max += caption_max + m_imgui->scaled(1.f);
caption_max += m_imgui->scaled(1.f);
const float sliders_left_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left));
const float slider_icon_width = ImGuiPureWrap::get_slider_icon_size().x;
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width);
window_width = std::max(window_width, split_triangles_checkbox_width);
window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill);
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
auto draw_text_with_caption = [&caption_max](const std::string &caption, const std::string &text) {
ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_ORANGE_LIGHT, caption);
ImGui::SameLine(caption_max);
ImGuiPureWrap::text(text);
};
for (const std::string t : {"add_fuzzy_skin", "remove_fuzzy_skin"}) {
draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t));
}
ImGui::Separator();
std::string format_str = std::string("%.f") + I18N::translate_utf8("°",
"Degree sign to use in the respective slider in fuzzy skin gizmo,"
"placed after the number with no whitespace in between.");
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
ImGui::AlignTextToFramePadding();
ImGuiPureWrap::text(m_desc["tool_type"]);
float tool_type_offset = tool_type_radio_left + (window_width - tool_type_radio_left - tool_type_radio_brush - tool_type_radio_smart_fill + m_imgui->scaled(0.5f)) / 2.f;
ImGui::SameLine(tool_type_offset);
ImGui::PushItemWidth(tool_type_radio_brush);
if (ImGuiPureWrap::radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH))
m_tool_type = ToolType::BRUSH;
if (ImGui::IsItemHovered())
ImGuiPureWrap::tooltip(_u8L("Paints facets according to the chosen painting brush."), max_tooltip_width);
ImGui::SameLine(tool_type_offset + tool_type_radio_brush);
ImGui::PushItemWidth(tool_type_radio_smart_fill);
if (ImGuiPureWrap::radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL))
m_tool_type = ToolType::SMART_FILL;
if (ImGui::IsItemHovered())
ImGuiPureWrap::tooltip(_u8L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width);
ImGui::Separator();
if (m_tool_type == ToolType::BRUSH) {
ImGuiPureWrap::text(m_desc.at("cursor_type"));
ImGui::NewLine();
float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f;
ImGui::SameLine(cursor_type_offset);
ImGui::PushItemWidth(cursor_type_radio_sphere);
if (ImGuiPureWrap::radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE))
m_cursor_type = TriangleSelector::CursorType::SPHERE;
if (ImGui::IsItemHovered())
ImGuiPureWrap::tooltip(_u8L("Paints all facets inside, regardless of their orientation."), max_tooltip_width);
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere);
ImGui::PushItemWidth(cursor_type_radio_circle);
if (ImGuiPureWrap::radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE))
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
if (ImGui::IsItemHovered())
ImGuiPureWrap::tooltip(_u8L("Ignores facets facing away from the camera."), max_tooltip_width);
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle);
ImGui::PushItemWidth(cursor_type_radio_pointer);
if (ImGuiPureWrap::radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER))
m_cursor_type = TriangleSelector::CursorType::POINTER;
if (ImGui::IsItemHovered())
ImGuiPureWrap::tooltip(_u8L("Paints only one facet."), max_tooltip_width);
m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE);
ImGui::AlignTextToFramePadding();
ImGuiPureWrap::text(m_desc.at("cursor_size"));
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel"));
ImGuiPureWrap::checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
if (ImGui::IsItemHovered())
ImGuiPureWrap::tooltip(_u8L("Splits bigger facets into smaller ones while the object is painted."), max_tooltip_width);
m_imgui->disabled_end();
} else {
assert(m_tool_type == ToolType::SMART_FILL);
ImGui::AlignTextToFramePadding();
ImGuiPureWrap::text(m_desc["smart_fill_angle"] + ":");
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel")))
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
}
ImGui::Separator();
if (m_c->object_clipper()->get_position() == 0.f) {
ImGui::AlignTextToFramePadding();
ImGuiPureWrap::text(m_desc.at("clipping_of_view"));
} else {
if (ImGuiPureWrap::button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this]() { m_c->object_clipper()->set_position_by_ratio(-1., false); });
}
}
auto clp_dist = float(m_c->object_clipper()->get_position());
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, from_u8(GUI::shortkey_ctrl_prefix()) + _L("Mouse wheel")))
m_c->object_clipper()->set_position_by_ratio(clp_dist, true);
ImGui::Separator();
if (ImGuiPureWrap::button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction);
ModelObject *mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume *mv : mo->volumes)
if (mv->is_model_part()) {
++idx;
m_triangle_selectors[idx]->reset();
m_triangle_selectors[idx]->request_update_render_data();
}
update_model_object();
m_parent.set_as_dirty();
}
ImGuiPureWrap::end();
}
void GLGizmoFuzzySkin::update_model_object() const
{
bool updated = false;
ModelObject *mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume *mv : mo->volumes) {
if (!mv->is_model_part())
continue;
++idx;
updated |= mv->fuzzy_skin_facets.set(*m_triangle_selectors[idx]);
}
if (updated) {
const ModelObjectPtrs &mos = wxGetApp().model().objects;
wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin());
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
}
void GLGizmoFuzzySkin::update_from_model_object()
{
wxBusyCursor wait;
const ModelObject *mo = m_c->selection_info()->model_object();
m_triangle_selectors.clear();
int volume_id = -1;
for (const ModelVolume *mv : mo->volumes) {
if (!mv->is_model_part())
continue;
++volume_id;
// This mesh does not account for the possible Z up SLA offset.
const TriangleMesh *mesh = &mv->mesh();
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
// Reset of TriangleSelector is done inside TriangleSelectorGUI's constructor, so we don't need it to perform it again in deserialize().
m_triangle_selectors.back()->deserialize(mv->fuzzy_skin_facets.get_data(), false);
m_triangle_selectors.back()->request_update_render_data();
}
}
PainterGizmoType GLGizmoFuzzySkin::get_painter_type() const
{
return PainterGizmoType::FUZZY_SKIN;
}
wxString GLGizmoFuzzySkin::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
{
return shift_down ? _L("Remove fuzzy skin") : _L("Add fuzzy skin");
}
} // namespace Slic3r::GUI

View File

@ -0,0 +1,47 @@
#ifndef slic3r_GLGizmoFuzzySkin_hpp_
#define slic3r_GLGizmoFuzzySkin_hpp_
#include "GLGizmoPainterBase.hpp"
#include "slic3r/GUI/I18N.hpp"
namespace Slic3r::GUI {
class GLGizmoFuzzySkin : public GLGizmoPainterBase
{
public:
GLGizmoFuzzySkin(GLCanvas3D &parent, const std::string &icon_filename, unsigned int sprite_id) : GLGizmoPainterBase(parent, icon_filename, sprite_id) {}
void render_painter_gizmo() override;
protected:
void on_render_input_window(float x, float y, float bottom_limit) override;
std::string on_get_name() const override;
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
std::string get_gizmo_entering_text() const override { return _u8L("Entering Paint-on fuzzy skin"); }
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Paint-on fuzzy skin"); }
std::string get_action_snapshot_name() const override { return _u8L("Paint-on fuzzy skin editing"); }
TriangleStateType get_left_button_state_type() const override { return TriangleStateType::FUZZY_SKIN; }
TriangleStateType get_right_button_state_type() const override { return TriangleStateType::NONE; }
private:
bool on_init() override;
void update_model_object() const override;
void update_from_model_object() override;
void on_opening() override {}
void on_shutdown() override;
PainterGizmoType get_painter_type() const override;
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated, and this class constructed again, so the change takes effect.
std::map<std::string, std::string> m_desc;
};
} // namespace Slic3r::GUI
#endif // slic3r_GLGizmoFuzzySkin_hpp_

View File

@ -560,7 +560,7 @@ void GLGizmoMmuSegmentation::update_from_model_object()
PainterGizmoType GLGizmoMmuSegmentation::get_painter_type() const
{
return PainterGizmoType::MMU_SEGMENTATION;
return PainterGizmoType::MM_SEGMENTATION;
}
ColorRGBA GLGizmoMmuSegmentation::get_cursor_sphere_left_button_color() const

View File

@ -31,7 +31,8 @@ class Selection;
enum class PainterGizmoType {
FDM_SUPPORTS,
SEAM,
MMU_SEGMENTATION
MM_SEGMENTATION,
FUZZY_SKIN
};
class TriangleSelectorGUI : public TriangleSelector {

View File

@ -36,6 +36,7 @@ enum class SLAGizmoEventType : unsigned char {
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"

View File

@ -21,6 +21,7 @@
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp"
@ -113,8 +114,9 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6));
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "fdm_supports.svg", 7));
m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "seam.svg", 8));
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "mmu_segmentation.svg", 9));
m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, "measure.svg", 10));
m_gizmos.emplace_back(new GLGizmoFuzzySkin(m_parent, "fuzzy_skin_painting.svg", 9));
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "mmu_segmentation.svg", 10));
m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, "measure.svg", 11));
m_gizmos.emplace_back(new GLGizmoEmboss(m_parent));
m_gizmos.emplace_back(new GLGizmoSVG(m_parent));
m_gizmos.emplace_back(new GLGizmoSimplify(m_parent));
@ -294,12 +296,14 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
return dynamic_cast<GLGizmoFdmSupports*>(m_gizmos[FdmSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == Seam)
return dynamic_cast<GLGizmoSeam*>(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == MmuSegmentation)
return dynamic_cast<GLGizmoMmuSegmentation*>(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == MmSegmentation)
return dynamic_cast<GLGizmoMmuSegmentation*>(m_gizmos[MmSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == Measure)
return dynamic_cast<GLGizmoMeasure*>(m_gizmos[Measure].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == Cut)
return dynamic_cast<GLGizmoCut3D*>(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == FuzzySkin)
return dynamic_cast<GLGizmoFuzzySkin*>(m_gizmos[FuzzySkin].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else
return false;
}
@ -367,7 +371,7 @@ bool GLGizmosManager::on_mouse_wheel(const wxMouseEvent &evt)
{
bool processed = false;
if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) {
if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmSegmentation || m_current == FuzzySkin) {
float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
processed = true;
@ -540,7 +544,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
case 'r' :
case 'R' :
{
if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmSegmentation || m_current == FuzzySkin) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
processed = true;
break;

View File

@ -80,7 +80,8 @@ public:
SlaSupports,
FdmSupports,
Seam,
MmuSegmentation,
FuzzySkin,
MmSegmentation,
Measure,
Emboss,
Svg,

View File

@ -164,6 +164,7 @@ void KBShortcutsDialog::fill_shortcuts()
{ "C", L("Gizmo cut") },
{ "F", L("Gizmo Place face on bed") },
{ "H", L("Gizmo SLA hollow") },
{ "H", L("Gizmo FDM paint-on fuzzy skin") },
{ "L", L("Gizmo SLA support points") },
{ "L", L("Gizmo FDM paint-on supports") },
{ "P", L("Gizmo FDM paint-on seam") },

View File

@ -1722,10 +1722,11 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty
switch ((*it).first) {
case InfoItemType::CustomSupports: text += format(_L_PLURAL("%1$d object was loaded with custom supports.", "%1$d objects were loaded with custom supports.", (*it).second), (*it).second) + "\n"; break;
case InfoItemType::CustomSeam: text += format(_L_PLURAL("%1$d object was loaded with custom seam.", "%1$d objects were loaded with custom seam.", (*it).second), (*it).second) + "\n"; break;
case InfoItemType::MmuSegmentation: text += format(_L_PLURAL("%1$d object was loaded with multimaterial painting.", "%1$d objects were loaded with multimaterial painting.",(*it).second), (*it).second) + "\n"; break;
case InfoItemType::MmSegmentation: text += format(_L_PLURAL("%1$d object was loaded with multimaterial painting.", "%1$d objects were loaded with multimaterial painting.",(*it).second), (*it).second) + "\n"; break;
case InfoItemType::VariableLayerHeight: text += format(_L_PLURAL("%1$d object was loaded with variable layer height.", "%1$d objects were loaded with variable layer height.", (*it).second), (*it).second) + "\n"; break;
case InfoItemType::Sinking: text += format(_L_PLURAL("%1$d object was loaded with partial sinking.", "%1$d objects were loaded with partial sinking.", (*it).second), (*it).second) + "\n"; break;
case InfoItemType::CutConnectors: text += format(_L_PLURAL("%1$d object was loaded as a part of cut object.", "%1$d objects were loaded as parts of cut object", (*it).second), (*it).second) + "\n"; break;
case InfoItemType::FuzzySkin: text += format(_L_PLURAL("%1$d object was loaded with fuzzy skin painting.", "%1$d objects were loaded with fuzzy skin painting.", (*it).second), (*it).second) + "\n"; break;
default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break;
}
}

View File

@ -68,12 +68,13 @@ struct InfoItemAtributes {
const std::map<InfoItemType, InfoItemAtributes> INFO_ITEMS{
// info_item Type info_item Name info_item BitmapName
{ InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports_" }, },
{ InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, },
{ InfoItemType::CutConnectors, {L("Connectors"), "cut_connectors" }, },
{ InfoItemType::MmuSegmentation, {L("Multimaterial painting"), "mmu_segmentation_"}, },
{ InfoItemType::Sinking, {L("Sinking"), "sinking"}, },
{ InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers"}, },
{ InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports_" }, },
{ InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, },
{ InfoItemType::CutConnectors, {L("Connectors"), "cut_connectors" }, },
{ InfoItemType::MmSegmentation, {L("Multimaterial painting"), "mmu_segmentation_" }, },
{ InfoItemType::Sinking, {L("Sinking"), "sinking" }, },
{ InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers" }, },
{ InfoItemType::FuzzySkin, {L("Paint-on fuzzy skin"), "fuzzy_skin_painting_" }, },
};
ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent,

View File

@ -56,7 +56,8 @@ enum class InfoItemType
CustomSupports,
CustomSeam,
CutConnectors,
MmuSegmentation,
MmSegmentation,
FuzzySkin,
Sinking,
VariableLayerHeight
};

View File

@ -2438,6 +2438,7 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const
new_volume->supported_facets.assign(old_volume->supported_facets);
new_volume->seam_facets.assign(old_volume->seam_facets);
new_volume->mm_segmentation_facets.assign(old_volume->mm_segmentation_facets);
new_volume->fuzzy_skin_facets.assign(old_volume->fuzzy_skin_facets);
}
std::swap(old_model_object->volumes[volume_idx], old_model_object->volumes.back());
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
@ -6686,11 +6687,12 @@ bool Plater::set_printer_technology(PrinterTechnology printer_technology)
}
void Plater::clear_before_change_volume(ModelVolume &mv, const std::string &notification_msg) {
// When we change the geometry of the volume, we remove any custom supports/seams/multi-material painting.
if (const bool paint_removed = !mv.supported_facets.empty() || !mv.seam_facets.empty() || !mv.mm_segmentation_facets.empty(); paint_removed) {
// When we change the geometry of the volume, we remove any custom supports/seams/multi-material/fuzzy skin painting.
if (const bool paint_removed = !mv.supported_facets.empty() || !mv.seam_facets.empty() || !mv.mm_segmentation_facets.empty() || !mv.fuzzy_skin_facets.empty(); paint_removed) {
mv.supported_facets.reset();
mv.seam_facets.reset();
mv.mm_segmentation_facets.reset();
mv.fuzzy_skin_facets.reset();
get_notification_manager()->push_notification(
NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
@ -6703,14 +6705,15 @@ void Plater::clear_before_change_mesh(int obj_idx, const std::string &notificati
{
ModelObject* mo = model().objects[obj_idx];
// If there are custom supports/seams/mmu segmentation, remove them. Fixed mesh
// If there are custom supports/seams/mm segmentation/fuzzy skin, remove them. Fixed mesh
// may be different and they would make no sense.
bool paint_removed = false;
for (ModelVolume* mv : mo->volumes) {
paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mm_segmentation_facets.empty();
for (ModelVolume *mv : mo->volumes) {
paint_removed |= !mv->supported_facets.empty() || !mv->seam_facets.empty() || !mv->mm_segmentation_facets.empty() || !mv->fuzzy_skin_facets.empty();
mv->supported_facets.reset();
mv->seam_facets.reset();
mv->mm_segmentation_facets.reset();
mv->fuzzy_skin_facets.reset();
}
if (paint_removed) {
// snapshot_time is captured by copy so the lambda knows where to undo/redo to.

View File

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