From ce9eb057476842e50568876a018875c905680ef6 Mon Sep 17 00:00:00 2001 From: Lam Wei Lun Date: Tue, 20 Oct 2026 13:01:48 +0800 Subject: [PATCH] Unify the below-plate view across the painting gizmos The painting gizmos clip the selected instance at the plate, so a sinking object stopped at the bed with nothing to show how it continues below it, and the plate disappeared along with the bed mesh. Give every paint tool the same view of that region: - Draw the part of the selected instance below the plate at 50% opacity with depth writes off, so it reads as the object going through the plate. - Keep the plate and its grid while the bed mesh is hidden, instead of losing every reference plane in the scene. - Mark the plate intersection with a 0.4 mm outline rather than an opaque filled cap, which covered the model and hid the ghost from above. - Gate the bed mesh and the below-plate pass on the same predicate. Texture displacement derives from GLGizmoPainterBase like the other painters but was missing from the gizmo type list in is_bed_visible(), so it kept its bed. - Add GLVolume::simple_render()'s ban_color_volume so the ghost keeps its alpha for volumes with MMU segmentation facets, whose sub-models carry their own opaque filament colours, and stop gating the ghost on is_active: the colour painter marks the whole instance inactive and draws it in its own pass. --- src/slic3r/GUI/3DScene.cpp | 4 +- src/slic3r/GUI/3DScene.hpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 90 ++++++++++++++++++++++-- src/slic3r/GUI/GLCanvas3D.hpp | 2 + src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 9 ++- 5 files changed, 95 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index acd2e4f41c..3895275a33 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -700,7 +700,7 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size) } //BBS add render for simple case -void GLVolume::simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_objects, std::vector& extruder_colors, bool ban_light) +void GLVolume::simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_objects, std::vector& extruder_colors, bool ban_light, bool ban_color_volume) { if (this->is_left_handed()) glFrontFace(GL_CW); @@ -734,7 +734,7 @@ void GLVolume::simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_obj } } while (0); - if (color_volume && !picking) { + if (color_volume && !picking && !ban_color_volume) { const bool brighten_selected = selected && !disabled && !force_native_color && !force_neutral_color; // when force_transparent, we need to keep the alpha diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 7a19b20a24..d7ff4168c8 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -370,7 +370,7 @@ public: virtual void render_with_outline(const GUI::Size& cnv_size); //BBS: add simple render function for thumbnail - void simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_objects, std::vector& extruder_colors, bool ban_light =false); + void simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_objects, std::vector& extruder_colors, bool ban_light =false, bool ban_color_volume = false); void set_bounding_boxes_as_dirty() { m_transformed_bounding_box.reset(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 95944a8da5..d77ed07095 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2804,7 +2804,9 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size) _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), show_axes); } m_frame_profiler.mark("bed"); - if (show_bed) //BBS: add outline logic + // Exception: the painting gizmos hide the bed mesh on purpose and draw the plate instead, + // so there the plate list is the only bed reference in the scene. + if (show_bed || _is_below_plate_view_active()) //BBS: add outline logic _render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid); if (m_design_canvas && show_bed) // Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid. @@ -2816,6 +2818,7 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size) _render_shadows(camera.get_view_matrix(), camera.get_projection_matrix()); m_frame_profiler.mark("shadows"); _render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running()); + _render_below_plate_volumes(); m_frame_profiler.mark("objects"); _render_sla_slices(); _render_selection(); @@ -8840,6 +8843,84 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_axes); } +bool GLCanvas3D::_is_below_plate_view_active() const +{ + return dynamic_cast(m_gizmos.get_current()) != nullptr && m_gizmos.is_hiding_instances(); +} + +// The painting gizmo clips the selected object at the plate, so the part under the plate is not drawn. +// This pass draws that part of the selected instance again at 50% opacity, depth writes off. +void GLCanvas3D::_render_below_plate_volumes() +{ + if (!_is_below_plate_view_active() || m_volumes.empty()) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); + if (shader == nullptr) + return; + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + const Transform3d& proj_matrix = camera.get_projection_matrix(); + const int object_idx = m_selection.get_object_idx(); + const int instance_idx = m_selection.get_instance_idx(); + + // Depth test stays on so whatever is already in the depth buffer hides the ghost; depth writes + // are off and back faces are culled, so each pixel blends only the near surface once. + const GLboolean cull_face = ::glIsEnabled(GL_CULL_FACE); + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glCullFace(GL_BACK)); + glsafe(::glDepthMask(GL_FALSE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + shader->start_using(); + // Keep z <= SINKING_Z_THRESHOLD, the complement of the clip the painting gizmo applies. + shader->set_uniform("z_range", std::array{ -FLT_MAX, float(SINKING_Z_THRESHOLD) }); + shader->set_uniform("clipping_plane", m_volumes.get_clipping_plane()); + shader->set_uniform("use_color_clip_plane", false); + shader->set_uniform("is_outline", false); + shader->set_uniform("slope.actived", false); + shader->set_uniform("print_volume.type", -1); + shader->set_uniform("extruder_printable_heights", std::array{ 0.0f, 0.0f, 0.0f }); + shader->set_uniform("shadow_intensity", 0.0f); + + // simple_render() needs these to reach the plain surface; with ban_color_volume it ignores both. + ModelObjectPtrs& model_objects = wxGetApp().model().objects; + std::vector extruder_colors = wxGetApp().plater()->get_extruders_colors(); + + for (GLVolume* volume : m_volumes.volumes) { + // is_active cannot gate this pass: the color painter marks the whole instance inactive and + // draws it in its own gizmo pass. The instance and print-part checks below are what pick + // this ghost out of the scene. + if (volume->object_idx() != object_idx || volume->instance_idx() != instance_idx || + volume->is_modifier || volume->is_wipe_tower || volume->volume_idx() < 0) + continue; + + volume->set_render_color(); + ColorRGBA color = volume->render_color; + color.a(0.5f); + volume->model.set_color(color); + + const Transform3d model_matrix = volume->world_matrix(); + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + shader->set_uniform("projection_matrix", proj_matrix); + shader->set_uniform("volume_world_matrix", model_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + // Painted facets would send render() through the MMU segmentation sub-models, which carry + // their own opaque filament colours; the ghost draws the plain surface so the alpha holds. + volume->simple_render(shader, model_objects, extruder_colors, false, true); + } + shader->stop_using(); + + glsafe(::glDisable(GL_BLEND)); + glsafe(::glDepthMask(GL_TRUE)); + if (cull_face == GL_FALSE) + glsafe(::glDisable(GL_CULL_FACE)); +} + bool GLCanvas3D::_hides_inactive_plates() const { return m_canvas_type == ECanvasType::CanvasView3D && !m_design_canvas && wxGetApp().focus_mode(); @@ -11968,9 +12049,10 @@ bool GLCanvas3D::is_bed_visible() const if (!m_main_toolbar.is_enabled()) return true; - const auto type = m_gizmos.get_current_type(); - return type != GLGizmosManager::FdmSupports && type != GLGizmosManager::Seam && - type != GLGizmosManager::MmSegmentation && type != GLGizmosManager::FuzzySkin; + // The painting gizmos clip the selected instance at the plate (see InstancesHider), so the bed + // mesh would sit in front of the part below it. They draw the plate and its grid instead (see + // _render_scene) and the below-plate part as a translucent ghost. + return !_is_below_plate_view_active(); } Vec3d GLCanvas3D::get_camera_pan_anchor(Camera& camera, ECameraNavigationType navigation_type, diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index e483859824..5acbba39a1 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1552,6 +1552,8 @@ private: void _render_imex_ghost_tooltip(); void _render_section_view_caps(); void _render_wireframe_overlay(); + bool _is_below_plate_view_active() const; + void _render_below_plate_volumes(); bool _is_xray_view_active() const; void _render_xray_volumes(); //BBS: GUI refactor: add canvas size as parameters diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index e391022d6c..9cabc2e3fe 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -7,7 +7,6 @@ #include -#include #include "slic3r/GUI/Selection.hpp" #include #include "libslic3r/Model.hpp" @@ -174,6 +173,9 @@ void InstancesHider::on_update() m_clippers.emplace_back(new MeshClipper); m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), z_min)); m_clippers.back()->set_mesh(mesh->its); + // Outline only: a filled cap would read as a surface the object does not have, and it + // covers the model below it. 0.4 mm matches the cut gizmo's contour width. + m_clippers.back()->set_behaviour(false, 0.4); } m_old_meshes = meshes; } @@ -214,10 +216,7 @@ void InstancesHider::render_cut() const else clipper->set_limiting_plane(ClippingPlane::ClipsNothing()); - glsafe(::glPushAttrib(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_DEPTH_TEST)); - clipper->render_cut(mv->is_model_part() ? ColorRGBA(0.8f, 0.3f, 0.0f, 1.0f) : color_from_model_volume(*mv)); - glsafe(::glPopAttrib()); + clipper->render_contour(mv->is_model_part() ? ColorRGBA(0.8f, 0.3f, 0.0f, 1.0f) : color_from_model_volume(*mv)); ++clipper_id; }