diff --git a/src/entities/mesh.rs b/src/entities/mesh.rs index ac59966e9..60f60fe86 100644 --- a/src/entities/mesh.rs +++ b/src/entities/mesh.rs @@ -1,4 +1,4 @@ -use acadrust::entities::{polygon_mesh::PolygonMesh, Face3D, PolyfaceMesh}; +use acadrust::entities::{mesh::Mesh, polygon_mesh::PolygonMesh, Face3D, PolyfaceMesh}; use glam::Vec3; use crate::command::EntityTransform; @@ -277,12 +277,38 @@ impl Grippable for PolygonMesh { impl PropertyEditable for PolygonMesh { fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection { + let smooth = match self.smooth_type { + acadrust::entities::polygon_mesh::SurfaceSmoothType::NoSmooth => "None", + acadrust::entities::polygon_mesh::SurfaceSmoothType::Quadratic => "Quadratic", + acadrust::entities::polygon_mesh::SurfaceSmoothType::Cubic => "Cubic", + acadrust::entities::polygon_mesh::SurfaceSmoothType::Bezier => "Bezier", + }; PropSection { title: "Geometry".into(), props: vec![ ro("M count", "pm_m", self.m_vertex_count.to_string()), ro("N count", "pm_n", self.n_vertex_count.to_string()), ro("Vertices", "pm_v", self.vertices.len().to_string()), + ro("Smooth Type", "pm_smooth", smooth), + ro( + "Smooth M Density", + "pm_smooth_m", + self.m_smooth_density.to_string(), + ), + ro( + "Smooth N Density", + "pm_smooth_n", + self.n_smooth_density.to_string(), + ), + ro("Elevation", "pm_elevation", format!("{:.4}", self.elevation)), + ro( + "Normal", + "pm_normal", + format!( + "{:.3}, {:.3}, {:.3}", + self.normal.x, self.normal.y, self.normal.z + ), + ), ], } } @@ -399,11 +425,36 @@ impl Grippable for PolyfaceMesh { impl PropertyEditable for PolyfaceMesh { fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection { + let smooth = match self.smooth_surface { + acadrust::entities::PolyfaceSmoothType::None => "None", + acadrust::entities::PolyfaceSmoothType::Quadratic => "Quadratic", + acadrust::entities::PolyfaceSmoothType::Cubic => "Cubic", + acadrust::entities::PolyfaceSmoothType::Bezier => "Bezier", + }; PropSection { title: "Geometry".into(), props: vec![ ro("Vertices", "pfm_v", self.vertices.len().to_string()), ro("Faces", "pfm_f", self.faces.len().to_string()), + ro("Smooth Surface", "pfm_smooth", smooth), + ro( + "Seqend", + "pfm_seqend_handle", + match self.seqend_handle { + Some(h) if !h.is_null() => format!("{:X}", h.value()), + _ => "(none)".to_string(), + }, + ), + ro("Elevation", "pfm_elevation", format!("{:.4}", self.elevation)), + ro( + "Normal", + "pfm_normal", + format!( + "{:.3}, {:.3}, {:.3}", + self.normal.x, self.normal.y, self.normal.z + ), + ), + ro("Thickness", "pfm_thickness", format!("{:.4}", self.thickness)), ], } } @@ -425,3 +476,156 @@ impl Transformable for PolyfaceMesh { }); } } + +// ── Mesh (SubD mesh) ────────────────────────────────────────────────────────── +// +// Modern subdivision mesh — distinct from PolygonMesh. The render path emits +// the file's per-edge wireframe and triangulates each face into fill_tris so +// solid views still draw a shaded surface. Subdivision-level smoothing is +// honoured only as metadata; we don't run a Catmull-Clark refinement pass +// here yet. + +impl TruckConvertible for Mesh { + fn to_truck(&self, _document: &acadrust::CadDocument) -> Option { + if self.vertices.is_empty() { + return None; + } + let get = |i: usize| -> Option<[f64; 3]> { + self.vertices.get(i).map(|v| [v.x, v.y, v.z]) + }; + + let mut pts: Vec<[f64; 3]> = Vec::new(); + if !self.edges.is_empty() { + for edge in &self.edges { + if let (Some(a), Some(b)) = (get(edge.start), get(edge.end)) { + pts.push([f64::NAN; 3]); + pts.push(a); + pts.push(b); + } + } + } else { + for face in &self.faces { + if face.vertices.len() < 2 { + continue; + } + pts.push([f64::NAN; 3]); + for &vi in &face.vertices { + if let Some(p) = get(vi) { + pts.push(p); + } + } + if let Some(first) = face.vertices.first().and_then(|&i| get(i)) { + pts.push(first); + } + } + } + + // Fan-triangulate each face into fill_tris so shaded views render + // the mesh as a solid surface. + let mut fill_tris: Vec<[f64; 3]> = Vec::new(); + for face in &self.faces { + if face.vertices.len() < 3 { + continue; + } + let v0 = match get(face.vertices[0]) { + Some(p) => p, + None => continue, + }; + for tri in 1..(face.vertices.len() - 1) { + let v1 = match get(face.vertices[tri]) { + Some(p) => p, + None => continue, + }; + let v2 = match get(face.vertices[tri + 1]) { + Some(p) => p, + None => continue, + }; + fill_tris.push(v0); + fill_tris.push(v1); + fill_tris.push(v2); + } + } + + let snap_pts: Vec<(Vec3, SnapHint)> = self + .vertices + .iter() + .map(|v| (Vec3::new(v.x as f32, v.y as f32, v.z as f32), SnapHint::Node)) + .collect(); + let key_vertices: Vec<[f64; 3]> = self.vertices.iter().map(|v| [v.x, v.y, v.z]).collect(); + + Some(TruckEntity { + object: TruckObject::Lines(pts), + snap_pts, + tangent_geoms: vec![], + key_vertices, + fill_tris, + }) + } +} + +impl Grippable for Mesh { + fn grips(&self) -> Vec { + self.vertices + .iter() + .enumerate() + .map(|(i, v)| { + square_grip(i, Vec3::new(v.x as f32, v.y as f32, v.z as f32)) + }) + .collect() + } + + fn apply_grip(&mut self, grip_id: usize, apply: GripApply) { + if let Some(v) = self.vertices.get_mut(grip_id) { + match apply { + GripApply::Translate(d) => { + v.x += d.x as f64; + v.y += d.y as f64; + v.z += d.z as f64; + } + GripApply::Absolute(p) => { + v.x = p.x as f64; + v.y = p.y as f64; + v.z = p.z as f64; + } + } + } + } +} + +impl PropertyEditable for Mesh { + fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection { + let creased_edges = self.edges.iter().filter(|e| e.has_crease()).count(); + PropSection { + title: "Geometry".into(), + props: vec![ + ro("Vertices", "msh_v", self.vertices.len().to_string()), + ro("Faces", "msh_f", self.faces.len().to_string()), + ro("Edges", "msh_e", self.edges.len().to_string()), + ro("Creased Edges", "msh_creased", creased_edges.to_string()), + ro("Version", "msh_version", self.version.to_string()), + ro( + "Subdivision Level", + "msh_subdiv", + self.subdivision_level.to_string(), + ), + ro( + "Blend Crease", + "msh_blend_crease", + if self.blend_crease { "Yes" } else { "No" }, + ), + ], + } + } + + fn apply_geom_prop(&mut self, _field: &str, _value: &str) {} +} + +impl Transformable for Mesh { + fn apply_transform(&mut self, t: &EntityTransform) { + crate::scene::transform::apply_standard_entity_transform(self, t, |entity, p1, p2| { + for v in &mut entity.vertices { + crate::scene::transform::reflect_xy_point(&mut v.x, &mut v.y, p1, p2); + } + }); + } +} diff --git a/src/entities/polyline.rs b/src/entities/polyline.rs index 65b89619d..4b6f67df2 100644 --- a/src/entities/polyline.rs +++ b/src/entities/polyline.rs @@ -520,10 +520,32 @@ impl Grippable for Polyline3D { impl PropertyEditable for Polyline3D { fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection { + use acadrust::entities::polyline3d::SmoothSurfaceType as SST; + let smooth = match self.smooth_type { + SST::None => "None", + SST::QuadraticBSpline => "Quadratic", + SST::CubicBSpline => "Cubic", + SST::Bezier => "Bezier", + }; PropSection { title: "Geometry".into(), props: vec![ ro("Vertices", "vertices", self.vertices.len().to_string()), + edit("Default Start W", "pl3_start_w", self.default_start_width), + edit("Default End W", "pl3_end_w", self.default_end_width), + ro("Smooth", "pl3_smooth", smooth), + ro("Mesh M", "pl3_mesh_m", self.mesh_m_count.to_string()), + ro("Mesh N", "pl3_mesh_n", self.mesh_n_count.to_string()), + ro( + "Smooth M Density", + "pl3_smooth_m", + self.smooth_m_density.to_string(), + ), + ro( + "Smooth N Density", + "pl3_smooth_n", + self.smooth_n_density.to_string(), + ), Property { label: "Closed".into(), field: "pl3_closed", @@ -548,6 +570,14 @@ impl PropertyEditable for Polyline3D { } else { self.open(); } + return; + } + if let Ok(v) = value.trim().parse::() { + match field { + "pl3_start_w" if v >= 0.0 => self.default_start_width = v, + "pl3_end_w" if v >= 0.0 => self.default_end_width = v, + _ => {} + } } } } diff --git a/src/entities/traits.rs b/src/entities/traits.rs index 1699abeeb..c1f959ec1 100644 --- a/src/entities/traits.rs +++ b/src/entities/traits.rs @@ -56,6 +56,7 @@ impl EntityTypeOps for EntityType { EntityType::Face3D(f) => TruckConvertible::to_truck(f, document), EntityType::PolygonMesh(pm) => TruckConvertible::to_truck(pm, document), EntityType::PolyfaceMesh(pfm) => TruckConvertible::to_truck(pfm, document), + EntityType::Mesh(m) => TruckConvertible::to_truck(m, document), EntityType::Table(tbl) => TruckConvertible::to_truck(tbl, document), EntityType::Text(text) => TruckConvertible::to_truck(text, document), EntityType::MText(text) => TruckConvertible::to_truck(text, document), @@ -93,6 +94,7 @@ impl EntityTypeOps for EntityType { EntityType::Face3D(f) => Grippable::grips(f), EntityType::PolygonMesh(pm) => Grippable::grips(pm), EntityType::PolyfaceMesh(pfm) => Grippable::grips(pfm), + EntityType::Mesh(m) => Grippable::grips(m), EntityType::Table(tbl) => Grippable::grips(tbl), EntityType::Point(pt) => Grippable::grips(pt), EntityType::Spline(spline) => Grippable::grips(spline), @@ -185,6 +187,9 @@ impl EntityTypeOps for EntityType { EntityType::PolyfaceMesh(pfm) => { Some(PropertyEditable::geometry_properties(pfm, text_style_names)) } + EntityType::Mesh(m) => { + Some(PropertyEditable::geometry_properties(m, text_style_names)) + } EntityType::Table(tbl) => { Some(PropertyEditable::geometry_properties(tbl, text_style_names)) } @@ -265,6 +270,7 @@ impl EntityTypeOps for EntityType { EntityType::Face3D(f) => PropertyEditable::apply_geom_prop(f, field, value), EntityType::PolygonMesh(pm) => PropertyEditable::apply_geom_prop(pm, field, value), EntityType::PolyfaceMesh(pfm) => PropertyEditable::apply_geom_prop(pfm, field, value), + EntityType::Mesh(m) => PropertyEditable::apply_geom_prop(m, field, value), EntityType::Table(tbl) => PropertyEditable::apply_geom_prop(tbl, field, value), EntityType::Hatch(hatch) => PropertyEditable::apply_geom_prop(hatch, field, value), EntityType::Point(pt) => PropertyEditable::apply_geom_prop(pt, field, value), @@ -308,6 +314,7 @@ impl EntityTypeOps for EntityType { EntityType::Face3D(f) => Grippable::apply_grip(f, grip_id, apply), EntityType::PolygonMesh(pm) => Grippable::apply_grip(pm, grip_id, apply), EntityType::PolyfaceMesh(pfm) => Grippable::apply_grip(pfm, grip_id, apply), + EntityType::Mesh(m) => Grippable::apply_grip(m, grip_id, apply), EntityType::Table(tbl) => Grippable::apply_grip(tbl, grip_id, apply), EntityType::Point(pt) => Grippable::apply_grip(pt, grip_id, apply), EntityType::Spline(spline) => Grippable::apply_grip(spline, grip_id, apply), @@ -350,6 +357,7 @@ impl EntityTypeOps for EntityType { EntityType::Face3D(f) => Transformable::apply_transform(f, t), EntityType::PolygonMesh(pm) => Transformable::apply_transform(pm, t), EntityType::PolyfaceMesh(pfm) => Transformable::apply_transform(pfm, t), + EntityType::Mesh(m) => Transformable::apply_transform(m, t), EntityType::Table(tbl) => Transformable::apply_transform(tbl, t), EntityType::MText(text) => Transformable::apply_transform(text, t), EntityType::Point(pt) => Transformable::apply_transform(pt, t),