Merge pull request #1346 from huaninstratech/fix/block-text-wipeout-depth

fix: keep block text above its own block's wipeouts (compose glyph depth through the scene graph)
This commit is contained in:
Hakan Seven committed 2026-09-18 18:52:37 +03:00
commit ce6db72c78
7 files changed
+214 -10

No files matched your search

+10 -1
View File
@@ -5612,7 +5612,12 @@ mod plot_paper_tests {
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/plot/page-setups-metric.dxf"
);
let reply = app.automation_op(&format!(r#"{{"op":"open","path":"{path}"}}"#));
// json! escapes the path: a Windows CARGO_MANIFEST_DIR carries
// backslashes, and a raw format! would put invalid \X escapes in the
// JSON string.
let reply = app.automation_op(
&serde_json::json!({"op": "open", "path": path}).to_string(),
);
assert_eq!(reply["ok"], true, "{reply}");
app
}
@@ -5911,6 +5916,10 @@ mod plot_paper_tests {
assert!(app.plot_dialog.custom_editor.is_none());
}
// The driver-options editor is the CUPS flow; on Windows
// PrinterProperties routes to the system preferences dialog instead, so
// the editor under test never opens there.
#[cfg(not(target_os = "windows"))]
#[test]
fn driver_options_are_edited_remembered_and_sent_with_the_job() {
use crate::io::print_to_printer::parse_lpoptions;
+7 -1
View File
@@ -605,6 +605,7 @@ fn append_pdf_page(
emit_wire_fills(
&mut ops,
std::slice::from_ref(&wire.wire),
wire.draw_depth,
ox,
oy,
plot_style,
@@ -1036,6 +1037,7 @@ fn plotted_color(
fn emit_wire_fills(
ops: &mut Vec<Op>,
wires: &[WireModel],
wire_depth: f32,
ox: f64,
oy: f64,
plot_style: Option<&PlotStyleTable>,
@@ -1090,7 +1092,11 @@ fn emit_wire_fills(
line_weight_px: wire.line_weight_px,
angle_offset: 0.0,
scale: 1.0 / scale.max(1.0e-6),
draw_depth: wire.depth_override.unwrap_or(0.0),
// The host wire's composed draw depth (PlotWire carries
// wire_draw_depth). depth_override alone is a per-block
// child label and would sort the fill outside its block's
// band; keep the pattern fill co-sorted with its wire.
draw_depth: wire_depth,
};
emit_hatch(
ops,
+19 -5
View File
@@ -1703,6 +1703,14 @@ impl Batches {
);
}
}
// Glyph quads stay at neutral depth on purpose: the per-wire
// depth_override (b.local_depth) is composed with the insert's
// scene-graph level at upload time (wire_draw_depth:
// depths[insert] + override * half), the same place the
// wipeout/hatch fills get theirs. Baking the raw child rank
// into the vertices here would double-count it in per-block
// label units — for a negative rank that sinks the text below
// its own block's wipes instead of ahead of them.
if !b.pattern_stations.is_empty() {
b.pattern_stations = encode_pattern_stations(
std::mem::take(&mut b.pattern_stations),
@@ -2571,11 +2579,17 @@ fn emit_wire(
.map(|marker| transformed_point_marker(marker, accum_xform));
let point_marker = marker_transform.map(|(marker, _)| marker);
// Only band wires take a per-child composed depth: their solid area is
// what covers siblings, and their width already splits them into their own
// batches — thin wires keep the shared whole-insert depth so same-style
// batches stay merged.
let local_depth = (lw.world_width > 0.0)
// Band wires take a per-child composed depth: their solid area is what
// covers siblings, and their width already splits them into their own
// batches. Text-bearing wires need it too — but as `depth_override`, not
// baked into vertices: the upload resolves it against the insert's
// scene-graph entry (depths[insert] + override * half), which is what
// keeps text interleaved with its sibling wipeout/hatch fills. Without a
// composed rank the text sits at the bare insert level, an exact tie with
// every sibling fill, and the later wipeout pass erases it (unselected
// block text vanished under its wipeout; selecting won only because the
// xray pass ignores depth).
let local_depth = (lw.world_width > 0.0 || !lw.text_verts.is_empty())
.then(|| d_range.0 + lw.local_rank * d_range.1);
let plot_visible = ctx.plot_visible
&& lw.plot_visible
+2 -1
View File
@@ -2204,7 +2204,8 @@ pub struct Scene {
RefCell<HashMap<(String, String), Option<Arc<crate::io::plot_style::PlotStyleTable>>>>,
styled_wire_cache: RefCell<HashMap<(u64, String), (u64, Arc<Vec<WireModel>>)>>,
styled_hatch_cache: RefCell<HashMap<(u64, usize, usize, String, u32), Arc<Vec<HatchModel>>>>,
styled_wire_fill_cache: RefCell<HashMap<(u64, String, u32), Arc<Vec<HatchModel>>>>,
styled_wire_fill_cache:
RefCell<HashMap<(u64, String, u32, u64), Arc<Vec<HatchModel>>>>,
/// Per-viewport projected wire cache for paper-space content viewports.
/// Stores projected + clipped wires in paper-space coordinates.
/// Maps vp_handle → (geometry_epoch, Vec<WireModel>).
+14 -1
View File
@@ -53,6 +53,13 @@ use crate::scene::model::image_model::ImageModel;
use crate::scene::model::mesh_model::MeshLodSet;
use crate::scene::model::wire_model::WireModel;
/// Worst-case raster depth bias (in 24-bit quanta, toward the camera) block
/// text is required to clear. The wipeout pipeline currently carries no bias
/// (a raster bias can jump a block's narrow depth band and erase its
/// foreground), so this is a safety budget: the block-text regression asserts
/// a larger margin over whatever bias the pipeline could reintroduce.
pub const WIPEOUT_DEPTH_BIAS_QUANTA: i32 = 2;
struct SilhouetteChunk {
vertex_buffer: wgpu::Buffer,
instance_buffer: wgpu::Buffer,
@@ -1056,7 +1063,13 @@ impl Pipeline {
depth_compare: Some(wgpu::CompareFunction::LessEqual),
stencil: content_stencil.clone(),
// The shader already applies draw order. A raster bias can
// jump across a block's narrow range and erase its foreground.
// jump across a block's narrow range and erase its foreground
// — block text clears its siblings' wipes by only 9-16 depth
// quanta, so the pipeline must stay bias-free. Coincident ties
// still resolve toward the mask, which draws later under
// LessEqual. WIPEOUT_DEPTH_BIAS_QUANTA is the budget the
// block-text regression asserts against if a bias ever
// returns.
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
+13 -1
View File
@@ -3102,14 +3102,19 @@ impl Scene {
let Some(style) = self.display_plot_style() else {
return None;
};
// Depth generation rides in the key: the fills below compose their
// depth from the live depth map, which can move independently of the
// wire set (a DRAWORDER edit re-ranks without retessellating).
let key = (
source_gen,
style.name.to_ascii_lowercase(),
pattern_scale.to_bits(),
self.draw_depth_generation(),
);
if let Some(hatches) = self.styled_wire_fill_cache.borrow().get(&key) {
return Some(Arc::clone(hatches));
}
let depths = self.draw_depth_map();
let mut hatches = Vec::new();
for wire in wires.iter().filter(|wire| wire.fill_is_2d_solid && wire.aci > 0) {
let Some(pattern) = style
@@ -3161,7 +3166,14 @@ impl Scene {
line_weight_px,
angle_offset: 0.0,
scale: pattern_scale,
draw_depth: wire.depth_override.unwrap_or(0.0),
// Compose against the scene graph exactly like the wire
// pipeline does. The raw depth_override is a per-block
// child label (or None for top-level wires) — either way
// it would place the fill outside its host's depth band
// and let sibling wipes/masks bury it.
draw_depth: crate::scene::pipeline::wire_gpu::wire_draw_depth(
wire, &depths,
),
});
}
}