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");