forked from 1b-agent/opencadstudio-upstream
Merge PR #1045: complete UNION behavior
This commit is contained in:
commit
050fbb0a6b
11 files changed
+528
-85
No files matched your search
Generated
+1
-1
@@ -882,7 +882,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=6a5e6fc2822f6af6b2611cd605b44437b35158f0#6a5e6fc2822f6af6b2611cd605b44437b35158f0"
|
source = "git+https://github.com/ramox81/cadkernel.git?rev=328b55a9a45bc2118526a49156bcb94e46f10444#328b55a9a45bc2118526a49156bcb94e46f10444"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"acadrust",
|
"acadrust",
|
||||||
"cavalier_contours",
|
"cavalier_contours",
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@ rfd = "0.17"
|
|||||||
clap = { version = "4", features = ["derive", "string"] }
|
clap = { version = "4", features = ["derive", "string"] }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "5b56571a190e7a17c8f12d36390d2938b0fb72f7", features = ["serde"] }
|
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 = "328b55a9a45bc2118526a49156bcb94e46f10444", 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"] }
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ thiserror = "1"
|
|||||||
|
|
||||||
# Host adapters convert ObjectId to acadrust handles; kernel handles stay local.
|
# 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 }
|
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 = "328b55a9a45bc2118526a49156bcb94e46f10444", features = ["acis", "offset"], optional = true }
|
||||||
ocs_plugin_api = { path = "../ocs_plugin_api", optional = true, features = ["host"] }
|
ocs_plugin_api = { path = "../ocs_plugin_api", optional = true, features = ["host"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|||||||
+65
-44
@@ -972,32 +972,46 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Solid booleans ─────────────────────────────────────────────
|
// ── Solid booleans ─────────────────────────────────────────────
|
||||||
"UNION" | "INTERSECT" => {
|
"UNION" => {
|
||||||
use crate::modules::model::boolean_cmd::BoolOp;
|
use crate::modules::model::boolean_cmd::BoolOp;
|
||||||
if let Some(op) = BoolOp::from_id(cmd) {
|
if self.union_ready() {
|
||||||
let solid_count = {
|
return Some(self.solid_boolean(BoolOp::Union));
|
||||||
let scene = &self.tabs[i].scene;
|
}
|
||||||
scene
|
use crate::modules::draw::select::SelectObjectsCommand;
|
||||||
.selected_handles_in_order()
|
let selection = SelectObjectsCommand::plain("UNION", "UNIONAPPLY");
|
||||||
.into_iter()
|
self.command_line.push_info(&selection.prompt());
|
||||||
.filter(|handle| !scene.is_layer_locked(*handle))
|
self.tabs[i].active_cmd = Some(Box::new(selection));
|
||||||
.filter(|handle| {
|
}
|
||||||
matches!(
|
|
||||||
scene.document.get_entity(*handle),
|
"UNIONAPPLY" => {
|
||||||
Some(acadrust::EntityType::Solid3D(_))
|
use crate::modules::model::boolean_cmd::BoolOp;
|
||||||
)
|
return Some(self.solid_boolean(BoolOp::Union));
|
||||||
})
|
}
|
||||||
.take(2)
|
|
||||||
.count()
|
"INTERSECT" => {
|
||||||
};
|
use crate::modules::model::boolean_cmd::BoolOp;
|
||||||
if solid_count < 2 {
|
let solid_count = {
|
||||||
use crate::modules::draw::select::SelectObjectsCommand;
|
let scene = &self.tabs[i].scene;
|
||||||
let selection = SelectObjectsCommand::new(cmd);
|
scene
|
||||||
self.command_line.push_info(&selection.prompt());
|
.selected_handles_in_order()
|
||||||
self.tabs[i].active_cmd = Some(Box::new(selection));
|
.into_iter()
|
||||||
} else {
|
.filter(|handle| !scene.is_layer_locked(*handle))
|
||||||
return Some(self.solid_boolean(op));
|
.filter(|handle| {
|
||||||
}
|
matches!(
|
||||||
|
scene.document.get_entity(*handle),
|
||||||
|
Some(acadrust::EntityType::Solid3D(_))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.take(2)
|
||||||
|
.count()
|
||||||
|
};
|
||||||
|
if solid_count < 2 {
|
||||||
|
use crate::modules::draw::select::SelectObjectsCommand;
|
||||||
|
let selection = SelectObjectsCommand::new(cmd);
|
||||||
|
self.command_line.push_info(&selection.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(selection));
|
||||||
|
} else {
|
||||||
|
return Some(self.solid_boolean(BoolOp::Intersect));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1069,9 +1083,9 @@ impl OpenCADStudio {
|
|||||||
// REGION — convert selected closed boundaries (closed polylines /
|
// REGION — convert selected closed boundaries (closed polylines /
|
||||||
// circles) into Region entities (one wire loop each).
|
// circles) into Region entities (one wire loop each).
|
||||||
"REGION" | "REG" => {
|
"REGION" | "REG" => {
|
||||||
use acadrust::entities::{Region, Wire};
|
use acadrust::entities::Region;
|
||||||
use acadrust::types::Vector3;
|
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() {
|
for (_, e) in self.tabs[i].scene.selected_entities().iter() {
|
||||||
let supported = matches!(
|
let supported = matches!(
|
||||||
e,
|
e,
|
||||||
@@ -1079,32 +1093,39 @@ impl OpenCADStudio {
|
|||||||
if pl.is_closed && pl.vertices.len() >= 3
|
if pl.is_closed && pl.vertices.len() >= 3
|
||||||
) || matches!(e, acadrust::EntityType::Circle(_));
|
) || matches!(e, acadrust::EntityType::Circle(_));
|
||||||
if supported {
|
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;
|
continue;
|
||||||
};
|
};
|
||||||
loops.push(
|
let Some(body) = cadkernel::brep::planar_region(plane, &loops) else {
|
||||||
crate::entities::curve::curve_points(&curve)
|
continue;
|
||||||
.into_iter()
|
};
|
||||||
.map(|point| Vector3::new(point[0], point[1], point[2]))
|
let mut region = Region::new();
|
||||||
.collect(),
|
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
|
self.command_line
|
||||||
.push_error(crate::t!("REGION: select closed polylines or circles.").as_ref());
|
.push_error(crate::t!("REGION: select closed polylines or circles.").as_ref());
|
||||||
} else {
|
} else {
|
||||||
self.push_undo_snapshot(i, "REGION");
|
self.push_undo_snapshot(i, "REGION");
|
||||||
let count = loops.len();
|
let count = regions.len();
|
||||||
for pts in loops {
|
let mut created = Vec::with_capacity(count);
|
||||||
let mut w = Wire::new();
|
for (region, body) in regions {
|
||||||
let first = pts.first().copied().unwrap_or(Vector3::new(0.0, 0.0, 0.0));
|
let handle = self.add_region_model(region, body);
|
||||||
w.points = pts;
|
if handle.is_null() {
|
||||||
let mut r = Region::new();
|
self.tabs[i].scene.rollback_new_entities(&created);
|
||||||
r.point_of_reference = first;
|
self.discard_last_undo_entry(i);
|
||||||
r.wires = vec![w];
|
return Some(iced::Task::none());
|
||||||
r.common.layer = self.tabs[i].active_layer.clone();
|
}
|
||||||
self.tabs[i].scene.add_entity(acadrust::EntityType::Region(r));
|
created.push(handle);
|
||||||
}
|
}
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
self.command_line
|
self.command_line
|
||||||
|
|||||||
+327
-5
@@ -1,7 +1,9 @@
|
|||||||
// Kernel B-rep solid modelling and exact ACIS persistence.
|
// Kernel B-rep solid modelling and exact ACIS persistence.
|
||||||
|
|
||||||
use acadrust::{
|
use acadrust::{
|
||||||
entities::Solid3D, objects::SolidHistoryOperation, EntityType, Handle,
|
entities::{EntityCommon, Region, Solid3D, Surface, SurfaceKind},
|
||||||
|
objects::SolidHistoryOperation,
|
||||||
|
EntityType, Handle,
|
||||||
};
|
};
|
||||||
use cadkernel::brep::Body;
|
use cadkernel::brep::Body;
|
||||||
use iced::Task;
|
use iced::Task;
|
||||||
@@ -11,19 +13,112 @@ use crate::modules::model::boolean_cmd::BoolOp;
|
|||||||
use crate::scene::model::solid_history;
|
use crate::scene::model::solid_history;
|
||||||
use crate::scene::model::solid_model::{self, Bool};
|
use crate::scene::model::solid_model::{self, Bool};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum UnionEntityKind {
|
||||||
|
Solid,
|
||||||
|
Region,
|
||||||
|
Surface,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnionEntityKind {
|
||||||
|
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 UnionGroup {
|
||||||
|
kind: UnionEntityKind,
|
||||||
|
handles: Vec<Handle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PreparedUnion {
|
||||||
|
kind: UnionEntityKind,
|
||||||
|
handles: Vec<Handle>,
|
||||||
|
body: Body,
|
||||||
|
common: EntityCommon,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inherited_common(source: &EntityCommon) -> EntityCommon {
|
||||||
|
let mut result = EntityCommon::new();
|
||||||
|
result.layer = source.layer.clone();
|
||||||
|
result.color = source.color;
|
||||||
|
result.line_weight = source.line_weight;
|
||||||
|
result.linetype = source.linetype.clone();
|
||||||
|
result.linetype_handle = source.linetype_handle;
|
||||||
|
result.linetype_scale = source.linetype_scale;
|
||||||
|
result.transparency = source.transparency;
|
||||||
|
result.color_name = source.color_name.clone();
|
||||||
|
result.invisible = source.invisible;
|
||||||
|
result.color_book_handle = source.color_book_handle;
|
||||||
|
result.full_visual_style_handle = source.full_visual_style_handle;
|
||||||
|
result.face_visual_style_handle = source.face_visual_style_handle;
|
||||||
|
result.edge_visual_style_handle = source.edge_visual_style_handle;
|
||||||
|
result.material_flags = source.material_flags;
|
||||||
|
result.material_handle = source.material_handle;
|
||||||
|
result.shadow_flags = source.shadow_flags;
|
||||||
|
result.plotstyle_flags = source.plotstyle_flags;
|
||||||
|
result.plotstyle_handle = source.plotstyle_handle;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn union_bodies(
|
||||||
|
kind: UnionEntityKind,
|
||||||
|
bodies: Vec<Body>,
|
||||||
|
) -> Result<Body, cadkernel::brep::Snag> {
|
||||||
|
if kind == UnionEntityKind::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::Union, &result, &operand)?;
|
||||||
|
}
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
let references = bodies.iter().collect::<Vec<_>>();
|
||||||
|
let tolerance = cadkernel::brep::operation_tolerance(&references);
|
||||||
|
cadkernel::brep::union_planar_regions(&bodies, tolerance)
|
||||||
|
}
|
||||||
|
|
||||||
impl super::OpenCADStudio {
|
impl super::OpenCADStudio {
|
||||||
/// Add a solid and register its persistent B-rep.
|
/// Add a solid and register its persistent B-rep.
|
||||||
pub(super) fn add_solid_model(
|
pub(super) fn add_solid_model(
|
||||||
|
&mut self,
|
||||||
|
entity: EntityType,
|
||||||
|
solid: Body,
|
||||||
|
history: SolidHistoryOperation,
|
||||||
|
) -> Handle {
|
||||||
|
self.add_solid_model_inner(entity, solid, history, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_solid_model_preserving_style(
|
||||||
|
&mut self,
|
||||||
|
entity: EntityType,
|
||||||
|
solid: Body,
|
||||||
|
history: SolidHistoryOperation,
|
||||||
|
) -> Handle {
|
||||||
|
self.add_solid_model_inner(entity, solid, history, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_solid_model_inner(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut entity: EntityType,
|
mut entity: EntityType,
|
||||||
solid: Body,
|
solid: Body,
|
||||||
history: SolidHistoryOperation,
|
history: SolidHistoryOperation,
|
||||||
|
apply_creation_style: bool,
|
||||||
) -> Handle {
|
) -> Handle {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let EntityType::Solid3D(inner) = &mut entity else {
|
let EntityType::Solid3D(inner) = &mut entity else {
|
||||||
return Handle::NULL;
|
return Handle::NULL;
|
||||||
};
|
};
|
||||||
inner.common.plotstyle_flags = 2;
|
if apply_creation_style {
|
||||||
|
inner.common.plotstyle_flags = 2;
|
||||||
|
}
|
||||||
inner.wires = solid_model::edge_wires(&solid);
|
inner.wires = solid_model::edge_wires(&solid);
|
||||||
let Some(document) = crate::scene::convert::acis_export::solid_to_sat(&solid)
|
let Some(document) = crate::scene::convert::acis_export::solid_to_sat(&solid)
|
||||||
else {
|
else {
|
||||||
@@ -32,7 +127,12 @@ impl super::OpenCADStudio {
|
|||||||
return Handle::NULL;
|
return Handle::NULL;
|
||||||
};
|
};
|
||||||
inner.set_sat_document(&document);
|
inner.set_sat_document(&document);
|
||||||
let Some(handle) = self.commit_entity_handle(entity) else {
|
let handle = if apply_creation_style {
|
||||||
|
self.commit_entity_handle(entity)
|
||||||
|
} else {
|
||||||
|
self.commit_entity_handle_preserve_style(entity)
|
||||||
|
};
|
||||||
|
let Some(handle) = handle else {
|
||||||
return Handle::NULL;
|
return Handle::NULL;
|
||||||
};
|
};
|
||||||
let require_complete = matches!(history, SolidHistoryOperation::Loft(_));
|
let require_complete = matches!(history, SolidHistoryOperation::Loft(_));
|
||||||
@@ -51,15 +151,34 @@ impl super::OpenCADStudio {
|
|||||||
/// Add an open sheet body as a persistent Surface entity and register its
|
/// Add an open sheet body as a persistent Surface entity and register its
|
||||||
/// exact B-rep for shaded and wireframe display.
|
/// exact B-rep for shaded and wireframe display.
|
||||||
pub(super) fn add_surface_model(
|
pub(super) fn add_surface_model(
|
||||||
|
&mut self,
|
||||||
|
entity: EntityType,
|
||||||
|
surface: Body,
|
||||||
|
) -> Handle {
|
||||||
|
self.add_surface_model_inner(entity, surface, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_surface_model_preserving_style(
|
||||||
|
&mut self,
|
||||||
|
entity: EntityType,
|
||||||
|
surface: Body,
|
||||||
|
) -> Handle {
|
||||||
|
self.add_surface_model_inner(entity, surface, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_surface_model_inner(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut entity: EntityType,
|
mut entity: EntityType,
|
||||||
surface: Body,
|
surface: Body,
|
||||||
|
apply_creation_style: bool,
|
||||||
) -> Handle {
|
) -> Handle {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let EntityType::Surface(inner) = &mut entity else {
|
let EntityType::Surface(inner) = &mut entity else {
|
||||||
return Handle::NULL;
|
return Handle::NULL;
|
||||||
};
|
};
|
||||||
inner.common.plotstyle_flags = 2;
|
if apply_creation_style {
|
||||||
|
inner.common.plotstyle_flags = 2;
|
||||||
|
}
|
||||||
inner.wires = solid_model::edge_wires(&surface);
|
inner.wires = solid_model::edge_wires(&surface);
|
||||||
let Some(document) = crate::scene::convert::acis_export::solid_to_sat(&surface)
|
let Some(document) = crate::scene::convert::acis_export::solid_to_sat(&surface)
|
||||||
else {
|
else {
|
||||||
@@ -68,7 +187,12 @@ impl super::OpenCADStudio {
|
|||||||
return Handle::NULL;
|
return Handle::NULL;
|
||||||
};
|
};
|
||||||
inner.acis_data = acadrust::entities::AcisData::from_sat(&document.to_sat_string());
|
inner.acis_data = acadrust::entities::AcisData::from_sat(&document.to_sat_string());
|
||||||
let Some(handle) = self.commit_entity_handle(entity) else {
|
let handle = if apply_creation_style {
|
||||||
|
self.commit_entity_handle(entity)
|
||||||
|
} else {
|
||||||
|
self.commit_entity_handle_preserve_style(entity)
|
||||||
|
};
|
||||||
|
let Some(handle) = handle else {
|
||||||
return Handle::NULL;
|
return Handle::NULL;
|
||||||
};
|
};
|
||||||
let require_complete = self.tabs[i].scene.document.get_entity(handle).is_some_and(|entity|
|
let require_complete = self.tabs[i].scene.document.get_entity(handle).is_some_and(|entity|
|
||||||
@@ -81,6 +205,44 @@ impl super::OpenCADStudio {
|
|||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn add_region_model(&mut self, region: Region, body: Body) -> Handle {
|
||||||
|
self.add_region_model_inner(region, body, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_region_model_preserving_style(&mut self, region: Region, body: Body) -> Handle {
|
||||||
|
self.add_region_model_inner(region, body, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_region_model_inner(
|
||||||
|
&mut self,
|
||||||
|
mut region: Region,
|
||||||
|
body: Body,
|
||||||
|
preserve_style: bool,
|
||||||
|
) -> 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 entity = EntityType::Region(region);
|
||||||
|
let handle = if preserve_style {
|
||||||
|
self.commit_entity_handle_preserve_style(entity)
|
||||||
|
} else {
|
||||||
|
self.commit_entity_handle(entity)
|
||||||
|
};
|
||||||
|
let Some(handle) = handle 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> {
|
fn selected_solid_handles(&mut self) -> Vec<Handle> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let mut handles: Vec<Handle> = self.tabs[i]
|
let mut handles: Vec<Handle> = self.tabs[i]
|
||||||
@@ -100,6 +262,38 @@ impl super::OpenCADStudio {
|
|||||||
handles
|
handles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn selected_union_groups(&self) -> Vec<UnionGroup> {
|
||||||
|
let scene = &self.tabs[self.active_tab].scene;
|
||||||
|
let mut groups: Vec<UnionGroup> = Vec::new();
|
||||||
|
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(UnionEntityKind::from_entity)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Some(group) = groups.iter_mut().find(|group| group.kind == kind) {
|
||||||
|
group.handles.push(handle);
|
||||||
|
} else {
|
||||||
|
groups.push(UnionGroup {
|
||||||
|
kind,
|
||||||
|
handles: vec![handle],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groups
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn union_ready(&self) -> bool {
|
||||||
|
self.selected_union_groups()
|
||||||
|
.iter()
|
||||||
|
.any(|group| group.handles.len() >= 2)
|
||||||
|
}
|
||||||
|
|
||||||
fn replace_solid_body(&mut self, handle: Handle, result: Body, label: &str) -> bool {
|
fn replace_solid_body(&mut self, handle: Handle, result: Body, label: &str) -> bool {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let Some(document) =
|
let Some(document) =
|
||||||
@@ -197,6 +391,9 @@ impl super::OpenCADStudio {
|
|||||||
|
|
||||||
/// Run a boolean over the selected solids in selection order.
|
/// Run a boolean over the selected solids in selection order.
|
||||||
pub(super) fn solid_boolean(&mut self, op: BoolOp) -> Task<Message> {
|
pub(super) fn solid_boolean(&mut self, op: BoolOp) -> Task<Message> {
|
||||||
|
if op == BoolOp::Union {
|
||||||
|
return self.union_selected_entities();
|
||||||
|
}
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles = self.selected_solid_handles();
|
let handles = self.selected_solid_handles();
|
||||||
if handles.len() < 2 {
|
if handles.len() < 2 {
|
||||||
@@ -243,6 +440,131 @@ impl super::OpenCADStudio {
|
|||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn union_selected_entities(&mut self) -> Task<Message> {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let mut groups = self.selected_union_groups();
|
||||||
|
let handles = groups
|
||||||
|
.iter()
|
||||||
|
.flat_map(|group| group.handles.iter().copied())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
self.tabs[i].scene.restore_solid_models(&handles);
|
||||||
|
for group in &mut groups {
|
||||||
|
group
|
||||||
|
.handles
|
||||||
|
.retain(|handle| self.tabs[i].scene.solid_models.contains_key(handle));
|
||||||
|
}
|
||||||
|
groups.retain(|group| group.handles.len() >= 2);
|
||||||
|
if groups.is_empty() {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::t!("UNION: select at least two solids, Regions, or planar Surfaces of the same type.")
|
||||||
|
.as_ref(),
|
||||||
|
);
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut prepared = Vec::with_capacity(groups.len());
|
||||||
|
for group in groups {
|
||||||
|
let bodies = group
|
||||||
|
.handles
|
||||||
|
.iter()
|
||||||
|
.filter_map(|handle| self.tabs[i].scene.solid_models.get(handle).cloned())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let result = match union_bodies(group.kind, bodies) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(cadkernel::brep::Snag::NoClosedForm) => {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::t!("UNION: 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!("UNION: the selected coincident geometry is ambiguous.").as_ref(),
|
||||||
|
);
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
Err(cadkernel::brep::Snag::CutRefused) => {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::t!("UNION: the selected topology could not be closed safely.").as_ref(),
|
||||||
|
);
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if crate::scene::convert::acis_export::solid_to_sat(&result).is_none() {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::t!("The UNION result could not be encoded as ACIS.").as_ref(),
|
||||||
|
);
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
let Some(source) = group
|
||||||
|
.handles
|
||||||
|
.first()
|
||||||
|
.and_then(|handle| self.tabs[i].scene.document.get_entity(*handle))
|
||||||
|
else {
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
prepared.push(PreparedUnion {
|
||||||
|
kind: group.kind,
|
||||||
|
handles: group.handles,
|
||||||
|
body: result,
|
||||||
|
common: inherited_common(source.common()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let consumed = prepared
|
||||||
|
.iter()
|
||||||
|
.flat_map(|group| group.handles.iter().copied())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
self.push_undo_snapshot(i, "UNION");
|
||||||
|
let mut created = Vec::with_capacity(prepared.len());
|
||||||
|
for group in prepared {
|
||||||
|
let handle = match group.kind {
|
||||||
|
UnionEntityKind::Solid => {
|
||||||
|
let history = solid_history::brep_op(&group.body);
|
||||||
|
let mut entity = Solid3D::new();
|
||||||
|
entity.common = group.common;
|
||||||
|
self.add_solid_model_preserving_style(
|
||||||
|
EntityType::Solid3D(entity),
|
||||||
|
group.body,
|
||||||
|
history,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
UnionEntityKind::Region => {
|
||||||
|
let mut entity = Region::new();
|
||||||
|
entity.common = group.common;
|
||||||
|
self.add_region_model_preserving_style(entity, group.body)
|
||||||
|
}
|
||||||
|
UnionEntityKind::Surface => {
|
||||||
|
let mut entity = Surface::new(SurfaceKind::Generic);
|
||||||
|
entity.common = group.common;
|
||||||
|
self.add_surface_model_preserving_style(
|
||||||
|
EntityType::Surface(entity),
|
||||||
|
group.body,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if handle.is_null() {
|
||||||
|
self.tabs[i].scene.rollback_new_entities(&created);
|
||||||
|
self.discard_last_undo_entry(i);
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
created.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tabs[i].scene.erase_entities(&consumed);
|
||||||
|
self.tabs[i].scene.deselect_all();
|
||||||
|
for handle in &created {
|
||||||
|
self.tabs[i].scene.select_entity(*handle, false);
|
||||||
|
}
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.refresh_properties();
|
||||||
|
self.command_line.push_output(
|
||||||
|
crate::tf!("UNION: created %{count} result object(s).", count = created.len()).as_ref(),
|
||||||
|
);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn solid_subtract(
|
pub(super) fn solid_subtract(
|
||||||
&mut self,
|
&mut self,
|
||||||
bases: &[Handle],
|
bases: &[Handle],
|
||||||
|
|||||||
+45
-29
@@ -440,15 +440,15 @@ impl OpenCADStudio {
|
|||||||
let display_entity = dispatch::entity_in_working_plane(contextual.as_ref(), plane);
|
let display_entity = dispatch::entity_in_working_plane(contextual.as_ref(), plane);
|
||||||
let entity = &display_entity;
|
let entity = &display_entity;
|
||||||
let group_names = self.tabs[i].scene.group_names_for_entity(handle);
|
let group_names = self.tabs[i].scene.group_names_for_entity(handle);
|
||||||
let specialized_primitive =
|
let compact_solid =
|
||||||
crate::scene::model::solid_history::has_specialized_primitive_properties(
|
crate::scene::model::solid_history::has_compact_solid_properties(
|
||||||
&self.tabs[i].scene.document,
|
&self.tabs[i].scene.document,
|
||||||
handle,
|
handle,
|
||||||
);
|
);
|
||||||
let mut sections =
|
let mut sections =
|
||||||
dispatch::properties_sectioned(handle, entity, &text_style_names);
|
dispatch::properties_sectioned(handle, entity, &text_style_names);
|
||||||
if specialized_primitive {
|
if compact_solid {
|
||||||
retain_specialized_primitive_sections(&mut sections);
|
retain_compact_solid_sections(&mut sections);
|
||||||
}
|
}
|
||||||
sections.extend(
|
sections.extend(
|
||||||
crate::scene::model::solid_history::primitive_properties(
|
crate::scene::model::solid_history::primitive_properties(
|
||||||
@@ -636,7 +636,7 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !specialized_primitive && matches!(
|
if !compact_solid && matches!(
|
||||||
entity,
|
entity,
|
||||||
acadrust::EntityType::Solid3D(_)
|
acadrust::EntityType::Solid3D(_)
|
||||||
| acadrust::EntityType::Region(_)
|
| acadrust::EntityType::Region(_)
|
||||||
@@ -710,7 +710,7 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !specialized_primitive {
|
if !compact_solid {
|
||||||
sections.extend(crate::entities::object_data::sections(
|
sections.extend(crate::entities::object_data::sections(
|
||||||
&self.tabs[i].scene.document,
|
&self.tabs[i].scene.document,
|
||||||
&self.tabs[i].scene.object_data_cache,
|
&self.tabs[i].scene.object_data_cache,
|
||||||
@@ -2068,8 +2068,8 @@ impl OpenCADStudio {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if specialized_primitive {
|
if compact_solid {
|
||||||
retain_specialized_primitive_sections(&mut sections);
|
retain_compact_solid_sections(&mut sections);
|
||||||
}
|
}
|
||||||
let title = match entity {
|
let title = match entity {
|
||||||
acadrust::EntityType::Insert(ins) => {
|
acadrust::EntityType::Insert(ins) => {
|
||||||
@@ -2166,7 +2166,7 @@ impl OpenCADStudio {
|
|||||||
.collect();
|
.collect();
|
||||||
let mut sections = aggregate_sections(&local_refs, &text_style_names);
|
let mut sections = aggregate_sections(&local_refs, &text_style_names);
|
||||||
if local_refs.iter().all(|(handle, _)| {
|
if local_refs.iter().all(|(handle, _)| {
|
||||||
crate::scene::model::solid_history::has_specialized_primitive_properties(
|
crate::scene::model::solid_history::has_compact_solid_properties(
|
||||||
&self.tabs[i].scene.document,
|
&self.tabs[i].scene.document,
|
||||||
*handle,
|
*handle,
|
||||||
)
|
)
|
||||||
@@ -2591,7 +2591,7 @@ impl OpenCADStudio {
|
|||||||
&mut self,
|
&mut self,
|
||||||
entity: acadrust::EntityType,
|
entity: acadrust::EntityType,
|
||||||
) -> Option<Handle> {
|
) -> Option<Handle> {
|
||||||
self.commit_entity_handle_with_policies(entity, false, false)
|
self.commit_entity_handle_with_policies(entity, false, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn commit_entity_handle_with_dimension_policy(
|
pub(super) fn commit_entity_handle_with_dimension_policy(
|
||||||
@@ -2603,6 +2603,7 @@ impl OpenCADStudio {
|
|||||||
entity,
|
entity,
|
||||||
preserve_dimension_layer_and_style,
|
preserve_dimension_layer_and_style,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2610,7 +2611,14 @@ impl OpenCADStudio {
|
|||||||
&mut self,
|
&mut self,
|
||||||
entity: acadrust::EntityType,
|
entity: acadrust::EntityType,
|
||||||
) -> Option<Handle> {
|
) -> Option<Handle> {
|
||||||
self.commit_entity_handle_with_policies(entity, false, true)
|
self.commit_entity_handle_with_policies(entity, false, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn commit_entity_handle_preserve_style(
|
||||||
|
&mut self,
|
||||||
|
entity: acadrust::EntityType,
|
||||||
|
) -> Option<Handle> {
|
||||||
|
self.commit_entity_handle_with_policies(entity, false, true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit_entity_handle_with_policies(
|
fn commit_entity_handle_with_policies(
|
||||||
@@ -2618,6 +2626,7 @@ impl OpenCADStudio {
|
|||||||
mut entity: acadrust::EntityType,
|
mut entity: acadrust::EntityType,
|
||||||
preserve_dimension_layer_and_style: bool,
|
preserve_dimension_layer_and_style: bool,
|
||||||
preserve_entity_layer: bool,
|
preserve_entity_layer: bool,
|
||||||
|
preserve_entity_style: bool,
|
||||||
) -> Option<Handle> {
|
) -> Option<Handle> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let tracks_dimension_chain = matches!(
|
let tracks_dimension_chain = matches!(
|
||||||
@@ -2735,25 +2744,32 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::scene::view::dispatch::apply_color(&mut entity, self.ribbon.active_color);
|
if !preserve_entity_style {
|
||||||
crate::scene::view::dispatch::apply_common_prop(
|
crate::scene::view::dispatch::apply_color(&mut entity, self.ribbon.active_color);
|
||||||
&mut entity,
|
crate::scene::view::dispatch::apply_common_prop(
|
||||||
"linetype",
|
&mut entity,
|
||||||
&self.ribbon.active_linetype.clone(),
|
"linetype",
|
||||||
);
|
&self.ribbon.active_linetype.clone(),
|
||||||
crate::scene::view::dispatch::apply_line_weight(&mut entity, self.ribbon.active_lineweight);
|
);
|
||||||
// CELTSCALE (header.current_entity_linetype_scale): new entities
|
crate::scene::view::dispatch::apply_line_weight(
|
||||||
// pick up the document's saved per-entity linetype scale. The user
|
&mut entity,
|
||||||
// can override per entity later via the properties panel.
|
self.ribbon.active_lineweight,
|
||||||
let celtscale = self.tabs[i].scene.document.header.current_entity_linetype_scale;
|
);
|
||||||
if (celtscale - 1.0).abs() > 1e-9 && celtscale.abs() > 1e-9 {
|
// CELTSCALE (header.current_entity_linetype_scale): new entities
|
||||||
entity.common_mut().linetype_scale = celtscale;
|
// pick up the document's saved per-entity linetype scale. The user
|
||||||
|
// can override per entity later via the properties panel.
|
||||||
|
let celtscale = self.tabs[i].scene.document.header.current_entity_linetype_scale;
|
||||||
|
if (celtscale - 1.0).abs() > 1e-9 && celtscale.abs() > 1e-9 {
|
||||||
|
entity.common_mut().linetype_scale = celtscale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::scene::creation_style::apply_current_creation_styles(
|
if !preserve_entity_style {
|
||||||
&self.tabs[i].scene.document,
|
crate::scene::creation_style::apply_current_creation_styles(
|
||||||
&mut entity,
|
&self.tabs[i].scene.document,
|
||||||
);
|
&mut entity,
|
||||||
|
);
|
||||||
|
}
|
||||||
if let (Some((layer, style_name)), acadrust::EntityType::Dimension(dimension)) =
|
if let (Some((layer, style_name)), acadrust::EntityType::Dimension(dimension)) =
|
||||||
(inherited_dimension, &mut entity)
|
(inherited_dimension, &mut entity)
|
||||||
{
|
{
|
||||||
@@ -3061,7 +3077,7 @@ fn aggregate_solid_history_sections(
|
|||||||
merged
|
merged
|
||||||
}
|
}
|
||||||
|
|
||||||
fn retain_specialized_primitive_sections(
|
fn retain_compact_solid_sections(
|
||||||
sections: &mut Vec<crate::scene::model::object::PropSection>,
|
sections: &mut Vec<crate::scene::model::object::PropSection>,
|
||||||
) {
|
) {
|
||||||
sections.iter_mut().for_each(|section| {
|
sections.iter_mut().for_each(|section| {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use crate::modules::draw::fence::FencePick;
|
|||||||
use crate::scene::model::wire_model::WireModel;
|
use crate::scene::model::wire_model::WireModel;
|
||||||
|
|
||||||
pub struct SelectObjectsCommand {
|
pub struct SelectObjectsCommand {
|
||||||
|
prompt_cmd: String,
|
||||||
pending_cmd: String,
|
pending_cmd: String,
|
||||||
/// Selection accumulated so far (kept in sync with the scene selection by
|
/// Selection accumulated so far (kept in sync with the scene selection by
|
||||||
/// each `on_selection_complete` call). Applied on Enter when `commit_on_enter`.
|
/// each `on_selection_complete` call). Applied on Enter when `commit_on_enter`.
|
||||||
@@ -24,6 +25,8 @@ pub struct SelectObjectsCommand {
|
|||||||
/// When true, gathering continues until Enter / right-click commits the set.
|
/// When true, gathering continues until Enter / right-click commits the set.
|
||||||
/// When false, the first completed selection action fires immediately.
|
/// When false, the first completed selection action fires immediately.
|
||||||
commit_on_enter: bool,
|
commit_on_enter: bool,
|
||||||
|
/// Whether the generic selection methods are shown as command buttons.
|
||||||
|
show_options: bool,
|
||||||
/// A Fence / WPolygon / CPolygon path being picked point by point. While
|
/// A Fence / WPolygon / CPolygon path being picked point by point. While
|
||||||
/// one is under way the host must send clicks here as points rather than
|
/// one is under way the host must send clicks here as points rather than
|
||||||
/// treating them as picks, which is what `is_selection_gathering` reports.
|
/// treating them as picks, which is what `is_selection_gathering` reports.
|
||||||
@@ -37,9 +40,25 @@ impl SelectObjectsCommand {
|
|||||||
/// Standard selection set: accumulate picks, apply on Enter / right-click.
|
/// Standard selection set: accumulate picks, apply on Enter / right-click.
|
||||||
pub fn new(pending_cmd: &str) -> Self {
|
pub fn new(pending_cmd: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
prompt_cmd: pending_cmd.to_string(),
|
||||||
pending_cmd: pending_cmd.to_string(),
|
pending_cmd: pending_cmd.to_string(),
|
||||||
handles: Vec::new(),
|
handles: Vec::new(),
|
||||||
commit_on_enter: true,
|
commit_on_enter: true,
|
||||||
|
show_options: true,
|
||||||
|
pick: None,
|
||||||
|
pick_crossing: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Plain gather prompt with no generic selection-method buttons. The
|
||||||
|
/// visible command name may differ from the private apply command.
|
||||||
|
pub fn plain(prompt_cmd: &str, pending_cmd: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
prompt_cmd: prompt_cmd.to_string(),
|
||||||
|
pending_cmd: pending_cmd.to_string(),
|
||||||
|
handles: Vec::new(),
|
||||||
|
commit_on_enter: true,
|
||||||
|
show_options: false,
|
||||||
pick: None,
|
pick: None,
|
||||||
pick_crossing: true,
|
pick_crossing: true,
|
||||||
}
|
}
|
||||||
@@ -49,9 +68,11 @@ impl SelectObjectsCommand {
|
|||||||
/// immediately, with no Enter (used by commands that act on one object).
|
/// immediately, with no Enter (used by commands that act on one object).
|
||||||
pub fn instant(pending_cmd: &str) -> Self {
|
pub fn instant(pending_cmd: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
prompt_cmd: pending_cmd.to_string(),
|
||||||
pending_cmd: pending_cmd.to_string(),
|
pending_cmd: pending_cmd.to_string(),
|
||||||
handles: Vec::new(),
|
handles: Vec::new(),
|
||||||
commit_on_enter: false,
|
commit_on_enter: false,
|
||||||
|
show_options: false,
|
||||||
pick: None,
|
pick: None,
|
||||||
pick_crossing: true,
|
pick_crossing: true,
|
||||||
}
|
}
|
||||||
@@ -82,12 +103,12 @@ impl CadCommand for SelectObjectsCommand {
|
|||||||
if self.commit_on_enter && !self.handles.is_empty() {
|
if self.commit_on_enter && !self.handles.is_empty() {
|
||||||
t!(
|
t!(
|
||||||
"%{cmd} Select objects (%{count} selected, Enter to apply):",
|
"%{cmd} Select objects (%{count} selected, Enter to apply):",
|
||||||
cmd = self.pending_cmd,
|
cmd = self.prompt_cmd,
|
||||||
count = self.handles.len()
|
count = self.handles.len()
|
||||||
)
|
)
|
||||||
.into_owned()
|
.into_owned()
|
||||||
} else {
|
} else {
|
||||||
t!("%{cmd} Select objects:", cmd = self.pending_cmd).into_owned()
|
t!("%{cmd} Select objects:", cmd = self.prompt_cmd).into_owned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +129,7 @@ impl CadCommand for SelectObjectsCommand {
|
|||||||
// they work typed as well — these buttons only surface them, which is what
|
// they work typed as well — these buttons only surface them, which is what
|
||||||
// the on-screen keyboard-less case needs.
|
// the on-screen keyboard-less case needs.
|
||||||
fn options(&self) -> Vec<CmdOption> {
|
fn options(&self) -> Vec<CmdOption> {
|
||||||
if self.commit_on_enter {
|
if self.commit_on_enter && self.show_options {
|
||||||
vec![
|
vec![
|
||||||
CmdOption::new(t!("Window").as_ref(), "W"),
|
CmdOption::new(t!("Window").as_ref(), "W"),
|
||||||
CmdOption::new(t!("Crossing").as_ref(), "C"),
|
CmdOption::new(t!("Crossing").as_ref(), "C"),
|
||||||
|
|||||||
@@ -163,6 +163,10 @@ pub fn kernel_surface_body(surface: &Surface) -> Option<KernelBody> {
|
|||||||
kernel_acis_body(&surface.acis_data)
|
kernel_acis_body(&surface.acis_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn kernel_region_body(region: &Region) -> Option<KernelBody> {
|
||||||
|
kernel_acis_body(®ion.acis_data)
|
||||||
|
}
|
||||||
|
|
||||||
fn kernel_acis_body(acis: &acadrust::entities::AcisData) -> Option<KernelBody> {
|
fn kernel_acis_body(acis: &acadrust::entities::AcisData) -> Option<KernelBody> {
|
||||||
let sat = parse_acis(
|
let sat = parse_acis(
|
||||||
|| acis.parse(),
|
|| acis.parse(),
|
||||||
|
|||||||
@@ -605,6 +605,9 @@ impl Scene {
|
|||||||
Some(EntityType::Surface(surface)) => {
|
Some(EntityType::Surface(surface)) => {
|
||||||
crate::scene::convert::solid3d_tess::kernel_surface_body(surface)
|
crate::scene::convert::solid3d_tess::kernel_surface_body(surface)
|
||||||
}
|
}
|
||||||
|
Some(EntityType::Region(region)) => {
|
||||||
|
crate::scene::convert::solid3d_tess::kernel_region_body(region)
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
})?;
|
})?;
|
||||||
Some((handle, body))
|
Some((handle, body))
|
||||||
|
|||||||
@@ -214,6 +214,21 @@ pub fn has_specialized_primitive_properties(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Solid history results whose public Properties palette is fully described by
|
||||||
|
/// the common entity rows plus [`primitive_properties`]. A generic B-rep has
|
||||||
|
/// no stable primitive dimensions to expose, but it still uses the compact
|
||||||
|
/// Solid History palette rather than the internal ACIS/cache diagnostics.
|
||||||
|
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(_))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reference_point(operation: &SolidHistoryOperation) -> Option<glam::DVec3> {
|
pub fn reference_point(operation: &SolidHistoryOperation) -> Option<glam::DVec3> {
|
||||||
match operation {
|
match operation {
|
||||||
SolidHistoryOperation::Box(value) | SolidHistoryOperation::Wedge(value) => world_point(
|
SolidHistoryOperation::Box(value) | SolidHistoryOperation::Wedge(value) => world_point(
|
||||||
@@ -975,6 +990,41 @@ fn cone_properties(
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn brep_properties(
|
||||||
|
document: &acadrust::CadDocument,
|
||||||
|
handle: acadrust::Handle,
|
||||||
|
) -> Vec<PropSection> {
|
||||||
|
let (record_history, object_show_history, show_history_mode) =
|
||||||
|
history_flags(document, handle).unwrap_or((false, false, 1));
|
||||||
|
let (show_history, show_history_editable) =
|
||||||
|
displayed_history_state(object_show_history, show_history_mode);
|
||||||
|
vec![PropSection {
|
||||||
|
title: t!("Solid History").into_owned(),
|
||||||
|
props: vec![
|
||||||
|
Property {
|
||||||
|
label: t!("History").into_owned(),
|
||||||
|
field: PROP_HISTORY,
|
||||||
|
value: PropValue::Choice {
|
||||||
|
selected: if record_history { "Record" } else { "None" }.to_string(),
|
||||||
|
options: vec!["None".to_string(), "Record".to_string()],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Property {
|
||||||
|
label: t!("Show History").into_owned(),
|
||||||
|
field: PROP_SHOW_HISTORY,
|
||||||
|
value: if show_history_editable {
|
||||||
|
PropValue::Choice {
|
||||||
|
selected: if show_history { "Yes" } else { "No" }.to_string(),
|
||||||
|
options: vec!["No".to_string(), "Yes".to_string()],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PropValue::ReadOnly(if show_history { "Yes" } else { "No" }.to_string())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
fn cylinder_properties(
|
fn cylinder_properties(
|
||||||
document: &acadrust::CadDocument,
|
document: &acadrust::CadDocument,
|
||||||
handle: acadrust::Handle,
|
handle: acadrust::Handle,
|
||||||
@@ -1411,6 +1461,7 @@ pub fn primitive_properties(
|
|||||||
extrusion_properties(document, handle, value)
|
extrusion_properties(document, handle, value)
|
||||||
}
|
}
|
||||||
SolidHistoryOperation::Revolve(value) => revolve_properties(document, handle, value),
|
SolidHistoryOperation::Revolve(value) => revolve_properties(document, handle, value),
|
||||||
|
SolidHistoryOperation::Brep(_) => brep_properties(document, handle),
|
||||||
_ => Vec::new(),
|
_ => Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -365,13 +365,18 @@ pub enum Bool {
|
|||||||
/// missing, and passing that on unchanged is the point: a half-done boolean
|
/// missing, and passing that on unchanged is the point: a half-done boolean
|
||||||
/// looks finished.
|
/// looks finished.
|
||||||
pub fn boolean(op: Bool, a: &Body, b: &Body) -> Option<Body> {
|
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 {
|
let how = match op {
|
||||||
Bool::Union => brep::Operation::Union,
|
Bool::Union => brep::Operation::Union,
|
||||||
Bool::Subtract => brep::Operation::Difference,
|
Bool::Subtract => brep::Operation::Difference,
|
||||||
Bool::Intersect => brep::Operation::Intersection,
|
Bool::Intersect => brep::Operation::Intersection,
|
||||||
};
|
};
|
||||||
let tolerance = brep::operation_tolerance(&[a, b]);
|
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 ────────────────────────────────────────────────────────────
|
// ── Tessellation ────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in new issue
Block a user