From eb5cbaeca068e2b05235c88b1cea41cb5525a759 Mon Sep 17 00:00:00 2001 From: gianlucafiore Date: Thu, 20 Aug 2026 20:37:27 -0300 Subject: [PATCH 1/2] feat(layers): add non-printing layer support --- src/app/mod.rs | 1 + src/app/update/file.rs | 2 +- src/app/update/mod.rs | 50 +++++++++++++++++++++++++++++++++++++++ src/scene/convert/tess.rs | 42 +++++++++++++++++++++++++++++++- src/scene/entity.rs | 48 +++++++++++++++++++++++++++++++++++-- src/scene/paper.rs | 21 ++++++++++++++-- src/ui/window/layers.rs | 34 ++++++++++++++++++++++++++ 7 files changed, 192 insertions(+), 6 deletions(-) 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, From 77e1f1b876f62e7ec5621f7c129baf7e29f1d29d Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 21 Aug 2026 18:57:23 +0300 Subject: [PATCH 2/2] fix(plot): respect non-printing layers Filter nested block, attribute, table, hatch, and wipeout geometry during export while preserving canvas visibility. --- src/app/update/file.rs | 2 +- src/app/update/mod.rs | 2 ++ src/entities/insert.rs | 19 ++++++++--- src/scene/cache/block_cache.rs | 58 +++++++++++++++++++++++++++++++--- src/scene/convert/tess.rs | 7 ++++ src/scene/entity.rs | 52 ++++++++++++++++++++---------- src/scene/mod.rs | 1 + src/scene/paper.rs | 21 +++++++++++- src/scene/project.rs | 1 + src/ui/window/layers.rs | 4 +++ 10 files changed, 140 insertions(+), 27 deletions(-) diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 4a87a47db..720f86cc2 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -172,7 +172,7 @@ fn plot_scene_content( paper_wires.retain(|wire| wire.plot_visible); model_wires.retain(|wire| wire.plot_visible); let paper_hatches = scene.paper_plot_hatches().as_ref().clone(); - let paper_wipeouts = scene.paper_canvas_wipeouts().as_ref().clone(); + let paper_wipeouts = scene.paper_plot_wipeouts().as_ref().clone(); if scene.current_layout == "Model" { let splits = crate::io::pdf_export::PlotGroupSplits { wires: paper_wires.len(), diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 9e1cce871..e5fb0be1c 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -2457,6 +2457,8 @@ impl OpenCADStudio { } } + self.tabs[i].layers.refresh_sort(); + self.tabs[i] .scene .invalidate_layer_dependencies(&targets); diff --git a/src/entities/insert.rs b/src/entities/insert.rs index f16888a44..aa2c2339d 100644 --- a/src/entities/insert.rs +++ b/src/entities/insert.rs @@ -196,6 +196,7 @@ pub(crate) fn append_insert_attribute_wires( ins_pat: [f32; 8], ins_lw_px: f32, ins_layer: render::InheritStyle, + ins_layer_plottable: bool, bg_color: [f32; 4], is_xref: bool, pslt_factor: f32, @@ -212,11 +213,8 @@ pub(crate) fn append_insert_attribute_wires( if attmode == 0 { return; } - // An annotative block scales as ONE uniform unit about its insertion point; - // its attributes are carried by that scale (AutoCAD even forbids annotative - // attributes inside annotative blocks, to avoid double-scaling). So pre-scale - // the annotative attribute about the insertion point and tessellate it at a - // neutral 1.0 — the text path must never scale it a second time. + // Attributes inherit the annotative block scale. Pre-scale them here so + // text tessellation stays at a neutral scale. let annotative = ins .common .extended_data @@ -260,6 +258,16 @@ pub(crate) fn append_insert_attribute_wires( } else { sub_color }; + let attr_plottable = if render::is_effective_layer_zero(&attr.common.layer) { + ins_layer_plottable + } else { + document + .layers + .get(&attr.common.layer) + .map(|layer| layer.is_plottable) + .unwrap_or(true) + }; + let attr_plottable = ins_layer_plottable && attr_plottable; let sub_aabb = crate::scene::entity_aabb(&attr_entity); let mut attr_wires = tessellate::tessellate( document, @@ -284,6 +292,7 @@ pub(crate) fn append_insert_attribute_wires( w.name = insert_handle.value().to_string(); w.aci = sub_aci; w.aabb = sub_aabb; + w.plot_visible &= attr_plottable; } wires.extend(attr_wires); } diff --git a/src/scene/cache/block_cache.rs b/src/scene/cache/block_cache.rs index d8f79c6cd..b3d5cab7c 100644 --- a/src/scene/cache/block_cache.rs +++ b/src/scene/cache/block_cache.rs @@ -74,6 +74,7 @@ pub struct LocalWire { pub world_width: f32, pub plinegen: bool, pub plot_visible: bool, + pub plot_l0: bool, pub hide_unselected: bool, /// Set at construction; used to discriminate fill-only GPU batches from /// stroke batches in [`StyleKey`]. Derived from @@ -108,6 +109,8 @@ pub struct NestedRef { pub xform: Transform, pub style: crate::scene::render_graph::InsertStyleSpec, pub instance_offsets: Vec<[f64; 3]>, + pub plot_visible: bool, + pub plot_l0: bool, /// XCLIP boundary for this nested insert, in the parent defn's local frame /// (`None` = unclipped). Baked at build time because the clip's spatial /// filter lives in `doc.objects`, which isn't reachable at expand time; on @@ -520,6 +523,14 @@ fn build_defn( if let Some(record) = baked.filter(|record| { !record.entity_handles.is_empty() }) { + let table_plot_l0 = crate::scene::view::render::is_effective_layer_zero( + &table.common.layer, + ); + let table_plot_visible = doc + .layers + .get(&table.common.layer) + .map(|layer| layer.is_plottable) + .unwrap_or(true); let mut insert = acadrust::entities::Insert::new( record.name.clone(), table.insertion_point, @@ -544,7 +555,7 @@ fn build_defn( |leaf, context| { let mut placed = leaf.clone(); placed.apply_transform(&context.transform); - for wire in tessellate_sub_local( + for mut wire in tessellate_sub_local( doc, &placed, anno_scale, @@ -553,6 +564,8 @@ fn build_defn( viewport, depth_map, ) { + wire.plot_visible &= table_plot_l0 || table_plot_visible; + wire.plot_l0 |= table_plot_l0; subs.push(LocalSub::Wire(wire)); } }, @@ -628,12 +641,22 @@ fn build_nested_ref( .map(|filter| { crate::scene::pick::xclip::world_clip_polygon_for_transform(filter, &xform) }); + let plot_l0 = crate::scene::view::render::is_effective_layer_zero( + &nested_ins.common.layer, + ); + let plot_visible = doc + .layers + .get(&nested_ins.common.layer) + .map(|layer| layer.is_plottable) + .unwrap_or(true); NestedRef { block_name: nested_ins.block_name.clone(), xform, style: crate::scene::render_graph::InsertStyleSpec::new(doc, nested_ins, viewport), instance_offsets: crate::scene::render_graph::array_offsets(nested_ins), + plot_visible, + plot_l0, clip_poly, local_rank: depth_map .get(&nested_ins.common.handle.value()) @@ -679,6 +702,11 @@ fn tessellate_sub_local( // INSERT's layer at expand time. Flag each ByLayer property so emit_wire // can override the cached (layer-0-resolved) value with the insert layer's. let on_l0 = crate::scene::view::render::is_effective_layer_zero(&sub.common().layer); + let layer_plottable = doc + .layers + .get(&sub.common().layer) + .map(|layer| layer.is_plottable) + .unwrap_or(true); let color_l0 = !has_book_color && on_l0 && sub.common().color == AcadColor::ByLayer; let transparency_l0 = on_l0 && sub.common().transparency.alpha() == 0; @@ -808,7 +836,9 @@ fn tessellate_sub_local( line_weight_px: lw_px, world_width: wire.world_width, plinegen: wire.plinegen, - plot_visible: frame_mode.is_none_or(|mode| mode == 1), + plot_visible: frame_mode.is_none_or(|mode| mode == 1) + && (on_l0 || layer_plottable), + plot_l0: on_l0, hide_unselected: frame_mode == Some(0), is_fill_only, color_is_byblock: color_is_byblock && wire_on_base_color, @@ -921,6 +951,7 @@ pub fn expand_insert( // The INSERT's own layer style — layer-0 inheritance target for children. ins_layer: crate::scene::view::render::InheritStyle, ins_layer_aci: u8, + ins_layer_plottable: bool, selected: bool, pslt_factor: f32, // World-space XY view AABB (with world_offset already subtracted, so the @@ -972,6 +1003,7 @@ pub fn expand_insert( ins_pat, ins_lw_px, ins_layer, + ins_layer_plottable, selected, pslt_factor, is_xref, @@ -1051,6 +1083,8 @@ pub fn expand_insert( ins_lw_px, l0: ins_layer, l0_aci: ins_layer_aci, + l0_plottable: ins_layer_plottable, + plot_visible: ins_layer_plottable, selected, pslt_factor, view_aabb: None, @@ -1154,6 +1188,7 @@ fn expansion_prototype_key( ins_pat: [f32; 8], ins_lw_px: f32, ins_layer: crate::scene::view::render::InheritStyle, + ins_layer_plottable: bool, selected: bool, pslt_factor: f32, is_xref: bool, @@ -1181,6 +1216,7 @@ fn expansion_prototype_key( insert_style.push(ins_layer.pat_len.to_bits()); insert_style.extend(ins_layer.pat.map(f32::to_bits)); insert_style.push(ins_layer.lw_px.to_bits()); + insert_style.push(ins_layer_plottable as u32); insert_style.push(pslt_factor.to_bits()); insert_style.extend(bg_color.map(f32::to_bits)); insert_style.push(anno_scale.to_bits()); @@ -1283,6 +1319,8 @@ struct ExpandCtx<'a> { /// for child wires on layer "0" whose properties are ByLayer. l0: crate::scene::view::render::InheritStyle, l0_aci: u8, + l0_plottable: bool, + plot_visible: bool, selected: bool, pslt_factor: f32, // World-space XY view AABB (post world_offset). `None` = no culling. @@ -1317,6 +1355,8 @@ fn nested_prototype_key( style.push(ctx.l0.pat_len.to_bits()); style.extend(ctx.l0.pat.map(f32::to_bits)); style.push(ctx.l0.lw_px.to_bits()); + style.push(ctx.l0_plottable as u32); + style.push(ctx.plot_visible as u32); style.push(ctx.pslt_factor.to_bits()); style.extend(ctx.bg_color.map(f32::to_bits)); NestedPrototypeKey { @@ -1724,6 +1764,11 @@ fn expand_defn( layer0_aci: ctx.l0_aci, }; let nested_style = nref.style.resolve(parent_style); + let nested_layer_plottable = if nref.plot_l0 { + ctx.l0_plottable + } else { + nref.plot_visible + }; let inner_ctx = ExpandCtx { cache: ctx.cache, ins_color: nested_style.insert.0, @@ -1733,6 +1778,8 @@ fn expand_defn( ins_lw_px: nested_style.insert.3, l0: nested_style.layer0, l0_aci: nested_style.layer0_aci, + l0_plottable: nested_layer_plottable, + plot_visible: ctx.plot_visible && nested_layer_plottable, selected: ctx.selected, pslt_factor: ctx.pslt_factor, view_aabb: ctx.view_aabb, @@ -2023,6 +2070,9 @@ fn emit_wire( // batches stay merged. let local_depth = (lw.world_width > 0.0) .then(|| d_range.0 + lw.local_rank * d_range.1); + let plot_visible = ctx.plot_visible + && lw.plot_visible + && (!lw.plot_l0 || ctx.l0_plottable); let key = style_key( final_color, @@ -2038,7 +2088,7 @@ fn emit_wire( lw.is_fill_only, lw.fill_is_2d_solid, lw.fill_is_3d, - lw.plot_visible, + plot_visible, lw.hide_unselected, local_depth, ); @@ -2067,7 +2117,7 @@ fn emit_wire( lw.is_fill_only, lw.fill_is_2d_solid, lw.fill_is_3d, - lw.plot_visible, + plot_visible, lw.hide_unselected, ) }); diff --git a/src/scene/convert/tess.rs b/src/scene/convert/tess.rs index 0d4a02ac7..fe3d3ca94 100644 --- a/src/scene/convert/tess.rs +++ b/src/scene/convert/tess.rs @@ -1096,6 +1096,11 @@ fn tessellate_entity_inner( _ => None, }) .unwrap_or(0); + let ins_layer_plottable = document + .layers + .get(&ins.common.layer) + .map(|layer| layer.is_plottable) + .unwrap_or(true); let ip = glam::Vec3::new( (ins.insert_point.x) as f32, (ins.insert_point.y) as f32, @@ -1167,6 +1172,7 @@ fn tessellate_entity_inner( ins_lw_px, ins_layer, ins_layer_aci, + ins_layer_plottable, sel, pslt_factor, view_aabb, @@ -1221,6 +1227,7 @@ fn tessellate_entity_inner( ins_pat, ins_lw_px, ins_layer, + ins_layer_plottable, bg_color, is_xref, pslt_factor, diff --git a/src/scene/entity.rs b/src/scene/entity.rs index 87c0ebc04..160ee2345 100644 --- a/src/scene/entity.rs +++ b/src/scene/entity.rs @@ -1147,6 +1147,33 @@ impl Scene { false, ) } + + fn layer_plottable_in_context( + &self, + entity: &EntityType, + context: &crate::scene::render_graph::RenderContext, + ) -> bool { + let common = entity.common(); + let layer = if crate::scene::view::render::is_effective_layer_zero(&common.layer) { + context + .insert_path + .iter() + .rev() + .find(|insert| { + !crate::scene::view::render::is_effective_layer_zero(&insert.common.layer) + }) + .map(|insert| insert.common.layer.as_str()) + .unwrap_or(common.layer.as_str()) + } else { + common.layer.as_str() + }; + self.document + .layers + .get(layer) + .map(|layer| layer.is_plottable) + .unwrap_or(true) + } + pub(super) fn instanced_plot_hatch_models( &self, layout_block: Handle, @@ -1239,13 +1266,7 @@ impl Scene { |entity, context| { let common = entity.common(); - if plot_only - && self - .document - .layers - .get(&common.layer) - .is_some_and(|layer| !layer.is_plottable) - { + if plot_only && !self.layer_plottable_in_context(entity, context) { return false; } @@ -1289,13 +1310,7 @@ 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) - { + if plot_only && !self.layer_plottable_in_context(entity, context) { return; } let style = context.style_for(&self.document, entity); @@ -1396,6 +1411,7 @@ impl Scene { all_visible, bg_color, false, + false, ) } @@ -1407,6 +1423,7 @@ impl Scene { all_visible: bool, bg_color: [f32; 4], tint_insert_selection: bool, + plot_only: bool, ) -> Vec { let depth_map = self.draw_depth_map(); let graph = crate::scene::render_graph::RenderSceneGraph::new( @@ -1421,8 +1438,11 @@ impl Scene { graph.walk_root( self.render_scene_root(target_block), |entity, context| { - context.is_instanced() - || !self.entity_temporarily_hidden(entity.common().handle) + let common = entity.common(); + if plot_only && !self.layer_plottable_in_context(entity, context) { + return false; + } + context.is_instanced() || !self.entity_temporarily_hidden(common.handle) }, |entity, context| { let EntityType::Wipeout(source) = entity else { diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 045480035..feb0bec3f 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -8676,6 +8676,7 @@ impl Scene { } EntityType::Insert(insert) => { for attribute in &insert.attributes { + add(&mut index.layers, &attribute.common.layer); add(&mut index.text_styles, &attribute.text_style); } } diff --git a/src/scene/paper.rs b/src/scene/paper.rs index 46285baf6..a3c11612f 100644 --- a/src/scene/paper.rs +++ b/src/scene/paper.rs @@ -490,6 +490,7 @@ impl Scene { None, self.paper_annotation_scale_handle(), self.annotation_all_visible(), + false, )) } @@ -502,6 +503,7 @@ impl Scene { frozen: Option<&rustc_hash::FxHashSet>, annotation_scale_handle: Option, all_visible: bool, + include_solids: bool, ) -> Vec { let layer_hidden = |layer: &str| { self.document @@ -528,6 +530,9 @@ impl Scene { annotation_scale_handle, ); let entity = contextual.as_ref(); + if !include_solids && matches!(entity, EntityType::Solid(_)) { + continue; + } let common = entity.common(); if common.invisible || self.entity_temporarily_hidden(handle) @@ -600,6 +605,7 @@ impl Scene { all_visible, self.paper_bg_color, highlight_selection, + true, ) } @@ -609,13 +615,26 @@ impl Scene { /// content viewport's GPU pipeline) don't get a second mis-projected /// copy on the paper sheet. pub fn paper_canvas_wipeouts(&self) -> Arc> { + let layout_block = self.current_layout_block_handle(); + Arc::new(self.wipeout_models_for_block_graph( + layout_block, + None, + self.paper_annotation_scale_handle(), + self.annotation_all_visible(), + self.paper_bg_color, + true, + false, + )) + } + + pub fn paper_plot_wipeouts(&self) -> Arc> { let layout_block = self.current_layout_block_handle(); Arc::new(self.plot_wipeouts_for_block( layout_block, None, self.paper_annotation_scale_handle(), self.annotation_all_visible(), - true, + false, )) } diff --git a/src/scene/project.rs b/src/scene/project.rs index c975ff2cb..e1b4ff775 100644 --- a/src/scene/project.rs +++ b/src/scene/project.rs @@ -436,6 +436,7 @@ impl Scene { Some(&frozen), self.viewport_scale_handle(viewport.common.handle), self.annotation_all_visible(), + true, ); for hatch in hatches { if matches!(&hatch.pattern, HatchPattern::Pattern(_)) { diff --git a/src/ui/window/layers.rs b/src/ui/window/layers.rs index 6798b73ea..cffcfa4ab 100644 --- a/src/ui/window/layers.rs +++ b/src/ui/window/layers.rs @@ -253,6 +253,10 @@ impl LayerPanel { self.apply_sort(); } + pub fn refresh_sort(&mut self) { + self.apply_sort(); + } + /// Reorder `self.layers` by the active sort column, preserving the current /// selection by name. No-op in document order (`sort_col == None`). fn apply_sort(&mut self) {