Keep the Ghost and Cap the Cut of the Solid Model While Dragging

In the solid model mode the translucent ghost disappeared for the length of every drag, since the scene drew either it or the solid; it is now drawn in every mode, so the part above the cut stays in view.

The cut itself was open: a sparse layer on the cut showed, between its lines, whatever lay below inside the hollow shell, the first layer or the bed, which moved with the camera and read as a flickering shadow; and a face of the model in the cut layer's own plane fought that layer's toolpaths pixel by pixel. The solid now runs from the top of the range's bottom layer to the bottom of its top layer, and a stencil pass caps each cut plane, body by body, with a quad in the body's colour darkened where the cut has opened it: back faces less front faces, counted with the depth test off, which leaves a closed body at zero.
This commit is contained in:
Hanif Koh
2026-10-02 17:27:39 +08:00
parent 12b6d6b9ba
commit 2fb07b328a
4 changed files with 105 additions and 7 deletions
+11 -3
View File
@@ -45,9 +45,17 @@ are the faces the range cuts open, and the top is what the user is looking at.
The preview already loads the sliced objects as shells for its translucent ghost.
`GCodeViewer::render_solid_model()` draws those shells opaque, in their filament colors, with the
`gouraud` shader, whose z range cuts them to the visible layer range. The two toolpath layers of
the reduced set are drawn afterwards and cap the cut with what was really printed there. The
shells hold only the objects, so while this mode is on the prime tower is added from its sliced
`gouraud` shader, whose z range cuts them to the visible layer range: from the top of the range's
bottom layer to the bottom of its top layer, since those two layers are drawn as toolpaths and a
face of the model in either layer's plane would flicker against them. The translucent ghost is
drawn first, as in every other mode, so the part above the cut stays visible while dragging. The
cut itself is capped, one body at a time: a pass counts, per pixel and with the depth test off,
the body's back faces less its front faces into the stencil buffer, which is zero for a closed
body and one where the cut has opened it, and a quad in each cut plane, in the body's colour
darkened, is drawn where it is not zero. Between the lines of a sparse layer on the cut, that quad is what shows, rather than
the first layer or the bed far below moving with the camera. A view through both cut planes of
a body with neither wall in the way is not capped. The two toolpath layers of the reduced set are
drawn afterwards and cap the cut with what was really printed there. The shells hold only the objects, so while this mode is on the prime tower is added from its sliced
mesh, positioned as the print placed it. It is added or removed on its own when the mode changes,
without reloading the objects, keeps its opaque color so that it never appears among the
translucent shells, and stays out of their bounding box. Supports have no mesh and are not shown,
+1
View File
@@ -389,6 +389,7 @@ public:
std::vector<GUI::GLModel> model_per_colors;
bool IsTransparent();
const std::vector<ColorRGBA>& colors() const { return m_colors; }
private:
std::vector<ColorRGBA> m_colors;
+92 -4
View File
@@ -1704,12 +1704,11 @@ void GCodeViewer::reset()
void GCodeViewer::render_scene(int canvas_width, int canvas_height)
{
glsafe(::glEnable(GL_DEPTH_TEST));
render_shells(canvas_width, canvas_height);
// while dragging in the solid model mode, the objects stand in for their toolpaths, cut to the
// visible layer range; the toolpath set then holds only the range's bottom and top layers
if (m_viewer.is_reduced_detail() && solid_model_enabled())
render_solid_model(canvas_width, canvas_height);
else
render_shells(canvas_width, canvas_height);
if (m_viewer.get_extrusion_roles_count() == 0)
return;
@@ -2770,9 +2769,12 @@ void GCodeViewer::render_solid_model(int canvas_width, int canvas_height)
if (shader == nullptr)
return;
// The range's end layers are drawn as toolpaths, so the solid runs from the top of the bottom
// one to the bottom of the top one. A face of the model in either layer's plane would otherwise
// fight the toolpaths for the same pixels and flicker while dragging.
const libvgcode::Interval& layers = m_viewer.get_layers_view_range();
const float z_top = m_viewer.get_layer_z(layers[1]) - m_z_offset + 0.001f;
const float z_bottom = (layers[0] > 0) ? m_viewer.get_layer_z(layers[0] - 1) - m_z_offset - 0.001f : -FLT_MAX;
const float z_top = (layers[1] > 0) ? m_viewer.get_layer_z(layers[1] - 1) - m_z_offset - 0.001f : -FLT_MAX;
const float z_bottom = m_viewer.get_layer_z(layers[0]) - m_z_offset + 0.001f;
std::vector<float> alphas;
alphas.reserve(m_shells.volumes.volumes.size());
@@ -2794,6 +2796,47 @@ void GCodeViewer::render_solid_model(int canvas_width, int canvas_height)
m_shells.volumes.render(GLVolumeCollection::ERenderType::Opaque, false, camera.get_view_matrix(), camera.get_projection_matrix(), {canvas_width, canvas_height});
shader->stop_using();
// The cut faces, one body at a time. A pixel looks into a body where, with the depth test off,
// its back faces outnumber its front faces: zero for a closed body, one where the cut has
// opened it. There a quad in the cut plane, in the body's own colour darkened, stands in for
// the material, so that a sparse layer on the cut shows a solid inside between its lines
// rather than whatever lies further down.
const bool bottom_cut = layers[0] > 0;
// the count needs every face of a body the range does not cut, its bottom included
m_shells.volumes.set_z_range(bottom_cut ? z_bottom : -FLT_MAX, z_top);
glsafe(::glEnable(GL_STENCIL_TEST));
for (const GLVolume* volume : m_shells.volumes.volumes) {
glsafe(::glStencilMask(0xFF));
glsafe(::glClear(GL_STENCIL_BUFFER_BIT));
glsafe(::glStencilFunc(GL_ALWAYS, 0, 0xFF));
glsafe(::glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_DECR_WRAP));
glsafe(::glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INCR_WRAP));
glsafe(::glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE));
glsafe(::glDepthMask(GL_FALSE));
glsafe(::glDisable(GL_DEPTH_TEST));
shader->start_using();
m_shells.volumes.render(GLVolumeCollection::ERenderType::Opaque, true, camera.get_view_matrix(), camera.get_projection_matrix(), {canvas_width, canvas_height},
[volume](const GLVolume& v) { return &v == volume; });
shader->stop_using();
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glDepthMask(GL_TRUE));
glsafe(::glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE));
glsafe(::glStencilFunc(GL_NOTEQUAL, 0, 0xFF));
glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP));
glsafe(::glStencilMask(0x00));
// the tower draws itself in its own colours and leaves the volume's at the default
ColorRGBA inside = volume->color;
if (const auto* tower = dynamic_cast<const GLWipeTowerVolume*>(volume); tower != nullptr && !tower->colors().empty())
inside = tower->colors().front();
inside.r(0.55f * inside.r());
inside.g(0.55f * inside.g());
inside.b(0.55f * inside.b());
inside.a(1.0f);
render_solid_model_caps(z_bottom, z_top, bottom_cut, inside);
}
glsafe(::glStencilMask(0xFF));
glsafe(::glDisable(GL_STENCIL_TEST));
m_shells.volumes.set_z_range(-FLT_MAX, FLT_MAX);
size_t k = 0;
for (GLVolume* volume : m_shells.volumes.volumes) {
@@ -2802,6 +2845,51 @@ void GCodeViewer::render_solid_model(int canvas_width, int canvas_height)
}
}
// A quad over the print in each cut plane, drawn where the stencil says the cut opened a model
void GCodeViewer::render_solid_model_caps(float z_bottom, float z_top, bool bottom_cut, const ColorRGBA& inside)
{
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader == nullptr)
return;
const BoundingBoxf3& box = m_paths_bounding_box;
const float margin = 1.0f;
const Vec2f min(static_cast<float>(box.min.x()) - margin, static_cast<float>(box.min.y()) - margin);
const Vec2f max(static_cast<float>(box.max.x()) + margin, static_cast<float>(box.max.y()) + margin);
auto build = [&](float z) {
GLModel::Geometry data;
data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 };
data.reserve_vertices(4);
data.reserve_indices(6);
data.add_vertex(Vec3f(min.x(), min.y(), z));
data.add_vertex(Vec3f(max.x(), min.y(), z));
data.add_vertex(Vec3f(max.x(), max.y(), z));
data.add_vertex(Vec3f(min.x(), max.y(), z));
data.add_triangle(0, 1, 2);
data.add_triangle(0, 2, 3);
GLModel model;
model.init_from(std::move(data));
return model;
};
const Camera& camera = wxGetApp().plater()->get_camera();
shader->start_using();
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
glsafe(::glDisable(GL_CULL_FACE));
if (z_top > -FLT_MAX) {
GLModel top = build(z_top + m_z_offset);
top.set_color(inside);
top.render();
}
if (bottom_cut) {
GLModel bottom = build(z_bottom + m_z_offset);
bottom.set_color(inside);
bottom.render();
}
glsafe(::glEnable(GL_CULL_FACE));
shader->stop_using();
}
//BBS
void GCodeViewer::render_all_plates_stats(const std::vector<const GCodeProcessorResult*>& gcode_result_list, bool show /*= true*/) const {
if (!show)
+1
View File
@@ -244,6 +244,7 @@ private:
// in the solid model mode, the sliced objects are drawn as solid shapes instead of toolpaths
bool solid_model_enabled() const { return m_reduced_detail_mode == libvgcode::EReducedDetailMode::EndLayersOnly; }
void render_solid_model(int canvas_width, int canvas_height);
void render_solid_model_caps(float z_bottom, float z_top, bool bottom_cut, const ColorRGBA& inside);
// the prime tower is only among the shells for the solid model, so it is added or removed when that changes
void reload_shells_if_solid_model_changed(bool was_enabled);
void update_shell_wipe_tower(const Print& print, bool initialized);