fix(dimension): render baked *D fills inside block dimensions
A dimension bakes its geometry into a per-instance *D block, offset into the owning block by the dimension insertion_point. When that dimension sits inside a block (INSERT → block → Dimension → *D), only its top-level form drew; nested it rendered nothing, and once the lines/text/arrow outlines were routed through the block cache the arrowhead FILLS were still missing: - 2D SOLID arrowheads: block_cache's Dimension arm now expands the *D block, translating each sub by insertion_point (WCS → block-local) and routing Insert subs through build_nested_ref, others through tessellate_sub_local (which emits the solid fill_tris). - Insert(hatch) arrowheads (custom arrow blocks = LwPolyline + Hatch): the hatch renders via the separate GPU explosion path, which only descends INSERT→INSERT→Hatch trees and so never reached a hatch behind a Dimension. block_has_hatch now recurses through a Dimension into its *D block, and the fill explosion's new explode_including_dims lifts each dimension child's *D content into the owning block's frame (translate insertion_point, then apply the insert transform) so its arrow inserts / hatches ride the normal walk. Table *T baked blocks get the mirror treatment in block_cache. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1803f10264
commit
3689f0e504
3 changed files with 160 additions and 3 deletions
104
src/scene/cache/block_cache.rs
vendored
104
src/scene/cache/block_cache.rs
vendored
|
|
@ -362,6 +362,110 @@ fn build_defn(
|
||||||
nested_ins, doc, bg_color, depth_map,
|
nested_ins, doc, bg_color, depth_map,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
// A dimension nested in a block bakes its geometry (extension /
|
||||||
|
// dim lines, arrows, text) into a per-instance `*D` block, exactly
|
||||||
|
// like a top-level dimension. The top-level path expands that block
|
||||||
|
// in `tessellate_entity`; the block-expand path calls the plain
|
||||||
|
// `tessellate::tessellate`, which does NOT, so nested dimensions
|
||||||
|
// drew nothing. Expand the `*D` block's entities here as block-local
|
||||||
|
// subs so they transform with the parent insert. (Empty block_name
|
||||||
|
// — a non-baked dimension — falls through to the default arm.)
|
||||||
|
EntityType::Dimension(dim) if !dim.base().block_name.trim().is_empty() => {
|
||||||
|
let dblk = doc
|
||||||
|
.block_records
|
||||||
|
.iter()
|
||||||
|
.find(|br| br.name.eq_ignore_ascii_case(&dim.base().block_name));
|
||||||
|
if let Some(dblk) = dblk {
|
||||||
|
// The `*D` block content is baked in the coordinate space it
|
||||||
|
// occupied when the dimension was created — for a dimension
|
||||||
|
// inside a block that is often the ORIGINAL WCS, not the
|
||||||
|
// block-local space. The dimension's insertion_point (DXF
|
||||||
|
// 12) is the offset that maps the baked content into the
|
||||||
|
// dimension's own (block-local) space; it is zero for
|
||||||
|
// dimensions baked in place, so this is a no-op there.
|
||||||
|
let ins = dim.base().insertion_point;
|
||||||
|
for &deh in &dblk.entity_handles {
|
||||||
|
let Some(dsub) = doc.get_entity(deh) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
// Definition points are baked as POINTs on Defpoints —
|
||||||
|
// grip markers, never drawn (matches the top-level path).
|
||||||
|
if matches!(dsub, EntityType::Point(_)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if dsub.common().invisible || layer_hidden(doc, &dsub.common().layer) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut placed = dsub.clone();
|
||||||
|
placed.as_entity_mut().translate(ins);
|
||||||
|
// An arrowhead is baked as a nested INSERT of an arrow
|
||||||
|
// block — route it through the nested-ref machinery so
|
||||||
|
// the arrow block expands (tessellate_sub_local doesn't
|
||||||
|
// expand inserts).
|
||||||
|
if let EntityType::Insert(arrow) = &placed {
|
||||||
|
subs.push(LocalSub::Nested(build_nested_ref(
|
||||||
|
arrow, doc, bg_color, depth_map,
|
||||||
|
)));
|
||||||
|
} else {
|
||||||
|
for lw in tessellate_sub_local(
|
||||||
|
doc, &placed, anno_scale, bg_color, depth_map,
|
||||||
|
) {
|
||||||
|
subs.push(LocalSub::Wire(lw));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A table nested in a block bakes its geometry (gridlines, cell
|
||||||
|
// text, fills) into a `*T` block, laid out in block-LOCAL space with
|
||||||
|
// the table carrying the world placement in insertion_point +
|
||||||
|
// horizontal_direction (like an INSERT). The top-level path expands
|
||||||
|
// and places it in `tessellate_entity`; the block-expand path skips
|
||||||
|
// that, so a nested table drew nothing. Place each `*T` sub by the
|
||||||
|
// table's rotation+insertion here (defn-local), then tessellate — the
|
||||||
|
// parent insert transform composes on top at expand time.
|
||||||
|
EntityType::Table(tab) => {
|
||||||
|
let tblk = tab
|
||||||
|
.block_record_handle
|
||||||
|
.and_then(|h| doc.block_records.iter().find(|br| br.handle == h));
|
||||||
|
if let Some(tblk) = tblk {
|
||||||
|
let ins = tab.insertion_point;
|
||||||
|
let angle = tab.horizontal_direction.y.atan2(tab.horizontal_direction.x);
|
||||||
|
for &teh in &tblk.entity_handles {
|
||||||
|
let Some(tsub) = doc.get_entity(teh) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if tsub.common().invisible || layer_hidden(doc, &tsub.common().layer) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut placed = tsub.clone();
|
||||||
|
{
|
||||||
|
let ent = placed.as_entity_mut();
|
||||||
|
if angle.abs() > 1e-9 {
|
||||||
|
ent.apply_rotation(
|
||||||
|
acadrust::types::Vector3::new(0.0, 0.0, 1.0),
|
||||||
|
angle,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ent.translate(ins);
|
||||||
|
}
|
||||||
|
// A baked table can nest inserts too (block cells);
|
||||||
|
// route those through the nested-ref path like the
|
||||||
|
// dimension arrows above.
|
||||||
|
if let EntityType::Insert(cell_ins) = &placed {
|
||||||
|
subs.push(LocalSub::Nested(build_nested_ref(
|
||||||
|
cell_ins, doc, bg_color, depth_map,
|
||||||
|
)));
|
||||||
|
} else {
|
||||||
|
for lw in
|
||||||
|
tessellate_sub_local(doc, &placed, anno_scale, bg_color, depth_map)
|
||||||
|
{
|
||||||
|
subs.push(LocalSub::Wire(lw));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// A wide polyline inside a block carries its `world_width` on
|
// A wide polyline inside a block carries its `world_width` on
|
||||||
// the LocalWire; `emit_wire` scales it by the insert transform
|
// the LocalWire; `emit_wire` scales it by the insert transform
|
||||||
|
|
|
||||||
|
|
@ -847,6 +847,52 @@ impl Scene {
|
||||||
// at world position, like a block-internal hatch. (#222)
|
// at world position, like a block-internal hatch. (#222)
|
||||||
let mut wide_block_memo: std::collections::HashMap<String, bool> =
|
let mut wide_block_memo: std::collections::HashMap<String, bool> =
|
||||||
std::collections::HashMap::new();
|
std::collections::HashMap::new();
|
||||||
|
// `explode_from_document` returns a Dimension child verbatim (it carries
|
||||||
|
// no fill of its own), so a filled arrowhead that lives as a nested
|
||||||
|
// Insert(hatch) inside the dimension's baked `*D` block never reaches
|
||||||
|
// this fill explosion. Expand each dimension child's `*D` content into
|
||||||
|
// the owning block's frame — translate by the dimension insertion_point
|
||||||
|
// (WCS → block-local) then apply the insert transform (block-local →
|
||||||
|
// world) — so its arrow inserts / hatches ride the normal walk. Only
|
||||||
|
// fill-bearing children (Insert / Hatch) are lifted; the dimension's
|
||||||
|
// lines and text still render through the wire path.
|
||||||
|
fn explode_including_dims(ins: &DxfInsert, doc: &CadDocument) -> Vec<EntityType> {
|
||||||
|
let mut out = ins.explode_from_document(doc);
|
||||||
|
let Some(br) = doc.block_records.get(&ins.block_name) else {
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
let xform = ins.get_transform();
|
||||||
|
for &beh in &br.entity_handles {
|
||||||
|
let Some(EntityType::Dimension(dim)) = doc.get_entity(beh) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let bn = dim.base().block_name.clone();
|
||||||
|
if bn.trim().is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let ins_pt = dim.base().insertion_point;
|
||||||
|
let Some(dblk) = doc
|
||||||
|
.block_records
|
||||||
|
.iter()
|
||||||
|
.find(|r| r.name.eq_ignore_ascii_case(&bn))
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
for &deh in &dblk.entity_handles {
|
||||||
|
let Some(dsub) = doc.get_entity(deh) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if !matches!(dsub, EntityType::Insert(_) | EntityType::Hatch(_)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut placed = dsub.clone();
|
||||||
|
placed.as_entity_mut().translate(ins_pt);
|
||||||
|
placed.apply_transform(&xform);
|
||||||
|
out.push(placed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
for entity in self.document.entities() {
|
for entity in self.document.entities() {
|
||||||
let EntityType::Insert(ins) = entity else {
|
let EntityType::Insert(ins) = entity else {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -902,8 +948,7 @@ impl Scene {
|
||||||
[f32; 4],
|
[f32; 4],
|
||||||
crate::scene::view::render::InheritStyle,
|
crate::scene::view::render::InheritStyle,
|
||||||
(f32, f32),
|
(f32, f32),
|
||||||
)> = ins
|
)> = explode_including_dims(ins, &self.document)
|
||||||
.explode_from_document(&self.document)
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|e| (normalize(e), 0usize, ins_color, l0, (base_depth, half_gap)))
|
.map(|e| (normalize(e), 0usize, ins_color, l0, (base_depth, half_gap)))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
@ -947,7 +992,7 @@ impl Scene {
|
||||||
.map_or(0.0, |d| d[0])
|
.map_or(0.0, |d| d[0])
|
||||||
* d_half;
|
* d_half;
|
||||||
let nd_half = d_half / (block_count(&nins.block_name) as f32 + 1.0);
|
let nd_half = d_half / (block_count(&nins.block_name) as f32 + 1.0);
|
||||||
for e in nins.explode_from_document(&self.document) {
|
for e in explode_including_dims(&nins, &self.document) {
|
||||||
stack.push((
|
stack.push((
|
||||||
normalize(e),
|
normalize(e),
|
||||||
depth + 1,
|
depth + 1,
|
||||||
|
|
|
||||||
|
|
@ -4092,6 +4092,14 @@ impl Scene {
|
||||||
br.entity_handles.iter().any(|&h| match self.document.get_entity(h) {
|
br.entity_handles.iter().any(|&h| match self.document.get_entity(h) {
|
||||||
Some(EntityType::Hatch(_)) => true,
|
Some(EntityType::Hatch(_)) => true,
|
||||||
Some(EntityType::Insert(ins)) => self.block_has_hatch(&ins.block_name, memo),
|
Some(EntityType::Insert(ins)) => self.block_has_hatch(&ins.block_name, memo),
|
||||||
|
// A dimension bakes its geometry into a per-instance `*D`
|
||||||
|
// block; a custom filled arrowhead lives there as a nested
|
||||||
|
// Insert(hatch). Recurse so the fill explosion knows to
|
||||||
|
// descend the dimension (see `explode_including_dims`).
|
||||||
|
Some(EntityType::Dimension(dim)) => {
|
||||||
|
!dim.base().block_name.trim().is_empty()
|
||||||
|
&& self.block_has_hatch(&dim.base().block_name, memo)
|
||||||
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue