mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-15 23:15:54 +08:00
Merge branch 'lh_fuzzy_skin'
This commit is contained in:
commit
4500de008b
6
resources/icons/fuzzy_skin_painting.svg
Normal file
6
resources/icons/fuzzy_skin_painting.svg
Normal 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 |
6
resources/icons/fuzzy_skin_painting_.svg
Normal file
6
resources/icons/fuzzy_skin_painting_.svg
Normal 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 |
574
src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp
Normal file
574
src/libslic3r/Algorithm/LineSegmentation/LineSegmentation.cpp
Normal file
@ -0,0 +1,574 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <limits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "clipper/clipper_z.hpp"
|
||||||
|
#include "libslic3r/Arachne/utils/ExtrusionLine.hpp"
|
||||||
|
#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp"
|
||||||
|
#include "libslic3r/ClipperZUtils.hpp"
|
||||||
|
#include "libslic3r/ExPolygon.hpp"
|
||||||
|
#include "libslic3r/PerimeterGenerator.hpp"
|
||||||
|
#include "libslic3r/Point.hpp"
|
||||||
|
#include "libslic3r/Polygon.hpp"
|
||||||
|
#include "libslic3r/Polyline.hpp"
|
||||||
|
#include "libslic3r/Print.hpp"
|
||||||
|
#include "libslic3r/libslic3r.h"
|
||||||
|
|
||||||
|
#include "LineSegmentation.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r::Algorithm::LineSegmentation {
|
||||||
|
|
||||||
|
const constexpr coord_t POINT_IS_ON_LINE_THRESHOLD_SQR = Slic3r::sqr(scaled<coord_t>(EPSILON));
|
||||||
|
|
||||||
|
struct ZAttributes
|
||||||
|
{
|
||||||
|
bool is_clip_point = false;
|
||||||
|
bool is_new_point = false;
|
||||||
|
uint32_t point_index = 0;
|
||||||
|
|
||||||
|
ZAttributes() = default;
|
||||||
|
|
||||||
|
explicit ZAttributes(const uint32_t clipper_coord) :
|
||||||
|
is_clip_point((clipper_coord >> 31) & 0x1), is_new_point((clipper_coord >> 30) & 0x1), point_index(clipper_coord & 0x3FFFFFFF) {}
|
||||||
|
|
||||||
|
explicit ZAttributes(const ClipperLib_Z::IntPoint &clipper_pt) : ZAttributes(clipper_pt.z()) {}
|
||||||
|
|
||||||
|
ZAttributes(const bool is_clip_point, const bool is_new_point, const uint32_t point_index) :
|
||||||
|
is_clip_point(is_clip_point), is_new_point(is_new_point), point_index(point_index)
|
||||||
|
{
|
||||||
|
assert(this->point_index < (1u << 30) && "point_index exceeds 30 bits!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the structure to uint32_t.
|
||||||
|
constexpr uint32_t encode() const
|
||||||
|
{
|
||||||
|
assert(this->point_index < (1u << 30) && "point_index exceeds 30 bits!");
|
||||||
|
return (this->is_clip_point << 31) | (this->is_new_point << 30) | (this->point_index & 0x3FFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the uint32_t to the structure.
|
||||||
|
static ZAttributes decode(const uint32_t clipper_coord)
|
||||||
|
{
|
||||||
|
return { bool((clipper_coord >> 31) & 0x1), bool((clipper_coord >> 30) & 0x1), clipper_coord & 0x3FFFFFFF };
|
||||||
|
}
|
||||||
|
|
||||||
|
static ZAttributes decode(const ClipperLib_Z::IntPoint &clipper_pt) { return ZAttributes::decode(clipper_pt.z()); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LineRegionRange
|
||||||
|
{
|
||||||
|
size_t begin_idx; // Index of the line on which the region begins.
|
||||||
|
double begin_t; // Scalar position on the begin_idx line in which the region begins. The value is from range <0., 1.>.
|
||||||
|
size_t end_idx; // Index of the line on which the region ends.
|
||||||
|
double end_t; // Scalar position on the end_idx line in which the region ends. The value is from range <0., 1.>.
|
||||||
|
size_t clip_idx; // Index of clipping ExPolygons to identified which ExPolygons group contains this line.
|
||||||
|
|
||||||
|
LineRegionRange(size_t begin_idx, double begin_t, size_t end_idx, double end_t, size_t clip_idx)
|
||||||
|
: begin_idx(begin_idx), begin_t(begin_t), end_idx(end_idx), end_t(end_t), clip_idx(clip_idx) {}
|
||||||
|
|
||||||
|
// Check if 'other' overlaps with this LineRegionRange.
|
||||||
|
bool is_overlap(const LineRegionRange &other) const
|
||||||
|
{
|
||||||
|
if (this->end_idx < other.begin_idx || this->begin_idx > other.end_idx) {
|
||||||
|
return false;
|
||||||
|
} else if (this->end_idx == other.begin_idx && this->end_t <= other.begin_t) {
|
||||||
|
return false;
|
||||||
|
} else if (this->begin_idx == other.end_idx && this->begin_t >= other.end_t) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if 'inner' is whole inside this LineRegionRange.
|
||||||
|
bool is_inside(const LineRegionRange &inner) const
|
||||||
|
{
|
||||||
|
if (!this->is_overlap(inner)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool starts_after = (this->begin_idx < inner.begin_idx) || (this->begin_idx == inner.begin_idx && this->begin_t <= inner.begin_t);
|
||||||
|
const bool ends_before = (this->end_idx > inner.end_idx) || (this->end_idx == inner.end_idx && this->end_t >= inner.end_t);
|
||||||
|
|
||||||
|
return starts_after && ends_before;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_zero_length() const { return this->begin_idx == this->end_idx && this->begin_t == this->end_t; }
|
||||||
|
|
||||||
|
bool operator<(const LineRegionRange &rhs) const
|
||||||
|
{
|
||||||
|
return this->begin_idx < rhs.begin_idx || (this->begin_idx == rhs.begin_idx && this->begin_t < rhs.begin_t);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using LineRegionRanges = std::vector<LineRegionRange>;
|
||||||
|
|
||||||
|
inline Point make_point(const ClipperLib_Z::IntPoint &clipper_pt) { return { clipper_pt.x(), clipper_pt.y() }; }
|
||||||
|
|
||||||
|
inline ClipperLib_Z::Paths to_clip_zpaths(const ExPolygons &clips) { return ClipperZUtils::expolygons_to_zpaths_with_same_z<false>(clips, coord_t(ZAttributes(true, false, 0).encode())); }
|
||||||
|
|
||||||
|
static ClipperLib_Z::Path subject_to_zpath(const Points &subject, const bool is_closed)
|
||||||
|
{
|
||||||
|
ZAttributes z_attributes(false, false, 0);
|
||||||
|
|
||||||
|
ClipperLib_Z::Path out;
|
||||||
|
if (!subject.empty()) {
|
||||||
|
out.reserve((subject.size() + is_closed) ? 1 : 0);
|
||||||
|
for (const Point &p : subject) {
|
||||||
|
out.emplace_back(p.x(), p.y(), z_attributes.encode());
|
||||||
|
++z_attributes.point_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_closed) {
|
||||||
|
// If it is closed, then duplicate the first point at the end to make a closed path open.
|
||||||
|
out.emplace_back(subject.front().x(), subject.front().y(), z_attributes.encode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ClipperLib_Z::Path subject_to_zpath(const Arachne::ExtrusionLine &subject)
|
||||||
|
{
|
||||||
|
// Closed Arachne::ExtrusionLine already has duplicated the last point.
|
||||||
|
ZAttributes z_attributes(false, false, 0);
|
||||||
|
|
||||||
|
ClipperLib_Z::Path out;
|
||||||
|
if (!subject.empty()) {
|
||||||
|
out.reserve(subject.size());
|
||||||
|
for (const Arachne::ExtrusionJunction &junction : subject) {
|
||||||
|
out.emplace_back(junction.p.x(), junction.p.y(), z_attributes.encode());
|
||||||
|
++z_attributes.point_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ClipperLib_Z::Path subject_to_zpath(const Polyline &subject) { return subject_to_zpath(subject.points, false); }
|
||||||
|
|
||||||
|
[[maybe_unused]] static ClipperLib_Z::Path subject_to_zpath(const Polygon &subject) { return subject_to_zpath(subject.points, true); }
|
||||||
|
|
||||||
|
struct ProjectionInfo
|
||||||
|
{
|
||||||
|
double projected_t;
|
||||||
|
double distance_sqr;
|
||||||
|
};
|
||||||
|
|
||||||
|
static ProjectionInfo project_point_on_line(const Point &line_from_pt, const Point &line_to_pt, const Point &query_pt)
|
||||||
|
{
|
||||||
|
const Vec2d line_vec = (line_to_pt - line_from_pt).template cast<double>();
|
||||||
|
const Vec2d query_vec = (query_pt - line_from_pt).template cast<double>();
|
||||||
|
const double line_length_sqr = line_vec.squaredNorm();
|
||||||
|
|
||||||
|
if (line_length_sqr <= 0.) {
|
||||||
|
return { std::numeric_limits<double>::max(), std::numeric_limits<double>::max() };
|
||||||
|
}
|
||||||
|
|
||||||
|
const double projected_t = query_vec.dot(line_vec);
|
||||||
|
const double projected_t_normalized = std::clamp(projected_t / line_length_sqr, 0., 1.);
|
||||||
|
// Projected point have to line on the line.
|
||||||
|
if (projected_t < 0. || projected_t > line_length_sqr) {
|
||||||
|
return { projected_t_normalized, std::numeric_limits<double>::max() };
|
||||||
|
}
|
||||||
|
|
||||||
|
const Vec2d projected_vec = projected_t_normalized * line_vec;
|
||||||
|
const double distance_sqr = (projected_vec - query_vec).squaredNorm();
|
||||||
|
|
||||||
|
return { projected_t_normalized, distance_sqr };
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t find_closest_line_to_point(const ClipperLib_Z::Path &subject, const ClipperLib_Z::IntPoint &query)
|
||||||
|
{
|
||||||
|
auto it_min = subject.end();
|
||||||
|
double distance_sqr_min = std::numeric_limits<double>::max();
|
||||||
|
|
||||||
|
const Point query_pt = make_point(query);
|
||||||
|
Point prev_pt = make_point(subject.front());
|
||||||
|
for (auto it_curr = std::next(subject.begin()); it_curr != subject.end(); ++it_curr) {
|
||||||
|
const Point curr_pt = make_point(*it_curr);
|
||||||
|
|
||||||
|
const double distance_sqr = project_point_on_line(prev_pt, curr_pt, query_pt).distance_sqr;
|
||||||
|
if (distance_sqr <= POINT_IS_ON_LINE_THRESHOLD_SQR) {
|
||||||
|
return int32_t(std::distance(subject.begin(), std::prev(it_curr)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distance_sqr < distance_sqr_min) {
|
||||||
|
distance_sqr_min = distance_sqr;
|
||||||
|
it_min = std::prev(it_curr);
|
||||||
|
}
|
||||||
|
|
||||||
|
prev_pt = curr_pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it_min != subject.end()) {
|
||||||
|
return int32_t(std::distance(subject.begin(), it_min));
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<LineRegionRange> create_line_region_range(ClipperLib_Z::Path &&intersection, const ClipperLib_Z::Path &subject, const size_t region_idx)
|
||||||
|
{
|
||||||
|
if (intersection.size() < 2) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto need_reverse = [&subject](const ClipperLib_Z::Path &intersection) -> bool {
|
||||||
|
for (size_t curr_idx = 1; curr_idx < intersection.size(); ++curr_idx) {
|
||||||
|
ZAttributes prev_z(intersection[curr_idx - 1]);
|
||||||
|
ZAttributes curr_z(intersection[curr_idx]);
|
||||||
|
|
||||||
|
if (!prev_z.is_clip_point && !curr_z.is_clip_point) {
|
||||||
|
if (prev_z.point_index > curr_z.point_index) {
|
||||||
|
return true;
|
||||||
|
} else if (curr_z.point_index == prev_z.point_index) {
|
||||||
|
assert(curr_z.point_index < subject.size());
|
||||||
|
const Point subject_pt = make_point(subject[curr_z.point_index]);
|
||||||
|
const Point prev_pt = make_point(intersection[curr_idx - 1]);
|
||||||
|
const Point curr_pt = make_point(intersection[curr_idx]);
|
||||||
|
|
||||||
|
const double prev_dist = (prev_pt - subject_pt).cast<double>().squaredNorm();
|
||||||
|
const double curr_dist = (curr_pt - subject_pt).cast<double>().squaredNorm();
|
||||||
|
if (prev_dist > curr_dist) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (ClipperLib_Z::IntPoint &clipper_pt : intersection) {
|
||||||
|
const ZAttributes clipper_pt_z(clipper_pt);
|
||||||
|
if (!clipper_pt_z.is_clip_point) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME @hejllukas: We could save searing for the source line in some cases using other intersection points,
|
||||||
|
// but in reality, the clip point will be inside the intersection in very rare cases.
|
||||||
|
if (int32_t subject_line_idx = find_closest_line_to_point(subject, clipper_pt); subject_line_idx != -1) {
|
||||||
|
clipper_pt.z() = coord_t(ZAttributes(false, true, subject_line_idx).encode());
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!ZAttributes(clipper_pt).is_clip_point);
|
||||||
|
if (ZAttributes(clipper_pt).is_clip_point) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that indices of source input are ordered in increasing order.
|
||||||
|
if (need_reverse(intersection)) {
|
||||||
|
std::reverse(intersection.begin(), intersection.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
ZAttributes begin_z(intersection.front());
|
||||||
|
ZAttributes end_z(intersection.back());
|
||||||
|
|
||||||
|
assert(begin_z.point_index <= subject.size() && end_z.point_index <= subject.size());
|
||||||
|
const size_t begin_idx = begin_z.point_index;
|
||||||
|
const size_t end_idx = end_z.point_index;
|
||||||
|
const double begin_t = begin_z.is_new_point ? project_point_on_line(make_point(subject[begin_idx]), make_point(subject[begin_idx + 1]), make_point(intersection.front())).projected_t : 0.;
|
||||||
|
const double end_t = end_z.is_new_point ? project_point_on_line(make_point(subject[end_idx]), make_point(subject[end_idx + 1]), make_point(intersection.back())).projected_t : 0.;
|
||||||
|
|
||||||
|
if (begin_t == std::numeric_limits<double>::max() || end_t == std::numeric_limits<double>::max()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LineRegionRange{ begin_idx, begin_t, end_idx, end_t, region_idx };
|
||||||
|
}
|
||||||
|
|
||||||
|
LineRegionRanges intersection_with_region(const ClipperLib_Z::Path &subject, const ClipperLib_Z::Paths &clips, const size_t region_config_idx)
|
||||||
|
{
|
||||||
|
ClipperLib_Z::Clipper clipper;
|
||||||
|
clipper.PreserveCollinear(true); // Especially with Arachne, we don't want to remove collinear edges.
|
||||||
|
clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top,
|
||||||
|
const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top,
|
||||||
|
ClipperLib_Z::IntPoint &new_pt) {
|
||||||
|
const ZAttributes e1bot_z(e1bot), e1top_z(e1top), e2bot_z(e2bot), e2top_z(e2top);
|
||||||
|
|
||||||
|
assert(e1bot_z.is_clip_point == e1top_z.is_clip_point);
|
||||||
|
assert(e2bot_z.is_clip_point == e2top_z.is_clip_point);
|
||||||
|
|
||||||
|
if (!e1bot_z.is_clip_point && !e1top_z.is_clip_point) {
|
||||||
|
assert(e1bot_z.point_index + 1 == e1top_z.point_index || e1bot_z.point_index == e1top_z.point_index + 1);
|
||||||
|
new_pt.z() = coord_t(ZAttributes(false, true, std::min(e1bot_z.point_index, e1top_z.point_index)).encode());
|
||||||
|
} else if (!e2bot_z.is_clip_point && !e2top_z.is_clip_point) {
|
||||||
|
assert(e2bot_z.point_index + 1 == e2top_z.point_index || e2bot_z.point_index == e2top_z.point_index + 1);
|
||||||
|
new_pt.z() = coord_t(ZAttributes(false, true, std::min(e2bot_z.point_index, e2top_z.point_index)).encode());
|
||||||
|
} else {
|
||||||
|
assert(false && "At least one of the conditions above has to be met.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
clipper.AddPath(subject, ClipperLib_Z::ptSubject, false);
|
||||||
|
clipper.AddPaths(clips, ClipperLib_Z::ptClip, true);
|
||||||
|
|
||||||
|
ClipperLib_Z::Paths intersections;
|
||||||
|
{
|
||||||
|
ClipperLib_Z::PolyTree clipped_polytree;
|
||||||
|
clipper.Execute(ClipperLib_Z::ctIntersection, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||||
|
ClipperLib_Z::PolyTreeToPaths(std::move(clipped_polytree), intersections);
|
||||||
|
}
|
||||||
|
|
||||||
|
LineRegionRanges line_region_ranges;
|
||||||
|
line_region_ranges.reserve(intersections.size());
|
||||||
|
for (ClipperLib_Z::Path &intersection : intersections) {
|
||||||
|
if (std::optional<LineRegionRange> region_range = create_line_region_range(std::move(intersection), subject, region_config_idx); region_range.has_value()) {
|
||||||
|
line_region_ranges.emplace_back(*region_range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return line_region_ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineRegionRanges create_continues_line_region_ranges(LineRegionRanges &&line_region_ranges, const size_t default_clip_idx, const size_t total_lines_cnt)
|
||||||
|
{
|
||||||
|
if (line_region_ranges.empty()) {
|
||||||
|
return line_region_ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(line_region_ranges.begin(), line_region_ranges.end());
|
||||||
|
|
||||||
|
// Resolve overlapping regions if it happens, but it should never happen.
|
||||||
|
for (size_t region_range_idx = 1; region_range_idx < line_region_ranges.size(); ++region_range_idx) {
|
||||||
|
LineRegionRange &prev_range = line_region_ranges[region_range_idx - 1];
|
||||||
|
LineRegionRange &curr_range = line_region_ranges[region_range_idx];
|
||||||
|
|
||||||
|
assert(!prev_range.is_overlap(curr_range));
|
||||||
|
if (prev_range.is_inside(curr_range)) {
|
||||||
|
// Make the previous range zero length to remove it later.
|
||||||
|
curr_range = prev_range;
|
||||||
|
prev_range.begin_idx = curr_range.begin_idx;
|
||||||
|
prev_range.begin_t = curr_range.begin_t;
|
||||||
|
prev_range.end_idx = curr_range.begin_idx;
|
||||||
|
prev_range.end_t = curr_range.begin_t;
|
||||||
|
} else if (prev_range.is_overlap(curr_range)) {
|
||||||
|
curr_range.begin_idx = prev_range.end_idx;
|
||||||
|
curr_range.begin_t = prev_range.end_t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill all gaps between regions with the default region.
|
||||||
|
LineRegionRanges line_region_ranges_out;
|
||||||
|
size_t prev_line_idx = 0.;
|
||||||
|
double prev_t = 0.;
|
||||||
|
for (const LineRegionRange &curr_line_region : line_region_ranges) {
|
||||||
|
if (curr_line_region.is_zero_length()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(prev_line_idx < curr_line_region.begin_idx || (prev_line_idx == curr_line_region.begin_idx && prev_t <= curr_line_region.begin_t));
|
||||||
|
|
||||||
|
// Fill the gap if it is necessary.
|
||||||
|
if (prev_line_idx != curr_line_region.begin_idx || prev_t != curr_line_region.begin_t) {
|
||||||
|
line_region_ranges_out.emplace_back(prev_line_idx, prev_t, curr_line_region.begin_idx, curr_line_region.begin_t, default_clip_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the current region.
|
||||||
|
line_region_ranges_out.emplace_back(curr_line_region);
|
||||||
|
prev_line_idx = curr_line_region.end_idx;
|
||||||
|
prev_t = curr_line_region.end_t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the last remaining gap if it exists.
|
||||||
|
const size_t last_line_idx = total_lines_cnt - 1;
|
||||||
|
if ((prev_line_idx == last_line_idx && prev_t == 1.) || ((prev_line_idx == total_lines_cnt && prev_t == 0.))) {
|
||||||
|
// There is no gap at the end.
|
||||||
|
return line_region_ranges_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the last remaining gap.
|
||||||
|
line_region_ranges_out.emplace_back(prev_line_idx, prev_t, last_line_idx, 1., default_clip_idx);
|
||||||
|
|
||||||
|
return line_region_ranges_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineRegionRanges subject_segmentation(const ClipperLib_Z::Path &subject, const std::vector<ExPolygons> &expolygons_clips, const size_t default_clip_idx = 0)
|
||||||
|
{
|
||||||
|
LineRegionRanges line_region_ranges;
|
||||||
|
for (const ExPolygons &expolygons_clip : expolygons_clips) {
|
||||||
|
const size_t expolygons_clip_idx = &expolygons_clip - expolygons_clips.data();
|
||||||
|
const ClipperLib_Z::Paths clips = to_clip_zpaths(expolygons_clip);
|
||||||
|
Slic3r::append(line_region_ranges, intersection_with_region(subject, clips, expolygons_clip_idx + default_clip_idx + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return create_continues_line_region_ranges(std::move(line_region_ranges), default_clip_idx, subject.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
PolylineSegment create_polyline_segment(const LineRegionRange &line_region_range, const Polyline &subject)
|
||||||
|
{
|
||||||
|
Polyline polyline_out;
|
||||||
|
if (line_region_range.begin_t == 0.) {
|
||||||
|
polyline_out.points.emplace_back(subject[line_region_range.begin_idx]);
|
||||||
|
} else {
|
||||||
|
assert(line_region_range.begin_idx <= subject.size());
|
||||||
|
Point interpolated_start_pt = lerp(subject[line_region_range.begin_idx], subject[line_region_range.begin_idx + 1], line_region_range.begin_t);
|
||||||
|
polyline_out.points.emplace_back(interpolated_start_pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t line_idx = line_region_range.begin_idx + 1; line_idx <= line_region_range.end_idx; ++line_idx) {
|
||||||
|
polyline_out.points.emplace_back(subject[line_idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line_region_range.end_t == 0.) {
|
||||||
|
polyline_out.points.emplace_back(subject[line_region_range.end_idx]);
|
||||||
|
} else if (line_region_range.end_t == 1.) {
|
||||||
|
assert(line_region_range.end_idx <= subject.size());
|
||||||
|
polyline_out.points.emplace_back(subject[line_region_range.end_idx + 1]);
|
||||||
|
} else {
|
||||||
|
assert(line_region_range.end_idx <= subject.size());
|
||||||
|
Point interpolated_end_pt = lerp(subject[line_region_range.end_idx], subject[line_region_range.end_idx + 1], line_region_range.end_t);
|
||||||
|
polyline_out.points.emplace_back(interpolated_end_pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { polyline_out, line_region_range.clip_idx };
|
||||||
|
}
|
||||||
|
|
||||||
|
PolylineSegments create_polyline_segments(const LineRegionRanges &line_region_ranges, const Polyline &subject)
|
||||||
|
{
|
||||||
|
PolylineSegments polyline_segments;
|
||||||
|
polyline_segments.reserve(line_region_ranges.size());
|
||||||
|
for (const LineRegionRange ®ion_range : line_region_ranges) {
|
||||||
|
polyline_segments.emplace_back(create_polyline_segment(region_range, subject));
|
||||||
|
}
|
||||||
|
|
||||||
|
return polyline_segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtrusionSegment create_extrusion_segment(const LineRegionRange &line_region_range, const Arachne::ExtrusionLine &subject)
|
||||||
|
{
|
||||||
|
// When we call this function, we split ExtrusionLine into at least two segments, so none of those segments are closed.
|
||||||
|
Arachne::ExtrusionLine extrusion_out(subject.inset_idx, subject.is_odd);
|
||||||
|
if (line_region_range.begin_t == 0.) {
|
||||||
|
extrusion_out.junctions.emplace_back(subject[line_region_range.begin_idx]);
|
||||||
|
} else {
|
||||||
|
assert(line_region_range.begin_idx <= subject.size());
|
||||||
|
const Arachne::ExtrusionJunction &junction_from = subject[line_region_range.begin_idx];
|
||||||
|
const Arachne::ExtrusionJunction &junction_to = subject[line_region_range.begin_idx + 1];
|
||||||
|
|
||||||
|
const Point interpolated_start_pt = lerp(junction_from.p, junction_to.p, line_region_range.begin_t);
|
||||||
|
const coord_t interpolated_start_w = lerp(junction_from.w, junction_to.w, line_region_range.begin_t);
|
||||||
|
|
||||||
|
assert(junction_from.perimeter_index == junction_to.perimeter_index);
|
||||||
|
extrusion_out.junctions.emplace_back(interpolated_start_pt, interpolated_start_w, junction_from.perimeter_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t line_idx = line_region_range.begin_idx + 1; line_idx <= line_region_range.end_idx; ++line_idx) {
|
||||||
|
extrusion_out.junctions.emplace_back(subject[line_idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line_region_range.end_t == 0.) {
|
||||||
|
extrusion_out.junctions.emplace_back(subject[line_region_range.end_idx]);
|
||||||
|
} else if (line_region_range.end_t == 1.) {
|
||||||
|
assert(line_region_range.end_idx <= subject.size());
|
||||||
|
extrusion_out.junctions.emplace_back(subject[line_region_range.end_idx + 1]);
|
||||||
|
} else {
|
||||||
|
assert(line_region_range.end_idx <= subject.size());
|
||||||
|
const Arachne::ExtrusionJunction &junction_from = subject[line_region_range.end_idx];
|
||||||
|
const Arachne::ExtrusionJunction &junction_to = subject[line_region_range.end_idx + 1];
|
||||||
|
|
||||||
|
const Point interpolated_end_pt = lerp(junction_from.p, junction_to.p, line_region_range.end_t);
|
||||||
|
const coord_t interpolated_end_w = lerp(junction_from.w, junction_to.w, line_region_range.end_t);
|
||||||
|
|
||||||
|
assert(junction_from.perimeter_index == junction_to.perimeter_index);
|
||||||
|
extrusion_out.junctions.emplace_back(interpolated_end_pt, interpolated_end_w, junction_from.perimeter_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { extrusion_out, line_region_range.clip_idx };
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtrusionSegments create_extrusion_segments(const LineRegionRanges &line_region_ranges, const Arachne::ExtrusionLine &subject)
|
||||||
|
{
|
||||||
|
ExtrusionSegments extrusion_segments;
|
||||||
|
extrusion_segments.reserve(line_region_ranges.size());
|
||||||
|
for (const LineRegionRange ®ion_range : line_region_ranges) {
|
||||||
|
extrusion_segments.emplace_back(create_extrusion_segment(region_range, subject));
|
||||||
|
}
|
||||||
|
|
||||||
|
return extrusion_segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
PolylineSegments polyline_segmentation(const Polyline &subject, const std::vector<ExPolygons> &expolygons_clips, const size_t default_clip_idx)
|
||||||
|
{
|
||||||
|
const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), expolygons_clips, default_clip_idx);
|
||||||
|
if (line_region_ranges.empty()) {
|
||||||
|
return { PolylineSegment{subject, default_clip_idx} };
|
||||||
|
} else if (line_region_ranges.size() == 1) {
|
||||||
|
return { PolylineSegment{subject, line_region_ranges.front().clip_idx} };
|
||||||
|
}
|
||||||
|
|
||||||
|
return create_polyline_segments(line_region_ranges, subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
PolylineSegments polygon_segmentation(const Polygon &subject, const std::vector<ExPolygons> &expolygons_clips, const size_t default_clip_idx)
|
||||||
|
{
|
||||||
|
return polyline_segmentation(to_polyline(subject), expolygons_clips, default_clip_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtrusionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const std::vector<ExPolygons> &expolygons_clips, const size_t default_clip_idx)
|
||||||
|
{
|
||||||
|
const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), expolygons_clips, default_clip_idx);
|
||||||
|
if (line_region_ranges.empty()) {
|
||||||
|
return { ExtrusionSegment{subject, default_clip_idx} };
|
||||||
|
} else if (line_region_ranges.size() == 1) {
|
||||||
|
return { ExtrusionSegment{subject, line_region_ranges.front().clip_idx} };
|
||||||
|
}
|
||||||
|
|
||||||
|
return create_extrusion_segments(line_region_ranges, subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::vector<ExPolygons> to_expolygons_clips(const PerimeterRegions &perimeter_regions_clips)
|
||||||
|
{
|
||||||
|
std::vector<ExPolygons> expolygons_clips;
|
||||||
|
expolygons_clips.reserve(perimeter_regions_clips.size());
|
||||||
|
for (const PerimeterRegion &perimeter_region_clip : perimeter_regions_clips) {
|
||||||
|
expolygons_clips.emplace_back(perimeter_region_clip.expolygons);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expolygons_clips;
|
||||||
|
}
|
||||||
|
|
||||||
|
PolylineRegionSegments polyline_segmentation(const Polyline &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips)
|
||||||
|
{
|
||||||
|
const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), to_expolygons_clips(perimeter_regions_clips));
|
||||||
|
if (line_region_ranges.empty()) {
|
||||||
|
return { PolylineRegionSegment{subject, base_config} };
|
||||||
|
} else if (line_region_ranges.size() == 1) {
|
||||||
|
return { PolylineRegionSegment{subject, perimeter_regions_clips[line_region_ranges.front().clip_idx - 1].region->config()} };
|
||||||
|
}
|
||||||
|
|
||||||
|
PolylineRegionSegments segments_out;
|
||||||
|
for (PolylineSegment &segment : create_polyline_segments(line_region_ranges, subject)) {
|
||||||
|
const PrintRegionConfig &config = segment.clip_idx == 0 ? base_config : perimeter_regions_clips[segment.clip_idx - 1].region->config();
|
||||||
|
segments_out.emplace_back(std::move(segment.polyline), config);
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
PolylineRegionSegments polygon_segmentation(const Polygon &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips)
|
||||||
|
{
|
||||||
|
return polyline_segmentation(to_polyline(subject), base_config, perimeter_regions_clips);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtrusionRegionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips)
|
||||||
|
{
|
||||||
|
const LineRegionRanges line_region_ranges = subject_segmentation(subject_to_zpath(subject), to_expolygons_clips(perimeter_regions_clips));
|
||||||
|
if (line_region_ranges.empty()) {
|
||||||
|
return { ExtrusionRegionSegment{subject, base_config} };
|
||||||
|
} else if (line_region_ranges.size() == 1) {
|
||||||
|
return { ExtrusionRegionSegment{subject, perimeter_regions_clips[line_region_ranges.front().clip_idx - 1].region->config()} };
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtrusionRegionSegments segments_out;
|
||||||
|
for (ExtrusionSegment &segment : create_extrusion_segments(line_region_ranges, subject)) {
|
||||||
|
const PrintRegionConfig &config = segment.clip_idx == 0 ? base_config : perimeter_regions_clips[segment.clip_idx - 1].region->config();
|
||||||
|
segments_out.emplace_back(std::move(segment.extrusion), config);
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r::Algorithm::LineSegmentation
|
@ -0,0 +1,69 @@
|
|||||||
|
#ifndef libslic3r_LineSegmentation_hpp_
|
||||||
|
#define libslic3r_LineSegmentation_hpp_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "libslic3r/Arachne/utils/ExtrusionLine.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
class ExPolygon;
|
||||||
|
class Polyline;
|
||||||
|
class Polygon;
|
||||||
|
class PrintRegionConfig;
|
||||||
|
|
||||||
|
struct PerimeterRegion;
|
||||||
|
|
||||||
|
using ExPolygons = std::vector<ExPolygon>;
|
||||||
|
using PerimeterRegions = std::vector<PerimeterRegion>;
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
namespace Slic3r::Arachne {
|
||||||
|
struct ExtrusionLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Slic3r::Algorithm::LineSegmentation {
|
||||||
|
|
||||||
|
struct PolylineSegment
|
||||||
|
{
|
||||||
|
Polyline polyline;
|
||||||
|
size_t clip_idx;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PolylineRegionSegment
|
||||||
|
{
|
||||||
|
Polyline polyline;
|
||||||
|
const PrintRegionConfig &config;
|
||||||
|
|
||||||
|
PolylineRegionSegment(const Polyline &polyline, const PrintRegionConfig &config) : polyline(polyline), config(config) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExtrusionSegment
|
||||||
|
{
|
||||||
|
Arachne::ExtrusionLine extrusion;
|
||||||
|
size_t clip_idx;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExtrusionRegionSegment
|
||||||
|
{
|
||||||
|
Arachne::ExtrusionLine extrusion;
|
||||||
|
const PrintRegionConfig &config;
|
||||||
|
|
||||||
|
ExtrusionRegionSegment(const Arachne::ExtrusionLine &extrusion, const PrintRegionConfig &config) : extrusion(extrusion), config(config) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
using PolylineSegments = std::vector<PolylineSegment>;
|
||||||
|
using ExtrusionSegments = std::vector<ExtrusionSegment>;
|
||||||
|
using PolylineRegionSegments = std::vector<PolylineRegionSegment>;
|
||||||
|
using ExtrusionRegionSegments = std::vector<ExtrusionRegionSegment>;
|
||||||
|
|
||||||
|
PolylineSegments polyline_segmentation(const Polyline &subject, const std::vector<ExPolygons> &expolygons_clips, size_t default_clip_idx = 0);
|
||||||
|
PolylineSegments polygon_segmentation(const Polygon &subject, const std::vector<ExPolygons> &expolygons_clips, size_t default_clip_idx = 0);
|
||||||
|
ExtrusionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const std::vector<ExPolygons> &expolygons_clips, size_t default_clip_idx = 0);
|
||||||
|
|
||||||
|
PolylineRegionSegments polyline_segmentation(const Polyline &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips);
|
||||||
|
PolylineRegionSegments polygon_segmentation(const Polygon &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips);
|
||||||
|
ExtrusionRegionSegments extrusion_segmentation(const Arachne::ExtrusionLine &subject, const PrintRegionConfig &base_config, const PerimeterRegions &perimeter_regions_clips);
|
||||||
|
|
||||||
|
} // namespace Slic3r::Algorithm::LineSegmentation
|
||||||
|
|
||||||
|
#endif // libslic3r_LineSegmentation_hpp_
|
@ -32,9 +32,6 @@ struct PerimeterExtrusion
|
|||||||
size_t depth = std::numeric_limits<size_t>::max();
|
size_t depth = std::numeric_limits<size_t>::max();
|
||||||
PerimeterExtrusion *nearest_external_perimeter = nullptr;
|
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.
|
// Returns if ExtrusionLine is a contour or a hole.
|
||||||
bool is_contour() const { return extrusion.is_contour(); }
|
bool is_contour() const { return extrusion.is_contour(); }
|
||||||
|
|
||||||
|
@ -55,7 +55,8 @@ inline const Point& make_point(const ExtrusionJunction& ej)
|
|||||||
return ej.p;
|
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
|
#endif // UTILS_EXTRUSION_JUNCTION_H
|
||||||
|
@ -34,6 +34,8 @@ set(SLIC3R_SOURCES
|
|||||||
AABBTreeLines.hpp
|
AABBTreeLines.hpp
|
||||||
AABBMesh.hpp
|
AABBMesh.hpp
|
||||||
AABBMesh.cpp
|
AABBMesh.cpp
|
||||||
|
Algorithm/LineSegmentation/LineSegmentation.cpp
|
||||||
|
Algorithm/LineSegmentation/LineSegmentation.hpp
|
||||||
Algorithm/PathSorting.hpp
|
Algorithm/PathSorting.hpp
|
||||||
Algorithm/RegionExpansion.hpp
|
Algorithm/RegionExpansion.hpp
|
||||||
Algorithm/RegionExpansion.cpp
|
Algorithm/RegionExpansion.cpp
|
||||||
@ -87,6 +89,8 @@ set(SLIC3R_SOURCES
|
|||||||
ExtrusionSimulator.cpp
|
ExtrusionSimulator.cpp
|
||||||
ExtrusionSimulator.hpp
|
ExtrusionSimulator.hpp
|
||||||
FileParserError.hpp
|
FileParserError.hpp
|
||||||
|
Feature/FuzzySkin/FuzzySkin.cpp
|
||||||
|
Feature/FuzzySkin/FuzzySkin.hpp
|
||||||
Fill/Fill.cpp
|
Fill/Fill.cpp
|
||||||
Fill/Fill3DHoneycomb.cpp
|
Fill/Fill3DHoneycomb.cpp
|
||||||
Fill/Fill3DHoneycomb.hpp
|
Fill/Fill3DHoneycomb.hpp
|
||||||
|
@ -71,6 +71,24 @@ inline ZPaths expolygons_to_zpaths(const ExPolygons &src, coord_t &base_idx)
|
|||||||
return out;
|
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.
|
// Convert a single path to path with a given Z coordinate.
|
||||||
// If Open, then duplicate the first point at the end.
|
// If Open, then duplicate the first point at the end.
|
||||||
template<bool Open = false>
|
template<bool Open = false>
|
||||||
|
230
src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp
Normal file
230
src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp
Normal 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
|
26
src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp
Normal file
26
src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp
Normal 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_
|
@ -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_SUPPORTS_ATTR = "slic3rpe:custom_supports";
|
||||||
static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam";
|
static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam";
|
||||||
static constexpr const char* MM_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation";
|
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* KEY_ATTR = "key";
|
||||||
static constexpr const char* VALUE_ATTR = "value";
|
static constexpr const char* VALUE_ATTR = "value";
|
||||||
@ -374,6 +375,7 @@ namespace Slic3r {
|
|||||||
std::vector<std::string> custom_supports;
|
std::vector<std::string> custom_supports;
|
||||||
std::vector<std::string> custom_seam;
|
std::vector<std::string> custom_seam;
|
||||||
std::vector<std::string> mm_segmentation;
|
std::vector<std::string> mm_segmentation;
|
||||||
|
std::vector<std::string> fuzzy_skin;
|
||||||
|
|
||||||
bool empty() { return vertices.empty() || triangles.empty(); }
|
bool empty() { return vertices.empty() || triangles.empty(); }
|
||||||
|
|
||||||
@ -383,6 +385,7 @@ namespace Slic3r {
|
|||||||
custom_supports.clear();
|
custom_supports.clear();
|
||||||
custom_seam.clear();
|
custom_seam.clear();
|
||||||
mm_segmentation.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_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.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,
|
// 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
|
// 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)
|
if (has_transform)
|
||||||
volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object);
|
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->supported_facets.reserve(triangles_count);
|
||||||
volume->seam_facets.reserve(triangles_count);
|
volume->seam_facets.reserve(triangles_count);
|
||||||
volume->mm_segmentation_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) {
|
for (size_t i=0; i<triangles_count; ++i) {
|
||||||
size_t index = volume_data.first_triangle_id + i;
|
size_t index = volume_data.first_triangle_id + i;
|
||||||
assert(index < geometry.custom_supports.size());
|
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->supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
|
||||||
volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[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->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->supported_facets.shrink_to_fit();
|
||||||
volume->seam_facets.shrink_to_fit();
|
volume->seam_facets.shrink_to_fit();
|
||||||
volume->mm_segmentation_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())
|
if (auto &es = volume_data.shape_configuration; es.has_value())
|
||||||
volume->emboss_shape = std::move(es);
|
volume->emboss_shape = std::move(es);
|
||||||
@ -3278,6 +3285,15 @@ namespace Slic3r {
|
|||||||
output_buffer += "\"";
|
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";
|
output_buffer += "/>\n";
|
||||||
|
|
||||||
if (! flush())
|
if (! flush())
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
#include "libslic3r/ExtrusionEntity.hpp"
|
#include "libslic3r/ExtrusionEntity.hpp"
|
||||||
#include "libslic3r/ExtrusionEntityCollection.hpp"
|
#include "libslic3r/ExtrusionEntityCollection.hpp"
|
||||||
#include "libslic3r/LayerRegion.hpp"
|
#include "libslic3r/LayerRegion.hpp"
|
||||||
|
#include "libslic3r/PerimeterGenerator.hpp"
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
#include "libslic3r/Surface.hpp"
|
#include "libslic3r/Surface.hpp"
|
||||||
#include "libslic3r/SurfaceCollection.hpp"
|
#include "libslic3r/SurfaceCollection.hpp"
|
||||||
@ -632,6 +633,36 @@ ExPolygons Layer::merged(float offset_scaled) const
|
|||||||
return out;
|
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.
|
// 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 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.
|
// 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)
|
for (LayerSlice &lslice : this->lslices_ex)
|
||||||
lslice.islands.clear();
|
lslice.islands.clear();
|
||||||
|
|
||||||
for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm)
|
for (auto it_curr_region = m_regions.cbegin(); it_curr_region != m_regions.cend(); ++it_curr_region) {
|
||||||
if (size_t region_id = layerm - m_regions.begin(); ! done[region_id]) {
|
const size_t curr_region_id = std::distance(m_regions.cbegin(), it_curr_region);
|
||||||
layer_region_reset_perimeters(**layerm);
|
if (done[curr_region_id]) {
|
||||||
if (! (*layerm)->slices().empty()) {
|
continue;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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";
|
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +89,8 @@ void LayerRegion::slices_to_fill_surfaces_clipped()
|
|||||||
void LayerRegion::make_perimeters(
|
void LayerRegion::make_perimeters(
|
||||||
// Input slices for which the perimeters, gap fills and fill expolygons are to be generated.
|
// Input slices for which the perimeters, gap fills and fill expolygons are to be generated.
|
||||||
const SurfaceCollection &slices,
|
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
|
// Ranges of perimeter extrusions and gap fill extrusions per suface, referencing
|
||||||
// newly created extrusions stored at this LayerRegion.
|
// newly created extrusions stored at this LayerRegion.
|
||||||
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
|
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
|
||||||
@ -123,6 +125,7 @@ void LayerRegion::make_perimeters(
|
|||||||
region_config,
|
region_config,
|
||||||
this->layer()->object()->config(),
|
this->layer()->object()->config(),
|
||||||
print_config,
|
print_config,
|
||||||
|
perimeter_regions,
|
||||||
spiral_vase
|
spiral_vase
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -29,6 +29,9 @@ class PrintObject;
|
|||||||
using LayerPtrs = std::vector<Layer*>;
|
using LayerPtrs = std::vector<Layer*>;
|
||||||
class PrintRegion;
|
class PrintRegion;
|
||||||
|
|
||||||
|
struct PerimeterRegion;
|
||||||
|
using PerimeterRegions = std::vector<PerimeterRegion>;
|
||||||
|
|
||||||
// Range of indices, providing support for range based loops.
|
// Range of indices, providing support for range based loops.
|
||||||
template<typename T>
|
template<typename T>
|
||||||
class IndexRange
|
class IndexRange
|
||||||
@ -119,6 +122,8 @@ public:
|
|||||||
void make_perimeters(
|
void make_perimeters(
|
||||||
// Input slices for which the perimeters, gap fills and fill expolygons are to be generated.
|
// Input slices for which the perimeters, gap fills and fill expolygons are to be generated.
|
||||||
const SurfaceCollection &slices,
|
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
|
// Ranges of perimeter extrusions and gap fill extrusions per suface, referencing
|
||||||
// newly created extrusions stored at this LayerRegion.
|
// newly created extrusions stored at this LayerRegion.
|
||||||
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
|
std::vector<std::pair<ExtrusionRange, ExtrusionRange>> &perimeter_and_gapfill_ranges,
|
||||||
@ -152,9 +157,10 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// Modifying m_slices
|
// Modifying m_slices
|
||||||
friend std::string fix_slicing_errors(LayerPtrs&, const std::function<void()>&);
|
|
||||||
template<typename ThrowOnCancel>
|
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;
|
Layer *m_layer;
|
||||||
const PrintRegion *m_region;
|
const PrintRegion *m_region;
|
||||||
|
@ -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(); });
|
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()
|
ModelObject::~ModelObject()
|
||||||
{
|
{
|
||||||
this->clear_volumes();
|
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(); });
|
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
|
bool ModelObject::is_text() const
|
||||||
{
|
{
|
||||||
return this->volumes.size() == 1 && this->volumes[0]->is_text();
|
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->supported_facets.assign(volume->supported_facets);
|
||||||
vol->seam_facets.assign(volume->seam_facets);
|
vol->seam_facets.assign(volume->seam_facets);
|
||||||
vol->mm_segmentation_facets.assign(volume->mm_segmentation_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.
|
// 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.
|
// 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->supported_facets.reset();
|
||||||
this->seam_facets.reset();
|
this->seam_facets.reset();
|
||||||
this->mm_segmentation_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();
|
supported_facets.set_new_unique_id();
|
||||||
seam_facets.set_new_unique_id();
|
seam_facets.set_new_unique_id();
|
||||||
mm_segmentation_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)
|
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); });
|
[](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)
|
bool model_has_parameter_modifiers_in_objects(const Model &model)
|
||||||
{
|
{
|
||||||
for (const auto& model_object : model.objects)
|
for (const auto& model_object : model.objects)
|
||||||
|
@ -393,6 +393,8 @@ public:
|
|||||||
bool is_seam_painted() const;
|
bool is_seam_painted() const;
|
||||||
// Checks if any of object volume is painted using the multi-material painting gizmo.
|
// Checks if any of object volume is painted using the multi-material painting gizmo.
|
||||||
bool is_mm_painted() const;
|
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
|
// Checks if object contains just one volume and it's a text
|
||||||
bool is_text() const;
|
bool is_text() const;
|
||||||
// This object may have a varying layer height by painting or by a table.
|
// 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.
|
// List of mesh facets painted for MM segmentation.
|
||||||
FacetsAnnotation mm_segmentation_facets;
|
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
|
// Is set only when volume is Embossed Text type
|
||||||
// Contain information how to re-create volume
|
// Contain information how to re-create volume
|
||||||
std::optional<TextConfiguration> text_configuration;
|
std::optional<TextConfiguration> text_configuration;
|
||||||
@ -906,11 +911,13 @@ public:
|
|||||||
this->supported_facets.set_new_unique_id();
|
this->supported_facets.set_new_unique_id();
|
||||||
this->seam_facets.set_new_unique_id();
|
this->seam_facets.set_new_unique_id();
|
||||||
this->mm_segmentation_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_fdm_support_painted() const { return !this->supported_facets.empty(); }
|
||||||
bool is_seam_painted() const { return !this->seam_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_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.
|
// Returns 0-based indices of extruders painted by multi-material painting gizmo.
|
||||||
std::vector<size_t> get_extruders_from_multi_material_painting() const;
|
std::vector<size_t> get_extruders_from_multi_material_painting() const;
|
||||||
@ -958,10 +965,12 @@ private:
|
|||||||
assert(this->supported_facets.id().valid());
|
assert(this->supported_facets.id().valid());
|
||||||
assert(this->seam_facets.id().valid());
|
assert(this->seam_facets.id().valid());
|
||||||
assert(this->mm_segmentation_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->config.id());
|
||||||
assert(this->id() != this->supported_facets.id());
|
assert(this->id() != this->supported_facets.id());
|
||||||
assert(this->id() != this->seam_facets.id());
|
assert(this->id() != this->seam_facets.id());
|
||||||
assert(this->id() != this->mm_segmentation_facets.id());
|
assert(this->id() != this->mm_segmentation_facets.id());
|
||||||
|
assert(this->id() != this->fuzzy_skin_facets.id());
|
||||||
return true;
|
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),
|
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),
|
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),
|
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->id().valid());
|
||||||
assert(this->config.id().valid());
|
assert(this->config.id().valid());
|
||||||
assert(this->supported_facets.id().valid());
|
assert(this->supported_facets.id().valid());
|
||||||
assert(this->seam_facets.id().valid());
|
assert(this->seam_facets.id().valid());
|
||||||
assert(this->mm_segmentation_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->config.id());
|
||||||
assert(this->id() != this->supported_facets.id());
|
assert(this->id() != this->supported_facets.id());
|
||||||
assert(this->id() != this->seam_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->supported_facets.id() == other.supported_facets.id());
|
||||||
assert(this->seam_facets.id() == other.seam_facets.id());
|
assert(this->seam_facets.id() == other.seam_facets.id());
|
||||||
assert(this->mm_segmentation_facets.id() == other.mm_segmentation_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());
|
this->set_material_id(other.material_id());
|
||||||
}
|
}
|
||||||
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
|
// 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->supported_facets.id().valid());
|
||||||
assert(this->seam_facets.id().valid());
|
assert(this->seam_facets.id().valid());
|
||||||
assert(this->mm_segmentation_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->config.id());
|
||||||
assert(this->id() != this->supported_facets.id());
|
assert(this->id() != this->supported_facets.id());
|
||||||
assert(this->id() != this->seam_facets.id());
|
assert(this->id() != this->seam_facets.id());
|
||||||
assert(this->id() != this->mm_segmentation_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->id() != other.id());
|
||||||
assert(this->config.id() == other.config.id());
|
assert(this->config.id() == other.config.id());
|
||||||
this->set_material_id(other.material_id());
|
this->set_material_id(other.material_id());
|
||||||
@ -1031,10 +1044,12 @@ private:
|
|||||||
assert(this->supported_facets.id() != other.supported_facets.id());
|
assert(this->supported_facets.id() != other.supported_facets.id());
|
||||||
assert(this->seam_facets.id() != other.seam_facets.id());
|
assert(this->seam_facets.id() != other.seam_facets.id());
|
||||||
assert(this->mm_segmentation_facets.id() != other.mm_segmentation_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->id() != this->config.id());
|
||||||
assert(this->supported_facets.empty());
|
assert(this->supported_facets.empty());
|
||||||
assert(this->seam_facets.empty());
|
assert(this->seam_facets.empty());
|
||||||
assert(this->mm_segmentation_facets.empty());
|
assert(this->mm_segmentation_facets.empty());
|
||||||
|
assert(this->fuzzy_skin_facets.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelVolume& operator=(ModelVolume &rhs) = delete;
|
ModelVolume& operator=(ModelVolume &rhs) = delete;
|
||||||
@ -1042,12 +1057,13 @@ private:
|
|||||||
friend class cereal::access;
|
friend class cereal::access;
|
||||||
friend class UndoRedo::StackImpl;
|
friend class UndoRedo::StackImpl;
|
||||||
// Used for deserialization, therefore no IDs are allocated.
|
// 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->id().invalid());
|
||||||
assert(this->config.id().invalid());
|
assert(this->config.id().invalid());
|
||||||
assert(this->supported_facets.id().invalid());
|
assert(this->supported_facets.id().invalid());
|
||||||
assert(this->seam_facets.id().invalid());
|
assert(this->seam_facets.id().invalid());
|
||||||
assert(this->mm_segmentation_facets.id().invalid());
|
assert(this->mm_segmentation_facets.id().invalid());
|
||||||
|
assert(this->fuzzy_skin_facets.id().invalid());
|
||||||
}
|
}
|
||||||
template<class Archive> void load(Archive &ar) {
|
template<class Archive> void load(Archive &ar) {
|
||||||
bool has_convex_hull;
|
bool has_convex_hull;
|
||||||
@ -1055,6 +1071,7 @@ private:
|
|||||||
cereal::load_by_value(ar, supported_facets);
|
cereal::load_by_value(ar, supported_facets);
|
||||||
cereal::load_by_value(ar, seam_facets);
|
cereal::load_by_value(ar, seam_facets);
|
||||||
cereal::load_by_value(ar, mm_segmentation_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_by_value(ar, config);
|
||||||
cereal::load(ar, text_configuration);
|
cereal::load(ar, text_configuration);
|
||||||
cereal::load(ar, emboss_shape);
|
cereal::load(ar, emboss_shape);
|
||||||
@ -1073,6 +1090,7 @@ private:
|
|||||||
cereal::save_by_value(ar, supported_facets);
|
cereal::save_by_value(ar, supported_facets);
|
||||||
cereal::save_by_value(ar, seam_facets);
|
cereal::save_by_value(ar, seam_facets);
|
||||||
cereal::save_by_value(ar, mm_segmentation_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_by_value(ar, config);
|
||||||
cereal::save(ar, text_configuration);
|
cereal::save(ar, text_configuration);
|
||||||
cereal::save(ar, emboss_shape);
|
cereal::save(ar, emboss_shape);
|
||||||
@ -1337,6 +1355,8 @@ public:
|
|||||||
bool is_seam_painted() const;
|
bool is_seam_painted() const;
|
||||||
// Checks if any of objects is painted using the multi-material painting gizmo.
|
// Checks if any of objects is painted using the multi-material painting gizmo.
|
||||||
bool is_mm_painted() const;
|
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:
|
private:
|
||||||
explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }
|
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.
|
// The function assumes that volumes list is synchronized.
|
||||||
extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new);
|
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.
|
// 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.
|
// Either the model cannot be loaded, or a SLA printer has to be activated.
|
||||||
bool model_has_parameter_modifiers_in_objects(const Model& model);
|
bool model_has_parameter_modifiers_in_objects(const Model& model);
|
||||||
|
@ -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,
|
// 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.
|
// 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,
|
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 size_t layer_idx)
|
||||||
{
|
{
|
||||||
const ColoredLines colored_lines = to_lines(colored_polygons);
|
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.
|
// 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()) {
|
for (const Voronoi::VD::cell_type &cell : vd.cells()) {
|
||||||
if (cell.is_degenerate() || !cell.contains_segment())
|
if (cell.is_degenerate() || !cell.contains_segment())
|
||||||
continue;
|
continue;
|
||||||
@ -657,7 +657,7 @@ static void cut_segmented_layers(const std::vector<ExPolygons> &input_exp
|
|||||||
const float interlocking_depth,
|
const float interlocking_depth,
|
||||||
const std::function<void()> &throw_on_cancel_callback)
|
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;
|
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) {
|
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) {
|
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
|
}); // 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) {
|
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;
|
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) {
|
for (const std::vector<Polygons> &top_or_bottom_raw : top_or_bottom_raw_by_extruder) {
|
||||||
if (top_or_bottom_raw.empty())
|
if (top_or_bottom_raw.empty())
|
||||||
continue;
|
continue;
|
||||||
@ -699,15 +700,16 @@ static inline ExPolygons trim_by_top_or_bottom_layer(ExPolygons expolygons_to_tr
|
|||||||
return expolygons_to_trim;
|
return expolygons_to_trim;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns MM segmentation of top and bottom layers based on painting in MM segmentation gizmo
|
// Returns segmentation of top and bottom layers based on painting in segmentation gizmos.
|
||||||
static inline std::vector<std::vector<ExPolygons>> mm_segmentation_top_and_bottom_layers(const PrintObject &print_object,
|
static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_layers(const PrintObject &print_object,
|
||||||
const std::vector<ExPolygons> &input_expolygons,
|
const std::vector<ExPolygons> &input_expolygons,
|
||||||
const std::function<void()> &throw_on_cancel_callback)
|
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";
|
BOOST_LOG_TRIVIAL(debug) << "Print object 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 size_t num_layers = input_expolygons.size();
|
const SpanOfConstPtrs<Layer> layers = print_object.layers();
|
||||||
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.
|
// Maximum number of top / bottom layers accounts for maximum overlap of one thread group into a neighbor thread group.
|
||||||
int max_top_layers = 0;
|
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 upwards pointing painted triangles over top surfaces,
|
||||||
// project downards pointing painted triangles over bottom 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);
|
std::vector<float> zs = zs_from_layers(layers);
|
||||||
Transform3d object_trafo = print_object.trafo_centered();
|
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)
|
for (const ModelVolume *mv : print_object.model_object()->volumes)
|
||||||
if (mv->is_model_part()) {
|
if (mv->is_model_part()) {
|
||||||
const Transform3d volume_trafo = object_trafo * mv->get_matrix();
|
const Transform3d volume_trafo = object_trafo * mv->get_matrix();
|
||||||
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) {
|
||||||
const indexed_triangle_set painted = mv->mm_segmentation_facets.get_facets_strict(*mv, TriangleStateType(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) {
|
if constexpr (MM_SEGMENTATION_DEBUG_TOP_BOTTOM) {
|
||||||
its_write_obj(painted, debug_out_path("mm-painted-patch-%d.obj", extruder_idx).c_str());
|
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 {
|
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_extruders; ++extruder_idx) {
|
for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) {
|
||||||
if (raw_surfaces[extruder_idx].empty())
|
if (raw_surfaces[extruder_idx].empty())
|
||||||
continue;
|
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));
|
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.
|
// 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) {
|
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_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();
|
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"};
|
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) {
|
for (size_t layer_id = 0; layer_id < zs.size(); ++layer_id) {
|
||||||
std::vector<std::pair<Slic3r::ExPolygons, SVG::ExPolygonAttributes>> svg;
|
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 (!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()) {
|
if (ExPolygons expoly = union_ex(top_raw[extruder_idx][layer_id]); !expoly.empty()) {
|
||||||
const std::string &color = colors[extruder_idx];
|
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_bottom(num_facets_states);
|
||||||
std::vector<std::vector<ExPolygons>> triangles_by_color_top(num_extruders);
|
std::vector<std::vector<ExPolygons>> triangles_by_color_top(num_facets_states);
|
||||||
triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
|
triangles_by_color_bottom.assign(num_facets_states, std::vector<ExPolygons>(num_layers * 2));
|
||||||
triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
|
triangles_by_color_top.assign(num_facets_states, std::vector<ExPolygons>(num_layers * 2));
|
||||||
|
|
||||||
struct LayerColorStat {
|
struct LayerColorStat {
|
||||||
// Number of regions for a queried color.
|
// 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;
|
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) {
|
&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 group_idx = range.begin() / granularity;
|
||||||
size_t layer_idx_offset = (group_idx & 1) * num_layers;
|
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 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();
|
throw_on_cancel_callback();
|
||||||
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
|
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
|
||||||
if (std::vector<Polygons> &top = top_raw[color_idx]; !top.empty() && !top[layer_idx].empty()) {
|
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);
|
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_facets_states);
|
||||||
triangles_by_color_merged.assign(num_extruders, std::vector<ExPolygons>(num_layers));
|
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) {
|
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) {
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||||
throw_on_cancel_callback();
|
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]);
|
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;
|
return triangles_by_color_merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<std::vector<ExPolygons>> merge_segmented_layers(
|
static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::vector<std::vector<ExPolygons>> &segmented_regions,
|
||||||
const std::vector<std::vector<ExPolygons>> &segmented_regions,
|
std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers,
|
||||||
std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers,
|
const size_t num_facets_states,
|
||||||
const size_t num_extruders,
|
const std::function<void()> &throw_on_cancel_callback)
|
||||||
const std::function<void()> &throw_on_cancel_callback)
|
|
||||||
{
|
{
|
||||||
const size_t num_layers = segmented_regions.size();
|
const size_t num_layers = segmented_regions.size();
|
||||||
std::vector<std::vector<ExPolygons>> segmented_regions_merged(num_layers);
|
std::vector<std::vector<ExPolygons>> segmented_regions_merged(num_layers);
|
||||||
segmented_regions_merged.assign(num_layers, std::vector<ExPolygons>(num_extruders));
|
segmented_regions_merged.assign(num_layers, std::vector<ExPolygons>(num_facets_states - 1));
|
||||||
assert(num_extruders + 1 == top_and_bottom_layers.size());
|
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";
|
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_extruders, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
|
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) {
|
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
|
// 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();
|
throw_on_cancel_callback();
|
||||||
if (!segmented_regions[layer_idx][extruder_id].empty()) {
|
if (!segmented_regions[layer_idx][extruder_id].empty()) {
|
||||||
ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id];
|
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_layers.empty()) {
|
||||||
if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty())
|
for (const std::vector<ExPolygons> &top_and_bottom_by_extruder : top_and_bottom_layers) {
|
||||||
segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]);
|
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);
|
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();
|
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]);
|
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
|
}); // 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;
|
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) {
|
static std::vector<ColorPolygons> slice_model_volume_with_color(const ModelVolume &model_volume,
|
||||||
if (const int volume_extruder_id = volume.extruder_id(); !volume.is_mm_painted() && volume_extruder_id >= 0) {
|
const std::function<ModelVolumeFacetsInfo(const ModelVolume &)> &extract_facets_info,
|
||||||
const TriangleMesh &mesh = volume.mesh();
|
const std::vector<float> &layer_zs,
|
||||||
return {mesh.its.indices, mesh.its.vertices, std::vector<uint8_t>(mesh.its.indices.size(), uint8_t(volume_extruder_id))};
|
const PrintObject &print_object)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
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 Transform3d trafo = print_object.trafo_centered() * model_volume.get_matrix();
|
||||||
const MeshSlicingParams slicing_params{trafo};
|
const MeshSlicingParams slicing_params{trafo};
|
||||||
|
|
||||||
std::vector<ColorPolygons> color_polygons_per_layer = slice_mesh(mesh_with_color, layer_zs, slicing_params);
|
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.
|
// 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 (ColorPolygons &color_polygons : color_polygons_per_layer) {
|
||||||
for (ColorPolygon &color_polygon : color_polygons) {
|
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));
|
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;
|
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 size_t num_layers = print_object.layers().size();
|
||||||
const SpanOfConstPtrs<Layer> layers = print_object.layers();
|
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);
|
std::vector<std::vector<ColorLines>> color_polygons_lines_layers(num_layers);
|
||||||
|
|
||||||
// Merge all regions and remove small holes
|
// 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) {
|
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) {
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||||
throw_on_cancel_callback();
|
throw_on_cancel_callback();
|
||||||
@ -1674,12 +1689,12 @@ std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}); // end of parallel_for
|
}); // 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);
|
const std::vector<float> layer_zs = get_print_object_layers_zs(layers);
|
||||||
for (const ModelVolume *mv : print_object.model_object()->volumes) {
|
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) {
|
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) {
|
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
|
}); // 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) {
|
if constexpr (MM_SEGMENTATION_DEBUG_FILTERED_COLOR_LINES) {
|
||||||
for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) {
|
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).
|
// 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) {
|
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) {
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||||
throw_on_cancel_callback();
|
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";
|
BOOST_LOG_TRIVIAL(debug) << "MM segmentation - Projection of painted triangles - End";
|
||||||
|
|
||||||
std::vector<std::vector<ExPolygons>> segmented_regions(num_layers);
|
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
|
// 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.
|
// 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";
|
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_extruders, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
|
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) {
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||||
throw_on_cancel_callback();
|
throw_on_cancel_callback();
|
||||||
|
|
||||||
@ -1797,7 +1812,7 @@ std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
|
|||||||
assert(!colored_polygons.front().empty());
|
assert(!colored_polygons.front().empty());
|
||||||
segmented_regions[layer_idx][size_t(colored_polygons.front().front().color)] = input_expolygons[layer_idx];
|
segmented_regions[layer_idx][size_t(colored_polygons.front().front().color)] = input_expolygons[layer_idx];
|
||||||
} else {
|
} 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) {
|
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
|
}); // 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();
|
throw_on_cancel_callback();
|
||||||
|
|
||||||
// The first index is extruder number (includes default extruder), and the second one is layer number
|
// 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);
|
std::vector<std::vector<ExPolygons>> top_and_bottom_layers;
|
||||||
throw_on_cancel_callback();
|
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);
|
||||||
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);
|
|
||||||
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();
|
throw_on_cancel_callback();
|
||||||
|
|
||||||
if constexpr (MM_SEGMENTATION_DEBUG_REGIONS) {
|
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;
|
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 ®ion = 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
|
} // namespace Slic3r
|
@ -17,8 +17,10 @@
|
|||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class PrintObject;
|
|
||||||
class ExPolygon;
|
class ExPolygon;
|
||||||
|
class ModelVolume;
|
||||||
|
class PrintObject;
|
||||||
|
class FacetsAnnotation;
|
||||||
|
|
||||||
using ExPolygons = std::vector<ExPolygon>;
|
using ExPolygons = std::vector<ExPolygon>;
|
||||||
|
|
||||||
@ -32,11 +34,36 @@ struct ColoredLine
|
|||||||
|
|
||||||
using ColoredLines = std::vector<ColoredLine>;
|
using ColoredLines = std::vector<ColoredLine>;
|
||||||
|
|
||||||
// Returns MMU segmentation based on painting in MMU segmentation gizmo
|
enum class IncludeTopAndBottomLayers {
|
||||||
std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback);
|
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);
|
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 Slic3r
|
||||||
|
|
||||||
namespace boost::polygon {
|
namespace boost::polygon {
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
#include "ExPolygon.hpp"
|
#include "ExPolygon.hpp"
|
||||||
#include "ExtrusionEntity.hpp"
|
#include "ExtrusionEntity.hpp"
|
||||||
#include "ExtrusionEntityCollection.hpp"
|
#include "ExtrusionEntityCollection.hpp"
|
||||||
|
#include "Feature/FuzzySkin/FuzzySkin.hpp"
|
||||||
#include "Point.hpp"
|
#include "Point.hpp"
|
||||||
#include "Polygon.hpp"
|
#include "Polygon.hpp"
|
||||||
#include "Polyline.hpp"
|
#include "Polyline.hpp"
|
||||||
@ -41,7 +42,9 @@
|
|||||||
#include "Arachne/utils/ExtrusionJunction.hpp"
|
#include "Arachne/utils/ExtrusionJunction.hpp"
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
#include "libslic3r/Flow.hpp"
|
#include "libslic3r/Flow.hpp"
|
||||||
|
#include "libslic3r/LayerRegion.hpp"
|
||||||
#include "libslic3r/Line.hpp"
|
#include "libslic3r/Line.hpp"
|
||||||
|
#include "libslic3r/Print.hpp"
|
||||||
|
|
||||||
//#define ARACHNE_DEBUG
|
//#define ARACHNE_DEBUG
|
||||||
|
|
||||||
@ -176,13 +179,11 @@ public:
|
|||||||
bool is_contour;
|
bool is_contour;
|
||||||
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
|
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
|
||||||
unsigned short depth;
|
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).
|
// Children contour, may be both CCW and CW oriented (outer contours or holes).
|
||||||
std::vector<PerimeterGeneratorLoop> children;
|
std::vector<PerimeterGeneratorLoop> children;
|
||||||
|
|
||||||
PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool fuzzify) :
|
PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour) :
|
||||||
polygon(polygon), is_contour(is_contour), depth(depth), fuzzify(fuzzify) {}
|
polygon(polygon), is_contour(is_contour), depth(depth) {}
|
||||||
// External perimeter. It may be CCW or CW oriented (outer contour or hole contour).
|
// External perimeter. It may be CCW or CW oriented (outer contour or hole contour).
|
||||||
bool is_external() const { return this->depth == 0; }
|
bool is_external() const { return this->depth == 0; }
|
||||||
// An island, which may have holes, but it does not have another internal island.
|
// 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>;
|
using PerimeterGeneratorLoops = std::vector<PerimeterGeneratorLoop>;
|
||||||
|
|
||||||
static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls)
|
static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls)
|
||||||
{
|
{
|
||||||
|
using namespace Slic3r::Feature::FuzzySkin;
|
||||||
|
|
||||||
// loops is an arrayref of ::Loop objects
|
// loops is an arrayref of ::Loop objects
|
||||||
// turn each one into an ExtrusionLoop object
|
// turn each one into an ExtrusionLoop object
|
||||||
ExtrusionEntityCollection coll;
|
ExtrusionEntityCollection coll;
|
||||||
Polygon fuzzified;
|
|
||||||
for (const PerimeterGeneratorLoop &loop : loops) {
|
for (const PerimeterGeneratorLoop &loop : loops) {
|
||||||
bool is_external = loop.is_external();
|
bool is_external = loop.is_external();
|
||||||
|
|
||||||
@ -301,16 +222,14 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator
|
|||||||
loop_role = elrDefault;
|
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;
|
ExtrusionPaths paths;
|
||||||
const Polygon &polygon = loop.fuzzify ? fuzzified : loop.polygon;
|
if (params.config.overhangs && params.layer_id > params.object_config.raft_layers &&
|
||||||
if (loop.fuzzify) {
|
!((params.object_config.support_material || params.object_config.support_material_enforce_layers > 0) &&
|
||||||
fuzzified = loop.polygon;
|
params.object_config.support_material_contact_distance.value == 0)) {
|
||||||
fuzzy_polygon(fuzzified, scaled<float>(params.config.fuzzy_skin_thickness.value), scaled<float>(params.config.fuzzy_skin_point_dist.value));
|
// Detect overhanging/bridging perimeters.
|
||||||
}
|
|
||||||
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)) {
|
|
||||||
BoundingBox bbox(polygon.points);
|
BoundingBox bbox(polygon.points);
|
||||||
bbox.offset(SCALED_EPSILON);
|
bbox.offset(SCALED_EPSILON);
|
||||||
Polygons lower_slices_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_slices_polygons_cache, bbox);
|
Polygons lower_slices_polygons_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_slices_polygons_cache, bbox);
|
||||||
@ -336,6 +255,10 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator
|
|||||||
ExtrusionFlow{ params.mm3_per_mm_overhang, params.overhang_flow.width(), params.overhang_flow.height() }
|
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.
|
// Reapply the nearest point search for starting point.
|
||||||
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
|
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
|
||||||
chain_and_reorder_extrusion_paths(paths, &paths.front().first_point());
|
chain_and_reorder_extrusion_paths(paths, &paths.front().first_point());
|
||||||
@ -493,9 +416,11 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con
|
|||||||
|
|
||||||
static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, Arachne::PerimeterOrder::PerimeterExtrusions &pg_extrusions)
|
static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::Parameters ¶ms, const Polygons &lower_slices_polygons_cache, Arachne::PerimeterOrder::PerimeterExtrusions &pg_extrusions)
|
||||||
{
|
{
|
||||||
|
using namespace Slic3r::Feature::FuzzySkin;
|
||||||
|
|
||||||
ExtrusionEntityCollection extrusion_coll;
|
ExtrusionEntityCollection extrusion_coll;
|
||||||
for (Arachne::PerimeterOrder::PerimeterExtrusion &pg_extrusion : pg_extrusions) {
|
for (Arachne::PerimeterOrder::PerimeterExtrusion &pg_extrusion : pg_extrusions) {
|
||||||
Arachne::ExtrusionLine &extrusion = pg_extrusion.extrusion;
|
Arachne::ExtrusionLine extrusion = pg_extrusion.extrusion;
|
||||||
if (extrusion.empty())
|
if (extrusion.empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -503,8 +428,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P
|
|||||||
ExtrusionRole role_normal = is_external ? ExtrusionRole::ExternalPerimeter : ExtrusionRole::Perimeter;
|
ExtrusionRole role_normal = is_external ? ExtrusionRole::ExternalPerimeter : ExtrusionRole::Perimeter;
|
||||||
ExtrusionRole role_overhang = role_normal | ExtrusionRoleModifier::Bridge;
|
ExtrusionRole role_overhang = role_normal | ExtrusionRoleModifier::Bridge;
|
||||||
|
|
||||||
if (pg_extrusion.fuzzify)
|
// Apply fuzzy skin if it is enabled for at least some part of the ExtrusionLine.
|
||||||
fuzzy_extrusion_line(extrusion, scaled<float>(params.config.fuzzy_skin_thickness.value), scaled<float>(params.config.fuzzy_skin_point_dist.value));
|
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;
|
ExtrusionPaths paths;
|
||||||
// detect overhanging/bridging perimeters
|
// 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);
|
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())
|
if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(params, lower_slices_polygons_cache, ordered_extrusions); !extrusion_coll.empty())
|
||||||
out_loops.append(extrusion_coll);
|
out_loops.append(extrusion_coll);
|
||||||
|
|
||||||
@ -1421,20 +1306,18 @@ void PerimeterGenerator::process_classic(
|
|||||||
break;
|
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) {
|
for (const ExPolygon &expolygon : offsets) {
|
||||||
// Outer contour may overlap with an inner contour,
|
// Outer contour may overlap with an inner contour,
|
||||||
// inner contour may overlap with another inner contour,
|
// inner contour may overlap with another inner contour,
|
||||||
// outer contour may overlap with itself.
|
// outer contour may overlap with itself.
|
||||||
//FIXME evaluate the overlaps, annotate each point with an overlap depth,
|
//FIXME evaluate the overlaps, annotate each point with an overlap depth,
|
||||||
// compensate for the depth of intersection.
|
// 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()) {
|
if (! expolygon.holes.empty()) {
|
||||||
holes[i].reserve(holes[i].size() + expolygon.holes.size());
|
holes[i].reserve(holes[i].size() + expolygon.holes.size());
|
||||||
for (const Polygon &hole : expolygon.holes)
|
for (const Polygon &hole : expolygon.holes)
|
||||||
holes[i].emplace_back(hole, i, false, fuzzify_holes);
|
holes[i].emplace_back(hole, i, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1657,4 +1540,43 @@ void PerimeterGenerator::process_classic(
|
|||||||
append(out_fill_expolygons, std::move(infill_areas));
|
append(out_fill_expolygons, std::move(infill_areas));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PerimeterRegion::PerimeterRegion(const LayerRegion &layer_region) : region(&layer_region.region())
|
||||||
|
{
|
||||||
|
this->expolygons = to_expolygons(layer_region.slices().surfaces);
|
||||||
|
this->bbox = get_extents(this->expolygons);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PerimeterRegion::has_compatible_perimeter_regions(const PrintRegionConfig &config, const PrintRegionConfig &other_config)
|
||||||
|
{
|
||||||
|
return config.fuzzy_skin == other_config.fuzzy_skin &&
|
||||||
|
config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness &&
|
||||||
|
config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PerimeterRegion::merge_compatible_perimeter_regions(PerimeterRegions &perimeter_regions)
|
||||||
|
{
|
||||||
|
if (perimeter_regions.size() <= 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PerimeterRegions perimeter_regions_merged;
|
||||||
|
for (auto it_curr_region = perimeter_regions.begin(); it_curr_region != perimeter_regions.end();) {
|
||||||
|
PerimeterRegion current_merge = *it_curr_region;
|
||||||
|
auto it_next_region = std::next(it_curr_region);
|
||||||
|
for (; it_next_region != perimeter_regions.end() && has_compatible_perimeter_regions(it_next_region->region->config(), it_curr_region->region->config()); ++it_next_region) {
|
||||||
|
Slic3r::append(current_merge.expolygons, std::move(it_next_region->expolygons));
|
||||||
|
current_merge.bbox.merge(it_next_region->bbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::distance(it_curr_region, it_next_region) > 1) {
|
||||||
|
current_merge.expolygons = union_ex(current_merge.expolygons);
|
||||||
|
}
|
||||||
|
|
||||||
|
perimeter_regions_merged.emplace_back(std::move(current_merge));
|
||||||
|
it_curr_region = it_next_region;
|
||||||
|
}
|
||||||
|
|
||||||
|
perimeter_regions = perimeter_regions_merged;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,31 @@
|
|||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
class ExtrusionEntityCollection;
|
class ExtrusionEntityCollection;
|
||||||
|
class LayerRegion;
|
||||||
class Surface;
|
class Surface;
|
||||||
|
class PrintRegion;
|
||||||
struct ThickPolyline;
|
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 {
|
struct Parameters {
|
||||||
Parameters(
|
Parameters(
|
||||||
@ -39,6 +59,7 @@ struct Parameters {
|
|||||||
const PrintRegionConfig &config,
|
const PrintRegionConfig &config,
|
||||||
const PrintObjectConfig &object_config,
|
const PrintObjectConfig &object_config,
|
||||||
const PrintConfig &print_config,
|
const PrintConfig &print_config,
|
||||||
|
const PerimeterRegions &perimeter_regions,
|
||||||
const bool spiral_vase) :
|
const bool spiral_vase) :
|
||||||
layer_height(layer_height),
|
layer_height(layer_height),
|
||||||
layer_id(layer_id),
|
layer_id(layer_id),
|
||||||
@ -49,6 +70,7 @@ struct Parameters {
|
|||||||
config(config),
|
config(config),
|
||||||
object_config(object_config),
|
object_config(object_config),
|
||||||
print_config(print_config),
|
print_config(print_config),
|
||||||
|
perimeter_regions(perimeter_regions),
|
||||||
spiral_vase(spiral_vase),
|
spiral_vase(spiral_vase),
|
||||||
scaled_resolution(scaled<double>(print_config.gcode_resolution.value)),
|
scaled_resolution(scaled<double>(print_config.gcode_resolution.value)),
|
||||||
mm3_per_mm(perimeter_flow.mm3_per_mm()),
|
mm3_per_mm(perimeter_flow.mm3_per_mm()),
|
||||||
@ -67,6 +89,7 @@ struct Parameters {
|
|||||||
const PrintRegionConfig &config;
|
const PrintRegionConfig &config;
|
||||||
const PrintObjectConfig &object_config;
|
const PrintObjectConfig &object_config;
|
||||||
const PrintConfig &print_config;
|
const PrintConfig &print_config;
|
||||||
|
const PerimeterRegions &perimeter_regions;
|
||||||
|
|
||||||
// Derived parameters
|
// Derived parameters
|
||||||
bool spiral_vase;
|
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);
|
ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, float tolerance, float merge_tolerance);
|
||||||
|
|
||||||
} // namespace PerimeterGenerator
|
} // namespace Slic3r::PerimeterGenerator
|
||||||
} // namespace Slic3r
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1762,5 +1762,21 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co
|
|||||||
return final_path;
|
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
|
} // namespace Slic3r
|
||||||
|
@ -150,8 +150,6 @@ using SpanOfConstPtrs = tcb::span<const T* const>;
|
|||||||
using LayerPtrs = std::vector<Layer*>;
|
using LayerPtrs = std::vector<Layer*>;
|
||||||
using SupportLayerPtrs = std::vector<SupportLayer*>;
|
using SupportLayerPtrs = std::vector<SupportLayer*>;
|
||||||
|
|
||||||
class BoundingBoxf3; // TODO: for temporary constructor parameter
|
|
||||||
|
|
||||||
// Single instance of a PrintObject.
|
// Single instance of a PrintObject.
|
||||||
// As multiple PrintObjects may be generated for a single ModelObject (their instances differ in rotation around Z),
|
// 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.
|
// ModelObject's instancess will be distributed among these multiple PrintObjects.
|
||||||
@ -204,6 +202,22 @@ public:
|
|||||||
PrintRegion *region { nullptr };
|
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
|
// 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.
|
// possibly clipped by the layer_height_range.
|
||||||
struct LayerRangeRegions
|
struct LayerRangeRegions
|
||||||
@ -216,8 +230,9 @@ public:
|
|||||||
std::vector<VolumeExtents> volumes;
|
std::vector<VolumeExtents> volumes;
|
||||||
|
|
||||||
// Sorted in the order of their source ModelVolumes, thus reflecting the order of region clipping, modifier overrides etc.
|
// 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<VolumeRegion> volume_regions;
|
||||||
std::vector<PaintedRegion> painted_regions;
|
std::vector<PaintedRegion> painted_regions;
|
||||||
|
std::vector<FuzzySkinPaintedRegion> fuzzy_skin_painted_regions;
|
||||||
|
|
||||||
bool has_volume(const ObjectID id) const {
|
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; });
|
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(); }
|
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.
|
// 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(); }
|
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)
|
// 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;
|
std::vector<unsigned int> object_extruders() const;
|
||||||
|
@ -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);
|
mv_dst.seam_facets.assign(mv_src.seam_facets);
|
||||||
assert(mv_dst.mm_segmentation_facets.id() == mv_src.mm_segmentation_facets.id());
|
assert(mv_dst.mm_segmentation_facets.id() == mv_src.mm_segmentation_facets.id());
|
||||||
mv_dst.mm_segmentation_facets.assign(mv_src.mm_segmentation_facets);
|
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?
|
//FIXME what to do with the materials?
|
||||||
// mv_dst.m_material_id = mv_src.m_material_id;
|
// mv_dst.m_material_id = mv_src.m_material_id;
|
||||||
++ i_src;
|
++ i_src;
|
||||||
@ -681,7 +683,6 @@ bool verify_update_print_object_regions(
|
|||||||
ModelVolumePtrs model_volumes,
|
ModelVolumePtrs model_volumes,
|
||||||
const PrintRegionConfig &default_region_config,
|
const PrintRegionConfig &default_region_config,
|
||||||
size_t num_extruders,
|
size_t num_extruders,
|
||||||
const std::vector<unsigned int> &painting_extruders,
|
|
||||||
PrintObjectRegions &print_object_regions,
|
PrintObjectRegions &print_object_regions,
|
||||||
const std::function<void(const PrintRegionConfig&, const PrintRegionConfig&, const t_config_option_keys&)> &callback_invalidate)
|
const std::function<void(const PrintRegionConfig&, const PrintRegionConfig&, const t_config_option_keys&)> &callback_invalidate)
|
||||||
{
|
{
|
||||||
@ -779,6 +780,29 @@ bool verify_update_print_object_regions(
|
|||||||
print_region_ref_inc(*region.region);
|
print_region_ref_inc(*region.region);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify and / or update PrintRegions produced by fuzzy skin painting.
|
||||||
|
for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) {
|
||||||
|
for (const PrintObjectRegions::FuzzySkinPaintedRegion ®ion : layer_range.fuzzy_skin_painted_regions) {
|
||||||
|
const PrintRegion &parent_print_region = *region.parent_print_object_region(layer_range);
|
||||||
|
PrintRegionConfig cfg = parent_print_region.config();
|
||||||
|
cfg.fuzzy_skin.value = FuzzySkinType::All;
|
||||||
|
if (cfg != region.region->config()) {
|
||||||
|
// Region configuration changed.
|
||||||
|
if (print_region_ref_cnt(*region.region) == 0) {
|
||||||
|
// Region is referenced for the first time. Just change its parameters.
|
||||||
|
// Stop the background process before assigning new configuration to the regions.
|
||||||
|
t_config_option_keys diff = region.region->config().diff(cfg);
|
||||||
|
callback_invalidate(region.region->config(), cfg, diff);
|
||||||
|
region.region->config_apply_only(cfg, diff, false);
|
||||||
|
} else {
|
||||||
|
// Region is referenced multiple times, thus the region is being split. We need to reslice.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print_region_ref_inc(*region.region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Lastly verify, whether some regions were not merged.
|
// Lastly verify, whether some regions were not merged.
|
||||||
{
|
{
|
||||||
std::vector<const PrintRegion*> regions;
|
std::vector<const PrintRegion*> regions;
|
||||||
@ -882,7 +906,8 @@ static PrintObjectRegions* generate_print_object_regions(
|
|||||||
const Transform3d &trafo,
|
const Transform3d &trafo,
|
||||||
size_t num_extruders,
|
size_t num_extruders,
|
||||||
const float xy_size_compensation,
|
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.
|
// 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>();
|
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.config = range.config;
|
||||||
r.volume_regions.clear();
|
r.volume_regions.clear();
|
||||||
r.painted_regions.clear();
|
r.painted_regions.clear();
|
||||||
|
r.fuzzy_skin_painted_regions.clear();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
out->trafo_bboxes = trafo;
|
out->trafo_bboxes = trafo;
|
||||||
@ -985,13 +1011,42 @@ static PrintObjectRegions* generate_print_object_regions(
|
|||||||
cfg.infill_extruder.value = painted_extruder_id;
|
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))});
|
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) {
|
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 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();
|
int rid = layer_range.volume_regions[r.parent].region->print_object_region_id();
|
||||||
return lid < rid || (lid == rid && l.extruder_id < r.extruder_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();
|
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.
|
// 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) ||
|
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_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) ||
|
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);
|
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());
|
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,
|
print_object.model_object()->volumes,
|
||||||
m_default_region_config,
|
m_default_region_config,
|
||||||
num_extruders,
|
num_extruders,
|
||||||
painting_extruders,
|
|
||||||
*print_object_regions,
|
*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) {
|
[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)
|
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,
|
model_object_status.print_instances.front().trafo,
|
||||||
num_extruders,
|
num_extruders,
|
||||||
print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_size_compensation.value),
|
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)
|
for (auto it = it_print_object; it != it_print_object_end; ++it)
|
||||||
if ((*it)->m_shared_regions) {
|
if ((*it)->m_shared_regions) {
|
||||||
|
@ -39,7 +39,6 @@
|
|||||||
#include "libslic3r/TriangleMeshSlicer.hpp"
|
#include "libslic3r/TriangleMeshSlicer.hpp"
|
||||||
#include "libslic3r/Utils.hpp"
|
#include "libslic3r/Utils.hpp"
|
||||||
#include "libslic3r/libslic3r.h"
|
#include "libslic3r/libslic3r.h"
|
||||||
#include "tcbspan/span.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
@ -580,34 +579,36 @@ void PrintObject::slice()
|
|||||||
template<typename ThrowOnCancel>
|
template<typename ThrowOnCancel>
|
||||||
void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel)
|
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);
|
std::vector<std::vector<ExPolygons>> segmentation = multi_material_segmentation_by_painting(print_object, throw_on_cancel);
|
||||||
assert(segmentation.size() == print_object.layer_count());
|
assert(segmentation.size() == print_object.layer_count());
|
||||||
tbb::parallel_for(
|
tbb::parallel_for(
|
||||||
tbb::blocked_range<size_t>(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))),
|
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) {
|
[&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range<size_t> &range) {
|
||||||
const auto &layer_ranges = print_object.shared_regions()->layer_ranges;
|
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);
|
auto it_layer_range = layer_range_first(layer_ranges, z);
|
||||||
const size_t num_extruders = print_object.print()->config().nozzle_diameter.size();
|
const size_t num_extruders = print_object.print()->config().nozzle_diameter.size();
|
||||||
|
|
||||||
struct ByExtruder {
|
struct ByExtruder {
|
||||||
ExPolygons expolygons;
|
ExPolygons expolygons;
|
||||||
BoundingBox bbox;
|
BoundingBox bbox;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<ByExtruder> by_extruder;
|
std::vector<ByExtruder> by_extruder;
|
||||||
struct ByRegion {
|
struct ByRegion {
|
||||||
ExPolygons expolygons;
|
ExPolygons expolygons;
|
||||||
bool needs_merge { false };
|
bool needs_merge { false };
|
||||||
};
|
};
|
||||||
std::vector<ByRegion> by_region;
|
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();
|
throw_on_cancel();
|
||||||
Layer *layer = print_object.get_layer(layer_id);
|
Layer &layer = *print_object.get_layer(int(layer_id));
|
||||||
it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer->slice_z);
|
it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z);
|
||||||
const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range;
|
const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range;
|
||||||
// Gather per extruder expolygons.
|
// Gather per extruder expolygons.
|
||||||
by_extruder.assign(num_extruders, ByExtruder());
|
by_extruder.assign(num_extruders, ByExtruder());
|
||||||
by_region.assign(layer->region_count(), ByRegion());
|
by_region.assign(layer.region_count(), ByRegion());
|
||||||
bool layer_split = false;
|
bool layer_split = false;
|
||||||
for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) {
|
for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) {
|
||||||
ByExtruder ®ion = by_extruder[extruder_id];
|
ByExtruder ®ion = by_extruder[extruder_id];
|
||||||
@ -617,92 +618,227 @@ void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_can
|
|||||||
layer_split = true;
|
layer_split = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (! layer_split)
|
|
||||||
|
if (!layer_split)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Split LayerRegions by by_extruder regions.
|
// Split LayerRegions by by_extruder regions.
|
||||||
// layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID.
|
// layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID.
|
||||||
auto it_painted_region = layer_range.painted_regions.begin();
|
auto it_painted_region_begin = layer_range.painted_regions.cbegin();
|
||||||
for (int region_id = 0; region_id < int(layer->region_count()); ++ region_id)
|
for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) {
|
||||||
if (LayerRegion &layerm = *layer->get_region(region_id); ! layerm.slices().empty()) {
|
if (it_painted_region_begin == layer_range.painted_regions.cend())
|
||||||
assert(layerm.region().print_object_region_id() == region_id);
|
continue;
|
||||||
const BoundingBox bbox = get_extents(layerm.slices().surfaces);
|
|
||||||
assert(it_painted_region < layer_range.painted_regions.end());
|
const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx);
|
||||||
// Find the first it_painted_region which overrides this region.
|
const PrintRegion &parent_print_region = parent_layer_region.region();
|
||||||
for (; layer_range.volume_regions[it_painted_region->parent].region->print_object_region_id() < region_id; ++ it_painted_region)
|
assert(parent_print_region.print_object_region_id() == parent_layer_region_idx);
|
||||||
assert(it_painted_region != layer_range.painted_regions.end());
|
if (parent_layer_region.slices().empty())
|
||||||
assert(it_painted_region != layer_range.painted_regions.end());
|
continue;
|
||||||
assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region());
|
|
||||||
// 1-based extruder ID
|
// Find the first PaintedRegion, which overrides the parent PrintRegion.
|
||||||
bool self_trimmed = false;
|
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) {
|
||||||
int self_extruder_id = -1;
|
return layer_range.volume_regions[painted_region.parent].region->print_object_region_id() == parent_print_region.print_object_region_id();
|
||||||
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.
|
if (it_first_painted_region == layer_range.painted_regions.cend())
|
||||||
for (; int(it_painted_region->extruder_id) < extruder_id; ++ it_painted_region)
|
continue; // This LayerRegion isn't overrides by any PaintedRegion.
|
||||||
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);
|
assert(&parent_print_region == layer_range.volume_regions[it_first_painted_region->parent].region);
|
||||||
//FIXME Don't trim by self, it is not reliable.
|
|
||||||
if (&layerm.region() == it_painted_region->region) {
|
// Find the first PaintedRegion with different parent PrintRegion.
|
||||||
self_extruder_id = extruder_id;
|
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) {
|
||||||
continue;
|
return painted_region.parent != it_first_painted_region->parent;
|
||||||
}
|
});
|
||||||
// Steal from this region.
|
|
||||||
int target_region_id = it_painted_region->region->print_object_region_id();
|
// Update the beginning PaintedRegion iterator for the next iteration.
|
||||||
ExPolygons stolen = intersection_ex(layerm.slices().surfaces, segmented.expolygons);
|
it_painted_region_begin = it_last_painted_region;
|
||||||
if (! stolen.empty()) {
|
|
||||||
ByRegion &dst = by_region[target_region_id];
|
const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices().surfaces);
|
||||||
if (dst.expolygons.empty()) {
|
bool self_trimmed = false;
|
||||||
dst.expolygons = std::move(stolen);
|
int self_extruder_id = -1; // 1-based extruder ID
|
||||||
} else {
|
for (auto it_painted_region = it_first_painted_region; it_painted_region != it_last_painted_region; ++it_painted_region) {
|
||||||
append(dst.expolygons, std::move(stolen));
|
const int extruder_id = int(it_painted_region->extruder_id); // 1-based extruder ID
|
||||||
dst.needs_merge = true;
|
assert(extruder_id > 0 && (extruder_id - 1) < int(by_extruder.size()));
|
||||||
}
|
assert(layer_range.volume_regions[it_painted_region->parent].region == &parent_print_region);
|
||||||
}
|
|
||||||
#if 0
|
const ByExtruder &segmented = by_extruder[extruder_id - 1];
|
||||||
if (&layerm.region() == it_painted_region->region)
|
if (!segmented.bbox.defined || !parent_layer_region_bbox.overlap(segmented.bbox))
|
||||||
// Slices of this LayerRegion were trimmed by a MMU region of the same PrintRegion.
|
continue;
|
||||||
self_trimmed = true;
|
|
||||||
#endif
|
// FIXME: Don't trim by self, it is not reliable.
|
||||||
}
|
if (it_painted_region->region == &parent_print_region) {
|
||||||
if (! self_trimmed) {
|
self_extruder_id = extruder_id;
|
||||||
// Trim slices of this LayerRegion with all the MMU regions.
|
continue;
|
||||||
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)) {
|
// Steal from this region.
|
||||||
mine = diff(mine, segmented.expolygons);
|
int target_region_id = it_painted_region->region->print_object_region_id();
|
||||||
if (mine.empty())
|
ExPolygons stolen = intersection_ex(parent_layer_region.slices().surfaces, segmented.expolygons);
|
||||||
break;
|
if (!stolen.empty()) {
|
||||||
}
|
ByRegion &dst = by_region[target_region_id];
|
||||||
// Filter out unprintable polygons produced by subtraction multi-material painted regions from layerm.region().
|
if (dst.expolygons.empty()) {
|
||||||
// ExPolygon returned from multi-material segmentation does not precisely match ExPolygons in layerm.region()
|
dst.expolygons = std::move(stolen);
|
||||||
// (because of preprocessing of the input regions in multi-material segmentation). Therefore, subtraction from
|
} else {
|
||||||
// layerm.region() could produce a huge number of small unprintable regions for the model's base extruder.
|
append(dst.expolygons, std::move(stolen));
|
||||||
// This could, on some models, produce bulges with the model's base color (#7109).
|
dst.needs_merge = true;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
// 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];
|
ByRegion &src = by_region[region_id];
|
||||||
if (src.needs_merge)
|
if (src.needs_merge) {
|
||||||
// Multiple regions were merged into one.
|
// Multiple regions were merged into one.
|
||||||
src.expolygons = closing_ex(src.expolygons, float(scale_(10 * EPSILON)));
|
src.expolygons = closing_ex(src.expolygons, scaled<float>(10. * EPSILON));
|
||||||
layer->get_region(region_id)->m_slices.set(std::move(src.expolygons), stInternal);
|
}
|
||||||
|
|
||||||
|
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,
|
// 1) Decides Z positions of the layers,
|
||||||
// 2) Initializes layers and their regions
|
// 2) Initializes layers and their regions
|
||||||
// 3) Slices the object meshes
|
// 3) Slices the object meshes
|
||||||
@ -752,7 +888,7 @@ void PrintObject::slice_volumes()
|
|||||||
m_layers.back()->upper_layer = nullptr;
|
m_layers.back()->upper_layer = nullptr;
|
||||||
m_print->throw_if_canceled();
|
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 (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
|
// 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.
|
// 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(); });
|
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";
|
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin";
|
||||||
{
|
{
|
||||||
|
@ -37,6 +37,8 @@ enum class TriangleStateType : int8_t {
|
|||||||
NONE = 0,
|
NONE = 0,
|
||||||
ENFORCER = 1,
|
ENFORCER = 1,
|
||||||
BLOCKER = 2,
|
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.
|
// Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code.
|
||||||
Extruder1 = ENFORCER,
|
Extruder1 = ENFORCER,
|
||||||
Extruder2 = BLOCKER,
|
Extruder2 = BLOCKER,
|
||||||
|
@ -81,6 +81,8 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
GUI/Gizmos/GLGizmoSlaSupports.hpp
|
GUI/Gizmos/GLGizmoSlaSupports.hpp
|
||||||
GUI/Gizmos/GLGizmoFdmSupports.cpp
|
GUI/Gizmos/GLGizmoFdmSupports.cpp
|
||||||
GUI/Gizmos/GLGizmoFdmSupports.hpp
|
GUI/Gizmos/GLGizmoFdmSupports.hpp
|
||||||
|
GUI/Gizmos/GLGizmoFuzzySkin.cpp
|
||||||
|
GUI/Gizmos/GLGizmoFuzzySkin.hpp
|
||||||
GUI/Gizmos/GLGizmoFlatten.cpp
|
GUI/Gizmos/GLGizmoFlatten.cpp
|
||||||
GUI/Gizmos/GLGizmoFlatten.hpp
|
GUI/Gizmos/GLGizmoFlatten.hpp
|
||||||
GUI/Gizmos/GLGizmoCut.cpp
|
GUI/Gizmos/GLGizmoCut.cpp
|
||||||
|
@ -1555,11 +1555,12 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
|
|||||||
auto gizmo_type = gm.get_current_type();
|
auto gizmo_type = gm.get_current_type();
|
||||||
if ( (gizmo_type == GLGizmosManager::FdmSupports
|
if ( (gizmo_type == GLGizmosManager::FdmSupports
|
||||||
|| gizmo_type == GLGizmosManager::Seam
|
|| gizmo_type == GLGizmosManager::Seam
|
||||||
|| gizmo_type == GLGizmosManager::Cut)
|
|| gizmo_type == GLGizmosManager::Cut
|
||||||
|
|| gizmo_type == GLGizmosManager::FuzzySkin)
|
||||||
&& !vol->is_modifier) {
|
&& !vol->is_modifier) {
|
||||||
vol->force_neutral_color = true;
|
vol->force_neutral_color = true;
|
||||||
}
|
}
|
||||||
else if (gizmo_type == GLGizmosManager::MmuSegmentation)
|
else if (gizmo_type == GLGizmosManager::MmSegmentation)
|
||||||
vol->is_active = false;
|
vol->is_active = false;
|
||||||
else
|
else
|
||||||
vol->force_native_color = true;
|
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();
|
const GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
|
||||||
if (keyCode == WXK_ALT && (gizmo_type == GLGizmosManager::FdmSupports ||
|
if (keyCode == WXK_ALT && (gizmo_type == GLGizmosManager::FdmSupports ||
|
||||||
gizmo_type == GLGizmosManager::Seam ||
|
gizmo_type == GLGizmosManager::Seam ||
|
||||||
gizmo_type == GLGizmosManager::MmuSegmentation)) {
|
gizmo_type == GLGizmosManager::MmSegmentation ||
|
||||||
// Prevents focusing on the menu bar when ALT is pressed in painting gizmos (FdmSupports, Seam, and MmuSegmentation).
|
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);
|
evt.Skip(false);
|
||||||
} else if (keyCode != WXK_TAB
|
} else if (keyCode != WXK_TAB
|
||||||
&& keyCode != WXK_LEFT
|
&& keyCode != WXK_LEFT
|
||||||
@ -3359,7 +3361,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||||||
m_dirty = true;
|
m_dirty = true;
|
||||||
// do not return if dragging or tooltip not empty to allow for tooltip update
|
// 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
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3540,7 +3542,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||||||
m_gizmos.get_current_type() != GLGizmosManager::Seam &&
|
m_gizmos.get_current_type() != GLGizmosManager::Seam &&
|
||||||
m_gizmos.get_current_type() != GLGizmosManager::Cut &&
|
m_gizmos.get_current_type() != GLGizmosManager::Cut &&
|
||||||
m_gizmos.get_current_type() != GLGizmosManager::Measure &&
|
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_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect);
|
||||||
m_dirty = true;
|
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::SlaSupports
|
||||||
&& m_gizmos.get_current_type() != GLGizmosManager::Hollow
|
&& m_gizmos.get_current_type() != GLGizmosManager::Hollow
|
||||||
&& m_gizmos.get_current_type() != GLGizmosManager::Seam
|
&& 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);
|
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::Flatten:
|
||||||
case GLGizmosManager::EType::Cut:
|
case GLGizmosManager::EType::Cut:
|
||||||
case GLGizmosManager::EType::MmuSegmentation:
|
case GLGizmosManager::EType::MmSegmentation:
|
||||||
case GLGizmosManager::EType::Measure:
|
case GLGizmosManager::EType::Measure:
|
||||||
case GLGizmosManager::EType::Emboss:
|
case GLGizmosManager::EType::Emboss:
|
||||||
case GLGizmosManager::EType::Simplify:
|
case GLGizmosManager::EType::Simplify:
|
||||||
case GLGizmosManager::EType::FdmSupports:
|
case GLGizmosManager::EType::FdmSupports:
|
||||||
case GLGizmosManager::EType::Seam: { return; }
|
case GLGizmosManager::EType::Seam:
|
||||||
|
case GLGizmosManager::EType::FuzzySkin: { return; }
|
||||||
default: { break; }
|
default: { break; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1921,13 +1921,20 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case InfoItemType::MmuSegmentation:
|
case InfoItemType::MmSegmentation:
|
||||||
cnv->get_gizmos_manager().reset_all_states();
|
cnv->get_gizmos_manager().reset_all_states();
|
||||||
Plater::TakeSnapshot(plater, _L("Remove Multi Material painting"));
|
Plater::TakeSnapshot(plater, _L("Remove Multi Material painting"));
|
||||||
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
|
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
|
||||||
mv->mm_segmentation_facets.reset();
|
mv->mm_segmentation_facets.reset();
|
||||||
break;
|
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:
|
case InfoItemType::Sinking:
|
||||||
Plater::TakeSnapshot(plater, _L("Shift objects to bed"));
|
Plater::TakeSnapshot(plater, _L("Shift objects to bed"));
|
||||||
(*m_objects)[obj_idx]->ensure_on_bed();
|
(*m_objects)[obj_idx]->ensure_on_bed();
|
||||||
@ -2132,8 +2139,8 @@ void ObjectList::split()
|
|||||||
|
|
||||||
take_snapshot(_(L("Split to Parts")));
|
take_snapshot(_(L("Split to Parts")));
|
||||||
|
|
||||||
// Before splitting volume we have to remove all custom supports, seams, and multimaterial painting.
|
// 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 and multimaterial painting were "
|
wxGetApp().plater()->clear_before_change_mesh(obj_idx, _u8L("Custom supports, seams, fuzzy skin and multi-material painting were "
|
||||||
"removed after splitting the object."));
|
"removed after splitting the object."));
|
||||||
|
|
||||||
volume->split(nozzle_dmrs_cnt);
|
volume->split(nozzle_dmrs_cnt);
|
||||||
@ -2148,8 +2155,8 @@ void ObjectList::split()
|
|||||||
// update printable state for new volumes on canvas3D
|
// update printable state for new volumes on canvas3D
|
||||||
wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object(obj_idx);
|
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
|
// 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, and multimaterial painting in the right panel.
|
// custom supports, seams, fuzzy skin, and multi-material painting in the right panel.
|
||||||
wxGetApp().obj_list()->update_info_items(obj_idx);
|
wxGetApp().obj_list()->update_info_items(obj_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2727,11 +2734,13 @@ void ObjectList::part_selection_changed()
|
|||||||
}
|
}
|
||||||
case InfoItemType::CustomSupports:
|
case InfoItemType::CustomSupports:
|
||||||
case InfoItemType::CustomSeam:
|
case InfoItemType::CustomSeam:
|
||||||
case InfoItemType::MmuSegmentation:
|
case InfoItemType::MmSegmentation:
|
||||||
|
case InfoItemType::FuzzySkin:
|
||||||
{
|
{
|
||||||
GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports :
|
GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports :
|
||||||
info_type == InfoItemType::CustomSeam ? GLGizmosManager::EType::Seam :
|
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)
|
if (gizmos_mgr.get_current_type() != gizmo_type)
|
||||||
gizmos_mgr.open_gizmo(gizmo_type);
|
gizmos_mgr.open_gizmo(gizmo_type);
|
||||||
break;
|
break;
|
||||||
@ -2901,7 +2910,8 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio
|
|||||||
for (InfoItemType type : {InfoItemType::CustomSupports,
|
for (InfoItemType type : {InfoItemType::CustomSupports,
|
||||||
InfoItemType::CustomSeam,
|
InfoItemType::CustomSeam,
|
||||||
InfoItemType::CutConnectors,
|
InfoItemType::CutConnectors,
|
||||||
InfoItemType::MmuSegmentation,
|
InfoItemType::MmSegmentation,
|
||||||
|
InfoItemType::FuzzySkin,
|
||||||
InfoItemType::Sinking,
|
InfoItemType::Sinking,
|
||||||
InfoItemType::VariableLayerHeight}) {
|
InfoItemType::VariableLayerHeight}) {
|
||||||
wxDataViewItem item = m_objects_model->GetInfoItemByType(item_obj, type);
|
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) {
|
switch (type) {
|
||||||
case InfoItemType::CustomSupports :
|
case InfoItemType::CustomSupports :
|
||||||
case InfoItemType::CustomSeam :
|
case InfoItemType::CustomSeam :
|
||||||
case InfoItemType::MmuSegmentation :
|
case InfoItemType::MmSegmentation :
|
||||||
|
case InfoItemType::FuzzySkin :
|
||||||
should_show = printer_technology() == ptFFF
|
should_show = printer_technology() == ptFFF
|
||||||
&& std::any_of(model_object->volumes.begin(), model_object->volumes.end(),
|
&& std::any_of(model_object->volumes.begin(), model_object->volumes.end(),
|
||||||
[type](const ModelVolume *mv) {
|
[type](const ModelVolume *mv) {
|
||||||
return !(type == InfoItemType::CustomSupports ? mv->supported_facets.empty() :
|
return !(type == InfoItemType::CustomSupports ? mv->supported_facets.empty() :
|
||||||
type == InfoItemType::CustomSeam ? mv->seam_facets.empty() :
|
type == InfoItemType::CustomSeam ? mv->seam_facets.empty() :
|
||||||
|
type == InfoItemType::FuzzySkin ? mv->fuzzy_skin_facets.empty() :
|
||||||
mv->mm_segmentation_facets.empty());
|
mv->mm_segmentation_facets.empty());
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@ -4642,7 +4654,7 @@ void ObjectList::fix_through_winsdk()
|
|||||||
msg += "\n";
|
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."));
|
"removed after repairing the mesh."));
|
||||||
std::string res;
|
std::string res;
|
||||||
if (!fix_model_by_win10_sdk_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, 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];
|
return (*m_objects)[obj_idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ObjectList::has_paint_on_segmentation()
|
|
||||||
{
|
|
||||||
return m_objects_model->HasInfoItem(InfoItemType::MmuSegmentation);
|
|
||||||
}
|
|
||||||
|
|
||||||
} //namespace GUI
|
} //namespace GUI
|
||||||
} //namespace Slic3r
|
} //namespace Slic3r
|
||||||
|
@ -410,7 +410,6 @@ public:
|
|||||||
void set_extruder_for_selected_items(const int extruder) const ;
|
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);
|
wxDataViewItemArray reorder_volumes_and_get_selection(size_t obj_idx, std::function<bool(const ModelVolume*)> add_to_selection = nullptr);
|
||||||
void apply_volumes_order();
|
void apply_volumes_order();
|
||||||
bool has_paint_on_segmentation();
|
|
||||||
|
|
||||||
bool is_editing() const { return m_is_editing_started; }
|
bool is_editing() const { return m_is_editing_started; }
|
||||||
|
|
||||||
|
311
src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp
Normal file
311
src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp
Normal 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
|
47
src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp
Normal file
47
src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp
Normal 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_
|
@ -560,7 +560,7 @@ void GLGizmoMmuSegmentation::update_from_model_object()
|
|||||||
|
|
||||||
PainterGizmoType GLGizmoMmuSegmentation::get_painter_type() const
|
PainterGizmoType GLGizmoMmuSegmentation::get_painter_type() const
|
||||||
{
|
{
|
||||||
return PainterGizmoType::MMU_SEGMENTATION;
|
return PainterGizmoType::MM_SEGMENTATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
ColorRGBA GLGizmoMmuSegmentation::get_cursor_sphere_left_button_color() const
|
ColorRGBA GLGizmoMmuSegmentation::get_cursor_sphere_left_button_color() const
|
||||||
|
@ -31,7 +31,8 @@ class Selection;
|
|||||||
enum class PainterGizmoType {
|
enum class PainterGizmoType {
|
||||||
FDM_SUPPORTS,
|
FDM_SUPPORTS,
|
||||||
SEAM,
|
SEAM,
|
||||||
MMU_SEGMENTATION
|
MM_SEGMENTATION,
|
||||||
|
FUZZY_SKIN
|
||||||
};
|
};
|
||||||
|
|
||||||
class TriangleSelectorGUI : public TriangleSelector {
|
class TriangleSelectorGUI : public TriangleSelector {
|
||||||
|
@ -36,6 +36,7 @@ enum class SLAGizmoEventType : unsigned char {
|
|||||||
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
|
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
|
#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp"
|
#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp"
|
||||||
|
#include "slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp"
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
|
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
|
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
|
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
|
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
|
#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp"
|
#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp"
|
||||||
|
#include "slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp"
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
|
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
|
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoSeam.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 GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6));
|
||||||
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "fdm_supports.svg", 7));
|
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 GLGizmoSeam(m_parent, "seam.svg", 8));
|
||||||
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "mmu_segmentation.svg", 9));
|
m_gizmos.emplace_back(new GLGizmoFuzzySkin(m_parent, "fuzzy_skin_painting.svg", 9));
|
||||||
m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, "measure.svg", 10));
|
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 GLGizmoEmboss(m_parent));
|
||||||
m_gizmos.emplace_back(new GLGizmoSVG(m_parent));
|
m_gizmos.emplace_back(new GLGizmoSVG(m_parent));
|
||||||
m_gizmos.emplace_back(new GLGizmoSimplify(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);
|
return dynamic_cast<GLGizmoFdmSupports*>(m_gizmos[FdmSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||||
else if (m_current == Seam)
|
else if (m_current == Seam)
|
||||||
return dynamic_cast<GLGizmoSeam*>(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
return dynamic_cast<GLGizmoSeam*>(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||||
else if (m_current == MmuSegmentation)
|
else if (m_current == MmSegmentation)
|
||||||
return dynamic_cast<GLGizmoMmuSegmentation*>(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
return dynamic_cast<GLGizmoMmuSegmentation*>(m_gizmos[MmSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||||
else if (m_current == Measure)
|
else if (m_current == Measure)
|
||||||
return dynamic_cast<GLGizmoMeasure*>(m_gizmos[Measure].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
return dynamic_cast<GLGizmoMeasure*>(m_gizmos[Measure].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||||
else if (m_current == Cut)
|
else if (m_current == Cut)
|
||||||
return dynamic_cast<GLGizmoCut3D*>(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
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
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -367,7 +371,7 @@ bool GLGizmosManager::on_mouse_wheel(const wxMouseEvent &evt)
|
|||||||
{
|
{
|
||||||
bool processed = false;
|
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();
|
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()))
|
if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
|
||||||
processed = true;
|
processed = true;
|
||||||
@ -540,7 +544,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
|||||||
case 'r' :
|
case 'r' :
|
||||||
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;
|
processed = true;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -80,7 +80,8 @@ public:
|
|||||||
SlaSupports,
|
SlaSupports,
|
||||||
FdmSupports,
|
FdmSupports,
|
||||||
Seam,
|
Seam,
|
||||||
MmuSegmentation,
|
FuzzySkin,
|
||||||
|
MmSegmentation,
|
||||||
Measure,
|
Measure,
|
||||||
Emboss,
|
Emboss,
|
||||||
Svg,
|
Svg,
|
||||||
|
@ -164,6 +164,7 @@ void KBShortcutsDialog::fill_shortcuts()
|
|||||||
{ "C", L("Gizmo cut") },
|
{ "C", L("Gizmo cut") },
|
||||||
{ "F", L("Gizmo Place face on bed") },
|
{ "F", L("Gizmo Place face on bed") },
|
||||||
{ "H", L("Gizmo SLA hollow") },
|
{ "H", L("Gizmo SLA hollow") },
|
||||||
|
{ "H", L("Gizmo FDM paint-on fuzzy skin") },
|
||||||
{ "L", L("Gizmo SLA support points") },
|
{ "L", L("Gizmo SLA support points") },
|
||||||
{ "L", L("Gizmo FDM paint-on supports") },
|
{ "L", L("Gizmo FDM paint-on supports") },
|
||||||
{ "P", L("Gizmo FDM paint-on seam") },
|
{ "P", L("Gizmo FDM paint-on seam") },
|
||||||
|
@ -1722,10 +1722,11 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty
|
|||||||
switch ((*it).first) {
|
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::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::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::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::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::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;
|
default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,12 +68,13 @@ struct InfoItemAtributes {
|
|||||||
|
|
||||||
const std::map<InfoItemType, InfoItemAtributes> INFO_ITEMS{
|
const std::map<InfoItemType, InfoItemAtributes> INFO_ITEMS{
|
||||||
// info_item Type info_item Name info_item BitmapName
|
// info_item Type info_item Name info_item BitmapName
|
||||||
{ InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports_" }, },
|
{ InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports_" }, },
|
||||||
{ InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, },
|
{ InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, },
|
||||||
{ InfoItemType::CutConnectors, {L("Connectors"), "cut_connectors" }, },
|
{ InfoItemType::CutConnectors, {L("Connectors"), "cut_connectors" }, },
|
||||||
{ InfoItemType::MmuSegmentation, {L("Multimaterial painting"), "mmu_segmentation_"}, },
|
{ InfoItemType::MmSegmentation, {L("Multimaterial painting"), "mmu_segmentation_" }, },
|
||||||
{ InfoItemType::Sinking, {L("Sinking"), "sinking"}, },
|
{ InfoItemType::Sinking, {L("Sinking"), "sinking" }, },
|
||||||
{ InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers"}, },
|
{ InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers" }, },
|
||||||
|
{ InfoItemType::FuzzySkin, {L("Paint-on fuzzy skin"), "fuzzy_skin_painting_" }, },
|
||||||
};
|
};
|
||||||
|
|
||||||
ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
|
ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
|
||||||
|
@ -56,7 +56,8 @@ enum class InfoItemType
|
|||||||
CustomSupports,
|
CustomSupports,
|
||||||
CustomSeam,
|
CustomSeam,
|
||||||
CutConnectors,
|
CutConnectors,
|
||||||
MmuSegmentation,
|
MmSegmentation,
|
||||||
|
FuzzySkin,
|
||||||
Sinking,
|
Sinking,
|
||||||
VariableLayerHeight
|
VariableLayerHeight
|
||||||
};
|
};
|
||||||
|
@ -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->supported_facets.assign(old_volume->supported_facets);
|
||||||
new_volume->seam_facets.assign(old_volume->seam_facets);
|
new_volume->seam_facets.assign(old_volume->seam_facets);
|
||||||
new_volume->mm_segmentation_facets.assign(old_volume->mm_segmentation_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());
|
std::swap(old_model_object->volumes[volume_idx], old_model_object->volumes.back());
|
||||||
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
|
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 ¬ification_msg) {
|
void Plater::clear_before_change_volume(ModelVolume &mv, const std::string ¬ification_msg) {
|
||||||
// When we change the geometry of the volume, we remove any custom supports/seams/multi-material painting.
|
// 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(); paint_removed) {
|
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.supported_facets.reset();
|
||||||
mv.seam_facets.reset();
|
mv.seam_facets.reset();
|
||||||
mv.mm_segmentation_facets.reset();
|
mv.mm_segmentation_facets.reset();
|
||||||
|
mv.fuzzy_skin_facets.reset();
|
||||||
|
|
||||||
get_notification_manager()->push_notification(
|
get_notification_manager()->push_notification(
|
||||||
NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
|
NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
|
||||||
@ -6703,14 +6705,15 @@ void Plater::clear_before_change_mesh(int obj_idx, const std::string ¬ificati
|
|||||||
{
|
{
|
||||||
ModelObject* mo = model().objects[obj_idx];
|
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.
|
// may be different and they would make no sense.
|
||||||
bool paint_removed = false;
|
bool paint_removed = false;
|
||||||
for (ModelVolume* mv : mo->volumes) {
|
for (ModelVolume *mv : mo->volumes) {
|
||||||
paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mm_segmentation_facets.empty();
|
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->supported_facets.reset();
|
||||||
mv->seam_facets.reset();
|
mv->seam_facets.reset();
|
||||||
mv->mm_segmentation_facets.reset();
|
mv->mm_segmentation_facets.reset();
|
||||||
|
mv->fuzzy_skin_facets.reset();
|
||||||
}
|
}
|
||||||
if (paint_removed) {
|
if (paint_removed) {
|
||||||
// snapshot_time is captured by copy so the lambda knows where to undo/redo to.
|
// snapshot_time is captured by copy so the lambda knows where to undo/redo to.
|
||||||
|
@ -46,6 +46,7 @@ SCENARIO("Perimeter nesting", "[Perimeters]")
|
|||||||
ExtrusionEntityCollection gap_fill;
|
ExtrusionEntityCollection gap_fill;
|
||||||
ExPolygons fill_expolygons;
|
ExPolygons fill_expolygons;
|
||||||
Flow flow(1., 1., 1.);
|
Flow flow(1., 1., 1.);
|
||||||
|
PerimeterRegions perimeter_regions;
|
||||||
PerimeterGenerator::Parameters perimeter_generator_params(
|
PerimeterGenerator::Parameters perimeter_generator_params(
|
||||||
1., // layer height
|
1., // layer height
|
||||||
-1, // layer ID
|
-1, // layer ID
|
||||||
@ -53,6 +54,7 @@ SCENARIO("Perimeter nesting", "[Perimeters]")
|
|||||||
static_cast<const PrintRegionConfig&>(config),
|
static_cast<const PrintRegionConfig&>(config),
|
||||||
static_cast<const PrintObjectConfig&>(config),
|
static_cast<const PrintObjectConfig&>(config),
|
||||||
static_cast<const PrintConfig&>(config),
|
static_cast<const PrintConfig&>(config),
|
||||||
|
perimeter_regions,
|
||||||
false); // spiral_vase
|
false); // spiral_vase
|
||||||
Polygons lower_layer_polygons_cache;
|
Polygons lower_layer_polygons_cache;
|
||||||
for (const Surface &surface : slices)
|
for (const Surface &surface : slices)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user