forked from 1b-agent/opencadstudio-upstream
feat(plot): PDF output carries drawing layers
Every plotted wire and hatch resolves to its entity's layer, and the PDF wraps each run of same-layer items in an optional-content group, so a viewer's layer panel lists the drawing's layers and can hide them. Draw order is unchanged; images stay outside any group. Expanded block geometry goes with its INSERT's layer. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ee2898095e
commit
de1e49a4c5
3 files changed
+78
No files matched your search
@@ -604,12 +604,15 @@ fn plot_scene_content(
|
||||
wipeouts: paper_wipeouts.len(),
|
||||
images: paper_images.len(),
|
||||
};
|
||||
let entity_layers =
|
||||
plot_entity_layers(scene, &paper_wires, &paper_hatches, &paper_wipeouts);
|
||||
return PlotContent {
|
||||
wires: std::sync::Arc::new(paper_wires),
|
||||
hatches: paper_hatches,
|
||||
wipeouts: paper_wipeouts,
|
||||
images: paper_images,
|
||||
group_splits: splits,
|
||||
entity_layers,
|
||||
};
|
||||
}
|
||||
let (mut model_pattern_wires, model_hatches, model_wipeouts, model_images) =
|
||||
@@ -658,15 +661,47 @@ fn plot_scene_content(
|
||||
images.extend(model_images);
|
||||
(wires, hatches, wipeouts, images, splits)
|
||||
};
|
||||
let entity_layers = plot_entity_layers(scene, &wires, &hatches, &wipeouts);
|
||||
PlotContent {
|
||||
wires: std::sync::Arc::new(wires),
|
||||
hatches,
|
||||
wipeouts,
|
||||
images,
|
||||
group_splits: splits,
|
||||
entity_layers,
|
||||
}
|
||||
}
|
||||
|
||||
/// Drawing layer of every entity the plot draws, keyed by the handle in its
|
||||
/// wire or hatch name.
|
||||
// ponytail: expanded block geometry carries its INSERT's handle, so a block's
|
||||
// children all land on the INSERT's layer; per-child layers need BlockCache to
|
||||
// keep the child handle on each wire.
|
||||
fn plot_entity_layers(
|
||||
scene: &crate::scene::Scene,
|
||||
wires: &[crate::io::pdf_export::PlotWire],
|
||||
hatches: &[crate::scene::model::hatch_model::HatchModel],
|
||||
wipeouts: &[crate::scene::model::hatch_model::HatchModel],
|
||||
) -> std::collections::HashMap<codec::Handle, String> {
|
||||
let names = wires
|
||||
.iter()
|
||||
.map(|wire| wire.name.as_str())
|
||||
.chain(hatches.iter().chain(wipeouts).map(|hatch| hatch.name.as_str()));
|
||||
let mut layers = std::collections::HashMap::new();
|
||||
for name in names {
|
||||
let Some(handle) = crate::scene::Scene::handle_from_wire_name(name) else {
|
||||
continue;
|
||||
};
|
||||
if layers.contains_key(&handle) {
|
||||
continue;
|
||||
}
|
||||
if let Some(entity) = scene.document.get_entity(handle) {
|
||||
layers.insert(handle, entity.common().layer.clone());
|
||||
}
|
||||
}
|
||||
layers
|
||||
}
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// Persist exact ACIS bodies, and an edge cache with them, for solids that
|
||||
/// have no ACIS data before saving. A solid that has ACIS data keeps its
|
||||
|
||||
@@ -442,11 +442,14 @@ pub(crate) fn render_pdf_bytes(
|
||||
// Allocation addresses cannot be reused by another source during this export.
|
||||
let mut image_resources = std::collections::HashMap::new();
|
||||
let mut text_fonts = TextFonts::default();
|
||||
// One optional-content group per drawing layer, shared by every page.
|
||||
let mut layer_ids = std::collections::HashMap::new();
|
||||
for (index, page) in pages.iter().enumerate() {
|
||||
append_pdf_page(
|
||||
&mut doc,
|
||||
&mut image_resources,
|
||||
&mut text_fonts,
|
||||
&mut layer_ids,
|
||||
page,
|
||||
plot_style,
|
||||
&fonts,
|
||||
@@ -473,6 +476,7 @@ fn append_pdf_page(
|
||||
doc: &mut PdfDocument,
|
||||
image_resources: &mut std::collections::HashMap<(usize, u32, u32), printpdf::XObjectId>,
|
||||
text_fonts: &mut TextFonts,
|
||||
layer_ids: &mut std::collections::HashMap<String, printpdf::LayerInternalId>,
|
||||
page: &PdfPageInput,
|
||||
fallback_plot_style: Option<&PlotStyleTable>,
|
||||
fonts: &SearchableFontSet,
|
||||
@@ -494,7 +498,13 @@ fn append_pdf_page(
|
||||
wipeouts,
|
||||
images,
|
||||
group_splits,
|
||||
entity_layers,
|
||||
} = &page.content;
|
||||
let layer_of = |name: &str| {
|
||||
crate::scene::Scene::handle_from_wire_name(name)
|
||||
.and_then(|handle| entity_layers.get(&handle))
|
||||
.map(String::as_str)
|
||||
};
|
||||
let (paper_w, paper_h) = (page.paper_w as f32, page.paper_h as f32);
|
||||
let (ox, oy) = (page.offset_x, page.offset_y);
|
||||
let (rotation_deg, scale, clip) = (page.rotation_deg, page.scale, page.clip);
|
||||
@@ -686,8 +696,35 @@ fn append_pdf_page(
|
||||
// Current PDF dash array (empty = solid). Tracked so the dash op is only
|
||||
// re-emitted when it actually changes between wires.
|
||||
let mut last_dash: Option<Vec<i64>> = None;
|
||||
// Consecutive items on one drawing layer share a marked-content run,
|
||||
// so a viewer's layer panel can hide that layer.
|
||||
let mut open_layer: Option<&str> = None;
|
||||
|
||||
for (_, _, _, item) in draw_items {
|
||||
let layer = match &item {
|
||||
DrawItem::WireFill(wire) | DrawItem::Wire(wire) | DrawItem::Text(wire) => {
|
||||
layer_of(&wire.name)
|
||||
}
|
||||
DrawItem::Hatch(hatch) => layer_of(&hatch.name),
|
||||
DrawItem::Image(_) => None,
|
||||
};
|
||||
if layer != open_layer {
|
||||
if open_layer.is_some() {
|
||||
ops.push(Op::EndLayer);
|
||||
}
|
||||
if let Some(name) = layer {
|
||||
let layer_id = layer_ids
|
||||
.entry(name.to_string())
|
||||
.or_insert_with(|| {
|
||||
let mut layer = printpdf::Layer::new(name);
|
||||
layer.creator = "Open CAD Studio".into();
|
||||
doc.add_layer(&layer)
|
||||
})
|
||||
.clone();
|
||||
ops.push(Op::BeginLayer { layer_id });
|
||||
}
|
||||
open_layer = layer;
|
||||
}
|
||||
let wire = match item {
|
||||
DrawItem::WireFill(wire) => {
|
||||
emit_wire_fills(
|
||||
@@ -1070,6 +1107,9 @@ fn append_pdf_page(
|
||||
}
|
||||
flush_line(&mut ops, &segment, dot_radius);
|
||||
}
|
||||
if open_layer.is_some() {
|
||||
ops.push(Op::EndLayer);
|
||||
}
|
||||
}
|
||||
|
||||
if needs_state {
|
||||
|
||||
@@ -90,6 +90,9 @@ pub struct PlotContent {
|
||||
pub wipeouts: Vec<HatchModel>,
|
||||
pub images: Vec<PlotImage>,
|
||||
pub group_splits: PlotGroupSplits,
|
||||
/// Drawing layer of each plotted entity, keyed by the handle its wire or
|
||||
/// hatch name carries; the PDF groups output into one layer per entry.
|
||||
pub entity_layers: std::collections::HashMap<codec::Handle, String>,
|
||||
}
|
||||
|
||||
/// Owned geometry and settings for one plot page.
|
||||
|
||||
Reference in new issue
Block a user