feat(blocks): implement Block Definition dialog (BLOCK/BMAKE) with on-screen sequencing

Implement the complete Block Definition dialog window launched via BLOCK
and BMAKE commands, featuring standard CAD layout, workflows, and entity
creation pipelines.

- Dialog UI & Layout:
  - 2-column compact CAD grid with synchronized vertical heights (580 × 390px).
  - Editable combobox with existing block names, invalid character validation,
    and inline redefinition badge.
  - Multi-line text editor widget for block descriptions (replacing single-line input).
  - Quick Select (QSELECT) button integration restoring dialog state upon completion.
  - Confirmation guard when attempting to redefine an existing block definition.
  - Centered radio buttons, clean coordinate inputs, and accessible keyboard shortcuts.

- Base Point & On-Screen Workflow:
  - BlockOnScreenCommand strictly enforces prompt sequencing: Base point is
    prompted before object selection.
  - Dynamically recalculates world_to_block and block_to_world affine transforms
    when base point is specified on-screen, preventing stale (0,0,0) normalization.
  - Supports Retain, Convert to block (INSERT placement), and Delete modes.

- Explode Enforcement:
  - Guarded EXPLODE command to prevent decomposing blocks flagged as non-explodable.

- Dynamic Themed Icons:
  - Added pick_point.svg, select_objects.svg, quick_select.svg, and warning_triangle.svg.
  - Routed all icons through the semantic theming engine so line work (#B4B6B9)
    and accents (#6DB7ED) adapt automatically to the active theme.

- Tests:
  - Added unit tests for retain/convert/delete modes, redefinition, explodable flag,
    on-screen base point transform recalculation, and command sequencing.
This commit is contained in:
Karim Jerbi committed 2026-09-22 17:05:15 +01:00
1 parent 889a19c362
commit 652f4d6bbc
19 files changed
+2316 -318

No files matched your search

+472 -232
View File
File diff suppressed because it is too large. Load diff
+9 -9
View File
@@ -495,8 +495,8 @@ impl OpenCADStudio {
return Some(Task::none());
}
let insert = match self.tabs[i].scene.document.get_entity(insert_handle) {
Some(acadrust::EntityType::Insert(ins)) => ins.clone(),
let block_name = match self.tabs[i].scene.document.get_entity(insert_handle) {
Some(acadrust::EntityType::Insert(ins)) => ins.block_name.clone(),
_ => {
self.command_line
.push_error(crate::t!("BEDIT: selected object is not a block reference.").as_ref());
@@ -509,13 +509,13 @@ impl OpenCADStudio {
.scene
.document
.block_records
.get(&insert.block_name)
.get(&block_name)
{
Some(br) => (br.handle, br.flags.is_xref),
None => {
self.command_line.push_error(crate::tf!(
"BEDIT: block \"{}\" not found.",
insert.block_name
block_name
).as_ref());
return Some(Task::none());
}
@@ -532,7 +532,7 @@ impl OpenCADStudio {
{
self.tabs[i].active_cmd = None;
return Some(Task::done(Message::BlockEditSwitch(
insert.block_name.clone(),
block_name.clone(),
)));
}
@@ -543,7 +543,7 @@ impl OpenCADStudio {
.scene
.document
.block_records
.get(&insert.block_name)
.get(&block_name)
.unwrap();
br.entity_handles
.iter()
@@ -564,7 +564,7 @@ impl OpenCADStudio {
.entities()
.filter_map(|entity| match entity {
acadrust::EntityType::Insert(reference)
if reference.block_name.eq_ignore_ascii_case(&insert.block_name)
if reference.block_name.eq_ignore_ascii_case(&block_name)
&& !reference.attributes.is_empty() =>
{
Some((
@@ -607,7 +607,7 @@ impl OpenCADStudio {
}
self.tabs[i].scene.block_edit_block = Some(br_handle);
self.tabs[i].block_edits.push(BlockEditSession {
block_name: insert.block_name.clone(),
block_name: block_name.clone(),
br_handle,
return_layout,
return_block,
@@ -651,7 +651,7 @@ impl OpenCADStudio {
self.tabs[i].dirty = true;
self.command_line.push_info(crate::tf!(
"BEDIT: Editing block \"{}\". Use Save Block or Discard to finish.",
insert.block_name
block_name
).as_ref());
}