feat(properties): editable Annotative toggle for MText and MLeader
The Annotative row becomes an editable toggle for the two entity types that carry a native per-object annotative flag — MTEXT (is_annotative) and MULTILEADER (enable_annotation_scale). Turning it on flags the object annotative (it then scales with the current annotation scale in model space); turning it off clears the flag AND removes the per-object annotation context (the AcDbContextDataManager -> ACDB_ANNOTATIONSCALES leaf subtree) plus the legacy annotative XDATA markers, via scene::annotative::set_entity_annotative / clear_annotation_context, so the object stops resolving annotative instead of staying on through a stale context. The shared SCALE objects are left intact. Style-derived types (text/dimension/leader/table/insert) keep a read-only Annotative row — their state comes from the assigned style, and making a single object annotative there needs a per-object context object (a dedicated acadrust ObjectContextData writer) which is out of scope here. Verified an existing annotative DWG round-trips its 134 per-object contexts through a save/reload unchanged, so the far more common open-and-save path is unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
fb8e2aeec1
commit
fc5e93dcc1
3 changed files with 105 additions and 14 deletions
|
|
@ -680,14 +680,26 @@ impl OpenCADStudio {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// The read-only text rows get an explicit Yes/No; MLeader
|
// MTEXT carries a per-object annotative flag, so its
|
||||||
// keeps its editable toggle.
|
// row is an editable toggle (like MLeader's); the
|
||||||
|
// style-derived types stay read-only Yes/No.
|
||||||
if anno_field == "annotative" {
|
if anno_field == "annotative" {
|
||||||
set_row(
|
if let acadrust::EntityType::MText(t) = entity {
|
||||||
&mut sections,
|
set_row_value(
|
||||||
"annotative",
|
&mut sections,
|
||||||
if is_anno { "Yes" } else { "No" }.to_string(),
|
"annotative",
|
||||||
);
|
crate::scene::model::object::PropValue::BoolToggle {
|
||||||
|
field: "is_annotative",
|
||||||
|
value: t.is_annotative,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
set_row(
|
||||||
|
&mut sections,
|
||||||
|
"annotative",
|
||||||
|
if is_anno { "Yes" } else { "No" }.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if is_anno {
|
if is_anno {
|
||||||
// The applied annotation scale follows the current
|
// The applied annotation scale follows the current
|
||||||
|
|
|
||||||
|
|
@ -2296,14 +2296,39 @@ impl OpenCADStudio {
|
||||||
if !handles.is_empty() {
|
if !handles.is_empty() {
|
||||||
self.push_undo_snapshot(i, "CHPROP");
|
self.push_undo_snapshot(i, "CHPROP");
|
||||||
for &handle in &handles {
|
for &handle in &handles {
|
||||||
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
match field {
|
||||||
match field {
|
// Per-object annotative flag (MTEXT / MULTILEADER): a
|
||||||
"invisible" => {
|
// doc-aware toggle so turning it off also removes the
|
||||||
crate::scene::view::dispatch::toggle_invisible(entity)
|
// per-object annotation context, not just the flag.
|
||||||
|
"is_annotative" | "enable_annotation_scale" => {
|
||||||
|
let cur = match self.tabs[i].scene.document.get_entity(handle) {
|
||||||
|
Some(acadrust::EntityType::MText(t)) => t.is_annotative,
|
||||||
|
Some(acadrust::EntityType::MultiLeader(m)) => {
|
||||||
|
m.enable_annotation_scale
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
crate::scene::annotative::set_entity_annotative(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
handle,
|
||||||
|
!cur,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
"invisible" => {
|
||||||
|
if let Some(entity) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
{
|
||||||
|
crate::scene::view::dispatch::toggle_invisible(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if let Some(entity) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
{
|
||||||
|
crate::scene::view::dispatch::apply_geom_prop(
|
||||||
|
entity, field, "toggle",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_ => crate::scene::view::dispatch::apply_geom_prop(
|
|
||||||
entity, field, "toggle",
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,60 @@ pub fn as_dict(doc: &CadDocument, handle: Handle) -> Option<&Dictionary> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the per-object annotative flag on the entity types that carry one
|
||||||
|
/// (MTEXT, MULTILEADER). Turning it off also strips the per-object annotation
|
||||||
|
/// context and legacy markers via [`clear_annotation_context`] so the object
|
||||||
|
/// stops resolving annotative; turning it on leaves the base geometry as the
|
||||||
|
/// single (implicit, current-scale) representation. Other entity types get
|
||||||
|
/// their annotative state from a style and are not toggled here.
|
||||||
|
pub fn set_entity_annotative(doc: &mut CadDocument, handle: Handle, want: bool) {
|
||||||
|
if let Some(e) = doc.get_entity_mut(handle) {
|
||||||
|
match e {
|
||||||
|
EntityType::MText(t) => t.is_annotative = want,
|
||||||
|
EntityType::MultiLeader(m) => m.enable_annotation_scale = want,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !want {
|
||||||
|
clear_annotation_context(doc, handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove an entity's per-object annotation context — the extension-dictionary
|
||||||
|
/// `AcDbContextDataManager` → `ACDB_ANNOTATIONSCALES` → per-scale leaf subtree —
|
||||||
|
/// and the legacy annotative XDATA markers, so [`is_annotative`] no longer fires
|
||||||
|
/// on it. The shared `SCALE` objects in `ACAD_SCALELIST` are document-level and
|
||||||
|
/// left intact.
|
||||||
|
pub fn clear_annotation_context(doc: &mut CadDocument, handle: Handle) {
|
||||||
|
if let Some(xdict_h) = doc.get_entity(handle).and_then(|e| e.common().xdictionary_handle) {
|
||||||
|
// Collect the manager subtree (manager dict, its scales dict, the leaves)
|
||||||
|
// before mutating, then drop them.
|
||||||
|
let mut remove = Vec::new();
|
||||||
|
if let Some(mgr_h) = as_dict(doc, xdict_h).and_then(|d| d.get("AcDbContextDataManager")) {
|
||||||
|
remove.push(mgr_h);
|
||||||
|
if let Some(scales_h) =
|
||||||
|
as_dict(doc, mgr_h).and_then(|d| d.get("ACDB_ANNOTATIONSCALES"))
|
||||||
|
{
|
||||||
|
remove.push(scales_h);
|
||||||
|
if let Some(scales) = as_dict(doc, scales_h) {
|
||||||
|
for (_, leaf) in &scales.entries {
|
||||||
|
remove.push(*leaf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ObjectType::Dictionary(xd)) = doc.objects.get_mut(&xdict_h) {
|
||||||
|
xd.entries.retain(|(k, _)| k != "AcDbContextDataManager");
|
||||||
|
}
|
||||||
|
for h in remove {
|
||||||
|
doc.objects.remove(&h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Strip the legacy annotative XDATA markers the detection also honours.
|
||||||
|
crate::scene::view::dispatch::set_entity_xdata(doc, handle, "AcAnnoPO", None);
|
||||||
|
crate::scene::view::dispatch::set_entity_xdata(doc, handle, "AcAnnotativeData", None);
|
||||||
|
}
|
||||||
|
|
||||||
/// Does a style name resolve to `name` (or to "Standard" when `name` is blank)?
|
/// Does a style name resolve to `name` (or to "Standard" when `name` is blank)?
|
||||||
fn name_matches(style_name: &str, name: &str) -> bool {
|
fn name_matches(style_name: &str, name: &str) -> bool {
|
||||||
style_name.eq_ignore_ascii_case(name)
|
style_name.eq_ignore_ascii_case(name)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue