Add fuzzy skin painting (#9979)

* SPE-2486: Refactor function apply_mm_segmentation() to prepare support for fuzzy skin painting.

(cherry picked from commit 2c06c81159f7aadd6ac20c7a7583c8f4959a5601)

* SPE-2585: Fix empty layers when multi-material painting and modifiers are used.

(cherry picked from commit 4b3da02ec26d43bfad91897cb34779fb21419e3e)

* Update project structure to match Prusa

* SPE-2486: Add a new gizmo for fuzzy skin painting.

(cherry picked from commit 886faac74ebe6978b828f51be62d26176e2900e5)

* Fix render

* Remove duplicated painting gizmo `render_triangles` code

* SPE-2486: Extend multi-material segmentation to allow segmentation of any painted faces.

(cherry picked from commit 519f5eea8e3be0d7c2cd5d030323ff264727e3d0)

---------

Co-authored-by: Lukáš Hejl <hejl.lukas@gmail.com>

* SPE-2486: Implement segmentation of layers based on fuzzy skin painting.

(cherry picked from commit 800b742b950438c5ed8323693074b6171300131c)

* SPE-2486: Separate fuzzy skin implementation into the separate file.

(cherry picked from commit efd95c1c66dc09fca7695fb82405056c687c2291)

* Move more fuzzy code to separate file

* Don't hide fuzzy skin option, so it can be applied to paint on fuzzy

* Fix build

* Add option group for fuzzy skin

* Update icon color

* Fix reset painting

* Update UI style

* Store fuzzy painting in bbs_3mf

* Add missing fuzzy paint code

* SPE-2486: Limit the depth of the painted fuzzy skin regions to make regions cover just external perimeters.

This reduces the possibility of artifacts that could happen during regions merging.

(cherry picked from commit fa2663f02647f80b239da4f45d92ef66f5ce048a)

* Update icons

---------

Co-authored-by: yw4z <ywsyildiz@gmail.com>

* Make the region compatible check a separate function

* Only warn about multi-material if it's truly multi-perimeters

* Improve gizmo UI & tooltips

---------

Co-authored-by: Lukáš Hejl <hejl.lukas@gmail.com>
Co-authored-by: yw4z <ywsyildiz@gmail.com>
This commit is contained in:
Noisyfox
2025-07-18 16:01:25 +08:00
committed by GitHub
parent c00502638c
commit 50e64d5961
50 changed files with 1614 additions and 940 deletions

View File

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

View File

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

View File

@@ -0,0 +1,391 @@
#include <random>
#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::thread::id>()(std::this_thread::get_id()));
thread_local std::uniform_real_distribution<double> 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<noise::module::Module> 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<noise::module::Perlin>(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<noise::module::Billow>(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<noise::module::RidgedMulti>(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<noise::module::Voronoi>(voronoi_noise);
} else {
return std::make_unique<UniformNoise>();
}
}
// Thanks Cura developers for this function.
void fuzzy_polyline(Points& poly, bool closed, coordf_t slice_z, const FuzzySkinConfig& cfg)
{
std::unique_ptr<noise::module::Module> 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>();
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<coord_t>();
double r = noise->GetValue(unscale_(pa.x()), unscale_(pa.y()), slice_z) * cfg.thickness;
out.emplace_back(pa + (perp(p0p1).cast<double>().normalized() * r).cast<coord_t>());
}
dist_left_over = p0pa_dist - p0p1_size;
p0 = &p1;
}
while (out.size() < 3) {
size_t point_idx = poly.size() - 2;
out.emplace_back(poly[point_idx]);
if (point_idx == 0)
break;
-- point_idx;
}
if (out.size() >= 3)
poly = std::move(out);
}
// 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::module::Module> 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>();
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<coord_t>();
double r = noise->GetValue(unscale_(pa.x()), unscale_(pa.y()), slice_z) * cfg.thickness;
out.emplace_back(pa + (perp(p0p1).cast<double>().normalized() * r).cast<coord_t>(), p1.w, p1.perimeter_index);
}
dist_left_over = p0pa_dist - p0p1_size;
p0 = &p1;
}
while (out.size() < 3) {
size_t point_idx = ext_lines.size() - 2;
out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index);
if (point_idx == 0)
break;
--point_idx;
}
if (ext_lines.back().p == ext_lines.front().p) // Connect endpoints.
out.front().p = out.back().p;
if (out.size() >= 3)
ext_lines = 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<FuzzySkinConfig, SurfacesPtr> regions;
for (auto region : *g.compatible_regions) {
const auto& region_config = region->region().config();
const FuzzySkinConfig cfg{region_config.fuzzy_skin,
scaled<coord_t>(region_config.fuzzy_skin_thickness.value),
scaled<coord_t>(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<std::pair<const FuzzySkinConfig&, const ExPolygons&>> 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<std::pair<const FuzzySkinConfig&, const ExPolygons&>> 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<Arachne::ExtrusionJunction> 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 = [&current_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

View File

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

View File

@@ -4,7 +4,7 @@
#ifndef INTERLOCKING_GENERATOR_HPP
#define INTERLOCKING_GENERATOR_HPP
#include "../Print.hpp"
#include "libslic3r/Print.hpp"
#include "VoxelUtils.hpp"
namespace Slic3r {

View File

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

View File

@@ -6,8 +6,8 @@
#include <functional>
#include "../Polygon.hpp"
#include "../ExPolygon.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/ExPolygon.hpp"
namespace Slic3r
{

View File

@@ -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<std::string> custom_supports;
std::vector<std::string> custom_seam;
std::vector<std::string> mmu_segmentation;
std::vector<std::string> 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; i<triangles_count; ++i) {
size_t index = volume_data.first_triangle_id + i;
assert(index < geometry.custom_supports.size());
@@ -2167,10 +2172,13 @@ ModelVolumeType type_from_string(const std::string &s)
volume->seam_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())

View File

@@ -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<std::string> custom_supports;
std::vector<std::string> custom_seam;
std::vector<std::string> mmu_segmentation;
std::vector<std::string> fuzzy_skin;
// BBS
std::vector<std::string> 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; i<triangles_count; ++i) {
assert(i < sub_object->geometry.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();

View File

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

View File

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

View File

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

View File

@@ -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<int> 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<class Archive> 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<CalibPressureAdvancePattern> 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.

View File

@@ -408,7 +408,7 @@ static inline Polygon to_polygon(const std::vector<std::pair<size_t, Linef>> &id
return poly_out;
}
static std::vector<ExPolygons> extract_colored_segments(const MMU_Graph &graph, const size_t num_extruders)
static std::vector<ExPolygons> extract_colored_segments(const MMU_Graph& graph, const size_t num_facets_states)
{
std::vector<bool> used_arcs(graph.arcs.size(), false);
@@ -416,7 +416,7 @@ static std::vector<ExPolygons> 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> expolygons_segments(num_extruders + 1);
std::vector<ExPolygons> 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<ExPolygons> extract_colored_segments(const std::vector<ColoredLines> &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<Point> 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<coord_t>(vertex) || !bbox.contains(Geometry::VoronoiUtils::to_point(vertex).cast<coord_t>()))
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<Point> cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, colored_lines.begin(), colored_lines.end()); cell_range.is_valid()) {
const ColoredLine &current_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<coord_t>())) {
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<ExPolygons> 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<Point> 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<coord_t>());
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<ExPolygons> &input_expolygons,
std::vector<std::vector<ExPolygons>> &segmented_regions,
const float cut_width,
const float interlocking_depth,
const std::function<void()> &throw_on_cancel_callback)
{
BOOST_LOG_TRIVIAL(debug) << "MM segmentation - cutting segmented layers in parallel - begin";
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - cutting segmented layers in parallel - begin";
const float interlocking_cut_width = interlocking_depth > 0.f ? std::max(cut_width - interlocking_depth, 0.f) : 0.f;
tbb::parallel_for(tbb::blocked_range<size_t>(0, segmented_regions.size()),
[&segmented_regions, &input_expolygons, &cut_width, &interlocking_depth, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
@@ -1302,7 +1164,7 @@ static void cut_segmented_layers(const std::vector<ExPolygons> &input_exp
}
}
}); // end of parallel_for
BOOST_LOG_TRIVIAL(debug) << "MM segmentation - cutting segmented layers in parallel - end";
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - cutting segmented layers in parallel - end";
}
static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d &trafo)
@@ -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<std::vector<ExPolygons>> mmu_segmentation_top_and_bottom_layers(const PrintObject &print_object,
const std::vector<ExPolygons> &input_expolygons,
const std::function<void()> &throw_on_cancel_callback)
// Returns segmentation of top and bottom layers based on painting in segmentation gizmos.
static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_layers(const PrintObject &print_object,
const std::vector<ExPolygons> &input_expolygons,
const std::function<ModelVolumeFacetsInfo(const ModelVolume &)> &extract_facets_info,
const size_t num_facets_states,
const std::function<void()> &throw_on_cancel_callback)
{
// 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<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
// Project upwards pointing painted triangles over top surfaces,
// project downards pointing painted triangles over bottom surfaces.
std::vector<std::vector<Polygons>> top_raw(num_extruders), bottom_raw(num_extruders);
std::vector<std::vector<Polygons>> top_raw(num_facets_states), bottom_raw(num_facets_states);
std::vector<float> zs = zs_from_layers(layers);
Transform3d object_trafo = print_object.trafo_centered();
@@ -1350,8 +1213,8 @@ static inline std::vector<std::vector<ExPolygons>> 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<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
}
}
auto filter_out_small_polygons = [&num_extruders, &num_layers](std::vector<std::vector<Polygons>> &raw_surfaces, double min_area) -> void {
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx)
auto filter_out_small_polygons = [&num_facets_states, &num_layers](std::vector<std::vector<Polygons>> &raw_surfaces, double min_area) -> void {
for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx)
if (!raw_surfaces[extruder_idx].empty())
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<std::vector<ExPolygons>> 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<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
}
}
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_extruders);
std::vector<std::vector<ExPolygons>> triangles_by_color_top(num_extruders);
triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_facets_states);
std::vector<std::vector<ExPolygons>> triangles_by_color_top(num_facets_states);
triangles_by_color_bottom.assign(num_facets_states, std::vector<ExPolygons>(num_layers * 2));
triangles_by_color_top.assign(num_facets_states, std::vector<ExPolygons>(num_layers * 2));
// 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<std::vector<ExPolygons>> shell_triangles_by_color_bottom(num_extruders);
std::vector<std::vector<ExPolygons>> shell_triangles_by_color_top(num_extruders);
shell_triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
shell_triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
std::vector<std::vector<ExPolygons>> shell_triangles_by_color_bottom(num_facets_states);
std::vector<std::vector<ExPolygons>> shell_triangles_by_color_top(num_facets_states);
shell_triangles_by_color_bottom.assign(num_facets_states, std::vector<ExPolygons>(num_layers * 2));
shell_triangles_by_color_top.assign(num_facets_states, std::vector<ExPolygons>(num_layers * 2));
struct LayerColorStat {
// Number of regions for a queried color.
@@ -1504,13 +1367,13 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
return out;
};
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers, granularity), [&granularity, &num_layers, &num_extruders, &layer_color_stat, &top_raw, &triangles_by_color_top,
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers, granularity), [&granularity, &num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top,
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom,
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
size_t group_idx = range.begin() / granularity;
size_t layer_idx_offset = (group_idx & 1) * num_layers;
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
for (size_t color_idx = 0; color_idx < num_extruders; ++ color_idx) {
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) {
throw_on_cancel_callback();
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
@@ -1557,8 +1420,8 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
}
});
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_extruders);
triangles_by_color_merged.assign(num_extruders, std::vector<ExPolygons>(num_layers));
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_facets_states);
triangles_by_color_merged.assign(num_facets_states, std::vector<ExPolygons>(num_layers));
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&triangles_by_color_merged, &triangles_by_color_bottom, &triangles_by_color_top, &num_layers, &throw_on_cancel_callback,
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
@@ -1602,6 +1465,7 @@ static inline std::vector<std::vector<ExPolygons>> 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<std::vector<ExPolygons>> merge_segmented_layers(
const std::vector<std::vector<ExPolygons>> &segmented_regions,
std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers,
const size_t num_extruders,
const std::function<void()> &throw_on_cancel_callback)
static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::vector<std::vector<ExPolygons>> &segmented_regions,
std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers,
const size_t num_facets_states,
const std::function<void()> &throw_on_cancel_callback)
{
const size_t num_layers = segmented_regions.size();
std::vector<std::vector<ExPolygons>> segmented_regions_merged(num_layers);
segmented_regions_merged.assign(num_layers, std::vector<ExPolygons>(num_extruders));
assert(num_extruders + 1 == top_and_bottom_layers.size());
segmented_regions_merged.assign(num_layers, std::vector<ExPolygons>(num_facets_states - 1));
assert(!top_and_bottom_layers.size() || num_facets_states == top_and_bottom_layers.size());
BOOST_LOG_TRIVIAL(debug) << "MM segmentation - merging segmented layers in parallel - begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
assert(segmented_regions[layer_idx].size() == num_extruders + 1);
assert(segmented_regions[layer_idx].size() == num_facets_states);
// Zero is skipped because it is the default color of the volume
for (size_t extruder_id = 1; extruder_id < num_extruders + 1; ++extruder_id) {
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) {
throw_on_cancel_callback();
if (!segmented_regions[layer_idx][extruder_id].empty()) {
ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id];
for (const std::vector<ExPolygons> &top_and_bottom_by_extruder : top_and_bottom_layers)
if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty())
segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]);
if (!top_and_bottom_layers.empty()) {
for (const std::vector<ExPolygons> &top_and_bottom_by_extruder : top_and_bottom_layers) {
if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty()) {
segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]);
}
}
}
segmented_regions_merged[layer_idx][extruder_id - 1] = std::move(segmented_regions_trimmed);
}
if (!top_and_bottom_layers[extruder_id][layer_idx].empty()) {
if (!top_and_bottom_layers.empty() && !top_and_bottom_layers[extruder_id][layer_idx].empty()) {
bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty();
append(segmented_regions_merged[layer_idx][extruder_id - 1], top_and_bottom_layers[extruder_id][layer_idx]);
@@ -1996,7 +1863,7 @@ static std::vector<std::vector<ExPolygons>> merge_segmented_layers(
}
}
}); // end of parallel_for
BOOST_LOG_TRIVIAL(debug) << "MM segmentation - merging segmented layers in parallel - end";
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - End";
return segmented_regions_merged;
}
@@ -2085,12 +1952,18 @@ static bool has_layer_only_one_color(const std::vector<ColoredLines> &colored_po
return true;
}
std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback)
std::vector<std::vector<ExPolygons>> segmentation_by_painting(const PrintObject &print_object,
const std::function<ModelVolumeFacetsInfo(const ModelVolume &)> &extract_facets_info,
const size_t num_facets_states,
const float segmentation_max_width,
const float segmentation_interlocking_depth,
const bool segmentation_interlocking_beam,
const IncludeTopAndBottomLayers include_top_and_bottom_layers,
const std::function<void()> &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<std::vector<ExPolygons>> segmented_regions(num_layers);
segmented_regions.assign(num_layers, std::vector<ExPolygons>(num_extruders + 1));
segmented_regions.assign(num_layers, std::vector<ExPolygons>(num_facets_states));
std::vector<std::vector<PaintedLine>> painted_lines(num_layers);
std::array<std::mutex, 64> painted_lines_mutex;
std::vector<EdgeGrid::Grid> edge_grids(num_layers);
@@ -2104,7 +1977,7 @@ std::vector<std::vector<ExPolygons>> 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<size_t>(0, num_layers), [&layers, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
@@ -2131,7 +2004,7 @@ std::vector<std::vector<ExPolygons>> 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<BoundingBox> layer_bboxes(num_layers);
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
@@ -2154,12 +2027,13 @@ std::vector<std::vector<ExPolygons>> 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<size_t>(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<size_t> &range) {
const ModelVolumeFacetsInfo facets_info = extract_facets_info(*mv);
tbb::parallel_for(tbb::blocked_range<size_t>(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<size_t> &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<std::vector<ExPolygons>> 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<PaintedLine> &pl) { return !pl.empty(); });
BOOST_LOG_TRIVIAL(debug) << "MM segmentation - layers segmentation in parallel - begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&edge_grids, &input_expolygons, &painted_lines, &segmented_regions, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - layers segmentation in parallel - begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&edge_grids, &input_expolygons, &painted_lines, &segmented_regions, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
if (!painted_lines[layer_idx].empty()) {
@@ -2279,7 +2153,7 @@ std::vector<std::vector<ExPolygons>> 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<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
}
}
}); // end of parallel_for
BOOST_LOG_TRIVIAL(debug) << "MM segmentation - layers segmentation in parallel - end";
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - layers segmentation in parallel - end";
throw_on_cancel_callback();
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<std::vector<ExPolygons>> top_and_bottom_layers = mmu_segmentation_top_and_bottom_layers(print_object, input_expolygons, throw_on_cancel_callback);
throw_on_cancel_callback();
std::vector<std::vector<ExPolygons>> top_and_bottom_layers;
if (include_top_and_bottom_layers == IncludeTopAndBottomLayers::Yes) {
top_and_bottom_layers = segmentation_top_and_bottom_layers(print_object, input_expolygons, extract_facets_info, num_facets_states, throw_on_cancel_callback);
throw_on_cancel_callback();
}
std::vector<std::vector<ExPolygons>> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_extruders, throw_on_cancel_callback);
std::vector<std::vector<ExPolygons>> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback);
throw_on_cancel_callback();
#ifdef MM_SEGMENTATION_DEBUG_REGIONS
@@ -2318,4 +2193,37 @@ std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
return segmented_regions_merged;
}
// Returns multi-material segmentation based on painting in multi-material segmentation gizmo
std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback) {
const size_t num_facets_states = print_object.print()->config().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<std::vector<ExPolygons>> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback) {
const size_t num_facets_states = 2; // Unpainted facets and facets painted with fuzzy skin.
const auto extract_facets_info = [](const ModelVolume &mv) -> ModelVolumeFacetsInfo {
return {mv.fuzzy_skin_facets, mv.is_fuzzy_skin_painted(), false};
};
// Because we apply fuzzy skin just on external perimeters, we limit the depth of fuzzy skin
// by the maximal extrusion width of external perimeters.
float max_external_perimeter_width = 0.;
for (size_t region_idx = 0; region_idx < print_object.num_printing_regions(); ++region_idx) {
const PrintRegion &region = print_object.printing_region(region_idx);
max_external_perimeter_width = std::max<float>(max_external_perimeter_width, region.flow(print_object, frExternalPerimeter, print_object.config().layer_height).width());
}
return segmentation_by_painting(print_object, extract_facets_info, num_facets_states, max_external_perimeter_width, 0.f, false, IncludeTopAndBottomLayers::No, throw_on_cancel_callback);
}
} // namespace Slic3r

