forked from 1b-agent/opencadstudio-upstream
Merge pull request #1070 from ramox81/codex/filletedge-parity
# Conflicts: # Cargo.lock
This commit is contained in:
commit
7ec07f3222
21 files changed
+1544
-138
No files matched your search
+2
-2
@@ -596,8 +596,8 @@ impl Scene {
|
||||
.filter_map(|&handle| {
|
||||
let from_history = self
|
||||
.document
|
||||
.solid_history_operation(handle)
|
||||
.and_then(|operation| cadkernel::acis::rebuild_body(operation).ok());
|
||||
.solid_history_operations(handle)
|
||||
.and_then(|operations| cadkernel::acis::rebuild_history(&operations).ok());
|
||||
let body = from_history.or_else(|| match self.document.get_entity(handle) {
|
||||
Some(EntityType::Solid3D(solid)) => {
|
||||
crate::scene::convert::solid3d_tess::kernel_body(solid)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use acadrust::entities::{EmbeddedEntity, Solid3D};
|
||||
use acadrust::objects::{
|
||||
DynamicBlockData, ObjectType, SolidHistoryBox, SolidHistoryBrep, SolidHistoryCone,
|
||||
SolidHistoryCylinder, SolidHistoryLoft, SolidHistoryLoftParameters, SolidHistoryNodeBase, SolidHistoryOperation,
|
||||
DynamicBlockData, ObjectType, SolidHistoryBox, SolidHistoryBrep, SolidHistoryChamfer,
|
||||
SolidHistoryCone, SolidHistoryCylinder, SolidHistoryFillet, SolidHistoryLoft,
|
||||
SolidHistoryLoftParameters, SolidHistoryNodeBase, SolidHistoryOperation,
|
||||
SolidHistoryPyramid, SolidHistoryRevolve, SolidHistorySphere, SolidHistorySweep,
|
||||
SolidHistoryTorus,
|
||||
};
|
||||
use acadrust::EntityType;
|
||||
use cadkernel::brep::Body;
|
||||
use cadkernel::brep::{Body, Surface};
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::traits::EntityTypeOps;
|
||||
@@ -32,6 +33,9 @@ pub const GRIP_SWEEP_PATH_FIRST: usize = 30_000;
|
||||
pub const GRIP_LOFT_SECTION_FIRST: usize = 40_000;
|
||||
pub const GRIP_REVOLVE_PROFILE_FIRST: usize = 50_000;
|
||||
pub const GRIP_REVOLVE_AXIS: usize = 10_013;
|
||||
pub const GRIP_FILLET_RADIUS: usize = 10_014;
|
||||
pub const GRIP_CHAMFER_DISTANCE1: usize = 10_015;
|
||||
pub const GRIP_CHAMFER_DISTANCE2: usize = 10_016;
|
||||
pub const GRIP_BOX_CORNER_FIRST: usize = 10_100;
|
||||
pub const GRIP_BOX_FACE_X_MIN: usize = 10_110;
|
||||
pub const GRIP_BOX_FACE_X_MAX: usize = 10_111;
|
||||
@@ -197,7 +201,7 @@ pub fn has_specialized_primitive_properties(
|
||||
handle: acadrust::Handle,
|
||||
) -> bool {
|
||||
matches!(
|
||||
document.solid_history_operation(handle),
|
||||
primitive_property_operation(document, handle).as_ref(),
|
||||
Some(
|
||||
SolidHistoryOperation::Box(_)
|
||||
| SolidHistoryOperation::Wedge(_)
|
||||
@@ -214,6 +218,19 @@ pub fn has_specialized_primitive_properties(
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the primitive that owns the public Geometry rows. Later operations
|
||||
/// such as edge fillets and chamfers refine that primitive but do not replace
|
||||
/// its editable type, position, or dimensions.
|
||||
pub fn primitive_property_operation(
|
||||
document: &acadrust::CadDocument,
|
||||
handle: acadrust::Handle,
|
||||
) -> Option<SolidHistoryOperation> {
|
||||
document
|
||||
.solid_history_operations(handle)
|
||||
.and_then(|operations| operations.into_iter().next())
|
||||
.or_else(|| document.solid_history_operation(handle).cloned())
|
||||
}
|
||||
|
||||
/// 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
|
||||
@@ -1438,10 +1455,10 @@ pub fn primitive_properties(
|
||||
document: &acadrust::CadDocument,
|
||||
handle: acadrust::Handle,
|
||||
) -> Vec<PropSection> {
|
||||
let Some(operation) = document.solid_history_operation(handle) else {
|
||||
let Some(operation) = primitive_property_operation(document, handle) else {
|
||||
return brep_properties(document, handle);
|
||||
};
|
||||
match operation {
|
||||
match &operation {
|
||||
SolidHistoryOperation::Box(value) => {
|
||||
rectangular_properties(document, handle, value, "Box")
|
||||
}
|
||||
@@ -3086,6 +3103,149 @@ fn grip(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fillet_radius_grip(body: &Body, radius: f64) -> Option<GripDef> {
|
||||
if !radius.is_finite() || radius <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
for (face_key, face) in body.faces.iter() {
|
||||
let Some(Surface::Cylinder(cylinder)) = body.surfaces.get(face.surface) else {
|
||||
continue;
|
||||
};
|
||||
if (cylinder.radius.abs() - radius).abs() > radius.max(1.0) * 1.0e-7 {
|
||||
continue;
|
||||
}
|
||||
let axis = glam::DVec3::from_array(cylinder.base.normal()?).try_normalize()?;
|
||||
let origin = glam::DVec3::from_array(cylinder.base.origin);
|
||||
let points = body
|
||||
.face_coedges(face_key)
|
||||
.into_iter()
|
||||
.filter_map(|coedge| body.coedges.get(coedge))
|
||||
.filter_map(|coedge| body.edges.get(coedge.edge))
|
||||
.flat_map(|edge| [edge.start, edge.end])
|
||||
.filter_map(|vertex| body.vertices.get(vertex))
|
||||
.map(|vertex| glam::DVec3::from_array(vertex.point))
|
||||
.collect::<Vec<_>>();
|
||||
let minimum = points
|
||||
.iter()
|
||||
.map(|point| (*point - origin).dot(axis))
|
||||
.reduce(f64::min)?;
|
||||
let maximum = points
|
||||
.iter()
|
||||
.map(|point| (*point - origin).dot(axis))
|
||||
.reduce(f64::max)?;
|
||||
let center = origin + axis * ((minimum + maximum) * 0.5);
|
||||
let radial = points
|
||||
.iter()
|
||||
.map(|point| *point - center - axis * (*point - center).dot(axis))
|
||||
.find_map(|radial| radial.try_normalize())?;
|
||||
return Some(grip(
|
||||
GRIP_FILLET_RADIUS,
|
||||
center + radial * radius,
|
||||
GripShape::Square,
|
||||
Some(radial),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn chamfer_distance_grips(
|
||||
document: &acadrust::CadDocument,
|
||||
handle: acadrust::Handle,
|
||||
value: &SolidHistoryChamfer,
|
||||
) -> Vec<GripDef> {
|
||||
let Some(operations) = document.solid_history_operations(handle) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let Some(chamfer_index) = operations
|
||||
.iter()
|
||||
.rposition(|operation| matches!(operation, SolidHistoryOperation::Chamfer(_)))
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
let Ok(source) = cadkernel::acis::rebuild_history(&operations[..chamfer_index]) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let edges = source.edge_keys().collect::<Vec<_>>();
|
||||
let faces = source.face_keys().collect::<Vec<_>>();
|
||||
let Some(edge) = value
|
||||
.edges
|
||||
.first()
|
||||
.and_then(|ordinal| usize::try_from(*ordinal).ok())
|
||||
.and_then(|ordinal| edges.get(ordinal).copied())
|
||||
.and_then(|edge| source.edges.get(edge))
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
let Some(base_face) = usize::try_from(value.base_face)
|
||||
.ok()
|
||||
.and_then(|ordinal| faces.get(ordinal).copied())
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
let adjacent = edge
|
||||
.coedges
|
||||
.iter()
|
||||
.filter_map(|coedge| source.coedges.get(*coedge))
|
||||
.filter_map(|coedge| source.loops.get(coedge.owner))
|
||||
.map(|edge_loop| edge_loop.owner)
|
||||
.collect::<Vec<_>>();
|
||||
let Some(other_face) = adjacent.iter().copied().find(|face| *face != base_face) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let outward = |face_key| {
|
||||
let face = source.faces.get(face_key)?;
|
||||
let Surface::Plane(plane) = source.surfaces.get(face.surface)? else {
|
||||
return None;
|
||||
};
|
||||
let normal = glam::DVec3::from_array(plane.normal()?).try_normalize()?;
|
||||
Some(if face.forward { normal } else { -normal })
|
||||
};
|
||||
let Some(base_normal) = outward(base_face) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let Some(other_normal) = outward(other_face) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let dot = base_normal.dot(other_normal);
|
||||
let Some(base_inward) = (-(other_normal - base_normal * dot)).try_normalize() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let Some(other_inward) = (-(base_normal - other_normal * dot)).try_normalize() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let Some(start) = source.vertices.get(edge.start) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let Some(end) = source.vertices.get(edge.end) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let midpoint =
|
||||
(glam::DVec3::from_array(start.point) + glam::DVec3::from_array(end.point)) * 0.5;
|
||||
[
|
||||
(
|
||||
GRIP_CHAMFER_DISTANCE1,
|
||||
value.base_distance,
|
||||
base_inward,
|
||||
),
|
||||
(
|
||||
GRIP_CHAMFER_DISTANCE2,
|
||||
value.other_distance,
|
||||
other_inward,
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|(_, distance, _)| distance.is_finite() && *distance > 0.0)
|
||||
.map(|(id, distance, axis)| {
|
||||
grip(
|
||||
id,
|
||||
midpoint + axis * distance,
|
||||
GripShape::Square,
|
||||
Some(axis),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn primitive_grips(
|
||||
document: &acadrust::CadDocument,
|
||||
handle: acadrust::Handle,
|
||||
@@ -3093,6 +3253,21 @@ pub fn primitive_grips(
|
||||
let Some(operation) = document.solid_history_operation(handle) else {
|
||||
return Vec::new();
|
||||
};
|
||||
if let SolidHistoryOperation::Fillet(value) = operation {
|
||||
let Some(radius) = value.radii.first().copied() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let Some(EntityType::Solid3D(solid)) = document.get_entity(handle) else {
|
||||
return Vec::new();
|
||||
};
|
||||
return crate::scene::convert::solid3d_tess::kernel_body(solid)
|
||||
.and_then(|body| fillet_radius_grip(&body, radius))
|
||||
.into_iter()
|
||||
.collect();
|
||||
}
|
||||
if let SolidHistoryOperation::Chamfer(value) = operation {
|
||||
return chamfer_distance_grips(document, handle, value);
|
||||
}
|
||||
let mut grips = Vec::new();
|
||||
let mut add = |id, transform, point, shape, axis: Option<[f64; 3]>| {
|
||||
if let Some(world) = world_point(transform, point) {
|
||||
@@ -3713,3 +3888,32 @@ pub fn brep_op(body: &Body) -> SolidHistoryOperation {
|
||||
..SolidHistoryBrep::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fillet_op(edges: Vec<i32>, radius: f64) -> SolidHistoryOperation {
|
||||
SolidHistoryOperation::Fillet(SolidHistoryFillet {
|
||||
base: base(glam::DMat4::IDENTITY.to_cols_array()),
|
||||
operation_major: 1,
|
||||
method: 0,
|
||||
edges,
|
||||
radii: vec![radius],
|
||||
..SolidHistoryFillet::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn chamfer_op(
|
||||
edges: Vec<i32>,
|
||||
base_face: i32,
|
||||
base_distance: f64,
|
||||
other_distance: f64,
|
||||
) -> SolidHistoryOperation {
|
||||
SolidHistoryOperation::Chamfer(SolidHistoryChamfer {
|
||||
base: base(glam::DMat4::IDENTITY.to_cols_array()),
|
||||
operation_major: 1,
|
||||
method: 0,
|
||||
base_distance,
|
||||
other_distance,
|
||||
edges,
|
||||
base_face,
|
||||
..SolidHistoryChamfer::default()
|
||||
})
|
||||
}
|
||||
+135
-6
@@ -802,11 +802,39 @@ impl Scene {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn append_solid_history(
|
||||
&mut self,
|
||||
handle: Handle,
|
||||
operation: acadrust::objects::SolidHistoryOperation,
|
||||
) -> bool {
|
||||
let previous = self
|
||||
.document
|
||||
.solid_history_graph(handle)
|
||||
.map(|graph| graph.nodes)
|
||||
.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 {
|
||||
if !previous.contains(&node) {
|
||||
self.record_undo_object_before(node, None);
|
||||
}
|
||||
}
|
||||
self.sync_solid_reference_point(handle);
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn sync_solid_reference_point(&mut self, handle: Handle) {
|
||||
let reference = self
|
||||
.document
|
||||
.solid_history_operation(handle)
|
||||
.and_then(crate::scene::model::solid_history::reference_point);
|
||||
.solid_history_operations(handle)
|
||||
.and_then(|operations| {
|
||||
operations
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(crate::scene::model::solid_history::reference_point)
|
||||
});
|
||||
let Some(reference) = reference else {
|
||||
return;
|
||||
};
|
||||
@@ -855,8 +883,31 @@ impl Scene {
|
||||
(EntityType::Surface(_), SolidHistoryOperation::Extrusion(value)) => {
|
||||
cadkernel::acis::rebuild_extrusion_with_mode(value, true).ok()
|
||||
}
|
||||
(EntityType::Surface(_), SolidHistoryOperation::Loft(_))
|
||||
| (EntityType::Solid3D(_), _) => cadkernel::acis::rebuild_body(operation).ok(),
|
||||
(EntityType::Surface(_), SolidHistoryOperation::Loft(_)) => {
|
||||
cadkernel::acis::rebuild_body(operation).ok()
|
||||
}
|
||||
(EntityType::Solid3D(_), _) => {
|
||||
let mut operations = self.document.solid_history_operations(handle)?;
|
||||
let replacement_id = operation.base().map(|base| {
|
||||
if base.eval.node_id > 0 {
|
||||
base.eval.node_id
|
||||
} else {
|
||||
base.step_id
|
||||
}
|
||||
})?;
|
||||
let target = operations.iter_mut().find(|candidate| {
|
||||
candidate.base().is_some_and(|base| {
|
||||
let node_id = if base.eval.node_id > 0 {
|
||||
base.eval.node_id
|
||||
} else {
|
||||
base.step_id
|
||||
};
|
||||
node_id == replacement_id
|
||||
})
|
||||
})?;
|
||||
*target = operation.clone();
|
||||
cadkernel::acis::rebuild_history(&operations).ok()
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -909,7 +960,7 @@ impl Scene {
|
||||
self.record_solid_history_before(handle);
|
||||
if self
|
||||
.document
|
||||
.update_solid_history(handle, operation)
|
||||
.update_solid_history_step(handle, operation)
|
||||
.is_none()
|
||||
{
|
||||
return false;
|
||||
@@ -1122,6 +1173,79 @@ impl Scene {
|
||||
let Some(mut operation) = self.document.solid_history_operation(handle).cloned() else {
|
||||
return false;
|
||||
};
|
||||
if grip_id == crate::scene::model::solid_history::GRIP_FILLET_RADIUS {
|
||||
let acadrust::objects::SolidHistoryOperation::Fillet(value) = &mut operation else {
|
||||
return false;
|
||||
};
|
||||
let Some(radius) = value.radii.first().copied() else {
|
||||
return false;
|
||||
};
|
||||
self.restore_solid_models(&[handle]);
|
||||
let Some(definition) = self
|
||||
.solid_models
|
||||
.get(&handle)
|
||||
.and_then(|body| {
|
||||
crate::scene::model::solid_history::fillet_radius_grip(body, radius)
|
||||
})
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let Some(axis) = definition.axis else {
|
||||
return false;
|
||||
};
|
||||
let change = match apply {
|
||||
GripApply::Absolute(world) => (world - definition.world).dot(axis),
|
||||
GripApply::Translate(delta) => delta.dot(axis),
|
||||
};
|
||||
let radius = radius + change;
|
||||
if !radius.is_finite() || radius <= 1.0e-6 {
|
||||
return false;
|
||||
}
|
||||
value.radii.clear();
|
||||
value.radii.push(radius);
|
||||
return self.preview_solid_history(handle, operation);
|
||||
}
|
||||
if matches!(
|
||||
grip_id,
|
||||
crate::scene::model::solid_history::GRIP_CHAMFER_DISTANCE1
|
||||
| crate::scene::model::solid_history::GRIP_CHAMFER_DISTANCE2
|
||||
) {
|
||||
let definitions = {
|
||||
let acadrust::objects::SolidHistoryOperation::Chamfer(value) = &operation else {
|
||||
return false;
|
||||
};
|
||||
crate::scene::model::solid_history::chamfer_distance_grips(
|
||||
&self.document,
|
||||
handle,
|
||||
value,
|
||||
)
|
||||
};
|
||||
let Some(definition) = definitions.into_iter().find(|grip| grip.id == grip_id) else {
|
||||
return false;
|
||||
};
|
||||
let Some(axis) = definition.axis else {
|
||||
return false;
|
||||
};
|
||||
let change = match apply {
|
||||
GripApply::Absolute(world) => (world - definition.world).dot(axis),
|
||||
GripApply::Translate(delta) => delta.dot(axis),
|
||||
};
|
||||
let acadrust::objects::SolidHistoryOperation::Chamfer(value) = &mut operation else {
|
||||
return false;
|
||||
};
|
||||
let distance = if grip_id
|
||||
== crate::scene::model::solid_history::GRIP_CHAMFER_DISTANCE1
|
||||
{
|
||||
&mut value.base_distance
|
||||
} else {
|
||||
&mut value.other_distance
|
||||
};
|
||||
*distance += change;
|
||||
if !distance.is_finite() || *distance <= 1.0e-6 {
|
||||
return false;
|
||||
}
|
||||
return self.preview_solid_history(handle, operation);
|
||||
}
|
||||
if !crate::scene::model::solid_history::apply_primitive_grip(
|
||||
&mut operation,
|
||||
grip_id,
|
||||
@@ -1138,7 +1262,12 @@ impl Scene {
|
||||
field: &str,
|
||||
value: &str,
|
||||
) -> bool {
|
||||
let Some(mut operation) = self.document.solid_history_operation(handle).cloned() else {
|
||||
let Some(mut operation) =
|
||||
crate::scene::model::solid_history::primitive_property_operation(
|
||||
&self.document,
|
||||
handle,
|
||||
)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if !crate::scene::model::solid_history::apply_primitive_property(
|
||||
|
||||
Reference in new issue
Block a user