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