fix(model): restore saved solid booleans
Reload exact B-reps from solid history or ACIS before model operations and pin the closed-cut kernel fix.
This commit is contained in:
parent
e94cbedaa2
commit
44a40eda0b
7 changed files with 82 additions and 85 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cadkernel"
|
name = "cadkernel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=895f8b7#895f8b7b4d53845608689ead46c1a122e352b79b"
|
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=467c3fd#467c3fd02f419ab5af4030691699b34b25d2f51a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"acadrust",
|
"acadrust",
|
||||||
"cavalier_contours",
|
"cavalier_contours",
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ rfd = "0.17"
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "db73dab", features = ["serde"] }
|
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "db73dab", features = ["serde"] }
|
||||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "895f8b7", features = ["acis", "offset"] }
|
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "467c3fd", features = ["acis", "offset"] }
|
||||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// 3D solid modelling support on the App: committing Model-tab primitives,
|
// 3D solid modelling support on the App: committing Model-tab primitives,
|
||||||
// and the Design-group boolean operations over the scene's session-cached
|
// and the Design-group boolean operations over the scene's cached
|
||||||
// B-reps.
|
// B-reps.
|
||||||
//
|
//
|
||||||
// The bodies come from the geometry kernel, so every operation here is on
|
// The bodies come from the geometry kernel, so every operation here is on
|
||||||
|
|
@ -36,18 +36,30 @@ impl super::OpenCADStudio {
|
||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a boolean (`union` / `subtract` / `intersect`) on exactly two
|
fn selected_solid_handles(&mut self) -> Vec<Handle> {
|
||||||
/// selected solids whose B-reps are in the session cache.
|
|
||||||
pub(super) fn solid_boolean(&mut self, op: BoolOp) -> Task<Message> {
|
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
// Selected entities that have a cached B-rep.
|
let mut handles: Vec<Handle> = self.tabs[i]
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
|
||||||
.scene
|
.scene
|
||||||
.selected_handles_in_order()
|
.selected_handles_in_order()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
.filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle))
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
.filter(|handle| {
|
||||||
|
matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(*handle),
|
||||||
|
Some(EntityType::Solid3D(_))
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
self.tabs[i].scene.restore_solid_models(&handles);
|
||||||
|
handles.retain(|handle| self.tabs[i].scene.solid_models.contains_key(handle));
|
||||||
|
handles
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run a boolean (`union` / `subtract` / `intersect`) on exactly two
|
||||||
|
/// selected solids whose B-reps can be restored by the kernel.
|
||||||
|
pub(super) fn solid_boolean(&mut self, op: BoolOp) -> Task<Message> {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let handles = self.selected_solid_handles();
|
||||||
if handles.len() != 2 {
|
if handles.len() != 2 {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("Boolean: select exactly two solids created this session.").as_ref());
|
.push_error(crate::t!("Boolean: select exactly two solids created this session.").as_ref());
|
||||||
|
|
@ -91,14 +103,7 @@ impl super::OpenCADStudio {
|
||||||
/// the same boolean path the Design-group tools use.
|
/// the same boolean path the Design-group tools use.
|
||||||
pub(super) fn solid_slice(&mut self, axis: usize, value: f64, keep_low: bool) -> Task<Message> {
|
pub(super) fn solid_slice(&mut self, axis: usize, value: f64, keep_low: bool) -> Task<Message> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.len() != 1 {
|
if handles.len() != 1 {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("SLICE: select exactly one solid created this session.").as_ref());
|
.push_error(crate::t!("SLICE: select exactly one solid created this session.").as_ref());
|
||||||
|
|
@ -167,14 +172,7 @@ impl super::OpenCADStudio {
|
||||||
/// leaving the originals in place (a non-destructive boolean intersect).
|
/// leaving the originals in place (a non-destructive boolean intersect).
|
||||||
pub(super) fn solid_interfere(&mut self) -> Task<Message> {
|
pub(super) fn solid_interfere(&mut self) -> Task<Message> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.len() != 2 {
|
if handles.len() != 2 {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("INTERFERE: select exactly two solids created this session.").as_ref());
|
.push_error(crate::t!("INTERFERE: select exactly two solids created this session.").as_ref());
|
||||||
|
|
@ -207,14 +205,7 @@ impl super::OpenCADStudio {
|
||||||
/// orientation, so it reuses the cached B-rep directly.
|
/// orientation, so it reuses the cached B-rep directly.
|
||||||
pub(super) fn solid_rotate3d(&mut self, axis: usize, angle_deg: f64) -> Task<Message> {
|
pub(super) fn solid_rotate3d(&mut self, axis: usize, angle_deg: f64) -> Task<Message> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.len() != 1 {
|
if handles.len() != 1 {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("3DROTATE: select exactly one solid created this session.").as_ref());
|
.push_error(crate::t!("3DROTATE: select exactly one solid created this session.").as_ref());
|
||||||
|
|
@ -333,14 +324,7 @@ impl super::OpenCADStudio {
|
||||||
/// loop on the way through; without that the copy lights black.
|
/// loop on the way through; without that the copy lights black.
|
||||||
pub(super) fn solid_mirror3d(&mut self, axis: usize) -> Task<Message> {
|
pub(super) fn solid_mirror3d(&mut self, axis: usize) -> Task<Message> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.len() != 1 {
|
if handles.len() != 1 {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("3DMIRROR: select exactly one solid created this session.").as_ref());
|
.push_error(crate::t!("3DMIRROR: select exactly one solid created this session.").as_ref());
|
||||||
|
|
@ -385,14 +369,7 @@ impl super::OpenCADStudio {
|
||||||
dst: [[f64; 3]; 3],
|
dst: [[f64; 3]; 3],
|
||||||
) -> Task<Message> {
|
) -> Task<Message> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.len() != 1 {
|
if handles.len() != 1 {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("3DALIGN: select exactly one solid created this session.").as_ref());
|
.push_error(crate::t!("3DALIGN: select exactly one solid created this session.").as_ref());
|
||||||
|
|
@ -453,14 +430,7 @@ impl super::OpenCADStudio {
|
||||||
use acadrust::Line;
|
use acadrust::Line;
|
||||||
|
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.len() != 1 {
|
if handles.len() != 1 {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("SECTION: select exactly one solid created this session.").as_ref());
|
.push_error(crate::t!("SECTION: select exactly one solid created this session.").as_ref());
|
||||||
|
|
@ -636,14 +606,7 @@ impl super::OpenCADStudio {
|
||||||
use acadrust::types::Vector3;
|
use acadrust::types::Vector3;
|
||||||
use acadrust::Line;
|
use acadrust::Line;
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.is_empty() {
|
if handles.is_empty() {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("FLATSHOT: select a solid created this session.").as_ref());
|
.push_error(crate::t!("FLATSHOT: select a solid created this session.").as_ref());
|
||||||
|
|
@ -677,14 +640,7 @@ impl super::OpenCADStudio {
|
||||||
use acadrust::entities::{Surface, SurfaceKind, Wire as AWire};
|
use acadrust::entities::{Surface, SurfaceKind, Wire as AWire};
|
||||||
use acadrust::types::Vector3;
|
use acadrust::types::Vector3;
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.is_empty() {
|
if handles.is_empty() {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("CONVTOSURFACE: select a solid created this session.").as_ref());
|
.push_error(crate::t!("CONVTOSURFACE: select a solid created this session.").as_ref());
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Boolean operations on 3D solids (Design group). The actual CSG runs in
|
// Boolean operations on 3D solids (Design group). The actual CSG runs in
|
||||||
// `App::solid_boolean` (src/app/model_ops.rs) using the kernel on the
|
// `App::solid_boolean` (src/app/model_ops.rs) using the kernel on the
|
||||||
// session-cached B-reps; this module just names the operations.
|
// cached B-reps; this module just names the operations.
|
||||||
|
|
||||||
/// Which boolean a Design-group tool performs on the two selected solids.
|
/// Which boolean a Design-group tool performs on the two selected solids.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use acadrust::entities::acis::{SabReader, SatBody, SatDocument};
|
use acadrust::entities::acis::{SabReader, SatBody, SatDocument};
|
||||||
use acadrust::entities::{Body, Region, Solid3D};
|
use acadrust::entities::{Body, Region, Solid3D};
|
||||||
|
use cadkernel::brep::{self, Body as KernelBody};
|
||||||
|
|
||||||
use crate::scene::model::mesh_model::{MeshLodSet, MeshModel};
|
use crate::scene::model::mesh_model::{MeshLodSet, MeshModel};
|
||||||
|
|
||||||
|
|
@ -154,6 +155,33 @@ fn parse_acis(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn kernel_body(solid: &Solid3D) -> Option<KernelBody> {
|
||||||
|
let sat = parse_acis(
|
||||||
|
|| solid.parse_sat(),
|
||||||
|
solid.acis_data.is_binary,
|
||||||
|
&solid.acis_data.sab_data,
|
||||||
|
)?;
|
||||||
|
let (mut bodies, loss) = cadkernel::acis::lift(&sat);
|
||||||
|
if !loss.is_empty() || bodies.len() != 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let body = bodies.pop()?;
|
||||||
|
let source = body.provenance.source()?;
|
||||||
|
let Some((matrix, translation, scale)) = body_transform(&sat, source.index() as usize).ok()?
|
||||||
|
else {
|
||||||
|
return Some(body);
|
||||||
|
};
|
||||||
|
brep::transform(
|
||||||
|
&body,
|
||||||
|
&brep::Placement {
|
||||||
|
x_axis: [scale * matrix[0], scale * matrix[1], scale * matrix[2]],
|
||||||
|
y_axis: [scale * matrix[3], scale * matrix[4], scale * matrix[5]],
|
||||||
|
z_axis: [scale * matrix[6], scale * matrix[7], scale * matrix[8]],
|
||||||
|
origin: translation,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn remap_acis_material_bindings(
|
fn remap_acis_material_bindings(
|
||||||
set: &mut MeshLodSet,
|
set: &mut MeshLodSet,
|
||||||
acis: &acadrust::entities::AcisData,
|
acis: &acadrust::entities::AcisData,
|
||||||
|
|
|
||||||
|
|
@ -586,11 +586,28 @@ impl Scene {
|
||||||
self.images.insert(handle, model);
|
self.images.insert(handle, model);
|
||||||
}
|
}
|
||||||
self.refresh_meshes_for_handles(&[handle]);
|
self.refresh_meshes_for_handles(&[handle]);
|
||||||
if let Some(operation) = self.document.solid_history_operation(handle).cloned() {
|
self.restore_solid_models(&[handle]);
|
||||||
if let Ok(body) = cadkernel::acis::rebuild_body(&operation) {
|
|
||||||
self.solid_models.insert(handle, body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn restore_solid_models(&mut self, handles: &[Handle]) {
|
||||||
|
let bodies: Vec<(Handle, cadkernel::brep::Body)> = handles
|
||||||
|
.iter()
|
||||||
|
.filter(|handle| !self.solid_models.contains_key(handle))
|
||||||
|
.filter_map(|&handle| {
|
||||||
|
let from_history = self
|
||||||
|
.document
|
||||||
|
.solid_history_operation(handle)
|
||||||
|
.and_then(|operation| cadkernel::acis::rebuild_body(operation).ok());
|
||||||
|
let body = from_history.or_else(|| match self.document.get_entity(handle) {
|
||||||
|
Some(EntityType::Solid3D(solid)) => {
|
||||||
|
crate::scene::convert::solid3d_tess::kernel_body(solid)
|
||||||
}
|
}
|
||||||
|
_ => None,
|
||||||
|
})?;
|
||||||
|
Some((handle, body))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
self.solid_models.extend(bodies);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Re-tessellate only the named ACIS entities. The former edit path
|
/// Re-tessellate only the named ACIS entities. The former edit path
|
||||||
|
|
|
||||||
|
|
@ -1645,11 +1645,7 @@ pub struct Scene {
|
||||||
/// owning block emits a transformed instance so a block placed at an
|
/// owning block emits a transformed instance so a block placed at an
|
||||||
/// INSERT scale renders at the right size. (#123)
|
/// INSERT scale renders at the right size. (#123)
|
||||||
pub block_meshes: HashMap<Handle, MeshLodSet>,
|
pub block_meshes: HashMap<Handle, MeshLodSet>,
|
||||||
/// Live B-reps for solids created this session by the Model tab, keyed by
|
/// Kernel B-reps used by solid operations and exact-geometry saves.
|
||||||
/// entity handle. Backs the Design-group boolean tools (a solid must be
|
|
||||||
/// here to be combined) and the exact-geometry save path, which writes
|
|
||||||
/// each one back out as ACIS rather than as facets. Not persisted —
|
|
||||||
/// rebuilt only by creating or combining primitives in-session.
|
|
||||||
pub solid_models: HashMap<Handle, cadkernel::brep::Body>,
|
pub solid_models: HashMap<Handle, cadkernel::brep::Body>,
|
||||||
/// GPU render data for raster images (RasterImage entities), keyed by handle.
|
/// GPU render data for raster images (RasterImage entities), keyed by handle.
|
||||||
pub images: HashMap<Handle, ImageModel>,
|
pub images: HashMap<Handle, ImageModel>,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue