From c6035a7c910e1342164b1935073cc3bb59d42326 Mon Sep 17 00:00:00 2001 From: sentientstardust Date: Fri, 22 May 2026 09:19:00 +0100 Subject: [PATCH] Update scene preview when 2D gradient settings are being changed --- .../110/painted_surface_gradient_preview.fs | 42 +++--- .../140/painted_surface_gradient_preview.fs | 42 +++--- src/libslic3r/GCode.cpp | 14 +- src/libslic3r/TextureMappingOffset.cpp | 11 +- src/libslic3r/TextureMappingOffset.hpp | 1 + src/slic3r/GUI/MMUPaintedTexturePreview.cpp | 27 +++- src/slic3r/GUI/Plater.cpp | 134 ++++++++++++++++-- 7 files changed, 208 insertions(+), 63 deletions(-) diff --git a/resources/shaders/110/painted_surface_gradient_preview.fs b/resources/shaders/110/painted_surface_gradient_preview.fs index e8a0c95d4e1..0bd056c7a70 100644 --- a/resources/shaders/110/painted_surface_gradient_preview.fs +++ b/resources/shaders/110/painted_surface_gradient_preview.fs @@ -18,6 +18,7 @@ uniform bool invalid_texture_mapping; uniform PrintVolumeDetection print_volume; uniform int gradient_component_count; +uniform vec3 gradient_base_color; uniform vec3 gradient_component_colors[MAX_GRADIENT_COMPONENTS]; uniform float gradient_distances_mm[MAX_GRADIENT_COMPONENTS]; uniform float gradient_angles_deg[MAX_GRADIENT_COMPONENTS]; @@ -96,7 +97,7 @@ float offset_fade_factor(int fade_mode, float progress01) if (fade_mode == 4) return abs(2.0 * p - 1.0); if (fade_mode == 5) - return 2.0 * p - 1.0; + return 1.0 - 2.0 * p; return 1.0; } @@ -196,17 +197,19 @@ vec3 surface_gradient_color() direction_vec = vec2(1.0, 0.0); float theta_deg = normalize_angle(degrees(atan(direction_vec.y, direction_vec.x)) - rotation_deg); - float fade_factor = abs(offset_fade_factor(gradient_fade_mode, z_progress)); + float signed_fade_factor = offset_fade_factor(gradient_fade_mode, z_progress); + float fade_factor = abs(signed_fade_factor); + float sample_theta_deg = signed_fade_factor < 0.0 ? normalize_angle(theta_deg + 180.0) : theta_deg; float influences[MAX_GRADIENT_COMPONENTS]; - float edge_reaches[MAX_GRADIENT_COMPONENTS]; - float min_reach = 1000000.0; - float max_reach = -1000000.0; + float visibility_weights[MAX_GRADIENT_COMPONENTS]; + float min_visibility = 1.0; + float max_visibility = 0.0; for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) { influences[i] = 0.0; - edge_reaches[i] = 0.0; + visibility_weights[i] = 0.0; if (i < count) - influences[i] = component_angular_influence(i, theta_deg); + influences[i] = component_angular_influence(i, sample_theta_deg); } for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) { @@ -223,24 +226,25 @@ vec3 surface_gradient_color() gradient_max_width_delta_limit_mm, gradient_minimum_offset_factors[i], gradient_strength_factors[i]); - edge_reaches[i] = clamp(gradient_max_width_delta_limit_mm - width_delta_mm, 0.0, gradient_max_width_delta_limit_mm); - min_reach = min(min_reach, edge_reaches[i]); - max_reach = max(max_reach, edge_reaches[i]); + visibility_weights[i] = 1.0 - clamp(width_delta_mm / max(gradient_max_width_delta_limit_mm, EPSILON), 0.0, 1.0); + min_visibility = min(min_visibility, visibility_weights[i]); + max_visibility = max(max_visibility, visibility_weights[i]); } - vec3 mixed_color = vec3(0.0); - float total_weight = 0.0; - float reach_span = max_reach - min_reach; + vec3 target_color = vec3(0.0); + float total_excess_weight = 0.0; for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) { if (i >= count) continue; - float weight = reach_span > EPSILON ? clamp((edge_reaches[i] - min_reach) / reach_span, 0.0, 1.0) : 1.0; - mixed_color += gradient_component_colors[i] * weight; - total_weight += weight; + float weight = max(0.0, visibility_weights[i] - min_visibility); + target_color += gradient_component_colors[i] * weight; + total_excess_weight += weight; } - if (total_weight <= EPSILON) - return gradient_component_colors[0]; - return clamp(mixed_color / total_weight, 0.0, 1.0); + if (total_excess_weight <= EPSILON) + return clamp(gradient_base_color, 0.0, 1.0); + float contrast = clamp(max_visibility - min_visibility, 0.0, 1.0); + target_color = clamp(target_color / total_excess_weight, 0.0, 1.0); + return mix(clamp(gradient_base_color, 0.0, 1.0), target_color, contrast); } float invalid_texture_mapping_checker() diff --git a/resources/shaders/140/painted_surface_gradient_preview.fs b/resources/shaders/140/painted_surface_gradient_preview.fs index 1b99abacba0..d573e979a5f 100644 --- a/resources/shaders/140/painted_surface_gradient_preview.fs +++ b/resources/shaders/140/painted_surface_gradient_preview.fs @@ -18,6 +18,7 @@ uniform bool invalid_texture_mapping; uniform PrintVolumeDetection print_volume; uniform int gradient_component_count; +uniform vec3 gradient_base_color; uniform vec3 gradient_component_colors[MAX_GRADIENT_COMPONENTS]; uniform float gradient_distances_mm[MAX_GRADIENT_COMPONENTS]; uniform float gradient_angles_deg[MAX_GRADIENT_COMPONENTS]; @@ -98,7 +99,7 @@ float offset_fade_factor(int fade_mode, float progress01) if (fade_mode == 4) return abs(2.0 * p - 1.0); if (fade_mode == 5) - return 2.0 * p - 1.0; + return 1.0 - 2.0 * p; return 1.0; } @@ -198,17 +199,19 @@ vec3 surface_gradient_color() direction_vec = vec2(1.0, 0.0); float theta_deg = normalize_angle(degrees(atan(direction_vec.y, direction_vec.x)) - rotation_deg); - float fade_factor = abs(offset_fade_factor(gradient_fade_mode, z_progress)); + float signed_fade_factor = offset_fade_factor(gradient_fade_mode, z_progress); + float fade_factor = abs(signed_fade_factor); + float sample_theta_deg = signed_fade_factor < 0.0 ? normalize_angle(theta_deg + 180.0) : theta_deg; float influences[MAX_GRADIENT_COMPONENTS]; - float edge_reaches[MAX_GRADIENT_COMPONENTS]; - float min_reach = 1000000.0; - float max_reach = -1000000.0; + float visibility_weights[MAX_GRADIENT_COMPONENTS]; + float min_visibility = 1.0; + float max_visibility = 0.0; for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) { influences[i] = 0.0; - edge_reaches[i] = 0.0; + visibility_weights[i] = 0.0; if (i < count) - influences[i] = component_angular_influence(i, theta_deg); + influences[i] = component_angular_influence(i, sample_theta_deg); } for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) { @@ -225,24 +228,25 @@ vec3 surface_gradient_color() gradient_max_width_delta_limit_mm, gradient_minimum_offset_factors[i], gradient_strength_factors[i]); - edge_reaches[i] = clamp(gradient_max_width_delta_limit_mm - width_delta_mm, 0.0, gradient_max_width_delta_limit_mm); - min_reach = min(min_reach, edge_reaches[i]); - max_reach = max(max_reach, edge_reaches[i]); + visibility_weights[i] = 1.0 - clamp(width_delta_mm / max(gradient_max_width_delta_limit_mm, EPSILON), 0.0, 1.0); + min_visibility = min(min_visibility, visibility_weights[i]); + max_visibility = max(max_visibility, visibility_weights[i]); } - vec3 mixed_color = vec3(0.0); - float total_weight = 0.0; - float reach_span = max_reach - min_reach; + vec3 target_color = vec3(0.0); + float total_excess_weight = 0.0; for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) { if (i >= count) continue; - float weight = reach_span > EPSILON ? clamp((edge_reaches[i] - min_reach) / reach_span, 0.0, 1.0) : 1.0; - mixed_color += gradient_component_colors[i] * weight; - total_weight += weight; + float weight = max(0.0, visibility_weights[i] - min_visibility); + target_color += gradient_component_colors[i] * weight; + total_excess_weight += weight; } - if (total_weight <= EPSILON) - return gradient_component_colors[0]; - return clamp(mixed_color / total_weight, 0.0, 1.0); + if (total_excess_weight <= EPSILON) + return clamp(gradient_base_color, 0.0, 1.0); + float contrast = clamp(max_visibility - min_visibility, 0.0, 1.0); + target_color = clamp(target_color / total_excess_weight, 0.0, 1.0); + return mix(clamp(gradient_base_color, 0.0, 1.0), target_color, contrast); } float invalid_texture_mapping_checker() diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index d227274a4d7..0390f39c933 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -9800,12 +9800,15 @@ std::optional GCode::texture_mapping_seam_hiding_hint(const const float theta_deg = normalize_angle_deg_for_gcode(float(Geometry::rad2deg(std::atan2(theta_direction_y, theta_direction_x)))); + const float sample_theta_deg = state.signed_fade_factor < 0.f ? + normalize_angle_deg_for_gcode(theta_deg + 180.f) : + theta_deg; float raw_inset_mm = 0.f; for (size_t i = 0; i < component_ids.size(); ++i) { if (i == state.active_component_idx) continue; const float influence = component_angular_influence_for_gcode(component_ids[i], - theta_deg, + sample_theta_deg, component_ids, rotated_angles); raw_inset_mm += distances_mm[i] * influence; @@ -9839,7 +9842,7 @@ std::optional GCode::texture_mapping_seam_hiding_hint(const return std::nullopt; const float centerline_shift_mm = base_centerline_shift_mm + 0.5f * width_delta_mm; - const float centerline_outward_shift_mm = state.signed_fade_factor >= 0.f ? -centerline_shift_mm : centerline_shift_mm; + const float centerline_outward_shift_mm = -centerline_shift_mm; const float outer_offset_mm = centerline_outward_shift_mm + 0.5f * target_width_mm; if (!std::isfinite(outer_offset_mm)) return std::nullopt; @@ -10765,7 +10768,6 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, bool object_center_mode { false }; Point object_center; float inset_strength_reference_mm { 0.f }; - float signed_fade_factor { 1.f }; float max_width_delta_mm { 0.f }; float base_outer_width_mm { 0.4f }; float dither_pitch_mm { 0.08f }; @@ -11002,7 +11004,6 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, outer_wall_gradient_dynamic_ctx.object_center_mode = object_center_mode; outer_wall_gradient_dynamic_ctx.object_center = object_center; outer_wall_gradient_dynamic_ctx.inset_strength_reference_mm = max_allowed_distance_mm; - outer_wall_gradient_dynamic_ctx.signed_fade_factor = signed_fade_factor; outer_wall_gradient_dynamic_ctx.max_width_delta_mm = effective_max_width_delta_mm; outer_wall_gradient_dynamic_ctx.base_outer_width_mm = base_outer_width_mm; outer_wall_gradient_dynamic_ctx.dither_pitch_mm = offset_context->dither_pitch_mm; @@ -11027,7 +11028,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if (std::abs(centerline_shift_mm) <= EPSILON) return true; - const bool reverse_shift = (signed_fade_factor < 0.f) != (centerline_shift_mm < 0.f); + const bool reverse_shift = centerline_shift_mm < 0.f; const double shift_x = reverse_shift ? -mod.shift_unit_x : mod.shift_unit_x; const double shift_y = reverse_shift ? -mod.shift_unit_y : mod.shift_unit_y; const double shift_scaled = scale_(double(std::abs(centerline_shift_mm))); @@ -11805,8 +11806,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if (std::abs(centerline_shift_mm) > EPSILON) { const double inward_x = -outward_x; const double inward_y = -outward_y; - const bool reverse_shift = - (outer_wall_gradient_dynamic_ctx.signed_fade_factor < 0.f) != (centerline_shift_mm < 0.f); + const bool reverse_shift = centerline_shift_mm < 0.f; const double shift_x = reverse_shift ? -inward_x : inward_x; const double shift_y = reverse_shift ? -inward_y : inward_y; const double shift_scaled = scale_(double(std::abs(centerline_shift_mm))); diff --git a/src/libslic3r/TextureMappingOffset.cpp b/src/libslic3r/TextureMappingOffset.cpp index c19812fa878..bbf1711a924 100644 --- a/src/libslic3r/TextureMappingOffset.cpp +++ b/src/libslic3r/TextureMappingOffset.cpp @@ -2512,7 +2512,7 @@ float texture_mapping_offset_fade_factor(int fade_mode, float progress01) case int(TextureMappingZone::OffsetFadeOutUp): return 1.f - p; case int(TextureMappingZone::OffsetFadeInOut): return 1.f - std::abs(2.f * p - 1.f); case int(TextureMappingZone::OffsetFadeOutIn): return std::abs(2.f * p - 1.f); - case int(TextureMappingZone::OffsetFadeOutInReversed): return 2.f * p - 1.f; + case int(TextureMappingZone::OffsetFadeOutInReversed): return 1.f - 2.f * p; default: return 1.f; } } @@ -2789,7 +2789,8 @@ std::optional build_texture_mapping_offset_context_ for (float &a : rotated_angles) a = normalize_texture_mapping_offset_angle_deg(a + rotation_deg); - const float fade_factor = std::abs(texture_mapping_offset_fade_factor(zone.offset_fade_mode, z_progress)); + const float signed_fade_factor = texture_mapping_offset_fade_factor(zone.offset_fade_mode, z_progress); + const float fade_factor = std::abs(signed_fade_factor); if (fade_factor <= EPSILON) return std::nullopt; @@ -2812,6 +2813,7 @@ std::optional build_texture_mapping_offset_context_ context.rotated_angles = std::move(rotated_angles); context.weight_field = std::move(weight_field); context.inset_strength_reference_mm = max_allowed_distance_mm; + context.signed_fade_factor = signed_fade_factor; context.fade_factor = fade_factor; context.max_width_delta_mm = max_width_delta_limit_mm; context.dither_pitch_mm = dither_pitch_mm; @@ -2882,6 +2884,9 @@ float texture_mapping_offset_surface_inset_mm(const TextureMappingOffsetContext const float theta_deg = normalize_texture_mapping_offset_angle_deg(float(Geometry::rad2deg(std::atan2(theta_direction_y, theta_direction_x)))); + const float sample_theta_deg = context.signed_fade_factor < 0.f ? + normalize_texture_mapping_offset_angle_deg(theta_deg + 180.f) : + theta_deg; float raw_inset_mm = 0.f; const size_t component_count = std::min(context.component_ids.size(), context.component_distances_mm.size()); for (size_t i = 0; i < component_count; ++i) { @@ -2889,7 +2894,7 @@ float texture_mapping_offset_surface_inset_mm(const TextureMappingOffsetContext continue; const float influence = component_angular_influence(context.component_ids[i], - theta_deg, + sample_theta_deg, context.component_ids, context.rotated_angles); raw_inset_mm += context.component_distances_mm[i] * influence; diff --git a/src/libslic3r/TextureMappingOffset.hpp b/src/libslic3r/TextureMappingOffset.hpp index 15eaed75380..95d9329fb10 100644 --- a/src/libslic3r/TextureMappingOffset.hpp +++ b/src/libslic3r/TextureMappingOffset.hpp @@ -68,6 +68,7 @@ struct TextureMappingOffsetContext { std::vector rotated_angles; TextureMappingOffsetWeightField weight_field; float inset_strength_reference_mm { 0.f }; + float signed_fade_factor { 1.f }; float fade_factor { 1.f }; float max_width_delta_mm { 0.f }; float dither_pitch_mm { TextureMappingZone::DefaultDitheringResolutionMm }; diff --git a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp index daceea78610..77c68c0f3b4 100644 --- a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp +++ b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp @@ -99,6 +99,7 @@ struct SurfaceGradientPreviewSettings { std::vector component_ids; std::vector> component_colors; + std::array base_color { 0.f, 0.f, 0.f }; std::vector distances_mm; std::vector angles_deg; std::vector strength_factors; @@ -4145,17 +4146,16 @@ std::optional surface_gradient_preview_settings_ settings.strength_factors.emplace_back(std::clamp((std::isfinite(strength_pct) ? strength_pct : 100.f) / 100.f, 0.f, 1.f)); settings.minimum_offset_factors.emplace_back(std::clamp((std::isfinite(minimum_offset_pct) ? minimum_offset_pct : 0.f) / 100.f, 0.f, 1.f)); } + const std::vector base_weights(settings.component_colors.size(), 1.f); + const ColorRGBA base_color = blend_component_colors(settings.component_colors, base_weights); + settings.base_color = { base_color.r(), base_color.g(), base_color.b() }; const float max_distance_mm = TextureMappingManager::max_component_surface_offset_mm(); settings.max_component_distance_mm = max_distance_mm; settings.distances_mm = TextureMappingManager::effective_offset_distances(zone, settings.component_ids.size()); - bool has_nonzero_distance = false; for (float &distance_mm : settings.distances_mm) { distance_mm = std::clamp(distance_mm, 0.f, max_distance_mm); - has_nonzero_distance = has_nonzero_distance || distance_mm > k_epsilon; } - if (!has_nonzero_distance) - return std::nullopt; settings.angles_deg = TextureMappingManager::effective_offset_angles(zone, settings.component_ids.size()); settings.angle_mode = std::clamp(zone.offset_angle_mode, @@ -4177,8 +4177,9 @@ std::optional surface_gradient_preview_settings_ const float global_strength_factor = std::clamp(surface_gradient_preview_config_float("texture_mapping_outer_wall_gradient_global_strength", 100.f) / 100.f, 0.f, 1.f); settings.max_width_delta_limit_mm = std::min((base_outer_width_mm - min_outer_width_mm) * global_strength_factor, 2.f * max_distance_mm); - if (!std::isfinite(settings.max_width_delta_limit_mm) || settings.max_width_delta_limit_mm <= k_epsilon) + if (!std::isfinite(settings.max_width_delta_limit_mm)) return std::nullopt; + settings.max_width_delta_limit_mm = std::max(0.f, settings.max_width_delta_limit_mm); const indexed_triangle_set &its = model_volume.mesh().its; if (its.vertices.empty()) @@ -4325,6 +4326,7 @@ void set_surface_gradient_preview_uniforms(GLShaderProgram &shader, const Surfac const size_t count = settings == nullptr ? size_t(0) : std::min(settings->component_colors.size(), k_surface_gradient_preview_max_components); shader.set_uniform("gradient_component_count", int(count)); + shader.set_uniform("gradient_base_color", settings != nullptr ? settings->base_color : std::array{ 0.f, 0.f, 0.f }); shader.set_uniform("gradient_max_component_distance_mm", settings != nullptr ? settings->max_component_distance_mm : 0.f); shader.set_uniform("gradient_max_width_delta_limit_mm", settings != nullptr ? settings->max_width_delta_limit_mm : 0.f); shader.set_uniform("gradient_angle_mode", settings != nullptr ? settings->angle_mode : int(TextureMappingZone::OffsetAngleObjectCenter)); @@ -4840,6 +4842,17 @@ static bool texture_preview_zone_uses_halftone_model(const TextureMappingZone &z method == int(TextureMappingZone::DitheringHalftoneIncreasedDetail)); } +static bool texture_preview_gradient_zone_uses_model(const TextureMappingZone &zone, size_t num_physical) +{ + if (!zone.enabled || zone.deleted || !zone.is_2d_gradient()) + return false; + if (TextureMappingManager::selected_component_ids(zone, num_physical).size() < 2) + return true; + + const std::vector component_ids = decode_surface_gradient_component_ids(zone, num_physical); + return component_ids.size() >= 2; +} + static void texture_preview_mix_zone_baked_model_settings(size_t &signature, const TextureMappingZone &zone, size_t num_physical) @@ -4958,7 +4971,9 @@ size_t texture_preview_model_settings_signature(size_t num_physical, signature_mix(std::hash{}(halftone_model ? 1 : 0)); signature_mix(std::hash{}(simulated_vertex_color_model ? 1 : 0)); - if (gradient_zone || halftone_model || simulated_vertex_color_model) + if (gradient_zone) + signature_mix(std::hash{}(texture_preview_gradient_zone_uses_model(zone, num_physical) ? 1 : 0)); + else if (halftone_model || simulated_vertex_color_model) texture_preview_mix_zone_baked_model_settings(signature, zone, num_physical); } return signature; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 4788161409d..827f99c69b5 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,7 @@ #include #include #include +#include #ifdef _WIN32 #include #include @@ -1092,9 +1094,12 @@ public: const TextureMappingZone &zone, size_t num_physical, const std::vector &nozzle_diameters, - const std::vector &palette) + const std::vector &palette, + std::function live_preview = {}) : wxDialog(parent, wxID_ANY, _L("Offset Gradient"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) , m_zone(zone) + , m_live_preview(std::move(live_preview)) + , m_live_preview_timer(this) { const int gap = FromDIP(8); const int compact_gap = std::max(FromDIP(3), gap / 2); @@ -1133,7 +1138,7 @@ public: mode_row->Add(m_basic_angle_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, compact_gap); m_basic_angle_spin = new wxSpinCtrlDouble(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(84), -1), wxSP_ARROW_KEYS | wxALIGN_RIGHT | wxTE_PROCESS_ENTER, 0.0, 360.0, - initial_angles.empty() ? 0.0 : std::clamp(double(initial_angles.front()), 0.0, 360.0), 1.0); + initial_angles.empty() ? 0.0 : std::clamp(double(initial_angles.front()), 0.0, 360.0), 10.0); m_basic_angle_spin->SetDigits(1); mode_row->Add(m_basic_angle_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, compact_gap); m_basic_angle_units = new wxStaticText(this, wxID_ANY, _L("deg")); @@ -1211,7 +1216,7 @@ public: row->Add(new wxStaticText(this, wxID_ANY, _L("%")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); wxSpinCtrlDouble *angle_spin = new wxSpinCtrlDouble(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(84), -1), wxSP_ARROW_KEYS | wxALIGN_RIGHT | wxTE_PROCESS_ENTER, 0.0, 360.0, - i < initial_angles.size() ? std::clamp(double(initial_angles[i]), 0.0, 360.0) : 0.0, 1.0); + i < initial_angles.size() ? std::clamp(double(initial_angles[i]), 0.0, 360.0) : 0.0, 10.0); angle_spin->SetDigits(1); row->Add(angle_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, compact_gap); row->Add(new wxStaticText(this, wxID_ANY, _L("deg")), 0, wxALIGN_CENTER_VERTICAL); @@ -1234,10 +1239,18 @@ public: EndModal(wxID_OK); }); - m_mode_choice->Bind(wxEVT_CHOICE, [this](wxCommandEvent &) { update_advanced_visibility(); }); + m_mode_choice->Bind(wxEVT_CHOICE, [this](wxCommandEvent &) { + update_advanced_visibility(); + emit_live_preview(); + }); + bind_live_preview_events(); SetSizerAndFit(root); SetMinSize(wxSize(FromDIP(560), std::max(GetSize().GetHeight(), FromDIP(420)))); update_advanced_visibility(); + if (m_live_preview) { + Bind(wxEVT_TIMER, [this](wxTimerEvent &) { emit_live_preview(); }); + m_live_preview_timer.Start(100); + } } bool apply_to(TextureMappingZone &out) @@ -1303,7 +1316,67 @@ private: Fit(); } + void bind_live_preview_events() + { + auto bind_spin = [this](wxSpinCtrlDouble *spin, double step, double min_value, double max_value) { + if (spin == nullptr) + return; + spin->Bind(wxEVT_SPINCTRLDOUBLE, [this](wxSpinDoubleEvent &) { emit_live_preview(); }); + spin->Bind(wxEVT_TEXT, [this](wxCommandEvent &) { emit_live_preview(); }); + spin->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent &) { emit_live_preview(); }); + spin->Bind(wxEVT_CHAR_HOOK, [this, spin, step, min_value, max_value](wxKeyEvent &evt) { + const int key = evt.GetKeyCode(); + if (key != WXK_UP && key != WXK_NUMPAD_UP && key != WXK_DOWN && key != WXK_NUMPAD_DOWN) { + evt.Skip(); + return; + } + const double direction = (key == WXK_UP || key == WXK_NUMPAD_UP) ? 1.0 : -1.0; + spin->SetValue(std::clamp(spin->GetValue() + direction * step, min_value, max_value)); + emit_live_preview(); + }); + spin->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent &evt) { + emit_live_preview(); + evt.Skip(); + }); + }; + auto bind_check = [this](wxCheckBox *check) { + if (check != nullptr) + check->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) { emit_live_preview(); }); + }; + auto bind_choice = [this](wxChoice *choice) { + if (choice != nullptr) + choice->Bind(wxEVT_CHOICE, [this](wxCommandEvent &) { emit_live_preview(); }); + }; + + bind_spin(m_basic_distance_spin, 1.0, 0.0, 100.0); + bind_spin(m_basic_angle_spin, 10.0, 0.0, 360.0); + bind_spin(m_rotations_spin, 0.1, -64.0, 64.0); + bind_spin(m_repeats_spin, 0.1, 1.0, 64.0); + for (wxSpinCtrlDouble *spin : m_distance_spins) + bind_spin(spin, 1.0, 0.0, 100.0); + for (wxSpinCtrlDouble *spin : m_angle_spins) + bind_spin(spin, 10.0, 0.0, 360.0); + bind_check(m_rotation_enabled); + bind_check(m_reverse_repeats); + bind_check(m_clockwise); + bind_choice(m_fade_choice); + bind_choice(m_angle_mode_choice); + } + + void emit_live_preview() + { + if (!m_live_preview) + return; + TextureMappingZone preview; + if (!apply_to(preview)) + return; + preview.surface_pattern = int(TextureMappingZone::Gradient2D); + m_live_preview(preview); + } + TextureMappingZone m_zone; + std::function m_live_preview; + wxTimer m_live_preview_timer; std::vector m_component_ids; wxChoice *m_mode_choice {nullptr}; wxSpinCtrlDouble *m_basic_distance_spin {nullptr}; @@ -6316,7 +6389,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) gap); auto *button_row = new wxBoxSizer(wxHORIZONTAL); - auto *offset_btn = new wxButton(editor, wxID_ANY, _L("Offset Gradient Settings")); + auto *offset_btn = new wxButton(editor, wxID_ANY, _L("2D Gradient Settings")); auto *advanced_btn = new wxButton(editor, wxID_ANY, _L("Advanced Options")); button_row->Add(offset_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); button_row->Add(advanced_btn, 0, wxALIGN_CENTER_VERTICAL); @@ -6466,13 +6539,56 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) apply_controls(); evt.Skip(); }); - offset_btn->Bind(wxEVT_BUTTON, [this, zone_index, mgr_ptr, palette, nozzle_diameters, apply_zone](wxCommandEvent &) { + offset_btn->Bind(wxEVT_BUTTON, [this, + zone_index, + mgr_ptr, + palette, + nozzle_diameters, + apply_zone, + set_config_string](wxCommandEvent &) { if (zone_index >= mgr_ptr->zones().size()) return; - TextureMappingZone updated = mgr_ptr->zones()[zone_index]; - TextureMappingOffsetGradientDialog dlg(this, updated, palette.size(), nozzle_diameters, palette); - if (dlg.ShowModal() != wxID_OK || !dlg.apply_to(updated)) + const TextureMappingZone original = mgr_ptr->zones()[zone_index]; + const std::string original_serialized = mgr_ptr->serialize_entries(); + TextureMappingZone updated = original; + bool live_preview_applied = false; + auto redraw_live_preview = [this]() { + if (p->plater == nullptr) + return; + auto mark_canvas_dirty = [](GLCanvas3D *canvas) { + if (canvas == nullptr) + return; + canvas->set_as_dirty(); + canvas->request_extra_frame(); + if (wxGLCanvas *wx_canvas = canvas->get_wxglcanvas(); wx_canvas != nullptr && wx_canvas->IsShownOnScreen()) + canvas->render(); + }; + mark_canvas_dirty(p->plater->get_view3D_canvas3D()); + mark_canvas_dirty(p->plater->get_assmeble_canvas3D()); + }; + auto live_preview = [zone_index, mgr_ptr, &live_preview_applied, redraw_live_preview, set_config_string](const TextureMappingZone &preview) { + if (zone_index >= mgr_ptr->zones().size()) + return; + if (mgr_ptr->zones()[zone_index] == preview) + return; + mgr_ptr->zones()[zone_index] = preview; + set_config_string("texture_mapping_definitions", mgr_ptr->serialize_entries()); + live_preview_applied = true; + redraw_live_preview(); + }; + TextureMappingOffsetGradientDialog dlg(this, updated, palette.size(), nozzle_diameters, palette, live_preview); + const int dialog_result = dlg.ShowModal(); + if (zone_index >= mgr_ptr->zones().size()) return; + if (live_preview_applied) + mgr_ptr->zones()[zone_index] = original; + if (live_preview_applied) + set_config_string("texture_mapping_definitions", original_serialized); + if (dialog_result != wxID_OK || !dlg.apply_to(updated)) { + if (live_preview_applied) + redraw_live_preview(); + return; + } updated.surface_pattern = int(TextureMappingZone::Gradient2D); apply_zone(std::move(updated)); CallAfter([this]() { update_texture_mapping_panel(false); });