From dc6dc2256d86eadc633d7f1c2dc9efc2e6f4f30d Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Tue, 11 Aug 2026 19:39:01 +0300 Subject: [PATCH] fix(model): sync solid render interaction --- src/app/command_driver.rs | 50 +++++++++++++++-- src/app/update/viewport.rs | 14 ++++- src/scene/mod.rs | 11 ++++ src/scene/modify.rs | 110 ++++++++++++++++++------------------- src/scene/view/render.rs | 6 ++ 5 files changed, 127 insertions(+), 64 deletions(-) diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index c201bb1e..a9fb06a2 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -135,6 +135,24 @@ impl OpenCADStudio { 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 let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) { *entity = original; } @@ -2615,10 +2633,16 @@ impl OpenCADStudio { let color = [0.6f32, 0.6, 0.8, 1.0]; // default colour; command embedded it let _ = color; // color is captured inside mesh_fn if let Some(mesh) = mesh_fn(name) { - self.tabs[i] - .scene - .meshes - .insert(handle, crate::scene::MeshLodSet::from_single(mesh)); + let set = crate::scene::MeshLodSet::from_single(mesh); + if let Some(acadrust::EntityType::Solid3D(entity)) = + self.tabs[i].scene.document.get_entity_mut(handle) + { + let center = set.metrics.centroid; + entity.point_of_reference = acadrust::types::Vector3::new( + center[0], center[1], center[2], + ); + } + self.tabs[i].scene.meshes.insert(handle, set); } self.tabs[i].dirty = true; self.command_line.push_output(crate::t!("Solid created.").as_ref()); @@ -2750,7 +2774,14 @@ impl OpenCADStudio { if let Some(mut set) = result { let pending = self.begin_undo(i, "SWEEP", 1, true); - let new_handle = self.tabs[i].scene.add_entity(empty_solid3d()); + let mut entity = empty_solid3d(); + if let acadrust::EntityType::Solid3D(solid) = &mut entity { + let center = set.metrics.centroid; + solid.point_of_reference = acadrust::types::Vector3::new( + center[0], center[1], center[2], + ); + } + let new_handle = self.tabs[i].scene.add_entity(entity); for mesh in &mut set.lods { mesh.name = format!("{}", new_handle.value()); } @@ -2780,7 +2811,14 @@ impl OpenCADStudio { .collect(); if let Some(mut set) = sweep_model::lofted(&profiles, color) { let pending = self.begin_undo(i, "LOFT", 1, true); - let new_handle = self.tabs[i].scene.add_entity(empty_solid3d()); + let mut entity = empty_solid3d(); + if let acadrust::EntityType::Solid3D(solid) = &mut entity { + let center = set.metrics.centroid; + solid.point_of_reference = acadrust::types::Vector3::new( + center[0], center[1], center[2], + ); + } + let new_handle = self.tabs[i].scene.add_entity(entity); for mesh in &mut set.lods { mesh.name = format!("{}", new_handle.value()); } diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index 741a0933..b49efe2c 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -1046,8 +1046,7 @@ impl OpenCADStudio { .filter(|handle| seen_handles.insert(*handle)) .collect(); - // First move of this drag: hide every edited entity from the base - // tessellation so subsequent moves refresh only the overlay. + // Wire entities use the overlay; solid meshes stay visible and move live. if self.grip_preview_handles != edited_handles { if self.grip_dirty_before.is_none() { self.grip_dirty_before = Some(self.tabs[i].dirty); @@ -1072,7 +1071,9 @@ impl OpenCADStudio { .collect(); } for &handle in &edited_handles { - self.tabs[i].scene.preview_hidden.insert(handle); + if !self.tabs[i].scene.meshes.contains_key(&handle) { + self.tabs[i].scene.preview_hidden.insert(handle); + } } let changes: Vec<_> = edited_handles .iter() @@ -1377,6 +1378,13 @@ impl OpenCADStudio { ); } } + let mesh_changes: Vec<_> = edited_handles + .iter() + .copied() + .filter(|handle| self.tabs[i].scene.meshes.contains_key(handle)) + .map(|handle| (handle, crate::scene::ChangeKind::Modified)) + .collect(); + self.tabs[i].scene.bump_entities(&mesh_changes); self.tabs[i].scene.set_preview_hatches(&edited_handles); self.tabs[i].dirty = true; if let Some(active) = self.tabs[i].active_grip.as_mut() { diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 3b77126c..f6ef8219 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -2680,11 +2680,22 @@ impl Scene { /// the mesh pipeline uses, so the mesh is stored as-is (Model-tab geometry /// is authored at world_offset 0). pub fn register_solid_model(&mut self, handle: Handle, solid: cadkernel::brep::Body) { + if let Some(center) = crate::scene::model::solid_model::centre(&solid) { + if let Some(EntityType::Solid3D(entity)) = self.document.get_entity_mut(handle) { + entity.point_of_reference = acadrust::types::Vector3::new( + center[0], center[1], center[2], + ); + } + } let entity = self.document.get_entity(handle); let color = entity .map(|e| self.render_style(e).0) .unwrap_or([0.8, 0.8, 0.85, 1.0]); if let Some(mut set) = crate::scene::model::solid_model::mesh_from_solid(&solid, color) { + let name = handle.value().to_string(); + for mesh in &mut set.lods { + mesh.name = name.clone(); + } if let Some(entity) = entity { crate::scene::model::material_model::resolve_material_with_base( &self.document, diff --git a/src/scene/modify.rs b/src/scene/modify.rs index b784a6bf..ef72c8e4 100644 --- a/src/scene/modify.rs +++ b/src/scene/modify.rs @@ -625,6 +625,59 @@ impl Scene { // ── Grip editing ────────────────────────────────────────────────────── + pub(crate) fn translate_solid_geometry(&mut self, handle: Handle, delta: [f64; 3]) { + if delta.iter().all(|value| value.abs() <= f64::EPSILON) { + return; + } + let placement = cadkernel::brep::Placement::at(delta); + if let Some(body) = self.solid_models.get(&handle).cloned() { + if let Some(moved) = cadkernel::brep::transform(&body, &placement) { + self.solid_models.insert(handle, moved); + } + } + let Some(set) = self.meshes.get_mut(&handle) else { + return; + }; + let translate_split = |high: &mut [f32; 3], low: &mut [f32; 3]| { + let absolute = [ + high[0] as f64 + low[0] as f64 + delta[0], + high[1] as f64 + low[1] as f64 + delta[1], + high[2] as f64 + low[2] as f64 + delta[2], + ]; + *high = [absolute[0] as f32, absolute[1] as f32, absolute[2] as f32]; + *low = [ + (absolute[0] - high[0] as f64) as f32, + (absolute[1] - high[1] as f64) as f32, + (absolute[2] - high[2] as f64) as f32, + ]; + }; + for lod in &mut set.lods { + if lod.verts_low.len() != lod.verts.len() { + lod.verts_low = vec![[0.0; 3]; lod.verts.len()]; + } + for (high, low) in lod.verts.iter_mut().zip(lod.verts_low.iter_mut()) { + translate_split(high, low); + } + } + if set.edge_verts_low.len() != set.edge_verts.len() { + set.edge_verts_low = vec![[0.0; 3]; set.edge_verts.len()]; + } + for (high, low) in set.edge_verts.iter_mut().zip(set.edge_verts_low.iter_mut()) { + translate_split(high, low); + } + for generator in &mut set.curved_gens { + if let Some(source) = + cadkernel::brep::mesh::transform_silhouette(&generator.source, &placement) + { + generator.source = source; + } + } + set.metrics.centroid[0] += delta[0]; + set.metrics.centroid[1] += delta[1]; + set.metrics.centroid[2] += delta[2]; + set.recompute_aabb(); + } + pub fn apply_grip(&mut self, handle: Handle, grip_id: usize, apply: GripApply) { // Objects on a locked layer can't be grip-edited. if self.is_layer_locked(handle) { @@ -660,57 +713,7 @@ 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]]; - if let Some(set) = self.meshes.get_mut(&handle) { - let translate_split = - |high: &mut [f32; 3], low: &mut [f32; 3]| { - let absolute = [ - high[0] as f64 + low[0] as f64 + delta[0], - high[1] as f64 + low[1] as f64 + delta[1], - high[2] as f64 + low[2] as f64 + delta[2], - ]; - *high = [ - absolute[0] as f32, - absolute[1] as f32, - absolute[2] as f32, - ]; - *low = [ - (absolute[0] - high[0] as f64) as f32, - (absolute[1] - high[1] as f64) as f32, - (absolute[2] - high[2] as f64) as f32, - ]; - }; - for lod in &mut set.lods { - if lod.verts_low.len() != lod.verts.len() { - lod.verts_low = vec![[0.0; 3]; lod.verts.len()]; - } - for (high, low) in lod.verts.iter_mut().zip(lod.verts_low.iter_mut()) { - translate_split(high, low); - } - } - if set.edge_verts_low.len() != set.edge_verts.len() { - set.edge_verts_low = vec![[0.0; 3]; set.edge_verts.len()]; - } - for (high, low) in set - .edge_verts - .iter_mut() - .zip(set.edge_verts_low.iter_mut()) - { - translate_split(high, low); - } - for generator in &mut set.curved_gens { - let placement = cadkernel::brep::Placement::at(delta); - if let Some(source) = cadkernel::brep::mesh::transform_silhouette( - &generator.source, - &placement, - ) { - generator.source = source; - } - } - set.metrics.centroid[0] += delta[0]; - set.metrics.centroid[1] += delta[1]; - set.metrics.centroid[2] += delta[2]; - set.recompute_aabb(); - } + self.translate_solid_geometry(handle, delta); } } @@ -731,9 +734,6 @@ impl Scene { } _ => {} } - // NOTE: no `bump_geometry()` here. The grip-drag caller hides the - // edited entity and previews it as an overlay during the drag (so a - // move doesn't re-tessellate the whole model), then bumps once on - // commit. Any other caller must bump geometry itself. + // The grip-drag caller refreshes changed resident meshes per move. } } diff --git a/src/scene/view/render.rs b/src/scene/view/render.rs index a62d28fb..ce875da1 100644 --- a/src/scene/view/render.rs +++ b/src/scene/view/render.rs @@ -1248,6 +1248,12 @@ fn render_signature(vp: &ViewportData, clip_w: u32, clip_h: u32) -> u64 { .map(|image| std::sync::Arc::as_ptr(&image.pixels) as usize) .unwrap_or(0) .hash(&mut h); + if vp.meshes.is_empty() { + 0usize + } else { + std::sync::Arc::as_ptr(&vp.meshes) as usize + } + .hash(&mut h); vp.geometry_epoch.hash(&mut h); vp.selection_generation.hash(&mut h); vp.selected_sig.hash(&mut h);