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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 1614 additions and 940 deletions

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18"><rect x="2" y="6" width="2" height="1" style="fill:#009688;"/><rect y="6" width="1" height="1" style="fill:#949494;"/><rect x="5" y="6" width="2" height="1" style="fill:#009688;"/><rect x="8" y="6" width="2" height="1" style="fill:#009688;"/><rect x="11" y="6" width="2" height="1" style="fill:#009688;"/><rect x="1" y="4" width="2" height="1" style="fill:#009688;"/><rect x="4" y="4" width="2" height="1" style="fill:#009688;"/><rect x="7" y="4" width="2" height="1" style="fill:#009688;"/><rect x="2" y="2" width="2" height="1" style="fill:#009688;"/><rect x="5" y="2" width="2" height="1" style="fill:#009688;"/><rect x="8" y="2" width="1" height="1" style="fill:#009688;"/><rect x="10" y="4" width="2" height="1" style="fill:#009688;"/><rect x="2" y="10" width="2" height="1" style="fill:#009688;"/><rect y="10" width="1" height="1" style="fill:#949494;"/><rect x="5" y="10" width="2" height="1" style="fill:#009688;"/><rect x="8" y="10" width="2" height="1" style="fill:#009688;"/><rect x="1" y="8" width="2" height="1" style="fill:#009688;"/><rect x="4" y="8" width="2" height="1" style="fill:#009688;"/><rect x="7" y="8" width="2" height="1" style="fill:#009688;"/><rect x="10" y="8" width="1" height="1" style="fill:#009688;"/><rect x="1" y="12" width="2" height="1" style="fill:#009688;"/><rect x="2" y="14" width="2" height="1" style="fill:#009688;"/><rect x="5" y="14" width="1" height="1" style="fill:#009688;"/><rect x="4" y="12" width="2" height="1" style="fill:#009688;"/><rect x="7" y="12" width="1" height="1" style="fill:#009688;"/><path d="M.5,14.5v1c0,.55.45,1,1,1h14c.55,0,1-.45,1-1V1.5c0-.55-.45-1-1-1H1.5C.95.5.5.95.5,1.5v1" style="fill:none; stroke:#949494; stroke-linecap:round; stroke-linejoin:round;"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><polyline points=".5 1.5 .5 .5 14.5 .5 14.5 4.5" style="fill:none; stroke:#949494; stroke-linecap:square; stroke-linejoin:round;"/><polyline points="5.5 14.5 .5 14.5 .5 13.5" style="fill:none; stroke:#949494; stroke-linecap:square; stroke-linejoin:round;"/><rect x="2" y="5" width="2" height="1" style="fill:#009688;"/><rect x="5" y="5" width="2" height="1" style="fill:#009688;"/><rect x="8" y="5" width="2" height="1" style="fill:#009688;"/><rect x="1" y="3" width="2" height="1" style="fill:#009688;"/><rect x="4" y="3" width="2" height="1" style="fill:#009688;"/><rect x="7" y="3" width="2" height="1" style="fill:#009688;"/><rect x="2" y="9" width="2" height="1" style="fill:#009688;"/><rect x="5" y="9" width="2" height="1" style="fill:#009688;"/><rect x="1" y="7" width="2" height="1" style="fill:#009688;"/><rect x="4" y="7" width="2" height="1" style="fill:#009688;"/><rect x="7" y="7" width="2" height="1" style="fill:#009688;"/><rect x="1" y="11" width="2" height="1" style="fill:#009688;"/><rect x="4" y="11" width="1" height="1" style="fill:#009688;"/><polyline points="14.5 12.5 14.5 14.5 13.5 14.5" style="fill:none; stroke:#949494; stroke-linecap:square; stroke-linejoin:round;"/><path d="M7.79,14.5c-.92,0,.67,0,.67-1.67,0-.92.75-1.67,1.67-1.67s1.67.75,1.67,1.67-.75,1.67-1.67,1.67h.33-2.67Z" style="fill:none; stroke:#009688; stroke-miterlimit:10;"/><path d="M11.79,11.83l2.64-4.18c.29-.46-.36-.94-.71-.51l-3.26,4.02" style="fill:none; stroke:#009688; stroke-miterlimit:10;"/><rect y="5" width="1" height="1" style="fill:#949494;"/><rect y="9" width="1" height="1" style="fill:#949494;"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M34.87,23.38c.4-.64.14-1.31-.32-1.65-.47-.33-1.19-.36-1.66.22l-5.27,6.49c-.1,0-.19-.03-.29-.03-1.79,0-3.25,1.46-3.25,3.25,0,1.46-.84,1.97-1.2,2.19-.14.09-.47.29-.36.67.11.39.45.39.96.39h4.4v-.05c1.53-.26,2.7-1.6,2.7-3.21,0-.59-.17-1.14-.45-1.62l.37.23,4.36-6.91ZM27.33,33.93h-2.99c.39-.48.74-1.19.74-2.25,0-1.24,1.01-2.25,2.25-2.25s2.25,1.01,2.25,2.25-1.01,2.25-2.25,2.25ZM28.68,28.72l4.98-6.13c.11-.14.22-.1.3-.04.08.06.15.15.06.3l-4.24,6.71c-.3-.35-.67-.64-1.1-.84Z" style="fill:#009688;"/><path d="M33.5,35h-1c-.28,0-.5-.22-.5-.5s.22-.5.5-.5h1c.28,0,.5-.22.5-.5v-5c0-.28.22-.5.5-.5s.5.22.5.5v5c0,.83-.67,1.5-1.5,1.5ZM35,18.5V5.5c0-.83-.67-1.5-1.5-1.5H5.5c-.83,0-1.5.67-1.5,1.5v2c0,.28.22.5.5.5s.5-.22.5-.5v-2c0-.28.22-.5.5-.5h28c.28,0,.5.22.5.5v13c0,.28.22.5.5.5s.5-.22.5-.5ZM20,34.5c0-.28-.22-.5-.5-.5H5.5c-.28,0-.5-.22-.5-.5v-2c0-.28-.22-.5-.5-.5s-.5.22-.5.5v2c0,.83.67,1.5,1.5,1.5h14c.28,0,.5-.22.5-.5ZM5,13h-1v1h1v-1ZM5,19h-1v1h1v-1ZM5,25h-1v1h1v-1Z" style="fill:#2b3436;"/><path d="M9,14h-2v-1h2v1ZM15,13h-4v1h4v-1ZM13,10h-4v1h4v-1ZM19,10h-4v1h4v-1ZM24,10h-3v1h3v-1ZM11,7h-4v1h4v-1ZM17,7h-4v1h4v-1ZM21,7h-2v1h2v-1ZM7,10h-2v1h2v-1ZM22,13h-5v1h5v-1ZM28,13h-4v1h4v-1ZM11,19h-4v1h4v-1ZM17,19h-4v1h4v-1ZM14,16h-4v1h4v-1ZM20,16h-4v1h4v-1ZM26,16h-4v1h4v-1ZM8,16h-3v1h3v-1ZM23,19h-4v1h4v-1ZM9,25h-2v1h2v-1ZM20,25h-3v1h3v-1ZM15,25h-4v1h4v-1ZM13,22h-4v1h4v-1ZM22,22h-1v1h1v-1ZM19,22h-4v1h4v-1ZM7,22h-2v1h2v-1ZM13,28h-4v1h4v-1ZM11,31h-4v1h4v-1ZM14,31h-1v1h1v-1ZM7,28h-2v1h2v-1ZM17,28h-2v1h2v-1Z" style="fill:#009688;"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M34.87,23.38c.4-.64.14-1.31-.32-1.65-.47-.33-1.19-.36-1.66.22l-5.27,6.49c-.1,0-.19-.03-.29-.03-1.79,0-3.25,1.46-3.25,3.25,0,1.46-.84,1.97-1.2,2.19-.14.09-.47.29-.36.67.11.39.45.39.96.39h4.4v-.05c1.53-.26,2.7-1.6,2.7-3.21,0-.59-.17-1.14-.45-1.62l.37.23,4.36-6.91ZM27.33,33.93h-2.99c.39-.48.74-1.19.74-2.25,0-1.24,1.01-2.25,2.25-2.25s2.25,1.01,2.25,2.25-1.01,2.25-2.25,2.25ZM28.68,28.72l4.98-6.13c.11-.14.22-.1.3-.04.08.06.15.15.06.3l-4.24,6.71c-.3-.35-.67-.64-1.1-.84Z" style="fill:#009688;"/><path d="M19.5,35H5.5c-.83,0-1.5-.67-1.5-1.5v-2c0-.28.22-.5.5-.5s.5.22.5.5v2c0,.28.22.5.5.5h14c.28,0,.5.22.5.5s-.22.5-.5.5Z" style="fill:#b6b6b6;"/><path d="M9,14h-2v-1h2v1ZM15,13h-4v1h4v-1ZM13,10h-4v1h4v-1ZM19,10h-4v1h4v-1ZM24,10h-3v1h3v-1ZM11,7h-4v1h4v-1ZM17,7h-4v1h4v-1ZM21,7h-2v1h2v-1ZM7,10h-2v1h2v-1ZM22,13h-5v1h5v-1ZM28,13h-4v1h4v-1ZM11,19h-4v1h4v-1ZM17,19h-4v1h4v-1ZM14,16h-4v1h4v-1ZM20,16h-4v1h4v-1ZM26,16h-4v1h4v-1ZM8,16h-3v1h3v-1ZM23,19h-4v1h4v-1ZM9,25h-2v1h2v-1ZM20,25h-3v1h3v-1ZM15,25h-4v1h4v-1ZM13,22h-4v1h4v-1ZM22,22h-1v1h1v-1ZM19,22h-4v1h4v-1ZM7,22h-2v1h2v-1ZM13,28h-4v1h4v-1ZM11,31h-4v1h4v-1ZM14,31h-1v1h1v-1ZM7,28h-2v1h2v-1ZM17,28h-2v1h2v-1Z" style="fill:#009688;"/><path d="M33.5,35h-1c-.28,0-.5-.22-.5-.5s.22-.5.5-.5h1c.28,0,.5-.22.5-.5v-5c0-.28.22-.5.5-.5s.5.22.5.5v5c0,.83-.67,1.5-1.5,1.5ZM35,18.5V5.5c0-.83-.67-1.5-1.5-1.5H5.5c-.83,0-1.5.67-1.5,1.5v2c0,.28.22.5.5.5s.5-.22.5-.5v-2c0-.28.22-.5.5-.5h28c.28,0,.5.22.5.5v13c0,.28.22.5.5.5s.5-.22.5-.5ZM5,13h-1v1h1v-1ZM5,19h-1v1h1v-1ZM5,25h-1v1h1v-1Z" style="fill:#b6b6b6;"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

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,

