forked from 1b-agent/opencadstudio-upstream
Use exact kernel curves for PRESSPULL area containment
This commit is contained in:
1 parent
eee077cbce
commit
d8790ef433
3 files changed
+59
-14
No files matched your search
@@ -237,7 +237,7 @@ impl super::OpenCADStudio {
|
|||||||
if let Some(command) = self.tabs[i].active_cmd.as_mut() {
|
if let Some(command) = self.tabs[i].active_cmd.as_mut() {
|
||||||
command.on_presspull_applied(true);
|
command.on_presspull_applied(true);
|
||||||
}
|
}
|
||||||
self.command_line.push_output(crate::tf!("PRESSPULL: %{count} object(s) updated.", count = handles.len()).as_ref());
|
self.command_line.push_output(crate::tf!("PRESSPULL: {count} object(s) updated.", count = handles.len()).as_ref());
|
||||||
self.refresh_properties();
|
self.refresh_properties();
|
||||||
self.refresh_presspull_prompt();
|
self.refresh_presspull_prompt();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,24 +141,20 @@ fn boundary_sources(scene: &Scene, plane: WorkingPlane) -> FxHashMap<Handle, Bou
|
|||||||
}).collect()
|
}).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ring_curves(ring: &[[f64; 2]]) -> Vec<Curve> {
|
|
||||||
ring.iter().copied().zip(ring.iter().copied().cycle().skip(1))
|
|
||||||
.take(ring.len()).map(|(start, end)| Curve::Line(Line { start, end })).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selected_rings(sources: &FxHashMap<Handle, BoundarySource>, point: [f64; 2]) -> Option<Vec<Vec<[f64; 2]>>> {
|
fn selected_rings(sources: &FxHashMap<Handle, BoundarySource>, point: [f64; 2]) -> Option<Vec<Vec<[f64; 2]>>> {
|
||||||
let rings = boundary_faces(sources, BOUNDARY_TOLERANCE);
|
let rings = boundary_faces(sources, BOUNDARY_TOLERANCE);
|
||||||
|
let loops = boundary_loops(sources, &rings)?;
|
||||||
let tolerance = Tolerance::new(BOUNDARY_TOLERANCE);
|
let tolerance = Tolerance::new(BOUNDARY_TOLERANCE);
|
||||||
let outer = rings.iter().enumerate()
|
let outer = rings.iter().enumerate()
|
||||||
.filter(|(_, ring)| contains(&ring_curves(ring), point, tolerance))
|
.filter(|(index, _)| contains(&loops[*index], point, tolerance))
|
||||||
.min_by(|(_, left), (_, right)| signed_area(left).abs().total_cmp(&signed_area(right).abs()))?
|
.min_by(|(_, left), (_, right)| signed_area(left).abs().total_cmp(&signed_area(right).abs()))?
|
||||||
.0;
|
.0;
|
||||||
let depths = ring_nesting_depths(&rings);
|
let depths = ring_nesting_depths(&rings);
|
||||||
let boundary = ring_curves(&rings[outer]);
|
let boundary = &loops[outer];
|
||||||
let mut result = vec![rings[outer].clone()];
|
let mut result = vec![rings[outer].clone()];
|
||||||
for (index, ring) in rings.iter().enumerate() {
|
for (index, ring) in rings.iter().enumerate() {
|
||||||
if index != outer && depths.get(index) == depths.get(outer).map(|depth| depth + 1).as_ref()
|
if index != outer && depths.get(index) == depths.get(outer).map(|depth| depth + 1).as_ref()
|
||||||
&& ring.first().is_some_and(|point| contains(&boundary, *point, tolerance))
|
&& ring.first().is_some_and(|point| contains(boundary, *point, tolerance))
|
||||||
{
|
{
|
||||||
result.push(ring.clone());
|
result.push(ring.clone());
|
||||||
}
|
}
|
||||||
@@ -166,17 +162,23 @@ fn selected_rings(sources: &FxHashMap<Handle, BoundarySource>, point: [f64; 2])
|
|||||||
Some(result)
|
Some(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn boundary_region(
|
fn boundary_loops(
|
||||||
sources: &FxHashMap<Handle, BoundarySource>, rings: &[Vec<[f64; 2]>], plane: WorkingPlane,
|
sources: &FxHashMap<Handle, BoundarySource>, rings: &[Vec<[f64; 2]>],
|
||||||
) -> Option<EntityType> {
|
) -> Option<Vec<Vec<Curve>>> {
|
||||||
let exterior = (0..rings.len()).map(|index| index == 0).collect::<Vec<_>>();
|
let exterior = (0..rings.len()).map(|index| index == 0).collect::<Vec<_>>();
|
||||||
let paths = exact_hatch_paths(rings, &exterior, sources, BOUNDARY_TOLERANCE);
|
let paths = exact_hatch_paths(rings, &exterior, sources, BOUNDARY_TOLERANCE);
|
||||||
if paths.len() != rings.len() {
|
if paths.len() != rings.len() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let loops = paths.iter().map(|path| {
|
paths.iter().map(|path| {
|
||||||
path.edges.iter().map(crate::entities::hatch::edge_curve).collect::<Option<Vec<_>>>()
|
path.edges.iter().map(crate::entities::hatch::edge_curve).collect::<Option<Vec<_>>>()
|
||||||
}).collect::<Option<Vec<_>>>()?;
|
}).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boundary_region(
|
||||||
|
sources: &FxHashMap<Handle, BoundarySource>, rings: &[Vec<[f64; 2]>], plane: WorkingPlane,
|
||||||
|
) -> Option<EntityType> {
|
||||||
|
let loops = boundary_loops(sources, rings)?;
|
||||||
let kernel_plane = Plane::from_axes(plane.origin.to_array(), plane.x.to_array(), plane.y.to_array());
|
let kernel_plane = Plane::from_axes(plane.origin.to_array(), plane.x.to_array(), plane.y.to_array());
|
||||||
let sheet = brep::planar_region(kernel_plane, &loops)?;
|
let sheet = brep::planar_region(kernel_plane, &loops)?;
|
||||||
let document = crate::scene::convert::acis_export::solid_to_sat(&sheet)?;
|
let document = crate::scene::convert::acis_export::solid_to_sat(&sheet)?;
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
use acadrust::{entities::Circle, types::Vector3, EntityType};
|
||||||
|
use glam::DVec3;
|
||||||
|
use OpenCADStudio::command::WorkingPlane;
|
||||||
|
use OpenCADStudio::scene::{model::presspull_model, Scene};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn area_picks_follow_exact_curves_and_keep_holes() {
|
||||||
|
let mut scene = Scene::new();
|
||||||
|
for radius in [1000.0, 500.0] {
|
||||||
|
scene
|
||||||
|
.document
|
||||||
|
.add_entity(EntityType::Circle(Circle::from_center_radius(
|
||||||
|
Vector3::new(0.0, 0.0, 0.0),
|
||||||
|
radius,
|
||||||
|
)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
// Halfway between display vertices, these points lie beyond the chords.
|
||||||
|
let angle = std::f64::consts::PI / 48.0;
|
||||||
|
for (radius, expected_loops) in [(999.0, 2), (499.0, 1)] {
|
||||||
|
let point = DVec3::new(radius * angle.cos(), radius * angle.sin(), 0.0);
|
||||||
|
let target =
|
||||||
|
presspull_model::resolve_target(&scene, None, point, WorkingPlane::default(), false)
|
||||||
|
.expect("point inside the exact circular boundary");
|
||||||
|
let presspull_model::PresspullTargetKind::Profile { entity, .. } = target.kind else {
|
||||||
|
panic!("expected a bounded profile");
|
||||||
|
};
|
||||||
|
let (_, loops, closed) = presspull_model::profile_geometry(&entity).unwrap();
|
||||||
|
assert!(closed);
|
||||||
|
assert_eq!(loops.len(), expected_loops);
|
||||||
|
assert!(loops
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.all(|curve| !matches!(curve, cadkernel::geom2d::Curve::Line(_))));
|
||||||
|
let body = presspull_model::extrusion_body(&entity, [0.0, 0.0, 10.0]).unwrap();
|
||||||
|
assert!(body.validate().is_empty());
|
||||||
|
}
|
||||||
|
let outside = DVec3::new(1001.0 * angle.cos(), 1001.0 * angle.sin(), 0.0);
|
||||||
|
assert!(
|
||||||
|
presspull_model::resolve_target(&scene, None, outside, WorkingPlane::default(), false,)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user