From 69604012d6aa2a27b6eb816d7bf0c3559b215f33 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Viet <123613986+NgoQuocViet2001@users.noreply.github.com> Date: Tue, 18 Aug 2026 01:40:17 +0700 Subject: [PATCH] fix(flatten): project polylines and planar faces Add initial FLATTEN coverage for polyline families, SOLID, and 3DFACE. Refs #728 --- src/app/commands/inquiry.rs | 128 ++++++++++++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 4 deletions(-) diff --git a/src/app/commands/inquiry.rs b/src/app/commands/inquiry.rs index 905c3ec1a..deaf860c0 100644 --- a/src/app/commands/inquiry.rs +++ b/src/app/commands/inquiry.rs @@ -1012,15 +1012,18 @@ impl OpenCADStudio { self.command_line.push_error(crate::t!("FLATTEN: no entities.").as_ref()); } else { self.push_undo_snapshot(i, "FLATTEN"); + let mut moved = 0usize; for h in &handles { if let Some(e) = self.tabs[i].scene.document.get_entity_mut(*h) { - flatten_entity_z(e); + if flatten_entity_z(e) { + moved += 1; + } } } self.tabs[i].dirty = true; self.command_line.push_output(crate::tf!( "FLATTEN: {} entity(ies) moved to Z=0.", - handles.len() + moved ).as_ref()); self.refresh_properties(); } @@ -1564,7 +1567,10 @@ fn entity_list_details(entity: &acadrust::EntityType) -> String { } } -fn flatten_entity_z(entity: &mut acadrust::EntityType) { +/// Project `entity` onto the Z=0 plane. Returns false when FLATTEN has no +/// projection for that entity type, so the caller can report how many entities +/// it actually moved instead of how many it looked at. +fn flatten_entity_z(entity: &mut acadrust::EntityType) -> bool { match entity { acadrust::EntityType::Line(l) => { l.start.z = 0.0; @@ -1579,6 +1585,25 @@ fn flatten_entity_z(entity: &mut acadrust::EntityType) { acadrust::EntityType::LwPolyline(p) => { p.elevation = 0.0; } + // Polyline vertices carry their own Z, so clearing the elevation alone + // leaves the geometry where it was. + acadrust::EntityType::Polyline(p) => { + for v in &mut p.vertices { + v.location.z = 0.0; + } + } + acadrust::EntityType::Polyline2D(p) => { + p.elevation = 0.0; + for v in &mut p.vertices { + v.location.z = 0.0; + } + } + acadrust::EntityType::Polyline3D(p) => { + p.elevation = 0.0; + for v in &mut p.vertices { + v.position.z = 0.0; + } + } acadrust::EntityType::Text(t) => { t.insertion_point.z = 0.0; } @@ -1602,8 +1627,21 @@ fn flatten_entity_z(entity: &mut acadrust::EntityType) { acadrust::EntityType::Ellipse(e) => { e.center.z = 0.0; } - _ => {} + acadrust::EntityType::Solid(s) => { + s.first_corner.z = 0.0; + s.second_corner.z = 0.0; + s.third_corner.z = 0.0; + s.fourth_corner.z = 0.0; + } + acadrust::EntityType::Face3D(f) => { + f.first_corner.z = 0.0; + f.second_corner.z = 0.0; + f.third_corner.z = 0.0; + f.fourth_corner.z = 0.0; + } + _ => return false, } + true } // ── DATAEXTRACTION ───────────────────────────────────────────────────────── @@ -1814,3 +1852,85 @@ impl ArithParser { s.parse::().map_err(|_| format!("bad number '{s}'")) } } + +#[cfg(test)] +mod flatten_tests { + use super::flatten_entity_z; + use acadrust::types::Vector3; + use acadrust::EntityType; + + #[test] + fn flattens_3d_polyline_vertices() { + let mut pl = acadrust::entities::Polyline3D::new(); + for z in [5.0, -2.0, 7.5] { + pl.vertices + .push(acadrust::entities::Vertex3DPolyline::new(Vector3::new(1.0, 2.0, z))); + } + pl.elevation = 5.0; + + let mut entity = EntityType::Polyline3D(pl); + assert!(flatten_entity_z(&mut entity)); + + let EntityType::Polyline3D(pl) = entity else { panic!("wrong variant") }; + assert_eq!(pl.elevation, 0.0); + assert!(pl.vertices.iter().all(|v| v.position.z == 0.0)); + // X/Y must survive the projection. + assert!(pl.vertices.iter().all(|v| v.position.x == 1.0 && v.position.y == 2.0)); + } + + #[test] + fn flattens_2d_polyline_vertices_and_elevation() { + let mut pl = acadrust::entities::Polyline2D::new(); + pl.vertices + .push(acadrust::entities::Vertex2D::new(Vector3::new(3.0, 4.0, 9.0))); + pl.elevation = 9.0; + + let mut entity = EntityType::Polyline2D(pl); + assert!(flatten_entity_z(&mut entity)); + + let EntityType::Polyline2D(pl) = entity else { panic!("wrong variant") }; + assert_eq!(pl.elevation, 0.0); + assert_eq!(pl.vertices[0].location.z, 0.0); + assert_eq!(pl.vertices[0].location.x, 3.0); + } + + #[test] + fn flattens_solid_corners() { + let solid = acadrust::entities::Solid::new( + Vector3::new(0.0, 0.0, 1.0), + Vector3::new(1.0, 0.0, 2.0), + Vector3::new(1.0, 1.0, 3.0), + Vector3::new(0.0, 1.0, 4.0), + ); + + let mut entity = EntityType::Solid(solid); + assert!(flatten_entity_z(&mut entity)); + + let EntityType::Solid(s) = entity else { panic!("wrong variant") }; + assert_eq!( + [s.first_corner.z, s.second_corner.z, s.third_corner.z, s.fourth_corner.z], + [0.0; 4] + ); + } + + #[test] + fn reports_unsupported_entities_as_not_moved() { + // Ray has no Z projection here; FLATTEN must not count it as moved. + let mut entity = EntityType::Ray(acadrust::entities::Ray::new( + Vector3::new(0.0, 0.0, 5.0), + Vector3::new(1.0, 0.0, 0.0), + )); + assert!(!flatten_entity_z(&mut entity)); + } + + #[test] + fn still_flattens_a_line() { + let mut line = acadrust::entities::Line::new(); + line.start = Vector3::new(0.0, 0.0, 3.0); + line.end = Vector3::new(1.0, 1.0, 4.0); + let mut entity = EntityType::Line(line); + assert!(flatten_entity_z(&mut entity)); + let EntityType::Line(l) = entity else { panic!("wrong variant") }; + assert_eq!((l.start.z, l.end.z), (0.0, 0.0)); + } +}