Fix the 3D-solids save/reopen bug: body_transform() dropped SAB integer tokens

Root cause of "reopen shows nothing, Zoom Extents finds nothing" for any
solid saved to DWG and reopened: SabWriter round-trips exact transform
values (0/1 in an identity matrix - the common case for a freshly drawn
solid) as compact SAB integer tokens (tag 0x04), not doubles.
body_transform()'s value-extraction loop only tried
coordinate_components() -> as_float() -> as_string(), never as_integer(),
so it silently parsed 0 of 13 values out of a perfectly valid transform
record. tessellate_sat() then hit its own (already-instrumented) "no
resolvable transform" bailout and aborted the whole document.

Reproduced directly against a real saved/reopened file (a standalone
harness against acadrust+cadkernel, bypassing the app's own broken native
desktop build - unrelated pre-existing issues, not touched here): the
transform record's tokens were confirmed to be Sab{tag:4,...}, and adding
the as_integer() fallback resolved them to the expected identity matrix.

Fix: fall back to token.as_integer() (cast to f64) alongside as_float()
in body_transform(). Added regression coverage with a synthetic
SAB-integer-encoded transform record.
This commit is contained in:
AI Assistant 2026-08-27 17:00:04 +10:00
commit 4a61c54826
2 changed files with 66 additions and 1 deletions

View file

@ -289,5 +289,4 @@ mod tests {
let moved = placed([0.0, 0.0, 0.0], quarter_turn());
assert_eq!(moved, [10.0, 20.0, 30.0]);
}
}

View file

@ -42,6 +42,16 @@ pub(crate) fn body_transform(
values.extend_from_slice(&components[..len]);
} else if let Some(value) = token.as_float() {
values.push(value);
} else if let Some(value) = token.as_integer() {
// A SAB-encoded transform's exact values (0/1 in an identity
// matrix - the common case for a freshly drawn solid) round-trip
// as compact SAB integer tokens (tag 0x04), not doubles. Missing
// this fallback silently parsed 0 of 13 values on any such
// transform, which read as "no resolvable transform" and aborted
// tessellation of the whole document (CMMS incident, 2026-08-27:
// reproduced on a solid saved and reopened, body_transform()
// parsed a valid identity transform record down to zero values).
values.push(value as f64);
} else if let Some(text) = token.as_string() {
for word in text.split_ascii_whitespace() {
let Ok(value) = word.parse::<f64>() else {
@ -71,6 +81,62 @@ pub(crate) fn body_transform(
.ok_or(())
}
#[cfg(test)]
mod tests {
use super::*;
use acadrust::entities::acis::{SatPointer, SatRecord, SatToken};
fn body_record(index: i32, transform_index: i32) -> SatRecord {
let mut r = SatRecord::new(index, "body");
r.tokens = vec![
SatToken::Pointer(SatPointer::NULL),
SatToken::Pointer(SatPointer::NULL),
SatToken::Pointer(SatPointer::NULL),
SatToken::Pointer(SatPointer::new(transform_index)),
];
r
}
/// A SAB-encoded transform round-trip through save/reopen writes its
/// exact 0/1 identity values as compact SAB integer tokens (tag 0x04),
/// not doubles - reproduces the "no resolvable transform" bug that
/// aborted tessellation of an entire document after any solid was saved
/// and reopened (CMMS incident, 2026-08-27).
fn sab_int_transform_record(index: i32) -> SatRecord {
let mut r = SatRecord::new(index, "transform");
r.tokens.push(SatToken::Pointer(SatPointer::NULL));
let values: [i32; 13] = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1];
for v in values {
r.tokens.push(SatToken::Sab {
tag: 0x04,
data: v.to_le_bytes().to_vec(),
});
}
r
}
#[test]
fn resolves_a_sab_integer_encoded_identity_transform() {
let mut sat = SatDocument::new();
sat.records.push(body_record(0, 1));
sat.records.push(sab_int_transform_record(1));
let result = body_transform(&sat, 0).expect("should resolve, not error");
let (matrix, translation, scale) = result.expect("should be Some, not identity-as-None");
assert_eq!(matrix, [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]);
assert_eq!(translation, [0.0, 0.0, 0.0]);
assert_eq!(scale, 1.0);
}
#[test]
fn a_body_with_a_null_transform_pointer_has_no_transform() {
let mut sat = SatDocument::new();
sat.records.push(body_record(0, -1));
assert_eq!(body_transform(&sat, 0), Ok(None));
}
}
pub(crate) fn finalize_mesh(
name: String,
verts: Vec<[f64; 3]>,