Display helical-trimmed faces from imported threads correctly (#1289) (#1300)

A modelled thread as other CAD systems write it to STEP has cylinder faces trimmed by helices
that turn several times around the shaft, and cylinder faces bounded by two circles with no
seam edge. The display planner refused any face whose boundary winds around its surface, so
the whole body fell back to the reference mesher, which drew those faces with triangles across
the solid.

The planner now cuts such a face along a parameter line into one period: every loop and its
copies a period apart are clipped to the strip, and the parts of the cut line inside the face
join the pieces into closed loops, with the seam nodes made once for both sides. The cut face
is meshed by Delaunay with sag refinement, and the sag check runs once more after shaping.
Bodies with a face trimmed by more than one turn take their reference mesh (picking, measuring,
the CPU viewport) from the plan too.

Tests: an ISO M10x1.5 and M12x1.75 thread built from helices, as made and after a STEP round
trip: the display mesh stays within the tolerance of its surfaces and the thread's volume, and
has no degenerate, sliver or flipped triangles.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bherbruckandClaude Opus 5.5 authored and GitHub committed 2026-10-15 00:51:30 -04:00
1 parent 5c2fa3e9ca
commit 8535d229ff
6 files changed
+712 -16

No files matched your search

+11
View File
@@ -1043,6 +1043,17 @@ impl Body {
if let Some(m) = &self.mesh {
return Ok(m.to_mesh());
}
// A face trimmed by a helix (a modelled thread from another CAD system) turns more than
// once around its cylinder; truck meshes it with triangles across the solid (#1289).
// Such a body is meshed from its display plan, which cuts the face along a seam.
if guard("tessellate", || Ok(crate::tess::seam::turns_more_than_once(&self.solid))).unwrap_or(false) {
let turn = (angle.is_finite() && angle > 0.0)
.then(|| crate::tess::size::Turn { angle: angle.max(MIN_DISPLAY_ANGLE), floor: tol * DISPLAY_ANGLE_FLOOR });
match guard("tessellate", || crate::tess::plan(&self.solid, tol, turn)) {
Ok((plan, _)) => return Ok(plan.evaluate()),
Err(e) => log::debug!("helical faces: no display plan ({e}); meshing the reference way"),
}
}
let deflection = if angle.is_finite() && angle > 0.0 {
Deflection { chord: tol, angle: angle.max(MIN_DISPLAY_ANGLE), floor: tol * DISPLAY_ANGLE_FLOOR, rulings: true }
} else {