mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-05 03:25:58 +00:00
step: imported faces and edges on their NURBS forms
`form::edge` is the one rule sew and the STEP reader share: a circle about a revolved face's axis is labelled on that face's frame, and a circle or ellipse edge gets its form. The STEP reader now gives every imported edge its label and form before any pcurve, and every cylinder, cone, sphere and torus face its form over the extent its label's pcurves give; the pcurves, the outer-loop choice and the turn-over test then run on the forms, and the record carries them. With the pcurve fit turned off, the STEP and IGES circle cases pass (8 of the 9 import cases that needed it; 3208's ellipse on an extruded spline surface remains). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BnQawySUFx57iaJZq7z1Fm
This commit is contained in:
@@ -195,3 +195,29 @@ pub fn on_frame(c: &Curve, [t0, t1]: [f64; 2], frame: &Frame, tol: f64) -> Optio
|
||||
let relabel = Frame { origin: [o.x, o.y, o.z], x_axis: [nx.x, nx.y, nx.z], y_axis: [ny.x, ny.y, ny.z] };
|
||||
Some((Curve::Circle { frame: relabel, radius: *radius }, [t0 + shift, t1 + shift]))
|
||||
}
|
||||
|
||||
/// The frame of a cylinder, cone, sphere or torus (`None` for another surface).
|
||||
pub fn frame_of(s: &Surface) -> Option<&Frame> {
|
||||
match s {
|
||||
Surface::Cylinder { frame, .. } | Surface::Cone { frame, .. } | Surface::Sphere { frame, .. } | Surface::Torus { frame, .. } => Some(frame),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// An edge's label and NURBS form: a circle about the axis of one of `frames` (the cylinder,
|
||||
/// cone, sphere and torus faces it bounds) is labelled on that frame ([`on_frame`]), so its form
|
||||
/// lies on that face's quarter grid; a circle or ellipse then has its form ([`curve`]). The label
|
||||
/// with its interval, and the form with its own; `None` for an arc of more than one turn.
|
||||
pub fn edge<'a>(
|
||||
c: &Curve,
|
||||
interval: [f64; 2],
|
||||
frames: impl IntoIterator<Item = &'a Frame>,
|
||||
tol: f64,
|
||||
) -> Option<(Curve, [f64; 2], Option<(Curve, [f64; 2])>)> {
|
||||
let (label, iv) = frames.into_iter().find_map(|f| on_frame(c, interval, f, tol)).unwrap_or_else(|| (c.clone(), interval));
|
||||
let form = match label {
|
||||
Curve::Circle { .. } | Curve::Ellipse { .. } => Some(curve(&label, iv)?),
|
||||
_ => None,
|
||||
};
|
||||
Some((label, iv, form))
|
||||
}
|
||||
|
||||
+20
-27
@@ -1362,38 +1362,31 @@ pub(crate) fn assemble(parts: Parts, solid: bool) -> Result<Assembled> {
|
||||
}
|
||||
g
|
||||
};
|
||||
// A circle on a cylinder, cone, sphere or torus about its axis is labelled on that face's
|
||||
// frame, so its form lies on the face's quarter grid and its pcurve there is a straight line.
|
||||
let mut frame_of: Vec<Option<&b::Frame>> = vec![None; edges.len()];
|
||||
// Each edge's label and NURBS form (`form::edge`): a circle about a revolved face's axis on
|
||||
// that face's frame, so its form lies on the face's quarter grid; an edge the caller gave a
|
||||
// pcurve for keeps the parameter that pcurve is on.
|
||||
let mut frames_of: Vec<Vec<&b::Frame>> = vec![Vec::new(); edges.len()];
|
||||
for f in &faces {
|
||||
let (b::Surface::Cylinder { frame, .. }
|
||||
| b::Surface::Cone { frame, .. }
|
||||
| b::Surface::Sphere { frame, .. }
|
||||
| b::Surface::Torus { frame, .. }) = &f.surface
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(frame) = b::form::frame_of(&f.surface) else { continue };
|
||||
for u in f.loops.iter().flatten() {
|
||||
if let Some(slot) = frame_of.get_mut(u.edge as usize).filter(|s| s.is_none()) {
|
||||
*slot = Some(frame);
|
||||
if let Some(slot) = frames_of.get_mut(u.edge as usize) {
|
||||
slot.push(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
let labels: Vec<(b::Curve, [f64; 2])> = edges
|
||||
.iter()
|
||||
.zip(&frame_of)
|
||||
.map(|(e, frame)| frame.and_then(|f| b::form::on_frame(&e.curve, e.interval, f, tol)).unwrap_or_else(|| (e.curve.clone(), e.interval)))
|
||||
.collect();
|
||||
let forms: Vec<Option<(b::Curve, [f64; 2])>> = labels
|
||||
.iter()
|
||||
.zip(&given_on)
|
||||
.map(|((curve, interval), given)| match curve {
|
||||
b::Curve::Circle { .. } | b::Curve::Ellipse { .. } if !given => {
|
||||
b::form::curve(curve, *interval).ok_or_else(|| fail("an arc of more than one turn".into())).map(Some)
|
||||
}
|
||||
_ => Ok(None),
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
let mut labels: Vec<(b::Curve, [f64; 2])> = Vec::with_capacity(edges.len());
|
||||
let mut forms: Vec<Option<(b::Curve, [f64; 2])>> = Vec::with_capacity(edges.len());
|
||||
for ((e, frames), given) in edges.iter().zip(&frames_of).zip(&given_on) {
|
||||
if *given {
|
||||
labels.push((e.curve.clone(), e.interval));
|
||||
forms.push(None);
|
||||
continue;
|
||||
}
|
||||
let (label, interval, form) =
|
||||
b::form::edge(&e.curve, e.interval, frames.iter().copied(), tol).ok_or_else(|| fail("an arc of more than one turn".into()))?;
|
||||
labels.push((label, interval));
|
||||
forms.push(form);
|
||||
}
|
||||
// Each face on its NURBS form: a cylinder, cone, sphere or torus over the extent its label's
|
||||
// pcurves give, then every pcurve again on the forms, exactly.
|
||||
let mut pcs: Vec<Vec<Vec<Curve2>>> = Vec::with_capacity(nf);
|
||||
|
||||
+70
-22
@@ -322,6 +322,8 @@ struct EdgeB {
|
||||
interval: [f64; 2],
|
||||
vertices: [u32; 2],
|
||||
tolerance: f64,
|
||||
/// The curve's NURBS form and its interval there (`form::edge`), set before the pcurves.
|
||||
form: Option<(BCurve, [f64; 2])>,
|
||||
}
|
||||
|
||||
enum LoopB {
|
||||
@@ -433,7 +435,7 @@ impl<'b, 'a> Builder<'b, 'a> {
|
||||
self.raise_vertex(v, gap, id)?;
|
||||
}
|
||||
let i = index(self.edges.len())?;
|
||||
self.edges.push(EdgeB { curve: mapped.curve, interval: mapped.interval, vertices, tolerance: self.tol });
|
||||
self.edges.push(EdgeB { curve: mapped.curve, interval: mapped.interval, vertices, tolerance: self.tol, form: None });
|
||||
self.edge_ids.insert(id, (i, mapped.with_step));
|
||||
Ok((i, mapped.with_step))
|
||||
}
|
||||
@@ -667,14 +669,36 @@ impl<'b, 'a> Builder<'b, 'a> {
|
||||
if self.sense_mismatches > 0 {
|
||||
notes.push(format!("{} edges run against their same_sense (read by their vertices)", self.sense_mismatches));
|
||||
}
|
||||
// Each edge's label and NURBS form: a circle about a revolved face's axis labelled on that
|
||||
// face's frame, so its form lies on the face's quarter grid.
|
||||
let mut frames: Vec<Vec<brep::Frame>> = vec![Vec::new(); self.edges.len()];
|
||||
for f in &self.faces {
|
||||
let Some(frame) = brep::form::frame_of(&f.surface) else { continue };
|
||||
for l in &f.loops {
|
||||
if let LoopB::Edges(es) = l {
|
||||
for (e, _) in es {
|
||||
if let Some(slot) = frames.get_mut(*e as usize) {
|
||||
slot.push(frame.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (edge, fr) in self.edges.iter_mut().zip(&frames) {
|
||||
let (label, interval, form) =
|
||||
brep::form::edge(&edge.curve, edge.interval, fr, self.tol).ok_or_else(|| unsupported("an arc of more than one turn"))?;
|
||||
(edge.curve, edge.interval, edge.form) = (label, interval, form);
|
||||
}
|
||||
// Each face's pcurves; a face whose loops disagree with its same_sense is turned over.
|
||||
let mut pcs: Vec<Vec<Vec<Curve2>>> = Vec::with_capacity(self.faces.len());
|
||||
let mut face_forms: Vec<Option<BSurface>> = Vec::with_capacity(self.faces.len());
|
||||
let mut domains = Vec::with_capacity(self.faces.len());
|
||||
let mut turned = vec![false; self.faces.len()];
|
||||
let (mut failed, mut first_failure, mut loosened) = (0usize, None, 0usize);
|
||||
for fi in 0..self.faces.len() {
|
||||
match self.face_pcurves(fi) {
|
||||
Ok((pc, domain, flipped, tol)) => {
|
||||
Ok((pc, domain, flipped, tol, form)) => {
|
||||
face_forms.push(form);
|
||||
if flipped && let Some(t) = turned.get_mut(fi) {
|
||||
*t = true;
|
||||
}
|
||||
@@ -689,6 +713,7 @@ impl<'b, 'a> Builder<'b, 'a> {
|
||||
first_failure.get_or_insert_with(|| format!("face #{}: {m}", self.faces.get(fi).map_or(0, |f| f.step)));
|
||||
pcs.push(Vec::new());
|
||||
domains.push([[0.0; 2]; 2]);
|
||||
face_forms.push(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -841,10 +866,14 @@ impl<'b, 'a> Builder<'b, 'a> {
|
||||
let mut edges = Vec::with_capacity(edge_order.len());
|
||||
for &e in &edge_order {
|
||||
let edge = self.edges.get(e as usize).ok_or("edge reference")?;
|
||||
let (nurbs, interval) = match &edge.form {
|
||||
Some((c, i)) => (Some(c.clone()), *i),
|
||||
None => (None, edge.interval),
|
||||
};
|
||||
edges.push(Edge {
|
||||
nurbs: None,
|
||||
nurbs,
|
||||
curve: edge.curve.clone(),
|
||||
interval: edge.interval,
|
||||
interval,
|
||||
vertices: [vmap(edge.vertices[0])?, vmap(edge.vertices[1])?],
|
||||
tolerance: edge.tolerance,
|
||||
uses: uses.get(e as usize).cloned().unwrap_or_default(),
|
||||
@@ -884,7 +913,8 @@ impl<'b, 'a> Builder<'b, 'a> {
|
||||
}
|
||||
}
|
||||
let domain = *domains.get(f as usize).ok_or("face reference")?;
|
||||
faces.push(Face { nurbs: None, surface: face.surface.clone(), domain, loops });
|
||||
let nurbs = face_forms.get(f as usize).cloned().flatten();
|
||||
faces.push(Face { nurbs, surface: face.surface.clone(), domain, loops });
|
||||
}
|
||||
let mut out_shells = Vec::with_capacity(shells.len());
|
||||
for s in &shells {
|
||||
@@ -927,9 +957,9 @@ impl<'b, 'a> Builder<'b, 'a> {
|
||||
/// marks none (or marks one that another loop encloses), and turns the face over when its
|
||||
/// outer loop runs clockwise about the natural normal on a surface where that cannot be a
|
||||
/// face's outer boundary.
|
||||
fn face_pcurves(&mut self, fi: usize) -> R<(Vec<Vec<Curve2>>, [[f64; 2]; 2], bool, f64)> {
|
||||
fn face_pcurves(&mut self, fi: usize) -> R<(Vec<Vec<Curve2>>, [[f64; 2]; 2], bool, f64, Option<BSurface>)> {
|
||||
let face = self.faces.get(fi).ok_or("face reference")?;
|
||||
let surface = face.surface.clone();
|
||||
let label = face.surface.clone();
|
||||
let edge_loops: Vec<Vec<(u32, bool)>> = face
|
||||
.loops
|
||||
.iter()
|
||||
@@ -941,24 +971,42 @@ impl<'b, 'a> Builder<'b, 'a> {
|
||||
// Which loop index in `face.loops` each edge loop is.
|
||||
let positions: Vec<usize> = face.loops.iter().enumerate().filter(|(_, l)| matches!(l, LoopB::Edges(_))).map(|(i, _)| i).collect();
|
||||
let mut tol = self.tol;
|
||||
let pcs = loop {
|
||||
let mut uses = Vec::with_capacity(edge_loops.len());
|
||||
for l in &edge_loops {
|
||||
let mut u = Vec::with_capacity(l.len());
|
||||
for &(e, forward) in l {
|
||||
let edge = self.edges.get(e as usize).ok_or("edge reference")?;
|
||||
u.push(EdgeUse { edge: e, curve: &edge.curve, interval: edge.interval, forward, tolerance: 0.0, given: None });
|
||||
// The face's pcurves on `on`, each edge on its NURBS form when `formed`.
|
||||
let pcurves_on = |on: &BSurface, formed: bool, tol: &mut f64| -> R<Vec<Vec<Curve2>>> {
|
||||
loop {
|
||||
let mut uses = Vec::with_capacity(edge_loops.len());
|
||||
for l in &edge_loops {
|
||||
let mut u = Vec::with_capacity(l.len());
|
||||
for &(e, forward) in l {
|
||||
let edge = self.edges.get(e as usize).ok_or("edge reference")?;
|
||||
let (curve, interval) = match &edge.form {
|
||||
Some((c, i)) if formed => (c, *i),
|
||||
_ => (&edge.curve, edge.interval),
|
||||
};
|
||||
u.push(EdgeUse { edge: e, curve, interval, forward, tolerance: 0.0, given: None });
|
||||
}
|
||||
uses.push(u);
|
||||
}
|
||||
uses.push(u);
|
||||
}
|
||||
match pcurve::pcurves(&surface, &uses, tol) {
|
||||
Ok(p) => break p,
|
||||
Err(pcurve::Error::Failed(m)) if !m.contains("not supported") && tol * 10.0 <= MAX_FIT_TOLERANCE * (1.0 + 1e-9) => {
|
||||
tol *= 10.0;
|
||||
match pcurve::pcurves(on, &uses, *tol) {
|
||||
Ok(p) => return Ok(p),
|
||||
Err(pcurve::Error::Failed(m)) if !m.contains("not supported") && *tol * 10.0 <= MAX_FIT_TOLERANCE * (1.0 + 1e-9) => {
|
||||
*tol *= 10.0;
|
||||
}
|
||||
Err(e) => return Err(e.to_string()),
|
||||
}
|
||||
Err(e) => return Err(e.to_string()),
|
||||
}
|
||||
};
|
||||
// A cylinder, cone, sphere or torus on its NURBS form, over the extent its label's pcurves
|
||||
// give; then every pcurve on the forms.
|
||||
let form = match brep::form::frame_of(&label) {
|
||||
Some(_) => {
|
||||
let extent = face_domain(&label, &pcurves_on(&label, false, &mut tol)?)?;
|
||||
Some(brep::form::surface(&label, extent).ok_or_else(|| unsupported("a face of more than one turn"))?.0)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let surface = form.clone().unwrap_or(label);
|
||||
let pcs = pcurves_on(&surface, true, &mut tol)?;
|
||||
if tol > self.tol {
|
||||
for l in &edge_loops {
|
||||
for &(e, _) in l {
|
||||
@@ -1029,7 +1077,7 @@ impl<'b, 'a> Builder<'b, 'a> {
|
||||
}
|
||||
let pcs: Vec<Vec<Curve2>> = if flip { pcs.into_iter().map(|l| l.into_iter().rev().collect()).collect() } else { pcs };
|
||||
let domain = face_domain(&surface, &pcs)?;
|
||||
Ok((pcs, domain, flip, tol))
|
||||
Ok((pcs, domain, flip, tol, form))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -311,7 +311,7 @@ impl Builder<'_, '_> {
|
||||
}
|
||||
}
|
||||
let i = index(self.edges.len())?;
|
||||
self.edges.push(EdgeB { curve, interval, vertices, tolerance: self.tol });
|
||||
self.edges.push(EdgeB { curve, interval, vertices, tolerance: self.tol, form: None });
|
||||
Ok((i, along))
|
||||
}
|
||||
|
||||
@@ -546,7 +546,7 @@ impl Builder<'_, '_> {
|
||||
x.interval = [lo, t];
|
||||
x.vertices = [a, v];
|
||||
}
|
||||
self.edges.push(EdgeB { curve, interval: [t, hi], vertices: [v, b], tolerance });
|
||||
self.edges.push(EdgeB { curve, interval: [t, hi], vertices: [v, b], tolerance, form: None });
|
||||
for f in &mut self.faces {
|
||||
for l in &mut f.loops {
|
||||
let LoopB::Edges(es) = l else { continue };
|
||||
|
||||
Reference in New Issue
Block a user