fix(3d): place and move ACIS solids and tilted-normal blocks correctly

Bump acadrust to pick up real ACIS body transforms (so moving/copying/
rotating a 3D solid relocates its geometry) and OCS-aware INSERT placement.

- body_transform: read the SAT transform record from its first 13 float
  tokens instead of by raw index, which skipped the matrix because the
  record leads with a book-keeping pointer — so the body placement was
  silently ignored and solids never honored their stored transform.
- Insert grips: the grip lives in world space but insert_point is stored in
  the block's OCS, so grip display and grip-drag now round-trip through the
  OCS. A block whose extrusion normal isn't +Z no longer shows its grip in
  the wrong place or drags along the wrong axes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-21 01:52:53 +03:00
commit 31ed911714
3 changed files with 27 additions and 20 deletions

2
Cargo.lock generated
View file

@ -70,7 +70,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
[[package]] [[package]]
name = "acadrust" name = "acadrust"
version = "0.3.4" version = "0.3.4"
source = "git+https://github.com/HakanSeven12/acadrust?branch=main#67db8eeeb63e429d301f7708ee8a4629ed3eb780" source = "git+https://github.com/HakanSeven12/acadrust?branch=main#33cb8b72dbf9f3dc585619a31848b42c5acf8c81"
dependencies = [ dependencies = [
"ahash", "ahash",
"anyhow", "anyhow",

View file

@ -1,5 +1,5 @@
use acadrust::entities::Insert; use acadrust::entities::Insert;
use acadrust::types::{Transform, Vector3}; use acadrust::types::{Matrix3, Transform, Vector3};
use acadrust::{EntityType, Handle}; use acadrust::{EntityType, Handle};
use glam::Vec3; use glam::Vec3;
@ -13,8 +13,10 @@ use crate::scene::convert::tessellate;
use crate::scene::view::render; use crate::scene::view::render;
fn grips(ins: &Insert) -> Vec<GripDef> { fn grips(ins: &Insert) -> Vec<GripDef> {
let p = glam::DVec3::new(ins.insert_point.x, ins.insert_point.y, ins.insert_point.z); // `insert_point` is in the OCS defined by `normal`; the grip must sit at
vec![square_grip(0, p)] // the world placement, so map it through the OCS. Identity for +Z.
let w = Matrix3::arbitrary_axis(ins.normal) * ins.insert_point;
vec![square_grip(0, glam::DVec3::new(w.x, w.y, w.z))]
} }
fn properties(ins: &Insert) -> PropSection { fn properties(ins: &Insert) -> PropSection {
@ -50,18 +52,18 @@ fn apply_geom_prop(ins: &mut Insert, field: &str, value: &str) {
} }
fn apply_grip(ins: &mut Insert, _grip_id: usize, apply: GripApply) { fn apply_grip(ins: &mut Insert, _grip_id: usize, apply: GripApply) {
match apply { // The grip works in world space, but `insert_point` is stored in the OCS
GripApply::Absolute(p) => { // defined by `normal`. Round-trip through the OCS so dragging a block
ins.insert_point.x = p.x as f64; // whose extrusion direction isn't +Z moves along world axes. Identity OCS
ins.insert_point.y = p.y as f64; // for a +Z normal, so this matches the old direct assignment there.
ins.insert_point.z = p.z as f64; let ocs = Matrix3::arbitrary_axis(ins.normal);
} let world = match apply {
GripApply::Absolute(p) => Vector3::new(p.x as f64, p.y as f64, p.z as f64),
GripApply::Translate(d) => { GripApply::Translate(d) => {
ins.insert_point.x += d.x as f64; ocs * ins.insert_point + Vector3::new(d.x as f64, d.y as f64, d.z as f64)
ins.insert_point.y += d.y as f64;
ins.insert_point.z += d.z as f64;
} }
} };
ins.insert_point = ocs.transpose() * world;
} }
fn apply_transform(ins: &mut Insert, t: &EntityTransform) { fn apply_transform(ins: &mut Insert, t: &EntityTransform) {

View file

@ -191,13 +191,18 @@ 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")?;
let mut m = [0.0f64; 9]; // The transform record's numeric payload is its first 13 float-valued
for (i, slot) in m.iter_mut().enumerate() { // tokens: 3×3 matrix, translation, scale. A leading book-keeping pointer
*slot = t.token_float(i)?; // (`$-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<f64> = t.tokens.iter().filter_map(|tok| tok.as_float()).take(13).collect();
if v.len() < 13 {
return None;
} }
let tr = [t.token_float(9)?, t.token_float(10)?, t.token_float(11)?]; let m = [v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8]];
let scale = t.token_float(12).unwrap_or(1.0); let tr = [v[9], v[10], v[11]];
Some((m, tr, scale)) Some((m, tr, v[12]))
} }
/// Apply a body placement transform to a mesh. ACIS treats points as row /// Apply a body placement transform to a mesh. ACIS treats points as row