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 = (
|
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>,
|
||||||
Vec<crate::scene::model::hatch_model::HatchModel>,
|
Vec<crate::scene::model::hatch_model::HatchModel>,
|
||||||
crate::io::pdf_export::PlotGroupSplits,
|
crate::io::pdf_export::PlotGroupSplits,
|
||||||
|
|
@ -107,7 +107,7 @@ type LayoutPlotParams = (
|
||||||
);
|
);
|
||||||
|
|
||||||
type ClippedPlotParams = (
|
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>,
|
||||||
Vec<crate::scene::model::hatch_model::HatchModel>,
|
Vec<crate::scene::model::hatch_model::HatchModel>,
|
||||||
crate::io::pdf_export::PlotGroupSplits,
|
crate::io::pdf_export::PlotGroupSplits,
|
||||||
|
|
@ -121,7 +121,7 @@ type ClippedPlotParams = (
|
||||||
);
|
);
|
||||||
|
|
||||||
fn plot_content_extents(
|
fn plot_content_extents(
|
||||||
wires: &[crate::scene::WireModel],
|
wires: &[crate::io::pdf_export::PlotWire],
|
||||||
hatches: &[crate::scene::model::hatch_model::HatchModel],
|
hatches: &[crate::scene::model::hatch_model::HatchModel],
|
||||||
wipeouts: &[crate::scene::model::hatch_model::HatchModel],
|
wipeouts: &[crate::scene::model::hatch_model::HatchModel],
|
||||||
) -> Option<(f64, f64, f64, f64)> {
|
) -> Option<(f64, f64, f64, f64)> {
|
||||||
|
|
@ -163,7 +163,7 @@ fn plot_scene_content(
|
||||||
paper_space_last: bool,
|
paper_space_last: bool,
|
||||||
render_mode_override: Option<acadrust::entities::ViewportRenderMode>,
|
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>,
|
||||||
Vec<crate::scene::model::hatch_model::HatchModel>,
|
Vec<crate::scene::model::hatch_model::HatchModel>,
|
||||||
crate::io::pdf_export::PlotGroupSplits,
|
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);
|
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 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_hatches = scene.paper_plot_hatches().as_ref().clone();
|
||||||
let paper_wipeouts = scene.paper_plot_wipeouts().as_ref().clone();
|
let paper_wipeouts = scene.paper_plot_wipeouts().as_ref().clone();
|
||||||
if scene.current_layout == "Model" {
|
if scene.current_layout == "Model" {
|
||||||
|
|
@ -188,7 +198,11 @@ fn plot_scene_content(
|
||||||
}
|
}
|
||||||
let (mut model_pattern_wires, model_hatches, model_wipeouts) =
|
let (mut model_pattern_wires, model_hatches, model_wipeouts) =
|
||||||
scene.viewport_plot_fills();
|
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 (wires, hatches, wipeouts, splits) = if paper_space_last {
|
||||||
let splits = crate::io::pdf_export::PlotGroupSplits {
|
let splits = crate::io::pdf_export::PlotGroupSplits {
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,26 @@ use printpdf::{
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
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
|
// 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
|
// lopdf → nom_locate) and no filesystem, so PDF export is native-only; the web
|
||||||
// build gets these stubs so the call sites still compile.
|
// build gets these stubs so the call sites still compile.
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn export_pdf(
|
pub fn export_pdf(
|
||||||
_wires: &[WireModel],
|
_wires: &[PlotWire],
|
||||||
_hatches: &[HatchModel],
|
_hatches: &[HatchModel],
|
||||||
_wipeouts: &[HatchModel],
|
_wipeouts: &[HatchModel],
|
||||||
_paper_w: f64,
|
_paper_w: f64,
|
||||||
|
|
@ -95,7 +109,7 @@ pub struct PlotGroupSplits {
|
||||||
/// Owned render data for one page in a multi-page PDF.
|
/// Owned render data for one page in a multi-page PDF.
|
||||||
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
||||||
pub struct PdfPageInput {
|
pub struct PdfPageInput {
|
||||||
pub wires: std::sync::Arc<Vec<WireModel>>,
|
pub wires: std::sync::Arc<Vec<PlotWire>>,
|
||||||
pub hatches: Vec<HatchModel>,
|
pub hatches: Vec<HatchModel>,
|
||||||
pub wipeouts: Vec<HatchModel>,
|
pub wipeouts: Vec<HatchModel>,
|
||||||
pub paper_w: f64,
|
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.
|
/// - `rotation_deg`: 0 | 90 | 180 | 270 — rotates the entire drawing on the page.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn export_pdf(
|
pub fn export_pdf(
|
||||||
wires: &[WireModel],
|
wires: &[PlotWire],
|
||||||
hatches: &[HatchModel],
|
hatches: &[HatchModel],
|
||||||
wipeouts: &[HatchModel],
|
wipeouts: &[HatchModel],
|
||||||
paper_w: f64,
|
paper_w: f64,
|
||||||
|
|
@ -204,7 +218,7 @@ pub fn pick_pdf_path_owned(
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn build_pdf(
|
fn build_pdf(
|
||||||
wires: &[WireModel],
|
wires: &[PlotWire],
|
||||||
hatches: &[HatchModel],
|
hatches: &[HatchModel],
|
||||||
wipeouts: &[HatchModel],
|
wipeouts: &[HatchModel],
|
||||||
paper_w: f32,
|
paper_w: f32,
|
||||||
|
|
@ -267,7 +281,7 @@ fn build_pdf_pages(pages: &[PdfPageInput], plot_style: Option<&PlotStyleTable>)
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn append_pdf_page(
|
fn append_pdf_page(
|
||||||
doc: &mut PdfDocument,
|
doc: &mut PdfDocument,
|
||||||
wires: &[WireModel],
|
wires: &[PlotWire],
|
||||||
hatches: &[HatchModel],
|
hatches: &[HatchModel],
|
||||||
wipeouts: &[HatchModel],
|
wipeouts: &[HatchModel],
|
||||||
paper_w: f32,
|
paper_w: f32,
|
||||||
|
|
@ -393,10 +407,60 @@ fn append_pdf_page(
|
||||||
(first_wires, first_hatches, first_wipeouts),
|
(first_wires, first_hatches, first_wipeouts),
|
||||||
(second_wires, second_hatches, second_wipeouts),
|
(second_wires, second_hatches, second_wipeouts),
|
||||||
] {
|
] {
|
||||||
emit_wire_fills(&mut ops, wires, ox, oy, plot_style, options);
|
enum DrawItem<'a> {
|
||||||
|
WireFill(&'a PlotWire),
|
||||||
|
Hatch(&'a HatchModel),
|
||||||
|
Wire(&'a PlotWire),
|
||||||
|
Text(&'a PlotWire),
|
||||||
|
}
|
||||||
|
|
||||||
// Hatch / wipeout fills render before wires so linework stays visible.
|
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()) {
|
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 (_, _, _, 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(
|
emit_hatch(
|
||||||
&mut ops,
|
&mut ops,
|
||||||
hatch,
|
hatch,
|
||||||
|
|
@ -407,15 +471,28 @@ fn append_pdf_page(
|
||||||
options,
|
options,
|
||||||
normal_blend.as_ref(),
|
normal_blend.as_ref(),
|
||||||
);
|
);
|
||||||
|
last_color = None;
|
||||||
|
last_lw = None;
|
||||||
|
last_dash = None;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
DrawItem::Text(wire) => {
|
||||||
let mut last_color: Option<[f32; 3]> = None;
|
emit_text(
|
||||||
let mut last_lw: Option<f32> = None;
|
&mut ops,
|
||||||
// Current PDF dash array (empty = solid). Tracked so the dash op is only
|
std::slice::from_ref(&wire.wire),
|
||||||
// re-emitted when it actually changes between wires.
|
ox,
|
||||||
let mut last_dash: Option<Vec<i64>> = None;
|
oy,
|
||||||
|
scale,
|
||||||
for wire in wires {
|
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;
|
let [mut r, mut g, mut b, a] = wire.color;
|
||||||
if a < 0.01 {
|
if a < 0.01 {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -552,13 +629,6 @@ fn append_pdf_page(
|
||||||
}
|
}
|
||||||
flush_line(&mut ops, &segment, dot_radius);
|
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 {
|
if needs_state {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
use crate::io::pdf_export;
|
use crate::io::pdf_export;
|
||||||
use crate::io::plot_style::PlotStyleTable;
|
use crate::io::plot_style::PlotStyleTable;
|
||||||
use crate::scene::model::hatch_model::HatchModel;
|
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`
|
/// 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
|
/// 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")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn print_wires_with(
|
pub async fn print_wires_with(
|
||||||
_wires: std::sync::Arc<Vec<WireModel>>,
|
_wires: std::sync::Arc<Vec<PlotWire>>,
|
||||||
_hatches: Vec<HatchModel>,
|
_hatches: Vec<HatchModel>,
|
||||||
_wipeouts: Vec<HatchModel>,
|
_wipeouts: Vec<HatchModel>,
|
||||||
_paper_w: f64,
|
_paper_w: f64,
|
||||||
|
|
@ -153,7 +153,7 @@ pub fn open_printer_properties(_printer: Option<&str>) -> Result<(), String> {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn print_wires_with(
|
pub async fn print_wires_with(
|
||||||
wires: std::sync::Arc<Vec<WireModel>>,
|
wires: std::sync::Arc<Vec<PlotWire>>,
|
||||||
hatches: Vec<HatchModel>,
|
hatches: Vec<HatchModel>,
|
||||||
wipeouts: Vec<HatchModel>,
|
wipeouts: Vec<HatchModel>,
|
||||||
paper_w: f64,
|
paper_w: f64,
|
||||||
|
|
|
||||||
|
|
@ -5454,6 +5454,14 @@ impl Scene {
|
||||||
(self.paper_sheet_wires_arc().as_ref().clone(), model_wires)
|
(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.
|
/// Per-entity stable draw-order depth, keyed by entity handle value.
|
||||||
/// A full build assigns sparse labels in effective draw order. Incremental
|
/// A full build assigns sparse labels in effective draw order. Incremental
|
||||||
/// Add/Remove then changes only the named handle: existing siblings retain
|
/// Add/Remove then changes only the named handle: existing siblings retain
|
||||||
|
|
|
||||||
|
|
@ -354,7 +354,7 @@ impl Scene {
|
||||||
/// needs the equivalent paper-space geometry explicitly.
|
/// needs the equivalent paper-space geometry explicitly.
|
||||||
pub fn viewport_plot_fills(
|
pub fn viewport_plot_fills(
|
||||||
&self,
|
&self,
|
||||||
) -> (Vec<WireModel>, Vec<HatchModel>, Vec<HatchModel>) {
|
) -> (Vec<(WireModel, f32)>, Vec<HatchModel>, Vec<HatchModel>) {
|
||||||
use acadrust::entities::Viewport;
|
use acadrust::entities::Viewport;
|
||||||
use model::hatch_model::HatchPattern;
|
use model::hatch_model::HatchPattern;
|
||||||
|
|
||||||
|
|
@ -478,7 +478,7 @@ impl Scene {
|
||||||
wire.aci = hatch.aci;
|
wire.aci = hatch.aci;
|
||||||
wire.line_weight_px = hatch.line_weight_px;
|
wire.line_weight_px = hatch.line_weight_px;
|
||||||
wire.aabb = aabb;
|
wire.aabb = aabb;
|
||||||
pattern_wires.push(wire);
|
pattern_wires.push((wire, hatch.draw_depth));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue