From 256f9a282bcda067a185a96cf22994ca2481b4fe Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 10 Jul 2026 16:05:12 +0300 Subject: [PATCH] refactor(settings): drop Ortho/OSNAP from settings.txt, now per-drawing only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ortho and the running object-snap set are stored in the drawing header ($ORTHOMODE / $OSMODE) since they were bound to the file, so persisting them app-globally in settings.txt too was redundant. Remove the ortho, osnap and snap_modes keys (and the now-dead modes_from / snap_id / snap_from_id helpers) from UserSettings, and stop reading/writing them in apply_settings / current_settings. No boot behavior change: Snapper::new already defaults to the same running-snap set (Endpoint, Midpoint, Center, Node, Quadrant, Intersection, Nearest) with the master off, and Ortho defaults to false — exactly what settings.txt used to seed. Old settings.txt files keep working; the stale keys are simply ignored. Co-Authored-By: Claude Opus 4.8 --- src/app/settings.rs | 73 ++++-------------------------------------- src/app/update/file.rs | 8 ++--- 2 files changed, 9 insertions(+), 72 deletions(-) diff --git a/src/app/settings.rs b/src/app/settings.rs index c58f1cb6..6f61bcc6 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -11,8 +11,9 @@ use crate::snap::SnapType; use std::path::PathBuf; -/// Canonical order for serializing snap modes, so the written list is stable -/// (a `HashSet` iterates in arbitrary order). +/// Canonical order of the user-toggleable object-snap modes. Drives the +/// deterministic order when decoding the `$OSMODE` bitmask (see +/// [`snaps_from_osmode`]). const SNAP_ORDER: &[SnapType] = &[ SnapType::Endpoint, SnapType::Midpoint, @@ -32,32 +33,6 @@ const SNAP_ORDER: &[SnapType] = &[ // persisted set. (#121) ]; -fn snap_id(s: SnapType) -> &'static str { - match s { - SnapType::Endpoint => "endpoint", - SnapType::Midpoint => "midpoint", - SnapType::Center => "center", - SnapType::Node => "node", - SnapType::Quadrant => "quadrant", - SnapType::Intersection => "intersection", - SnapType::Extension => "extension", - SnapType::Insertion => "insertion", - SnapType::Perpendicular => "perpendicular", - SnapType::Tangent => "tangent", - SnapType::Nearest => "nearest", - SnapType::ApparentIntersection => "apparentintersection", - SnapType::Parallel => "parallel", - SnapType::Grid => "grid", - // Internal plugin object-pick snap — not a user-toggleable OSNAP mode, - // so it never enters SNAP_ORDER / the persisted set. - SnapType::ObjectPick => "objectpick", - } -} - -fn snap_from_id(s: &str) -> Option { - SNAP_ORDER.iter().copied().find(|t| snap_id(*t) == s) -} - /// `$OSMODE` bit for each running object-snap mode (standard AutoCAD bitmask). /// `None` for OCS-only snaps (Grid, ObjectPick) that have no standard bit. fn snap_bit(s: SnapType) -> Option { @@ -138,13 +113,12 @@ fn rgb_to_str(c: Option<[u8; 3]>) -> String { #[derive(Clone, PartialEq)] pub struct UserSettings { pub dyn_input: bool, - pub ortho: bool, pub polar: bool, pub polar_increment_deg: f32, - pub snap_enabled: bool, pub otrack: bool, - /// Active snap modes, in `SNAP_ORDER`. - pub snap_modes: Vec, + // Ortho ($ORTHOMODE) and the running OSNAP set ($OSMODE) are per-drawing — + // stored in the document header, not here (they used to be persisted app- + // globally, which duplicated the file's own state). /// Whether the one-time "make Open CAD Studio the default for .dwg/.dxf?" /// prompt has already been shown. Set once the user answers (either way), /// so we never nag again on subsequent launches. @@ -181,20 +155,9 @@ impl Default for UserSettings { fn default() -> Self { Self { dyn_input: true, - ortho: false, polar: false, polar_increment_deg: 45.0, - snap_enabled: false, otrack: false, - snap_modes: vec![ - SnapType::Endpoint, - SnapType::Midpoint, - SnapType::Center, - SnapType::Node, - SnapType::Quadrant, - SnapType::Intersection, - SnapType::Nearest, - ], default_assoc_prompted: false, disabled_plugins: Vec::new(), plugin_repos: Vec::new(), @@ -210,12 +173,6 @@ impl Default for UserSettings { } impl UserSettings { - /// Build the active-mode set in canonical order from any iterator of modes. - pub fn modes_from<'a>(modes: impl IntoIterator) -> Vec { - let set: std::collections::HashSet = modes.into_iter().copied().collect(); - SNAP_ORDER.iter().copied().filter(|t| set.contains(t)).collect() - } - /// Read the saved preferences, or `None` when no settings file exists yet. /// Unknown / missing keys fall back to [`UserSettings::default`]. pub fn load() -> Option { @@ -228,14 +185,12 @@ impl UserSettings { let (key, val) = (key.trim(), val.trim()); match key { "dyn" => s.dyn_input = val == "1", - "ortho" => s.ortho = val == "1", "polar" => s.polar = val == "1", "polar_increment_deg" => { if let Ok(v) = val.parse::() { s.polar_increment_deg = v; } } - "osnap" => s.snap_enabled = val == "1", "otrack" => s.otrack = val == "1", "bg_color" => s.bg_color = parse_rgb(val), "paper_bg_color" => s.paper_bg_color = parse_rgb(val), @@ -271,11 +226,6 @@ impl UserSettings { .map(|t| t.to_string()) .collect(); } - "snap_modes" => { - let modes: Vec = - val.split(',').filter_map(|t| snap_from_id(t.trim())).collect(); - s.snap_modes = UserSettings::modes_from(modes.iter()); - } _ => {} } } @@ -289,22 +239,13 @@ impl UserSettings { let _ = std::fs::create_dir_all(dir); } let b = |v: bool| if v { "1" } else { "0" }; - let modes = self - .snap_modes - .iter() - .map(|t| snap_id(*t)) - .collect::>() - .join(","); let body = format!( - "dyn={}\northo={}\npolar={}\npolar_increment_deg={}\nosnap={}\notrack={}\ndefault_assoc_prompted={}\nsnap_modes={}\ndisabled_plugins={}\nplugin_repos={}\ntexteditmode={}\ntextfill={}\nbackup_on_save={}\nfile_assoc_enabled={}\nsavetime_min={}\nbg_color={}\npaper_bg_color={}\n", + "dyn={}\npolar={}\npolar_increment_deg={}\notrack={}\ndefault_assoc_prompted={}\ndisabled_plugins={}\nplugin_repos={}\ntexteditmode={}\ntextfill={}\nbackup_on_save={}\nfile_assoc_enabled={}\nsavetime_min={}\nbg_color={}\npaper_bg_color={}\n", b(self.dyn_input), - b(self.ortho), b(self.polar), self.polar_increment_deg, - b(self.snap_enabled), b(self.otrack), b(self.default_assoc_prompted), - modes, self.disabled_plugins.join(","), self.plugin_repos.join(","), self.texteditmode, diff --git a/src/app/update/file.rs b/src/app/update/file.rs index a174016e..b8ea68ba 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -65,12 +65,9 @@ impl OpenCADStudio { pub(in crate::app) fn current_settings(&self) -> crate::app::settings::UserSettings { crate::app::settings::UserSettings { dyn_input: self.dyn_input, - ortho: self.ortho_mode, polar: self.polar_mode, polar_increment_deg: self.polar_increment_deg, - snap_enabled: self.snapper.snap_enabled, otrack: self.snapper.otrack_enabled, - snap_modes: crate::app::settings::UserSettings::modes_from(self.snapper.enabled.iter()), default_assoc_prompted: self.default_assoc_prompted, disabled_plugins: { let mut v: Vec = self.disabled_plugins.iter().cloned().collect(); @@ -91,12 +88,11 @@ impl OpenCADStudio { /// Apply restored preferences to live state. pub(in crate::app) fn apply_settings(&mut self, s: &crate::app::settings::UserSettings) { self.dyn_input = s.dyn_input; - self.ortho_mode = s.ortho; self.polar_mode = s.polar; self.polar_increment_deg = s.polar_increment_deg; - self.snapper.snap_enabled = s.snap_enabled; + // Ortho + running OSNAP are per-drawing (adopted from the header on + // open / tab switch), not app-global, so they are not applied here. self.snapper.otrack_enabled = s.otrack; - self.snapper.enabled = s.snap_modes.iter().copied().collect(); self.default_assoc_prompted = s.default_assoc_prompted; self.disabled_plugins = s.disabled_plugins.iter().cloned().collect(); self.plugin_repos = s.plugin_repos.clone();