diff --git a/src/app/mod.rs b/src/app/mod.rs index ba3e8a050..71afb4649 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2013,6 +2013,7 @@ pub enum Message { LayerToggleVisible(usize), LayerToggleLock(usize), LayerToggleFreeze(usize), + LayerTogglePlot(usize), /// Sort the Layer Manager table by a clicked column header. LayerSort(crate::ui::window::layers::LayerSortCol), /// Toggle per-viewport freeze: (layer_index, vp_col_index) diff --git a/src/app/update/file.rs b/src/app/update/file.rs index a3a3f4bb2..4a87a47db 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -171,7 +171,7 @@ fn plot_scene_content( let (mut paper_wires, mut model_wires) = scene.plot_wire_groups(render_mode_override); paper_wires.retain(|wire| wire.plot_visible); model_wires.retain(|wire| wire.plot_visible); - let paper_hatches = scene.paper_canvas_hatches().as_ref().clone(); + let paper_hatches = scene.paper_plot_hatches().as_ref().clone(); let paper_wipeouts = scene.paper_canvas_wipeouts().as_ref().clone(); if scene.current_layout == "Model" { let splits = crate::io::pdf_export::PlotGroupSplits { diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 45354aa30..9e1cce871 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -2427,6 +2427,56 @@ impl OpenCADStudio { } Task::none() } + Message::LayerTogglePlot(idx) => { + let i = self.active_tab; + + let plottable = self.tabs[i] + .layers + .layers + .get(idx) + .map(|layer| !layer.plottable); + + let targets = self.layer_row_action_targets(i, idx); + + if let Some(plottable) = plottable { + if !targets.is_empty() { + let undo = self.begin_layer_undo(i, "LAYER PLOT/NOPLOT", &targets); + + for name in &targets { + if let Some(layer) = self.tabs[i].scene.document.layers.get_mut(name) { + layer.is_plottable = plottable; + } + + if let Some(layer) = self.tabs[i] + .layers + .layers + .iter_mut() + .find(|layer| &layer.name == name) + { + layer.plottable = plottable; + } + } + + self.tabs[i] + .scene + .invalidate_layer_dependencies(&targets); + + self.tabs[i].dirty = true; + self.commit_layer_undo(i, undo); + + self.command_line.push_output( + crate::tf!( + "{} layer(s) set to {}", + targets.len(), + if plottable { "Plot" } else { "No Plot" } + ) + .as_ref(), + ); + } + } + + Task::none() + }, Message::LayerToggleVpFreeze(layer_idx, vp_col_idx) => { self.on_layer_toggle_vp_freeze(layer_idx, vp_col_idx) diff --git a/src/scene/convert/tess.rs b/src/scene/convert/tess.rs index 71a60a6ac..0d4a02ac7 100644 --- a/src/scene/convert/tess.rs +++ b/src/scene/convert/tess.rs @@ -339,8 +339,48 @@ pub(crate) fn tessellate_entity_dim_text( } wires } - pub(crate) fn tessellate_entity( + document: &acadrust::CadDocument, + selected: &HashSet, + active_viewport: Option, + bg_color: [f32; 4], + anno_scale: f32, + annotation_scale_handle: Option, + e: &EntityType, + block_cache: Option<&cache::block_cache::BlockCache>, + view_aabb: Option<[f32; 4]>, + world_per_pixel: Option, + paper_space: bool, +) -> Vec { + let mut wires = tessellate_entity_inner( + document, + selected, + active_viewport, + bg_color, + anno_scale, + annotation_scale_handle, + e, + block_cache, + view_aabb, + world_per_pixel, + paper_space, + ); + + let layer_plottable = document + .layers + .get(&e.common().layer) + .map(|layer| layer.is_plottable) + .unwrap_or(true); + + if !layer_plottable { + for wire in &mut wires { + wire.plot_visible = false; + } + } + + wires +} +fn tessellate_entity_inner( document: &acadrust::CadDocument, selected: &HashSet, active_viewport: Option, diff --git a/src/scene/entity.rs b/src/scene/entity.rs index fd82c8cc9..87c0ebc04 100644 --- a/src/scene/entity.rs +++ b/src/scene/entity.rs @@ -1144,9 +1144,31 @@ impl Scene { viewport, None, false, + false, + ) + } + pub(super) fn instanced_plot_hatch_models( + &self, + layout_block: Handle, + hatch_bg: [f32; 4], + frozen: Option<&rustc_hash::FxHashSet>, + annotation_scale_handle: Option, + all_visible: bool, + viewport: Option, + ) -> Vec { + self.instanced_hatch_models_filtered( + layout_block, + hatch_bg, + false, + frozen, + annotation_scale_handle, + all_visible, + viewport, + None, + false, + true, ) } - /// Build live hatch overlays for INSERT grip previews. The edited INSERT /// is intentionally hidden from the resident scene while its current /// document entity moves, so include that hidden target and reuse the full @@ -1183,6 +1205,7 @@ impl Scene { self.active_viewport, Some(&targets), true, + false, ) } @@ -1197,6 +1220,7 @@ impl Scene { viewport: Option, targets: Option<&rustc_hash::FxHashSet>, include_preview_hidden: bool, + plot_only: bool, ) -> Vec { let depth_map = self.draw_depth_map(); let graph = crate::scene::render_graph::RenderSceneGraph::new( @@ -1213,10 +1237,21 @@ impl Scene { graph.walk_root( self.render_scene_root(layout_block), |entity, context| { + let common = entity.common(); + + if plot_only + && self + .document + .layers + .get(&common.layer) + .is_some_and(|layer| !layer.is_plottable) + { + return false; + } + if context.is_instanced() { return true; } - let common = entity.common(); if self.object_isolation.hides(common.handle) || (!include_preview_hidden && self.preview_hidden.contains(&common.handle)) @@ -1254,6 +1289,15 @@ impl Scene { let EntityType::Hatch(source_hatch) = entity else { return; }; + if plot_only + && self + .document + .layers + .get(&source_hatch.common.layer) + .is_some_and(|layer| !layer.is_plottable) + { + return; + } let style = context.style_for(&self.document, entity); let preserve_white_mask = source_hatch.is_solid && matches!( diff --git a/src/scene/paper.rs b/src/scene/paper.rs index 4f0578f31..46285baf6 100644 --- a/src/scene/paper.rs +++ b/src/scene/paper.rs @@ -482,6 +482,16 @@ impl Scene { models.extend(instanced); Arc::new(models) } + pub fn paper_plot_hatches(&self) -> Arc> { + let layout_block = self.current_layout_block_handle(); + + Arc::new(self.plot_hatches_for_block( + layout_block, + None, + self.paper_annotation_scale_handle(), + self.annotation_all_visible(), + )) + } /// Plot-only hatch set for a specific block. Paper PDF generation uses this /// for the model block behind each floating viewport; unlike @@ -500,6 +510,13 @@ impl Scene { .map(|l| l.flags.off || l.flags.frozen) .unwrap_or(false) }; + let layer_plottable = |layer: &str| { + self.document + .layers + .get(layer) + .map(|l| l.is_plottable) + .unwrap_or(true) + }; let mut models = Vec::new(); for (&handle, model) in self.hatches.iter() { let Some(source) = self.document.get_entity(handle) else { @@ -515,6 +532,7 @@ impl Scene { if common.invisible || self.entity_temporarily_hidden(handle) || layer_hidden(&common.layer) + || !layer_plottable(&common.layer) || self.layer_frozen_in(&common.layer, frozen) || crate::scene::annotative::annotative_offscale_for( &self.document, @@ -554,10 +572,9 @@ impl Scene { } models.push(hatch); } - models.extend(self.instanced_hatch_models( + models.extend(self.instanced_plot_hatch_models( block, self.paper_bg_color, - false, frozen, annotation_scale_handle, all_visible, diff --git a/src/ui/window/layers.rs b/src/ui/window/layers.rs index 5499e75ad..6798b73ea 100644 --- a/src/ui/window/layers.rs +++ b/src/ui/window/layers.rs @@ -32,6 +32,7 @@ pub enum LayerSortCol { On, Freeze, Lock, + Plot, Color, Linetype, Lineweight, @@ -86,6 +87,7 @@ pub struct Layer { pub visible: bool, pub frozen: bool, pub locked: bool, + pub plottable: bool, pub color: AcadColor, pub linetype: String, pub lineweight: LineWeight, @@ -101,6 +103,7 @@ impl Layer { visible: true, frozen: false, locked: false, + plottable: true, color, linetype: "Continuous".to_string(), lineweight: LineWeight::Default, @@ -214,6 +217,7 @@ impl LayerPanel { visible: !l.flags.off, frozen: l.flags.frozen, locked: l.flags.locked, + plottable: l.is_plottable, color: l.color, linetype: if l.line_type.is_empty() { "Continuous".to_string() @@ -275,6 +279,7 @@ impl LayerPanel { LayerSortCol::On => a.visible.cmp(&b.visible), LayerSortCol::Freeze => a.frozen.cmp(&b.frozen), LayerSortCol::Lock => a.locked.cmp(&b.locked), + LayerSortCol::Plot => a.plottable.cmp(&b.plottable), LayerSortCol::Color => color_sort_key(a.color).cmp(&color_sort_key(b.color)), LayerSortCol::Linetype => { a.linetype.to_lowercase().cmp(&b.linetype.to_lowercase()) @@ -392,6 +397,7 @@ impl LayerPanel { sortable_header(t!("On"), LayerSortCol::On, Length::Fixed(COL_ICON), sc, sa), sortable_header(t!("Freeze"), LayerSortCol::Freeze, Length::Fixed(COL_ICON), sc, sa), sortable_header(t!("Lock"), LayerSortCol::Lock, Length::Fixed(COL_ICON), sc, sa), + sortable_header(t!("Plot"), LayerSortCol::Plot, Length::Fixed(COL_ICON), sc, sa), sortable_header(t!("Color"), LayerSortCol::Color, Length::Fixed(COL_COLOR), sc, sa), sortable_header(t!("Linetype"), LayerSortCol::Linetype, Length::Fixed(COL_LT), sc, sa), sortable_header(t!("Lineweight"), LayerSortCol::Lineweight, Length::Fixed(COL_LW), sc, sa), @@ -719,6 +725,31 @@ fn layer_row<'a>( let vis_svg = crate::ui::icons::layer_visible(layer.visible); let frz_svg = crate::ui::icons::layer_freeze(layer.frozen); let lck_svg = crate::ui::icons::layer_lock(layer.locked); + let plot_icon: Element<'_, Message> = if layer.plottable { + crate::ui::icons::themed(crate::ui::icons::PRINT, ICON_SZ) + } else { + row![ + crate::ui::icons::themed(crate::ui::icons::PRINT, ICON_SZ), + crate::ui::icons::themed_danger(crate::ui::icons::CLOSE, ICON_SZ * 0.65), + ] + .spacing(0) + .align_y(iced::Center) + .into() + }; + + let plot_btn: Element<'_, Message> = button(plot_icon) + .on_press(Message::LayerTogglePlot(index)) + .style(move |theme: &Theme, status| { + layer_cell_button_style(theme, status, is_selected, index) + }) + .padding(Padding { + top: COMBO_PAD_V, + bottom: COMBO_PAD_V, + left: 4.0, + right: 4.0, + }) + .height(Length::Fixed(ROW_H)) + .into(); let status_dot: Element<'_, Message> = if is_current { crate::ui::icons::themed_success(crate::ui::icons::CHECK, 13.0) @@ -877,6 +908,9 @@ fn layer_row<'a>( container(svg_btn(lck_svg, Message::LayerToggleLock(index))) .width(Length::Fixed(COL_ICON)) .align_x(iced::Center), + container(plot_btn) + .width(Length::Fixed(COL_ICON)) + .align_x(iced::Center), color_cell, lt_cell, lw_cell,