From 0b5c6863718abc0c44daf832f38331c4f542e0ab Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 21 Sep 2026 13:36:01 +0300 Subject: [PATCH] Dynamic dimensions: constant screen size, transform keeps the second constraint point Every dynamic dimension gets a DIMSCALE override for 12 px text, refreshed per zoom band, at creation and after load. A SCALE/ROTATE of an entity whose own end points a driving dimension measures now pins its second constraint point so the dimension moves the first end, as the reference does. Co-Authored-By: Claude Fable 5.1 --- src/app/command_driver.rs | 1 + src/app/update/viewport.rs | 2 + src/scene/dwg_native_constraints.rs | 3 ++ src/scene/mod.rs | 4 ++ src/scene/parametric_constraints.rs | 60 +++++++++++++++++++++++++++++ src/scene/parametric_solve.rs | 9 +++-- 6 files changed, 75 insertions(+), 4 deletions(-) diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index 1c435fd22..a1a093528 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -3688,6 +3688,7 @@ impl OpenCADStudio { ); // DYNCONSTRAINTDISPLAY 0 also keeps a new dynamic dimension off screen. self.tabs[i].scene.refresh_hidden_dynamic_dimensions(); + self.tabs[i].scene.refresh_dynamic_dimension_scales(true); self.tabs[i].dirty = true; self.tabs[i].active_cmd = None; self.tabs[i].snap_result = None; diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index c5279e5a8..dee93d74c 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -785,6 +785,8 @@ impl OpenCADStudio { pub(super) fn on_tick(&mut self, t: Instant) -> Task { let i = self.active_tab; self.tabs[i].scene.update(t - self.start); + // Dynamic dimensions keep their screen size across zoom bands. + self.tabs[i].scene.refresh_dynamic_dimension_scales(false); // If the camera moved since we last synced, write it back to // the document and mark the file dirty. diff --git a/src/scene/dwg_native_constraints.rs b/src/scene/dwg_native_constraints.rs index dc8174bf5..f1e4a208d 100644 --- a/src/scene/dwg_native_constraints.rs +++ b/src/scene/dwg_native_constraints.rs @@ -2810,6 +2810,9 @@ impl Scene { pub(crate) fn load_parametric_constraints_from_document(&mut self) { self.parametric_constraints.clear(); + // Dynamic dimensions read from the file get their screen size on the + // first tick after the view is known. + self.dynamic_dimension_band = 0.0; let owners: Vec<_> = self .document .block_records diff --git a/src/scene/mod.rs b/src/scene/mod.rs index bf4dec17d..aae7b130d 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -2302,6 +2302,9 @@ pub struct Scene { pub dynamic_constraint_display: bool, /// Dynamic dimensions DCHIDE or DYNCONSTRAINTDISPLAY 0 keep off screen. hidden_dynamic_dimensions: HashSet, + /// The zoom band (`quantize_wpp`) the dynamic dimensions were last + /// scaled for; 0 until the first scaling. + dynamic_dimension_band: f32, /// Session-only visibility overrides for constraint glyphs. hidden_parametric_constraints: HashSet<( parametric_constraints::ParametricScope, @@ -2635,6 +2638,7 @@ impl Scene { constraint_name_format: 2, dynamic_constraint_display: true, hidden_dynamic_dimensions: HashSet::default(), + dynamic_dimension_band: 0.0, hidden_parametric_constraints: HashSet::default(), shown_parametric_constraints: HashSet::default(), named_parameters: named_parameters::ParameterTable::new(), diff --git a/src/scene/parametric_constraints.rs b/src/scene/parametric_constraints.rs index eb8e2c0f6..a34b1989b 100644 --- a/src/scene/parametric_constraints.rs +++ b/src/scene/parametric_constraints.rs @@ -364,6 +364,9 @@ pub(crate) fn next_dimensional_parameter_name( /// The constraint-bar label that draws a dynamic dimension's lock mark. pub const DYNAMIC_DIMENSION_GLYPH: &str = "\u{1F512}"; +/// The on-screen text height of a dynamic dimension, in pixels. +pub const DYNAMIC_DIMENSION_TEXT_PX: f32 = 12.0; + /// The text a dynamic dimension shows: CONSTRAINTNAMEFORMAT 0 = name, /// 1 = value, 2 = name=value. pub(crate) fn dynamic_dimension_text(name: &str, value: f64, format: u8) -> String { @@ -1575,6 +1578,63 @@ impl super::Scene { .any(|set| set.dimensions.values().any(|dimension| *dimension == handle)) } + /// Gives every dynamic dimension a DIMSCALE override that keeps its + /// text, arrows and offsets at a screen size (the reference draws them + /// at a constant pixel size whatever the zoom). Runs once per zoom band; + /// `force` re-applies after a dimension was created or loaded. + pub fn refresh_dynamic_dimension_scales(&mut self, force: bool) { + let Some(band) = super::Scene::quantize_wpp(self.world_per_pixel()) else { + return; + }; + if !force && band == self.dynamic_dimension_band { + return; + } + self.dynamic_dimension_band = band; + let handles: Vec = self + .parametric_constraints + .iter() + .flat_map(|set| set.dimensions.values().copied()) + .collect(); + let mut changes = Vec::new(); + for handle in handles { + let Some(acadrust::EntityType::Dimension(dimension)) = self.document.get_entity(handle) + else { + continue; + }; + let style_name = dimension.base().style_name.clone(); + let text_height = self + .document + .dim_styles + .iter() + .find(|style| { + style.name.eq_ignore_ascii_case(&style_name) + || (style_name.trim().is_empty() + && style.name.eq_ignore_ascii_case("Standard")) + }) + .map(|style| style.dimtxt) + .filter(|height| *height > 1e-9) + .unwrap_or(0.18); + let scale = f64::from(band) * f64::from(DYNAMIC_DIMENSION_TEXT_PX) / text_height; + let current = crate::entities::dim_override::real( + &dimension.base().common.extended_data, + crate::entities::dim_override::DIMSCALE, + ); + if current.is_some_and(|value| (value - scale).abs() < 1e-9) { + continue; + } + crate::entities::dim_override::set( + &mut self.document, + handle, + crate::entities::dim_override::DIMSCALE, + Some(acadrust::xdata::XDataValue::Real(scale)), + ); + changes.push((handle, super::ChangeKind::Modified)); + } + if !changes.is_empty() { + self.bump_entities(&changes); + } + } + /// Re-derives which dynamic dimensions stay off screen (DCHIDE, or /// DYNCONSTRAINTDISPLAY 0 for all of them) and redraws the ones that /// changed. diff --git a/src/scene/parametric_solve.rs b/src/scene/parametric_solve.rs index c337bf2e7..3101a2103 100644 --- a/src/scene/parametric_solve.rs +++ b/src/scene/parametric_solve.rs @@ -2442,9 +2442,10 @@ fn solve_scope( }) .flatten(); // A transformed entity whose own end points a driving dimension - // measures keeps only the first constraint point where the transform - // put it; the dimension pulls the other end back to its value (a - // scaled line keeps its d1), as in the reference. + // measures keeps only its second constraint point where the transform + // put it; the dimension pulls the first end back to its value (a line + // scaled 2x about its start keeps its d1 by moving that start), as in + // the reference. let dimension_pin = (reference.marker.is_none() && axis_pin.is_none()) .then(|| { constraints.iter().find_map(|c| { @@ -2461,7 +2462,7 @@ fn solve_scope( && c.refs .iter() .all(|r| r.entity == reference.entity && r.marker.is_some())) - .then(|| c.refs[0]) + .then(|| c.refs[1]) }) }) .flatten();