mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 10:35:58 +00:00
solidcraft-smlib: an arc past a full turn goes to SMLib a turn back
Schema 4 lets a circle or ellipse edge run up to one turn past its seam (a quarter arc after the seam is [2π, 2.5π]); SMLib's circles take angles within [-2π, 2π], and the flat import refused the whole body as invalid input. The mapping now gives SMLib the same arc a whole turn back, and moves the edge's pcurves on B-spline faces by the same amount, since their parameter is the edge's. step-conformance's plate_100_holes now imports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -165,6 +165,9 @@ fn laid_out(brep: &b::Brep, trusted: bool, analytic: bool) -> R<Arrays> {
|
||||
}
|
||||
}
|
||||
|
||||
// Each edge's interval as SMLib takes it, and how far it moved (see `turned`).
|
||||
let intervals: Vec<([f64; 2], f64)> = brep.edges.iter().map(|e| turned(&e.curve, e.interval)).collect();
|
||||
|
||||
// Faces, loops and edgeuses, with the surfaces and, on B-spline faces, the trims.
|
||||
let any_spline_face = brep.faces.iter().any(|f| matches!(f.surface, b::Surface::Spline { .. } | b::Surface::RationalSpline { .. }));
|
||||
for f in &brep.faces {
|
||||
@@ -185,7 +188,8 @@ fn laid_out(brep: &b::Brep, trusted: bool, analytic: bool) -> R<Arrays> {
|
||||
a.i(flat::EDGEUSE_ORIENTATION).push(i64::from(u.forward));
|
||||
if any_spline_face {
|
||||
if spline {
|
||||
trim(&mut a, &u.pcurve, u.forward)?;
|
||||
let shift = intervals.get(u.edge as usize).map_or(0.0, |i| i.1);
|
||||
trim(&mut a, &u.pcurve, u.forward, shift)?;
|
||||
} else {
|
||||
a.i(flat::EDGEUSE_NURBS_VERTEX_COUNT).push(0);
|
||||
a.i(flat::EDGEUSE_NURBS_ORDER).push(0);
|
||||
@@ -199,10 +203,10 @@ fn laid_out(brep: &b::Brep, trusted: bool, analytic: bool) -> R<Arrays> {
|
||||
a.i(flat::EDGEUSE_NEXT_RADIAL).extend(next);
|
||||
a.i(flat::EDGEUSE_RADIAL_ENTRY).extend(entry);
|
||||
|
||||
for e in &brep.edges {
|
||||
for (e, (interval, _)) in brep.edges.iter().zip(&intervals) {
|
||||
curve(&mut a, &e.curve)?;
|
||||
a.i(flat::EDGE_VERTICES).extend([i64::from(e.vertices[0]), i64::from(e.vertices[1])]);
|
||||
a.r(flat::EDGE_RANGE).extend(e.interval);
|
||||
a.r(flat::EDGE_RANGE).extend(*interval);
|
||||
// Each edge's own tolerance, never below the body's.
|
||||
a.r(flat::EDGE_TOLERANCE).push(e.tolerance.max(brep.tolerance));
|
||||
}
|
||||
@@ -445,14 +449,35 @@ fn plane(a: &mut Arrays, origin: [f64; 3], u: [f64; 3], v: [f64; 3], domain: [[f
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A pcurve as the edgeuse's UV trim: reversed when the loop runs against the edge.
|
||||
fn trim(a: &mut Arrays, c: &b::Curve2, forward: bool) -> R<()> {
|
||||
/// A circle's or ellipse's interval (radians) as SMLib takes it, within [-2π, 2π]: schema 4
|
||||
/// lets one run past a full turn (a quarter arc after the seam, [2π, 2.5π]), SMLib's circles
|
||||
/// take angles up to one turn either way. The same arc a whole turn back, and that shift, by
|
||||
/// which the edge's pcurves move too (their parameter is the edge's).
|
||||
fn turned(c: &b::Curve, interval: [f64; 2]) -> ([f64; 2], f64) {
|
||||
use std::f64::consts::TAU;
|
||||
if !matches!(c, b::Curve::Circle { .. } | b::Curve::Ellipse { .. }) {
|
||||
return (interval, 0.0);
|
||||
}
|
||||
let [lo, hi] = interval;
|
||||
let shift = if hi > TAU {
|
||||
-TAU * ((hi - TAU) / TAU).ceil()
|
||||
} else if lo < -TAU {
|
||||
TAU * ((-TAU - lo) / TAU).ceil()
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
([lo + shift, hi + shift], shift)
|
||||
}
|
||||
|
||||
/// A pcurve as the edgeuse's UV trim, its parameter moved by `shift` (the edge's, see
|
||||
/// [`turned`]): reversed when the loop runs against the edge.
|
||||
fn trim(a: &mut Arrays, c: &b::Curve2, forward: bool, shift: f64) -> R<()> {
|
||||
let (knots, mut pts): (&[f64], Vec<([f64; 2], f64)>) = match c {
|
||||
b::Curve2::Spline { knots, points } => (knots, points.iter().map(|p| (*p, 1.0)).collect()),
|
||||
b::Curve2::RationalSpline { knots, homogeneous } => (knots, homogeneous.iter().map(|h| ([h[0] / h[2], h[1] / h[2]], h[2])).collect()),
|
||||
};
|
||||
let order = knots.len().checked_sub(pts.len()).filter(|o| *o >= 2).ok_or(Unsupported::Invalid("pcurve order"))?;
|
||||
let mut k = knots.to_vec();
|
||||
let mut k: Vec<f64> = knots.iter().map(|t| t + shift).collect();
|
||||
if !forward {
|
||||
pts.reverse();
|
||||
k = k.iter().rev().map(|t| -t).collect();
|
||||
|
||||
@@ -220,3 +220,19 @@ fn revolutions_become_the_same_surface_as_a_rational_spline() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A circle or ellipse edge past a full turn (schema 4 allows up to one turn past the seam)
|
||||
/// goes to SMLib a whole turn back, within [-2π, 2π]; other curves keep their interval.
|
||||
#[test]
|
||||
fn arcs_past_a_turn_come_a_turn_back() {
|
||||
use std::f64::consts::{FRAC_PI_2, TAU};
|
||||
let frame = solidcraft_geom::brep::Frame { origin: [0.0; 3], x_axis: [1.0, 0.0, 0.0], y_axis: [0.0, 1.0, 0.0] };
|
||||
let circle = Curve::Circle { frame, radius: 2.0 };
|
||||
assert_eq!(turned(&circle, [TAU, TAU + FRAC_PI_2]), ([0.0, FRAC_PI_2], -TAU));
|
||||
assert_eq!(turned(&circle, [-TAU - FRAC_PI_2, -TAU]), ([-FRAC_PI_2, 0.0], TAU));
|
||||
let (i, s) = turned(&circle, [5.0, 5.0 + TAU]);
|
||||
assert!(s == -TAU && i[0] >= -TAU && i[1] <= TAU, "{i:?}");
|
||||
assert_eq!(turned(&circle, [0.5, 2.0]), ([0.5, 2.0], 0.0));
|
||||
let line = Curve::Line { start: [0.0; 3], end: [1.0, 0.0, 0.0] };
|
||||
assert_eq!(turned(&line, [7.0, 9.0]), ([7.0, 9.0], 0.0));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user