feat(block): BEDIT block editor as a dedicated space tab (#261)

Selecting a block and running BEDIT (command, ribbon button, or a
double-click on the block) opens a block-editor "space" tab, named after
the block, alongside Model/Layout in the status bar. The tab draws ONLY
the block record's own entities in block-local coordinates; edits are
live on the definition, so every INSERT updates. Save Block / Discard
buttons (side toolbar) finish the session; Discard restores the on-entry
snapshot. Space switching is locked while a block is open, and BEDIT and
REFEDIT are mutually exclusive.

Implementation: a Scene.block_edit_block override at the top of
current_layout_block_handle scopes render/pick/new-entity ownership to the
edited block while current_layout stays "Model" (so the pervasive
Model-vs-paper code keeps model behaviour). The model render/pick paths
that hardcoded model_space_block_handle (wire tile set, hatch render +
pick, block-instance meshes) are made binding-aware so the block isolation
actually takes effect and model content does not leak in. Entering fits
the camera to the block (fit_all already scopes via the binding). Renaming
the block tab renames the block itself — new Scene::rename_block re-keys
the record, updates the Block marker and repoints every INSERT reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-14 13:03:04 +03:00
1 parent a4fc34c6a2
commit 3548de332c
8 files changed
+435 -18

No files matched your search

+43 -5
View File
@@ -2592,9 +2592,9 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
}
// Double-clicking a block with attributes opens the
// attribute editor (edit its values); a block with
// no attributes enters in-place block edit (REFEDIT),
// so its geometry can be edited and the change
// reflects in every instance. (#136, #192)
// no attributes opens the block editor (BEDIT) — a
// space tab scoped to the block's own geometry, so
// edits reflect in every instance. (#136, #192, #261)
let insert_has_attrs = matches!(
self.tabs[i].scene.document.get_entity(handle),
Some(AcadEntityType::Insert(ins)) if !ins.attributes.is_empty()
@@ -2606,9 +2606,12 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
self.tabs[i].scene.document.get_entity(handle),
Some(AcadEntityType::Insert(_))
);
if is_insert && self.tabs[i].refedit_session.is_none() {
if is_insert
&& self.tabs[i].refedit_session.is_none()
&& self.tabs[i].block_edit.is_none()
{
return Task::done(Message::Command(format!(
"REFEDIT_BEGIN:{}",
"BEDIT_BEGIN:{}",
handle.value()
)));
}
@@ -2989,6 +2992,14 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
pub(super) fn on_layout_switch(&mut self, name: String) -> Task<Message> {
let i = self.active_tab;
// A BEDIT block editor locks the active space; finish it with
// Save Block or Discard before switching spaces. (#261)
if self.tabs[i].block_edit.is_some() {
self.command_line.push_info(
"Finish the block editor (Save Block or Discard) before switching spaces.",
);
return Task::none();
}
let going_to_paper = name != "Model";
// Persist the camera of the layout we're leaving BEFORE switching
// so returning to it restores where the user left off (the
@@ -3073,6 +3084,33 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
let new_name = new_name.trim().to_string();
if !new_name.is_empty() && new_name != orig {
let i = self.active_tab;
// BEDIT block tab: renaming it renames the BLOCK itself —
// its record, marker and every INSERT reference. (#261)
let is_block_tab = self.tabs[i]
.block_edit
.as_ref()
.map(|be| be.block_name == orig)
.unwrap_or(false);
if is_block_tab {
if self.tabs[i].scene.document.block_records.get(&new_name).is_some() {
self.command_line
.push_error(&format!("\"{}\" name already in use", new_name));
} else {
self.push_undo_snapshot(i, "BLOCK RENAME");
if self.tabs[i].scene.rename_block(&orig, &new_name) {
if let Some(be) = self.tabs[i].block_edit.as_mut() {
be.block_name = new_name.clone();
}
self.tabs[i].dirty = true;
self.command_line
.push_output(&format!("Block \"{orig}\" → \"{new_name}\""));
} else {
self.command_line
.push_error(&format!("Could not rename block \"{orig}\""));
}
}
return Task::none();
}
let exists = self.tabs[i]
.scene
.layout_names()