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.
This commit is contained in:
SoftFever
2026-10-12 23:18:38 +08:00
parent 7149728219
commit c6a338f35f
3 changed files with 34 additions and 24 deletions
@@ -1036,6 +1036,11 @@ bool GLGizmoPainterBase::on_mouse(const wxMouseEvent &mouse_event)
Vec2d mouse_pos = mouse_coord.cast<double>();
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());
@@ -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<float, 12>
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<GLTexture> 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<GLTexture> 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();
@@ -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<std::unique_ptr<GLTexture>, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnails;
std::array<const void *, TEXTURE_DISPLACEMENT_MAX_LAYERS> 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<std::unique_ptr<GLTexture>, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnails;
std::array<std::weak_ptr<const std::vector<unsigned char>>, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_source;
// The smoothing each cached thumbnail was built at, so a smoothing change re-uploads it.
std::array<float, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_smoothing{};
std::array<float, TEXTURE_DISPLACEMENT_MAX_LAYERS> 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<GLTexture> m_height_tex;
const void *m_height_tex_source = nullptr;
float m_height_tex_smoothing = -1.f;
std::unique_ptr<GLTexture> m_height_tex;
std::weak_ptr<const std::vector<unsigned char>> 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<GLTexture> m_color_tex;
const void *m_color_tex_source = nullptr;
float m_color_tex_smoothing = -1.f;
std::unique_ptr<GLTexture> m_color_tex;
std::weak_ptr<const std::vector<unsigned char>> 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<std::string, LibraryTexture> m_library_textures;