Keep Facing Walls on the Shell and Merge Sub-Pixel Layers at Rest

Two cubes standing a few millimetres apart lost the walls that face each
other: the gap-closing that makes sparse infill read as solid bridged the gap
as well, so the footprint became one block and those walls were no longer on
its boundary. The closing now dilates each connected component on its own and
leaves a cell two components both reach empty, so no gap between objects is
ever bridged, and the raw cells are put back after the erosion, which would
otherwise eat into a wall that faces such a gap. On a two-cube fixture with a
2 mm gap every facing wall segment is kept where none was before.

Looking from above, every layer's walls project onto the same outline, yet
each cost a segment. With the shell drawn at rest the walls of one layer in N
are now drawn, N layers tall through a shader uniform so the wall stays solid,
while the exposed top and bottom surfaces of every layer stay so that no step
disappears. N follows the view: how many layers fit in two pixels at the
current zoom and tilt, from 1 side-on and close up to 64 straight down, and it
is left alone while the user is dragging since a change rebuilds the sets. The
same taller drawing applies to the dragging set, which stops it looking
striped, and in shell mode the dragging set keeps the exposed surfaces of the
layers it skips too. The first layer's grown boxes are clamped at the bed.

On the tall fixture with the shell at rest, against 334 ms at full detail:

  default view    37585 segments   85 ms   (was 215797 / 181 ms)
  from above      28682 segments   58 ms

The classification now records which shell segments are exposed from above or
below, in a second bit set alongside the first.
This commit is contained in:
Hanif Koh
2026-09-10 18:09:22 +08:00
parent e0275c4d69
commit f2143d3c91
9 changed files with 265 additions and 34 deletions
+37
View File
@@ -1178,6 +1178,20 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
m_reduced_detail_layer_stride = static_cast<unsigned int>(std::max(1, std::stoi(get_app_config()->get("preview_reduced_detail_layer_stride"))));
m_rest_detail_mode = reduced_detail_mode_from_string(get_app_config()->get("preview_rest_detail_mode"));
apply_reduced_detail_settings();
// the median z step between layers, robust to the first layer and to variable layer height
{
std::vector<float> steps;
for (size_t i = 1; i < m_viewer.get_layers_count(); ++i) {
const float step = m_viewer.get_layer_z(i) - m_viewer.get_layer_z(i - 1);
if (step > 0.0f)
steps.push_back(step);
}
m_typical_layer_height = 0.0f;
if (!steps.empty()) {
std::nth_element(steps.begin(), steps.begin() + steps.size() / 2, steps.end());
m_typical_layer_height = steps[steps.size() / 2];
}
}
// ORCA: darken the layers the preview layer slider is not scrubbed to
m_viewer.set_dim_previous_layers(get_app_config()->get_bool("preview_dim_previous_layers"));
@@ -1632,6 +1646,8 @@ void GCodeViewer::render(int canvas_width, int canvas_height, int right_margin)
if (m_viewer.get_extrusion_roles_count() == 0)
return;
update_rest_layer_stride();
render_toolpaths();
float legend_height = 0.0f;
@@ -1916,9 +1932,30 @@ void GCodeViewer::update_layers_slider_mode()
void GCodeViewer::set_interacting(bool interacting)
{
m_interacting = interacting;
m_viewer.set_reduced_detail(m_reduced_detail_while_dragging && interacting);
}
// ORCA: with the shell drawn at rest, layers thinner than a couple of pixels on screen are merged:
// the walls of one layer in N are drawn N layers tall, which looks the same and costs 1/N. N follows
// the view, from 1 side-on and zoomed in to the cap looking straight down, where the walls are edge-on
// and every layer's exposed surfaces are all there is to see. Changing N rebuilds the sets, so it is
// left alone while the user is dragging.
void GCodeViewer::update_rest_layer_stride()
{
if (m_interacting || m_rest_detail_mode != libvgcode::EReducedDetailMode::ShellOnly || m_typical_layer_height <= 0.0f)
return;
static constexpr double MERGE_BELOW_PX = 2.0;
static constexpr unsigned int MAX_STRIDE = 64;
const Camera& camera = wxGetApp().plater()->get_camera();
const double dz = std::abs(camera.get_dir_forward().z());
const double tilt = std::sqrt(std::max(0.0, 1.0 - dz * dz));
const double layer_px = static_cast<double>(m_typical_layer_height) * camera.get_zoom() * tilt;
const unsigned int stride = (layer_px * MAX_STRIDE <= MERGE_BELOW_PX) ? MAX_STRIDE :
std::clamp(static_cast<unsigned int>(MERGE_BELOW_PX / layer_px), 1u, MAX_STRIDE);
m_viewer.set_rest_layer_stride(stride);
}
// ORCA: libvgcode only builds a reduced set while its mode is not Off, so the preference switch is
// folded into the mode it is given. Every setter goes through here.
void GCodeViewer::apply_reduced_detail_settings()