feat(annotation): integrate object contexts

This commit is contained in:
Hakan Seven 2026-08-02 12:00:15 +03:00
commit e03e091b3c
37 changed files with 2820 additions and 581 deletions

View file

@ -1491,15 +1491,19 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
// take the resolved value directly (None = the default
// "Closed filled" / "ByBlock" option).
let doc = &self.tabs[i].scene.document;
let resolved_mleader_style = (field == "mleader_style")
.then(|| {
doc.objects.values().find_map(|object| match object {
acadrust::objects::ObjectType::MultiLeaderStyle(style)
if style.name == value => Some(style.clone()),
_ => None,
})
})
.flatten();
let resolved: Option<acadrust::Handle> = match field {
"mleader_style" => doc.objects.iter().find_map(|(h, o)| match o {
acadrust::objects::ObjectType::MultiLeaderStyle(s)
if s.name == value =>
{
Some(*h)
}
_ => None,
}),
"mleader_style" => {
resolved_mleader_style.as_ref().map(|style| style.handle)
}
"text_style_handle" => doc
.text_styles
.iter()
@ -1531,15 +1535,20 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
if self.tabs[i].scene.is_layer_locked(handle) {
continue;
}
if let Some(acadrust::EntityType::MultiLeader(ml)) =
let mut style_annotation = None;
if field == "mleader_style" {
if let Some(style) = &resolved_mleader_style {
crate::scene::annotative::apply_mleader_style_to_object(
&mut self.tabs[i].scene.document,
handle,
style,
);
style_annotation = Some(style.is_annotative);
}
} else if let Some(acadrust::EntityType::MultiLeader(ml)) =
self.tabs[i].scene.document.get_entity_mut(handle)
{
match field {
"mleader_style" => {
if let Some(h) = resolved {
ml.style_handle = Some(h);
}
}
"text_style_handle" => {
if let Some(h) = resolved {
ml.text_style_handle = Some(h);
@ -1550,6 +1559,24 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
_ => {}
}
}
if let Some(annotative) = style_annotation {
if annotative {
if let Some(scale) =
self.tabs[i].scene.creation_annotation_scale_handle()
{
crate::scene::annotative::create_annotation_context(
&mut self.tabs[i].scene.document,
handle,
scale,
);
}
} else {
crate::scene::annotative::clear_annotation_context(
&mut self.tabs[i].scene.document,
handle,
);
}
}
}
} else if matches!(field, "arrow_block" | "dim_line_lw" | "text_pos_vert") {
// Leader dim-var overrides picked from a dropdown. The

View file

@ -516,6 +516,7 @@ impl OpenCADStudio {
section: self.start_section,
},
statusbar: self.statusbar_config.clone(),
annotation_auto_scale: self.annotation_auto_scale,
ribbon: crate::app::config::RibbonConfig {
collapse: self.ribbon.collapse_mode(),
},
@ -545,6 +546,7 @@ impl OpenCADStudio {
// (`refresh_recent_thumbs`) — never here on the boot path.
self.start_section = cfg.start.section;
self.statusbar_config = cfg.statusbar;
self.annotation_auto_scale = cfg.annotation_auto_scale.clamp(-4, 4);
self.ribbon.set_collapse_mode(cfg.ribbon.collapse);
self.plot_dialog = cfg.plot;
}

View file

@ -2589,17 +2589,60 @@ impl OpenCADStudio {
}
Message::SetAnnotationScale(scale) => {
self.scale_popup_open = false;
let auto_scale = self.annotation_auto_scale;
if let Some(tab) = self.tabs.get_mut(self.active_tab) {
tab.scene.annotation_scale = scale;
util::sync_annotation_scale_header(&mut tab.scene);
tab.scene.invalidate_annotation_dependencies();
let previous = tab.scene.displayed_annotation_scale_handle();
if let Some(handle) = tab.scene.set_annotation_scale_named(&scale) {
if auto_scale > 0 {
tab.scene.add_annotation_scale_to_objects(
handle,
previous,
auto_scale as u8,
);
}
tab.dirty = true;
}
}
Task::none()
}
Message::SetViewportScale(scale) => {
self.scale_popup_open = false;
let auto_scale = self.annotation_auto_scale;
if let Some(tab) = self.tabs.get_mut(self.active_tab) {
tab.scene.set_viewport_scale(scale);
let previous = tab.scene.displayed_annotation_scale_handle();
if let Some(handle) = tab.scene.set_viewport_scale_named(&scale) {
if auto_scale > 0 {
tab.scene.add_annotation_scale_to_objects(
handle,
previous,
auto_scale as u8,
);
}
tab.dirty = true;
}
}
Task::none()
}
Message::ToggleAnnotationVisibility => {
if let Some(tab) = self.tabs.get_mut(self.active_tab) {
let value = !tab.scene.annotation_all_visible();
tab.scene.set_annotation_all_visible(value);
tab.dirty = true;
}
Task::none()
}
Message::ToggleAnnotationAutoAdd => {
self.annotation_auto_scale = match self.annotation_auto_scale {
0 => 4,
value => -value,
};
Task::none()
}
Message::SyncViewportAnnotationScale => {
if let Some(tab) = self.tabs.get_mut(self.active_tab) {
if tab.scene.sync_viewport_annotation_scale() {
tab.dirty = true;
}
}
Task::none()
}
@ -2647,22 +2690,17 @@ impl OpenCADStudio {
let i = self.active_tab;
let handles = self.property_target_handles(i);
if handles.len() == 1 {
// Only object types that carry a per-object context.
let ok = matches!(
self.tabs[i].scene.document.get_entity(handles[0]),
Some(
acadrust::EntityType::Text(_)
| acadrust::EntityType::MText(_)
| acadrust::EntityType::Insert(_)
)
);
let ok = self.tabs[i]
.scene
.document
.get_entity(handles[0])
.is_some_and(crate::scene::annotative::supports_annotation_context);
if ok {
self.anno_object_scale_target = Some(handles[0]);
self.active_modal = Some(crate::app::ModalKind::AnnoObjectScale);
} else {
self.command_line.push_info(
"OBJECTSCALE applies to a single Text, MText or block reference.",
);
self.command_line
.push_info("The selected object does not support annotation scales.");
}
} else {
self.command_line
@ -2816,21 +2854,16 @@ impl OpenCADStudio {
// never rolled back when the manager closes.
let i = self.active_tab;
let sel = self.scale_manager_selected.clone();
if let Some((_, anno, _)) = self
.tabs[i]
.scene
.scale_list()
.into_iter()
.find(|(n, _, _)| n.eq_ignore_ascii_case(&sel))
{
self.tabs[i].scene.annotation_scale = anno;
self.tabs[i].scene.document.header.current_annotation_scale = sel.clone();
if let Some((p, d)) = self.tabs[i].scene.scale_paper_drawing(&sel) {
if d != 0.0 {
self.tabs[i].scene.document.header.annotation_scale_value = p / d;
}
let previous = self.tabs[i].scene.displayed_annotation_scale_handle();
if let Some(scale) = self.tabs[i].scene.set_annotation_scale_named(&sel) {
if self.annotation_auto_scale > 0 {
self.tabs[i].scene.add_annotation_scale_to_objects(
scale,
previous,
self.annotation_auto_scale as u8,
);
}
self.tabs[i].scene.invalidate_annotation_dependencies();
self.tabs[i].dirty = true;
}
Task::none()
}
@ -3657,7 +3690,7 @@ impl OpenCADStudio {
// object. Off is handled inside set_entity_*.
if !cur {
if let Some(sh) =
self.tabs[i].scene.current_annotation_scale_handle()
self.tabs[i].scene.creation_annotation_scale_handle()
{
crate::scene::annotative::create_annotation_context(
&mut self.tabs[i].scene.document,

View file

@ -299,7 +299,12 @@ impl OpenCADStudio {
set_f64!(dimcen, self.ds_dimcen);
set_f64!(dimtsz, self.ds_dimtsz);
set_f64!(dimtxt, self.ds_dimtxt);
set_f64!(dimscale, self.ds_dimscale);
if self.ds_annotative {
ds.dimscale = 0.0;
self.ds_dimscale = "0".to_string();
} else {
set_f64!(dimscale, self.ds_dimscale);
}
set_f64!(dimlfac, self.ds_dimlfac);
set_f64!(dimtp, self.ds_dimtp);
set_f64!(dimtm, self.ds_dimtm);
@ -442,7 +447,12 @@ impl OpenCADStudio {
Dimtoh => self.ds_dimtoh = !self.ds_dimtoh,
Dimtol => self.ds_dimtol = !self.ds_dimtol,
Dimlim => self.ds_dimlim = !self.ds_dimlim,
Annotative => self.ds_annotative = !self.ds_annotative,
Annotative => {
self.ds_annotative = !self.ds_annotative;
if self.ds_annotative {
self.ds_dimscale = "0".to_string();
}
}
Dimfxlon => self.ds_dimfxlon = !self.ds_dimfxlon,
Dimsah => self.ds_dimsah = !self.ds_dimsah,
Dimtxtdirection => self.ds_dimtxtdirection = !self.ds_dimtxtdirection,

View file

@ -4,9 +4,8 @@ use crate::scene::Scene;
/// Parse a scale string like "1:50" or "2:1" into (numerator, denominator).
/// Returns (1.0, 1.0) for "Fit" or unknown formats.
/// Sync the model-space annotation scale into the standard CANNOSCALE /
/// CANNOSCALEVALUE header variables before a save, so the scale round-trips
/// through the file (and is read correctly by other CAD applications).
/// Sync the model-space annotation scale into its named drawing variable and
/// numeric header mirror before a save.
pub(super) fn sync_annotation_scale_header(scene: &mut Scene) {
let anno = scene.annotation_scale;
let value = if anno.abs() > 1e-9 {
@ -14,17 +13,25 @@ pub(super) fn sync_annotation_scale_header(scene: &mut Scene) {
} else {
1.0
};
// Prefer the name of a matching scale already in the drawing's list;
// fall back to a formatted ratio when none matches.
let name = scene
.scale_list()
.into_iter()
.find(|(_, a, _)| (a - anno).abs() < 0.001 * anno.max(0.001))
.map(|(n, _, _)| n)
.unwrap_or_else(|| format_annotation_scale_name(anno));
let current = scene.document.header.current_annotation_scale.clone();
let current_matches = scene.scale_list().into_iter().any(|(name, factor, _)| {
name.eq_ignore_ascii_case(&current)
&& (factor - anno).abs() < 0.001 * anno.max(0.001)
});
let name = if current_matches {
current
} else {
scene
.scale_list()
.into_iter()
.find(|(_, factor, _)| (factor - anno).abs() < 0.001 * anno.max(0.001))
.map(|(name, _, _)| name)
.unwrap_or_else(|| format_annotation_scale_name(anno))
};
let hdr = &mut scene.document.header;
hdr.current_annotation_scale = name;
hdr.current_annotation_scale = name.clone();
hdr.annotation_scale_value = value;
crate::io::set_drawing_variable(&mut scene.document, "CANNOSCALE", &name);
}
/// Format an annotation-scale multiplier as a ratio name: 50.0 -> "1:50",
@ -101,4 +108,3 @@ pub(super) fn f4_to_u3([r, g, b, _]: [f32; 4]) -> [u8; 3] {
pub(super) fn u3_to_f4([r, g, b]: [u8; 3]) -> [f32; 4] {
[r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0]
}