fix(plot): honor wipeout draw order
This commit is contained in:
parent
cfbf3233be
commit
e5b84ab8de
5 changed files with 127 additions and 35 deletions
|
|
@ -93,7 +93,7 @@ fn native_paths_match(left: &std::path::Path, right: &std::path::Path) -> bool {
|
|||
}
|
||||
|
||||
type LayoutPlotParams = (
|
||||
std::sync::Arc<Vec<crate::scene::WireModel>>,
|
||||
std::sync::Arc<Vec<crate::io::pdf_export::PlotWire>>,
|
||||
Vec<crate::scene::model::hatch_model::HatchModel>,
|
||||
Vec<crate::scene::model::hatch_model::HatchModel>,
|
||||
crate::io::pdf_export::PlotGroupSplits,
|
||||
|
|
@ -107,7 +107,7 @@ type LayoutPlotParams = (
|
|||
);
|
||||
|
||||
type ClippedPlotParams = (
|
||||
Vec<crate::scene::WireModel>,
|
||||
Vec<crate::io::pdf_export::PlotWire>,
|
||||
Vec<crate::scene::model::hatch_model::HatchModel>,
|
||||
Vec<crate::scene::model::hatch_model::HatchModel>,
|
||||
crate::io::pdf_export::PlotGroupSplits,
|
||||
|
|
@ -121,7 +121,7 @@ type ClippedPlotParams = (
|
|||
);
|
||||
|
||||
fn plot_content_extents(
|
||||
wires: &[crate::scene::WireModel],
|
||||
wires: &[crate::io::pdf_export::PlotWire],
|
||||
hatches: &[crate::scene::model::hatch_model::HatchModel],
|
||||
wipeouts: &[crate::scene::model::hatch_model::HatchModel],
|
||||
) -> Option<(f64, f64, f64, f64)> {
|
||||
|
|
@ -163,7 +163,7 @@ fn plot_scene_content(
|
|||
paper_space_last: bool,
|
||||
render_mode_override: Option<acadrust::entities::ViewportRenderMode>,
|
||||
) -> (
|
||||
std::sync::Arc<Vec<crate::scene::WireModel>>,
|
||||
std::sync::Arc<Vec<crate::io::pdf_export::PlotWire>>,
|
||||
Vec<crate::scene::model::hatch_model::HatchModel>,
|
||||
Vec<crate::scene::model::hatch_model::HatchModel>,
|
||||
crate::io::pdf_export::PlotGroupSplits,
|
||||
|
|
@ -171,6 +171,16 @@ 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 with_depth = |wires: Vec<crate::scene::WireModel>| {
|
||||
let depths = scene.plot_wire_depths(&wires);
|
||||
wires
|
||||
.into_iter()
|
||||
.zip(depths)
|
||||
.map(|(wire, draw_depth)| crate::io::pdf_export::PlotWire { wire, draw_depth })
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
let paper_wires = with_depth(paper_wires);
|
||||
let model_wires = with_depth(model_wires);
|
||||
let paper_hatches = scene.paper_plot_hatches().as_ref().clone();
|
||||
let paper_wipeouts = scene.paper_plot_wipeouts().as_ref().clone();
|
||||
if scene.current_layout == "Model" {
|
||||
|
|
@ -188,7 +198,11 @@ fn plot_scene_content(
|
|||
}
|
||||
let (mut model_pattern_wires, model_hatches, model_wipeouts) =
|
||||
scene.viewport_plot_fills();
|
||||
model_pattern_wires.retain(|wire| wire.plot_visible);
|
||||
model_pattern_wires.retain(|(wire, _)| wire.plot_visible);
|
||||
let model_pattern_wires = model_pattern_wires
|
||||
.into_iter()
|
||||
.map(|(wire, draw_depth)| crate::io::pdf_export::PlotWire { wire, draw_depth })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (wires, hatches, wipeouts, splits) = if paper_space_last {
|
||||
let splits = crate::io::pdf_export::PlotGroupSplits {
|
||||
|
|
|
|||
|
|
@ -24,12 +24,26 @@ use printpdf::{
|
|||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PlotWire {
|
||||
pub wire: WireModel,
|
||||
pub draw_depth: f32,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for PlotWire {
|
||||
type Target = WireModel;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.wire
|
||||
}
|
||||
}
|
||||
|
||||
// The web build has no `printpdf` (it pulls a wasm-incompatible `memchr` via
|
||||
// lopdf → nom_locate) and no filesystem, so PDF export is native-only; the web
|
||||
// build gets these stubs so the call sites still compile.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn export_pdf(
|
||||
_wires: &[WireModel],
|
||||
_wires: &[PlotWire],
|
||||
_hatches: &[HatchModel],
|
||||
_wipeouts: &[HatchModel],
|
||||
_paper_w: f64,
|
||||
|
|
@ -95,7 +109,7 @@ pub struct PlotGroupSplits {
|
|||
/// Owned render data for one page in a multi-page PDF.
|
||||
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
||||
pub struct PdfPageInput {
|
||||
pub wires: std::sync::Arc<Vec<WireModel>>,
|
||||
pub wires: std::sync::Arc<Vec<PlotWire>>,
|
||||
pub hatches: Vec<HatchModel>,
|
||||
pub wipeouts: Vec<HatchModel>,
|
||||
pub paper_w: f64,
|
||||
|
|
@ -131,7 +145,7 @@ impl Default for PdfPlotOptions {
|
|||
/// - `rotation_deg`: 0 | 90 | 180 | 270 — rotates the entire drawing on the page.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn export_pdf(
|
||||
wires: &[WireModel],
|
||||
wires: &[PlotWire],
|
||||
hatches: &[HatchModel],
|
||||
wipeouts: &[HatchModel],
|
||||
paper_w: f64,
|
||||
|
|
@ -204,7 +218,7 @@ pub fn pick_pdf_path_owned(
|
|||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn build_pdf(
|
||||
wires: &[WireModel],
|
||||
wires: &[PlotWire],
|
||||
hatches: &[HatchModel],
|
||||
wipeouts: &[HatchModel],
|
||||
paper_w: f32,
|
||||
|
|
@ -267,7 +281,7 @@ fn build_pdf_pages(pages: &[PdfPageInput], plot_style: Option<&PlotStyleTable>)
|
|||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn append_pdf_page(
|
||||
doc: &mut PdfDocument,
|
||||
wires: &[WireModel],
|
||||
wires: &[PlotWire],
|
||||
hatches: &[HatchModel],
|
||||
wipeouts: &[HatchModel],
|
||||
paper_w: f32,
|
||||
|
|
@ -393,29 +407,92 @@ fn append_pdf_page(
|
|||
(first_wires, first_hatches, first_wipeouts),
|
||||
(second_wires, second_hatches, second_wipeouts),
|
||||
] {
|
||||
emit_wire_fills(&mut ops, wires, ox, oy, plot_style, options);
|
||||
|
||||
// Hatch / wipeout fills render before wires so linework stays visible.
|
||||
for hatch in wipeouts.iter().chain(hatches.iter()) {
|
||||
emit_hatch(
|
||||
&mut ops,
|
||||
hatch,
|
||||
ox,
|
||||
oy,
|
||||
plot_style,
|
||||
scale,
|
||||
options,
|
||||
normal_blend.as_ref(),
|
||||
);
|
||||
enum DrawItem<'a> {
|
||||
WireFill(&'a PlotWire),
|
||||
Hatch(&'a HatchModel),
|
||||
Wire(&'a PlotWire),
|
||||
Text(&'a PlotWire),
|
||||
}
|
||||
|
||||
let mut draw_items = Vec::with_capacity(wires.len() * 2 + hatches.len() + wipeouts.len());
|
||||
let mut sequence = 0usize;
|
||||
for wire in wires {
|
||||
if !wire.fill_tris.is_empty() {
|
||||
draw_items.push((wire.draw_depth, 0u8, sequence, DrawItem::WireFill(wire)));
|
||||
sequence += 1;
|
||||
}
|
||||
draw_items.push((wire.draw_depth, 2u8, sequence, DrawItem::Wire(wire)));
|
||||
sequence += 1;
|
||||
if !wire.text_verts.is_empty() {
|
||||
draw_items.push((wire.draw_depth, 3u8, sequence, DrawItem::Text(wire)));
|
||||
sequence += 1;
|
||||
}
|
||||
}
|
||||
for hatch in wipeouts.iter().chain(hatches.iter()) {
|
||||
draw_items.push((hatch.draw_depth, 1u8, sequence, DrawItem::Hatch(hatch)));
|
||||
sequence += 1;
|
||||
}
|
||||
draw_items.sort_by(|a, b| {
|
||||
a.0.total_cmp(&b.0)
|
||||
.then_with(|| a.1.cmp(&b.1))
|
||||
.then_with(|| a.2.cmp(&b.2))
|
||||
});
|
||||
|
||||
let mut last_color: Option<[f32; 3]> = None;
|
||||
let mut last_lw: Option<f32> = None;
|
||||
// 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;
|
||||
|
||||
for wire in wires {
|
||||
for (_, _, _, item) in draw_items {
|
||||
let wire = match item {
|
||||
DrawItem::WireFill(wire) => {
|
||||
emit_wire_fills(
|
||||
&mut ops,
|
||||
std::slice::from_ref(&wire.wire),
|
||||
ox,
|
||||
oy,
|
||||
plot_style,
|
||||
options,
|
||||
);
|
||||
last_color = None;
|
||||
last_lw = None;
|
||||
last_dash = None;
|
||||
continue;
|
||||
}
|
||||
DrawItem::Hatch(hatch) => {
|
||||
emit_hatch(
|
||||
&mut ops,
|
||||
hatch,
|
||||
ox,
|
||||
oy,
|
||||
plot_style,
|
||||
scale,
|
||||
options,
|
||||
normal_blend.as_ref(),
|
||||
);
|
||||
last_color = None;
|
||||
last_lw = None;
|
||||
last_dash = None;
|
||||
continue;
|
||||
}
|
||||
DrawItem::Text(wire) => {
|
||||
emit_text(
|
||||
&mut ops,
|
||||
std::slice::from_ref(&wire.wire),
|
||||
ox,
|
||||
oy,
|
||||
scale,
|
||||
plot_style,
|
||||
options,
|
||||
);
|
||||
last_color = None;
|
||||
last_lw = None;
|
||||
last_dash = None;
|
||||
continue;
|
||||
}
|
||||
DrawItem::Wire(wire) => wire,
|
||||
};
|
||||
let [mut r, mut g, mut b, a] = wire.color;
|
||||
if a < 0.01 {
|
||||
continue;
|
||||
|
|
@ -552,13 +629,6 @@ fn append_pdf_page(
|
|||
}
|
||||
flush_line(&mut ops, &segment, dot_radius);
|
||||
}
|
||||
|
||||
// Text (SDF glyph quads) — re-emitted as vector strokes / fills. Text now
|
||||
// renders on-screen only as textured SDF quads (`wire.text_verts`), which
|
||||
// this CPU exporter can't sample, so without this pass all text — including
|
||||
// dimension text — is missing from the PDF (issue #385). Drawn after the
|
||||
// wires (on top) and under the same rotation/scale/clip CTM.
|
||||
emit_text(&mut ops, wires, ox, oy, scale, plot_style, options);
|
||||
}
|
||||
|
||||
if needs_state {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
use crate::io::pdf_export;
|
||||
use crate::io::plot_style::PlotStyleTable;
|
||||
use crate::scene::model::hatch_model::HatchModel;
|
||||
use crate::scene::WireModel;
|
||||
use crate::io::pdf_export::PlotWire;
|
||||
|
||||
/// Extra options for a print job. On CUPS (Linux/macOS) these map to `lp`
|
||||
/// flags / `-o` options. On Windows the generated PDF already carries render
|
||||
|
|
@ -38,7 +38,7 @@ pub fn list_printers() -> Vec<String> {
|
|||
#[cfg(target_arch = "wasm32")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn print_wires_with(
|
||||
_wires: std::sync::Arc<Vec<WireModel>>,
|
||||
_wires: std::sync::Arc<Vec<PlotWire>>,
|
||||
_hatches: Vec<HatchModel>,
|
||||
_wipeouts: Vec<HatchModel>,
|
||||
_paper_w: f64,
|
||||
|
|
@ -153,7 +153,7 @@ pub fn open_printer_properties(_printer: Option<&str>) -> Result<(), String> {
|
|||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn print_wires_with(
|
||||
wires: std::sync::Arc<Vec<WireModel>>,
|
||||
wires: std::sync::Arc<Vec<PlotWire>>,
|
||||
hatches: Vec<HatchModel>,
|
||||
wipeouts: Vec<HatchModel>,
|
||||
paper_w: f64,
|
||||
|
|
|
|||
|
|
@ -5454,6 +5454,14 @@ impl Scene {
|
|||
(self.paper_sheet_wires_arc().as_ref().clone(), model_wires)
|
||||
}
|
||||
|
||||
pub(crate) fn plot_wire_depths(&self, wires: &[WireModel]) -> Vec<f32> {
|
||||
let depths = self.draw_depth_map();
|
||||
wires
|
||||
.iter()
|
||||
.map(|wire| pipeline::wire_gpu::wire_draw_depth(wire, depths.as_ref()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Per-entity stable draw-order depth, keyed by entity handle value.
|
||||
/// A full build assigns sparse labels in effective draw order. Incremental
|
||||
/// Add/Remove then changes only the named handle: existing siblings retain
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ impl Scene {
|
|||
/// needs the equivalent paper-space geometry explicitly.
|
||||
pub fn viewport_plot_fills(
|
||||
&self,
|
||||
) -> (Vec<WireModel>, Vec<HatchModel>, Vec<HatchModel>) {
|
||||
) -> (Vec<(WireModel, f32)>, Vec<HatchModel>, Vec<HatchModel>) {
|
||||
use acadrust::entities::Viewport;
|
||||
use model::hatch_model::HatchPattern;
|
||||
|
||||
|
|
@ -478,7 +478,7 @@ impl Scene {
|
|||
wire.aci = hatch.aci;
|
||||
wire.line_weight_px = hatch.line_weight_px;
|
||||
wire.aabb = aabb;
|
||||
pattern_wires.push(wire);
|
||||
pattern_wires.push((wire, hatch.draw_depth));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue