From de1f1c0e241572ff6a4fa56b006129c5ee3427fc Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sun, 21 Jun 2026 02:46:56 +0300 Subject: [PATCH] fix(3d): move and paste ACIS solids to the right place Solids were missing from the entity transform dispatch, so move / rotate / scale / mirror were silently no-ops on a 3D solid (only its wires shifted) and a pasted solid stayed at its original location. Add a Transformable impl for Solid3D/Region/Body/Surface (delegating to acadrust, which now composes the move into the ACIS body placement) and register them in the dispatcher. Also make cross-drawing paste land at the cursor: the clipboard centroid is measured in the source drawing's offset-relative frame, so correct the paste translation by the source/target world_offset difference (zero within one drawing). Bumps acadrust for the solid-geometry transform fixes. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 2 +- src/app/cmd_result.rs | 14 +++++++++++++- src/app/commands.rs | 2 ++ src/app/mod.rs | 7 +++++++ src/entities/solid3d.rs | 27 +++++++++++++++++++++++++-- src/entities/traits.rs | 1 + 6 files changed, 49 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c6908a6..0d5b5e08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,7 +70,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "acadrust" version = "0.3.4" -source = "git+https://github.com/HakanSeven12/acadrust?branch=main#33cb8b72dbf9f3dc585619a31848b42c5acf8c81" +source = "git+https://github.com/HakanSeven12/acadrust?branch=main#07006d104150006332d2873413afba9620cf0aa3" dependencies = [ "ahash", "anyhow", diff --git a/src/app/cmd_result.rs b/src/app/cmd_result.rs index d27d118f..06b9c17e 100644 --- a/src/app/cmd_result.rs +++ b/src/app/cmd_result.rs @@ -594,7 +594,19 @@ impl OpenCADStudio { if self.clipboard.is_empty() { self.command_line.push_error("Clipboard is empty."); } else { - let delta = base_pt - self.clipboard_centroid; + // The centroid was measured in the source drawing's + // offset-relative frame and `base_pt` is in this drawing's; + // correct by the world_offset difference so a cross-drawing + // paste lands at the cursor instead of drifting by the + // offset gap. Zero for a same-drawing paste. (#135) + let src_wo = self.clipboard_world_offset; + let tgt_wo = self.tabs[i].scene.world_offset; + let wo_corr = glam::Vec3::new( + (tgt_wo[0] - src_wo[0]) as f32, + (tgt_wo[1] - src_wo[1]) as f32, + (tgt_wo[2] - src_wo[2]) as f32, + ); + let delta = base_pt - self.clipboard_centroid + wo_corr; let translate = crate::command::EntityTransform::Translate(delta); self.push_undo_snapshot(i, "PASTECLIP"); // Recreate any layer / linetype / style the copied entities diff --git a/src/app/commands.rs b/src/app/commands.rs index f7780309..e280e3b2 100644 --- a/src/app/commands.rs +++ b/src/app/commands.rs @@ -540,6 +540,7 @@ impl OpenCADStudio { self.clipboard_centroid = super::helpers::entities_centroid( &self.tabs[i].scene.wire_models_for(&handles), ); + self.clipboard_world_offset = self.tabs[i].scene.world_offset; self.clipboard = entities; self.clipboard_deps = super::ClipboardDeps::capture( &self.tabs[i].scene.document, @@ -572,6 +573,7 @@ impl OpenCADStudio { self.clipboard_centroid = super::helpers::entities_centroid( &self.tabs[i].scene.wire_models_for(&handles), ); + self.clipboard_world_offset = self.tabs[i].scene.world_offset; let count = entities.len(); self.clipboard = entities; self.clipboard_deps = super::ClipboardDeps::capture( diff --git a/src/app/mod.rs b/src/app/mod.rs index e84b7c78..3c85e2f3 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -369,6 +369,12 @@ pub(super) struct OpenCADStudio { clipboard: Vec, /// Centroid of the clipboard entities (world XY plane). clipboard_centroid: glam::Vec3, + /// The source drawing's `world_offset` at copy time. The centroid above is + /// measured in that drawing's offset-relative frame; pasting into a drawing + /// with a different offset (e.g. a large-coordinate file → a fresh one) + /// must correct the paste translation by the offset difference, else the + /// objects land `source_offset − target_offset` away from the cursor. + clipboard_world_offset: [f64; 3], /// Table records (layer / linetype / text + dim style) the clipboard /// entities reference, captured from the source drawing at copy time so a /// paste into a *different* drawing can recreate any that are missing — @@ -1721,6 +1727,7 @@ impl OpenCADStudio { update_notice_body: None, clipboard: Vec::new(), clipboard_centroid: glam::Vec3::ZERO, + clipboard_world_offset: [0.0; 3], clipboard_deps: ClipboardDeps::default(), shift_down: false, mtext_editor: None, diff --git a/src/entities/solid3d.rs b/src/entities/solid3d.rs index 1a97cdb2..64e6f5b3 100644 --- a/src/entities/solid3d.rs +++ b/src/entities/solid3d.rs @@ -6,13 +6,36 @@ // fallback stays in sync; the caller (scene/mod.rs apply_grip) translates // the MeshModel vertices to match. -use acadrust::entities::{Body, Region, Solid3D}; +use acadrust::entities::{Body, Region, Solid3D, Surface}; use glam::Vec3; +use crate::command::EntityTransform; use crate::entities::common::{center_grip, ro_prop as ro}; -use crate::entities::traits::{Grippable, PropertyEditable}; +use crate::entities::traits::{Grippable, PropertyEditable, Transformable}; use crate::scene::model::object::{GripApply, GripDef, PropSection}; +/// Shared transform for the ACIS volume entities. Translate / rotate / scale +/// delegate to acadrust (which composes the move into the solid's ACIS +/// placement), and mirror delegates via a reflection transform. Without this +/// the entity dispatcher treated solids as non-transformable, so a moved or +/// pasted solid stayed at its original ACIS placement. +macro_rules! impl_acis_transformable { + ($ty:ty) => { + impl Transformable for $ty { + fn apply_transform(&mut self, t: &EntityTransform) { + crate::scene::view::transform::apply_standard_entity_transform(self, t, |e, p1, p2| { + let m = crate::scene::view::transform::reflection_about_xy_line(p1, p2); + acadrust::Entity::apply_transform(e, &m); + }); + } + } + }; +} +impl_acis_transformable!(Solid3D); +impl_acis_transformable!(Region); +impl_acis_transformable!(Body); +impl_acis_transformable!(Surface); + // ── shared helpers ──────────────────────────────────────────────────────────── fn dvec3(v: &acadrust::types::Vector3) -> glam::DVec3 { diff --git a/src/entities/traits.rs b/src/entities/traits.rs index 64c763b7..269012e8 100644 --- a/src/entities/traits.rs +++ b/src/entities/traits.rs @@ -422,6 +422,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, ], _ => {}, )