Add 'paint by color' utility. Add minimum visibility offset

This commit is contained in:
sentientstardust
2026-05-17 10:38:45 +01:00
parent c26d06125d
commit 396cca82bb
19 changed files with 1096 additions and 16 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "OrcaFilamentLibrary",
"version": "02.03.02.60",
"version": "02.03.02.61",
"force_update": "0",
"description": "Orca Filament Library",
"filament_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Prusa",
"version": "02.03.02.60",
"version": "02.03.02.61",
"force_update": "0",
"description": "Prusa configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Snapmaker",
"version": "02.03.02.60",
"version": "02.03.02.61",
"force_update": "0",
"description": "Snapmaker configurations",
"machine_model_list": [

View File

@@ -1,6 +1,6 @@
{
"name": "Template",
"version": "01.07.00.02",
"version": "01.07.00.03",
"force_update": "0",
"description": "Template configurations",
"machine_model_list": [],

View File

@@ -174,6 +174,14 @@ void main()
color = color * 0.5 + LightRed * 0.5;
alpha = 1.0;
}
} else if (slope.preview_mode == 4) {
float preview_luma = dot(slope.highlight_color.rgb, vec3(0.2126, 0.7152, 0.0722));
vec3 preview_accent = preview_luma > 0.75 ? vec3(0.02, 0.08, 0.24) : vec3(1.0, 1.0, 1.0);
float preview_check = slopePreviewChecker(world_pos.xyz, transformed_normal);
vec3 preview_color_a = slope.highlight_color.rgb * 0.78 + preview_accent * 0.22;
vec3 preview_color_b = slope.highlight_color.rgb * 0.25 + preview_accent * 0.75;
color = mix(preview_color_a, preview_color_b, preview_check);
alpha = max(alpha, slope.highlight_color.a);
} else {
int effective_state = slope.current_state == 0 ? slope.base_state : slope.current_state;
if (slopeOverrideMatches(effective_state) && slopePreviewMatches(smooth_world_normal_z)) {

View File

@@ -176,6 +176,14 @@ void main()
color = color * 0.5 + LightRed * 0.5;
alpha = 1.0;
}
} else if (slope.preview_mode == 4) {
float preview_luma = dot(slope.highlight_color.rgb, vec3(0.2126, 0.7152, 0.0722));
vec3 preview_accent = preview_luma > 0.75 ? vec3(0.02, 0.08, 0.24) : vec3(1.0, 1.0, 1.0);
float preview_check = slopePreviewChecker(world_pos.xyz, transformed_normal);
vec3 preview_color_a = slope.highlight_color.rgb * 0.78 + preview_accent * 0.22;
vec3 preview_color_b = slope.highlight_color.rgb * 0.25 + preview_accent * 0.75;
color = mix(preview_color_a, preview_color_b, preview_check);
alpha = max(alpha, slope.highlight_color.a);
} else {
int effective_state = slope.current_state == 0 ? slope.base_state : slope.current_state;
if (slopeOverrideMatches(effective_state) && slopePreviewMatches(smooth_world_normal_z)) {

View File

@@ -1,3 +1,5 @@
// original author: sentientstardust
#include "ImageMapRawFilamentOffsetAtlas.hpp"
#include <algorithm>

View File

@@ -1,3 +1,5 @@
// original author: sentientstardust
#ifndef slic3r_ImageMapRawFilamentOffsetAtlas_hpp_
#define slic3r_ImageMapRawFilamentOffsetAtlas_hpp_

View File

@@ -797,6 +797,8 @@ bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const
compact_offset_mode == rhs.compact_offset_mode &&
use_legacy_fixed_color_mode == rhs.use_legacy_fixed_color_mode &&
high_speed_image_texture_sampling == rhs.high_speed_image_texture_sampling &&
minimum_visibility_offset_enabled == rhs.minimum_visibility_offset_enabled &&
std::abs(minimum_visibility_offset_pct - rhs.minimum_visibility_offset_pct) <= eps &&
generic_solver_lookup_mode == rhs.generic_solver_lookup_mode &&
generic_solver_mode == rhs.generic_solver_mode &&
generic_solver_mix_model == rhs.generic_solver_mix_model &&
@@ -1044,6 +1046,9 @@ std::string TextureMappingManager::serialize_entries()
texture["compact_offset_mode"] = zone.compact_offset_mode;
texture["use_legacy_fixed_color_mode"] = zone.use_legacy_fixed_color_mode;
texture["high_speed_image_texture_sampling"] = zone.high_speed_image_texture_sampling;
texture["minimum_visibility_offset_enabled"] = zone.minimum_visibility_offset_enabled;
texture["minimum_visibility_offset_pct"] =
std::clamp(finite_or(zone.minimum_visibility_offset_pct, TextureMappingZone::DefaultMinimumVisibilityOffsetPct), 0.f, 100.f);
texture["generic_solver_lookup"] = generic_solver_lookup_mode_name(zone.generic_solver_lookup_mode);
texture["generic_solver_mode"] = generic_solver_mode_name(zone.generic_solver_mode);
texture["generic_solver_mix_model"] = generic_solver_mix_model_name(zone.generic_solver_mix_model);
@@ -1174,6 +1179,13 @@ void TextureMappingManager::load_entries(const std::string &serialized,
texture.value("use_legacy_fixed_color_mode", TextureMappingZone::DefaultUseLegacyFixedColorMode);
zone.high_speed_image_texture_sampling =
texture.value("high_speed_image_texture_sampling", TextureMappingZone::DefaultHighSpeedImageTextureSampling);
zone.minimum_visibility_offset_enabled =
texture.value("minimum_visibility_offset_enabled", TextureMappingZone::DefaultMinimumVisibilityOffsetEnabled);
zone.minimum_visibility_offset_pct =
std::clamp(finite_or(texture.value("minimum_visibility_offset_pct", TextureMappingZone::DefaultMinimumVisibilityOffsetPct),
TextureMappingZone::DefaultMinimumVisibilityOffsetPct),
0.f,
100.f);
zone.generic_solver_lookup_mode =
generic_solver_lookup_mode_from_name(texture.value("generic_solver_lookup", std::string("closest_mix")));
const auto generic_solver_mode_it = texture.find("generic_solver_mode");

View File

@@ -97,6 +97,8 @@ struct TextureMappingZone
static constexpr bool DefaultCompactOffsetMode = true;
static constexpr bool DefaultUseLegacyFixedColorMode = false;
static constexpr bool DefaultHighSpeedImageTextureSampling = true;
static constexpr bool DefaultMinimumVisibilityOffsetEnabled = false;
static constexpr float DefaultMinimumVisibilityOffsetPct = 30.f;
static constexpr int DefaultGenericSolverLookupMode = int(GenericSolverClosestMix);
static constexpr int DefaultGenericSolverMode = int(GenericSolverV2);
static constexpr int DefaultGenericSolverMixModel = int(GenericSolverPigmentPainter);
@@ -139,6 +141,8 @@ struct TextureMappingZone
bool compact_offset_mode = DefaultCompactOffsetMode;
bool use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode;
bool high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling;
bool minimum_visibility_offset_enabled = DefaultMinimumVisibilityOffsetEnabled;
float minimum_visibility_offset_pct = DefaultMinimumVisibilityOffsetPct;
int generic_solver_lookup_mode = DefaultGenericSolverLookupMode;
int generic_solver_mode = DefaultGenericSolverMode;
int generic_solver_mix_model = DefaultGenericSolverMixModel;
@@ -182,6 +186,8 @@ struct TextureMappingZone
compact_offset_mode = DefaultCompactOffsetMode;
use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode;
high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling;
minimum_visibility_offset_enabled = DefaultMinimumVisibilityOffsetEnabled;
minimum_visibility_offset_pct = DefaultMinimumVisibilityOffsetPct;
generic_solver_lookup_mode = DefaultGenericSolverLookupMode;
generic_solver_mode = DefaultGenericSolverMode;
generic_solver_mix_model = DefaultGenericSolverMixModel;

View File

@@ -20,6 +20,38 @@ static Vec3f normalized_or_default(const Vec3f &normal, const Vec3f &fallback)
return len > float(EPSILON) ? normal / len : fallback;
}
static Vec3f barycentric_for_triangle_sample(const indexed_triangle_set &its, int tri_idx, const Vec3f &point)
{
if (tri_idx < 0 || tri_idx >= int(its.indices.size()))
return Vec3f(1.f / 3.f, 1.f / 3.f, 1.f / 3.f);
const stl_triangle_vertex_indices &source_indices = its.indices[size_t(tri_idx)];
if (source_indices[0] < 0 || source_indices[1] < 0 || source_indices[2] < 0 ||
source_indices[0] >= int(its.vertices.size()) ||
source_indices[1] >= int(its.vertices.size()) ||
source_indices[2] >= int(its.vertices.size()))
return Vec3f(1.f / 3.f, 1.f / 3.f, 1.f / 3.f);
const Vec3f &a = its.vertices[size_t(source_indices[0])];
const Vec3f &b = its.vertices[size_t(source_indices[1])];
const Vec3f &c = its.vertices[size_t(source_indices[2])];
const Vec3f v0 = b - a;
const Vec3f v1 = c - a;
const Vec3f v2 = point - a;
const float d00 = v0.dot(v0);
const float d01 = v0.dot(v1);
const float d11 = v1.dot(v1);
const float d20 = v2.dot(v0);
const float d21 = v2.dot(v1);
const float denom = d00 * d11 - d01 * d01;
if (std::abs(denom) <= float(EPSILON))
return Vec3f(1.f / 3.f, 1.f / 3.f, 1.f / 3.f);
const float v = (d11 * d20 - d01 * d21) / denom;
const float w = (d00 * d21 - d01 * d20) / denom;
return Vec3f(1.f - v - w, v, w);
}
static std::vector<std::array<Vec3f, 3>> its_corner_normals(const indexed_triangle_set &its)
{
constexpr float crease_dot = 0.5f;
@@ -1255,6 +1287,131 @@ bool TriangleSelector::apply_state_by_smooth_world_normal(const Transform3d &tra
return changed;
}
bool TriangleSelector::apply_state_by_triangle_sampler_recursive(
int facet_idx,
const Vec3i32 &neighbors,
const Transform3f &trafo,
EnforcerBlockerType new_state,
const std::function<bool(EnforcerBlockerType, size_t, const Vec3f &, const Vec3f &)> &predicate,
float max_world_edge_sqr,
int max_depth,
bool clear_non_matching)
{
if (facet_idx < 0 || facet_idx >= int(m_triangles.size()) || !m_triangles[size_t(facet_idx)].valid())
return false;
Triangle *tr = &m_triangles[size_t(facet_idx)];
if (tr->is_split()) {
bool changed = false;
const int child_count = tr->number_of_split_sides() + 1;
for (int child_idx = 0; child_idx < child_count; ++child_idx) {
changed |= apply_state_by_triangle_sampler_recursive(tr->children[size_t(child_idx)],
child_neighbors(*tr, neighbors, child_idx),
trafo,
new_state,
predicate,
max_world_edge_sqr,
max_depth,
clear_non_matching);
tr = &m_triangles[size_t(facet_idx)];
}
return changed;
}
const EnforcerBlockerType current_state = tr->get_state();
std::array<Vec3f, 4> sample_points;
sample_points[0] = m_vertices[size_t(tr->verts_idxs[0])].v;
sample_points[1] = m_vertices[size_t(tr->verts_idxs[1])].v;
sample_points[2] = m_vertices[size_t(tr->verts_idxs[2])].v;
sample_points[3] = (sample_points[0] + sample_points[1] + sample_points[2]) / 3.f;
std::array<bool, 4> matches;
int match_count = 0;
for (size_t sample_idx = 0; sample_idx < sample_points.size(); ++sample_idx) {
const Vec3f barycentric = barycentric_for_triangle_sample(m_mesh.its, tr->source_triangle, sample_points[sample_idx]);
matches[sample_idx] = predicate(current_state, size_t(std::max(tr->source_triangle, 0)), sample_points[sample_idx], barycentric);
if (matches[sample_idx])
++match_count;
}
if (match_count == 0) {
const EnforcerBlockerType next_state = clear_non_matching ? EnforcerBlockerType::NONE : current_state;
if (next_state != current_state) {
tr->set_state(next_state);
return true;
}
return false;
}
if (match_count == int(matches.size())) {
if (current_state != new_state) {
tr->set_state(new_state);
return true;
}
return false;
}
if (max_depth > 0 && triangle_max_world_edge_sqr(*tr, trafo) > max_world_edge_sqr &&
split_triangle_for_world_edge_limit(facet_idx, neighbors, trafo, max_world_edge_sqr)) {
bool changed = false;
tr = &m_triangles[size_t(facet_idx)];
const int child_count = tr->number_of_split_sides() + 1;
for (int child_idx = 0; child_idx < child_count; ++child_idx) {
changed |= apply_state_by_triangle_sampler_recursive(tr->children[size_t(child_idx)],
child_neighbors(*tr, neighbors, child_idx),
trafo,
new_state,
predicate,
max_world_edge_sqr,
max_depth - 1,
clear_non_matching);
tr = &m_triangles[size_t(facet_idx)];
}
return changed;
}
const EnforcerBlockerType next_state = matches[3] ? new_state : (clear_non_matching ? EnforcerBlockerType::NONE : current_state);
if (next_state != current_state) {
tr->set_state(next_state);
return true;
}
return false;
}
bool TriangleSelector::apply_state_by_triangle_sampler(
const Transform3d &trafo_no_translate,
EnforcerBlockerType new_state,
const std::function<bool(EnforcerBlockerType, size_t, const Vec3f &, const Vec3f &)> &predicate,
float max_world_edge,
int max_depth,
bool clear_non_matching)
{
if (!predicate)
return false;
const Transform3f trafo = trafo_no_translate.cast<float>();
const float max_world_edge_sqr = std::max(max_world_edge, float(EPSILON)) * std::max(max_world_edge, float(EPSILON));
bool changed = false;
for (int facet_idx = 0; facet_idx < m_orig_size_indices; ++facet_idx) {
changed |= apply_state_by_triangle_sampler_recursive(facet_idx,
m_neighbors[size_t(facet_idx)],
trafo,
new_state,
predicate,
max_world_edge_sqr,
max_depth,
clear_non_matching);
}
if (changed) {
for (int facet_idx = 0; facet_idx < m_orig_size_indices; ++facet_idx)
if (m_triangles[size_t(facet_idx)].valid() && m_triangles[size_t(facet_idx)].is_split())
remove_useless_children(facet_idx);
}
return changed;
}
// called by select_patch()->select_triangle()...select_triangle()
// to decide which sides of the triangle to split and to actually split it calling set_division() and perform_split().
void TriangleSelector::split_triangle(int facet_idx, const Vec3i32 &neighbors)

View File

@@ -358,6 +358,15 @@ public:
float max_world_edge,
int max_depth,
bool clear_non_matching);
bool apply_state_by_triangle_sampler(const Transform3d &trafo_no_translate,
EnforcerBlockerType new_state,
const std::function<bool(EnforcerBlockerType,
size_t,
const Vec3f &,
const Vec3f &)> &predicate,
float max_world_edge,
int max_depth,
bool clear_non_matching);
// Clear everything and make the tree empty.
void reset();
@@ -514,6 +523,17 @@ private:
float max_world_edge_sqr,
int max_depth,
bool clear_non_matching);
bool apply_state_by_triangle_sampler_recursive(int facet_idx,
const Vec3i32 &neighbors,
const Transform3f &trafo,
EnforcerBlockerType new_state,
const std::function<bool(EnforcerBlockerType,
size_t,
const Vec3f &,
const Vec3f &)> &predicate,
float max_world_edge_sqr,
int max_depth,
bool clear_non_matching);
void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant.
bool is_facet_clipped(int facet_idx, const ClippingPlane &clp) const;
int push_triangle(int a, int b, int c, int source_triangle, EnforcerBlockerType state = EnforcerBlockerType{0});

View File

@@ -140,6 +140,17 @@ struct TrueColorRgbDataConversionResult
bool canceled = false;
};
struct ColorAutoPaintPreviewTask
{
const ModelObject *object = nullptr;
const ModelVolume *volume = nullptr;
TriangleSelector::TriangleSplittingData selector_data;
std::vector<ColorRGBA> ebt_colors;
Transform3d trafo_no_translate = Transform3d::Identity();
EnforcerBlockerType max_ebt = EnforcerBlockerType::NONE;
bool valid = false;
};
struct GLGizmoTrueColorPainting::RgbDataConversionState
{
std::atomic_bool cancel { false };
@@ -192,10 +203,19 @@ void GLGizmoMmuSegmentation::on_opening()
show_notification_extruders_limit_exceeded();
}
GLGizmoMmuSegmentation::~GLGizmoMmuSegmentation()
{
cancel_color_auto_paint_preview_worker();
}
void GLGizmoMmuSegmentation::on_shutdown()
{
m_show_slope_auto_paint_overlay = false;
m_slope_auto_paint_preview_active = false;
m_show_color_auto_paint_overlay = false;
m_color_auto_paint_preview_active = false;
cancel_color_auto_paint_preview_worker();
m_color_auto_paint_preview_selectors.clear();
m_parent.use_slope(false);
m_parent.toggle_model_objects_visibility(true);
}
@@ -309,6 +329,20 @@ static wxString slope_auto_paint_filament_label(unsigned int filament_id)
wxString::Format(_L("Filament %u"), filament_id);
}
static std::vector<ColorRGBA> mmu_segmentation_selector_colors_for_volume(const ModelVolume &volume,
const std::vector<ColorRGBA> &extruder_colors)
{
if (extruder_colors.empty())
return {};
const unsigned int extruder_id = volume.extruder_id() > 0 ? unsigned(volume.extruder_id()) : 1u;
const size_t extruder_color_idx = extruder_color_index_for_filament_id(extruder_id, extruder_colors.size());
std::vector<ColorRGBA> colors;
colors.emplace_back(extruder_colors[extruder_color_idx]);
colors.insert(colors.end(), extruder_colors.begin(), extruder_colors.end());
return colors;
}
static bool slope_auto_paint_matches_normal(const SlopeAutoPaintSettings &settings, float world_normal_z)
{
constexpr float normal_epsilon = 0.0001f;
@@ -3749,6 +3783,126 @@ 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 ColorRGBA composite_color_over_background(const ColorRGBA &color, const ColorRGBA &background)
{
const float alpha = std::clamp(color.a(), 0.f, 1.f);
return ColorRGBA(color.r() * alpha + background.r() * (1.f - alpha),
color.g() * alpha + background.g() * (1.f - alpha),
color.b() * alpha + background.b() * (1.f - alpha),
1.f);
}
static std::optional<ColorRGBA> sample_color_auto_paint_source(const ModelObject *object,
const ModelVolume &volume,
const VolumeColorSource &source,
size_t tri_idx,
const Vec3f &point,
const Vec3f &barycentric)
{
const ColorRGBA configured_background =
configured_texture_mapping_background_color_for_volume(volume).value_or(managed_color_data_background_color(object));
if (source.rgb_background_color) {
ColorRGBA color = *source.rgb_background_color;
if (std::optional<ColorRGBA> sampled = sample_rgb_color_facets(source.rgb_facets,
source.rgb_by_source_triangle,
int(tri_idx),
point))
color = *sampled;
ColorRGBA background = *source.rgb_background_color;
background.a(1.f);
return composite_color_over_background(color, background);
}
const indexed_triangle_set &its = volume.mesh().its;
if (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]);
return composite_color_over_background(sample_texture_rgba_for_face_bake(volume.imported_texture_rgba,
volume.imported_texture_width,
volume.imported_texture_height,
uv0,
uv1,
uv2,
barycentric),
configured_background);
}
}
if (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])]);
const ColorRGBA sampled(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());
return composite_color_over_background(sampled, configured_background);
}
}
return std::nullopt;
}
static std::array<float, 3> color_auto_paint_oklab(const ColorRGBA &color)
{
return color_solver_oklab_from_srgb({ std::clamp(color.r(), 0.f, 1.f),
std::clamp(color.g(), 0.f, 1.f),
std::clamp(color.b(), 0.f, 1.f) });
}
static float color_auto_paint_max_oklab_distance()
{
static const float max_distance = []() {
const std::array<ColorRGBA, 8> corners = {
ColorRGBA(0.f, 0.f, 0.f, 1.f),
ColorRGBA(0.f, 0.f, 1.f, 1.f),
ColorRGBA(0.f, 1.f, 0.f, 1.f),
ColorRGBA(0.f, 1.f, 1.f, 1.f),
ColorRGBA(1.f, 0.f, 0.f, 1.f),
ColorRGBA(1.f, 0.f, 1.f, 1.f),
ColorRGBA(1.f, 1.f, 0.f, 1.f),
ColorRGBA(1.f, 1.f, 1.f, 1.f)
};
float distance = 0.f;
for (const ColorRGBA &lhs : corners) {
const std::array<float, 3> lhs_oklab = color_auto_paint_oklab(lhs);
for (const ColorRGBA &rhs : corners) {
const std::array<float, 3> rhs_oklab = color_auto_paint_oklab(rhs);
const float dl = lhs_oklab[0] - rhs_oklab[0];
const float da = lhs_oklab[1] - rhs_oklab[1];
const float db = lhs_oklab[2] - rhs_oklab[2];
distance = std::max(distance, std::sqrt(dl * dl + da * da + db * db));
}
}
return std::max(distance, 1.f);
}();
return max_distance;
}
static bool color_auto_paint_matches_target(const ColorRGBA &sample, const ColorAutoPaintSettings &settings)
{
const float tolerance_pct = std::clamp(settings.tolerance_pct, 0.f, 100.f);
if (tolerance_pct >= 100.f)
return true;
const std::array<float, 3> sample_oklab = color_auto_paint_oklab(sample);
const std::array<float, 3> target_oklab = color_auto_paint_oklab(settings.target_color);
const float dl = sample_oklab[0] - target_oklab[0];
const float da = sample_oklab[1] - target_oklab[1];
const float db = sample_oklab[2] - target_oklab[2];
const float tolerance = tolerance_pct * 0.01f * color_auto_paint_max_oklab_distance();
return dl * dl + da * da + db * db <= tolerance * tolerance;
}
static bool snapshot_has_bakeable_image_texture_data(const TrueColorRgbDataConversionVolumeSnapshot &snapshot)
{
return !snapshot.its.vertices.empty() &&
@@ -8646,6 +8800,70 @@ bool GLGizmoMmuSegmentation::should_render_triangle_texture_preview() const
return !m_slope_auto_paint_preview_active;
}
void GLGizmoMmuSegmentation::render_extra_triangle_overlays(int mesh_id,
const Transform3d &matrix,
const Transform3d &view_matrix,
const Transform3d &projection_matrix,
const std::array<float, 2> &z_range,
const std::array<float, 4> &clipping_plane) const
{
(void)view_matrix;
(void)projection_matrix;
(void)z_range;
(void)clipping_plane;
if (!m_color_auto_paint_preview_active ||
mesh_id < 0 ||
size_t(mesh_id) >= m_color_auto_paint_preview_selectors.size() ||
m_color_auto_paint_preview_selectors[size_t(mesh_id)] == nullptr)
return;
GLShaderProgram *shader = wxGetApp().get_current_shader();
if (shader == nullptr)
return;
const size_t target_color_idx =
extruder_color_index_for_filament_id(m_color_auto_paint_preview_settings.target_filament_id, m_extruders_colors.size());
ColorRGBA highlight_color = m_extruders_colors.empty() ?
ColorRGBA(0.15f, 0.65f, 0.6f, 1.f) :
m_extruders_colors[target_color_idx];
highlight_color.a(0.95f);
const Matrix3f normal_matrix = static_cast<Matrix3f>(matrix.matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>());
const std::array<float, 4> empty_mask { 0.f, 0.f, 0.f, 0.f };
GLint depth_func = GL_LESS;
GLfloat polygon_offset_factor = 0.f;
GLfloat polygon_offset_units = 0.f;
GLboolean polygon_offset_fill_enabled = glIsEnabled(GL_POLYGON_OFFSET_FILL);
glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &depth_func));
glsafe(::glGetFloatv(GL_POLYGON_OFFSET_FACTOR, &polygon_offset_factor));
glsafe(::glGetFloatv(GL_POLYGON_OFFSET_UNITS, &polygon_offset_units));
shader->set_uniform("slope.actived", true);
shader->set_uniform("slope.volume_world_normal_matrix", normal_matrix);
shader->set_uniform("slope.normal_z", 0.f);
shader->set_uniform("slope.preview_mode", 4);
shader->set_uniform("slope.top_z", 1.f);
shader->set_uniform("slope.bottom_z", -1.f);
shader->set_uniform("slope.highlight_color", highlight_color);
shader->set_uniform("slope.override_all", true);
shader->set_uniform("slope.current_state", 0);
shader->set_uniform("slope.base_state", 1);
shader->set_uniform("slope.override_mask0", empty_mask);
shader->set_uniform("slope.override_mask1", empty_mask);
shader->set_uniform("slope.override_mask2", empty_mask);
shader->set_uniform("slope.override_mask3", empty_mask);
glsafe(::glDepthFunc(GL_LEQUAL));
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
glsafe(::glPolygonOffset(-1.f, -1.f));
m_color_auto_paint_preview_selectors[size_t(mesh_id)]->render(m_imgui, matrix);
glsafe(::glPolygonOffset(polygon_offset_factor, polygon_offset_units));
if (!polygon_offset_fill_enabled)
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
glsafe(::glDepthFunc(depth_func));
shader->set_uniform("slope.actived", false);
}
void GLGizmoMmuSegmentation::render_slope_auto_paint_overlay()
{
if (!m_show_slope_auto_paint_overlay)
@@ -8742,6 +8960,7 @@ void GLGizmoMmuSegmentation::render_slope_auto_paint_overlay()
changed |= render_angle(_L("Angle from bottom"), "##slope_bottom_angle", m_slope_auto_paint_settings.bottom_angle_deg);
ImGui::Separator();
m_imgui->text(_L("Mask:"));
bool override_all = m_slope_auto_paint_settings.override_all;
if (m_imgui->bbl_checkbox(_L("All"), override_all)) {
if (override_all) {
@@ -8831,6 +9050,10 @@ void GLGizmoMmuSegmentation::open_slope_auto_paint_overlay()
if (m_c->selection_info()->model_object() == nullptr)
return;
m_show_color_auto_paint_overlay = false;
m_color_auto_paint_overlay_positioned = false;
clear_color_auto_paint_preview();
m_slope_auto_paint_settings = SlopeAutoPaintSettings();
m_slope_auto_paint_settings.target_filament_id = m_selected_extruder_idx < m_display_filament_ids.size() ?
m_display_filament_ids[m_selected_extruder_idx] :
@@ -8909,9 +9132,10 @@ bool GLGizmoMmuSegmentation::apply_slope_auto_paint(const SlopeAutoPaintSettings
EnforcerBlockerType current_state, float world_normal_z) {
if (!slope_auto_paint_matches_normal(settings, world_normal_z))
return false;
const unsigned int current_filament_id = current_state == EnforcerBlockerType::NONE ?
base_filament_id :
static_cast<unsigned int>(current_state);
const unsigned int current_filament_id =
current_state == EnforcerBlockerType::NONE ?
base_filament_id :
static_cast<unsigned int>(current_state);
return settings.override_all || override_ids.count(current_filament_id) != 0;
},
SlopeAutoPaintMaxEdgeMm,
@@ -8924,6 +9148,446 @@ bool GLGizmoMmuSegmentation::apply_slope_auto_paint(const SlopeAutoPaintSettings
return changed;
}
void GLGizmoMmuSegmentation::render_color_auto_paint_overlay()
{
if (!m_show_color_auto_paint_overlay)
return;
if (!selected_object_has_color_auto_paint_source()) {
m_show_color_auto_paint_overlay = false;
clear_color_auto_paint_preview();
return;
}
std::vector<unsigned int> filament_ids = m_display_filament_ids;
if (filament_ids.empty())
filament_ids.emplace_back(1u);
std::vector<std::string> filament_labels;
std::vector<ColorRGBA> filament_colors;
filament_labels.reserve(filament_ids.size());
filament_colors.reserve(filament_ids.size());
for (const unsigned int filament_id : filament_ids) {
filament_labels.emplace_back(into_u8(slope_auto_paint_filament_label(filament_id)));
const size_t color_idx = extruder_color_index_for_filament_id(filament_id, m_extruders_colors.size());
filament_colors.emplace_back(m_extruders_colors.empty() ? ColorRGBA(0.15f, 0.65f, 0.6f, 1.f) : m_extruders_colors[color_idx]);
}
if (std::find(filament_ids.begin(), filament_ids.end(), m_color_auto_paint_settings.target_filament_id) == filament_ids.end())
m_color_auto_paint_settings.target_filament_id = filament_ids.front();
if (!m_color_auto_paint_overlay_positioned) {
const Size canvas_size = m_parent.get_canvas_size();
const float window_width = m_imgui->scaled(22.f);
const float window_x = std::max(m_imgui->scaled(8.f),
float(canvas_size.get_width()) - window_width - m_imgui->scaled(24.f));
ImGui::SetNextWindowPos(ImVec2(window_x, m_parent.get_main_toolbar_height() + m_imgui->scaled(16.f)),
ImGuiCond_Always);
m_color_auto_paint_overlay_positioned = true;
}
m_imgui->push_common_window_style(m_parent.get_scale());
ImGui::SetNextWindowSize(ImVec2(m_imgui->scaled(22.f), 0.f), ImGuiCond_FirstUseEver);
bool open = true;
const int flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse;
m_imgui->begin(_L("Paint by color"), &open, flags);
bool changed = false;
const float label_width = m_imgui->scaled(7.5f);
const float item_width = m_imgui->scaled(11.f);
size_t target_idx = 0;
for (size_t idx = 0; idx < filament_ids.size(); ++idx) {
if (filament_ids[idx] == m_color_auto_paint_settings.target_filament_id) {
target_idx = idx;
break;
}
}
const size_t old_target_idx = target_idx;
ImGui::AlignTextToFramePadding();
m_imgui->text(_L("Paint with"));
ImGui::SameLine(label_width);
ImGui::PushItemWidth(item_width);
render_extruders_combo("##color_auto_paint_target", filament_labels, filament_colors, target_idx);
ImGui::PopItemWidth();
if (target_idx != old_target_idx && target_idx < filament_ids.size()) {
m_color_auto_paint_settings.target_filament_id = filament_ids[target_idx];
changed = true;
}
std::array<float, 3> target_color = {
std::clamp(m_color_auto_paint_settings.target_color.r(), 0.f, 1.f),
std::clamp(m_color_auto_paint_settings.target_color.g(), 0.f, 1.f),
std::clamp(m_color_auto_paint_settings.target_color.b(), 0.f, 1.f)
};
ImGui::AlignTextToFramePadding();
m_imgui->text(_L("Color"));
ImGui::SameLine(label_width);
ImGui::PushItemWidth(item_width);
ImGuiColorEditFlags color_flags = ImGuiColorEditFlags_DisplayRGB |
ImGuiColorEditFlags_InputRGB |
ImGuiColorEditFlags_NoInputs;
if (ImGui::ColorEdit3("##color_auto_paint_picker", target_color.data(), color_flags)) {
m_color_auto_paint_settings.target_color = ColorRGBA(target_color[0], target_color[1], target_color[2], 1.f);
changed = true;
}
ImGui::PopItemWidth();
ImGui::AlignTextToFramePadding();
m_imgui->text(_L("Tolerance"));
ImGui::SameLine(label_width);
ImGui::PushItemWidth(item_width);
if (ImGui::SliderFloat("##color_auto_paint_tolerance", &m_color_auto_paint_settings.tolerance_pct, 0.f, 100.f, "%.0f%%")) {
m_color_auto_paint_settings.tolerance_pct = std::clamp(m_color_auto_paint_settings.tolerance_pct, 0.f, 100.f);
changed = true;
}
ImGui::PopItemWidth();
ImGui::Separator();
m_imgui->text(_L("Mask:"));
bool override_all = m_color_auto_paint_settings.override_all;
if (m_imgui->bbl_checkbox(_L("All"), override_all)) {
if (override_all) {
m_color_auto_paint_settings.override_all = true;
m_color_auto_paint_settings.override_filament_ids.clear();
changed = true;
} else {
override_all = true;
}
}
const float row_height = ImGui::GetTextLineHeightWithSpacing();
const float override_height = std::min(row_height * (float(filament_ids.size()) + 0.5f), row_height * 6.5f);
ImGui::BeginChild("##color_auto_paint_override_scroll", ImVec2(0.f, override_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar);
for (size_t idx = 0; idx < filament_ids.size(); ++idx) {
const unsigned int filament_id = filament_ids[idx];
const bool currently_selected = !m_color_auto_paint_settings.override_all &&
std::find(m_color_auto_paint_settings.override_filament_ids.begin(),
m_color_auto_paint_settings.override_filament_ids.end(),
filament_id) != m_color_auto_paint_settings.override_filament_ids.end();
bool selected = currently_selected;
ImGui::PushID(int(filament_id));
if (ImGui::Checkbox("##override", &selected)) {
if (selected) {
if (m_color_auto_paint_settings.override_all) {
m_color_auto_paint_settings.override_all = false;
m_color_auto_paint_settings.override_filament_ids.clear();
}
if (std::find(m_color_auto_paint_settings.override_filament_ids.begin(),
m_color_auto_paint_settings.override_filament_ids.end(),
filament_id) == m_color_auto_paint_settings.override_filament_ids.end())
m_color_auto_paint_settings.override_filament_ids.emplace_back(filament_id);
} else {
auto &override_ids = m_color_auto_paint_settings.override_filament_ids;
override_ids.erase(std::remove(override_ids.begin(), override_ids.end(), filament_id), override_ids.end());
if (override_ids.empty())
m_color_auto_paint_settings.override_all = true;
}
changed = true;
}
ImGui::SameLine();
ImGuiColorEditFlags swatch_flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel |
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip;
ImGui::ColorButton("##swatch", ImGuiWrapper::to_ImVec4(filament_colors[idx]), swatch_flags,
ImVec2(ImGui::GetTextLineHeight() * 1.5f, ImGui::GetTextLineHeight()));
ImGui::SameLine();
ImGui::Text("%s", filament_labels[idx].c_str());
ImGui::PopID();
}
ImGui::EndChild();
if (changed) {
update_color_auto_paint_preview(m_color_auto_paint_settings);
} else if (!m_color_auto_paint_preview_active && !m_color_auto_paint_preview_update_pending) {
update_color_auto_paint_preview(m_color_auto_paint_settings);
}
ImGui::Separator();
bool close_overlay = false;
const bool preview_processing = m_color_auto_paint_preview_update_pending || m_color_auto_paint_preview_worker_running;
m_imgui->push_confirm_button_style();
m_imgui->disabled_begin(preview_processing);
if (m_imgui->bbl_button(_L("Apply"))) {
m_color_auto_paint_preview_update_pending = false;
++m_color_auto_paint_preview_generation;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Paint by color", UndoRedo::SnapshotType::GizmoAction);
if (apply_color_auto_paint(m_color_auto_paint_settings, false)) {
update_model_object();
m_parent.set_as_dirty();
}
close_overlay = true;
}
m_imgui->disabled_end();
m_imgui->pop_confirm_button_style();
ImGui::SameLine();
m_imgui->push_cancel_button_style();
if (m_imgui->bbl_button(_L("Cancel")))
close_overlay = true;
m_imgui->pop_cancel_button_style();
if (preview_processing) {
ImGui::SameLine();
m_imgui->text(_L("Processing..."));
}
m_imgui->end();
m_imgui->pop_common_window_style();
if (!open || close_overlay) {
m_show_color_auto_paint_overlay = false;
m_color_auto_paint_overlay_positioned = false;
clear_color_auto_paint_preview();
}
}
void GLGizmoMmuSegmentation::open_color_auto_paint_overlay()
{
if (m_c->selection_info()->model_object() == nullptr || !selected_object_has_color_auto_paint_source())
return;
m_show_slope_auto_paint_overlay = false;
m_slope_auto_paint_overlay_positioned = false;
clear_slope_auto_paint_preview();
m_color_auto_paint_settings = ColorAutoPaintSettings();
m_color_auto_paint_settings.target_filament_id = m_selected_extruder_idx < m_display_filament_ids.size() ?
m_display_filament_ids[m_selected_extruder_idx] :
1u;
if (!m_display_filament_ids.empty() &&
std::find(m_display_filament_ids.begin(), m_display_filament_ids.end(), m_color_auto_paint_settings.target_filament_id) ==
m_display_filament_ids.end())
m_color_auto_paint_settings.target_filament_id = m_display_filament_ids.front();
const size_t color_idx =
extruder_color_index_for_filament_id(m_color_auto_paint_settings.target_filament_id, m_extruders_colors.size());
if (!m_extruders_colors.empty()) {
m_color_auto_paint_settings.target_color = m_extruders_colors[color_idx];
m_color_auto_paint_settings.target_color.a(1.f);
}
m_show_color_auto_paint_overlay = true;
m_color_auto_paint_overlay_positioned = false;
update_color_auto_paint_preview(m_color_auto_paint_settings);
}
bool GLGizmoMmuSegmentation::apply_color_auto_paint_to_selector(const ModelObject &object,
const ModelVolume &volume,
TriangleSelector &selector,
const Transform3d &trafo_no_translate,
const ColorAutoPaintSettings &settings,
bool clear_non_matching) const
{
const VolumeColorSource source = build_volume_color_source(volume);
const std::unordered_set<unsigned int> override_ids(settings.override_filament_ids.begin(), settings.override_filament_ids.end());
const unsigned int base_filament_id = volume.extruder_id() > 0 ? static_cast<unsigned int>(volume.extruder_id()) : 1u;
return selector.apply_state_by_triangle_sampler(
trafo_no_translate,
static_cast<EnforcerBlockerType>(settings.target_filament_id),
[&object, &volume, &source, &settings, &override_ids, base_filament_id](
EnforcerBlockerType current_state, size_t source_triangle, const Vec3f &point, const Vec3f &barycentric) {
const unsigned int current_filament_id = current_state == EnforcerBlockerType::NONE ?
base_filament_id :
static_cast<unsigned int>(current_state);
if (!settings.override_all && override_ids.count(current_filament_id) == 0)
return false;
const std::optional<ColorRGBA> color =
sample_color_auto_paint_source(&object, volume, source, source_triangle, point, barycentric);
return color && color_auto_paint_matches_target(*color, settings);
},
SlopeAutoPaintMaxEdgeMm,
SlopeAutoPaintMaxDepth,
clear_non_matching);
}
void GLGizmoMmuSegmentation::update_color_auto_paint_preview(const ColorAutoPaintSettings &settings)
{
m_color_auto_paint_requested_settings = settings;
m_color_auto_paint_preview_update_pending = true;
++m_color_auto_paint_preview_generation;
start_color_auto_paint_preview_worker();
m_parent.schedule_extra_frame(0);
}
void GLGizmoMmuSegmentation::start_color_auto_paint_preview_worker()
{
if (m_color_auto_paint_preview_worker_running)
return;
if (m_color_auto_paint_preview_thread.joinable())
m_color_auto_paint_preview_thread.join();
ModelObject *mo = m_c->selection_info()->model_object();
const Selection &selection = m_parent.get_selection();
const bool active = mo != nullptr &&
!m_triangle_selectors.empty() &&
selection.get_instance_idx() >= 0 &&
selection.get_instance_idx() < int(mo->instances.size()) &&
selected_object_has_color_auto_paint_source();
if (!active) {
m_color_auto_paint_preview_selectors.clear();
m_color_auto_paint_preview_active = false;
m_color_auto_paint_preview_update_pending = false;
m_parent.set_as_dirty();
m_parent.schedule_extra_frame(0);
return;
}
const Transform3d instance_trafo_not_translate = m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView ?
mo->instances[selection.get_instance_idx()]->get_assemble_transformation().get_matrix_no_offset() :
mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix_no_offset();
std::vector<ColorAutoPaintPreviewTask> tasks;
int selector_idx = -1;
for (const ModelVolume *mv : mo->volumes) {
if (!mv->is_model_part())
continue;
++selector_idx;
ColorAutoPaintPreviewTask task;
if (selector_idx < 0 ||
size_t(selector_idx) >= m_triangle_selectors.size() ||
m_triangle_selectors[size_t(selector_idx)] == nullptr) {
tasks.emplace_back(std::move(task));
continue;
}
std::vector<ColorRGBA> ebt_colors = mmu_segmentation_selector_colors_for_volume(*mv, m_extruders_colors);
if (ebt_colors.empty()) {
tasks.emplace_back(std::move(task));
continue;
}
task.object = mo;
task.volume = mv;
task.selector_data = m_triangle_selectors[size_t(selector_idx)]->serialize();
task.ebt_colors = std::move(ebt_colors);
task.trafo_no_translate = instance_trafo_not_translate * mv->get_matrix_no_offset();
task.max_ebt = (EnforcerBlockerType)std::min(m_extruders_colors.size(), (size_t)EnforcerBlockerType::ExtruderMax);
task.valid = true;
tasks.emplace_back(std::move(task));
}
const uint64_t generation = m_color_auto_paint_preview_generation;
const ColorAutoPaintSettings settings = m_color_auto_paint_requested_settings;
m_color_auto_paint_preview_worker_running = true;
m_color_auto_paint_preview_thread = std::thread([this, generation, settings, tasks = std::move(tasks)]() mutable {
auto selectors = std::make_shared<std::vector<std::unique_ptr<TriangleSelectorPatch>>>();
selectors->reserve(tasks.size());
for (const ColorAutoPaintPreviewTask &task : tasks) {
if (!task.valid || task.object == nullptr || task.volume == nullptr) {
selectors->emplace_back();
continue;
}
std::unique_ptr<TriangleSelectorPatch> preview_selector =
std::make_unique<TriangleSelectorPatch>(task.volume->mesh(), task.volume, task.ebt_colors, 0.2f);
preview_selector->deserialize(task.selector_data, false, task.max_ebt);
preview_selector->set_none_state_rendered(false);
preview_selector->set_texture_preview_needed(false);
preview_selector->set_wireframe_needed(false);
apply_color_auto_paint_to_selector(*task.object,
*task.volume,
*preview_selector,
task.trafo_no_translate,
settings,
true);
preview_selector->request_update_render_data(true);
selectors->emplace_back(std::move(preview_selector));
}
wxGetApp().CallAfter([this, generation, settings, selectors]() mutable {
finish_color_auto_paint_preview_update(generation, settings, true, std::move(*selectors));
});
});
}
void GLGizmoMmuSegmentation::finish_color_auto_paint_preview_update(
uint64_t generation,
const ColorAutoPaintSettings &settings,
bool active,
std::vector<std::unique_ptr<TriangleSelectorPatch>> &&selectors)
{
if (m_color_auto_paint_preview_thread.joinable())
m_color_auto_paint_preview_thread.join();
m_color_auto_paint_preview_worker_running = false;
if (generation == m_color_auto_paint_preview_generation && m_show_color_auto_paint_overlay) {
m_color_auto_paint_preview_selectors = std::move(selectors);
m_color_auto_paint_preview_settings = settings;
m_color_auto_paint_preview_active = active;
m_color_auto_paint_preview_update_pending = false;
m_parent.set_as_dirty();
m_parent.schedule_extra_frame(0);
}
if (m_color_auto_paint_preview_update_pending && m_show_color_auto_paint_overlay)
start_color_auto_paint_preview_worker();
}
void GLGizmoMmuSegmentation::cancel_color_auto_paint_preview_worker()
{
++m_color_auto_paint_preview_generation;
m_color_auto_paint_preview_update_pending = false;
if (m_color_auto_paint_preview_thread.joinable())
m_color_auto_paint_preview_thread.join();
m_color_auto_paint_preview_worker_running = false;
}
void GLGizmoMmuSegmentation::clear_color_auto_paint_preview()
{
++m_color_auto_paint_preview_generation;
m_color_auto_paint_preview_active = false;
m_color_auto_paint_preview_update_pending = false;
m_color_auto_paint_preview_selectors.clear();
m_parent.set_as_dirty();
m_parent.schedule_extra_frame(0);
}
bool GLGizmoMmuSegmentation::apply_color_auto_paint(const ColorAutoPaintSettings &settings, bool preview)
{
if (preview) {
update_color_auto_paint_preview(settings);
return false;
}
ModelObject *mo = m_c->selection_info()->model_object();
if (mo == nullptr || m_triangle_selectors.empty())
return false;
const Selection &selection = m_parent.get_selection();
if (selection.get_instance_idx() < 0 || selection.get_instance_idx() >= int(mo->instances.size()))
return false;
const Transform3d instance_trafo_not_translate = m_parent.get_canvas_type() == GLCanvas3D::CanvasAssembleView ?
mo->instances[selection.get_instance_idx()]->get_assemble_transformation().get_matrix_no_offset() :
mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix_no_offset();
bool changed = false;
int selector_idx = -1;
for (const ModelVolume *mv : mo->volumes) {
if (!mv->is_model_part())
continue;
++selector_idx;
if (selector_idx < 0 || size_t(selector_idx) >= m_triangle_selectors.size() ||
m_triangle_selectors[size_t(selector_idx)] == nullptr)
continue;
const Transform3d trafo_matrix_not_translate = instance_trafo_not_translate * mv->get_matrix_no_offset();
const bool selector_changed =
apply_color_auto_paint_to_selector(*mo,
*mv,
*m_triangle_selectors[size_t(selector_idx)],
trafo_matrix_not_translate,
settings,
false);
m_triangle_selectors[size_t(selector_idx)]->request_update_render_data(true);
changed |= selector_changed;
}
return changed;
}
void GLGizmoMmuSegmentation::data_changed(bool is_serializing)
{
GLGizmoPainterBase::data_changed(is_serializing);
@@ -9490,6 +10154,25 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Paint color regions by surface slope with a live preview."), max_tooltip_width);
const bool can_paint_by_color = selected_object_has_color_auto_paint_source();
m_imgui->disabled_begin(!can_paint_by_color);
if (m_imgui->button(_L("Paint by color"))) {
if (m_show_color_auto_paint_overlay) {
m_show_color_auto_paint_overlay = false;
m_color_auto_paint_overlay_positioned = false;
clear_color_auto_paint_preview();
} else {
open_color_auto_paint_overlay();
}
}
if (ImGui::IsItemHovered()) {
if (can_paint_by_color)
m_imgui->tooltip(_L("Paint color regions by matching the visible source color with a live preview."), max_tooltip_width);
else
m_imgui->tooltip(_L("This object does not have RGBA, image texture, or vertex color data."), max_tooltip_width);
}
m_imgui->disabled_end();
ImGui::Separator();
const bool has_painted_regions = selected_object_has_painted_regions();
@@ -9624,6 +10307,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
ImGuiWrapper::pop_toolbar_style();
render_slope_auto_paint_overlay();
render_color_auto_paint_overlay();
}
@@ -9676,6 +10360,10 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors()
const ModelObject *mo = m_c->selection_info()->model_object();
m_triangle_selectors.clear();
m_slope_auto_paint_preview_active = false;
m_color_auto_paint_preview_active = false;
++m_color_auto_paint_preview_generation;
m_color_auto_paint_preview_update_pending = false;
m_color_auto_paint_preview_selectors.clear();
m_volumes_extruder_idxs.clear();
// Don't continue when extruders colors are not initialized
@@ -9833,6 +10521,24 @@ bool GLGizmoMmuSegmentation::selected_object_has_painted_regions() const
return false;
}
bool GLGizmoMmuSegmentation::selected_object_has_color_auto_paint_source() const
{
const ModelObject *object = m_c->selection_info()->model_object();
if (object == nullptr)
return false;
for (const ModelVolume *volume : object->volumes) {
if (volume == nullptr || !volume->is_model_part())
continue;
const indexed_triangle_set &its = volume->mesh().its;
if (!volume->texture_mapping_color_facets.empty() ||
model_volume_has_bakeable_image_texture_data(volume) ||
volume->imported_vertex_colors_rgba.size() == its.vertices.size())
return true;
}
return false;
}
void GLGizmoMmuSegmentation::open_obj_vertex_color_mapping_dialog()
{
ModelObject *object = m_c->selection_info()->model_object();
@@ -10420,7 +11126,7 @@ PainterGizmoType GLGizmoTrueColorPainting::get_painter_type() const
std::string GLGizmoTrueColorPainting::on_get_name() const
{
return _u8L("True Color Painting");
return _u8L("RGB Color Painting");
}
bool GLGizmoTrueColorPainting::on_is_selectable() const

View File

@@ -45,6 +45,15 @@ struct SlopeAutoPaintSettings
std::vector<unsigned int> override_filament_ids;
};
struct ColorAutoPaintSettings
{
ColorRGBA target_color = ColorRGBA(1.f, 1.f, 1.f, 1.f);
float tolerance_pct = 5.f;
unsigned int target_filament_id = 1;
bool override_all = true;
std::vector<unsigned int> override_filament_ids;
};
class GLMmSegmentationGizmo3DScene
{
public:
@@ -105,7 +114,7 @@ class GLGizmoMmuSegmentation : public GLGizmoPainterBase
{
public:
GLGizmoMmuSegmentation(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
~GLGizmoMmuSegmentation() override = default;
~GLGizmoMmuSegmentation() override;
void render_painter_gizmo() override;
@@ -187,6 +196,29 @@ private:
bool apply_slope_auto_paint(const SlopeAutoPaintSettings &settings, bool preview);
void set_render_triangle_slope_uniforms(GLShaderProgram *shader, const ModelVolume *model_volume, const Matrix3f &normal_matrix) const override;
bool should_render_triangle_texture_preview() const override;
void open_color_auto_paint_overlay();
void render_color_auto_paint_overlay();
void update_color_auto_paint_preview(const ColorAutoPaintSettings &settings);
void start_color_auto_paint_preview_worker();
void finish_color_auto_paint_preview_update(uint64_t generation,
const ColorAutoPaintSettings &settings,
bool active,
std::vector<std::unique_ptr<TriangleSelectorPatch>> &&selectors);
void cancel_color_auto_paint_preview_worker();
void clear_color_auto_paint_preview();
bool apply_color_auto_paint(const ColorAutoPaintSettings &settings, bool preview);
bool apply_color_auto_paint_to_selector(const ModelObject &object,
const ModelVolume &volume,
TriangleSelector &selector,
const Transform3d &trafo_no_translate,
const ColorAutoPaintSettings &settings,
bool clear_non_matching) const;
void render_extra_triangle_overlays(int mesh_id,
const Transform3d &matrix,
const Transform3d &view_matrix,
const Transform3d &projection_matrix,
const std::array<float, 2> &z_range,
const std::array<float, 4> &clipping_plane) const override;
// Filament remapping methods
void remap_filament_assignments();
@@ -196,6 +228,7 @@ private:
bool selected_object_has_bakeable_image_texture_data() const;
bool selected_object_has_texture_mapping_color_data() const;
bool selected_object_has_painted_regions() const;
bool selected_object_has_color_auto_paint_source() const;
void open_obj_vertex_color_mapping_dialog();
void bake_selected_object_image_texture_to_vertex_colors();
void convert_selected_object_vertex_colors_to_texture_mapping_colors();
@@ -215,6 +248,17 @@ private:
bool m_show_slope_auto_paint_overlay = false;
bool m_slope_auto_paint_overlay_positioned = false;
bool m_slope_auto_paint_preview_active = false;
ColorAutoPaintSettings m_color_auto_paint_settings;
ColorAutoPaintSettings m_color_auto_paint_preview_settings;
bool m_show_color_auto_paint_overlay = false;
bool m_color_auto_paint_overlay_positioned = false;
bool m_color_auto_paint_preview_active = false;
bool m_color_auto_paint_preview_update_pending = false;
bool m_color_auto_paint_preview_worker_running = false;
uint64_t m_color_auto_paint_preview_generation = 0;
ColorAutoPaintSettings m_color_auto_paint_requested_settings;
std::thread m_color_auto_paint_preview_thread;
std::vector<std::unique_ptr<TriangleSelectorPatch>> m_color_auto_paint_preview_selectors;
};
class GLGizmoTrueColorPainting : public GLGizmoPainterBase
@@ -241,9 +285,9 @@ protected:
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
std::string get_gizmo_entering_text() const override { return "Entering true color painting"; }
std::string get_gizmo_leaving_text() const override { return "Leaving true color painting"; }
std::string get_action_snapshot_name() const override { return "True color painting editing"; }
std::string get_gizmo_entering_text() const override { return "Entering RGB color painting"; }
std::string get_gizmo_leaving_text() const override { return "Leaving RGB color painting"; }
std::string get_action_snapshot_name() const override { return "RGB color painting editing"; }
private:
enum class ColorInputMode : int

View File

@@ -164,8 +164,9 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const
const Camera& camera = wxGetApp().plater()->get_camera();
const Transform3d& view_matrix = camera.get_view_matrix();
const Transform3d& projection_matrix = camera.get_projection_matrix();
shader->set_uniform("view_model_matrix", view_matrix * trafo_matrix);
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
shader->set_uniform("projection_matrix", projection_matrix);
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
shader->set_uniform("view_normal_matrix", view_normal_matrix);
@@ -178,10 +179,16 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const
if (should_render_triangle_texture_preview())
m_triangle_selectors[mesh_id]->render_texture_preview(trafo_matrix,
view_matrix,
camera.get_projection_matrix(),
projection_matrix,
clp_data.z_range,
clp_data.clp_dataf);
shader->start_using();
render_extra_triangle_overlays(mesh_id,
trafo_matrix,
view_matrix,
projection_matrix,
clp_data.z_range,
clp_data.clp_dataf);
if (is_left_handed)
glsafe(::glFrontFace(GL_CCW));

View File

@@ -254,6 +254,12 @@ protected:
virtual void render_triangles(const Selection& selection) const;
virtual void set_render_triangle_slope_uniforms(GLShaderProgram *shader, const ModelVolume *model_volume, const Matrix3f &normal_matrix) const;
virtual bool should_render_triangle_texture_preview() const { return true; }
virtual void render_extra_triangle_overlays(int mesh_id,
const Transform3d &matrix,
const Transform3d &view_matrix,
const Transform3d &projection_matrix,
const std::array<float, 2> &z_range,
const std::array<float, 4> &clipping_plane) const {}
void render_cursor();
void render_cursor_circle();
void render_cursor_sphere(const Transform3d& trafo) const;

View File

@@ -1489,7 +1489,7 @@ std::string get_name_from_gizmo_etype(GLGizmosManager::EType type)
case GLGizmosManager::EType::MmSegmentation:
return "Color Region Painting";
case GLGizmosManager::EType::TrueColorPainting:
return "True Color Painting";
return "RGB Color Painting";
case GLGizmosManager::EType::ImageProjection:
return "Project image to model surface";
case GLGizmosManager::EType::FuzzySkin:

View File

@@ -78,6 +78,7 @@ struct TexturePreviewSimulationSettings
int generic_solver_lookup_mode = int(TextureMappingZone::GenericSolverClosestMix);
int generic_solver_mode = int(TextureMappingZone::GenericSolverV2);
int generic_solver_mix_model = TextureMappingZone::DefaultGenericSolverMixModel;
float minimum_visibility_offset_factor = 0.f;
std::vector<unsigned int> component_ids;
std::vector<std::array<float, 3>> component_colors;
std::vector<float> component_strength_factors;
@@ -324,6 +325,28 @@ float clamp01(float value)
return std::clamp(value, 0.f, 1.f);
}
float apply_minimum_visibility_offset(float value, float offset)
{
const float safe_value = clamp01(value);
const float safe_offset = clamp01(offset);
if (safe_offset <= k_epsilon)
return safe_value;
if (safe_value <= safe_offset)
return 0.f;
const float denominator = 1.f - safe_offset;
if (denominator <= k_epsilon)
return 0.f;
return clamp01((safe_value - safe_offset) / denominator);
}
void apply_minimum_visibility_offset(std::vector<float> &weights, float offset)
{
if (offset <= k_epsilon)
return;
for (float &weight : weights)
weight = apply_minimum_visibility_offset(weight, offset);
}
float texture_preview_config_float(const char *key, float fallback)
{
if (GUI::wxGetApp().preset_bundle == nullptr)
@@ -1624,6 +1647,7 @@ std::vector<float> component_weights_for_texture_preview(const TexturePreviewSim
for (size_t idx = 0; idx < desired.size() && idx < settings.component_strength_factors.size(); ++idx)
desired[idx] = clamp01(desired[idx] * settings.component_strength_factors[idx]);
apply_minimum_visibility_offset(desired, settings.minimum_visibility_offset_factor);
return desired;
}
@@ -1636,10 +1660,17 @@ std::vector<float> raw_offset_print_width_weights_for_texture_preview(const Text
const float strength = idx < settings.component_strength_factors.size() ? settings.component_strength_factors[idx] : 1.f;
const float minimum = idx < settings.component_minimum_offset_factors.size() ? settings.component_minimum_offset_factors[idx] : 0.f;
const float adjusted_visibility = clamp01(minimum + clamp01(raw_weights[idx]) * strength * (1.f - minimum));
if (settings.minimum_visibility_offset_factor > k_epsilon) {
width_factors[idx] = apply_minimum_visibility_offset(adjusted_visibility, settings.minimum_visibility_offset_factor);
continue;
}
width_factors[idx] = clamp01(settings.raw_offset_base_visibility_factor +
settings.raw_offset_visibility_range_factor * adjusted_visibility);
}
if (settings.minimum_visibility_offset_factor > k_epsilon)
return width_factors;
const auto min_width = std::min_element(width_factors.begin(), width_factors.end());
if (min_width == width_factors.end())
return {};
@@ -1732,6 +1763,13 @@ std::optional<TexturePreviewSimulationSettings> texture_preview_simulation_setti
int(TextureMappingZone::GenericSolverLegacy),
int(TextureMappingZone::GenericSolverV2));
settings.generic_solver_mix_model = TextureMappingZone::DefaultGenericSolverMixModel;
settings.minimum_visibility_offset_factor = zone->minimum_visibility_offset_enabled ?
std::clamp((std::isfinite(zone->minimum_visibility_offset_pct) ?
zone->minimum_visibility_offset_pct :
TextureMappingZone::DefaultMinimumVisibilityOffsetPct) / 100.f,
0.f,
1.f) :
0.f;
settings.component_ids = TextureMappingManager::effective_texture_component_ids(*zone, num_physical, physical_colors);
if (settings.component_ids.empty())
return std::nullopt;
@@ -1792,6 +1830,7 @@ size_t texture_preview_simulation_signature(const ModelVolume &model_volume,
mix(std::hash<int>{}(settings.generic_solver_mix_model));
mix(std::hash<int>{}(int(std::lround(settings.contrast_pct * 100.f))));
mix(std::hash<int>{}(int(std::lround(settings.tone_gamma * 1000.f))));
mix(std::hash<int>{}(int(std::lround(settings.minimum_visibility_offset_factor * 100000.f))));
for (const unsigned int id : settings.component_ids)
mix(std::hash<unsigned int>{}(id));
for (const auto &color : settings.component_colors) {
@@ -1896,7 +1935,10 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig
float activity = 0.f;
for (const float weight : component_weights)
activity = std::max(activity, clamp01(weight));
if (use_raw_offsets && activity <= k_epsilon && !component_weights.empty()) {
if (use_raw_offsets &&
activity <= k_epsilon &&
settings.minimum_visibility_offset_factor <= k_epsilon &&
!component_weights.empty()) {
std::fill(component_weights.begin(), component_weights.end(), 1.f);
activity = 1.f;
}
@@ -3869,6 +3911,8 @@ size_t texture_preview_settings_signature(size_t num_physical, const TextureMapp
signature_mix(std::hash<int>{}(zone.nonlinear_offset_adjustment ? 1 : 0));
signature_mix(std::hash<int>{}(zone.compact_offset_mode ? 1 : 0));
signature_mix(std::hash<int>{}(zone.use_legacy_fixed_color_mode ? 1 : 0));
signature_mix(std::hash<int>{}(zone.minimum_visibility_offset_enabled ? 1 : 0));
signature_mix_float(zone.minimum_visibility_offset_pct, 100.f);
signature_mix(std::hash<int>{}(zone.generic_solver_lookup_mode));
signature_mix(std::hash<int>{}(zone.generic_solver_mode));
signature_mix(std::hash<int>{}(TextureMappingZone::DefaultGenericSolverMixModel));

View File

@@ -1079,6 +1079,8 @@ public:
bool compact_offset_mode,
bool use_legacy_fixed_color_mode,
bool high_speed_image_texture_sampling,
bool minimum_visibility_offset_enabled,
float minimum_visibility_offset_pct,
int generic_solver_lookup_mode,
int generic_solver_mode,
int generic_solver_mix_model,
@@ -1349,6 +1351,27 @@ public:
m_generic_solver_lookup_choice->SetToolTip(_L("Controls how the fallback Generic Solver picks from precomputed filament color mixes."));
generic_solver_row->Add(m_generic_solver_lookup_choice, 1, wxALIGN_CENTER_VERTICAL);
experimental_box->Add(generic_solver_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap);
auto *minimum_visibility_offset_row = new wxBoxSizer(wxHORIZONTAL);
m_minimum_visibility_offset_checkbox = new wxCheckBox(experimental_page, wxID_ANY, _L("Minimum visibility offset"));
m_minimum_visibility_offset_checkbox->SetValue(minimum_visibility_offset_enabled);
minimum_visibility_offset_row->Add(m_minimum_visibility_offset_checkbox, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap);
m_minimum_visibility_offset_spin = new wxSpinCtrl(experimental_page,
wxID_ANY,
wxEmptyString,
wxDefaultPosition,
wxSize(FromDIP(70), -1),
wxSP_ARROW_KEYS | wxALIGN_RIGHT,
0,
100,
std::clamp(int(std::lround(minimum_visibility_offset_pct)), 0, 100));
minimum_visibility_offset_row->Add(m_minimum_visibility_offset_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2);
m_minimum_visibility_offset_units = new wxStaticText(experimental_page, wxID_ANY, _L("%"));
minimum_visibility_offset_row->Add(m_minimum_visibility_offset_units, 0, wxALIGN_CENTER_VERTICAL);
m_minimum_visibility_offset_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) {
update_minimum_visibility_offset_visibility(true);
});
experimental_box->Add(minimum_visibility_offset_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap);
update_minimum_visibility_offset_visibility(false);
experimental_root->Add(experimental_box, 0, wxEXPAND | wxALL, gap);
m_prime_tower_mapping_enabled_checkbox = new wxCheckBox(global_page, wxID_ANY, _L("Enable prime tower image mapping"));
@@ -1531,6 +1554,18 @@ public:
bool compact_offset_mode() const { return m_compact_offset_mode_checkbox && m_compact_offset_mode_checkbox->GetValue(); }
bool use_legacy_fixed_color_mode() const { return m_use_legacy_fixed_color_mode_checkbox && m_use_legacy_fixed_color_mode_checkbox->GetValue(); }
bool high_speed_image_texture_sampling() const { return m_high_speed_image_texture_sampling_checkbox == nullptr || m_high_speed_image_texture_sampling_checkbox->GetValue(); }
bool minimum_visibility_offset_enabled() const
{
return m_minimum_visibility_offset_checkbox && m_minimum_visibility_offset_checkbox->GetValue();
}
float minimum_visibility_offset_pct() const
{
const int value = m_minimum_visibility_offset_spin != nullptr ?
m_minimum_visibility_offset_spin->GetValue() :
int(TextureMappingZone::DefaultMinimumVisibilityOffsetPct);
return float(std::clamp(value, 0, 100));
}
int generic_solver_lookup_mode() const
{
return m_generic_solver_lookup_choice ?
@@ -1791,6 +1826,22 @@ private:
}
}
void update_minimum_visibility_offset_visibility(bool fit_dialog)
{
const bool enabled = m_minimum_visibility_offset_checkbox != nullptr && m_minimum_visibility_offset_checkbox->GetValue();
if (m_minimum_visibility_offset_spin != nullptr)
m_minimum_visibility_offset_spin->Show(enabled);
if (m_minimum_visibility_offset_units != nullptr)
m_minimum_visibility_offset_units->Show(enabled);
if (!fit_dialog)
return;
update_options_book_min_size();
if (GetSizer() != nullptr) {
Layout();
Fit();
}
}
wxChoice *m_options_tab_choice {nullptr};
wxSimplebook *m_options_book {nullptr};
wxChoice *m_texture_mapping_mode_choice {nullptr};
@@ -1809,6 +1860,9 @@ private:
wxCheckBox *m_compact_offset_mode_checkbox {nullptr};
wxCheckBox *m_use_legacy_fixed_color_mode_checkbox {nullptr};
wxCheckBox *m_high_speed_image_texture_sampling_checkbox {nullptr};
wxCheckBox *m_minimum_visibility_offset_checkbox {nullptr};
wxSpinCtrl *m_minimum_visibility_offset_spin {nullptr};
wxStaticText *m_minimum_visibility_offset_units {nullptr};
wxChoice *m_generic_solver_lookup_choice {nullptr};
wxChoice *m_generic_solver_mode_choice {nullptr};
wxChoice *m_generic_solver_mix_model_choice {nullptr};
@@ -5913,6 +5967,8 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
updated.compact_offset_mode,
updated.use_legacy_fixed_color_mode,
updated.high_speed_image_texture_sampling,
updated.minimum_visibility_offset_enabled,
updated.minimum_visibility_offset_pct,
updated.generic_solver_lookup_mode,
updated.generic_solver_mode,
updated.generic_solver_mix_model,
@@ -5945,6 +6001,8 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
updated.compact_offset_mode = dlg.compact_offset_mode();
updated.use_legacy_fixed_color_mode = dlg.use_legacy_fixed_color_mode();
updated.high_speed_image_texture_sampling = dlg.high_speed_image_texture_sampling();
updated.minimum_visibility_offset_enabled = dlg.minimum_visibility_offset_enabled();
updated.minimum_visibility_offset_pct = dlg.minimum_visibility_offset_pct();
updated.generic_solver_lookup_mode = dlg.generic_solver_lookup_mode();
updated.generic_solver_mode = dlg.generic_solver_mode();
updated.generic_solver_mix_model = dlg.generic_solver_mix_model();