Add 'paint by color' utility. Add minimum visibility offset
This commit is contained in:
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": [],
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// original author: sentientstardust
|
||||
|
||||
#include "ImageMapRawFilamentOffsetAtlas.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// original author: sentientstardust
|
||||
|
||||
#ifndef slic3r_ImageMapRawFilamentOffsetAtlas_hpp_
|
||||
#define slic3r_ImageMapRawFilamentOffsetAtlas_hpp_
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user