mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-24 07:03:59 -06:00
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:
parent
c00502638c
commit
50e64d5961
50 changed files with 1614 additions and 940 deletions
1
resources/images/fuzzy_skin.svg
Normal file
1
resources/images/fuzzy_skin.svg
Normal 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 |
1
resources/images/objlist_fuzzy_skin_paint.svg
Normal file
1
resources/images/objlist_fuzzy_skin_paint.svg
Normal 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 |
1
resources/images/toolbar_fuzzy_skin_paint.svg
Normal file
1
resources/images/toolbar_fuzzy_skin_paint.svg
Normal 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 |
1
resources/images/toolbar_fuzzy_skin_paint_dark.svg
Normal file
1
resources/images/toolbar_fuzzy_skin_paint_dark.svg
Normal 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 |
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
391
src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp
Normal file
391
src/libslic3r/Feature/FuzzySkin/FuzzySkin.cpp
Normal 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 = [¤t_ext](const Algorithm::SplitLineJunction& j) -> Arachne::ExtrusionJunction {
|
||||
Arachne::ExtrusionJunction res = current_ext[j.get_src_index()];
|
||||
if (!j.is_src()) {
|
||||
res.p = j.p;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
for (const auto& p : splitted) {
|
||||
if (p.clipped) {
|
||||
segment.push_back(to_ex_junction(p));
|
||||
} else {
|
||||
if (segment.empty()) {
|
||||
extrusion->junctions.push_back(to_ex_junction(p));
|
||||
} else {
|
||||
segment.push_back(to_ex_junction(p));
|
||||
fuzzy_current_segment();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!segment.empty()) {
|
||||
fuzzy_current_segment();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Feature::FuzzySkin
|
23
src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp
Normal file
23
src/libslic3r/Feature/FuzzySkin/FuzzySkin.hpp
Normal 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_
|
|
@ -4,7 +4,7 @@
|
|||
#ifndef INTERLOCKING_GENERATOR_HPP
|
||||
#define INTERLOCKING_GENERATOR_HPP
|
||||
|
||||
#include "../Print.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "VoxelUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
|
@ -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
|
||||
{
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
#include <functional>
|
||||
|
||||
#include "../Polygon.hpp"
|
||||
#include "../ExPolygon.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
|
||||
namespace Slic3r
|
||||
{
|
|
@ -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())
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 ¤t_line = Geometry::VoronoiUtils::get_source_segment(cell, colored_lines.begin(), colored_lines.end());
|
||||
const ColoredLine &next_line = get_next_contour_line(current_line);
|
||||
|
||||
const VD::edge_type *edge = cell_range.edge_begin;
|
||||
do {
|
||||
if (edge->color() == VD_ANNOTATION::DELETED)
|
||||
continue;
|
||||
|
||||
if (!points_inside(current_line.line, next_line.line, Geometry::VoronoiUtils::to_point(edge->vertex1()).cast<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 ®ion = print_object.printing_region(region_idx);
|
||||
max_external_perimeter_width = std::max<float>(max_external_perimeter_width, region.flow(print_object, frExternalPerimeter, print_object.config().layer_height).width());
|
||||
}
|
||||
|
||||
return segmentation_by_painting(print_object, extract_facets_info, num_facets_states, max_external_perimeter_width, 0.f, false, IncludeTopAndBottomLayers::No, throw_on_cancel_callback);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 = [¤t_ext](const Algorithm::SplitLineJunction& j) -> Arachne::ExtrusionJunction {
|
||||
Arachne::ExtrusionJunction res = current_ext[j.get_src_index()];
|
||||
if (!j.is_src()) {
|
||||
res.p = j.p;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
for (const auto& p : splitted) {
|
||||
if (p.clipped) {
|
||||
segment.push_back(to_ex_junction(p));
|
||||
} else {
|
||||
if (segment.empty()) {
|
||||
extrusion->junctions.push_back(to_ex_junction(p));
|
||||
} else {
|
||||
segment.push_back(to_ex_junction(p));
|
||||
fuzzy_current_segment();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!segment.empty()) {
|
||||
fuzzy_current_segment();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
apply_fuzzy_skin(extrusion, perimeter_generator, is_contour);
|
||||
|
||||
ExtrusionPaths paths;
|
||||
// detect overhanging/bridging perimeters
|
||||
|
@ -1430,48 +1113,6 @@ static void reorient_perimeters(ExtrusionEntityCollection &entities, bool steep_
|
|||
}
|
||||
}
|
||||
|
||||
static void group_region_by_fuzzify(PerimeterGenerator& g)
|
||||
{
|
||||
g.regions_by_fuzzify.clear();
|
||||
g.has_fuzzy_skin = false;
|
||||
g.has_fuzzy_hole = false;
|
||||
|
||||
std::unordered_map<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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ®ion : layer_range.fuzzy_skin_painted_regions) {
|
||||
const PrintRegion &parent_print_region = *region.parent_print_object_region(layer_range);
|
||||
PrintRegionConfig cfg = parent_print_region.config();
|
||||
cfg.fuzzy_skin.value = FuzzySkinType::All;
|
||||
if (cfg != region.region->config()) {
|
||||
// Region configuration changed.
|
||||
if (print_region_ref_cnt(*region.region) == 0) {
|
||||
// Region is referenced for the first time. Just change its parameters.
|
||||
// Stop the background process before assigning new configuration to the regions.
|
||||
t_config_option_keys diff = region.region->config().diff(cfg);
|
||||
callback_invalidate(region.region->config(), cfg, diff);
|
||||
region.region->config_apply_only(cfg, diff, false);
|
||||
} else {
|
||||
// Region is referenced multiple times, thus the region is being split. We need to reslice.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
print_region_ref_inc(*region.region);
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly verify, whether some regions were not merged.
|
||||
{
|
||||
std::vector<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) {
|
||||
|
|
|
@ -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 ®ion = by_extruder[extruder_id];
|
||||
|
@ -883,92 +886,228 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
|
|||
layer_split = true;
|
||||
}
|
||||
}
|
||||
if (! layer_split)
|
||||
|
||||
if (!layer_split)
|
||||
continue;
|
||||
|
||||
// Split LayerRegions by by_extruder regions.
|
||||
// layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID.
|
||||
auto it_painted_region = layer_range.painted_regions.begin();
|
||||
for (int region_id = 0; region_id < int(layer->region_count()); ++ region_id)
|
||||
if (LayerRegion &layerm = *layer->get_region(region_id); ! layerm.slices.surfaces.empty()) {
|
||||
assert(layerm.region().print_object_region_id() == region_id);
|
||||
const BoundingBox bbox = get_extents(layerm.slices.surfaces);
|
||||
assert(it_painted_region < layer_range.painted_regions.end());
|
||||
// Find the first it_painted_region which overrides this region.
|
||||
for (; layer_range.volume_regions[it_painted_region->parent].region->print_object_region_id() < region_id; ++ it_painted_region)
|
||||
assert(it_painted_region != layer_range.painted_regions.end());
|
||||
assert(it_painted_region != layer_range.painted_regions.end());
|
||||
assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region());
|
||||
// 1-based extruder ID
|
||||
bool self_trimmed = false;
|
||||
int self_extruder_id = -1;
|
||||
for (int extruder_id = 1; extruder_id <= int(by_extruder.size()); ++ extruder_id)
|
||||
if (ByExtruder &segmented = by_extruder[extruder_id - 1]; segmented.bbox.defined && bbox.overlap(segmented.bbox)) {
|
||||
// Find the target region.
|
||||
for (; int(it_painted_region->extruder_id) < extruder_id; ++ it_painted_region)
|
||||
assert(it_painted_region != layer_range.painted_regions.end());
|
||||
assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region() && int(it_painted_region->extruder_id) == extruder_id);
|
||||
//FIXME Don't trim by self, it is not reliable.
|
||||
if (&layerm.region() == it_painted_region->region) {
|
||||
self_extruder_id = extruder_id;
|
||||
continue;
|
||||
}
|
||||
// Steal from this region.
|
||||
int target_region_id = it_painted_region->region->print_object_region_id();
|
||||
ExPolygons stolen = intersection_ex(layerm.slices.surfaces, segmented.expolygons);
|
||||
if (! stolen.empty()) {
|
||||
ByRegion &dst = by_region[target_region_id];
|
||||
if (dst.expolygons.empty()) {
|
||||
dst.expolygons = std::move(stolen);
|
||||
} else {
|
||||
append(dst.expolygons, std::move(stolen));
|
||||
dst.needs_merge = true;
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
if (&layerm.region() == it_painted_region->region)
|
||||
// Slices of this LayerRegion were trimmed by a MMU region of the same PrintRegion.
|
||||
self_trimmed = true;
|
||||
#endif
|
||||
}
|
||||
if (! self_trimmed) {
|
||||
// Trim slices of this LayerRegion with all the MMU regions.
|
||||
Polygons mine = to_polygons(std::move(layerm.slices.surfaces));
|
||||
for (auto &segmented : by_extruder)
|
||||
if (&segmented - by_extruder.data() + 1 != self_extruder_id && segmented.bbox.defined && bbox.overlap(segmented.bbox)) {
|
||||
mine = diff(mine, segmented.expolygons);
|
||||
if (mine.empty())
|
||||
break;
|
||||
}
|
||||
// Filter out unprintable polygons produced by subtraction multi-material painted regions from layerm.region().
|
||||
// ExPolygon returned from multi-material segmentation does not precisely match ExPolygons in layerm.region()
|
||||
// (because of preprocessing of the input regions in multi-material segmentation). Therefore, subtraction from
|
||||
// layerm.region() could produce a huge number of small unprintable regions for the model's base extruder.
|
||||
// This could, on some models, produce bulges with the model's base color (#7109).
|
||||
if (! mine.empty())
|
||||
mine = opening(union_ex(mine), float(scale_(5 * EPSILON)), float(scale_(5 * EPSILON)));
|
||||
if (! mine.empty()) {
|
||||
ByRegion &dst = by_region[layerm.region().print_object_region_id()];
|
||||
if (dst.expolygons.empty()) {
|
||||
dst.expolygons = union_ex(mine);
|
||||
} else {
|
||||
append(dst.expolygons, union_ex(mine));
|
||||
dst.needs_merge = true;
|
||||
}
|
||||
auto it_painted_region_begin = layer_range.painted_regions.cbegin();
|
||||
for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) {
|
||||
if (it_painted_region_begin == layer_range.painted_regions.cend())
|
||||
continue;
|
||||
|
||||
const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx);
|
||||
const PrintRegion &parent_print_region = parent_layer_region.region();
|
||||
assert(parent_print_region.print_object_region_id() == parent_layer_region_idx);
|
||||
if (parent_layer_region.slices.empty())
|
||||
continue;
|
||||
|
||||
// Find the first PaintedRegion, which overrides the parent PrintRegion.
|
||||
auto it_first_painted_region = std::find_if(it_painted_region_begin, layer_range.painted_regions.cend(), [&layer_range, &parent_print_region](const auto &painted_region) {
|
||||
return layer_range.volume_regions[painted_region.parent].region->print_object_region_id() == parent_print_region.print_object_region_id();
|
||||
});
|
||||
|
||||
if (it_first_painted_region == layer_range.painted_regions.cend())
|
||||
continue; // This LayerRegion isn't overrides by any PaintedRegion.
|
||||
|
||||
assert(&parent_print_region == layer_range.volume_regions[it_first_painted_region->parent].region);
|
||||
|
||||
// Update the beginning PaintedRegion iterator for the next iteration.
|
||||
it_painted_region_begin = it_first_painted_region;
|
||||
|
||||
const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices.surfaces);
|
||||
bool self_trimmed = false;
|
||||
int self_extruder_id = -1; // 1-based extruder ID
|
||||
for (int extruder_id = 1; extruder_id <= int(by_extruder.size()); ++extruder_id) {
|
||||
const ByExtruder &segmented = by_extruder[extruder_id - 1];
|
||||
if (!segmented.bbox.defined || !parent_layer_region_bbox.overlap(segmented.bbox))
|
||||
continue;
|
||||
|
||||
// Find the first target region iterator.
|
||||
auto it_target_region = std::find_if(it_painted_region_begin, layer_range.painted_regions.cend(), [extruder_id](const auto &painted_region) {
|
||||
return int(painted_region.extruder_id) >= extruder_id;
|
||||
});
|
||||
|
||||
assert(it_target_region != layer_range.painted_regions.end());
|
||||
assert(layer_range.volume_regions[it_target_region->parent].region == &parent_print_region && int(it_target_region->extruder_id) == extruder_id);
|
||||
|
||||
// Update the beginning PaintedRegion iterator for the next iteration.
|
||||
it_painted_region_begin = it_target_region;
|
||||
|
||||
// FIXME: Don't trim by self, it is not reliable.
|
||||
if (it_target_region->region == &parent_print_region) {
|
||||
self_extruder_id = extruder_id;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Steal from this region.
|
||||
int target_region_id = it_target_region->region->print_object_region_id();
|
||||
ExPolygons stolen = intersection_ex(parent_layer_region.slices.surfaces, segmented.expolygons);
|
||||
if (!stolen.empty()) {
|
||||
ByRegion &dst = by_region[target_region_id];
|
||||
if (dst.expolygons.empty()) {
|
||||
dst.expolygons = std::move(stolen);
|
||||
} else {
|
||||
append(dst.expolygons, std::move(stolen));
|
||||
dst.needs_merge = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!self_trimmed) {
|
||||
// Trim slices of this LayerRegion with all the MM regions.
|
||||
Polygons mine = to_polygons(parent_layer_region.slices.surfaces);
|
||||
for (auto &segmented : by_extruder) {
|
||||
if (&segmented - by_extruder.data() + 1 != self_extruder_id && segmented.bbox.defined && parent_layer_region_bbox.overlap(segmented.bbox)) {
|
||||
mine = diff(mine, segmented.expolygons);
|
||||
if (mine.empty())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out unprintable polygons produced by subtraction multi-material painted regions from layerm.region().
|
||||
// ExPolygon returned from multi-material segmentation does not precisely match ExPolygons in layerm.region()
|
||||
// (because of preprocessing of the input regions in multi-material segmentation). Therefore, subtraction from
|
||||
// layerm.region() could produce a huge number of small unprintable regions for the model's base extruder.
|
||||
// This could, on some models, produce bulges with the model's base color (#7109).
|
||||
if (!mine.empty()) {
|
||||
mine = opening(union_ex(mine), scaled<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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
390
src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp
Normal file
390
src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.cpp
Normal 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
|
52
src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp
Normal file
52
src/slic3r/GUI/Gizmos/GLGizmoFuzzySkin.hpp
Normal 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_
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -26,7 +26,8 @@ class Selection;
|
|||
enum class PainterGizmoType {
|
||||
FDM_SUPPORTS,
|
||||
SEAM,
|
||||
MMU_SEGMENTATION
|
||||
MM_SEGMENTATION,
|
||||
FUZZY_SKIN
|
||||
};
|
||||
|
||||
class TriangleSelectorGUI : public TriangleSelector {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 "";
|
||||
}
|
||||
|
|
|
@ -82,7 +82,8 @@ public:
|
|||
MeshBoolean,
|
||||
FdmSupports,
|
||||
Seam,
|
||||
MmuSegmentation,
|
||||
FuzzySkin,
|
||||
MmSegmentation,
|
||||
Emboss,
|
||||
Svg,
|
||||
Measure,
|
||||
|
|
|
@ -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") },
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -60,7 +60,8 @@ enum class InfoItemType
|
|||
Undef,
|
||||
CustomSupports,
|
||||
//CustomSeam,
|
||||
MmuSegmentation,
|
||||
MmSegmentation,
|
||||
FuzzySkin,
|
||||
//Sinking
|
||||
CutConnectors,
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue