fix(model): preserve solid history lifecycle
This commit is contained in:
parent
d2993fd054
commit
4ac10187c2
15 changed files with 292 additions and 63 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -84,7 +84,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "acadrust"
|
||||
version = "0.4.1"
|
||||
source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=1ece83a#1ece83af08a93bf84cac16f919a4b274298c8c59"
|
||||
source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=74c5fe9#74c5fe9faafd3d573745a7e9559072dfb66adb62"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"anyhow",
|
||||
|
|
@ -890,7 +890,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
|||
[[package]]
|
||||
name = "cadkernel"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=bbd6688#bbd6688f4cdffae3e69787ab0a8e5a2b9aed60ee"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=f85efb6#f85efb659741f8d19d6bf750f3234aa333826926"
|
||||
dependencies = [
|
||||
"acadrust",
|
||||
"cavalier_contours",
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ glam = { version = "0.33", features = ["bytemuck"] }
|
|||
rfd = "0.17"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
env_logger = "0.11"
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "1ece83a", features = ["serde"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "bbd6688", features = ["acis", "offset"] }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "74c5fe9", features = ["serde"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "f85efb6", features = ["acis", "offset"] }
|
||||
acadifc = { git = "https://github.com/OpenAEC-Foundation/acadifc.git", rev = "47b5603", optional = true }
|
||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||
flate2 = "1"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ license = "GPL-3.0-only"
|
|||
# Pulled in only by the `host` feature, which adds the `acadrust`-typed
|
||||
# `HostApi` runtime surface. The default crate stays dependency-free so engine
|
||||
# crates and external tooling can depend on the manifest/ribbon contract cheaply.
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "1ece83a", optional = true, features = ["serde"] }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "74c5fe9", optional = true, features = ["serde"] }
|
||||
|
||||
# Runtime IPC and serialization (host feature only).
|
||||
interprocess = { version = "2", optional = true }
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ publish = false
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "1ece83a", features = ["serde"] }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "74c5fe9", features = ["serde"] }
|
||||
bincode = "1.3"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
console_error_panic_hook = "0.1"
|
||||
|
|
|
|||
|
|
@ -107,11 +107,25 @@ impl OpenCADStudio {
|
|||
|
||||
/// Roll a hot grip back to its pre-drag image and remove every grip-owned
|
||||
/// overlay. Shared by Escape and drawing-space transitions.
|
||||
pub(super) fn capture_grip_history_originals(&mut self, i: usize, handles: &[Handle]) {
|
||||
if !self.grip_history_originals.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.grip_history_originals = handles
|
||||
.iter()
|
||||
.filter_map(|&handle| {
|
||||
let objects = self.tabs[i].scene.solid_history_objects(handle);
|
||||
(!objects.is_empty()).then_some((handle, objects))
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
pub(super) fn cancel_active_grip_edit(&mut self) -> bool {
|
||||
let i = self.active_tab;
|
||||
let had_grip = self.tabs[i].active_grip.take().is_some()
|
||||
|| self.grip_add_provisional.is_some()
|
||||
|| !self.grip_preview_handles.is_empty();
|
||||
|| !self.grip_preview_handles.is_empty()
|
||||
|| !self.grip_history_originals.is_empty();
|
||||
if !had_grip {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -132,31 +146,62 @@ impl OpenCADStudio {
|
|||
|
||||
let handles = std::mem::take(&mut self.grip_preview_handles);
|
||||
let originals = std::mem::take(&mut self.grip_originals);
|
||||
let history_originals = std::mem::take(&mut self.grip_history_originals);
|
||||
let history_handles: rustc_hash::FxHashSet<_> = history_originals
|
||||
.iter()
|
||||
.map(|(handle, _)| *handle)
|
||||
.collect();
|
||||
for (_, objects) in history_originals {
|
||||
for (object_handle, object) in objects {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.objects
|
||||
.insert(object_handle, object);
|
||||
}
|
||||
}
|
||||
let mut changed_handles: rustc_hash::FxHashSet<_> = handles.iter().copied().collect();
|
||||
for (handle, original) in originals {
|
||||
changed_handles.insert(handle);
|
||||
let current = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.and_then(crate::entities::solid3d::point_of_reference)
|
||||
.map(|point| [point.x, point.y, point.z]);
|
||||
let target = crate::entities::solid3d::point_of_reference(&original)
|
||||
.map(|point| [point.x, point.y, point.z]);
|
||||
if let (Some(current), Some(target)) = (current, target) {
|
||||
self.tabs[i].scene.translate_solid_geometry(
|
||||
handle,
|
||||
[
|
||||
target[0] - current[0],
|
||||
target[1] - current[1],
|
||||
target[2] - current[2],
|
||||
],
|
||||
);
|
||||
if !history_handles.contains(&handle) {
|
||||
let current = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.and_then(crate::entities::solid3d::point_of_reference)
|
||||
.map(|point| [point.x, point.y, point.z]);
|
||||
let target = crate::entities::solid3d::point_of_reference(&original)
|
||||
.map(|point| [point.x, point.y, point.z]);
|
||||
if let (Some(current), Some(target)) = (current, target) {
|
||||
self.tabs[i].scene.translate_solid_geometry(
|
||||
handle,
|
||||
[
|
||||
target[0] - current[0],
|
||||
target[1] - current[1],
|
||||
target[2] - current[2],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
||||
*entity = original;
|
||||
}
|
||||
}
|
||||
for handle in history_handles {
|
||||
let restored = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.solid_history_operation(handle)
|
||||
.cloned()
|
||||
.is_some_and(|operation| {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.rebuild_solid_history(handle, operation)
|
||||
});
|
||||
if !restored {
|
||||
self.tabs[i].scene.reseed_derived_caches(handle);
|
||||
}
|
||||
}
|
||||
for &handle in &handles {
|
||||
self.tabs[i].scene.preview_hidden.remove(&handle);
|
||||
}
|
||||
|
|
@ -962,13 +1007,6 @@ impl OpenCADStudio {
|
|||
// transform_entities — always delta-safe.
|
||||
let pending = self.begin_undo(i, label, handles.len(), true);
|
||||
self.tabs[i].scene.transform_entities(&handles, &transform);
|
||||
// ACIS solids render from a cached mesh, so a move/rotate/
|
||||
// scale/mirror needs the mesh re-tessellated from the now-moved
|
||||
// body — wire re-tessellation alone leaves the solid drawn at
|
||||
// its old spot. (#135)
|
||||
if self.tabs[i].scene.any_solid(&handles) {
|
||||
self.tabs[i].scene.refresh_meshes_for_handles(&handles);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.tabs[i].active_cmd = None;
|
||||
|
|
@ -986,9 +1024,6 @@ impl OpenCADStudio {
|
|||
let delta_safe = self.delta_copy_safe(i, &handles);
|
||||
let pending = self.begin_undo(i, label, handles.len(), delta_safe);
|
||||
let new_handles = self.tabs[i].scene.copy_entities(&handles, &transform);
|
||||
if self.tabs[i].scene.any_solid(&new_handles) {
|
||||
self.tabs[i].scene.refresh_meshes_for_handles(&new_handles);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].scene.deselect_all();
|
||||
for h in new_handles {
|
||||
|
|
@ -2689,7 +2724,6 @@ impl OpenCADStudio {
|
|||
let new_handle = self.tabs[i].scene.add_entity(s3d);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.create_solid_history(new_handle, history);
|
||||
self.tabs[i].scene.register_solid_model(new_handle, solid);
|
||||
let _ = mesh;
|
||||
|
|
@ -2752,7 +2786,6 @@ impl OpenCADStudio {
|
|||
let new_handle = self.tabs[i].scene.add_entity(s3d);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.create_solid_history(new_handle, history);
|
||||
self.tabs[i].scene.register_solid_model(new_handle, solid);
|
||||
let _ = mesh;
|
||||
|
|
@ -2813,7 +2846,6 @@ impl OpenCADStudio {
|
|||
if let Some(history) = history {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.create_solid_history(new_handle, history);
|
||||
}
|
||||
for mesh in &mut set.lods {
|
||||
|
|
@ -2856,7 +2888,6 @@ impl OpenCADStudio {
|
|||
let new_handle = self.tabs[i].scene.add_entity(entity);
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.create_solid_history(new_handle, history);
|
||||
for mesh in &mut set.lods {
|
||||
mesh.name = format!("{}", new_handle.value());
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ impl OpenCADStudio {
|
|||
i: usize,
|
||||
label: impl Into<String>,
|
||||
before: Vec<(Handle, Arc<EntityType>)>,
|
||||
object_before: Vec<(Handle, acadrust::objects::ObjectType)>,
|
||||
dirty_before: bool,
|
||||
) {
|
||||
self.finish_pending_history(i);
|
||||
|
|
@ -199,7 +200,18 @@ impl OpenCADStudio {
|
|||
Some((handle, Some(original), Some(after)))
|
||||
})
|
||||
.collect();
|
||||
if entities.is_empty() {
|
||||
let objects: Vec<_> = object_before
|
||||
.into_iter()
|
||||
.filter_map(|(handle, before)| {
|
||||
let after = self.tabs[i].scene.document.objects.get(&handle).cloned();
|
||||
(Some(before.clone()) != after).then_some(ObjectEntryDelta {
|
||||
handle,
|
||||
before: Some(before),
|
||||
after,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
if entities.is_empty() && objects.is_empty() {
|
||||
return;
|
||||
}
|
||||
let selected: Vec<Handle> = self.tabs[i].scene.selected.iter().copied().collect();
|
||||
|
|
@ -211,7 +223,7 @@ impl OpenCADStudio {
|
|||
selected_after: selected,
|
||||
dirty_before,
|
||||
dirty_after: true,
|
||||
structure: None,
|
||||
structure: (!objects.is_empty()).then_some(StructureSnapshot::Objects(objects)),
|
||||
label: label.into(),
|
||||
};
|
||||
self.push_undo_entry(i, HistorySnapshot::Delta(delta));
|
||||
|
|
|
|||
|
|
@ -543,6 +543,11 @@ pub(super) struct OpenCADStudio {
|
|||
/// Snapshots of edited entities taken at the start of a grip drag. The drag
|
||||
/// mutates the document live, so Escape restores this group atomically.
|
||||
grip_originals: Vec<(acadrust::Handle, acadrust::EntityType)>,
|
||||
/// Solid-history objects paired with their owning entity before a grip drag.
|
||||
grip_history_originals: Vec<(
|
||||
acadrust::Handle,
|
||||
Vec<(acadrust::Handle, acadrust::objects::ObjectType)>,
|
||||
)>,
|
||||
/// Document dirty state before the live grip mutation began.
|
||||
grip_dirty_before: Option<bool>,
|
||||
/// Frozen wire geometry of the entities being grip-edited.
|
||||
|
|
@ -3136,6 +3141,7 @@ impl OpenCADStudio {
|
|||
grip_preview_handles: Vec::new(),
|
||||
hover_dwell: None,
|
||||
grip_originals: Vec::new(),
|
||||
grip_history_originals: Vec::new(),
|
||||
grip_dirty_before: None,
|
||||
grip_snap_wires: Vec::new(),
|
||||
grip_text_verts: Vec::new(),
|
||||
|
|
|
|||
|
|
@ -31,10 +31,7 @@ impl super::OpenCADStudio {
|
|||
let Some(handle) = self.commit_entity_handle(entity) else {
|
||||
return Handle::NULL;
|
||||
};
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.create_solid_history(handle, history);
|
||||
self.tabs[i].scene.create_solid_history(handle, history);
|
||||
self.tabs[i].scene.register_solid_model(handle, solid);
|
||||
handle
|
||||
}
|
||||
|
|
|
|||
|
|
@ -369,6 +369,8 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
.collect();
|
||||
}
|
||||
|
||||
self.capture_grip_history_originals(i, &edited_handles);
|
||||
|
||||
self.grip_preview_handles = edited_handles;
|
||||
}
|
||||
let delta = target - grip.last_world;
|
||||
|
|
|
|||
|
|
@ -1070,6 +1070,7 @@ impl OpenCADStudio {
|
|||
})
|
||||
.collect();
|
||||
}
|
||||
self.capture_grip_history_originals(i, &edited_handles);
|
||||
for &handle in &edited_handles {
|
||||
if !self.tabs[i].scene.meshes.contains_key(&handle) {
|
||||
self.tabs[i].scene.preview_hidden.insert(handle);
|
||||
|
|
@ -2733,6 +2734,7 @@ impl OpenCADStudio {
|
|||
// edited entity back into the resident tessellation.
|
||||
let handles = std::mem::take(&mut self.grip_preview_handles);
|
||||
let originals = std::mem::take(&mut self.grip_originals);
|
||||
let history_originals = std::mem::take(&mut self.grip_history_originals);
|
||||
let dirty_before = self.grip_dirty_before.take().unwrap_or(self.tabs[i].dirty);
|
||||
if !handles.is_empty() {
|
||||
if !originals.is_empty() {
|
||||
|
|
@ -2743,6 +2745,10 @@ impl OpenCADStudio {
|
|||
.into_iter()
|
||||
.map(|(handle, entity)| (handle, std::sync::Arc::new(entity)))
|
||||
.collect(),
|
||||
history_originals
|
||||
.into_iter()
|
||||
.flat_map(|(_, objects)| objects)
|
||||
.collect(),
|
||||
dirty_before,
|
||||
);
|
||||
self.tabs[i].dirty = true;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue