fix(solid): read SAB transform Position tokens for body placement

The SAB reader tokenizes an ACIS transform record's matrix rows and
translation as Position triplets, while SAT text yields 13 individual
floats. body_transform() collected only float tokens, so every binary-SAB
solid/region (R2013+ AcDs payloads) lost its placement and rendered at
the body-local origin. Flatten Position tokens into the numeric run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-07-22 23:36:07 +03:00
commit 22a7c4490c

View file

@ -16,8 +16,8 @@ use std::f64::consts::TAU;
use acadrust::entities::acis::types::Sense; use acadrust::entities::acis::types::Sense;
use acadrust::entities::acis::{ use acadrust::entities::acis::{
SabReader, SatCoedge, SatConeSurface, SatDocument, SatEdge, SatEllipseCurve, SatFace, SabReader, SatCoedge, SatConeSurface, SatDocument, SatEdge, SatEllipseCurve, SatFace,
SatIntCurve, SatLoop, SatPlaneSurface, SatPoint, SatPointer, SatSphereSurface, SatTorusSurface, SatIntCurve, SatLoop, SatPlaneSurface, SatPoint, SatPointer, SatSphereSurface, SatToken,
SatVertex, SatTorusSurface, SatVertex,
}; };
use acadrust::entities::{Body, Region, Solid3D}; use acadrust::entities::{Body, Region, Solid3D};
@ -896,17 +896,27 @@ fn tessellate_sat_lods(
/// document has no transform (treated as identity). /// document has no transform (treated as identity).
pub(crate) fn body_transform(sat: &SatDocument) -> Option<([f64; 9], [f64; 3], f64)> { pub(crate) fn body_transform(sat: &SatDocument) -> Option<([f64; 9], [f64; 3], f64)> {
let t = sat.records.iter().find(|r| r.entity_type == "transform")?; let t = sat.records.iter().find(|r| r.entity_type == "transform")?;
// The transform record's numeric payload is its first 13 float-valued // The transform record's numeric payload is its first 13 numeric values:
// tokens: 3×3 matrix, translation, scale. A leading book-keeping pointer // 3×3 matrix, translation, scale. A leading book-keeping pointer (`$-1`)
// (`$-1`) and the trailing rotate/reflect/shear flags aren't floats, so // and the trailing rotate/reflect/shear flags aren't numeric, so
// collecting float tokens skips them — reading by raw token index would // collecting numeric tokens skips them — reading by raw token index would
// be thrown off by the leading pointer. // be thrown off by the leading pointer. SAT text tokenizes the payload as
let v: Vec<f64> = t // 13 individual floats, but the SAB reader groups the matrix rows and the
.tokens // translation into `Position` triplets, so those must be flattened too.
.iter() let mut v: Vec<f64> = Vec::with_capacity(13);
.filter_map(|tok| tok.as_float()) for tok in &t.tokens {
.take(13) if v.len() >= 13 {
.collect(); break;
}
match tok {
SatToken::Position(x, y, z) => v.extend([*x, *y, *z]),
_ => {
if let Some(f) = tok.as_float() {
v.push(f);
}
}
}
}
if v.len() < 13 { if v.len() < 13 {
return None; return None;
} }