From eb90b68129bdb9ece37d4487ceb94b567b7ae92f Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 2 Oct 2026 10:45:09 +0300 Subject: [PATCH 1/2] fix(solids): keep history-less solids without history on edge fillet/chamfer; undo the evaluation graph Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 2 +- Cargo.toml | 4 ++++ src/app/model_ops.rs | 49 +++++++++++++++++--------------------------- src/scene/modify.rs | 10 +++++---- 4 files changed, 30 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a2ce9f354..9583ca664 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4206,7 +4206,7 @@ dependencies = [ [[package]] name = "opencadcodec" version = "0.5.5" -source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=d28fd71#d28fd710268e7301798703d3ca9c5698a1af91ed" +source = "git+https://github.com/ramox81/cadcodec.git?rev=25227f5#25227f504006d62373c44d5064116607865b813a" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 8ebca382d..738b6a1b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,10 @@ objc2-app-kit = { version = "0.2", features = ["NSApplication", "NSResponder", " block2 = "0.5" objc2-foundation = { version = "0.2", features = ["NSArray", "NSURL", "NSString", "NSObject", "NSNotification", "NSThread", "NSEnumerator"] } +# Solid history evaluation graphs and history node layouts (opencadcodec PR pending). +[patch."https://github.com/HakanSeven12/opencadcodec.git"] +opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "25227f5" } + [patch.crates-io] iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" } iced_widget = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" } diff --git a/src/app/model_ops.rs b/src/app/model_ops.rs index 0d522ada2..ca5fb2554 100644 --- a/src/app/model_ops.rs +++ b/src/app/model_ops.rs @@ -655,16 +655,9 @@ impl super::OpenCADStudio { }; self.push_undo_snapshot(i, "FILLETEDGE"); - if self.tabs[i].scene.document.solid_history_graph(handle).is_none() - && !self.tabs[i] - .scene - .create_solid_history(handle, solid_history::brep_op(source)) - { - self.command_line - .push_error(crate::t!("The edge body history could not be created.").as_ref()); - return false; - } - if !self.tabs[i].scene.append_solid_history( + // A solid without history stays without one, as in the reference. + let has_history = self.tabs[i].scene.document.solid_history_graph(handle).is_some(); + if has_history && !self.tabs[i].scene.append_solid_history( handle, solid_history::fillet_op(history_edges, radius), ) { @@ -672,11 +665,13 @@ impl super::OpenCADStudio { .push_error(crate::t!("The fillet history could not be recorded.").as_ref()); return false; } - let _ = self.tabs[i].scene.apply_solid_history_choice( - handle, - solid_history::PROP_HISTORY, - "Record", - ); + if has_history { + let _ = self.tabs[i].scene.apply_solid_history_choice( + handle, + solid_history::PROP_HISTORY, + "Record", + ); + } let Some(entity) = self.tabs[i].scene.document.get_entity(handle).cloned() else { return false; }; @@ -743,16 +738,8 @@ impl super::OpenCADStudio { }; self.push_undo_snapshot(i, "CHAMFEREDGE"); - if self.tabs[i].scene.document.solid_history_graph(handle).is_none() - && !self.tabs[i] - .scene - .create_solid_history(handle, solid_history::brep_op(source)) - { - self.command_line - .push_error(crate::t!("The edge body history could not be created.").as_ref()); - return false; - } - if !self.tabs[i].scene.append_solid_history( + let has_history = self.tabs[i].scene.document.solid_history_graph(handle).is_some(); + if has_history && !self.tabs[i].scene.append_solid_history( handle, solid_history::chamfer_op( history_edges, @@ -765,11 +752,13 @@ impl super::OpenCADStudio { .push_error(crate::t!("The chamfer history could not be recorded.").as_ref()); return false; } - let _ = self.tabs[i].scene.apply_solid_history_choice( - handle, - solid_history::PROP_HISTORY, - "Record", - ); + if has_history { + let _ = self.tabs[i].scene.apply_solid_history_choice( + handle, + solid_history::PROP_HISTORY, + "Record", + ); + } let Some(entity) = self.tabs[i].scene.document.get_entity(handle).cloned() else { return false; }; diff --git a/src/scene/modify.rs b/src/scene/modify.rs index 632493ad5..d3d035ace 100644 --- a/src/scene/modify.rs +++ b/src/scene/modify.rs @@ -841,6 +841,7 @@ impl Scene { }; std::iter::once(graph.root) .chain(graph.nodes) + .chain(graph.evaluation_graph) .filter_map(|object_handle| { self.document .objects @@ -869,7 +870,7 @@ impl Scene { return false; }; self.record_undo_object_before(graph.root, None); - for node in graph.nodes { + for node in graph.nodes.into_iter().chain(graph.evaluation_graph) { self.record_undo_object_before(node, None); } self.sync_solid_reference_point(handle); @@ -881,16 +882,17 @@ impl Scene { handle: Handle, operation: codec::objects::SolidHistoryOperation, ) -> bool { - let previous = self + let previous: Vec = self .document .solid_history_graph(handle) - .map(|graph| graph.nodes) + .map(|graph| graph.nodes.into_iter().chain(graph.evaluation_graph).collect()) .unwrap_or_default(); self.record_solid_history_before(handle); let Some(graph) = self.document.append_solid_history(handle, operation) else { return false; }; - for node in graph.nodes { + // A history saved without an evaluation graph gets one on append. + for node in graph.nodes.into_iter().chain(graph.evaluation_graph) { if !previous.contains(&node) { self.record_undo_object_before(node, None); } From d7a19e5d8e877054c398c085a793bc2e9c1cbb57 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 2 Oct 2026 11:57:53 +0300 Subject: [PATCH 2/2] fix(sweep): store the sweep profile placed on the path with flag 295, as the reference reads it Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 6 ++--- Cargo.toml | 7 ++++- src/scene/model/sweep_command_model.rs | 37 ++++++++++++++++++++++++-- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9583ca664..89e56a6d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4206,7 +4206,7 @@ dependencies = [ [[package]] name = "opencadcodec" version = "0.5.5" -source = "git+https://github.com/ramox81/cadcodec.git?rev=25227f5#25227f504006d62373c44d5064116607865b813a" +source = "git+https://github.com/ramox81/cadcodec.git?rev=3ffd8aa#3ffd8aad6b675da103a7feb586816dfc387686f5" dependencies = [ "ahash 0.8.12", "anyhow", @@ -4238,7 +4238,7 @@ dependencies = [ [[package]] name = "opencadkernel" version = "0.1.0" -source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=a03e4f0#a03e4f094331949c8b27fb659e6891b84eeaa813" +source = "git+https://github.com/ramox81/cadkernel.git?rev=04843b5#04843b583afb06d427d3ebdeab09e7e34bce77f0" dependencies = [ "cavalier_contours", "opencadcodec", @@ -4249,7 +4249,7 @@ dependencies = [ [[package]] name = "opencadkernel-constraints" version = "0.1.0" -source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=a03e4f0#a03e4f094331949c8b27fb659e6891b84eeaa813" +source = "git+https://github.com/ramox81/cadkernel.git?rev=04843b5#04843b583afb06d427d3ebdeab09e7e34bce77f0" dependencies = [ "nalgebra", ] diff --git a/Cargo.toml b/Cargo.toml index 738b6a1b5..e1c4ba1c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,7 +92,12 @@ objc2-foundation = { version = "0.2", features = ["NSArray", "NSURL", "NSString" # Solid history evaluation graphs and history node layouts (opencadcodec PR pending). [patch."https://github.com/HakanSeven12/opencadcodec.git"] -opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "25227f5" } +opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "3ffd8aa" } + +# Sweep history with a placed profile (opencadkernel PR pending). +[patch."https://github.com/HakanSeven12/opencadkernel.git"] +opencadkernel = { git = "https://github.com/ramox81/cadkernel.git", rev = "04843b5" } +opencadkernel-constraints = { git = "https://github.com/ramox81/cadkernel.git", rev = "04843b5" } [patch.crates-io] iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" } diff --git a/src/scene/model/sweep_command_model.rs b/src/scene/model/sweep_command_model.rs index da2310700..5a9671728 100644 --- a/src/scene/model/sweep_command_model.rs +++ b/src/scene/model/sweep_command_model.rs @@ -60,7 +60,7 @@ pub fn sweep_record(profile: &EntityType, path: &EntityType, options: SweepOptio }; let mut base = SolidHistoryNodeBase::new(1); base.transform = glam::DMat4::IDENTITY.to_cols_array(); - Some(SolidHistorySweep { + let record = SolidHistorySweep { base, operation_major: 1, sweep_entity: Some(sweep_entity), @@ -74,7 +74,40 @@ pub fn sweep_record(profile: &EntityType, path: &EntityType, options: SweepOptio path_entity_transform: glam::DMat4::IDENTITY.to_cols_array(), reference_point: Vector3::new(base_point[0], base_point[1], base_point[2]), ..SolidHistorySweep::default() - }) + }; + placed_sweep_record(profile, record) +} + +/// The record the reference reads: the profile stored already placed at the +/// path start (base point, alignment and profile rotation applied) with flag +/// 295 set, and no reference point. +fn placed_sweep_record(profile: &EntityType, mut record: SolidHistorySweep) -> Option { + let (placed, _) = kernel::acis::sweep_history_placements(&record).ok()?; + let embedded_to_world = glam::DMat4::from_cols( + glam::DVec4::new(placed.x_axis[0], placed.x_axis[1], placed.x_axis[2], 0.0), + glam::DVec4::new(placed.y_axis[0], placed.y_axis[1], placed.y_axis[2], 0.0), + glam::DVec4::new(placed.z_axis[0], placed.z_axis[1], placed.z_axis[2], 0.0), + glam::DVec4::new(placed.origin[0], placed.origin[1], placed.origin[2], 1.0), + ); + // Source world geometry -> placed world geometry. + let map = embedded_to_world * glam::DMat4::from_cols_array(&record.sweep_entity_transform).inverse(); + let m = map.to_cols_array_2d(); + let transform = codec::types::Transform::from_matrix(codec::types::Matrix4 { + m: [ + [m[0][0], m[1][0], m[2][0], m[3][0]], + [m[0][1], m[1][1], m[2][1], m[3][1]], + [m[0][2], m[1][2], m[2][2], m[3][2]], + [0.0, 0.0, 0.0, 1.0], + ], + }); + let mut moved = profile.clone(); + crate::scene::view::dispatch::apply_transform(&mut moved, &crate::command::EntityTransform::Affine(transform)); + let (sweep_entity, sweep_entity_transform) = embedded_sweep_profile(&moved)?; + record.sweep_entity = Some(sweep_entity); + record.sweep_entity_transform = sweep_entity_transform; + record.flags_294_296 = [false, true, true]; + record.reference_point = Vector3::new(0.0, 0.0, 0.0); + Some(record) } pub fn swept_with_options(profile: &EntityType, path: &EntityType, mode: ExtrudeMode, options: SweepOptions) -> Option {