fix(blocks): preserve UCS transformations

Bake BEDIT frames into block-local geometry while keeping model and viewport UCS isolated. Normalize legacy nonzero block origins during load.
This commit is contained in:
Hakan Seven committed 2026-07-30 19:50:33 +03:00
1 parent 02d15f5d23
commit 2c64888048
22 files changed
+693 -41

No files matched your search

+7 -1
View File
@@ -493,7 +493,12 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
&& (self.ucs_grip_drag.is_some() || self.ucs_icon_selected)
{
let i = self.active_tab;
self.ucs_grip_drag = None;
// A UCS grip drag is live-only until mouse release. Reload
// the persisted pane UCS so Escape really cancels it.
if self.ucs_grip_drag.take().is_some() {
self.tabs[i].refresh_active_ucs();
self.tabs[i].scene.camera_generation += 1;
}
self.ucs_icon_selected = false;
self.ucs_icon_hover = false;
self.tabs[i].snap_result = None;
@@ -1319,6 +1324,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
vp.ucs_origin = ucs.origin.clone();
vp.ucs_x_axis = ucs.x_axis.clone();
vp.ucs_y_axis = ucs.y_axis.clone();
vp.ucs_per_viewport = true;
}
}
}
+7 -3
View File
@@ -2344,16 +2344,20 @@ impl OpenCADStudio {
Message::SetViewcubeUcs(name) => {
let i = self.active_tab;
let mut changed = false;
if name.is_empty() || name == "WCS" {
self.tabs[i].active_ucs = None;
self.command_line.push_output("UCS: World");
changed = true;
} else if let Some(named) = self.tabs[i].scene.document.ucss.get(&name).cloned() {
self.tabs[i].active_ucs = Some(named);
self.command_line.push_output(&format!("UCS: {}", name));
changed = true;
}
if changed {
self.commit_active_ucs_change(i, "UCS");
self.tabs[i].scene.camera_generation += 1;
}
self.tabs[i].sync_ucs_to_scene();
self.tabs[i].scene.camera_generation += 1;
self.tabs[i].dirty = true;
Task::none()
}
+79 -3
View File
@@ -1752,10 +1752,81 @@ impl OpenCADStudio {
}
}
self.tabs[i].sync_ucs_to_scene();
self.tabs[i].dirty = true;
self.tabs[i].scene.camera_generation += 1;
}
/// Commit the live UCS to its owning space. Model/viewport panes persist
/// their normal UCS fields. A BEDIT pane has no persistent UCS of its own:
/// its transient origin and axes are baked into the block definition, then
/// the editor returns to canonical local coordinates.
pub(in crate::app) fn commit_active_ucs_change(&mut self, i: usize, label: &'static str) {
if let Some((session_index, block_record)) = self.tabs[i]
.active_block_edit
.and_then(|index| {
self.tabs[i]
.block_edits
.get(index)
.map(|session| (index, session.br_handle))
})
{
let frame = self.tabs[i].ucs_xform();
if !frame.is_identity() {
self.push_undo_snapshot(i, label);
let local_from_old = frame.to_ucs_transform();
let changed = self.tabs[i]
.scene
.reframe_block_definition(block_record, &local_from_old);
if changed > 0 {
// Carry the editor camera through the same rigid transform
// so changing the block coordinate frame does not make its
// contents jump on screen.
self.tabs[i]
.scene
.camera
.borrow_mut()
.apply_rigid_transform(&local_from_old);
self.tabs[i].scene.camera_generation += 1;
self.tabs[i].dirty = true;
}
}
// Blocks do not own a saved UCS. The chosen frame has now become
// their identity coordinates.
self.tabs[i].active_ucs = None;
let editor_camera = self.tabs[i].scene.camera.borrow().clone();
if let Some(session) = self.tabs[i].block_edits.get_mut(session_index) {
session.editor_ucs = None;
session.editor_camera = editor_camera;
}
self.tabs[i].sync_ucs_to_scene();
return;
}
let persisted = if let Some(handle) = self.tabs[i].scene.active_viewport {
self.tabs[i].ucs_from_viewport(handle)
} else if self.tabs[i].scene.current_layout == "Model" {
self.tabs[i].model_ucs_from_header()
} else {
None
};
let same_basis = |a: Option<&acadrust::tables::Ucs>,
b: Option<&acadrust::tables::Ucs>| {
match (a, b) {
(None, None) => true,
(Some(a), Some(b)) => {
a.origin == b.origin && a.x_axis == b.x_axis && a.y_axis == b.y_axis
}
_ => false,
}
};
if !same_basis(self.tabs[i].active_ucs.as_ref(), persisted.as_ref()) {
self.push_undo_snapshot(i, label);
self.tabs[i].persist_active_ucs();
self.tabs[i].dirty = true;
}
self.tabs[i].sync_ucs_to_scene();
}
// ── Per-pane Model viewport (pane_grid) ───────────────────────────────
/// Focus Model pane `idx` (cursor entered it): swap in its camera, sync the
@@ -2033,9 +2104,10 @@ impl OpenCADStudio {
}
// Commit a UCS icon grip drag: persist the new UCS so it
// round-trips, and clear the lingering press state.
// round-trips, and clear the lingering press state. In BEDIT this
// rebases only the block; elsewhere it writes the model/viewport UCS.
if self.ucs_grip_drag.take().is_some() {
self.tabs[i].persist_active_ucs();
self.commit_active_ucs_change(i, "UCS");
self.tabs[i].snap_result = None;
self.snapper.from_point = None;
let mut sel = self.tabs[i].scene.selection.borrow_mut();
@@ -3819,9 +3891,11 @@ impl OpenCADStudio {
let cancel_task = self.cancel_active_command_for_space_change();
let current_camera = self.tabs[i].scene.camera.borrow().clone();
let current_ucs = self.tabs[i].active_ucs.clone();
if let Some(active_index) = self.tabs[i].active_block_edit {
if let Some(session) = self.tabs[i].block_edits.get_mut(active_index) {
session.editor_camera = current_camera;
session.editor_ucs = current_ucs;
}
} else {
self.tabs[i].scene.sync_camera_to_document();
@@ -3902,9 +3976,11 @@ impl OpenCADStudio {
};
if let Some(active_index) = self.tabs[i].active_block_edit.take() {
let camera = self.tabs[i].scene.camera.borrow().clone();
let editor_ucs = self.tabs[i].active_ucs.clone();
let (return_layout, return_camera) = {
let session = &mut self.tabs[i].block_edits[active_index];
session.editor_camera = camera;
session.editor_ucs = editor_ucs;
(session.return_layout.clone(), session.return_camera.clone())
};
let return_layout = if self.tabs[i].scene.layout_names().contains(&return_layout) {