forked from 1b-agent/opencadstudio-upstream
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 <[email protected]>
This commit is contained in:
1 parent
5e2b67c642
commit
d54fb9e2aa
5 files changed
+113
-2
No files matched your search
@@ -222,6 +222,59 @@ pub fn create_annotation_context(
|
||||
true
|
||||
}
|
||||
|
||||
/// True when `common` belongs to an annotative object whose per-object scale
|
||||
/// contexts are *all* bound to scales other than the current one — an
|
||||
/// off-scale representation that must stay hidden. A drawing may materialise
|
||||
/// each annotation scale as its own object (e.g. a block insert per scale on a
|
||||
/// "0 @ <scale>" layer); only the current scale's representation should draw.
|
||||
///
|
||||
/// False for non-annotative objects (no per-object context — the vast
|
||||
/// majority) and for objects whose contexts include the current scale. Gated
|
||||
/// on an extension dictionary so non-annotative entities skip the lookup.
|
||||
pub fn annotative_offscale(doc: &CadDocument, common: &EntityCommon) -> bool {
|
||||
if !common
|
||||
.xdictionary_handle
|
||||
.map(|h| !h.is_null())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let scales = object_scale_memberships(doc, common.handle);
|
||||
if scales.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let cur = &doc.header.current_annotation_scale;
|
||||
if scales.iter().any(|(name, _)| name.eq_ignore_ascii_case(cur)) {
|
||||
return false;
|
||||
}
|
||||
// Off-scale (no context for the current scale). If some representation in
|
||||
// the drawing DOES provide the current scale, hide this one — the matching
|
||||
// representation is the one to show.
|
||||
if current_scale_provided(doc) {
|
||||
return true;
|
||||
}
|
||||
// The current scale is unsupported by any representation. Fall back to the
|
||||
// base "1:1" representation: keep it, hide the enlarged copies — otherwise
|
||||
// every scale representation stacks (or, if all were hidden, the object
|
||||
// vanishes). Without this, opening at e.g. CANNOSCALE 10:1 shows both a 1×
|
||||
// and a 10× copy of the same block.
|
||||
!scales.iter().any(|(name, _)| name.eq_ignore_ascii_case("1:1"))
|
||||
}
|
||||
|
||||
/// Whether any annotative representation in the drawing targets the current
|
||||
/// annotation scale.
|
||||
fn current_scale_provided(doc: &CadDocument) -> bool {
|
||||
let cur = &doc.header.current_annotation_scale;
|
||||
doc.objects.values().any(|o| {
|
||||
if let ObjectType::ObjectContextData(cd) = o {
|
||||
if let Some(ObjectType::Scale(s)) = doc.objects.get(&cd.scale) {
|
||||
return s.name.eq_ignore_ascii_case(cur);
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
/// The annotation scales an object currently carries a per-object context for,
|
||||
/// as `(scale name, scale handle)` pairs (one per representation). Empty when
|
||||
/// the object has no per-object context chain.
|
||||
|
||||
Vendored
+6
@@ -342,6 +342,12 @@ fn build_defn(
|
||||
if layer_hidden(doc, &entity.common().layer) {
|
||||
continue;
|
||||
}
|
||||
// Annotative scale representation: bake only the current scale's copy
|
||||
// into the defn so off-scale representations don't stack (e.g. a 1×
|
||||
// copy under a 10×). See `annotative::annotative_offscale`.
|
||||
if crate::scene::annotative::annotative_offscale(doc, entity.common()) {
|
||||
continue;
|
||||
}
|
||||
match entity {
|
||||
EntityType::Block(_) | EntityType::BlockEnd(_) => continue,
|
||||
// A non-constant ATTDEF is only a template — the insert supplies an
|
||||
|
||||
@@ -325,6 +325,7 @@ pub(crate) fn tessellate_entity_dim_text(
|
||||
None,
|
||||
view_aabb,
|
||||
world_per_pixel,
|
||||
false,
|
||||
);
|
||||
for w in &mut wires {
|
||||
// Synth dim text carries no real entity colour — paint everything
|
||||
@@ -351,6 +352,11 @@ pub(crate) fn tessellate_entity(
|
||||
view_aabb: Option<[f32; 4]>,
|
||||
// World units per screen pixel for LOD culling. `None` = no LOD.
|
||||
world_per_pixel: Option<f32>,
|
||||
// True only when tessellating content shown INSIDE a paper-space viewport,
|
||||
// where PSLTSCALE scales linetypes by the viewport scale. Model-space (and
|
||||
// paper-sheet) rendering passes false: a drawing's annotation scale must
|
||||
// not resize model-space linetypes (that is MSLTSCALE, off here).
|
||||
paper_space: bool,
|
||||
) -> Vec<WireModel> {
|
||||
let h = e.common().handle;
|
||||
let sel = selected.contains(&h);
|
||||
@@ -501,8 +507,12 @@ pub(crate) fn tessellate_entity(
|
||||
let entity_color = fade_if_locked(document, e, entity_color, bg_color);
|
||||
let lt_scale = document.header.linetype_scale as f32 * e.common().linetype_scale as f32;
|
||||
let lt_name = view::render::linetype_name_for(document, e);
|
||||
// PSLTSCALE: scale linetype dashes by viewport anno_scale so they appear uniform in paper space.
|
||||
let pslt_factor = if document.header.paper_space_linetype_scaling {
|
||||
// PSLTSCALE: scale linetype dashes by the viewport scale so they appear
|
||||
// uniform in paper space. Only applies to content shown inside a paper-space
|
||||
// viewport (`paper_space`); model space uses LTSCALE × CELTSCALE unscaled by
|
||||
// the annotation scale, otherwise a drawing at e.g. CANNOSCALE 10:1 draws
|
||||
// its linetypes 10× off.
|
||||
let pslt_factor = if paper_space && document.header.paper_space_linetype_scaling {
|
||||
anno_scale
|
||||
} else {
|
||||
1.0
|
||||
@@ -760,6 +770,7 @@ pub(crate) fn tessellate_entity(
|
||||
block_cache,
|
||||
view_aabb,
|
||||
world_per_pixel,
|
||||
paper_space,
|
||||
);
|
||||
for mut w in sub_wires {
|
||||
w.name = h.value().to_string();
|
||||
@@ -921,6 +932,7 @@ pub(crate) fn tessellate_entity(
|
||||
block_cache,
|
||||
view_aabb,
|
||||
world_per_pixel,
|
||||
paper_space,
|
||||
);
|
||||
for mut w in sub_wires {
|
||||
w.name = h.value().to_string();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -3212,6 +3212,7 @@ impl Scene {
|
||||
Some(&blk),
|
||||
None,
|
||||
None,
|
||||
anno_scale_override.is_some(),
|
||||
);
|
||||
memo_updates.push((*h, Arc::new(raw.clone())));
|
||||
let mut faded = raw;
|
||||
@@ -4541,6 +4542,16 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Annotative scale representation: draw only the current scale's copy.
|
||||
// Model-space only (`frozen_layers` is None): a paper-space viewport
|
||||
// renders at its own annotation scale and already hides the off-scale
|
||||
// representations through its per-viewport frozen "0 @ <scale>" layers,
|
||||
// so applying the model-space scale here would fight that.
|
||||
if frozen_layers.is_none()
|
||||
&& crate::scene::annotative::annotative_offscale(&self.document, c)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
self.belongs_to_visible_block(c.handle, c.owner_handle, block_handle)
|
||||
}
|
||||
|
||||
@@ -4674,6 +4685,9 @@ impl Scene {
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
// Content shown inside a paper-space viewport carries a scale override;
|
||||
// only there does PSLTSCALE resize linetypes by the viewport scale.
|
||||
let paper = anno_scale_override.is_some();
|
||||
let blk_cache = self.block_cache_arc();
|
||||
let blk_ref: &cache::block_cache::BlockCache = &blk_cache;
|
||||
// Zoom-adaptive curve sampling for top-level Edge tessellation. Target
|
||||
@@ -4776,6 +4790,7 @@ impl Scene {
|
||||
let e: &EntityType = e;
|
||||
let w = tessellate_entity(
|
||||
doc, sel, avp, bg, anno, e, Some(blk_ref), view_aabb, wpp,
|
||||
paper,
|
||||
);
|
||||
(e.common().handle, Arc::new(w))
|
||||
})
|
||||
@@ -4795,6 +4810,7 @@ impl Scene {
|
||||
.flat_map(|e| {
|
||||
tessellate_entity(
|
||||
doc, sel, avp, bg, anno, e, Some(blk_ref), view_aabb, wpp,
|
||||
paper,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
@@ -5055,6 +5071,7 @@ impl Scene {
|
||||
Some(&blk_cache),
|
||||
None,
|
||||
None,
|
||||
self.current_layout != "Model",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user