View file

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

View file

@ -787,14 +787,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
toggle_line("support_interface_not_for_body",config->opt_int("support_interface_filament")&&!config->opt_int("support_filament"));
bool has_fuzzy_skin = (config->opt_enum<FuzzySkinType>("fuzzy_skin") != FuzzySkinType::None);
for (auto el : { "fuzzy_skin_thickness", "fuzzy_skin_point_distance", "fuzzy_skin_first_layer", "fuzzy_skin_noise_type"})
toggle_line(el, has_fuzzy_skin);
NoiseType fuzzy_skin_noise_type = config->opt_enum<NoiseType>("fuzzy_skin_noise_type");
toggle_line("fuzzy_skin_scale", has_fuzzy_skin && fuzzy_skin_noise_type != NoiseType::Classic);
toggle_line("fuzzy_skin_octaves", has_fuzzy_skin && fuzzy_skin_noise_type != NoiseType::Classic && fuzzy_skin_noise_type != NoiseType::Voronoi);
toggle_line("fuzzy_skin_persistence", has_fuzzy_skin && (fuzzy_skin_noise_type == NoiseType::Perlin || fuzzy_skin_noise_type == NoiseType::Billow));
toggle_line("fuzzy_skin_scale", fuzzy_skin_noise_type != NoiseType::Classic);
toggle_line("fuzzy_skin_octaves", fuzzy_skin_noise_type != NoiseType::Classic && fuzzy_skin_noise_type != NoiseType::Voronoi);
toggle_line("fuzzy_skin_persistence", fuzzy_skin_noise_type == NoiseType::Perlin || fuzzy_skin_noise_type == NoiseType::Billow);
bool have_arachne = config->opt_enum<PerimeterGeneratorType>("wall_generator") == PerimeterGeneratorType::Arachne;
for (auto el : { "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",

View file

@ -1445,13 +1445,14 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
auto gizmo_type = gm.get_current_type();
if ( (gizmo_type == GLGizmosManager::FdmSupports
|| gizmo_type == GLGizmosManager::Seam
|| gizmo_type == GLGizmosManager::Cut)
|| gizmo_type == GLGizmosManager::Cut
|| gizmo_type == GLGizmosManager::FuzzySkin)
&& !vol->is_modifier) {
vol->force_neutral_color = true;
}
else if (gizmo_type == GLGizmosManager::BrimEars)
vol->force_neutral_color = false;
else if (gizmo_type == GLGizmosManager::MmuSegmentation)
else if (gizmo_type == GLGizmosManager::MmSegmentation)
vol->is_active = false;
else
vol->force_native_color = true;
@ -1924,7 +1925,7 @@ void GLCanvas3D::render(bool only_init)
//only_body = true;
only_current = true;
}
else if ((gizmo_type == GLGizmosManager::FdmSupports) || (gizmo_type == GLGizmosManager::Seam) || (gizmo_type == GLGizmosManager::MmuSegmentation))
else if ((gizmo_type == GLGizmosManager::FdmSupports) || (gizmo_type == GLGizmosManager::Seam) || (gizmo_type == GLGizmosManager::MmSegmentation) || (gizmo_type == GLGizmosManager::FuzzySkin))
no_partplate = true;
else if (gizmo_type == GLGizmosManager::BrimEars && !camera.is_looking_downward())
show_grid = false;
@ -3291,7 +3292,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
if (keyCode < '7') keyCode += 10;
m_timer_set_color.Stop();
}
if (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation)
if (m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation)
obj_list->set_extruder_for_selected_items(keyCode - '0');
break;
}
@ -3834,7 +3835,7 @@ void GLCanvas3D::on_render_timer(wxTimerEvent& evt)
void GLCanvas3D::on_set_color_timer(wxTimerEvent& evt)
{
auto obj_list = wxGetApp().obj_list();
if (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation)
if (m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation)
obj_list->set_extruder_for_selected_items(1);
m_timer_set_color.Stop();
}
@ -3989,7 +3990,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_dirty = true;
// do not return if dragging or tooltip not empty to allow for tooltip update
// also, do not return if the mouse is moving and also is inside MM gizmo to allow update seed fill selection
if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation || !evt.Moving()))
if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation || !evt.Moving()))
return;
}
@ -4179,7 +4180,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
&&*/ m_gizmos.get_current_type() != GLGizmosManager::FdmSupports
&& m_gizmos.get_current_type() != GLGizmosManager::Seam
&& m_gizmos.get_current_type() != GLGizmosManager::Cut
&& m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) {
&& m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation
&& m_gizmos.get_current_type() != GLGizmosManager::FuzzySkin) {
m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect);
m_dirty = true;
}
@ -4330,7 +4332,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
const double mult = mult_pref.empty() ? 1.0 : std::stod(mult_pref);
const Vec3d rot = (Vec3d(pos.x(), pos.y(), 0.) - m_mouse.drag.start_position_3D) * (PI * TRACKBALLSIZE / 180.) * mult;
if (this->m_canvas_type == ECanvasType::CanvasAssembleView || m_gizmos.get_current_type() == GLGizmosManager::FdmSupports ||
m_gizmos.get_current_type() == GLGizmosManager::Seam || m_gizmos.get_current_type() == GLGizmosManager::MmuSegmentation) {
m_gizmos.get_current_type() == GLGizmosManager::Seam || m_gizmos.get_current_type() == GLGizmosManager::MmSegmentation ||
m_gizmos.get_current_type() == GLGizmosManager::FuzzySkin) {
Vec3d rotate_target = Vec3d::Zero();
if (!m_selection.is_empty())
rotate_target = m_selection.get_bounding_box().center();
@ -7258,7 +7261,8 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d&
&& m_gizmos.get_current_type() != GLGizmosManager::SlaSupports
&& m_gizmos.get_current_type() != GLGizmosManager::Hollow
&& m_gizmos.get_current_type() != GLGizmosManager::Seam
&& m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation);
&& m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation
&& m_gizmos.get_current_type() != GLGizmosManager::FuzzySkin);
*/
//bool show_texture = true;
//BBS set axes mode
@ -8513,7 +8517,7 @@ void GLCanvas3D::_render_assemble_control()
GLVolume::explosion_ratio = m_explosion_ratio = 1.0;
return;
}
if (m_gizmos.get_current_type() == GLGizmosManager::EType::MmuSegmentation) {
if (m_gizmos.get_current_type() == GLGizmosManager::EType::MmSegmentation) {
m_gizmos.m_assemble_view_data->model_objects_clipper()->set_position(0.0, true);
return;
}

View file

@ -1290,8 +1290,8 @@ void ObjectList::list_manipulation(const wxPoint& mouse_pos, bool evt_context_me
ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID();
if (node && node->HasColorPainting()) {
GLGizmosManager& gizmos_mgr = wxGetApp().plater()->get_view3D_canvas3D()->get_gizmos_manager();
if (gizmos_mgr.get_current_type() != GLGizmosManager::EType::MmuSegmentation)
gizmos_mgr.open_gizmo(GLGizmosManager::EType::MmuSegmentation);
if (gizmos_mgr.get_current_type() != GLGizmosManager::EType::MmSegmentation)
gizmos_mgr.open_gizmo(GLGizmosManager::EType::MmSegmentation);
else
gizmos_mgr.reset_all_states();
}
@ -2401,12 +2401,6 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
break;
// BBS: remove CustomSeam
case InfoItemType::MmuSegmentation:
cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, "Remove color painting");
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->mmu_segmentation_facets.reset();
break;
case InfoItemType::CutConnectors:
if (!del_from_cut_object(true)) {
@ -2415,6 +2409,20 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
}
break;
case InfoItemType::MmSegmentation:
cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, "Remove color painting");
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->mmu_segmentation_facets.reset();
break;
case InfoItemType::FuzzySkin:
cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, _u8L("Remove paint-on fuzzy skin"));
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->fuzzy_skin_facets.reset();
break;
// BBS: remove Sinking
case InfoItemType::Undef : assert(false); break;
}
@ -3335,11 +3343,13 @@ void ObjectList::part_selection_changed()
case InfoItemType::CustomSupports:
// BBS: remove CustomSeam
//case InfoItemType::CustomSeam:
case InfoItemType::MmuSegmentation:
case InfoItemType::MmSegmentation:
case InfoItemType::FuzzySkin:
{
GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports :
/*info_type == InfoItemType::CustomSeam ? GLGizmosManager::EType::Seam :*/
GLGizmosManager::EType::MmuSegmentation;
info_type == InfoItemType::FuzzySkin ? GLGizmosManager::EType::FuzzySkin :
GLGizmosManager::EType::MmSegmentation;
GLGizmosManager& gizmos_mgr = wxGetApp().plater()->get_view3D_canvas3D()->get_gizmos_manager();
if (gizmos_mgr.get_current_type() != gizmo_type)
gizmos_mgr.open_gizmo(gizmo_type);
@ -5607,8 +5617,8 @@ void ObjectList::OnEditingStarted(wxDataViewEvent &event)
ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID();
if (node->HasColorPainting()) {
GLGizmosManager& gizmos_mgr = wxGetApp().plater()->get_view3D_canvas3D()->get_gizmos_manager();
if (gizmos_mgr.get_current_type() != GLGizmosManager::EType::MmuSegmentation)
gizmos_mgr.open_gizmo(GLGizmosManager::EType::MmuSegmentation);
if (gizmos_mgr.get_current_type() != GLGizmosManager::EType::MmSegmentation)
gizmos_mgr.open_gizmo(GLGizmosManager::EType::MmSegmentation);
else
gizmos_mgr.reset_all_states();
}
@ -5945,11 +5955,6 @@ ModelObject* ObjectList::object(const int obj_idx) const
return (*m_objects)[obj_idx];
}
bool ObjectList::has_paint_on_segmentation()
{
return m_objects_model->HasInfoItem(InfoItemType::MmuSegmentation);
}
void ObjectList::apply_object_instance_transfrom_to_all_volumes(ModelObject *model_object, bool need_update_assemble_matrix)
{
const Geometry::Transformation &instance_transformation = model_object->instances[0]->get_transformation();

View file

@ -444,7 +444,6 @@ public:
void set_extruder_for_selected_items(const int extruder);
wxDataViewItemArray reorder_volumes_and_get_selection(int obj_idx, std::function<bool(const ModelVolume*)> add_to_selection = nullptr);
void apply_volumes_order();
bool has_paint_on_segmentation();
// BBS
void on_plate_added(PartPlate* part_plate);

View file

@ -151,54 +151,6 @@ bool GLGizmoFdmSupports::on_key_down_select_tool_type(int keyCode) {
return true;
}
// BBS
void GLGizmoFdmSupports::render_triangles(const Selection& selection) const
{
ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data();
auto* shader = wxGetApp().get_shader("mm_gouraud");
if (!shader)
return;
shader->start_using();
shader->set_uniform("clipping_plane", clp_data.clp_dataf);
shader->set_uniform("z_range", clp_data.z_range);
ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
const ModelObject* mo = m_c->selection_info()->model_object();
int mesh_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (!mv->is_model_part())
continue;
++mesh_id;
const Transform3d trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix();
bool is_left_handed = trafo_matrix.matrix().determinant() < 0.;
if (is_left_handed)
glsafe(::glFrontFace(GL_CW));
const Camera& camera = wxGetApp().plater()->get_camera();
const Transform3d& view_matrix = camera.get_view_matrix();
shader->set_uniform("view_model_matrix", view_matrix * trafo_matrix);
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
shader->set_uniform("view_normal_matrix", view_normal_matrix);
float normal_z = -::cos(Geometry::deg2rad(m_highlight_by_angle_threshold_deg));
Matrix3f normal_matrix = static_cast<Matrix3f>(trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>());
shader->set_uniform("volume_world_matrix", trafo_matrix);
shader->set_uniform("volume_mirrored", is_left_handed);
shader->set_uniform("slope.actived", m_parent.is_using_slope());
shader->set_uniform("slope.volume_world_normal_matrix", normal_matrix);
shader->set_uniform("slope.normal_z", normal_z);
m_triangle_selectors[mesh_id]->render(m_imgui, trafo_matrix);
if (is_left_handed)
glsafe(::glFrontFace(GL_CCW));
}
}
void GLGizmoFdmSupports::on_set_state()
{
GLGizmoPainterBase::on_set_state();

View file

@ -32,8 +32,6 @@ protected:
void on_render_input_window(float x, float y, float bottom_limit) override;
std::string on_get_name() const override;
// BBS
void render_triangles(const Selection& selection) const override;
void on_set_state() override;
void show_tooltip_information(float caption_max, float x, float y);
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;

View file

@ -0,0 +1,390 @@
#include "GLGizmoFuzzySkin.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/Print.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include <GL/glew.h>
#include <algorithm>
namespace Slic3r::GUI {
void GLGizmoFuzzySkin::on_shutdown()
{
m_parent.use_slope(false);
m_parent.toggle_model_objects_visibility(true);
}
std::string GLGizmoFuzzySkin::on_get_name() const
{
return _u8L("Paint-on fuzzy skin");
}
bool GLGizmoFuzzySkin::on_init()
{
m_shortcut_key = WXK_CONTROL_H;
m_desc["clipping_of_view_caption"] = _L("Alt + Mouse wheel");
m_desc["clipping_of_view"] = _L("Section view");
m_desc["reset_direction"] = _L("Reset direction");
m_desc["cursor_size_caption"] = _L("Ctrl + Mouse wheel");
m_desc["cursor_size"] = _L("Brush size");
m_desc["cursor_type"] = _L("Brush shape") ;
m_desc["add_fuzzy_skin_caption"] = _L("Left mouse button");
m_desc["add_fuzzy_skin"] = _L("Add fuzzy skin");
m_desc["remove_fuzzy_skin_caption"] = _L("Shift + Left mouse button");
m_desc["remove_fuzzy_skin"] = _L("Remove fuzzy skin");
m_desc["remove_all"] = _L("Erase all painting");
m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere");
m_desc["pointer"] = _L("Triangles");
m_desc["tool_type"] = _L("Tool type");
m_desc["tool_brush"] = _L("Brush");
m_desc["tool_smart_fill"] = _L("Smart fill");
m_desc["smart_fill_angle_caption"] = _L("Ctrl + Mouse wheel");
m_desc["smart_fill_angle"] = _L("Smart fill angle");
return true;
}
GLGizmoFuzzySkin::GLGizmoFuzzySkin(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoPainterBase(parent, icon_filename, sprite_id), m_current_tool(ImGui::CircleButtonIcon)
{
}
void GLGizmoFuzzySkin::render_painter_gizmo()
{
const Selection &selection = m_parent.get_selection();
glsafe(::glEnable(GL_BLEND));
glsafe(::glEnable(GL_DEPTH_TEST));
render_triangles(selection);
m_c->object_clipper()->render_cut();
m_c->instances_hider()->render_cut();
render_cursor();
glsafe(::glDisable(GL_BLEND));
}
void GLGizmoFuzzySkin::show_tooltip_information(float caption_max, float x, float y)
{
ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP);
ImTextureID hover_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER);
caption_max += m_imgui->calc_text_size(std::string_view{": "}).x + 15.f;
float scale = m_parent.get_scale();
ImVec2 button_size = ImVec2(25 * scale, 25 * scale); // ORCA: Use exact resolution will prevent blur on icon
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, 0}); // ORCA: Dont add padding
ImGui::ImageButton3(normal_id, hover_id, button_size);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip2(ImVec2(x, y));
auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
m_imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption);
ImGui::SameLine(caption_max);
m_imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text);
};
std::vector<std::string> tip_items;
switch (m_tool_type) {
case ToolType::BRUSH:
if (m_cursor_type == TriangleSelector::POINTER) {
tip_items = {"add_fuzzy_skin", "remove_fuzzy_skin", "clipping_of_view"};
} else {
tip_items = {"add_fuzzy_skin", "remove_fuzzy_skin", "cursor_size", "clipping_of_view"};
}
break;
case ToolType::SMART_FILL:
tip_items = {"add_fuzzy_skin", "remove_fuzzy_skin", "smart_fill_angle", "clipping_of_view"};
break;
default:
break;
}
for (const auto &t : tip_items) draw_text_with_caption(m_desc.at(t + "_caption") + ": ", m_desc.at(t));
ImGui::EndTooltip();
}
ImGui::PopStyleVar(2);
}
void GLGizmoFuzzySkin::on_render_input_window(float x, float y, float bottom_limit)
{
if (!m_c->selection_info()->model_object())
return;
const float approx_height = m_imgui->scaled(22.f);
y = std::min(y, bottom_limit - approx_height);
//BBS: GUI refactor: move gizmo to the right
#if BBS_TOOLBAR_ON_TOP
GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f);
#else
GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 1.0f, 0.0f);
#endif
//m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
// BBS
ImGuiWrapper::push_toolbar_style(m_parent.get_scale());
GizmoImguiBegin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float space_size = m_imgui->get_style_scaling() * 8;
const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x + m_imgui->scaled(1.5f),
m_imgui->calc_text_size(m_desc.at("reset_direction")).x + m_imgui->scaled(1.5f) + ImGui::GetStyle().FramePadding.x * 2);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.5f);
const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.5f);
const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f);
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
const float buttons_width = m_imgui->scaled(0.5f);
const float minimal_slider_width = m_imgui->scaled(4.f);
const float tool_type_radio_left = m_imgui->calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f);
const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f);
float caption_max = 0.f;
float total_text_max = 0.f;
for (const std::string t : {"add_fuzzy_skin", "remove_fuzzy_skin"}) {
caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x);
total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x);
}
total_text_max += caption_max + m_imgui->scaled(1.f);
caption_max += m_imgui->scaled(1.f);
const float circle_max_width = std::max(clipping_slider_left, cursor_slider_left);
const float sliders_left_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left));
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
const float empty_button_width = m_imgui->calc_button_size("").x;
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width);
window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill);
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
const float sliders_width = m_imgui->scaled(7.0f);
const float drag_left_width = ImGui::GetStyle().WindowPadding.x + sliders_width - space_size;
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["tool_type"]);
std::array<wchar_t, 4> tool_ids;
tool_ids = { ImGui::CircleButtonIcon, ImGui::SphereButtonIcon, ImGui::TriangleButtonIcon, ImGui::FillButtonIcon };
std::array<wchar_t, 4> icons;
if (m_is_dark_mode)
icons = { ImGui::CircleButtonDarkIcon, ImGui::SphereButtonDarkIcon, ImGui::TriangleButtonDarkIcon, ImGui::FillButtonDarkIcon };
else
icons = { ImGui::CircleButtonIcon, ImGui::SphereButtonIcon, ImGui::TriangleButtonIcon, ImGui::FillButtonIcon };
std::array<wxString, 4> tool_tips = { _L("Circle"), _L("Sphere"), _L("Triangle"), _L("Fill") };
for (int i = 0; i < tool_ids.size(); i++) {
std::string str_label = std::string("");
std::wstring btn_name = icons[i] + boost::nowide::widen(str_label);
if (i != 0) ImGui::SameLine((empty_button_width + m_imgui->scaled(1.75f)) * i + m_imgui->scaled(1.5f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.f, 0.f, 0.f, 0.f)); // ORCA Removes button background on dark mode
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f)); // ORCA Fixes icon rendered without colors while using Light theme
if (m_current_tool == tool_ids[i]) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.f, 0.59f, 0.53f, 0.25f)); // ORCA use orca color for selected tool / brush
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.f, 0.59f, 0.53f, 0.25f)); // ORCA use orca color for selected tool / brush
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.f, 0.59f, 0.53f, 0.30f)); // ORCA use orca color for selected tool / brush
ImGui::PushStyleColor(ImGuiCol_Border, ImGuiWrapper::COL_ORCA); // ORCA use orca color for border on selected tool / brush
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 1.0);
}
bool btn_clicked = ImGui::Button(into_u8(btn_name).c_str());
if (m_current_tool == tool_ids[i])
{
ImGui::PopStyleColor(4);
ImGui::PopStyleVar(2);
}
ImGui::PopStyleColor(2);
ImGui::PopStyleVar(1);
if (btn_clicked && m_current_tool != tool_ids[i]) {
m_current_tool = tool_ids[i];
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
}
if (ImGui::IsItemHovered()) {
m_imgui->tooltip(tool_tips[i], max_tooltip_width);
}
}
ImGui::Dummy(ImVec2(0.0f, ImGui::GetFontSize() * 0.1));
if (m_current_tool == ImGui::CircleButtonIcon || m_current_tool == ImGui::SphereButtonIcon) {
if (m_current_tool == ImGui::CircleButtonIcon)
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
else
m_cursor_type = TriangleSelector::CursorType::SPHERE;
m_tool_type = ToolType::BRUSH;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(circle_max_width);
ImGui::PushItemWidth(sliders_width);
m_imgui->bbl_slider_float_style("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true);
ImGui::SameLine(drag_left_width + circle_max_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##cursor_radius_input", &m_cursor_radius, 0.05f, 0.0f, 0.0f, "%.2f");
} else if (m_current_tool == ImGui::TriangleButtonIcon) {
m_cursor_type = TriangleSelector::CursorType::POINTER;
m_tool_type = ToolType::BRUSH;
} else {
assert(m_current_tool == ImGui::FillButtonIcon);
m_cursor_type = TriangleSelector::CursorType::POINTER;
m_tool_type = ToolType::SMART_FILL;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["smart_fill_angle"]);
std::string format_str = std::string("%.f") +
I18N::translate_utf8("°", "Degree sign to use in the respective slider in fuzzy skin gizmo,"
"placed after the number with no whitespace in between.");
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(sliders_width);
if (m_imgui->bbl_slider_float_style("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true))
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
ImGui::SameLine(drag_left_width + sliders_left_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
ImGui::BBLDragFloat("##smart_fill_angle_input", &m_smart_fill_angle, 0.05f, 0.0f, 0.0f, "%.2f");
}
ImGui::Separator();
if (m_c->object_clipper()->get_position() == 0.f) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view"));
}
else {
if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){
m_c->object_clipper()->set_position_by_ratio(-1., false);
});
}
}
auto clp_dist = float(m_c->object_clipper()->get_position());
ImGui::SameLine(sliders_left_width);
ImGui::PushItemWidth(sliders_width);
bool slider_clp_dist = m_imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true);
ImGui::SameLine(drag_left_width + sliders_left_width);
ImGui::PushItemWidth(1.5 * slider_icon_width);
bool b_clp_dist_input = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f");
if (slider_clp_dist || b_clp_dist_input) { m_c->object_clipper()->set_position_by_ratio(clp_dist, true); }
ImGui::Separator();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 10.0f));
float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + y;
show_tooltip_information(caption_max, x, get_cur_y);
float f_scale = m_parent.get_gizmos_manager().get_layout_scale();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 4.0f * f_scale));
ImGui::SameLine();
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _u8L("Reset selection"), UndoRedo::SnapshotType::GizmoAction);
ModelObject *mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume *mv : mo->volumes)
if (mv->is_model_part()) {
++idx;
m_triangle_selectors[idx]->reset();
m_triangle_selectors[idx]->request_update_render_data(true);
}
update_model_object();
m_parent.set_as_dirty();
}
ImGui::PopStyleVar(2);
GizmoImguiEnd();
// BBS
ImGuiWrapper::pop_toolbar_style();
}
void GLGizmoFuzzySkin::update_model_object()
{
bool updated = false;
ModelObject *mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume *mv : mo->volumes) {
if (!mv->is_model_part())
continue;
++idx;
updated |= mv->fuzzy_skin_facets.set(*m_triangle_selectors[idx]);
}
if (updated) {
const ModelObjectPtrs &mos = wxGetApp().model().objects;
wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin());
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
}
void GLGizmoFuzzySkin::update_from_model_object(bool first_update)
{
wxBusyCursor wait;
const ModelObject *mo = m_c->selection_info()->model_object();
m_triangle_selectors.clear();
int volume_id = -1;
std::vector<ColorRGBA> ebt_colors;
ebt_colors.push_back(GLVolume::NEUTRAL_COLOR);
ebt_colors.push_back(TriangleSelectorGUI::enforcers_color);
ebt_colors.push_back(TriangleSelectorGUI::blockers_color);
for (const ModelVolume *mv : mo->volumes) {
if (!mv->is_model_part())
continue;
++volume_id;
// This mesh does not account for the possible Z up SLA offset.
const TriangleMesh* mesh = &mv->mesh();
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorPatch>(*mesh, ebt_colors));
// Reset of TriangleSelector is done inside TriangleSelectorGUI's constructor, so we don't need it to perform it again in deserialize().
m_triangle_selectors.back()->deserialize(mv->fuzzy_skin_facets.get_data(), false);
m_triangle_selectors.back()->request_update_render_data();
}
}
PainterGizmoType GLGizmoFuzzySkin::get_painter_type() const
{
return PainterGizmoType::FUZZY_SKIN;
}
wxString GLGizmoFuzzySkin::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
{
return shift_down ? _L("Remove fuzzy skin") : _L("Add fuzzy skin");
}
} // namespace Slic3r::GUI

