forked from 1b-agent/opencadstudio-upstream
feat(solid history): booleans keep the solids they joined editable
With SOLIDHIST on, UNION, SUBTRACT and INTERSECT no longer reduce the result's history to a frozen body: each solid's history joins the result's under a boolean step, in the order the result applied them. The Properties palette lists a composite's solids under Operand; the one chosen shows its own geometry rows and grips, placed where it sits in the composite, and editing it replays the whole tree. A composite read from a file keeps its saved body; the tree is replayed only on edits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d1f3bf3013
commit
d3033caf24
28 files changed
+383
-52
No files matched your search
+79
-7
@@ -4,7 +4,7 @@ use codec::{
|
||||
entities::{
|
||||
AcisData, EntityCommon, Region, Solid3D, Surface, SurfaceData, SurfaceKind, Wire,
|
||||
},
|
||||
objects::SolidHistoryOperation,
|
||||
objects::{SolidHistoryBoolean, SolidHistoryOperation},
|
||||
EntityType, Handle,
|
||||
};
|
||||
use kernel::brep::{Body, EdgeKey, FaceKey};
|
||||
@@ -1006,6 +1006,32 @@ impl super::OpenCADStudio {
|
||||
}
|
||||
|
||||
/// Intersect compatible selected solids, Regions, and coplanar Surfaces.
|
||||
/// Before a boolean replaces `retained` with `entity`: with SOLIDHIST on,
|
||||
/// join the histories of the solids it combined (`steps`, in the order
|
||||
/// the result applied them) into `retained`'s and hand that history to
|
||||
/// `entity`; otherwise drop it. True when the history was kept.
|
||||
fn join_boolean_history(
|
||||
&mut self,
|
||||
retained: Handle,
|
||||
entity: &mut EntityType,
|
||||
steps: &[(Handle, u8)],
|
||||
bodies: &HashMap<Handle, Body>,
|
||||
) -> bool {
|
||||
let scene = &mut self.tabs[self.active_tab].scene;
|
||||
let joined = scene.document.header.record_solid_history
|
||||
&& scene.join_boolean_histories(retained, retained, steps, bodies);
|
||||
if !joined {
|
||||
scene.delete_solid_history(retained);
|
||||
return false;
|
||||
}
|
||||
if let (EntityType::Solid3D(entity), Some(EntityType::Solid3D(current))) =
|
||||
(entity, scene.document.get_entity(retained))
|
||||
{
|
||||
entity.history_handle = current.history_handle;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn solid_intersect(&mut self) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
let mut handles = self.selected_intersect_handles();
|
||||
@@ -1139,11 +1165,17 @@ impl super::OpenCADStudio {
|
||||
match group.outcome {
|
||||
PreparedIntersectOutcome::Replace {
|
||||
retained,
|
||||
entity,
|
||||
mut entity,
|
||||
body,
|
||||
display,
|
||||
} => {
|
||||
self.tabs[i].scene.delete_solid_history(retained);
|
||||
let steps = group
|
||||
.handles
|
||||
.iter()
|
||||
.filter(|handle| **handle != retained)
|
||||
.map(|handle| (*handle, SolidHistoryBoolean::INTERSECT))
|
||||
.collect::<Vec<_>>();
|
||||
let joined = self.join_boolean_history(retained, &mut entity, &steps, &bodies);
|
||||
if !self.tabs[i].scene.update_entity(entity) {
|
||||
self.command_line.push_error(
|
||||
crate::t!("INTERSECT: the retained object could not be updated.")
|
||||
@@ -1152,6 +1184,7 @@ impl super::OpenCADStudio {
|
||||
return Task::none();
|
||||
}
|
||||
if record_history
|
||||
&& !joined
|
||||
&& matches!(
|
||||
self.tabs[i].scene.document.get_entity(retained),
|
||||
Some(EntityType::Solid3D(_))
|
||||
@@ -1328,11 +1361,37 @@ impl super::OpenCADStudio {
|
||||
let history = solid_history::brep_op(&group.body);
|
||||
let mut entity = Solid3D::new();
|
||||
entity.common = group.common;
|
||||
self.add_solid_model_preserving_style(
|
||||
let handle = self.add_solid_model_preserving_style(
|
||||
EntityType::Solid3D(entity),
|
||||
group.body,
|
||||
history,
|
||||
)
|
||||
);
|
||||
// The new solid's history becomes the joined histories
|
||||
// of the solids it unites.
|
||||
let record = self.tabs[i].scene.document.header.record_solid_history;
|
||||
if !handle.is_null() && record {
|
||||
let bodies = group
|
||||
.handles
|
||||
.iter()
|
||||
.filter_map(|operand| {
|
||||
let body = self.tabs[i].scene.solid_models.get(operand)?;
|
||||
Some((*operand, body.clone()))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
let steps = group
|
||||
.handles
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|operand| (*operand, SolidHistoryBoolean::UNION))
|
||||
.collect::<Vec<_>>();
|
||||
let scene = &mut self.tabs[i].scene;
|
||||
if !scene.join_boolean_histories(handle, group.handles[0], &steps, &bodies) {
|
||||
if let Some(body) = scene.solid_models.get(&handle).cloned() {
|
||||
scene.create_solid_history(handle, solid_history::brep_op(&body));
|
||||
}
|
||||
}
|
||||
}
|
||||
handle
|
||||
}
|
||||
UnionEntityKind::Region => {
|
||||
let mut entity = Region::new();
|
||||
@@ -1588,8 +1647,20 @@ impl super::OpenCADStudio {
|
||||
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 let Some((handle, mut entity, body, display)) = group.result {
|
||||
let steps = group
|
||||
.bases
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|base| (*base, SolidHistoryBoolean::UNION))
|
||||
.chain(
|
||||
group
|
||||
.cutters
|
||||
.iter()
|
||||
.map(|cutter| (*cutter, SolidHistoryBoolean::SUBTRACT)),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
let joined = self.join_boolean_history(handle, &mut entity, &steps, &operand_bodies);
|
||||
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(),
|
||||
@@ -1597,6 +1668,7 @@ impl super::OpenCADStudio {
|
||||
return Task::none();
|
||||
}
|
||||
if record_history
|
||||
&& !joined
|
||||
&& matches!(
|
||||
self.tabs[i].scene.document.get_entity(handle),
|
||||
Some(EntityType::Solid3D(_))
|
||||
|
||||
@@ -2732,6 +2732,19 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
self.tabs[i].properties.edit_choice_open = false;
|
||||
return Task::none();
|
||||
}
|
||||
if field == crate::scene::model::solid_history::PROP_OPERAND {
|
||||
// Which solid of a composite to edit: palette state only.
|
||||
for handle in &handles {
|
||||
crate::scene::model::solid_history::select_operand(
|
||||
&self.tabs[i].scene.document,
|
||||
*handle,
|
||||
&value,
|
||||
);
|
||||
}
|
||||
self.tabs[i].properties.edit_choice_open = false;
|
||||
self.refresh_properties();
|
||||
return Task::none();
|
||||
}
|
||||
if crate::scene::model::solid_history::is_history_choice(field) {
|
||||
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
|
||||
if app.tabs[i].scene.is_layer_locked(handle) {
|
||||
|
||||
Reference in new issue
Block a user