refactor(render): unify scene rendering paths

Route model, paper, viewport and nested block content through one traversal while preserving leaf renderers. Carry decoded material state into mesh shading and harden solid geometry conversion.
This commit is contained in:
Hakan Seven 2026-08-05 21:44:59 +03:00
commit b667eeeb05
17 changed files with 2218 additions and 1871 deletions

View file

@ -1309,7 +1309,8 @@ use acadrust::{CadDocument, EntityType, Handle};
use crate::scene::convert::tess_util::aci_to_rgba;
use crate::scene::convert::tessellate::{
add_polyline, add_segment, append_arrow, arrow_from_block, normalized_or, ArrowKind, DimGeom,
add_polyline, add_segment, append_arrow, arrow_from_block_with_deferred_hatch,
normalized_or, ArrowKind, DimGeom,
};
use crate::scene::model::wire_model::{SnapHint, WireModel};
@ -1660,6 +1661,16 @@ fn tessellate_dimension_inner(
// 3. DIMSAH true → DIMBLK1 (first end), DIMBLK2 (second end).
// Unknown / NULL block handles fall back to ClosedFilled.
let dimasz = (dimasz_raw as f32).max(0.001);
let defer_arrow_hatches = {
let name = dim.base().block_name.trim();
let mut memo = std::collections::HashMap::new();
!name.is_empty()
&& crate::scene::render_graph::block_contains_hatch(
document,
name,
&mut memo,
)
};
let (arrow1, arrow2) = if dimtsz_raw > 1e-9 {
let t = ArrowKind::Tick {
size: (dimtsz_raw as f32).max(0.001),
@ -1668,11 +1679,26 @@ fn tessellate_dimension_inner(
} else if let Some(s) = style {
if dimsah {
(
arrow_from_block(document, s.dimblk1, dimasz),
arrow_from_block(document, s.dimblk2, dimasz),
arrow_from_block_with_deferred_hatch(
document,
s.dimblk1,
dimasz,
defer_arrow_hatches,
),
arrow_from_block_with_deferred_hatch(
document,
s.dimblk2,
dimasz,
defer_arrow_hatches,
),
)
} else {
let a = arrow_from_block(document, s.dimblk, dimasz);
let a = arrow_from_block_with_deferred_hatch(
document,
s.dimblk,
dimasz,
defer_arrow_hatches,
);
(a.clone(), a)
}
} else {

View file

@ -1538,9 +1538,9 @@ impl MultiLeaderTess for MultiLeader {
// ── Block content ───────────────────────────────────────────────────────
// When content_type == Block, the MultiLeader displays a block reference
// at block_content_location with the recorded rotation/scale. Synthesize
// an Insert and explode it through the standard tessellator. The block
// resolves via block_content_handle (handle → block_record name).
// at block_content_location with the recorded rotation/scale. A
// synthetic Insert supplies that context to the shared scene graph;
// every child then uses its normal tessellator.
if ml.content_type == LeaderContentType::Block && ml.context.has_block_contents {
let block_name = match ml.block_content_handle {
Some(h) if !h.is_null() => document
@ -1567,29 +1567,42 @@ impl MultiLeaderTess for MultiLeader {
// anchor when *creating* the multileader; at render time the
// file's stored leader endpoints already encode that choice.
let _ = ml.block_connection_type;
for sub in synth_ins.explode_from_document(document) {
let normalized =
crate::modules::draw::modify::explode::normalize_insert_entity(sub);
let mut sub_wires = tessellate(
document,
handle,
&normalized,
selected,
block_color,
leader_pat_len,
leader_pat,
leader_lw_px,
1.0,
None,
None,
bg_color,
false,
);
for w in &mut sub_wires {
w.name = name.clone();
}
wires.extend(sub_wires);
}
let depths = rustc_hash::FxHashMap::default();
let graph = crate::scene::render_graph::RenderSceneGraph::new(
document,
None,
None,
true,
&depths,
);
graph.walk_insert(
&synth_ins,
handle,
|_, _| true,
|entity, context| {
let mut placed = entity.clone();
placed.apply_transform(&context.transform);
let mut sub_wires = tessellate(
document,
handle,
&placed,
selected,
block_color,
leader_pat_len,
leader_pat,
leader_lw_px,
1.0,
None,
None,
bg_color,
false,
);
for w in &mut sub_wires {
w.name = name.clone();
}
wires.extend(sub_wires);
},
);
// Block attributes attached to the multileader — render each as
// its own attribute entity at WCS location like INSERT does.
for ba in &ml.block_attributes {