Merge remote-tracking branch 'origin/main' into codex/block-definition-command-line

# Conflicts:
#	src/app/commands/inquiry.rs
#	src/modules/draw/modify/block_edit.rs
This commit is contained in:
ramox81 committed 2026-10-14 17:51:56 +03:00
commit 99e30f1548
33 files changed
+1660 -199

No files matched your search

+1 -1
View File
@@ -38,7 +38,7 @@ impl OpenCADStudio {
let mut saved = 0usize;
let mut skipped = 0usize;
for t in 0..self.tabs.len() {
if self.tabs[t].is_start {
if self.tabs[t].is_start || self.tabs[t].block_test.is_some() {
continue;
}
if let Some(path) = self.tabs[t].current_path.clone() {
+121 -106
View File
@@ -458,7 +458,6 @@ impl OpenCADStudio {
}
"BEDIT" => {
use crate::modules::draw::modify::block_edit::BlockEditPickCommand;
if self.tabs[i].refedit_session.is_some() {
self.command_line.push_error(
crate::t!("BEDIT: finish the active REFEDIT (REFCLOSE) first.").as_ref(),
@@ -476,12 +475,46 @@ impl OpenCADStudio {
return Some(Task::none());
}
}
let cmd_obj = BlockEditPickCommand::new();
self.command_line.push_info(&cmd_obj.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd_obj));
self.open_bedit_dialog(i);
}
}
cmd if cmd.starts_with("BEDIT_NEW:") => {
if self.tabs[i].refedit_session.is_some() {
self.command_line.push_error(
crate::t!("BEDIT: finish the active REFEDIT (REFCLOSE) first.").as_ref(),
);
return Some(Task::none());
}
let raw_name = &cmd["BEDIT_NEW:".len()..];
let block_name = match crate::scene::block_service::validate_block_name(raw_name) {
Ok(n) => n,
Err(e) => {
self.command_line.push_error(&e.to_string());
return Some(Task::none());
}
};
if let Some(br) = self.tabs[i].scene.document.block_records.get(&block_name) {
let real_name = br.name.clone();
return Some(self.dispatch_command(&format!("BEDIT_OPEN:{real_name}")));
}
if self.tabs[i].active_block_edit_session().is_some() {
return Some(self.request_block_edit_close(
i,
crate::app::block_editor::BlockEditContinuation::Create(block_name),
));
}
let pre_doc = self.tabs[i].scene.document.clone();
self.tabs[i]
.scene
.define_block_raw(&block_name, codec::types::Vector3::ZERO, vec![]);
let Some(record) = self.tabs[i].scene.document.block_records.get(&block_name) else {
return Some(Task::none());
};
let br_handle = record.handle;
self.start_block_edit_session(i, block_name, br_handle, Some(pre_doc));
}
cmd if cmd.starts_with("BEDIT_BEGIN:") || cmd.starts_with("BEDIT_OPEN:") => {
if self.tabs[i].refedit_session.is_some() {
self.command_line.push_error(
@@ -489,7 +522,6 @@ impl OpenCADStudio {
);
return Some(Task::none());
}
use crate::modules::draw::modify::block_edit::BlockEditSession;
use codec::Handle;
let block_name = if let Some(name) = cmd.strip_prefix("BEDIT_OPEN:") {
@@ -512,36 +544,6 @@ impl OpenCADStudio {
}
};
// A name that is not a block yet opens an empty definition,
// dropped again when the editor closes without saving.
let mut created_for_edit = false;
if self.tabs[i].scene.document.block_records.get(&block_name).is_none()
&& cmd.starts_with("BEDIT_OPEN:")
&& crate::ui::window::block_definition::invalid_block_name_char(&block_name)
.is_none()
&& !block_name.trim().is_empty()
&& !block_name.starts_with('*')
{
let options = crate::scene::CreateBlockOptions {
name: block_name.clone(),
handles: Vec::new(),
base_point: glam::DVec3::ZERO,
world_to_block: codec::types::Transform::identity(),
block_to_world: codec::types::Transform::identity(),
mode: crate::ui::window::block_definition::BlockObjectMode::Retain,
annotative: false,
match_orientation: false,
scale_uniformly: false,
allow_exploding: true,
unit: self.tabs[i].scene.document.header.insertion_units,
description: String::new(),
hyperlink_url: String::new(),
hyperlink_desc: String::new(),
redefine: false,
};
created_for_edit =
self.tabs[i].scene.create_block_with_options(options).is_ok();
}
// Resolve the block record; reject external references (xrefs).
let (br_handle, is_xref) =
match self.tabs[i].scene.document.block_records.get(&block_name) {
@@ -569,82 +571,14 @@ impl OpenCADStudio {
));
}
// Capture the camera before the editor reframes it, so leaving
// the block editor returns the view exactly where it was (#425).
let current_camera = self.tabs[i].scene.camera.borrow().clone();
let current_ucs = self.tabs[i].active_ucs.clone();
let (return_layout, return_block, return_camera) =
if let Some(parent_index) = self.tabs[i].active_block_edit {
let parent = &mut self.tabs[i].block_edits[parent_index];
parent.editor_camera = current_camera.clone();
parent.editor_ucs = current_ucs;
(
parent.return_layout.clone(),
Some(parent.block_name.clone()),
parent.return_camera.clone(),
)
} else {
self.tabs[i].scene.sync_camera_to_document();
(
self.tabs[i].scene.current_layout.clone(),
None,
current_camera.clone(),
)
};
// A block editor renders in model style; switch to Model first so
// the paper-space code paths stay off, then scope to the block.
if self.tabs[i].scene.current_layout != "Model" {
self.tabs[i].scene.set_current_layout("Model".to_string());
}
self.tabs[i].scene.block_edit_block = Some(br_handle);
self.tabs[i].block_edits.push(BlockEditSession {
block_name: block_name.clone(),
br_handle,
return_layout,
return_block,
return_camera,
editor_camera: current_camera,
editor_ucs: None,
created_for_edit,
});
self.tabs[i].active_block_edit = Some(self.tabs[i].block_edits.len() - 1);
// Every BEDIT tab owns an isolated local UCS. Never inherit or
// overwrite the drawing's model-space UCS.
self.tabs[i].refresh_active_ucs();
self.tabs[i].scene.deselect_all();
// The first click of a double-click selects the model-space
// INSERT and populates the cached grip overlay. Clearing only
// Scene::selected leaves that INSERT's grips active inside the
// block editor, where dragging one moves the outer reference
// instead of block-local geometry (#517).
self.tabs[i].active_grip = None;
self.grip_hover = None;
self.grip_popup = None;
self.visibility_popup = None;
// Entering BEDIT changes which block is assembled, not the
// cached geometry of that block's entities.
self.tabs[i].scene.bump_geometry_no_blocks();
// Frame the camera on the block's own geometry (block-local, near
// origin) — fit_all() goes through current_layout_block_handle so
// it already scopes to the edited block. Without this the view
// stays wherever model/paper space was. (#261)
self.tabs[i].scene.fit_all();
let editor_camera = self.tabs[i].scene.camera.borrow().clone();
if let Some(session) = self.tabs[i].active_block_edit_session_mut() {
session.editor_camera = editor_camera;
}
self.tabs[i].last_synced_camera_gen = self.tabs[i].scene.camera_generation;
self.refresh_properties();
self.tabs[i].active_cmd = None;
self.begin_block_edit_history(i);
self.command_line
.push_info(crate::tf!("Block Editor: editing {}", block_name).as_ref());
self.start_block_edit_session(i, block_name, br_handle, None);
}
"BSAVE" => {
self.save_block_edit(i);
}
"BTESTBLOCK" => return Some(self.open_block_test(i)),
"BCLOSETESTBLOCK" => return Some(self.close_block_test(i)),
"BSAVEAS" => {
self.open_block_save_as_dialog(i);
}
@@ -1056,6 +990,87 @@ impl OpenCADStudio {
Some(self.finish_dispatch(cmd))
}
pub(in crate::app) fn start_block_edit_session(
&mut self,
i: usize,
block_name: String,
br_handle: codec::Handle,
unpersisted_pre_document: Option<codec::CadDocument>,
) {
use crate::modules::draw::modify::block_edit::BlockEditSession;
// Capture the camera before the editor reframes it, so leaving
// the block editor returns the view exactly where it was (#425).
let current_camera = self.tabs[i].scene.camera.borrow().clone();
let current_ucs = self.tabs[i].active_ucs.clone();
let (return_layout, return_block, return_camera) =
if let Some(parent_index) = self.tabs[i].active_block_edit {
let parent = &mut self.tabs[i].block_edits[parent_index];
parent.editor_camera = current_camera.clone();
parent.editor_ucs = current_ucs;
(
parent.return_layout.clone(),
Some(parent.block_name.clone()),
parent.return_camera.clone(),
)
} else {
self.tabs[i].scene.sync_camera_to_document();
(
self.tabs[i].scene.current_layout.clone(),
None,
current_camera.clone(),
)
};
// A block editor renders in model style; switch to Model first so
// the paper-space code paths stay off, then scope to the block.
if self.tabs[i].scene.current_layout != "Model" {
self.tabs[i].scene.set_current_layout("Model".to_string());
}
self.tabs[i].scene.block_edit_block = Some(br_handle);
self.tabs[i].block_edits.push(BlockEditSession {
block_name: block_name.clone(),
br_handle,
return_layout,
return_block,
return_camera,
editor_camera: current_camera,
editor_ucs: None,
unpersisted_pre_document,
});
self.tabs[i].active_block_edit = Some(self.tabs[i].block_edits.len() - 1);
// Every BEDIT tab owns an isolated local UCS. Never inherit or
// overwrite the drawing's model-space UCS.
self.tabs[i].refresh_active_ucs();
self.tabs[i].scene.deselect_all();
// The first click of a double-click selects the model-space
// INSERT and populates the cached grip overlay. Clearing only
// Scene::selected leaves that INSERT's grips active inside the
// block editor, where dragging one moves the outer reference
// instead of block-local geometry (#517).
self.tabs[i].active_grip = None;
self.grip_hover = None;
self.grip_popup = None;
self.visibility_popup = None;
// Entering BEDIT changes which block is assembled, not the
// cached geometry of that block's entities.
self.tabs[i].scene.bump_geometry_no_blocks();
// Frame the camera on the block's own geometry (block-local, near
// origin) — fit_all() goes through current_layout_block_handle so
// it already scopes to the edited block. Without this the view
// stays wherever model/paper space was. (#261)
self.tabs[i].scene.fit_all();
let editor_camera = self.tabs[i].scene.camera.borrow().clone();
if let Some(session) = self.tabs[i].active_block_edit_session_mut() {
session.editor_camera = editor_camera;
}
self.tabs[i].last_synced_camera_gen = self.tabs[i].scene.camera_generation;
self.refresh_properties();
self.tabs[i].active_cmd = None;
self.begin_block_edit_history(i);
self.command_line
.push_info(crate::tf!("Block Editor: editing {}", block_name).as_ref());
}
pub(in crate::app) fn restore_after_block_edit_close(
&mut self,
i: usize,
+27
View File
@@ -150,6 +150,33 @@ impl OpenCADStudio {
};
let resolved = self.resolve_alias(cmd);
let cmd = resolved.as_deref().unwrap_or(cmd);
if self.tabs[i].block_test.is_some() {
let name = cmd.split([' ', ':']).next().unwrap_or(cmd);
if matches!(
name,
"BEDIT"
| "BEDIT_BEGIN"
| "BEDIT_OPEN"
| "REFEDIT"
| "REFEDIT_BEGIN"
| "BSAVE"
| "BSAVEAS"
| "SAVE"
| "QSAVE"
| "SAVEAS"
| "SAVEALL"
| "WBLOCK"
| "EXPORT"
| "ETRANSMIT"
| "ARCHIVE"
) {
self.command_line.push_error(
crate::t!("Close Test Block before saving or opening another block editor.")
.as_ref(),
);
return Task::none();
}
}
if is_spacemouse_command(cmd) {
return self.run_action(cmd);
}