Sketch projection: a projected face's curves meet at the face's vertices

A face's edges are projected exactly: lines, and arcs read off SMLib's NURBS within the
planar-edge tolerance (Body::planar_edges). An SMLib shell's offset corner is a NURBS whose
arc came out with its centre 2e-6 mm off and its ends up to 6e-7 mm from the lines they meet,
beyond the link's merge tolerance (1e-7): the inner loop of the sensor enclosure's case top
projected as sixteen loose curves, the offset of one found no chain to follow, and the Lid lip
extrude found no profile l13..a16 (the five acceptance scenarios, action_matrix
an_axis_through_a_corner_round_that_a_parameter_moves). Each edge's pieces now end on the
edge's vertices where they lie within the edge's and the reading's tolerance of them (the
vertex is the point both edges share), and an arc whose ends moved takes the centre nearest
its own that is equally far from both, so the sketch keeps both ends on its circle.
Body::planar_edges_tolerance names the reading tolerance planar_edges uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jyviko
2026-10-29 00:03:21 -04:00
committed by Irakli Kourtis
parent 271f0c5de1
commit 983b1ca8f0
2 changed files with 57 additions and 2 deletions
+49 -1
View File
@@ -426,7 +426,10 @@ fn projected_edges(body: &crate::ModelBody, mesh: &Mesh, plane: &Plane, edges: &
let mut out = Vec::new();
for (e, exact) in edges.iter().zip(exact_edges(body, plane, edges)) {
match exact {
Some(g) => out.extend(g),
Some(mut g) => {
at_vertices(body, mesh, plane, *e, &mut g);
out.extend(g);
}
None => {
if let Some(pts) = mesh.edges.get(*e) {
out.extend(fit_world(plane, pts));
@@ -437,6 +440,51 @@ fn projected_edges(body: &crate::ModelBody, mesh: &Mesh, plane: &Plane, edges: &
out
}
/// Edge `e`'s exact pieces `g` with their outer ends on the edge's vertices, where they lie
/// within the edge's and the reading's tolerance of them. An edge's curve need only pass within
/// its tolerance of its vertices, and an arc is read off a NURBS within the planar-edge
/// tolerance: one SMLib made (a shell's offset corner) ended 2e-6 mm from the line it meets, and
/// the two ends, not taken for one point, left the loop open, so an offset of one curve found
/// no chain to follow. The vertex is the point both edges share.
fn at_vertices(body: &crate::ModelBody, mesh: &Mesh, plane: &Plane, e: usize, g: &mut [LinkGeom]) {
let record = body.body.record();
let Some(edge) = record.edges.get(e) else { return };
let Some((first, last)) = mesh.edges.get(e).and_then(|pts| pts.first().zip(pts.last())) else { return };
// The pieces are read within the planar-edge tolerance of the edge's curve, which passes
// within its own tolerance of the vertices.
let tol = edge.vertices.iter().filter_map(|v| record.vertices.get(*v as usize)).map(|v| v.tolerance).fold(edge.tolerance, f64::max)
+ body.body.planar_edges_tolerance();
// Each vertex moves the piece end nearest it (the pieces' order and direction need not be
// the edge's).
for v in [plane.to_local(*first), plane.to_local(*last)] {
let mut best: Option<(f64, &mut Vec2)> = None;
for piece in g.iter_mut() {
if let LinkGeom::Line(a, b) | LinkGeom::Arc { a, b, .. } | LinkGeom::Conic { a, b, .. } = piece {
for p in [a, b] {
let d = p.dist(v);
if d <= tol && best.as_ref().is_none_or(|(bd, _)| d < *bd) {
best = Some((d, p));
}
}
}
}
if let Some((_, p)) = best {
*p = v;
}
}
// An arc is its centre and two ends on one circle: with its ends moved, its centre moves to
// the nearest point equally far from both (on their bisector), or the sketch would move an
// end back off the vertex onto the circle through the other.
for piece in g.iter_mut() {
if let LinkGeom::Arc { c, a, b } = piece
&& let Some(across) = Vec2::new(a.y - b.y, b.x - a.x).normalized()
{
let mid = (*a + *b) * 0.5;
*c = mid + across * (*c - mid).dot(across);
}
}
}
/// A boundary segment as the link geometry that is the same curve.
fn link_geom(s: &Seg2) -> Option<LinkGeom> {
Some(match *s {
+8 -1
View File
@@ -1198,6 +1198,13 @@ impl Body {
.collect())
}
/// How far [`Body::planar_edges`] lets a piece it reads as a line or an arc lie from the
/// edge's curve (a NURBS within this of a circle is that arc), so how far a piece's ends may
/// lie from the edge's vertices.
pub fn planar_edges_tolerance(&self) -> f64 {
(self.size() * 1e-7).max(1e-9) * 10.0
}
/// Edges `indices` (record edge indices) in `plane`'s coordinates, exactly: lines, circular
/// arcs, and free-form curves as the Bézier spans they are, for a sketch to project them.
/// `None` for an edge that doesn't lie in a plane parallel to `plane`, isn't read exactly or
@@ -1206,7 +1213,7 @@ impl Body {
if self.is_mesh() {
return vec![None; indices.len()];
}
let tol = (self.size() * 1e-7).max(1e-9) * 10.0;
let tol = self.planar_edges_tolerance();
indices
.iter()
.map(|i| {