mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-05 03:25:58 +00:00
STEP import: a whole sphere bounded by one seam has its frame on that seam
A SPHERICAL_SURFACE face bounded by one meridian used both ways is a whole ball, but the
file's placement need not have its poles at the meridian's ends: a sphere's frame is any
right-handed frame about its centre (the same surface, its natural normal outward). With
the poles elsewhere, the seam is a slit across the face's form, not the form's own seam;
SMLib's export refused the record ("a closed shell bounds two regions and encloses one"),
and the ball was not imported
(seam_only_sphere_whose_placement_axis_misses_its_poles_imports_as_a_whole_ball).
Before anything is measured on the frame, such a sphere's frame is put on its seam: the
axis through the seam's ends (which must be antipodes on the sphere, within the
tolerances), the x axis through the seam's middle. A seam already between the frame's
poles keeps the file's frame.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -943,6 +943,9 @@ impl<'b, 'a> Builder<'b, 'a> {
|
||||
fn finish(mut self, item: u64, name: String, shells: Vec<Vec<(u32, bool)>>, kind: Kind) -> R<Solid> {
|
||||
let _span = solidcraft_profile::span("import", "step.solid_record");
|
||||
let mut notes = Vec::new();
|
||||
// A whole sphere bounded by one seam has its frame on that seam, before anything is
|
||||
// measured on the frame.
|
||||
self.seat_spheres_on_seams()?;
|
||||
let at_poles = self.split_at_poles()?;
|
||||
if at_poles > 0 {
|
||||
notes.push(format!("{at_poles} edges split where they pass through a face's pole"));
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
//! group as the file has it.
|
||||
|
||||
use solidcraft_geom::Vec3;
|
||||
use solidcraft_geom::brep::{self, Curve as BCurve};
|
||||
use solidcraft_geom::brep::eval::CurveEval;
|
||||
use solidcraft_geom::brep::{self, Curve as BCurve, Frame, Surface as BSurface};
|
||||
|
||||
use super::{Builder, LoopB, R};
|
||||
|
||||
@@ -178,3 +179,50 @@ impl Builder<'_, '_> {
|
||||
Ok(turned)
|
||||
}
|
||||
}
|
||||
|
||||
impl Builder<'_, '_> {
|
||||
/// Put the frame of each whole sphere bounded by one seam alone (a meridian from pole to
|
||||
/// pole, used both ways) on that seam: its axis through the seam's ends, its x axis through
|
||||
/// the seam's middle. A sphere's frame is the writer's choice (any right-handed frame about
|
||||
/// the centre is the same surface, its natural normal outward); one whose poles are not the
|
||||
/// seam's leaves the seam a slit across the form, not the form's own seam, and no kernel
|
||||
/// takes that as the whole ball. Returns how many frames were moved.
|
||||
pub(super) fn seat_spheres_on_seams(&mut self) -> R<usize> {
|
||||
let mut seated = 0;
|
||||
for fi in 0..self.faces.len() {
|
||||
let Some(face) = self.faces.get(fi) else { continue };
|
||||
let BSurface::Sphere { frame, radius } = &face.surface else { continue };
|
||||
let (frame, radius) = (frame.clone(), *radius);
|
||||
let [LoopB::Edges(uses)] = face.loops.as_slice() else { continue };
|
||||
let [(e, a), (f, b)] = uses.as_slice() else { continue };
|
||||
if e != f || a == b {
|
||||
continue;
|
||||
}
|
||||
let edge = self.edges.get(*e as usize).ok_or("edge reference")?;
|
||||
let point = |v: u32| self.vertices.get(v as usize).map(|x| (Vec3::from(x.point), x.tolerance));
|
||||
let (Some((start, t0)), Some((end, t1))) = (point(edge.vertices[0]), point(edge.vertices[1])) else { continue };
|
||||
let centre = Vec3::from(frame.origin);
|
||||
let reach = self.tol.max(edge.tolerance).max(t0).max(t1);
|
||||
// The seam runs between antipodes: its ends on the sphere, opposite each other.
|
||||
if (start.dist(centre) - radius.abs()).abs() > reach || (start - centre).dist(centre - end) > 2.0 * reach {
|
||||
continue;
|
||||
}
|
||||
let Some(z) = (start - end).normalized() else { continue };
|
||||
let axis = Vec3::from(frame.x_axis).cross(Vec3::from(frame.y_axis));
|
||||
// Already the frame's poles.
|
||||
if axis.cross(z).len() * radius.abs() <= reach {
|
||||
continue;
|
||||
}
|
||||
let c = CurveEval::new(&edge.curve).map_err(|m| m.to_string())?;
|
||||
let middle = c.point(0.5 * (edge.interval[0] + edge.interval[1])).ok_or("edge point")?;
|
||||
let off = middle - centre;
|
||||
let Some(x) = (off - z * off.dot(z)).normalized() else { continue };
|
||||
let y = z.cross(x);
|
||||
let Some(face) = self.faces.get_mut(fi) else { continue };
|
||||
face.surface = BSurface::Sphere { frame: Frame { origin: frame.origin, x_axis: [x.x, x.y, x.z], y_axis: [y.x, y.y, y.z] }, radius };
|
||||
face.extent = None;
|
||||
seated += 1;
|
||||
}
|
||||
Ok(seated)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user