From fd2062bdec95780d0d7d58356397a44c0c740a5b Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sun, 20 Sep 2026 21:31:54 +0300 Subject: [PATCH] fix(insert): attribute grips follow ATTMODE 2 and annotative scale Follow-up to the block-attribute grips picked from #1386. - Movability no longer includes the invisible flag: an attribute ATTMODE 2 displays is grippable, so the grip the display-aware list shows can be dragged. The document-less grips() still skips invisible attributes, since it cannot consult ATTMODE. - The grip sits on the displayed attribute, which an annotative insert scales about its insertion point; Scene::apply_grip now unscales the dragged target the same way before the attribute stores it, so the grip no longer jumps to twice the drag at scale 2. Co-Authored-By: Claude Fable 5.1 --- src/entities/insert.rs | 45 ++++++++++++++++++++++++++++++++++-------- src/scene/modify.rs | 11 +++++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/entities/insert.rs b/src/entities/insert.rs index 343df25d3..5fe92b024 100644 --- a/src/entities/insert.rs +++ b/src/entities/insert.rs @@ -20,11 +20,39 @@ use crate::scene::view::render; const ATTRIBUTE_GRIP_BASE: usize = 1; /// Whether an attribute is one the user can pick up and reposition. A constant -/// attribute belongs to the block definition rather than this insert, and an -/// invisible one is not drawn, so neither has a grip to offer. The attribute -/// itself declines a locked position. +/// attribute belongs to the block definition rather than this insert, and a +/// locked position cannot be dragged. Visibility is a separate question: an +/// invisible attribute that ATTMODE 2 displays is grippable while it shows. fn attribute_is_movable(att: &acadrust::entities::AttributeEntity) -> bool { - !att.flags.constant && !att.flags.invisible + !att.flags.constant && !att.lock_position && !att.flags.locked_position +} + +/// A dragged attribute grip, mapped back to the attribute's stored position. +/// The grip is placed on the displayed attribute, which an annotative insert +/// scales about its insertion point; the target has to be unscaled the same +/// way or the attribute lands `scale` times too far and the grip jumps. +pub(crate) fn unscale_attribute_grip( + insert: &Insert, + annotation_scale: f32, + grip_id: usize, + apply: GripApply, +) -> GripApply { + if grip_id < ATTRIBUTE_GRIP_BASE { + return apply; + } + let block_scale = insert_attribute_block_scale( + insert, + annotation_scale, + crate::scene::BlockScalePolicy::FromInsert, + ); + if (block_scale - 1.0).abs() <= 1.0e-6 { + return apply; + } + let origin = glam::DVec3::new(insert.insert_point.x, insert.insert_point.y, insert.insert_point.z); + match apply { + GripApply::Absolute(target) => GripApply::Absolute(origin + (target - origin) / block_scale), + GripApply::Translate(delta) => GripApply::Translate(delta / block_scale), + } } fn attribute_is_render_visible( @@ -89,8 +117,11 @@ fn grips(ins: &Insert) -> Vec { // block definition put it without dragging the whole block (#1259). // Attributes are stored beside the block in world space — the same space // `apply_grip` already translates them in. + // Without the document there is no ATTMODE to consult, so only an + // attribute that is drawn on its own terms gets a grip here; + // `visible_attribute_grips` is the display-aware list. out.extend(ins.attributes.iter().enumerate().filter_map(|(i, att)| { - if !attribute_is_movable(att) { + if !attribute_is_movable(att) || att.flags.invisible || att.common.invisible { return None; } let grip = Grippable::grips(att).into_iter().next()?; @@ -121,9 +152,7 @@ pub(crate) fn visible_attribute_grips( .iter() .enumerate() .filter_map(|(i, attribute)| { - if attribute.flags.constant - || attribute.lock_position - || attribute.flags.locked_position + if !attribute_is_movable(attribute) || !attribute_is_render_visible(document, visibility, attribute) { return None; diff --git a/src/scene/modify.rs b/src/scene/modify.rs index e4ae6e69c..0cecabf48 100644 --- a/src/scene/modify.rs +++ b/src/scene/modify.rs @@ -1443,6 +1443,17 @@ impl Scene { if self.is_layer_locked(handle) { return; } + // An attribute grip sits on the displayed (annotation-scaled) + // attribute; the stored one is unscaled. + let apply = match self.document.get_entity(handle) { + Some(EntityType::Insert(insert)) => crate::entities::insert::unscale_attribute_grip( + insert, + self.annotation_scale, + grip_id, + apply, + ), + _ => apply, + }; if self.apply_solid_history_grip(handle, grip_id, apply.clone()) { return; }