View file

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

View file

@ -189,58 +189,6 @@ void GLGizmoMmuSegmentation::data_changed(bool is_serializing)
}
}
void GLGizmoMmuSegmentation::render_triangles(const Selection &selection) const
{
ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data();
auto* shader = wxGetApp().get_shader("mm_gouraud");
if (!shader)
return;
shader->start_using();
shader->set_uniform("clipping_plane", clp_data.clp_dataf);
shader->set_uniform("z_range", clp_data.z_range);
shader->set_uniform("slope.actived", m_parent.is_using_slope());
ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
//BBS: to improve the random white pixel issue
glsafe(::glDisable(GL_CULL_FACE));
const ModelObject *mo = m_c->selection_info()->model_object();
int mesh_id = -1;
for (const ModelVolume *mv : mo->volumes) {
if (!mv->is_model_part())
continue;
++mesh_id;
Transform3d trafo_matrix;
if (m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView) {
trafo_matrix = mo->instances[selection.get_instance_idx()]->get_assemble_transformation().get_matrix() * mv->get_matrix();
trafo_matrix.translate(mv->get_transformation().get_offset() * (GLVolume::explosion_ratio - 1.0) + mo->instances[selection.get_instance_idx()]->get_offset_to_assembly() * (GLVolume::explosion_ratio - 1.0));
}
else {
trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix()* mv->get_matrix();
}
const bool is_left_handed = trafo_matrix.matrix().determinant() < 0.0;
if (is_left_handed)
glsafe(::glFrontFace(GL_CW));
const Camera& camera = wxGetApp().plater()->get_camera();
const Transform3d& view_matrix = camera.get_view_matrix();
shader->set_uniform("view_model_matrix", view_matrix * trafo_matrix);
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
shader->set_uniform("view_normal_matrix", view_normal_matrix);
shader->set_uniform("volume_world_matrix", trafo_matrix);
shader->set_uniform("volume_mirrored", is_left_handed);
m_triangle_selectors[mesh_id]->render(m_imgui, trafo_matrix);
if (is_left_handed)
glsafe(::glFrontFace(GL_CCW));
}
}
// BBS
bool GLGizmoMmuSegmentation::on_number_key_down(int number)
{
@ -869,7 +817,7 @@ void GLGizmoMmuSegmentation::tool_changed(wchar_t old_tool, wchar_t new_tool)
PainterGizmoType GLGizmoMmuSegmentation::get_painter_type() const
{
return PainterGizmoType::MMU_SEGMENTATION;
return PainterGizmoType::MM_SEGMENTATION;
}
// BBS

View file

@ -71,8 +71,6 @@ public:
void data_changed(bool is_serializing) override;
void render_triangles(const Selection& selection) const override;
// TriangleSelector::serialization/deserialization has a limit to store 19 different states.
// EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored.
// When increasing EXTRUDER_LIMIT, it needs to ensure that TriangleSelector::serialization/deserialization

View file

@ -75,23 +75,27 @@ GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_pl
void GLGizmoPainterBase::render_triangles(const Selection& selection) const
{
auto* shader = wxGetApp().get_shader("gouraud");
if (! shader)
auto* shader = wxGetApp().get_shader("mm_gouraud");
if (!shader)
return;
shader->start_using();
shader->set_uniform("slope.actived", false);
shader->set_uniform("print_volume.type", 0);
shader->set_uniform("clipping_plane", this->get_clipping_plane_data().clp_dataf);
ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
const ModelObject *mo = m_c->selection_info()->model_object();
ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data();
shader->set_uniform("clipping_plane", clp_data.clp_dataf);
shader->set_uniform("z_range", clp_data.z_range);
// BBS: to improve the random white pixel issue
glsafe(::glDisable(GL_CULL_FACE));
const ModelObject* mo = m_c->selection_info()->model_object();
int mesh_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
if (!mv->is_model_part())
continue;
++mesh_id;
Transform3d trafo_matrix;
if (m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView) {
trafo_matrix = mo->instances[selection.get_instance_idx()]->get_assemble_transformation().get_matrix() * mv->get_matrix();
@ -112,13 +116,16 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
shader->set_uniform("view_normal_matrix", view_normal_matrix);
// For printers with multiple extruders, it is necessary to pass trafo_matrix
// to the shader input variable print_box.volume_world_matrix before
// rendering the painted triangles. When this matrix is not set, the
// wrong transformation matrix is used for "Clipping of view".
shader->set_uniform("volume_world_matrix", trafo_matrix);
float normal_z = -::cos(Geometry::deg2rad(m_highlight_by_angle_threshold_deg));
Matrix3f normal_matrix = static_cast<Matrix3f>(trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>());
shader->set_uniform("volume_world_matrix", trafo_matrix);
shader->set_uniform("volume_mirrored", is_left_handed);
shader->set_uniform("slope.actived", m_parent.is_using_slope());
shader->set_uniform("slope.volume_world_normal_matrix", normal_matrix);
shader->set_uniform("slope.normal_z", normal_z);
m_triangle_selectors[mesh_id]->render(m_imgui, trafo_matrix);
if (is_left_handed)
glsafe(::glFrontFace(GL_CCW));
}

View file

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

View file

@ -95,53 +95,6 @@ bool GLGizmoSeam::on_key_down_select_tool_type(int keyCode) {
return true;
}
void GLGizmoSeam::render_triangles(const Selection& selection) const
{
ClippingPlaneDataWrapper clp_data = this->get_clipping_plane_data();
auto* shader = wxGetApp().get_shader("mm_gouraud");
if (!shader)
return;
shader->start_using();
shader->set_uniform("clipping_plane", clp_data.clp_dataf);
shader->set_uniform("z_range", clp_data.z_range);
ScopeGuard guard([shader]() { if (shader) shader->stop_using(); });
const ModelObject* mo = m_c->selection_info()->model_object();
int mesh_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (!mv->is_model_part())
continue;
++mesh_id;
const Transform3d trafo_matrix = mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix();
bool is_left_handed = trafo_matrix.matrix().determinant() < 0.;
if (is_left_handed)
glsafe(::glFrontFace(GL_CW));
const Camera& camera = wxGetApp().plater()->get_camera();
const Transform3d& view_matrix = camera.get_view_matrix();
shader->set_uniform("view_model_matrix", view_matrix * trafo_matrix);
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
shader->set_uniform("view_normal_matrix", view_normal_matrix);
float normal_z = -::cos(Geometry::deg2rad(m_highlight_by_angle_threshold_deg));
Matrix3f normal_matrix = static_cast<Matrix3f>(trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>());
shader->set_uniform("volume_world_matrix", trafo_matrix);
shader->set_uniform("volume_mirrored", is_left_handed);
shader->set_uniform("slope.actived", m_parent.is_using_slope());
shader->set_uniform("slope.volume_world_normal_matrix", normal_matrix);
shader->set_uniform("slope.normal_z", normal_z);
m_triangle_selectors[mesh_id]->render(m_imgui, trafo_matrix);
if (is_left_handed)
glsafe(::glFrontFace(GL_CCW));
}
}
void GLGizmoSeam::show_tooltip_information(float caption_max, float x, float y)
{
ImTextureID normal_id = m_parent.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP);

View file

@ -24,8 +24,6 @@ protected:
std::string on_get_name() const override;
PainterGizmoType get_painter_type() const override;
void render_triangles(const Selection &selection) const override;
void show_tooltip_information(float caption_max, float x, float y);
void tool_changed(wchar_t old_tool, wchar_t new_tool);

View file

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

View file

@ -15,6 +15,7 @@
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
//#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoBrimEars.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
//#include "slic3r/GUI/Gizmos/GLGizmoFaceDetector.hpp"
@ -66,7 +67,7 @@ std::vector<size_t> GLGizmosManager::get_selectable_idxs() const
m_gizmos[i]->get_sprite_id() == (unsigned int) Rotate ||
m_gizmos[i]->get_sprite_id() == (unsigned int) Measure ||
m_gizmos[i]->get_sprite_id() == (unsigned int) Assembly ||
m_gizmos[i]->get_sprite_id() == (unsigned int) MmuSegmentation)
m_gizmos[i]->get_sprite_id() == (unsigned int) MmSegmentation)
out.push_back(i);
}
else {
@ -157,9 +158,12 @@ void GLGizmosManager::switch_gizmos_icon_filename()
case(EType::Emboss):
gizmo->set_icon_filename(m_is_dark ? "toolbar_text_dark.svg" : "toolbar_text.svg");
break;
case(EType::MmuSegmentation):
case(EType::MmSegmentation):
gizmo->set_icon_filename(m_is_dark ? "mmu_segmentation_dark.svg" : "mmu_segmentation.svg");
break;
case(EType::FuzzySkin):
gizmo->set_icon_filename(m_is_dark ? "toolbar_fuzzy_skin_paint_dark.svg" : "toolbar_fuzzy_skin_paint.svg");
break;
case(EType::MeshBoolean):
gizmo->set_icon_filename(m_is_dark ? "toolbar_meshboolean_dark.svg" : "toolbar_meshboolean.svg");
break;
@ -206,7 +210,8 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoMeshBoolean(m_parent, m_is_dark ? "toolbar_meshboolean_dark.svg" : "toolbar_meshboolean.svg", EType::MeshBoolean));
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, m_is_dark ? "toolbar_support_dark.svg" : "toolbar_support.svg", EType::FdmSupports));
m_gizmos.emplace_back(new GLGizmoSeam(m_parent, m_is_dark ? "toolbar_seam_dark.svg" : "toolbar_seam.svg", EType::Seam));
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, m_is_dark ? "mmu_segmentation_dark.svg" : "mmu_segmentation.svg", EType::MmuSegmentation));
m_gizmos.emplace_back(new GLGizmoFuzzySkin(m_parent, m_is_dark ? "toolbar_fuzzy_skin_paint_dark.svg" : "toolbar_fuzzy_skin_paint.svg", EType::FuzzySkin));
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, m_is_dark ? "mmu_segmentation_dark.svg" : "mmu_segmentation.svg", EType::MmSegmentation));
m_gizmos.emplace_back(new GLGizmoEmboss(m_parent, m_is_dark ? "toolbar_text_dark.svg" : "toolbar_text.svg", EType::Emboss));
m_gizmos.emplace_back(new GLGizmoSVG(m_parent));
m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, m_is_dark ? "toolbar_measure_dark.svg" : "toolbar_measure.svg", EType::Measure));
@ -465,14 +470,16 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
return dynamic_cast<GLGizmoFdmSupports*>(m_gizmos[FdmSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == Seam)
return dynamic_cast<GLGizmoSeam*>(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == MmuSegmentation)
return dynamic_cast<GLGizmoMmuSegmentation*>(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == MmSegmentation)
return dynamic_cast<GLGizmoMmuSegmentation*>(m_gizmos[MmSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == Measure)
return dynamic_cast<GLGizmoMeasure *>(m_gizmos[Measure].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == Assembly)
return dynamic_cast<GLGizmoAssembly *>(m_gizmos[Assembly].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == Cut)
return dynamic_cast<GLGizmoCut3D*>(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == FuzzySkin)
return dynamic_cast<GLGizmoFuzzySkin*>(m_gizmos[FuzzySkin].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == MeshBoolean)
return dynamic_cast<GLGizmoMeshBoolean*>(m_gizmos[MeshBoolean].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == BrimEars)
@ -484,7 +491,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
bool GLGizmosManager::is_paint_gizmo()
{
return m_current == EType::FdmSupports ||
m_current == EType::MmuSegmentation ||
m_current == EType::MmSegmentation ||
m_current == EType::FuzzySkin ||
m_current == EType::Seam;
}
@ -583,7 +591,7 @@ bool GLGizmosManager::on_mouse_wheel(const wxMouseEvent &evt)
{
bool processed = false;
if (/*m_current == SlaSupports || m_current == Hollow ||*/ m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation || m_current == BrimEars) {
if (/*m_current == SlaSupports || m_current == Hollow ||*/ m_current == FdmSupports || m_current == Seam || m_current == MmSegmentation || m_current == FuzzySkin || m_current == BrimEars) {
float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown()
// BBS
@ -781,7 +789,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
//case 'r' :
//case 'R' :
//{
//if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
//if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam || m_current == MmSegmentation || m_current == FuzzySkin) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
// processed = true;
//break;
@ -890,7 +898,7 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
}
}
// BBS
if (m_current == MmuSegmentation && keyCode > '0' && keyCode <= '9') {
if (m_current == MmSegmentation && keyCode > '0' && keyCode <= '9') {
// capture number key
processed = true;
}
@ -939,7 +947,7 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
processed = simplify->on_esc_key_down();
}
// BBS
else if (m_current == MmuSegmentation) {
else if (m_current == MmSegmentation) {
GLGizmoMmuSegmentation* mmu_seg = dynamic_cast<GLGizmoMmuSegmentation*>(get_current());
if (mmu_seg != nullptr) {
if (keyCode >= WXK_NUMPAD0 && keyCode <= WXK_NUMPAD9) {
@ -1002,7 +1010,7 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
void GLGizmosManager::on_set_color_timer(wxTimerEvent& evt)
{
if (m_current == MmuSegmentation) {
if (m_current == MmSegmentation) {
GLGizmoMmuSegmentation* mmu_seg = dynamic_cast<GLGizmoMmuSegmentation*>(get_current());
mmu_seg->on_number_key_down(1);
m_parent.set_as_dirty();
@ -1415,8 +1423,10 @@ std::string get_name_from_gizmo_etype(GLGizmosManager::EType type)
return "Seam";
case GLGizmosManager::EType::Emboss:
return "Text";
case GLGizmosManager::EType::MmuSegmentation:
case GLGizmosManager::EType::MmSegmentation:
return "Color Painting";
case GLGizmosManager::EType::FuzzySkin:
return "Fuzzy Skin Painting";
default:
return "";
}

View file

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

View file

@ -259,6 +259,7 @@ void KBShortcutsDialog::fill_shortcuts()
{ "F", L("Gizmo place face on bed") },
{ "C", L("Gizmo cut") },
{ "B", L("Gizmo mesh boolean") },
{ "H", L("Gizmo FDM paint-on fuzzy skin") },
{ "L", L("Gizmo SLA support points") },
{ "P", L("Gizmo FDM paint-on seam") },
{ "T", L("Gizmo text emboss/engrave") },

View file

@ -1272,10 +1272,11 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty
case InfoItemType::CustomSupports: text += format(_L_PLURAL("%1$d Object has custom supports.", "%1$d Objects have custom supports.", (*it).second), (*it).second) + "\n"; break;
// BBS
//case InfoItemType::CustomSeam: text += format(("%1$d Object has custom seam.", "%1$d Objects have custom seam.", (*it).second), (*it).second) + "\n"; break;
case InfoItemType::MmuSegmentation: text += format(_L_PLURAL("%1$d Object has color painting.", "%1$d Objects have color painting.",(*it).second), (*it).second) + "\n"; break;
case InfoItemType::MmSegmentation: text += format(_L_PLURAL("%1$d Object has color painting.", "%1$d Objects have color painting.",(*it).second), (*it).second) + "\n"; break;
// BBS
//case InfoItemType::Sinking: text += format(("%1$d Object has partial sinking.", "%1$d Objects have partial sinking.", (*it).second), (*it).second) + "\n"; break;
case InfoItemType::CutConnectors: text += format(_L_PLURAL("%1$d object was loaded as a part of cut object.", "%1$d objects were loaded as parts of cut object.", (*it).second), (*it).second) + "\n"; break;
case InfoItemType::FuzzySkin: text += format(_L_PLURAL("%1$d object was loaded with fuzzy skin painting.", "%1$d objects were loaded with fuzzy skin painting.", (*it).second), (*it).second) + "\n"; break;
default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break;
}
}

View file

@ -69,9 +69,10 @@ const std::map<InfoItemType, InfoItemAtributes> INFO_ITEMS{
// info_item Type info_item Name info_item BitmapName
{ InfoItemType::CustomSupports, {L("Support painting"), "toolbar_support" }, },
//{ InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, },
{ InfoItemType::MmuSegmentation, {L("Color painting"), "mmu_segmentation"}, },
{ InfoItemType::MmSegmentation, {L("Color painting"), "mmu_segmentation"}, },
//{ InfoItemType::Sinking, {L("Sinking"), "objlist_sinking"}, },
{ InfoItemType::CutConnectors, {L("Cut connectors"), "cut_connectors" }, },
{ InfoItemType::FuzzySkin, {L("Paint-on fuzzy skin"), "objlist_fuzzy_skin_paint" }, },
};
ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent,

View file

@ -60,7 +60,8 @@ enum class InfoItemType
Undef,
CustomSupports,
//CustomSeam,
MmuSegmentation,
MmSegmentation,
FuzzySkin,
//Sinking
CutConnectors,
};

View file

@ -5683,6 +5683,7 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const
new_volume->supported_facets.assign(old_volume->supported_facets);
new_volume->seam_facets.assign(old_volume->seam_facets);
new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets);
new_volume->fuzzy_skin_facets.assign(old_volume->fuzzy_skin_facets);
std::swap(old_model_object->volumes[volume_idx], old_model_object->volumes.back());
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
if (!sinking)
@ -13474,14 +13475,15 @@ void Plater::clear_before_change_mesh(int obj_idx)
{
ModelObject* mo = model().objects[obj_idx];
// If there are custom supports/seams/mmu segmentation, remove them. Fixed mesh
// If there are custom supports/seams/mmu/fuzzy skin segmentation, remove them. Fixed mesh
// may be different and they would make no sense.
bool paint_removed = false;
for (ModelVolume* mv : mo->volumes) {
paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty();
paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty() || !mv->fuzzy_skin_facets.empty();
mv->supported_facets.reset();
mv->seam_facets.reset();
mv->mmu_segmentation_facets.reset();
mv->fuzzy_skin_facets.reset();
}
if (paint_removed) {
// snapshot_time is captured by copy so the lambda knows where to undo/redo to.

View file

@ -2452,6 +2452,7 @@ optgroup->append_single_option_line("skirt_loops", "others_settings_skirt#loops"
optgroup->append_single_option_line("timelapse_type", "others_settings_special_mode#timelapse");
optgroup = page->new_optgroup(L("Fuzzy Skin"), L"fuzzy_skin");
optgroup->append_single_option_line("fuzzy_skin", "others_settings_special_mode#fuzzy-skin");
optgroup->append_single_option_line("fuzzy_skin_noise_type", "others_settings_special_mode#fuzzy-skin-mode");
optgroup->append_single_option_line("fuzzy_skin_point_distance", "others_settings_special_mode#point-distance");