From 07facf465c6b42bdec412238c839021ab99eab8b Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 2 Oct 2026 15:10:37 +0300 Subject: [PATCH] fix(solids): box and wedge histories framed at the bounding-box centre; no second transform after ACIS lift Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 6 ++-- Cargo.toml | 9 ++++++ src/modules/model/primitive_cmd.rs | 10 ++++-- src/scene/convert/acis_kernel.rs | 15 ++------- src/scene/convert/solid3d_tess.rs | 19 ++---------- src/scene/model/solid_history.rs | 50 ++++++++++++++++++++++++++++-- 6 files changed, 74 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 51294fb85..04cdd81bc 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=d96e3fa#d96e3fa2fe5acbeac966f1db4c01142618bf9c79" +source = "git+https://github.com/ramox81/cadcodec.git?rev=1802e8c#1802e8cc4d213da60156e7e540965ea398768d18" 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=9cdd36d#9cdd36dc527216ba7929d8cfcdc1b7712a2d4cb8" +source = "git+https://github.com/ramox81/cadkernel.git?rev=6dc98c6#6dc98c6749af2b6e0586eb4279eb5e389dd2aa16" 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=9cdd36d#9cdd36dc527216ba7929d8cfcdc1b7712a2d4cb8" +source = "git+https://github.com/ramox81/cadkernel.git?rev=6dc98c6#6dc98c6749af2b6e0586eb4279eb5e389dd2aa16" dependencies = [ "nalgebra", ] diff --git a/Cargo.toml b/Cargo.toml index 85cc56ce7..096b15548 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,15 @@ 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"] } +# Column-major history matrices and embedded circle centres (opencadcodec #77). +[patch."https://github.com/HakanSeven12/opencadcodec.git"] +opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "1802e8c" } + +# Box frame at the centre and transform-aware ACIS lift (opencadkernel #73). +[patch."https://github.com/HakanSeven12/opencadkernel.git"] +opencadkernel = { git = "https://github.com/ramox81/cadkernel.git", rev = "6dc98c6" } +opencadkernel-constraints = { git = "https://github.com/ramox81/cadkernel.git", rev = "6dc98c6" } + [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/modules/model/primitive_cmd.rs b/src/modules/model/primitive_cmd.rs index 016640de0..cca272072 100644 --- a/src/modules/model/primitive_cmd.rs +++ b/src/modules/model/primitive_cmd.rs @@ -1894,7 +1894,7 @@ impl PrimitiveCommand { Some(( placed, solid_history::box_op( - self.history_transform_axes(origin, x_axis, y_axis), + self.history_transform_axes(box_centre(origin, x_axis, y_axis, length, width, height), x_axis, y_axis), length, width, height, @@ -2024,7 +2024,7 @@ impl PrimitiveCommand { Some(( placed, solid_history::wedge_op( - self.history_transform_axes(origin, x_axis, y_axis), + self.history_transform_axes(box_centre(origin, x_axis, y_axis, length, width, height), x_axis, y_axis), length, width, height, @@ -3714,3 +3714,9 @@ fn wire(name: &str, points: Vec<[f32; 3]>) -> WireModel { ..Default::default() } } + +/// Box and wedge histories are framed at the centre of their bounding box, +/// as the reference stores them (lengths stay positive). +fn box_centre(origin: DVec3, x_axis: DVec3, y_axis: DVec3, length: f64, width: f64, height: f64) -> DVec3 { + origin + x_axis * (length * 0.5) + y_axis * (width * 0.5) + DVec3::Z * (height * 0.5) +} diff --git a/src/scene/convert/acis_kernel.rs b/src/scene/convert/acis_kernel.rs index d4ede7699..d1affae13 100644 --- a/src/scene/convert/acis_kernel.rs +++ b/src/scene/convert/acis_kernel.rs @@ -31,23 +31,14 @@ pub fn tessellate_sat( if bodies.is_empty() { return None; } + // The kernel's lift already applies each body's transform record; its + // scale still sets the tessellation tolerance. let mut placed_bodies = Vec::with_capacity(bodies.len()); for body in bodies { let source = body.provenance.source()?; let transform = body_transform(document, source.index() as usize).ok()?; let placement_scale = transform.map_or(1.0, |(_, _, scale)| scale.abs()); - let placed = if let Some((matrix, translation, scale)) = transform { - let placement = brep::Placement { - x_axis: [scale * matrix[0], scale * matrix[1], scale * matrix[2]], - y_axis: [scale * matrix[3], scale * matrix[4], scale * matrix[5]], - z_axis: [scale * matrix[6], scale * matrix[7], scale * matrix[8]], - origin: translation, - }; - brep::transform(&body, &placement)? - } else { - body - }; - placed_bodies.push((placed, placement_scale)); + placed_bodies.push((body, placement_scale)); } let bodies = placed_bodies; let resolution = if facet_res.is_finite() && facet_res > 0.0 { diff --git a/src/scene/convert/solid3d_tess.rs b/src/scene/convert/solid3d_tess.rs index 987b920a7..1bda680d5 100644 --- a/src/scene/convert/solid3d_tess.rs +++ b/src/scene/convert/solid3d_tess.rs @@ -1,6 +1,6 @@ use codec::entities::acis::{SabReader, SatBody, SatDocument}; use codec::entities::{Body, Region, Solid3D, Surface}; -use kernel::brep::{self, Body as KernelBody}; +use kernel::brep::Body as KernelBody; use crate::scene::model::mesh_model::{MeshLodSet, MeshModel}; @@ -179,21 +179,8 @@ pub(crate) fn kernel_acis_body(acis: &codec::entities::AcisData) -> Option Option match operation { SolidHistoryOperation::Box(value) | SolidHistoryOperation::Wedge(value) => world_point( value.base.transform, - [value.length * 0.5, value.width * 0.5, 0.0], + [0.0, 0.0, -value.height * 0.5], ), SolidHistoryOperation::Sphere(value) => { world_point(value.base.transform, [0.0, 0.0, 0.0]) @@ -1615,9 +1615,10 @@ pub fn primitive_properties( ) { return Vec::new(); } - let Some(operation) = primitive_property_operation(document, handle) else { + let Some(mut operation) = primitive_property_operation(document, handle) else { return brep_properties(document, handle); }; + corner_frame(&mut operation); match &operation { SolidHistoryOperation::Box(value) => { rectangular_properties(document, handle, value, "Box") @@ -2414,6 +2415,17 @@ pub fn apply_primitive_property( operation: &mut SolidHistoryOperation, field: &str, value: &str, +) -> bool { + corner_frame(operation); + let applied = apply_primitive_property_at_corner(operation, field, value); + centre_frame(operation); + applied +} + +fn apply_primitive_property_at_corner( + operation: &mut SolidHistoryOperation, + field: &str, + value: &str, ) -> bool { if let SolidHistoryOperation::Brep(brep_value) = operation { if let Some(applied) = apply_brep_position_property(brep_value, field, value) { @@ -3460,6 +3472,9 @@ pub fn primitive_grips( let Some(operation) = document.solid_history_operation(handle) else { return Vec::new(); }; + let mut operation = operation.clone(); + corner_frame(&mut operation); + let operation = &operation; if let SolidHistoryOperation::Fillet(value) = operation { let Some(radius) = value.radii.first().copied() else { return Vec::new(); @@ -3743,6 +3758,17 @@ pub fn apply_primitive_grip( operation: &mut SolidHistoryOperation, grip_id: usize, apply: GripApply, +) -> bool { + corner_frame(operation); + let applied = apply_primitive_grip_at_corner(operation, grip_id, apply); + centre_frame(operation); + applied +} + +fn apply_primitive_grip_at_corner( + operation: &mut SolidHistoryOperation, + grip_id: usize, + apply: GripApply, ) -> bool { if let SolidHistoryOperation::Revolve(value) = operation { if let Some(index) = grip_id.checked_sub(GRIP_REVOLVE_PROFILE_FIRST) { @@ -4138,3 +4164,23 @@ pub fn chamfer_op( ..SolidHistoryChamfer::default() }) } + +/// Box and wedge histories are framed at the centre of their bounding box, +/// as the reference stores them; the editing code here works in the frame at +/// the base corner. These move a box or wedge between the two frames. +fn corner_frame(operation: &mut SolidHistoryOperation) { + shift_rectangular_frame(operation, -0.5); +} + +fn centre_frame(operation: &mut SolidHistoryOperation) { + shift_rectangular_frame(operation, 0.5); +} + +fn shift_rectangular_frame(operation: &mut SolidHistoryOperation, factor: f64) { + if let SolidHistoryOperation::Box(value) | SolidHistoryOperation::Wedge(value) = operation { + if let Some(current) = matrix(value.base.transform) { + let half = glam::DVec3::new(value.length, value.width, value.height) * factor; + value.base.transform = (current * glam::DMat4::from_translation(half)).to_cols_array(); + } + } +}