Merge PR #1589 from ramox81/codex/history-frame-lift: centre-framed boxes and wedges; no double transform after ACIS lift

The PR pinned codec and kernel to its fork through [patch]; codec #77 and
kernel #73 are merged upstream, so the patch sections are replaced by
the upstream pins (codec 8d23408, kernel d22a270, graph 9b5ae39) in this
same commit: the kernel now applies the body transform during the lift,
and an OCS without this PR's change would apply it twice. The new codec
also brings AssocCurveValue, registered with the plugin API's type
registry, and EmbeddedEntity::Body, named as a polyline profile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-02 20:08:19 +03:00
commit 54a4a02d62
12 files changed
+83 -45

No files matched your search

Generated
+4 -4
View File
@@ -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/HakanSeven12/opencadcodec.git?rev=8d23408#8d2340876caef34f68436f62e2c9402be0aabb28"
dependencies = [
"ahash 0.8.12",
"anyhow",
@@ -4229,7 +4229,7 @@ dependencies = [
[[package]]
name = "opencadgraph"
version = "0.1.0"
source = "git+https://github.com/HakanSeven12/opencadgraph.git?rev=10d0858#10d0858a7252f85df07be4dfa00946658b6979e6"
source = "git+https://github.com/HakanSeven12/opencadgraph.git?rev=9b5ae39#9b5ae39ea35567e67faf8bc8d20b40e2bbab1177"
dependencies = [
"opencadkernel",
"serde_json",
@@ -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/HakanSeven12/opencadkernel.git?rev=d22a270#d22a270a0af79db9f10428a2d941152f6bb28158"
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/HakanSeven12/opencadkernel.git?rev=d22a270#d22a270a0af79db9f10428a2d941152f6bb28158"
dependencies = [
"nalgebra",
]
+4 -4
View File
@@ -34,10 +34,10 @@ rfd = "0.17"
clap = { version = "4", features = ["derive", "string"] }
env_logger = "0.11"
log = "0.4"
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "d96e3fa", features = ["serde"] }
kernel = { package = "opencadkernel", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "9cdd36d", features = ["acis", "offset"] }
kernel-constraints = { package = "opencadkernel-constraints", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "9cdd36d" }
graph = { package = "opencadgraph", git = "https://github.com/HakanSeven12/opencadgraph.git", rev = "10d0858" }
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "8d23408", features = ["serde"] }
kernel = { package = "opencadkernel", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "d22a270", features = ["acis", "offset"] }
kernel-constraints = { package = "opencadkernel-constraints", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "d22a270" }
graph = { package = "opencadgraph", git = "https://github.com/HakanSeven12/opencadgraph.git", rev = "9b5ae39" }
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
flate2 = "1"
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
+2 -2
View File
@@ -15,7 +15,7 @@ serde_json = { version = "1", optional = true }
# Pulled in only by the `host` feature, which adds the `opencadcodec`-typed
# `HostApi` runtime surface. The default crate stays dependency-free so engine
# crates and external tooling can depend on the manifest/ribbon contract cheaply.
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "d96e3fa", optional = true, features = ["serde"] }
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "8d23408", optional = true, features = ["serde"] }
# Runtime IPC and serialization (host feature only).
interprocess = { version = "2", optional = true }
@@ -39,7 +39,7 @@ serde_json = "1"
serde = { version = "1", features = ["derive"] }
cargo-lock = "11"
# opencadcodec is scanned at build time to generate the embedded type registry.
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "d96e3fa", features = ["serde"] }
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "8d23408", features = ["serde"] }
[dev-dependencies]
serde_json = "1"
+4
View File
@@ -162,6 +162,10 @@ fn generate_type_registry(out_dir: &Path) {
"AssocConstraintNodeData",
trace_simple::<codec::objects::AssocConstraintNodeData>,
),
(
"AssocCurveValue",
trace_simple::<codec::objects::AssocCurveValue>,
),
(
"AssocEvalValue",
trace_simple::<codec::objects::AssocEvalValue>,
+1 -1
View File
@@ -8,7 +8,7 @@ publish = false
crate-type = ["cdylib"]
[dependencies]
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "d96e3fa", features = ["serde"] }
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "8d23408", features = ["serde"] }
bincode = "1.3"
serde = { version = "1", features = ["derive"] }
console_error_panic_hook = "0.1"
+1 -1
View File
@@ -1265,7 +1265,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/HakanSeven12/opencadcodec.git?rev=8d23408#8d2340876caef34f68436f62e2c9402be0aabb28"
dependencies = [
"ahash 0.8.12",
"anyhow",
+2
View File
@@ -228,6 +228,8 @@ fn embedded_name(value: Option<&codec::entities::EmbeddedEntity>) -> &'static st
Some(codec::entities::EmbeddedEntity::Ellipse(_)) => "Ellipse",
Some(codec::entities::EmbeddedEntity::Spline(_)) => "Spline",
Some(codec::entities::EmbeddedEntity::LwPolyline(_)) => "Polyline",
// A 2D / 3D polyline profile the modeler keeps as a wire body.
Some(codec::entities::EmbeddedEntity::Body { .. }) => "Polyline",
Some(codec::entities::EmbeddedEntity::Region(_)) => "Region",
Some(codec::entities::EmbeddedEntity::Ray(_)) => "Ray",
Some(codec::entities::EmbeddedEntity::XLine(_)) => "XLine",
+2
View File
@@ -447,6 +447,8 @@ fn embedded_name(entity: Option<&codec::entities::EmbeddedEntity>) -> &'static s
Some(codec::entities::EmbeddedEntity::Ellipse(_)) => "Ellipse",
Some(codec::entities::EmbeddedEntity::Spline(_)) => "Spline",
Some(codec::entities::EmbeddedEntity::LwPolyline(_)) => "Polyline",
// A 2D / 3D polyline profile the modeler keeps as a wire body.
Some(codec::entities::EmbeddedEntity::Body { .. }) => "Polyline",
Some(codec::entities::EmbeddedEntity::Region(_)) => "Region",
Some(codec::entities::EmbeddedEntity::Ray(_)) => "Ray",
Some(codec::entities::EmbeddedEntity::XLine(_)) => "XLine",
+8 -2
View File
@@ -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)
}
+3 -12
View File
@@ -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 {
+3 -16
View File
@@ -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<Kerne
if !loss.is_empty() || bodies.len() != 1 {
return None;
}
let body = bodies.pop()?;
let source = body.provenance.source()?;
let Some((matrix, translation, scale)) = body_transform(&sat, source.index() as usize).ok()?
else {
return Some(body);
};
brep::transform(
&body,
&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,
},
)
// The kernel's lift already applies the body's transform record.
bodies.pop()
}
fn remap_acis_material_bindings(
+49 -3
View File
@@ -278,7 +278,7 @@ pub fn reference_point(operation: &SolidHistoryOperation) -> Option<glam::DVec3>
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) {
@@ -2757,7 +2769,7 @@ fn embedded_entity(value: &EmbeddedEntity) -> Option<EntityType> {
EmbeddedEntity::Region(value) => EntityType::Region(value.clone()),
EmbeddedEntity::Ray(value) => EntityType::Ray(value.clone()),
EmbeddedEntity::XLine(value) => EntityType::XLine(value.clone()),
EmbeddedEntity::Unknown { .. } => return None,
EmbeddedEntity::Body { .. } | EmbeddedEntity::Unknown { .. } => return None,
})
}
@@ -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();
}
}
}