From d69a5f0ac4a67b9175e9f334b7e5723fe8953450 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Thu, 15 Oct 2026 09:50:35 +0300 Subject: [PATCH 1/2] Block Definition dialog: preview, empty-block band, open in editor The name card draws the selected objects, or the existing definition the name picks. OK with no objects shows an in-window band offering to select objects or to define an empty block. A switch opens the new block in the block editor once it is defined; it is remembered with the other dialog choices. Automation gains block_def_dialog. Co-Authored-By: Claude Opus 5.5 --- locales/ar-SA/opencadstudio.ftl | 6 ++ locales/bg-BG/opencadstudio.ftl | 6 ++ locales/cs-CZ/opencadstudio.ftl | 6 ++ locales/de-DE/opencadstudio.ftl | 6 ++ locales/el-GR/opencadstudio.ftl | 6 ++ locales/en-US/opencadstudio.ftl | 6 ++ locales/es-ES/opencadstudio.ftl | 6 ++ locales/fi-FI/opencadstudio.ftl | 6 ++ locales/fr-FR/opencadstudio.ftl | 6 ++ locales/hi-IN/opencadstudio.ftl | 6 ++ locales/hu-HU/opencadstudio.ftl | 6 ++ locales/it-IT/opencadstudio.ftl | 6 ++ locales/ja-JP/opencadstudio.ftl | 6 ++ locales/ko-KR/opencadstudio.ftl | 6 ++ locales/lo-LA/opencadstudio.ftl | 6 ++ locales/nl-NL/opencadstudio.ftl | 6 ++ locales/pl-PL/opencadstudio.ftl | 6 ++ locales/pt-BR/opencadstudio.ftl | 6 ++ locales/ru-RU/opencadstudio.ftl | 6 ++ locales/tr-TR/opencadstudio.ftl | 6 ++ locales/zh-CN/opencadstudio.ftl | 6 ++ locales/zh-TW/opencadstudio.ftl | 6 ++ src/app/command_driver/entity_commit.rs | 8 ++ src/app/commands/blocks.rs | 32 +++++++ src/app/control/actions.rs | 21 +++++ src/app/mod.rs | 8 ++ src/app/update/mod.rs | 27 ++++-- src/app/view/modal.rs | 2 +- src/locale_catalog.rs | 4 + src/ui/window/block_definition.rs | 108 +++++++++++++++++++++++- 30 files changed, 329 insertions(+), 13 deletions(-) diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index 83175dbf1..c5c927550 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -6609,3 +6609,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index cc20285c3..f64f2f593 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -6597,3 +6597,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 66afe10f6..a40b9b788 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -6594,3 +6594,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 61eef3101..68a4aa7e8 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -6595,3 +6595,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 07d706269..8ba4f5c4b 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -6597,3 +6597,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index e241beee5..d8a0296c7 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -6596,3 +6596,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 9a4f1c86b..be24b3766 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -6597,3 +6597,9 @@ ucs-follow = .prompt = UCSFOLLOW: introduce 0 (conservar vista) o 1 (seguir SCP) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activa una ventana de modelo; no se aplica al espacio papel. .invalid = UCSFOLLOW: solo se permite 0 o 1. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 0ec188498..32065ae57 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -6594,3 +6594,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index ae9d437ba..23558d9eb 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -6597,3 +6597,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index db5a95696..e0c784c17 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -6585,3 +6585,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index a31cd56a4..7ad18adcf 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -6581,3 +6581,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 1d1563b99..e7d0cb8d6 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -6596,3 +6596,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 26de504d7..a96c75f29 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -6585,3 +6585,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index c43410f04..8e3ee12f4 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -6596,3 +6596,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/lo-LA/opencadstudio.ftl b/locales/lo-LA/opencadstudio.ftl index 8f51fc302..b7f78d302 100644 --- a/locales/lo-LA/opencadstudio.ftl +++ b/locales/lo-LA/opencadstudio.ftl @@ -6597,3 +6597,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 761118ba4..852cf6c41 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -6595,3 +6595,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 16d72a254..16b9c1b2d 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -6596,3 +6596,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 3ffec2a7b..8d943503a 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -6597,3 +6597,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index 3840440f1..7b30bc16a 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -6598,3 +6598,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 2733014ca..24065801d 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -6518,3 +6518,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Oluşturduktan sonra blok düzenleyicide aç + .remembered = Ekranda belirt, nesne seçeneği, dağıtmaya izin ve bu anahtar sonraki açılışta hatırlanır. + .no-objects = Blok için nesne seçilmedi. + .create-empty = Boş blok oluştur diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 81edca569..cb132f887 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -6580,3 +6580,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index b7941d3ed..56390b604 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -6592,3 +6592,9 @@ ucs-follow = .prompt = UCSFOLLOW: enter 0 (keep view) or 1 (follow UCS) <__ocs_fmt_0__>: .paper-space = UCSFOLLOW: activate a model viewport; paper space cannot follow the UCS. .invalid = UCSFOLLOW: only 0 or 1 is allowed. + +block-def-dialog = + .open-in-editor = Open in block editor after creating + .remembered = Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time. + .no-objects = No objects are selected for the block. + .create-empty = Create empty block diff --git a/src/app/command_driver/entity_commit.rs b/src/app/command_driver/entity_commit.rs index 6efce0043..559961a35 100644 --- a/src/app/command_driver/entity_commit.rs +++ b/src/app/command_driver/entity_commit.rs @@ -810,6 +810,14 @@ impl OpenCADStudio { self.refresh_properties(); self.note_recent_block(&name); self.refresh_block_palette(); + // The Block Definition dialog asked to edit the new block. + if self + .block_definition_open_editor + .take() + .is_some_and(|wanted| wanted.eq_ignore_ascii_case(&name)) + { + return Some(self.dispatch_command(&format!("BEDIT_OPEN:{name}"))); + } } Err(err) => { self.discard_last_undo_entry(i); diff --git a/src/app/commands/blocks.rs b/src/app/commands/blocks.rs index e59fdbb38..59f04f27f 100644 --- a/src/app/commands/blocks.rs +++ b/src/app/commands/blocks.rs @@ -61,7 +61,11 @@ impl OpenCADStudio { remembered.objects_on_screen = state.objects_specify_onscreen; remembered.mode = state.object_mode; remembered.allow_exploding = state.allow_exploding; + remembered.open_in_editor = state.open_in_editor; } + self.block_definition_open_editor = state + .open_in_editor + .then(|| state.name.trim().to_string()); self.persist_settings_if_changed(); let i = self.active_tab; let base_point = state.parse_base_point(); @@ -122,6 +126,32 @@ impl OpenCADStudio { ) } + /// Draw the selected objects, or else the existing definition the name + /// picks, in the Block Definition dialog's name card. + pub(in crate::app) fn refresh_block_definition_preview(&mut self) { + let scene = &self.tabs[self.active_tab].scene; + let Some(state) = self.block_definition.as_mut() else { + return; + }; + state.preview = if state.selected_handles.is_empty() { + let name = state.name.trim(); + state + .existing_names + .iter() + .find(|existing| existing.eq_ignore_ascii_case(name)) + .map(|existing| scene.block_preview_wires(existing)) + .unwrap_or_default() + } else { + state.empty_prompt = false; + let entities: Vec<_> = state + .selected_handles + .iter() + .filter_map(|handle| scene.document.get_entity(*handle).cloned()) + .collect(); + scene.wires_for_entities(&entities) + }; + } + pub(super) fn dispatch_blocks(&mut self, cmd: &str, i: usize) -> Option> { match cmd { // ── BASE — drawing insertion base point ─────────────────────── @@ -469,6 +499,7 @@ impl OpenCADStudio { ), ); self.active_modal = Some(crate::app::ModalKind::BlockDefinition); + self.refresh_block_definition_preview(); } cmd if cmd.starts_with("BLOCK_POINT_PICKED ") => { @@ -504,6 +535,7 @@ impl OpenCADStudio { } self.tabs[i].active_cmd = None; self.active_modal = Some(crate::app::ModalKind::BlockDefinition); + self.refresh_block_definition_preview(); } cmd if cmd.starts_with("WBLOCK_POINT_PICKED ") => { diff --git a/src/app/control/actions.rs b/src/app/control/actions.rs index aa397d1f4..f6dcc36af 100644 --- a/src/app/control/actions.rs +++ b/src/app/control/actions.rs @@ -118,6 +118,7 @@ pub(super) const NAMES: &[&str] = &[ "ribbon_dropdown", "keytips", "dialog_ok", + "block_def_dialog", "attdef_dialog", "field_dialog", "sheet_set", @@ -1502,6 +1503,26 @@ impl OpenCADStudio { } return Ok(Task::none()); } + // The open Block Definition dialog: `name=…`, `open_in_editor=0|1`, + // `base_on_screen=0|1`, `objects_on_screen=0|1`, `create_empty`, + // `select_objects`. + "block_def_dialog" => { + if self.block_definition.is_none() { + return Err(failure("no_dialog", "The Block Definition dialog is not open")); + } + let value = string(req, "value")?; + let (key, v) = value.split_once('=').unwrap_or((value, "")); + let on = v == "1"; + match key { + "name" => Message::BlockDefNameSelect(v.to_string()), + "open_in_editor" => Message::BlockDefOpenInEditor(on), + "base_on_screen" => Message::BlockDefBaseOnScreen(on), + "objects_on_screen" => Message::BlockDefObjectsOnScreen(on), + "create_empty" => Message::BlockDefCreateEmpty, + "select_objects" => Message::BlockDefSelectObjects, + _ => return Err(failure("invalid_value", "Unknown block definition field")), + } + } // One field of the open attribute definition dialog: `tag=…`, // `prompt=…`, `default=…`, `justify=MC`, `style=…`, `height=…`, // `rotation=…`, `width=…`, `on_screen=0|1`, `x=…` / `y=…` / `z=…`, diff --git a/src/app/mod.rs b/src/app/mod.rs index 8e3bebe25..460539553 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -577,6 +577,9 @@ pub(super) struct OpenCADStudio { drawing_units: Option, /// Working copy of the Block Definition dialog; `None` while it is closed. block_definition: Option, + /// The block the Block Definition dialog defines with "Open in block + /// editor" on; it opens once the definition is committed. + block_definition_open_editor: Option, block_edit_picker: Option, block_save_as: Option, /// PDF dialogs' working copies; `None` while closed. @@ -3441,6 +3444,10 @@ pub enum Message { BlockDefConfirmRedefine(bool), BlockDefDismissError, BlockDefHelp, + /// Block Definition: open the new block in the block editor. + BlockDefOpenInEditor(bool), + /// Block Definition: define the block with no objects. + BlockDefCreateEmpty, /// Write Block (WBLOCK) dialog messages WblockSourceMode(crate::ui::window::wblock::WblockSourceMode), WblockBlockName(String), @@ -4602,6 +4609,7 @@ impl OpenCADStudio { layer_translator: None, drawing_units: None, block_definition: None, + block_definition_open_editor: None, block_edit_picker: None, block_save_as: None, pdf_attach: None, diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index bcfb76142..26af706d5 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -5686,6 +5686,7 @@ impl OpenCADStudio { state.error_message = None; state.confirm_redefine = None; } + self.refresh_block_definition_preview(); Task::none() } Message::BlockDefNameSelect(chosen) => { @@ -5701,6 +5702,7 @@ impl OpenCADStudio { ); state.set_base_point(glam::DVec3::new(base.x, base.y, base.z)); } + self.refresh_block_definition_preview(); Task::none() } Message::BlockDefBaseOnScreen(val) => { @@ -5821,6 +5823,12 @@ impl OpenCADStudio { } Task::none() } + Message::BlockDefOpenInEditor(on) => { + if let Some(state) = self.block_definition.as_mut() { + state.open_in_editor = on; + } + Task::none() + } Message::BlockDefHelp => { self.command_line.push_info( crate::t!("BLOCK creates a block definition from objects you select.").as_ref(), @@ -5836,7 +5844,8 @@ impl OpenCADStudio { } self.commit_block_definition(true) } - Message::BlockDefApply => { + m @ (Message::BlockDefApply | Message::BlockDefCreateEmpty) => { + let create_empty = matches!(m, Message::BlockDefCreateEmpty); let Some(state) = self.block_definition.as_mut() else { return Task::none(); }; @@ -5859,15 +5868,16 @@ impl OpenCADStudio { Some(crate::t!("Block name cannot start with '*'.").into_owned()); return Task::none(); } - if !state.objects_specify_onscreen && state.selected_handles.is_empty() { - state.error_message = Some( - crate::t!( - "No objects selected. You must select objects to define a block." - ) - .into_owned(), - ); + // No objects: the band offers to select some or to define + // an empty block. + if !state.objects_specify_onscreen + && state.selected_handles.is_empty() + && !create_empty + { + state.empty_prompt = true; return Task::none(); } + state.empty_prompt = false; let i = self.active_tab; if self.tabs[i] .scene @@ -7394,6 +7404,7 @@ impl OpenCADStudio { .collect(); block_def.error_message = None; self.active_modal = Some(super::ModalKind::BlockDefinition); + self.refresh_block_definition_preview(); } else if let Some(ref mut wblock) = self.wblock { wblock.selected_handles = self.tabs[i] .scene diff --git a/src/app/view/modal.rs b/src/app/view/modal.rs index dfb36033d..e233386bf 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -505,7 +505,7 @@ impl OpenCADStudio { } super::super::ModalKind::BlockDefinition => { let state = self.block_definition.as_ref()?; - sized_flow(ex, 580, 390, |flow| { + sized_flow(ex, 600, 560, |flow| { crate::ui::window::block_definition::view_window(state, flow) }) } diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 46a790cc3..379e342b1 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -3,6 +3,10 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static str)> { match source { + "Open in block editor after creating" => Some(("block-def-dialog", "open-in-editor")), + "Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time." => Some(("block-def-dialog", "remembered")), + "No objects are selected for the block." => Some(("block-def-dialog", "no-objects")), + "Create empty block" => Some(("block-def-dialog", "create-empty")), "{} entities added" => Some(("ref-edit", "entities-added")), "Use REFCLOSE or the Refedit toolbar to end reference editing session." => Some(("ref-edit", "use-refclose")), "Specify opposite corner:" => Some(("ref-edit", "opposite-corner")), diff --git a/src/ui/window/block_definition.rs b/src/ui/window/block_definition.rs index 8f76f5331..5d42c2153 100644 --- a/src/ui/window/block_definition.rs +++ b/src/ui/window/block_definition.rs @@ -11,6 +11,7 @@ use iced::widget::{ use iced::{Background, Border, Element, Fill, Length, Theme}; use crate::app::Message; +use crate::scene::WireModel; use crate::modules::draw::units; use crate::t; use crate::ui::style::common::muted_style; @@ -73,6 +74,14 @@ pub struct BlockDefinitionState { pub error_message: Option, pub confirm_redefine: Option, pub desc_placeholder: String, + /// The selected objects, or the chosen existing definition, as drawn in + /// the name card. + pub preview: Vec, + /// OK was pressed with no objects: the band offers to select some or to + /// define an empty block. + pub empty_prompt: bool, + /// Open the new block in the block editor once it is defined. + pub open_in_editor: bool, } impl Clone for BlockDefinitionState { @@ -100,6 +109,9 @@ impl Clone for BlockDefinitionState { error_message: self.error_message.clone(), confirm_redefine: self.confirm_redefine.clone(), desc_placeholder: self.desc_placeholder.clone(), + preview: self.preview.clone(), + empty_prompt: self.empty_prompt, + open_in_editor: self.open_in_editor, } } } @@ -132,6 +144,9 @@ impl BlockDefinitionState { error_message: None, confirm_redefine: None, desc_placeholder: crate::t!("Enter block description...").into_owned(), + preview: Vec::new(), + empty_prompt: false, + open_in_editor: remembered.open_in_editor, } } @@ -219,6 +234,44 @@ pub(crate) fn labeled_checkbox<'a>( .into() } +/// The in-window warning shown when OK is pressed with no objects: select +/// some, or define an empty block. +fn empty_band<'a>() -> Element<'a, Message> { + container( + row![ + crate::ui::icons::semantic(ICON_WARN, 15.0), + text(t!("No objects are selected for the block.")) + .size(11) + .width(Fill), + button(text(t!("Select objects")).size(11)) + .on_press(Message::BlockDefSelectObjects) + .style(button_style(false)) + .padding([4, 12]), + button(text(t!("Create empty block")).size(11)) + .on_press(Message::BlockDefCreateEmpty) + .style(button_style(false)) + .padding([4, 12]), + ] + .spacing(10) + .align_y(iced::Center), + ) + .padding([7, 10]) + .width(Fill) + .style(|theme: &Theme| { + let warn = theme.palette().warning.base.color; + container::Style { + background: Some(Background::Color(warn.scale_alpha(0.16))), + border: Border { + width: 1.0, + radius: 6.0.into(), + color: warn, + }, + ..Default::default() + } + }) + .into() +} + pub fn view_window<'a>( state: &'a BlockDefinitionState, sizing: crate::ui::modal::ModalSizing, @@ -274,7 +327,21 @@ pub fn view_window<'a>( name_column = name_column.push(hint); } - let name_section = name_column; + // The name card carries a live preview of the selected objects, or of + // the existing definition the name picks. + let preview = container(super::block_palette::preview_canvas(&state.preview, 86.0)) + .padding(3) + .width(Length::Fixed(150.0)) + .height(Length::Fixed(92.0)) + .style(crate::ui::window::pdf_dialogs::well_style); + let name_section = container( + row![name_column, preview] + .spacing(12) + .align_y(iced::Center), + ) + .padding([8, 10]) + .width(Fill) + .style(crate::ui::window::pdf_dialogs::card_style); // ── Group: Base Point ───────────────────────────────────────────────────── let base_onscreen = labeled_checkbox( @@ -587,16 +654,49 @@ pub fn view_window<'a>( // ── Footer Bar ──────────────────────────────────────────────────────────── let actions = row![ + button(text("?").size(12)) + .on_press(Message::BlockDefHelp) + .style(button_style(false)) + .padding([3, 10]), Space::new().width(Fill), - dialog_button(t!("OK"), Message::BlockDefApply, true), dialog_button(t!("Cancel"), Message::CloseModal, false), - dialog_button(t!("Help"), Message::BlockDefHelp, false), + dialog_button(t!("OK"), Message::BlockDefApply, true), ] .spacing(8) .align_y(iced::Center); + // ── Open in the block editor ───────────────────────────────────────────── + let editor_card = container( + column![ + row![ + iced::widget::toggler(state.open_in_editor) + .on_toggle(Message::BlockDefOpenInEditor) + .size(16.0), + text(t!("Open in block editor after creating")).size(11), + ] + .spacing(8) + .align_y(iced::Center), + text(t!( + "Specify On-screen, the object option, Allow exploding and this switch are remembered for the next time." + )) + .size(10) + .style(muted_style), + ] + .spacing(4), + ) + .padding([8, 10]) + .width(Fill) + .style(crate::ui::window::pdf_dialogs::card_style); + // ── Error Banner ────────────────────────────────────────────────────────── - let mut main_column = column![name_section, main_body, actions,] + let mut main_column = column![name_section]; + if state.empty_prompt { + main_column = main_column.push(empty_band()); + } + let mut main_column = main_column + .push(main_body) + .push(editor_card) + .push(actions) .spacing(6) .padding([8, 10]) .width(sizing.width); From 367c89a2c8e342dbbfda67ce4b1490bdbacb709e Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Thu, 15 Oct 2026 10:49:59 +0300 Subject: [PATCH 2/2] Block Definition dialog: preview references, amber band A selected block reference is previewed from its definition placed by the reference's transform; the no-objects band is amber. Co-Authored-By: Claude Opus 5.5 --- src/app/commands/blocks.rs | 39 ++++++++++++++++++++++++++----- src/ui/window/block_definition.rs | 5 ++-- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/app/commands/blocks.rs b/src/app/commands/blocks.rs index 59f04f27f..a10432569 100644 --- a/src/app/commands/blocks.rs +++ b/src/app/commands/blocks.rs @@ -143,12 +143,39 @@ impl OpenCADStudio { .unwrap_or_default() } else { state.empty_prompt = false; - let entities: Vec<_> = state - .selected_handles - .iter() - .filter_map(|handle| scene.document.get_entity(*handle).cloned()) - .collect(); - scene.wires_for_entities(&entities) + let mut wires = Vec::new(); + for handle in &state.selected_handles { + match scene.document.get_entity(*handle) { + // A reference is drawn from its definition, placed by the + // reference's transform. + Some(codec::EntityType::Insert(insert)) => { + let base = crate::scene::render_graph::block_base_point( + &scene.document, + &insert.block_name, + ); + let xform = + crate::scene::render_graph::insert_transform_with_base(insert, base); + for mut wire in scene.block_preview_wires(&insert.block_name) { + for p in &mut wire.points { + if p.iter().all(|v| v.is_finite()) { + let q = xform.apply(codec::types::Vector3::new( + f64::from(p[0]), + f64::from(p[1]), + f64::from(p[2]), + )); + *p = [q.x as f32, q.y as f32, q.z as f32]; + } + } + wires.push(wire); + } + } + Some(entity) => { + wires.extend(scene.wires_for_entities(std::slice::from_ref(entity))) + } + None => {} + } + } + wires }; } diff --git a/src/ui/window/block_definition.rs b/src/ui/window/block_definition.rs index 5d42c2153..e500f96c0 100644 --- a/src/ui/window/block_definition.rs +++ b/src/ui/window/block_definition.rs @@ -257,8 +257,9 @@ fn empty_band<'a>() -> Element<'a, Message> { ) .padding([7, 10]) .width(Fill) - .style(|theme: &Theme| { - let warn = theme.palette().warning.base.color; + .style(|_: &Theme| { + // Amber: the theme's warning colour is not a caution tone in every theme. + let warn = iced::Color::from_rgb8(0xD9, 0xA4, 0x00); container::Style { background: Some(Background::Color(warn.scale_alpha(0.16))), border: Border {