From e0ec7f19995ea2080002332d1537b1de69b8db5a Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Tue, 19 May 2026 21:04:40 +0300 Subject: [PATCH] fix(scene): render INSERT attribute values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inserting a block with attributes (ATTRIB entities attached to the INSERT) drew only the block's primary geometry — the attribute values the user filled in were silently dropped. block_cache::expand_insert walks the block's BlockRecord which lists the ATTDEF templates, not the per-INSERT ATTRIB instances, so they were never tessellated. Add append_insert_attribute_wires: after expanding the block sub-wires, iterate ins.attributes, skip the entries flagged invisible, and emit a text wire for each at its stored WCS position. The wire's name reuses the parent INSERT handle so selection / picking still treats the block and its attribute text as one entity. Runs on both the block-cache fast path and the legacy explode fallback. Closes #20 Co-Authored-By: Claude Opus 4.7 --- src/scene/mod.rs | 103 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 26848bea8..617f62138 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -4778,6 +4778,27 @@ fn tessellate_entity( is_xref, bg_color, ) { + // Per-INSERT attribute values. The block defn carries the + // AttributeDefinitions (templates) which expand_insert skips; + // the AttributeEntity instances live on the Insert itself in + // WCS and need their own tessellation so the user sees the + // values they actually filled in. See #20. + append_insert_attribute_wires( + &mut wires, + document, + ins, + h, + sel, + ins_color, + ins_pat_len, + ins_pat, + ins_lw_px, + bg_color, + is_xref, + pslt_factor, + anno_scale, + world_offset, + ); wires.push(marker); return wires; } @@ -4832,6 +4853,22 @@ fn tessellate_entity( vec![wire] }) .collect(); + append_insert_attribute_wires( + &mut wires, + document, + ins, + h, + sel, + ins_color, + ins_pat_len, + ins_pat, + ins_lw_px, + bg_color, + is_xref, + pslt_factor, + anno_scale, + world_offset, + ); wires.push(marker); return wires; } @@ -5089,6 +5126,72 @@ pub(crate) fn text_obb_corners_native( ]) } +/// Tessellate each visible AttributeEntity attached to an Insert and append +/// the resulting wires. AttributeEntity positions are already in WCS — the +/// INSERT only stamps the geometry once, attribute text sits at the world +/// position recorded on each ATTRIB. See #20. +#[allow(clippy::too_many_arguments)] +fn append_insert_attribute_wires( + wires: &mut Vec, + document: &acadrust::CadDocument, + ins: &acadrust::entities::Insert, + insert_handle: Handle, + sel: bool, + ins_color: [f32; 4], + ins_pat_len: f32, + ins_pat: [f32; 8], + ins_lw_px: f32, + bg_color: [f32; 4], + is_xref: bool, + pslt_factor: f32, + anno_scale: f32, + world_offset: [f64; 3], +) { + if ins.attributes.is_empty() { + return; + } + for attr in &ins.attributes { + if attr.common.invisible || attr.flags.invisible { + continue; + } + let attr_entity = EntityType::AttributeEntity(attr.clone()); + let (sub_color, sub_plen, sub_pat, sub_lw_px, sub_aci) = + render::render_style_for_block_sub( + document, + &attr_entity, + ins_color, + ins_pat_len, + ins_pat, + ins_lw_px, + ); + let sub_color = render::adapt_to_bg(sub_color, bg_color); + let sub_color = if is_xref && !sel { + block_cache::fade_toward_bg(sub_color, bg_color) + } else { + sub_color + }; + let sub_aabb = entity_aabb(&attr_entity, world_offset); + let mut wire = tessellate::tessellate( + document, + insert_handle, + &attr_entity, + sel, + sub_color, + sub_plen * pslt_factor, + sub_pat.map(|v| v * pslt_factor), + sub_lw_px, + world_offset, + anno_scale, + ); + // Use the INSERT's handle so selection / picking groups attribute + // text with the parent insert instead of treating it as a stray text. + wire.name = insert_handle.value().to_string(); + wire.aci = sub_aci; + wire.aabb = sub_aabb; + wires.push(wire); + } +} + /// NaN-separated baseline segments — one per visible wrap line — for a /// sub-pixel Text / MText, in world-offset-subtracted f32 space. `n_lines` /// must be the actual rendered line count (1 for Text, the wrap-expanded