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.
This commit is contained in:
Hanif Koh
2026-09-23 12:53:58 +08:00
parent 8095904bc2
commit 8b2ae48e4a
2 changed files with 61 additions and 44 deletions
+5 -2
View File
@@ -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
+56 -42
View File
@@ -1535,20 +1535,58 @@ void ViewerImpl::update_shell_bitset()
kept.bottom.assign(cells_count, NO_LAYER);
std::vector<OccupancyGrid> 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<size_t, std::vector<uint32_t>> 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<size_t, std::vector<uint32_t>>;
std::vector<WallMap> 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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(i));
}
}