forked from 1b-agent/opencadstudio-upstream
fix(mleader): improve behavior and persistence
Keep annotation contexts, grip placement, and style dictionary entries consistent across editing and file saves. Refs #779
This commit is contained in:
commit
20c7a2df58
29 files changed
+858
-195
No files matched your search
@@ -1270,10 +1270,29 @@ impl OpenCADStudio {
|
||||
self.restore_pre_cmd_tangent();
|
||||
}
|
||||
CmdResult::CommitAndEditText(entity) => {
|
||||
let annotative_mleader = matches!(
|
||||
&entity,
|
||||
acadrust::EntityType::MultiLeader(ml) if ml.enable_annotation_scale
|
||||
);
|
||||
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
||||
let delta_safe = self.delta_add_safe(i, &entity);
|
||||
let delta_safe = self.delta_add_safe(i, &entity) && !annotative_mleader;
|
||||
let pending = self.begin_undo(i, label, 1, delta_safe);
|
||||
let handle = self.commit_entity_handle(entity);
|
||||
|
||||
if annotative_mleader {
|
||||
let scale_handle = self.tabs[i].scene.creation_annotation_scale_handle();
|
||||
if let (Some(handle), Some(scale_handle)) = (handle, scale_handle) {
|
||||
crate::scene::annotative::create_annotation_context(
|
||||
&mut self.tabs[i].scene.document,
|
||||
handle,
|
||||
scale_handle,
|
||||
);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||
}
|
||||
}
|
||||
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.tabs[i].active_cmd = None;
|
||||
|
||||
+14
-7
@@ -143,15 +143,22 @@ impl OpenCADStudio {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let active_mleader = self.tabs[i].active_mleader_style.clone();
|
||||
let active_mleader = if mleader_names.contains(&active_mleader) {
|
||||
active_mleader
|
||||
} else {
|
||||
let current_mleader =
|
||||
doc.header.current_mleader_style_name.clone();
|
||||
|
||||
let active_mleader =
|
||||
mleader_names
|
||||
.first()
|
||||
.iter()
|
||||
.find(|name| {
|
||||
name.eq_ignore_ascii_case(
|
||||
¤t_mleader
|
||||
)
|
||||
})
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
};
|
||||
.or_else(|| {
|
||||
mleader_names.first().cloned()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let table_names: Vec<String> = doc
|
||||
.objects
|
||||
|
||||
@@ -1248,6 +1248,15 @@ impl super::OpenCADStudio {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// Keep the displayed annotation context in sync.
|
||||
if matches!(
|
||||
self.tabs[i].scene.document.get_entity(h),
|
||||
Some(EntityType::MultiLeader(_))
|
||||
) {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.sync_displayed_annotation_context(h);
|
||||
}
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(h, crate::scene::ChangeKind::Modified)]);
|
||||
@@ -1336,6 +1345,14 @@ impl super::OpenCADStudio {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if matches!(
|
||||
self.tabs[i].scene.document.get_entity(h),
|
||||
Some(EntityType::MultiLeader(_))
|
||||
) {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.sync_displayed_annotation_context(h);
|
||||
}
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(h, crate::scene::ChangeKind::Modified)]);
|
||||
|
||||
+188
-1
@@ -18,16 +18,134 @@
|
||||
//! style added without a handle (dropped on DWG save, issue #67).
|
||||
|
||||
use super::OpenCADStudio;
|
||||
use acadrust::objects::{MLineStyle, MultiLeaderStyle, ObjectType, TableStyle};
|
||||
use acadrust::objects::{
|
||||
Dictionary, MLineStyle, MultiLeaderStyle, ObjectType, TableStyle,
|
||||
};
|
||||
use acadrust::tables::{DimStyle, TextStyle};
|
||||
use acadrust::types::Handle;
|
||||
|
||||
const MLEADERSTYLE_DICT_NAME: &str = "ACAD_MLEADERSTYLE";
|
||||
|
||||
fn mleaderstyle_dict_handle(doc: &acadrust::CadDocument) -> Option<Handle> {
|
||||
let root_h = doc.header.named_objects_dict_handle;
|
||||
|
||||
let root = match doc.objects.get(&root_h) {
|
||||
Some(ObjectType::Dictionary(root)) => root,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
root.entries
|
||||
.iter()
|
||||
.find(|(name, _)| name.eq_ignore_ascii_case(MLEADERSTYLE_DICT_NAME))
|
||||
.map(|(_, handle)| *handle)
|
||||
.filter(|handle| {
|
||||
matches!(
|
||||
doc.objects.get(handle),
|
||||
Some(ObjectType::Dictionary(_))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn import_mleaderstyle_names_from_dictionary(doc: &mut acadrust::CadDocument) {
|
||||
let Some(dict_h) = mleaderstyle_dict_handle(doc) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let entries = match doc.objects.get(&dict_h) {
|
||||
Some(ObjectType::Dictionary(dict)) => dict.entries.clone(),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
for (name, handle) in entries {
|
||||
if let Some(ObjectType::MultiLeaderStyle(style)) =
|
||||
doc.objects.get_mut(&handle)
|
||||
{
|
||||
style.name = name;
|
||||
style.owner_handle = dict_h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_mleaderstyle_dictionary(doc: &mut acadrust::CadDocument) {
|
||||
let root_h = crate::scene::annotative::root_named_dict_handle(doc);
|
||||
|
||||
let existing = match doc.objects.get(&root_h) {
|
||||
Some(ObjectType::Dictionary(root)) => root
|
||||
.entries
|
||||
.iter()
|
||||
.find(|(name, _)| name.eq_ignore_ascii_case(MLEADERSTYLE_DICT_NAME))
|
||||
.map(|(_, handle)| *handle),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let dict_h = match existing.filter(|handle| {
|
||||
matches!(
|
||||
doc.objects.get(handle),
|
||||
Some(ObjectType::Dictionary(_))
|
||||
)
|
||||
}) {
|
||||
Some(handle) => handle,
|
||||
None => {
|
||||
let handle = doc.allocate_handle();
|
||||
|
||||
let mut dict = Dictionary::new();
|
||||
dict.handle = handle;
|
||||
dict.owner = root_h;
|
||||
|
||||
doc.objects
|
||||
.insert(handle, ObjectType::Dictionary(dict));
|
||||
|
||||
handle
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(ObjectType::Dictionary(root)) = doc.objects.get_mut(&root_h) {
|
||||
root.entries
|
||||
.retain(|(name, _)| !name.eq_ignore_ascii_case(MLEADERSTYLE_DICT_NAME));
|
||||
|
||||
root.add_entry(MLEADERSTYLE_DICT_NAME, dict_h);
|
||||
}
|
||||
|
||||
let mut entries: Vec<(String, Handle)> = doc
|
||||
.objects
|
||||
.iter()
|
||||
.filter_map(|(&handle, object)| match object {
|
||||
ObjectType::MultiLeaderStyle(style) => {
|
||||
Some((style.name.clone(), handle))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
entries.sort_by(|a, b| {
|
||||
a.0.to_lowercase().cmp(&b.0.to_lowercase())
|
||||
});
|
||||
|
||||
for (_, handle) in &entries {
|
||||
if let Some(ObjectType::MultiLeaderStyle(style)) =
|
||||
doc.objects.get_mut(handle)
|
||||
{
|
||||
style.owner_handle = dict_h;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ObjectType::Dictionary(dict)) = doc.objects.get_mut(&dict_h) {
|
||||
dict.owner = root_h;
|
||||
dict.entries = entries;
|
||||
|
||||
// MLEADERSTYLE uses soft-owner entries (group 350).
|
||||
dict.hard_owner = false;
|
||||
dict.hard_owner_entries.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Guarantee the built-in "Standard" style of every kind exists in `doc` —
|
||||
/// a foreign or damaged file saved without them leaves the style dropdowns
|
||||
/// broken with no way to recover, and new text/dimensions have nothing to
|
||||
/// reference (#366). Missing entries are re-seeded with the app defaults.
|
||||
/// Called on every file open; a no-op for healthy documents.
|
||||
pub(crate) fn ensure_standard_styles(doc: &mut acadrust::CadDocument) {
|
||||
import_mleaderstyle_names_from_dictionary(doc);
|
||||
if !doc
|
||||
.text_styles
|
||||
.iter()
|
||||
@@ -68,6 +186,8 @@ pub(crate) fn ensure_standard_styles(doc: &mut acadrust::CadDocument) {
|
||||
s.handle = doc.allocate_handle();
|
||||
doc.objects.insert(s.handle, ObjectType::MultiLeaderStyle(s));
|
||||
}
|
||||
sync_mleaderstyle_dictionary(doc);
|
||||
|
||||
if !has(doc, |o| match o {
|
||||
ObjectType::MLineStyle(s) => Some(&s.name),
|
||||
_ => None,
|
||||
@@ -741,6 +861,7 @@ impl OpenCADStudio {
|
||||
for (h, o) in &snap.style_objects {
|
||||
doc.objects.insert(*h, o.clone());
|
||||
}
|
||||
sync_mleaderstyle_dictionary(doc);
|
||||
doc.header.current_text_style_name = snap.current_text.clone();
|
||||
doc.header.current_dimstyle_name = snap.current_dim.clone();
|
||||
doc.header.multiline_style = snap.multiline_style.clone();
|
||||
@@ -772,11 +893,77 @@ impl OpenCADStudio {
|
||||
self.sync_ribbon_styles();
|
||||
return;
|
||||
};
|
||||
sync_mleaderstyle_dictionary(
|
||||
&mut self.tabs[i].scene.document,
|
||||
);
|
||||
|
||||
let edited = self.capture_style_state();
|
||||
let changed = edited != stage.baseline;
|
||||
if changed {
|
||||
self.tabs[i].dirty = true;
|
||||
let (text_names, dim_names, object_handles) = edited.changed_keys(&stage.baseline);
|
||||
let changed_mleader_styles:
|
||||
Vec<acadrust::objects::MultiLeaderStyle> =
|
||||
object_handles
|
||||
.iter()
|
||||
.filter_map(|handle| {
|
||||
match self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.objects
|
||||
.get(handle)
|
||||
{
|
||||
Some(
|
||||
acadrust::objects::ObjectType::
|
||||
MultiLeaderStyle(style),
|
||||
) => Some(style.clone()),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut changed_mleaders = Vec::new();
|
||||
|
||||
for style in changed_mleader_styles {
|
||||
let entity_handles: Vec<acadrust::Handle> = {
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
|
||||
doc.entities()
|
||||
.filter_map(|entity| {
|
||||
match entity {
|
||||
acadrust::EntityType::MultiLeader(ml)
|
||||
if ml.style_handle
|
||||
== Some(style.handle) =>
|
||||
{
|
||||
Some(ml.common.handle)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
for handle in entity_handles {
|
||||
if crate::scene::annotative::
|
||||
apply_mleader_style_to_object(
|
||||
&mut self.tabs[i].scene.document,
|
||||
handle,
|
||||
&style,
|
||||
)
|
||||
{
|
||||
changed_mleaders.push((
|
||||
handle,
|
||||
crate::scene::ChangeKind::Modified,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !changed_mleaders.is_empty() {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&changed_mleaders);
|
||||
}
|
||||
self.tabs[i].scene.invalidate_text_style_dependencies_many(&text_names);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
|
||||
@@ -1673,9 +1673,18 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
}
|
||||
}
|
||||
StyleKey::MLeaderStyle => {
|
||||
self.ribbon.active_mleader_style = name.clone();
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].active_mleader_style = name;
|
||||
|
||||
self.ribbon.active_mleader_style = name.clone();
|
||||
self.tabs[i].active_mleader_style = name.clone();
|
||||
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.header
|
||||
.current_mleader_style_name = name;
|
||||
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
StyleKey::TableStyle => {
|
||||
self.ribbon.active_table_style = name;
|
||||
|
||||
Reference in new issue
Block a user