From bb68bf8c3aed40ef0ff420bbbf358e701e9ee3c9 Mon Sep 17 00:00:00 2001 From: Orual Date: Sun, 2 Aug 2026 18:04:14 -0400 Subject: [PATCH] PM-78: expose instance transforms Epic: PM-86 Task: PM-78 --- crates/polymodel-ldraw-core/src/model.rs | 13 ++++++++++- crates/polymodel-ldraw-core/src/traversal.rs | 23 +++++++++++++++----- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/crates/polymodel-ldraw-core/src/model.rs b/crates/polymodel-ldraw-core/src/model.rs index 1bd709e..750bd01 100644 --- a/crates/polymodel-ldraw-core/src/model.rs +++ b/crates/polymodel-ldraw-core/src/model.rs @@ -197,10 +197,21 @@ pub struct SemanticRecord<'src> { pub limits: Vec>, pub texmap_events: Vec>, } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct InstanceRecord { + pub instance_id: String, + pub source_model: String, + pub target_model: String, + pub transform: Transform, + pub inverted: bool, +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SceneRecord { pub model_id: String, pub instance_ids: Vec, + #[serde(default)] + pub instances: Vec, pub triangles: u64, pub quads: u64, pub lines: u64, @@ -223,7 +234,7 @@ pub struct CanonicalRecord<'src> { #[derive(Clone, Debug)] pub(crate) struct Include<'src> { pub(crate) name: Cow<'src, str>, - pub(crate) transform: Transform, + pub transform: Transform, pub(crate) span: Span, pub(crate) valid: bool, pub(crate) inverted: bool, diff --git a/crates/polymodel-ldraw-core/src/traversal.rs b/crates/polymodel-ldraw-core/src/traversal.rs index c5a5c21..34dd00c 100644 --- a/crates/polymodel-ldraw-core/src/traversal.rs +++ b/crates/polymodel-ldraw-core/src/traversal.rs @@ -1,6 +1,6 @@ use crate::cache::CacheKey; use crate::geom::{Bounds3, Point3, Transform}; -use crate::model::{ModelData, ModelSummary, SceneRecord}; +use crate::model::{InstanceRecord, ModelData, ModelSummary, SceneRecord}; use crate::types::{ Diagnostic, DiagnosticCode, LimitCounters, LimitKind, ParseError, ParseOptions, add_diag, limit_error, @@ -18,6 +18,7 @@ pub(crate) fn traverse( SceneRecord { model_id: String::new(), instance_ids: Vec::new(), + instances: Vec::new(), triangles: 0, quads: 0, lines: 0, @@ -48,6 +49,7 @@ pub(crate) fn traverse( let mut stack = VecDeque::::new(); let mut summaries = Vec::new(); let mut instance_ids = Vec::new(); + let mut instances = Vec::new(); let mut scene_bounds = Bounds3::default(); let mut scene_triangles = 0u64; let mut scene_quads = 0u64; @@ -153,12 +155,22 @@ pub(crate) fn traverse( let child_name = target .map(|index| models[index].path.to_string()) .unwrap_or_else(|| include.name.replace('\\', "/")); - instance_ids.push(format!( + let instance_id = format!( "{}→{}#{}", model.path, child_name, include_position + 1 - )); + ); + let composed_transform = transform.compose(&include.transform); + let inverted = reflected ^ include.inverted ^ include.transform.reflection(); + instance_ids.push(instance_id.clone()); + instances.push(InstanceRecord { + instance_id, + source_model: model.path.to_string(), + target_model: child_name.clone(), + transform: composed_transform, + inverted, + }); let child_depth = depth .checked_add(1) .ok_or(ParseError::Overflow("include depth"))?; @@ -207,9 +219,9 @@ pub(crate) fn traverse( } stack.push_back(Visit::Enter { index: target, - transform: transform.compose(&include.transform), + transform: composed_transform, depth: child_depth, - reflected: reflected ^ include.inverted ^ include.transform.reflection(), + reflected: inverted, }); } } @@ -218,6 +230,7 @@ pub(crate) fn traverse( let scene = SceneRecord { model_id: models[0].path.to_string(), instance_ids, + instances, triangles: scene_triangles, quads: scene_quads, lines: scene_lines, -- 2.51.2