From f000a8acfb7b7ff8e27fa804937e6f478bfb5331 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Tue, 5 May 2026 20:12:44 +0300 Subject: [PATCH] feat: add OLE2Frame interactivity + fix Shape rotation from DXF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OLE2Frame: full TruckConvertible/Grippable/PropertyEditable/Transformable impls in src/entities/ole2frame.rs — grips on corners + center, properties panel, translate/scale transforms. Shape: rotation (DXF code 50) now converted degrees→radians on load in fix_dxf_dimension_rotations(), matching Dimension and Attribute behaviour. All ❌ gaps resolved. Remaining: Viewport GPU scissor, LWPolyline plinegen. Co-Authored-By: Claude Sonnet 4.6 --- INTEGRATION_GAPS.md | 10 +-- src/entities/mod.rs | 1 + src/entities/ole2frame.rs | 151 ++++++++++++++++++++++++++++++++++++++ src/entities/traits.rs | 9 +++ src/io/mod.rs | 3 + 5 files changed, 169 insertions(+), 5 deletions(-) create mode 100644 src/entities/ole2frame.rs diff --git a/INTEGRATION_GAPS.md b/INTEGRATION_GAPS.md index 433bc5616..c19bca3bc 100644 --- a/INTEGRATION_GAPS.md +++ b/INTEGRATION_GAPS.md @@ -43,7 +43,7 @@ Legend: ✅ Done · ⚠️ Partial · ❌ Not done | Status | Entity | Notes | |---|---|---| | ✅ | **Face3D** | Full tessellation, grips, properties panel — `src/entities/mesh.rs` | -| ❌ | **OLE2Frame** | Bounding box + X mark only; no interactivity | +| ✅ | **OLE2Frame** | Bounding box + X mark with grips, properties panel, transform — `src/entities/ole2frame.rs` | ### Wire Fallback Only (no full mesh) @@ -189,7 +189,7 @@ These are fixed in our post-load `fix_dxf_dimension_rotations()` in `src/io/mod. | ✅ | **Dimension (all)** | `text_rotation` | 53 | `src/scene/tessellate.rs::dimension_text_natural_rotation()` | | ✅ | **AttributeEntity** | `rotation` | 50 | `src/io/mod.rs:224` | | ✅ | **AttributeDefinition** | `rotation` | 50 | `src/io/mod.rs:219` | -| ❌ | **Shape** | `rotation` | 50 | Shape not currently rendered — low priority | +| ✅ | **Shape** | `rotation` | 50 | Fixed in `fix_dxf_dimension_rotations()` — `src/io/mod.rs` | --- @@ -207,13 +207,13 @@ These are fixed in our post-load `fix_dxf_dimension_rotations()` in `src/io/mod. | Status | Count | |---|---| -| ✅ Done | 44 | +| ✅ Done | 46 | | ⚠️ Partial | 3 | -| ❌ Not done | 2 | +| ❌ Not done | 0 | | **Total** | **49** | ### Remaining gaps by priority **Medium:** Viewport GPU scissor rect (pixel-level boundary clipping for overlapping viewports) -**Low:** LWPolyline plinegen (GPU shader change needed) · OLE2Frame improvement · Shape rotation +**Low:** LWPolyline plinegen (GPU shader change needed) diff --git a/src/entities/mod.rs b/src/entities/mod.rs index 5ccbb4899..b1aa9e247 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -13,6 +13,7 @@ pub mod lwpolyline; pub mod mesh; pub mod mline; pub mod mtext; +pub mod ole2frame; pub mod point; pub mod polyline; pub mod raster_image; diff --git a/src/entities/ole2frame.rs b/src/entities/ole2frame.rs new file mode 100644 index 000000000..b2a598b67 --- /dev/null +++ b/src/entities/ole2frame.rs @@ -0,0 +1,151 @@ +use acadrust::entities::{Ole2Frame, OleObjectType}; +use glam::Vec3; + +use crate::command::EntityTransform; +use crate::entities::common::{diamond_grip, 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; + +fn to_truck(ole: &Ole2Frame) -> TruckEntity { + let x0 = ole.upper_left_corner.x as f32; + let y0 = ole.lower_right_corner.y as f32; + let x1 = ole.lower_right_corner.x as f32; + let y1 = ole.upper_left_corner.y as f32; + let z = ole.upper_left_corner.z as f32; + + if (x1 - x0).abs() < 1e-6 && (y1 - y0).abs() < 1e-6 { + let s = 0.5_f32; + return TruckEntity { + object: TruckObject::Lines(vec![[-s, 0.0, z], [s, 0.0, z]]), + snap_pts: vec![], + tangent_geoms: vec![], + key_vertices: vec![], + }; + } + + let cx = (x0 + x1) * 0.5; + let cy = (y0 + y1) * 0.5; + let pts = vec![ + // Rectangle + [x0, y0, z], [x1, y0, z], [x1, y0, z], [x1, y1, z], + [x1, y1, z], [x0, y1, z], [x0, y1, z], [x0, y0, z], + // Diagonal X + [x0, y0, z], [x1, y1, z], + [f32::NAN; 3], + [x1, y0, z], [x0, y1, z], + ]; + let center = Vec3::new(cx, cy, z); + TruckEntity { + object: TruckObject::Lines(pts), + snap_pts: vec![(center, SnapHint::Center)], + tangent_geoms: vec![], + key_vertices: vec![[x0, y0, z], [x1, y1, z]], + } +} + +fn grips(ole: &Ole2Frame) -> Vec { + let ul = Vec3::new( + ole.upper_left_corner.x as f32, + ole.upper_left_corner.y as f32, + ole.upper_left_corner.z as f32, + ); + let lr = Vec3::new( + ole.lower_right_corner.x as f32, + ole.lower_right_corner.y as f32, + ole.lower_right_corner.z as f32, + ); + let center = (ul + lr) * 0.5; + vec![square_grip(0, ul), square_grip(1, lr), diamond_grip(2, center)] +} + +fn properties(ole: &Ole2Frame) -> PropSection { + let type_str = match ole.ole_object_type { + OleObjectType::Link => "Link", + OleObjectType::Embedded => "Embedded", + OleObjectType::Static => "Static", + }; + PropSection { + title: "Geometry".into(), + props: vec![ + ro("Type", "ole_type", type_str), + edit("Upper Left X", "ole_ulx", ole.upper_left_corner.x), + edit("Upper Left Y", "ole_uly", ole.upper_left_corner.y), + edit("Lower Right X", "ole_lrx", ole.lower_right_corner.x), + edit("Lower Right Y", "ole_lry", ole.lower_right_corner.y), + ], + } +} + +fn apply_geom_prop(ole: &mut Ole2Frame, field: &str, value: &str) { + let Ok(v) = value.trim().parse::() else { return }; + match field { + "ole_ulx" => ole.upper_left_corner.x = v, + "ole_uly" => ole.upper_left_corner.y = v, + "ole_lrx" => ole.lower_right_corner.x = v, + "ole_lry" => ole.lower_right_corner.y = v, + _ => {} + } +} + +fn apply_grip(ole: &mut Ole2Frame, grip_id: usize, apply: GripApply) { + match (grip_id, apply) { + (0, GripApply::Absolute(p)) => { + ole.upper_left_corner.x = p.x as f64; + ole.upper_left_corner.y = p.y as f64; + } + (1, GripApply::Absolute(p)) => { + ole.lower_right_corner.x = p.x as f64; + ole.lower_right_corner.y = p.y as f64; + } + (2, GripApply::Translate(d)) => { + ole.upper_left_corner.x += d.x as f64; + ole.upper_left_corner.y += d.y as f64; + ole.lower_right_corner.x += d.x as f64; + ole.lower_right_corner.y += d.y as f64; + } + _ => {} + } +} + +fn apply_transform(ole: &mut Ole2Frame, t: &EntityTransform) { + match t { + EntityTransform::Translate(d) => { + ole.upper_left_corner.x += d.x as f64; + ole.upper_left_corner.y += d.y as f64; + ole.upper_left_corner.z += d.z as f64; + ole.lower_right_corner.x += d.x as f64; + ole.lower_right_corner.y += d.y as f64; + ole.lower_right_corner.z += d.z as f64; + } + EntityTransform::Scale { center, factor } => { + let scale = |v: f64, c: f64| c + (v - c) * (*factor as f64); + ole.upper_left_corner.x = scale(ole.upper_left_corner.x, center.x as f64); + ole.upper_left_corner.y = scale(ole.upper_left_corner.y, center.y as f64); + ole.lower_right_corner.x = scale(ole.lower_right_corner.x, center.x as f64); + ole.lower_right_corner.y = scale(ole.lower_right_corner.y, center.y as f64); + } + _ => {} + } +} + +impl TruckConvertible for Ole2Frame { + fn to_truck(&self, _document: &acadrust::CadDocument) -> Option { + Some(to_truck(self)) + } +} + +impl Grippable for Ole2Frame { + fn grips(&self) -> Vec { grips(self) } + fn apply_grip(&mut self, grip_id: usize, apply: GripApply) { apply_grip(self, grip_id, apply); } +} + +impl PropertyEditable for Ole2Frame { + fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection { properties(self) } + fn apply_geom_prop(&mut self, field: &str, value: &str) { apply_geom_prop(self, field, value); } +} + +impl Transformable for Ole2Frame { + fn apply_transform(&mut self, t: &EntityTransform) { apply_transform(self, t); } +} diff --git a/src/entities/traits.rs b/src/entities/traits.rs index a0ac3bdbc..b9eb0f613 100644 --- a/src/entities/traits.rs +++ b/src/entities/traits.rs @@ -63,6 +63,7 @@ impl EntityTypeOps for EntityType { EntityType::MultiLeader(ml) => TruckConvertible::to_truck(ml, document), EntityType::Underlay(ul) => TruckConvertible::to_truck(ul, document), EntityType::Shape(shp) => TruckConvertible::to_truck(shp, document), + EntityType::Ole2Frame(ole) => TruckConvertible::to_truck(ole, document), _ => None, } } @@ -102,6 +103,7 @@ impl EntityTypeOps for EntityType { EntityType::Hatch(hatch) => Grippable::grips(hatch), EntityType::Underlay(ul) => Grippable::grips(ul), EntityType::Shape(shp) => Grippable::grips(shp), + EntityType::Ole2Frame(ole) => Grippable::grips(ole), _ => vec![], } } @@ -236,6 +238,10 @@ impl EntityTypeOps for EntityType { shp, text_style_names, )), + EntityType::Ole2Frame(ole) => Some(PropertyEditable::geometry_properties( + ole, + text_style_names, + )), _ => None, } } @@ -277,6 +283,7 @@ impl EntityTypeOps for EntityType { 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), + EntityType::Ole2Frame(ole) => PropertyEditable::apply_geom_prop(ole, field, value), _ => {} } } @@ -316,6 +323,7 @@ impl EntityTypeOps for EntityType { 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), + EntityType::Ole2Frame(ole) => Grippable::apply_grip(ole, grip_id, apply), _ => {} } } @@ -355,6 +363,7 @@ impl EntityTypeOps for EntityType { EntityType::MultiLeader(ml) => Transformable::apply_transform(ml, t), EntityType::Underlay(ul) => Transformable::apply_transform(ul, t), EntityType::Shape(shp) => Transformable::apply_transform(shp, t), + EntityType::Ole2Frame(ole) => Transformable::apply_transform(ole, t), _ => {} } } diff --git a/src/io/mod.rs b/src/io/mod.rs index 085e06622..7f486afa4 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -221,6 +221,9 @@ fn fix_dxf_dimension_rotations(doc: &mut CadDocument) { EntityType::AttributeEntity(a) => { a.rotation = a.rotation.to_radians(); } + EntityType::Shape(s) => { + s.rotation = s.rotation.to_radians(); + } _ => {} } }