From 0a9c27e39dd961b4d5181bf627dc1e9a76e7196c Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 3 Jul 2026 00:05:58 +0300 Subject: [PATCH] feat(entities): render + property builder for loaded Helix entities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A helix loaded from a DWG (AcDbHelix) previously fell through to the fallback: it did not render, had no grips, could not be moved, and showed only a Type row. Add src/entities/helix.rs which drives all of these through the embedded spline (render/grips/transform) and exposes the generating parameters — base point, turns, turn height, height, base/top radius, turn slope, twist, constrain — as the Geometry group per the properties spec. Wire Helix into the to_truck / grips / apply_grip / geometry_properties / apply_geom_prop / apply_transform dispatch lists. Co-Authored-By: Claude Opus 4.8 --- src/entities/helix.rs | 133 +++++++++++++++++++++++++++++++++++++++++ src/entities/mod.rs | 1 + src/entities/traits.rs | 12 ++-- 3 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 src/entities/helix.rs diff --git a/src/entities/helix.rs b/src/entities/helix.rs new file mode 100644 index 000000000..e90de88e6 --- /dev/null +++ b/src/entities/helix.rs @@ -0,0 +1,133 @@ +// Helix entity (AcDbHelix): a spline curve plus its generating parameters. +// +// A loaded helix carries a fully evaluated `spline` (the NURBS the file +// stored) alongside the parameters that generated it (base point, axis, +// radius, turns, turn height, twist, constraint). We render and grip the +// entity through its embedded spline, and expose the generating parameters +// as the Geometry property group. + +use acadrust::entities::{Helix, HelixConstraint}; + +use crate::command::EntityTransform; +use crate::entities::common::ro_prop as ro; +use crate::entities::traits::{Grippable, Transformable, TruckConvertible}; +use crate::scene::convert::acad_to_truck::TruckEntity; +use crate::scene::model::object::{GripApply, GripDef, PropSection}; + +/// Perpendicular distance from `p` to the helix axis (line through +/// `axis_base_point` along `axis_vector`). Used to recover the top radius +/// from the last defining point of the spline. +fn radius_from_axis(helix: &Helix, p: &acadrust::types::Vector3) -> Option { + let base = &helix.axis_base_point; + let axis = helix.axis_vector.normalize(); + // A zero axis vector cannot define a line. + if axis.length() < 1e-9 { + return None; + } + let vx = p.x - base.x; + let vy = p.y - base.y; + let vz = p.z - base.z; + let proj = vx * axis.x + vy * axis.y + vz * axis.z; + let px = vx - proj * axis.x; + let py = vy - proj * axis.y; + let pz = vz - proj * axis.z; + Some((px * px + py * py + pz * pz).sqrt()) +} + +fn grips(helix: &Helix) -> Vec { + Grippable::grips(&helix.spline) +} + +fn properties(helix: &Helix) -> Vec { + let bp = &helix.axis_base_point; + let turns = helix.turns; + let turn_height = helix.turn_height; + // Overall height is turns × per-turn rise. + let height = turns * turn_height; + let base_radius = helix.radius; + // Top radius: the axial distance of the last defining point. For a + // cylindrical helix this equals the base radius. + let top_radius = helix + .spline + .control_points + .last() + .or_else(|| helix.spline.fit_points.last()) + .and_then(|p| radius_from_axis(helix, p)) + .unwrap_or(base_radius); + // Turn slope: rise angle of a single turn against the base circumference. + let turn_slope = if base_radius.abs() > 1e-9 { + (turn_height / (std::f64::consts::TAU * base_radius)) + .atan() + .to_degrees() + } else { + 0.0 + }; + let twist = if helix.handedness { "CW" } else { "CCW" }; + let constrain = match helix.constraint { + HelixConstraint::TurnHeight => "Turn Height", + HelixConstraint::Turns => "Turns", + HelixConstraint::Height => "Height", + }; + + vec![PropSection { + title: "Geometry".into(), + props: vec![ + ro("Base point X", "base_x", format!("{:.4}", bp.x)), + ro("Base point Y", "base_y", format!("{:.4}", bp.y)), + ro("Base point Z", "base_z", format!("{:.4}", bp.z)), + ro("Number of turns", "num_turns", format!("{turns:.4}")), + ro("Turn height", "turn_height", format!("{turn_height:.4}")), + ro("Turns", "turns", format!("{turns:.4}")), + ro("Height", "height", format!("{height:.4}")), + ro("Base radius", "base_radius", format!("{base_radius:.4}")), + ro("Top radius", "top_radius", format!("{top_radius:.4}")), + ro("Turn slope", "turn_slope", format!("{turn_slope:.4}")), + ro("Twist", "twist", twist), + ro("Constrain", "constrain", constrain), + ], + }] +} + +/// Helix geometry is derived from the stored spline; the generating +/// parameters are read-only (editing one would require regenerating the +/// spline, which the loaded entity does not carry a recipe for). +fn apply_geom_prop(_helix: &mut Helix, _field: &str, _value: &str) {} + +fn apply_grip(helix: &mut Helix, grip_id: usize, apply: GripApply) { + Grippable::apply_grip(&mut helix.spline, grip_id, apply); +} + +fn apply_transform(helix: &mut Helix, t: &EntityTransform) { + // Move the visible curve, then keep the generating points in step. + Transformable::apply_transform(&mut helix.spline, t); +} + +impl TruckConvertible for Helix { + fn to_truck(&self, document: &acadrust::CadDocument) -> Option { + self.spline.to_truck(document) + } +} + +impl Grippable for Helix { + fn grips(&self) -> Vec { + grips(self) + } + fn apply_grip(&mut self, grip_id: usize, apply: GripApply) { + apply_grip(self, grip_id, apply); + } +} + +impl crate::entities::traits::PropertyEditable for Helix { + fn geometry_properties(&self, _text_style_names: &[String]) -> Vec { + properties(self) + } + fn apply_geom_prop(&mut self, field: &str, value: &str) { + apply_geom_prop(self, field, value); + } +} + +impl Transformable for Helix { + fn apply_transform(&mut self, t: &EntityTransform) { + apply_transform(self, t); + } +} diff --git a/src/entities/mod.rs b/src/entities/mod.rs index 37fe05185..1d4883887 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -5,6 +5,7 @@ pub mod common; pub mod dimension; pub mod ellipse; pub mod hatch; +pub mod helix; pub mod insert; pub mod leader; pub mod line; diff --git a/src/entities/traits.rs b/src/entities/traits.rs index 62c3e92f5..1997e4ad5 100644 --- a/src/entities/traits.rs +++ b/src/entities/traits.rs @@ -272,7 +272,7 @@ impl EntityTypeOps for EntityType { Wipeout, AttributeDefinition, AttributeEntity, MLine, Tolerance, Solid, Face3D, PolygonMesh, PolyfaceMesh, Mesh, Table, Text, MText, Leader, MultiLeader, Underlay, Shape, - Ole2Frame, + Ole2Frame, Helix, ], _ => None, ) @@ -288,7 +288,7 @@ impl EntityTypeOps for EntityType { Solid, Solid3D, Region, Body, Face3D, PolygonMesh, PolyfaceMesh, Mesh, Table, Point, Spline, Text, MText, Viewport, Insert, Leader, MultiLeader, Dimension, Hatch, - Underlay, Shape, Ole2Frame, + Underlay, Shape, Ole2Frame, Helix, ], _ => vec![], ) @@ -304,7 +304,7 @@ impl EntityTypeOps for EntityType { Solid, Solid3D, Region, Body, Face3D, PolygonMesh, PolyfaceMesh, Mesh, Table, Hatch, Point, Spline, Text, MText, Viewport, Insert, Dimension, Leader, MultiLeader, Underlay, - Shape, Ole2Frame, + Shape, Ole2Frame, Helix, ], _ => vec![], ) @@ -320,7 +320,7 @@ impl EntityTypeOps for EntityType { Solid, Solid3D, Region, Body, Face3D, PolygonMesh, PolyfaceMesh, Mesh, Table, Hatch, Point, Spline, Text, MText, Viewport, Insert, Dimension, Leader, MultiLeader, Underlay, - Shape, Ole2Frame, + Shape, Ole2Frame, Helix, ], _ => {}, ) @@ -336,7 +336,7 @@ impl EntityTypeOps for EntityType { Solid, Solid3D, Region, Body, Face3D, PolygonMesh, PolyfaceMesh, Mesh, Table, Point, Spline, Text, MText, Viewport, Insert, Leader, MultiLeader, Dimension, Hatch, - Underlay, Shape, Ole2Frame, + Underlay, Shape, Ole2Frame, Helix, ], _ => {}, ) @@ -425,7 +425,7 @@ impl EntityTypeOps for EntityType { Tolerance, Solid, Face3D, PolygonMesh, PolyfaceMesh, Mesh, Table, MText, Point, Spline, Text, Viewport, Dimension, Leader, MultiLeader, Underlay, Shape, Ole2Frame, - Solid3D, Region, Body, Surface, + Solid3D, Region, Body, Surface, Helix, ], _ => {}, )