fix(render): annotative scale representations + model-space linetype scale

Two rendering defects surfaced on the budweiser benchmark drawings, both
tied to a drawing's annotation scale.

Annotative scale representations. A drawing may materialise each annotation
scale as its own object (here a block insert per scale, on a "0 @ <scale>"
layer, each carrying a per-object AcDbContextData bound to one scale).
Nothing filtered them, so every scale drew at once — a 1× copy stacked
under a 10× one. Add annotative::annotative_offscale and apply it in the
three paths that expand block content: the wire block-defn cache, the
resident/top-level visibility gate (model space only — paper viewports use
their per-viewport frozen scale layers), and the exploded-insert hatch/fill
walk (filtered before normalize strips the handle). An object is shown when
a representation matches the current scale; the off-scale ones are hidden.
When the current scale is unsupported by any representation, fall back to
the base "1:1" copy instead of hiding everything (the object must not
vanish) or showing everything (the copies must not stack).

Model-space linetype scale. PSLTSCALE (a paper-space flag) was applied to
model-space linetypes via the annotation scale, so a drawing saved at e.g.
CANNOSCALE 10:1 drew its linetypes 10× off. Thread a `paper_space` flag
through tessellate_entity and gate the PSLTSCALE factor on it, so it only
scales linetypes for content shown inside a paper-space viewport; model
space keeps LTSCALE × CELTSCALE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-23 21:24:56 +03:00
1 parent 5e2b67c642
commit d54fb9e2aa
5 files changed
+113 -2

No files matched your search

+23
View File
@@ -912,6 +912,14 @@ impl Scene {
) {
continue;
}
// Off-scale annotative representation (model space only — paper
// viewports use their per-viewport frozen scale layers). Skips its
// whole fill subtree, matching the wire path.
if frozen.is_none()
&& crate::scene::annotative::annotative_offscale(&self.document, &ins.common)
{
continue;
}
if !self.block_has_hatch(&ins.block_name, &mut hatch_block_memo)
&& !self.block_has_wide_poly(&ins.block_name, &mut wide_block_memo)
{
@@ -942,6 +950,17 @@ impl Scene {
.get(&ins.common.handle.value())
.copied()
.unwrap_or([0.0, 0.0]);
// Drop off-scale annotative representations (e.g. a 10× copy on
// layer "0 @ 10" when the current scale is 1:1) before `normalize`
// strips their handle — otherwise their whole fill subtree stacks
// under the current-scale copy. Model space only; paper viewports
// use their per-viewport frozen scale layers. `explode` preserves
// the child handle, so the membership lookup still resolves here.
let offscale = |e: &EntityType| -> bool {
frozen.is_none()
&& matches!(e, EntityType::Insert(ni)
if crate::scene::annotative::annotative_offscale(&self.document, &ni.common))
};
let mut stack: Vec<(
EntityType,
usize,
@@ -950,6 +969,7 @@ impl Scene {
(f32, f32),
)> = explode_including_dims(ins, &self.document)
.into_iter()
.filter(|e| !offscale(e))
.map(|e| (normalize(e), 0usize, ins_color, l0, (base_depth, half_gap)))
.collect();
while let Some((sub, depth, sub_ins_color, sub_l0, (d_base, d_half))) = stack.pop() {
@@ -993,6 +1013,9 @@ impl Scene {
* d_half;
let nd_half = d_half / (block_count(&nins.block_name) as f32 + 1.0);
for e in explode_including_dims(&nins, &self.document) {
if offscale(&e) {
continue;
}
stack.push((
normalize(e),
depth + 1,