From 7c3ec2666c61cd6daf4d10b1e25418243485f9f3 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Thu, 15 Oct 2026 00:46:39 +0300 Subject: [PATCH] layer states: restore into the active layout viewport (#1796) Restoring a layer state while a layout viewport is active now applies it to that viewport alone, as viewport freezes and viewport overrides for colour, linetype, lineweight and transparency (where they differ from the layer, or an override is already there). On/off, lock and plot have no per-viewport form and still apply to the drawing. Viewport colour overrides are packed as the format does (0xC3 index, 0xC2 true colour); reading them now honours that type byte, so an index override from a file no longer shows as a near-black true colour. Co-Authored-By: Claude Opus 5.5 --- src/app/commands/layers.rs | 134 +++++++++++++++++++++++++++++++++++-- src/app/update/mod.rs | 6 +- src/scene/view/render.rs | 30 ++++++++- 3 files changed, 158 insertions(+), 12 deletions(-) diff --git a/src/app/commands/layers.rs b/src/app/commands/layers.rs index d444b05c2..01d9d14a5 100644 --- a/src/app/commands/layers.rs +++ b/src/app/commands/layers.rs @@ -335,11 +335,7 @@ impl OpenCADStudio { .map(|layer| layer.name.clone()) .collect(); self.push_undo_snapshot(i, "LAYERSTATE RESTORE"); - let n = self.tabs[i] - .scene - .document - .restore_layer_state(arg) - .unwrap_or(0); + let n = self.restore_layer_state_here(i, arg); self.tabs[i].active_layer = self.tabs[i] .scene .document @@ -1145,3 +1141,131 @@ impl OpenCADStudio { Task::none() } } + +impl OpenCADStudio { + /// Restore the saved layer state `name`, returning how many layers it + /// touched. Inside a layout viewport the state applies to that viewport + /// alone: its frozen layers become the viewport's freezes, and colour, + /// linetype, lineweight and transparency become viewport overrides where + /// they differ from the layer. On / off, lock and plot have no + /// per-viewport form and apply to the drawing. + pub(in crate::app) fn restore_layer_state_here(&mut self, i: usize, name: &str) -> usize { + use codec::layer_state::LayerStateMask as Mask; + use codec::objects::{KnownXRecordKind as Kind, XRecordValue as Value}; + use codec::EntityType; + let scene = &mut self.tabs[i].scene; + let viewport = scene.active_viewport.filter(|handle| { + scene.current_layout != "Model" + && matches!(scene.document.get_entity(*handle), Some(EntityType::Viewport(_))) + }); + let document = &mut scene.document; + let Some(viewport) = viewport else { + return document.restore_layer_state(name).unwrap_or(0); + }; + let Some(state) = document.layer_state(name) else { + return 0; + }; + let mut restored = 0; + let (mut freeze, mut thaw) = (Vec::new(), Vec::new()); + for saved in &state.layers { + let found = document + .layers + .iter() + .find(|layer| !saved.layer_handle.is_null() && layer.handle == saved.layer_handle) + .or_else(|| document.layers.get(&saved.layer_name)) + .map(|layer| layer.name.clone()); + let Some(layer_name) = found else { + continue; + }; + let line_type_handle = document + .line_types + .iter() + .find(|line_type| line_type.name.eq_ignore_ascii_case(&saved.line_type)) + .map(|line_type| line_type.handle); + let Some(layer) = document.layers.get_mut(&layer_name) else { + continue; + }; + restored += 1; + if state.mask.contains(Mask::ON) { + layer.flags.off = saved.off; + } + if state.mask.contains(Mask::LOCKED) { + layer.flags.locked = saved.locked; + } + if state.mask.contains(Mask::PLOT) { + layer.is_plottable = saved.plottable; + } + if state.mask.contains(Mask::NEW_VIEWPORT) { + layer.flags.frozen_in_new_viewport = saved.new_viewport_frozen; + } + let layer = layer.clone(); + if state.mask.contains(Mask::FROZEN) { + if saved.frozen { &mut freeze } else { &mut thaw }.push(layer.handle); + } + // (kind, saved value, whether it differs from the layer's own) + let mut overrides: Vec<(Kind, Option, bool)> = Vec::new(); + if state.mask.contains(Mask::COLOR) { + let packed = crate::scene::view::render::pack_layer_override_color(&saved.color); + overrides.push(( + Kind::LayerViewportColorOverride, + packed.map(Value::Int32), + saved.color != layer.color, + )); + } + if state.mask.contains(Mask::LINE_TYPE) { + overrides.push(( + Kind::LayerViewportLinetypeOverride, + line_type_handle.map(Value::Handle), + !saved.line_type.eq_ignore_ascii_case(&layer.line_type), + )); + } + if state.mask.contains(Mask::LINE_WEIGHT) { + overrides.push(( + Kind::LayerViewportLineweightOverride, + Some(Value::Int32(saved.line_weight.value() as i32)), + saved.line_weight != layer.line_weight, + )); + } + if let (true, Some(transparency)) = + (state.mask.contains(Mask::TRANSPARENCY), saved.transparency) + { + // The opacity byte under the "by alpha" method, as the format packs it. + let packed = 0x0200_0000u32 | (255 - transparency.alpha()) as u32; + overrides.push(( + Kind::LayerViewportAlphaOverride, + Some(Value::Int32(packed as i32)), + transparency != layer.transparency, + )); + } + // An override is written where the state differs from the layer, + // and where one already exists for this viewport, so it cannot + // keep an older value. + let overrides: Vec<(Kind, Value)> = overrides + .into_iter() + .filter_map(|(kind, value, differs)| { + let existing = document + .layer_viewport_overrides(layer.handle, kind) + .iter() + .any(|(handle, _)| *handle == viewport); + value.filter(|_| differs || existing).map(|value| (kind, value)) + }) + .collect(); + for (kind, value) in overrides { + document.set_layer_viewport_override(layer.handle, kind, viewport, value); + } + } + if let Some(EntityType::Viewport(vp)) = document.get_entity_mut(viewport) { + vp.frozen_layers.retain(|handle| !thaw.contains(handle)); + for handle in freeze { + if !vp.frozen_layers.contains(&handle) { + vp.frozen_layers.push(handle); + } + } + } + scene.bump_geometry(); + let doc_layers = self.tabs[i].scene.document.layers.clone(); + let vp_info = self.tabs[i].scene.viewport_list(); + self.tabs[i].layers.sync_with_viewports(&doc_layers, vp_info); + restored + } +} diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index ca6c167ec..f625b1095 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -3799,11 +3799,7 @@ impl OpenCADStudio { .map(|layer| layer.name.clone()) .collect(); self.push_undo_snapshot(i, "LAYERSTATE RESTORE"); - let restored = self.tabs[i] - .scene - .document - .restore_layer_state(&name) - .unwrap_or(0); + let restored = self.restore_layer_state_here(i, &name); let active = self.tabs[i] .scene .document diff --git a/src/scene/view/render.rs b/src/scene/view/render.rs index 81a559e9f..7808e21e2 100644 --- a/src/scene/view/render.rs +++ b/src/scene/view/render.rs @@ -3433,6 +3433,32 @@ pub(in crate::scene) fn render_style_for( render_style_for_viewport(document, e, None) } +/// A viewport layer colour override as the format packs it (code 420): the +/// top byte says what follows — 0xC3 an index colour in the low byte, 0xC2 a +/// 24-bit true colour, 0xC0 / 0xC1 ByLayer / ByBlock. A bare 24-bit value is +/// read as true colour. +pub(crate) fn layer_override_color(value: i32) -> AcadColor { + match (value as u32) >> 24 { + 0xC0 => AcadColor::ByLayer, + 0xC1 => AcadColor::ByBlock, + 0xC3 => AcadColor::Index((value & 0xFF) as u8), + _ => AcadColor::from_true_color_value(value), + } +} + +/// [`layer_override_color`] the other way round; `None` for a colour an +/// override cannot hold. +pub(crate) fn pack_layer_override_color(color: &AcadColor) -> Option { + let packed = match color { + AcadColor::Index(index) => 0xC300_0000u32 | *index as u32, + AcadColor::Rgb { r, g, b } => { + 0xC200_0000u32 | (*r as u32) << 16 | (*g as u32) << 8 | *b as u32 + } + _ => return None, + }; + Some(packed as i32) +} + fn viewport_override( document: &CadDocument, layer_name: &str, @@ -3473,7 +3499,7 @@ pub(crate) fn render_style_for_common_viewport( codec::objects::KnownXRecordKind::LayerViewportColorOverride, ) .and_then(|value| value.as_i32()) - .map(AcadColor::from_true_color_value) + .map(layer_override_color) }) .flatten(); let resolved = if *ec == AcadColor::ByLayer { @@ -3622,7 +3648,7 @@ pub(crate) fn layer_render_style_viewport( codec::objects::KnownXRecordKind::LayerViewportColorOverride, ) .and_then(|value| value.as_i32()) - .map(AcadColor::from_true_color_value) + .map(layer_override_color) } else { None };