fix(annotation): apply object context scaling

This commit is contained in:
Hakan Seven committed 2026-08-02 03:02:11 +03:00
1 parent c4c673336c
commit 5909540ebf
4 files changed
+103 -9

No files matched your search

+1 -1
View File
@@ -1320,7 +1320,7 @@ impl MultiLeaderTess for MultiLeader {
// ml.scale_factor is always applied; anno_scale is only applied when the
// multileader is marked annotative.
let effective_scale = (ml.scale_factor as f32)
* if ml.enable_annotation_scale {
* if crate::scene::annotative::mleader_is_annotative(document, ml) {
anno_scale
} else {
1.0
+86 -3
View File
@@ -377,6 +377,80 @@ pub fn active_object_context(
default.or(first)
}
/// Resolve the display multiplier for an entity at the current annotation
/// scale. A per-object context stores geometry relative to its native/default
/// representation, so its current multiplier is the active scale's drawing
/// factor divided by the default scale's drawing factor. Falling back preserves
/// the legacy whole-drawing multiplier for annotative objects without a usable
/// default context (including style-only DIMENSION and MULTILEADER entities).
pub fn effective_annotation_scale(
doc: &CadDocument,
entity: &EntityType,
fallback: f32,
) -> f32 {
if !is_annotative(doc, entity) {
return 1.0;
}
// Unlike MTEXT / DIMENSION contexts, an MLEADER context carries its
// already-scaled text height and overall scale factor. Keep the text
// height as stored; make `ml.scale_factor * anno_scale` resolve to the
// active context's scale factor for arrows, doglegs, and fallback text.
if let EntityType::MultiLeader(mleader) = entity {
let Some(active) = active_object_context(doc, entity.common().handle) else {
return fallback;
};
let ObjectContextKind::MLeader(context) = &active.kind else {
return fallback;
};
let base = mleader.scale_factor;
if base.abs() <= 1.0e-12 {
return fallback;
}
let relative = context.scale_factor / base;
return if relative.is_finite() && relative > 0.0 {
relative as f32
} else {
fallback
};
}
let Some(coll_h) = annotation_scales_dict(doc, entity.common().handle) else {
return fallback;
};
let Some(coll) = as_dict(doc, coll_h) else {
return fallback;
};
let active = active_object_context(doc, entity.common().handle);
let native = coll.entries.iter().find_map(|(_, leaf_h)| {
match doc.objects.get(leaf_h) {
Some(ObjectType::ObjectContextData(leaf)) if leaf.is_default => Some(leaf),
_ => None,
}
});
let (Some(active), Some(native)) = (active, native) else {
return fallback;
};
let (Some(ObjectType::Scale(active_scale)), Some(ObjectType::Scale(native_scale))) = (
doc.objects.get(&active.scale),
doc.objects.get(&native.scale),
) else {
return fallback;
};
let native_factor = native_scale.inverse_factor();
if native_factor.abs() <= 1.0e-12 {
return fallback;
}
let relative = active_scale.inverse_factor() / native_factor;
if relative.is_finite() && relative > 0.0 {
relative as f32
} else {
fallback
}
}
fn text_horizontal(value: i16) -> acadrust::entities::TextHorizontalAlignment {
use acadrust::entities::TextHorizontalAlignment;
match value {
@@ -1030,6 +1104,17 @@ fn has_context_manager(doc: &CadDocument, common: &EntityCommon) -> bool {
.unwrap_or(false)
}
/// Whether a MULTILEADER participates in annotation scaling through its
/// per-object context, entity flag, or assigned annotative style.
pub fn mleader_is_annotative(
doc: &CadDocument,
mleader: &acadrust::entities::MultiLeader,
) -> bool {
has_context_manager(doc, &mleader.common)
|| mleader.enable_annotation_scale
|| mleader_style_annotative(doc, mleader.style_handle)
}
/// Whether an entity participates in annotation scaling.
pub fn is_annotative(doc: &CadDocument, entity: &EntityType) -> bool {
// Per-object annotation context (works regardless of style).
@@ -1049,9 +1134,7 @@ pub fn is_annotative(doc: &CadDocument, entity: &EntityType) -> bool {
EntityType::MText(t) => t.is_annotative,
EntityType::Dimension(d) => dim_style_annotative(doc, &d.base().style_name),
EntityType::Leader(l) => dim_style_annotative(doc, &l.dimension_style),
EntityType::MultiLeader(ml) => {
ml.enable_annotation_scale || mleader_style_annotative(doc, ml.style_handle)
}
EntityType::MultiLeader(ml) => mleader_is_annotative(doc, ml),
EntityType::Table(t) => table_style_annotative(doc, t.table_style_handle),
_ => false,
}
+14
View File
@@ -361,6 +361,20 @@ pub(crate) fn tessellate_entity(
let e = contextual.as_ref();
let h = e.common().handle;
let sel = selected.contains(&h);
// Per-object annotation contexts store each representation relative to the
// native/default scale. Resolve that ratio once before TEXT/MTEXT,
// DIMENSION, and MULTILEADER reach their independent tessellation paths.
let anno_scale = if matches!(
e,
EntityType::Text(_)
| EntityType::MText(_)
| EntityType::Dimension(_)
| EntityType::MultiLeader(_)
) {
crate::scene::annotative::effective_annotation_scale(document, e, anno_scale)
} else {
anno_scale
};
// Frustum + LOD cull for non-Insert, non-Viewport entities. Insert is
// handled separately (its WCS bbox depends on the block defn AABB ×
+2 -5
View File
@@ -156,11 +156,8 @@ pub fn tessellate(
// oversized text). Annotative-ness is resolved centrally from the entity's
// per-object context, legacy XDATA, or annotative style (see
// `scene::annotative::is_annotative`) so the bake and the panel agree.
let anno_scale = if crate::scene::annotative::is_annotative(document, entity) {
anno_scale
} else {
1.0
};
let anno_scale =
crate::scene::annotative::effective_annotation_scale(document, entity, anno_scale);
// A HATCH is drawn as a fill by the hatch pipeline and highlighted via a
// fill tint when selected (issue #71), so it carries no boundary outline in