From 22a7c4490c5fccf13ad3353a7160de9b80fc2ed2 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 22 Jul 2026 23:36:07 +0300 Subject: [PATCH] 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 --- src/scene/convert/solid3d_tess.rs | 36 ++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/scene/convert/solid3d_tess.rs b/src/scene/convert/solid3d_tess.rs index a55035392..b67fd6c68 100644 --- a/src/scene/convert/solid3d_tess.rs +++ b/src/scene/convert/solid3d_tess.rs @@ -16,8 +16,8 @@ use std::f64::consts::TAU; use acadrust::entities::acis::types::Sense; use acadrust::entities::acis::{ SabReader, SatCoedge, SatConeSurface, SatDocument, SatEdge, SatEllipseCurve, SatFace, - SatIntCurve, SatLoop, SatPlaneSurface, SatPoint, SatPointer, SatSphereSurface, SatTorusSurface, - SatVertex, + SatIntCurve, SatLoop, SatPlaneSurface, SatPoint, SatPointer, SatSphereSurface, SatToken, + SatTorusSurface, SatVertex, }; use acadrust::entities::{Body, Region, Solid3D}; @@ -896,17 +896,27 @@ fn tessellate_sat_lods( /// document has no transform (treated as identity). pub(crate) fn body_transform(sat: &SatDocument) -> Option<([f64; 9], [f64; 3], f64)> { let t = sat.records.iter().find(|r| r.entity_type == "transform")?; - // The transform record's numeric payload is its first 13 float-valued - // tokens: 3×3 matrix, translation, scale. A leading book-keeping pointer - // (`$-1`) and the trailing rotate/reflect/shear flags aren't floats, so - // collecting float tokens skips them — reading by raw token index would - // be thrown off by the leading pointer. - let v: Vec = t - .tokens - .iter() - .filter_map(|tok| tok.as_float()) - .take(13) - .collect(); + // The transform record's numeric payload is its first 13 numeric values: + // 3×3 matrix, translation, scale. A leading book-keeping pointer (`$-1`) + // and the trailing rotate/reflect/shear flags aren't numeric, so + // collecting numeric tokens skips them — reading by raw token index would + // be thrown off by the leading pointer. SAT text tokenizes the payload as + // 13 individual floats, but the SAB reader groups the matrix rows and the + // translation into `Position` triplets, so those must be flattened too. + let mut v: Vec = Vec::with_capacity(13); + for tok in &t.tokens { + if v.len() >= 13 { + 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 { return None; }