feat(layers): add non-printing layer support

This commit is contained in:
gianlucafiore 2026-08-20 20:37:27 -03:00
commit eb5cbaeca0
7 changed files with 192 additions and 6 deletions

View file

@ -2013,6 +2013,7 @@ pub enum Message {
LayerToggleVisible(usize), LayerToggleVisible(usize),
LayerToggleLock(usize), LayerToggleLock(usize),
LayerToggleFreeze(usize), LayerToggleFreeze(usize),
LayerTogglePlot(usize),
/// Sort the Layer Manager table by a clicked column header. /// Sort the Layer Manager table by a clicked column header.
LayerSort(crate::ui::window::layers::LayerSortCol), LayerSort(crate::ui::window::layers::LayerSortCol),
/// Toggle per-viewport freeze: (layer_index, vp_col_index) /// Toggle per-viewport freeze: (layer_index, vp_col_index)

View file

@ -171,7 +171,7 @@ fn plot_scene_content(
let (mut paper_wires, mut model_wires) = scene.plot_wire_groups(render_mode_override); let (mut paper_wires, mut model_wires) = scene.plot_wire_groups(render_mode_override);
paper_wires.retain(|wire| wire.plot_visible); paper_wires.retain(|wire| wire.plot_visible);
model_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(); let paper_wipeouts = scene.paper_canvas_wipeouts().as_ref().clone();
if scene.current_layout == "Model" { if scene.current_layout == "Model" {
let splits = crate::io::pdf_export::PlotGroupSplits { let splits = crate::io::pdf_export::PlotGroupSplits {

View file

@ -2427,6 +2427,56 @@ impl OpenCADStudio {
} }
Task::none() 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) => { Message::LayerToggleVpFreeze(layer_idx, vp_col_idx) => {
self.on_layer_toggle_vp_freeze(layer_idx, vp_col_idx) self.on_layer_toggle_vp_freeze(layer_idx, vp_col_idx)

View file

@ -339,8 +339,48 @@ pub(crate) fn tessellate_entity_dim_text(
} }
wires wires
} }
pub(crate) fn tessellate_entity( pub(crate) fn tessellate_entity(
document: &acadrust::CadDocument,
selected: &HashSet<Handle>,
active_viewport: Option<Handle>,
bg_color: [f32; 4],
anno_scale: f32,
annotation_scale_handle: Option<Handle>,
e: &EntityType,
block_cache: Option<&cache::block_cache::BlockCache>,
view_aabb: Option<[f32; 4]>,
world_per_pixel: Option<f32>,
paper_space: bool,
) -> Vec<WireModel> {
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, document: &acadrust::CadDocument,
selected: &HashSet<Handle>, selected: &HashSet<Handle>,
active_viewport: Option<Handle>, active_viewport: Option<Handle>,

View file

@ -1144,9 +1144,31 @@ impl Scene {
viewport, viewport,
None, None,
false, false,
false,
)
}
pub(super) fn instanced_plot_hatch_models(
&self,
layout_block: Handle,
hatch_bg: [f32; 4],
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
annotation_scale_handle: Option<Handle>,
all_visible: bool,
viewport: Option<Handle>,
) -> Vec<HatchModel> {
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 /// Build live hatch overlays for INSERT grip previews. The edited INSERT
/// is intentionally hidden from the resident scene while its current /// is intentionally hidden from the resident scene while its current
/// document entity moves, so include that hidden target and reuse the full /// document entity moves, so include that hidden target and reuse the full
@ -1183,6 +1205,7 @@ impl Scene {
self.active_viewport, self.active_viewport,
Some(&targets), Some(&targets),
true, true,
false,
) )
} }
@ -1197,6 +1220,7 @@ impl Scene {
viewport: Option<Handle>, viewport: Option<Handle>,
targets: Option<&rustc_hash::FxHashSet<Handle>>, targets: Option<&rustc_hash::FxHashSet<Handle>>,
include_preview_hidden: bool, include_preview_hidden: bool,
plot_only: bool,
) -> Vec<HatchModel> { ) -> Vec<HatchModel> {
let depth_map = self.draw_depth_map(); let depth_map = self.draw_depth_map();
let graph = crate::scene::render_graph::RenderSceneGraph::new( let graph = crate::scene::render_graph::RenderSceneGraph::new(
@ -1213,10 +1237,21 @@ impl Scene {
graph.walk_root( graph.walk_root(
self.render_scene_root(layout_block), self.render_scene_root(layout_block),
|entity, context| { |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() { if context.is_instanced() {
return true; return true;
} }
let common = entity.common();
if self.object_isolation.hides(common.handle) if self.object_isolation.hides(common.handle)
|| (!include_preview_hidden || (!include_preview_hidden
&& self.preview_hidden.contains(&common.handle)) && self.preview_hidden.contains(&common.handle))
@ -1254,6 +1289,15 @@ impl Scene {
let EntityType::Hatch(source_hatch) = entity else { let EntityType::Hatch(source_hatch) = entity else {
return; 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 style = context.style_for(&self.document, entity);
let preserve_white_mask = source_hatch.is_solid let preserve_white_mask = source_hatch.is_solid
&& matches!( && matches!(

View file

@ -482,6 +482,16 @@ impl Scene {
models.extend(instanced); models.extend(instanced);
Arc::new(models) Arc::new(models)
} }
pub fn paper_plot_hatches(&self) -> Arc<Vec<HatchModel>> {
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 /// Plot-only hatch set for a specific block. Paper PDF generation uses this
/// for the model block behind each floating viewport; unlike /// for the model block behind each floating viewport; unlike
@ -500,6 +510,13 @@ impl Scene {
.map(|l| l.flags.off || l.flags.frozen) .map(|l| l.flags.off || l.flags.frozen)
.unwrap_or(false) .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(); let mut models = Vec::new();
for (&handle, model) in self.hatches.iter() { for (&handle, model) in self.hatches.iter() {
let Some(source) = self.document.get_entity(handle) else { let Some(source) = self.document.get_entity(handle) else {
@ -515,6 +532,7 @@ impl Scene {
if common.invisible if common.invisible
|| self.entity_temporarily_hidden(handle) || self.entity_temporarily_hidden(handle)
|| layer_hidden(&common.layer) || layer_hidden(&common.layer)
|| !layer_plottable(&common.layer)
|| self.layer_frozen_in(&common.layer, frozen) || self.layer_frozen_in(&common.layer, frozen)
|| crate::scene::annotative::annotative_offscale_for( || crate::scene::annotative::annotative_offscale_for(
&self.document, &self.document,
@ -554,10 +572,9 @@ impl Scene {
} }
models.push(hatch); models.push(hatch);
} }
models.extend(self.instanced_hatch_models( models.extend(self.instanced_plot_hatch_models(
block, block,
self.paper_bg_color, self.paper_bg_color,
false,
frozen, frozen,
annotation_scale_handle, annotation_scale_handle,
all_visible, all_visible,

View file

@ -32,6 +32,7 @@ pub enum LayerSortCol {
On, On,
Freeze, Freeze,
Lock, Lock,
Plot,
Color, Color,
Linetype, Linetype,
Lineweight, Lineweight,
@ -86,6 +87,7 @@ pub struct Layer {
pub visible: bool, pub visible: bool,
pub frozen: bool, pub frozen: bool,
pub locked: bool, pub locked: bool,
pub plottable: bool,
pub color: AcadColor, pub color: AcadColor,
pub linetype: String, pub linetype: String,
pub lineweight: LineWeight, pub lineweight: LineWeight,
@ -101,6 +103,7 @@ impl Layer {
visible: true, visible: true,
frozen: false, frozen: false,
locked: false, locked: false,
plottable: true,
color, color,
linetype: "Continuous".to_string(), linetype: "Continuous".to_string(),
lineweight: LineWeight::Default, lineweight: LineWeight::Default,
@ -214,6 +217,7 @@ impl LayerPanel {
visible: !l.flags.off, visible: !l.flags.off,
frozen: l.flags.frozen, frozen: l.flags.frozen,
locked: l.flags.locked, locked: l.flags.locked,
plottable: l.is_plottable,
color: l.color, color: l.color,
linetype: if l.line_type.is_empty() { linetype: if l.line_type.is_empty() {
"Continuous".to_string() "Continuous".to_string()
@ -275,6 +279,7 @@ impl LayerPanel {
LayerSortCol::On => a.visible.cmp(&b.visible), LayerSortCol::On => a.visible.cmp(&b.visible),
LayerSortCol::Freeze => a.frozen.cmp(&b.frozen), LayerSortCol::Freeze => a.frozen.cmp(&b.frozen),
LayerSortCol::Lock => a.locked.cmp(&b.locked), 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::Color => color_sort_key(a.color).cmp(&color_sort_key(b.color)),
LayerSortCol::Linetype => { LayerSortCol::Linetype => {
a.linetype.to_lowercase().cmp(&b.linetype.to_lowercase()) 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!("On"), LayerSortCol::On, Length::Fixed(COL_ICON), sc, sa),
sortable_header(t!("Freeze"), LayerSortCol::Freeze, 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!("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!("Color"), LayerSortCol::Color, Length::Fixed(COL_COLOR), sc, sa),
sortable_header(t!("Linetype"), LayerSortCol::Linetype, Length::Fixed(COL_LT), 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), 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 vis_svg = crate::ui::icons::layer_visible(layer.visible);
let frz_svg = crate::ui::icons::layer_freeze(layer.frozen); let frz_svg = crate::ui::icons::layer_freeze(layer.frozen);
let lck_svg = crate::ui::icons::layer_lock(layer.locked); 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 { let status_dot: Element<'_, Message> = if is_current {
crate::ui::icons::themed_success(crate::ui::icons::CHECK, 13.0) 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))) container(svg_btn(lck_svg, Message::LayerToggleLock(index)))
.width(Length::Fixed(COL_ICON)) .width(Length::Fixed(COL_ICON))
.align_x(iced::Center), .align_x(iced::Center),
container(plot_btn)
.width(Length::Fixed(COL_ICON))
.align_x(iced::Center),
color_cell, color_cell,
lt_cell, lt_cell,
lw_cell, lw_cell,