From cc314e75fc62df0a6a70e1ccc6fb8da146c1fa49 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Mon, 6 Apr 2026 23:30:27 +0300 Subject: [PATCH] feat: Shape entity support (render + grips + props + transform) - src/entities/shape.rs: diamond marker at insertion point (SHX glyphs cannot be rendered without the binary .shx file, so a placeholder is used) - Grips: single insertion-point grip - Properties: Name, Style (read-only), Insert X/Y/Z, Size, Rotation - Transformable: translate, mirror, scale, rotate - Registered in entities/mod.rs, entities/traits.rs, app/commands.rs Co-Authored-By: Claude Sonnet 4.6 --- src/app/commands.rs | 1 + src/entities/mod.rs | 1 + src/entities/shape.rs | 130 +++++++++++++++++++++++++++++++++++++++++ src/entities/traits.rs | 9 +++ 4 files changed, 141 insertions(+) create mode 100644 src/entities/shape.rs diff --git a/src/app/commands.rs b/src/app/commands.rs index c43a95f63..6d47e55d0 100644 --- a/src/app/commands.rs +++ b/src/app/commands.rs @@ -3276,6 +3276,7 @@ fn entity_type_name(entity: &acadrust::EntityType) -> &'static str { acadrust::EntityType::AttributeEntity(_) => "ATTRIB", acadrust::EntityType::Leader(_) => "LEADER", acadrust::EntityType::Tolerance(_) => "TOLERANCE", + acadrust::EntityType::Shape(_) => "SHAPE", _ => "ENTITY", } } diff --git a/src/entities/mod.rs b/src/entities/mod.rs index fe5f604fe..3cd149942 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -18,6 +18,7 @@ pub mod polyline; pub mod raster_image; pub mod underlay; pub mod ray; +pub mod shape; pub mod solid; pub mod spline; pub mod text; diff --git a/src/entities/shape.rs b/src/entities/shape.rs new file mode 100644 index 000000000..fc0a8fffa --- /dev/null +++ b/src/entities/shape.rs @@ -0,0 +1,130 @@ +// SHAPE entity — reference to an .SHX shape-file glyph. +// +// Since .SHX binary files are not parsed, we render a small diamond marker +// at the insertion point (same approach as unknown/unsupported entities). +// The shape_name and style_name are surfaced as read-only properties. + +use acadrust::entities::Shape; +use glam::Vec3; + +use crate::command::EntityTransform; +use crate::entities::common::{ro_prop as ro, edit_prop as edit, square_grip}; +use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible}; +use crate::scene::acad_to_truck::{TruckEntity, TruckObject}; +use crate::scene::object::{GripApply, GripDef, PropSection}; +use crate::scene::wire_model::SnapHint; +use crate::scene::transform; + +// ── Marker geometry ─────────────────────────────────────────────────────────── + +/// Small diamond marker at the shape insertion point. +fn shape_marker(ox: f32, oy: f32, oz: f32, size: f32) -> Vec<[f32; 3]> { + let s = size * 0.5; + vec![ + [ox, oy + s, oz], // top + [ox + s, oy, oz], // right + [ox, oy - s, oz], // bottom + [ox - s, oy, oz], // left + [ox, oy + s, oz], // close + [f32::NAN; 3], + ] +} + +// ── TruckConvertible ────────────────────────────────────────────────────────── + +impl TruckConvertible for Shape { + fn to_truck(&self, _document: &acadrust::CadDocument) -> Option { + let ox = self.insertion_point.x as f32; + let oy = self.insertion_point.y as f32; + let oz = self.insertion_point.z as f32; + let size = (self.size as f32).abs().max(0.5); + + let snap_pt = Vec3::new(ox, oy, oz); + let pts = shape_marker(ox, oy, oz, size); + + Some(TruckEntity { + object: TruckObject::Lines(pts), + snap_pts: vec![(snap_pt, SnapHint::Insertion)], + tangent_geoms: vec![], + key_vertices: vec![[ox, oy, oz]], + }) + } +} + +// ── Grippable ───────────────────────────────────────────────────────────────── + +impl Grippable for Shape { + fn grips(&self) -> Vec { + vec![square_grip( + 0, + Vec3::new( + self.insertion_point.x as f32, + self.insertion_point.y as f32, + self.insertion_point.z as f32, + ), + )] + } + + fn apply_grip(&mut self, grip_id: usize, apply: GripApply) { + if grip_id == 0 { + match apply { + GripApply::Translate(d) => { + self.insertion_point.x += d.x as f64; + self.insertion_point.y += d.y as f64; + self.insertion_point.z += d.z as f64; + } + GripApply::Absolute(p) => { + self.insertion_point.x = p.x as f64; + self.insertion_point.y = p.y as f64; + self.insertion_point.z = p.z as f64; + } + } + } + } +} + +// ── PropertyEditable ────────────────────────────────────────────────────────── + +impl PropertyEditable for Shape { + fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection { + PropSection { + title: "Geometry".into(), + props: vec![ + ro("Name", "shp_name", self.shape_name.clone()), + ro("Style", "shp_style", self.style_name.clone()), + edit("Insert X", "shp_ix", self.insertion_point.x), + edit("Insert Y", "shp_iy", self.insertion_point.y), + edit("Insert Z", "shp_iz", self.insertion_point.z), + edit("Size", "shp_sz", self.size), + edit("Rotation","shp_rot", self.rotation.to_degrees()), + ], + } + } + + fn apply_geom_prop(&mut self, field: &str, value: &str) { + let Ok(v) = value.trim().parse::() else { return }; + match field { + "shp_ix" => self.insertion_point.x = v, + "shp_iy" => self.insertion_point.y = v, + "shp_iz" => self.insertion_point.z = v, + "shp_sz" => self.size = v.max(0.001), + "shp_rot" => self.rotation = v.to_radians(), + _ => {} + } + } +} + +// ── Transformable ───────────────────────────────────────────────────────────── + +impl Transformable for Shape { + fn apply_transform(&mut self, t: &EntityTransform) { + transform::apply_standard_entity_transform(self, t, |entity, p1, p2| { + transform::reflect_xy_point( + &mut entity.insertion_point.x, + &mut entity.insertion_point.y, + p1, + p2, + ); + }); + } +} diff --git a/src/entities/traits.rs b/src/entities/traits.rs index 30605a77f..a0ac3bdbc 100644 --- a/src/entities/traits.rs +++ b/src/entities/traits.rs @@ -62,6 +62,7 @@ impl EntityTypeOps for EntityType { EntityType::Leader(leader) => TruckConvertible::to_truck(leader, document), EntityType::MultiLeader(ml) => TruckConvertible::to_truck(ml, document), EntityType::Underlay(ul) => TruckConvertible::to_truck(ul, document), + EntityType::Shape(shp) => TruckConvertible::to_truck(shp, document), _ => None, } } @@ -100,6 +101,7 @@ impl EntityTypeOps for EntityType { EntityType::Dimension(dim) => Grippable::grips(dim), EntityType::Hatch(hatch) => Grippable::grips(hatch), EntityType::Underlay(ul) => Grippable::grips(ul), + EntityType::Shape(shp) => Grippable::grips(shp), _ => vec![], } } @@ -230,6 +232,10 @@ impl EntityTypeOps for EntityType { ul, text_style_names, )), + EntityType::Shape(shp) => Some(PropertyEditable::geometry_properties( + shp, + text_style_names, + )), _ => None, } } @@ -270,6 +276,7 @@ impl EntityTypeOps for EntityType { EntityType::Leader(leader) => PropertyEditable::apply_geom_prop(leader, field, value), EntityType::MultiLeader(ml) => PropertyEditable::apply_geom_prop(ml, field, value), EntityType::Underlay(ul) => PropertyEditable::apply_geom_prop(ul, field, value), + EntityType::Shape(shp) => PropertyEditable::apply_geom_prop(shp, field, value), _ => {} } } @@ -308,6 +315,7 @@ impl EntityTypeOps for EntityType { EntityType::Dimension(dim) => Grippable::apply_grip(dim, grip_id, apply), EntityType::Hatch(hatch) => Grippable::apply_grip(hatch, grip_id, apply), EntityType::Underlay(ul) => Grippable::apply_grip(ul, grip_id, apply), + EntityType::Shape(shp) => Grippable::apply_grip(shp, grip_id, apply), _ => {} } } @@ -346,6 +354,7 @@ impl EntityTypeOps for EntityType { EntityType::Leader(leader) => Transformable::apply_transform(leader, t), EntityType::MultiLeader(ml) => Transformable::apply_transform(ml, t), EntityType::Underlay(ul) => Transformable::apply_transform(ul, t), + EntityType::Shape(shp) => Transformable::apply_transform(shp, t), _ => {} } }