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 };