feat(block): double-click to edit a block in place (#136)

Double-clicking a block reference (its wire or its solid body) now enters
in-place block edit (REFEDIT), so its geometry can be modified and the change
reflects in every instance on REFCLOSE.

Also drop REFEDIT's "non-uniform scale not supported" restriction: the
session now carries the INSERT's full forward/inverse affine (OCS, rotation
and non-uniform / mirrored scale) and applies it via the entity transform,
instead of a uniform scale + rotate + translate sequence. Mirrored blocks
(e.g. x_scale = -1) are editable now.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-21 07:43:59 +03:00
1 parent 65b8e85751
commit 9f447308a6
3 files changed
+48 -95

No files matched your search

+20 -11
View File
@@ -2104,15 +2104,27 @@ impl OpenCADStudio {
}
};
// Validate: non-uniform scale is not supported.
// Build the INSERT's full placement transform (OCS + rotation +
// scale, including non-uniform / mirrored) and its inverse, so
// edits round-trip back to block-local coordinates on SAVE.
let sx = insert.x_scale();
let sy = insert.y_scale();
let sz = insert.z_scale();
if (sx - sy).abs() > 1e-6 || (sx - sz).abs() > 1e-6 {
self.command_line
.push_error("REFEDIT: non-uniform scale inserts are not supported.");
return Task::none();
}
let forward = insert.get_transform();
let inverse = {
use acadrust::types::{Matrix3, Matrix4, Transform};
let ocs_t =
Matrix4::from_matrix3(Matrix3::arbitrary_axis(insert.normal).transpose());
let t_inv = Matrix4::translation(
-insert.insert_point.x,
-insert.insert_point.y,
-insert.insert_point.z,
);
let r_inv = Matrix4::rotation_z(-insert.rotation);
let s_inv = Matrix4::scaling(1.0 / sx, 1.0 / sy, 1.0 / sz);
// inverse(OCS·T·R·S) = S⁻¹·R⁻¹·T⁻¹·OCSᵀ
Transform::from_matrix(s_inv * r_inv * t_inv * ocs_t)
};
// Find the block record.
let br_handle = match self.tabs[i]
@@ -2162,11 +2174,8 @@ impl OpenCADStudio {
block_name: insert.block_name.clone(),
br_handle,
temp_handles: vec![],
insert_x: insert.insert_point.x,
insert_y: insert.insert_point.y,
insert_z: insert.insert_point.z,
rotation_deg: insert.rotation.to_degrees(),
scale: sx,
forward,
inverse,
};
self.push_undo_snapshot(i, "REFEDIT");
+18 -1
View File
@@ -4023,8 +4023,12 @@ impl OpenCADStudio {
};
let vp_mat = self.tabs[i].scene.camera.borrow().view_proj(bounds);
let all_wires = self.tabs[i].scene.hit_test_wires();
// Resolve the double-clicked object — its wire, or (for a
// block/solid with no wire under the cursor) its shaded
// body, which maps to the parent INSERT.
let hit = scene::pick::hit_test::click_hit(p, &all_wires[..], vp_mat, bounds)
.and_then(|s| Scene::handle_from_wire_name(s));
.and_then(|s| Scene::handle_from_wire_name(s))
.or_else(|| self.tabs[i].scene.solid_click_hit(p, vp_mat, bounds));
if let Some(handle) = hit {
// Any text-bearing entity opens its in-place editor
// (plain box or rich MText editor, per type). A
@@ -4040,6 +4044,19 @@ impl OpenCADStudio {
if is_editable_text {
return self.begin_text_edit(handle);
}
// Double-clicking a block reference enters in-place
// block edit (REFEDIT), so its geometry can be edited
// and the change reflects in every instance. (#136)
let is_insert = matches!(
self.tabs[i].scene.document.get_entity(handle),
Some(AcadEntityType::Insert(_))
);
if is_insert && self.tabs[i].refedit_session.is_none() {
return Task::done(Message::Command(format!(
"REFEDIT_BEGIN:{}",
handle.value()
)));
}
}
}
}