diff --git a/resources/images/fuzzy_skin.svg b/resources/images/fuzzy_skin.svg
new file mode 100644
index 0000000000..78294b863f
--- /dev/null
+++ b/resources/images/fuzzy_skin.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/objlist_fuzzy_skin_paint.svg b/resources/images/objlist_fuzzy_skin_paint.svg
new file mode 100644
index 0000000000..b011d2cf1e
--- /dev/null
+++ b/resources/images/objlist_fuzzy_skin_paint.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/toolbar_fuzzy_skin_paint.svg b/resources/images/toolbar_fuzzy_skin_paint.svg
new file mode 100644
index 0000000000..3bf4f83ab7
--- /dev/null
+++ b/resources/images/toolbar_fuzzy_skin_paint.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/images/toolbar_fuzzy_skin_paint_dark.svg b/resources/images/toolbar_fuzzy_skin_paint_dark.svg
new file mode 100644
index 0000000000..da06932f64
--- /dev/null
+++ b/resources/images/toolbar_fuzzy_skin_paint_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp
index 7789612e4b..625bf9b727 100644
--- a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp
+++ b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp
@@ -59,7 +59,8 @@ inline const Point& make_point(const ExtrusionJunction& ej)
return ej.p;
}
-using LineJunctions = std::vector; //; //;
}
#endif // UTILS_EXTRUSION_JUNCTION_H
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index 6a1bc24d81..2e56523334 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -77,7 +77,13 @@ set(lisbslic3r_sources
ExtrusionEntityCollection.hpp
ExtrusionSimulator.cpp
ExtrusionSimulator.hpp
+ Feature/Interlocking/InterlockingGenerator.cpp
+ Feature/Interlocking/InterlockingGenerator.hpp
+ Feature/Interlocking/VoxelUtils.cpp
+ Feature/Interlocking/VoxelUtils.hpp
FileParserError.hpp
+ Feature/FuzzySkin/FuzzySkin.cpp
+ Feature/FuzzySkin/FuzzySkin.hpp
Fill/Fill.cpp
Fill/Fill.hpp
Fill/Fill3DHoneycomb.cpp
@@ -445,10 +451,6 @@ set(lisbslic3r_sources
calib.cpp
GCode/Thumbnails.cpp
GCode/Thumbnails.hpp
- Interlocking/InterlockingGenerator.hpp
- Interlocking/InterlockingGenerator.cpp
- Interlocking/VoxelUtils.hpp
- Interlocking/VoxelUtils.cpp
)
if (APPLE)
diff --git a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp
new file mode 100644
index 0000000000..a79220951e
--- /dev/null
+++ b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp
@@ -0,0 +1,391 @@
+#include
+
+#include "libslic3r/Algorithm/LineSplit.hpp"
+#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp"
+#include "libslic3r/Arachne/utils/ExtrusionLine.hpp"
+#include "libslic3r/Layer.hpp"
+#include "libslic3r/PerimeterGenerator.hpp"
+#include "libslic3r/Point.hpp"
+#include "libslic3r/Polygon.hpp"
+#include "libslic3r/Print.hpp"
+#include "libslic3r/PrintConfig.hpp"
+
+#include "FuzzySkin.hpp"
+
+#include "libnoise/noise.h"
+
+// #define DEBUG_FUZZY
+
+using namespace Slic3r;
+
+namespace Slic3r::Feature::FuzzySkin {
+
+// Produces a random value between 0 and 1. Thread-safe.
+static double random_value() {
+ thread_local std::random_device rd;
+ // Hash thread ID for random number seed if no hardware rng seed is available
+ thread_local std::mt19937 gen(rd.entropy() > 0 ? rd() : std::hash()(std::this_thread::get_id()));
+ thread_local std::uniform_real_distribution dist(0.0, 1.0);
+ return dist(gen);
+}
+
+class UniformNoise: public noise::module::Module {
+ public:
+ UniformNoise(): Module (GetSourceModuleCount ()) {};
+
+ virtual int GetSourceModuleCount() const { return 0; }
+ virtual double GetValue(double x, double y, double z) const { return random_value() * 2 - 1; }
+};
+
+static std::unique_ptr get_noise_module(const FuzzySkinConfig& cfg) {
+ if (cfg.noise_type == NoiseType::Perlin) {
+ auto perlin_noise = noise::module::Perlin();
+ perlin_noise.SetFrequency(1 / cfg.noise_scale);
+ perlin_noise.SetOctaveCount(cfg.noise_octaves);
+ perlin_noise.SetPersistence(cfg.noise_persistence);
+ return std::make_unique(perlin_noise);
+ } else if (cfg.noise_type == NoiseType::Billow) {
+ auto billow_noise = noise::module::Billow();
+ billow_noise.SetFrequency(1 / cfg.noise_scale);
+ billow_noise.SetOctaveCount(cfg.noise_octaves);
+ billow_noise.SetPersistence(cfg.noise_persistence);
+ return std::make_unique(billow_noise);
+ } else if (cfg.noise_type == NoiseType::RidgedMulti) {
+ auto ridged_multi_noise = noise::module::RidgedMulti();
+ ridged_multi_noise.SetFrequency(1 / cfg.noise_scale);
+ ridged_multi_noise.SetOctaveCount(cfg.noise_octaves);
+ return std::make_unique(ridged_multi_noise);
+ } else if (cfg.noise_type == NoiseType::Voronoi) {
+ auto voronoi_noise = noise::module::Voronoi();
+ voronoi_noise.SetFrequency(1 / cfg.noise_scale);
+ voronoi_noise.SetDisplacement(1.0);
+ return std::make_unique(voronoi_noise);
+ } else {
+ return std::make_unique();
+ }
+}
+
+// Thanks Cura developers for this function.
+void fuzzy_polyline(Points& poly, bool closed, coordf_t slice_z, const FuzzySkinConfig& cfg)
+{
+ std::unique_ptr noise = get_noise_module(cfg);
+
+ const double min_dist_between_points = cfg.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 = cfg.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
+ Point* p0 = &poly.back();
+ Points out;
+ out.reserve(poly.size());
+ for (Point &p1 : poly)
+ {
+ if (!closed) {
+ // Skip the first point for open path
+ closed = true;
+ p0 = &p1;
+ continue;
+ }
+ // 'a' is the (next) new point between p0 and p1
+ Vec2d p0p1 = (p1 - *p0).cast();
+ double p0p1_size = p0p1.norm();
+ double p0pa_dist = dist_left_over;
+ for (; p0pa_dist < p0p1_size;
+ p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist)
+ {
+ Point pa = *p0 + (p0p1 * (p0pa_dist / p0p1_size)).cast();
+ double r = noise->GetValue(unscale_(pa.x()), unscale_(pa.y()), slice_z) * cfg.thickness;
+ out.emplace_back(pa + (perp(p0p1).cast().normalized() * r).cast());
+ }
+ dist_left_over = p0pa_dist - p0p1_size;
+ p0 = &p1;
+ }
+ while (out.size() < 3) {
+ size_t point_idx = poly.size() - 2;
+ out.emplace_back(poly[point_idx]);
+ if (point_idx == 0)
+ break;
+ -- point_idx;
+ }
+ if (out.size() >= 3)
+ poly = std::move(out);
+}
+
+// Thanks Cura developers for this function.
+void fuzzy_extrusion_line(Arachne::ExtrusionJunctions& ext_lines, coordf_t slice_z, const FuzzySkinConfig& cfg)
+{
+ std::unique_ptr noise = get_noise_module(cfg);
+
+ const double min_dist_between_points = cfg.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 = cfg.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
+
+ auto* p0 = &ext_lines.front();
+ Arachne::ExtrusionJunctions out;
+ out.reserve(ext_lines.size());
+ for (auto& p1 : ext_lines) {
+ if (p0->p == p1.p) { // Connect endpoints.
+ out.emplace_back(p1.p, p1.w, p1.perimeter_index);
+ continue;
+ }
+
+ // 'a' is the (next) new point between p0 and p1
+ Vec2d p0p1 = (p1.p - p0->p).cast();
+ double p0p1_size = p0p1.norm();
+ double p0pa_dist = dist_left_over;
+ for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) {
+ Point pa = p0->p + (p0p1 * (p0pa_dist / p0p1_size)).cast();
+ double r = noise->GetValue(unscale_(pa.x()), unscale_(pa.y()), slice_z) * cfg.thickness;
+ out.emplace_back(pa + (perp(p0p1).cast().normalized() * r).cast(), p1.w, p1.perimeter_index);
+ }
+ dist_left_over = p0pa_dist - p0p1_size;
+ p0 = &p1;
+ }
+
+ while (out.size() < 3) {
+ size_t point_idx = ext_lines.size() - 2;
+ out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index);
+ if (point_idx == 0)
+ break;
+ --point_idx;
+ }
+
+ if (ext_lines.back().p == ext_lines.front().p) // Connect endpoints.
+ out.front().p = out.back().p;
+
+ if (out.size() >= 3)
+ ext_lines = std::move(out);
+}
+
+void group_region_by_fuzzify(PerimeterGenerator& g)
+{
+ g.regions_by_fuzzify.clear();
+ g.has_fuzzy_skin = false;
+ g.has_fuzzy_hole = false;
+
+ std::unordered_map regions;
+ for (auto region : *g.compatible_regions) {
+ const auto& region_config = region->region().config();
+ const FuzzySkinConfig cfg{region_config.fuzzy_skin,
+ scaled(region_config.fuzzy_skin_thickness.value),
+ scaled(region_config.fuzzy_skin_point_distance.value),
+ region_config.fuzzy_skin_first_layer,
+ region_config.fuzzy_skin_noise_type,
+ region_config.fuzzy_skin_scale,
+ region_config.fuzzy_skin_octaves,
+ region_config.fuzzy_skin_persistence};
+ auto& surfaces = regions[cfg];
+ for (const auto& surface : region->slices.surfaces) {
+ surfaces.push_back(&surface);
+ }
+
+ if (cfg.type != FuzzySkinType::None) {
+ g.has_fuzzy_skin = true;
+ if (cfg.type != FuzzySkinType::External) {
+ g.has_fuzzy_hole = true;
+ }
+ }
+ }
+
+ if (regions.size() == 1) { // optimization
+ g.regions_by_fuzzify[regions.begin()->first] = {};
+ return;
+ }
+
+ for (auto& it : regions) {
+ g.regions_by_fuzzify[it.first] = offset_ex(it.second, ClipperSafetyOffset);
+ }
+}
+
+bool should_fuzzify(const FuzzySkinConfig& config, const int layer_id, const size_t loop_idx, const bool is_contour)
+{
+ const auto fuzziy_type = config.type;
+
+ if (fuzziy_type == FuzzySkinType::None) {
+ return false;
+ }
+ if (!config.fuzzy_first_layer && layer_id <= 0) {
+ // Do not fuzzy first layer unless told to
+ return false;
+ }
+
+ const bool fuzzify_contours = loop_idx == 0 || fuzziy_type == FuzzySkinType::AllWalls;
+ const bool fuzzify_holes = fuzzify_contours && (fuzziy_type == FuzzySkinType::All || fuzziy_type == FuzzySkinType::AllWalls);
+
+ return is_contour ? fuzzify_contours : fuzzify_holes;
+}
+
+Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perimeter_generator, const size_t loop_idx, const bool is_contour)
+{
+ Polygon fuzzified;
+
+ const auto slice_z = perimeter_generator.slice_z;
+ const auto& regions = perimeter_generator.regions_by_fuzzify;
+ if (regions.size() == 1) { // optimization
+ const auto& config = regions.begin()->first;
+ const bool fuzzify = should_fuzzify(config, perimeter_generator.layer_id, loop_idx, is_contour);
+ if (!fuzzify) {
+ return polygon;
+ }
+
+ fuzzified = polygon;
+ fuzzy_polyline(fuzzified.points, true, slice_z, config);
+ return fuzzified;
+ }
+
+ // Find all affective regions
+ std::vector> fuzzified_regions;
+ fuzzified_regions.reserve(regions.size());
+ for (const auto& region : regions) {
+ if (should_fuzzify(region.first, perimeter_generator.layer_id, loop_idx, is_contour)) {
+ fuzzified_regions.emplace_back(region.first, region.second);
+ }
+ }
+ if (fuzzified_regions.empty()) {
+ return polygon;
+ }
+
+#ifdef DEBUG_FUZZY
+ {
+ int i = 0;
+ for (const auto& r : fuzzified_regions) {
+ BoundingBox bbox = get_extents(perimeter_generator.slices->surfaces);
+ bbox.offset(scale_(1.));
+ ::Slic3r::SVG svg(debug_out_path("fuzzy_traverse_loops_%d_%d_%d_region_%d.svg", perimeter_generator.layer_id,
+ loop.is_contour ? 0 : 1, loop.depth, i)
+ .c_str(),
+ bbox);
+ svg.draw_outline(perimeter_generator.slices->surfaces);
+ svg.draw_outline(loop.polygon, "green");
+ svg.draw(r.second, "red", 0.5);
+ svg.draw_outline(r.second, "red");
+ svg.Close();
+ i++;
+ }
+ }
+#endif
+
+ // Split the loops into lines with different config, and fuzzy them separately
+ fuzzified = polygon;
+ for (const auto& r : fuzzified_regions) {
+ const auto splitted = Algorithm::split_line(fuzzified, r.second, true);
+ if (splitted.empty()) {
+ // No intersection, skip
+ continue;
+ }
+
+ // Fuzzy splitted polygon
+ if (std::all_of(splitted.begin(), splitted.end(), [](const Algorithm::SplitLineJunction& j) { return j.clipped; })) {
+ // The entire polygon is fuzzified
+ fuzzy_polyline(fuzzified.points, true, slice_z, r.first);
+ } else {
+ Points segment;
+ segment.reserve(splitted.size());
+ fuzzified.points.clear();
+
+ const auto fuzzy_current_segment = [&segment, &fuzzified, &r, slice_z]() {
+ fuzzified.points.push_back(segment.front());
+ const auto back = segment.back();
+ fuzzy_polyline(segment, false, slice_z, r.first);
+ fuzzified.points.insert(fuzzified.points.end(), segment.begin(), segment.end());
+ fuzzified.points.push_back(back);
+ segment.clear();
+ };
+
+ for (const auto& p : splitted) {
+ if (p.clipped) {
+ segment.push_back(p.p);
+ } else {
+ if (segment.empty()) {
+ fuzzified.points.push_back(p.p);
+ } else {
+ segment.push_back(p.p);
+ fuzzy_current_segment();
+ }
+ }
+ }
+ if (!segment.empty()) {
+ // Close the loop
+ segment.push_back(splitted.front().p);
+ fuzzy_current_segment();
+ }
+ }
+ }
+
+ return fuzzified;
+}
+
+void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerator& perimeter_generator, const bool is_contour)
+{
+ const auto slice_z = perimeter_generator.slice_z;
+ const auto& regions = perimeter_generator.regions_by_fuzzify;
+ if (regions.size() == 1) { // optimization
+ const auto& config = regions.begin()->first;
+ const bool fuzzify = should_fuzzify(config, perimeter_generator.layer_id, extrusion->inset_idx, is_contour);
+ if (fuzzify)
+ fuzzy_extrusion_line(extrusion->junctions, slice_z, config);
+ } else {
+ // Find all affective regions
+ std::vector> fuzzified_regions;
+ fuzzified_regions.reserve(regions.size());
+ for (const auto& region : regions) {
+ if (should_fuzzify(region.first, perimeter_generator.layer_id, extrusion->inset_idx, is_contour)) {
+ fuzzified_regions.emplace_back(region.first, region.second);
+ }
+ }
+ if (!fuzzified_regions.empty()) {
+ // Split the loops into lines with different config, and fuzzy them separately
+ for (const auto& r : fuzzified_regions) {
+ const auto splitted = Algorithm::split_line(*extrusion, r.second, false);
+ if (splitted.empty()) {
+ // No intersection, skip
+ continue;
+ }
+
+ // Fuzzy splitted extrusion
+ if (std::all_of(splitted.begin(), splitted.end(), [](const Algorithm::SplitLineJunction& j) { return j.clipped; })) {
+ // The entire polygon is fuzzified
+ fuzzy_extrusion_line(extrusion->junctions, slice_z, r.first);
+ } else {
+ const auto current_ext = extrusion->junctions;
+ std::vector segment;
+ segment.reserve(current_ext.size());
+ extrusion->junctions.clear();
+
+ const auto fuzzy_current_segment = [&segment, &extrusion, &r, slice_z]() {
+ extrusion->junctions.push_back(segment.front());
+ const auto back = segment.back();
+ fuzzy_extrusion_line(segment, slice_z, r.first);
+ extrusion->junctions.insert(extrusion->junctions.end(), segment.begin(), segment.end());
+ extrusion->junctions.push_back(back);
+ segment.clear();
+ };
+
+ const auto to_ex_junction = [¤t_ext](const Algorithm::SplitLineJunction& j) -> Arachne::ExtrusionJunction {
+ Arachne::ExtrusionJunction res = current_ext[j.get_src_index()];
+ if (!j.is_src()) {
+ res.p = j.p;
+ }
+ return res;
+ };
+
+ for (const auto& p : splitted) {
+ if (p.clipped) {
+ segment.push_back(to_ex_junction(p));
+ } else {
+ if (segment.empty()) {
+ extrusion->junctions.push_back(to_ex_junction(p));
+ } else {
+ segment.push_back(to_ex_junction(p));
+ fuzzy_current_segment();
+ }
+ }
+ }
+ if (!segment.empty()) {
+ fuzzy_current_segment();
+ }
+ }
+ }
+ }
+ }
+}
+
+} // namespace Slic3r::Feature::FuzzySkin
diff --git a/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp
new file mode 100644
index 0000000000..be0b9750c1
--- /dev/null
+++ b/src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp
@@ -0,0 +1,23 @@
+#ifndef libslic3r_FuzzySkin_hpp_
+#define libslic3r_FuzzySkin_hpp_
+
+#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp"
+#include "libslic3r/Arachne/utils/ExtrusionLine.hpp"
+#include "libslic3r/PerimeterGenerator.hpp"
+
+namespace Slic3r::Feature::FuzzySkin {
+
+void fuzzy_polyline(Points& poly, bool closed, coordf_t slice_z, const FuzzySkinConfig& cfg);
+
+void fuzzy_extrusion_line(Arachne::ExtrusionJunctions& ext_lines, coordf_t slice_z, const FuzzySkinConfig& cfg);
+
+void group_region_by_fuzzify(PerimeterGenerator& g);
+
+bool should_fuzzify(const FuzzySkinConfig& config, int layer_id, size_t loop_idx, bool is_contour);
+
+Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perimeter_generator, size_t loop_idx, bool is_contour);
+void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerator& perimeter_generator, bool is_contour);
+
+} // namespace Slic3r::Feature::FuzzySkin
+
+#endif // libslic3r_FuzzySkin_hpp_
diff --git a/src/libslic3r/Interlocking/InterlockingGenerator.cpp b/src/libslic3r/Feature/Interlocking/InterlockingGenerator.cpp
similarity index 100%
rename from src/libslic3r/Interlocking/InterlockingGenerator.cpp
rename to src/libslic3r/Feature/Interlocking/InterlockingGenerator.cpp
diff --git a/src/libslic3r/Interlocking/InterlockingGenerator.hpp b/src/libslic3r/Feature/Interlocking/InterlockingGenerator.hpp
similarity index 99%
rename from src/libslic3r/Interlocking/InterlockingGenerator.hpp
rename to src/libslic3r/Feature/Interlocking/InterlockingGenerator.hpp
index aca74010cd..6f331fb8ef 100644
--- a/src/libslic3r/Interlocking/InterlockingGenerator.hpp
+++ b/src/libslic3r/Feature/Interlocking/InterlockingGenerator.hpp
@@ -4,7 +4,7 @@
#ifndef INTERLOCKING_GENERATOR_HPP
#define INTERLOCKING_GENERATOR_HPP
-#include "../Print.hpp"
+#include "libslic3r/Print.hpp"
#include "VoxelUtils.hpp"
namespace Slic3r {
diff --git a/src/libslic3r/Interlocking/VoxelUtils.cpp b/src/libslic3r/Feature/Interlocking/VoxelUtils.cpp
similarity index 98%
rename from src/libslic3r/Interlocking/VoxelUtils.cpp
rename to src/libslic3r/Feature/Interlocking/VoxelUtils.cpp
index ed012233ba..303473ae48 100644
--- a/src/libslic3r/Interlocking/VoxelUtils.cpp
+++ b/src/libslic3r/Feature/Interlocking/VoxelUtils.cpp
@@ -2,9 +2,9 @@
// CuraEngine is released under the terms of the AGPLv3 or higher.
#include "VoxelUtils.hpp"
-#include "../Geometry.hpp"
-#include "../Fill/FillRectilinear.hpp"
-#include "../Surface.hpp"
+#include "libslic3r/Geometry.hpp"
+#include "libslic3r/Fill/FillRectilinear.hpp"
+#include "libslic3r/Surface.hpp"
namespace Slic3r
{
diff --git a/src/libslic3r/Interlocking/VoxelUtils.hpp b/src/libslic3r/Feature/Interlocking/VoxelUtils.hpp
similarity index 99%
rename from src/libslic3r/Interlocking/VoxelUtils.hpp
rename to src/libslic3r/Feature/Interlocking/VoxelUtils.hpp
index 8496b3b233..0348b69598 100644
--- a/src/libslic3r/Interlocking/VoxelUtils.hpp
+++ b/src/libslic3r/Feature/Interlocking/VoxelUtils.hpp
@@ -6,8 +6,8 @@
#include
-#include "../Polygon.hpp"
-#include "../ExPolygon.hpp"
+#include "libslic3r/Polygon.hpp"
+#include "libslic3r/ExPolygon.hpp"
namespace Slic3r
{
diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp
index 7ca0bceb97..812e9f0154 100644
--- a/src/libslic3r/Format/3mf.cpp
+++ b/src/libslic3r/Format/3mf.cpp
@@ -112,6 +112,7 @@ static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count";
static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports";
static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam";
static constexpr const char* MMU_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation";
+static constexpr const char* FUZZY_SKIN_ATTR = "slic3rpe:fuzzy_skin";
static constexpr const char* KEY_ATTR = "key";
static constexpr const char* VALUE_ATTR = "value";
@@ -417,6 +418,7 @@ ModelVolumeType type_from_string(const std::string &s)
std::vector custom_supports;
std::vector custom_seam;
std::vector mmu_segmentation;
+ std::vector fuzzy_skin;
bool empty() { return vertices.empty() || triangles.empty(); }
@@ -426,6 +428,7 @@ ModelVolumeType type_from_string(const std::string &s)
custom_supports.clear();
custom_seam.clear();
mmu_segmentation.clear();
+ fuzzy_skin.clear();
}
};
@@ -1739,6 +1742,7 @@ ModelVolumeType type_from_string(const std::string &s)
m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR));
m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
+ m_curr_object.geometry.fuzzy_skin.push_back(get_attribute_value_string(attributes, num_attributes, FUZZY_SKIN_ATTR));
m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
return true;
}
@@ -2152,10 +2156,11 @@ ModelVolumeType type_from_string(const std::string &s)
if (has_transform)
volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object);
- // recreate custom supports, seam and mmu segmentation from previously loaded attribute
+ // recreate custom supports, seam, mm segmentation and fuzzy skin from previously loaded attribute
volume->supported_facets.reserve(triangles_count);
volume->seam_facets.reserve(triangles_count);
volume->mmu_segmentation_facets.reserve(triangles_count);
+ volume->fuzzy_skin_facets.reserve(triangles_count);
for (size_t i=0; iseam_facets.set_triangle_from_string(i, geometry.custom_seam[index]);
if (! geometry.mmu_segmentation[index].empty())
volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]);
+ if (! geometry.fuzzy_skin[index].empty())
+ volume->fuzzy_skin_facets.set_triangle_from_string(i, geometry.fuzzy_skin[index]);
}
volume->supported_facets.shrink_to_fit();
volume->seam_facets.shrink_to_fit();
volume->mmu_segmentation_facets.shrink_to_fit();
+ volume->fuzzy_skin_facets.shrink_to_fit();
// apply the remaining volume's metadata
for (const Metadata& metadata : volume_data.metadata) {
@@ -2824,6 +2832,15 @@ ModelVolumeType type_from_string(const std::string &s)
output_buffer += "\"";
}
+ std::string fuzzy_skin_data_string = volume->fuzzy_skin_facets.get_triangle_as_string(i);
+ if (!fuzzy_skin_data_string.empty()) {
+ output_buffer += " ";
+ output_buffer += FUZZY_SKIN_ATTR;
+ output_buffer += "=\"";
+ output_buffer += fuzzy_skin_data_string;
+ output_buffer += "\"";
+ }
+
output_buffer += "/>\n";
if (! flush())
diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp
index 00c588283d..67e20d81a8 100644
--- a/src/libslic3r/Format/bbs_3mf.cpp
+++ b/src/libslic3r/Format/bbs_3mf.cpp
@@ -276,6 +276,7 @@ static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count";
static constexpr const char* CUSTOM_SUPPORTS_ATTR = "paint_supports";
static constexpr const char* CUSTOM_SEAM_ATTR = "paint_seam";
static constexpr const char* MMU_SEGMENTATION_ATTR = "paint_color";
+static constexpr const char* FUZZY_SKIN_ATTR = "paint_fuzzy";
// BBS
static constexpr const char* FACE_PROPERTY_ATTR = "face_property";
@@ -661,6 +662,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
std::vector custom_supports;
std::vector custom_seam;
std::vector mmu_segmentation;
+ std::vector fuzzy_skin;
// BBS
std::vector face_properties;
@@ -680,6 +682,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
custom_supports.clear();
custom_seam.clear();
mmu_segmentation.clear();
+ fuzzy_skin.clear();
}
};
@@ -3602,6 +3605,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
m_curr_object->geometry.custom_supports.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR));
m_curr_object->geometry.custom_seam.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
m_curr_object->geometry.mmu_segmentation.push_back(bbs_get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
+ m_curr_object->geometry.fuzzy_skin.push_back(bbs_get_attribute_value_string(attributes, num_attributes, FUZZY_SKIN_ATTR));
// BBS
m_curr_object->geometry.face_properties.push_back(bbs_get_attribute_value_string(attributes, num_attributes, FACE_PROPERTY_ATTR));
}
@@ -4764,21 +4768,27 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
volume->supported_facets.reserve(triangles_count);
volume->seam_facets.reserve(triangles_count);
volume->mmu_segmentation_facets.reserve(triangles_count);
+ volume->fuzzy_skin_facets.reserve(triangles_count);
for (size_t i=0; igeometry.custom_supports.size());
assert(i < sub_object->geometry.custom_seam.size());
assert(i < sub_object->geometry.mmu_segmentation.size());
+ assert(i < sub_object->geometry.fuzzy_skin.size());
if (! sub_object->geometry.custom_supports[i].empty())
volume->supported_facets.set_triangle_from_string(i, sub_object->geometry.custom_supports[i]);
if (! sub_object->geometry.custom_seam[i].empty())
volume->seam_facets.set_triangle_from_string(i, sub_object->geometry.custom_seam[i]);
if (! sub_object->geometry.mmu_segmentation[i].empty())
volume->mmu_segmentation_facets.set_triangle_from_string(i, sub_object->geometry.mmu_segmentation[i]);
+ if (!sub_object->geometry.fuzzy_skin[i].empty())
+ volume->fuzzy_skin_facets.set_triangle_from_string(i, sub_object->geometry.fuzzy_skin[i]);
}
volume->supported_facets.shrink_to_fit();
volume->seam_facets.shrink_to_fit();
volume->mmu_segmentation_facets.shrink_to_fit();
volume->mmu_segmentation_facets.touch();
+ volume->fuzzy_skin_facets.shrink_to_fit();
+ volume->fuzzy_skin_facets.touch();
}
volume->set_type(volume_data->part_type);
@@ -5237,6 +5247,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
current_object->geometry.custom_supports.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR));
current_object->geometry.custom_seam.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
current_object->geometry.mmu_segmentation.push_back(bbs_get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
+ current_object->geometry.fuzzy_skin.push_back(bbs_get_attribute_value_string(attributes, num_attributes, FUZZY_SKIN_ATTR));
// BBS
current_object->geometry.face_properties.push_back(bbs_get_attribute_value_string(attributes, num_attributes, FACE_PROPERTY_ATTR));
}
@@ -6616,7 +6627,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
const ModelVolume* shared_volume = iter->second.second;
if ((shared_volume->supported_facets.equals(volume->supported_facets))
&& (shared_volume->seam_facets.equals(volume->seam_facets))
- && (shared_volume->mmu_segmentation_facets.equals(volume->mmu_segmentation_facets)))
+ && (shared_volume->mmu_segmentation_facets.equals(volume->mmu_segmentation_facets))
+ && (shared_volume->fuzzy_skin_facets.equals(volume->fuzzy_skin_facets)))
{
auto data = iter->second.first;
const_cast<_BBS_3MF_Exporter *>(this)->m_volume_paths.insert({volume, {data->sub_path, data->volumes_objectID.find(iter->second.second)->second}});
@@ -7022,6 +7034,15 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
output_buffer += "\"";
}
+ std::string fuzzy_skin_painting_data_string = volume->fuzzy_skin_facets.get_triangle_as_string(i);
+ if (!fuzzy_skin_painting_data_string.empty()) {
+ output_buffer += " ";
+ output_buffer += FUZZY_SKIN_ATTR;
+ output_buffer += "=\"";
+ output_buffer += fuzzy_skin_painting_data_string;
+ output_buffer += "\"";
+ }
+
// BBS
if (i < its.properties.size()) {
std::string prop_str = its.properties[i].to_string();
diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp
index e419b5497b..326d1fffbe 100644
--- a/src/libslic3r/Layer.cpp
+++ b/src/libslic3r/Layer.cpp
@@ -136,6 +136,42 @@ ExPolygons Layer::merged(float offset_scaled) const
return out;
}
+bool Layer::is_perimeter_compatible(const PrintRegion& a, const PrintRegion& b)
+{
+ const PrintRegionConfig& config = a.config();
+ const PrintRegionConfig& other_config = b.config();
+
+ return config.wall_filament == other_config.wall_filament
+ && config.wall_loops == other_config.wall_loops
+ && config.wall_sequence == other_config.wall_sequence
+ && config.is_infill_first == other_config.is_infill_first
+ && config.inner_wall_speed == other_config.inner_wall_speed
+ && config.outer_wall_speed == other_config.outer_wall_speed
+ && config.small_perimeter_speed == other_config.small_perimeter_speed
+ && config.gap_infill_speed.value == other_config.gap_infill_speed.value
+ && config.filter_out_gap_fill.value == other_config.filter_out_gap_fill.value
+ && config.detect_overhang_wall == other_config.detect_overhang_wall
+ && config.overhang_reverse == other_config.overhang_reverse
+ && config.overhang_reverse_threshold == other_config.overhang_reverse_threshold
+ && config.wall_direction == other_config.wall_direction
+ && config.opt_serialize("inner_wall_line_width") == other_config.opt_serialize("inner_wall_line_width")
+ && config.opt_serialize("outer_wall_line_width") == other_config.opt_serialize("outer_wall_line_width")
+ && config.detect_thin_wall == other_config.detect_thin_wall
+ && config.infill_wall_overlap == other_config.infill_wall_overlap
+ && config.top_bottom_infill_wall_overlap == other_config.top_bottom_infill_wall_overlap
+ && config.seam_slope_type == other_config.seam_slope_type
+ && config.seam_slope_conditional == other_config.seam_slope_conditional
+ && config.scarf_angle_threshold == other_config.scarf_angle_threshold
+ && config.scarf_overhang_threshold == other_config.scarf_overhang_threshold
+ && config.scarf_joint_speed == other_config.scarf_joint_speed
+ && config.scarf_joint_flow_ratio == other_config.scarf_joint_flow_ratio
+ && config.seam_slope_start_height == other_config.seam_slope_start_height
+ && config.seam_slope_entire_loop == other_config.seam_slope_entire_loop
+ && config.seam_slope_min_length == other_config.seam_slope_min_length
+ && config.seam_slope_steps == other_config.seam_slope_steps
+ && config.seam_slope_inner_walls == other_config.seam_slope_inner_walls;
+}
+
// Here the perimeters are created cummulatively for all layer regions sharing the same parameters influencing the perimeters.
// The perimeter paths and the thin fills (ExtrusionEntityCollection) are assigned to the first compatible layer region.
// The resulting fill surface is split back among the originating regions.
@@ -157,7 +193,7 @@ void Layer::make_perimeters()
continue;
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id;
done[region_id] = true;
- const PrintRegionConfig &config = (*layerm)->region().config();
+ const PrintRegion &this_region = (*layerm)->region();
// find compatible regions
LayerRegionPtrs layerms;
@@ -165,36 +201,8 @@ void Layer::make_perimeters()
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();
- if (config.wall_filament == other_config.wall_filament
- && config.wall_loops == other_config.wall_loops
- && config.wall_sequence == other_config.wall_sequence
- && config.is_infill_first == other_config.is_infill_first
- && config.inner_wall_speed == other_config.inner_wall_speed
- && config.outer_wall_speed == other_config.outer_wall_speed
- && config.small_perimeter_speed == other_config.small_perimeter_speed
- && config.gap_infill_speed.value == other_config.gap_infill_speed.value
- && config.filter_out_gap_fill.value == other_config.filter_out_gap_fill.value
- && config.detect_overhang_wall == other_config.detect_overhang_wall
- && config.overhang_reverse == other_config.overhang_reverse
- && config.overhang_reverse_threshold == other_config.overhang_reverse_threshold
- && config.wall_direction == other_config.wall_direction
- && config.opt_serialize("inner_wall_line_width") == other_config.opt_serialize("inner_wall_line_width")
- && config.opt_serialize("outer_wall_line_width") == other_config.opt_serialize("outer_wall_line_width")
- && config.detect_thin_wall == other_config.detect_thin_wall
- && config.infill_wall_overlap == other_config.infill_wall_overlap
- && config.top_bottom_infill_wall_overlap == other_config.top_bottom_infill_wall_overlap
- && config.seam_slope_type == other_config.seam_slope_type
- && config.seam_slope_conditional == other_config.seam_slope_conditional
- && config.scarf_angle_threshold == other_config.scarf_angle_threshold
- && config.scarf_overhang_threshold == other_config.scarf_overhang_threshold
- && config.scarf_joint_speed == other_config.scarf_joint_speed
- && config.scarf_joint_flow_ratio == other_config.scarf_joint_flow_ratio
- && config.seam_slope_start_height == other_config.seam_slope_start_height
- && config.seam_slope_entire_loop == other_config.seam_slope_entire_loop
- && config.seam_slope_min_length == other_config.seam_slope_min_length
- && config.seam_slope_steps == other_config.seam_slope_steps
- && config.seam_slope_inner_walls == other_config.seam_slope_inner_walls)
+ const PrintRegion &other_region = other_layerm->region();
+ if (is_perimeter_compatible(this_region, other_region))
{
other_layerm->perimeters.clear();
other_layerm->fills.clear();
diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp
index 48f1ca95e2..1c325034cd 100644
--- a/src/libslic3r/Layer.hpp
+++ b/src/libslic3r/Layer.hpp
@@ -180,6 +180,9 @@ public:
for (const LayerRegion *layerm : m_regions) if (layerm->slices.any_bottom_contains(item)) return true;
return false;
}
+
+ // Whether two regions can be printed in a continues perimeter
+ static bool is_perimeter_compatible(const PrintRegion& a, const PrintRegion& b);
void make_perimeters();
// Phony version of make_fills() without parameters for Perl integration only.
void make_fills() { this->make_fills(nullptr, nullptr); }
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index b9d3b7db3e..4edec076af 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -1070,6 +1070,11 @@ bool Model::is_mm_painted() const
return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_mm_painted(); });
}
+bool Model::is_fuzzy_skin_painted() const
+{
+ return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_fuzzy_skin_painted(); });
+}
+
static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART)
{
@@ -1336,6 +1341,11 @@ bool ModelObject::is_mm_painted() const
return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); });
}
+bool ModelObject::is_fuzzy_skin_painted() const
+{
+ return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_fuzzy_skin_painted(); });
+}
+
void ModelObject::sort_volumes(bool full_sort)
{
// sort volumes inside the object to order "Model Part, Negative Volume, Modifier, Support Blocker and Support Enforcer. "
@@ -1818,6 +1828,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
vol->supported_facets.assign(volume->supported_facets);
vol->seam_facets.assign(volume->seam_facets);
vol->mmu_segmentation_facets.assign(volume->mmu_segmentation_facets);
+ vol->fuzzy_skin_facets.assign(volume->fuzzy_skin_facets);
// Perform conversion only if the target "imperial" state is different from the current one.
// This check supports conversion of "mixed" set of volumes, each with different "imperial" state.
@@ -1929,6 +1940,7 @@ void ModelVolume::reset_extra_facets()
this->supported_facets.reset();
this->seam_facets.reset();
this->mmu_segmentation_facets.reset();
+ this->fuzzy_skin_facets.reset();
}
static void invalidate_translations(ModelObject* object, const ModelInstance* src_instance)
@@ -2658,6 +2670,7 @@ size_t ModelVolume::split(unsigned int max_extruders)
this->exterior_facets.reset();
this->supported_facets.reset();
this->seam_facets.reset();
+ this->fuzzy_skin_facets.reset();
}
else
this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(mesh)));
@@ -2718,6 +2731,7 @@ void ModelVolume::assign_new_unique_ids_recursive()
supported_facets.set_new_unique_id();
seam_facets.set_new_unique_id();
mmu_segmentation_facets.set_new_unique_id();
+ fuzzy_skin_facets.set_new_unique_id();
}
void ModelVolume::rotate(double angle, Axis axis)
@@ -3579,6 +3593,13 @@ bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObjec
[](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mmu_segmentation_facets.timestamp_matches(mv_new.mmu_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_brim_points_data_changed(const ModelObject& mo, const ModelObject& mo_new)
{
if (mo.brim_points.size() != mo_new.brim_points.size())
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index 3866e252b7..0456448e65 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -426,6 +426,8 @@ public:
bool is_seam_painted() const;
// Checks if any of object volume is painted using the multi-material painting gizmo.
bool is_mm_painted() const;
+ // Checks if any of object volume is painted using the fuzzy skin painting gizmo.
+ bool is_fuzzy_skin_painted() const;
// This object may have a varying layer height by painting or by a table.
// Even if true is returned, the layer height profile may be "flat" with no difference to default layering.
bool has_custom_layering() const
@@ -868,6 +870,9 @@ public:
// List of mesh facets painted for MMU segmentation.
FacetsAnnotation mmu_segmentation_facets;
+ // List of mesh facets painted for fuzzy skin.
+ FacetsAnnotation fuzzy_skin_facets;
+
// BBS: quick access for volume extruders, 1 based
mutable std::vector mmuseg_extruders;
mutable Timestamp mmuseg_ts;
@@ -990,11 +995,13 @@ public:
this->supported_facets.set_new_unique_id();
this->seam_facets.set_new_unique_id();
this->mmu_segmentation_facets.set_new_unique_id();
+ this->fuzzy_skin_facets.set_new_unique_id();
}
bool is_fdm_support_painted() const { return !this->supported_facets.empty(); }
bool is_seam_painted() const { return !this->seam_facets.empty(); }
bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); }
+ bool is_fuzzy_skin_painted() const { return !this->fuzzy_skin_facets.empty(); }
// Orca: Implement prusa's filament shrink compensation approach
// Returns 0-based indices of extruders painted by multi-material painting gizmo.
@@ -1046,10 +1053,12 @@ private:
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid());
+ assert(this->fuzzy_skin_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id());
+ assert(this->id() != this->fuzzy_skin_facets.id());
if (mesh.facets_count() > 1)
calculate_convex_hull();
}
@@ -1060,10 +1069,12 @@ private:
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid());
+ assert(this->fuzzy_skin_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id());
+ assert(this->id() != this->fuzzy_skin_facets.id());
}
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull, ModelVolumeType type = ModelVolumeType::MODEL_PART) :
m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(type), object(object) {
@@ -1072,10 +1083,12 @@ private:
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid());
+ assert(this->fuzzy_skin_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id());
+ assert(this->id() != this->fuzzy_skin_facets.id());
}
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
@@ -1084,13 +1097,14 @@ private:
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets),
- cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
+ fuzzy_skin_facets(other.fuzzy_skin_facets), cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
{
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid());
+ assert(this->fuzzy_skin_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
@@ -1100,6 +1114,7 @@ private:
assert(this->supported_facets.id() == other.supported_facets.id());
assert(this->seam_facets.id() == other.seam_facets.id());
assert(this->mmu_segmentation_facets.id() == other.mmu_segmentation_facets.id());
+ assert(this->fuzzy_skin_facets.id() == other.fuzzy_skin_facets.id());
this->set_material_id(other.material_id());
}
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
@@ -1112,10 +1127,12 @@ private:
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid());
+ assert(this->fuzzy_skin_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id());
+ assert(this->id() != this->fuzzy_skin_facets.id());
assert(this->id() != other.id());
assert(this->config.id() == other.config.id());
this->set_material_id(other.material_id());
@@ -1127,10 +1144,12 @@ private:
assert(this->supported_facets.id() != other.supported_facets.id());
assert(this->seam_facets.id() != other.seam_facets.id());
assert(this->mmu_segmentation_facets.id() != other.mmu_segmentation_facets.id());
+ assert(this->fuzzy_skin_facets.id() != other.fuzzy_skin_facets.id());
assert(this->id() != this->config.id());
assert(this->supported_facets.empty());
assert(this->seam_facets.empty());
assert(this->mmu_segmentation_facets.empty());
+ assert(this->fuzzy_skin_facets.empty());
}
ModelVolume& operator=(ModelVolume &rhs) = delete;
@@ -1138,12 +1157,13 @@ private:
friend class cereal::access;
friend class UndoRedo::StackImpl;
// Used for deserialization, therefore no IDs are allocated.
- ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), object(nullptr) {
+ ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), fuzzy_skin_facets(-1), object(nullptr) {
assert(this->id().invalid());
assert(this->config.id().invalid());
assert(this->supported_facets.id().invalid());
assert(this->seam_facets.id().invalid());
assert(this->mmu_segmentation_facets.id().invalid());
+ assert(this->fuzzy_skin_facets.id().invalid());
}
template void load(Archive &ar) {
bool has_convex_hull;
@@ -1161,6 +1181,8 @@ private:
t = mmu_segmentation_facets.timestamp();
cereal::load_by_value(ar, mmu_segmentation_facets);
mesh_changed |= t != mmu_segmentation_facets.timestamp();
+ cereal::load_by_value(ar, fuzzy_skin_facets);
+ mesh_changed |= t != fuzzy_skin_facets.timestamp();
cereal::load_by_value(ar, config);
cereal::load(ar, text_configuration);
cereal::load(ar, emboss_shape);
@@ -1181,6 +1203,7 @@ private:
cereal::save_by_value(ar, supported_facets);
cereal::save_by_value(ar, seam_facets);
cereal::save_by_value(ar, mmu_segmentation_facets);
+ cereal::save_by_value(ar, fuzzy_skin_facets);
cereal::save_by_value(ar, config);
cereal::save(ar, text_configuration);
cereal::save(ar, emboss_shape);
@@ -1657,6 +1680,8 @@ public:
bool is_seam_painted() const;
// Checks if any of objects is painted using the multi-material painting gizmo.
bool is_mm_painted() const;
+ // Checks if any of objects is painted using the fuzzy skin painting gizmo.
+ bool is_fuzzy_skin_painted() const;
std::unique_ptr calib_pa_pattern;
@@ -1715,6 +1740,10 @@ bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo
// The function assumes that volumes list is synchronized.
extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new);
+// Test whether the now ModelObject has newer fuzzy skin data than the old one.
+// The function assumes that volumes list is synchronized.
+extern bool model_fuzzy_skin_data_changed(const ModelObject &mo, const ModelObject &mo_new);
+
bool model_brim_points_data_changed(const ModelObject& mo, const ModelObject& mo_new);
// If the model has multi-part objects, then it is currently not supported by the SLA mode.
diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp
index 492e70b730..4946ad45c8 100644
--- a/src/libslic3r/MultiMaterialSegmentation.cpp
+++ b/src/libslic3r/MultiMaterialSegmentation.cpp
@@ -408,7 +408,7 @@ static inline Polygon to_polygon(const std::vector> &id
return poly_out;
}
-static std::vector extract_colored_segments(const MMU_Graph &graph, const size_t num_extruders)
+static std::vector extract_colored_segments(const MMU_Graph& graph, const size_t num_facets_states)
{
std::vector used_arcs(graph.arcs.size(), false);
@@ -416,7 +416,7 @@ static std::vector extract_colored_segments(const MMU_Graph &graph,
return std::all_of(node.arc_idxs.cbegin(), node.arc_idxs.cend(), [&used_arcs](const size_t &arc_idx) -> bool { return used_arcs[arc_idx]; });
};
- std::vector expolygons_segments(num_extruders + 1);
+ std::vector expolygons_segments(num_facets_states);
for (size_t node_idx = 0; node_idx < graph.all_border_points; ++node_idx) {
const MMU_Graph::Node &node = graph.nodes[node_idx];
@@ -1141,151 +1141,13 @@ static void remove_multiple_edges_in_vertex(const VD::vertex_type &vertex) {
}
}
-#if (0)
-// Returns list of ExPolygons for each extruder + 1 for default unpainted regions.
-// It iterates through all nodes on the border between two different colors, and from this point,
-// start selection always left most edges for every node to construct CCW polygons.
-static std::vector extract_colored_segments(const std::vector &colored_polygons,
- const size_t num_extruders,
- const size_t layer_idx)
-{
- const ColoredLines colored_lines = to_lines(colored_polygons);
- const BoundingBox bbox = get_extents(colored_polygons);
-
- auto get_next_contour_line = [&colored_polygons](const ColoredLine &line) -> const ColoredLine & {
- size_t contour_line_size = colored_polygons[line.poly_idx].size();
- size_t contour_next_idx = (line.local_line_idx + 1) % contour_line_size;
- return colored_polygons[line.poly_idx][contour_next_idx];
- };
-
- Voronoi::VD vd;
- vd.construct_voronoi(colored_lines.begin(), colored_lines.end());
-
- // First, mark each Voronoi vertex on the input polygon to prevent it from being deleted later.
- for (const Voronoi::VD::cell_type &cell : vd.cells()) {
- if (cell.is_degenerate() || !cell.contains_segment())
- continue;
-
- if (const Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, colored_lines.begin(), colored_lines.end()); cell_range.is_valid())
- cell_range.edge_begin->vertex0()->color(VD_ANNOTATION::VERTEX_ON_CONTOUR);
- }
-
- // Second, remove all Voronoi vertices that are outside the bounding box of input polygons.
- // Such Voronoi vertices are definitely not inside of input polygons, so we don't care about them.
- for (const Voronoi::VD::vertex_type &vertex : vd.vertices()) {
- if (vertex.color() == VD_ANNOTATION::DELETED || vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR)
- continue;
-
- if (!Geometry::VoronoiUtils::is_in_range(vertex) || !bbox.contains(Geometry::VoronoiUtils::to_point(vertex).cast()))
- delete_vertex_deep(vertex);
- }
-
- // Third, remove all Voronoi edges that are infinite.
- for (const Voronoi::VD::edge_type &edge : vd.edges()) {
- if (edge.color() != VD_ANNOTATION::DELETED && edge.is_infinite()) {
- edge.color(VD_ANNOTATION::DELETED);
- edge.twin()->color(VD_ANNOTATION::DELETED);
-
- if (edge.vertex0() != nullptr && can_vertex_be_deleted(*edge.vertex0()))
- delete_vertex_deep(*edge.vertex0());
-
- if (edge.vertex1() != nullptr && can_vertex_be_deleted(*edge.vertex1()))
- delete_vertex_deep(*edge.vertex1());
- }
- }
-
- // Fourth, remove all edges that point outward from the input polygon.
- for (Voronoi::VD::cell_type cell : vd.cells()) {
- if (cell.is_degenerate() || !cell.contains_segment())
- continue;
-
- if (const Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, colored_lines.begin(), colored_lines.end()); cell_range.is_valid()) {
- const ColoredLine ¤t_line = Geometry::VoronoiUtils::get_source_segment(cell, colored_lines.begin(), colored_lines.end());
- const ColoredLine &next_line = get_next_contour_line(current_line);
-
- const VD::edge_type *edge = cell_range.edge_begin;
- do {
- if (edge->color() == VD_ANNOTATION::DELETED)
- continue;
-
- if (!points_inside(current_line.line, next_line.line, Geometry::VoronoiUtils::to_point(edge->vertex1()).cast())) {
- edge->color(VD_ANNOTATION::DELETED);
- edge->twin()->color(VD_ANNOTATION::DELETED);
- delete_vertex_deep(*edge->vertex1());
- }
- } while (edge = edge->prev()->twin(), edge != cell_range.edge_begin);
- }
- }
-
- // Fifth, if a Voronoi vertex has more than one Voronoi edge, remove all but one of them based on heuristics.
- for (const Voronoi::VD::vertex_type &vertex : vd.vertices()) {
- if (vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR)
- remove_multiple_edges_in_vertex(vertex);
- }
-
-#ifdef MM_SEGMENTATION_DEBUG_GRAPH
- {
- static int iRun = 0;
- export_graph_to_svg(debug_out_path("mm-graph-%d-%d.svg", layer_idx, iRun++), vd, colored_polygons);
- }
-#endif // MM_SEGMENTATION_DEBUG_GRAPH
-
- // Sixth, extract the colored segments from the annotated Voronoi diagram.
- std::vector segmented_expolygons_per_extruder(num_extruders + 1);
- for (const Voronoi::VD::cell_type &cell : vd.cells()) {
- if (cell.is_degenerate() || !cell.contains_segment())
- continue;
-
- if (const Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, colored_lines.begin(), colored_lines.end()); cell_range.is_valid()) {
- if (cell_range.edge_begin->vertex0()->color() != VD_ANNOTATION::VERTEX_ON_CONTOUR)
- continue;
-
- const ColoredLine source_segment = Geometry::VoronoiUtils::get_source_segment(cell, colored_lines.begin(), colored_lines.end());
-
- Polygon segmented_polygon;
- segmented_polygon.points.emplace_back(source_segment.line.b);
-
- // We have ensured that each segmented_polygon have to start at edge_begin->vertex0() and end at edge_end->vertex1().
- const VD::edge_type *edge = cell_range.edge_begin;
- do {
- if (edge->color() == VD_ANNOTATION::DELETED)
- continue;
-
- const VD::vertex_type &next_vertex = *edge->vertex1();
- segmented_polygon.points.emplace_back(Geometry::VoronoiUtils::to_point(next_vertex).cast());
- edge->color(VD_ANNOTATION::DELETED);
-
- if (next_vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR || next_vertex.color() == VD_ANNOTATION::DELETED) {
- assert(next_vertex.color() == VD_ANNOTATION::VERTEX_ON_CONTOUR);
- break;
- }
-
- edge = edge->twin();
- } while (edge = edge->twin()->next(), edge != cell_range.edge_begin);
-
- if (edge->vertex1() != cell_range.edge_end->vertex1())
- continue;
-
- cell_range.edge_begin->vertex0()->color(VD_ANNOTATION::DELETED);
- segmented_expolygons_per_extruder[source_segment.color].emplace_back(std::move(segmented_polygon));
- }
- }
-
- // Merge all polygons together for each extruder
- for (auto &segmented_expolygons : segmented_expolygons_per_extruder)
- segmented_expolygons = union_ex(segmented_expolygons);
-
- return segmented_expolygons_per_extruder;
-}
-#endif
-
static void cut_segmented_layers(const std::vector &input_expolygons,
std::vector> &segmented_regions,
const float cut_width,
const float interlocking_depth,
const std::function &throw_on_cancel_callback)
{
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - cutting segmented layers in parallel - begin";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - cutting segmented layers in parallel - begin";
const float interlocking_cut_width = interlocking_depth > 0.f ? std::max(cut_width - interlocking_depth, 0.f) : 0.f;
tbb::parallel_for(tbb::blocked_range(0, segmented_regions.size()),
[&segmented_regions, &input_expolygons, &cut_width, &interlocking_depth, &throw_on_cancel_callback](const tbb::blocked_range &range) {
@@ -1302,7 +1164,7 @@ static void cut_segmented_layers(const std::vector &input_exp
}
}
}); // end of parallel_for
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - cutting segmented layers in parallel - end";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - cutting segmented layers in parallel - end";
}
static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d &trafo)
@@ -1315,13 +1177,14 @@ static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d
//#define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
-// Returns MMU segmentation of top and bottom layers based on painting in MMU segmentation gizmo
-static inline std::vector> mmu_segmentation_top_and_bottom_layers(const PrintObject &print_object,
- const std::vector &input_expolygons,
- const std::function &throw_on_cancel_callback)
+// Returns segmentation of top and bottom layers based on painting in segmentation gizmos.
+static inline std::vector> segmentation_top_and_bottom_layers(const PrintObject &print_object,
+ const std::vector &input_expolygons,
+ const std::function &extract_facets_info,
+ const size_t num_facets_states,
+ const std::function &throw_on_cancel_callback)
{
- // BBS
- const size_t num_extruders = print_object.print()->config().filament_colour.size() + 1;
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Segmentation of top and bottom layers in parallel - Begin";
const size_t num_layers = input_expolygons.size();
const ConstLayerPtrsAdaptor layers = print_object.layers();
@@ -1338,7 +1201,7 @@ static inline std::vector> mmu_segmentation_top_and_bott
// Project upwards pointing painted triangles over top surfaces,
// project downards pointing painted triangles over bottom surfaces.
- std::vector> top_raw(num_extruders), bottom_raw(num_extruders);
+ std::vector> top_raw(num_facets_states), bottom_raw(num_facets_states);
std::vector zs = zs_from_layers(layers);
Transform3d object_trafo = print_object.trafo_centered();
@@ -1350,8 +1213,8 @@ static inline std::vector> mmu_segmentation_top_and_bott
for (const ModelVolume *mv : print_object.model_object()->volumes)
if (mv->is_model_part()) {
const Transform3d volume_trafo = object_trafo * mv->get_matrix();
- for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) {
- const indexed_triangle_set painted = mv->mmu_segmentation_facets.get_facets_strict(*mv, EnforcerBlockerType(extruder_idx));
+ for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) {
+ const indexed_triangle_set painted = extract_facets_info(*mv).facets_annotation.get_facets_strict(*mv, EnforcerBlockerType(extruder_idx));
#ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM
{
static int iRun = 0;
@@ -1400,8 +1263,8 @@ static inline std::vector> mmu_segmentation_top_and_bott
}
}
- auto filter_out_small_polygons = [&num_extruders, &num_layers](std::vector> &raw_surfaces, double min_area) -> void {
- for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx)
+ auto filter_out_small_polygons = [&num_facets_states, &num_layers](std::vector> &raw_surfaces, double min_area) -> void {
+ for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx)
if (!raw_surfaces[extruder_idx].empty())
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx)
if (!raw_surfaces[extruder_idx][layer_idx].empty())
@@ -1438,7 +1301,7 @@ static inline std::vector> mmu_segmentation_top_and_bott
// When the upper surface of an object is occluded, it should no longer be considered the upper surface
{
- 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 < layers.size(); ++layer_idx) {
if (!top_raw[extruder_idx].empty() && !top_raw[extruder_idx][layer_idx].empty() && layer_idx + 1 < layers.size()) {
top_raw[extruder_idx][layer_idx] = diff(top_raw[extruder_idx][layer_idx], input_expolygons[layer_idx + 1]);
@@ -1450,16 +1313,16 @@ static inline std::vector> mmu_segmentation_top_and_bott
}
}
- std::vector> triangles_by_color_bottom(num_extruders);
- std::vector> triangles_by_color_top(num_extruders);
- triangles_by_color_bottom.assign(num_extruders, std::vector(num_layers * 2));
- triangles_by_color_top.assign(num_extruders, std::vector(num_layers * 2));
+ std::vector> triangles_by_color_bottom(num_facets_states);
+ std::vector> triangles_by_color_top(num_facets_states);
+ triangles_by_color_bottom.assign(num_facets_states, std::vector(num_layers * 2));
+ triangles_by_color_top.assign(num_facets_states, std::vector(num_layers * 2));
// BBS: use shell_triangles_by_color_bottom & shell_triangles_by_color_top to save the top and bottom embedded layers's color information
- std::vector> shell_triangles_by_color_bottom(num_extruders);
- std::vector> shell_triangles_by_color_top(num_extruders);
- shell_triangles_by_color_bottom.assign(num_extruders, std::vector(num_layers * 2));
- shell_triangles_by_color_top.assign(num_extruders, std::vector(num_layers * 2));
+ std::vector> shell_triangles_by_color_bottom(num_facets_states);
+ std::vector> shell_triangles_by_color_top(num_facets_states);
+ shell_triangles_by_color_bottom.assign(num_facets_states, std::vector(num_layers * 2));
+ shell_triangles_by_color_top.assign(num_facets_states, std::vector(num_layers * 2));
struct LayerColorStat {
// Number of regions for a queried color.
@@ -1504,13 +1367,13 @@ static inline std::vector> mmu_segmentation_top_and_bott
return out;
};
- tbb::parallel_for(tbb::blocked_range(0, num_layers, granularity), [&granularity, &num_layers, &num_extruders, &layer_color_stat, &top_raw, &triangles_by_color_top,
+ tbb::parallel_for(tbb::blocked_range(0, num_layers, granularity), [&granularity, &num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top,
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom,
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range &range) {
size_t group_idx = range.begin() / granularity;
size_t layer_idx_offset = (group_idx & 1) * num_layers;
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
- for (size_t color_idx = 0; color_idx < num_extruders; ++ color_idx) {
+ for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) {
throw_on_cancel_callback();
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
if (std::vector &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
@@ -1557,8 +1420,8 @@ static inline std::vector> mmu_segmentation_top_and_bott
}
});
- std::vector> triangles_by_color_merged(num_extruders);
- triangles_by_color_merged.assign(num_extruders, std::vector(num_layers));
+ std::vector> triangles_by_color_merged(num_facets_states);
+ triangles_by_color_merged.assign(num_facets_states, std::vector(num_layers));
tbb::parallel_for(tbb::blocked_range(0, num_layers), [&triangles_by_color_merged, &triangles_by_color_bottom, &triangles_by_color_top, &num_layers, &throw_on_cancel_callback,
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
@@ -1602,6 +1465,7 @@ static inline std::vector> mmu_segmentation_top_and_bott
triangles_by_color_merged[0][layer_idx] = diff_ex(triangles_by_color_merged[0][layer_idx], painted_regions);
}
});
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Segmentation of top and bottom layers in parallel - End";
return triangles_by_color_merged;
}
@@ -1958,34 +1822,37 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto
}
}
-static std::vector> merge_segmented_layers(
- const std::vector> &segmented_regions,
- std::vector> &&top_and_bottom_layers,
- const size_t num_extruders,
- const std::function &throw_on_cancel_callback)
+static std::vector> merge_segmented_layers(const std::vector> &segmented_regions,
+ std::vector> &&top_and_bottom_layers,
+ const size_t num_facets_states,
+ const std::function &throw_on_cancel_callback)
{
const size_t num_layers = segmented_regions.size();
std::vector> segmented_regions_merged(num_layers);
- segmented_regions_merged.assign(num_layers, std::vector(num_extruders));
- assert(num_extruders + 1 == top_and_bottom_layers.size());
+ segmented_regions_merged.assign(num_layers, std::vector(num_facets_states - 1));
+ assert(!top_and_bottom_layers.size() || num_facets_states == top_and_bottom_layers.size());
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - merging segmented layers in parallel - begin";
- tbb::parallel_for(tbb::blocked_range(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range &range) {
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin";
+ tbb::parallel_for(tbb::blocked_range(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
- assert(segmented_regions[layer_idx].size() == num_extruders + 1);
+ assert(segmented_regions[layer_idx].size() == num_facets_states);
// Zero is skipped because it is the default color of the volume
- for (size_t extruder_id = 1; extruder_id < num_extruders + 1; ++extruder_id) {
+ for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) {
throw_on_cancel_callback();
if (!segmented_regions[layer_idx][extruder_id].empty()) {
ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id];
- for (const std::vector &top_and_bottom_by_extruder : top_and_bottom_layers)
- if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty())
- segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]);
+ if (!top_and_bottom_layers.empty()) {
+ for (const std::vector &top_and_bottom_by_extruder : top_and_bottom_layers) {
+ if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty()) {
+ segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]);
+ }
+ }
+ }
segmented_regions_merged[layer_idx][extruder_id - 1] = std::move(segmented_regions_trimmed);
}
- if (!top_and_bottom_layers[extruder_id][layer_idx].empty()) {
+ if (!top_and_bottom_layers.empty() && !top_and_bottom_layers[extruder_id][layer_idx].empty()) {
bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty();
append(segmented_regions_merged[layer_idx][extruder_id - 1], top_and_bottom_layers[extruder_id][layer_idx]);
@@ -1996,7 +1863,7 @@ static std::vector> merge_segmented_layers(
}
}
}); // end of parallel_for
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - merging segmented layers in parallel - end";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - End";
return segmented_regions_merged;
}
@@ -2085,12 +1952,18 @@ static bool has_layer_only_one_color(const std::vector &colored_po
return true;
}
-std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback)
+std::vector> segmentation_by_painting(const PrintObject &print_object,
+ const std::function &extract_facets_info,
+ const size_t num_facets_states,
+ const float segmentation_max_width,
+ const float segmentation_interlocking_depth,
+ const bool segmentation_interlocking_beam,
+ const IncludeTopAndBottomLayers include_top_and_bottom_layers,
+ const std::function &throw_on_cancel_callback)
{
- const size_t num_extruders = print_object.print()->config().filament_colour.size();
const size_t num_layers = print_object.layers().size();
std::vector> segmented_regions(num_layers);
- segmented_regions.assign(num_layers, std::vector(num_extruders + 1));
+ segmented_regions.assign(num_layers, std::vector(num_facets_states));
std::vector> painted_lines(num_layers);
std::array painted_lines_mutex;
std::vector edge_grids(num_layers);
@@ -2104,7 +1977,7 @@ std::vector> multi_material_segmentation_by_painting(con
#endif // MM_SEGMENTATION_DEBUG
// Merge all regions and remove small holes
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - slices preparation in parallel - begin";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Slices preprocessing in parallel - Begin";
tbb::parallel_for(tbb::blocked_range(0, num_layers), [&layers, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
@@ -2131,7 +2004,7 @@ std::vector> multi_material_segmentation_by_painting(con
#endif // MM_SEGMENTATION_DEBUG_INPUT
}
}); // end of parallel_for
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - slices preparation in parallel - end";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Slices preprocessing in parallel - End";
std::vector layer_bboxes(num_layers);
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
@@ -2154,12 +2027,13 @@ std::vector> multi_material_segmentation_by_painting(con
edge_grids[layer_idx].create(input_expolygons[layer_idx], coord_t(scale_(10.)));
}
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - projection of painted triangles - begin";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Projection of painted triangles - Begin";
for (const ModelVolume *mv : print_object.model_object()->volumes) {
- tbb::parallel_for(tbb::blocked_range(1, num_extruders + 1), [&mv, &print_object, &layers, &edge_grids, &painted_lines, &painted_lines_mutex, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) {
+ const ModelVolumeFacetsInfo facets_info = extract_facets_info(*mv);
+ tbb::parallel_for(tbb::blocked_range(1, num_facets_states), [&mv, &print_object, &facets_info, &layers, &edge_grids, &painted_lines, &painted_lines_mutex, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) {
for (size_t extruder_idx = range.begin(); extruder_idx < range.end(); ++extruder_idx) {
throw_on_cancel_callback();
- const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx));
+ const indexed_triangle_set custom_facets = facets_info.facets_annotation.get_facets(*mv, EnforcerBlockerType(extruder_idx));
if (!mv->is_model_part() || custom_facets.indices.empty())
continue;
@@ -2245,12 +2119,12 @@ std::vector> multi_material_segmentation_by_painting(con
}
}); // end of parallel_for
}
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - projection of painted triangles - end";
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - painted layers count: "
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - projection of painted triangles - end";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - painted layers count: "
<< std::count_if(painted_lines.begin(), painted_lines.end(), [](const std::vector &pl) { return !pl.empty(); });
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - layers segmentation in parallel - begin";
- tbb::parallel_for(tbb::blocked_range(0, num_layers), [&edge_grids, &input_expolygons, &painted_lines, &segmented_regions, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range &range) {
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - layers segmentation in parallel - begin";
+ tbb::parallel_for(tbb::blocked_range(0, num_layers), [&edge_grids, &input_expolygons, &painted_lines, &segmented_regions, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
if (!painted_lines[layer_idx].empty()) {
@@ -2279,7 +2153,7 @@ std::vector> multi_material_segmentation_by_painting(con
MMU_Graph graph = build_graph(layer_idx, color_poly);
remove_multiple_edges_in_vertices(graph, color_poly);
graph.remove_nodes_with_one_arc();
- segmented_regions[layer_idx] = extract_colored_segments(graph, num_extruders);
+ segmented_regions[layer_idx] = extract_colored_segments(graph, num_facets_states);
//segmented_regions[layer_idx] = extract_colored_segments(color_poly, num_extruders, layer_idx);
}
@@ -2289,21 +2163,22 @@ std::vector> multi_material_segmentation_by_painting(con
}
}
}); // end of parallel_for
- BOOST_LOG_TRIVIAL(debug) << "MM segmentation - layers segmentation in parallel - end";
+ BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - layers segmentation in parallel - end";
throw_on_cancel_callback();
- auto interlocking_beam = print_object.config().interlocking_beam;
- if (auto max_width = print_object.config().mmu_segmented_region_max_width, interlocking_depth = print_object.config().mmu_segmented_region_interlocking_depth;
- !interlocking_beam && (max_width > 0.f || interlocking_depth > 0.f)) {
- cut_segmented_layers(input_expolygons, segmented_regions, float(scale_(max_width)), float(scale_(interlocking_depth)), throw_on_cancel_callback);
+ if ((segmentation_max_width > 0.f || segmentation_interlocking_depth > 0.f) && !segmentation_interlocking_beam) {
+ cut_segmented_layers(input_expolygons, segmented_regions, float(scale_(segmentation_max_width)), float(scale_(segmentation_interlocking_depth)), throw_on_cancel_callback);
throw_on_cancel_callback();
}
// The first index is extruder number (includes default extruder), and the second one is layer number
- std::vector> top_and_bottom_layers = mmu_segmentation_top_and_bottom_layers(print_object, input_expolygons, throw_on_cancel_callback);
- throw_on_cancel_callback();
+ std::vector> top_and_bottom_layers;
+ if (include_top_and_bottom_layers == IncludeTopAndBottomLayers::Yes) {
+ top_and_bottom_layers = segmentation_top_and_bottom_layers(print_object, input_expolygons, extract_facets_info, num_facets_states, throw_on_cancel_callback);
+ throw_on_cancel_callback();
+ }
- std::vector> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_extruders, throw_on_cancel_callback);
+ std::vector> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback);
throw_on_cancel_callback();
#ifdef MM_SEGMENTATION_DEBUG_REGIONS
@@ -2318,4 +2193,37 @@ std::vector> multi_material_segmentation_by_painting(con
return segmented_regions_merged;
}
+// Returns multi-material segmentation based on painting in multi-material segmentation gizmo
+std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback) {
+ const size_t num_facets_states = print_object.print()->config().filament_colour.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 bool interlocking_beam = print_object.config().interlocking_beam.value;
+
+ const auto extract_facets_info = [](const ModelVolume &mv) -> ModelVolumeFacetsInfo {
+ return {mv.mmu_segmentation_facets, mv.is_mm_painted(), false};
+ };
+
+ return segmentation_by_painting(print_object, extract_facets_info, num_facets_states, max_width, interlocking_depth, interlocking_beam, IncludeTopAndBottomLayers::Yes, throw_on_cancel_callback);
+}
+
+// Returns fuzzy skin segmentation based on painting in fuzzy skin segmentation gizmo
+std::vector> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback) {
+ const size_t num_facets_states = 2; // Unpainted facets and facets painted with fuzzy skin.
+
+ const auto extract_facets_info = [](const ModelVolume &mv) -> ModelVolumeFacetsInfo {
+ return {mv.fuzzy_skin_facets, mv.is_fuzzy_skin_painted(), false};
+ };
+
+ // Because we apply fuzzy skin just on external perimeters, we limit the depth of fuzzy skin
+ // by the maximal extrusion width of external perimeters.
+ float max_external_perimeter_width = 0.;
+ for (size_t region_idx = 0; region_idx < print_object.num_printing_regions(); ++region_idx) {
+ const PrintRegion ®ion = print_object.printing_region(region_idx);
+ max_external_perimeter_width = std::max(max_external_perimeter_width, region.flow(print_object, frExternalPerimeter, print_object.config().layer_height).width());
+ }
+
+ return segmentation_by_painting(print_object, extract_facets_info, num_facets_states, max_external_perimeter_width, 0.f, false, IncludeTopAndBottomLayers::No, throw_on_cancel_callback);
+}
+
} // namespace Slic3r
diff --git a/src/libslic3r/MultiMaterialSegmentation.hpp b/src/libslic3r/MultiMaterialSegmentation.hpp
index 91d0f298bc..fad97e4cf2 100644
--- a/src/libslic3r/MultiMaterialSegmentation.hpp
+++ b/src/libslic3r/MultiMaterialSegmentation.hpp
@@ -6,8 +6,11 @@
namespace Slic3r {
-class PrintObject;
class ExPolygon;
+class ModelVolume;
+class PrintObject;
+class FacetsAnnotation;
+
using ExPolygons = std::vector;
struct ColoredLine
@@ -20,9 +23,35 @@ struct ColoredLine
using ColoredLines = std::vector;
-// Returns MMU segmentation based on painting in MMU segmentation gizmo
+enum class IncludeTopAndBottomLayers {
+ Yes,
+ No
+};
+
+struct ModelVolumeFacetsInfo {
+ const FacetsAnnotation &facets_annotation;
+ // Indicate if model volume is painted.
+ const bool is_painted;
+ // Indicate if the default extruder (TriangleStateType::NONE) should be replaced with the volume extruder.
+ const bool replace_default_extruder;
+};
+
+// Returns segmentation based on painting in segmentation gizmos.
+std::vector> segmentation_by_painting(const PrintObject &print_object,
+ const std::function &extract_facets_info,
+ size_t num_facets_states,
+ float segmentation_max_width,
+ float segmentation_interlocking_depth,
+ bool segmentation_interlocking_beam,
+ IncludeTopAndBottomLayers include_top_and_bottom_layers,
+ const std::function &throw_on_cancel_callback);
+
+// Returns multi-material segmentation based on painting in multi-material segmentation gizmo
std::vector> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback);
+// Returns fuzzy skin segmentation based on painting in fuzzy skin segmentation gizmo
+std::vector> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function &throw_on_cancel_callback);
+
} // namespace Slic3r
namespace boost::polygon {
diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp
index 07d1409481..c67f0cf3f3 100644
--- a/src/libslic3r/PerimeterGenerator.cpp
+++ b/src/libslic3r/PerimeterGenerator.cpp
@@ -4,6 +4,7 @@
#include "ClipperUtils.hpp"
#include "ExtrusionEntity.hpp"
#include "ExtrusionEntityCollection.hpp"
+#include "Feature/FuzzySkin/FuzzySkin.hpp"
#include "PrintConfig.hpp"
#include "ShortestPath.hpp"
#include "VariableWidth.hpp"
@@ -14,13 +15,10 @@
#include "Line.hpp"
#include
#include
-#include
#include
#include
#include "libslic3r/AABBTreeLines.hpp"
#include "Print.hpp"
-#include "Algorithm/LineSplit.hpp"
-#include "libnoise/noise.h"
static const int overhang_sampling_number = 6;
static const double narrow_loop_length_threshold = 10;
//BBS: when the width of expolygon is smaller than
@@ -28,26 +26,9 @@ static const double narrow_loop_length_threshold = 10;
//we think it's small detail area and will generate smaller line width for it
static constexpr double SMALLER_EXT_INSET_OVERLAP_TOLERANCE = 0.22;
-//#define DEBUG_FUZZY
-
namespace Slic3r {
-
-// Produces a random value between 0 and 1. Thread-safe.
-static double random_value() {
- thread_local std::random_device rd;
- // Hash thread ID for random number seed if no hardware rng seed is available
- thread_local std::mt19937 gen(rd.entropy() > 0 ? rd() : std::hash()(std::this_thread::get_id()));
- thread_local std::uniform_real_distribution dist(0.0, 1.0);
- return dist(gen);
-}
-
-class UniformNoise: public noise::module::Module {
- public:
- UniformNoise(): Module (GetSourceModuleCount ()) {};
-
- virtual int GetSourceModuleCount() const { return 0; }
- virtual double GetValue(double x, double y, double z) const { return random_value() * 2 - 1; }
-};
+
+using namespace Slic3r::Feature::FuzzySkin;
// Hierarchy of perimeters.
class PerimeterGeneratorLoop {
@@ -71,124 +52,6 @@ public:
bool is_internal_contour() const;
};
-static std::unique_ptr get_noise_module(const FuzzySkinConfig& cfg) {
- if (cfg.noise_type == NoiseType::Perlin) {
- auto perlin_noise = noise::module::Perlin();
- perlin_noise.SetFrequency(1 / cfg.noise_scale);
- perlin_noise.SetOctaveCount(cfg.noise_octaves);
- perlin_noise.SetPersistence(cfg.noise_persistence);
- return std::make_unique(perlin_noise);
- } else if (cfg.noise_type == NoiseType::Billow) {
- auto billow_noise = noise::module::Billow();
- billow_noise.SetFrequency(1 / cfg.noise_scale);
- billow_noise.SetOctaveCount(cfg.noise_octaves);
- billow_noise.SetPersistence(cfg.noise_persistence);
- return std::make_unique(billow_noise);
- } else if (cfg.noise_type == NoiseType::RidgedMulti) {
- auto ridged_multi_noise = noise::module::RidgedMulti();
- ridged_multi_noise.SetFrequency(1 / cfg.noise_scale);
- ridged_multi_noise.SetOctaveCount(cfg.noise_octaves);
- return std::make_unique(ridged_multi_noise);
- } else if (cfg.noise_type == NoiseType::Voronoi) {
- auto voronoi_noise = noise::module::Voronoi();
- voronoi_noise.SetFrequency(1 / cfg.noise_scale);
- voronoi_noise.SetDisplacement(1.0);
- return std::make_unique(voronoi_noise);
- } else {
- return std::make_unique();
- }
-}
-
-// Thanks Cura developers for this function.
-static void fuzzy_polyline(Points& poly, bool closed, coordf_t slice_z, const FuzzySkinConfig& cfg)
-{
- std::unique_ptr noise = get_noise_module(cfg);
-
- const double min_dist_between_points = cfg.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 = cfg.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
- Point* p0 = &poly.back();
- Points out;
- out.reserve(poly.size());
- for (Point &p1 : poly)
- {
- if (!closed) {
- // Skip the first point for open path
- closed = true;
- p0 = &p1;
- continue;
- }
- // 'a' is the (next) new point between p0 and p1
- Vec2d p0p1 = (p1 - *p0).cast();
- double p0p1_size = p0p1.norm();
- double p0pa_dist = dist_left_over;
- for (; p0pa_dist < p0p1_size;
- p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist)
- {
- Point pa = *p0 + (p0p1 * (p0pa_dist / p0p1_size)).cast();
- double r = noise->GetValue(unscale_(pa.x()), unscale_(pa.y()), slice_z) * cfg.thickness;
- out.emplace_back(pa + (perp(p0p1).cast().normalized() * r).cast());
- }
- dist_left_over = p0pa_dist - p0p1_size;
- p0 = &p1;
- }
- while (out.size() < 3) {
- size_t point_idx = poly.size() - 2;
- out.emplace_back(poly[point_idx]);
- if (point_idx == 0)
- break;
- -- point_idx;
- }
- if (out.size() >= 3)
- poly = std::move(out);
-}
-
-// Thanks Cura developers for this function.
-static void fuzzy_extrusion_line(std::vector& ext_lines, coordf_t slice_z, const FuzzySkinConfig& cfg)
-{
- std::unique_ptr noise = get_noise_module(cfg);
-
- const double min_dist_between_points = cfg.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 = cfg.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
-
- auto* p0 = &ext_lines.front();
- std::vector out;
- out.reserve(ext_lines.size());
- for (auto& p1 : ext_lines) {
- if (p0->p == p1.p) { // Connect endpoints.
- out.emplace_back(p1.p, p1.w, p1.perimeter_index);
- continue;
- }
-
- // 'a' is the (next) new point between p0 and p1
- Vec2d p0p1 = (p1.p - p0->p).cast();
- double p0p1_size = p0p1.norm();
- double p0pa_dist = dist_left_over;
- for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) {
- Point pa = p0->p + (p0p1 * (p0pa_dist / p0p1_size)).cast();
- double r = noise->GetValue(unscale_(pa.x()), unscale_(pa.y()), slice_z) * cfg.thickness;
- out.emplace_back(pa + (perp(p0p1).cast().normalized() * r).cast(), p1.w, p1.perimeter_index);
- }
- dist_left_over = p0pa_dist - p0p1_size;
- p0 = &p1;
- }
-
- while (out.size() < 3) {
- size_t point_idx = ext_lines.size() - 2;
- out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index);
- if (point_idx == 0)
- break;
- --point_idx;
- }
-
- if (ext_lines.back().p == ext_lines.front().p) // Connect endpoints.
- out.front().p = out.back().p;
-
- if (out.size() >= 3)
- ext_lines = std::move(out);
-}
-
using PerimeterGeneratorLoops = std::vector;
template
@@ -234,31 +97,12 @@ static bool detect_steep_overhang(const PrintRegionConfig *config,
return false;
}
-static bool should_fuzzify(const FuzzySkinConfig& config, const int layer_id, const size_t loop_idx, const bool is_contour)
-{
- const auto fuzziy_type = config.type;
-
- if (fuzziy_type == FuzzySkinType::None) {
- return false;
- }
- if (!config.fuzzy_first_layer && layer_id <= 0) {
- // Do not fuzzy first layer unless told to
- return false;
- }
-
- const bool fuzzify_contours = loop_idx == 0 || fuzziy_type == FuzzySkinType::AllWalls;
- const bool fuzzify_holes = fuzzify_contours && (fuzziy_type == FuzzySkinType::All || fuzziy_type == FuzzySkinType::AllWalls);
-
- return is_contour ? fuzzify_contours : fuzzify_holes;
-}
-
static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls,
bool &steep_overhang_contour, bool &steep_overhang_hole)
{
// loops is an arrayref of ::Loop objects
// turn each one into an ExtrusionLoop object
ExtrusionEntityCollection coll;
- Polygon fuzzified;
// Detect steep overhangs
bool overhangs_reverse = perimeter_generator.config->overhang_reverse &&
@@ -302,99 +146,9 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
extrusion_mm3_per_mm = perimeter_generator.mm3_per_mm();
extrusion_width = perimeter_generator.perimeter_flow.width();
}
- const Polygon& polygon = *([&perimeter_generator, &loop, &fuzzified]() ->const Polygon* {
- const auto& regions = perimeter_generator.regions_by_fuzzify;
- if (regions.size() == 1) { // optimization
- const auto& config = regions.begin()->first;
- const bool fuzzify = should_fuzzify(config, perimeter_generator.layer_id, loop.depth, loop.is_contour);
- if (!fuzzify) {
- return &loop.polygon;
- }
- fuzzified = loop.polygon;
- fuzzy_polyline(fuzzified.points, true, perimeter_generator.slice_z, config);
- return &fuzzified;
- }
-
- // Find all affective regions
- std::vector> fuzzified_regions;
- fuzzified_regions.reserve(regions.size());
- for (const auto & region : regions) {
- if (should_fuzzify(region.first, perimeter_generator.layer_id, loop.depth, loop.is_contour)) {
- fuzzified_regions.emplace_back(region.first, region.second);
- }
- }
- if (fuzzified_regions.empty()) {
- return &loop.polygon;
- }
-
-#ifdef DEBUG_FUZZY
- {
- int i = 0;
- for (const auto & r : fuzzified_regions) {
- BoundingBox bbox = get_extents(perimeter_generator.slices->surfaces);
- bbox.offset(scale_(1.));
- ::Slic3r::SVG svg(debug_out_path("fuzzy_traverse_loops_%d_%d_%d_region_%d.svg", perimeter_generator.layer_id, loop.is_contour ? 0 : 1, loop.depth, i).c_str(), bbox);
- svg.draw_outline(perimeter_generator.slices->surfaces);
- svg.draw_outline(loop.polygon, "green");
- svg.draw(r.second, "red", 0.5);
- svg.draw_outline(r.second, "red");
- svg.Close();
- i++;
- }
- }
-#endif
-
- // Split the loops into lines with different config, and fuzzy them separately
- fuzzified = loop.polygon;
- for (const auto& r : fuzzified_regions) {
- const auto splitted = Algorithm::split_line(fuzzified, r.second, true);
- if (splitted.empty()) {
- // No intersection, skip
- continue;
- }
-
- // Fuzzy splitted polygon
- if (std::all_of(splitted.begin(), splitted.end(), [](const Algorithm::SplitLineJunction& j) { return j.clipped; })) {
- // The entire polygon is fuzzified
- fuzzy_polyline(fuzzified.points, true, perimeter_generator.slice_z, r.first);
- } else {
- Points segment;
- segment.reserve(splitted.size());
- fuzzified.points.clear();
-
- const auto slice_z = perimeter_generator.slice_z;
- const auto fuzzy_current_segment = [&segment, &fuzzified, &r, slice_z]() {
- fuzzified.points.push_back(segment.front());
- const auto back = segment.back();
- fuzzy_polyline(segment, false, slice_z, r.first);
- fuzzified.points.insert(fuzzified.points.end(), segment.begin(), segment.end());
- fuzzified.points.push_back(back);
- segment.clear();
- };
-
- for (const auto& p : splitted) {
- if (p.clipped) {
- segment.push_back(p.p);
- } else {
- if (segment.empty()) {
- fuzzified.points.push_back(p.p);
- } else {
- segment.push_back(p.p);
- fuzzy_current_segment();
- }
- }
- }
- if (!segment.empty()) {
- // Close the loop
- segment.push_back(splitted.front().p);
- fuzzy_current_segment();
- }
- }
- }
-
- return &fuzzified;
- }());
+ // Apply fuzzy skin if it is enabled for at least some part of the polygon.
+ const Polygon polygon = apply_fuzzy_skin(loop.polygon, perimeter_generator, loop.depth, loop.is_contour);
ExtrusionPaths paths;
if (perimeter_generator.config->detect_overhang_wall && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) {
@@ -603,8 +357,6 @@ struct PerimeterGeneratorArachneExtrusion
static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& perimeter_generator, std::vector& pg_extrusions,
bool &steep_overhang_contour, bool &steep_overhang_hole)
{
- const auto slice_z = perimeter_generator.slice_z;
-
// Detect steep overhangs
bool overhangs_reverse = perimeter_generator.config->overhang_reverse &&
perimeter_generator.layer_id % 2 == 1; // Only calculate overhang degree on even (from GUI POV) layers
@@ -618,77 +370,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
const bool is_external = extrusion->inset_idx == 0;
ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter;
- const auto& regions = perimeter_generator.regions_by_fuzzify;
const bool is_contour = !extrusion->is_closed || pg_extrusion.is_contour;
- if (regions.size() == 1) { // optimization
- const auto& config = regions.begin()->first;
- const bool fuzzify = should_fuzzify(config, perimeter_generator.layer_id, extrusion->inset_idx, is_contour);
- if (fuzzify)
- fuzzy_extrusion_line(extrusion->junctions, slice_z, config);
- } else {
- // Find all affective regions
- std::vector> fuzzified_regions;
- fuzzified_regions.reserve(regions.size());
- for (const auto& region : regions) {
- if (should_fuzzify(region.first, perimeter_generator.layer_id, extrusion->inset_idx, is_contour)) {
- fuzzified_regions.emplace_back(region.first, region.second);
- }
- }
- if (!fuzzified_regions.empty()) {
- // Split the loops into lines with different config, and fuzzy them separately
- for (const auto& r : fuzzified_regions) {
- const auto splitted = Algorithm::split_line(*extrusion, r.second, false);
- if (splitted.empty()) {
- // No intersection, skip
- continue;
- }
-
- // Fuzzy splitted extrusion
- if (std::all_of(splitted.begin(), splitted.end(), [](const Algorithm::SplitLineJunction& j) { return j.clipped; })) {
- // The entire polygon is fuzzified
- fuzzy_extrusion_line(extrusion->junctions, slice_z, r.first);
- } else {
- const auto current_ext = extrusion->junctions;
- std::vector segment;
- segment.reserve(current_ext.size());
- extrusion->junctions.clear();
-
- const auto fuzzy_current_segment = [&segment, &extrusion, &r, slice_z]() {
- extrusion->junctions.push_back(segment.front());
- const auto back = segment.back();
- fuzzy_extrusion_line(segment, slice_z, r.first);
- extrusion->junctions.insert(extrusion->junctions.end(), segment.begin(), segment.end());
- extrusion->junctions.push_back(back);
- segment.clear();
- };
-
- const auto to_ex_junction = [¤t_ext](const Algorithm::SplitLineJunction& j) -> Arachne::ExtrusionJunction {
- Arachne::ExtrusionJunction res = current_ext[j.get_src_index()];
- if (!j.is_src()) {
- res.p = j.p;
- }
- return res;
- };
-
- for (const auto& p : splitted) {
- if (p.clipped) {
- segment.push_back(to_ex_junction(p));
- } else {
- if (segment.empty()) {
- extrusion->junctions.push_back(to_ex_junction(p));
- } else {
- segment.push_back(to_ex_junction(p));
- fuzzy_current_segment();
- }
- }
- }
- if (!segment.empty()) {
- fuzzy_current_segment();
- }
- }
- }
- }
- }
+ apply_fuzzy_skin(extrusion, perimeter_generator, is_contour);
ExtrusionPaths paths;
// detect overhanging/bridging perimeters
@@ -1430,48 +1113,6 @@ static void reorient_perimeters(ExtrusionEntityCollection &entities, bool steep_
}
}
-static void group_region_by_fuzzify(PerimeterGenerator& g)
-{
- g.regions_by_fuzzify.clear();
- g.has_fuzzy_skin = false;
- g.has_fuzzy_hole = false;
-
- std::unordered_map regions;
- for (auto region : *g.compatible_regions) {
- const auto& region_config = region->region().config();
- const FuzzySkinConfig cfg{
- region_config.fuzzy_skin,
- scaled(region_config.fuzzy_skin_thickness.value),
- scaled(region_config.fuzzy_skin_point_distance.value),
- region_config.fuzzy_skin_first_layer,
- region_config.fuzzy_skin_noise_type,
- region_config.fuzzy_skin_scale,
- region_config.fuzzy_skin_octaves,
- region_config.fuzzy_skin_persistence
- };
- auto& surfaces = regions[cfg];
- for (const auto& surface : region->slices.surfaces) {
- surfaces.push_back(&surface);
- }
-
- if (cfg.type != FuzzySkinType::None) {
- g.has_fuzzy_skin = true;
- if (cfg.type != FuzzySkinType::External) {
- g.has_fuzzy_hole = true;
- }
- }
- }
-
- if (regions.size() == 1) { // optimization
- g.regions_by_fuzzify[regions.begin()->first] = {};
- return;
- }
-
- for (auto& it : regions) {
- g.regions_by_fuzzify[it.first] = offset_ex(it.second, ClipperSafetyOffset);
- }
-}
-
void PerimeterGenerator::process_classic()
{
group_region_by_fuzzify(*this);
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
index 858fa32053..a9db96d090 100644
--- a/src/libslic3r/Print.cpp
+++ b/src/libslic3r/Print.cpp
@@ -1098,8 +1098,15 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
if (total_copies_count > 1 && m_config.print_sequence != PrintSequence::ByObject)
return {L("Please select \"By object\" print sequence to print multiple objects in spiral vase mode."), nullptr, "spiral_mode"};
assert(m_objects.size() == 1);
- if (m_objects.front()->all_regions().size() > 1)
- return {L("The spiral vase mode does not work when an object contains more than one materials."), nullptr, "spiral_mode"};
+ const auto all_regions = m_objects.front()->all_regions();
+ if (all_regions.size() > 1) {
+ // Orca: make sure regions are not compatible
+ if (std::any_of(all_regions.begin() + 1, all_regions.end(), [ra = all_regions.front()](const auto rb) {
+ return !Layer::is_perimeter_compatible(ra, rb);
+ })) {
+ return {L("The spiral vase mode does not work when an object contains more than one materials."), nullptr, "spiral_mode"};
+ }
+ }
}
// Cache of layer height profiles for checking:
@@ -1881,6 +1888,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
return false;
if (!model_volume1.mmu_segmentation_facets.equals(model_volume2.mmu_segmentation_facets))
return false;
+ if (!model_volume1.fuzzy_skin_facets.equals(model_volume2.fuzzy_skin_facets))
+ return false;
if (model_volume1.config.get() != model_volume2.config.get())
return false;
}
@@ -4331,4 +4340,21 @@ Point PrintInstance::shift_without_plate_offset() const
return shift - Point(scaled(plate_offset.x()), scaled(plate_offset.y()));
}
+PrintRegion *PrintObjectRegions::FuzzySkinPaintedRegion::parent_print_object_region(const LayerRangeRegions &layer_range) const
+{
+ using FuzzySkinParentType = PrintObjectRegions::FuzzySkinPaintedRegion::ParentType;
+
+ if (this->parent_type == FuzzySkinParentType::PaintedRegion) {
+ return layer_range.painted_regions[this->parent].region;
+ }
+
+ assert(this->parent_type == FuzzySkinParentType::VolumeRegion);
+ return layer_range.volume_regions[this->parent].region;
+}
+
+int PrintObjectRegions::FuzzySkinPaintedRegion::parent_print_object_region_id(const LayerRangeRegions &layer_range) const
+{
+ return this->parent_print_object_region(layer_range)->print_object_region_id();
+}
+
} // namespace Slic3r
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
index ac3e17a4eb..7261bc2ce6 100644
--- a/src/libslic3r/Print.hpp
+++ b/src/libslic3r/Print.hpp
@@ -185,8 +185,6 @@ class ConstSupportLayerPtrsAdaptor : public ConstVectorOfPtrsAdaptor(data) {}
};
-class BoundingBoxf3; // TODO: for temporary constructor parameter
-
// Single instance of a PrintObject.
// As multiple PrintObjects may be generated for a single ModelObject (their instances differ in rotation around Z),
// ModelObject's instancess will be distributed among these multiple PrintObjects.
@@ -251,6 +249,22 @@ public:
PrintRegion *region { nullptr };
};
+ struct LayerRangeRegions;
+
+ struct FuzzySkinPaintedRegion
+ {
+ enum class ParentType { VolumeRegion, PaintedRegion };
+
+ ParentType parent_type { ParentType::VolumeRegion };
+ // Index of a parent VolumeRegion or PaintedRegion.
+ int parent { -1 };
+ // Pointer to PrintObjectRegions::all_regions.
+ PrintRegion *region { nullptr };
+
+ PrintRegion *parent_print_object_region(const LayerRangeRegions &layer_range) const;
+ int parent_print_object_region_id(const LayerRangeRegions &layer_range) const;
+ };
+
// One slice over the PrintObject (possibly the whole PrintObject) and a list of ModelVolumes and their bounding boxes
// possibly clipped by the layer_height_range.
struct LayerRangeRegions
@@ -263,8 +277,9 @@ public:
std::vector volumes;
// Sorted in the order of their source ModelVolumes, thus reflecting the order of region clipping, modifier overrides etc.
- std::vector volume_regions;
- std::vector painted_regions;
+ std::vector volume_regions;
+ std::vector painted_regions;
+ std::vector fuzzy_skin_painted_regions;
bool has_volume(const ObjectID id) const {
auto it = lower_bound_by_predicate(this->volumes.begin(), this->volumes.end(), [id](const VolumeExtents &l) { return l.volume_id < id; });
@@ -415,6 +430,8 @@ public:
bool has_support_material() const { return this->has_support() || this->has_raft(); }
// Checks if the model object is painted using the multi-material painting gizmo.
bool is_mm_painted() const { return this->model_object()->is_mm_painted(); }
+ // Checks if the model object is painted using the fuzzy skin painting gizmo.
+ bool is_fuzzy_skin_painted() const { return this->model_object()->is_fuzzy_skin_painted(); }
// returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions)
std::vector object_extruders() const;
diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp
index b7d630c0d5..3921ea48a8 100644
--- a/src/libslic3r/PrintApply.cpp
+++ b/src/libslic3r/PrintApply.cpp
@@ -77,6 +77,8 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst,
mv_dst.seam_facets.assign(mv_src.seam_facets);
assert(mv_dst.mmu_segmentation_facets.id() == mv_src.mmu_segmentation_facets.id());
mv_dst.mmu_segmentation_facets.assign(mv_src.mmu_segmentation_facets);
+ assert(mv_dst.fuzzy_skin_facets.id() == mv_src.fuzzy_skin_facets.id());
+ mv_dst.fuzzy_skin_facets.assign(mv_src.fuzzy_skin_facets);
//FIXME what to do with the materials?
// mv_dst.m_material_id = mv_src.m_material_id;
++ i_src;
@@ -705,7 +707,6 @@ bool verify_update_print_object_regions(
ModelVolumePtrs model_volumes,
const PrintRegionConfig &default_region_config,
size_t num_extruders,
- const std::vector &painting_extruders,
PrintObjectRegions &print_object_regions,
const std::function &callback_invalidate)
{
@@ -803,6 +804,29 @@ bool verify_update_print_object_regions(
print_region_ref_inc(*region.region);
}
+ // Verify and / or update PrintRegions produced by fuzzy skin painting.
+ for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) {
+ for (const PrintObjectRegions::FuzzySkinPaintedRegion ®ion : layer_range.fuzzy_skin_painted_regions) {
+ const PrintRegion &parent_print_region = *region.parent_print_object_region(layer_range);
+ PrintRegionConfig cfg = parent_print_region.config();
+ cfg.fuzzy_skin.value = FuzzySkinType::All;
+ if (cfg != region.region->config()) {
+ // Region configuration changed.
+ if (print_region_ref_cnt(*region.region) == 0) {
+ // Region is referenced for the first time. Just change its parameters.
+ // Stop the background process before assigning new configuration to the regions.
+ t_config_option_keys diff = region.region->config().diff(cfg);
+ callback_invalidate(region.region->config(), cfg, diff);
+ region.region->config_apply_only(cfg, diff, false);
+ } else {
+ // Region is referenced multiple times, thus the region is being split. We need to reslice.
+ return false;
+ }
+ }
+ print_region_ref_inc(*region.region);
+ }
+ }
+
// Lastly verify, whether some regions were not merged.
{
std::vector regions;
@@ -906,7 +930,8 @@ static PrintObjectRegions* generate_print_object_regions(
const Transform3d &trafo,
size_t num_extruders,
const float xy_contour_compensation,
- const std::vector & painting_extruders)
+ const std::vector &painting_extruders,
+ const bool has_painted_fuzzy_skin)
{
// Reuse the old object or generate a new one.
auto out = print_object_regions_old ? std::unique_ptr(print_object_regions_old) : std::make_unique();
@@ -928,6 +953,7 @@ static PrintObjectRegions* generate_print_object_regions(
r.config = range.config;
r.volume_regions.clear();
r.painted_regions.clear();
+ r.fuzzy_skin_painted_regions.clear();
}
} else {
out->trafo_bboxes = trafo;
@@ -1009,13 +1035,42 @@ static PrintObjectRegions* generate_print_object_regions(
cfg.sparse_infill_filament.value = painted_extruder_id;
layer_range.painted_regions.push_back({ painted_extruder_id, parent_region_id, get_create_region(std::move(cfg))});
}
- // Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MMU segmentation.
+ // Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MM segmentation.
std::sort(layer_range.painted_regions.begin(), layer_range.painted_regions.end(), [&layer_range](auto &l, auto &r) {
int lid = layer_range.volume_regions[l.parent].region->print_object_region_id();
int rid = layer_range.volume_regions[r.parent].region->print_object_region_id();
return lid < rid || (lid == rid && l.extruder_id < r.extruder_id); });
}
+ if (has_painted_fuzzy_skin) {
+ using FuzzySkinParentType = PrintObjectRegions::FuzzySkinPaintedRegion::ParentType;
+
+ for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions) {
+ // FuzzySkinPaintedRegion can override different parts of the Layer than PaintedRegions,
+ // so FuzzySkinPaintedRegion has to point to both VolumeRegion and PaintedRegion.
+ for (int parent_volume_region_id = 0; parent_volume_region_id < int(layer_range.volume_regions.size()); ++parent_volume_region_id) {
+ if (const PrintObjectRegions::VolumeRegion &parent_volume_region = layer_range.volume_regions[parent_volume_region_id]; parent_volume_region.model_volume->is_model_part() || parent_volume_region.model_volume->is_modifier()) {
+ PrintRegionConfig cfg = parent_volume_region.region->config();
+ cfg.fuzzy_skin.value = FuzzySkinType::All;
+ layer_range.fuzzy_skin_painted_regions.push_back({FuzzySkinParentType::VolumeRegion, parent_volume_region_id, get_create_region(std::move(cfg))});
+ }
+ }
+
+ for (int parent_painted_regions_id = 0; parent_painted_regions_id < int(layer_range.painted_regions.size()); ++parent_painted_regions_id) {
+ const PrintObjectRegions::PaintedRegion &parent_painted_region = layer_range.painted_regions[parent_painted_regions_id];
+
+ PrintRegionConfig cfg = parent_painted_region.region->config();
+ cfg.fuzzy_skin.value = FuzzySkinType::All;
+ layer_range.fuzzy_skin_painted_regions.push_back({FuzzySkinParentType::PaintedRegion, parent_painted_regions_id, get_create_region(std::move(cfg))});
+ }
+
+ // Sort the regions by parent region::print_object_region_id() to help the slicing algorithm when applying fuzzy skin segmentation.
+ std::sort(layer_range.fuzzy_skin_painted_regions.begin(), layer_range.fuzzy_skin_painted_regions.end(), [&layer_range](auto &l, auto &r) {
+ return l.parent_print_object_region_id(layer_range) < r.parent_print_object_region_id(layer_range);
+ });
+ }
+ }
+
return out.release();
}
@@ -1251,7 +1306,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked.
bool solid_or_modifier_differ = model_volume_list_changed(model_object, model_object_new, solid_or_modifier_types) ||
model_mmu_segmentation_data_changed(model_object, model_object_new) ||
- (model_object_new.is_mm_painted() && num_extruders_changed );
+ (model_object_new.is_mm_painted() && num_extruders_changed) ||
+ model_fuzzy_skin_data_changed(model_object, model_object_new);
bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) ||
model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER);
bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty());
@@ -1541,8 +1597,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
verify_update_print_object_regions(
print_object.model_object()->volumes,
m_default_region_config,
- num_extruders ,
- painting_extruders,
+ num_extruders,
*print_object_regions,
[it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) {
for (auto it = it_print_object; it != it_print_object_end; ++it)
@@ -1569,7 +1624,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
model_object_status.print_instances.front().trafo,
num_extruders ,
print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_contour_compensation.value),
- painting_extruders);
+ painting_extruders,
+ print_object.is_fuzzy_skin_painted());
}
for (auto it = it_print_object; it != it_print_object_end; ++it)
if ((*it)->m_shared_regions) {
diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp
index 36a5c22d2a..532307414d 100644
--- a/src/libslic3r/PrintObjectSlice.cpp
+++ b/src/libslic3r/PrintObjectSlice.cpp
@@ -1,16 +1,16 @@
+#include
+
+#include
+
+#include "ClipperUtils.hpp"
#include "ElephantFootCompensation.hpp"
#include "I18N.hpp"
#include "Layer.hpp"
#include "MultiMaterialSegmentation.hpp"
#include "Print.hpp"
-#include "ClipperUtils.hpp"
-#include "Interlocking/InterlockingGenerator.hpp"
//BBS
#include "ShortestPath.hpp"
-
-#include
-
-#include
+#include "libslic3r/Feature/Interlocking/InterlockingGenerator.hpp"
//! macro used to mark string used at localization, return same string
#define L(s) Slic3r::I18N::translate(s)
@@ -845,35 +845,38 @@ void PrintObject::slice()
template
static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel)
{
- // Returns MMU segmentation based on painting in MMU segmentation gizmo
+ // Returns MM segmentation based on painting in MM segmentation gizmo
std::vector> segmentation = multi_material_segmentation_by_painting(print_object, throw_on_cancel);
assert(segmentation.size() == print_object.layer_count());
tbb::parallel_for(
tbb::blocked_range(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))),
[&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range &range) {
const auto &layer_ranges = print_object.shared_regions()->layer_ranges;
- double z = print_object.get_layer(range.begin())->slice_z;
+ double z = print_object.get_layer(int(range.begin()))->slice_z;
auto it_layer_range = layer_range_first(layer_ranges, z);
// BBS
const size_t num_extruders = print_object.print()->config().filament_diameter.size();
+
struct ByExtruder {
ExPolygons expolygons;
BoundingBox bbox;
};
- std::vector by_extruder;
+
struct ByRegion {
- ExPolygons expolygons;
- bool needs_merge { false };
+ ExPolygons expolygons;
+ bool needs_merge { false };
};
- std::vector by_region;
- for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
+
+ std::vector by_extruder;
+ std::vector by_region;
+ for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) {
throw_on_cancel();
- Layer *layer = print_object.get_layer(layer_id);
- it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer->slice_z);
+ Layer &layer = *print_object.get_layer(int(layer_id));
+ it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z);
const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range;
// Gather per extruder expolygons.
by_extruder.assign(num_extruders, ByExtruder());
- by_region.assign(layer->region_count(), ByRegion());
+ by_region.assign(layer.region_count(), ByRegion());
bool layer_split = false;
for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) {
ByExtruder ®ion = by_extruder[extruder_id];
@@ -883,92 +886,228 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
layer_split = true;
}
}
- if (! layer_split)
+
+ if (!layer_split)
continue;
+
// Split LayerRegions by by_extruder regions.
// layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID.
- auto it_painted_region = layer_range.painted_regions.begin();
- for (int region_id = 0; region_id < int(layer->region_count()); ++ region_id)
- if (LayerRegion &layerm = *layer->get_region(region_id); ! layerm.slices.surfaces.empty()) {
- assert(layerm.region().print_object_region_id() == region_id);
- const BoundingBox bbox = get_extents(layerm.slices.surfaces);
- assert(it_painted_region < layer_range.painted_regions.end());
- // Find the first it_painted_region which overrides this region.
- for (; layer_range.volume_regions[it_painted_region->parent].region->print_object_region_id() < region_id; ++ it_painted_region)
- assert(it_painted_region != layer_range.painted_regions.end());
- assert(it_painted_region != layer_range.painted_regions.end());
- assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region());
- // 1-based extruder ID
- bool self_trimmed = false;
- int self_extruder_id = -1;
- for (int extruder_id = 1; extruder_id <= int(by_extruder.size()); ++ extruder_id)
- if (ByExtruder &segmented = by_extruder[extruder_id - 1]; segmented.bbox.defined && bbox.overlap(segmented.bbox)) {
- // Find the target region.
- for (; int(it_painted_region->extruder_id) < extruder_id; ++ it_painted_region)
- assert(it_painted_region != layer_range.painted_regions.end());
- assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region() && int(it_painted_region->extruder_id) == extruder_id);
- //FIXME Don't trim by self, it is not reliable.
- if (&layerm.region() == it_painted_region->region) {
- self_extruder_id = extruder_id;
- continue;
- }
- // Steal from this region.
- int target_region_id = it_painted_region->region->print_object_region_id();
- ExPolygons stolen = intersection_ex(layerm.slices.surfaces, segmented.expolygons);
- if (! stolen.empty()) {
- ByRegion &dst = by_region[target_region_id];
- if (dst.expolygons.empty()) {
- dst.expolygons = std::move(stolen);
- } else {
- append(dst.expolygons, std::move(stolen));
- dst.needs_merge = true;
- }
- }
-#if 0
- if (&layerm.region() == it_painted_region->region)
- // Slices of this LayerRegion were trimmed by a MMU region of the same PrintRegion.
- self_trimmed = true;
-#endif
- }
- if (! self_trimmed) {
- // Trim slices of this LayerRegion with all the MMU regions.
- Polygons mine = to_polygons(std::move(layerm.slices.surfaces));
- for (auto &segmented : by_extruder)
- if (&segmented - by_extruder.data() + 1 != self_extruder_id && segmented.bbox.defined && bbox.overlap(segmented.bbox)) {
- mine = diff(mine, segmented.expolygons);
- if (mine.empty())
- break;
- }
- // Filter out unprintable polygons produced by subtraction multi-material painted regions from layerm.region().
- // ExPolygon returned from multi-material segmentation does not precisely match ExPolygons in layerm.region()
- // (because of preprocessing of the input regions in multi-material segmentation). Therefore, subtraction from
- // layerm.region() could produce a huge number of small unprintable regions for the model's base extruder.
- // This could, on some models, produce bulges with the model's base color (#7109).
- if (! mine.empty())
- mine = opening(union_ex(mine), float(scale_(5 * EPSILON)), float(scale_(5 * EPSILON)));
- if (! mine.empty()) {
- ByRegion &dst = by_region[layerm.region().print_object_region_id()];
- if (dst.expolygons.empty()) {
- dst.expolygons = union_ex(mine);
- } else {
- append(dst.expolygons, union_ex(mine));
- dst.needs_merge = true;
- }
+ auto it_painted_region_begin = layer_range.painted_regions.cbegin();
+ for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) {
+ if (it_painted_region_begin == layer_range.painted_regions.cend())
+ continue;
+
+ const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx);
+ const PrintRegion &parent_print_region = parent_layer_region.region();
+ assert(parent_print_region.print_object_region_id() == parent_layer_region_idx);
+ if (parent_layer_region.slices.empty())
+ continue;
+
+ // Find the first PaintedRegion, which overrides the parent PrintRegion.
+ auto it_first_painted_region = std::find_if(it_painted_region_begin, layer_range.painted_regions.cend(), [&layer_range, &parent_print_region](const auto &painted_region) {
+ return layer_range.volume_regions[painted_region.parent].region->print_object_region_id() == parent_print_region.print_object_region_id();
+ });
+
+ if (it_first_painted_region == layer_range.painted_regions.cend())
+ continue; // This LayerRegion isn't overrides by any PaintedRegion.
+
+ assert(&parent_print_region == layer_range.volume_regions[it_first_painted_region->parent].region);
+
+ // Update the beginning PaintedRegion iterator for the next iteration.
+ it_painted_region_begin = it_first_painted_region;
+
+ const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices.surfaces);
+ bool self_trimmed = false;
+ int self_extruder_id = -1; // 1-based extruder ID
+ for (int extruder_id = 1; extruder_id <= int(by_extruder.size()); ++extruder_id) {
+ const ByExtruder &segmented = by_extruder[extruder_id - 1];
+ if (!segmented.bbox.defined || !parent_layer_region_bbox.overlap(segmented.bbox))
+ continue;
+
+ // Find the first target region iterator.
+ auto it_target_region = std::find_if(it_painted_region_begin, layer_range.painted_regions.cend(), [extruder_id](const auto &painted_region) {
+ return int(painted_region.extruder_id) >= extruder_id;
+ });
+
+ assert(it_target_region != layer_range.painted_regions.end());
+ assert(layer_range.volume_regions[it_target_region->parent].region == &parent_print_region && int(it_target_region->extruder_id) == extruder_id);
+
+ // Update the beginning PaintedRegion iterator for the next iteration.
+ it_painted_region_begin = it_target_region;
+
+ // FIXME: Don't trim by self, it is not reliable.
+ if (it_target_region->region == &parent_print_region) {
+ self_extruder_id = extruder_id;
+ continue;
+ }
+
+ // Steal from this region.
+ int target_region_id = it_target_region->region->print_object_region_id();
+ ExPolygons stolen = intersection_ex(parent_layer_region.slices.surfaces, segmented.expolygons);
+ if (!stolen.empty()) {
+ ByRegion &dst = by_region[target_region_id];
+ if (dst.expolygons.empty()) {
+ dst.expolygons = std::move(stolen);
+ } else {
+ append(dst.expolygons, std::move(stolen));
+ dst.needs_merge = true;
}
}
}
+
+ if (!self_trimmed) {
+ // Trim slices of this LayerRegion with all the MM regions.
+ Polygons mine = to_polygons(parent_layer_region.slices.surfaces);
+ for (auto &segmented : by_extruder) {
+ if (&segmented - by_extruder.data() + 1 != self_extruder_id && segmented.bbox.defined && parent_layer_region_bbox.overlap(segmented.bbox)) {
+ mine = diff(mine, segmented.expolygons);
+ if (mine.empty())
+ break;
+ }
+ }
+
+ // Filter out unprintable polygons produced by subtraction multi-material painted regions from layerm.region().
+ // ExPolygon returned from multi-material segmentation does not precisely match ExPolygons in layerm.region()
+ // (because of preprocessing of the input regions in multi-material segmentation). Therefore, subtraction from
+ // layerm.region() could produce a huge number of small unprintable regions for the model's base extruder.
+ // This could, on some models, produce bulges with the model's base color (#7109).
+ if (!mine.empty()) {
+ mine = opening(union_ex(mine), scaled(5. * EPSILON), scaled(5. * EPSILON));
+ }
+
+ if (!mine.empty()) {
+ ByRegion &dst = by_region[parent_print_region.print_object_region_id()];
+ if (dst.expolygons.empty()) {
+ dst.expolygons = union_ex(mine);
+ } else {
+ append(dst.expolygons, union_ex(mine));
+ dst.needs_merge = true;
+ }
+ }
+ }
+ }
+
// Re-create Surfaces of LayerRegions.
- for (size_t region_id = 0; region_id < layer->region_count(); ++ region_id) {
+ for (int region_id = 0; region_id < layer.region_count(); ++region_id) {
ByRegion &src = by_region[region_id];
- if (src.needs_merge)
+ if (src.needs_merge) {
// Multiple regions were merged into one.
- src.expolygons = closing_ex(src.expolygons, float(scale_(10 * EPSILON)));
- layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal);
+ src.expolygons = closing_ex(src.expolygons, scaled(10. * EPSILON));
+ }
+
+ layer.get_region(region_id)->slices.set(std::move(src.expolygons), stInternal);
}
}
});
}
+template
+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> 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(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))), [&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range &range) {
+ const auto &layer_ranges = print_object.shared_regions()->layer_ranges;
+ 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 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(5. * EPSILON), scaled(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(10. * EPSILON));
+ }
+
+ layer.get_region(region_id)->slices.set(std::move(src.expolygons), stInternal);
+ }
+ }
+ }); // end of parallel_for
+}
// 1) Decides Z positions of the layers,
// 2) Initializes layers and their regions
@@ -1038,7 +1177,7 @@ void PrintObject::slice_volumes()
this->apply_conical_overhang();
- // Is any ModelVolume MMU painted?
+ // Is any ModelVolume multi-material painted?
if (const auto& volumes = this->model_object()->volumes;
m_print->config().filament_diameter.size() > 1 && // BBS
std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mmu_segmentation_facets.empty(); }) != volumes.end()) {
@@ -1057,7 +1196,21 @@ void PrintObject::slice_volumes()
apply_mm_segmentation(*this, [print]() { print->throw_if_canceled(); });
}
- m_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_hole_compensation.value != 0.f || m_config.xy_contour_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(); });
+ }
InterlockingGenerator::generate_interlocking_structure(this);
m_print->throw_if_canceled();
diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp
index f6026b1697..464804c276 100644
--- a/src/libslic3r/TriangleSelector.hpp
+++ b/src/libslic3r/TriangleSelector.hpp
@@ -15,6 +15,8 @@ enum class EnforcerBlockerType : int8_t {
NONE = 0,
ENFORCER = 1,
BLOCKER = 2,
+ // For the fuzzy skin, we use just two values (NONE and FUZZY_SKIN).
+ FUZZY_SKIN = ENFORCER,
// Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code.
Extruder1 = ENFORCER,
Extruder2 = BLOCKER,
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index a71c26508a..923cf948d6 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -135,6 +135,8 @@ set(SLIC3R_GUI_SOURCES
#GUI/Gizmos/GLGizmoSlaSupports.hpp
GUI/Gizmos/GLGizmoFdmSupports.cpp
GUI/Gizmos/GLGizmoFdmSupports.hpp
+ GUI/Gizmos/GLGizmoFuzzySkin.cpp
+ GUI/Gizmos/GLGizmoFuzzySkin.hpp
GUI/Gizmos/GLGizmoFlatten.cpp
GUI/Gizmos/GLGizmoFlatten.hpp
GUI/Gizmos/GLGizmoCut.cpp
diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp
index 9818260b00..f8bc5393b9 100644
--- a/src/slic3r/GUI/ConfigManipulation.cpp
+++ b/src/slic3r/GUI/ConfigManipulation.cpp
@@ -787,14 +787,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
toggle_line("support_interface_not_for_body",config->opt_int("support_interface_filament")&&!config->opt_int("support_filament"));
- bool has_fuzzy_skin = (config->opt_enum("fuzzy_skin") != FuzzySkinType::None);
- for (auto el : { "fuzzy_skin_thickness", "fuzzy_skin_point_distance", "fuzzy_skin_first_layer", "fuzzy_skin_noise_type"})
- toggle_line(el, has_fuzzy_skin);
-
NoiseType fuzzy_skin_noise_type = config->opt_enum("fuzzy_skin_noise_type");
- toggle_line("fuzzy_skin_scale", has_fuzzy_skin && fuzzy_skin_noise_type != NoiseType::Classic);
- toggle_line("fuzzy_skin_octaves", has_fuzzy_skin && fuzzy_skin_noise_type != NoiseType::Classic && fuzzy_skin_noise_type != NoiseType::Voronoi);
- toggle_line("fuzzy_skin_persistence", has_fuzzy_skin && (fuzzy_skin_noise_type == NoiseType::Perlin || fuzzy_skin_noise_type == NoiseType::Billow));
+ toggle_line("fuzzy_skin_scale", fuzzy_skin_noise_type != NoiseType::Classic);
+ toggle_line("fuzzy_skin_octaves", fuzzy_skin_noise_type != NoiseType::Classic && fuzzy_skin_noise_type != NoiseType::Voronoi);
+ toggle_line("fuzzy_skin_persistence", fuzzy_skin_noise_type == NoiseType::Perlin || fuzzy_skin_noise_type == NoiseType::Billow);
bool have_arachne = config->opt_enum("wall_generator") == PerimeterGeneratorType::Arachne;
for (auto el : { "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index e8cf2235b0..90678a58f0 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -1445,13 +1445,14 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
auto gizmo_type = gm.get_current_type();
if ( (gizmo_type == GLGizmosManager::FdmSupports
|| gizmo_type == GLGizmosManager::Seam
- || gizmo_type == GLGizmosManager::Cut)
+ || gizmo_type == GLGizmosManager::Cut
+ || gizmo_type == GLGizmosManager::FuzzySkin)
&& !vol->is_modifier) {
vol->force_neutral_color = true;
}
else if (gizmo_type == GLGizmosManager::BrimEars)
vol->force_neutral_color = false;
- else if (gizmo_type == GLGizmosManager::MmuSegmentation)
+ else if (gizmo_type == GLGizmosManager::MmSegmentation)
vol->is_active = false;
else
vol->force_native_color = true;
@@ -1924,7 +1925,7 @@ void GLCanvas3D::render(bool only_init)
//only_body = true;
only_current = true;
}
- else if ((gizmo_type == GLGizmosManager::FdmSupports) || (gizmo_type == GLGizmosManager::Seam) || (gizmo_type == GLGizmosManager::MmuSegmentation))
+ else if ((gizmo_type == GLGizmosManager::FdmSupports) || (gizmo_type == GLGizmosManager::Seam) || (gizmo_type == GLGizmosManager::MmSegmentation) || (gizmo_type == GLGizmosManager::FuzzySkin))
no_partplate = true;
else if (gizmo_type == GLGizmosManager::BrimEars && !camera.is_looking_downward())
show_grid = false;
@@ -3291,7 +3292,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
if (keyCode < '7') keyCode += 10;
m_timer_set_color.Stop();
}
- if (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation)
+ if (m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation)
obj_list->set_extruder_for_selected_items(keyCode - '0');
break;
}
@@ -3834,7 +3835,7 @@ void GLCanvas3D::on_render_timer(wxTimerEvent& evt)
void GLCanvas3D::on_set_color_timer(wxTimerEvent& evt)
{
auto obj_list = wxGetApp().obj_list();
- if (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation)
+ if (m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation)
obj_list->set_extruder_for_selected_items(1);
m_timer_set_color.Stop();
}
@@ -3989,7 +3990,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_dirty = true;
// do not return if dragging or tooltip not empty to allow for tooltip update
// also, do not return if the mouse is moving and also is inside MM gizmo to allow update seed fill selection
- if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation || !evt.Moving()))
+ if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation || !evt.Moving()))
return;
}
@@ -4179,7 +4180,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
&&*/ m_gizmos.get_current_type() != GLGizmosManager::FdmSupports
&& m_gizmos.get_current_type() != GLGizmosManager::Seam
&& m_gizmos.get_current_type() != GLGizmosManager::Cut
- && 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::Select : GLSelectionRectangle::Deselect);
m_dirty = true;
}
@@ -4330,7 +4332,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
const double mult = mult_pref.empty() ? 1.0 : std::stod(mult_pref);
const Vec3d rot = (Vec3d(pos.x(), pos.y(), 0.) - m_mouse.drag.start_position_3D) * (PI * TRACKBALLSIZE / 180.) * mult;
if (this->m_canvas_type == ECanvasType::CanvasAssembleView || m_gizmos.get_current_type() == GLGizmosManager::FdmSupports ||
- m_gizmos.get_current_type() == GLGizmosManager::Seam || m_gizmos.get_current_type() == GLGizmosManager::MmuSegmentation) {
+ m_gizmos.get_current_type() == GLGizmosManager::Seam || m_gizmos.get_current_type() == GLGizmosManager::MmSegmentation ||
+ m_gizmos.get_current_type() == GLGizmosManager::FuzzySkin) {
Vec3d rotate_target = Vec3d::Zero();
if (!m_selection.is_empty())
rotate_target = m_selection.get_bounding_box().center();
@@ -7258,7 +7261,8 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d&
&& m_gizmos.get_current_type() != GLGizmosManager::SlaSupports
&& m_gizmos.get_current_type() != GLGizmosManager::Hollow
&& m_gizmos.get_current_type() != GLGizmosManager::Seam
- && m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation);
+ && m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation
+ && m_gizmos.get_current_type() != GLGizmosManager::FuzzySkin);
*/
//bool show_texture = true;
//BBS set axes mode
@@ -8513,7 +8517,7 @@ void GLCanvas3D::_render_assemble_control()
GLVolume::explosion_ratio = m_explosion_ratio = 1.0;
return;
}
- if (m_gizmos.get_current_type() == GLGizmosManager::EType::MmuSegmentation) {
+ if (m_gizmos.get_current_type() == GLGizmosManager::EType::MmSegmentation) {
m_gizmos.m_assemble_view_data->model_objects_clipper()->set_position(0.0, true);
return;
}
diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp
index 07d1bbc006..ac4a2c3070 100644
--- a/src/slic3r/GUI/GUI_ObjectList.cpp
+++ b/src/slic3r/GUI/GUI_ObjectList.cpp
@@ -1290,8 +1290,8 @@ void ObjectList::list_manipulation(const wxPoint& mouse_pos, bool evt_context_me
ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID();
if (node && node->HasColorPainting()) {
GLGizmosManager& gizmos_mgr = wxGetApp().plater()->get_view3D_canvas3D()->get_gizmos_manager();
- if (gizmos_mgr.get_current_type() != GLGizmosManager::EType::MmuSegmentation)
- gizmos_mgr.open_gizmo(GLGizmosManager::EType::MmuSegmentation);
+ if (gizmos_mgr.get_current_type() != GLGizmosManager::EType::MmSegmentation)
+ gizmos_mgr.open_gizmo(GLGizmosManager::EType::MmSegmentation);
else
gizmos_mgr.reset_all_states();
}
@@ -2401,12 +2401,6 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
break;
// BBS: remove CustomSeam
- case InfoItemType::MmuSegmentation:
- cnv->get_gizmos_manager().reset_all_states();
- Plater::TakeSnapshot(plater, "Remove color painting");
- for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
- mv->mmu_segmentation_facets.reset();
- break;
case InfoItemType::CutConnectors:
if (!del_from_cut_object(true)) {
@@ -2415,6 +2409,20 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
}
break;
+ case InfoItemType::MmSegmentation:
+ cnv->get_gizmos_manager().reset_all_states();
+ Plater::TakeSnapshot(plater, "Remove color painting");
+ for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
+ mv->mmu_segmentation_facets.reset();
+ break;
+
+ case InfoItemType::FuzzySkin:
+ cnv->get_gizmos_manager().reset_all_states();
+ Plater::TakeSnapshot(plater, _u8L("Remove paint-on fuzzy skin"));
+ for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
+ mv->fuzzy_skin_facets.reset();
+ break;
+
// BBS: remove Sinking
case InfoItemType::Undef : assert(false); break;
}
@@ -3335,11 +3343,13 @@ void ObjectList::part_selection_changed()
case InfoItemType::CustomSupports:
// BBS: remove CustomSeam
//case InfoItemType::CustomSeam:
- case InfoItemType::MmuSegmentation:
+ case InfoItemType::MmSegmentation:
+ case InfoItemType::FuzzySkin:
{
GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports :
/*info_type == InfoItemType::CustomSeam ? GLGizmosManager::EType::Seam :*/
- GLGizmosManager::EType::MmuSegmentation;
+ info_type == InfoItemType::FuzzySkin ? GLGizmosManager::EType::FuzzySkin :
+ GLGizmosManager::EType::MmSegmentation;
GLGizmosManager& gizmos_mgr = wxGetApp().plater()->get_view3D_canvas3D()->get_gizmos_manager();
if (gizmos_mgr.get_current_type() != gizmo_type)
gizmos_mgr.open_gizmo(gizmo_type);
@@ -5607,8 +5617,8 @@ void ObjectList::OnEditingStarted(wxDataViewEvent &event)
ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID();
if (node->HasColorPainting()) {
GLGizmosManager& gizmos_mgr = wxGetApp().plater()->get_view3D_canvas3D()->get_gizmos_manager();
- if (gizmos_mgr.get_current_type() != GLGizmosManager::EType::MmuSegmentation)
- gizmos_mgr.open_gizmo(GLGizmosManager::EType::MmuSegmentation);
+ if (gizmos_mgr.get_current_type() != GLGizmosManager::EType::MmSegmentation)
+ gizmos_mgr.open_gizmo(GLGizmosManager::EType::MmSegmentation);
else
gizmos_mgr.reset_all_states();
}
@@ -5945,11 +5955,6 @@ ModelObject* ObjectList::object(const int obj_idx) const
return (*m_objects)[obj_idx];
}
-bool ObjectList::has_paint_on_segmentation()
-{
- return m_objects_model->HasInfoItem(InfoItemType::MmuSegmentation);
-}
-
void ObjectList::apply_object_instance_transfrom_to_all_volumes(ModelObject *model_object, bool need_update_assemble_matrix)
{
const Geometry::Transformation &instance_transformation = model_object->instances[0]->get_transformation();
diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp
index f8058dd701..9eff46f034 100644
--- a/src/slic3r/GUI/GUI_ObjectList.hpp
+++ b/src/slic3r/GUI/GUI_ObjectList.hpp
@@ -444,7 +444,6 @@ public:
void set_extruder_for_selected_items(const int extruder);
wxDataViewItemArray reorder_volumes_and_get_selection(int obj_idx, std::function add_to_selection = nullptr);
void apply_volumes_order();
- bool has_paint_on_segmentation();
// BBS
void on_plate_added(PartPlate* part_plate);
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
index 2d5213d263..3c831aa98c 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp
@@ -151,54 +151,6 @@ bool GLGizmoFdmSupports::on_key_down_select_tool_type(int keyCode) {
return true;
}
-// BBS
-void GLGizmoFdmSupports::render_triangles(const Selection& selection) const
-{
- ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data();
- auto* shader = wxGetApp().get_shader("mm_gouraud");
- if (!shader)
- return;
- shader->start_using();
- shader->set_uniform("clipping_plane", clp_data.clp_dataf);
- shader->set_uniform("z_range", clp_data.z_range);
- ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
-
- const ModelObject* mo = m_c->selection_info()->model_object();
- int mesh_id = -1;
- for (const ModelVolume* mv : mo->volumes) {
- if (!mv->is_model_part())
- continue;
-
- ++mesh_id;
-
- const Transform3d trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix();
-
- bool is_left_handed = trafo_matrix.matrix().determinant() < 0.;
- if (is_left_handed)
- glsafe(::glFrontFace(GL_CW));
-
- const Camera& camera = wxGetApp().plater()->get_camera();
- const Transform3d& view_matrix = camera.get_view_matrix();
- shader->set_uniform("view_model_matrix", view_matrix * trafo_matrix);
- shader->set_uniform("projection_matrix", camera.get_projection_matrix());
- const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
- shader->set_uniform("view_normal_matrix", view_normal_matrix);
-
- float normal_z = -::cos(Geometry::deg2rad(m_highlight_by_angle_threshold_deg));
- Matrix3f normal_matrix = static_cast(trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose().cast