feat(annotation): integrate object contexts
This commit is contained in:
parent
f39481ac49
commit
e03e091b3c
37 changed files with 2820 additions and 581 deletions
|
|
@ -3155,7 +3155,11 @@ impl OpenCADStudio {
|
|||
self.tabs[i].scene.add_entity_clone(entity)
|
||||
})
|
||||
.collect();
|
||||
self.merge_clipboard_ext_objects(i, &by_index);
|
||||
let annotation_delta = match translate {
|
||||
Some(crate::command::EntityTransform::Translate(delta)) => delta,
|
||||
_ => glam::DVec3::ZERO,
|
||||
};
|
||||
self.merge_clipboard_ext_objects(i, &by_index, annotation_delta);
|
||||
// Recreate any group whose whole membership was copied, so a pasted
|
||||
// group stays grouped — cross-drawing too, since the groups were
|
||||
// snapshotted into the clipboard at copy time. `by_index` is aligned
|
||||
|
|
@ -3187,7 +3191,12 @@ impl OpenCADStudio {
|
|||
/// references, and re-pointing the pasted entity's `xdictionary_handle` at
|
||||
/// the new root. `by_index` is the paste's new entity handles, aligned with
|
||||
/// the clipboard order (NULL where the add failed). No-op without captures.
|
||||
pub(super) fn merge_clipboard_ext_objects(&mut self, i: usize, by_index: &[Handle]) {
|
||||
pub(super) fn merge_clipboard_ext_objects(
|
||||
&mut self,
|
||||
i: usize,
|
||||
by_index: &[Handle],
|
||||
annotation_delta: glam::DVec3,
|
||||
) {
|
||||
if self.clipboard_deps.ext_objects.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
|
@ -3204,6 +3213,11 @@ impl OpenCADStudio {
|
|||
if let Some(e) = doc.get_entity_mut(new_entity) {
|
||||
e.common_mut().xdictionary_handle = Some(new_root);
|
||||
}
|
||||
crate::scene::annotative::translate_annotation_contexts(
|
||||
doc,
|
||||
new_entity,
|
||||
annotation_delta,
|
||||
);
|
||||
}
|
||||
}
|
||||
// The wires were tessellated before the filters existed; refresh only
|
||||
|
|
@ -3270,6 +3284,10 @@ fn recreate_ext_subtree(
|
|||
if let Some(eh) = entity_handle {
|
||||
remap.insert(cap.src_entity_handle, eh);
|
||||
}
|
||||
for (old, scale) in &cap.annotation_scales {
|
||||
let target = crate::scene::annotative::ensure_scale_object(doc, scale);
|
||||
remap.insert(*old, target);
|
||||
}
|
||||
for (old, _) in &cap.objects {
|
||||
remap.insert(*old, doc.allocate_handle());
|
||||
}
|
||||
|
|
@ -3282,6 +3300,50 @@ fn recreate_ext_subtree(
|
|||
remap.get(&cap.root).copied()
|
||||
}
|
||||
|
||||
/// Replace references to a clipboard entity inside one recreated extension
|
||||
/// dictionary graph after its final block-owned handle becomes known.
|
||||
pub(crate) fn remap_ext_subtree_reference(
|
||||
doc: &mut acadrust::CadDocument,
|
||||
root: Handle,
|
||||
source_entity: Handle,
|
||||
target_entity: Handle,
|
||||
) {
|
||||
use acadrust::objects::ObjectType;
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::collections::HashMap;
|
||||
|
||||
let remap = HashMap::from([(source_entity, target_entity)]);
|
||||
let mut seen = FxHashSet::default();
|
||||
let mut pending = vec![root];
|
||||
while let Some(handle) = pending.pop() {
|
||||
if handle.is_null() || !seen.insert(handle) {
|
||||
continue;
|
||||
}
|
||||
let children = match doc.objects.get(&handle) {
|
||||
Some(ObjectType::Dictionary(dictionary)) => {
|
||||
let mut children: Vec<_> =
|
||||
dictionary.entries.iter().map(|(_, child)| *child).collect();
|
||||
if let Some(extension) = dictionary.xdictionary_handle {
|
||||
children.push(extension);
|
||||
}
|
||||
children
|
||||
}
|
||||
Some(ObjectType::DictionaryWithDefault(dictionary)) => {
|
||||
let mut children: Vec<_> =
|
||||
dictionary.entries.iter().map(|(_, child)| *child).collect();
|
||||
children.push(dictionary.default_handle);
|
||||
children
|
||||
}
|
||||
_ => Vec::new(),
|
||||
};
|
||||
pending.extend(children);
|
||||
if let Some(mut object) = doc.objects.remove(&handle) {
|
||||
remap_object(&mut object, handle, &remap);
|
||||
doc.objects.insert(handle, object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rewrite a cloned extension-dictionary object onto fresh handles: set its own
|
||||
/// handle to `new_handle` and remap its owner and any handle references it holds
|
||||
/// through `remap` (a handle still in the source space stays unchanged, which is
|
||||
|
|
@ -3326,6 +3388,11 @@ fn remap_object(
|
|||
ObjectType::XRecord(x) => {
|
||||
x.handle = new_handle;
|
||||
x.owner = map(x.owner);
|
||||
for entry in &mut x.entries {
|
||||
if let acadrust::objects::XRecordValue::Handle(handle) = &mut entry.value {
|
||||
*handle = map(*handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
ObjectType::Group(g) => {
|
||||
g.handle = new_handle;
|
||||
|
|
@ -3334,6 +3401,59 @@ fn remap_object(
|
|||
*h = map(*h);
|
||||
}
|
||||
}
|
||||
ObjectType::ObjectContextData(context) => {
|
||||
context.handle = new_handle;
|
||||
context.owner_handle = map(context.owner_handle);
|
||||
for reactor in &mut context.reactors {
|
||||
*reactor = map(*reactor);
|
||||
}
|
||||
if let Some(dictionary) = &mut context.xdictionary_handle {
|
||||
*dictionary = map(*dictionary);
|
||||
}
|
||||
context.scale = map(context.scale);
|
||||
match &mut context.kind {
|
||||
acadrust::objects::ObjectContextKind::Dim(dimension) => {
|
||||
dimension.block = map(dimension.block);
|
||||
}
|
||||
acadrust::objects::ObjectContextKind::HatchView(hatch) => {
|
||||
hatch.view = map(hatch.view);
|
||||
}
|
||||
acadrust::objects::ObjectContextKind::MTextAttribute(attribute) => {
|
||||
if let Some(embedded) = &mut attribute.context {
|
||||
embedded.owner_handle = map(embedded.owner_handle);
|
||||
for reactor in &mut embedded.reactors {
|
||||
*reactor = map(*reactor);
|
||||
}
|
||||
if let Some(dictionary) = &mut embedded.xdictionary_handle {
|
||||
*dictionary = map(*dictionary);
|
||||
}
|
||||
embedded.scale = map(embedded.scale);
|
||||
}
|
||||
}
|
||||
acadrust::objects::ObjectContextKind::MLeader(mleader) => {
|
||||
if let Some(handle) = &mut mleader.text_style_handle {
|
||||
*handle = map(*handle);
|
||||
}
|
||||
if let Some(handle) = &mut mleader.block_content_handle {
|
||||
*handle = map(*handle);
|
||||
}
|
||||
if let Some(handle) = &mut mleader.scale_handle {
|
||||
*handle = map(*handle);
|
||||
}
|
||||
for root in &mut mleader.leader_roots {
|
||||
for line in &mut root.lines {
|
||||
if let Some(handle) = &mut line.line_type_handle {
|
||||
*handle = map(*handle);
|
||||
}
|
||||
if let Some(handle) = &mut line.arrowhead_handle {
|
||||
*handle = map(*handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// Other leaf object kinds don't appear in an entity xdictionary; if one
|
||||
// does, it's inserted with the fresh handle below via the caller's key,
|
||||
// but its internal owner is left as-is (best effort).
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ impl OpenCADStudio {
|
|||
let name = self.unique_block_name("Block");
|
||||
let base = self.clipboard_base;
|
||||
let mut entities = self.clipboard.clone();
|
||||
for (idx, root) in ext_roots {
|
||||
for (&idx, &root) in &ext_roots {
|
||||
if let Some(e) = entities.get_mut(idx) {
|
||||
e.common_mut().xdictionary_handle = Some(root);
|
||||
}
|
||||
|
|
@ -255,7 +255,32 @@ impl OpenCADStudio {
|
|||
.scene
|
||||
.define_block_from_owned_entities(entities, &name, base)
|
||||
{
|
||||
Ok(()) => {
|
||||
Ok(entity_handles) => {
|
||||
let remaps: Vec<_> = ext_roots
|
||||
.iter()
|
||||
.filter_map(|(&idx, &root)| {
|
||||
Some((
|
||||
root,
|
||||
self.clipboard.get(idx)?.common().handle,
|
||||
*entity_handles.get(idx)?,
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
let scene = &mut self.tabs[i].scene;
|
||||
for (root, source, target) in remaps {
|
||||
super::super::command_driver::remap_ext_subtree_reference(
|
||||
&mut scene.document,
|
||||
root,
|
||||
source,
|
||||
target,
|
||||
);
|
||||
crate::scene::annotative::translate_annotation_contexts(
|
||||
&mut scene.document,
|
||||
target,
|
||||
-base,
|
||||
);
|
||||
}
|
||||
scene.bump_geometry();
|
||||
// Block defined; now place it interactively so the
|
||||
// user picks the drop point (insertion uses the
|
||||
// clipboard lower-left corner as the block's base). The
|
||||
|
|
|
|||
|
|
@ -624,12 +624,9 @@ impl OpenCADStudio {
|
|||
// still being built. Acknowledge them with an honest status so the
|
||||
// button responds instead of reporting an unknown command; each is
|
||||
// replaced by its real handler as the feature lands.
|
||||
// OBJECTSCALE ADD — the ribbon "Add Scale" quick action: mark the
|
||||
// selected objects annotative by attaching the AcAnnotativeData XData
|
||||
// record the tessellator already honours, so they scale with the
|
||||
// current annotation scale. Bare OBJECTSCALE opens the dialog below.
|
||||
// OBJECTSCALE ADD — add the active scale representation to every
|
||||
// selected object that supports per-scale context data.
|
||||
"OBJECTSCALE ADD" => {
|
||||
use acadrust::xdata::{ExtendedDataRecord, XDataValue};
|
||||
let handles: Vec<acadrust::Handle> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
|
|
@ -642,15 +639,23 @@ impl OpenCADStudio {
|
|||
return Some(Task::none());
|
||||
}
|
||||
self.push_undo_snapshot(i, "OBJECTSCALE");
|
||||
let Some(scale) = self.tabs[i].scene.creation_annotation_scale_handle() else {
|
||||
self.command_line
|
||||
.push_error("OBJECTSCALE: the active annotation scale is unavailable.");
|
||||
return Some(Task::none());
|
||||
};
|
||||
let mut n = 0usize;
|
||||
for h in &handles {
|
||||
if let Some(e) = self.tabs[i].scene.document.get_entity_mut(*h) {
|
||||
let xd = &mut e.common_mut().extended_data;
|
||||
if xd.get_record("AcAnnotativeData").is_none() {
|
||||
let mut rec = ExtendedDataRecord::new("AcAnnotativeData");
|
||||
rec.add_value(XDataValue::String("1".to_string()));
|
||||
xd.add_record(rec);
|
||||
}
|
||||
if crate::scene::annotative::create_annotation_context(
|
||||
&mut self.tabs[i].scene.document,
|
||||
*h,
|
||||
scale,
|
||||
) {
|
||||
crate::scene::annotative::set_entity_annotative(
|
||||
&mut self.tabs[i].scene.document,
|
||||
*h,
|
||||
true,
|
||||
);
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -661,7 +666,7 @@ impl OpenCADStudio {
|
|||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(&format!(
|
||||
"OBJECTSCALE: marked {n} object(s) annotative (they scale with the annotation scale)."
|
||||
"OBJECTSCALE: added the active scale to {n} object(s)."
|
||||
));
|
||||
return Some(Task::none());
|
||||
}
|
||||
|
|
@ -810,6 +815,85 @@ impl OpenCADStudio {
|
|||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
"ANNOALLVISIBLE" => {
|
||||
use crate::command::ValuePromptCommand;
|
||||
let c = ValuePromptCommand::new(
|
||||
"ANNOALLVISIBLE",
|
||||
"ANNOALLVISIBLE new value [0/1]:",
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("ANNOALLVISIBLE ") => {
|
||||
let value = cmd.split_whitespace().nth(1).unwrap_or("");
|
||||
match value {
|
||||
"0" | "OFF" | "FALSE" => {
|
||||
self.tabs[i].scene.set_annotation_all_visible(false);
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
"1" | "ON" | "TRUE" => {
|
||||
self.tabs[i].scene.set_annotation_all_visible(true);
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
_ => self
|
||||
.command_line
|
||||
.push_error("ANNOALLVISIBLE: enter 0 or 1."),
|
||||
}
|
||||
}
|
||||
"ANNOAUTOSCALE" => {
|
||||
use crate::command::ValuePromptCommand;
|
||||
let c = ValuePromptCommand::new(
|
||||
"ANNOAUTOSCALE",
|
||||
"ANNOAUTOSCALE new value [-4..4]:",
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("ANNOAUTOSCALE ") => {
|
||||
let value = cmd.split_whitespace().nth(1).unwrap_or("");
|
||||
match value.parse::<i8>() {
|
||||
Ok(mode @ -4..=4) => self.annotation_auto_scale = mode,
|
||||
_ => self.command_line.push_error(
|
||||
"ANNOAUTOSCALE: enter an integer from -4 through 4.",
|
||||
),
|
||||
}
|
||||
}
|
||||
"ANNOUPDATE" => {
|
||||
let handles: Vec<_> = self.tabs[i]
|
||||
.scene
|
||||
.selected_entities()
|
||||
.iter()
|
||||
.map(|(handle, _)| *handle)
|
||||
.collect();
|
||||
if handles.is_empty() {
|
||||
self.command_line
|
||||
.push_error("ANNOUPDATE: select annotation objects first.");
|
||||
return Some(Task::none());
|
||||
}
|
||||
self.push_undo_snapshot(i, "ANNOUPDATE");
|
||||
let scale = self.tabs[i].scene.creation_annotation_scale_handle();
|
||||
let mut updated = 0usize;
|
||||
for handle in &handles {
|
||||
if crate::scene::annotative::update_entity_from_annotation_style(
|
||||
&mut self.tabs[i].scene.document,
|
||||
*handle,
|
||||
scale,
|
||||
) {
|
||||
updated += 1;
|
||||
}
|
||||
}
|
||||
if updated > 0 {
|
||||
let changes: Vec<_> = handles
|
||||
.into_iter()
|
||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||
.collect();
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
self.command_line
|
||||
.push_output(&format!("ANNOUPDATE: updated {updated} object(s)."));
|
||||
return Some(Task::none());
|
||||
}
|
||||
cmd if cmd.starts_with("ANNOSCALE ") || cmd.starts_with("CANNOSCALE ") => {
|
||||
let arg = cmd
|
||||
.split_whitespace()
|
||||
|
|
@ -828,27 +912,21 @@ impl OpenCADStudio {
|
|||
.push_output(&format!("Current annotation scale: {name}"));
|
||||
return Some(Task::none());
|
||||
}
|
||||
// anno multiplier = denominator / numerator: 1:50 → 50, 2:1 → 0.5.
|
||||
let anno = if let Some((a, b)) = arg.split_once(':') {
|
||||
match (a.trim().parse::<f64>(), b.trim().parse::<f64>()) {
|
||||
(Ok(a), Ok(b)) if a != 0.0 => Some((b / a) as f32),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
arg.parse::<f32>().ok()
|
||||
};
|
||||
match anno {
|
||||
Some(v) if v > 0.0 => {
|
||||
self.tabs[i].scene.annotation_scale = v;
|
||||
let hdr = &mut self.tabs[i].scene.document.header;
|
||||
hdr.current_annotation_scale = arg.clone();
|
||||
hdr.annotation_scale_value = 1.0 / v as f64;
|
||||
self.tabs[i].scene.invalidate_annotation_dependencies();
|
||||
let previous = self.tabs[i].scene.displayed_annotation_scale_handle();
|
||||
match self.tabs[i].scene.set_annotation_scale_named(&arg) {
|
||||
Some(handle) => {
|
||||
if self.annotation_auto_scale > 0 {
|
||||
self.tabs[i].scene.add_annotation_scale_to_objects(
|
||||
handle,
|
||||
previous,
|
||||
self.annotation_auto_scale as u8,
|
||||
);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line
|
||||
.push_output(&format!("Annotation scale: {arg}"));
|
||||
}
|
||||
_ => self
|
||||
None => self
|
||||
.command_line
|
||||
.push_error("Usage: ANNOSCALE <ratio> e.g. 1:50, 2:1, or a factor"),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -336,6 +336,9 @@ inventory::submit!(crate::command::CommandRegistration {
|
|||
// Annotation scale.
|
||||
"ANNOSCALE",
|
||||
"CANNOSCALE",
|
||||
"ANNOALLVISIBLE",
|
||||
"ANNOAUTOSCALE",
|
||||
"ANNOUPDATE",
|
||||
"SCALELISTEDIT",
|
||||
"OBJECTSCALE",
|
||||
// Import CSV into a table + LandXML survey points.
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ pub struct AppConfig {
|
|||
pub start: StartConfig,
|
||||
/// Which status-bar pills the user has hidden.
|
||||
pub statusbar: StatusBarConfig,
|
||||
/// Add a newly selected annotation scale to existing annotative objects.
|
||||
pub annotation_auto_scale: i8,
|
||||
/// Ribbon collapse density.
|
||||
pub ribbon: RibbonConfig,
|
||||
/// Print dialog preferences (only the persisted fields; runtime state is
|
||||
|
|
@ -42,6 +44,7 @@ impl Default for AppConfig {
|
|||
recent: RecentConfig::default(),
|
||||
start: StartConfig::default(),
|
||||
statusbar: StatusBarConfig::default(),
|
||||
annotation_auto_scale: -4,
|
||||
ribbon: RibbonConfig::default(),
|
||||
plot: PlotDialogState::default(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -349,6 +349,8 @@ pub(super) struct OpenCADStudio {
|
|||
cycle_candidates: Option<(iced::Point, Vec<acadrust::Handle>)>,
|
||||
/// Which status-bar pills the user has chosen to show (persisted).
|
||||
statusbar_config: crate::ui::statusbar::statusbar_config::StatusBarConfig,
|
||||
/// Add selected scales to existing annotative objects.
|
||||
annotation_auto_scale: i8,
|
||||
/// Last persisted user preferences (DYN/OSNAP/OTRACK/POLAR/…). Compared
|
||||
/// after each message so a change is written to disk exactly once.
|
||||
last_saved_config: Option<config::AppConfig>,
|
||||
|
|
@ -1093,6 +1095,7 @@ pub struct ClipExtObjects {
|
|||
pub src_entity_handle: acadrust::Handle,
|
||||
pub root: acadrust::Handle,
|
||||
pub objects: Vec<(acadrust::Handle, acadrust::objects::ObjectType)>,
|
||||
pub annotation_scales: Vec<(acadrust::Handle, acadrust::objects::Scale)>,
|
||||
}
|
||||
|
||||
impl ClipboardDeps {
|
||||
|
|
@ -1154,11 +1157,29 @@ impl ClipboardDeps {
|
|||
}
|
||||
let objects = Self::collect_ext_subtree(doc, root);
|
||||
if !objects.is_empty() {
|
||||
let mut annotation_scales = Vec::new();
|
||||
for (_, object) in &objects {
|
||||
let acadrust::objects::ObjectType::ObjectContextData(context) = object else {
|
||||
continue;
|
||||
};
|
||||
if annotation_scales
|
||||
.iter()
|
||||
.any(|(handle, _)| *handle == context.scale)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Some(acadrust::objects::ObjectType::Scale(scale)) =
|
||||
doc.objects.get(&context.scale)
|
||||
{
|
||||
annotation_scales.push((context.scale, scale.clone()));
|
||||
}
|
||||
}
|
||||
ext_objects.push(ClipExtObjects {
|
||||
entity_index,
|
||||
src_entity_handle: c.handle,
|
||||
root,
|
||||
objects,
|
||||
annotation_scales,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1873,9 +1894,12 @@ pub enum Message {
|
|||
/// Apply the typed custom polar angle (Enter in the picker's field).
|
||||
SubmitPolarCustom,
|
||||
/// Set the model-space annotation scale (CANNOSCALE equivalent).
|
||||
SetAnnotationScale(f32),
|
||||
SetAnnotationScale(String),
|
||||
/// Set the active viewport's custom_scale (paper space).
|
||||
SetViewportScale(f64),
|
||||
SetViewportScale(String),
|
||||
ToggleAnnotationVisibility,
|
||||
ToggleAnnotationAutoAdd,
|
||||
SyncViewportAnnotationScale,
|
||||
/// Toggle the scale picker popup open/closed.
|
||||
ToggleScalePopup,
|
||||
/// Close the scale picker popup.
|
||||
|
|
@ -2667,6 +2691,7 @@ impl OpenCADStudio {
|
|||
selection_filter_popup_open: false,
|
||||
status_menu_tooltip_hidden: false,
|
||||
statusbar_config: crate::ui::statusbar::statusbar_config::StatusBarConfig::default(),
|
||||
annotation_auto_scale: -4,
|
||||
last_saved_config: None,
|
||||
otrack_active: None,
|
||||
clean_screen: false,
|
||||
|
|
|
|||
|
|
@ -657,6 +657,7 @@ impl super::OpenCADStudio {
|
|||
1.0,
|
||||
anno,
|
||||
None,
|
||||
None,
|
||||
bg,
|
||||
// Editor preview draws on a 2D canvas with no SDF shader — force the
|
||||
// glyph outline strokes so the text is visible (#308).
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ impl OpenCADStudio {
|
|||
};
|
||||
crate::scene::view::dispatch::set_prop_current_vertex(prop_vertex);
|
||||
|
||||
let annotation_scale_handle = self.tabs[i].scene.displayed_annotation_scale_handle();
|
||||
let new_panel = {
|
||||
let selected = self.tabs[i].scene.selected_entities();
|
||||
let mut panel = match selected.len() {
|
||||
|
|
@ -368,9 +369,10 @@ impl OpenCADStudio {
|
|||
}
|
||||
1 => {
|
||||
let (handle, source_entity) = selected[0];
|
||||
let contextual = crate::scene::annotative::entity_for_active_context(
|
||||
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||
&self.tabs[i].scene.document,
|
||||
source_entity,
|
||||
annotation_scale_handle,
|
||||
);
|
||||
let entity = contextual.as_ref();
|
||||
let group_names = self.tabs[i].scene.group_names_for_entity(handle);
|
||||
|
|
@ -1688,14 +1690,16 @@ impl OpenCADStudio {
|
|||
[0.0_f64; 3]
|
||||
};
|
||||
let (new_handle, new_grips, new_grip_handles) = {
|
||||
let annotation_scale_handle = self.tabs[i].scene.displayed_annotation_scale_handle();
|
||||
let selected = self.tabs[i].scene.selected_entities();
|
||||
let single_handle = (selected.len() == 1).then(|| selected[0].0);
|
||||
let mut grips = Vec::new();
|
||||
let mut handles = Vec::new();
|
||||
for (handle, entity) in selected {
|
||||
let contextual = crate::scene::annotative::entity_for_active_context(
|
||||
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||
&self.tabs[i].scene.document,
|
||||
entity,
|
||||
annotation_scale_handle,
|
||||
);
|
||||
for mut grip in dispatch::grips(contextual.as_ref()) {
|
||||
// Subtract in f64: at UTM magnitudes an f32 cast before
|
||||
|
|
@ -1738,11 +1742,9 @@ impl OpenCADStudio {
|
|||
pub(super) fn invalidate_property_targets(&mut self, i: usize, handles: &[Handle]) {
|
||||
let mut context_object_changed = false;
|
||||
for &handle in handles {
|
||||
context_object_changed |=
|
||||
crate::scene::annotative::sync_active_context_from_entity(
|
||||
&mut self.tabs[i].scene.document,
|
||||
handle,
|
||||
);
|
||||
context_object_changed |= self.tabs[i]
|
||||
.scene
|
||||
.sync_displayed_annotation_context(handle);
|
||||
// Hatch / SOLID fills render from prebuilt cached models; rebuild
|
||||
// them or pattern edits (scale, background, …) stay invisible
|
||||
// (#415).
|
||||
|
|
@ -1790,22 +1792,6 @@ impl OpenCADStudio {
|
|||
entity.as_entity_mut().set_layer(layer.clone());
|
||||
}
|
||||
|
||||
// A new dimension adopts the current dimension style (DIMSTYLE), like
|
||||
// AutoCAD — the DIM commands leave the default "Standard" on the entity,
|
||||
// so stamp the header's current style here. ADDSELECTED sets DIMSTYLE to
|
||||
// the template's first, so a cloned dimension keeps its style (#239).
|
||||
if let acadrust::EntityType::Dimension(ref mut d) = entity {
|
||||
let cur = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.header
|
||||
.current_dimstyle_name
|
||||
.clone();
|
||||
if !cur.trim().is_empty() {
|
||||
d.base_mut().style_name = cur;
|
||||
}
|
||||
}
|
||||
|
||||
// INSUNITS: when inserting a block whose BlockRecord.units differ
|
||||
// from the host's header.insertion_units, scale the new INSERT so
|
||||
// 1 source-unit equals the matching host length. When either side
|
||||
|
|
@ -1889,45 +1875,8 @@ impl OpenCADStudio {
|
|||
_ => None,
|
||||
});
|
||||
if let Some((h, s)) = found {
|
||||
ml.style_handle = Some(h);
|
||||
// Inherit the style's settings so a new multileader
|
||||
// reflects the current MLeaderStyle (the renderer reads
|
||||
// these entity fields). See #94.
|
||||
// The entity and style enums are distinct types with
|
||||
// matching discriminants — round-trip through i16.
|
||||
ml.content_type = (s.content_type as i16).into();
|
||||
ml.path_type = (s.path_type as i16).into();
|
||||
ml.line_color = s.line_color;
|
||||
ml.line_type_handle = s.line_type_handle;
|
||||
ml.line_weight = s.line_weight;
|
||||
ml.enable_landing = s.enable_landing;
|
||||
ml.enable_dogleg = s.enable_dogleg;
|
||||
ml.dogleg_length = s.landing_distance;
|
||||
ml.arrowhead_handle = s.arrowhead_handle;
|
||||
ml.arrowhead_size = s.arrowhead_size;
|
||||
ml.text_style_handle = s.text_style_handle;
|
||||
ml.text_color = s.text_color;
|
||||
ml.text_frame = s.text_frame;
|
||||
ml.text_height = s.text_height;
|
||||
ml.context.text_height = s.text_height;
|
||||
ml.text_left_attachment = (s.text_left_attachment as i16).into();
|
||||
ml.text_right_attachment = (s.text_right_attachment as i16).into();
|
||||
ml.text_top_attachment = (s.text_top_attachment as i16).into();
|
||||
ml.text_bottom_attachment = (s.text_bottom_attachment as i16).into();
|
||||
ml.text_attachment_direction =
|
||||
(s.text_attachment_direction as i16).into();
|
||||
ml.text_alignment = (s.text_alignment as i16).into();
|
||||
ml.text_angle_type = (s.text_angle_type as i16).into();
|
||||
ml.block_content_handle = s.block_content_handle;
|
||||
ml.block_content_color = s.block_content_color;
|
||||
ml.block_connection_type = (s.block_content_connection as i16).into();
|
||||
ml.block_rotation = s.block_content_rotation;
|
||||
ml.block_scale = acadrust::types::Vector3::new(
|
||||
s.block_content_scale_x,
|
||||
s.block_content_scale_y,
|
||||
s.block_content_scale_z,
|
||||
);
|
||||
ml.scale_factor = s.scale_factor;
|
||||
debug_assert_eq!(h, s.handle);
|
||||
crate::scene::annotative::apply_mleader_style(ml, &s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1955,6 +1904,52 @@ impl OpenCADStudio {
|
|||
_ => {}
|
||||
}
|
||||
|
||||
let text_style_annotative = match &entity {
|
||||
acadrust::EntityType::Text(text) => {
|
||||
crate::scene::annotative::text_style_is_annotative(
|
||||
&self.tabs[i].scene.document,
|
||||
&text.style,
|
||||
)
|
||||
}
|
||||
acadrust::EntityType::MText(text) => {
|
||||
crate::scene::annotative::text_style_is_annotative(
|
||||
&self.tabs[i].scene.document,
|
||||
&text.style,
|
||||
)
|
||||
}
|
||||
acadrust::EntityType::AttributeEntity(attribute) => {
|
||||
crate::scene::annotative::text_style_is_annotative(
|
||||
&self.tabs[i].scene.document,
|
||||
&attribute.text_style,
|
||||
)
|
||||
}
|
||||
acadrust::EntityType::AttributeDefinition(attribute) => {
|
||||
crate::scene::annotative::text_style_is_annotative(
|
||||
&self.tabs[i].scene.document,
|
||||
&attribute.text_style,
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if text_style_annotative {
|
||||
match &mut entity {
|
||||
acadrust::EntityType::MText(text) => text.is_annotative = true,
|
||||
acadrust::EntityType::AttributeEntity(attribute) => {
|
||||
attribute.flags.annotative = true
|
||||
}
|
||||
acadrust::EntityType::AttributeDefinition(attribute) => {
|
||||
attribute.flags.annotative = true
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let needs_annotation_context = crate::scene::annotative::is_annotative(
|
||||
&self.tabs[i].scene.document,
|
||||
&entity,
|
||||
) || crate::scene::annotative::annotation_style_is_annotative(
|
||||
&self.tabs[i].scene.document,
|
||||
&entity,
|
||||
);
|
||||
|
||||
let new_handle = if matches!(&entity, acadrust::EntityType::Viewport(_))
|
||||
&& self.tabs[i].scene.current_layout != "Model"
|
||||
|
|
@ -2011,6 +2006,22 @@ impl OpenCADStudio {
|
|||
Some(self.tabs[i].scene.add_entity(entity))
|
||||
};
|
||||
|
||||
if needs_annotation_context {
|
||||
if let (Some(handle), Some(scale)) = (
|
||||
new_handle,
|
||||
self.tabs[i].scene.creation_annotation_scale_handle(),
|
||||
) {
|
||||
crate::scene::annotative::create_annotation_context(
|
||||
&mut self.tabs[i].scene.document,
|
||||
handle,
|
||||
scale,
|
||||
);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||
}
|
||||
}
|
||||
|
||||
if tracks_draw_anchor {
|
||||
if let Some(handle) = new_handle {
|
||||
self.tabs[i].last_draw_anchor = Some(handle);
|
||||
|
|
|
|||
|
|
@ -301,6 +301,124 @@ impl OpenCADStudio {
|
|||
}
|
||||
}
|
||||
|
||||
fn style_in_use(&self, kind: StyleKind, name: &str) -> bool {
|
||||
use acadrust::entities::EntityType;
|
||||
|
||||
let i = self.active_tab;
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
match kind {
|
||||
StyleKind::Text => {
|
||||
if doc.header.current_text_style_name.eq_ignore_ascii_case(name) {
|
||||
return true;
|
||||
}
|
||||
let style_handle = doc.text_styles.get(name).map(|style| style.handle);
|
||||
let referenced_by_entity = doc.entities().any(|entity| match entity {
|
||||
EntityType::Text(text) => text.style.eq_ignore_ascii_case(name),
|
||||
EntityType::MText(text) => text.style.eq_ignore_ascii_case(name),
|
||||
EntityType::AttributeEntity(attribute) => {
|
||||
attribute.text_style.eq_ignore_ascii_case(name)
|
||||
}
|
||||
EntityType::AttributeDefinition(attribute) => {
|
||||
attribute.text_style.eq_ignore_ascii_case(name)
|
||||
}
|
||||
EntityType::Insert(insert) => insert.attributes.iter().any(|attribute| {
|
||||
attribute.text_style.eq_ignore_ascii_case(name)
|
||||
}),
|
||||
EntityType::MultiLeader(leader) => [
|
||||
leader.text_style_handle,
|
||||
leader.context.text_style_handle,
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.any(|handle| Some(handle) == style_handle),
|
||||
EntityType::Table(table) => table.rows.iter().any(|row| {
|
||||
row.style
|
||||
.as_ref()
|
||||
.and_then(|style| style.text_style_handle)
|
||||
.is_some_and(|handle| Some(handle) == style_handle)
|
||||
|| row.cells.iter().any(|cell| {
|
||||
cell.style
|
||||
.as_ref()
|
||||
.and_then(|style| style.text_style_handle)
|
||||
.is_some_and(|handle| Some(handle) == style_handle)
|
||||
|| cell.contents.iter().any(|content| {
|
||||
content
|
||||
.text_style_handle
|
||||
.is_some_and(|handle| Some(handle) == style_handle)
|
||||
})
|
||||
})
|
||||
}),
|
||||
_ => false,
|
||||
});
|
||||
referenced_by_entity
|
||||
|| doc
|
||||
.dim_styles
|
||||
.iter()
|
||||
.any(|style| style.dimtxsty.eq_ignore_ascii_case(name))
|
||||
|| doc.objects.values().any(|object| match object {
|
||||
ObjectType::TableStyle(style) => [
|
||||
&style.data_row_style,
|
||||
&style.header_row_style,
|
||||
&style.title_row_style,
|
||||
]
|
||||
.into_iter()
|
||||
.any(|row| row.text_style_name.eq_ignore_ascii_case(name)),
|
||||
ObjectType::MultiLeaderStyle(style) => {
|
||||
style.text_style_handle == style_handle
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
StyleKind::Dim => {
|
||||
doc.header.current_dimstyle_name.eq_ignore_ascii_case(name)
|
||||
|| doc.entities().any(|entity| match entity {
|
||||
EntityType::Dimension(dimension) => {
|
||||
dimension.base().style_name.eq_ignore_ascii_case(name)
|
||||
}
|
||||
EntityType::Leader(leader) => {
|
||||
leader.dimension_style.eq_ignore_ascii_case(name)
|
||||
}
|
||||
EntityType::Tolerance(tolerance) => tolerance
|
||||
.dimension_style_name
|
||||
.eq_ignore_ascii_case(name),
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
StyleKind::Table | StyleKind::MLeader | StyleKind::MLine => {
|
||||
let Some(handle) = object_handle(doc, name, kind) else {
|
||||
return false;
|
||||
};
|
||||
let is_current = match kind {
|
||||
StyleKind::Table => {
|
||||
doc.header.current_table_style_name.eq_ignore_ascii_case(name)
|
||||
}
|
||||
StyleKind::MLeader => {
|
||||
doc.header.current_mleader_style_name.eq_ignore_ascii_case(name)
|
||||
|| self.tabs[i]
|
||||
.active_mleader_style
|
||||
.eq_ignore_ascii_case(name)
|
||||
}
|
||||
StyleKind::MLine => doc.header.multiline_style.eq_ignore_ascii_case(name),
|
||||
StyleKind::Text | StyleKind::Dim => false,
|
||||
};
|
||||
is_current
|
||||
|| doc.entities().any(|entity| match (kind, entity) {
|
||||
(StyleKind::Table, EntityType::Table(table)) => {
|
||||
table.table_style_handle == Some(handle)
|
||||
}
|
||||
(StyleKind::MLeader, EntityType::MultiLeader(leader)) => {
|
||||
leader.style_handle == Some(handle)
|
||||
}
|
||||
(StyleKind::MLine, EntityType::MLine(line)) => {
|
||||
line.style_handle == Some(handle)
|
||||
|| line.style_name.eq_ignore_ascii_case(name)
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rename `old`→`new` in the backing store, re-keying table entries and
|
||||
/// rewriting name-based references + current-style pointers.
|
||||
fn rename_style_storage(&mut self, kind: StyleKind, old: &str, new: &str) {
|
||||
|
|
@ -331,9 +449,40 @@ impl OpenCADStudio {
|
|||
{
|
||||
t.style = new.to_string();
|
||||
}
|
||||
acadrust::entities::EntityType::AttributeEntity(a)
|
||||
if a.text_style.eq_ignore_ascii_case(old) =>
|
||||
{
|
||||
a.text_style = new.to_string();
|
||||
}
|
||||
acadrust::entities::EntityType::AttributeDefinition(a)
|
||||
if a.text_style.eq_ignore_ascii_case(old) =>
|
||||
{
|
||||
a.text_style = new.to_string();
|
||||
}
|
||||
acadrust::entities::EntityType::Insert(insert) => {
|
||||
for attribute in &mut insert.attributes {
|
||||
if attribute.text_style.eq_ignore_ascii_case(old) {
|
||||
attribute.text_style = new.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
for object in doc.objects.values_mut() {
|
||||
let ObjectType::TableStyle(style) = object else {
|
||||
continue;
|
||||
};
|
||||
for row in [
|
||||
&mut style.data_row_style,
|
||||
&mut style.header_row_style,
|
||||
&mut style.title_row_style,
|
||||
] {
|
||||
if row.text_style_name.eq_ignore_ascii_case(old) {
|
||||
row.text_style_name = new.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StyleKind::Dim => {
|
||||
let doc = &mut self.tabs[i].scene.document;
|
||||
|
|
@ -349,10 +498,23 @@ impl OpenCADStudio {
|
|||
doc.header.current_dimstyle_name = new.to_string();
|
||||
}
|
||||
for e in doc.entities_mut() {
|
||||
if let acadrust::entities::EntityType::Dimension(d) = e {
|
||||
if d.base().style_name.eq_ignore_ascii_case(old) {
|
||||
match e {
|
||||
acadrust::entities::EntityType::Dimension(d)
|
||||
if d.base().style_name.eq_ignore_ascii_case(old) =>
|
||||
{
|
||||
d.base_mut().style_name = new.to_string();
|
||||
}
|
||||
acadrust::entities::EntityType::Leader(l)
|
||||
if l.dimension_style.eq_ignore_ascii_case(old) =>
|
||||
{
|
||||
l.dimension_style = new.to_string();
|
||||
}
|
||||
acadrust::entities::EntityType::Tolerance(t)
|
||||
if t.dimension_style_name.eq_ignore_ascii_case(old) =>
|
||||
{
|
||||
t.dimension_style_name = new.to_string();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -366,12 +528,24 @@ impl OpenCADStudio {
|
|||
if self.ribbon.active_table_style.eq_ignore_ascii_case(old) {
|
||||
self.ribbon.active_table_style = new.to_string();
|
||||
}
|
||||
if doc.header.current_table_style_name.eq_ignore_ascii_case(old) {
|
||||
doc.header.current_table_style_name = new.to_string();
|
||||
}
|
||||
}
|
||||
StyleKind::MLeader => {
|
||||
let doc = &mut self.tabs[i].scene.document;
|
||||
if let Some(h) = object_handle(doc, old, kind) {
|
||||
if let Some(ObjectType::MultiLeaderStyle(s)) = doc.objects.get_mut(&h) {
|
||||
s.name = new.to_string();
|
||||
{
|
||||
let doc = &mut self.tabs[i].scene.document;
|
||||
if let Some(h) = object_handle(doc, old, kind) {
|
||||
if let Some(ObjectType::MultiLeaderStyle(s)) = doc.objects.get_mut(&h) {
|
||||
s.name = new.to_string();
|
||||
}
|
||||
}
|
||||
if doc
|
||||
.header
|
||||
.current_mleader_style_name
|
||||
.eq_ignore_ascii_case(old)
|
||||
{
|
||||
doc.header.current_mleader_style_name = new.to_string();
|
||||
}
|
||||
}
|
||||
if self.tabs[i].active_mleader_style.eq_ignore_ascii_case(old) {
|
||||
|
|
@ -391,6 +565,13 @@ impl OpenCADStudio {
|
|||
if doc.header.multiline_style.eq_ignore_ascii_case(old) {
|
||||
doc.header.multiline_style = new.to_string();
|
||||
}
|
||||
for entity in doc.entities_mut() {
|
||||
if let acadrust::entities::EntityType::MLine(line) = entity {
|
||||
if line.style_name.eq_ignore_ascii_case(old) {
|
||||
line.style_name = new.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -437,6 +618,11 @@ impl OpenCADStudio {
|
|||
.push_error("Cannot delete the Standard style.");
|
||||
return;
|
||||
}
|
||||
if self.style_in_use(kind, &name) {
|
||||
self.command_line
|
||||
.push_error("Cannot delete a style that is current or in use.");
|
||||
return;
|
||||
}
|
||||
if !self.remove_style_storage(kind, &name) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(¤t)
|
||||
&& (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]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1470,6 +1470,7 @@ impl OpenCADStudio {
|
|||
layout_names: layout_names.clone(),
|
||||
polar_custom_input: &self.polar_custom_input,
|
||||
scale_is_model: is_model,
|
||||
current_scale_name: tab.scene.displayed_annotation_scale_name(),
|
||||
scale_list: tab.scene.scale_picker_list(),
|
||||
has_selection: !tab.scene.selected.is_empty(),
|
||||
selection_types: tab
|
||||
|
|
@ -1501,6 +1502,9 @@ impl OpenCADStudio {
|
|||
self.show_layout_tabs,
|
||||
tab.scene.annotation_scale,
|
||||
scale_pill_enabled,
|
||||
tab.scene.annotation_all_visible(),
|
||||
self.annotation_auto_scale > 0,
|
||||
tab.scene.viewport_annotation_scale_synced(),
|
||||
tab.scene.document.header.lineweight_display,
|
||||
cursor_coord,
|
||||
coords_mode,
|
||||
|
|
|
|||
Loading…
Reference in a new issue