From d36ab23c468859ce9b322e349b79cb41f51ba54a Mon Sep 17 00:00:00 2001 From: Orual Date: Sun, 2 Aug 2026 16:29:55 -0400 Subject: [PATCH] PM-78: derive model identity from actual model graph Use canonical LDraw paths for scene and summary identities, and preserve parent-child include positions in occurrence identifiers so parsed graphs remain round-trippable. Epic: PM-86 Task: PM-78 --- crates/polymodel-ldraw-core/src/lib.rs | 8 +++--- crates/polymodel-ldraw-core/src/traversal.rs | 30 +++++++++++--------- crates/polymodel-ldraw-core/src/types.rs | 1 - 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/crates/polymodel-ldraw-core/src/lib.rs b/crates/polymodel-ldraw-core/src/lib.rs index 71e46cd..ac9dbbf 100644 --- a/crates/polymodel-ldraw-core/src/lib.rs +++ b/crates/polymodel-ldraw-core/src/lib.rs @@ -102,11 +102,11 @@ mod tests { let first = LdrawParser .parse_bytes(bytes, options(&first_ledger)) .unwrap(); - assert_eq!(first.scene.model_id, "model-00000001"); - assert_eq!(first.scene.instance_ids.len(), 1); + assert_eq!(first.scene.model_id, "root.ldr"); + assert_eq!(first.scene.instance_ids, vec!["root.ldr→child.dat#1"]); assert_eq!( - first.scene.instance_ids, - vec!["instance-00000001".to_string()] + first.models.iter().map(|model| model.id.as_str()).collect::>(), + vec!["root.ldr", "child.dat"] ); let second_ledger = ReservationLedger::new(); diff --git a/crates/polymodel-ldraw-core/src/traversal.rs b/crates/polymodel-ldraw-core/src/traversal.rs index e361cd0..6dd5c41 100644 --- a/crates/polymodel-ldraw-core/src/traversal.rs +++ b/crates/polymodel-ldraw-core/src/traversal.rs @@ -16,7 +16,7 @@ pub(crate) fn traverse( if models.is_empty() { return Ok(( SceneRecord { - model_id: "model-00000001".into(), + model_id: String::new(), instance_ids: Vec::new(), triangles: 0, quads: 0, @@ -56,7 +56,7 @@ pub(crate) fn traverse( let mut reflection = false; let root_key = models[0].key.clone(); - ids.insert(root_key.clone(), "model-00000001".into()); + ids.insert(root_key.clone(), models[0].path.to_string()); stack.push_back(Visit::Enter { index: 0, transform: Transform::default(), @@ -86,9 +86,9 @@ pub(crate) fn traverse( } let first_parse = !completed.contains(&key); let model_id = ids - .get(&key) - .cloned() - .unwrap_or_else(|| format!("model-{:08}", ids.len() + 1)); + .entry(key.clone()) + .or_insert_with(|| model.path.to_string()) + .clone(); if !summaries .iter() .any(|summary: &ModelSummary| summary.id == model_id) @@ -139,20 +139,28 @@ pub(crate) fn traverse( stack.push_back(Visit::Exit { key: key.clone() }); if first_parse { - for include in model.includes.iter().rev() { + for (include_position, include) in model.includes.iter().enumerate().rev() { if options.cancellation.cancelled { return Err(ParseError::Cancelled); } counters .add(LimitKind::Instances, 1, &options.limits) .map_err(|name| limit_error(name, Some(include.span)))?; - instance_ids.push(format!("instance-{:08}", counters.instances)); let target = models.iter().position(|candidate| { candidate .path .as_str() .eq_ignore_ascii_case(&include.name.replace('\\', "/")) }); + let child_name = target + .map(|index| models[index].path.to_string()) + .unwrap_or_else(|| include.name.replace('\\', "/")); + instance_ids.push(format!( + "{}→{}#{}", + model.path, + child_name, + include_position + 1 + )); let child_depth = depth .checked_add(1) .ok_or(ParseError::Overflow("include depth"))?; @@ -197,11 +205,7 @@ pub(crate) fn traverse( continue; } if !ids.contains_key(&child.key) { - let next_id = ids - .len() - .checked_add(1) - .ok_or(ParseError::Overflow("model id"))?; - ids.insert(child.key.clone(), format!("model-{next_id:08}")); + ids.insert(child.key.clone(), child.path.to_string()); } stack.push_back(Visit::Enter { index: target, @@ -217,7 +221,7 @@ pub(crate) fn traverse( } } let scene = SceneRecord { - model_id: "model-00000001".into(), + model_id: models[0].path.to_string(), instance_ids, triangles: scene_triangles, quads: scene_quads, diff --git a/crates/polymodel-ldraw-core/src/types.rs b/crates/polymodel-ldraw-core/src/types.rs index 728aa7c..b91e9e3 100644 --- a/crates/polymodel-ldraw-core/src/types.rs +++ b/crates/polymodel-ldraw-core/src/types.rs @@ -11,7 +11,6 @@ pub const SCHEMA_VERSION: &str = "ldraw-canonical-v1"; pub const DEFAULT_ROOT_NAME: &str = "model.ldr"; pub const DEFAULT_COLOUR: u16 = 16; pub const DEFAULT_PROVENANCE: &str = "local-parse"; -pub const ROOT_MODEL_ID: &str = "model-00000001"; pub const DEFAULT_RESOURCE_BYTES: u64 = 100 * 1024 * 1024; pub const DEFAULT_LINE_BYTES: u64 = 1024 * 1024; pub const DEFAULT_FILES: u64 = 16_384; -- 2.51.2