Files
OneCAD/vendor/truck-stepio/src/in/alias.rs
T
bherbruckandClaude Opus 5.5 08cf223ced STEP export: unique ids, plain curves, right revolution senses; seams on split faces
Vendored truck-stepio (writer only): an intersection curve's second surface
was written at the first one's id (duplicate ids for every boolean edge),
and revolved surfaces lost their orientation (the modeling-surface sense was
always true; inverted or mirrored revolutions came out inside out). Export
now writes intersection edges as C1 cubic B-splines through exact points,
so files carry plain 3D curves and no redundant surfaces.

Vendored truck-meshalgo: hinted parameter searches that run off a B-spline's
domain are rejected or clamped (meshing their grid never finished).

STEP import: straight revolution profiles are bounded in the meridian plane
and stop at the axis; loops on revolutions start away from the axis; face
extents include edge end points. Edges between pieces of one split surface
draw as seams.

kernel::step_validate checks a STEP file (duplicate ids, dangling
references, dense numbering); the oracle replay now exports every rebuilt
part, validates it and re-imports it with no warnings and the same volume
(all 32 rebuildable cases pass).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
2026-10-07 23:40:07 +00:00

398 lines
11 KiB
Rust

use crate::{self as truck_stepio};
use derive_more::*;
use serde::{Deserialize, Serialize};
use truck_derivers::{DisplayByStep, StepCurve, StepLength, StepSurface};
/// re-export structs in `truck-geometry` and `truck-polymesh`.
pub mod re_exports {
pub use truck_geometry::prelude::*;
pub use truck_polymesh::*;
}
pub use re_exports::*;
/// Errors that occur when converting STEP format
pub type StepConvertingError = Box<dyn std::error::Error>;
/// `ellipse`, realized in `truck`
pub type Ellipse<P, M> = Processor<TrimmedCurve<UnitCircle<P>>, M>;
/// `hyperbola`, realized in `truck`
pub type Hyperbola<P, M> = Processor<TrimmedCurve<UnitHyperbola<P>>, M>;
/// `parabola`, realized in `truck`
pub type Parabola<P, M> = Processor<TrimmedCurve<UnitParabola<P>>, M>;
/// `spherical_surface`, realized in `truck`
pub type SphericalSurface = Processor<Sphere, Matrix4>;
/// `cylindrical_surface`, realized in `truck`
pub type CylindricalSurface = Processor<RevolutedCurve<Line<Point3>>, Matrix4>;
/// `toroidal_surface`, realized in `truck`
pub type ToroidalSurface = Processor<Torus, Matrix4>;
/// `conical_surface`, realized in `truck`
pub type ConicalSurface = Processor<RevolutedCurve<Line<Point3>>, Matrix4>;
/// `surface_of_linear_extrusion`, realized in `truck`
pub type StepExtrudedCurve = ExtrudedCurve<Curve3D, Vector3>;
/// `surface_of_revolution`, realized in `truck`
pub type StepRevolutedCurve = Processor<RevolutedCurve<Curve3D>, Matrix4>;
/// `pcurve`, realized in `truck`
pub type PCurve = truck_geometry::prelude::PCurve<Box<Curve2D>, Box<Surface>>;
/// `conic` in 2D, realized in `truck`
#[derive(
Clone,
Copy,
Debug,
PartialEq,
From,
Serialize,
Deserialize,
ParametricCurve,
BoundedCurve,
Cut,
Invertible,
ParameterDivision1D,
SearchParameterD1,
SearchNearestParameterD1,
TransformedM3,
StepLength,
DisplayByStep,
StepCurve,
)]
pub enum Conic2D {
Ellipse(Ellipse<Point2, Matrix3>),
Hyperbola(Hyperbola<Point2, Matrix3>),
Parabola(Parabola<Point2, Matrix3>),
}
/// `curve` in 2D, realized in `truck`
#[derive(
Clone,
Debug,
PartialEq,
From,
Serialize,
Deserialize,
ParametricCurve,
BoundedCurve,
Cut,
Invertible,
ParameterDivision1D,
SearchParameterD1,
SearchNearestParameterD1,
TransformedM3,
StepLength,
DisplayByStep,
StepCurve,
)]
pub enum Curve2D {
Line(Line<Point2>),
Polyline(PolylineCurve<Point2>),
Conic(Conic2D),
BSplineCurve(BSplineCurve<Point2>),
NurbsCurve(NurbsCurve<Vector3>),
}
/// `conic` in 3D, realized in `truck`
#[derive(
Clone,
Copy,
Debug,
PartialEq,
From,
Serialize,
Deserialize,
ParametricCurve,
BoundedCurve,
Cut,
Invertible,
ParameterDivision1D,
SearchParameterD1,
SearchNearestParameterD1,
TransformedM4,
StepLength,
DisplayByStep,
StepCurve,
)]
pub enum Conic3D {
Ellipse(Ellipse<Point3, Matrix4>),
Hyperbola(Hyperbola<Point3, Matrix4>),
Parabola(Parabola<Point3, Matrix4>),
}
/// `curve` in 3D, realized in `truck`
#[derive(
Clone,
Debug,
PartialEq,
From,
Serialize,
Deserialize,
ParametricCurve,
BoundedCurve,
Cut,
Invertible,
ParameterDivision1D,
SearchParameterD1,
SearchNearestParameterD1,
TransformedM4,
StepLength,
DisplayByStep,
StepCurve,
)]
pub enum Curve3D {
Line(Line<Point3>),
Polyline(PolylineCurve<Point3>),
Conic(Conic3D),
BSplineCurve(BSplineCurve<Point3>),
PCurve(PCurve),
NurbsCurve(NurbsCurve<Vector4>),
}
/// `elementary_surface`, realized in `truck`
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Serialize,
Deserialize,
ParametricSurface3D,
ParameterDivision2D,
SearchParameterD2,
SearchNearestParameterD2,
Invertible,
TransformedM4,
StepLength,
DisplayByStep,
StepSurface,
)]
pub enum ElementarySurface {
Plane(Plane),
Sphere(SphericalSurface),
CylindricalSurface(CylindricalSurface),
ToroidalSurface(ToroidalSurface),
ConicalSurface(ConicalSurface),
}
/// `swept_surface`, realized in `truck`
#[derive(
Clone,
Debug,
PartialEq,
Serialize,
Deserialize,
ParametricSurface3D,
ParameterDivision2D,
SearchParameterD2,
SearchNearestParameterD2,
Invertible,
TransformedM4,
StepLength,
DisplayByStep,
StepSurface,
)]
pub enum SweptCurve {
ExtrudedCurve(StepExtrudedCurve),
RevolutedCurve(StepRevolutedCurve),
}
/// `surface`, realized in `truck`
#[derive(
Clone,
Debug,
PartialEq,
Serialize,
Deserialize,
ParametricSurface3D,
ParameterDivision2D,
SearchParameterD2,
SearchNearestParameterD2,
Invertible,
TransformedM4,
StepLength,
StepSurface,
)]
pub enum Surface {
ElementarySurface(Box<ElementarySurface>),
SweptCurve(Box<SweptCurve>),
BSplineSurface(Box<BSplineSurface<Point3>>),
NurbsSurface(Box<NurbsSurface<Vector4>>),
}
impl truck_stepio::out::DisplayByStep for Surface {
fn fmt(&self, idx: usize, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use Surface::*;
match self {
ElementarySurface(x) => x.fmt(idx, f),
SweptCurve(x) => x.fmt(idx, f),
BSplineSurface(x) => x.fmt(idx, f),
NurbsSurface(x) => x.fmt(idx, f),
}
}
}
/// `spherical_surface`, realized in `truck`
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, StepSurface)]
pub struct Sphere(pub truck_geometry::prelude::Sphere);
impl truck_stepio::out::StepSurface for Processor<Sphere, Matrix4> {
#[inline(always)]
fn same_sense(&self) -> bool { self.orientation() }
}
mod sphere {
use super::*;
use std::f64::consts::PI;
use std::ops::Bound;
impl ParametricSurface for Sphere {
type Point = Point3;
type Vector = Vector3;
#[inline]
fn subs(&self, u: f64, v: f64) -> Point3 { self.0.subs(PI / 2.0 - v, u) }
#[inline]
fn uder(&self, u: f64, v: f64) -> Vector3 { self.0.vder(PI / 2.0 - v, u) }
#[inline]
fn vder(&self, u: f64, v: f64) -> Vector3 { -self.0.uder(PI / 2.0 - v, u) }
#[inline]
fn uuder(&self, u: f64, v: f64) -> Vector3 { self.0.vvder(PI / 2.0 - v, u) }
#[inline]
fn uvder(&self, u: f64, v: f64) -> Vector3 { -self.0.uvder(PI / 2.0 - v, u) }
#[inline]
fn vvder(&self, u: f64, v: f64) -> Vector3 { self.0.uuder(PI / 2.0 - v, u) }
#[inline]
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
(
(Bound::Included(0.0), Bound::Excluded(2.0 * PI)),
(Bound::Included(-PI / 2.0), Bound::Excluded(PI / 2.0)),
)
}
#[inline]
fn u_period(&self) -> Option<f64> { Some(2.0 * PI) }
}
impl ParametricSurface3D for Sphere {
#[inline]
fn normal(&self, u: f64, v: f64) -> Vector3 { self.0.normal(PI / 2.0 - v, u) }
}
impl SearchNearestParameter<D2> for Sphere {
type Point = Point3;
#[inline]
fn search_nearest_parameter<H: Into<SPHint2D>>(
&self,
point: Self::Point,
hint: H,
trials: usize,
) -> Option<(f64, f64)> {
self.0
.search_nearest_parameter(point, hint, trials)
.map(|(u, v)| (v, PI / 2.0 - u))
}
}
impl SearchParameter<D2> for Sphere {
type Point = Point3;
#[inline]
fn search_parameter<H: Into<SPHint2D>>(
&self,
point: Self::Point,
hint: H,
trials: usize,
) -> Option<(f64, f64)> {
self.0
.search_parameter(point, hint, trials)
.map(|(u, v)| (v, PI / 2.0 - u))
}
}
impl ParameterDivision2D for Sphere {
#[inline]
fn parameter_division(
&self,
((u0, u1), (v0, v1)): ((f64, f64), (f64, f64)),
tol: f64,
) -> (Vec<f64>, Vec<f64>) {
let range = ((PI / 2.0 - v1, PI / 2.0 - v0), (u0, u1));
let (udiv0, vdiv0) = self.0.parameter_division(range, tol);
let vdiv = udiv0.into_iter().map(|u| PI / 2.0 - u).collect();
(vdiv0, vdiv)
}
}
#[cfg(test)]
proptest::proptest! {
#[test]
fn surface(
center in proptest::array::uniform3(-100.0f64..100.0f64),
radius in 0.1f64..100.0f64,
(u, v) in (0.0..=2.0 * PI, -PI / 2.0..=PI / 2.0),
) {
const EPS: f64 = 1.0e-3;
let sphere = Sphere(truck_geometry::prelude::Sphere::new(center.into(), radius));
let uder0 = sphere.uder(u, v);
let uder1 = (sphere.subs(u + EPS, v) - sphere.subs(u - EPS, v)) / (2.0 * EPS);
assert!(
(uder0 - uder1).magnitude2() < EPS,
"uder failed: {uder0:?}, {uder1:?}"
);
let vder0 = sphere.vder(u, v);
let vder1 = (sphere.subs(u, v + EPS) - sphere.subs(u, v - EPS)) / (2.0 * EPS);
assert!(
(vder0 - vder1).magnitude2() < EPS,
"vder failed: {vder0:?}, {vder1:?}"
);
let uuder0 = sphere.uuder(u, v);
let uuder1 = (sphere.uder(u + EPS, v) - sphere.uder(u - EPS, v)) / (2.0 * EPS);
assert!(
(uuder0 - uuder1).magnitude2() < EPS,
"uuder failed: {uuder0:?}, {uuder1:?}"
);
let uvder0 = sphere.uvder(u, v);
let uvder1 = (sphere.uder(u, v + EPS) - sphere.uder(u, v - EPS)) / (2.0 * EPS);
assert!(
(uvder0 - uvder1).magnitude2() < EPS,
"uvder failed: {uvder0:?}, {uvder1:?}"
);
let vvder0 = sphere.vvder(u, v);
let vvder1 = (sphere.vder(u, v + EPS) - sphere.vder(u, v - EPS)) / (2.0 * EPS);
assert!(
(vvder0 - vvder1).magnitude2() < EPS,
"vvder failed: {vvder0:?}, {vvder1:?}"
);
let n0 = sphere.normal(u, v);
let n1 = sphere.uder(u, v).cross(sphere.vder(u, v)).normalize();
assert!(
(n0 - n1).magnitude2() < EPS,
"normal failed: {n0:?}, {n1:?}"
);
}
}
}
impl truck_stepio::out::ConstStepLength for Processor<Sphere, Matrix4> {
const LENGTH: usize = Processor::<truck_geometry::prelude::Sphere, Matrix4>::LENGTH;
}
impl truck_stepio::out::StepLength for Processor<Sphere, Matrix4> {
fn step_length(&self) -> usize { <Self as truck_stepio::out::ConstStepLength>::LENGTH }
}
impl truck_stepio::out::DisplayByStep for Processor<Sphere, Matrix4> {
fn fmt(&self, idx: usize, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Processor::new(self.entity().0)
.transformed(*self.transform())
.fmt(idx, f)
}
}
/// Implementation required to apply a closed surface division to a shape parsed from a STEP file.
mod from_pcurve {
use super::{Curve2D, Curve3D, Surface};
use truck_geometry::prelude::*;
impl From<PCurve<Line<Point2>, Surface>> for Curve3D {
fn from(value: PCurve<Line<Point2>, Surface>) -> Self {
let (line, surface) = value.decompose();
Curve3D::PCurve(PCurve::new(Curve2D::Line(line).into(), surface.into()))
}
}
}