View File

@@ -6,8 +6,11 @@
namespace Slic3r {
class PrintObject;
class ExPolygon;
class ModelVolume;
class PrintObject;
class FacetsAnnotation;
using ExPolygons = std::vector<ExPolygon>;
struct ColoredLine
@@ -20,9 +23,35 @@ struct ColoredLine
using ColoredLines = std::vector<ColoredLine>;
// 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<std::vector<ExPolygons>> segmentation_by_painting(const PrintObject &print_object,
const std::function<ModelVolumeFacetsInfo(const ModelVolume &)> &extract_facets_info,
size_t num_facets_states,
float segmentation_max_width,
float segmentation_interlocking_depth,
bool segmentation_interlocking_beam,
IncludeTopAndBottomLayers include_top_and_bottom_layers,
const std::function<void()> &throw_on_cancel_callback);
// Returns multi-material segmentation based on painting in multi-material segmentation gizmo
std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback);
// Returns fuzzy skin segmentation based on painting in fuzzy skin segmentation gizmo
std::vector<std::vector<ExPolygons>> fuzzy_skin_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback);
} // namespace Slic3r
namespace boost::polygon {

View File

@@ -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 <cmath>
#include <cassert>
#include <random>
#include <unordered_set>
#include <thread>
#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::thread::id>()(std::this_thread::get_id()));
thread_local std::uniform_real_distribution<double> 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<noise::module::Module> 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<noise::module::Perlin>(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<noise::module::Billow>(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<noise::module::RidgedMulti>(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<noise::module::Voronoi>(voronoi_noise);
} else {
return std::make_unique<UniformNoise>();
}
}
// 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::module::Module> 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>();
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<coord_t>();
double r = noise->GetValue(unscale_(pa.x()), unscale_(pa.y()), slice_z) * cfg.thickness;
out.emplace_back(pa + (perp(p0p1).cast<double>().normalized() * r).cast<coord_t>());
}
dist_left_over = p0pa_dist - p0p1_size;
p0 = &p1;
}
while (out.size() < 3) {
size_t point_idx = poly.size() - 2;
out.emplace_back(poly[point_idx]);
if (point_idx == 0)
break;
-- point_idx;
}
if (out.size() >= 3)
poly = std::move(out);
}
// Thanks Cura developers for this function.
static void fuzzy_extrusion_line(std::vector<Arachne::ExtrusionJunction>& ext_lines, coordf_t slice_z, const FuzzySkinConfig& cfg)
{
std::unique_ptr<noise::module::Module> 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<Arachne::ExtrusionJunction> out;
out.reserve(ext_lines.size());
for (auto& p1 : ext_lines) {
if (p0->p == p1.p) { // Connect endpoints.
out.emplace_back(p1.p, p1.w, p1.perimeter_index);
continue;
}
// 'a' is the (next) new point between p0 and p1
Vec2d p0p1 = (p1.p - p0->p).cast<double>();
double p0p1_size = p0p1.norm();
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<coord_t>();
double r = noise->GetValue(unscale_(pa.x()), unscale_(pa.y()), slice_z) * cfg.thickness;
out.emplace_back(pa + (perp(p0p1).cast<double>().normalized() * r).cast<coord_t>(), p1.w, p1.perimeter_index);
}
dist_left_over = p0pa_dist - p0p1_size;
p0 = &p1;
}
while (out.size() < 3) {
size_t point_idx = ext_lines.size() - 2;
out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index);
if (point_idx == 0)
break;
--point_idx;
}
if (ext_lines.back().p == ext_lines.front().p) // Connect endpoints.
out.front().p = out.back().p;
if (out.size() >= 3)
ext_lines = std::move(out);
}
using PerimeterGeneratorLoops = std::vector<PerimeterGeneratorLoop>;
template<class _T>
@@ -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<std::pair<const FuzzySkinConfig&, const ExPolygons&>> 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<PerimeterGeneratorArachneExtrusion>& 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<std::pair<const FuzzySkinConfig&, const ExPolygons&>> 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<Arachne::ExtrusionJunction> 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 = [&current_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<FuzzySkinConfig, SurfacesPtr> regions;
for (auto region : *g.compatible_regions) {
const auto& region_config = region->region().config();
const FuzzySkinConfig cfg{
region_config.fuzzy_skin,
scaled<coord_t>(region_config.fuzzy_skin_thickness.value),
scaled<coord_t>(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);

View File

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

View File

@@ -185,8 +185,6 @@ class ConstSupportLayerPtrsAdaptor : public ConstVectorOfPtrsAdaptor<SupportLaye
ConstSupportLayerPtrsAdaptor(const SupportLayerPtrs *data) : ConstVectorOfPtrsAdaptor<SupportLayer>(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<VolumeExtents> volumes;
// Sorted in the order of their source ModelVolumes, thus reflecting the order of region clipping, modifier overrides etc.
std::vector<VolumeRegion> volume_regions;
std::vector<PaintedRegion> painted_regions;
std::vector<VolumeRegion> volume_regions;
std::vector<PaintedRegion> painted_regions;
std::vector<FuzzySkinPaintedRegion> fuzzy_skin_painted_regions;
bool has_volume(const ObjectID id) const {
auto it = lower_bound_by_predicate(this->volumes.begin(), this->volumes.end(), [id](const VolumeExtents &l) { return l.volume_id < id; });
@@ -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<unsigned int> object_extruders() const;

View File

@@ -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<unsigned int> &painting_extruders,
PrintObjectRegions &print_object_regions,
const std::function<void(const PrintRegionConfig&, const PrintRegionConfig&, const t_config_option_keys&)> &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 &region : layer_range.fuzzy_skin_painted_regions) {
const PrintRegion &parent_print_region = *region.parent_print_object_region(layer_range);
PrintRegionConfig cfg = parent_print_region.config();
cfg.fuzzy_skin.value = FuzzySkinType::All;
if (cfg != region.region->config()) {
// Region configuration changed.
if (print_region_ref_cnt(*region.region) == 0) {
// Region is referenced for the first time. Just change its parameters.
// Stop the background process before assigning new configuration to the regions.
t_config_option_keys diff = region.region->config().diff(cfg);
callback_invalidate(region.region->config(), cfg, diff);
region.region->config_apply_only(cfg, diff, false);
} else {
// Region is referenced multiple times, thus the region is being split. We need to reslice.
return false;
}
}
print_region_ref_inc(*region.region);
}
}
// Lastly verify, whether some regions were not merged.
{
std::vector<const PrintRegion*> regions;
@@ -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<unsigned int> & painting_extruders)
const std::vector<unsigned int> &painting_extruders,
const bool has_painted_fuzzy_skin)
{
// Reuse the old object or generate a new one.
auto out = print_object_regions_old ? std::unique_ptr<PrintObjectRegions>(print_object_regions_old) : std::make_unique<PrintObjectRegions>();
@@ -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) {

View File

@@ -1,16 +1,16 @@
#include <boost/log/trivial.hpp>
#include <tbb/parallel_for.h>
#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 <boost/log/trivial.hpp>
#include <tbb/parallel_for.h>
#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<typename ThrowOnCancel>
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<std::vector<ExPolygons>> segmentation = multi_material_segmentation_by_painting(print_object, throw_on_cancel);
assert(segmentation.size() == print_object.layer_count());
tbb::parallel_for(
tbb::blocked_range<size_t>(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))),
[&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range<size_t> &range) {
const auto &layer_ranges = print_object.shared_regions()->layer_ranges;
double z = print_object.get_layer(range.begin())->slice_z;
double z = print_object.get_layer(int(range.begin()))->slice_z;
auto it_layer_range = layer_range_first(layer_ranges, z);
// BBS
const size_t num_extruders = print_object.print()->config().filament_diameter.size();
struct ByExtruder {
ExPolygons expolygons;
BoundingBox bbox;
};
std::vector<ByExtruder> by_extruder;
struct ByRegion {
ExPolygons expolygons;
bool needs_merge { false };
ExPolygons expolygons;
bool needs_merge { false };
};
std::vector<ByRegion> by_region;
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
std::vector<ByExtruder> by_extruder;
std::vector<ByRegion> 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 &region = 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<float>(5. * EPSILON), scaled<float>(5. * EPSILON));
}
if (!mine.empty()) {
ByRegion &dst = by_region[parent_print_region.print_object_region_id()];
if (dst.expolygons.empty()) {
dst.expolygons = union_ex(mine);
} else {
append(dst.expolygons, union_ex(mine));
dst.needs_merge = true;
}
}
}
}
// Re-create Surfaces of LayerRegions.
for (size_t region_id = 0; region_id < layer->region_count(); ++ region_id) {
for (int region_id = 0; region_id < layer.region_count(); ++region_id) {
ByRegion &src = by_region[region_id];
if (src.needs_merge)
if (src.needs_merge) {
// Multiple regions were merged into one.
src.expolygons = closing_ex(src.expolygons, float(scale_(10 * EPSILON)));
layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal);
src.expolygons = closing_ex(src.expolygons, scaled<float>(10. * EPSILON));
}
layer.get_region(region_id)->slices.set(std::move(src.expolygons), stInternal);
}
}
});
}
template<typename ThrowOnCancel>
void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel)
{
// Returns fuzzy skin segmentation based on painting in the fuzzy skin painting gizmo.
std::vector<std::vector<ExPolygons>> segmentation = fuzzy_skin_segmentation_by_painting(print_object, throw_on_cancel);
assert(segmentation.size() == print_object.layer_count());
struct ByRegion
{
ExPolygons expolygons;
bool needs_merge { false };
};
tbb::parallel_for(tbb::blocked_range<size_t>(0, segmentation.size(), std::max(segmentation.size() / 128, size_t(1))), [&print_object, &segmentation, throw_on_cancel](const tbb::blocked_range<size_t> &range) {
const auto &layer_ranges = print_object.shared_regions()->layer_ranges;
auto it_layer_range = layer_range_first(layer_ranges, print_object.get_layer(int(range.begin()))->slice_z);
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel();
Layer &layer = *print_object.get_layer(int(layer_idx));
it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z);
const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range;
assert(segmentation[layer_idx].size() == 1);
const ExPolygons &fuzzy_skin_segmentation = segmentation[layer_idx][0];
const BoundingBox fuzzy_skin_segmentation_bbox = get_extents(fuzzy_skin_segmentation);
if (fuzzy_skin_segmentation.empty())
continue;
// Split LayerRegions by painted fuzzy skin regions.
// layer_range.fuzzy_skin_painted_regions are sorted by parent PrintObject region ID.
std::vector<ByRegion> by_region(layer.region_count());
auto it_fuzzy_skin_region_begin = layer_range.fuzzy_skin_painted_regions.cbegin();
for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) {
if (it_fuzzy_skin_region_begin == layer_range.fuzzy_skin_painted_regions.cend())
continue;
const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx);
const PrintRegion &parent_print_region = parent_layer_region.region();
assert(parent_print_region.print_object_region_id() == parent_layer_region_idx);
if (parent_layer_region.slices.empty())
continue;
// Find the first FuzzySkinPaintedRegion, which overrides the parent PrintRegion.
auto it_fuzzy_skin_region = std::find_if(it_fuzzy_skin_region_begin, layer_range.fuzzy_skin_painted_regions.cend(), [&layer_range, &parent_print_region](const auto &fuzzy_skin_region) {
return fuzzy_skin_region.parent_print_object_region_id(layer_range) == parent_print_region.print_object_region_id();
});
if (it_fuzzy_skin_region == layer_range.fuzzy_skin_painted_regions.cend())
continue; // This LayerRegion isn't overrides by any FuzzySkinPaintedRegion.
assert(it_fuzzy_skin_region->parent_print_object_region(layer_range) == &parent_print_region);
// Update the beginning FuzzySkinPaintedRegion iterator for the next iteration.
it_fuzzy_skin_region_begin = std::next(it_fuzzy_skin_region);
const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices.surfaces);
Polygons layer_region_remaining_polygons = to_polygons(parent_layer_region.slices.surfaces);
// Don't trim by self, it is not reliable.
if (parent_layer_region_bbox.overlap(fuzzy_skin_segmentation_bbox) && it_fuzzy_skin_region->region != &parent_print_region) {
// Steal from this region.
const int target_region_id = it_fuzzy_skin_region->region->print_object_region_id();
ExPolygons stolen = intersection_ex(parent_layer_region.slices.surfaces, fuzzy_skin_segmentation);
if (!stolen.empty()) {
ByRegion &dst = by_region[target_region_id];
if (dst.expolygons.empty()) {
dst.expolygons = std::move(stolen);
} else {
append(dst.expolygons, std::move(stolen));
dst.needs_merge = true;
}
}
// Trim slices of this LayerRegion by the fuzzy skin region.
layer_region_remaining_polygons = diff(layer_region_remaining_polygons, fuzzy_skin_segmentation);
// Filter out unprintable polygons. Detailed explanation is inside apply_mm_segmentation.
if (!layer_region_remaining_polygons.empty()) {
layer_region_remaining_polygons = opening(union_ex(layer_region_remaining_polygons), scaled<float>(5. * EPSILON), scaled<float>(5. * EPSILON));
}
}
if (!layer_region_remaining_polygons.empty()) {
ByRegion &dst = by_region[parent_print_region.print_object_region_id()];
if (dst.expolygons.empty()) {
dst.expolygons = union_ex(layer_region_remaining_polygons);
} else {
append(dst.expolygons, union_ex(layer_region_remaining_polygons));
dst.needs_merge = true;
}
}
}
// Re-create Surfaces of LayerRegions.
for (int region_id = 0; region_id < layer.region_count(); ++region_id) {
ByRegion &src = by_region[region_id];
if (src.needs_merge) {
// Multiple regions were merged into one.
src.expolygons = closing_ex(src.expolygons, scaled<float>(10. * EPSILON));
}
layer.get_region(region_id)->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();

View File

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