diff --git a/resources/images/image_projection.svg b/resources/images/image_projection.svg
index cfafff833b7..1df694c159d 100644
--- a/resources/images/image_projection.svg
+++ b/resources/images/image_projection.svg
@@ -25,8 +25,8 @@
inkscape:cy="23.971385"
inkscape:window-width="1135"
inkscape:window-height="616"
- inkscape:window-x="558"
- inkscape:window-y="275"
+ inkscape:window-x="555"
+ inkscape:window-y="322"
inkscape:window-maximized="0"
inkscape:current-layer="svg17" />
diff --git a/resources/images/image_projection_dark.svg b/resources/images/image_projection_dark.svg
index f27861f6347..013f0ddd62d 100644
--- a/resources/images/image_projection_dark.svg
+++ b/resources/images/image_projection_dark.svg
@@ -25,8 +25,8 @@
inkscape:cy="23.971385"
inkscape:window-width="1135"
inkscape:window-height="616"
- inkscape:window-x="558"
- inkscape:window-y="275"
+ inkscape:window-x="555"
+ inkscape:window-y="322"
inkscape:window-maximized="0"
inkscape:current-layer="svg17" />
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 09c8133929e..5273e165395 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -3347,6 +3347,10 @@ void ModelVolume::assign_new_unique_ids_recursive()
seam_facets.set_new_unique_id();
mmu_segmentation_facets.set_new_unique_id();
texture_mapping_color_facets.set_new_unique_id();
+ imported_vertex_colors_rgba.set_new_unique_id();
+ imported_texture_uvs_per_face.set_new_unique_id();
+ imported_texture_uv_valid.set_new_unique_id();
+ imported_texture_rgba.set_new_unique_id();
fuzzy_skin_facets.set_new_unique_id();
}
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index 2ffaf299ac1..44834fa961e 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -37,6 +37,7 @@
#include
#include
#include
+#include
#include
namespace cereal {
@@ -867,6 +868,10 @@ public:
void set_metadata_json(std::string metadata_json);
bool empty() const { return m_data.triangles_to_split.empty(); }
void reset();
+ static std::unique_ptr make_temporary()
+ {
+ return std::unique_ptr(new ColorFacetsAnnotation());
+ }
void reserve(int n_triangles) { m_data.triangles_to_split.reserve(n_triangles); }
void shrink_to_fit()
{
@@ -905,6 +910,55 @@ private:
TriangleColorSplittingData m_data;
};
+template
+class ModelVolumeImportedVector final : public ObjectBase, public std::vector
+{
+public:
+ using std::vector::vector;
+
+ ModelVolumeImportedVector() = default;
+ ModelVolumeImportedVector(const ModelVolumeImportedVector &rhs) = default;
+ ModelVolumeImportedVector(ModelVolumeImportedVector &&rhs) = default;
+ ModelVolumeImportedVector& operator=(const ModelVolumeImportedVector &rhs) = default;
+ ModelVolumeImportedVector& operator=(ModelVolumeImportedVector &&rhs) = default;
+
+ ModelVolumeImportedVector& operator=(const std::vector &rhs)
+ {
+ this->as_vector() = rhs;
+ return *this;
+ }
+
+ ModelVolumeImportedVector& operator=(std::vector &&rhs)
+ {
+ this->as_vector() = std::move(rhs);
+ return *this;
+ }
+
+ ModelVolumeImportedVector& operator=(std::initializer_list rhs)
+ {
+ this->as_vector() = rhs;
+ return *this;
+ }
+
+private:
+ explicit ModelVolumeImportedVector(int) : ObjectBase(-1) {}
+
+ std::vector& as_vector() { return static_cast&>(*this); }
+ const std::vector& as_vector() const { return static_cast&>(*this); }
+
+ friend class cereal::access;
+ friend class UndoRedo::StackImpl;
+ friend class ModelVolume;
+
+ template void save(Archive &ar) const { ar(as_vector()); }
+ template void load(Archive &ar)
+ {
+ std::vector loaded;
+ ar(loaded);
+ this->as_vector().swap(loaded);
+ }
+};
+
// An object STL, or a modifier volume, over which a different set of parameters shall be applied.
// ModelVolume instances are owned by a ModelObject.
class ModelVolume final : public ObjectBase
@@ -993,11 +1047,11 @@ public:
ColorFacetsAnnotation texture_mapping_color_facets;
- std::vector imported_vertex_colors_rgba;
+ ModelVolumeImportedVector imported_vertex_colors_rgba;
- std::vector imported_texture_uvs_per_face;
- std::vector imported_texture_uv_valid;
- std::vector imported_texture_rgba;
+ ModelVolumeImportedVector imported_texture_uvs_per_face;
+ ModelVolumeImportedVector imported_texture_uv_valid;
+ ModelVolumeImportedVector imported_texture_rgba;
uint32_t imported_texture_width{0};
uint32_t imported_texture_height{0};
@@ -1128,6 +1182,10 @@ public:
this->seam_facets.set_new_unique_id();
this->mmu_segmentation_facets.set_new_unique_id();
this->texture_mapping_color_facets.set_new_unique_id();
+ this->imported_vertex_colors_rgba.set_new_unique_id();
+ this->imported_texture_uvs_per_face.set_new_unique_id();
+ this->imported_texture_uv_valid.set_new_unique_id();
+ this->imported_texture_rgba.set_new_unique_id();
this->fuzzy_skin_facets.set_new_unique_id();
}
@@ -1311,7 +1369,7 @@ 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), texture_mapping_color_facets(-1), fuzzy_skin_facets(-1), object(nullptr) {
+ ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), texture_mapping_color_facets(-1), imported_vertex_colors_rgba(-1), imported_texture_uvs_per_face(-1), imported_texture_uv_valid(-1), imported_texture_rgba(-1), fuzzy_skin_facets(-1), object(nullptr) {
assert(this->id().invalid());
assert(this->config.id().invalid());
assert(this->supported_facets.id().invalid());
@@ -1339,8 +1397,11 @@ private:
t = texture_mapping_color_facets.timestamp();
cereal::load_by_value(ar, texture_mapping_color_facets);
mesh_changed |= t != texture_mapping_color_facets.timestamp();
- ar(imported_vertex_colors_rgba);
- ar(imported_texture_uvs_per_face, imported_texture_uv_valid, imported_texture_rgba, imported_texture_width, imported_texture_height);
+ cereal::load_by_value(ar, imported_vertex_colors_rgba);
+ cereal::load_by_value(ar, imported_texture_uvs_per_face);
+ cereal::load_by_value(ar, imported_texture_uv_valid);
+ cereal::load_by_value(ar, imported_texture_rgba);
+ ar(imported_texture_width, imported_texture_height);
cereal::load_by_value(ar, fuzzy_skin_facets);
mesh_changed |= t != fuzzy_skin_facets.timestamp();
cereal::load_by_value(ar, config);
@@ -1374,8 +1435,11 @@ private:
cereal::save_by_value(ar, seam_facets);
cereal::save_by_value(ar, mmu_segmentation_facets);
cereal::save_by_value(ar, texture_mapping_color_facets);
- ar(imported_vertex_colors_rgba);
- ar(imported_texture_uvs_per_face, imported_texture_uv_valid, imported_texture_rgba, imported_texture_width, imported_texture_height);
+ cereal::save_by_value(ar, imported_vertex_colors_rgba);
+ cereal::save_by_value(ar, imported_texture_uvs_per_face);
+ cereal::save_by_value(ar, imported_texture_uv_valid);
+ cereal::save_by_value(ar, imported_texture_rgba);
+ ar(imported_texture_width, imported_texture_height);
cereal::save_by_value(ar, fuzzy_skin_facets);
cereal::save_by_value(ar, config);
cereal::save(ar, text_configuration);
@@ -1952,6 +2016,8 @@ static const double SINKING_MIN_Z_THRESHOLD = 0.05;
namespace cereal
{
+ template
+ struct specialize, cereal::specialization::member_load_save> {};
template struct specialize {};
// BBS: backup
template struct specialize {};
diff --git a/src/libslic3r/TextureMapping.cpp b/src/libslic3r/TextureMapping.cpp
index c795f300174..dbf13f520b5 100644
--- a/src/libslic3r/TextureMapping.cpp
+++ b/src/libslic3r/TextureMapping.cpp
@@ -881,7 +881,7 @@ void TextureMappingManager::load_entries(const std::string &serialized,
zone.reduce_outer_surface_texture = texture.value("reduce_outer_surface_texture", false);
zone.seam_hiding = texture.value("hide_seams", false);
zone.nonlinear_offset_adjustment = texture.value("nonlinear_offset_adjustment", false);
- zone.compact_offset_mode = texture.value("compact_offset_mode", false);
+ zone.compact_offset_mode = texture.value("compact_offset_mode", TextureMappingZone::DefaultCompactOffsetMode);
zone.contrast_pct = std::clamp(texture.value("contrast_pct", 100.f), 25.f, 300.f);
zone.high_resolution_sampling = texture.value("high_resolution_sampling", true);
zone.tone_gamma = normalize_tone_gamma(texture.value("tone_gamma", 1.f));
diff --git a/src/libslic3r/TextureMapping.hpp b/src/libslic3r/TextureMapping.hpp
index 0572a50802c..e594a36a72a 100644
--- a/src/libslic3r/TextureMapping.hpp
+++ b/src/libslic3r/TextureMapping.hpp
@@ -70,7 +70,7 @@ struct TextureMappingZone
static constexpr bool DefaultReduceOuterSurfaceTexture = false;
static constexpr bool DefaultSeamHiding = false;
static constexpr bool DefaultNonlinearOffsetAdjustment = false;
- static constexpr bool DefaultCompactOffsetMode = false;
+ static constexpr bool DefaultCompactOffsetMode = true;
static constexpr float DefaultContrastPct = 100.f;
static constexpr bool DefaultHighResolutionSampling = true;
static constexpr float DefaultToneGamma = 1.f;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp
index 381ca85a292..de68dee2442 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp
@@ -26,12 +26,19 @@
#include
#include
#include
+#include
#include
#include
#include
#include
+#include
+#include
#include
#include
+#include
+#include
+#include
+#include
namespace Slic3r::GUI {
@@ -315,6 +322,15 @@ static int texture_mapping_depth_for_budget(size_t triangle_count, int requested
return depth;
}
+static constexpr float TRUE_COLOR_BRUSH_SUBDIVISION_FRACTION = 1.f / 8.f;
+static constexpr float TRUE_COLOR_BRUSH_MIN_SUBDIVISION_EDGE_MM = 0.1f;
+
+static float true_color_brush_subdivision_target(float brush_radius)
+{
+ return std::max(std::max(brush_radius, 0.f) * TRUE_COLOR_BRUSH_SUBDIVISION_FRACTION,
+ TRUE_COLOR_BRUSH_MIN_SUBDIVISION_EDGE_MM);
+}
+
static void normalize_color_mix_weights(std::vector &weights)
{
float sum = 0.f;
@@ -651,6 +667,168 @@ static float distance_to_segment(const Vec3f &point, const Vec3f &a, const Vec3f
return (point - (a + ab * t)).norm();
}
+static float distance_between_segments(const Vec3f &p1, const Vec3f &q1, const Vec3f &p2, const Vec3f &q2)
+{
+ const Vec3f d1 = q1 - p1;
+ const Vec3f d2 = q2 - p2;
+ const Vec3f r = p1 - p2;
+ const float a = d1.dot(d1);
+ const float e = d2.dot(d2);
+ const float f = d2.dot(r);
+
+ float s = 0.f;
+ float t = 0.f;
+ if (a <= EPSILON && e <= EPSILON)
+ return (p1 - p2).norm();
+ if (a <= EPSILON) {
+ t = std::clamp(f / e, 0.f, 1.f);
+ } else {
+ const float c = d1.dot(r);
+ if (e <= EPSILON) {
+ s = std::clamp(-c / a, 0.f, 1.f);
+ } else {
+ const float b = d1.dot(d2);
+ const float denom = a * e - b * b;
+ if (denom > EPSILON)
+ s = std::clamp((b * f - c * e) / denom, 0.f, 1.f);
+ const float tnom = b * s + f;
+ if (tnom < 0.f) {
+ t = 0.f;
+ s = std::clamp(-c / a, 0.f, 1.f);
+ } else if (tnom > e) {
+ t = 1.f;
+ s = std::clamp((b - c) / a, 0.f, 1.f);
+ } else {
+ t = tnom / e;
+ }
+ }
+ }
+ return (p1 + d1 * s - (p2 + d2 * t)).norm();
+}
+
+static Vec3f transform_point(const Transform3d &matrix, const Vec3f &point)
+{
+ return (matrix * point.cast()).cast();
+}
+
+static std::array transform_triangle(const Transform3d &matrix, const std::array &vertices)
+{
+ return {
+ transform_point(matrix, vertices[0]),
+ transform_point(matrix, vertices[1]),
+ transform_point(matrix, vertices[2])
+ };
+}
+
+static Vec3f closest_point_on_triangle(const Vec3f &point, const Vec3f &a, const Vec3f &b, const Vec3f &c)
+{
+ const Vec3f ab = b - a;
+ const Vec3f ac = c - a;
+ const Vec3f ap = point - a;
+ const float d1 = ab.dot(ap);
+ const float d2 = ac.dot(ap);
+ if (d1 <= 0.f && d2 <= 0.f)
+ return a;
+
+ const Vec3f bp = point - b;
+ const float d3 = ab.dot(bp);
+ const float d4 = ac.dot(bp);
+ if (d3 >= 0.f && d4 <= d3)
+ return b;
+
+ const float vc = d1 * d4 - d3 * d2;
+ if (vc <= 0.f && d1 >= 0.f && d3 <= 0.f)
+ return a + ab * (d1 / (d1 - d3));
+
+ const Vec3f cp = point - c;
+ const float d5 = ab.dot(cp);
+ const float d6 = ac.dot(cp);
+ if (d6 >= 0.f && d5 <= d6)
+ return c;
+
+ const float vb = d5 * d2 - d1 * d6;
+ if (vb <= 0.f && d2 >= 0.f && d6 <= 0.f)
+ return a + ac * (d2 / (d2 - d6));
+
+ const float va = d3 * d6 - d5 * d4;
+ if (va <= 0.f && d4 - d3 >= 0.f && d5 - d6 >= 0.f)
+ return b + (c - b) * ((d4 - d3) / ((d4 - d3) + (d5 - d6)));
+
+ const float denom_sum = va + vb + vc;
+ if (std::abs(denom_sum) <= EPSILON)
+ return a;
+ const float denom = 1.f / denom_sum;
+ const float v = vb * denom;
+ const float w = vc * denom;
+ return a + ab * v + ac * w;
+}
+
+static float distance_to_triangle(const Vec3f &point, const std::array &vertices)
+{
+ return (point - closest_point_on_triangle(point, vertices[0], vertices[1], vertices[2])).norm();
+}
+
+static bool aabb_overlap(const Vec3f &min_a, const Vec3f &max_a, const Vec3f &min_b, const Vec3f &max_b)
+{
+ return min_a.x() <= max_b.x() && max_a.x() >= min_b.x() &&
+ min_a.y() <= max_b.y() && max_a.y() >= min_b.y() &&
+ min_a.z() <= max_b.z() && max_a.z() >= min_b.z();
+}
+
+static bool triangle_intersects_brush_segment(const std::array &vertices, const Vec3f &a, const Vec3f &b, float radius)
+{
+ if (distance_to_triangle(a, vertices) <= radius || distance_to_triangle(b, vertices) <= radius)
+ return true;
+ for (const Vec3f &vertex : vertices)
+ if (distance_to_segment(vertex, a, b) <= radius)
+ return true;
+ for (size_t edge_idx = 0; edge_idx < 3; ++edge_idx)
+ if (distance_between_segments(vertices[edge_idx], vertices[(edge_idx + 1) % 3], a, b) <= radius)
+ return true;
+ return false;
+}
+
+static float distance_to_brush_path(const std::vector &stroke_points, const Vec3f &point)
+{
+ if (stroke_points.empty())
+ return std::numeric_limits::max();
+ if (stroke_points.size() == 1)
+ return (point - stroke_points.front()).norm();
+
+ float distance = std::numeric_limits::max();
+ for (size_t idx = 1; idx < stroke_points.size(); ++idx)
+ distance = std::min(distance, distance_to_segment(point, stroke_points[idx - 1], stroke_points[idx]));
+ return distance;
+}
+
+static float sample_rgb_brush_path_alpha(const std::vector &stroke_points,
+ const Vec3f &point,
+ float hardness,
+ float opacity,
+ float brush_radius)
+{
+ opacity = std::clamp(opacity, 0.f, 1.f);
+ if (opacity <= 0.f || brush_radius <= EPSILON)
+ return 0.f;
+
+ const float distance = distance_to_brush_path(stroke_points, point);
+ if (!std::isfinite(distance) || distance > brush_radius)
+ return 0.f;
+
+ hardness = std::clamp(hardness, 0.f, 1.f);
+ const float solid_radius = brush_radius * hardness;
+ if (distance <= solid_radius)
+ return opacity;
+
+ const float fade_width = brush_radius - solid_radius;
+ if (fade_width <= EPSILON)
+ return opacity;
+
+ const float t = std::clamp((brush_radius - distance) / fade_width, 0.f, 1.f);
+ const float soft_alpha = t * t * (3.f - 2.f * t);
+ return opacity * soft_alpha;
+}
+
static float sample_rgb_stroke_alpha(const std::vector &stroke_facets,
const std::unordered_map> &stroke_by_source_triangle,
const RGBStrokeBoundaryEdges &stroke_boundary_edges,
@@ -714,12 +892,109 @@ static float sample_rgb_stroke_alpha(const std::vector rgb_brush_candidate_source_triangles(
+ const ModelVolume &volume,
+ const std::vector &stroke_points,
+ float brush_radius,
+ const std::unordered_map> &stroke_by_source_triangle,
+ const Transform3d &world_matrix)
+{
+ const indexed_triangle_set &its = volume.mesh().its;
+ std::vector candidates(its.indices.size(), false);
+ for (const auto &entry : stroke_by_source_triangle)
+ if (entry.first >= 0 && size_t(entry.first) < candidates.size())
+ candidates[size_t(entry.first)] = true;
+
+ if (stroke_points.empty() || brush_radius <= EPSILON)
+ return candidates;
+
+ std::vector stroke_points_world;
+ stroke_points_world.reserve(stroke_points.size());
+ for (const Vec3f &point : stroke_points)
+ stroke_points_world.emplace_back(transform_point(world_matrix, point));
+
+ Vec3f path_min = stroke_points_world.front();
+ Vec3f path_max = stroke_points_world.front();
+ for (const Vec3f &point : stroke_points_world) {
+ path_min = path_min.cwiseMin(point);
+ path_max = path_max.cwiseMax(point);
+ }
+ path_min -= Vec3f::Constant(brush_radius);
+ path_max += Vec3f::Constant(brush_radius);
+
+ for (size_t tri_idx = 0; tri_idx < its.indices.size(); ++tri_idx) {
+ if (candidates[tri_idx])
+ continue;
+
+ const stl_triangle_vertex_indices &tri = its.indices[tri_idx];
+ if (tri[0] < 0 || tri[1] < 0 || tri[2] < 0)
+ continue;
+ if (size_t(tri[0]) >= its.vertices.size() ||
+ size_t(tri[1]) >= its.vertices.size() ||
+ size_t(tri[2]) >= its.vertices.size())
+ continue;
+
+ const std::array local_vertices = {
+ its.vertices[size_t(tri[0])].cast(),
+ its.vertices[size_t(tri[1])].cast(),
+ its.vertices[size_t(tri[2])].cast()
+ };
+ const std::array vertices = transform_triangle(world_matrix, local_vertices);
+ Vec3f tri_min = vertices[0].cwiseMin(vertices[1]).cwiseMin(vertices[2]);
+ Vec3f tri_max = vertices[0].cwiseMax(vertices[1]).cwiseMax(vertices[2]);
+ if (!aabb_overlap(tri_min, tri_max, path_min, path_max))
+ continue;
+
+ for (size_t point_idx = 0; point_idx < stroke_points_world.size(); ++point_idx) {
+ const Vec3f segment_a = stroke_points_world[point_idx];
+ const Vec3f segment_b = point_idx + 1 < stroke_points_world.size() ?
+ stroke_points_world[point_idx + 1] :
+ stroke_points_world[point_idx];
+ Vec3f segment_min = segment_a.cwiseMin(segment_b) - Vec3f::Constant(brush_radius);
+ Vec3f segment_max = segment_a.cwiseMax(segment_b) + Vec3f::Constant(brush_radius);
+ if (aabb_overlap(tri_min, tri_max, segment_min, segment_max) &&
+ triangle_intersects_brush_segment(vertices, segment_a, segment_b, brush_radius)) {
+ candidates[tri_idx] = true;
+ break;
+ }
+ }
+ }
+
+ return candidates;
+}
+
+static int rgb_leaf_count_depth(size_t leaf_count)
+{
+ int depth = 0;
+ size_t depth_leaf_count = 1;
+ while (depth_leaf_count < leaf_count && depth < 7) {
+ depth_leaf_count *= 4;
+ ++depth;
+ }
+ return depth;
+}
+
+static std::vector rgb_existing_source_triangle_depths(const std::vector &facets, size_t triangle_count)
+{
+ std::vector leaf_counts(triangle_count, 0);
+ for (const ColorFacetTriangle &facet : facets)
+ if (facet.source_triangle >= 0 && size_t(facet.source_triangle) < leaf_counts.size())
+ ++leaf_counts[size_t(facet.source_triangle)];
+
+ std::vector depths(triangle_count, 0);
+ for (size_t tri_idx = 0; tri_idx < leaf_counts.size(); ++tri_idx)
+ depths[tri_idx] = rgb_leaf_count_depth(leaf_counts[tri_idx]);
+ return depths;
+}
+
static bool apply_rgb_stroke_to_volume(ModelVolume &volume,
const std::vector &stroke_facets,
const ColorRGBA &brush_color,
float hardness,
float opacity,
- float brush_radius)
+ float brush_radius,
+ const std::vector &brush_stroke_points,
+ const Transform3d &world_matrix)
{
if (stroke_facets.empty())
return false;
@@ -736,6 +1011,15 @@ static bool apply_rgb_stroke_to_volume(ModelVolume
for (size_t idx = 0; idx < stroke_facets.size(); ++idx)
stroke_by_source_triangle[stroke_facets[idx].source_triangle].emplace_back(idx);
RGBStrokeBoundaryEdges stroke_boundary_edges = build_rgb_stroke_boundary_edges(stroke_facets);
+ const std::vector brush_candidate_triangles =
+ rgb_brush_candidate_source_triangles(volume, brush_stroke_points, brush_radius, stroke_by_source_triangle, world_matrix);
+ const bool use_brush_path = !brush_stroke_points.empty();
+ std::vector brush_stroke_points_world;
+ if (use_brush_path) {
+ brush_stroke_points_world.reserve(brush_stroke_points.size());
+ for (const Vec3f &point : brush_stroke_points)
+ brush_stroke_points_world.emplace_back(transform_point(world_matrix, point));
+ }
const ColorRGBA background = rgb_metadata_background_color(volume.texture_mapping_color_facets);
const uint32_t brush_packed = pack_vertex_color_rgba(brush_color);
@@ -749,21 +1033,34 @@ static bool apply_rgb_stroke_to_volume(ModelVolume
brush_packed,
hardness,
opacity,
- brush_radius](size_t tri_idx, const Vec3f &point, const Vec3f &) {
+ brush_radius,
+ use_brush_path,
+ &brush_candidate_triangles,
+ &brush_stroke_points_world,
+ &world_matrix](size_t tri_idx, const Vec3f &point, const Vec3f &) {
ColorRGBA source_color = background;
if (std::optional sampled =
sample_rgb_color_facets(existing_facets, existing_by_source_triangle, int(tri_idx), point)) {
source_color = *sampled;
}
- const float alpha = sample_rgb_stroke_alpha(stroke_facets,
- stroke_by_source_triangle,
- stroke_boundary_edges,
- int(tri_idx),
- point,
- hardness,
- opacity,
- brush_radius);
+ if (use_brush_path && (tri_idx >= brush_candidate_triangles.size() || !brush_candidate_triangles[tri_idx]))
+ return pack_vertex_color_rgba(source_color);
+
+ const float alpha = use_brush_path ?
+ sample_rgb_brush_path_alpha(brush_stroke_points_world,
+ transform_point(world_matrix, point),
+ hardness,
+ opacity,
+ brush_radius) :
+ sample_rgb_stroke_alpha(stroke_facets,
+ stroke_by_source_triangle,
+ stroke_boundary_edges,
+ int(tri_idx),
+ point,
+ hardness,
+ opacity,
+ brush_radius);
if (alpha <= 0.f)
return pack_vertex_color_rgba(source_color);
if (alpha >= 1.f)
@@ -777,29 +1074,60 @@ static bool apply_rgb_stroke_to_volume(ModelVolume
const float mesh_span = mesh_max_axis_span(volume.mesh().its);
const int safe_max_depth = texture_mapping_depth_for_budget(volume.mesh().its.indices.size(), 7, 1800000);
+ const float brush_subdivision_target = true_color_brush_subdivision_target(brush_radius);
+ const std::vector existing_source_triangle_depths =
+ rgb_existing_source_triangle_depths(existing_facets, volume.mesh().its.indices.size());
TextureMappingColorSubdivisionDepths subdivision_depths =
- [mesh_span, safe_max_depth, &stroke_by_source_triangle](size_t tri_idx, const std::array &vertices) {
+ [mesh_span,
+ safe_max_depth,
+ brush_subdivision_target,
+ &brush_candidate_triangles,
+ &existing_source_triangle_depths,
+ &world_matrix](size_t tri_idx, const std::array &vertices) {
const int base_depth = texture_mapping_depth_from_span(triangle_max_edge_length(vertices),
std::max(mesh_span / 220.f, 0.18f),
std::min(6, safe_max_depth));
- if (stroke_by_source_triangle.find(int(tri_idx)) != stroke_by_source_triangle.end())
- return std::make_pair(std::min(std::max(base_depth, 4), safe_max_depth), safe_max_depth);
- return std::make_pair(base_depth, safe_max_depth);
+ const int preserved_depth = tri_idx < existing_source_triangle_depths.size() ?
+ existing_source_triangle_depths[tri_idx] :
+ 0;
+ int min_depth = std::max(base_depth, preserved_depth);
+ int max_depth = std::max(safe_max_depth, preserved_depth);
+ if (tri_idx < brush_candidate_triangles.size() && brush_candidate_triangles[tri_idx]) {
+ const std::array world_vertices = transform_triangle(world_matrix, vertices);
+ const int brush_depth = texture_mapping_depth_from_span(triangle_max_edge_length(world_vertices),
+ brush_subdivision_target,
+ 7);
+ min_depth = std::max(min_depth, brush_depth);
+ max_depth = std::max(max_depth, min_depth);
+ }
+ return std::make_pair(min_depth, max_depth);
};
return volume.texture_mapping_color_facets.set_from_triangle_sampler(volume, sampler, safe_max_depth, 0.012f, subdivision_depths);
}
-static bool initialize_volume_rgb_data(ModelVolume &volume, const ColorRGBA &background)
+static bool build_volume_rgb_data(const ModelVolume &volume, const ColorRGBA &background, ColorFacetsAnnotation &out)
{
if (volume.mesh().its.indices.empty() || volume.mesh().its.vertices.empty())
return false;
+ out.reset();
const uint32_t packed = pack_vertex_color_rgba(background);
TextureMappingColorSampler sampler = [packed](size_t, const Vec3f &, const Vec3f &) { return packed; };
- const bool changed = volume.texture_mapping_color_facets.set_from_triangle_sampler(volume, sampler, 0, 0.f);
- volume.texture_mapping_color_facets.set_metadata_json(rgb_metadata_json(background));
- return changed;
+ const bool changed = out.set_from_triangle_sampler(volume, sampler, 0, 0.f);
+ out.set_metadata_json(rgb_metadata_json(background));
+ return changed || !out.empty();
+}
+
+static bool initialize_volume_rgb_data(ModelVolume &volume, const ColorRGBA &background)
+{
+ std::unique_ptr rgb_data = ColorFacetsAnnotation::make_temporary();
+ if (!rgb_data || !build_volume_rgb_data(volume, background, *rgb_data))
+ return false;
+ if (volume.texture_mapping_color_facets.equals(*rgb_data))
+ return false;
+ volume.texture_mapping_color_facets.assign(*rgb_data);
+ return true;
}
struct ProjectionContext
@@ -1181,7 +1509,9 @@ static ColorRGBA sample_volume_color_source(const ModelVolume &volume,
return fallback_color != nullptr ? *fallback_color : ColorRGBA(1.f, 1.f, 1.f, 1.f);
}
-static bool initialize_volume_rgb_data_from_current_surface_color(ModelVolume &volume, const ColorRGBA &fallback_color)
+static bool build_volume_rgb_data_from_current_surface_color(const ModelVolume &volume,
+ const ColorRGBA &fallback_color,
+ ColorFacetsAnnotation &out)
{
const indexed_triangle_set &its = volume.mesh().its;
if (its.indices.empty() || its.vertices.empty())
@@ -1190,8 +1520,9 @@ static bool initialize_volume_rgb_data_from_current_surface_color(ModelVolume &v
const bool has_image_texture = model_volume_has_bakeable_image_texture_data(&volume);
const bool has_vertex_colors = volume.imported_vertex_colors_rgba.size() == its.vertices.size();
if (!has_image_texture && !has_vertex_colors)
- return initialize_volume_rgb_data(volume, fallback_color);
+ return build_volume_rgb_data(volume, fallback_color, out);
+ out.reset();
const VolumeColorSource source = build_volume_color_source(volume);
TextureMappingColorSampler sampler = [&volume, source, fallback_color](size_t tri_idx, const Vec3f &point, const Vec3f &barycentric) {
return pack_vertex_color_rgba(sample_volume_color_source(volume, source, tri_idx, point, barycentric, true, &fallback_color));
@@ -1204,23 +1535,30 @@ static bool initialize_volume_rgb_data_from_current_surface_color(ModelVolume &v
const int depth = texture_mapping_depth_from_span(texture_triangle_uv_pixel_span(&volume, tri_idx), 8.f, safe_max_depth);
return std::make_pair(depth, depth);
};
- changed = volume.texture_mapping_color_facets.set_from_triangle_sampler(volume,
- sampler,
- safe_max_depth,
- 0.015f,
- subdivision_depths);
+ changed = out.set_from_triangle_sampler(volume, sampler, safe_max_depth, 0.015f, subdivision_depths);
} else {
const float target_edge = std::max(mesh_max_axis_span(its) / 160.f, 0.25f);
TextureMappingColorSubdivisionDepths subdivision_depths = [target_edge](size_t, const std::array &vertices) {
const int depth = texture_mapping_depth_from_span(triangle_max_edge_length(vertices), target_edge, 5);
return std::make_pair(depth, depth);
};
- changed = volume.texture_mapping_color_facets.set_from_triangle_sampler(volume, sampler, 5, 0.025f, subdivision_depths);
+ changed = out.set_from_triangle_sampler(volume, sampler, 5, 0.025f, subdivision_depths);
}
- if (changed && volume.texture_mapping_color_facets.metadata_json().empty())
- volume.texture_mapping_color_facets.set_metadata_json(rgb_metadata_json(fallback_color));
- return changed;
+ if (changed && out.metadata_json().empty())
+ out.set_metadata_json(rgb_metadata_json(fallback_color));
+ return changed || !out.empty();
+}
+
+static bool initialize_volume_rgb_data_from_current_surface_color(ModelVolume &volume, const ColorRGBA &fallback_color)
+{
+ std::unique_ptr rgb_data = ColorFacetsAnnotation::make_temporary();
+ if (!rgb_data || !build_volume_rgb_data_from_current_surface_color(volume, fallback_color, *rgb_data))
+ return false;
+ if (volume.texture_mapping_color_facets.equals(*rgb_data))
+ return false;
+ volume.texture_mapping_color_facets.assign(*rgb_data);
+ return true;
}
static ColorRGBA projection_base_color_for_volume(const ModelVolume &volume)
@@ -1420,6 +1758,1233 @@ static bool projection_point_is_visible(const ProjectionVisibility &visibility,
return depth <= nearest + 2e-3f;
}
+enum class ManagedColorDataType
+{
+ ColorRegions,
+ VertexColors,
+ ImageTexture,
+ RgbaData
+};
+
+struct ManagedColorDataCreateSource
+{
+ std::optional type;
+};
+
+struct ManagedColorDataSummary
+{
+ bool has_color_regions = false;
+ bool has_vertex_colors = false;
+ bool has_image_texture = false;
+ bool has_rgba_data = false;
+ size_t color_region_triangle_count = 0;
+ size_t vertex_color_count = 0;
+ size_t image_texture_count = 0;
+ uint32_t max_texture_width = 0;
+ uint32_t max_texture_height = 0;
+ size_t rgba_data_bytes = 0;
+};
+
+static bool object_has_color_regions(const ModelObject &object)
+{
+ for (const ModelVolume *volume : object.volumes)
+ if (volume != nullptr && volume->is_model_part() && !volume->mmu_segmentation_facets.empty())
+ return true;
+ return false;
+}
+
+static bool object_has_vertex_color_data(const ModelObject &object)
+{
+ for (const ModelVolume *volume : object.volumes)
+ if (volume != nullptr && volume->is_model_part() && !volume->imported_vertex_colors_rgba.empty())
+ return true;
+ return false;
+}
+
+static bool object_has_image_texture_data(const ModelObject &object)
+{
+ for (const ModelVolume *volume : object.volumes)
+ if (volume != nullptr && volume->is_model_part() && model_volume_has_imported_image_texture_data(volume))
+ return true;
+ return false;
+}
+
+static bool object_has_rgba_data(const ModelObject &object)
+{
+ for (const ModelVolume *volume : object.volumes)
+ if (volume != nullptr && volume->is_model_part() && !volume->texture_mapping_color_facets.empty())
+ return true;
+ return false;
+}
+
+static bool object_has_managed_color_data(const ModelObject &object, ManagedColorDataType type)
+{
+ switch (type) {
+ case ManagedColorDataType::ColorRegions:
+ return object_has_color_regions(object);
+ case ManagedColorDataType::VertexColors:
+ return object_has_vertex_color_data(object);
+ case ManagedColorDataType::ImageTexture:
+ return object_has_image_texture_data(object);
+ case ManagedColorDataType::RgbaData:
+ return object_has_rgba_data(object);
+ }
+ return false;
+}
+
+static bool managed_color_data_summary_has_type(const ManagedColorDataSummary &summary, ManagedColorDataType type)
+{
+ switch (type) {
+ case ManagedColorDataType::ColorRegions:
+ return summary.has_color_regions;
+ case ManagedColorDataType::VertexColors:
+ return summary.has_vertex_colors;
+ case ManagedColorDataType::ImageTexture:
+ return summary.has_image_texture;
+ case ManagedColorDataType::RgbaData:
+ return summary.has_rgba_data;
+ }
+ return false;
+}
+
+static wxString managed_color_data_type_label(ManagedColorDataType type)
+{
+ switch (type) {
+ case ManagedColorDataType::ColorRegions:
+ return _L("3mf color regions");
+ case ManagedColorDataType::VertexColors:
+ return _L("Vertex Colors");
+ case ManagedColorDataType::ImageTexture:
+ return _L("Image Texture");
+ case ManagedColorDataType::RgbaData:
+ return _L("RGBA data");
+ }
+ return wxString();
+}
+
+static size_t estimated_rgba_data_bytes(const ColorFacetsAnnotation &annotation)
+{
+ const TriangleColorSplittingData &data = annotation.get_data();
+ return data.triangles_to_split.size() * sizeof(ColorTriangleBitStreamMapping) +
+ (data.bitstream.size() + 7) / 8 +
+ data.colors_rgba.size() * sizeof(uint32_t) +
+ data.metadata_json.size();
+}
+
+static ManagedColorDataSummary summarize_managed_color_data(const ModelObject *object)
+{
+ ManagedColorDataSummary summary;
+ if (object == nullptr)
+ return summary;
+
+ for (const ModelVolume *volume : object->volumes) {
+ if (volume == nullptr || !volume->is_model_part())
+ continue;
+
+ if (!volume->mmu_segmentation_facets.empty()) {
+ summary.has_color_regions = true;
+ summary.color_region_triangle_count += volume->mmu_segmentation_facets.get_data().triangles_to_split.size();
+ }
+
+ if (!volume->imported_vertex_colors_rgba.empty()) {
+ summary.has_vertex_colors = true;
+ summary.vertex_color_count += volume->imported_vertex_colors_rgba.size();
+ }
+
+ if (model_volume_has_imported_image_texture_data(volume)) {
+ summary.has_image_texture = true;
+ ++summary.image_texture_count;
+ summary.max_texture_width = std::max(summary.max_texture_width, volume->imported_texture_width);
+ summary.max_texture_height = std::max(summary.max_texture_height, volume->imported_texture_height);
+ }
+
+ if (!volume->texture_mapping_color_facets.empty()) {
+ summary.has_rgba_data = true;
+ summary.rgba_data_bytes += estimated_rgba_data_bytes(volume->texture_mapping_color_facets);
+ }
+ }
+ return summary;
+}
+
+static wxString managed_color_data_size_text(const ManagedColorDataSummary &summary, ManagedColorDataType type)
+{
+ switch (type) {
+ case ManagedColorDataType::ColorRegions:
+ return wxString::Format(_L("%llu triangles"), static_cast(summary.color_region_triangle_count));
+ case ManagedColorDataType::VertexColors:
+ return wxString::Format(_L("%llu vertices"), static_cast(summary.vertex_color_count));
+ case ManagedColorDataType::ImageTexture:
+ if (!summary.has_image_texture)
+ return _L("0 x 0");
+ if (summary.image_texture_count <= 1)
+ return wxString::Format(_L("%u x %u"),
+ static_cast(summary.max_texture_width),
+ static_cast(summary.max_texture_height));
+ return wxString::Format(_L("%llu textures, max %u x %u"),
+ static_cast(summary.image_texture_count),
+ static_cast(summary.max_texture_width),
+ static_cast(summary.max_texture_height));
+ case ManagedColorDataType::RgbaData:
+ if (summary.rgba_data_bytes > 0 && summary.rgba_data_bytes < 1024 * 1024 / 100)
+ return _L("<0.01 MB");
+ return wxString::Format(_L("%.2f MB"), double(summary.rgba_data_bytes) / (1024.0 * 1024.0));
+ }
+ return wxString();
+}
+
+static bool clear_object_managed_color_data(ModelObject &object, ManagedColorDataType type)
+{
+ bool changed = false;
+ for (ModelVolume *volume : object.volumes) {
+ if (volume == nullptr || !volume->is_model_part())
+ continue;
+
+ switch (type) {
+ case ManagedColorDataType::ColorRegions:
+ if (!volume->mmu_segmentation_facets.empty()) {
+ volume->mmu_segmentation_facets.reset();
+ changed = true;
+ }
+ break;
+ case ManagedColorDataType::VertexColors:
+ if (!volume->imported_vertex_colors_rgba.empty()) {
+ volume->imported_vertex_colors_rgba.clear();
+ changed = true;
+ }
+ break;
+ case ManagedColorDataType::ImageTexture:
+ if (!volume->imported_texture_rgba.empty() ||
+ !volume->imported_texture_uvs_per_face.empty() ||
+ !volume->imported_texture_uv_valid.empty() ||
+ volume->imported_texture_width != 0 ||
+ volume->imported_texture_height != 0) {
+ volume->imported_texture_uvs_per_face.clear();
+ volume->imported_texture_uv_valid.clear();
+ volume->imported_texture_rgba.clear();
+ volume->imported_texture_width = 0;
+ volume->imported_texture_height = 0;
+ changed = true;
+ }
+ break;
+ case ManagedColorDataType::RgbaData:
+ if (!volume->texture_mapping_color_facets.empty()) {
+ volume->texture_mapping_color_facets.reset();
+ changed = true;
+ }
+ break;
+ }
+ }
+ return changed;
+}
+
+static bool assign_object_to_texture_mapping_zone(ModelObject &object)
+{
+ const unsigned int texture_mapping_filament_id = ensure_texture_mapping_zone();
+ if (texture_mapping_filament_id == 0)
+ return false;
+
+ object.config.set("extruder", int(texture_mapping_filament_id));
+ for (ModelVolume *volume : object.volumes)
+ if (volume != nullptr && volume->is_model_part())
+ volume->config.set("extruder", int(texture_mapping_filament_id));
+ return true;
+}
+
+struct ManagedRegionColorSource
+{
+ std::vector> triangles_per_type;
+ std::vector>> by_source_triangle;
+ std::vector state_colors;
+};
+
+struct ManagedColorSourceFlags
+{
+ bool use_rgba = false;
+ bool use_image_texture = false;
+ bool use_vertex_colors = false;
+ bool use_color_regions = false;
+};
+
+static ManagedColorSourceFlags managed_color_source_flags(const ManagedColorDataCreateSource &source)
+{
+ ManagedColorSourceFlags flags;
+ if (!source.type)
+ return flags;
+
+ switch (*source.type) {
+ case ManagedColorDataType::ColorRegions:
+ flags.use_color_regions = true;
+ break;
+ case ManagedColorDataType::VertexColors:
+ flags.use_vertex_colors = true;
+ break;
+ case ManagedColorDataType::ImageTexture:
+ flags.use_image_texture = true;
+ break;
+ case ManagedColorDataType::RgbaData:
+ flags.use_rgba = true;
+ break;
+ }
+ return flags;
+}
+
+static ColorRGBA blank_color_for_managed_target(ManagedColorDataType target)
+{
+ return target == ManagedColorDataType::RgbaData ? ColorRGBA(1.f, 1.f, 1.f, 0.f) :
+ ColorRGBA(1.f, 1.f, 1.f, 1.f);
+}
+
+static std::vector parse_managed_color_strings(const std::vector &color_strings)
+{
+ std::vector colors;
+ colors.reserve(color_strings.size());
+ for (const std::string &color_string : color_strings) {
+ unsigned char rgba[4] = { 38, 166, 154, 255 };
+ BitmapCache::parse_color4(color_string, rgba);
+ colors.emplace_back(float(rgba[0]) / 255.f,
+ float(rgba[1]) / 255.f,
+ float(rgba[2]) / 255.f,
+ float(rgba[3]) / 255.f);
+ }
+ return colors;
+}
+
+static ColorRGBA managed_filament_color(unsigned int filament_id,
+ unsigned int base_filament_id,
+ const std::vector &physical_colors,
+ const std::vector &display_colors)
+{
+ const size_t physical_count = physical_colors.size();
+ const ColorRGBA fallback = physical_colors.empty() ? ColorRGBA(0.15f, 0.65f, 0.6f, 1.f) : physical_colors.front();
+
+ auto physical_or_fallback = [&physical_colors, fallback](unsigned int id) {
+ if (id >= 1 && id <= physical_colors.size())
+ return physical_colors[size_t(id - 1)];
+ return fallback;
+ };
+
+ const bool texture_mapping_zone =
+ wxGetApp().preset_bundle != nullptr &&
+ wxGetApp().preset_bundle->texture_mapping_zones.is_texture_mapping_zone_id(filament_id);
+ if (texture_mapping_zone) {
+ if (base_filament_id != 0 && base_filament_id != filament_id)
+ return managed_filament_color(base_filament_id, 0, physical_colors, display_colors);
+ return fallback;
+ }
+
+ if (filament_id >= 1 && filament_id <= physical_count)
+ return physical_or_fallback(filament_id);
+
+ if (filament_id >= 1 && filament_id <= display_colors.size())
+ return display_colors[size_t(filament_id - 1)];
+
+ return fallback;
+}
+
+static ManagedRegionColorSource build_managed_region_color_source(const ModelVolume &volume)
+{
+ ManagedRegionColorSource source;
+ if (volume.mmu_segmentation_facets.empty())
+ return source;
+
+ volume.mmu_segmentation_facets.get_facet_triangles(volume, source.triangles_per_type);
+ source.by_source_triangle.resize(source.triangles_per_type.size());
+
+ const std::vector physical_color_strings =
+ wxGetApp().plater() != nullptr ? wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, false) :
+ std::vector();
+ const std::vector display_color_strings =
+ wxGetApp().plater() != nullptr ? wxGetApp().plater()->get_extruder_colors_from_plater_config() :
+ physical_color_strings;
+ const std::vector physical_colors = parse_managed_color_strings(physical_color_strings);
+ const std::vector display_colors = parse_managed_color_strings(display_color_strings);
+ const unsigned int base_filament_id = volume.extruder_id() > 0 ? unsigned(volume.extruder_id()) : 1u;
+
+ source.state_colors.reserve(source.triangles_per_type.size());
+ for (size_t state_idx = 0; state_idx < source.triangles_per_type.size(); ++state_idx) {
+ const unsigned int filament_id = state_idx == 0 ? base_filament_id : unsigned(state_idx);
+ ColorRGBA color = managed_filament_color(filament_id, base_filament_id, physical_colors, display_colors);
+ color.a(1.f);
+ source.state_colors.emplace_back(color);
+
+ std::unordered_map> &by_source = source.by_source_triangle[state_idx];
+ by_source.reserve(source.triangles_per_type[state_idx].size());
+ for (size_t idx = 0; idx < source.triangles_per_type[state_idx].size(); ++idx)
+ by_source[source.triangles_per_type[state_idx][idx].source_triangle].emplace_back(idx);
+ }
+
+ return source;
+}
+
+static std::optional sample_managed_region_color_source(const ManagedRegionColorSource &source,
+ int source_triangle,
+ const Vec3f &point)
+{
+ std::optional inside_color;
+ float best_inside_score = -std::numeric_limits::max();
+ size_t best_inside_state = 0;
+ std::optional nearest_color;
+ float nearest_distance_sq = std::numeric_limits::max();
+ size_t nearest_state = 0;
+
+ for (size_t state_idx = 0; state_idx < source.triangles_per_type.size(); ++state_idx) {
+ if (state_idx >= source.by_source_triangle.size() || state_idx >= source.state_colors.size())
+ continue;
+
+ const auto found = source.by_source_triangle[state_idx].find(source_triangle);
+ if (found == source.by_source_triangle[state_idx].end())
+ continue;
+
+ const float tolerance = -1e-4f;
+ const std::vector &state_triangles = source.triangles_per_type[state_idx];
+ for (const size_t facet_idx : found->second) {
+ if (facet_idx >= state_triangles.size())
+ continue;
+
+ const TriangleSelector::FacetStateTriangle &facet = state_triangles[facet_idx];
+ Vec3f weights = Vec3f::Zero();
+ if (!barycentric_weights_for_region_vertex_colors(point, facet.vertices[0], facet.vertices[1], facet.vertices[2], weights))
+ continue;
+ if (weights.x() >= tolerance && weights.y() >= tolerance && weights.z() >= tolerance) {
+ const float score = std::min({ weights.x(), weights.y(), weights.z() });
+ if (!inside_color ||
+ score > best_inside_score + 1e-6f ||
+ (std::abs(score - best_inside_score) <= 1e-6f && state_idx > best_inside_state)) {
+ inside_color = source.state_colors[state_idx];
+ best_inside_score = score;
+ best_inside_state = state_idx;
+ }
+ }
+
+ const Vec3f closest = closest_point_on_triangle(point, facet.vertices[0], facet.vertices[1], facet.vertices[2]);
+ const float distance_sq = (point - closest).squaredNorm();
+ if (!nearest_color ||
+ distance_sq < nearest_distance_sq - 1e-8f ||
+ (std::abs(distance_sq - nearest_distance_sq) <= 1e-8f && state_idx > nearest_state)) {
+ nearest_color = source.state_colors[state_idx];
+ nearest_distance_sq = distance_sq;
+ nearest_state = state_idx;
+ }
+ }
+ }
+
+ return inside_color ? inside_color : nearest_color;
+}
+
+static ColorRGBA sample_managed_volume_color_source(const ModelVolume &volume,
+ const VolumeColorSource &rgba_source,
+ const ManagedRegionColorSource ®ion_source,
+ size_t tri_idx,
+ const Vec3f &point,
+ const Vec3f &barycentric,
+ bool use_rgba,
+ bool use_image_texture,
+ bool use_vertex_colors,
+ bool use_color_regions,
+ const ColorRGBA &fallback_color)
+{
+ if (use_rgba && !volume.texture_mapping_color_facets.empty()) {
+ if (std::optional color = sample_rgb_color_facets(rgba_source.rgb_facets,
+ rgba_source.rgb_by_source_triangle,
+ int(tri_idx),
+ point))
+ return *color;
+ return rgb_metadata_background_color(volume.texture_mapping_color_facets);
+ }
+
+ const indexed_triangle_set &its = volume.mesh().its;
+ if (use_image_texture && model_volume_has_bakeable_image_texture_data(&volume) && tri_idx < volume.imported_texture_uv_valid.size()) {
+ const size_t uv_offset = tri_idx * 6;
+ if (volume.imported_texture_uv_valid[tri_idx] != 0 && uv_offset + 5 < volume.imported_texture_uvs_per_face.size()) {
+ const Vec2f uv0(volume.imported_texture_uvs_per_face[uv_offset + 0], volume.imported_texture_uvs_per_face[uv_offset + 1]);
+ const Vec2f uv1(volume.imported_texture_uvs_per_face[uv_offset + 2], volume.imported_texture_uvs_per_face[uv_offset + 3]);
+ const Vec2f uv2(volume.imported_texture_uvs_per_face[uv_offset + 4], volume.imported_texture_uvs_per_face[uv_offset + 5]);
+ const Vec2f uv = uv0 * barycentric.x() + uv1 * barycentric.y() + uv2 * barycentric.z();
+ return sample_texture_rgba_for_vertex_bake(volume.imported_texture_rgba,
+ volume.imported_texture_width,
+ volume.imported_texture_height,
+ uv);
+ }
+ }
+
+ if (use_vertex_colors && volume.imported_vertex_colors_rgba.size() == its.vertices.size() && tri_idx < its.indices.size()) {
+ const stl_triangle_vertex_indices &tri = its.indices[tri_idx];
+ if (tri[0] >= 0 && tri[1] >= 0 && tri[2] >= 0 &&
+ size_t(tri[0]) < volume.imported_vertex_colors_rgba.size() &&
+ size_t(tri[1]) < volume.imported_vertex_colors_rgba.size() &&
+ size_t(tri[2]) < volume.imported_vertex_colors_rgba.size()) {
+ const ColorRGBA c0 = unpack_vertex_color_rgba_for_conversion(volume.imported_vertex_colors_rgba[size_t(tri[0])]);
+ const ColorRGBA c1 = unpack_vertex_color_rgba_for_conversion(volume.imported_vertex_colors_rgba[size_t(tri[1])]);
+ const ColorRGBA c2 = unpack_vertex_color_rgba_for_conversion(volume.imported_vertex_colors_rgba[size_t(tri[2])]);
+ return ColorRGBA(c0.r() * barycentric.x() + c1.r() * barycentric.y() + c2.r() * barycentric.z(),
+ c0.g() * barycentric.x() + c1.g() * barycentric.y() + c2.g() * barycentric.z(),
+ c0.b() * barycentric.x() + c1.b() * barycentric.y() + c2.b() * barycentric.z(),
+ c0.a() * barycentric.x() + c1.a() * barycentric.y() + c2.a() * barycentric.z());
+ }
+ }
+
+ if (use_color_regions) {
+ if (std::optional color = sample_managed_region_color_source(region_source, int(tri_idx), point))
+ return *color;
+ }
+
+ return fallback_color;
+}
+
+static bool convert_object_to_vertex_colors(ModelObject &object, const ManagedColorDataCreateSource &source)
+{
+ if (object_has_vertex_color_data(object))
+ return false;
+
+ bool changed = false;
+ const ManagedColorSourceFlags source_flags = managed_color_source_flags(source);
+ const ColorRGBA fallback_color = blank_color_for_managed_target(ManagedColorDataType::VertexColors);
+ for (ModelVolume *volume : object.volumes) {
+ if (volume == nullptr || !volume->is_model_part())
+ continue;
+
+ const indexed_triangle_set &its = volume->mesh().its;
+ if (its.vertices.empty())
+ continue;
+
+ const VolumeColorSource rgba_source = build_volume_color_source(*volume);
+ const ManagedRegionColorSource region_source = build_managed_region_color_source(*volume);
+ std::vector> accumulators(its.vertices.size(), { 0.f, 0.f, 0.f, 0.f });
+ std::vector counts(its.vertices.size(), 0);
+
+ for (size_t tri_idx = 0; tri_idx < its.indices.size(); ++tri_idx) {
+ const stl_triangle_vertex_indices &tri = its.indices[tri_idx];
+ for (int corner = 0; corner < 3; ++corner) {
+ if (tri[corner] < 0 || size_t(tri[corner]) >= its.vertices.size())
+ continue;
+ Vec3f barycentric = Vec3f::Zero();
+ barycentric[corner] = 1.f;
+ const ColorRGBA color = sample_managed_volume_color_source(*volume,
+ rgba_source,
+ region_source,
+ tri_idx,
+ its.vertices[size_t(tri[corner])].cast(),
+ barycentric,
+ source_flags.use_rgba,
+ source_flags.use_image_texture,
+ source_flags.use_vertex_colors,
+ source_flags.use_color_regions,
+ fallback_color);
+ std::array &accumulator = accumulators[size_t(tri[corner])];
+ accumulator[0] += color.r();
+ accumulator[1] += color.g();
+ accumulator[2] += color.b();
+ accumulator[3] += color.a();
+ ++counts[size_t(tri[corner])];
+ }
+ }
+
+ std::vector vertex_colors;
+ vertex_colors.reserve(its.vertices.size());
+ for (size_t idx = 0; idx < its.vertices.size(); ++idx) {
+ ColorRGBA color = fallback_color;
+ if (counts[idx] > 0) {
+ const float inv = 1.f / float(counts[idx]);
+ color = ColorRGBA(accumulators[idx][0] * inv,
+ accumulators[idx][1] * inv,
+ accumulators[idx][2] * inv,
+ accumulators[idx][3] * inv);
+ }
+ vertex_colors.emplace_back(pack_vertex_color_rgba(color));
+ }
+
+ volume->imported_vertex_colors_rgba = std::move(vertex_colors);
+ changed = true;
+ }
+ return changed;
+}
+
+static bool convert_object_to_image_texture(ModelObject &object, const ManagedColorDataCreateSource &source)
+{
+ if (object_has_image_texture_data(object))
+ return false;
+
+ bool changed = false;
+ const ManagedColorSourceFlags source_flags = managed_color_source_flags(source);
+ const ColorRGBA fallback_color = blank_color_for_managed_target(ManagedColorDataType::ImageTexture);
+ const uint32_t fallback_packed = pack_vertex_color_rgba(fallback_color);
+ for (ModelVolume *volume : object.volumes) {
+ if (volume == nullptr || !volume->is_model_part())
+ continue;
+
+ const indexed_triangle_set &its = volume->mesh().its;
+ if (its.vertices.empty() || its.indices.empty())
+ continue;
+
+ const uint32_t texture_size = projection_texture_size_for_triangles(its.indices.size());
+ const uint32_t grid = uint32_t(std::ceil(std::sqrt(double(std::max(its.indices.size(), 1)))));
+ const float tile = float(texture_size) / float(std::max(grid, 1));
+ volume->imported_texture_width = texture_size;
+ volume->imported_texture_height = texture_size;
+ volume->imported_texture_rgba.assign(size_t(texture_size) * size_t(texture_size) * 4, 0);
+ for (size_t idx = 0; idx < size_t(texture_size) * size_t(texture_size); ++idx) {
+ volume->imported_texture_rgba[idx * 4 + 0] = uint8_t((fallback_packed >> 24) & 0xFFu);
+ volume->imported_texture_rgba[idx * 4 + 1] = uint8_t((fallback_packed >> 16) & 0xFFu);
+ volume->imported_texture_rgba[idx * 4 + 2] = uint8_t((fallback_packed >> 8) & 0xFFu);
+ volume->imported_texture_rgba[idx * 4 + 3] = uint8_t(fallback_packed & 0xFFu);
+ }
+ volume->imported_texture_uv_valid.assign(its.indices.size(), 1);
+ volume->imported_texture_uvs_per_face.assign(its.indices.size() * 6, 0.f);
+
+ for (size_t tri_idx = 0; tri_idx < its.indices.size(); ++tri_idx) {
+ const uint32_t cell_x = uint32_t(tri_idx % grid);
+ const uint32_t cell_y = uint32_t(tri_idx / grid);
+ const float left = float(cell_x) * tile + 0.5f;
+ const float top = float(cell_y) * tile + 0.5f;
+ const float right = std::min(float(texture_size) - 0.5f, float(cell_x + 1) * tile - 0.5f);
+ const float bottom = std::min(float(texture_size) - 0.5f, float(cell_y + 1) * tile - 0.5f);
+ const size_t uv = tri_idx * 6;
+ volume->imported_texture_uvs_per_face[uv + 0] = left / float(texture_size);
+ volume->imported_texture_uvs_per_face[uv + 1] = top / float(texture_size);
+ volume->imported_texture_uvs_per_face[uv + 2] = right / float(texture_size);
+ volume->imported_texture_uvs_per_face[uv + 3] = top / float(texture_size);
+ volume->imported_texture_uvs_per_face[uv + 4] = left / float(texture_size);
+ volume->imported_texture_uvs_per_face[uv + 5] = bottom / float(texture_size);
+ }
+
+ const VolumeColorSource rgba_source = build_volume_color_source(*volume);
+ const ManagedRegionColorSource region_source = build_managed_region_color_source(*volume);
+ for (size_t tri_idx = 0; tri_idx < its.indices.size(); ++tri_idx) {
+ const stl_triangle_vertex_indices &tri = its.indices[tri_idx];
+ if (tri[0] < 0 || tri[1] < 0 || tri[2] < 0)
+ continue;
+ if (size_t(tri[0]) >= its.vertices.size() ||
+ size_t(tri[1]) >= its.vertices.size() ||
+ size_t(tri[2]) >= its.vertices.size())
+ continue;
+
+ const size_t uv_offset = tri_idx * 6;
+ const std::array uvs = unwrap_projection_uvs(std::array{
+ Vec2f(volume->imported_texture_uvs_per_face[uv_offset + 0], volume->imported_texture_uvs_per_face[uv_offset + 1]),
+ Vec2f(volume->imported_texture_uvs_per_face[uv_offset + 2], volume->imported_texture_uvs_per_face[uv_offset + 3]),
+ Vec2f(volume->imported_texture_uvs_per_face[uv_offset + 4], volume->imported_texture_uvs_per_face[uv_offset + 5])
+ });
+ const std::array vertices = {
+ its.vertices[size_t(tri[0])].cast(),
+ its.vertices[size_t(tri[1])].cast(),
+ its.vertices[size_t(tri[2])].cast()
+ };
+ const float texture_width = float(volume->imported_texture_width);
+ const float texture_height = float(volume->imported_texture_height);
+ const std::array pixel_uvs = {
+ Vec2f(uvs[0].x() * texture_width, uvs[0].y() * texture_height),
+ Vec2f(uvs[1].x() * texture_width, uvs[1].y() * texture_height),
+ Vec2f(uvs[2].x() * texture_width, uvs[2].y() * texture_height)
+ };
+ const float texture_padding = 2.f;
+ const int padding_px = int(std::ceil(texture_padding));
+ int min_x = int(std::floor(std::min({ uvs[0].x(), uvs[1].x(), uvs[2].x() }) * texture_width)) - padding_px;
+ int max_x = int(std::ceil(std::max({ uvs[0].x(), uvs[1].x(), uvs[2].x() }) * texture_width)) + padding_px;
+ int min_y = int(std::floor(std::min({ uvs[0].y(), uvs[1].y(), uvs[2].y() }) * texture_height)) - padding_px;
+ int max_y = int(std::ceil(std::max({ uvs[0].y(), uvs[1].y(), uvs[2].y() }) * texture_height)) + padding_px;
+ const int cell_min_x = std::clamp(int(std::floor(float(cell_x) * tile)), 0, int(volume->imported_texture_width) - 1);
+ const int cell_max_x = std::clamp(int(std::ceil(float(cell_x + 1) * tile)) - 1, 0, int(volume->imported_texture_width) - 1);
+ const int cell_min_y = std::clamp(int(std::floor(float(cell_y) * tile)), 0, int(volume->imported_texture_height) - 1);
+ const int cell_max_y = std::clamp(int(std::ceil(float(cell_y + 1) * tile)) - 1, 0, int(volume->imported_texture_height) - 1);
+
+ min_x = std::clamp(min_x, cell_min_x, cell_max_x);
+ max_x = std::clamp(max_x, cell_min_x, cell_max_x);
+ min_y = std::clamp(min_y, cell_min_y, cell_max_y);
+ max_y = std::clamp(max_y, cell_min_y, cell_max_y);
+ for (int y_px = min_y; y_px <= max_y; ++y_px) {
+ for (int x_px = min_x; x_px <= max_x; ++x_px) {
+ Vec3f barycentric = Vec3f::Zero();
+ const Vec2f pixel(float(x_px) + 0.5f, float(y_px) + 0.5f);
+ if (!conservative_barycentric_weights_2d(pixel, pixel_uvs[0], pixel_uvs[1], pixel_uvs[2], texture_padding, barycentric))
+ continue;
+
+ const Vec3f point = vertices[0] * barycentric.x() +
+ vertices[1] * barycentric.y() +
+ vertices[2] * barycentric.z();
+ const ColorRGBA color = sample_managed_volume_color_source(*volume,
+ rgba_source,
+ region_source,
+ tri_idx,
+ point,
+ barycentric,
+ source_flags.use_rgba,
+ source_flags.use_image_texture,
+ source_flags.use_vertex_colors,
+ source_flags.use_color_regions,
+ fallback_color);
+ write_rgba_pixel(volume->imported_texture_rgba,
+ volume->imported_texture_width,
+ uint32_t(x_px),
+ uint32_t(y_px),
+ color);
+ }
+ }
+ }
+
+ refresh_imported_texture_storage(*volume);
+ changed = true;
+ }
+ return changed;
+}
+
+static bool convert_object_to_rgba_data(ModelObject &object, const ManagedColorDataCreateSource &source)
+{
+ if (object_has_rgba_data(object))
+ return false;
+
+ bool changed = false;
+ const ManagedColorSourceFlags source_flags = managed_color_source_flags(source);
+ const ColorRGBA fallback_color = blank_color_for_managed_target(ManagedColorDataType::RgbaData);
+ for (ModelVolume *volume : object.volumes) {
+ if (volume == nullptr || !volume->is_model_part())
+ continue;
+
+ const indexed_triangle_set &its = volume->mesh().its;
+ if (its.indices.empty() || its.vertices.empty())
+ continue;
+
+ std::unique_ptr rgb_data = ColorFacetsAnnotation::make_temporary();
+ if (!rgb_data)
+ continue;
+
+ const VolumeColorSource rgba_source = build_volume_color_source(*volume);
+ const ManagedRegionColorSource region_source = build_managed_region_color_source(*volume);
+ TextureMappingColorSampler sampler = [volume, rgba_source, region_source, fallback_color, source_flags](size_t tri_idx,
+ const Vec3f &point,
+ const Vec3f &barycentric) {
+ return pack_vertex_color_rgba(sample_managed_volume_color_source(*volume,
+ rgba_source,
+ region_source,
+ tri_idx,
+ point,
+ barycentric,
+ source_flags.use_rgba,
+ source_flags.use_image_texture,
+ source_flags.use_vertex_colors,
+ source_flags.use_color_regions,
+ fallback_color));
+ };
+
+ bool sampled = false;
+ if (source_flags.use_image_texture && model_volume_has_bakeable_image_texture_data(volume)) {
+ const int safe_max_depth = texture_mapping_depth_for_budget(its.indices.size(), 7, 3200000);
+ TextureMappingColorSubdivisionDepths subdivision_depths = [volume, safe_max_depth](size_t tri_idx,
+ const std::array &) {
+ const int depth = texture_mapping_depth_from_span(texture_triangle_uv_pixel_span(volume, tri_idx), 8.f, safe_max_depth);
+ return std::make_pair(depth, depth);
+ };
+ sampled = rgb_data->set_from_triangle_sampler(*volume, sampler, safe_max_depth, 0.015f, subdivision_depths);
+ } else if ((source_flags.use_vertex_colors && !volume->imported_vertex_colors_rgba.empty()) ||
+ (source_flags.use_color_regions && !volume->mmu_segmentation_facets.empty())) {
+ const float target_edge = std::max(mesh_max_axis_span(its) / 160.f, 0.25f);
+ TextureMappingColorSubdivisionDepths subdivision_depths = [target_edge](size_t, const std::array &vertices) {
+ const int depth = texture_mapping_depth_from_span(triangle_max_edge_length(vertices), target_edge, 5);
+ return std::make_pair(depth, depth);
+ };
+ sampled = rgb_data->set_from_triangle_sampler(*volume, sampler, 5, 0.025f, subdivision_depths);
+ } else {
+ sampled = build_volume_rgb_data(*volume, fallback_color, *rgb_data);
+ }
+
+ if (!sampled && rgb_data->empty())
+ continue;
+ if (rgb_data->metadata_json().empty())
+ rgb_data->set_metadata_json(rgb_metadata_json(fallback_color));
+ if (volume->texture_mapping_color_facets.equals(*rgb_data))
+ continue;
+ volume->texture_mapping_color_facets.assign(*rgb_data);
+ changed = true;
+ }
+
+ if (changed)
+ assign_object_to_texture_mapping_zone(object);
+ return changed;
+}
+
+static bool append_dialog_vertex_colors_for_volume(const ModelVolume &volume,
+ std::vector &input_colors,
+ const ManagedColorDataCreateSource &source)
+{
+ const indexed_triangle_set &its = volume.mesh().its;
+ if (its.vertices.empty())
+ return false;
+
+ const ManagedColorSourceFlags source_flags = managed_color_source_flags(source);
+ if (source_flags.use_vertex_colors && volume.imported_vertex_colors_rgba.size() == its.vertices.size()) {
+ input_colors.reserve(input_colors.size() + volume.imported_vertex_colors_rgba.size());
+ for (const uint32_t packed : volume.imported_vertex_colors_rgba) {
+ const ColorRGBA color = unpack_vertex_color_rgba_for_conversion(packed);
+ input_colors.emplace_back(RGBA{ color.r(), color.g(), color.b(), color.a() });
+ }
+ return true;
+ }
+
+ const ColorRGBA fallback_color = blank_color_for_managed_target(ManagedColorDataType::ColorRegions);
+ const VolumeColorSource rgba_source = build_volume_color_source(volume);
+ const ManagedRegionColorSource empty_region_source;
+ std::vector> accumulators(its.vertices.size(), { 0.f, 0.f, 0.f, 0.f });
+ std::vector counts(its.vertices.size(), 0);
+
+ for (size_t tri_idx = 0; tri_idx < its.indices.size(); ++tri_idx) {
+ const stl_triangle_vertex_indices &tri = its.indices[tri_idx];
+ for (int corner = 0; corner < 3; ++corner) {
+ if (tri[corner] < 0 || size_t(tri[corner]) >= its.vertices.size())
+ continue;
+ Vec3f barycentric = Vec3f::Zero();
+ barycentric[corner] = 1.f;
+ const ColorRGBA color = sample_managed_volume_color_source(volume,
+ rgba_source,
+ empty_region_source,
+ tri_idx,
+ its.vertices[size_t(tri[corner])].cast(),
+ barycentric,
+ source_flags.use_rgba,
+ source_flags.use_image_texture,
+ false,
+ false,
+ fallback_color);
+ std::array &accumulator = accumulators[size_t(tri[corner])];
+ accumulator[0] += color.r();
+ accumulator[1] += color.g();
+ accumulator[2] += color.b();
+ accumulator[3] += color.a();
+ ++counts[size_t(tri[corner])];
+ }
+ }
+
+ input_colors.reserve(input_colors.size() + its.vertices.size());
+ for (size_t idx = 0; idx < its.vertices.size(); ++idx) {
+ ColorRGBA color = fallback_color;
+ if (counts[idx] > 0) {
+ const float inv = 1.f / float(counts[idx]);
+ color = ColorRGBA(accumulators[idx][0] * inv,
+ accumulators[idx][1] * inv,
+ accumulators[idx][2] * inv,
+ accumulators[idx][3] * inv);
+ }
+ input_colors.emplace_back(RGBA{ color.r(), color.g(), color.b(), color.a() });
+ }
+ return true;
+}
+
+static std::string encode_managed_region_state_to_hex(unsigned int state)
+{
+ std::vector nibbles;
+ if (state < 3U) {
+ nibbles.emplace_back(int(state) << 2);
+ } else {
+ nibbles.emplace_back(0x0C);
+ unsigned int remainder = state - 3U;
+ while (remainder >= 15U) {
+ nibbles.emplace_back(0x0F);
+ remainder -= 15U;
+ }
+ nibbles.emplace_back(int(remainder));
+ }
+
+ std::string encoded;
+ encoded.reserve(nibbles.size());
+ for (auto it = nibbles.rbegin(); it != nibbles.rend(); ++it) {
+ const int nibble = *it;
+ encoded.push_back(char(nibble < 10 ? ('0' + nibble) : ('A' + (nibble - 10))));
+ }
+ return encoded;
+}
+
+static unsigned char normalized_region_filament_id(unsigned char filament_id, unsigned char first_extruder_id)
+{
+ if (filament_id == 0)
+ return first_extruder_id == 0 ? 1 : first_extruder_id;
+ return filament_id;
+}
+
+static bool set_volume_regions_from_vertex_filament_ids(ModelVolume &volume,
+ const std::vector &vertex_filament_ids,
+ size_t offset,
+ unsigned char first_extruder_id)
+{
+ const indexed_triangle_set &its = volume.mesh().its;
+ if (offset + its.vertices.size() > vertex_filament_ids.size())
+ return false;
+
+ first_extruder_id = first_extruder_id == 0 ? 1 : first_extruder_id;
+ volume.config.set("extruder", int(first_extruder_id));
+ volume.mmu_segmentation_facets.reset();
+ volume.mmu_segmentation_facets.reserve(int(its.indices.size()));
+
+ auto filament_id = [&vertex_filament_ids, offset, first_extruder_id](int vertex_idx) {
+ return normalized_region_filament_id(vertex_filament_ids[offset + size_t(vertex_idx)], first_extruder_id);
+ };
+ auto encoded = [](unsigned char id) {
+ return encode_managed_region_state_to_hex(unsigned(id));
+ };
+ auto safe_angle = [](const Vec3f &a, const Vec3f &b) {
+ if (a.squaredNorm() <= EPSILON || b.squaredNorm() <= EPSILON)
+ return 0.f;
+ return std::acos(std::clamp(a.normalized().dot(b.normalized()), -1.f, 1.f));
+ };
+
+ for (size_t tri_idx = 0; tri_idx < its.indices.size(); ++tri_idx) {
+ const stl_triangle_vertex_indices &tri = its.indices[tri_idx];
+ if (tri[0] < 0 || tri[1] < 0 || tri[2] < 0)
+ continue;
+ if (size_t(tri[0]) >= its.vertices.size() ||
+ size_t(tri[1]) >= its.vertices.size() ||
+ size_t(tri[2]) >= its.vertices.size())
+ continue;
+
+ const unsigned char id0 = filament_id(tri[0]);
+ const unsigned char id1 = filament_id(tri[1]);
+ const unsigned char id2 = filament_id(tri[2]);
+ if (id0 == first_extruder_id && id1 == first_extruder_id && id2 == first_extruder_id)
+ continue;
+
+ if (id0 == id1 && id1 == id2) {
+ volume.mmu_segmentation_facets.set_triangle_from_string(int(tri_idx), encoded(id0));
+ continue;
+ }
+
+ const std::string result0 = encoded(id0);
+ const std::string result1 = encoded(id1);
+ const std::string result2 = encoded(id2);
+ if (id0 != id1 && id1 != id2 && id0 != id2) {
+ const Vec3f v0 = its.vertices[size_t(tri[0])].cast();
+ const Vec3f v1 = its.vertices[size_t(tri[1])].cast();
+ const Vec3f v2 = its.vertices[size_t(tri[2])].cast();
+ const float angle0 = safe_angle(v1 - v0, v2 - v0);
+ const float angle1 = safe_angle(v0 - v1, v2 - v1);
+ const float angle2 = PI - angle0 - angle1;
+ std::array angles = { angle0, angle1, angle2 };
+ int max_angle_vertex_index = 0;
+ for (size_t idx = 1; idx < angles.size(); ++idx)
+ if (angles[idx] > angles[size_t(max_angle_vertex_index)])
+ max_angle_vertex_index = int(idx);
+
+ if (max_angle_vertex_index == 0)
+ volume.mmu_segmentation_facets.set_triangle_from_string(int(tri_idx),
+ result0 + result1 + result2 + (result1 + result2 + "5") + "3");
+ else if (max_angle_vertex_index == 1)
+ volume.mmu_segmentation_facets.set_triangle_from_string(int(tri_idx),
+ result0 + result1 + result2 + (result0 + result2 + "9") + "3");
+ else
+ volume.mmu_segmentation_facets.set_triangle_from_string(int(tri_idx),
+ result0 + result1 + result2 + (result1 + result0 + "1") + "3");
+ continue;
+ }
+
+ if (id0 == id1)
+ volume.mmu_segmentation_facets.set_triangle_from_string(int(tri_idx), result2 + result0 + result0 + "A");
+ else if (id1 == id2)
+ volume.mmu_segmentation_facets.set_triangle_from_string(int(tri_idx), result0 + result1 + result2 + "2");
+ else if (id0 == id2)
+ volume.mmu_segmentation_facets.set_triangle_from_string(int(tri_idx), result1 + result0 + result0 + "6");
+ }
+
+ return true;
+}
+
+static bool apply_dialog_vertex_filaments_to_color_regions(ModelObject &object,
+ const std::vector &vertex_filament_ids,
+ unsigned char first_extruder_id)
+{
+ size_t offset = 0;
+ bool changed = false;
+ first_extruder_id = first_extruder_id == 0 ? 1 : first_extruder_id;
+ object.config.set("extruder", int(first_extruder_id));
+ for (ModelVolume *volume : object.volumes) {
+ if (volume == nullptr || !volume->is_model_part())
+ continue;
+ const size_t vertex_count = volume->mesh().its.vertices.size();
+ if (!set_volume_regions_from_vertex_filament_ids(*volume, vertex_filament_ids, offset, first_extruder_id))
+ return false;
+ offset += vertex_count;
+ changed = true;
+ }
+ return changed && offset == vertex_filament_ids.size();
+}
+
+static bool convert_object_to_color_regions(ModelObject &object, const ManagedColorDataCreateSource &source, wxWindow *parent)
+{
+ if (object_has_color_regions(object))
+ return false;
+
+ std::vector input_colors;
+ for (const ModelVolume *volume : object.volumes) {
+ if (volume == nullptr || !volume->is_model_part())
+ continue;
+ append_dialog_vertex_colors_for_volume(*volume, input_colors, source);
+ }
+ if (input_colors.empty())
+ return false;
+
+ bool is_single_color = true;
+ const RGBA first_color = input_colors.front();
+ for (const RGBA &color : input_colors) {
+ if (color != first_color) {
+ is_single_color = false;
+ break;
+ }
+ }
+
+ std::vector filament_ids;
+ unsigned char first_extruder_id = 1;
+ const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config();
+ ObjColorDialog color_dlg(parent, input_colors, is_single_color, extruder_colours, filament_ids, first_extruder_id);
+ if (color_dlg.ShowModal() != wxID_OK || filament_ids.size() != input_colors.size())
+ return false;
+
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Create 3mf color regions", UndoRedo::SnapshotType::GizmoAction);
+ return apply_dialog_vertex_filaments_to_color_regions(object, filament_ids, first_extruder_id);
+}
+
+static bool convert_object_managed_color_data(ModelObject &object,
+ ManagedColorDataType type,
+ const ManagedColorDataCreateSource &source,
+ wxWindow *parent = nullptr)
+{
+ switch (type) {
+ case ManagedColorDataType::ColorRegions:
+ return convert_object_to_color_regions(object, source, parent);
+ case ManagedColorDataType::VertexColors:
+ return convert_object_to_vertex_colors(object, source);
+ case ManagedColorDataType::ImageTexture:
+ return convert_object_to_image_texture(object, source);
+ case ManagedColorDataType::RgbaData:
+ return convert_object_to_rgba_data(object, source);
+ }
+ return false;
+}
+
+static void refresh_managed_color_data_object(GLCanvas3D &parent, ModelObject *object)
+{
+ if (object == nullptr)
+ return;
+
+ parent.update_volumes_colors_by_extruder();
+ parent.set_as_dirty();
+ parent.request_extra_frame();
+
+ const ModelObjectPtrs &objects = wxGetApp().model().objects;
+ const size_t object_idx = size_t(std::find(objects.begin(), objects.end(), object) - objects.begin());
+ if (object_idx < objects.size()) {
+ wxGetApp().obj_list()->update_info_items(object_idx);
+ wxGetApp().plater()->get_partplate_list().notify_instance_update(object_idx, 0);
+ }
+ parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
+}
+
+class ColorDataManagementDialog : public wxDialog
+{
+public:
+ ColorDataManagementDialog(wxWindow *parent, GLCanvas3D &canvas, ModelObject *object, std::function on_object_changed = {})
+ : wxDialog(parent,
+ wxID_ANY,
+ _L("Manage Color Data for this object"),
+ wxDefaultPosition,
+ wxDefaultSize,
+ wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
+ , m_canvas(canvas)
+ , m_object(object)
+ , m_on_object_changed(std::move(on_object_changed))
+ {
+ wxBoxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
+ wxFlexGridSizer *grid = new wxFlexGridSizer(5, 8, 14);
+ grid->AddGrowableCol(2, 1);
+
+ grid->Add(new wxStaticText(this, wxID_ANY, _L("Type")), 0, wxALIGN_CENTER_VERTICAL);
+ grid->Add(new wxStaticText(this, wxID_ANY, _L("Status")), 0, wxALIGN_CENTER_VERTICAL);
+ grid->Add(new wxStaticText(this, wxID_ANY, _L("Size")), 0, wxALIGN_CENTER_VERTICAL);
+ grid->AddSpacer(1);
+ grid->AddSpacer(1);
+
+ add_row(grid, ManagedColorDataType::ColorRegions, managed_color_data_type_label(ManagedColorDataType::ColorRegions));
+ add_row(grid, ManagedColorDataType::VertexColors, managed_color_data_type_label(ManagedColorDataType::VertexColors));
+ add_row(grid, ManagedColorDataType::ImageTexture, managed_color_data_type_label(ManagedColorDataType::ImageTexture));
+ add_row(grid, ManagedColorDataType::RgbaData, managed_color_data_type_label(ManagedColorDataType::RgbaData));
+
+ main_sizer->Add(grid, 1, wxEXPAND | wxALL, 16);
+ main_sizer->Add(new wxStaticLine(this), 0, wxEXPAND | wxLEFT | wxRIGHT, 16);
+
+ wxStdDialogButtonSizer *buttons = new wxStdDialogButtonSizer();
+ wxButton *close_button = new wxButton(this, wxID_CLOSE, _L("Close"));
+ buttons->AddButton(close_button);
+ buttons->Realize();
+ main_sizer->Add(buttons, 0, wxEXPAND | wxALL, 16);
+
+ SetSizer(main_sizer);
+ refresh_rows();
+ Fit();
+ SetMinSize(GetSize());
+ CenterOnParent();
+
+ Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { EndModal(wxID_CLOSE); }, wxID_CLOSE);
+ }
+
+private:
+ struct Row
+ {
+ ManagedColorDataType type;
+ wxStaticText *status = nullptr;
+ wxStaticText *size = nullptr;
+ wxButton *clear = nullptr;
+ wxButton *create = nullptr;
+ };
+
+ void add_row(wxFlexGridSizer *grid, ManagedColorDataType type, const wxString &label)
+ {
+ wxStaticText *status = new wxStaticText(this, wxID_ANY, wxString());
+ wxStaticText *size = new wxStaticText(this, wxID_ANY, wxString());
+ wxButton *clear = new wxButton(this, wxID_ANY, _L("Clear"));
+ wxButton *create = new wxButton(this, wxID_ANY, _L("Create From..."));
+
+ grid->Add(new wxStaticText(this, wxID_ANY, label), 0, wxALIGN_CENTER_VERTICAL);
+ grid->Add(status, 0, wxALIGN_CENTER_VERTICAL);
+ grid->Add(size, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
+ grid->Add(clear, 0, wxALIGN_CENTER_VERTICAL);
+ grid->Add(create, 0, wxALIGN_CENTER_VERTICAL);
+
+ clear->Bind(wxEVT_BUTTON, [this, type](wxCommandEvent &) { clear_data(type); });
+ create->Bind(wxEVT_BUTTON, [this, type, create](wxCommandEvent &) { show_create_menu(type, create); });
+ m_rows.push_back({ type, status, size, clear, create });
+ }
+
+ void refresh_rows()
+ {
+ const ManagedColorDataSummary summary = summarize_managed_color_data(m_object);
+ for (Row &row : m_rows) {
+ const bool has_data = managed_color_data_summary_has_type(summary, row.type);
+ row.status->SetLabel(has_data ? _L("Present") : _L("None"));
+ row.size->SetLabel(managed_color_data_size_text(summary, row.type));
+ row.clear->Enable(has_data);
+ row.create->Enable(m_object != nullptr && !has_data);
+ }
+ Layout();
+ Fit();
+ }
+
+ void clear_data(ManagedColorDataType type)
+ {
+ if (m_object == nullptr || !object_has_managed_color_data(*m_object, type))
+ return;
+
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), clear_snapshot_name(type), UndoRedo::SnapshotType::GizmoAction);
+ if (!clear_object_managed_color_data(*m_object, type))
+ return;
+
+ refresh_managed_color_data_object(m_canvas, m_object);
+ notify_object_changed();
+ refresh_rows();
+ }
+
+ void show_create_menu(ManagedColorDataType type, wxButton *button)
+ {
+ if (m_object == nullptr || object_has_managed_color_data(*m_object, type))
+ return;
+
+ wxMenu menu;
+ std::vector> sources;
+ const ManagedColorDataSummary summary = summarize_managed_color_data(m_object);
+ auto add_item = [&menu, &sources](const wxString &label, const ManagedColorDataCreateSource &source) {
+ const int id = wxWindow::NewControlId();
+ menu.Append(id, label);
+ sources.emplace_back(id, source);
+ };
+
+ const std::array types = {
+ ManagedColorDataType::ColorRegions,
+ ManagedColorDataType::VertexColors,
+ ManagedColorDataType::ImageTexture,
+ ManagedColorDataType::RgbaData
+ };
+ bool added_data_source = false;
+ for (const ManagedColorDataType source_type : types) {
+ if (source_type == type || !managed_color_data_summary_has_type(summary, source_type))
+ continue;
+ add_item(managed_color_data_type_label(source_type),
+ ManagedColorDataCreateSource{ std::optional(source_type) });
+ added_data_source = true;
+ }
+ if (added_data_source)
+ menu.AppendSeparator();
+ add_item(_L("Blank Canvas"), ManagedColorDataCreateSource{});
+
+ menu.Bind(wxEVT_COMMAND_MENU_SELECTED, [this, type, sources](wxCommandEvent &event) {
+ for (const auto &source : sources) {
+ if (source.first == event.GetId()) {
+ create_data(type, source.second);
+ break;
+ }
+ }
+ });
+ button->PopupMenu(&menu, wxPoint(0, button->GetSize().GetHeight()));
+ }
+
+ void create_data(ManagedColorDataType type, const ManagedColorDataCreateSource &source)
+ {
+ if (m_object == nullptr || object_has_managed_color_data(*m_object, type) || (source.type && *source.type == type))
+ return;
+
+ if (type == ManagedColorDataType::ColorRegions) {
+ if (!convert_object_managed_color_data(*m_object, type, source, this))
+ return;
+
+ refresh_managed_color_data_object(m_canvas, m_object);
+ notify_object_changed();
+ refresh_rows();
+ return;
+ }
+
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), create_snapshot_name(type), UndoRedo::SnapshotType::GizmoAction);
+ if (!convert_object_managed_color_data(*m_object, type, source))
+ return;
+
+ refresh_managed_color_data_object(m_canvas, m_object);
+ notify_object_changed();
+ refresh_rows();
+ }
+
+ void notify_object_changed()
+ {
+ if (m_on_object_changed)
+ m_on_object_changed();
+ }
+
+ const char *clear_snapshot_name(ManagedColorDataType type) const
+ {
+ switch (type) {
+ case ManagedColorDataType::ColorRegions:
+ return "Clear 3mf color regions";
+ case ManagedColorDataType::VertexColors:
+ return "Clear vertex colors";
+ case ManagedColorDataType::ImageTexture:
+ return "Clear image texture data";
+ case ManagedColorDataType::RgbaData:
+ return "Clear RGBA data";
+ }
+ return "Clear color data";
+ }
+
+ const char *create_snapshot_name(ManagedColorDataType type) const
+ {
+ switch (type) {
+ case ManagedColorDataType::ColorRegions:
+ return "Create 3mf color regions";
+ case ManagedColorDataType::VertexColors:
+ return "Create vertex colors";
+ case ManagedColorDataType::ImageTexture:
+ return "Create image texture";
+ case ManagedColorDataType::RgbaData:
+ return "Create RGBA data";
+ }
+ return "Create color data";
+ }
+
+ GLCanvas3D &m_canvas;
+ ModelObject *m_object = nullptr;
+ std::function m_on_object_changed;
+ std::vector m_rows;
+};
+
void GLGizmoMmuSegmentation::init_extruders_data(const std::vector &extruder_colors)
{
const unsigned int old_selected_filament_id =
@@ -2083,67 +3648,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
}
m_imgui->disabled_end();
- const bool can_bake_image_texture_data = selected_object_has_bakeable_image_texture_data();
- m_imgui->disabled_begin(!can_bake_image_texture_data);
- if (m_imgui->button(_L("Bake image texture to vertex colors")))
- bake_selected_object_image_texture_to_vertex_colors();
- if (ImGui::IsItemHovered()) {
- if (can_bake_image_texture_data)
- m_imgui->tooltip(_L("Sample imported image texture UVs into stored vertex colors, then discard the baked image texture data."),
- max_tooltip_width);
- else
- m_imgui->tooltip(_L("This object does not have imported image texture data with UVs."), max_tooltip_width);
- }
- m_imgui->disabled_end();
-
- const bool can_convert_vertex_colors_to_texture_mapping_colors = selected_object_has_imported_vertex_colors();
- m_imgui->disabled_begin(!can_convert_vertex_colors_to_texture_mapping_colors);
- if (m_imgui->button(_L("Convert vertex colors to RGB data")))
- convert_selected_object_vertex_colors_to_texture_mapping_colors();
- if (ImGui::IsItemHovered()) {
- if (can_convert_vertex_colors_to_texture_mapping_colors)
- m_imgui->tooltip(_L("Convert imported vertex colors into RGB data for texture mapping zones."), max_tooltip_width);
- else
- m_imgui->tooltip(_L("This object does not have stored imported vertex colors."), max_tooltip_width);
- }
- m_imgui->disabled_end();
-
- const bool can_convert_image_texture_to_texture_mapping_colors = selected_object_has_bakeable_image_texture_data();
- m_imgui->disabled_begin(!can_convert_image_texture_to_texture_mapping_colors);
- if (m_imgui->button(_L("Convert image texture to RGB data")))
- convert_selected_object_image_texture_to_texture_mapping_colors();
- if (ImGui::IsItemHovered()) {
- if (can_convert_image_texture_to_texture_mapping_colors)
- m_imgui->tooltip(_L("Sample imported image texture UVs into RGB data for texture mapping zones."), max_tooltip_width);
- else
- m_imgui->tooltip(_L("This object does not have imported image texture data with UVs."), max_tooltip_width);
- }
- m_imgui->disabled_end();
-
- const bool can_clear_image_texture_data = selected_object_has_imported_texture_data();
- m_imgui->disabled_begin(!can_clear_image_texture_data);
- if (m_imgui->button(_L("Clear Image Texture Data")))
- clear_selected_object_image_texture_data();
- if (ImGui::IsItemHovered()) {
- if (can_clear_image_texture_data)
- m_imgui->tooltip(_L("Discard imported image texture data from the selected object."), max_tooltip_width);
- else
- m_imgui->tooltip(_L("This object does not have imported image texture data."), max_tooltip_width);
- }
- m_imgui->disabled_end();
-
- const bool can_clear_texture_mapping_color_data = selected_object_has_texture_mapping_color_data();
- m_imgui->disabled_begin(!can_clear_texture_mapping_color_data);
- if (m_imgui->button(_L("Clear RGB Data")))
- clear_selected_object_texture_mapping_color_data();
- if (ImGui::IsItemHovered()) {
- if (can_clear_texture_mapping_color_data)
- m_imgui->tooltip(_L("Discard RGB data from the selected object."), max_tooltip_width);
- else
- m_imgui->tooltip(_L("This object does not have RGB data."), max_tooltip_width);
- }
- m_imgui->disabled_end();
-
ImGui::Separator();
const bool can_apply_stored_vertex_colors = selected_object_has_imported_vertex_colors();
@@ -3059,6 +4563,8 @@ void GLGizmoTrueColorPainting::on_opening()
void GLGizmoTrueColorPainting::on_shutdown()
{
m_color_picker_active = false;
+ clear_brush_stroke_points();
+ m_preview_rgb_data_by_volume.clear();
m_color_picker_source_cache.clear();
m_parent.use_slope(false);
m_parent.toggle_model_objects_visibility(true);
@@ -3161,13 +4667,28 @@ bool GLGizmoTrueColorPainting::gizmo_event(SLAGizmoEventType action,
return true;
}
- return GLGizmoPainterBase::gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
+ if (action == SLAGizmoEventType::LeftDown) {
+ clear_brush_stroke_points();
+ m_brush_stroke_active = !shift_down && !control_down && record_brush_stroke_point(mouse_position);
+ } else if (action == SLAGizmoEventType::Dragging && m_brush_stroke_active && !shift_down && !control_down) {
+ record_brush_stroke_point(mouse_position);
+ } else if (action == SLAGizmoEventType::RightDown || (action == SLAGizmoEventType::Dragging && shift_down)) {
+ clear_brush_stroke_points();
+ }
+
+ const bool handled = GLGizmoPainterBase::gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
+ if (action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp) {
+ clear_brush_stroke_points();
+ m_brush_stroke_active = false;
+ }
+ return handled;
}
void GLGizmoTrueColorPainting::init_model_triangle_selectors()
{
const ModelObject *object = selected_model_object();
m_triangle_selectors.clear();
+ m_preview_rgb_data_by_volume.clear();
if (object == nullptr)
return;
@@ -3179,10 +4700,22 @@ void GLGizmoTrueColorPainting::init_model_triangle_selectors()
if (volume == nullptr || !volume->is_model_part())
continue;
+ const ColorFacetsAnnotation *preview_rgb_data = nullptr;
+ m_preview_rgb_data_by_volume.emplace_back();
+ if (volume->texture_mapping_color_facets.empty()) {
+ std::unique_ptr preview = ColorFacetsAnnotation::make_temporary();
+ if (preview &&
+ build_volume_rgb_data_from_current_surface_color(*volume, ColorRGBA(1.f, 1.f, 1.f, 1.f), *preview)) {
+ preview_rgb_data = preview.get();
+ m_preview_rgb_data_by_volume.back() = std::move(preview);
+ }
+ }
+
m_triangle_selectors.emplace_back(std::make_unique(volume->mesh(), volume, colors, 0.2f));
if (TriangleSelectorPatch *patch = dynamic_cast(m_triangle_selectors.back().get())) {
patch->set_none_state_rendered(false);
- patch->set_texture_preview_needed(true);
+ patch->set_texture_mapping_color_preview(preview_rgb_data);
+ patch->set_texture_preview_needed(!volume->texture_mapping_color_facets.empty() || preview_rgb_data != nullptr);
patch->set_texture_preview_opaque(true);
}
m_triangle_selectors.back()->set_wireframe_needed(true);
@@ -3190,6 +4723,52 @@ void GLGizmoTrueColorPainting::init_model_triangle_selectors()
}
}
+bool GLGizmoTrueColorPainting::record_brush_stroke_point(const Vec2d &mouse_position)
+{
+ int mesh_id = -1;
+ Vec3f hit = Vec3f::Zero();
+ size_t facet = 0;
+ if (!raycast_to_selected_mesh(mouse_position, mesh_id, hit, facet) || mesh_id < 0)
+ return false;
+
+ if (m_brush_stroke_points_by_volume.size() <= size_t(mesh_id))
+ m_brush_stroke_points_by_volume.resize(size_t(mesh_id) + 1);
+
+ const ModelObject *object = selected_model_object();
+ const ModelVolume *hit_volume = nullptr;
+ if (object != nullptr) {
+ int model_part_idx = -1;
+ for (const ModelVolume *volume : object->volumes) {
+ if (volume == nullptr || !volume->is_model_part())
+ continue;
+ ++model_part_idx;
+ if (model_part_idx == mesh_id) {
+ hit_volume = volume;
+ break;
+ }
+ }
+ }
+
+ Transform3d world_matrix = Transform3d::Identity();
+ if (object != nullptr && hit_volume != nullptr) {
+ const Selection &selection = m_parent.get_selection();
+ world_matrix = projection_world_matrix_for_volume(m_parent, object, hit_volume, selection.get_instance_idx());
+ }
+
+ std::vector &points = m_brush_stroke_points_by_volume[size_t(mesh_id)];
+ const float min_spacing = true_color_brush_subdivision_target(m_cursor_radius);
+ if (points.empty() ||
+ (transform_point(world_matrix, points.back()) - transform_point(world_matrix, hit)).norm() >= min_spacing)
+ points.emplace_back(hit);
+ return true;
+}
+
+void GLGizmoTrueColorPainting::clear_brush_stroke_points()
+{
+ m_brush_stroke_points_by_volume.clear();
+ m_brush_stroke_active = false;
+}
+
void GLGizmoTrueColorPainting::update_triangle_selectors_color()
{
const std::vector colors = {
@@ -3236,16 +4815,38 @@ void GLGizmoTrueColorPainting::update_model_object()
if (triangles_per_type.size() <= paint_state || triangles_per_type[paint_state].empty())
continue;
- if (volume->texture_mapping_color_facets.empty())
- initialize_volume_rgb_data_from_current_surface_color(*volume, ColorRGBA(1.f, 1.f, 1.f, 1.f));
+ bool initialized_rgb_data = false;
+ if (volume->texture_mapping_color_facets.empty()) {
+ bool initialized = false;
+ if (selector_idx < int(m_preview_rgb_data_by_volume.size()) &&
+ m_preview_rgb_data_by_volume[size_t(selector_idx)] != nullptr &&
+ !m_preview_rgb_data_by_volume[size_t(selector_idx)]->empty()) {
+ volume->texture_mapping_color_facets.assign(*m_preview_rgb_data_by_volume[size_t(selector_idx)]);
+ initialized = true;
+ initialized_rgb_data = true;
+ }
+ if (!initialized)
+ initialized_rgb_data = initialize_volume_rgb_data_from_current_surface_color(*volume, ColorRGBA(1.f, 1.f, 1.f, 1.f));
+ }
const ColorRGBA brush_color(m_rgb_color[0], m_rgb_color[1], m_rgb_color[2], 1.f);
- updated |= apply_rgb_stroke_to_volume(*volume,
- triangles_per_type[paint_state],
- brush_color,
- m_brush_hardness,
- m_opacity,
- m_cursor_radius);
+ const std::vector empty_brush_stroke_points;
+ const std::vector &brush_stroke_points =
+ selector_idx < int(m_brush_stroke_points_by_volume.size()) ?
+ m_brush_stroke_points_by_volume[size_t(selector_idx)] :
+ empty_brush_stroke_points;
+ const Selection &selection = m_parent.get_selection();
+ const Transform3d world_matrix =
+ projection_world_matrix_for_volume(m_parent, object, volume, selection.get_instance_idx());
+ const bool stroke_changed = apply_rgb_stroke_to_volume(*volume,
+ triangles_per_type[paint_state],
+ brush_color,
+ m_brush_hardness,
+ m_opacity,
+ m_cursor_radius,
+ brush_stroke_points,
+ world_matrix);
+ updated |= initialized_rgb_data || stroke_changed;
m_triangle_selectors[size_t(selector_idx)]->reset();
m_triangle_selectors[size_t(selector_idx)]->request_update_render_data(true);
}
@@ -3272,6 +4873,25 @@ ModelObject *GLGizmoTrueColorPainting::selected_model_object() const
return selection_info != nullptr ? selection_info->model_object() : nullptr;
}
+void GLGizmoTrueColorPainting::open_color_data_management_dialog()
+{
+ ModelObject *object = selected_model_object();
+ if (object == nullptr)
+ return;
+
+ ColorDataManagementDialog dialog(wxGetApp().mainframe, m_parent, object, [this]() {
+ update_selected_object_color_state();
+ init_model_triangle_selectors();
+ m_parent.set_as_dirty();
+ m_parent.request_extra_frame();
+ });
+ dialog.ShowModal();
+ update_selected_object_color_state();
+ init_model_triangle_selectors();
+ m_parent.set_as_dirty();
+ m_parent.request_extra_frame();
+}
+
void GLGizmoTrueColorPainting::update_selected_object_color_state()
{
m_selected_has_rgb_data = false;
@@ -4060,6 +5680,9 @@ void GLGizmoTrueColorPainting::on_render_input_window(float x, float y, float bo
m_parent.set_as_dirty();
}
+ if (m_imgui->button(_L("Manage Color Data for this object")))
+ open_color_data_management_dialog();
+
bool color_changed = false;
switch (m_color_input_mode) {
case ColorInputMode::FilamentColors:
@@ -4277,6 +5900,25 @@ ModelObject *GLGizmoImageProjection::selected_model_object() const
return selection_info != nullptr ? selection_info->model_object() : nullptr;
}
+void GLGizmoImageProjection::open_color_data_management_dialog()
+{
+ ModelObject *object = selected_model_object();
+ if (object == nullptr)
+ return;
+
+ ColorDataManagementDialog dialog(wxGetApp().mainframe, m_parent, object, [this]() {
+ m_projection_mode_initialized = false;
+ update_default_projection_mode();
+ m_parent.set_as_dirty();
+ m_parent.request_extra_frame();
+ });
+ dialog.ShowModal();
+ m_projection_mode_initialized = false;
+ update_default_projection_mode();
+ m_parent.set_as_dirty();
+ m_parent.request_extra_frame();
+}
+
void GLGizmoImageProjection::update_default_projection_mode()
{
const ModelObject *object = selected_model_object();
@@ -4394,6 +6036,9 @@ void GLGizmoImageProjection::on_render_input_window(float x, float y, float bott
m_imgui->text(from_u8(slash == std::string::npos ? m_image_path : m_image_path.substr(slash + 1)));
}
+ if (m_imgui->button(_L("Manage Color Data for this object")))
+ open_color_data_management_dialog();
+
m_imgui->text(_L("Apply to:"));
ImGui::SameLine();
const char *mode_labels[] = { "Vertex colors", "Image Texture", "RGB data" };
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp
index 4c61b968fb4..bc6dc7056d5 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp
@@ -5,6 +5,7 @@
#include
#include
+#include
#include
#include
#include
@@ -63,7 +64,6 @@ public:
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
// Zero if the VBOs are not sent to GPU yet.
- unsigned int vertices_VAO_id{ 0 };
unsigned int vertices_VBO_id{0};
std::vector triangle_indices_VBO_ids;
};
@@ -78,11 +78,8 @@ public:
void data_changed(bool is_serializing) 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
- // will be also extended to support additional states, requiring at least one state to remain free out of 19 states.
- static const constexpr size_t EXTRUDERS_LIMIT = 16;
+ // Keep this in sync with the shared triangle-selector state range.
+ static const constexpr size_t EXTRUDERS_LIMIT = static_cast(EnforcerBlockerType::ExtruderMax);
const float get_cursor_radius_min() const override { return CursorRadiusMin; }
@@ -95,12 +92,17 @@ protected:
ColorRGBA get_cursor_hover_color() const override;
void on_set_state() override;
- EnforcerBlockerType get_left_button_state_type() const override { return EnforcerBlockerType(m_selected_extruder_idx + 1); }
+ EnforcerBlockerType get_left_button_state_type() const override
+ {
+ if (m_selected_extruder_idx < m_display_filament_ids.size())
+ return EnforcerBlockerType(m_display_filament_ids[m_selected_extruder_idx]);
+ return EnforcerBlockerType::Extruder1;
+ }
EnforcerBlockerType get_right_button_state_type() const override { return EnforcerBlockerType(-1); }
void on_render_input_window(float x, float y, float bottom_limit) override;
std::string on_get_name() const override;
- void render_tooltip_button(float x, float y);
+ void show_tooltip_information(float caption_max, float x, float y);
bool on_is_selectable() const override;
bool on_is_activable() const override;
@@ -165,13 +167,6 @@ private:
// 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;
-
- // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")}
- std::vector> m_shortcuts_brush;
- // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")}
- std::vector> m_shortcuts_bucket_fill;
- // Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")}
- std::vector> m_shortcuts_gap_fill;
};
class GLGizmoTrueColorPainting : public GLGizmoPainterBase
@@ -225,6 +220,7 @@ private:
void init_model_triangle_selectors();
ModelObject *selected_model_object() const;
void update_selected_object_color_state();
+ void open_color_data_management_dialog();
bool selected_object_has_rgb_data() const;
bool selected_object_has_imported_color_data() const;
void initialize_selected_object_rgb_data();
@@ -232,6 +228,8 @@ private:
void convert_selected_object_image_texture_to_rgb_data();
void refresh_selected_object_after_rgb_change(ModelObject *object);
void update_triangle_selectors_color();
+ bool record_brush_stroke_point(const Vec2d &mouse_position);
+ void clear_brush_stroke_points();
bool pick_color_from_model(const Vec2d &mouse_position);
bool sample_color_from_model(const Vec2d &mouse_position, ColorRGBA &color) const;
void set_active_color_from_sample(const ColorRGBA &color);
@@ -277,7 +275,10 @@ private:
bool m_selected_can_convert_vertex = false;
bool m_selected_can_convert_image = false;
bool m_color_picker_active = false;
+ bool m_brush_stroke_active = false;
ObjectID m_selected_color_state_object_id;
+ std::vector> m_brush_stroke_points_by_volume;
+ std::vector> m_preview_rgb_data_by_volume;
struct ColorPickerVolumeSourceCache
{
ObjectID volume_id;
@@ -328,6 +329,7 @@ private:
bool ensure_overlay_texture();
OverlayRect overlay_rect() const;
ModelObject *selected_model_object() const;
+ void open_color_data_management_dialog();
void update_default_projection_mode();
ProjectionMode default_projection_mode() const;
bool projection_mode_allowed(ProjectionMode mode) const;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
index 08539c8b950..de79f8240be 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
@@ -1685,7 +1685,18 @@ void TriangleSelectorPatch::update_render_data()
const TextureMappingManager *texture_mgr = wxGetApp().preset_bundle != nullptr ?
&wxGetApp().preset_bundle->texture_mapping_zones : nullptr;
m_texture_preview_visual_signature = texture_preview_settings_signature(num_physical, texture_mgr);
- if (model_volume_has_texture_preview_data_for_painting(*m_model_volume)) {
+ if (m_texture_mapping_color_preview != nullptr && !m_texture_mapping_color_preview->empty()) {
+ build_mmu_vertex_color_preview_models(*m_model_volume,
+ triangles_per_type,
+ m_ebt_colors,
+ m_model_volume->extruder_id() > 0 ? unsigned(m_model_volume->extruder_id()) : 0u,
+ num_physical,
+ texture_mgr,
+ m_vertex_color_preview_models,
+ m_vertex_color_preview_colors,
+ m_vertex_color_preview_filament_ids,
+ m_texture_mapping_color_preview);
+ } else if (model_volume_has_texture_preview_data_for_painting(*m_model_volume)) {
build_mmu_texture_preview_models(*m_model_volume,
triangles_per_type,
m_ebt_colors,
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp
index fb98c85d184..e9e77a8d31b 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp
@@ -132,6 +132,7 @@ public:
void set_none_state_rendered(bool rendered) { m_render_none_state = rendered; }
void set_texture_preview_needed(bool needed) { m_texture_preview_needed = needed; }
void set_texture_preview_opaque(bool opaque) { m_texture_preview_opaque = opaque; }
+ void set_texture_mapping_color_preview(const ColorFacetsAnnotation *preview) { m_texture_mapping_color_preview = preview; }
constexpr static float GapAreaMin = 0.f;
constexpr static float GapAreaMax = 5.f;
@@ -196,6 +197,7 @@ protected:
mutable std::vector m_vertex_color_preview_models;
std::vector m_vertex_color_preview_colors;
std::vector m_vertex_color_preview_filament_ids;
+ const ColorFacetsAnnotation *m_texture_mapping_color_preview { nullptr };
bool m_filter_state = false;
bool m_render_none_state = true;
diff --git a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp
index ddacaf3506c..2e46e7799e2 100644
--- a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp
+++ b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp
@@ -1387,13 +1387,17 @@ bool build_texture_mapping_color_preview_model_for_state(
const ModelVolume &model_volume,
const std::vector &state_triangles,
const TexturePreviewSimulationSettings *simulation_settings,
- GUI::GLModel &out_model)
+ GUI::GLModel &out_model,
+ const ColorFacetsAnnotation *texture_mapping_color_facets_override = nullptr)
{
- if (!model_volume_has_texture_mapping_color_preview_data(model_volume) || state_triangles.empty())
+ const ColorFacetsAnnotation *color_source = texture_mapping_color_facets_override;
+ if (color_source == nullptr || color_source->empty())
+ color_source = &model_volume.texture_mapping_color_facets;
+ if (color_source == nullptr || color_source->empty() || state_triangles.empty())
return false;
std::vector color_facets;
- model_volume.texture_mapping_color_facets.get_facet_triangles(model_volume, color_facets);
+ color_source->get_facet_triangles(model_volume, color_facets);
if (color_facets.empty())
return false;
@@ -2053,18 +2057,44 @@ bool build_mmu_vertex_color_preview_models(
const Transform3d &world_matrix,
std::vector &out_models,
std::vector &out_colors,
- std::vector &out_filament_ids)
+ std::vector &out_filament_ids,
+ const ColorFacetsAnnotation *texture_mapping_color_facets_override)
{
out_models.clear();
out_colors.clear();
out_filament_ids.clear();
+ const bool has_texture_mapping_color_override =
+ texture_mapping_color_facets_override != nullptr && !texture_mapping_color_facets_override->empty();
+ if (triangles_per_type.empty() || (texture_mgr == nullptr && !has_texture_mapping_color_override))
+ return false;
+
const std::vector physical_colors = physical_filament_colors_for_texture_preview(num_physical);
bool built_any = false;
for (size_t state_id = 0; state_id < triangles_per_type.size(); ++state_id) {
const unsigned int filament_id = filament_id_for_state(state_id, base_filament_id);
const TextureMappingZone *zone = zone_for_filament(filament_id, num_physical, texture_mgr);
- if (zone == nullptr || (!is_image_zone(*zone) && !is_gradient_zone(*zone)))
+ if (zone == nullptr) {
+ if (!has_texture_mapping_color_override || state_id != 0)
+ continue;
+
+ GUI::GLModel model;
+ if (!build_texture_mapping_color_preview_model_for_state(model_volume,
+ triangles_per_type[state_id],
+ nullptr,
+ model,
+ texture_mapping_color_facets_override) ||
+ !model.is_initialized())
+ continue;
+
+ out_models.emplace_back(std::move(model));
+ out_colors.emplace_back(state_id < state_colors.size() ? state_colors[state_id] :
+ (state_colors.empty() ? ColorRGBA(0.15f, 0.65f, 0.6f, 1.f) : state_colors.back()));
+ out_filament_ids.emplace_back(0u);
+ built_any = true;
+ continue;
+ }
+ if (!is_image_zone(*zone) && !is_gradient_zone(*zone))
continue;
GUI::GLModel model;
@@ -2079,11 +2109,16 @@ bool build_mmu_vertex_color_preview_models(
texture_preview_simulation_settings_for_filament(filament_id, num_physical, texture_mgr, physical_colors);
if (simulation_settings)
prepare_texture_preview_simulation_settings(*simulation_settings);
- if (model_volume_has_texture_mapping_color_preview_data(model_volume)) {
+ const bool has_texture_mapping_color_preview =
+ has_texture_mapping_color_override || model_volume_has_texture_mapping_color_preview_data(model_volume);
+ if (has_texture_mapping_color_preview) {
+ const ColorFacetsAnnotation *preview_override =
+ has_texture_mapping_color_override ? texture_mapping_color_facets_override : nullptr;
if (!build_texture_mapping_color_preview_model_for_state(model_volume,
triangles_per_type[state_id],
simulation_settings ? &*simulation_settings : nullptr,
- model))
+ model,
+ preview_override))
continue;
} else {
if (!build_vertex_color_preview_model_for_state(model_volume,
@@ -2112,7 +2147,8 @@ bool build_mmu_vertex_color_preview_models(
const TextureMappingManager *texture_mgr,
std::vector &out_models,
std::vector &out_colors,
- std::vector &out_filament_ids)
+ std::vector &out_filament_ids,
+ const ColorFacetsAnnotation *texture_mapping_color_facets_override)
{
return build_mmu_vertex_color_preview_models(model_volume,
triangles_per_type,
@@ -2123,7 +2159,8 @@ bool build_mmu_vertex_color_preview_models(
Transform3d::Identity(),
out_models,
out_colors,
- out_filament_ids);
+ out_filament_ids,
+ texture_mapping_color_facets_override);
}
size_t model_volume_texture_preview_signature(const ModelVolume &model_volume)
@@ -2285,8 +2322,13 @@ void render_model_texture_preview_models(
const size_t texture_signature = model_volume_texture_preview_signature(model_volume);
GLuint bound_texture_id = 0;
for (size_t idx = 0; idx < models.size(); ++idx) {
- const float mix = texture_preview_mix_for_filament(filament_ids[idx], num_physical, texture_mgr);
- const bool invalid = texture_preview_settings_invalid_for_filament(filament_ids[idx], num_physical, texture_mgr);
+ const bool raw_vertex_color_preview = filament_ids[idx] == 0;
+ const float mix = raw_vertex_color_preview ?
+ 1.f :
+ texture_preview_mix_for_filament(filament_ids[idx], num_physical, texture_mgr);
+ const bool invalid = raw_vertex_color_preview ?
+ false :
+ texture_preview_settings_invalid_for_filament(filament_ids[idx], num_physical, texture_mgr);
if (mix <= 0.f && !invalid)
continue;
diff --git a/src/slic3r/GUI/MMUPaintedTexturePreview.hpp b/src/slic3r/GUI/MMUPaintedTexturePreview.hpp
index f7e749cd305..0cd144e4467 100644
--- a/src/slic3r/GUI/MMUPaintedTexturePreview.hpp
+++ b/src/slic3r/GUI/MMUPaintedTexturePreview.hpp
@@ -36,7 +36,8 @@ bool build_mmu_vertex_color_preview_models(
const Transform3d &world_matrix,
std::vector &out_models,
std::vector &out_colors,
- std::vector &out_filament_ids);
+ std::vector &out_filament_ids,
+ const ColorFacetsAnnotation *texture_mapping_color_facets_override = nullptr);
bool build_mmu_vertex_color_preview_models(
const ModelVolume &model_volume,
@@ -47,7 +48,8 @@ bool build_mmu_vertex_color_preview_models(
const TextureMappingManager *texture_mgr,
std::vector &out_models,
std::vector &out_colors,
- std::vector &out_filament_ids);
+ std::vector &out_filament_ids,
+ const ColorFacetsAnnotation *texture_mapping_color_facets_override = nullptr);
size_t model_volume_texture_preview_signature(const ModelVolume &model_volume);
size_t model_volume_texture_mapping_color_preview_signature(const ModelVolume &model_volume);