mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-06 03:51:08 +00:00
SMLib export: a face's outer area reads only its own loop's edges
outer_area, which decides whether SMLib's normal is the record's for each face, copied every edge of the body into a record for each face before computing the pcurves of that face's outer loop: O(faces x edges) curve clones, on every operand and every boolean result. pcurves now takes an edge lookup instead of a slice of edge records, so outer_area reads the loop's edges where they are. The pcurve routine gets the same curve, interval, tolerance and trims as before, so every area, face turn, record and numbering is bit for bit what it was. On the level-4 Menger sponge with 3 tools, body_from_smlib_ordered goes from 92.6 s to 1.3 s of self time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+26
-14
@@ -1259,6 +1259,10 @@ fn canonical(parts: Parts, n_vertices: usize, flat: View, tolerances: &Tolerance
|
||||
uses,
|
||||
});
|
||||
}
|
||||
let record_geometry = |e: u32| {
|
||||
let edge = edge_records.get(e as usize)?;
|
||||
Some(EdgeGeometry { curve: edge.nurbs.as_ref().unwrap_or(&edge.curve), interval: edge.interval, tolerance: edge.tolerance })
|
||||
};
|
||||
let mut face_records = Vec::with_capacity(face_order.len());
|
||||
for (fi, &f) in face_order.iter().enumerate() {
|
||||
let face = faces.get(f as usize).ok_or_else(|| invalid("face"))?;
|
||||
@@ -1271,7 +1275,7 @@ fn canonical(parts: Parts, n_vertices: usize, flat: View, tolerances: &Tolerance
|
||||
})
|
||||
.collect::<Res<_>>()?;
|
||||
let mut curves =
|
||||
pcurves(face.nurbs.as_ref().unwrap_or(&face.surface), face.domain, &renumbered, &face.given, &edge_records, tolerance)?.into_iter();
|
||||
pcurves(face.nurbs.as_ref().unwrap_or(&face.surface), face.domain, &renumbered, &face.given, record_geometry, tolerance)?.into_iter();
|
||||
let mut loops = Vec::with_capacity(renumbered.len());
|
||||
for (li, l) in renumbered.iter().enumerate() {
|
||||
loops.push(match l {
|
||||
@@ -1532,15 +1536,23 @@ fn split_touching(faces: &mut [Face], edges: &mut Vec<Edge>, edge_source: &mut V
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// What the pcurve routine needs of one edge: the curve its parameter is on, its interval and its
|
||||
/// tolerance.
|
||||
struct EdgeGeometry<'a> {
|
||||
curve: &'a b::Curve,
|
||||
interval: [f64; 2],
|
||||
tolerance: f64,
|
||||
}
|
||||
|
||||
/// A face's pcurves, edge loop by edge loop: SMLib's own UV trims where `given` (loop by loop as
|
||||
/// `loops`) has them, the plain-record routine on the face's records for the rest; each loop moved
|
||||
/// by whole periods into the face's domain.
|
||||
fn pcurves(
|
||||
/// by whole periods into the face's domain. `edge` looks up an edge of `loops` by its index.
|
||||
fn pcurves<'a>(
|
||||
surface: &b::Surface,
|
||||
domain: [[f64; 2]; 2],
|
||||
loops: &[Loop],
|
||||
given: &[Vec<Option<b::Curve2>>],
|
||||
edges: &[b::Edge],
|
||||
given: &'a [Vec<Option<b::Curve2>>],
|
||||
edge: impl Fn(u32) -> Option<EdgeGeometry<'a>>,
|
||||
tolerance: f64,
|
||||
) -> Res<Vec<Vec<b::Curve2>>> {
|
||||
if given.len() != loops.len() {
|
||||
@@ -1554,13 +1566,13 @@ fn pcurves(
|
||||
}
|
||||
let mut loop_uses = Vec::with_capacity(l.len());
|
||||
for (&(e, forward), given) in l.iter().zip(g) {
|
||||
let edge = edges.get(e as usize).ok_or_else(|| invalid("edge"))?;
|
||||
let geometry = edge(e).ok_or_else(|| invalid("edge"))?;
|
||||
loop_uses.push(b::pcurve::EdgeUse {
|
||||
edge: e,
|
||||
curve: edge.nurbs.as_ref().unwrap_or(&edge.curve),
|
||||
interval: edge.interval,
|
||||
curve: geometry.curve,
|
||||
interval: geometry.interval,
|
||||
forward,
|
||||
tolerance: edge.tolerance,
|
||||
tolerance: geometry.tolerance,
|
||||
given: given.as_ref(),
|
||||
});
|
||||
}
|
||||
@@ -1575,12 +1587,12 @@ fn pcurves(
|
||||
/// (positive: counter-clockwise, the record's convention); 0 where it has no edge loop.
|
||||
fn outer_area(face: &Face, edges: &[Edge], tolerance: f64) -> Res<f64> {
|
||||
let Some(Loop::Edges(outer)) = face.loops.first() else { return Ok(0.0) };
|
||||
let records: Vec<b::Edge> = edges
|
||||
.iter()
|
||||
.map(|e| b::Edge { nurbs: e.nurbs.clone(), curve: e.curve.clone(), interval: e.interval, vertices: e.vertices, tolerance, uses: Vec::new() })
|
||||
.collect();
|
||||
let geometry = |e: u32| {
|
||||
let edge = edges.get(e as usize)?;
|
||||
Some(EdgeGeometry { curve: edge.nurbs.as_ref().unwrap_or(&edge.curve), interval: edge.interval, tolerance })
|
||||
};
|
||||
let given = face.given.first().map(std::slice::from_ref).unwrap_or(&[]);
|
||||
let curves = pcurves(face.nurbs.as_ref().unwrap_or(&face.surface), face.domain, &[Loop::Edges(outer.clone())], given, &records, tolerance)?;
|
||||
let curves = pcurves(face.nurbs.as_ref().unwrap_or(&face.surface), face.domain, &[Loop::Edges(outer.clone())], given, geometry, tolerance)?;
|
||||
let mut pts: Vec<[f64; 2]> = Vec::new();
|
||||
for (pc, &(e, forward)) in curves.first().map(Vec::as_slice).unwrap_or(&[]).iter().zip(outer) {
|
||||
let edge = edges.get(e as usize).ok_or_else(|| invalid("edge"))?;
|
||||
|
||||
@@ -281,6 +281,95 @@ mod native_tests {
|
||||
assert!(native(&shifted(1e-3)).map(|(r, _)| r.validate()).map_or(true, |v| v.is_err()), "a real overshoot is not moved");
|
||||
}
|
||||
|
||||
/// [`outer_area`] as it was: every edge of the body copied into a record for each face.
|
||||
#[allow(clippy::unwrap_used)] // test reference
|
||||
fn outer_area_copying_every_edge(face: &Face, edges: &[Edge], tolerance: f64) -> Res<f64> {
|
||||
let Some(Loop::Edges(outer)) = face.loops.first() else { return Ok(0.0) };
|
||||
let records: Vec<b::Edge> = edges
|
||||
.iter()
|
||||
.map(|e| b::Edge {
|
||||
nurbs: e.nurbs.clone(),
|
||||
curve: e.curve.clone(),
|
||||
interval: e.interval,
|
||||
vertices: e.vertices,
|
||||
tolerance,
|
||||
uses: Vec::new(),
|
||||
})
|
||||
.collect();
|
||||
let lookup = |e: u32| {
|
||||
let edge = records.get(e as usize)?;
|
||||
Some(EdgeGeometry { curve: edge.nurbs.as_ref().unwrap_or(&edge.curve), interval: edge.interval, tolerance: edge.tolerance })
|
||||
};
|
||||
let given = face.given.first().map(std::slice::from_ref).unwrap_or(&[]);
|
||||
let curves = pcurves(face.nurbs.as_ref().unwrap_or(&face.surface), face.domain, &[Loop::Edges(outer.clone())], given, lookup, tolerance)?;
|
||||
let mut pts: Vec<[f64; 2]> = Vec::new();
|
||||
for (pc, &(e, forward)) in curves.first().map(Vec::as_slice).unwrap_or(&[]).iter().zip(outer) {
|
||||
let [a, z] = edges.get(e as usize).unwrap().interval;
|
||||
let mut run: Vec<[f64; 2]> = (0..=32).filter_map(|i| point2(pc, a + (z - a) * f64::from(i) / 32.0)).collect();
|
||||
if !forward {
|
||||
run.reverse();
|
||||
}
|
||||
pts.extend(run);
|
||||
}
|
||||
let n = pts.len();
|
||||
Ok(0.5 * (0..n).map(|k| (pts[k], pts[(k + 1) % n])).map(|(p, q)| p[0] * q[1] - q[0] * p[1]).sum::<f64>())
|
||||
}
|
||||
|
||||
/// A face's outer area reads only its own loop's edges, and is bit for bit what copying every
|
||||
/// edge of the body gave: the export's face turns, and so its records and numbering, are
|
||||
/// unchanged. Each body's record faces are counter-clockwise by convention, so positive.
|
||||
#[test]
|
||||
fn a_faces_outer_area_reads_only_its_own_loop_and_is_unchanged() {
|
||||
let block = Brep::make_box([0.0; 3], [31.0, 29.0, 10.0]).unwrap();
|
||||
let hole = Brep::make_cylinder([12.0, 14.5, -1.0], 5.0, 12.0).unwrap();
|
||||
let rounded = Brep::make_box([0.0; 3], [60.0, 40.0, 20.0]).unwrap().fillet(&[[60.0, 40.0, 10.0]], 0.1, 5.0).unwrap();
|
||||
let mut bodies: Vec<(&str, Brep)> = primitives().into_iter().map(|(name, b, _)| (name, b)).collect();
|
||||
bodies.push(("box minus cylinder", block.boolean(&hole, crate::BooleanOp::Subtract).unwrap().unwrap()));
|
||||
bodies.push(("rounded box", rounded));
|
||||
for (name, body) in bodies {
|
||||
let record = body.to_native().unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
let edges: Vec<Edge> = record
|
||||
.edges
|
||||
.iter()
|
||||
.map(|e| Edge {
|
||||
curve: e.curve.clone(),
|
||||
nurbs: e.nurbs.clone(),
|
||||
interval: e.interval,
|
||||
vertices: e.vertices,
|
||||
reversed: false,
|
||||
trim_map: None,
|
||||
})
|
||||
.collect();
|
||||
for (f, rf) in record.faces.iter().enumerate() {
|
||||
// Without SMLib's trims (the pcurve routine) and with the record's pcurves as them.
|
||||
for with_given in [false, true] {
|
||||
let loops: Vec<Loop> = rf
|
||||
.loops
|
||||
.iter()
|
||||
.map(|l| match l {
|
||||
b::Loop::Edges { edges } => Loop::Edges(edges.iter().map(|u| (u.edge, u.forward)).collect()),
|
||||
b::Loop::Vertex { vertex } => Loop::Vertex(*vertex),
|
||||
})
|
||||
.collect();
|
||||
let given = rf
|
||||
.loops
|
||||
.iter()
|
||||
.map(|l| match l {
|
||||
b::Loop::Edges { edges } => edges.iter().map(|u| with_given.then(|| u.pcurve.clone())).collect(),
|
||||
b::Loop::Vertex { .. } => Vec::new(),
|
||||
})
|
||||
.collect();
|
||||
let records = loops.iter().map(|_| Vec::new()).collect();
|
||||
let face = Face { nurbs: rf.nurbs.clone(), surface: rf.surface.clone(), domain: rf.domain, agrees: true, loops, records, given };
|
||||
let now = outer_area(&face, &edges, record.tolerance).unwrap_or_else(|e| panic!("{name}: face {f}: {e}"));
|
||||
let before = outer_area_copying_every_edge(&face, &edges, record.tolerance).unwrap();
|
||||
assert_eq!(now.to_bits(), before.to_bits(), "{name}: face {f}, trims given {with_given}: {now} != {before}");
|
||||
assert!(now > 0.0, "{name}: face {f}, trims given {with_given}: area {now}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_arrays_are_errors_never_panics() {
|
||||
let good = Brep::make_box([0.0; 3], [1.0, 2.0, 3.0]).unwrap().arrays().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user