forked from 1b-agent/opencadstudio-upstream
Complete exact INTERSECT behavior
This commit is contained in:
1 parent
143703d809
commit
1e118b2e90
8 files changed
+497
-26
No files matched your search
Generated
+1
-1
@@ -882,7 +882,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
[[package]]
|
||||
name = "cadkernel"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=6a5e6fc2822f6af6b2611cd605b44437b35158f0#6a5e6fc2822f6af6b2611cd605b44437b35158f0"
|
||||
source = "git+https://github.com/ramox81/cadkernel.git?rev=fb8946b7f051b67066d044fd5bb63681b64eaa35#fb8946b7f051b67066d044fd5bb63681b64eaa35"
|
||||
dependencies = [
|
||||
"acadrust",
|
||||
"cavalier_contours",
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ rfd = "0.17"
|
||||
clap = { version = "4", features = ["derive", "string"] }
|
||||
env_logger = "0.11"
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "5b56571a190e7a17c8f12d36390d2938b0fb72f7", features = ["serde"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "6a5e6fc2822f6af6b2611cd605b44437b35158f0", features = ["acis", "offset"] }
|
||||
cadkernel = { git = "https://github.com/ramox81/cadkernel.git", rev = "fb8946b7f051b67066d044fd5bb63681b64eaa35", features = ["acis", "offset"] }
|
||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||
flate2 = "1"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||
|
||||
@@ -15,7 +15,7 @@ thiserror = "1"
|
||||
|
||||
# Host adapters convert ObjectId to acadrust handles; kernel handles stay local.
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "5b56571a190e7a17c8f12d36390d2938b0fb72f7", features = ["serde"], optional = true }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "6a5e6fc2822f6af6b2611cd605b44437b35158f0", features = ["acis", "offset"], optional = true }
|
||||
cadkernel = { git = "https://github.com/ramox81/cadkernel.git", rev = "fb8946b7f051b67066d044fd5bb63681b64eaa35", features = ["acis", "offset"], optional = true }
|
||||
ocs_plugin_api = { path = "../ocs_plugin_api", optional = true, features = ["host"] }
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
+31
-21
@@ -975,7 +975,9 @@ impl OpenCADStudio {
|
||||
"UNION" | "INTERSECT" => {
|
||||
use crate::modules::model::boolean_cmd::BoolOp;
|
||||
if let Some(op) = BoolOp::from_id(cmd) {
|
||||
let solid_count = {
|
||||
let ready = if op == BoolOp::Intersect {
|
||||
self.intersect_ready()
|
||||
} else {
|
||||
let scene = &self.tabs[i].scene;
|
||||
scene
|
||||
.selected_handles_in_order()
|
||||
@@ -989,8 +991,9 @@ impl OpenCADStudio {
|
||||
})
|
||||
.take(2)
|
||||
.count()
|
||||
>= 2
|
||||
};
|
||||
if solid_count < 2 {
|
||||
if !ready {
|
||||
use crate::modules::draw::select::SelectObjectsCommand;
|
||||
let selection = SelectObjectsCommand::new(cmd);
|
||||
self.command_line.push_info(&selection.prompt());
|
||||
@@ -1069,9 +1072,9 @@ impl OpenCADStudio {
|
||||
// REGION — convert selected closed boundaries (closed polylines /
|
||||
// circles) into Region entities (one wire loop each).
|
||||
"REGION" | "REG" => {
|
||||
use acadrust::entities::{Region, Wire};
|
||||
use acadrust::entities::Region;
|
||||
use acadrust::types::Vector3;
|
||||
let mut loops: Vec<Vec<Vector3>> = Vec::new();
|
||||
let mut regions = Vec::new();
|
||||
for (_, e) in self.tabs[i].scene.selected_entities().iter() {
|
||||
let supported = matches!(
|
||||
e,
|
||||
@@ -1079,32 +1082,39 @@ impl OpenCADStudio {
|
||||
if pl.is_closed && pl.vertices.len() >= 3
|
||||
) || matches!(e, acadrust::EntityType::Circle(_));
|
||||
if supported {
|
||||
let Some(curve) = crate::entities::curve::entity_curve(e) else {
|
||||
let Some((plane, loops, true)) =
|
||||
crate::scene::model::presspull_model::profile_geometry(e)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
loops.push(
|
||||
crate::entities::curve::curve_points(&curve)
|
||||
.into_iter()
|
||||
.map(|point| Vector3::new(point[0], point[1], point[2]))
|
||||
.collect(),
|
||||
let Some(body) = cadkernel::brep::planar_region(plane, &loops) else {
|
||||
continue;
|
||||
};
|
||||
let mut region = Region::new();
|
||||
region.point_of_reference = Vector3::new(
|
||||
plane.origin[0],
|
||||
plane.origin[1],
|
||||
plane.origin[2],
|
||||
);
|
||||
region.common.layer = self.tabs[i].active_layer.clone();
|
||||
regions.push((region, body));
|
||||
}
|
||||
}
|
||||
if loops.is_empty() {
|
||||
if regions.is_empty() {
|
||||
self.command_line
|
||||
.push_error(crate::t!("REGION: select closed polylines or circles.").as_ref());
|
||||
} else {
|
||||
self.push_undo_snapshot(i, "REGION");
|
||||
let count = loops.len();
|
||||
for pts in loops {
|
||||
let mut w = Wire::new();
|
||||
let first = pts.first().copied().unwrap_or(Vector3::new(0.0, 0.0, 0.0));
|
||||
w.points = pts;
|
||||
let mut r = Region::new();
|
||||
r.point_of_reference = first;
|
||||
r.wires = vec![w];
|
||||
r.common.layer = self.tabs[i].active_layer.clone();
|
||||
self.tabs[i].scene.add_entity(acadrust::EntityType::Region(r));
|
||||
let count = regions.len();
|
||||
let mut created = Vec::with_capacity(count);
|
||||
for (region, body) in regions {
|
||||
let handle = self.add_region_model(region, body);
|
||||
if handle.is_null() {
|
||||
self.tabs[i].scene.rollback_new_entities(&created);
|
||||
self.discard_last_undo_entry(i);
|
||||
return Some(iced::Task::none());
|
||||
}
|
||||
created.push(handle);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line
|
||||
|
||||
+450
-1
@@ -1,16 +1,166 @@
|
||||
// Kernel B-rep solid modelling and exact ACIS persistence.
|
||||
|
||||
use acadrust::{
|
||||
entities::Solid3D, objects::SolidHistoryOperation, EntityType, Handle,
|
||||
entities::{AcisData, Solid3D, SurfaceData, SurfaceKind, Wire},
|
||||
objects::SolidHistoryOperation,
|
||||
EntityType, Handle,
|
||||
};
|
||||
use cadkernel::brep::Body;
|
||||
use iced::Task;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::Message;
|
||||
use crate::modules::model::boolean_cmd::BoolOp;
|
||||
use crate::scene::model::solid_history;
|
||||
use crate::scene::model::solid_model::{self, Bool};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum IntersectEntityKind {
|
||||
Solid,
|
||||
Region,
|
||||
Surface,
|
||||
}
|
||||
|
||||
impl IntersectEntityKind {
|
||||
fn from_entity(entity: &EntityType) -> Option<Self> {
|
||||
Some(match entity {
|
||||
EntityType::Solid3D(_) => Self::Solid,
|
||||
EntityType::Region(_) => Self::Region,
|
||||
EntityType::Surface(_) => Self::Surface,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct IntersectGroup {
|
||||
kind: IntersectEntityKind,
|
||||
plane: Option<cadkernel::space::Plane>,
|
||||
handles: Vec<Handle>,
|
||||
}
|
||||
|
||||
type PreparedSolidDisplay = (
|
||||
crate::scene::model::mesh_model::MeshLodSet,
|
||||
Vec<Wire>,
|
||||
[f64; 3],
|
||||
);
|
||||
|
||||
enum PreparedIntersectOutcome {
|
||||
Replace {
|
||||
retained: Handle,
|
||||
entity: EntityType,
|
||||
body: Body,
|
||||
display: PreparedSolidDisplay,
|
||||
},
|
||||
Consume,
|
||||
}
|
||||
|
||||
struct PreparedIntersect {
|
||||
handles: Vec<Handle>,
|
||||
outcome: PreparedIntersectOutcome,
|
||||
}
|
||||
|
||||
fn intersect_planar_body_plane(body: &Body) -> Option<cadkernel::space::Plane> {
|
||||
let mut faces = body.face_keys();
|
||||
let first = cadkernel::brep::planar_face_profile(body, faces.next()?)?.plane;
|
||||
let first_normal = glam::DVec3::from_array(first.normal()?);
|
||||
let tolerance = cadkernel::brep::operation_tolerance(&[body]);
|
||||
faces
|
||||
.all(|face| {
|
||||
let Some(profile) = cadkernel::brep::planar_face_profile(body, face) else {
|
||||
return false;
|
||||
};
|
||||
let Some(normal) = profile.plane.normal() else {
|
||||
return false;
|
||||
};
|
||||
first_normal.dot(glam::DVec3::from_array(normal)).abs() >= 1.0 - 1e-9
|
||||
&& first
|
||||
.distance_to(profile.plane.origin)
|
||||
.is_some_and(|distance| distance.abs() <= tolerance * 4.0)
|
||||
})
|
||||
.then_some(first)
|
||||
}
|
||||
|
||||
fn intersect_coplanar_body(plane: cadkernel::space::Plane, body: &Body) -> bool {
|
||||
let Some(other) = intersect_planar_body_plane(body) else {
|
||||
return false;
|
||||
};
|
||||
let Some(one_normal) = plane.normal().map(glam::DVec3::from_array) else {
|
||||
return false;
|
||||
};
|
||||
let Some(other_normal) = other.normal().map(glam::DVec3::from_array) else {
|
||||
return false;
|
||||
};
|
||||
let tolerance = cadkernel::brep::operation_tolerance(&[body]);
|
||||
one_normal.dot(other_normal).abs() >= 1.0 - 1e-9
|
||||
&& plane
|
||||
.distance_to(other.origin)
|
||||
.is_some_and(|distance| distance.abs() <= tolerance * 4.0)
|
||||
}
|
||||
|
||||
fn entity_with_intersection_body(mut source: EntityType, body: &Body) -> Option<EntityType> {
|
||||
let document = crate::scene::convert::acis_export::solid_to_sat(body)?;
|
||||
let wires = solid_model::edge_wires(body);
|
||||
match &mut source {
|
||||
EntityType::Solid3D(entity) => {
|
||||
entity.wires = wires;
|
||||
entity.silhouettes.clear();
|
||||
entity.history_handle = None;
|
||||
entity.set_sat_document(&document);
|
||||
}
|
||||
EntityType::Region(entity) => {
|
||||
entity.wires = wires;
|
||||
entity.silhouettes.clear();
|
||||
entity.history_handle = None;
|
||||
entity.set_sat_document(&document);
|
||||
}
|
||||
EntityType::Surface(entity) => {
|
||||
entity.wires = wires;
|
||||
entity.silhouettes.clear();
|
||||
entity.history_handle = None;
|
||||
if entity.kind != SurfaceKind::Plane {
|
||||
entity.kind = SurfaceKind::Generic;
|
||||
entity.surface_data = SurfaceData::Generic;
|
||||
}
|
||||
entity.acis_data = AcisData::from_sat(&document.to_sat_string());
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
Some(source)
|
||||
}
|
||||
|
||||
enum IntersectBodyOutcome {
|
||||
Area(Body),
|
||||
Touching,
|
||||
Disjoint,
|
||||
}
|
||||
|
||||
fn intersect_bodies(
|
||||
kind: IntersectEntityKind,
|
||||
bodies: Vec<Body>,
|
||||
) -> Result<IntersectBodyOutcome, cadkernel::brep::Snag> {
|
||||
if kind == IntersectEntityKind::Solid {
|
||||
let mut operands = bodies.into_iter();
|
||||
let mut result = operands
|
||||
.next()
|
||||
.ok_or(cadkernel::brep::Snag::CutRefused)?;
|
||||
for operand in operands {
|
||||
result = solid_model::boolean_result(Bool::Intersect, &result, &operand)?;
|
||||
if result.faces.is_empty() {
|
||||
return Ok(IntersectBodyOutcome::Disjoint);
|
||||
}
|
||||
}
|
||||
return Ok(IntersectBodyOutcome::Area(result));
|
||||
}
|
||||
|
||||
let references = bodies.iter().collect::<Vec<_>>();
|
||||
let tolerance = cadkernel::brep::operation_tolerance(&references);
|
||||
Ok(match cadkernel::brep::intersect_planar_regions(&bodies, tolerance)? {
|
||||
cadkernel::brep::PlanarIntersection::Area(body) => IntersectBodyOutcome::Area(body),
|
||||
cadkernel::brep::PlanarIntersection::Touching => IntersectBodyOutcome::Touching,
|
||||
cadkernel::brep::PlanarIntersection::Disjoint => IntersectBodyOutcome::Disjoint,
|
||||
})
|
||||
}
|
||||
|
||||
impl super::OpenCADStudio {
|
||||
/// Add a solid and register its persistent B-rep.
|
||||
pub(super) fn add_solid_model(
|
||||
@@ -81,6 +231,29 @@ impl super::OpenCADStudio {
|
||||
handle
|
||||
}
|
||||
|
||||
pub(super) fn add_region_model(
|
||||
&mut self,
|
||||
mut region: acadrust::entities::Region,
|
||||
body: Body,
|
||||
) -> Handle {
|
||||
let i = self.active_tab;
|
||||
region.wires = solid_model::edge_wires(&body);
|
||||
let Some(document) = crate::scene::convert::acis_export::solid_to_sat(&body) else {
|
||||
self.command_line
|
||||
.push_error(crate::t!("The region could not be encoded as ACIS.").as_ref());
|
||||
return Handle::NULL;
|
||||
};
|
||||
region.set_sat_document(&document);
|
||||
let Some(handle) = self.commit_entity_handle(EntityType::Region(region)) else {
|
||||
return Handle::NULL;
|
||||
};
|
||||
if !self.tabs[i].scene.register_solid_model(handle, body) {
|
||||
self.tabs[i].scene.rollback_new_entities(&[handle]);
|
||||
return Handle::NULL;
|
||||
}
|
||||
handle
|
||||
}
|
||||
|
||||
fn selected_solid_handles(&mut self) -> Vec<Handle> {
|
||||
let i = self.active_tab;
|
||||
let mut handles: Vec<Handle> = self.tabs[i]
|
||||
@@ -100,6 +273,91 @@ impl super::OpenCADStudio {
|
||||
handles
|
||||
}
|
||||
|
||||
fn selected_intersect_handles(&self) -> Vec<Handle> {
|
||||
let scene = &self.tabs[self.active_tab].scene;
|
||||
scene
|
||||
.selected_handles_in_order()
|
||||
.into_iter()
|
||||
.filter(|handle| !scene.is_layer_locked(*handle))
|
||||
.filter(|handle| {
|
||||
scene
|
||||
.document
|
||||
.get_entity(*handle)
|
||||
.and_then(IntersectEntityKind::from_entity)
|
||||
.is_some()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(super) fn intersect_ready(&self) -> bool {
|
||||
let scene = &self.tabs[self.active_tab].scene;
|
||||
let mut counts = [0usize; 3];
|
||||
for handle in scene.selected_handles_in_order() {
|
||||
if scene.is_layer_locked(handle) {
|
||||
continue;
|
||||
}
|
||||
let Some(kind) = scene
|
||||
.document
|
||||
.get_entity(handle)
|
||||
.and_then(IntersectEntityKind::from_entity)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let index = match kind {
|
||||
IntersectEntityKind::Solid => 0,
|
||||
IntersectEntityKind::Region => 1,
|
||||
IntersectEntityKind::Surface => 2,
|
||||
};
|
||||
counts[index] += 1;
|
||||
}
|
||||
counts.into_iter().any(|count| count >= 2)
|
||||
}
|
||||
|
||||
fn intersect_groups(
|
||||
&self,
|
||||
handles: &[Handle],
|
||||
bodies: &HashMap<Handle, Body>,
|
||||
) -> Vec<IntersectGroup> {
|
||||
let scene = &self.tabs[self.active_tab].scene;
|
||||
let mut groups = Vec::<IntersectGroup>::new();
|
||||
for handle in handles {
|
||||
let Some(kind) = scene
|
||||
.document
|
||||
.get_entity(*handle)
|
||||
.and_then(IntersectEntityKind::from_entity)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let plane = (kind != IntersectEntityKind::Solid)
|
||||
.then(|| intersect_planar_body_plane(&bodies[handle]))
|
||||
.flatten();
|
||||
if let Some(group) = groups.iter_mut().find(|group| {
|
||||
group.kind == kind
|
||||
&& if kind == IntersectEntityKind::Solid {
|
||||
true
|
||||
} else {
|
||||
match (group.plane, plane) {
|
||||
(Some(group_plane), Some(_)) => {
|
||||
intersect_coplanar_body(group_plane, &bodies[handle])
|
||||
}
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}) {
|
||||
group.handles.push(*handle);
|
||||
} else {
|
||||
groups.push(IntersectGroup {
|
||||
kind,
|
||||
plane,
|
||||
handles: vec![*handle],
|
||||
});
|
||||
}
|
||||
}
|
||||
groups.retain(|group| group.handles.len() >= 2);
|
||||
groups
|
||||
}
|
||||
|
||||
fn replace_solid_body(&mut self, handle: Handle, result: Body, label: &str) -> bool {
|
||||
let i = self.active_tab;
|
||||
let Some(document) =
|
||||
@@ -195,8 +453,199 @@ impl super::OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Intersect compatible selected solids, Regions, and coplanar Surfaces.
|
||||
pub(super) fn solid_intersect(&mut self) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
let mut handles = self.selected_intersect_handles();
|
||||
self.tabs[i].scene.restore_solid_models(&handles);
|
||||
handles.retain(|handle| self.tabs[i].scene.solid_models.contains_key(handle));
|
||||
let bodies = handles
|
||||
.iter()
|
||||
.filter_map(|handle| {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.solid_models
|
||||
.get(handle)
|
||||
.cloned()
|
||||
.map(|body| (*handle, body))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
let groups = self.intersect_groups(&handles, &bodies);
|
||||
if groups.is_empty() {
|
||||
self.command_line.push_error(
|
||||
crate::t!("INTERSECT: select at least two solids, Regions, or coplanar Surfaces of a compatible type.")
|
||||
.as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
let mut prepared = Vec::new();
|
||||
let mut retained_without_change = 0usize;
|
||||
for group in groups {
|
||||
let operands = group
|
||||
.handles
|
||||
.iter()
|
||||
.map(|handle| bodies[handle].clone())
|
||||
.collect::<Vec<_>>();
|
||||
let outcome = match intersect_bodies(group.kind, operands) {
|
||||
Ok(outcome) => outcome,
|
||||
Err(cadkernel::brep::Snag::NoClosedForm) => {
|
||||
self.command_line.push_error(
|
||||
crate::t!("INTERSECT: the selected objects do not have a supported coplanar surface intersection.")
|
||||
.as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
Err(cadkernel::brep::Snag::Coincident) => {
|
||||
self.command_line.push_error(
|
||||
crate::t!("INTERSECT: the selected coincident geometry is ambiguous.")
|
||||
.as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
Err(cadkernel::brep::Snag::CutRefused) => {
|
||||
self.command_line.push_error(
|
||||
crate::t!("INTERSECT: the selected topology could not be intersected safely.")
|
||||
.as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
};
|
||||
|
||||
match outcome {
|
||||
IntersectBodyOutcome::Area(body) => {
|
||||
let retained = group.handles[0];
|
||||
let Some(source) = self.tabs[i].scene.document.get_entity(retained).cloned()
|
||||
else {
|
||||
return Task::none();
|
||||
};
|
||||
let Some(entity) = entity_with_intersection_body(source, &body) else {
|
||||
self.command_line.push_error(
|
||||
crate::t!("The INTERSECT result could not be encoded as ACIS.")
|
||||
.as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
};
|
||||
let Some(display) = self.tabs[i]
|
||||
.scene
|
||||
.prepare_solid_model_display(retained, &body)
|
||||
.filter(|display| display.0.complete)
|
||||
else {
|
||||
self.command_line.push_error(
|
||||
crate::t!("The INTERSECT result could not be displayed completely. The original objects were retained.")
|
||||
.as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
};
|
||||
prepared.push(PreparedIntersect {
|
||||
handles: group.handles,
|
||||
outcome: PreparedIntersectOutcome::Replace {
|
||||
retained,
|
||||
entity,
|
||||
body,
|
||||
display,
|
||||
},
|
||||
});
|
||||
}
|
||||
IntersectBodyOutcome::Touching
|
||||
if group.kind == IntersectEntityKind::Region
|
||||
|| group.kind == IntersectEntityKind::Surface =>
|
||||
{
|
||||
prepared.push(PreparedIntersect {
|
||||
handles: group.handles,
|
||||
outcome: PreparedIntersectOutcome::Consume,
|
||||
});
|
||||
}
|
||||
IntersectBodyOutcome::Disjoint
|
||||
if group.kind == IntersectEntityKind::Region =>
|
||||
{
|
||||
prepared.push(PreparedIntersect {
|
||||
handles: group.handles,
|
||||
outcome: PreparedIntersectOutcome::Consume,
|
||||
});
|
||||
}
|
||||
IntersectBodyOutcome::Touching | IntersectBodyOutcome::Disjoint => {
|
||||
retained_without_change += group.handles.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if prepared.is_empty() {
|
||||
self.command_line.push_output(
|
||||
crate::tf!(
|
||||
"INTERSECT: no positive-area common result; %{count} original object(s) retained.",
|
||||
count = retained_without_change
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
let record_history = self.tabs[i].scene.document.header.record_solid_history;
|
||||
self.push_undo_snapshot(i, "INTERSECT");
|
||||
let mut results = Vec::new();
|
||||
let mut consumed = Vec::new();
|
||||
for group in prepared {
|
||||
match group.outcome {
|
||||
PreparedIntersectOutcome::Replace {
|
||||
retained,
|
||||
entity,
|
||||
body,
|
||||
display,
|
||||
} => {
|
||||
self.tabs[i].scene.delete_solid_history(retained);
|
||||
if !self.tabs[i].scene.update_entity(entity) {
|
||||
self.command_line.push_error(
|
||||
crate::t!("INTERSECT: the retained object could not be updated.")
|
||||
.as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
}
|
||||
if record_history
|
||||
&& matches!(
|
||||
self.tabs[i].scene.document.get_entity(retained),
|
||||
Some(EntityType::Solid3D(_))
|
||||
)
|
||||
{
|
||||
let history = solid_history::brep_op(&body);
|
||||
self.tabs[i].scene.create_solid_history(retained, history);
|
||||
}
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.register_prepared_solid_model(retained, body, display);
|
||||
results.push(retained);
|
||||
consumed.extend(
|
||||
group
|
||||
.handles
|
||||
.into_iter()
|
||||
.filter(|handle| *handle != retained),
|
||||
);
|
||||
}
|
||||
PreparedIntersectOutcome::Consume => consumed.extend(group.handles),
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.erase_entities(&consumed);
|
||||
self.tabs[i].scene.deselect_all();
|
||||
for handle in &results {
|
||||
self.tabs[i].scene.select_entity(*handle, false);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
self.command_line.push_output(
|
||||
crate::tf!(
|
||||
"INTERSECT: created %{count} result object(s).",
|
||||
count = results.len()
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Run a boolean over the selected solids in selection order.
|
||||
pub(super) fn solid_boolean(&mut self, op: BoolOp) -> Task<Message> {
|
||||
if op == BoolOp::Intersect {
|
||||
return self.solid_intersect();
|
||||
}
|
||||
let i = self.active_tab;
|
||||
let handles = self.selected_solid_handles();
|
||||
if handles.len() < 2 {
|
||||
|
||||
@@ -159,6 +159,10 @@ pub fn kernel_body(solid: &Solid3D) -> Option<KernelBody> {
|
||||
kernel_acis_body(&solid.acis_data)
|
||||
}
|
||||
|
||||
pub fn kernel_region_body(region: &Region) -> Option<KernelBody> {
|
||||
kernel_acis_body(®ion.acis_data)
|
||||
}
|
||||
|
||||
pub fn kernel_surface_body(surface: &Surface) -> Option<KernelBody> {
|
||||
kernel_acis_body(&surface.acis_data)
|
||||
}
|
||||
|
||||
@@ -602,6 +602,9 @@ impl Scene {
|
||||
Some(EntityType::Solid3D(solid)) => {
|
||||
crate::scene::convert::solid3d_tess::kernel_body(solid)
|
||||
}
|
||||
Some(EntityType::Region(region)) => {
|
||||
crate::scene::convert::solid3d_tess::kernel_region_body(region)
|
||||
}
|
||||
Some(EntityType::Surface(surface)) => {
|
||||
crate::scene::convert::solid3d_tess::kernel_surface_body(surface)
|
||||
}
|
||||
|
||||
@@ -365,13 +365,18 @@ pub enum Bool {
|
||||
/// missing, and passing that on unchanged is the point: a half-done boolean
|
||||
/// looks finished.
|
||||
pub fn boolean(op: Bool, a: &Body, b: &Body) -> Option<Body> {
|
||||
boolean_result(op, a, b).ok()
|
||||
}
|
||||
|
||||
/// Combine two solids while retaining the kernel's exact refusal reason.
|
||||
pub fn boolean_result(op: Bool, a: &Body, b: &Body) -> Result<Body, brep::Snag> {
|
||||
let how = match op {
|
||||
Bool::Union => brep::Operation::Union,
|
||||
Bool::Subtract => brep::Operation::Difference,
|
||||
Bool::Intersect => brep::Operation::Intersection,
|
||||
};
|
||||
let tolerance = brep::operation_tolerance(&[a, b]);
|
||||
brep::combine(a.clone(), b.clone(), how, tolerance).ok()
|
||||
brep::combine(a.clone(), b.clone(), how, tolerance)
|
||||
}
|
||||
|
||||
// ── Tessellation ────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in new issue
Block a user