Complete SUBTRACT behavior

This commit is contained in:
ramox81 committed 2026-09-07 13:58:31 +03:00
1 parent 5857ffa2bd
commit 04a9a1b0a0
10 files changed
+562 -69

No files matched your search

Generated
+1 -1
View File
@@ -882,7 +882,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]]
name = "cadkernel"
version = "0.1.0"
source = "git+https://github.com/ramox81/cadkernel.git?rev=328b55a9a45bc2118526a49156bcb94e46f10444#328b55a9a45bc2118526a49156bcb94e46f10444"
source = "git+https://github.com/ramox81/cadkernel.git?rev=2162f5cce085929966047b8c24c7e0da83c26910#2162f5cce085929966047b8c24c7e0da83c26910"
dependencies = [
"acadrust",
"cavalier_contours",
+1 -1
View File
@@ -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/ramox81/cadkernel.git", rev = "328b55a9a45bc2118526a49156bcb94e46f10444", features = ["acis", "offset"] }
cadkernel = { git = "https://github.com/ramox81/cadkernel.git", rev = "2162f5cce085929966047b8c24c7e0da83c26910", features = ["acis", "offset"] }
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
flate2 = "1"
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
+1 -1
View File
@@ -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/ramox81/cadkernel.git", rev = "328b55a9a45bc2118526a49156bcb94e46f10444", features = ["acis", "offset"], optional = true }
cadkernel = { git = "https://github.com/ramox81/cadkernel.git", rev = "2162f5cce085929966047b8c24c7e0da83c26910", features = ["acis", "offset"], optional = true }
ocs_plugin_api = { path = "../ocs_plugin_api", optional = true, features = ["host"] }
[build-dependencies]
+6 -2
View File
@@ -4115,8 +4115,12 @@ impl OpenCADStudio {
return task;
}
CmdResult::SolidSubtract { bases, cutters } => {
let task = self.solid_subtract(&bases, &cutters);
CmdResult::SolidSubtract {
bases,
cutters,
convert_meshes,
} => {
let task = self.solid_subtract(&bases, &cutters, convert_meshes);
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
self.tabs[i].scene.clear_preview_wire();
+23 -5
View File
@@ -1017,22 +1017,40 @@ impl OpenCADStudio {
"SUBTRACT" => {
use crate::modules::model::boolean_cmd::SubtractCommand;
let bases = {
let (bases, bases_have_mesh) = {
let scene = &self.tabs[i].scene;
scene
let bases = scene
.selected_handles_in_order()
.into_iter()
.filter(|handle| !scene.is_layer_locked(*handle))
.filter(|handle| {
matches!(
scene.document.get_entity(*handle),
Some(acadrust::EntityType::Solid3D(_))
Some(
acadrust::EntityType::Solid3D(_)
| acadrust::EntityType::Region(_)
| acadrust::EntityType::Surface(_)
| acadrust::EntityType::Mesh(_)
| acadrust::EntityType::PolygonMesh(_)
| acadrust::EntityType::PolyfaceMesh(_)
)
)
})
.collect()
.collect::<Vec<_>>();
let bases_have_mesh = bases.iter().any(|handle| {
matches!(
scene.document.get_entity(*handle),
Some(
acadrust::EntityType::Mesh(_)
| acadrust::EntityType::PolygonMesh(_)
| acadrust::EntityType::PolyfaceMesh(_)
)
)
});
(bases, bases_have_mesh)
};
self.tabs[i].scene.deselect_all();
let subtract = SubtractCommand::new(bases);
let subtract = SubtractCommand::new(bases, bases_have_mesh);
self.command_line.push_info(&subtract.prompt());
self.tabs[i].active_cmd = Some(Box::new(subtract));
}
+369 -43
View File
@@ -1,7 +1,9 @@
// Kernel B-rep solid modelling and exact ACIS persistence.
use acadrust::{
entities::{EntityCommon, Region, Solid3D, Surface, SurfaceKind},
entities::{
AcisData, EntityCommon, Region, Solid3D, Surface, SurfaceData, SurfaceKind, Wire,
},
objects::SolidHistoryOperation,
EntityType, Handle,
};
@@ -31,6 +33,17 @@ impl UnionEntityKind {
}
}
fn subtract_entity_kind(entity: &EntityType, convert_meshes: bool) -> Option<UnionEntityKind> {
UnionEntityKind::from_entity(entity).or_else(|| {
(convert_meshes
&& matches!(
entity,
EntityType::Mesh(_) | EntityType::PolygonMesh(_) | EntityType::PolyfaceMesh(_)
))
.then_some(UnionEntityKind::Solid)
})
}
struct UnionGroup {
kind: UnionEntityKind,
handles: Vec<Handle>,
@@ -43,6 +56,25 @@ struct PreparedUnion {
common: EntityCommon,
}
struct SubtractGroup {
kind: UnionEntityKind,
plane: Option<cadkernel::space::Plane>,
bases: Vec<Handle>,
cutters: Vec<Handle>,
}
type PreparedSolidDisplay = (
crate::scene::model::mesh_model::MeshLodSet,
Vec<Wire>,
[f64; 3],
);
struct PreparedSubtract {
bases: Vec<Handle>,
cutters: Vec<Handle>,
result: Option<(Handle, EntityType, Body, PreparedSolidDisplay)>,
}
fn inherited_common(source: &EntityCommon) -> EntityCommon {
let mut result = EntityCommon::new();
result.layer = source.layer.clone();
@@ -85,6 +117,111 @@ fn union_bodies(
cadkernel::brep::union_planar_regions(&bodies, tolerance)
}
fn 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 coplanar_bodies(plane: cadkernel::space::Plane, body: &Body) -> bool {
let Some(other) = 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 subtract_bodies(
kind: UnionEntityKind,
plane: Option<cadkernel::space::Plane>,
bases: Vec<Body>,
cutters: Vec<Body>,
) -> Result<Body, cadkernel::brep::Snag> {
if kind != UnionEntityKind::Solid && plane.is_none() {
return Err(cadkernel::brep::Snag::NoClosedForm);
}
if kind != UnionEntityKind::Solid && plane.is_some() {
let references = bases.iter().chain(&cutters).collect::<Vec<_>>();
let tolerance = cadkernel::brep::operation_tolerance(&references);
return cadkernel::brep::subtract_planar_regions(&bases, &cutters, tolerance);
}
let mut bases = bases.into_iter();
let mut result = bases
.next()
.ok_or(cadkernel::brep::Snag::CutRefused)?;
for base in bases {
result = solid_model::boolean_result(Bool::Union, &result, &base)?;
}
for cutter in cutters {
result = solid_model::boolean_result(Bool::Subtract, &result, &cutter)?;
}
Ok(result)
}
fn entity_with_subtract_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);
if matches!(
&source,
EntityType::Mesh(_) | EntityType::PolygonMesh(_) | EntityType::PolyfaceMesh(_)
) {
let mut solid = Solid3D::new();
solid.common = source.common().clone();
source = EntityType::Solid3D(solid);
}
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)
}
impl super::OpenCADStudio {
/// Add a solid and register its persistent B-rep.
pub(super) fn add_solid_model(
@@ -569,73 +706,262 @@ impl super::OpenCADStudio {
&mut self,
bases: &[Handle],
cutters: &[Handle],
convert_meshes: bool,
) -> Task<Message> {
let i = self.active_tab;
let valid_solid = |handle: &Handle| {
let valid_operand = |handle: &Handle| {
!self.tabs[i].scene.is_layer_locked(*handle)
&& matches!(
self.tabs[i].scene.document.get_entity(*handle),
Some(EntityType::Solid3D(_))
)
&& self.tabs[i]
.scene
.document
.get_entity(*handle)
.and_then(|entity| subtract_entity_kind(entity, convert_meshes))
.is_some()
};
let mut base_handles: Vec<_> = bases.iter().copied().filter(valid_solid).collect();
let mut cutter_handles: Vec<_> = cutters.iter().copied().filter(valid_solid).collect();
let mut base_handles: Vec<_> = bases.iter().copied().filter(valid_operand).collect();
let mut cutter_handles: Vec<_> = cutters.iter().copied().filter(valid_operand).collect();
cutter_handles.retain(|handle| !base_handles.contains(handle));
let mut operands = base_handles.clone();
operands.extend(cutter_handles.iter().copied());
self.tabs[i].scene.restore_solid_models(&operands);
base_handles.retain(|handle| self.tabs[i].scene.solid_models.contains_key(handle));
cutter_handles.retain(|handle| self.tabs[i].scene.solid_models.contains_key(handle));
let mut operand_bodies = std::collections::HashMap::<Handle, Body>::new();
for handle in &operands {
if let Some(body) = self.tabs[i].scene.solid_models.get(handle).cloned() {
operand_bodies.insert(*handle, body);
continue;
}
let Some(entity) = self.tabs[i].scene.document.get_entity(*handle) else {
continue;
};
if convert_meshes
&& matches!(
entity,
EntityType::Mesh(_) | EntityType::PolygonMesh(_) | EntityType::PolyfaceMesh(_)
)
{
let Some(body) = crate::entities::mesh::closed_mesh_body(entity) else {
self.command_line.push_error(
crate::t!("SUBTRACT: a selected Mesh is not a supported closed single-shell mesh; no object was changed.")
.as_ref(),
);
return Task::none();
};
operand_bodies.insert(*handle, body);
}
}
base_handles.retain(|handle| operand_bodies.contains_key(handle));
cutter_handles.retain(|handle| operand_bodies.contains_key(handle));
if base_handles.is_empty() || cutter_handles.is_empty() {
self.command_line.push_error(
crate::t!("SUBTRACT: select at least one base solid and one cutter solid.")
crate::t!("SUBTRACT: select at least one base and one cutter Solid, Region, or Surface.")
.as_ref(),
);
return Task::none();
}
let mut result = self.tabs[i].scene.solid_models[&base_handles[0]].clone();
for handle in &base_handles[1..] {
let operand = &self.tabs[i].scene.solid_models[handle];
let Some(combined) = solid_model::boolean(Bool::Union, &result, operand) else {
self.command_line.push_error(
crate::t!("SUBTRACT failed while combining the base solids.").as_ref(),
);
return Task::none();
};
result = combined;
let kind_for = |handle: Handle| {
self.tabs[i]
.scene
.document
.get_entity(handle)
.and_then(|entity| subtract_entity_kind(entity, convert_meshes))
};
let mut groups = Vec::<SubtractGroup>::new();
for kind in [
UnionEntityKind::Solid,
UnionEntityKind::Region,
UnionEntityKind::Surface,
] {
let kind_bases = base_handles
.iter()
.copied()
.filter(|handle| kind_for(*handle) == Some(kind))
.collect::<Vec<_>>();
if kind_bases.is_empty() {
continue;
}
if kind == UnionEntityKind::Solid {
let kind_cutters = cutter_handles
.iter()
.copied()
.filter(|handle| kind_for(*handle) == Some(kind))
.collect::<Vec<_>>();
if !kind_cutters.is_empty() {
groups.push(SubtractGroup {
kind,
plane: None,
bases: kind_bases,
cutters: kind_cutters,
});
}
continue;
}
for handle in kind_bases {
let body = &operand_bodies[&handle];
let plane = planar_body_plane(body);
if let Some(group) = groups.iter_mut().find(|group| {
group.kind == kind
&& match (group.plane, plane) {
(Some(group_plane), Some(_)) => coplanar_bodies(group_plane, body),
(None, None) => true,
_ => false,
}
}) {
group.bases.push(handle);
} else {
groups.push(SubtractGroup {
kind,
plane,
bases: vec![handle],
cutters: Vec::new(),
});
}
}
for handle in cutter_handles
.iter()
.copied()
.filter(|handle| kind_for(*handle) == Some(kind))
{
let body = &operand_bodies[&handle];
let plane = planar_body_plane(body);
if let Some(group) = groups.iter_mut().find(|group| {
group.kind == kind
&& match (group.plane, plane) {
(Some(group_plane), Some(_)) => coplanar_bodies(group_plane, body),
(None, None) => true,
_ => false,
}
}) {
group.cutters.push(handle);
}
}
}
for handle in &cutter_handles {
let operand = &self.tabs[i].scene.solid_models[handle];
let Some(difference) = solid_model::boolean(Bool::Subtract, &result, operand) else {
self.command_line.push_error(
crate::t!("SUBTRACT failed while removing the selected solids.").as_ref(),
);
return Task::none();
};
result = difference;
}
if crate::scene::convert::acis_export::solid_to_sat(&result).is_none() {
self.command_line
.push_error(crate::t!("The boolean result could not be encoded as ACIS.").as_ref());
groups.retain(|group| !group.bases.is_empty() && !group.cutters.is_empty());
if groups.is_empty() {
self.command_line.push_error(
crate::t!("SUBTRACT: no compatible base and cutter types or planes were selected.")
.as_ref(),
);
return Task::none();
}
operands = base_handles;
operands.extend(cutter_handles);
let mut prepared = Vec::with_capacity(groups.len());
for group in groups {
let base_bodies = group
.bases
.iter()
.map(|handle| operand_bodies[handle].clone())
.collect::<Vec<_>>();
let cutter_bodies = group
.cutters
.iter()
.map(|handle| operand_bodies[handle].clone())
.collect::<Vec<_>>();
let result = match subtract_bodies(group.kind, group.plane, base_bodies, cutter_bodies) {
Ok(result) => result,
Err(cadkernel::brep::Snag::NoClosedForm) => {
self.command_line.push_error(
crate::t!("SUBTRACT: the selected geometry includes an unsupported surface intersection.")
.as_ref(),
);
return Task::none();
}
Err(cadkernel::brep::Snag::Coincident) => {
self.command_line.push_error(
crate::t!("SUBTRACT: the selected coincident geometry is ambiguous.")
.as_ref(),
);
return Task::none();
}
Err(cadkernel::brep::Snag::CutRefused) => {
self.command_line.push_error(
crate::t!("SUBTRACT: the selected topology could not be cut safely.")
.as_ref(),
);
return Task::none();
}
};
let staged_result = if result.faces.is_empty() {
None
} else {
let retained = group.bases[0];
let Some(source) = self.tabs[i].scene.document.get_entity(retained).cloned() else {
return Task::none();
};
let Some(entity) = entity_with_subtract_body(source, &result) else {
self.command_line.push_error(
crate::t!("The SUBTRACT result could not be encoded as ACIS.").as_ref(),
);
return Task::none();
};
let Some(display) = self.tabs[i]
.scene
.prepare_solid_model_display(retained, &result)
.filter(|display| display.0.complete)
else {
self.command_line.push_error(
crate::t!("The SUBTRACT result could not be displayed completely. The original objects were retained.")
.as_ref(),
);
return Task::none();
};
Some((retained, entity, result, display))
};
prepared.push(PreparedSubtract {
bases: group.bases,
cutters: group.cutters,
result: staged_result,
});
}
let record_history = self.tabs[i].scene.document.header.record_solid_history;
self.push_undo_snapshot(i, "SUBTRACT");
self.tabs[i].scene.erase_entities(&operands);
let mut entity = Solid3D::new();
entity.wires = solid_model::edge_wires(&result);
let history = solid_history::brep_op(&result);
let handle = self.add_solid_model(EntityType::Solid3D(entity), result, history);
let mut retained = Vec::new();
let mut consumed = Vec::new();
for group in prepared {
if let Some((handle, entity, body, display)) = group.result {
self.tabs[i].scene.delete_solid_history(handle);
if !self.tabs[i].scene.update_entity(entity) {
self.command_line.push_error(
crate::t!("SUBTRACT: the retained base object could not be updated.").as_ref(),
);
return Task::none();
}
if record_history
&& matches!(
self.tabs[i].scene.document.get_entity(handle),
Some(EntityType::Solid3D(_))
)
{
let history = solid_history::brep_op(&body);
self.tabs[i].scene.create_solid_history(handle, history);
}
self.tabs[i]
.scene
.register_prepared_solid_model(handle, body, display);
retained.push(handle);
consumed.extend(group.bases.into_iter().skip(1));
} else {
consumed.extend(group.bases);
}
consumed.extend(group.cutters);
}
self.tabs[i].scene.erase_entities(&consumed);
self.tabs[i].scene.deselect_all();
if !handle.is_null() {
self.tabs[i].scene.select_entity(handle, false);
for handle in &retained {
self.tabs[i].scene.select_entity(*handle, false);
}
self.tabs[i].dirty = true;
self.refresh_properties();
self.command_line.push_output(
crate::tf!(
"SUBTRACT: created %{count} result object(s).",
count = retained.len()
)
.as_ref(),
);
Task::none()
}
+1
View File
@@ -1557,6 +1557,7 @@ pub enum CmdResult {
SolidSubtract {
bases: Vec<Handle>,
cutters: Vec<Handle>,
convert_meshes: bool,
},
/// INSERT landed on a block that has AttributeDefinitions.
/// The host should look up the attdefs for `block_name` from the document
+61
View File
@@ -991,6 +991,67 @@ fn display_mesh(mesh: &Mesh) -> RefinedMesh {
refined
}
/// Convert a closed indexed mesh entity to an exact planar-faced B-rep.
/// Open, non-manifold, non-planar, and degenerate meshes return `None` so a
/// modelling command can leave the source untouched.
pub(crate) fn closed_mesh_body(entity: &acadrust::EntityType) -> Option<cadkernel::brep::Body> {
match entity {
acadrust::EntityType::Mesh(mesh) => {
let base = base_refined_mesh(mesh);
cadkernel::brep::make::faceted_solid(&base.vertices, &base.faces)
}
acadrust::EntityType::PolygonMesh(mesh) => {
if !mesh.is_closed_m() || !mesh.is_closed_n() {
return None;
}
let m = mesh.m_vertex_count.max(0) as usize;
let n = mesh.n_vertex_count.max(0) as usize;
let vertices = mesh
.vertices
.iter()
.take(m.saturating_mul(n))
.map(|vertex| v3(&vertex.location))
.collect::<Vec<_>>();
if m < 2 || n < 2 || vertices.len() != m.saturating_mul(n) {
return None;
}
let mut faces = Vec::with_capacity(m.saturating_mul(n));
for row in 0..m {
for column in 0..n {
faces.push(vec![
row * n + column,
((row + 1) % m) * n + column,
((row + 1) % m) * n + (column + 1) % n,
row * n + (column + 1) % n,
]);
}
}
cadkernel::brep::make::faceted_solid(&vertices, &faces)
}
acadrust::EntityType::PolyfaceMesh(mesh) => {
let vertices = mesh
.vertices
.iter()
.map(|vertex| v3(&vertex.location))
.collect::<Vec<_>>();
let faces = mesh
.faces
.iter()
.filter_map(|face| {
let indices = [face.index1, face.index2, face.index3, face.index4]
.into_iter()
.filter(|index| *index != 0)
.map(|index| (index.unsigned_abs() as usize).checked_sub(1))
.collect::<Option<Vec<_>>>()?;
(indices.len() >= 3).then_some(indices)
})
.collect::<Vec<_>>();
cadkernel::brep::make::faceted_solid(&vertices, &faces)
}
_ => None,
}
}
fn face_triangle_indices(
vertices: &[[f64; 3]],
faces: &[Vec<usize>],
+80 -9
View File
@@ -25,16 +25,20 @@ impl BoolOp {
enum SubtractStep {
Bases,
Cutters,
ConvertMeshes,
}
pub struct SubtractCommand {
step: SubtractStep,
bases: Vec<Handle>,
cutters: Vec<Handle>,
selected: Vec<Handle>,
bases_have_mesh: bool,
selected_has_mesh: bool,
}
impl SubtractCommand {
pub fn new(bases: Vec<Handle>) -> Self {
pub fn new(bases: Vec<Handle>, bases_have_mesh: bool) -> Self {
let step = if bases.is_empty() {
SubtractStep::Bases
} else {
@@ -43,7 +47,18 @@ impl SubtractCommand {
Self {
step,
bases,
cutters: Vec::new(),
selected: Vec::new(),
bases_have_mesh,
selected_has_mesh: false,
}
}
fn finish(&mut self, convert_meshes: bool) -> CmdResult {
CmdResult::SolidSubtract {
bases: std::mem::take(&mut self.bases),
cutters: std::mem::take(&mut self.cutters),
convert_meshes,
}
}
}
@@ -55,11 +70,26 @@ impl CadCommand for SubtractCommand {
fn prompt(&self) -> String {
match self.step {
SubtractStep::Bases => crate::t!("SUBTRACT Select base solids, then press Enter:")
SubtractStep::Bases => crate::t!("SUBTRACT Select base Solids, Regions, Surfaces, or Meshes, then press Enter:")
.into_owned(),
SubtractStep::Cutters => {
crate::t!("SUBTRACT Select solids to subtract, then press Enter:").into_owned()
crate::t!("SUBTRACT Select Solids, Regions, Surfaces, or Meshes to subtract, then press Enter:").into_owned()
}
SubtractStep::ConvertMeshes => crate::t!(
"SUBTRACT Convert selected closed Mesh objects to solids? [Yes/No] <Yes>:"
)
.into_owned(),
}
}
fn options(&self) -> Vec<crate::command::CmdOption> {
if matches!(self.step, SubtractStep::ConvertMeshes) {
vec![
crate::command::CmdOption::new(crate::t!("Yes").as_ref(), "Y"),
crate::command::CmdOption::new(crate::t!("No").as_ref(), "N"),
]
} else {
Vec::new()
}
}
@@ -68,31 +98,72 @@ impl CadCommand for SubtractCommand {
}
fn on_enter(&mut self) -> CmdResult {
if matches!(self.step, SubtractStep::ConvertMeshes) {
return self.finish(true);
}
if self.selected.is_empty() {
return CmdResult::NeedPoint;
}
match self.step {
SubtractStep::Bases => {
self.bases = std::mem::take(&mut self.selected);
self.bases_have_mesh = self.selected_has_mesh;
self.selected_has_mesh = false;
self.step = SubtractStep::Cutters;
CmdResult::DeselectAndContinue
}
SubtractStep::Cutters => CmdResult::SolidSubtract {
bases: std::mem::take(&mut self.bases),
cutters: std::mem::take(&mut self.selected),
},
SubtractStep::Cutters => {
self.cutters = std::mem::take(&mut self.selected);
if self.bases_have_mesh || self.selected_has_mesh {
self.step = SubtractStep::ConvertMeshes;
CmdResult::NeedPoint
} else {
self.finish(false)
}
}
SubtractStep::ConvertMeshes => unreachable!(),
}
}
fn wants_text_input(&self) -> bool {
matches!(self.step, SubtractStep::ConvertMeshes)
}
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
if !matches!(self.step, SubtractStep::ConvertMeshes) {
return None;
}
Some(match text.trim().to_ascii_lowercase().as_str() {
"" | "y" | "yes" => self.finish(true),
"n" | "no" => self.finish(false),
_ => CmdResult::NeedPoint,
})
}
fn is_selection_gathering(&self) -> bool {
true
!matches!(self.step, SubtractStep::ConvertMeshes)
}
fn inject_selection_entities(&mut self, entities: Vec<SelectionEntity>) {
self.selected_has_mesh = entities.iter().any(|entity| {
matches!(
entity.entity,
EntityType::Mesh(_) | EntityType::PolygonMesh(_) | EntityType::PolyfaceMesh(_)
)
});
self.selected = entities
.into_iter()
.filter_map(|entity| {
matches!(entity.entity, EntityType::Solid3D(_)).then_some(entity.handle)
matches!(
entity.entity,
EntityType::Solid3D(_)
| EntityType::Region(_)
| EntityType::Surface(_)
| EntityType::Mesh(_)
| EntityType::PolygonMesh(_)
| EntityType::PolyfaceMesh(_)
)
.then_some(entity.handle)
})
.collect();
}
+19 -7
View File
@@ -222,11 +222,9 @@ pub fn has_compact_solid_properties(
document: &acadrust::CadDocument,
handle: acadrust::Handle,
) -> bool {
has_specialized_primitive_properties(document, handle)
|| matches!(
document.solid_history_operation(handle),
Some(SolidHistoryOperation::Brep(_))
)
document
.get_entity(handle)
.is_some_and(|entity| matches!(entity, acadrust::EntityType::Solid3D(_)))
}
pub fn reference_point(operation: &SolidHistoryOperation) -> Option<glam::DVec3> {
@@ -1441,7 +1439,21 @@ pub fn primitive_properties(
handle: acadrust::Handle,
) -> Vec<PropSection> {
let Some(operation) = document.solid_history_operation(handle) else {
return Vec::new();
return vec![PropSection {
title: t!("Solid History").into_owned(),
props: vec![
Property {
label: t!("History").into_owned(),
field: PROP_HISTORY,
value: PropValue::ReadOnly("None".to_string()),
},
Property {
label: t!("Show History").into_owned(),
field: PROP_SHOW_HISTORY,
value: PropValue::ReadOnly("No".to_string()),
},
],
}];
};
match operation {
SolidHistoryOperation::Box(value) => {
@@ -1462,7 +1474,7 @@ pub fn primitive_properties(
}
SolidHistoryOperation::Revolve(value) => revolve_properties(document, handle, value),
SolidHistoryOperation::Brep(_) => brep_properties(document, handle),
_ => Vec::new(),
_ => brep_properties(document, handle),
}
}