Merge pull request #1738 from gianlucafiore/fix/block-wipeout-hatch-order

This commit is contained in:
Hakan Seven committed 2026-10-13 07:32:50 +03:00
commit 5581bbeedb
4 files changed
+47 -33

No files matched your search

+21 -3
View File
@@ -1038,7 +1038,20 @@ pub fn expand_insert(
scale_policy,
);
let name = ins_handle.value().to_string();
let prototype_key = if !ins.is_array() && cache.prototype_blocks.contains(&ins.block_name) {
// Mask-bearing blocks must not reuse translated GPU prototypes: ordinary
// strokes and wipeout fills have different upload paths. Their shared
// source_id plus per-copy translated geometry can make a moved INSERT
// display stale wipeout geometry or disappear as the view changes.
// Expand these from their definition until both GPU paths share a
// verified identical instancing convention.
let has_masks = defn.subs.iter().any(|sub| match sub {
LocalSub::Wire(wire) => wire.canvas_color,
LocalSub::Nested(_) => false,
});
let prototype_key = if !ins.is_array()
&& !has_masks
&& cache.prototype_blocks.contains(&ins.block_name)
{
Some(expansion_prototype_key(
ins,
&xform,
@@ -2618,8 +2631,13 @@ fn emit_wire(
// MTEXT background (a dimension's DIMTFILL box) left at the bare insert
// level sat in front of its own text whenever that text's rank was
// below zero.
let local_depth = (lw.world_width > 0.0 || lw.is_fill_only || !lw.text_verts.is_empty())
.then(|| d_range.0 + lw.local_rank * d_range.1);
// Every block member must retain its block-local draw rank, including
// ordinary thin LINE/LWPOLYLINE wires. Previously these wires had no
// depth_override, so they were drawn at the INSERT's bare depth while
// wipeout/hatch fills were positioned at the member rank. COPY and grip
// moves rebuild instance batches and exposed this mismatch in model,
// paper-space and PDF output.
let local_depth = Some(d_range.0 + lw.local_rank * d_range.1);
let plot_visible = ctx.plot_visible && lw.plot_visible && (!lw.plot_l0 || ctx.l0_plottable);
// Batched wires share one dash distance, which runs on across the NaN
+8 -4
View File
@@ -7932,15 +7932,19 @@ impl Scene {
let cache = self.wipeout_cache.borrow();
match cache.get(&key) {
Some((cached_epoch, arc))
// wipeout_models scans the whole document for Wipeout
// entities; relevance = the changed handle is a Wipeout.
// The displayed wipeout set also contains masks
// expanded from INSERTs. COPY and grip edits modify or
// add INSERT handles without changing the child WIPEOUT
// entity, so the old predicate reused stale positions
// until REGEN. Track both categories, including
// removed INSERTs via the recorded removal bits.
if self.category_cache_valid(
*cached_epoch,
CACHE_CATEGORY_WIPEOUT,
CACHE_CATEGORY_WIPEOUT | CACHE_CATEGORY_INSERT_HATCH,
|h| {
matches!(
self.document.get_entity(h),
Some(EntityType::Wipeout(_))
Some(EntityType::Wipeout(_) | EntityType::Insert(_))
)
},
) =>
+10 -17
View File
@@ -172,23 +172,16 @@ impl WipeoutGpu {
models: &[HatchModel],
bgl1: &wgpu::BindGroupLayout,
) -> Vec<Self> {
let mut slots = rustc_hash::FxHashMap::default();
let mut groups: Vec<Vec<&HatchModel>> = Vec::new();
for (index, model) in models.iter().enumerate() {
let key = model
.render_instance
.map(|instance| (true, instance.source_id))
.unwrap_or((false, index as u64));
let slot = *slots.entry(key).or_insert_with(|| {
let slot = groups.len();
groups.push(Vec::new());
slot
});
groups[slot].push(model);
}
groups
.into_iter()
.map(|group| Self::new(device, queue, &group, bgl1))
// A source_id only identifies reusable render geometry, not an
// interchangeable hatch boundary in world space. Grouping masks by
// source_id can incorrectly reuse the first model's boundary, origin,
// plane and uniform for later INSERT copies. Unlike wires, a mask's
// GPU shader clips against its own boundary, so build each mask from
// its actual transformed model until we have a geometry-local,
// translation-safe instancing path.
models
.iter()
.map(|model| Self::new(device, queue, &[model], bgl1))
.collect()
}
+8 -9
View File
@@ -1,13 +1,12 @@
// Positive order draws in front; zero retains geometric depth. Keep the usual
// 0.001 NDC offset away from the clipping planes, but reduce it near either
// plane: perspective depth can be much closer to 1 than this fixed offset.
//
// Using order / (1 + abs(order)) for the limited region keeps ordering monotonic
// (including the paper background's -2). Less than half the remaining depth is
// consumed, so valid vertices stay valid. Out-of-range/behind-eye vertices get
// no offset and remain subject to the real near/far clipping planes.
// Positive draw order moves geometry towards the camera. In dense blocks,
// child ranks occupy a narrow fraction of the INSERT depth band: a 0.001
// NDC bias can quantize two overlapping lines/masks to the same depth value,
// especially after changing view/zoom. Use a wider, yet bounded range for
// CAD 2D draw order. Limit displacement to less than half the available
// clip-space room, so we never push valid geometry across near/far planes.
// This is a screen-only GPU change; PDF order uses its own stable sorting.
fn apply_draw_order(clip: vec4<f32>, order: f32) -> vec4<f32> {
let room = 0.5 * max(0.0, min(clip.z, clip.w - clip.z));
let scale = min(0.001 * max(clip.w, 0.0), room / (1.0 + abs(order)));
let scale = min(0.02 * max(clip.w, 0.0), room / (1.0 + abs(order)));
return vec4<f32>(clip.xy, clip.z - order * scale, clip.w);
}