From 7d60c72de0500c912b5c358b8f19383a9dbf3794 Mon Sep 17 00:00:00 2001 From: Orual Date: Sun, 2 Aug 2026 20:39:40 -0400 Subject: [PATCH] PM-78: distinguish conditional-line endpoints and controls Add a typed accessor for type-5 geometry while preserving the existing serde shape, with regression coverage for endpoint/control roles. Epic: PM-86 Task: PM-78 --- crates/polymodel-ldraw-core/src/geom.rs | 15 ++++++++++++++ crates/polymodel-ldraw-core/src/lib.rs | 26 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/crates/polymodel-ldraw-core/src/geom.rs b/crates/polymodel-ldraw-core/src/geom.rs index 0a0525b..52cd9db 100644 --- a/crates/polymodel-ldraw-core/src/geom.rs +++ b/crates/polymodel-ldraw-core/src/geom.rs @@ -255,6 +255,21 @@ pub struct GeometryRecord { pub vertices: Vec, } +impl GeometryRecord { + /// Return the endpoint and control-point pairs for a conditional line. + /// + /// The fixed-size array references make the type-5 geometry contract + /// explicit to callers without changing the serialized representation. + pub fn conditional_endpoints(&self) -> Option<(&[Point3; 2], &[Point3; 2])> { + if self.line_type != LineType::Five || self.vertices.len() != 4 { + return None; + } + let endpoints = self.vertices.get(..2)?.try_into().ok()?; + let controls = self.vertices.get(2..)?.try_into().ok()?; + Some((endpoints, controls)) + } +} + pub(crate) fn parse_primitive(line: &ScannedLine) -> Option { match line.line_type { Some(LineType::One) => { diff --git a/crates/polymodel-ldraw-core/src/lib.rs b/crates/polymodel-ldraw-core/src/lib.rs index 84f84c1..26c6eee 100644 --- a/crates/polymodel-ldraw-core/src/lib.rs +++ b/crates/polymodel-ldraw-core/src/lib.rs @@ -72,6 +72,32 @@ mod tests { assert_eq!(ledger.snapshot().reservations, 0); } #[test] + fn conditional_geometry_exposes_endpoints_and_controls() { + let ledger = ReservationLedger::new(); + let result = LdrawParser + .parse_bytes( + b"5 16 0 0 0 1 0 0 0 1 0 2 0 0\n", + options(&ledger), + None, + ) + .unwrap(); + let geometry = &result.models[0].geometry[0]; + let (endpoints, controls) = geometry + .conditional_endpoints() + .expect("type-5 geometry should expose its point roles"); + assert_eq!(endpoints[0], Point3::new(0.0, 0.0, 0.0)); + assert_eq!(endpoints[1], Point3::new(1.0, 0.0, 0.0)); + assert_eq!(controls[0], Point3::new(0.0, 1.0, 0.0)); + assert_eq!(controls[1], Point3::new(2.0, 0.0, 0.0)); + assert!(GeometryRecord { + line_type: LineType::Two, + colour: ColourCode::from(16_u16), + vertices: Vec::new(), + } + .conditional_endpoints() + .is_none()); + } + #[test] fn bfc_state_machine_certify_then_nocertify() { let fixture = corpus_fixture("bfc-directives"); let bytes = fixture.bytes; -- 2.51.2