From c6a338f35f975ac9a2bdcdbde6754d720e1fd170 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Mon, 12 Oct 2026 23:18:38 +0800 Subject: [PATCH] Fix stale paint highlights and textures in the texture displacement gizmo Erase whole model, Select whole model and Capture current view update the highlight at once. A stroke released outside the 3D view ends on the next mouse move, in every paint gizmo, instead of leaving the brush in its pressed colour. The layer textures no longer show an image that was replaced. --- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 9 +++++++ .../GUI/Gizmos/GLGizmoTextureDisplacement.cpp | 26 +++++++++---------- .../GUI/Gizmos/GLGizmoTextureDisplacement.hpp | 23 ++++++++-------- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 4bae9a28fe..388b04312d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -1036,6 +1036,11 @@ bool GLGizmoPainterBase::on_mouse(const wxMouseEvent &mouse_event) Vec2d mouse_pos = mouse_coord.cast(); if (mouse_event.Moving()) { + // No button is down, yet a stroke is still open: its release went elsewhere, as a paint drag does not + // capture the mouse. End the stroke as that release would have. + if (m_button_down != Button::None) + gizmo_event(m_button_down == Button::Left ? SLAGizmoEventType::LeftUp : SLAGizmoEventType::RightUp, mouse_pos, + mouse_event.ShiftDown(), mouse_event.AltDown(), false); gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false); return false; } @@ -1205,6 +1210,10 @@ void GLGizmoPainterBase::on_set_state() m_old_mo_id = -1; //m_iva.release_geometry(); m_triangle_selectors.clear(); + // A stroke still open goes with the selectors. Its state must not carry into the next session, where the + // first move would end it as if released and the first press would paint a line from its last position. + m_button_down = Button::None; + m_last_mouse_click = Vec2d::Zero(); //Camera& camera = wxGetApp().plater()->get_camera(); //camera.look_at(camera.get_position(), m_previous_target, Vec3d::UnitZ()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp index 45ff19480f..048b005c69 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp @@ -1520,7 +1520,7 @@ void GLGizmoTextureDisplacement::render_shaded_preview_mesh() // is box-filtered down to 128 px for a ~48 px row, and feeding it to the shader cost the preview // three quarters of the height map's detail - and, since height_tex_texel is derived from it, also // flattened the shading gradient and made the parallax march skip itself at angles where it should - // run. Cached on the image_data pointer + smoothing, so no PNG is decoded per frame. + // run. Cached per image_data and smoothing, so no PNG is decoded per frame. GLTexture *tex = get_layer_height_texture(*layer); if (tex == nullptr || tex->get_width() <= 0 || tex->get_height() <= 0) return; @@ -4038,8 +4038,8 @@ void GLGizmoTextureDisplacement::remove_texture_layer(int slot) if (slot >= 0 && slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS)) { mv->texture_displacement_facet(slot).reset(); m_thumbnails[size_t(slot)].reset(); - m_thumbnail_source[size_t(slot)] = nullptr; - m_layer_expanded[size_t(slot)] = false; + m_thumbnail_source[size_t(slot)].reset(); + m_layer_expanded[size_t(slot)] = false; } if (m_picker_slot == slot) m_picker_slot = -1; @@ -4213,7 +4213,7 @@ int GLGizmoTextureDisplacement::select_visible_faces(const std::array selector.reset(); for (unsigned i : unobscured) selector.set_facet(int(front_facing[i]), EnforcerBlockerType::ENFORCER); - selector.request_update_render_data(); + selector.request_update_render_data(true); update_model_object(); m_parent.set_as_dirty(); @@ -4236,7 +4236,7 @@ void GLGizmoTextureDisplacement::select_whole_model() const size_t facet_count = v->mesh().its.indices.size(); for (size_t i = 0; i < facet_count; ++i) m_triangle_selectors[idx]->set_facet(int(i), EnforcerBlockerType::ENFORCER); - m_triangle_selectors[idx]->request_update_render_data(); + m_triangle_selectors[idx]->request_update_render_data(true); } update_model_object(); m_parent.set_as_dirty(); @@ -5342,7 +5342,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_thumbnail(const TextureDisplace return nullptr; const size_t slot = size_t(layer.slot); - if (m_thumbnails[slot] && m_thumbnail_source[slot] == layer.image_data.get() && + if (m_thumbnails[slot] && m_thumbnail_source[slot].lock() == layer.image_data && m_thumbnail_smoothing[slot] == layer.smoothing) return m_thumbnails[slot].get(); @@ -5355,7 +5355,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_thumbnail(const TextureDisplace return nullptr; m_thumbnails[slot] = std::move(texture); - m_thumbnail_source[slot] = layer.image_data.get(); + m_thumbnail_source[slot] = layer.image_data; m_thumbnail_smoothing[slot] = layer.smoothing; return m_thumbnails[slot].get(); } @@ -5364,17 +5364,17 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_color_texture(const TextureDisp { if (layer.empty() || !layer.color_enabled) return nullptr; - if (m_color_tex && m_color_tex_source == layer.image_data.get() && m_color_tex_smoothing == layer.smoothing) + if (m_color_tex && m_color_tex_source.lock() == layer.image_data && m_color_tex_smoothing == layer.smoothing) return m_color_tex.get(); std::unique_ptr texture = upload_color_texture(decode_height_texture(layer), HEIGHT_TEX_MAX_PX); if (!texture) { m_color_tex.reset(); - m_color_tex_source = nullptr; + m_color_tex_source.reset(); return nullptr; } m_color_tex = std::move(texture); - m_color_tex_source = layer.image_data.get(); + m_color_tex_source = layer.image_data; m_color_tex_smoothing = layer.smoothing; return m_color_tex.get(); } @@ -5386,7 +5386,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_height_texture(const TextureDis // One slot, not one per layer: the preview shader only ever shades the *active* layer, so a single // full-resolution upload is enough and the VRAM cost stays at one texture rather than eight. - if (m_height_tex && m_height_tex_source == layer.image_data.get() && m_height_tex_smoothing == layer.smoothing) + if (m_height_tex && m_height_tex_source.lock() == layer.image_data && m_height_tex_smoothing == layer.smoothing) return m_height_tex.get(); std::unique_ptr texture = upload_height_thumbnail(decode_height_texture(layer), HEIGHT_TEX_MAX_PX); @@ -5394,7 +5394,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_height_texture(const TextureDis return nullptr; m_height_tex = std::move(texture); - m_height_tex_source = layer.image_data.get(); + m_height_tex_source = layer.image_data; m_height_tex_smoothing = layer.smoothing; return m_height_tex.get(); } @@ -6281,7 +6281,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float if (v->is_model_part()) { ++idx; m_triangle_selectors[idx]->reset(); - m_triangle_selectors[idx]->request_update_render_data(); + m_triangle_selectors[idx]->request_update_render_data(true); } update_model_object(); m_parent.set_as_dirty(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp index a520fba426..631254313c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp @@ -924,25 +924,26 @@ private: bool m_preview_job_pending = false; void queue_preview_job(); - // Per-slot GPU thumbnail cache for the layer list panel, keyed by the image_data pointer that - // was current the last time each thumbnail was built (see get_layer_thumbnail()). - std::array, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnails; - std::array m_thumbnail_source{}; + // Per-slot GPU thumbnail cache for the layer list panel, each keyed by the image_data it was built from + // (see get_layer_thumbnail()). Held weakly rather than by address, as decode_height_texture()'s cache is: + // an image freed and replaced by another at the same address must not pass for it. + std::array, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnails; + std::array>, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_source; // The smoothing each cached thumbnail was built at, so a smoothing change re-uploads it. - std::array m_thumbnail_smoothing{}; + std::array m_thumbnail_smoothing{}; // Full-resolution height texture for the preview shader, keyed the same way (see // get_layer_height_texture()). A smoothing change re-uploads it, so the fast preview shows the // blur the bake will apply. - std::unique_ptr m_height_tex; - const void *m_height_tex_source = nullptr; - float m_height_tex_smoothing = -1.f; + std::unique_ptr m_height_tex; + std::weak_ptr> m_height_tex_source; + float m_height_tex_smoothing = -1.f; // The same, for the layer's *colour*, which the fast preview quantizes per fragment so it shows // the image at texel resolution rather than at the mesh's. Null for a grayscale texture. - std::unique_ptr m_color_tex; - const void *m_color_tex_source = nullptr; - float m_color_tex_smoothing = -1.f; + std::unique_ptr m_color_tex; + std::weak_ptr> m_color_tex_source; + float m_color_tex_smoothing = -1.f; // Library textures the picker has shown at least once, keyed by file path (see LibraryTexture). std::map m_library_textures;