mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 19:01:02 +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.indices.reserve(raw.indices.size());
|
||||||
its.vertices.reserve(raw.vertices.size() / 2);
|
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;
|
stl_triangle_vertex_indices new_tri;
|
||||||
for (int j = 0; j < 3; ++j) {
|
for (int j = 0; j < 3; ++j) {
|
||||||
const stl_vertex& v = raw.vertices[tri[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;
|
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);
|
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));
|
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();
|
auto hits = doc.check_interference();
|
||||||
REQUIRE(hits.size() == 1);
|
REQUIRE(hits.size() == 1);
|
||||||
REQUIRE(hits[0].volume > 1.0);
|
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