feat(sysvars): persist Ortho, running OSNAP and Isolate in the drawing
Ortho and the running object-snap set were app-global (settings.txt only) and Isolate/Hide was a session-only view state, so none survived save/reopen and none were per-drawing. Bind all three to the file like AutoCAD: - Ortho -> header.ortho_mode ($ORTHOMODE); running OSNAP <-> header .object_snap_mode ($OSMODE bitmask) via a new SnapType<->bit map in settings (osmode_from_snaps / snaps_from_osmode, round-trip tested). The header is the per-drawing store, live state mirrors the active drawing: adopt header->live on open and tab-switch, stamp live->header on save and when leaving a tab, a fresh drawing inherits the current values, and SETVAR ORTHOMODE/OSMODE now also update the live state. settings.txt stays as the boot/new-drawing default; the per-file header overrides it on open. - Isolate/Hide now set each entity's persisted invisible flag (DXF code 60, which acadrust round-trips in DXF and DWG) in addition to the session set; End Isolation clears it. The set is reseeded from invisible entities on load and after undo/redo, the commands are undoable and mark the tab dirty. The renderer already skipped invisible entities, so no render change was needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7cba5c8679
commit
da2c600953
7 changed files with 220 additions and 3 deletions
|
|
@ -58,6 +58,61 @@ fn snap_from_id(s: &str) -> Option<SnapType> {
|
|||
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<i32> {
|
||||
Some(match s {
|
||||
SnapType::Endpoint => 1,
|
||||
SnapType::Midpoint => 2,
|
||||
SnapType::Center => 4,
|
||||
SnapType::Node => 8,
|
||||
SnapType::Quadrant => 16,
|
||||
SnapType::Intersection => 32,
|
||||
SnapType::Insertion => 64,
|
||||
SnapType::Perpendicular => 128,
|
||||
SnapType::Tangent => 256,
|
||||
SnapType::Nearest => 512,
|
||||
SnapType::ApparentIntersection => 2048,
|
||||
SnapType::Extension => 4096,
|
||||
SnapType::Parallel => 8192,
|
||||
SnapType::Grid | SnapType::ObjectPick => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Bit 14 of `$OSMODE` — object snap turned off (master suppress).
|
||||
const OSMODE_SUPPRESS: i32 = 16384;
|
||||
|
||||
/// Encode the running-snap set + master toggle into an `$OSMODE` bitmask for the
|
||||
/// drawing header. OCS-only snaps (Grid, ObjectPick) have no bit and are
|
||||
/// dropped. A cleared master toggle sets the suppress bit.
|
||||
pub(crate) fn osmode_from_snaps<'a>(
|
||||
enabled: impl IntoIterator<Item = &'a SnapType>,
|
||||
snap_enabled: bool,
|
||||
) -> i32 {
|
||||
let mut bits = 0;
|
||||
for t in enabled {
|
||||
if let Some(b) = snap_bit(*t) {
|
||||
bits |= b;
|
||||
}
|
||||
}
|
||||
if !snap_enabled {
|
||||
bits |= OSMODE_SUPPRESS;
|
||||
}
|
||||
bits
|
||||
}
|
||||
|
||||
/// Decode an `$OSMODE` bitmask into `(running-snap set, master enabled)`. Only
|
||||
/// the standard mappable modes are produced; the suppress bit maps (inverted) to
|
||||
/// the master toggle.
|
||||
pub(crate) fn snaps_from_osmode(osmode: i32) -> (Vec<SnapType>, bool) {
|
||||
let modes = SNAP_ORDER
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|t| snap_bit(*t).is_some_and(|b| osmode & b != 0))
|
||||
.collect();
|
||||
(modes, osmode & OSMODE_SUPPRESS == 0)
|
||||
}
|
||||
|
||||
/// Parse a persisted `r,g,b` background triplet (each 0–255). Returns `None`
|
||||
/// for an empty or malformed value so a missing/garbage key falls back to the
|
||||
/// app default rather than a wrong colour.
|
||||
|
|
@ -286,3 +341,45 @@ fn config_path() -> Option<PathBuf> {
|
|||
p.push("OpenCADStudio");
|
||||
Some(p.join("settings.txt"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn osmode_encodes_bits_and_suppress() {
|
||||
// Endpoint(1) + Midpoint(2) + Intersection(32) = 35, master on.
|
||||
let on = [SnapType::Endpoint, SnapType::Midpoint, SnapType::Intersection];
|
||||
assert_eq!(osmode_from_snaps(on.iter(), true), 35);
|
||||
// Master off sets the suppress bit (16384).
|
||||
assert_eq!(osmode_from_snaps(on.iter(), false), 35 | 16384);
|
||||
// OCS-only snaps carry no bit and are dropped.
|
||||
assert_eq!(osmode_from_snaps([SnapType::Grid, SnapType::ObjectPick].iter(), true), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osmode_decodes_bits_and_suppress() {
|
||||
let (modes, enabled) = snaps_from_osmode(35);
|
||||
let set: std::collections::HashSet<_> = modes.into_iter().collect();
|
||||
assert!(enabled);
|
||||
assert_eq!(set.len(), 3);
|
||||
assert!(set.contains(&SnapType::Endpoint));
|
||||
assert!(set.contains(&SnapType::Midpoint));
|
||||
assert!(set.contains(&SnapType::Intersection));
|
||||
// Suppress bit → master off; the mode bits still decode.
|
||||
let (_m, en) = snaps_from_osmode(35 | 16384);
|
||||
assert!(!en);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osmode_round_trips_every_mappable_mode() {
|
||||
// All 13 running snaps + master on encode and decode back to the same set.
|
||||
let all: Vec<SnapType> = SNAP_ORDER.to_vec();
|
||||
let bits = osmode_from_snaps(all.iter(), true);
|
||||
let (back, enabled) = snaps_from_osmode(bits);
|
||||
assert!(enabled);
|
||||
let a: std::collections::HashSet<_> = all.into_iter().collect();
|
||||
let b: std::collections::HashSet<_> = back.into_iter().collect();
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue