fix(model): preserve solid history lifecycle

This commit is contained in:
Hakan Seven committed 2026-08-11 20:10:23 +03:00
1 parent d2993fd054
commit 4ac10187c2
15 files changed
+292 -63

No files matched your search

+5
View File
@@ -475,6 +475,11 @@ impl Scene {
self.images.insert(handle, model);
}
self.refresh_meshes_for_handles(&[handle]);
if let Some(operation) = self.document.solid_history_operation(handle).cloned() {
if let Ok(body) = cadkernel::acis::rebuild_body(&operation) {
self.solid_models.insert(handle, body);
}
}
}
/// Re-tessellate only the named ACIS entities. The former edit path
-18
View File
@@ -7626,24 +7626,6 @@ impl Scene {
handles
}
/// True when any handle resolves to an ACIS volume entity (3D solid /
/// region / body / surface) — i.e. one whose render geometry is a cached
/// mesh that must be re-tessellated after an edit.
pub fn any_solid(&self, handles: &[Handle]) -> bool {
handles.iter().any(|&h| {
matches!(
self.document.get_entity(h),
Some(EntityType::Solid3D(_))
| Some(EntityType::Region(_))
| Some(EntityType::Body(_))
| Some(EntityType::Surface(_))
| Some(EntityType::Mesh(_))
| Some(EntityType::PolygonMesh(_))
| Some(EntityType::PolyfaceMesh(_))
)
})
}
/// Top-level solid handles caught by a rectangular selection box.
pub fn mesh_box_hit(
&self,
+66
View File
@@ -9,6 +9,7 @@ use acadrust::EntityType;
use cadkernel::brep::Body;
use crate::scene::model::object::{GripApply, GripDef, GripShape};
use crate::command::EntityTransform;
pub const GRIP_LENGTH: usize = 10_001;
pub const GRIP_WIDTH: usize = 10_002;
@@ -23,6 +24,71 @@ fn matrix(transform: [f64; 16]) -> Option<glam::DMat4> {
(matrix.is_finite() && matrix.determinant().abs() > 1e-12).then_some(matrix)
}
fn codec_matrix(transform: &acadrust::types::Transform) -> glam::DMat4 {
let matrix = transform.matrix.m;
glam::DMat4::from_cols_array(&[
matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0],
matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1],
matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2],
matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3],
])
}
fn transform_matrix(transform: &EntityTransform) -> Option<glam::DMat4> {
Some(match transform {
EntityTransform::Translate(delta) => glam::DMat4::from_translation(*delta),
EntityTransform::Rotate {
center,
axis,
angle_rad,
} => {
let axis = axis.normalize_or_zero();
if axis.length_squared() <= 1e-12 {
return None;
}
glam::DMat4::from_translation(*center)
* glam::DMat4::from_axis_angle(axis, *angle_rad)
* glam::DMat4::from_translation(-*center)
}
EntityTransform::Scale { center, factor } => {
glam::DMat4::from_translation(*center)
* glam::DMat4::from_scale(glam::DVec3::splat(*factor))
* glam::DMat4::from_translation(-*center)
}
EntityTransform::Mirror {
p1,
p2,
working_normal,
} => codec_matrix(&crate::scene::view::transform::reflection_about_working_line(
*p1,
*p2,
*working_normal,
)),
EntityTransform::Affine(value) => codec_matrix(value),
})
}
pub fn transform_operation(
operation: &mut SolidHistoryOperation,
transform: &EntityTransform,
) -> bool {
let Some(base) = operation.base_mut() else {
return false;
};
let Some(current) = matrix(base.transform) else {
return false;
};
let Some(by) = transform_matrix(transform) else {
return false;
};
let transformed = by * current;
if !transformed.is_finite() || transformed.determinant().abs() <= 1e-12 {
return false;
}
base.transform = transformed.to_cols_array();
true
}
fn world_point(transform: [f64; 16], point: [f64; 3]) -> Option<glam::DVec3> {
Some(matrix(transform)?.transform_point3(glam::DVec3::from_array(point)))
}
+122 -1
View File
@@ -214,6 +214,7 @@ impl Scene {
})
.flatten()
.collect();
let mut refresh_solid_handles = Vec::new();
for &h in handles {
// Delta-undo: capture the pre-transform image before mutating.
if self.is_recording_undo() {
@@ -226,6 +227,20 @@ impl Scene {
mirror_true_text_flags(entity);
}
}
let rebuilt_history = self.transform_solid_history(h, t);
if !rebuilt_history
&& self.document.get_entity(h).is_some_and(|entity| {
matches!(
entity,
EntityType::Solid3D(_)
| EntityType::Region(_)
| EntityType::Body(_)
| EntityType::Surface(_)
)
})
{
refresh_solid_handles.push(h);
}
if self.hatches.contains_key(&h) {
let existing_color = self.hatches[&h].color;
let new_model = match self.document.get_entity(h) {
@@ -271,6 +286,7 @@ impl Scene {
let changes: Vec<(Handle, ChangeKind)> =
handles.iter().map(|&h| (h, ChangeKind::Modified)).collect();
self.bump_entities(&changes);
self.refresh_meshes_for_handles(&refresh_solid_handles);
}
/// Entities in anonymous dimension blocks that visually belong to
@@ -550,6 +566,7 @@ impl Scene {
matches!(t, EntityTransform::Mirror { .. }) && self.document.header.mirror_text;
let mut new_handles = Vec::with_capacity(clones.len());
let mut handle_map = rustc_hash::FxHashMap::default();
let mut refresh_solid_handles = Vec::new();
for (src_handle, mut entity) in clones {
let text_orient = if preserve_text_orientation {
capture_text_orient(&entity)
@@ -602,6 +619,21 @@ impl Scene {
if let Some(model) = new_model {
self.hatches.insert(h, model);
}
let rebuilt_history = self.copy_solid_history(src_handle, h)
&& self.transform_solid_history(h, t);
if !rebuilt_history
&& self.document.get_entity(h).is_some_and(|entity| {
matches!(
entity,
EntityType::Solid3D(_)
| EntityType::Region(_)
| EntityType::Body(_)
| EntityType::Surface(_)
)
})
{
refresh_solid_handles.push(h);
}
}
new_handles.push(h);
if !h.is_null() {
@@ -620,11 +652,71 @@ impl Scene {
.map(|&h| (h, ChangeKind::Added))
.collect();
self.bump_entities(&changes);
self.refresh_meshes_for_handles(&refresh_solid_handles);
new_handles
}
// ── Grip editing ──────────────────────────────────────────────────────
pub(crate) fn solid_history_objects(
&self,
handle: Handle,
) -> Vec<(Handle, acadrust::objects::ObjectType)> {
let Some(graph) = self.document.solid_history_graph(handle) else {
return Vec::new();
};
std::iter::once(graph.root)
.chain(graph.nodes)
.filter_map(|object_handle| {
self.document
.objects
.get(&object_handle)
.cloned()
.map(|object| (object_handle, object))
})
.collect()
}
fn record_solid_history_before(&mut self, handle: Handle) {
if !self.is_recording_undo() {
return;
}
for (object_handle, object) in self.solid_history_objects(handle) {
self.record_undo_object_before(object_handle, Some(object));
}
}
pub fn create_solid_history(
&mut self,
handle: Handle,
operation: acadrust::objects::SolidHistoryOperation,
) -> bool {
let Some(graph) = self.document.create_solid_history(handle, operation) else {
return false;
};
self.record_undo_object_before(graph.root, None);
for node in graph.nodes {
self.record_undo_object_before(node, None);
}
true
}
fn copy_solid_history(&mut self, source: Handle, target: Handle) -> bool {
let Some(graph) = self.document.copy_solid_history(source, target) else {
return false;
};
self.record_undo_object_before(graph.root, None);
for node in graph.nodes {
self.record_undo_object_before(node, None);
}
true
}
pub(crate) fn delete_solid_history(&mut self, handle: Handle) {
self.record_solid_history_before(handle);
self.document.delete_solid_history(handle);
}
pub fn rebuild_solid_history(
&mut self,
handle: Handle,
@@ -637,6 +729,7 @@ impl Scene {
return false;
};
let wires = crate::scene::model::solid_model::edge_wires(&body);
self.record_solid_history_before(handle);
if self
.document
.update_solid_history(handle, operation)
@@ -654,6 +747,28 @@ impl Scene {
true
}
fn transform_solid_history(
&mut self,
handle: Handle,
transform: &EntityTransform,
) -> bool {
let Some(mut operation) = self.document.solid_history_operation(handle).cloned() else {
return false;
};
if !crate::scene::model::solid_history::transform_operation(
&mut operation,
transform,
) {
return false;
}
if self.rebuild_solid_history(handle, operation.clone()) {
return true;
}
self.record_solid_history_before(handle);
let _ = self.document.update_solid_history(handle, operation);
false
}
fn apply_solid_history_grip(
&mut self,
handle: Handle,
@@ -764,7 +879,13 @@ impl Scene {
.map(|p| [p.x, p.y, p.z]);
if let Some(new) = new_por {
let delta = [new[0] - old[0], new[1] - old[1], new[2] - old[2]];
self.translate_solid_geometry(handle, delta);
let moved_history = self.transform_solid_history(
handle,
&EntityTransform::Translate(glam::DVec3::from_array(delta)),
);
if !moved_history {
self.translate_solid_geometry(handle, delta);
}
}
}
+1
View File
@@ -631,6 +631,7 @@ impl Scene {
let before = self.document.get_entity_arc(h);
self.record_undo_before(h, before);
}
self.delete_solid_history(h);
self.remember_removed_cache_categories(h);
self.document.remove_entity_arc(h);
highlight_changed |= self.selected.remove(&h);