diff --git a/src/app/document.rs b/src/app/document.rs index f9bef2bbb..209bd502b 100644 --- a/src/app/document.rs +++ b/src/app/document.rs @@ -206,6 +206,12 @@ pub(super) struct DocumentTab { pub(super) dyn_active: usize, pub(super) history: HistoryState, pub(super) active_layer: String, + /// Block references whose properties panel shows per-axis Scale X/Y/Z even + /// though the three factors are currently equal — the user unchecked the + /// "Uniform scale" box for them (#427). Per-tab: fresh documents restart + /// handle numbering, so an app-global set keyed by raw handle leaks one + /// tab's opt-out into another tab reusing the value (#47). + pub(super) props_asym_scale: std::collections::HashSet, /// Node graph shown by the command line's graph button. pub(super) graph: crate::ui::node_graph::Graph, /// Currently active UCS. `None` means WCS (identity transform). @@ -649,6 +655,7 @@ impl DocumentTab { dyn_active: 0, history: HistoryState::default(), active_layer: "0".to_string(), + props_asym_scale: std::collections::HashSet::new(), graph: Default::default(), active_ucs: None, sketch_session: None, diff --git a/src/app/mod.rs b/src/app/mod.rs index cb9aa099d..37a73f74c 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -419,10 +419,6 @@ pub(super) struct OpenCADStudio { discussions: Vec, /// True while the boot-time Discussions refresh is still in flight. discussions_loading: bool, - /// Block references whose properties panel shows per-axis Scale X/Y/Z even - /// though the three factors are currently equal — the user unchecked the - /// "Uniform scale" box for them (#427). Keyed by entity handle. - props_asym_scale: std::collections::HashSet, /// Collapsed Properties-panel section titles. This belongs to the app, /// rather than an individual document tab, so the same view preference is /// used by every currently open drawing/project. @@ -4110,7 +4106,6 @@ impl OpenCADStudio { videos_loading: false, discussions: Vec::new(), discussions_loading: false, - props_asym_scale: std::collections::HashSet::new(), collapsed_property_sections: rustc_hash::FxHashSet::default(), start_section: StartSection::default(), start_action_w: std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0)), diff --git a/src/app/properties.rs b/src/app/properties.rs index 3883c7f50..3237203e0 100644 --- a/src/app/properties.rs +++ b/src/app/properties.rs @@ -463,7 +463,7 @@ impl OpenCADStudio { if let codec::EntityType::Insert(ins) = entity { let eq = (ins.x_scale() - ins.y_scale()).abs() < 1e-12 && (ins.x_scale() - ins.z_scale()).abs() < 1e-12; - let uniform = eq && !self.props_asym_scale.contains(&handle.value()); + let uniform = eq && !self.tabs[i].props_asym_scale.contains(&handle.value()); for section in sections.iter_mut() { let Some(xi) = section.props.iter().position(|p| p.field == "x_scale") else { @@ -4373,6 +4373,45 @@ fn apply_insert_unit_scale(ins: &mut codec::entities::Insert, ratio: f64) -> boo true } +#[cfg(test)] +mod asym_scale_tests { + // #47: the uniform-scale opt-out must be per-tab. Fresh documents restart + // handle numbering (seed 1), so two tabs hand out identical handles — an + // app-global set leaks one tab's opt-out into the other. + use crate::app::OpenCADStudio; + + fn tab_with_insert(app: &mut OpenCADStudio) -> (usize, codec::Handle) { + app.tab_counter += 1; + let n = app.tab_counter; + app.tabs + .push(crate::app::document::DocumentTab::new_drawing(n)); + let idx = app.tabs.len() - 1; + app.active_tab = idx; + let ins = + codec::entities::Insert::new("B", codec::types::Vector3::ZERO); + let h = app + .commit_entity_handle(codec::EntityType::Insert(ins)) + .expect("insert commits"); + (idx, h) + } + + #[test] + fn asym_scale_opt_out_does_not_leak_across_tabs() { + let mut app = OpenCADStudio::new_for_test(); + let (a, ha) = tab_with_insert(&mut app); + let (b, hb) = tab_with_insert(&mut app); + assert_eq!( + ha, hb, + "premise: identically built fresh docs reuse handle values" + ); + app.tabs[a].props_asym_scale.insert(ha.value()); + assert!( + !app.tabs[b].props_asym_scale.contains(&hb.value()), + "tab B must not see tab A's opt-out" + ); + } +} + #[cfg(test)] mod insert_unit_scale_tests { use super::{apply_insert_unit_scale, format_unit_factor, insert_unit_scale, insunits_to_mm}; diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 985df0ebb..f3dcbc669 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -6895,11 +6895,11 @@ impl OpenCADStudio { }; let Some((sx, sy, sz)) = scales else { return }; let eq = (sx - sy).abs() < 1e-12 && (sx - sz).abs() < 1e-12; - let checked = eq && !app.props_asym_scale.contains(&handle.value()); + let checked = eq && !app.tabs[i].props_asym_scale.contains(&handle.value()); if checked { - app.props_asym_scale.insert(handle.value()); + app.tabs[i].props_asym_scale.insert(handle.value()); } else { - app.props_asym_scale.remove(&handle.value()); + app.tabs[i].props_asym_scale.remove(&handle.value()); if let Some(codec::EntityType::Insert(ins)) = app.tabs[i].scene.document.get_entity_mut(handle) { diff --git a/src/ui/overlay.rs b/src/ui/overlay.rs index e8cd3f062..0b4804fa7 100644 --- a/src/ui/overlay.rs +++ b/src/ui/overlay.rs @@ -2961,7 +2961,6 @@ fn draw_axes( axes: (Vec3, Vec3, Vec3), bg_luminance: f32, ) { - let (ax, ay, az) = axes; let axis_stroke = |r: f32, g: f32, b: f32| canvas::Stroke { width: 1.5, style: canvas::Style::Solid(Color { r, g, b, a: 0.85 }),