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)
|
self.tabs[i].scene.add_entity_clone(entity)
|
||||||
})
|
})
|
||||||
.collect();
|
.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
|
// Recreate any group whose whole membership was copied, so a pasted
|
||||||
// group stays grouped — cross-drawing too, since the groups were
|
// group stays grouped — cross-drawing too, since the groups were
|
||||||
// snapshotted into the clipboard at copy time. `by_index` is aligned
|
// 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
|
/// 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 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.
|
/// 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() {
|
if self.clipboard_deps.ext_objects.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -3204,6 +3213,11 @@ impl OpenCADStudio {
|
||||||
if let Some(e) = doc.get_entity_mut(new_entity) {
|
if let Some(e) = doc.get_entity_mut(new_entity) {
|
||||||
e.common_mut().xdictionary_handle = Some(new_root);
|
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
|
// The wires were tessellated before the filters existed; refresh only
|
||||||
|
|
@ -3270,6 +3284,10 @@ fn recreate_ext_subtree(
|
||||||
if let Some(eh) = entity_handle {
|
if let Some(eh) = entity_handle {
|
||||||
remap.insert(cap.src_entity_handle, eh);
|
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 {
|
for (old, _) in &cap.objects {
|
||||||
remap.insert(*old, doc.allocate_handle());
|
remap.insert(*old, doc.allocate_handle());
|
||||||
}
|
}
|
||||||
|
|
@ -3282,6 +3300,50 @@ fn recreate_ext_subtree(
|
||||||
remap.get(&cap.root).copied()
|
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
|
/// 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
|
/// 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
|
/// through `remap` (a handle still in the source space stays unchanged, which is
|
||||||
|
|
@ -3326,6 +3388,11 @@ fn remap_object(
|
||||||
ObjectType::XRecord(x) => {
|
ObjectType::XRecord(x) => {
|
||||||
x.handle = new_handle;
|
x.handle = new_handle;
|
||||||
x.owner = map(x.owner);
|
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) => {
|
ObjectType::Group(g) => {
|
||||||
g.handle = new_handle;
|
g.handle = new_handle;
|
||||||
|
|
@ -3334,6 +3401,59 @@ fn remap_object(
|
||||||
*h = map(*h);
|
*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
|
// 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,
|
// does, it's inserted with the fresh handle below via the caller's key,
|
||||||
// but its internal owner is left as-is (best effort).
|
// but its internal owner is left as-is (best effort).
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,7 @@ impl OpenCADStudio {
|
||||||
let name = self.unique_block_name("Block");
|
let name = self.unique_block_name("Block");
|
||||||
let base = self.clipboard_base;
|
let base = self.clipboard_base;
|
||||||
let mut entities = self.clipboard.clone();
|
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) {
|
if let Some(e) = entities.get_mut(idx) {
|
||||||
e.common_mut().xdictionary_handle = Some(root);
|
e.common_mut().xdictionary_handle = Some(root);
|
||||||
}
|
}
|
||||||
|
|
@ -255,7 +255,32 @@ impl OpenCADStudio {
|
||||||
.scene
|
.scene
|
||||||
.define_block_from_owned_entities(entities, &name, base)
|
.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
|
// Block defined; now place it interactively so the
|
||||||
// user picks the drop point (insertion uses the
|
// user picks the drop point (insertion uses the
|
||||||
// clipboard lower-left corner as the block's base). 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
|
// still being built. Acknowledge them with an honest status so the
|
||||||
// button responds instead of reporting an unknown command; each is
|
// button responds instead of reporting an unknown command; each is
|
||||||
// replaced by its real handler as the feature lands.
|
// replaced by its real handler as the feature lands.
|
||||||
// OBJECTSCALE ADD — the ribbon "Add Scale" quick action: mark the
|
// OBJECTSCALE ADD — add the active scale representation to every
|
||||||
// selected objects annotative by attaching the AcAnnotativeData XData
|
// selected object that supports per-scale context data.
|
||||||
// record the tessellator already honours, so they scale with the
|
|
||||||
// current annotation scale. Bare OBJECTSCALE opens the dialog below.
|
|
||||||
"OBJECTSCALE ADD" => {
|
"OBJECTSCALE ADD" => {
|
||||||
use acadrust::xdata::{ExtendedDataRecord, XDataValue};
|
|
||||||
let handles: Vec<acadrust::Handle> = self.tabs[i]
|
let handles: Vec<acadrust::Handle> = self.tabs[i]
|
||||||
.scene
|
.scene
|
||||||
.selected_entities()
|
.selected_entities()
|
||||||
|
|
@ -642,15 +639,23 @@ impl OpenCADStudio {
|
||||||
return Some(Task::none());
|
return Some(Task::none());
|
||||||
}
|
}
|
||||||
self.push_undo_snapshot(i, "OBJECTSCALE");
|
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;
|
let mut n = 0usize;
|
||||||
for h in &handles {
|
for h in &handles {
|
||||||
if let Some(e) = self.tabs[i].scene.document.get_entity_mut(*h) {
|
if crate::scene::annotative::create_annotation_context(
|
||||||
let xd = &mut e.common_mut().extended_data;
|
&mut self.tabs[i].scene.document,
|
||||||
if xd.get_record("AcAnnotativeData").is_none() {
|
*h,
|
||||||
let mut rec = ExtendedDataRecord::new("AcAnnotativeData");
|
scale,
|
||||||
rec.add_value(XDataValue::String("1".to_string()));
|
) {
|
||||||
xd.add_record(rec);
|
crate::scene::annotative::set_entity_annotative(
|
||||||
}
|
&mut self.tabs[i].scene.document,
|
||||||
|
*h,
|
||||||
|
true,
|
||||||
|
);
|
||||||
n += 1;
|
n += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -661,7 +666,7 @@ impl OpenCADStudio {
|
||||||
self.tabs[i].scene.bump_entities(&changes);
|
self.tabs[i].scene.bump_entities(&changes);
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
self.command_line.push_output(&format!(
|
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());
|
return Some(Task::none());
|
||||||
}
|
}
|
||||||
|
|
@ -810,6 +815,85 @@ impl OpenCADStudio {
|
||||||
self.command_line.push_info(&c.prompt());
|
self.command_line.push_info(&c.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
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 ") => {
|
cmd if cmd.starts_with("ANNOSCALE ") || cmd.starts_with("CANNOSCALE ") => {
|
||||||
let arg = cmd
|
let arg = cmd
|
||||||
.split_whitespace()
|
.split_whitespace()
|
||||||
|
|
@ -828,27 +912,21 @@ impl OpenCADStudio {
|
||||||
.push_output(&format!("Current annotation scale: {name}"));
|
.push_output(&format!("Current annotation scale: {name}"));
|
||||||
return Some(Task::none());
|
return Some(Task::none());
|
||||||
}
|
}
|
||||||
// anno multiplier = denominator / numerator: 1:50 → 50, 2:1 → 0.5.
|
let previous = self.tabs[i].scene.displayed_annotation_scale_handle();
|
||||||
let anno = if let Some((a, b)) = arg.split_once(':') {
|
match self.tabs[i].scene.set_annotation_scale_named(&arg) {
|
||||||
match (a.trim().parse::<f64>(), b.trim().parse::<f64>()) {
|
Some(handle) => {
|
||||||
(Ok(a), Ok(b)) if a != 0.0 => Some((b / a) as f32),
|
if self.annotation_auto_scale > 0 {
|
||||||
_ => None,
|
self.tabs[i].scene.add_annotation_scale_to_objects(
|
||||||
}
|
handle,
|
||||||
} else {
|
previous,
|
||||||
arg.parse::<f32>().ok()
|
self.annotation_auto_scale as u8,
|
||||||
};
|
);
|
||||||
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();
|
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_output(&format!("Annotation scale: {arg}"));
|
.push_output(&format!("Annotation scale: {arg}"));
|
||||||
}
|
}
|
||||||
_ => self
|
None => self
|
||||||
.command_line
|
.command_line
|
||||||
.push_error("Usage: ANNOSCALE <ratio> e.g. 1:50, 2:1, or a factor"),
|
.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.
|
// Annotation scale.
|
||||||
"ANNOSCALE",
|
"ANNOSCALE",
|
||||||
"CANNOSCALE",
|
"CANNOSCALE",
|
||||||
|
"ANNOALLVISIBLE",
|
||||||
|
"ANNOAUTOSCALE",
|
||||||
|
"ANNOUPDATE",
|
||||||
"SCALELISTEDIT",
|
"SCALELISTEDIT",
|
||||||
"OBJECTSCALE",
|
"OBJECTSCALE",
|
||||||
// Import CSV into a table + LandXML survey points.
|
// Import CSV into a table + LandXML survey points.
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ pub struct AppConfig {
|
||||||
pub start: StartConfig,
|
pub start: StartConfig,
|
||||||
/// Which status-bar pills the user has hidden.
|
/// Which status-bar pills the user has hidden.
|
||||||
pub statusbar: StatusBarConfig,
|
pub statusbar: StatusBarConfig,
|
||||||
|
/// Add a newly selected annotation scale to existing annotative objects.
|
||||||
|
pub annotation_auto_scale: i8,
|
||||||
/// Ribbon collapse density.
|
/// Ribbon collapse density.
|
||||||
pub ribbon: RibbonConfig,
|
pub ribbon: RibbonConfig,
|
||||||
/// Print dialog preferences (only the persisted fields; runtime state is
|
/// Print dialog preferences (only the persisted fields; runtime state is
|
||||||
|
|
@ -42,6 +44,7 @@ impl Default for AppConfig {
|
||||||
recent: RecentConfig::default(),
|
recent: RecentConfig::default(),
|
||||||
start: StartConfig::default(),
|
start: StartConfig::default(),
|
||||||
statusbar: StatusBarConfig::default(),
|
statusbar: StatusBarConfig::default(),
|
||||||
|
annotation_auto_scale: -4,
|
||||||
ribbon: RibbonConfig::default(),
|
ribbon: RibbonConfig::default(),
|
||||||
plot: PlotDialogState::default(),
|
plot: PlotDialogState::default(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -349,6 +349,8 @@ pub(super) struct OpenCADStudio {
|
||||||
cycle_candidates: Option<(iced::Point, Vec<acadrust::Handle>)>,
|
cycle_candidates: Option<(iced::Point, Vec<acadrust::Handle>)>,
|
||||||
/// Which status-bar pills the user has chosen to show (persisted).
|
/// Which status-bar pills the user has chosen to show (persisted).
|
||||||
statusbar_config: crate::ui::statusbar::statusbar_config::StatusBarConfig,
|
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
|
/// Last persisted user preferences (DYN/OSNAP/OTRACK/POLAR/…). Compared
|
||||||
/// after each message so a change is written to disk exactly once.
|
/// after each message so a change is written to disk exactly once.
|
||||||
last_saved_config: Option<config::AppConfig>,
|
last_saved_config: Option<config::AppConfig>,
|
||||||
|
|
@ -1093,6 +1095,7 @@ pub struct ClipExtObjects {
|
||||||
pub src_entity_handle: acadrust::Handle,
|
pub src_entity_handle: acadrust::Handle,
|
||||||
pub root: acadrust::Handle,
|
pub root: acadrust::Handle,
|
||||||
pub objects: Vec<(acadrust::Handle, acadrust::objects::ObjectType)>,
|
pub objects: Vec<(acadrust::Handle, acadrust::objects::ObjectType)>,
|
||||||
|
pub annotation_scales: Vec<(acadrust::Handle, acadrust::objects::Scale)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClipboardDeps {
|
impl ClipboardDeps {
|
||||||
|
|
@ -1154,11 +1157,29 @@ impl ClipboardDeps {
|
||||||
}
|
}
|
||||||
let objects = Self::collect_ext_subtree(doc, root);
|
let objects = Self::collect_ext_subtree(doc, root);
|
||||||
if !objects.is_empty() {
|
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 {
|
ext_objects.push(ClipExtObjects {
|
||||||
entity_index,
|
entity_index,
|
||||||
src_entity_handle: c.handle,
|
src_entity_handle: c.handle,
|
||||||
root,
|
root,
|
||||||
objects,
|
objects,
|
||||||
|
annotation_scales,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1873,9 +1894,12 @@ pub enum Message {
|
||||||
/// Apply the typed custom polar angle (Enter in the picker's field).
|
/// Apply the typed custom polar angle (Enter in the picker's field).
|
||||||
SubmitPolarCustom,
|
SubmitPolarCustom,
|
||||||
/// Set the model-space annotation scale (CANNOSCALE equivalent).
|
/// Set the model-space annotation scale (CANNOSCALE equivalent).
|
||||||
SetAnnotationScale(f32),
|
SetAnnotationScale(String),
|
||||||
/// Set the active viewport's custom_scale (paper space).
|
/// Set the active viewport's custom_scale (paper space).
|
||||||
SetViewportScale(f64),
|
SetViewportScale(String),
|
||||||
|
ToggleAnnotationVisibility,
|
||||||
|
ToggleAnnotationAutoAdd,
|
||||||
|
SyncViewportAnnotationScale,
|
||||||
/// Toggle the scale picker popup open/closed.
|
/// Toggle the scale picker popup open/closed.
|
||||||
ToggleScalePopup,
|
ToggleScalePopup,
|
||||||
/// Close the scale picker popup.
|
/// Close the scale picker popup.
|
||||||
|
|
@ -2667,6 +2691,7 @@ impl OpenCADStudio {
|
||||||
selection_filter_popup_open: false,
|
selection_filter_popup_open: false,
|
||||||
status_menu_tooltip_hidden: false,
|
status_menu_tooltip_hidden: false,
|
||||||
statusbar_config: crate::ui::statusbar::statusbar_config::StatusBarConfig::default(),
|
statusbar_config: crate::ui::statusbar::statusbar_config::StatusBarConfig::default(),
|
||||||
|
annotation_auto_scale: -4,
|
||||||
last_saved_config: None,
|
last_saved_config: None,
|
||||||
otrack_active: None,
|
otrack_active: None,
|
||||||
clean_screen: false,
|
clean_screen: false,
|
||||||
|
|
|
||||||
|
|
@ -657,6 +657,7 @@ impl super::OpenCADStudio {
|
||||||
1.0,
|
1.0,
|
||||||
anno,
|
anno,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
bg,
|
bg,
|
||||||
// Editor preview draws on a 2D canvas with no SDF shader — force the
|
// Editor preview draws on a 2D canvas with no SDF shader — force the
|
||||||
// glyph outline strokes so the text is visible (#308).
|
// 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);
|
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 new_panel = {
|
||||||
let selected = self.tabs[i].scene.selected_entities();
|
let selected = self.tabs[i].scene.selected_entities();
|
||||||
let mut panel = match selected.len() {
|
let mut panel = match selected.len() {
|
||||||
|
|
@ -368,9 +369,10 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
let (handle, source_entity) = selected[0];
|
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,
|
&self.tabs[i].scene.document,
|
||||||
source_entity,
|
source_entity,
|
||||||
|
annotation_scale_handle,
|
||||||
);
|
);
|
||||||
let entity = contextual.as_ref();
|
let entity = contextual.as_ref();
|
||||||
let group_names = self.tabs[i].scene.group_names_for_entity(handle);
|
let group_names = self.tabs[i].scene.group_names_for_entity(handle);
|
||||||
|
|
@ -1688,14 +1690,16 @@ impl OpenCADStudio {
|
||||||
[0.0_f64; 3]
|
[0.0_f64; 3]
|
||||||
};
|
};
|
||||||
let (new_handle, new_grips, new_grip_handles) = {
|
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 selected = self.tabs[i].scene.selected_entities();
|
||||||
let single_handle = (selected.len() == 1).then(|| selected[0].0);
|
let single_handle = (selected.len() == 1).then(|| selected[0].0);
|
||||||
let mut grips = Vec::new();
|
let mut grips = Vec::new();
|
||||||
let mut handles = Vec::new();
|
let mut handles = Vec::new();
|
||||||
for (handle, entity) in selected {
|
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,
|
&self.tabs[i].scene.document,
|
||||||
entity,
|
entity,
|
||||||
|
annotation_scale_handle,
|
||||||
);
|
);
|
||||||
for mut grip in dispatch::grips(contextual.as_ref()) {
|
for mut grip in dispatch::grips(contextual.as_ref()) {
|
||||||
// Subtract in f64: at UTM magnitudes an f32 cast before
|
// 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]) {
|
pub(super) fn invalidate_property_targets(&mut self, i: usize, handles: &[Handle]) {
|
||||||
let mut context_object_changed = false;
|
let mut context_object_changed = false;
|
||||||
for &handle in handles {
|
for &handle in handles {
|
||||||
context_object_changed |=
|
context_object_changed |= self.tabs[i]
|
||||||
crate::scene::annotative::sync_active_context_from_entity(
|
.scene
|
||||||
&mut self.tabs[i].scene.document,
|
.sync_displayed_annotation_context(handle);
|
||||||
handle,
|
|
||||||
);
|
|
||||||
// Hatch / SOLID fills render from prebuilt cached models; rebuild
|
// Hatch / SOLID fills render from prebuilt cached models; rebuild
|
||||||
// them or pattern edits (scale, background, …) stay invisible
|
// them or pattern edits (scale, background, …) stay invisible
|
||||||
// (#415).
|
// (#415).
|
||||||
|
|
@ -1790,22 +1792,6 @@ impl OpenCADStudio {
|
||||||
entity.as_entity_mut().set_layer(layer.clone());
|
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
|
// INSUNITS: when inserting a block whose BlockRecord.units differ
|
||||||
// from the host's header.insertion_units, scale the new INSERT so
|
// from the host's header.insertion_units, scale the new INSERT so
|
||||||
// 1 source-unit equals the matching host length. When either side
|
// 1 source-unit equals the matching host length. When either side
|
||||||
|
|
@ -1889,45 +1875,8 @@ impl OpenCADStudio {
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
if let Some((h, s)) = found {
|
if let Some((h, s)) = found {
|
||||||
ml.style_handle = Some(h);
|
debug_assert_eq!(h, s.handle);
|
||||||
// Inherit the style's settings so a new multileader
|
crate::scene::annotative::apply_mleader_style(ml, &s);
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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(_))
|
let new_handle = if matches!(&entity, acadrust::EntityType::Viewport(_))
|
||||||
&& self.tabs[i].scene.current_layout != "Model"
|
&& self.tabs[i].scene.current_layout != "Model"
|
||||||
|
|
@ -2011,6 +2006,22 @@ impl OpenCADStudio {
|
||||||
Some(self.tabs[i].scene.add_entity(entity))
|
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 tracks_draw_anchor {
|
||||||
if let Some(handle) = new_handle {
|
if let Some(handle) = new_handle {
|
||||||
self.tabs[i].last_draw_anchor = Some(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
|
/// Rename `old`→`new` in the backing store, re-keying table entries and
|
||||||
/// rewriting name-based references + current-style pointers.
|
/// rewriting name-based references + current-style pointers.
|
||||||
fn rename_style_storage(&mut self, kind: StyleKind, old: &str, new: &str) {
|
fn rename_style_storage(&mut self, kind: StyleKind, old: &str, new: &str) {
|
||||||
|
|
@ -331,9 +449,40 @@ impl OpenCADStudio {
|
||||||
{
|
{
|
||||||
t.style = new.to_string();
|
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 => {
|
StyleKind::Dim => {
|
||||||
let doc = &mut self.tabs[i].scene.document;
|
let doc = &mut self.tabs[i].scene.document;
|
||||||
|
|
@ -349,10 +498,23 @@ impl OpenCADStudio {
|
||||||
doc.header.current_dimstyle_name = new.to_string();
|
doc.header.current_dimstyle_name = new.to_string();
|
||||||
}
|
}
|
||||||
for e in doc.entities_mut() {
|
for e in doc.entities_mut() {
|
||||||
if let acadrust::entities::EntityType::Dimension(d) = e {
|
match e {
|
||||||
if d.base().style_name.eq_ignore_ascii_case(old) {
|
acadrust::entities::EntityType::Dimension(d)
|
||||||
|
if d.base().style_name.eq_ignore_ascii_case(old) =>
|
||||||
|
{
|
||||||
d.base_mut().style_name = new.to_string();
|
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) {
|
if self.ribbon.active_table_style.eq_ignore_ascii_case(old) {
|
||||||
self.ribbon.active_table_style = new.to_string();
|
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 => {
|
StyleKind::MLeader => {
|
||||||
let doc = &mut self.tabs[i].scene.document;
|
{
|
||||||
if let Some(h) = object_handle(doc, old, kind) {
|
let doc = &mut self.tabs[i].scene.document;
|
||||||
if let Some(ObjectType::MultiLeaderStyle(s)) = doc.objects.get_mut(&h) {
|
if let Some(h) = object_handle(doc, old, kind) {
|
||||||
s.name = new.to_string();
|
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) {
|
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) {
|
if doc.header.multiline_style.eq_ignore_ascii_case(old) {
|
||||||
doc.header.multiline_style = new.to_string();
|
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.");
|
.push_error("Cannot delete the Standard style.");
|
||||||
return;
|
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) {
|
if !self.remove_style_storage(kind, &name) {
|
||||||
return;
|
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
|
// take the resolved value directly (None = the default
|
||||||
// "Closed filled" / "ByBlock" option).
|
// "Closed filled" / "ByBlock" option).
|
||||||
let doc = &self.tabs[i].scene.document;
|
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 {
|
let resolved: Option<acadrust::Handle> = match field {
|
||||||
"mleader_style" => doc.objects.iter().find_map(|(h, o)| match o {
|
"mleader_style" => {
|
||||||
acadrust::objects::ObjectType::MultiLeaderStyle(s)
|
resolved_mleader_style.as_ref().map(|style| style.handle)
|
||||||
if s.name == value =>
|
}
|
||||||
{
|
|
||||||
Some(*h)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}),
|
|
||||||
"text_style_handle" => doc
|
"text_style_handle" => doc
|
||||||
.text_styles
|
.text_styles
|
||||||
.iter()
|
.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) {
|
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||||
continue;
|
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)
|
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
{
|
{
|
||||||
match field {
|
match field {
|
||||||
"mleader_style" => {
|
|
||||||
if let Some(h) = resolved {
|
|
||||||
ml.style_handle = Some(h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"text_style_handle" => {
|
"text_style_handle" => {
|
||||||
if let Some(h) = resolved {
|
if let Some(h) = resolved {
|
||||||
ml.text_style_handle = Some(h);
|
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") {
|
} else if matches!(field, "arrow_block" | "dim_line_lw" | "text_pos_vert") {
|
||||||
// Leader dim-var overrides picked from a dropdown. The
|
// Leader dim-var overrides picked from a dropdown. The
|
||||||
|
|
|
||||||
|
|
@ -516,6 +516,7 @@ impl OpenCADStudio {
|
||||||
section: self.start_section,
|
section: self.start_section,
|
||||||
},
|
},
|
||||||
statusbar: self.statusbar_config.clone(),
|
statusbar: self.statusbar_config.clone(),
|
||||||
|
annotation_auto_scale: self.annotation_auto_scale,
|
||||||
ribbon: crate::app::config::RibbonConfig {
|
ribbon: crate::app::config::RibbonConfig {
|
||||||
collapse: self.ribbon.collapse_mode(),
|
collapse: self.ribbon.collapse_mode(),
|
||||||
},
|
},
|
||||||
|
|
@ -545,6 +546,7 @@ impl OpenCADStudio {
|
||||||
// (`refresh_recent_thumbs`) — never here on the boot path.
|
// (`refresh_recent_thumbs`) — never here on the boot path.
|
||||||
self.start_section = cfg.start.section;
|
self.start_section = cfg.start.section;
|
||||||
self.statusbar_config = cfg.statusbar;
|
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.ribbon.set_collapse_mode(cfg.ribbon.collapse);
|
||||||
self.plot_dialog = cfg.plot;
|
self.plot_dialog = cfg.plot;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2589,17 +2589,60 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
Message::SetAnnotationScale(scale) => {
|
Message::SetAnnotationScale(scale) => {
|
||||||
self.scale_popup_open = false;
|
self.scale_popup_open = false;
|
||||||
|
let auto_scale = self.annotation_auto_scale;
|
||||||
if let Some(tab) = self.tabs.get_mut(self.active_tab) {
|
if let Some(tab) = self.tabs.get_mut(self.active_tab) {
|
||||||
tab.scene.annotation_scale = scale;
|
let previous = tab.scene.displayed_annotation_scale_handle();
|
||||||
util::sync_annotation_scale_header(&mut tab.scene);
|
if let Some(handle) = tab.scene.set_annotation_scale_named(&scale) {
|
||||||
tab.scene.invalidate_annotation_dependencies();
|
if auto_scale > 0 {
|
||||||
|
tab.scene.add_annotation_scale_to_objects(
|
||||||
|
handle,
|
||||||
|
previous,
|
||||||
|
auto_scale as u8,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
tab.dirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::SetViewportScale(scale) => {
|
Message::SetViewportScale(scale) => {
|
||||||
self.scale_popup_open = false;
|
self.scale_popup_open = false;
|
||||||
|
let auto_scale = self.annotation_auto_scale;
|
||||||
if let Some(tab) = self.tabs.get_mut(self.active_tab) {
|
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()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -2647,22 +2690,17 @@ impl OpenCADStudio {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles = self.property_target_handles(i);
|
let handles = self.property_target_handles(i);
|
||||||
if handles.len() == 1 {
|
if handles.len() == 1 {
|
||||||
// Only object types that carry a per-object context.
|
let ok = self.tabs[i]
|
||||||
let ok = matches!(
|
.scene
|
||||||
self.tabs[i].scene.document.get_entity(handles[0]),
|
.document
|
||||||
Some(
|
.get_entity(handles[0])
|
||||||
acadrust::EntityType::Text(_)
|
.is_some_and(crate::scene::annotative::supports_annotation_context);
|
||||||
| acadrust::EntityType::MText(_)
|
|
||||||
| acadrust::EntityType::Insert(_)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if ok {
|
if ok {
|
||||||
self.anno_object_scale_target = Some(handles[0]);
|
self.anno_object_scale_target = Some(handles[0]);
|
||||||
self.active_modal = Some(crate::app::ModalKind::AnnoObjectScale);
|
self.active_modal = Some(crate::app::ModalKind::AnnoObjectScale);
|
||||||
} else {
|
} else {
|
||||||
self.command_line.push_info(
|
self.command_line
|
||||||
"OBJECTSCALE applies to a single Text, MText or block reference.",
|
.push_info("The selected object does not support annotation scales.");
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.command_line
|
self.command_line
|
||||||
|
|
@ -2816,21 +2854,16 @@ impl OpenCADStudio {
|
||||||
// never rolled back when the manager closes.
|
// never rolled back when the manager closes.
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let sel = self.scale_manager_selected.clone();
|
let sel = self.scale_manager_selected.clone();
|
||||||
if let Some((_, anno, _)) = self
|
let previous = self.tabs[i].scene.displayed_annotation_scale_handle();
|
||||||
.tabs[i]
|
if let Some(scale) = self.tabs[i].scene.set_annotation_scale_named(&sel) {
|
||||||
.scene
|
if self.annotation_auto_scale > 0 {
|
||||||
.scale_list()
|
self.tabs[i].scene.add_annotation_scale_to_objects(
|
||||||
.into_iter()
|
scale,
|
||||||
.find(|(n, _, _)| n.eq_ignore_ascii_case(&sel))
|
previous,
|
||||||
{
|
self.annotation_auto_scale as u8,
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.tabs[i].scene.invalidate_annotation_dependencies();
|
self.tabs[i].dirty = true;
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -3657,7 +3690,7 @@ impl OpenCADStudio {
|
||||||
// object. Off is handled inside set_entity_*.
|
// object. Off is handled inside set_entity_*.
|
||||||
if !cur {
|
if !cur {
|
||||||
if let Some(sh) =
|
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(
|
crate::scene::annotative::create_annotation_context(
|
||||||
&mut self.tabs[i].scene.document,
|
&mut self.tabs[i].scene.document,
|
||||||
|
|
|
||||||
|
|
@ -299,7 +299,12 @@ impl OpenCADStudio {
|
||||||
set_f64!(dimcen, self.ds_dimcen);
|
set_f64!(dimcen, self.ds_dimcen);
|
||||||
set_f64!(dimtsz, self.ds_dimtsz);
|
set_f64!(dimtsz, self.ds_dimtsz);
|
||||||
set_f64!(dimtxt, self.ds_dimtxt);
|
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!(dimlfac, self.ds_dimlfac);
|
||||||
set_f64!(dimtp, self.ds_dimtp);
|
set_f64!(dimtp, self.ds_dimtp);
|
||||||
set_f64!(dimtm, self.ds_dimtm);
|
set_f64!(dimtm, self.ds_dimtm);
|
||||||
|
|
@ -442,7 +447,12 @@ impl OpenCADStudio {
|
||||||
Dimtoh => self.ds_dimtoh = !self.ds_dimtoh,
|
Dimtoh => self.ds_dimtoh = !self.ds_dimtoh,
|
||||||
Dimtol => self.ds_dimtol = !self.ds_dimtol,
|
Dimtol => self.ds_dimtol = !self.ds_dimtol,
|
||||||
Dimlim => self.ds_dimlim = !self.ds_dimlim,
|
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,
|
Dimfxlon => self.ds_dimfxlon = !self.ds_dimfxlon,
|
||||||
Dimsah => self.ds_dimsah = !self.ds_dimsah,
|
Dimsah => self.ds_dimsah = !self.ds_dimsah,
|
||||||
Dimtxtdirection => self.ds_dimtxtdirection = !self.ds_dimtxtdirection,
|
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).
|
/// Parse a scale string like "1:50" or "2:1" into (numerator, denominator).
|
||||||
/// Returns (1.0, 1.0) for "Fit" or unknown formats.
|
/// Returns (1.0, 1.0) for "Fit" or unknown formats.
|
||||||
/// Sync the model-space annotation scale into the standard CANNOSCALE /
|
/// Sync the model-space annotation scale into its named drawing variable and
|
||||||
/// CANNOSCALEVALUE header variables before a save, so the scale round-trips
|
/// numeric header mirror before a save.
|
||||||
/// through the file (and is read correctly by other CAD applications).
|
|
||||||
pub(super) fn sync_annotation_scale_header(scene: &mut Scene) {
|
pub(super) fn sync_annotation_scale_header(scene: &mut Scene) {
|
||||||
let anno = scene.annotation_scale;
|
let anno = scene.annotation_scale;
|
||||||
let value = if anno.abs() > 1e-9 {
|
let value = if anno.abs() > 1e-9 {
|
||||||
|
|
@ -14,17 +13,25 @@ pub(super) fn sync_annotation_scale_header(scene: &mut Scene) {
|
||||||
} else {
|
} else {
|
||||||
1.0
|
1.0
|
||||||
};
|
};
|
||||||
// Prefer the name of a matching scale already in the drawing's list;
|
let current = scene.document.header.current_annotation_scale.clone();
|
||||||
// fall back to a formatted ratio when none matches.
|
let current_matches = scene.scale_list().into_iter().any(|(name, factor, _)| {
|
||||||
let name = scene
|
name.eq_ignore_ascii_case(¤t)
|
||||||
.scale_list()
|
&& (factor - anno).abs() < 0.001 * anno.max(0.001)
|
||||||
.into_iter()
|
});
|
||||||
.find(|(_, a, _)| (a - anno).abs() < 0.001 * anno.max(0.001))
|
let name = if current_matches {
|
||||||
.map(|(n, _, _)| n)
|
current
|
||||||
.unwrap_or_else(|| format_annotation_scale_name(anno));
|
} 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;
|
let hdr = &mut scene.document.header;
|
||||||
hdr.current_annotation_scale = name;
|
hdr.current_annotation_scale = name.clone();
|
||||||
hdr.annotation_scale_value = value;
|
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",
|
/// 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] {
|
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]
|
[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(),
|
layout_names: layout_names.clone(),
|
||||||
polar_custom_input: &self.polar_custom_input,
|
polar_custom_input: &self.polar_custom_input,
|
||||||
scale_is_model: is_model,
|
scale_is_model: is_model,
|
||||||
|
current_scale_name: tab.scene.displayed_annotation_scale_name(),
|
||||||
scale_list: tab.scene.scale_picker_list(),
|
scale_list: tab.scene.scale_picker_list(),
|
||||||
has_selection: !tab.scene.selected.is_empty(),
|
has_selection: !tab.scene.selected.is_empty(),
|
||||||
selection_types: tab
|
selection_types: tab
|
||||||
|
|
@ -1501,6 +1502,9 @@ impl OpenCADStudio {
|
||||||
self.show_layout_tabs,
|
self.show_layout_tabs,
|
||||||
tab.scene.annotation_scale,
|
tab.scene.annotation_scale,
|
||||||
scale_pill_enabled,
|
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,
|
tab.scene.document.header.lineweight_display,
|
||||||
cursor_coord,
|
cursor_coord,
|
||||||
coords_mode,
|
coords_mode,
|
||||||
|
|
|
||||||
|
|
@ -274,6 +274,7 @@ pub(crate) fn append_insert_attribute_wires(
|
||||||
// above, so the text path must not scale it a second time.
|
// above, so the text path must not scale it a second time.
|
||||||
1.0,
|
1.0,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
bg_color,
|
bg_color,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
|
||||||
let nan = [f64::NAN; 3];
|
let nan = [f64::NAN; 3];
|
||||||
let p3 = |v: &acadrust::types::Vector3| -> [f64; 3] { [v.x, v.y, v.z] };
|
let p3 = |v: &acadrust::types::Vector3| -> [f64; 3] { [v.x, v.y, v.z] };
|
||||||
|
|
||||||
let arrow_size = ml.arrowhead_size;
|
let arrow_size = ml.context.arrowhead_size;
|
||||||
let draw_arrow = arrow_size > 0.0;
|
let draw_arrow = arrow_size > 0.0;
|
||||||
let invisible = ml.path_type == MultiLeaderPathType::Invisible;
|
let invisible = ml.path_type == MultiLeaderPathType::Invisible;
|
||||||
|
|
||||||
|
|
@ -191,7 +191,11 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
let dogleg = if ml.enable_landing && ml.enable_dogleg {
|
let dogleg = if ml.enable_landing && ml.enable_dogleg {
|
||||||
ml.dogleg_length.max(0.0)
|
ml.context
|
||||||
|
.leader_roots
|
||||||
|
.first()
|
||||||
|
.map(|root| root.landing_distance.max(0.0))
|
||||||
|
.unwrap_or_else(|| ml.dogleg_length.max(0.0))
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
|
|
@ -1317,16 +1321,17 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Scaling ──────────────────────────────────────────────────────────────
|
// ── Scaling ──────────────────────────────────────────────────────────────
|
||||||
// ml.scale_factor is always applied; anno_scale is only applied when the
|
// Used only when a context omits an already-resolved content size.
|
||||||
// multileader is marked annotative.
|
let fallback_content_scale = (ml.scale_factor as f32)
|
||||||
let effective_scale = (ml.scale_factor as f32)
|
|
||||||
* if crate::scene::annotative::mleader_is_annotative(document, ml) {
|
* if crate::scene::annotative::mleader_is_annotative(document, ml) {
|
||||||
anno_scale
|
anno_scale
|
||||||
} else {
|
} else {
|
||||||
1.0
|
1.0
|
||||||
};
|
};
|
||||||
|
|
||||||
let arrow_size = ml.arrowhead_size as f32 * effective_scale;
|
// The active context stores the resolved world-space arrow size.
|
||||||
|
// Reapplying the entity scale here makes context-sized arrows grow twice.
|
||||||
|
let arrow_size = ml.context.arrowhead_size as f32;
|
||||||
let draw_arrow = arrow_size > 0.0;
|
let draw_arrow = arrow_size > 0.0;
|
||||||
let invisible = ml.path_type == MultiLeaderPathType::Invisible;
|
let invisible = ml.path_type == MultiLeaderPathType::Invisible;
|
||||||
// arrowhead_handle resolves through the block records to a named arrow
|
// arrowhead_handle resolves through the block records to a named arrow
|
||||||
|
|
@ -1452,7 +1457,10 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
ml.text_attachment_direction,
|
ml.text_attachment_direction,
|
||||||
acadrust::entities::multileader::TextAttachmentDirectionType::Vertical
|
acadrust::entities::multileader::TextAttachmentDirectionType::Vertical
|
||||||
);
|
);
|
||||||
if ml.enable_landing && ml.enable_dogleg && ml.dogleg_length > 0.0 && !vertical_attach
|
if ml.enable_landing
|
||||||
|
&& ml.enable_dogleg
|
||||||
|
&& root.landing_distance > 0.0
|
||||||
|
&& !vertical_attach
|
||||||
{
|
{
|
||||||
// Horizontal landing (dogleg) from the leader elbow (connection
|
// Horizontal landing (dogleg) from the leader elbow (connection
|
||||||
// point) toward the text side. The stored geometry places the
|
// point) toward the text side. The stored geometry places the
|
||||||
|
|
@ -1460,7 +1468,9 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
// dogleg end, so the dogleg stops here — drawing on to
|
// dogleg end, so the dogleg stops here — drawing on to
|
||||||
// text_location (the block's top-left insertion) would streak a
|
// text_location (the block's top-left insertion) would streak a
|
||||||
// stray line up the side of the text.
|
// stray line up the side of the text.
|
||||||
let d = ml.dogleg_length * effective_scale as f64;
|
// Landing distance belongs to the selected leader-root context
|
||||||
|
// and is already resolved in world units.
|
||||||
|
let d = root.landing_distance;
|
||||||
// The dogleg runs along the leader root's stored direction —
|
// The dogleg runs along the leader root's stored direction —
|
||||||
// for a rotated leader that is the angled baseline, not world
|
// for a rotated leader that is the angled baseline, not world
|
||||||
// X. Roots without a usable direction keep the legacy
|
// X. Roots without a usable direction keep the legacy
|
||||||
|
|
@ -1569,6 +1579,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
leader_lw_px,
|
leader_lw_px,
|
||||||
1.0,
|
1.0,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
bg_color,
|
bg_color,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
@ -1604,7 +1615,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
let height = if ctx.text_height > 0.0 {
|
let height = if ctx.text_height > 0.0 {
|
||||||
ctx.text_height as f32
|
ctx.text_height as f32
|
||||||
} else {
|
} else {
|
||||||
ml.text_height as f32 * effective_scale
|
ml.text_height as f32 * fallback_content_scale
|
||||||
};
|
};
|
||||||
|
|
||||||
let ins = &ctx.text_location;
|
let ins = &ctx.text_location;
|
||||||
|
|
|
||||||
|
|
@ -1095,14 +1095,44 @@ fn vardict_value(doc: &CadDocument, name: &str) -> Option<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a value into an existing variable-dictionary entry. No-op when the
|
/// Write a drawing variable, creating the variable dictionary and record when
|
||||||
/// entry is absent (e.g. a brand-new document with no variable dictionary).
|
/// needed so new drawings preserve the value too.
|
||||||
fn set_vardict_value(doc: &mut CadDocument, name: &str, value: &str) {
|
pub(crate) fn set_drawing_variable(doc: &mut CadDocument, name: &str, value: &str) {
|
||||||
use acadrust::objects::ObjectType;
|
use acadrust::objects::{Dictionary, DictionaryVariable, ObjectType};
|
||||||
if let Some(h) = vardict_handle(doc, name) {
|
if let Some(h) = vardict_handle(doc, name) {
|
||||||
if let Some(ObjectType::DictionaryVariable(v)) = doc.objects.get_mut(&h) {
|
if let Some(ObjectType::DictionaryVariable(v)) = doc.objects.get_mut(&h) {
|
||||||
v.value = value.to_string();
|
v.value = value.to_string();
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let root = crate::scene::annotative::root_named_dict_handle(doc);
|
||||||
|
let variable_dictionary = crate::scene::annotative::as_dict(doc, root)
|
||||||
|
.and_then(|dictionary| dictionary.get("AcDbVariableDictionary"))
|
||||||
|
.filter(|handle| {
|
||||||
|
matches!(doc.objects.get(handle), Some(ObjectType::Dictionary(_)))
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let handle = doc.allocate_handle();
|
||||||
|
let mut dictionary = Dictionary::new();
|
||||||
|
dictionary.handle = handle;
|
||||||
|
dictionary.owner = root;
|
||||||
|
doc.objects
|
||||||
|
.insert(handle, ObjectType::Dictionary(dictionary));
|
||||||
|
if let Some(ObjectType::Dictionary(root_dictionary)) = doc.objects.get_mut(&root) {
|
||||||
|
root_dictionary.add_entry("AcDbVariableDictionary", handle);
|
||||||
|
}
|
||||||
|
handle
|
||||||
|
});
|
||||||
|
|
||||||
|
let handle = doc.allocate_handle();
|
||||||
|
let mut variable = DictionaryVariable::new(name, value);
|
||||||
|
variable.handle = handle;
|
||||||
|
variable.owner_handle = variable_dictionary;
|
||||||
|
doc.objects
|
||||||
|
.insert(handle, ObjectType::DictionaryVariable(variable));
|
||||||
|
if let Some(ObjectType::Dictionary(dictionary)) = doc.objects.get_mut(&variable_dictionary) {
|
||||||
|
dictionary.add_entry(name, handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1119,26 +1149,7 @@ pub fn saved_active_layout(doc: &CadDocument) -> Option<String> {
|
||||||
/// carried it (e.g. a document authored here from scratch) — otherwise the exact
|
/// carried it (e.g. a document authored here from scratch) — otherwise the exact
|
||||||
/// paper layout would be lost and reopening fell back to the first paper tab.
|
/// paper layout would be lost and reopening fell back to the first paper tab.
|
||||||
pub fn set_saved_active_layout(doc: &mut CadDocument, name: &str) {
|
pub fn set_saved_active_layout(doc: &mut CadDocument, name: &str) {
|
||||||
use acadrust::objects::{DictionaryVariable, ObjectType};
|
set_drawing_variable(doc, "CTAB", name);
|
||||||
if let Some(h) = vardict_handle(doc, "CTAB") {
|
|
||||||
if let Some(ObjectType::DictionaryVariable(v)) = doc.objects.get_mut(&h) {
|
|
||||||
v.value = name.to_string();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Attach a new CTAB entry to the root named-object dictionary. Resolve it
|
|
||||||
// robustly (or synthesise one) so the current-tab record persists even on a
|
|
||||||
// from-scratch document, or a foreign DWG whose header root pointer is
|
|
||||||
// unresolvable. See `annotative::root_named_dict_handle`.
|
|
||||||
let root = crate::scene::annotative::root_named_dict_handle(doc);
|
|
||||||
let handle = doc.allocate_handle();
|
|
||||||
let mut var = DictionaryVariable::new("CTAB", name);
|
|
||||||
var.handle = handle;
|
|
||||||
var.owner_handle = root;
|
|
||||||
doc.objects.insert(handle, ObjectType::DictionaryVariable(var));
|
|
||||||
if let Some(ObjectType::Dictionary(rd)) = doc.objects.get_mut(&root) {
|
|
||||||
rd.entries.push(("CTAB".to_string(), handle));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Materialise the current-style choices into their format-specific storage
|
/// Materialise the current-style choices into their format-specific storage
|
||||||
|
|
@ -1181,8 +1192,10 @@ fn sync_current_styles_on_save(doc: &mut CadDocument) {
|
||||||
|
|
||||||
let table = doc.header.current_table_style_name.clone();
|
let table = doc.header.current_table_style_name.clone();
|
||||||
let mleader = doc.header.current_mleader_style_name.clone();
|
let mleader = doc.header.current_mleader_style_name.clone();
|
||||||
set_vardict_value(doc, "CTABLESTYLE", &table);
|
set_drawing_variable(doc, "CTABLESTYLE", &table);
|
||||||
set_vardict_value(doc, "CMLEADERSTYLE", &mleader);
|
set_drawing_variable(doc, "CMLEADERSTYLE", &mleader);
|
||||||
|
let annotation = doc.header.current_annotation_scale.clone();
|
||||||
|
set_drawing_variable(doc, "CANNOSCALE", &annotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Corrupt-entity guard ──────────────────────────────────────────────────
|
// ── Corrupt-entity guard ──────────────────────────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,14 @@
|
||||||
//! scale) must agree on *which* entities are annotative — so that logic lives
|
//! scale) must agree on *which* entities are annotative — so that logic lives
|
||||||
//! here, once. An entity is annotative if it carries a per-object annotation
|
//! here, once. An entity is annotative if it carries a per-object annotation
|
||||||
//! context, legacy annotative XDATA, or an entity-level annotative flag. Text
|
//! context, legacy annotative XDATA, or an entity-level annotative flag. Text
|
||||||
//! style state is consulted only while creating a new text object: changing a
|
//! style state is consulted while creating an object and when an explicit
|
||||||
//! style later must not retroactively scale existing text.
|
//! annotation-style update is requested; changing a style alone does not
|
||||||
|
//! retroactively scale existing text.
|
||||||
|
|
||||||
use acadrust::entities::{EntityCommon, EntityType};
|
use acadrust::entities::{EntityCommon, EntityType};
|
||||||
use acadrust::objects::{
|
use acadrust::objects::{
|
||||||
Dictionary, HatchScaleContext, MTextContext, ObjectContextData, ObjectContextKind, ObjectType,
|
Dictionary, DimContext, DimSubtype, EmbeddedMTextContext, HatchScaleContext,
|
||||||
|
MTextAttributeContext, MTextContext, ObjectContextData, ObjectContextKind, ObjectType,
|
||||||
};
|
};
|
||||||
use acadrust::types::{Vector2, Vector3};
|
use acadrust::types::{Vector2, Vector3};
|
||||||
use acadrust::{CadDocument, Handle};
|
use acadrust::{CadDocument, Handle};
|
||||||
|
|
@ -72,7 +74,7 @@ pub fn root_named_dict_handle(doc: &mut CadDocument) -> Handle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the per-object annotative flag on the entity types that carry one
|
/// Set the per-object annotative flag on the entity types that carry one
|
||||||
/// (MTEXT, MULTILEADER). Turning it off also strips the per-object annotation
|
/// (MTEXT, MULTILEADER, ATTRIB and ATTDEF). Turning it off also strips the per-object annotation
|
||||||
/// context and legacy markers via [`clear_annotation_context`] so the object
|
/// context and legacy markers via [`clear_annotation_context`] so the object
|
||||||
/// stops resolving annotative; turning it on leaves the base geometry as the
|
/// stops resolving annotative; turning it on leaves the base geometry as the
|
||||||
/// single (implicit, current-scale) representation. TEXT uses a context rather
|
/// single (implicit, current-scale) representation. TEXT uses a context rather
|
||||||
|
|
@ -82,6 +84,8 @@ pub fn set_entity_annotative(doc: &mut CadDocument, handle: Handle, want: bool)
|
||||||
match e {
|
match e {
|
||||||
EntityType::MText(t) => t.is_annotative = want,
|
EntityType::MText(t) => t.is_annotative = want,
|
||||||
EntityType::MultiLeader(m) => m.enable_annotation_scale = want,
|
EntityType::MultiLeader(m) => m.enable_annotation_scale = want,
|
||||||
|
EntityType::AttributeEntity(attribute) => attribute.flags.annotative = want,
|
||||||
|
EntityType::AttributeDefinition(attribute) => attribute.flags.annotative = want,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -92,9 +96,121 @@ pub fn set_entity_annotative(doc: &mut CadDocument, handle: Handle, want: bool)
|
||||||
|
|
||||||
/// Derive the per-scale context payload for an entity from its current
|
/// Derive the per-scale context payload for an entity from its current
|
||||||
/// placement. Returns the concrete class name and the context kind, or `None`
|
/// placement. Returns the concrete class name and the context kind, or `None`
|
||||||
/// for entity types that do not carry a per-object annotation context (their
|
/// for entity types that do not carry a per-object annotation context.
|
||||||
/// annotative state comes from a style, e.g. DIMENSION/TABLE).
|
fn dimension_context_for(doc: &CadDocument, dimension: &acadrust::entities::Dimension) -> Option<DimContext> {
|
||||||
fn context_kind_for(entity: &EntityType) -> Option<(&'static str, ObjectContextKind)> {
|
use acadrust::entities::Dimension;
|
||||||
|
|
||||||
|
let subtype = match dimension {
|
||||||
|
Dimension::Aligned(dim) => DimSubtype::Aligned {
|
||||||
|
dimline_pt: dim.definition_point,
|
||||||
|
},
|
||||||
|
Dimension::Linear(dim) => DimSubtype::Aligned {
|
||||||
|
dimline_pt: dim.definition_point,
|
||||||
|
},
|
||||||
|
Dimension::Angular2Ln(dim) => DimSubtype::Angular {
|
||||||
|
arc_pt: dim.dimension_arc,
|
||||||
|
},
|
||||||
|
Dimension::Angular3Pt(dim) => DimSubtype::Angular {
|
||||||
|
arc_pt: dim.definition_point,
|
||||||
|
},
|
||||||
|
Dimension::Diameter(dim) => DimSubtype::Diametric {
|
||||||
|
first_arc_pt: dim.angle_vertex,
|
||||||
|
def_pt: dim.definition_point,
|
||||||
|
},
|
||||||
|
Dimension::Radius(dim) => DimSubtype::Radial {
|
||||||
|
first_arc_pt: dim.definition_point,
|
||||||
|
},
|
||||||
|
Dimension::LargeRadial(dim) => DimSubtype::RadialLarge {
|
||||||
|
ovr_center: dim.override_center,
|
||||||
|
jog_point: dim.jog_point,
|
||||||
|
},
|
||||||
|
Dimension::Ordinate(dim) => DimSubtype::Ordinate {
|
||||||
|
feature_location_pt: dim.feature_location,
|
||||||
|
leader_endpt: dim.leader_endpoint,
|
||||||
|
},
|
||||||
|
Dimension::Arc(_) => return None,
|
||||||
|
};
|
||||||
|
let base = dimension.base();
|
||||||
|
let block = doc
|
||||||
|
.block_records
|
||||||
|
.iter()
|
||||||
|
.find(|record| record.name.eq_ignore_ascii_case(&base.block_name))
|
||||||
|
.map(|record| record.handle)
|
||||||
|
.unwrap_or(Handle::NULL);
|
||||||
|
Some(DimContext {
|
||||||
|
def_pt: Vector2::new(base.text_middle_point.x, base.text_middle_point.y),
|
||||||
|
is_def_textloc: base.text_user_positioned,
|
||||||
|
text_rotation: base.text_rotation,
|
||||||
|
block,
|
||||||
|
b293: false,
|
||||||
|
dimtofl: false,
|
||||||
|
dimosxd: false,
|
||||||
|
dimatfit: false,
|
||||||
|
dimtix: false,
|
||||||
|
dimtmove: false,
|
||||||
|
override_code: 0,
|
||||||
|
has_arrow2: false,
|
||||||
|
flip_arrow2: base.flip_arrow2,
|
||||||
|
flip_arrow1: base.flip_arrow1,
|
||||||
|
subtype,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mtext_context_for(m: &acadrust::entities::MText) -> MTextContext {
|
||||||
|
MTextContext {
|
||||||
|
attachment: m.attachment_point as i32,
|
||||||
|
x_axis_dir: m
|
||||||
|
.dwg_x_direction
|
||||||
|
.unwrap_or_else(|| Vector3::new(m.rotation.cos(), m.rotation.sin(), 0.0)),
|
||||||
|
insertion: m.insertion_point,
|
||||||
|
rect_width: m.rectangle_width,
|
||||||
|
rect_height: m.rectangle_height.unwrap_or(0.0),
|
||||||
|
extents_width: m.extents_width,
|
||||||
|
extents_height: m.extents_height,
|
||||||
|
column_type: m.column_data.column_type as i32,
|
||||||
|
columns: (m.column_data.column_type != 0).then(|| acadrust::objects::MTextColumns {
|
||||||
|
num_heights: m.column_data.column_count,
|
||||||
|
width: m.column_data.width,
|
||||||
|
gutter: m.column_data.gutter,
|
||||||
|
auto_height: m.column_data.auto_height,
|
||||||
|
flow_reversed: m.column_data.flow_reversed,
|
||||||
|
heights: m.column_data.heights.clone(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attribute_context_for(
|
||||||
|
insertion: Vector3,
|
||||||
|
alignment: Vector3,
|
||||||
|
rotation: f64,
|
||||||
|
horizontal_mode: i16,
|
||||||
|
embedded: Option<&acadrust::entities::MText>,
|
||||||
|
scale: Handle,
|
||||||
|
) -> MTextAttributeContext {
|
||||||
|
MTextAttributeContext {
|
||||||
|
horizontal_mode,
|
||||||
|
rotation,
|
||||||
|
insertion: Vector2::new(insertion.x, insertion.y),
|
||||||
|
alignment: Vector2::new(alignment.x, alignment.y),
|
||||||
|
enable_context: embedded.is_some(),
|
||||||
|
context: embedded.map(|mtext| EmbeddedMTextContext {
|
||||||
|
owner_handle: Handle::NULL,
|
||||||
|
reactors: Vec::new(),
|
||||||
|
xdictionary_handle: None,
|
||||||
|
has_binary_data: false,
|
||||||
|
class_version: 3,
|
||||||
|
is_default: false,
|
||||||
|
scale,
|
||||||
|
mtext: mtext_context_for(mtext),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn context_kind_for(
|
||||||
|
doc: &CadDocument,
|
||||||
|
entity: &EntityType,
|
||||||
|
scale: Handle,
|
||||||
|
) -> Option<(&'static str, ObjectContextKind)> {
|
||||||
match entity {
|
match entity {
|
||||||
EntityType::Insert(ins) => Some((
|
EntityType::Insert(ins) => Some((
|
||||||
"ACDB_BLKREFOBJECTCONTEXTDATA_CLASS",
|
"ACDB_BLKREFOBJECTCONTEXTDATA_CLASS",
|
||||||
|
|
@ -118,23 +234,126 @@ fn context_kind_for(entity: &EntityType) -> Option<(&'static str, ObjectContextK
|
||||||
)),
|
)),
|
||||||
EntityType::MText(m) => Some((
|
EntityType::MText(m) => Some((
|
||||||
"ACDB_MTEXTOBJECTCONTEXTDATA_CLASS",
|
"ACDB_MTEXTOBJECTCONTEXTDATA_CLASS",
|
||||||
ObjectContextKind::MText(MTextContext {
|
ObjectContextKind::MText(mtext_context_for(m)),
|
||||||
attachment: m.attachment_point as i32,
|
)),
|
||||||
// MTEXT stores a text X-axis direction; derive it from rotation.
|
EntityType::Dimension(dimension) => {
|
||||||
x_axis_dir: Vector3::new(m.rotation.cos(), m.rotation.sin(), 0.0),
|
let context = dimension_context_for(doc, dimension)?;
|
||||||
insertion: m.insertion_point,
|
Some((context.subtype.class_name(), ObjectContextKind::Dim(context)))
|
||||||
rect_width: m.rectangle_width,
|
}
|
||||||
rect_height: 0.0,
|
EntityType::MultiLeader(mleader) => Some((
|
||||||
extents_width: 0.0,
|
"ACDB_MLEADEROBJECTCONTEXTDATA_CLASS",
|
||||||
extents_height: 0.0,
|
ObjectContextKind::MLeader(mleader.context.clone()),
|
||||||
column_type: 0,
|
)),
|
||||||
columns: None,
|
EntityType::AttributeEntity(attribute) => Some((
|
||||||
|
"ACDB_MTEXTATTRIBUTEOBJECTCONTEXTDATA_CLASS",
|
||||||
|
ObjectContextKind::MTextAttribute(attribute_context_for(
|
||||||
|
attribute.insertion_point,
|
||||||
|
attribute.alignment_point,
|
||||||
|
attribute.rotation,
|
||||||
|
attribute.horizontal_alignment.to_value(),
|
||||||
|
attribute.embedded_mtext.as_deref(),
|
||||||
|
scale,
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
EntityType::AttributeDefinition(attribute) => Some((
|
||||||
|
"ACDB_MTEXTATTRIBUTEOBJECTCONTEXTDATA_CLASS",
|
||||||
|
ObjectContextKind::MTextAttribute(attribute_context_for(
|
||||||
|
attribute.insertion_point,
|
||||||
|
attribute.alignment_point,
|
||||||
|
attribute.rotation,
|
||||||
|
attribute.horizontal_alignment.to_value(),
|
||||||
|
attribute.embedded_mtext.as_deref(),
|
||||||
|
scale,
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
EntityType::Leader(leader) => Some((
|
||||||
|
"ACDB_LEADEROBJECTCONTEXTDATA_CLASS",
|
||||||
|
ObjectContextKind::Leader(acadrust::objects::LeaderContext {
|
||||||
|
points: leader.vertices.clone(),
|
||||||
|
x_direction: leader.horizontal_direction,
|
||||||
|
annotation_enabled: !leader.annotation_handle.is_null(),
|
||||||
|
insertion_offset: Vector3::ZERO,
|
||||||
|
endpoint_projection: leader.annotation_offset,
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
EntityType::Tolerance(tolerance) => Some((
|
||||||
|
"ACDB_FCFOBJECTCONTEXTDATA_CLASS",
|
||||||
|
ObjectContextKind::Fcf {
|
||||||
|
location: tolerance.insertion_point,
|
||||||
|
horizontal_direction: tolerance.direction,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
EntityType::Hatch(hatch) => Some((
|
||||||
|
"ACDB_HATCHSCALECONTEXTDATA_CLASS",
|
||||||
|
ObjectContextKind::HatchScale(HatchScaleContext {
|
||||||
|
pattern_lines: hatch.pattern.lines.clone(),
|
||||||
|
pattern_scale: hatch.pattern_scale,
|
||||||
|
pattern_base: Vector3::ZERO,
|
||||||
|
loop_types: hatch
|
||||||
|
.paths
|
||||||
|
.iter()
|
||||||
|
.map(|path| path.flags.bits() as i32)
|
||||||
|
.collect(),
|
||||||
|
supports_context: true,
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn supports_annotation_context(entity: &EntityType) -> bool {
|
||||||
|
match entity {
|
||||||
|
EntityType::Insert(_)
|
||||||
|
| EntityType::Text(_)
|
||||||
|
| EntityType::MText(_)
|
||||||
|
| EntityType::MultiLeader(_)
|
||||||
|
| EntityType::AttributeEntity(_)
|
||||||
|
| EntityType::AttributeDefinition(_)
|
||||||
|
| EntityType::Leader(_)
|
||||||
|
| EntityType::Tolerance(_)
|
||||||
|
| EntityType::Hatch(_) => true,
|
||||||
|
EntityType::Dimension(dimension) => {
|
||||||
|
!matches!(dimension, acadrust::entities::Dimension::Arc(_))
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_context_class(doc: &mut CadDocument, dxf_name: &str) {
|
||||||
|
doc.register_object_context_class(dxf_name);
|
||||||
|
if doc.classes.get_by_name(dxf_name).is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let cpp_name = match dxf_name {
|
||||||
|
"ACDB_MLEADEROBJECTCONTEXTDATA_CLASS" => "AcDbMLeaderObjectContextData",
|
||||||
|
"ACDB_MTEXTATTRIBUTEOBJECTCONTEXTDATA_CLASS" => "AcDbMTextAttributeObjectContextData",
|
||||||
|
"ACDB_LEADEROBJECTCONTEXTDATA_CLASS" => "AcDbLeaderObjectContextData",
|
||||||
|
"ACDB_FCFOBJECTCONTEXTDATA_CLASS" => "AcDbFcfObjectContextData",
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
use acadrust::classes::{DxfClass, ProxyFlags};
|
||||||
|
let proxy_flags = ProxyFlags(
|
||||||
|
ProxyFlags::ERASE_ALLOWED.0
|
||||||
|
| ProxyFlags::CLONING_ALLOWED.0
|
||||||
|
| ProxyFlags::DISABLES_PROXY_WARNING_DIALOG.0,
|
||||||
|
);
|
||||||
|
doc.classes.add_or_update(DxfClass {
|
||||||
|
dxf_name: dxf_name.to_string(),
|
||||||
|
cpp_class_name: cpp_name.to_string(),
|
||||||
|
application_name: "ObjectDBX Classes".to_string(),
|
||||||
|
proxy_flags,
|
||||||
|
instance_count: 0,
|
||||||
|
was_zombie: false,
|
||||||
|
is_an_entity: false,
|
||||||
|
class_number: 0,
|
||||||
|
item_class_id: 0x1F3,
|
||||||
|
dwg_version: 0,
|
||||||
|
maintenance_version: 0,
|
||||||
|
unknown1: 0,
|
||||||
|
unknown2: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Give an entity a per-object annotation context for `scale_handle`,
|
/// Give an entity a per-object annotation context for `scale_handle`,
|
||||||
/// synthesizing the extension-dictionary chain it hangs from when absent:
|
/// synthesizing the extension-dictionary chain it hangs from when absent:
|
||||||
///
|
///
|
||||||
|
|
@ -153,11 +372,14 @@ pub fn create_annotation_context(
|
||||||
entity_handle: Handle,
|
entity_handle: Handle,
|
||||||
scale_handle: Handle,
|
scale_handle: Handle,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let Some((class_name, kind)) = doc.get_entity(entity_handle).and_then(context_kind_for) else {
|
let Some((class_name, kind)) = doc
|
||||||
|
.get_entity(entity_handle)
|
||||||
|
.and_then(|entity| context_kind_for(doc, entity, scale_handle))
|
||||||
|
else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
// The writer emits a 500+ class number only for registered classes.
|
// The writer emits a 500+ class number only for registered classes.
|
||||||
doc.register_object_context_class(class_name);
|
register_context_class(doc, class_name);
|
||||||
|
|
||||||
// Extension dictionary (hard-owns its entries; 280 = 1). Create it if the
|
// Extension dictionary (hard-owns its entries; 280 = 1). Create it if the
|
||||||
// entity has none, and point the entity at it.
|
// entity has none, and point the entity at it.
|
||||||
|
|
@ -231,7 +453,15 @@ pub fn create_annotation_context(
|
||||||
/// False for non-annotative objects (no per-object context — the vast
|
/// False for non-annotative objects (no per-object context — the vast
|
||||||
/// majority) and for objects whose contexts include the current scale. Gated
|
/// majority) and for objects whose contexts include the current scale. Gated
|
||||||
/// on an extension dictionary so non-annotative entities skip the lookup.
|
/// on an extension dictionary so non-annotative entities skip the lookup.
|
||||||
pub fn annotative_offscale(doc: &CadDocument, common: &EntityCommon) -> bool {
|
pub fn annotative_offscale_for(
|
||||||
|
doc: &CadDocument,
|
||||||
|
common: &EntityCommon,
|
||||||
|
scale_handle: Option<Handle>,
|
||||||
|
all_visible: bool,
|
||||||
|
) -> bool {
|
||||||
|
if all_visible {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if !common
|
if !common
|
||||||
.xdictionary_handle
|
.xdictionary_handle
|
||||||
.map(|h| !h.is_null())
|
.map(|h| !h.is_null())
|
||||||
|
|
@ -243,38 +473,60 @@ pub fn annotative_offscale(doc: &CadDocument, common: &EntityCommon) -> bool {
|
||||||
if scales.is_empty() {
|
if scales.is_empty() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let cur = &doc.header.current_annotation_scale;
|
match scale_handle {
|
||||||
if scales.iter().any(|(name, _)| name.eq_ignore_ascii_case(cur)) {
|
Some(handle) => !scales.iter().any(|(_, member)| *member == handle),
|
||||||
return false;
|
None => !scales.iter().any(|(name, _)| {
|
||||||
|
name.eq_ignore_ascii_case(&doc.header.current_annotation_scale)
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
// Off-scale (no context for the current scale). If some representation in
|
|
||||||
// the drawing DOES provide the current scale, hide this one — the matching
|
|
||||||
// representation is the one to show.
|
|
||||||
if current_scale_provided(doc) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// The current scale is unsupported by any representation. Fall back to the
|
|
||||||
// base "1:1" representation: keep it, hide the enlarged copies — otherwise
|
|
||||||
// every scale representation stacks (or, if all were hidden, the object
|
|
||||||
// vanishes). Without this, opening at e.g. CANNOSCALE 10:1 shows both a 1×
|
|
||||||
// and a 10× copy of the same block.
|
|
||||||
!scales.iter().any(|(name, _)| name.eq_ignore_ascii_case("1:1"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether any annotative representation in the drawing targets the current
|
pub fn scale_handle_by_name(doc: &CadDocument, name: &str) -> Option<Handle> {
|
||||||
/// annotation scale.
|
doc.objects.iter().find_map(|(handle, object)| match object {
|
||||||
fn current_scale_provided(doc: &CadDocument) -> bool {
|
ObjectType::Scale(scale)
|
||||||
let cur = &doc.header.current_annotation_scale;
|
if !scale.is_temporary && scale.name.eq_ignore_ascii_case(name) =>
|
||||||
doc.objects.values().any(|o| {
|
{
|
||||||
if let ObjectType::ObjectContextData(cd) = o {
|
Some(*handle)
|
||||||
if let Some(ObjectType::Scale(s)) = doc.objects.get(&cd.scale) {
|
|
||||||
return s.name.eq_ignore_ascii_case(cur);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
false
|
_ => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ensure_scale_object(
|
||||||
|
doc: &mut CadDocument,
|
||||||
|
source: &acadrust::objects::Scale,
|
||||||
|
) -> Handle {
|
||||||
|
if let Some(handle) = scale_handle_by_name(doc, &source.name) {
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
let root = root_named_dict_handle(doc);
|
||||||
|
let scale_dictionary = as_dict(doc, root)
|
||||||
|
.and_then(|dictionary| dictionary.get("ACAD_SCALELIST"))
|
||||||
|
.filter(|handle| matches!(doc.objects.get(handle), Some(ObjectType::Dictionary(_))))
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let handle = doc.allocate_handle();
|
||||||
|
let mut dictionary = Dictionary::new();
|
||||||
|
dictionary.handle = handle;
|
||||||
|
dictionary.owner = root;
|
||||||
|
doc.objects
|
||||||
|
.insert(handle, ObjectType::Dictionary(dictionary));
|
||||||
|
if let Some(ObjectType::Dictionary(root_dictionary)) = doc.objects.get_mut(&root) {
|
||||||
|
root_dictionary.add_entry("ACAD_SCALELIST", handle);
|
||||||
|
}
|
||||||
|
handle
|
||||||
|
});
|
||||||
|
let handle = doc.allocate_handle();
|
||||||
|
let mut scale = source.clone();
|
||||||
|
scale.handle = handle;
|
||||||
|
scale.owner_handle = scale_dictionary;
|
||||||
|
scale.is_temporary = false;
|
||||||
|
doc.objects.insert(handle, ObjectType::Scale(scale));
|
||||||
|
if let Some(ObjectType::Dictionary(dictionary)) = doc.objects.get_mut(&scale_dictionary) {
|
||||||
|
dictionary.add_entry(source.name.clone(), handle);
|
||||||
|
}
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
|
||||||
/// The annotation scales an object currently carries a per-object context for,
|
/// The annotation scales an object currently carries a per-object context for,
|
||||||
/// as `(scale name, scale handle)` pairs (one per representation). Empty when
|
/// as `(scale name, scale handle)` pairs (one per representation). Empty when
|
||||||
/// the object has no per-object context chain.
|
/// the object has no per-object context chain.
|
||||||
|
|
@ -348,9 +600,10 @@ fn annotation_scales_dict(doc: &CadDocument, entity: Handle) -> Option<Handle> {
|
||||||
/// scale. Broken scale handles are ignored. When the current named scale is
|
/// scale. Broken scale handles are ignored. When the current named scale is
|
||||||
/// absent, the leaf explicitly marked as the native/default representation is
|
/// absent, the leaf explicitly marked as the native/default representation is
|
||||||
/// preferred, followed by the first valid leaf.
|
/// preferred, followed by the first valid leaf.
|
||||||
pub fn active_object_context(
|
pub fn active_object_context_for_scale(
|
||||||
doc: &CadDocument,
|
doc: &CadDocument,
|
||||||
entity: Handle,
|
entity: Handle,
|
||||||
|
scale_handle: Option<Handle>,
|
||||||
) -> Option<&ObjectContextData> {
|
) -> Option<&ObjectContextData> {
|
||||||
let coll_h = annotation_scales_dict(doc, entity)?;
|
let coll_h = annotation_scales_dict(doc, entity)?;
|
||||||
let coll = as_dict(doc, coll_h)?;
|
let coll = as_dict(doc, coll_h)?;
|
||||||
|
|
@ -364,29 +617,27 @@ pub fn active_object_context(
|
||||||
if leaf.is_default {
|
if leaf.is_default {
|
||||||
default = Some(leaf);
|
default = Some(leaf);
|
||||||
}
|
}
|
||||||
let Some(ObjectType::Scale(scale)) = doc.objects.get(&leaf.scale) else {
|
if let Some(target) = scale_handle {
|
||||||
continue;
|
if leaf.scale == target {
|
||||||
};
|
return Some(leaf);
|
||||||
if scale
|
}
|
||||||
.name
|
} else if let Some(ObjectType::Scale(scale)) = doc.objects.get(&leaf.scale) {
|
||||||
.eq_ignore_ascii_case(&doc.header.current_annotation_scale)
|
if scale
|
||||||
{
|
.name
|
||||||
return Some(leaf);
|
.eq_ignore_ascii_case(&doc.header.current_annotation_scale)
|
||||||
|
{
|
||||||
|
return Some(leaf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default.or(first)
|
default.or(first)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the display multiplier for an entity at the current annotation
|
pub fn effective_annotation_scale_for(
|
||||||
/// 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,
|
doc: &CadDocument,
|
||||||
entity: &EntityType,
|
entity: &EntityType,
|
||||||
fallback: f32,
|
fallback: f32,
|
||||||
|
scale_handle: Option<Handle>,
|
||||||
) -> f32 {
|
) -> f32 {
|
||||||
if !is_annotative(doc, entity) {
|
if !is_annotative(doc, entity) {
|
||||||
return 1.0;
|
return 1.0;
|
||||||
|
|
@ -397,7 +648,9 @@ pub fn effective_annotation_scale(
|
||||||
// height as stored; make `ml.scale_factor * anno_scale` resolve to the
|
// height as stored; make `ml.scale_factor * anno_scale` resolve to the
|
||||||
// active context's scale factor for arrows, doglegs, and fallback text.
|
// active context's scale factor for arrows, doglegs, and fallback text.
|
||||||
if let EntityType::MultiLeader(mleader) = entity {
|
if let EntityType::MultiLeader(mleader) = entity {
|
||||||
let Some(active) = active_object_context(doc, entity.common().handle) else {
|
let Some(active) =
|
||||||
|
active_object_context_for_scale(doc, entity.common().handle, scale_handle)
|
||||||
|
else {
|
||||||
return fallback;
|
return fallback;
|
||||||
};
|
};
|
||||||
let ObjectContextKind::MLeader(context) = &active.kind else {
|
let ObjectContextKind::MLeader(context) = &active.kind else {
|
||||||
|
|
@ -422,7 +675,7 @@ pub fn effective_annotation_scale(
|
||||||
return fallback;
|
return fallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
let active = active_object_context(doc, entity.common().handle);
|
let active = active_object_context_for_scale(doc, entity.common().handle, scale_handle);
|
||||||
let native = coll.entries.iter().find_map(|(_, leaf_h)| {
|
let native = coll.entries.iter().find_map(|(_, leaf_h)| {
|
||||||
match doc.objects.get(leaf_h) {
|
match doc.objects.get(leaf_h) {
|
||||||
Some(ObjectType::ObjectContextData(leaf)) if leaf.is_default => Some(leaf),
|
Some(ObjectType::ObjectContextData(leaf)) if leaf.is_default => Some(leaf),
|
||||||
|
|
@ -613,15 +866,14 @@ fn apply_hatch_context(hatch: &mut acadrust::entities::Hatch, context: &HatchSca
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an ephemeral entity representation with the active scale leaf
|
pub fn entity_for_annotation_context<'a>(
|
||||||
/// overlaid on its base geometry. The source document remains unchanged, which
|
|
||||||
/// keeps save/round-trip data intact while render, picking and block expansion
|
|
||||||
/// all see the scale-specific placement.
|
|
||||||
pub fn entity_for_active_context<'a>(
|
|
||||||
doc: &'a CadDocument,
|
doc: &'a CadDocument,
|
||||||
entity: &'a EntityType,
|
entity: &'a EntityType,
|
||||||
|
scale_handle: Option<Handle>,
|
||||||
) -> Cow<'a, EntityType> {
|
) -> Cow<'a, EntityType> {
|
||||||
let Some(context) = active_object_context(doc, entity.common().handle) else {
|
let Some(context) =
|
||||||
|
active_object_context_for_scale(doc, entity.common().handle, scale_handle)
|
||||||
|
else {
|
||||||
return Cow::Borrowed(entity);
|
return Cow::Borrowed(entity);
|
||||||
};
|
};
|
||||||
let mut placed = entity.clone();
|
let mut placed = entity.clone();
|
||||||
|
|
@ -824,15 +1076,15 @@ fn sync_dimension_context(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy an edited entity's placement back into its active per-scale leaf.
|
/// Copy an edited entity's placement back into one per-scale leaf so geometry
|
||||||
/// Geometry edits therefore remain visible at the current annotation scale and
|
/// edits remain attached to the representation displayed by the caller.
|
||||||
/// round-trip as genuine `AcDb*ObjectContextData`, while the base entity stays
|
pub fn sync_annotation_context_from_entity(
|
||||||
/// usable as the default representation.
|
|
||||||
pub fn sync_active_context_from_entity(
|
|
||||||
doc: &mut CadDocument,
|
doc: &mut CadDocument,
|
||||||
entity_handle: Handle,
|
entity_handle: Handle,
|
||||||
|
scale_handle: Option<Handle>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let Some(leaf_handle) = active_object_context(doc, entity_handle).map(|leaf| leaf.handle)
|
let Some(leaf_handle) =
|
||||||
|
active_object_context_for_scale(doc, entity_handle, scale_handle).map(|leaf| leaf.handle)
|
||||||
else {
|
else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
@ -978,6 +1230,81 @@ pub fn sync_active_context_from_entity(
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Move every stored scale representation with a pasted entity. The base
|
||||||
|
/// entity has already moved when this runs; each context leaf still contains
|
||||||
|
/// its source placement, so it is materialized, translated, and written back
|
||||||
|
/// without disturbing the transformed base representation.
|
||||||
|
pub fn translate_annotation_contexts(
|
||||||
|
doc: &mut CadDocument,
|
||||||
|
entity_handle: Handle,
|
||||||
|
delta: glam::DVec3,
|
||||||
|
) -> bool {
|
||||||
|
let Some(base_entity) = doc.get_entity(entity_handle).cloned() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let leaves: Vec<_> = annotation_scales_dict(doc, entity_handle)
|
||||||
|
.and_then(|collection| as_dict(doc, collection))
|
||||||
|
.map(|collection| {
|
||||||
|
collection
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(_, leaf_handle)| match doc.objects.get(leaf_handle) {
|
||||||
|
Some(ObjectType::ObjectContextData(leaf)) => {
|
||||||
|
Some((leaf.handle, leaf.scale))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
if leaves.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut changed = false;
|
||||||
|
for (_, scale) in leaves {
|
||||||
|
let mut placed = entity_for_annotation_context(doc, &base_entity, Some(scale)).into_owned();
|
||||||
|
crate::scene::view::dispatch::apply_transform(
|
||||||
|
&mut placed,
|
||||||
|
&crate::command::EntityTransform::Translate(delta),
|
||||||
|
);
|
||||||
|
|
||||||
|
// The entity translator keeps the compatibility break list in sync,
|
||||||
|
// while the complete per-segment list is a separate persisted field.
|
||||||
|
if let EntityType::MultiLeader(mleader) = &mut placed {
|
||||||
|
let offset = Vector3::new(delta.x, delta.y, delta.z);
|
||||||
|
for root in &mut mleader.context.leader_roots {
|
||||||
|
for line in &mut root.lines {
|
||||||
|
for info in &mut line.break_infos {
|
||||||
|
for pair in &mut info.break_points {
|
||||||
|
pair.start_point = pair.start_point + offset;
|
||||||
|
pair.end_point = pair.end_point + offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A pasted dimension owns a newly generated graphics block. A source
|
||||||
|
// context can still carry the old block handle, so retain the block
|
||||||
|
// selected for the transformed base entity before synchronizing it.
|
||||||
|
if let (EntityType::Dimension(placed), EntityType::Dimension(base)) =
|
||||||
|
(&mut placed, &base_entity)
|
||||||
|
{
|
||||||
|
placed.base_mut().block_name.clone_from(&base.base().block_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(entity) = doc.get_entity_mut(entity_handle) {
|
||||||
|
*entity = placed;
|
||||||
|
}
|
||||||
|
changed |= sync_annotation_context_from_entity(doc, entity_handle, Some(scale));
|
||||||
|
if let Some(entity) = doc.get_entity_mut(entity_handle) {
|
||||||
|
*entity = base_entity.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the child dictionary stored under `key` in `parent_h`, creating an empty
|
/// Get the child dictionary stored under `key` in `parent_h`, creating an empty
|
||||||
/// one (owned by `parent_h`) and registering the entry when absent.
|
/// one (owned by `parent_h`) and registering the entry when absent.
|
||||||
fn get_or_create_child_dict(doc: &mut CadDocument, parent_h: Handle, key: &str) -> Handle {
|
fn get_or_create_child_dict(doc: &mut CadDocument, parent_h: Handle, key: &str) -> Handle {
|
||||||
|
|
@ -1026,6 +1353,7 @@ pub fn clear_annotation_context(doc: &mut CadDocument, handle: Handle) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Strip the legacy annotative XDATA markers the detection also honours.
|
// Strip the legacy annotative XDATA markers the detection also honours.
|
||||||
|
crate::scene::view::dispatch::set_entity_xdata(doc, handle, "AcadAnnotative", None);
|
||||||
crate::scene::view::dispatch::set_entity_xdata(doc, handle, "AcAnnoPO", None);
|
crate::scene::view::dispatch::set_entity_xdata(doc, handle, "AcAnnoPO", None);
|
||||||
crate::scene::view::dispatch::set_entity_xdata(doc, handle, "AcAnnotativeData", None);
|
crate::scene::view::dispatch::set_entity_xdata(doc, handle, "AcAnnotativeData", None);
|
||||||
}
|
}
|
||||||
|
|
@ -1048,7 +1376,7 @@ pub fn text_style_is_annotative(doc: &CadDocument, name: &str) -> bool {
|
||||||
.is_some_and(|s| s.annotative)
|
.is_some_and(|s| s.annotative)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dim_style_annotative(doc: &CadDocument, name: &str) -> bool {
|
pub fn dim_style_is_annotative(doc: &CadDocument, name: &str) -> bool {
|
||||||
doc.dim_styles
|
doc.dim_styles
|
||||||
.iter()
|
.iter()
|
||||||
.find(|s| name_matches(&s.name, name))
|
.find(|s| name_matches(&s.name, name))
|
||||||
|
|
@ -1064,15 +1392,6 @@ fn mleader_style_annotative(doc: &CadDocument, handle: Option<Handle>) -> bool {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_style_annotative(doc: &CadDocument, handle: Option<Handle>) -> bool {
|
|
||||||
let Some(h) = handle else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
doc.objects
|
|
||||||
.iter()
|
|
||||||
.any(|(oh, o)| matches!(o, ObjectType::TableStyle(s) if *oh == h && s.annotative))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether an object carries a per-object annotation context with at least one
|
/// Whether an object carries a per-object annotation context with at least one
|
||||||
/// per-scale representation — its extension dictionary holds an
|
/// per-scale representation — its extension dictionary holds an
|
||||||
/// `AcDbContextDataManager` whose `ACDB_ANNOTATIONSCALES` collection is
|
/// `AcDbContextDataManager` whose `ACDB_ANNOTATIONSCALES` collection is
|
||||||
|
|
@ -1105,14 +1424,241 @@ fn has_context_manager(doc: &CadDocument, common: &EntityCommon) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether a MULTILEADER participates in annotation scaling through its
|
/// Whether a MULTILEADER participates in annotation scaling through its
|
||||||
/// per-object context, entity flag, or assigned annotative style.
|
/// per-object context or entity flag. A later style edit is applied only by an
|
||||||
|
/// explicit style update, so it cannot retroactively change existing objects.
|
||||||
pub fn mleader_is_annotative(
|
pub fn mleader_is_annotative(
|
||||||
doc: &CadDocument,
|
doc: &CadDocument,
|
||||||
mleader: &acadrust::entities::MultiLeader,
|
mleader: &acadrust::entities::MultiLeader,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
has_context_manager(doc, &mleader.common)
|
has_context_manager(doc, &mleader.common)
|
||||||
|| mleader.enable_annotation_scale
|
|| mleader.enable_annotation_scale
|
||||||
|| mleader_style_annotative(doc, mleader.style_handle)
|
}
|
||||||
|
|
||||||
|
pub fn annotation_style_is_annotative(doc: &CadDocument, entity: &EntityType) -> bool {
|
||||||
|
match entity {
|
||||||
|
EntityType::Text(text) => text_style_is_annotative(doc, &text.style),
|
||||||
|
EntityType::MText(text) => text_style_is_annotative(doc, &text.style),
|
||||||
|
EntityType::AttributeEntity(attribute) => {
|
||||||
|
text_style_is_annotative(doc, &attribute.text_style)
|
||||||
|
}
|
||||||
|
EntityType::AttributeDefinition(attribute) => {
|
||||||
|
text_style_is_annotative(doc, &attribute.text_style)
|
||||||
|
}
|
||||||
|
EntityType::Dimension(dimension) => {
|
||||||
|
dim_style_is_annotative(doc, &dimension.base().style_name)
|
||||||
|
}
|
||||||
|
EntityType::Leader(leader) => dim_style_is_annotative(doc, &leader.dimension_style),
|
||||||
|
EntityType::Tolerance(tolerance) => {
|
||||||
|
dim_style_is_annotative(doc, &tolerance.dimension_style_name)
|
||||||
|
}
|
||||||
|
EntityType::MultiLeader(leader) => {
|
||||||
|
mleader_style_annotative(doc, leader.style_handle)
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_mleader_style(
|
||||||
|
entity: &mut acadrust::entities::MultiLeader,
|
||||||
|
style: &acadrust::objects::MultiLeaderStyle,
|
||||||
|
) {
|
||||||
|
entity.style_handle = Some(style.handle);
|
||||||
|
entity.content_type = (style.content_type as i16).into();
|
||||||
|
entity.path_type = (style.path_type as i16).into();
|
||||||
|
entity.line_color = style.line_color;
|
||||||
|
entity.line_type_handle = style.line_type_handle;
|
||||||
|
entity.line_weight = style.line_weight;
|
||||||
|
entity.enable_landing = style.enable_landing;
|
||||||
|
entity.enable_dogleg = style.enable_dogleg;
|
||||||
|
entity.dogleg_length = style.landing_distance;
|
||||||
|
entity.arrowhead_handle = style.arrowhead_handle;
|
||||||
|
entity.arrowhead_size = style.arrowhead_size;
|
||||||
|
entity.text_style_handle = style.text_style_handle;
|
||||||
|
entity.text_color = style.text_color;
|
||||||
|
entity.text_frame = style.text_frame;
|
||||||
|
entity.text_height = style.text_height;
|
||||||
|
entity.context.text_height = style.text_height;
|
||||||
|
entity.context.text_style_handle = style.text_style_handle;
|
||||||
|
entity.context.text_color = style.text_color;
|
||||||
|
entity.text_left_attachment = (style.text_left_attachment as i16).into();
|
||||||
|
entity.text_right_attachment = (style.text_right_attachment as i16).into();
|
||||||
|
entity.text_top_attachment = (style.text_top_attachment as i16).into();
|
||||||
|
entity.text_bottom_attachment = (style.text_bottom_attachment as i16).into();
|
||||||
|
entity.text_attachment_direction = (style.text_attachment_direction as i16).into();
|
||||||
|
entity.text_alignment = (style.text_alignment as i16).into();
|
||||||
|
entity.text_angle_type = (style.text_angle_type as i16).into();
|
||||||
|
entity.context.text_left_attachment = entity.text_left_attachment;
|
||||||
|
entity.context.text_right_attachment = entity.text_right_attachment;
|
||||||
|
entity.context.text_top_attachment = entity.text_top_attachment;
|
||||||
|
entity.context.text_bottom_attachment = entity.text_bottom_attachment;
|
||||||
|
entity.context.text_alignment = entity.text_alignment;
|
||||||
|
entity.block_content_handle = style.block_content_handle;
|
||||||
|
entity.block_content_color = style.block_content_color;
|
||||||
|
entity.block_connection_type = (style.block_content_connection as i16).into();
|
||||||
|
entity.block_rotation = style.block_content_rotation;
|
||||||
|
entity.block_scale = Vector3::new(
|
||||||
|
style.block_content_scale_x,
|
||||||
|
style.block_content_scale_y,
|
||||||
|
style.block_content_scale_z,
|
||||||
|
);
|
||||||
|
entity.scale_factor = style.scale_factor;
|
||||||
|
entity.context.block_content_handle = style.block_content_handle;
|
||||||
|
entity.context.block_content_color = style.block_content_color;
|
||||||
|
entity.context.block_connection_type = entity.block_connection_type;
|
||||||
|
entity.context.block_rotation = style.block_content_rotation;
|
||||||
|
entity.context.block_content_scale = entity.block_scale;
|
||||||
|
entity.context.scale_factor = style.scale_factor;
|
||||||
|
entity.enable_annotation_scale = style.is_annotative;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_mleader_style_to_object(
|
||||||
|
doc: &mut CadDocument,
|
||||||
|
handle: Handle,
|
||||||
|
style: &acadrust::objects::MultiLeaderStyle,
|
||||||
|
) -> bool {
|
||||||
|
let Some(EntityType::MultiLeader(original)) = doc.get_entity(handle).cloned() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let mut styled = original.clone();
|
||||||
|
apply_mleader_style(&mut styled, style);
|
||||||
|
if let Some(EntityType::MultiLeader(entity)) = doc.get_entity_mut(handle) {
|
||||||
|
*entity = styled;
|
||||||
|
}
|
||||||
|
|
||||||
|
let leaf_handles: Vec<_> = annotation_scales_dict(doc, handle)
|
||||||
|
.and_then(|collection| as_dict(doc, collection))
|
||||||
|
.map(|collection| collection.entries.iter().map(|(_, leaf)| *leaf).collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
for leaf_handle in leaf_handles {
|
||||||
|
let Some(ObjectType::ObjectContextData(leaf)) = doc.objects.get_mut(&leaf_handle) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let ObjectContextKind::MLeader(context) = &mut leaf.kind else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let context_scale = context.scale_factor;
|
||||||
|
let text_height_ratio = if original.text_height.abs() > 1.0e-12 {
|
||||||
|
context.text_height / original.text_height
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
let mut per_scale = original.clone();
|
||||||
|
per_scale.context.clone_from(context);
|
||||||
|
apply_mleader_style(&mut per_scale, style);
|
||||||
|
per_scale.context.scale_factor = context_scale;
|
||||||
|
if style.text_height > 0.0 && text_height_ratio.is_finite() {
|
||||||
|
per_scale.context.text_height = style.text_height * text_height_ratio;
|
||||||
|
}
|
||||||
|
context.clone_from(&per_scale.context);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_entity_from_annotation_style(
|
||||||
|
doc: &mut CadDocument,
|
||||||
|
handle: Handle,
|
||||||
|
current_scale: Option<Handle>,
|
||||||
|
) -> bool {
|
||||||
|
enum StyleUpdate {
|
||||||
|
Text { annotative: bool, height: f64 },
|
||||||
|
Dimension { annotative: bool },
|
||||||
|
MultiLeader(acadrust::objects::MultiLeaderStyle),
|
||||||
|
ContextOnly,
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(entity) = doc.get_entity(handle) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let update = match entity {
|
||||||
|
EntityType::Text(text) => doc.text_styles.get(&text.style).map(|style| {
|
||||||
|
StyleUpdate::Text {
|
||||||
|
annotative: style.annotative,
|
||||||
|
height: style.height,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
EntityType::MText(text) => doc.text_styles.get(&text.style).map(|style| {
|
||||||
|
StyleUpdate::Text {
|
||||||
|
annotative: style.annotative,
|
||||||
|
height: style.height,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
EntityType::AttributeEntity(attribute) => doc
|
||||||
|
.text_styles
|
||||||
|
.get(&attribute.text_style)
|
||||||
|
.map(|style| StyleUpdate::Text {
|
||||||
|
annotative: style.annotative,
|
||||||
|
height: style.height,
|
||||||
|
}),
|
||||||
|
EntityType::AttributeDefinition(attribute) => doc
|
||||||
|
.text_styles
|
||||||
|
.get(&attribute.text_style)
|
||||||
|
.map(|style| StyleUpdate::Text {
|
||||||
|
annotative: style.annotative,
|
||||||
|
height: style.height,
|
||||||
|
}),
|
||||||
|
EntityType::Dimension(dimension) => doc
|
||||||
|
.dim_styles
|
||||||
|
.get(&dimension.base().style_name)
|
||||||
|
.map(|style| StyleUpdate::Dimension {
|
||||||
|
annotative: style.annotative,
|
||||||
|
}),
|
||||||
|
EntityType::Leader(leader) => doc
|
||||||
|
.dim_styles
|
||||||
|
.get(&leader.dimension_style)
|
||||||
|
.map(|style| StyleUpdate::Dimension {
|
||||||
|
annotative: style.annotative,
|
||||||
|
}),
|
||||||
|
EntityType::Tolerance(tolerance) => doc
|
||||||
|
.dim_styles
|
||||||
|
.get(&tolerance.dimension_style_name)
|
||||||
|
.map(|style| StyleUpdate::Dimension {
|
||||||
|
annotative: style.annotative,
|
||||||
|
}),
|
||||||
|
EntityType::MultiLeader(leader) => leader.style_handle.and_then(|style_handle| {
|
||||||
|
match doc.objects.get(&style_handle) {
|
||||||
|
Some(ObjectType::MultiLeaderStyle(style)) => {
|
||||||
|
Some(StyleUpdate::MultiLeader(style.clone()))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_ if is_annotative(doc, entity) => Some(StyleUpdate::ContextOnly),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let Some(update) = update else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let annotative = match update {
|
||||||
|
StyleUpdate::Text { annotative, height } => {
|
||||||
|
if height > 0.0 {
|
||||||
|
if let Some(entity) = doc.get_entity_mut(handle) {
|
||||||
|
match entity {
|
||||||
|
EntityType::Text(text) => text.height = height,
|
||||||
|
EntityType::MText(text) => text.height = height,
|
||||||
|
EntityType::AttributeEntity(attribute) => attribute.height = height,
|
||||||
|
EntityType::AttributeDefinition(attribute) => attribute.height = height,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
annotative
|
||||||
|
}
|
||||||
|
StyleUpdate::Dimension { annotative } => annotative,
|
||||||
|
StyleUpdate::MultiLeader(style) => {
|
||||||
|
apply_mleader_style_to_object(doc, handle, &style);
|
||||||
|
style.is_annotative
|
||||||
|
}
|
||||||
|
StyleUpdate::ContextOnly => return true,
|
||||||
|
};
|
||||||
|
|
||||||
|
set_entity_annotative(doc, handle, annotative);
|
||||||
|
if annotative {
|
||||||
|
if let Some(scale) = current_scale {
|
||||||
|
create_annotation_context(doc, handle, scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether an entity participates in annotation scaling.
|
/// Whether an entity participates in annotation scaling.
|
||||||
|
|
@ -1123,19 +1669,30 @@ pub fn is_annotative(doc: &CadDocument, entity: &EntityType) -> bool {
|
||||||
}
|
}
|
||||||
// Legacy annotative XDATA markers.
|
// Legacy annotative XDATA markers.
|
||||||
let xd = &entity.common().extended_data;
|
let xd = &entity.common().extended_data;
|
||||||
if xd.get_record("AcAnnoPO").is_some() || xd.get_record("AcAnnotativeData").is_some() {
|
let standard_marker = xd
|
||||||
|
.get_record("AcadAnnotative")
|
||||||
|
.and_then(|record| {
|
||||||
|
record.values.iter().filter_map(|value| match value {
|
||||||
|
acadrust::xdata::XDataValue::Integer16(value) => Some(*value),
|
||||||
|
_ => None,
|
||||||
|
}).last()
|
||||||
|
})
|
||||||
|
.is_some_and(|value| value != 0);
|
||||||
|
if standard_marker
|
||||||
|
|| xd.get_record("AcAnnoPO").is_some()
|
||||||
|
|| xd.get_record("AcAnnotativeData").is_some()
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Annotative via the entity's own flag or assigned non-text style.
|
// Annotative via the entity's own flag.
|
||||||
// Text styles can be made annotative without converting existing text;
|
// Text styles can be made annotative without converting existing text;
|
||||||
// those objects must keep their stored height until explicitly updated.
|
// those objects must keep their stored height until explicitly updated.
|
||||||
match entity {
|
match entity {
|
||||||
EntityType::Text(_) => false,
|
EntityType::Text(_) => false,
|
||||||
EntityType::MText(t) => t.is_annotative,
|
EntityType::MText(t) => t.is_annotative,
|
||||||
EntityType::Dimension(d) => dim_style_annotative(doc, &d.base().style_name),
|
EntityType::AttributeEntity(attribute) => attribute.flags.annotative,
|
||||||
EntityType::Leader(l) => dim_style_annotative(doc, &l.dimension_style),
|
EntityType::AttributeDefinition(attribute) => attribute.flags.annotative,
|
||||||
EntityType::MultiLeader(ml) => mleader_is_annotative(doc, ml),
|
EntityType::MultiLeader(ml) => mleader_is_annotative(doc, ml),
|
||||||
EntityType::Table(t) => table_style_annotative(doc, t.table_style_handle),
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
77
src/scene/cache/block_cache.rs
vendored
77
src/scene/cache/block_cache.rs
vendored
|
|
@ -196,6 +196,8 @@ impl BlockCache {
|
||||||
pub fn build(
|
pub fn build(
|
||||||
doc: &CadDocument,
|
doc: &CadDocument,
|
||||||
anno_scale: f32,
|
anno_scale: f32,
|
||||||
|
annotation_scale_handle: Option<Handle>,
|
||||||
|
all_visible: bool,
|
||||||
bg_color: [f32; 4],
|
bg_color: [f32; 4],
|
||||||
// Scene draw-depth map ([depth, half] per handle) — source of each
|
// Scene draw-depth map ([depth, half] per handle) — source of each
|
||||||
// block child's in-block rank, so band depth composition agrees with
|
// block child's in-block rank, so band depth composition agrees with
|
||||||
|
|
@ -228,7 +230,15 @@ impl BlockCache {
|
||||||
.map(|name| {
|
.map(|name| {
|
||||||
(
|
(
|
||||||
name.clone(),
|
name.clone(),
|
||||||
Arc::new(build_defn(doc, name, anno_scale, bg_color, depth_map)),
|
Arc::new(build_defn(
|
||||||
|
doc,
|
||||||
|
name,
|
||||||
|
anno_scale,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
|
bg_color,
|
||||||
|
depth_map,
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
@ -345,6 +355,8 @@ fn build_defn(
|
||||||
doc: &CadDocument,
|
doc: &CadDocument,
|
||||||
block_name: &str,
|
block_name: &str,
|
||||||
anno_scale: f32,
|
anno_scale: f32,
|
||||||
|
annotation_scale_handle: Option<Handle>,
|
||||||
|
all_visible: bool,
|
||||||
bg_color: [f32; 4],
|
bg_color: [f32; 4],
|
||||||
depth_map: &HashMap<u64, [f32; 2]>,
|
depth_map: &HashMap<u64, [f32; 2]>,
|
||||||
) -> BlockDefn {
|
) -> BlockDefn {
|
||||||
|
|
@ -362,8 +374,11 @@ fn build_defn(
|
||||||
let Some(source_entity) = doc.get_entity(eh) else {
|
let Some(source_entity) = doc.get_entity(eh) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let contextual =
|
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||||
crate::scene::annotative::entity_for_active_context(doc, source_entity);
|
doc,
|
||||||
|
source_entity,
|
||||||
|
annotation_scale_handle,
|
||||||
|
);
|
||||||
let entity = contextual.as_ref();
|
let entity = contextual.as_ref();
|
||||||
// Skip entities flagged invisible. Dynamic blocks (e.g. a visibility-
|
// Skip entities flagged invisible. Dynamic blocks (e.g. a visibility-
|
||||||
// state parametric block) keep the geometry for every state in one
|
// state parametric block) keep the geometry for every state in one
|
||||||
|
|
@ -383,7 +398,12 @@ fn build_defn(
|
||||||
// Annotative scale representation: bake only the current scale's copy
|
// Annotative scale representation: bake only the current scale's copy
|
||||||
// into the defn so off-scale representations don't stack (e.g. a 1×
|
// into the defn so off-scale representations don't stack (e.g. a 1×
|
||||||
// copy under a 10×). See `annotative::annotative_offscale`.
|
// copy under a 10×). See `annotative::annotative_offscale`.
|
||||||
if crate::scene::annotative::annotative_offscale(doc, entity.common()) {
|
if crate::scene::annotative::annotative_offscale_for(
|
||||||
|
doc,
|
||||||
|
entity.common(),
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match entity {
|
match entity {
|
||||||
|
|
@ -452,7 +472,12 @@ fn build_defn(
|
||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
for lw in tessellate_sub_local(
|
for lw in tessellate_sub_local(
|
||||||
doc, &placed, anno_scale, bg_color, depth_map,
|
doc,
|
||||||
|
&placed,
|
||||||
|
anno_scale,
|
||||||
|
annotation_scale_handle,
|
||||||
|
bg_color,
|
||||||
|
depth_map,
|
||||||
) {
|
) {
|
||||||
subs.push(LocalSub::Wire(lw));
|
subs.push(LocalSub::Wire(lw));
|
||||||
}
|
}
|
||||||
|
|
@ -504,7 +529,14 @@ fn build_defn(
|
||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
for lw in
|
for lw in
|
||||||
tessellate_sub_local(doc, &placed, anno_scale, bg_color, depth_map)
|
tessellate_sub_local(
|
||||||
|
doc,
|
||||||
|
&placed,
|
||||||
|
anno_scale,
|
||||||
|
annotation_scale_handle,
|
||||||
|
bg_color,
|
||||||
|
depth_map,
|
||||||
|
)
|
||||||
{
|
{
|
||||||
subs.push(LocalSub::Wire(lw));
|
subs.push(LocalSub::Wire(lw));
|
||||||
}
|
}
|
||||||
|
|
@ -513,7 +545,14 @@ fn build_defn(
|
||||||
}
|
}
|
||||||
if !used_baked {
|
if !used_baked {
|
||||||
for lw in
|
for lw in
|
||||||
tessellate_sub_local(doc, entity, anno_scale, bg_color, depth_map)
|
tessellate_sub_local(
|
||||||
|
doc,
|
||||||
|
entity,
|
||||||
|
anno_scale,
|
||||||
|
annotation_scale_handle,
|
||||||
|
bg_color,
|
||||||
|
depth_map,
|
||||||
|
)
|
||||||
{
|
{
|
||||||
subs.push(LocalSub::Wire(lw));
|
subs.push(LocalSub::Wire(lw));
|
||||||
}
|
}
|
||||||
|
|
@ -536,7 +575,14 @@ fn build_defn(
|
||||||
// the LocalWire; `emit_wire` scales it by the insert transform
|
// the LocalWire; `emit_wire` scales it by the insert transform
|
||||||
// so the shader band matches the scaled geometry (same band the
|
// so the shader band matches the scaled geometry (same band the
|
||||||
// top-level path draws — depth-tested + linetype-dashed).
|
// top-level path draws — depth-tested + linetype-dashed).
|
||||||
for lw in tessellate_sub_local(doc, entity, anno_scale, bg_color, depth_map) {
|
for lw in tessellate_sub_local(
|
||||||
|
doc,
|
||||||
|
entity,
|
||||||
|
anno_scale,
|
||||||
|
annotation_scale_handle,
|
||||||
|
bg_color,
|
||||||
|
depth_map,
|
||||||
|
) {
|
||||||
subs.push(LocalSub::Wire(lw));
|
subs.push(LocalSub::Wire(lw));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -609,6 +655,7 @@ fn tessellate_sub_local(
|
||||||
doc: &CadDocument,
|
doc: &CadDocument,
|
||||||
sub: &EntityType,
|
sub: &EntityType,
|
||||||
anno_scale: f32,
|
anno_scale: f32,
|
||||||
|
annotation_scale_handle: Option<Handle>,
|
||||||
bg_color: [f32; 4],
|
bg_color: [f32; 4],
|
||||||
depth_map: &HashMap<u64, [f32; 2]>,
|
depth_map: &HashMap<u64, [f32; 2]>,
|
||||||
) -> Vec<LocalWire> {
|
) -> Vec<LocalWire> {
|
||||||
|
|
@ -657,7 +704,19 @@ fn tessellate_sub_local(
|
||||||
// before casting to f32 — same precision-preservation trick used for
|
// before casting to f32 — same precision-preservation trick used for
|
||||||
// top-level entities, applied per-defn.
|
// top-level entities, applied per-defn.
|
||||||
let wires_out = tessellate::tessellate(
|
let wires_out = tessellate::tessellate(
|
||||||
doc, h, sub, false, sub_color, pat_len, pat, lw_px, anno_scale, None, bg_color, false,
|
doc,
|
||||||
|
h,
|
||||||
|
sub,
|
||||||
|
false,
|
||||||
|
sub_color,
|
||||||
|
pat_len,
|
||||||
|
pat,
|
||||||
|
lw_px,
|
||||||
|
anno_scale,
|
||||||
|
annotation_scale_handle,
|
||||||
|
None,
|
||||||
|
bg_color,
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
if wires_out.is_empty() {
|
if wires_out.is_empty() {
|
||||||
return vec![];
|
return vec![];
|
||||||
|
|
|
||||||
|
|
@ -920,7 +920,23 @@ impl Scene {
|
||||||
|
|
||||||
// Paper entities and viewport borders belong to the sheet. Model
|
// Paper entities and viewport borders belong to the sheet. Model
|
||||||
// content projected through those viewports deliberately does not.
|
// content projected through those viewports deliberately does not.
|
||||||
for wire in self.wires_for_block_culled(layout_block, None, None, None, None) {
|
let scale = if self.current_layout == "Model" {
|
||||||
|
crate::scene::annotative::scale_handle_by_name(
|
||||||
|
&self.document,
|
||||||
|
&self.document.header.current_annotation_scale,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
self.paper_annotation_scale_handle()
|
||||||
|
};
|
||||||
|
for wire in self.wires_for_block_culled(
|
||||||
|
layout_block,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
scale,
|
||||||
|
self.annotation_all_visible(),
|
||||||
|
) {
|
||||||
let is_infinite = Self::handle_from_wire_name(&wire.name)
|
let is_infinite = Self::handle_from_wire_name(&wire.name)
|
||||||
.and_then(|handle| self.document.get_entity(handle))
|
.and_then(|handle| self.document.get_entity(handle))
|
||||||
.is_some_and(|entity| {
|
.is_some_and(|entity| {
|
||||||
|
|
@ -1000,7 +1016,23 @@ impl Scene {
|
||||||
// (issue #51). `wpp = None` also tessellates at a fixed tolerance so
|
// (issue #51). `wpp = None` also tessellates at a fixed tolerance so
|
||||||
// the bounds don't drift with zoom-adaptive curve sampling.
|
// the bounds don't drift with zoom-adaptive curve sampling.
|
||||||
let layout_block = self.current_layout_block_handle();
|
let layout_block = self.current_layout_block_handle();
|
||||||
let mut wires = self.wires_for_block_culled(layout_block, None, None, None, None);
|
let scale = if self.current_layout == "Model" {
|
||||||
|
crate::scene::annotative::scale_handle_by_name(
|
||||||
|
&self.document,
|
||||||
|
&self.document.header.current_annotation_scale,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
self.paper_annotation_scale_handle()
|
||||||
|
};
|
||||||
|
let mut wires = self.wires_for_block_culled(
|
||||||
|
layout_block,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
scale,
|
||||||
|
self.annotation_all_visible(),
|
||||||
|
);
|
||||||
// Ray / XLine tessellate as ±DISPLAY_EXTENT display segments
|
// Ray / XLine tessellate as ±DISPLAY_EXTENT display segments
|
||||||
// (entities/ray.rs) — their endpoints are rendering artifacts, not
|
// (entities/ray.rs) — their endpoints are rendering artifacts, not
|
||||||
// drawing extent. A construction line through the drawing defeats
|
// drawing extent. A construction line through the drawing defeats
|
||||||
|
|
|
||||||
|
|
@ -321,6 +321,7 @@ pub fn place_block_wires(
|
||||||
[0.0; 8],
|
[0.0; 8],
|
||||||
line_weight_px,
|
line_weight_px,
|
||||||
anno_scale,
|
anno_scale,
|
||||||
|
None,
|
||||||
world_per_pixel,
|
world_per_pixel,
|
||||||
bg_color,
|
bg_color,
|
||||||
false,
|
false,
|
||||||
|
|
|
||||||
|
|
@ -321,6 +321,7 @@ pub(crate) fn tessellate_entity_dim_text(
|
||||||
active_viewport,
|
active_viewport,
|
||||||
bg_color,
|
bg_color,
|
||||||
anno_scale,
|
anno_scale,
|
||||||
|
None,
|
||||||
e,
|
e,
|
||||||
None,
|
None,
|
||||||
view_aabb,
|
view_aabb,
|
||||||
|
|
@ -345,6 +346,7 @@ pub(crate) fn tessellate_entity(
|
||||||
active_viewport: Option<Handle>,
|
active_viewport: Option<Handle>,
|
||||||
bg_color: [f32; 4],
|
bg_color: [f32; 4],
|
||||||
anno_scale: f32,
|
anno_scale: f32,
|
||||||
|
annotation_scale_handle: Option<Handle>,
|
||||||
e: &EntityType,
|
e: &EntityType,
|
||||||
block_cache: Option<&cache::block_cache::BlockCache>,
|
block_cache: Option<&cache::block_cache::BlockCache>,
|
||||||
// World-space XY view AABB (post `world_offset` subtraction). When
|
// World-space XY view AABB (post `world_offset` subtraction). When
|
||||||
|
|
@ -357,7 +359,11 @@ pub(crate) fn tessellate_entity(
|
||||||
// by the viewport's GPU uniform so it never changes resident wire content.
|
// by the viewport's GPU uniform so it never changes resident wire content.
|
||||||
paper_space: bool,
|
paper_space: bool,
|
||||||
) -> Vec<WireModel> {
|
) -> Vec<WireModel> {
|
||||||
let contextual = crate::scene::annotative::entity_for_active_context(document, e);
|
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||||
|
document,
|
||||||
|
e,
|
||||||
|
annotation_scale_handle,
|
||||||
|
);
|
||||||
let e = contextual.as_ref();
|
let e = contextual.as_ref();
|
||||||
let h = e.common().handle;
|
let h = e.common().handle;
|
||||||
let sel = selected.contains(&h);
|
let sel = selected.contains(&h);
|
||||||
|
|
@ -371,7 +377,12 @@ pub(crate) fn tessellate_entity(
|
||||||
| EntityType::Dimension(_)
|
| EntityType::Dimension(_)
|
||||||
| EntityType::MultiLeader(_)
|
| EntityType::MultiLeader(_)
|
||||||
) {
|
) {
|
||||||
crate::scene::annotative::effective_annotation_scale(document, e, anno_scale)
|
crate::scene::annotative::effective_annotation_scale_for(
|
||||||
|
document,
|
||||||
|
e,
|
||||||
|
anno_scale,
|
||||||
|
annotation_scale_handle,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
anno_scale
|
anno_scale
|
||||||
};
|
};
|
||||||
|
|
@ -505,6 +516,7 @@ pub(crate) fn tessellate_entity(
|
||||||
pattern,
|
pattern,
|
||||||
1.5,
|
1.5,
|
||||||
1.0,
|
1.0,
|
||||||
|
annotation_scale_handle,
|
||||||
world_per_pixel,
|
world_per_pixel,
|
||||||
bg_color,
|
bg_color,
|
||||||
false,
|
false,
|
||||||
|
|
@ -788,6 +800,7 @@ pub(crate) fn tessellate_entity(
|
||||||
// Block contents are baked at the final WCS size —
|
// Block contents are baked at the final WCS size —
|
||||||
// don't let downstream paths re-apply anno_scale.
|
// don't let downstream paths re-apply anno_scale.
|
||||||
1.0,
|
1.0,
|
||||||
|
None,
|
||||||
sub,
|
sub,
|
||||||
block_cache,
|
block_cache,
|
||||||
view_aabb,
|
view_aabb,
|
||||||
|
|
@ -954,6 +967,7 @@ pub(crate) fn tessellate_entity(
|
||||||
active_viewport,
|
active_viewport,
|
||||||
bg_color,
|
bg_color,
|
||||||
anno_scale,
|
anno_scale,
|
||||||
|
annotation_scale_handle,
|
||||||
&placed,
|
&placed,
|
||||||
block_cache,
|
block_cache,
|
||||||
view_aabb,
|
view_aabb,
|
||||||
|
|
@ -994,14 +1008,7 @@ pub(crate) fn tessellate_entity(
|
||||||
// No baked block (e.g. a table created in-app) — synthesise coloured
|
// No baked block (e.g. a table created in-app) — synthesise coloured
|
||||||
// geometry from the rows + TableStyle so fills/colours/borders/margins
|
// geometry from the rows + TableStyle so fills/colours/borders/margins
|
||||||
// are honoured instead of the monochrome fallback.
|
// are honoured instead of the monochrome fallback.
|
||||||
// Annotative tables scale with the current annotation scale (their
|
let table_anno = 1.0;
|
||||||
// stored geometry is at paper size); non-annotative tables are already
|
|
||||||
// model-size, so pass 1.0.
|
|
||||||
let table_anno = if crate::scene::annotative::is_annotative(document, e) {
|
|
||||||
anno_scale
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
};
|
|
||||||
let mut wires = crate::entities::table::tessellate_table(
|
let mut wires = crate::entities::table::tessellate_table(
|
||||||
tab,
|
tab,
|
||||||
document,
|
document,
|
||||||
|
|
@ -1019,6 +1026,7 @@ pub(crate) fn tessellate_entity(
|
||||||
active_viewport,
|
active_viewport,
|
||||||
bg_color,
|
bg_color,
|
||||||
1.0,
|
1.0,
|
||||||
|
None,
|
||||||
&EntityType::Insert(insert),
|
&EntityType::Insert(insert),
|
||||||
block_cache,
|
block_cache,
|
||||||
view_aabb,
|
view_aabb,
|
||||||
|
|
@ -1198,6 +1206,7 @@ pub(crate) fn tessellate_entity(
|
||||||
sub_pattern,
|
sub_pattern,
|
||||||
sub_line_weight_px,
|
sub_line_weight_px,
|
||||||
anno_scale,
|
anno_scale,
|
||||||
|
annotation_scale_handle,
|
||||||
world_per_pixel,
|
world_per_pixel,
|
||||||
bg_color,
|
bg_color,
|
||||||
false,
|
false,
|
||||||
|
|
@ -1260,6 +1269,7 @@ pub(crate) fn tessellate_entity(
|
||||||
pattern,
|
pattern,
|
||||||
line_weight_px,
|
line_weight_px,
|
||||||
anno_scale,
|
anno_scale,
|
||||||
|
annotation_scale_handle,
|
||||||
world_per_pixel,
|
world_per_pixel,
|
||||||
bg_color,
|
bg_color,
|
||||||
false,
|
false,
|
||||||
|
|
@ -1398,6 +1408,7 @@ pub(crate) fn tessellate_entity(
|
||||||
pattern,
|
pattern,
|
||||||
line_weight_px,
|
line_weight_px,
|
||||||
anno_scale,
|
anno_scale,
|
||||||
|
annotation_scale_handle,
|
||||||
world_per_pixel,
|
world_per_pixel,
|
||||||
bg_color,
|
bg_color,
|
||||||
false,
|
false,
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ pub fn tessellate(
|
||||||
pattern: [f32; 8],
|
pattern: [f32; 8],
|
||||||
line_weight_px: f32,
|
line_weight_px: f32,
|
||||||
anno_scale: f32,
|
anno_scale: f32,
|
||||||
|
annotation_scale_handle: Option<Handle>,
|
||||||
world_per_pixel: Option<f32>,
|
world_per_pixel: Option<f32>,
|
||||||
// Canvas background colour — used for the MTEXT background *mask* fill
|
// Canvas background colour — used for the MTEXT background *mask* fill
|
||||||
// (flag 0x02, "use drawing window colour") so the mask erases geometry
|
// (flag 0x02, "use drawing window colour") so the mask erases geometry
|
||||||
|
|
@ -156,8 +157,12 @@ pub fn tessellate(
|
||||||
// oversized text). Annotative-ness is resolved centrally from the entity's
|
// oversized text). Annotative-ness is resolved centrally from the entity's
|
||||||
// per-object context, legacy XDATA, or annotative style (see
|
// per-object context, legacy XDATA, or annotative style (see
|
||||||
// `scene::annotative::is_annotative`) so the bake and the panel agree.
|
// `scene::annotative::is_annotative`) so the bake and the panel agree.
|
||||||
let anno_scale =
|
let anno_scale = crate::scene::annotative::effective_annotation_scale_for(
|
||||||
crate::scene::annotative::effective_annotation_scale(document, entity, anno_scale);
|
document,
|
||||||
|
entity,
|
||||||
|
anno_scale,
|
||||||
|
annotation_scale_handle,
|
||||||
|
);
|
||||||
|
|
||||||
// A HATCH is drawn as a fill by the hatch pipeline and highlighted via a
|
// 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
|
// fill tint when selected (issue #71), so it carries no boundary outline in
|
||||||
|
|
|
||||||
|
|
@ -685,7 +685,7 @@ impl Scene {
|
||||||
entities: Vec<EntityType>,
|
entities: Vec<EntityType>,
|
||||||
name: &str,
|
name: &str,
|
||||||
base: glam::DVec3,
|
base: glam::DVec3,
|
||||||
) -> Result<(), String> {
|
) -> Result<Vec<Handle>, String> {
|
||||||
let name = name.trim();
|
let name = name.trim();
|
||||||
if name.is_empty() {
|
if name.is_empty() {
|
||||||
return Err("Block name cannot be empty.".into());
|
return Err("Block name cannot be empty.".into());
|
||||||
|
|
@ -729,20 +729,23 @@ impl Scene {
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let local = EntityTransform::Translate(-base);
|
let local = EntityTransform::Translate(-base);
|
||||||
|
let mut entity_handles = Vec::with_capacity(entities.len());
|
||||||
for mut entity in entities {
|
for mut entity in entities {
|
||||||
view::dispatch::apply_transform(&mut entity, &local);
|
view::dispatch::apply_transform(&mut entity, &local);
|
||||||
entity = crate::modules::draw::modify::explode::normalize_entity_for_block(entity);
|
entity = crate::modules::draw::modify::explode::normalize_entity_for_block(entity);
|
||||||
Self::reset_clone_subhandles(&mut self.document, &mut entity);
|
Self::reset_clone_subhandles(&mut self.document, &mut entity);
|
||||||
entity.common_mut().handle = Handle::NULL;
|
entity.common_mut().handle = Handle::NULL;
|
||||||
entity.common_mut().owner_handle = br_handle;
|
entity.common_mut().owner_handle = br_handle;
|
||||||
self.document
|
let handle = self
|
||||||
|
.document
|
||||||
.add_entity(entity)
|
.add_entity(entity)
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
entity_handles.push(handle);
|
||||||
}
|
}
|
||||||
// Block defns don't render on their own, but the geometry cache must
|
// Block defns don't render on their own, but the geometry cache must
|
||||||
// pick up the new definition so the interactive insert can preview it.
|
// pick up the new definition so the interactive insert can preview it.
|
||||||
self.bump_geometry();
|
self.bump_geometry();
|
||||||
Ok(())
|
Ok(entity_handles)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recreate a block definition verbatim — the entities are already in
|
/// Recreate a block definition verbatim — the entities are already in
|
||||||
|
|
@ -794,6 +797,8 @@ impl Scene {
|
||||||
&self,
|
&self,
|
||||||
target_block: Handle,
|
target_block: Handle,
|
||||||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||||
|
annotation_scale_handle: Option<Handle>,
|
||||||
|
all_visible: bool,
|
||||||
) -> Vec<HatchModel> {
|
) -> Vec<HatchModel> {
|
||||||
let layer_hidden = |layer: &str| {
|
let layer_hidden = |layer: &str| {
|
||||||
self.document
|
self.document
|
||||||
|
|
@ -832,6 +837,12 @@ impl Scene {
|
||||||
if c.invisible
|
if c.invisible
|
||||||
|| self.entity_temporarily_hidden(handle)
|
|| self.entity_temporarily_hidden(handle)
|
||||||
|| layer_hidden(&c.layer)
|
|| layer_hidden(&c.layer)
|
||||||
|
|| crate::scene::annotative::annotative_offscale_for(
|
||||||
|
&self.document,
|
||||||
|
c,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -851,17 +862,19 @@ impl Scene {
|
||||||
.document
|
.document
|
||||||
.get_entity(handle)
|
.get_entity(handle)
|
||||||
.map(|entity| {
|
.map(|entity| {
|
||||||
crate::scene::annotative::entity_for_active_context(
|
crate::scene::annotative::entity_for_annotation_context(
|
||||||
&self.document,
|
&self.document,
|
||||||
entity,
|
entity,
|
||||||
|
annotation_scale_handle,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
let entity = contextual.as_deref();
|
let entity = contextual.as_deref();
|
||||||
let mut m = match entity {
|
let mut m = match entity {
|
||||||
Some(EntityType::Hatch(dxf))
|
Some(EntityType::Hatch(dxf))
|
||||||
if crate::scene::annotative::active_object_context(
|
if crate::scene::annotative::active_object_context_for_scale(
|
||||||
&self.document,
|
&self.document,
|
||||||
handle,
|
handle,
|
||||||
|
annotation_scale_handle,
|
||||||
)
|
)
|
||||||
.is_some() =>
|
.is_some() =>
|
||||||
{
|
{
|
||||||
|
|
@ -935,12 +948,7 @@ impl Scene {
|
||||||
if dxf.pattern.lines.is_empty() =>
|
if dxf.pattern.lines.is_empty() =>
|
||||||
{
|
{
|
||||||
m.angle_offset = dxf.pattern_angle as f32;
|
m.angle_offset = dxf.pattern_angle as f32;
|
||||||
let anno = if self.current_layout == "Model" {
|
m.scale = dxf.pattern_scale as f32;
|
||||||
self.annotation_scale
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
};
|
|
||||||
m.scale = dxf.pattern_scale as f32 * anno;
|
|
||||||
}
|
}
|
||||||
model::hatch_model::HatchPattern::Gradient { angle_deg, .. } => {
|
model::hatch_model::HatchPattern::Gradient { angle_deg, .. } => {
|
||||||
*angle_deg = dxf.pattern_angle.to_degrees() as f32;
|
*angle_deg = dxf.pattern_angle.to_degrees() as f32;
|
||||||
|
|
@ -975,6 +983,8 @@ impl Scene {
|
||||||
hatch_bg,
|
hatch_bg,
|
||||||
true,
|
true,
|
||||||
frozen,
|
frozen,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
));
|
));
|
||||||
|
|
||||||
// Wide LwPolyline / Polyline2D bands are no longer hatch fills at
|
// Wide LwPolyline / Polyline2D bands are no longer hatch fills at
|
||||||
|
|
@ -1007,12 +1017,16 @@ impl Scene {
|
||||||
hatch_bg: [f32; 4],
|
hatch_bg: [f32; 4],
|
||||||
tint_selected: bool,
|
tint_selected: bool,
|
||||||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||||
|
annotation_scale_handle: Option<Handle>,
|
||||||
|
all_visible: bool,
|
||||||
) -> Vec<HatchModel> {
|
) -> Vec<HatchModel> {
|
||||||
self.exploded_insert_hatch_models_filtered(
|
self.exploded_insert_hatch_models_filtered(
|
||||||
layout_block,
|
layout_block,
|
||||||
hatch_bg,
|
hatch_bg,
|
||||||
tint_selected,
|
tint_selected,
|
||||||
frozen,
|
frozen,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|
@ -1049,6 +1063,8 @@ impl Scene {
|
||||||
hatch_bg,
|
hatch_bg,
|
||||||
true,
|
true,
|
||||||
(!frozen.is_empty()).then_some(&frozen),
|
(!frozen.is_empty()).then_some(&frozen),
|
||||||
|
self.displayed_annotation_scale_handle(),
|
||||||
|
self.annotation_all_visible(),
|
||||||
Some(&targets),
|
Some(&targets),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
|
|
@ -1060,6 +1076,8 @@ impl Scene {
|
||||||
hatch_bg: [f32; 4],
|
hatch_bg: [f32; 4],
|
||||||
tint_selected: bool,
|
tint_selected: bool,
|
||||||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||||
|
annotation_scale_handle: Option<Handle>,
|
||||||
|
all_visible: bool,
|
||||||
targets: Option<&rustc_hash::FxHashSet<Handle>>,
|
targets: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||||
include_preview_hidden: bool,
|
include_preview_hidden: bool,
|
||||||
) -> Vec<HatchModel> {
|
) -> Vec<HatchModel> {
|
||||||
|
|
@ -1148,8 +1166,11 @@ impl Scene {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
for entity in self.document.entities() {
|
for entity in self.document.entities() {
|
||||||
let contextual =
|
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||||
crate::scene::annotative::entity_for_active_context(&self.document, entity);
|
&self.document,
|
||||||
|
entity,
|
||||||
|
annotation_scale_handle,
|
||||||
|
);
|
||||||
let EntityType::Insert(ins) = contextual.as_ref() else {
|
let EntityType::Insert(ins) = contextual.as_ref() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
@ -1179,9 +1200,12 @@ impl Scene {
|
||||||
// Off-scale annotative representation (model space only — paper
|
// Off-scale annotative representation (model space only — paper
|
||||||
// viewports use their per-viewport frozen scale layers). Skips its
|
// viewports use their per-viewport frozen scale layers). Skips its
|
||||||
// whole fill subtree, matching the wire path.
|
// whole fill subtree, matching the wire path.
|
||||||
if frozen.is_none()
|
if crate::scene::annotative::annotative_offscale_for(
|
||||||
&& crate::scene::annotative::annotative_offscale(&self.document, &ins.common)
|
&self.document,
|
||||||
{
|
&ins.common,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if !self.block_has_hatch(&ins.block_name, &mut hatch_block_memo)
|
if !self.block_has_hatch(&ins.block_name, &mut hatch_block_memo)
|
||||||
|
|
@ -1232,9 +1256,13 @@ impl Scene {
|
||||||
// use their per-viewport frozen scale layers. `explode` preserves
|
// use their per-viewport frozen scale layers. `explode` preserves
|
||||||
// the child handle, so the membership lookup still resolves here.
|
// the child handle, so the membership lookup still resolves here.
|
||||||
let offscale = |e: &EntityType| -> bool {
|
let offscale = |e: &EntityType| -> bool {
|
||||||
frozen.is_none()
|
matches!(e, EntityType::Insert(ni)
|
||||||
&& matches!(e, EntityType::Insert(ni)
|
if crate::scene::annotative::annotative_offscale_for(
|
||||||
if crate::scene::annotative::annotative_offscale(&self.document, &ni.common))
|
&self.document,
|
||||||
|
&ni.common,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
|
))
|
||||||
};
|
};
|
||||||
type ResolvedStyle = ([f32; 4], f32, [f32; 8], f32, u8);
|
type ResolvedStyle = ([f32; 4], f32, [f32; 8], f32, u8);
|
||||||
let mut stack: Vec<(
|
let mut stack: Vec<(
|
||||||
|
|
@ -1248,7 +1276,11 @@ impl Scene {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|e| !offscale(e))
|
.filter(|e| !offscale(e))
|
||||||
.map(|e| {
|
.map(|e| {
|
||||||
crate::scene::annotative::entity_for_active_context(&self.document, &e)
|
crate::scene::annotative::entity_for_annotation_context(
|
||||||
|
&self.document,
|
||||||
|
&e,
|
||||||
|
annotation_scale_handle,
|
||||||
|
)
|
||||||
.into_owned()
|
.into_owned()
|
||||||
})
|
})
|
||||||
.map(|e| {
|
.map(|e| {
|
||||||
|
|
@ -1338,9 +1370,10 @@ impl Scene {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let e =
|
let e =
|
||||||
crate::scene::annotative::entity_for_active_context(
|
crate::scene::annotative::entity_for_annotation_context(
|
||||||
&self.document,
|
&self.document,
|
||||||
&e,
|
&e,
|
||||||
|
annotation_scale_handle,
|
||||||
)
|
)
|
||||||
.into_owned();
|
.into_owned();
|
||||||
stack.push((
|
stack.push((
|
||||||
|
|
@ -1451,6 +1484,8 @@ impl Scene {
|
||||||
&self,
|
&self,
|
||||||
target_block: Handle,
|
target_block: Handle,
|
||||||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||||
|
annotation_scale_handle: Option<Handle>,
|
||||||
|
all_visible: bool,
|
||||||
) -> Vec<HatchModel> {
|
) -> Vec<HatchModel> {
|
||||||
let is_paper = self.current_layout != "Model";
|
let is_paper = self.current_layout != "Model";
|
||||||
let bg_color: [f32; 4] = if is_paper {
|
let bg_color: [f32; 4] = if is_paper {
|
||||||
|
|
@ -1537,8 +1572,11 @@ impl Scene {
|
||||||
// apply_rotation) rather than through Insert::explode — the latter
|
// apply_rotation) rather than through Insert::explode — the latter
|
||||||
// double-scales the u/v basis.
|
// double-scales the u/v basis.
|
||||||
for entity in self.document.entities() {
|
for entity in self.document.entities() {
|
||||||
let contextual =
|
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||||
crate::scene::annotative::entity_for_active_context(&self.document, entity);
|
&self.document,
|
||||||
|
entity,
|
||||||
|
annotation_scale_handle,
|
||||||
|
);
|
||||||
let EntityType::Insert(ins) = contextual.as_ref() else {
|
let EntityType::Insert(ins) = contextual.as_ref() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
@ -1558,9 +1596,12 @@ impl Scene {
|
||||||
if !self.belongs_to_visible_block(c.handle, c.owner_handle, target_block) {
|
if !self.belongs_to_visible_block(c.handle, c.owner_handle, target_block) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if frozen.is_none()
|
if crate::scene::annotative::annotative_offscale_for(
|
||||||
&& crate::scene::annotative::annotative_offscale(&self.document, c)
|
&self.document,
|
||||||
{
|
c,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
self.collect_block_wipeouts(
|
self.collect_block_wipeouts(
|
||||||
|
|
@ -1571,6 +1612,8 @@ impl Scene {
|
||||||
bg_color,
|
bg_color,
|
||||||
&depth_map,
|
&depth_map,
|
||||||
&mut models,
|
&mut models,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
models
|
models
|
||||||
|
|
@ -1588,6 +1631,8 @@ impl Scene {
|
||||||
bg_color: [f32; 4],
|
bg_color: [f32; 4],
|
||||||
depth_map: &HashMap<u64, [f32; 2]>,
|
depth_map: &HashMap<u64, [f32; 2]>,
|
||||||
models: &mut Vec<HatchModel>,
|
models: &mut Vec<HatchModel>,
|
||||||
|
annotation_scale_handle: Option<Handle>,
|
||||||
|
all_visible: bool,
|
||||||
) {
|
) {
|
||||||
if depth > 32 {
|
if depth > 32 {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1601,9 +1646,15 @@ impl Scene {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
for &eh in &br.entity_handles {
|
for &eh in &br.entity_handles {
|
||||||
let Some(e) = self.document.get_entity(eh) else {
|
let Some(source) = self.document.get_entity(eh) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||||
|
&self.document,
|
||||||
|
source,
|
||||||
|
annotation_scale_handle,
|
||||||
|
);
|
||||||
|
let e = contextual.as_ref();
|
||||||
let c = e.common();
|
let c = e.common();
|
||||||
if c.invisible
|
if c.invisible
|
||||||
|| self
|
|| self
|
||||||
|
|
@ -1613,6 +1664,12 @@ impl Scene {
|
||||||
.map(|l| l.flags.off || l.flags.frozen)
|
.map(|l| l.flags.off || l.flags.frozen)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
|| self.layer_frozen_in(&c.layer, frozen)
|
|| self.layer_frozen_in(&c.layer, frozen)
|
||||||
|
|| crate::scene::annotative::annotative_offscale_for(
|
||||||
|
&self.document,
|
||||||
|
c,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
|
)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1655,6 +1712,8 @@ impl Scene {
|
||||||
bg_color,
|
bg_color,
|
||||||
depth_map,
|
depth_map,
|
||||||
models,
|
models,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -2240,7 +2299,11 @@ impl Scene {
|
||||||
.document
|
.document
|
||||||
.get_entity(handle)
|
.get_entity(handle)
|
||||||
.map(|entity| {
|
.map(|entity| {
|
||||||
crate::scene::annotative::entity_for_active_context(&self.document, entity)
|
crate::scene::annotative::entity_for_annotation_context(
|
||||||
|
&self.document,
|
||||||
|
entity,
|
||||||
|
self.displayed_annotation_scale_handle(),
|
||||||
|
)
|
||||||
});
|
});
|
||||||
let new_model = match contextual.as_deref() {
|
let new_model = match contextual.as_deref() {
|
||||||
Some(EntityType::Hatch(dxf)) => {
|
Some(EntityType::Hatch(dxf)) => {
|
||||||
|
|
@ -2272,7 +2335,11 @@ impl Scene {
|
||||||
.filter_map(|e| match e {
|
.filter_map(|e| match e {
|
||||||
EntityType::Hatch(h) => Some((
|
EntityType::Hatch(h) => Some((
|
||||||
h.common.handle,
|
h.common.handle,
|
||||||
crate::scene::annotative::entity_for_active_context(&self.document, e)
|
crate::scene::annotative::entity_for_annotation_context(
|
||||||
|
&self.document,
|
||||||
|
e,
|
||||||
|
self.displayed_annotation_scale_handle(),
|
||||||
|
)
|
||||||
.into_owned(),
|
.into_owned(),
|
||||||
)),
|
)),
|
||||||
EntityType::Solid(s) => Some((s.common.handle, e.clone())),
|
EntityType::Solid(s) => Some((s.common.handle, e.clone())),
|
||||||
|
|
|
||||||
1085
src/scene/mod.rs
1085
src/scene/mod.rs
File diff suppressed because it is too large
Load diff
|
|
@ -115,6 +115,15 @@ fn mirror_true_text_flags(e: &mut EntityType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
|
pub(crate) fn sync_displayed_annotation_context(&mut self, handle: Handle) -> bool {
|
||||||
|
let scale = self.displayed_annotation_scale_handle();
|
||||||
|
crate::scene::annotative::sync_annotation_context_from_entity(
|
||||||
|
&mut self.document,
|
||||||
|
handle,
|
||||||
|
scale,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Invalidate a dimension's baked block while capturing every removed
|
/// Invalidate a dimension's baked block while capturing every removed
|
||||||
/// sub-entity for an active history transaction.
|
/// sub-entity for an active history transaction.
|
||||||
pub fn invalidate_dim_block_recorded(&mut self, handle: Handle) {
|
pub fn invalidate_dim_block_recorded(&mut self, handle: Handle) {
|
||||||
|
|
@ -239,10 +248,7 @@ impl Scene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for &h in handles {
|
for &h in handles {
|
||||||
if crate::scene::annotative::sync_active_context_from_entity(
|
if self.sync_displayed_annotation_context(h) {
|
||||||
&mut self.document,
|
|
||||||
h,
|
|
||||||
) {
|
|
||||||
self.poison_undo_recording();
|
self.poison_undo_recording();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -414,10 +420,7 @@ impl Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
for handle in changed.iter().copied() {
|
for handle in changed.iter().copied() {
|
||||||
let _ = crate::scene::annotative::sync_active_context_from_entity(
|
let _ = self.sync_displayed_annotation_context(handle);
|
||||||
&mut self.document,
|
|
||||||
handle,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if !changed.is_empty() {
|
if !changed.is_empty() {
|
||||||
self.rebuild_derived_caches();
|
self.rebuild_derived_caches();
|
||||||
|
|
@ -639,10 +642,7 @@ impl Scene {
|
||||||
if let Some(entity) = self.document.get_entity_mut(handle) {
|
if let Some(entity) = self.document.get_entity_mut(handle) {
|
||||||
view::dispatch::apply_grip(entity, grip_id, apply);
|
view::dispatch::apply_grip(entity, grip_id, apply);
|
||||||
}
|
}
|
||||||
if crate::scene::annotative::sync_active_context_from_entity(
|
if self.sync_displayed_annotation_context(handle) {
|
||||||
&mut self.document,
|
|
||||||
handle,
|
|
||||||
) {
|
|
||||||
self.poison_undo_recording();
|
self.poison_undo_recording();
|
||||||
}
|
}
|
||||||
// A dimension loaded from a file renders through its baked *D block;
|
// A dimension loaded from a file renders through its baked *D block;
|
||||||
|
|
|
||||||
|
|
@ -385,12 +385,17 @@ impl Scene {
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
};
|
};
|
||||||
let mut models: Vec<HatchModel> = Vec::new();
|
let mut models: Vec<HatchModel> = Vec::new();
|
||||||
|
let annotation_scale_handle = self.paper_annotation_scale_handle();
|
||||||
|
let all_visible = self.annotation_all_visible();
|
||||||
for (&handle, model) in self.hatches.iter() {
|
for (&handle, model) in self.hatches.iter() {
|
||||||
let Some(source) = self.document.get_entity(handle) else {
|
let Some(source) = self.document.get_entity(handle) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let contextual =
|
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||||
crate::scene::annotative::entity_for_active_context(&self.document, source);
|
&self.document,
|
||||||
|
source,
|
||||||
|
annotation_scale_handle,
|
||||||
|
);
|
||||||
let entity = contextual.as_ref();
|
let entity = contextual.as_ref();
|
||||||
// Paper-space SOLIDs already carry WCS-aware wire fill triangles.
|
// Paper-space SOLIDs already carry WCS-aware wire fill triangles.
|
||||||
// Keep their cached XY HatchModel out of the sheet set so the same
|
// Keep their cached XY HatchModel out of the sheet set so the same
|
||||||
|
|
@ -403,6 +408,12 @@ impl Scene {
|
||||||
if c.invisible
|
if c.invisible
|
||||||
|| self.entity_temporarily_hidden(handle)
|
|| self.entity_temporarily_hidden(handle)
|
||||||
|| layer_hidden(&c.layer)
|
|| layer_hidden(&c.layer)
|
||||||
|
|| crate::scene::annotative::annotative_offscale_for(
|
||||||
|
&self.document,
|
||||||
|
c,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
|
)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -411,9 +422,10 @@ impl Scene {
|
||||||
}
|
}
|
||||||
let mut m = match entity {
|
let mut m = match entity {
|
||||||
EntityType::Hatch(dxf)
|
EntityType::Hatch(dxf)
|
||||||
if crate::scene::annotative::active_object_context(
|
if crate::scene::annotative::active_object_context_for_scale(
|
||||||
&self.document,
|
&self.document,
|
||||||
handle,
|
handle,
|
||||||
|
annotation_scale_handle,
|
||||||
)
|
)
|
||||||
.is_some() =>
|
.is_some() =>
|
||||||
{
|
{
|
||||||
|
|
@ -453,7 +465,14 @@ impl Scene {
|
||||||
} else {
|
} else {
|
||||||
self.bg_color
|
self.bg_color
|
||||||
};
|
};
|
||||||
let exploded = self.exploded_insert_hatch_models(layout_block, hatch_bg, false, None);
|
let exploded = self.exploded_insert_hatch_models(
|
||||||
|
layout_block,
|
||||||
|
hatch_bg,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
|
);
|
||||||
models.extend(exploded);
|
models.extend(exploded);
|
||||||
Arc::new(models)
|
Arc::new(models)
|
||||||
}
|
}
|
||||||
|
|
@ -465,6 +484,8 @@ impl Scene {
|
||||||
&self,
|
&self,
|
||||||
block: Handle,
|
block: Handle,
|
||||||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||||
|
annotation_scale_handle: Option<Handle>,
|
||||||
|
all_visible: bool,
|
||||||
) -> Vec<HatchModel> {
|
) -> Vec<HatchModel> {
|
||||||
let layer_hidden = |layer: &str| {
|
let layer_hidden = |layer: &str| {
|
||||||
self.document
|
self.document
|
||||||
|
|
@ -478,23 +499,33 @@ impl Scene {
|
||||||
let Some(source) = self.document.get_entity(handle) else {
|
let Some(source) = self.document.get_entity(handle) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let contextual =
|
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||||
crate::scene::annotative::entity_for_active_context(&self.document, source);
|
&self.document,
|
||||||
|
source,
|
||||||
|
annotation_scale_handle,
|
||||||
|
);
|
||||||
let entity = contextual.as_ref();
|
let entity = contextual.as_ref();
|
||||||
let common = entity.common();
|
let common = entity.common();
|
||||||
if common.invisible
|
if common.invisible
|
||||||
|| self.entity_temporarily_hidden(handle)
|
|| self.entity_temporarily_hidden(handle)
|
||||||
|| layer_hidden(&common.layer)
|
|| layer_hidden(&common.layer)
|
||||||
|| self.layer_frozen_in(&common.layer, frozen)
|
|| self.layer_frozen_in(&common.layer, frozen)
|
||||||
|
|| crate::scene::annotative::annotative_offscale_for(
|
||||||
|
&self.document,
|
||||||
|
common,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
|
)
|
||||||
|| !self.belongs_to_visible_block(handle, common.owner_handle, block)
|
|| !self.belongs_to_visible_block(handle, common.owner_handle, block)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut hatch = match entity {
|
let mut hatch = match entity {
|
||||||
EntityType::Hatch(dxf)
|
EntityType::Hatch(dxf)
|
||||||
if crate::scene::annotative::active_object_context(
|
if crate::scene::annotative::active_object_context_for_scale(
|
||||||
&self.document,
|
&self.document,
|
||||||
handle,
|
handle,
|
||||||
|
annotation_scale_handle,
|
||||||
)
|
)
|
||||||
.is_some() =>
|
.is_some() =>
|
||||||
{
|
{
|
||||||
|
|
@ -522,6 +553,8 @@ impl Scene {
|
||||||
self.paper_bg_color,
|
self.paper_bg_color,
|
||||||
false,
|
false,
|
||||||
frozen,
|
frozen,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
));
|
));
|
||||||
models
|
models
|
||||||
}
|
}
|
||||||
|
|
@ -532,6 +565,9 @@ impl Scene {
|
||||||
&self,
|
&self,
|
||||||
block: Handle,
|
block: Handle,
|
||||||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||||
|
annotation_scale_handle: Option<Handle>,
|
||||||
|
all_visible: bool,
|
||||||
|
highlight_selection: bool,
|
||||||
) -> Vec<HatchModel> {
|
) -> Vec<HatchModel> {
|
||||||
let depth_map = self.draw_depth_map();
|
let depth_map = self.draw_depth_map();
|
||||||
let mut models = Vec::new();
|
let mut models = Vec::new();
|
||||||
|
|
@ -562,7 +598,11 @@ impl Scene {
|
||||||
boundary_wcs: None,
|
boundary_wcs: None,
|
||||||
pattern: model::hatch_model::HatchPattern::Solid,
|
pattern: model::hatch_model::HatchPattern::Solid,
|
||||||
name: "WIPEOUT_FILL".into(),
|
name: "WIPEOUT_FILL".into(),
|
||||||
color: self.paper_bg_color,
|
color: if highlight_selection && self.selected.contains(&common.handle) {
|
||||||
|
[0.15, 0.55, 1.00, 0.35]
|
||||||
|
} else {
|
||||||
|
self.paper_bg_color
|
||||||
|
},
|
||||||
aci: 0,
|
aci: 0,
|
||||||
line_weight_px: 1.0,
|
line_weight_px: 1.0,
|
||||||
angle_offset: 0.0,
|
angle_offset: 0.0,
|
||||||
|
|
@ -574,8 +614,11 @@ impl Scene {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for entity in self.document.entities() {
|
for entity in self.document.entities() {
|
||||||
let contextual =
|
let contextual = crate::scene::annotative::entity_for_annotation_context(
|
||||||
crate::scene::annotative::entity_for_active_context(&self.document, entity);
|
&self.document,
|
||||||
|
entity,
|
||||||
|
annotation_scale_handle,
|
||||||
|
);
|
||||||
let EntityType::Insert(insert) = contextual.as_ref() else {
|
let EntityType::Insert(insert) = contextual.as_ref() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
@ -589,6 +632,12 @@ impl Scene {
|
||||||
.map(|layer| layer.flags.off || layer.flags.frozen)
|
.map(|layer| layer.flags.off || layer.flags.frozen)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
|| self.layer_frozen_in(&common.layer, frozen)
|
|| self.layer_frozen_in(&common.layer, frozen)
|
||||||
|
|| crate::scene::annotative::annotative_offscale_for(
|
||||||
|
&self.document,
|
||||||
|
common,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
|
)
|
||||||
|| !self.belongs_to_visible_block(common.handle, common.owner_handle, block)
|
|| !self.belongs_to_visible_block(common.handle, common.owner_handle, block)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -598,9 +647,15 @@ impl Scene {
|
||||||
&insert.block_name,
|
&insert.block_name,
|
||||||
0,
|
0,
|
||||||
frozen,
|
frozen,
|
||||||
self.paper_bg_color,
|
if highlight_selection && self.selected.contains(&common.handle) {
|
||||||
|
[0.15, 0.55, 1.00, 0.35]
|
||||||
|
} else {
|
||||||
|
self.paper_bg_color
|
||||||
|
},
|
||||||
&depth_map,
|
&depth_map,
|
||||||
&mut models,
|
&mut models,
|
||||||
|
annotation_scale_handle,
|
||||||
|
all_visible,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
models
|
models
|
||||||
|
|
@ -613,54 +668,13 @@ impl Scene {
|
||||||
/// copy on the paper sheet.
|
/// copy on the paper sheet.
|
||||||
pub fn paper_canvas_wipeouts(&self) -> Arc<Vec<HatchModel>> {
|
pub fn paper_canvas_wipeouts(&self) -> Arc<Vec<HatchModel>> {
|
||||||
let layout_block = self.current_layout_block_handle();
|
let layout_block = self.current_layout_block_handle();
|
||||||
let bg_color = self.paper_bg_color;
|
Arc::new(self.plot_wipeouts_for_block(
|
||||||
let mut models = Vec::new();
|
layout_block,
|
||||||
for entity in self.document.entities() {
|
None,
|
||||||
let EntityType::Wipeout(wo) = entity else {
|
self.paper_annotation_scale_handle(),
|
||||||
continue;
|
self.annotation_all_visible(),
|
||||||
};
|
true,
|
||||||
if wo.common.invisible
|
))
|
||||||
|| self.entity_temporarily_hidden(wo.common.handle)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if self
|
|
||||||
.document
|
|
||||||
.layers
|
|
||||||
.get(&wo.common.layer)
|
|
||||||
.map(|l| l.flags.off || l.flags.frozen)
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if !self.belongs_to_visible_block(wo.common.handle, wo.common.owner_handle, layout_block)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Paper-block wipeouts live in paper coords — no `world_offset`.
|
|
||||||
let (fill_origin, boundary) = Self::wipeout_boundary_2d(wo);
|
|
||||||
if boundary.len() < 3 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let mut fill_color = bg_color;
|
|
||||||
if self.selected.contains(&wo.common.handle) {
|
|
||||||
fill_color = [0.15, 0.55, 1.00, 0.35];
|
|
||||||
}
|
|
||||||
models.push(HatchModel {
|
|
||||||
boundary: Arc::new(boundary),
|
|
||||||
boundary_wcs: None,
|
|
||||||
pattern: model::hatch_model::HatchPattern::Solid,
|
|
||||||
name: "WIPEOUT_FILL".into(),
|
|
||||||
color: fill_color,
|
|
||||||
aci: 0,
|
|
||||||
line_weight_px: 1.0,
|
|
||||||
angle_offset: 0.0,
|
|
||||||
scale: 1.0,
|
|
||||||
world_origin: fill_origin,
|
|
||||||
draw_depth: 0.0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Arc::new(models)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a Camera oriented and scaled to match a paper-space Viewport entity.
|
/// Build a Camera oriented and scaled to match a paper-space Viewport entity.
|
||||||
|
|
@ -805,8 +819,8 @@ impl Scene {
|
||||||
// Its live zoom is camera magnification, not CANNOSCALE: tying
|
// Its live zoom is camera magnification, not CANNOSCALE: tying
|
||||||
// annotation geometry to view_height rebuilt the entire model on every
|
// annotation geometry to view_height rebuilt the entire model on every
|
||||||
// wheel tick whenever the drawing contained one annotative object.
|
// wheel tick whenever the drawing contained one annotative object.
|
||||||
// Explicit annotation-scale changes still rebuild through
|
// Explicit viewport annotation-scale changes still rebuild the resident
|
||||||
// `self.annotation_scale`; PSLTSCALE is a viewport GPU uniform.
|
// set; PSLTSCALE is a viewport GPU uniform.
|
||||||
let frozen = match self.document.get_entity(vp_handle) {
|
let frozen = match self.document.get_entity(vp_handle) {
|
||||||
Some(EntityType::Viewport(vp)) => {
|
Some(EntityType::Viewport(vp)) => {
|
||||||
let f: HSet<Handle> = vp.frozen_layers.iter().cloned().collect();
|
let f: HSet<Handle> = vp.frozen_layers.iter().cloned().collect();
|
||||||
|
|
@ -815,9 +829,11 @@ impl Scene {
|
||||||
_ => HSet::default(),
|
_ => HSet::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let scale_handle = self.viewport_scale_handle(vp_handle);
|
||||||
self.resident_wires_for(
|
self.resident_wires_for(
|
||||||
self.model_space_block_handle(),
|
self.model_space_block_handle(),
|
||||||
Some(self.annotation_scale),
|
Some(self.viewport_annotation_multiplier(vp_handle)),
|
||||||
|
scale_handle,
|
||||||
Some(&frozen),
|
Some(&frozen),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -430,7 +430,12 @@ impl Scene {
|
||||||
|
|
||||||
let frozen: rustc_hash::FxHashSet<Handle> =
|
let frozen: rustc_hash::FxHashSet<Handle> =
|
||||||
viewport.frozen_layers.iter().copied().collect();
|
viewport.frozen_layers.iter().copied().collect();
|
||||||
let hatches = self.plot_hatches_for_block(model_block, Some(&frozen));
|
let hatches = self.plot_hatches_for_block(
|
||||||
|
model_block,
|
||||||
|
Some(&frozen),
|
||||||
|
self.viewport_scale_handle(viewport.common.handle),
|
||||||
|
self.annotation_all_visible(),
|
||||||
|
);
|
||||||
for hatch in hatches {
|
for hatch in hatches {
|
||||||
if matches!(&hatch.pattern, HatchPattern::Pattern(_)) {
|
if matches!(&hatch.pattern, HatchPattern::Pattern(_)) {
|
||||||
let mut points = Vec::new();
|
let mut points = Vec::new();
|
||||||
|
|
@ -482,7 +487,13 @@ impl Scene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for wipeout in self.plot_wipeouts_for_block(model_block, Some(&frozen)) {
|
for wipeout in self.plot_wipeouts_for_block(
|
||||||
|
model_block,
|
||||||
|
Some(&frozen),
|
||||||
|
self.viewport_scale_handle(viewport.common.handle),
|
||||||
|
self.annotation_all_visible(),
|
||||||
|
false,
|
||||||
|
) {
|
||||||
if let Some(wipeout) =
|
if let Some(wipeout) =
|
||||||
project_plot_fill(wipeout, &project, xmin, ymin, xmax, ymax)
|
project_plot_fill(wipeout, &project, xmin, ymin, xmax, ymax)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2243,15 +2243,15 @@ impl Scene {
|
||||||
(hatches, wipeouts, Some(images))
|
(hatches, wipeouts, Some(images))
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
self.hatch_models_for_viewport(&vp_frozen),
|
self.hatch_models_for_viewport(inst.handle, &vp_frozen),
|
||||||
self.wipeout_models_for_viewport(&vp_frozen),
|
self.wipeout_models_for_viewport(inst.handle, &vp_frozen),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let images = if let Some(images) = paper_images {
|
let images = if let Some(images) = paper_images {
|
||||||
images
|
images
|
||||||
} else {
|
} else {
|
||||||
self.images_for_viewport(&vp_frozen)
|
self.images_for_viewport(inst.handle, &vp_frozen)
|
||||||
};
|
};
|
||||||
// The paper sheet shows the layout's own 2-D content (fills, borders,
|
// The paper sheet shows the layout's own 2-D content (fills, borders,
|
||||||
// annotation) — never the model's 3-D solids. Those are drawn inside
|
// annotation) — never the model's 3-D solids. Those are drawn inside
|
||||||
|
|
@ -2263,7 +2263,7 @@ impl Scene {
|
||||||
let meshes = if inst.paper_sheet {
|
let meshes = if inst.paper_sheet {
|
||||||
Arc::new(Vec::new())
|
Arc::new(Vec::new())
|
||||||
} else {
|
} else {
|
||||||
self.meshes_for_viewport(&vp_frozen)
|
self.meshes_for_viewport(inst.handle, &vp_frozen)
|
||||||
};
|
};
|
||||||
|
|
||||||
// SDF text quads (behind OCS_TEXT_SDF). The glyph quads ride on each
|
// SDF text quads (behind OCS_TEXT_SDF). The glyph quads ride on each
|
||||||
|
|
|
||||||
|
|
@ -14,24 +14,28 @@ use crate::ui::statusbar::status_menu::Entry;
|
||||||
/// scales of its own.
|
/// scales of its own.
|
||||||
pub fn menu_entries(
|
pub fn menu_entries(
|
||||||
is_model: bool,
|
is_model: bool,
|
||||||
current_anno_scale: f32,
|
current_scale_name: &str,
|
||||||
viewport_scale: Option<f64>,
|
viewport_scale: Option<f64>,
|
||||||
file_scales: Vec<(String, f32, f64)>,
|
file_scales: Vec<(String, f32, f64)>,
|
||||||
) -> Vec<Entry<'static>> {
|
) -> Vec<Entry<'static>> {
|
||||||
let mut entries: Vec<Entry<'static>> = file_scales
|
let mut entries: Vec<Entry<'static>> = file_scales
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(label, anno_scale, vp_scale)| {
|
.map(|(label, _anno_scale, vp_scale)| {
|
||||||
let active = if is_model {
|
let active = if is_model {
|
||||||
(current_anno_scale - anno_scale).abs() < 0.001 * current_anno_scale.max(0.001)
|
label.eq_ignore_ascii_case(current_scale_name)
|
||||||
} else {
|
} else {
|
||||||
viewport_scale
|
label.eq_ignore_ascii_case(current_scale_name)
|
||||||
.map(|vs| (vs - vp_scale).abs() < 0.001 * vp_scale.max(0.001))
|
|| (current_scale_name.is_empty()
|
||||||
.unwrap_or(false)
|
&& viewport_scale
|
||||||
|
.map(|vs| {
|
||||||
|
(vs - vp_scale).abs() < 0.001 * vp_scale.max(0.001)
|
||||||
|
})
|
||||||
|
.unwrap_or(false))
|
||||||
};
|
};
|
||||||
let msg = if is_model {
|
let msg = if is_model {
|
||||||
Message::SetAnnotationScale(anno_scale)
|
Message::SetAnnotationScale(label.clone())
|
||||||
} else {
|
} else {
|
||||||
Message::SetViewportScale(vp_scale)
|
Message::SetViewportScale(label.clone())
|
||||||
};
|
};
|
||||||
Entry::close(scale_row(label, active, msg))
|
Entry::close(scale_row(label, active, msg))
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,15 @@ use crate::ui::statusbar::statusbar_config::{StatusBarConfig, StatusPill};
|
||||||
use crate::ui::statusbar::status_menu::Entry as StatusMenuEntry;
|
use crate::ui::statusbar::status_menu::Entry as StatusMenuEntry;
|
||||||
use crate::ui::wrap_bar::WrapBar;
|
use crate::ui::wrap_bar::WrapBar;
|
||||||
|
|
||||||
|
const ST_ANNO_VISIBILITY: &[u8] = include_bytes!("../../../assets/icons/scale_list.svg");
|
||||||
|
const ST_ANNO_AUTO_ADD: &[u8] = include_bytes!("../../../assets/icons/add_scale.svg");
|
||||||
|
const ST_VP_SCALE_SYNC: &[u8] = include_bytes!("../../../assets/icons/sync.svg");
|
||||||
|
|
||||||
pub struct StatusMenuData<'a> {
|
pub struct StatusMenuData<'a> {
|
||||||
pub layout_names: Vec<String>,
|
pub layout_names: Vec<String>,
|
||||||
pub polar_custom_input: &'a str,
|
pub polar_custom_input: &'a str,
|
||||||
pub scale_is_model: bool,
|
pub scale_is_model: bool,
|
||||||
|
pub current_scale_name: String,
|
||||||
pub scale_list: Vec<(String, f32, f64)>,
|
pub scale_list: Vec<(String, f32, f64)>,
|
||||||
pub has_selection: bool,
|
pub has_selection: bool,
|
||||||
pub selection_types: Vec<String>,
|
pub selection_types: Vec<String>,
|
||||||
|
|
@ -80,6 +85,9 @@ impl StatusBar {
|
||||||
annotation_scale: f32,
|
annotation_scale: f32,
|
||||||
// True when the scale pill is interactive (always model space; paper space only when a viewport is active/selected).
|
// True when the scale pill is interactive (always model space; paper space only when a viewport is active/selected).
|
||||||
scale_pill_enabled: bool,
|
scale_pill_enabled: bool,
|
||||||
|
annotation_all_visible: bool,
|
||||||
|
annotation_auto_add: bool,
|
||||||
|
viewport_scale_synced: Option<bool>,
|
||||||
// LWDISPLAY header flag — controls lineweight visibility in the viewport.
|
// LWDISPLAY header flag — controls lineweight visibility in the viewport.
|
||||||
lineweight_display: bool,
|
lineweight_display: bool,
|
||||||
// Live cursor position in model coordinates, for the coordinate readout.
|
// Live cursor position in model coordinates, for the coordinate readout.
|
||||||
|
|
@ -113,6 +121,7 @@ impl StatusBar {
|
||||||
layout_names,
|
layout_names,
|
||||||
polar_custom_input,
|
polar_custom_input,
|
||||||
scale_is_model,
|
scale_is_model,
|
||||||
|
current_scale_name,
|
||||||
scale_list,
|
scale_list,
|
||||||
has_selection,
|
has_selection,
|
||||||
selection_types,
|
selection_types,
|
||||||
|
|
@ -175,19 +184,23 @@ impl StatusBar {
|
||||||
// Keep its text identical to the active drawing-defined scale. Rebuilding
|
// Keep its text identical to the active drawing-defined scale. Rebuilding
|
||||||
// the label from the numeric factor turns an architectural
|
// the label from the numeric factor turns an architectural
|
||||||
// `1/2" = 1'-0"` scale into `1:24`, mixing formats in the same control.
|
// `1/2" = 1'-0"` scale into `1:24`, mixing formats in the same control.
|
||||||
let scale_label = active_scale_label(
|
let scale_label = if current_scale_name.is_empty() {
|
||||||
scale_is_model,
|
active_scale_label(
|
||||||
annotation_scale,
|
scale_is_model,
|
||||||
viewport_scale,
|
annotation_scale,
|
||||||
&scale_list,
|
viewport_scale,
|
||||||
)
|
&scale_list,
|
||||||
.unwrap_or_else(|| {
|
)
|
||||||
if scale_is_model {
|
.unwrap_or_else(|| {
|
||||||
format_scale(Some(1.0 / annotation_scale as f64))
|
if scale_is_model {
|
||||||
} else {
|
format_scale(Some(1.0 / annotation_scale as f64))
|
||||||
format_scale(viewport_scale)
|
} else {
|
||||||
}
|
format_scale(viewport_scale)
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
current_scale_name.clone()
|
||||||
|
};
|
||||||
let scale_element: Element<'_, Message> = if scale_pill_enabled {
|
let scale_element: Element<'_, Message> = if scale_pill_enabled {
|
||||||
status_menu::menu_bar(
|
status_menu::menu_bar(
|
||||||
menu_tip(
|
menu_tip(
|
||||||
|
|
@ -197,7 +210,7 @@ impl StatusBar {
|
||||||
),
|
),
|
||||||
crate::ui::popup::scale_popup::menu_entries(
|
crate::ui::popup::scale_popup::menu_entries(
|
||||||
scale_is_model,
|
scale_is_model,
|
||||||
annotation_scale,
|
¤t_scale_name,
|
||||||
viewport_scale,
|
viewport_scale,
|
||||||
scale_list,
|
scale_list,
|
||||||
),
|
),
|
||||||
|
|
@ -294,6 +307,47 @@ impl StatusBar {
|
||||||
if vis(StatusPill::Scale) {
|
if vis(StatusPill::Scale) {
|
||||||
pills.push(scale_element);
|
pills.push(scale_element);
|
||||||
}
|
}
|
||||||
|
if vis(StatusPill::AnnoVisibility) {
|
||||||
|
pills.push(
|
||||||
|
tip(
|
||||||
|
toggle_pill(
|
||||||
|
ST_ANNO_VISIBILITY,
|
||||||
|
annotation_all_visible,
|
||||||
|
Message::ToggleAnnotationVisibility,
|
||||||
|
),
|
||||||
|
"Show Annotation Objects",
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if vis(StatusPill::AnnoAutoAdd) {
|
||||||
|
pills.push(
|
||||||
|
tip(
|
||||||
|
toggle_pill(
|
||||||
|
ST_ANNO_AUTO_ADD,
|
||||||
|
annotation_auto_add,
|
||||||
|
Message::ToggleAnnotationAutoAdd,
|
||||||
|
),
|
||||||
|
"Automatically Add Scales",
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if vis(StatusPill::VpScaleSync) {
|
||||||
|
if let Some(synced) = viewport_scale_synced {
|
||||||
|
pills.push(
|
||||||
|
tip(
|
||||||
|
toggle_pill(
|
||||||
|
ST_VP_SCALE_SYNC,
|
||||||
|
synced,
|
||||||
|
Message::SyncViewportAnnotationScale,
|
||||||
|
),
|
||||||
|
"Viewport / Annotation Scale Sync",
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
if vis(StatusPill::Units) {
|
if vis(StatusPill::Units) {
|
||||||
pills.push(
|
pills.push(
|
||||||
status_menu::menu_bar(
|
status_menu::menu_bar(
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ pub enum StatusPill {
|
||||||
Osnap,
|
Osnap,
|
||||||
Space,
|
Space,
|
||||||
Scale,
|
Scale,
|
||||||
|
AnnoVisibility,
|
||||||
|
AnnoAutoAdd,
|
||||||
|
VpScaleSync,
|
||||||
Units,
|
Units,
|
||||||
Transparency,
|
Transparency,
|
||||||
Isolate,
|
Isolate,
|
||||||
|
|
@ -43,6 +46,9 @@ impl StatusPill {
|
||||||
StatusPill::Osnap,
|
StatusPill::Osnap,
|
||||||
StatusPill::Space,
|
StatusPill::Space,
|
||||||
StatusPill::Scale,
|
StatusPill::Scale,
|
||||||
|
StatusPill::AnnoVisibility,
|
||||||
|
StatusPill::AnnoAutoAdd,
|
||||||
|
StatusPill::VpScaleSync,
|
||||||
StatusPill::Units,
|
StatusPill::Units,
|
||||||
StatusPill::Transparency,
|
StatusPill::Transparency,
|
||||||
StatusPill::Isolate,
|
StatusPill::Isolate,
|
||||||
|
|
@ -65,6 +71,9 @@ impl StatusPill {
|
||||||
StatusPill::Osnap => "osnap",
|
StatusPill::Osnap => "osnap",
|
||||||
StatusPill::Space => "space",
|
StatusPill::Space => "space",
|
||||||
StatusPill::Scale => "scale",
|
StatusPill::Scale => "scale",
|
||||||
|
StatusPill::AnnoVisibility => "anno_visibility",
|
||||||
|
StatusPill::AnnoAutoAdd => "anno_auto_add",
|
||||||
|
StatusPill::VpScaleSync => "vp_scale_sync",
|
||||||
StatusPill::Units => "units",
|
StatusPill::Units => "units",
|
||||||
StatusPill::Transparency => "transparency",
|
StatusPill::Transparency => "transparency",
|
||||||
StatusPill::Isolate => "isolate",
|
StatusPill::Isolate => "isolate",
|
||||||
|
|
@ -88,6 +97,9 @@ impl StatusPill {
|
||||||
StatusPill::Osnap => "Object Snap",
|
StatusPill::Osnap => "Object Snap",
|
||||||
StatusPill::Space => "Model/Paper Space",
|
StatusPill::Space => "Model/Paper Space",
|
||||||
StatusPill::Scale => "Annotation Scale",
|
StatusPill::Scale => "Annotation Scale",
|
||||||
|
StatusPill::AnnoVisibility => "Show Annotation Objects",
|
||||||
|
StatusPill::AnnoAutoAdd => "Automatically Add Scales",
|
||||||
|
StatusPill::VpScaleSync => "Viewport / Annotation Scale Sync",
|
||||||
StatusPill::Units => "Drawing Units",
|
StatusPill::Units => "Drawing Units",
|
||||||
StatusPill::Transparency => "Show Transparency",
|
StatusPill::Transparency => "Show Transparency",
|
||||||
StatusPill::Isolate => "Isolate Objects",
|
StatusPill::Isolate => "Isolate Objects",
|
||||||
|
|
|
||||||
|
|
@ -537,7 +537,15 @@ pub fn view_window<'a>(
|
||||||
chk("Annotative", vals.annotative, DsField::Annotative),
|
chk("Annotative", vals.annotative, DsField::Annotative),
|
||||||
row![
|
row![
|
||||||
lbl("Overall scale (DIMSCALE)"),
|
lbl("Overall scale (DIMSCALE)"),
|
||||||
mk_field(DsField::Dimscale, vals.dimscale)
|
if vals.annotative {
|
||||||
|
text_input("", "0")
|
||||||
|
.style(field_style)
|
||||||
|
.size(11)
|
||||||
|
.width(100)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
mk_field(DsField::Dimscale, vals.dimscale)
|
||||||
|
}
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,16 @@ fn num_row<'a>(
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn readonly_num_row<'a>(label: &'static str, value: &'a str) -> Element<'a, Message> {
|
||||||
|
row![
|
||||||
|
text(label).size(11).style(muted_style).width(150),
|
||||||
|
text(value).size(11).style(muted_style),
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Center)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Shared colour selector row. Reuses MLeaderStyleEdit by sending the chosen
|
/// Shared colour selector row. Reuses MLeaderStyleEdit by sending the chosen
|
||||||
/// colour as an ACI string; `open` shows the expanded palette.
|
/// colour as an ACI string; `open` shows the expanded palette.
|
||||||
fn color_row<'a>(
|
fn color_row<'a>(
|
||||||
|
|
@ -288,7 +298,11 @@ pub fn view_window<'a>(
|
||||||
v.second_seg_angle,
|
v.second_seg_angle,
|
||||||
"second_seg_angle"
|
"second_seg_angle"
|
||||||
),
|
),
|
||||||
num_row("Scale factor:", "1.0", v.scale_factor, "scale_factor"),
|
if s.is_annotative {
|
||||||
|
readonly_num_row("Scale factor:", "By annotation scale")
|
||||||
|
} else {
|
||||||
|
num_row("Scale factor:", "1.0", v.scale_factor, "scale_factor")
|
||||||
|
},
|
||||||
num_row("Align space:", "4.0", v.align_space, "align_space"),
|
num_row("Align space:", "4.0", v.align_space, "align_space"),
|
||||||
enum_row(
|
enum_row(
|
||||||
"Leader draw order:",
|
"Leader draw order:",
|
||||||
|
|
|
||||||
|
|
@ -293,11 +293,6 @@ pub fn view_window<'a>(
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
checkbox(s.annotative)
|
|
||||||
.label("Annotative")
|
|
||||||
.on_toggle(|_| Message::TableStyleToggleAnnotative)
|
|
||||||
.size(14)
|
|
||||||
.text_size(11),
|
|
||||||
row![
|
row![
|
||||||
text("H Margin:").size(11).style(muted_style).width(160),
|
text("H Margin:").size(11).style(muted_style).width(160),
|
||||||
text_input("1.5", hmargin_buf)
|
text_input("1.5", hmargin_buf)
|
||||||
|
|
|
||||||
|
|
@ -298,7 +298,16 @@ pub fn view_window<'a>(
|
||||||
text("Properties").size(11).style(primary_style),
|
text("Properties").size(11).style(primary_style),
|
||||||
frow("Big Font:", "big-font file…", bigfont_buf, "bigfont"),
|
frow("Big Font:", "big-font file…", bigfont_buf, "bigfont"),
|
||||||
frow("TrueType Font:", "e.g. Arial", ttf_buf, "ttf"),
|
frow("TrueType Font:", "e.g. Arial", ttf_buf, "ttf"),
|
||||||
frow("Fixed Height:", "0 = variable", height_buf, "height"),
|
frow(
|
||||||
|
if annotative {
|
||||||
|
"Paper Text Height:"
|
||||||
|
} else {
|
||||||
|
"Fixed Height:"
|
||||||
|
},
|
||||||
|
"0 = variable",
|
||||||
|
height_buf,
|
||||||
|
"height",
|
||||||
|
),
|
||||||
frow("Width Factor:", "1.0", width_buf, "width"),
|
frow("Width Factor:", "1.0", width_buf, "width"),
|
||||||
frow("Oblique (°):", "0.0", oblique_buf, "oblique"),
|
frow("Oblique (°):", "0.0", oblique_buf, "oblique"),
|
||||||
row![
|
row![
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue