feat(blocks): implement native Array action authoring and dynamic evaluation

Add the native Array action (\BARRAY\, \BACTION ARRAY\, and Authoring Palettes Actions tab \Array\) supporting Linear, Polar, and XY parameters.

Prompt for column offset (and row offset for XY), constructing native \DynamicBlockData::ArrayAction(BlockArrayAction)\ with class \AcDbBlockArrayAction\ and 4 native connections (\Base\, \End\, \UpdatedBase\, \UpdatedEnd\) bound to the parameter node ID.

Evaluate step displacement along 1D rays and 2D grids, tagging clones with deterministic element IDs to reconcile representations cleanly across incremental grip adjustments without mutating baseline geometry. Validate native DWG/DXF roundtrip, reference independence, COPY isolation, rejections, and dock palette tool state.
This commit is contained in:
Karim Jerbi committed 2026-10-17 12:49:51 +01:00
1 parent 5a6a7fab93
commit 26071f2021
14 files changed
+806 -57

No files matched your search

+4 -2
View File
@@ -95,7 +95,7 @@ impl OpenCADStudio {
};
if !args.is_empty() && kind.is_none() {
self.command_line.push_error(
crate::t!("Supported actions: Move, Scale, Stretch, Rotate, Flip. Other action types are not wired yet.")
crate::t!("Supported actions: Move, Scale, Stretch, Rotate, Flip, Array. Other action types are not wired yet.")
.as_ref(),
);
return Task::none();
@@ -181,6 +181,7 @@ impl OpenCADStudio {
targets,
location,
frame,
array_offsets,
} => {
if self.tabs[i].id != document
|| self.tabs[i]
@@ -195,7 +196,7 @@ impl OpenCADStudio {
return Task::none();
}
let mut staged = self.tabs[i].scene.document.clone();
match actions::create_with_frame(
match actions::create_with_options(
&mut staged,
definition,
kind,
@@ -203,6 +204,7 @@ impl OpenCADStudio {
&targets,
location,
frame,
array_offsets,
) {
Ok(_) => {
self.push_undo_snapshot(i, &format!("{} action", kind.name()));
+1
View File
@@ -1575,6 +1575,7 @@ fn block_actions_rejected_outside_editor_preserve_running_command_and_stale_resu
targets: members(&app),
location: glam::DVec3::ZERO,
frame: None,
array_offsets: None,
});
assert_eq!(app.tabs[i].scene.document, before);
let _ = app.dispatch_command("LINE");
+8 -2
View File
@@ -395,11 +395,17 @@ impl OpenCADStudio {
cmd if cmd == "BACTION"
|| cmd.starts_with("BACTION ")
|| cmd == "BACTIONTOOL"
|| cmd.starts_with("BACTIONTOOL ") =>
|| cmd.starts_with("BACTIONTOOL ")
|| cmd == "BARRAY"
|| cmd.starts_with("BARRAY ") =>
{
Some(
self.start_block_action(
cmd.split_once(' ').map_or("", |(_, args)| args.trim()),
if cmd == "BARRAY" || cmd.starts_with("BARRAY ") {
"ARRAY"
} else {
cmd.split_once(' ').map_or("", |(_, args)| args.trim())
},
),
)
}
+7 -3
View File
@@ -181,12 +181,16 @@ impl OpenCADStudio {
return Task::none();
}
}
if matches!(cmd.split(' ').next(), Some("BACTION" | "BACTIONTOOL")) {
let args = cmd.split_once(' ').map_or("", |(_, a)| a.trim());
if matches!(cmd.split(' ').next(), Some("BACTION" | "BACTIONTOOL" | "BARRAY")) {
let args = if cmd == "BARRAY" || cmd.starts_with("BARRAY ") {
"ARRAY"
} else {
cmd.split_once(' ').map_or("", |(_, a)| a.trim())
};
if !args.is_empty() && crate::scene::block_actions::Kind::parse(args).is_none() {
self.command_line.push_error(
crate::t!(
"Supported actions: Move, Scale, Stretch, Rotate, Flip. Other action types are not wired yet."
"Supported actions: Move, Scale, Stretch, Rotate, Flip, Array. Other action types are not wired yet."
)
.as_ref(),
);
+25
View File
@@ -1334,6 +1334,31 @@ fn block_actions_stretch_palette_requires_distance_parameter() {
});
}
#[test]
fn block_actions_array_palette_requires_distance_parameter() {
with_stack(|| {
let mut app = authoring_app();
let _ = app.dispatch_command("BAUTHORPALETTE");
app.authoring_palette_tab = crate::ui::window::authoring_palettes::Tab::Actions;
let point = authoring_control(&app, "authoring-tool-array-action");
authoring_click(&mut app, point);
assert!(app.tabs[app.active_tab].active_cmd.is_none());
let _ = app.run_command_line("BPARAMETER POINT 0.5,0.5 0.7,0.7");
let point = authoring_control(&app, "authoring-tool-array-action");
authoring_click(&mut app, point);
assert!(app.tabs[app.active_tab].active_cmd.is_none());
let _ = app.run_command_line("BPARAMETER LINEAR 0,0 10,0 5,2");
let point = authoring_control(&app, "authoring-tool-array-action");
authoring_click(&mut app, point);
assert!(app.tabs[app.active_tab]
.active_cmd
.as_ref()
.unwrap()
.prompt()
.contains("Array"));
});
}
#[test]
fn block_actions_rotate_flip_palette_requires_matching_parameters() {
with_stack(|| {
+1
View File
@@ -416,6 +416,7 @@ impl OpenCADStudio {
self.action_error(Some(crate::scene::block_actions::Kind::Rotate)),
self.action_error(Some(crate::scene::block_actions::Kind::Flip)),
self.action_error(Some(crate::scene::block_actions::Kind::Stretch)),
self.action_error(Some(crate::scene::block_actions::Kind::Array)),
)
}
crate::ui::dock::PanelId::BlockPalette => {
+1 -1
View File
@@ -5267,7 +5267,7 @@ mod tablestyle_buffer_tests {
let mut app = OpenCADStudio::new_for_test();
app.automation_op(r#"{"op":"new"}"#);
app.ts_cell_textstyle[0] = "STALE_SENTINEL".to_string();
app.style_new(StyleKind::Table);
let _ = app.style_new(StyleKind::Table);
assert_ne!(
app.ts_cell_textstyle[0], "STALE_SENTINEL",
"buffers must reload from the fresh style"