forked from 1b-agent/opencadstudio-upstream
fix(xref): blocks respect their internal draw order
Xref merge now inserts entities in the source block's chain order and carries SortEntitiesTable objects across (remapped to merged handles). draw_depth_map values become [depth, half] so exploded block/xref fills and wide-polyline band wires both compose child depth as insert_depth + in-block rank x half, recursing into nested inserts with the same divisor - bands and fills from one block interleave by the block's internal order instead of stacking at the insert's single depth. Band batches split per rank via WireModel.depth_override; thin wires stay merged. The wire arena falls back to a full rebuild when a slab carries mixed depths so refreshes don't flatten band overrides. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
1546c1dc30
commit
e177f92ff5
48 files changed
+337
-50
No files matched your search
@@ -1237,6 +1237,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
let guide = crate::scene::WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -1277,6 +1278,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
previews.push(crate::scene::WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -1354,6 +1354,7 @@ fn tessellate_dimension_inner(
|
||||
wires.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -1382,6 +1383,7 @@ fn tessellate_dimension_inner(
|
||||
wires.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -1410,6 +1412,7 @@ fn tessellate_dimension_inner(
|
||||
wires.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -1439,6 +1442,7 @@ fn tessellate_dimension_inner(
|
||||
wires.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -1484,6 +1488,7 @@ fn tessellate_dimension_inner(
|
||||
wires.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -592,6 +592,7 @@ impl LeaderTess for Leader {
|
||||
return WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -700,6 +701,7 @@ impl LeaderTess for Leader {
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -1406,6 +1406,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||
wires.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -1697,6 +1698,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||
wires.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -1766,6 +1768,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||
wires.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -1803,6 +1806,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||
wires.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -755,6 +755,7 @@ pub fn tessellate_table(
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
+74
-7
@@ -5,7 +5,7 @@ use acadrust::entities::{Block, BlockEnd};
|
||||
use acadrust::tables::TableEntry;
|
||||
use acadrust::types::{Handle, Vector3};
|
||||
use acadrust::{CadDocument, EntityType};
|
||||
use rustc_hash::FxHashMap as HashMap;
|
||||
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Status of an external reference block.
|
||||
@@ -234,14 +234,40 @@ fn merge_xref_into_block(
|
||||
|
||||
// ── Entities ────────────────────────────────────────────────────────
|
||||
let xref_ms_handle = xref_doc.header.model_space_block_handle;
|
||||
let entities: Vec<EntityType> = xref_doc
|
||||
.entities()
|
||||
.filter(|e| !matches!(e, EntityType::Block(_) | EntityType::BlockEnd(_)))
|
||||
.cloned()
|
||||
// Merge in each block's DISPLAY order: a block record's entity_handles
|
||||
// chain is the draw order inside that block, while the flat entity list
|
||||
// is stream order — merging in stream order scrambled the xref's own
|
||||
// stacking (fills over outlines etc.), since the host ranks draw order
|
||||
// by the freshly-allocated handles. Entities missing from every chain
|
||||
// keep their stream order at the end.
|
||||
let ordered: Vec<Handle> = {
|
||||
let mut ordered: Vec<Handle> = Vec::new();
|
||||
let mut seen: HashSet<Handle> = HashSet::default();
|
||||
for br in xref_doc.block_records.iter() {
|
||||
for &h in &br.entity_handles {
|
||||
if seen.insert(h) {
|
||||
ordered.push(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
for e in xref_doc.entities() {
|
||||
let h = e.common().handle;
|
||||
if seen.insert(h) {
|
||||
ordered.push(h);
|
||||
}
|
||||
}
|
||||
ordered
|
||||
};
|
||||
let entities: Vec<(Handle, EntityType)> = ordered
|
||||
.into_iter()
|
||||
.filter_map(|h| xref_doc.get_entity(h).map(|e| (h, e.clone())))
|
||||
.filter(|(_, e)| !matches!(e, EntityType::Block(_) | EntityType::BlockEnd(_)))
|
||||
.collect();
|
||||
|
||||
// old xref-doc entity handle → merged host handle, for the sortents copy.
|
||||
let mut entity_handle_map: HashMap<Handle, Handle> = HashMap::default();
|
||||
let mut dropped = 0usize;
|
||||
for mut entity in entities {
|
||||
for (old_h, mut entity) in entities {
|
||||
// Drop parser-garbage entities (bad normals / vertex counts / inf
|
||||
// coords) before they enter the host doc — they trigger huge
|
||||
// allocations and unbounded recursion in the wire pipeline.
|
||||
@@ -285,7 +311,48 @@ fn merge_xref_into_block(
|
||||
entity.common_mut().owner_handle = new_owner;
|
||||
// Clear the foreign handle so acadrust assigns a new one.
|
||||
set_handle(&mut entity, Handle::NULL);
|
||||
let _ = doc.add_entity(entity);
|
||||
if let Ok(new_h) = doc.add_entity(entity) {
|
||||
entity_handle_map.insert(old_h, new_h);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Draw-order overrides ────────────────────────────────────────────
|
||||
// Re-point the xref's SortEntitiesTables at the merged blocks with the
|
||||
// merged entity handles, so an explicit DRAWORDER inside the xref
|
||||
// survives the merge.
|
||||
{
|
||||
use acadrust::objects::{ObjectType, SortEntitiesTable};
|
||||
for obj in xref_doc.objects.values() {
|
||||
let ObjectType::SortEntitiesTable(t) = obj else {
|
||||
continue;
|
||||
};
|
||||
if t.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let new_block = if t.block_owner_handle == xref_ms_handle {
|
||||
br_handle
|
||||
} else if let Some(&h) = br_handle_map.get(&t.block_owner_handle) {
|
||||
h
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let mut nt = SortEntitiesTable::new();
|
||||
nt.handle = doc.allocate_handle();
|
||||
nt.block_owner_handle = new_block;
|
||||
for e in t.entries() {
|
||||
let Some(&ne) = entity_handle_map.get(&e.entity_handle) else {
|
||||
continue;
|
||||
};
|
||||
let ns = entity_handle_map
|
||||
.get(&e.sort_handle)
|
||||
.copied()
|
||||
.unwrap_or(e.sort_handle);
|
||||
nt.add_entry(ne, ns);
|
||||
}
|
||||
if !nt.is_empty() {
|
||||
doc.objects.insert(nt.handle, ObjectType::SortEntitiesTable(nt));
|
||||
}
|
||||
}
|
||||
}
|
||||
dropped
|
||||
}
|
||||
|
||||
@@ -188,6 +188,7 @@ impl CadCommand for AlignedDimensionCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -241,6 +242,7 @@ fn preview_aligned(p1: DVec3, p2: DVec3, dim_pt: DVec3) -> WireModel {
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -132,6 +132,7 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -182,6 +182,7 @@ fn preview_line(a: Vec3, b: Vec3) -> WireModel {
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -181,6 +181,7 @@ impl CadCommand for DimBaselineCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -161,6 +161,7 @@ impl CadCommand for DimContinueCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -87,6 +87,7 @@ impl CadCommand for DimJogLineCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -102,6 +102,7 @@ impl CadCommand for DimTeditCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -165,6 +165,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -220,6 +220,7 @@ fn preview_wire(points: Vec<DVec3>) -> WireModel {
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -152,6 +152,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -460,6 +460,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -123,6 +123,7 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -92,6 +92,7 @@ impl CadCommand for QdimCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -187,6 +187,7 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -141,6 +141,7 @@ impl CadCommand for TableCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -86,6 +86,7 @@ impl CadCommand for ToleranceCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -162,6 +162,7 @@ impl CadCommand for AttdefCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -138,6 +138,7 @@ impl CadCommand for MlineCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -110,6 +110,7 @@ impl CadCommand for ImageCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -86,6 +86,7 @@ impl CadCommand for RayCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -183,6 +184,7 @@ impl CadCommand for XLineCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -91,6 +91,7 @@ impl CadCommand for RevCloudCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -116,6 +116,7 @@ impl CadCommand for WipeoutCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -164,6 +165,7 @@ impl CadCommand for WipeoutCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -71,6 +71,7 @@ impl CadCommand for AreaCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -65,6 +65,7 @@ impl CadCommand for DistCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -69,6 +69,7 @@ impl MeasureGeomCommand {
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -3135,6 +3135,7 @@ fn preview_wire(points: Vec<[f32; 3]>, color: [f32; 4], name: &str) -> WireModel
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -83,6 +83,7 @@ impl CadCommand for MviewCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -307,6 +307,7 @@ fn wire(name: &str, points: Vec<[f32; 3]>) -> WireModel {
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -50,6 +50,7 @@ impl CadCommand for PlotWindowCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -67,6 +67,7 @@ impl CadCommand for ZoomWindowCommand {
|
||||
Some(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
Vendored
+82
-7
@@ -85,6 +85,11 @@ pub struct LocalWire {
|
||||
/// expand-time: transform corners by the Insert transform → world AABB
|
||||
/// → test against the camera's world-space view rect.
|
||||
pub aabb_local: [f32; 4],
|
||||
/// This child's signed draw-order rank in (-1,1) within its owning block
|
||||
/// (from the scene draw-depth map, so SortEntitiesTable overrides apply).
|
||||
/// Composed at expand time into a `depth_override` for wide-polyline
|
||||
/// bands so a band orders against its block siblings.
|
||||
pub local_rank: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -119,6 +124,9 @@ pub struct NestedRef {
|
||||
/// expansion it is mapped to world by the accumulated transform and the
|
||||
/// nested wires are clipped to it.
|
||||
pub clip_poly: Option<Vec<[f64; 2]>>,
|
||||
/// The nested INSERT's own signed draw-order rank in (-1,1) within the
|
||||
/// parent block — narrows the depth sub-range its children may occupy.
|
||||
pub local_rank: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -135,6 +143,11 @@ pub struct BlockDefn {
|
||||
/// the wire renderer is 2D-dominant. Expressed in this defn's *offset*
|
||||
/// Absolute world-space XY (the double-single render path keeps it precise).
|
||||
pub aabb_local: [f32; 4],
|
||||
/// Raw entity count of the source block record (`entity_handles.len()`).
|
||||
/// Divisor for nested depth composition: a nested insert's children get a
|
||||
/// sub-range of `parent_scale / (child_count + 1)` — the same formula the
|
||||
/// exploded-fill path uses, so bands and fills stay on one depth scale.
|
||||
pub child_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
@@ -156,7 +169,15 @@ impl BlockCache {
|
||||
/// included too. The Model_Space / Paper_Space block_records are skipped
|
||||
/// because their entities are emitted as top-level wires, not via the
|
||||
/// cache.
|
||||
pub fn build(doc: &CadDocument, anno_scale: f32, bg_color: [f32; 4]) -> Self {
|
||||
pub fn build(
|
||||
doc: &CadDocument,
|
||||
anno_scale: f32,
|
||||
bg_color: [f32; 4],
|
||||
// Scene draw-depth map ([depth, half] per handle) — source of each
|
||||
// block child's in-block rank, so band depth composition agrees with
|
||||
// the SortEntitiesTable-aware ranking the fill explosion uses.
|
||||
depth_map: &HashMap<u64, [f32; 2]>,
|
||||
) -> Self {
|
||||
use crate::par::prelude::*;
|
||||
let mut cache = Self::new();
|
||||
let referenced = collect_referenced_blocks(doc);
|
||||
@@ -168,7 +189,12 @@ impl BlockCache {
|
||||
// post-pass (it resolves nested references and is comparatively cheap).
|
||||
cache.defns = referenced
|
||||
.par_iter()
|
||||
.map(|name| (name.clone(), Arc::new(build_defn(doc, name, anno_scale, bg_color))))
|
||||
.map(|name| {
|
||||
(
|
||||
name.clone(),
|
||||
Arc::new(build_defn(doc, name, anno_scale, bg_color, depth_map)),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
cache.compute_block_aabbs(&referenced);
|
||||
cache
|
||||
@@ -285,6 +311,7 @@ fn build_defn(
|
||||
block_name: &str,
|
||||
anno_scale: f32,
|
||||
bg_color: [f32; 4],
|
||||
depth_map: &HashMap<u64, [f32; 2]>,
|
||||
) -> BlockDefn {
|
||||
let br = match doc.block_records.get(block_name) {
|
||||
Some(br) => br,
|
||||
@@ -326,14 +353,16 @@ fn build_defn(
|
||||
continue
|
||||
}
|
||||
EntityType::Insert(nested_ins) => {
|
||||
subs.push(LocalSub::Nested(build_nested_ref(nested_ins, doc, bg_color)));
|
||||
subs.push(LocalSub::Nested(build_nested_ref(
|
||||
nested_ins, doc, bg_color, depth_map,
|
||||
)));
|
||||
}
|
||||
_ => {
|
||||
// A wide polyline inside a block carries its `world_width` on
|
||||
// the LocalWire; `emit_wire` scales it by the insert transform
|
||||
// so the shader band matches the scaled geometry (same band the
|
||||
// top-level path draws — depth-tested + linetype-dashed).
|
||||
for lw in tessellate_sub_local(doc, entity, anno_scale, bg_color) {
|
||||
for lw in tessellate_sub_local(doc, entity, anno_scale, bg_color, depth_map) {
|
||||
subs.push(LocalSub::Wire(lw));
|
||||
}
|
||||
}
|
||||
@@ -342,6 +371,7 @@ fn build_defn(
|
||||
BlockDefn {
|
||||
subs,
|
||||
aabb_local: [0.0; 4],
|
||||
child_count: br.entity_handles.len(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,6 +379,7 @@ fn build_nested_ref(
|
||||
nested_ins: &acadrust::entities::Insert,
|
||||
doc: &CadDocument,
|
||||
bg_color: [f32; 4],
|
||||
depth_map: &HashMap<u64, [f32; 2]>,
|
||||
) -> NestedRef {
|
||||
// Store the RAW colour — `adapt_to_bg` runs at emit time
|
||||
// (`Batches::finalize`) so the same cached defn can serve renders
|
||||
@@ -389,6 +420,9 @@ fn build_nested_ref(
|
||||
l0,
|
||||
instance_offsets: array_offsets(nested_ins),
|
||||
clip_poly,
|
||||
local_rank: depth_map
|
||||
.get(&nested_ins.common.handle.value())
|
||||
.map_or(0.0, |d| d[0]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +431,7 @@ fn tessellate_sub_local(
|
||||
sub: &EntityType,
|
||||
anno_scale: f32,
|
||||
bg_color: [f32; 4],
|
||||
depth_map: &HashMap<u64, [f32; 2]>,
|
||||
) -> Vec<LocalWire> {
|
||||
let h = sub.common().handle;
|
||||
|
||||
@@ -505,6 +540,7 @@ fn tessellate_sub_local(
|
||||
lt_l0,
|
||||
lw_l0,
|
||||
aabb_local,
|
||||
local_rank: depth_map.get(&h.value()).map_or(0.0, |d| d[0]),
|
||||
});
|
||||
}
|
||||
result
|
||||
@@ -693,7 +729,7 @@ pub fn expand_insert(
|
||||
is_xref,
|
||||
bg_color,
|
||||
};
|
||||
expand_defn(defn, &base_xform, &ctx, &mut batches, &mut visited, 0);
|
||||
expand_defn(defn, &base_xform, &ctx, &mut batches, &mut visited, 0, (0.0, 1.0));
|
||||
}
|
||||
Some(batches.finalize(&name, selected, bg_color))
|
||||
}
|
||||
@@ -758,6 +794,10 @@ struct StyleKey {
|
||||
/// discriminator, so greek fills must stay in their own batches even
|
||||
/// when their color/style would otherwise collide with regular wires.
|
||||
is_fill_only: bool,
|
||||
/// Bit-cast composed block-local depth for band wires (`0` = no override).
|
||||
/// Keeps bands of different in-block draw ranks in separate batches so
|
||||
/// each finalized WireModel carries one correct `depth_override`.
|
||||
depth_bits: u32,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
@@ -769,6 +809,9 @@ struct BatchEntry {
|
||||
world_width: f32,
|
||||
aci: u8,
|
||||
plinegen: bool,
|
||||
/// Composed block-local draw-order offset for a band batch (see
|
||||
/// `WireModel::depth_override`). `None` for every other batch.
|
||||
local_depth: Option<f32>,
|
||||
points: Vec<[f32; 3]>,
|
||||
points_low: Vec<[f32; 3]>,
|
||||
snap_pts: Vec<(glam::DVec3, SnapHint)>,
|
||||
@@ -866,6 +909,7 @@ impl Batches {
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: b.world_width,
|
||||
depth_override: b.local_depth,
|
||||
fill_is_3d: false,
|
||||
pick_tris: b.pick_tris,
|
||||
pick_tris_low: b.pick_tris_low,
|
||||
@@ -911,6 +955,7 @@ fn style_key(
|
||||
aci: u8,
|
||||
plinegen: bool,
|
||||
is_fill_only: bool,
|
||||
local_depth: Option<f32>,
|
||||
) -> StyleKey {
|
||||
StyleKey {
|
||||
color: [
|
||||
@@ -935,6 +980,7 @@ fn style_key(
|
||||
aci,
|
||||
plinegen,
|
||||
is_fill_only,
|
||||
depth_bits: local_depth.map_or(0, f32::to_bits),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -945,6 +991,10 @@ fn expand_defn(
|
||||
out: &mut Batches,
|
||||
visited: &mut Vec<String>,
|
||||
depth: usize,
|
||||
// Block-local depth sub-range `(base, scale)` accumulated through nested
|
||||
// inserts: a child at rank r lands at `base + r * scale`, all within
|
||||
// (-1,1) of the top-level insert. Seeded `(0.0, 1.0)` by `expand_insert`.
|
||||
d_range: (f32, f32),
|
||||
) {
|
||||
if depth > MAX_NESTING_DEPTH {
|
||||
eprintln!("block_cache: nested-block depth > {MAX_NESTING_DEPTH}, truncating");
|
||||
@@ -977,7 +1027,7 @@ fn expand_defn(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
emit_wire(lw, accum_xform, ctx, out);
|
||||
emit_wire(lw, accum_xform, ctx, out, d_range);
|
||||
}
|
||||
LocalSub::Nested(nref) => {
|
||||
if visited.iter().any(|n| n == &nref.block_name) {
|
||||
@@ -1055,6 +1105,12 @@ fn expand_defn(
|
||||
bg_color: ctx.bg_color,
|
||||
};
|
||||
visited.push(nref.block_name.clone());
|
||||
// Children of this nested insert stack inside the slot its own
|
||||
// rank owns — same composition the exploded-fill path applies.
|
||||
let nested_range = (
|
||||
d_range.0 + nref.local_rank * d_range.1,
|
||||
d_range.1 / (nested_defn.child_count.max(1) as f32 + 1.0),
|
||||
);
|
||||
if let Some(cp) = &nref.clip_poly {
|
||||
// XCLIP'd nested insert: expand into an isolated batch set,
|
||||
// finalize to whole wires, clip them to the boundary mapped
|
||||
@@ -1065,7 +1121,15 @@ fn expand_defn(
|
||||
// instance would differ).
|
||||
let composed = nref.xform.then(accum_xform);
|
||||
let mut sub = Batches::default();
|
||||
expand_defn(nested_defn, &composed, &inner_ctx, &mut sub, visited, depth + 1);
|
||||
expand_defn(
|
||||
nested_defn,
|
||||
&composed,
|
||||
&inner_ctx,
|
||||
&mut sub,
|
||||
visited,
|
||||
depth + 1,
|
||||
nested_range,
|
||||
);
|
||||
let mut wires = sub.finalize("", ctx.selected, ctx.bg_color);
|
||||
let world_poly: Vec<[f64; 2]> = cp
|
||||
.iter()
|
||||
@@ -1093,6 +1157,7 @@ fn expand_defn(
|
||||
out,
|
||||
visited,
|
||||
depth + 1,
|
||||
nested_range,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1129,6 +1194,7 @@ fn emit_wire(
|
||||
accum_xform: &Transform,
|
||||
ctx: &ExpandCtx,
|
||||
out: &mut Batches,
|
||||
d_range: (f32, f32),
|
||||
) {
|
||||
if lw.points.is_empty()
|
||||
&& lw.fill_tris.is_empty()
|
||||
@@ -1173,6 +1239,13 @@ fn emit_wire(
|
||||
0.0
|
||||
};
|
||||
|
||||
// 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)
|
||||
.then(|| d_range.0 + lw.local_rank * d_range.1);
|
||||
|
||||
let key = style_key(
|
||||
final_color,
|
||||
final_pat_len,
|
||||
@@ -1182,6 +1255,7 @@ fn emit_wire(
|
||||
lw.aci,
|
||||
lw.plinegen,
|
||||
lw.is_fill_only,
|
||||
local_depth,
|
||||
);
|
||||
|
||||
// If the open batch for this style would exceed wgpu's per-buffer limit
|
||||
@@ -1205,6 +1279,7 @@ fn emit_wire(
|
||||
lw.is_fill_only,
|
||||
)
|
||||
});
|
||||
entry.local_depth = local_depth;
|
||||
|
||||
// NaN separator between previously-appended geometry and this wire so the
|
||||
// GPU shader treats them as disconnected polylines within one buffer.
|
||||
|
||||
@@ -656,6 +656,7 @@ pub(crate) fn tessellate_entity(
|
||||
return vec![WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris,
|
||||
pick_tris_low,
|
||||
@@ -995,6 +996,7 @@ pub(crate) fn tessellate_entity(
|
||||
let marker = WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -1376,6 +1378,7 @@ fn lod_stub_wire(
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -1464,6 +1467,7 @@ fn lod_stub_wire_3d(
|
||||
WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -372,6 +372,7 @@ pub fn tessellate(
|
||||
elem_wires.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -664,6 +665,7 @@ pub fn tessellate(
|
||||
wires.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -703,6 +705,7 @@ pub fn tessellate(
|
||||
wires.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -749,6 +752,7 @@ pub fn tessellate(
|
||||
wires.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -776,6 +780,7 @@ pub fn tessellate(
|
||||
wires.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -833,6 +838,7 @@ pub fn tessellate(
|
||||
out.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -872,6 +878,7 @@ pub fn tessellate(
|
||||
out.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -908,6 +915,7 @@ pub fn tessellate(
|
||||
out.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -937,6 +945,7 @@ pub fn tessellate(
|
||||
out.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -988,6 +997,7 @@ pub fn tessellate(
|
||||
return vec![WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -1038,6 +1048,7 @@ pub fn tessellate(
|
||||
return vec![WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -1081,6 +1092,7 @@ pub fn tessellate(
|
||||
return vec![WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: polyline_band_width(entity),
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris,
|
||||
pick_tris_low,
|
||||
@@ -1173,6 +1185,7 @@ pub fn tessellate(
|
||||
out.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris,
|
||||
pick_tris_low,
|
||||
@@ -1233,6 +1246,7 @@ pub fn tessellate(
|
||||
fill_tris,
|
||||
fill_tris_low,
|
||||
fill_is_3d,
|
||||
depth_override: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1240,6 +1254,7 @@ pub fn tessellate(
|
||||
out.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -1282,6 +1297,7 @@ pub fn tessellate(
|
||||
return vec![WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: polyline_band_width(entity),
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris,
|
||||
pick_tris_low,
|
||||
@@ -1325,6 +1341,7 @@ pub fn tessellate(
|
||||
return vec![WireModel {
|
||||
taper_widths: widths,
|
||||
world_width,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris,
|
||||
pick_tris_low,
|
||||
@@ -1424,6 +1441,7 @@ pub fn tessellate(
|
||||
vec![WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris,
|
||||
pick_tris_low,
|
||||
|
||||
+44
-4
@@ -754,7 +754,7 @@ impl Scene {
|
||||
if self.selected.contains(&handle) {
|
||||
m.color = [0.15, 0.55, 1.00, m.color[3]];
|
||||
}
|
||||
let d = depth_map.get(&handle.value()).copied().unwrap_or(0.0);
|
||||
let d = depth_map.get(&handle.value()).map_or(0.0, |d| d[0]);
|
||||
m.draw_depth = d;
|
||||
if let Some(b) = &mut backdrop {
|
||||
b.draw_depth = d;
|
||||
@@ -814,6 +814,21 @@ impl Scene {
|
||||
.unwrap_or(false)
|
||||
};
|
||||
let mut models: Vec<HatchModel> = Vec::new();
|
||||
// Draw-order composition (#408): every fill inherits its INSERT's
|
||||
// draw-depth slot, offset by the child's rank INSIDE its block
|
||||
// (draw_depth_map ranks per owning block), scaled to stay within the
|
||||
// insert's half-gap in the host ordering. Without this every block /
|
||||
// xref fill landed at depth 0 and the stack's LIFO pop reversed the
|
||||
// block's internal stacking.
|
||||
let depth_map = self.draw_depth_map();
|
||||
let block_count = |name: &str| -> usize {
|
||||
self.document
|
||||
.block_records
|
||||
.get(name)
|
||||
.map(|br| br.entity_handles.len())
|
||||
.unwrap_or(1)
|
||||
.max(1)
|
||||
};
|
||||
// Exploding an INSERT materializes (clones) every child of its block —
|
||||
// including 3D solids that each carry megabytes of SAB geometry — just
|
||||
// to scan the result for hatch fills. On xref-heavy drawings whose
|
||||
@@ -874,17 +889,22 @@ impl Scene {
|
||||
&self.document,
|
||||
&ins.common.layer,
|
||||
);
|
||||
let [base_depth, half_gap] = depth_map
|
||||
.get(&ins.common.handle.value())
|
||||
.copied()
|
||||
.unwrap_or([0.0, 0.0]);
|
||||
let mut stack: Vec<(
|
||||
EntityType,
|
||||
usize,
|
||||
[f32; 4],
|
||||
crate::scene::view::render::InheritStyle,
|
||||
(f32, f32),
|
||||
)> = ins
|
||||
.explode_from_document(&self.document)
|
||||
.into_iter()
|
||||
.map(|e| (normalize(e), 0usize, ins_color, l0))
|
||||
.map(|e| (normalize(e), 0usize, ins_color, l0, (base_depth, half_gap)))
|
||||
.collect();
|
||||
while let Some((sub, depth, sub_ins_color, sub_l0)) = stack.pop() {
|
||||
while let Some((sub, depth, sub_ins_color, sub_l0, (d_base, d_half))) = stack.pop() {
|
||||
match sub {
|
||||
EntityType::Insert(nins) => {
|
||||
if depth >= 32 {
|
||||
@@ -916,8 +936,22 @@ impl Scene {
|
||||
&nins.common.layer,
|
||||
)
|
||||
};
|
||||
// Nested insert: its slot = parent slot + its rank in
|
||||
// the parent block, subdivided by its own block size.
|
||||
let nd_base = d_base
|
||||
+ depth_map
|
||||
.get(&nins.common.handle.value())
|
||||
.map_or(0.0, |d| d[0])
|
||||
* d_half;
|
||||
let nd_half = d_half / (block_count(&nins.block_name) as f32 + 1.0);
|
||||
for e in nins.explode_from_document(&self.document) {
|
||||
stack.push((normalize(e), depth + 1, child_ins_color, child_l0));
|
||||
stack.push((
|
||||
normalize(e),
|
||||
depth + 1,
|
||||
child_ins_color,
|
||||
child_l0,
|
||||
(nd_base, nd_half),
|
||||
));
|
||||
}
|
||||
}
|
||||
EntityType::Hatch(dxf) => {
|
||||
@@ -942,6 +976,12 @@ impl Scene {
|
||||
if let Some(mut model) =
|
||||
Self::hatch_model_from_dxf(&dxf, color)
|
||||
{
|
||||
// In-block rank → within the insert's depth slot.
|
||||
model.draw_depth = d_base
|
||||
+ depth_map
|
||||
.get(&dxf.common.handle.value())
|
||||
.map_or(0.0, |d| d[0])
|
||||
* d_half;
|
||||
if let Some(poly) = &clip_poly {
|
||||
let clipped = pick::xclip::clip_hatch_boundary(
|
||||
&model.boundary,
|
||||
|
||||
+17
-8
@@ -873,8 +873,13 @@ pub struct Scene {
|
||||
/// effective sort key (SortEntitiesTable override or own handle), then
|
||||
/// fed to the 2D pipelines as a small clip-z bias so entities of
|
||||
/// *different* types order correctly against each other. 3D meshes are
|
||||
/// excluded (they keep real geometric depth).
|
||||
draw_depth_cache: RefCell<Option<(u64, Arc<HashMap<u64, f32>>)>>,
|
||||
/// excluded (they keep real geometric depth). Each value is
|
||||
/// `[depth, half]`: `depth` = the entity's signed rank in (-1,1), `half` =
|
||||
/// half the gap to the neighbouring ranks (`1/(block_count+1)`) — the
|
||||
/// sub-range a block INSERT's children may occupy without crossing the
|
||||
/// insert's siblings. Fill explosion and band wires compose child depths
|
||||
/// as `depth + child_rank * half`.
|
||||
draw_depth_cache: RefCell<Option<(u64, Arc<HashMap<u64, [f32; 2]>>)>>,
|
||||
/// Cached hatch fill models, keyed by geometry_epoch. View culling
|
||||
/// is handled at draw time via `hatch_skip_flags` in the pipeline,
|
||||
/// not at build time — that lets the GPU buffer stay stable across
|
||||
@@ -1295,7 +1300,8 @@ impl Scene {
|
||||
// its internal geometry / text / attributes must NOT be scaled
|
||||
// individually (that would double-scale — AutoCAD even forbids
|
||||
// annotative attributes inside annotative blocks for this reason).
|
||||
let built = cache::block_cache::BlockCache::build(&self.document, 1.0, bg);
|
||||
let built =
|
||||
cache::block_cache::BlockCache::build(&self.document, 1.0, bg, &self.draw_depth_map());
|
||||
let arc = Arc::new(built);
|
||||
*self.block_defn_cache.borrow_mut() = Some((self.block_epoch, Arc::clone(&arc)));
|
||||
arc
|
||||
@@ -3314,7 +3320,7 @@ impl Scene {
|
||||
/// its owning block by effective sort key (SortEntitiesTable override or
|
||||
/// own handle). The result feeds the 2D pipelines as a clip-z bias so
|
||||
/// entities of different types order correctly against each other.
|
||||
pub(super) fn draw_depth_map(&self) -> Arc<HashMap<u64, f32>> {
|
||||
pub(super) fn draw_depth_map(&self) -> Arc<HashMap<u64, [f32; 2]>> {
|
||||
{
|
||||
// Draw order depends on each block's entity COUNT (the rank
|
||||
// denominator) and the entities' handles / SortEntitiesTable — none of
|
||||
@@ -3384,17 +3390,20 @@ impl Scene {
|
||||
.unwrap_or(hv);
|
||||
by_block.entry(block).or_default().push((hv, eff));
|
||||
}
|
||||
let mut depth_map: HashMap<u64, f32> = HashMap::default();
|
||||
let mut depth_map: HashMap<u64, [f32; 2]> = HashMap::default();
|
||||
for (_block, mut v) in by_block {
|
||||
v.sort_by_key(|(_, eff)| *eff);
|
||||
let denom = (v.len() as f32) + 1.0;
|
||||
// Adjacent ranks are 2/denom apart; a child sub-range of
|
||||
// ±half = ±1/denom around the parent depth never crosses them.
|
||||
let half = 1.0 / denom;
|
||||
for (rank, (hv, _)) in v.into_iter().enumerate() {
|
||||
// Signed (-1,1): back ranks → negative, front → positive,
|
||||
// mid → ~0. The shader applies `z -= draw_depth * BIAS`, so a
|
||||
// default/unranked 0.0 means "no bias" (neutral) — which keeps
|
||||
// 3D mesh faces and transient wires at their real depth.
|
||||
let norm = (rank as f32 + 1.0) / denom; // (0,1)
|
||||
depth_map.insert(hv, (norm - 0.5) * 2.0);
|
||||
depth_map.insert(hv, [(norm - 0.5) * 2.0, half]);
|
||||
}
|
||||
}
|
||||
let arc = Arc::new(depth_map);
|
||||
@@ -3527,7 +3536,7 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
let mut m = model.clone();
|
||||
m.draw_depth = depth_map.get(&handle.value()).copied().unwrap_or(0.0);
|
||||
m.draw_depth = depth_map.get(&handle.value()).map_or(0.0, |d| d[0]);
|
||||
Some(m)
|
||||
})
|
||||
.collect()
|
||||
@@ -3551,7 +3560,7 @@ impl Scene {
|
||||
return None;
|
||||
}
|
||||
let mut m = model.clone();
|
||||
m.draw_depth = depth_map.get(&handle.value()).copied().unwrap_or(0.0);
|
||||
m.draw_depth = depth_map.get(&handle.value()).map_or(0.0, |d| d[0]);
|
||||
Some(m)
|
||||
})
|
||||
.collect(),
|
||||
|
||||
@@ -130,6 +130,14 @@ pub struct WireModel {
|
||||
/// exactly like `points` — no separate collector pass. The renderer
|
||||
/// gathers these across all wires into the text vertex buffer.
|
||||
pub text_verts: Vec<crate::scene::pipeline::text_gpu::TextVertex>,
|
||||
/// Block-local composed draw-order offset in (-1, 1) for a wire that must
|
||||
/// order against its *siblings inside the block* — currently wide-polyline
|
||||
/// bands, whose solid area would otherwise cover later-drawn siblings.
|
||||
/// `None` (all other wires) = the whole-insert depth resolved from `name`.
|
||||
/// `Some(local)` composes at draw time: `insert_depth + local * insert_half`,
|
||||
/// mirroring how exploded block fills seed their depth, so bands and fills
|
||||
/// from the same block interleave by the block's internal draw order.
|
||||
pub depth_override: Option<f32>,
|
||||
/// `true` when [`fill_tris`] is a real 3-D surface (PolyfaceMesh /
|
||||
/// PolygonMesh face) that must render with hidden-surface depth and only in
|
||||
/// shaded modes. `false` for a flat 2-D overlay fill (SOLID arrowhead,
|
||||
@@ -186,6 +194,7 @@ impl WireModel {
|
||||
Self {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -419,6 +428,7 @@ impl Default for WireModel {
|
||||
dash_align_end: None,
|
||||
fill_tris: Vec::new(),
|
||||
fill_tris_low: Vec::new(),
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -114,15 +114,10 @@ impl Face3DGpu {
|
||||
face3d_wires: &[WireModel],
|
||||
all_wires: &[WireModel],
|
||||
keep_3d_mesh_fills: bool,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, f32>,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
) -> Self {
|
||||
let depth_of = |w: &WireModel| -> f32 {
|
||||
w.name
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.and_then(|h| depth_map.get(&h).copied())
|
||||
.unwrap_or(0.0)
|
||||
};
|
||||
let depth_of =
|
||||
|w: &WireModel| -> f32 { super::wire_gpu::wire_draw_depth(w, depth_map) };
|
||||
let mut verts_3d: Vec<Face3DVertex> = Vec::with_capacity(face3d_wires.len() * 6);
|
||||
let mut verts_2d: Vec<Face3DVertex> = Vec::new();
|
||||
|
||||
|
||||
@@ -1477,7 +1477,7 @@ impl Pipeline {
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
wires: &[WireModel],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, f32>,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
) -> (
|
||||
std::sync::Arc<Vec<WireGpu>>,
|
||||
std::sync::Arc<rustc_hash::FxHashMap<u64, Vec<u32>>>,
|
||||
@@ -1543,7 +1543,7 @@ impl Pipeline {
|
||||
wires: &[WireModel],
|
||||
selected: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||
hover: Option<acadrust::Handle>,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, f32>,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
) {
|
||||
let hover = hover.filter(|h| !selected.contains(h));
|
||||
if selected.is_empty() && hover.is_none() {
|
||||
@@ -1627,7 +1627,7 @@ impl Pipeline {
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
wires: &[WireModel],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, f32>,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
) {
|
||||
self.gpu_preview_wires = if wires.is_empty() {
|
||||
vec![]
|
||||
@@ -1912,7 +1912,7 @@ impl Pipeline {
|
||||
face3d_wires: &[WireModel],
|
||||
all_wires: &[WireModel],
|
||||
wireframe_only: bool,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, f32>,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
) {
|
||||
// Edge buffer is always built from `face3d_wires`, so 3DFACE
|
||||
// outlines stay on the screen regardless of mode.
|
||||
|
||||
@@ -105,7 +105,7 @@ pub fn is_mesh_edge(w: &WireModel, mesh_names: &rustc_hash::FxHashSet<u64>) -> b
|
||||
/// Surface) are excluded from `draw_depth_map`, so their fallback edge wires
|
||||
/// get draw_depth 0.0. Two coincident opaque such wires share a z-bias and
|
||||
/// resolve by submission order, which a tail relocation would flip.
|
||||
fn append_unsafe(wires: &[&WireModel], depth_map: &FxHashMap<u64, f32>) -> bool {
|
||||
fn append_unsafe(wires: &[&WireModel], depth_map: &FxHashMap<u64, [f32; 2]>) -> bool {
|
||||
wires.iter().any(|w| {
|
||||
w.color[3] < 0.999
|
||||
|| handle_of(w).map_or(true, |h| !depth_map.contains_key(&h.value()))
|
||||
@@ -202,7 +202,7 @@ impl WireArena {
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
wires: &[&WireModel],
|
||||
depth_map: &FxHashMap<u64, f32>,
|
||||
depth_map: &FxHashMap<u64, [f32; 2]>,
|
||||
const_bgl: &wgpu::BindGroupLayout,
|
||||
mesh_edge: bool,
|
||||
) -> Option<Self> {
|
||||
@@ -288,7 +288,7 @@ impl WireArena {
|
||||
queue: &wgpu::Queue,
|
||||
changes: &[(Handle, ChangeKind)],
|
||||
wires: &[&WireModel],
|
||||
depth_map: &FxHashMap<u64, f32>,
|
||||
depth_map: &FxHashMap<u64, [f32; 2]>,
|
||||
) -> bool {
|
||||
let Some(ranges) = handle_ranges(wires) else {
|
||||
return false;
|
||||
@@ -405,13 +405,27 @@ impl WireArena {
|
||||
// entity's z-bias. Refresh each live slab's draw_depth from the new
|
||||
// depth map and re-upload the whole (small) const buffer; the instance
|
||||
// buffer is untouched.
|
||||
// A slab whose consts carry DIFFERENT depths holds block-band wires
|
||||
// with composed per-child offsets (`depth_override`); a flat refresh
|
||||
// to the entity's base depth would erase them. Fall back to a full
|
||||
// rebuild, which recomputes each wire's composed depth.
|
||||
if !self.mesh_edge {
|
||||
for slab in self.slabs.values() {
|
||||
let first = self.consts_cpu[slab.const_off as usize].draw_depth;
|
||||
for k in 1..slab.const_len {
|
||||
if self.consts_cpu[(slab.const_off + k) as usize].draw_depth != first {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (h, slab) in &self.slabs {
|
||||
// Mesh-edge wires keep depth 0 (no draw-order bias); regular wires
|
||||
// take the re-normalised map value.
|
||||
let dd = if self.mesh_edge {
|
||||
0.0
|
||||
} else {
|
||||
depth_map.get(&h.value()).copied().unwrap_or(0.0)
|
||||
depth_map.get(&h.value()).map_or(0.0, |d| d[0])
|
||||
};
|
||||
for k in 0..slab.const_len {
|
||||
self.consts_cpu[(slab.const_off + k) as usize].draw_depth = dd;
|
||||
|
||||
@@ -488,17 +488,23 @@ pub(crate) fn emit_wire_native(
|
||||
|
||||
/// Looks up a wire's draw-order depth from the per-entity map using the
|
||||
/// handle encoded in its `name`. Falls back to 0.0 (transient / preview
|
||||
/// wires that carry no document handle).
|
||||
/// wires that carry no document handle). A wire carrying a block-local
|
||||
/// `depth_override` (a wide-polyline band inside a block) composes it into
|
||||
/// the insert's own sub-range so the band orders against its block siblings.
|
||||
pub(crate) fn wire_draw_depth(
|
||||
wire: &WireModel,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, f32>,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
) -> f32 {
|
||||
wire
|
||||
let base = wire
|
||||
.name
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.and_then(|h| depth_map.get(&h).copied())
|
||||
.unwrap_or(0.0)
|
||||
.and_then(|h| depth_map.get(&h).copied());
|
||||
match (base, wire.depth_override) {
|
||||
(Some([d, half]), Some(local)) => d + local * half,
|
||||
(Some([d, _]), None) => d,
|
||||
(None, _) => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the shared per-wire `WireConst` storage buffer and its bind group
|
||||
@@ -546,7 +552,7 @@ impl WireGpu {
|
||||
pub fn from_run(
|
||||
device: &wgpu::Device,
|
||||
wires: &[WireModel],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, f32>,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
mesh_edge: bool,
|
||||
const_bgl: Option<&wgpu::BindGroupLayout>,
|
||||
) -> Vec<Self> {
|
||||
@@ -632,7 +638,7 @@ impl WireGpu {
|
||||
pub fn from_batch(
|
||||
device: &wgpu::Device,
|
||||
wires: &[WireModel],
|
||||
depth_map: &rustc_hash::FxHashMap<u64, f32>,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
const_bgl: Option<&wgpu::BindGroupLayout>,
|
||||
) -> Vec<Self> {
|
||||
let total_segs: usize = wires.iter().map(|w| w.points.len().saturating_sub(1)).sum();
|
||||
|
||||
@@ -307,6 +307,7 @@ pub fn apply_along(
|
||||
.map(|pts| WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
@@ -348,6 +349,7 @@ pub fn apply_along(
|
||||
out.push(WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
|
||||
@@ -60,7 +60,7 @@ pub struct ViewportData {
|
||||
/// by the wire / face3d pipelines as a clip-z bias. WireModels carry no
|
||||
/// depth field (84 construction sites); the bias is looked up by handle
|
||||
/// at GPU-upload time from this map instead.
|
||||
pub(in crate::scene) draw_depths: Arc<rustc_hash::FxHashMap<u64, f32>>,
|
||||
pub(in crate::scene) draw_depths: Arc<rustc_hash::FxHashMap<u64, [f32; 2]>>,
|
||||
pub(in crate::scene) hatches: Arc<Vec<HatchModel>>,
|
||||
/// Wipeout fills — rendered in a separate pass AFTER wires.
|
||||
pub(in crate::scene) wipeout_hatches: Arc<Vec<HatchModel>>,
|
||||
|
||||
Reference in new issue
Block a user