feat(dimension): the standard arrowheads in every arrowhead picker

The dimension style, multileader style and Properties pickers listed
every block of the drawing as an arrowhead, and nothing else. They now
list the 19 standard arrowheads (Closed filled, Open, Dot, Architectural
tick, Oblique, Right angle, Datum triangle, …), followed by any user
arrow block the dimension or style already uses. Picking one creates
its block (`_Open`, `_ArchTick`, …) with the geometry the reference
program writes, so other programs draw the same arrow; ODA reads the
saved file cleanly.

`_Small` now draws as the reference's "Dot small blank", a hollow circle
half the arrow size, instead of a small filled triangle.

Also drops a duplicated "DGN Import" catalog entry left by #1810/#1812.
(#1820, #1821)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-15 22:18:35 +03:00
1 parent 4143a01ff4
commit bec9e63679
14 files changed
+288 -107

No files matched your search

+1 -1
View File
@@ -707,7 +707,7 @@ fn add_entity_clone_doc(
/// Document-level verbatim block-definition recreate: the `&mut CadDocument`
/// half of `Scene::define_block_raw` (no-op when the block exists). The
/// trailing `bump_geometry` stays caller-side.
fn define_block_raw_doc(
pub(crate) fn define_block_raw_doc(
doc: &mut codec::CadDocument,
name: &str,
base_point: codec::types::Vector3,
+2 -2
View File
@@ -95,8 +95,8 @@ mod utilities;
mod viewport;
pub(crate) use clipboard::{
copy_to_clipboard_kernel, paste_entities_kernel, remap_ext_subtree_reference, remap_object,
PasteSpace,
copy_to_clipboard_kernel, define_block_raw_doc, paste_entities_kernel,
remap_ext_subtree_reference, remap_object, PasteSpace,
};
impl OpenCADStudio {
+3 -1
View File
@@ -20,7 +20,9 @@ mod block_test;
#[cfg(test)]
mod block_test_tests;
mod command_driver;
pub(crate) use command_driver::{copy_to_clipboard_kernel, paste_entities_kernel, PasteSpace};
pub(crate) use command_driver::{
copy_to_clipboard_kernel, define_block_raw_doc, paste_entities_kernel, PasteSpace,
};
pub(crate) mod commands;
pub(crate) mod dim_viewport;
#[cfg(test)]
+7 -17
View File
@@ -554,14 +554,8 @@ impl OpenCADStudio {
// Arrowheads are blocks; the closed-filled default has no
// block, so seed the list with it and add the arrowhead
// blocks (leading underscore) present in the drawing.
let arrow_names: Vec<String> = std::iter::once("Closed filled".to_string())
.chain(
doc.block_records
.iter()
.filter(|b| b.name.starts_with('_'))
.map(|b| b.name.clone()),
)
.collect();
// Filled in below from the current arrowhead. (#1820)
let arrow_names: Vec<String>;
let block_names: Vec<String> = doc
.block_records
.iter()
@@ -590,15 +584,11 @@ impl OpenCADStudio {
.map(|s| s.name.clone())
})
.unwrap_or_else(|| "Standard".to_string());
let cur_arrow = ml
.arrowhead_handle
.and_then(|h| {
doc.block_records
.iter()
.find(|b| b.handle == h)
.map(|b| b.name.clone())
})
.unwrap_or_else(|| "Closed filled".to_string());
let cur_arrow = crate::entities::arrowheads::label(
doc,
ml.arrowhead_handle.unwrap_or(codec::Handle::NULL),
);
arrow_names = crate::entities::arrowheads::options([cur_arrow.as_str()]);
let cur_ltype = ml
.line_type_handle
.and_then(|h| {
+11 -10
View File
@@ -3184,6 +3184,16 @@ impl OpenCADStudio {
// handle on a failed lookup; the arrowhead/linetype rows
// take the resolved value directly (None = the default
// "Closed filled" / "ByBlock" option).
// A standard arrowhead's block is created on first use.
let picked_arrow = (field == "arrowhead_handle")
.then(|| {
crate::entities::arrowheads::resolve(
&mut self.tabs[i].scene.document,
&value,
)
})
.flatten()
.filter(|h| !h.is_null());
let doc = &self.tabs[i].scene.document;
let resolved_mleader_style = (field == "mleader_style")
.then(|| {
@@ -3204,16 +3214,7 @@ impl OpenCADStudio {
.iter()
.find(|s| s.name == value)
.map(|s| s.handle),
"arrowhead_handle" => {
if value == "Closed filled" {
None
} else {
doc.block_records
.iter()
.find(|b| b.name == value)
.map(|b| b.handle)
}
}
"arrowhead_handle" => picked_arrow,
"line_type_handle" => {
if value == "ByBlock" {
None
+14 -6
View File
@@ -976,6 +976,17 @@ impl OpenCADStudio {
value: String,
) -> Task<Message> {
let i = self.active_tab;
if field == "arrowhead_handle" {
let handle = crate::entities::arrowheads::resolve(
&mut self.tabs[i].scene.document,
&value,
)
.filter(|h| !h.is_null());
if let Some(s) = self.mleaderstyle_mut(i) {
s.arrowhead_handle = handle;
}
return Task::none();
}
let doc = &self.tabs[i].scene.document;
let handle: Option<codec::types::Handle> =
if value == "None" || value == "ByBlock" || value == "Closed filled" {
@@ -1255,7 +1266,7 @@ impl OpenCADStudio {
field,
"dimltex_handle" | "dimltex1_handle" | "dimltex2_handle"
);
let doc = &self.tabs[i].scene.document;
let doc = &mut self.tabs[i].scene.document;
let handle = if value == "Default" || value == "ByBlock" {
codec::types::Handle::NULL
} else if is_lt {
@@ -1265,11 +1276,8 @@ impl OpenCADStudio {
.map(|lt| lt.handle)
.unwrap_or(codec::types::Handle::NULL)
} else {
doc.block_records
.iter()
.find(|b| b.name == value)
.map(|b| b.handle)
.unwrap_or(codec::types::Handle::NULL)
// A standard arrowhead's block is created on first use.
crate::entities::arrowheads::resolve(doc, &value).unwrap_or(codec::types::Handle::NULL)
};
// Staged: persists on Apply.
if let Some(ds) = self.tabs[i].scene.document.dim_styles.get_mut(&name) {
+22 -34
View File
@@ -1164,8 +1164,12 @@ impl OpenCADStudio {
let doc = &tab.scene.document;
let mut block_opts: Vec<String> = vec!["None".to_string()];
block_opts.extend(doc.block_records.iter().map(|b| b.name.clone()));
let mut arrow_opts: Vec<String> = vec!["Closed filled".to_string()];
arrow_opts.extend(doc.block_records.iter().map(|b| b.name.clone()));
let arrow_opts = crate::entities::arrowheads::options(
selected_style
.and_then(|s| s.arrowhead_handle)
.map(|h| crate::entities::arrowheads::label(doc, h))
.as_deref(),
);
let mut lt_opts: Vec<String> = vec!["ByBlock".to_string()];
lt_opts.extend(doc.line_types.iter().map(|lt| lt.name.clone()));
let mut textstyle_opts: Vec<String> = vec!["None".to_string()];
@@ -1212,14 +1216,10 @@ impl OpenCADStudio {
match selected_style {
Some(s) => (
opt_lt(s.line_type_handle),
s.arrowhead_handle
.and_then(|handle| {
doc.block_records
.iter()
.find(|record| record.handle == handle)
.map(|record| record.name.clone())
})
.unwrap_or_else(|| "Closed filled".to_string()),
crate::entities::arrowheads::label(
doc,
s.arrowhead_handle.unwrap_or(codec::types::Handle::NULL),
),
opt_ts(s.text_style_handle),
opt_block(s.block_content_handle),
),
@@ -1417,20 +1417,17 @@ impl OpenCADStudio {
let doc = &tab.scene.document;
// Dropdown options (names must match the records exactly so the
// selection can be resolved back to a handle on the update side).
let mut block_opts: Vec<String> = vec!["Default".to_string()];
block_opts.extend(
doc.block_records
// The standard arrowheads, then the user arrow blocks the
// selected style uses; not every block of the drawing. (#1820)
let block_opts = crate::entities::arrowheads::options(
doc.dim_styles
.get(&self.dimstyle_selected)
.iter()
.filter(|b| {
!b.is_layout()
&& !b.is_model_space()
&& !b.is_paper_space()
&& !b.flags.is_xref
&& !b.flags.is_xref_overlay
&& !b.flags.is_external
&& !b.name.starts_with('*')
})
.map(|b| b.name.clone()),
.flat_map(|d| [d.dimblk, d.dimblk1, d.dimblk2, d.dimldrblk])
.map(|h| crate::entities::arrowheads::label(doc, h))
.collect::<Vec<_>>()
.iter()
.map(String::as_str),
);
let mut lt_opts: Vec<String> = vec!["ByBlock".to_string()];
lt_opts.extend(doc.line_types.iter().map(|lt| lt.name.clone()));
@@ -1445,17 +1442,8 @@ impl OpenCADStudio {
.get(&self.ds_dimtxsty)
.map(|style| style.height)
.filter(|height| *height > 0.0);
let blk_name = |h: codec::types::Handle| -> String {
if h.is_null() {
"Default".to_string()
} else {
doc.block_records
.iter()
.find(|b| b.handle == h)
.map(|b| b.name.clone())
.unwrap_or_else(|| "Default".to_string())
}
};
let blk_name =
|h: codec::types::Handle| crate::entities::arrowheads::label(doc, h);
let lt_name = |h: codec::types::Handle| -> String {
if h.is_null() {
"ByBlock".to_string()