mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 14:32:36 +00:00
Drop the degenerate triangle OCCT emits at every filleted corner
A filleted solid arrived on the plate as a broken model: the slicer reported
"8 non-manifold edges" on an 80x50x12 box with r=3 on all edges, and advised
repairing it in another CAD application -- the exact round trip the Design tab
exists to remove. The same box without the fillet committed cleanly.
Measured rather than guessed. Each of the 8 bad edges is degenerate, both
endpoints the same vertex:
open tri=145 edge=1 face=5 v59(3.000000 3.000000 0.000000) v59(3.000000 3.000000 0.000000)
open tri=538 edge=1 face=6 v87(3.000000 3.000000 12.000000) v87(...)
... one per corner, 8 corners
OCCT triangulates a degenerate surface parameterization with a triangle at the
pole; a corner sphere patch has exactly one. Its two pole nodes are distinct in
the per-face triangulation and collapse to a single vertex when the faces are
welded, leaving a zero-area triangle whose v->v edge can never pair with a
neighbour. its_face_neighbors counts it as open, and the field the object panel
prints as "non-manifold edges" is in fact stats.open_edges.
So the geometry was never wrong -- the B-rep volume matches the Steiner formula
for a box dilated by a ball to 0.016%. Only the bookkeeping was.
Dropping those triangles after the weld removes 8 of 3492 and takes open_edges
to 0. Zero area, so nothing about the shape changes. tri_face is compacted in
the same pass, since it must stay index-aligned with the triangle list that the
face picking and per-body colouring both index into.
Guarded by a new [CadDocument] case that asserts open_edges == 0, no degenerate
triangle survives, and both per-triangle maps still match the triangle count.
The existing suite only ever checked B-rep volumes and areas, which is why a
mesh defect this visible went unnoticed: 150 cases / 2049 assertions green on
both forks.
snaporca-agw
This commit is contained in:
@@ -479,7 +479,11 @@ TriangleMesh SketchEngine::tessellate(const TopoDS_Shape& shape,
|
||||
its.indices.reserve(raw.indices.size());
|
||||
its.vertices.reserve(raw.vertices.size() / 2);
|
||||
|
||||
for (const auto& tri : raw.indices) {
|
||||
std::vector<int> kept_face;
|
||||
kept_face.reserve(tri_face.size());
|
||||
|
||||
for (size_t ti = 0; ti < raw.indices.size(); ++ti) {
|
||||
const auto& tri = raw.indices[ti];
|
||||
stl_triangle_vertex_indices new_tri;
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
const stl_vertex& v = raw.vertices[tri[j]];
|
||||
@@ -494,8 +498,18 @@ TriangleMesh SketchEngine::tessellate(const TopoDS_Shape& shape,
|
||||
new_tri[j] = it->second;
|
||||
}
|
||||
}
|
||||
// Drop triangles that welding collapsed to a repeated vertex. OCCT emits one at the
|
||||
// pole of every degenerate surface parameterization — a sphere patch at a filleted
|
||||
// corner has exactly one — and its v->v edge can never pair with a neighbour, so the
|
||||
// mesh reports an open edge per corner and the slicer declares the model non-manifold
|
||||
// and tells the user to repair it elsewhere. The triangle has zero area: removing it
|
||||
// changes no geometry, only the mesh's bookkeeping.
|
||||
if (new_tri[0] == new_tri[1] || new_tri[1] == new_tri[2] || new_tri[0] == new_tri[2])
|
||||
continue;
|
||||
its.indices.push_back(new_tri);
|
||||
kept_face.push_back(tri_face[ti]);
|
||||
}
|
||||
tri_face.swap(kept_face); // tri_face stays index-aligned with its.indices
|
||||
|
||||
return TriangleMesh(std::move(its));
|
||||
}
|
||||
|
||||
@@ -6590,4 +6590,33 @@ TEST_CASE("interference: detects a clash created by a mate", "[CadDocument][inte
|
||||
auto hits = doc.check_interference();
|
||||
REQUIRE(hits.size() == 1);
|
||||
REQUIRE(hits[0].volume > 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
// A filleted solid must reach the plate as a watertight mesh. OCCT emits one degenerate
|
||||
// triangle at the pole of every corner sphere patch; welded, its v->v edge counts as an open
|
||||
// edge and the slicer tells the user to go repair the model in another CAD application --
|
||||
// the exact round trip this feature exists to remove. snaporca-agw.
|
||||
TEST_CASE("CadDocument filleted solid tessellates watertight", "[CadDocument]")
|
||||
{
|
||||
CadDocument doc;
|
||||
SketchProfile sp;
|
||||
sp.points.push_back(Vec2d( 0, 0));
|
||||
sp.points.push_back(Vec2d( 80, 0));
|
||||
sp.points.push_back(Vec2d( 80, 50));
|
||||
sp.points.push_back(Vec2d( 0, 50));
|
||||
sp.closed = true;
|
||||
const int sk = doc.add_sketch_profile(sp, SketchPlane::XY(), "P");
|
||||
doc.add_extrude(sk, 12.0, false, BooleanMode::New, "E");
|
||||
doc.add_fillet(3.0, FaceGroup::All, "F");
|
||||
REQUIRE(doc.recompute());
|
||||
|
||||
CHECK(doc.display_mesh.stats().open_edges == 0);
|
||||
CHECK(its_num_open_edges(doc.display_mesh.its) == 0);
|
||||
// No degenerate triangles survive the weld, and the per-triangle face map stays aligned.
|
||||
size_t degenerate = 0;
|
||||
for (const auto& t : doc.display_mesh.its.indices)
|
||||
if (t[0] == t[1] || t[1] == t[2] || t[0] == t[2]) ++degenerate;
|
||||
REQUIRE(degenerate == 0);
|
||||
REQUIRE(doc.display_tri_face.size() == doc.display_mesh.its.indices.size());
|
||||
REQUIRE(doc.display_tri_body.size() == doc.display_mesh.its.indices.size());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user