From 8b2ae48e4ac40eedf35f8037ae8ede8f8400f245 Mon Sep 17 00:00:00 2001 From: Hanif Koh Date: Wed, 23 Sep 2026 12:53:58 +0800 Subject: [PATCH] Keep the Exposed Band of a Sub-Cell Step Beside the Neighbouring Layer's Outer Wall On a shallow surface the band of a layer that the layer above leaves exposed is narrower than a grid cell, so whether the layer is the topmost occupant of any cell flickers from layer to layer and the band's segments, inner walls and solid infill hidden by role, came and went in a dashed ring. That band is always in the same place: the strip just outside the outer wall of the layer above, or of the layer below for the underside of an overhang. The classifier now keeps the outer-wall cell map of the previous and next layers as well as the current one, rotating like the footprints, and keeps any segment whose midpoint lies within a line and a half, or a cell if larger, of a neighbouring layer's outer wall. It can only add segments, so no hole is opened by it; what it over-keeps is the covered strip under the neighbour's wall. --- docs/HLSD/gcode-preview-dragging.md | 7 ++- src/libvgcode/src/ViewerImpl.cpp | 98 ++++++++++++++++------------- 2 files changed, 61 insertions(+), 44 deletions(-) diff --git a/docs/HLSD/gcode-preview-dragging.md b/docs/HLSD/gcode-preview-dragging.md index 600c7f5cd6..a61e90513f 100644 --- a/docs/HLSD/gcode-preview-dragging.md +++ b/docs/HLSD/gcode-preview-dragging.md @@ -80,9 +80,12 @@ wall would otherwise pass by the thousand. Two refinements keep sloped surfaces closed: -- **Near-shell inner walls.** The step between one layer's outer wall and the next is often +- **Near-wall segments.** The step between one layer's outer wall and the next is often narrower than a cell. An inner wall (`Perimeter`) segment whose midpoint lies within a line and - a half of an outer or overhang perimeter of the same layer is kept as well. + a half of an outer or overhang perimeter of the same layer is kept as well. So is any segment, + whatever its role, whose midpoint lies within that reach, or a cell if larger, of an outer wall + of the layer above or below: that strip is the exposed band of the step, which the cell tests + cannot see when it is narrower than a cell. - **Top and bottom visibility.** The same pass records the highest and lowest layer occupying each cell over the whole print. A segment whose layer is the topmost occupant of any cell it crosses is visible from above, and likewise from below with the lowest. These segments are kept diff --git a/src/libvgcode/src/ViewerImpl.cpp b/src/libvgcode/src/ViewerImpl.cpp index b23cb1b0af..bb4b117dd7 100644 --- a/src/libvgcode/src/ViewerImpl.cpp +++ b/src/libvgcode/src/ViewerImpl.cpp @@ -1535,20 +1535,58 @@ void ViewerImpl::update_shell_bitset() kept.bottom.assign(cells_count, NO_LAYER); std::vector footprints(3, OccupancyGrid(nx, ny)); OccupancyGrid shell_cells(nx, ny); - // the outer wall segments of the layer, by every cell they cross - std::unordered_map> outer_walls_by_cell; + // the outer wall segments of a layer, by every cell they cross; kept for the layer below + // and above as well, since the exposed band of a step lies just outside their walls + using WallMap = std::unordered_map>; + std::vector wall_maps(3); + const WallMap no_walls; ClosingScratch scratch; const auto footprint = [&](size_t layer) -> OccupancyGrid& { return footprints[layer % 3]; }; + const auto walls = [&](size_t layer) -> WallMap& { return wall_maps[layer % 3]; }; const auto prepare = [&](size_t layer) { OccupancyGrid& g = footprint(layer); g.clear(); + WallMap& w = walls(layer); + w.clear(); const auto [first, last] = layer_segments(layer); for (size_t i = first; i < last; ++i) { - if (is_drawn_extrusion(i)) - for_each_cell(i, [&](int x, int y) { g.set(x, y); }); + if (!is_drawn_extrusion(i)) + continue; + for_each_cell(i, [&](int x, int y) { g.set(x, y); }); + if (is_outer_wall(m_vertices[i].role)) + for_each_cell(i, [&](int x, int y) { w[cell_index(x, y)].push_back(static_cast(i)); }); } close_gaps(g, radius, scratch); }; + // whether the midpoint of the segment starting at vertex i lies within reach of an outer + // wall segment listed in the map + const auto beside_wall = [&](size_t i, const WallMap& map, float reach) { + const Vec3& a = m_vertices[i].position; + const Vec3& b = m_vertices[i + 1].position; + const float mx = 0.5f * (a[0] + b[0]); + const float my = 0.5f * (a[1] + b[1]); + const auto [cx, cy] = cell_of(mx, my); + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + const auto it = map.find(cell_index(cx + dx, cy + dy)); + if (it == map.end()) + continue; + for (uint32_t o : it->second) { + const Vec3& p = m_vertices[o].position; + const Vec3& q = m_vertices[o + 1].position; + const float ex = q[0] - p[0]; + const float ey = q[1] - p[1]; + const float len2 = ex * ex + ey * ey; + const float t = (len2 > 0.0f) ? std::clamp(((mx - p[0]) * ex + (my - p[1]) * ey) / len2, 0.0f, 1.0f) : 0.0f; + const float ddx = mx - (p[0] + t * ex); + const float ddy = my - (p[1] + t * ey); + if (ddx * ddx + ddy * ddy <= reach * reach) + return true; + } + } + } + return false; + }; if (first_layer > 0) prepare(first_layer - 1); @@ -1583,43 +1621,9 @@ void ViewerImpl::update_shell_bitset() } } const auto [first, last] = layer_segments(layer); - outer_walls_by_cell.clear(); - for (size_t i = first; i < last; ++i) { - const EGCodeExtrusionRole role = m_vertices[i].role; - if (is_drawn_extrusion(i) && is_outer_wall(role)) - for_each_cell(i, [&](int x, int y) { outer_walls_by_cell[cell_index(x, y)].push_back(static_cast(i)); }); - } - // an inner wall segment is the first inner wall when its midpoint lies within a line - // and a half of an outer wall segment of the same layer - const auto beside_outer_wall = [&](size_t i) { - const Vec3& a = m_vertices[i].position; - const Vec3& b = m_vertices[i + 1].position; - const float mx = 0.5f * (a[0] + b[0]); - const float my = 0.5f * (a[1] + b[1]); - const float reach = 1.5f * m_vertices[i].width; - const auto [cx, cy] = cell_of(mx, my); - for (int dy = -1; dy <= 1; ++dy) { - for (int dx = -1; dx <= 1; ++dx) { - const auto it = outer_walls_by_cell.find(cell_index(cx + dx, cy + dy)); - if (it == outer_walls_by_cell.end()) - continue; - for (uint32_t o : it->second) { - const Vec3& p = m_vertices[o].position; - const Vec3& q = m_vertices[o + 1].position; - const float ex = q[0] - p[0]; - const float ey = q[1] - p[1]; - const float len2 = ex * ex + ey * ey; - const float t = (len2 > 0.0f) ? std::clamp(((mx - p[0]) * ex + (my - p[1]) * ey) / len2, 0.0f, 1.0f) : 0.0f; - const float ddx = mx - (p[0] + t * ex); - const float ddy = my - (p[1] + t * ey); - if (ddx * ddx + ddy * ddy <= reach * reach) - return true; - } - } - } - return false; - }; - + const WallMap& walls_below = (layer > 0) ? walls(layer - 1) : no_walls; + const WallMap& walls_cur = walls(layer); + const WallMap& walls_above = (layer + 1 < layers_count) ? walls(layer + 1) : no_walls; for (size_t i = first; i < last; ++i) { if (!is_drawn_extrusion(i)) continue; @@ -1631,7 +1635,17 @@ void ViewerImpl::update_shell_bitset() }); if (2 * on_shell >= total) kept.shell.push_back(static_cast(i)); - if (m_vertices[i].role == EGCodeExtrusionRole::Perimeter && beside_outer_wall(i)) + // an inner wall segment is the first inner wall when its midpoint lies within a line + // and a half of an outer wall of the same layer + const float reach = 1.5f * m_vertices[i].width; + if (m_vertices[i].role == EGCodeExtrusionRole::Perimeter && beside_wall(i, walls_cur, reach)) { + kept.near_shell.push_back(static_cast(i)); + continue; + } + // the exposed band of a step is the strip just outside the outer wall of the layer + // above or below, whatever role fills it; a step narrower than a cell is invisible + // to the grid, so the reach is at least a cell + if (beside_wall(i, walls_above, std::max(reach, cell)) || beside_wall(i, walls_below, std::max(reach, cell))) kept.near_shell.push_back(static_cast(i)); } }