forked from 1b-agent/opencadstudio-upstream
Merge pull request #1284 from ramox81/codex/autoconstrain-parity
# Conflicts: # Cargo.lock # Cargo.toml # src/app/view/mod.rs # src/ui/overlay.rs
This commit is contained in:
commit
c9eeb6f66e
23 files changed
+1461
-99
No files matched your search
@@ -83,6 +83,45 @@ impl ParametricRef {
|
||||
}
|
||||
}
|
||||
|
||||
/// Point reference driven by a native entity grip. Segment midpoint and
|
||||
/// scalar radius grips deliberately return None because they do not identify
|
||||
/// one stored point parameter.
|
||||
pub(crate) fn driven_ref_for_grip(
|
||||
entity: &acadrust::EntityType,
|
||||
handle: Handle,
|
||||
grip_id: usize,
|
||||
) -> Option<ParametricRef> {
|
||||
match entity {
|
||||
acadrust::EntityType::Line(_) if grip_id <= 1 => {
|
||||
Some(ParametricRef::point(handle, grip_id as i32))
|
||||
}
|
||||
acadrust::EntityType::LwPolyline(polyline) if grip_id < polyline.vertices.len() => {
|
||||
Some(ParametricRef::point(handle, grip_id as i32))
|
||||
}
|
||||
acadrust::EntityType::Polyline2D(polyline) if grip_id < polyline.vertices.len() => {
|
||||
Some(ParametricRef::point(handle, grip_id as i32))
|
||||
}
|
||||
acadrust::EntityType::Arc(_) => match grip_id {
|
||||
0 => Some(ParametricRef::center(handle)),
|
||||
1 => Some(ParametricRef::point(handle, 0)),
|
||||
2 => Some(ParametricRef::point(handle, 1)),
|
||||
_ => None,
|
||||
},
|
||||
acadrust::EntityType::Circle(_) if grip_id == 0 => {
|
||||
Some(ParametricRef::center(handle))
|
||||
}
|
||||
acadrust::EntityType::Point(_)
|
||||
| acadrust::EntityType::Insert(_)
|
||||
| acadrust::EntityType::Text(_)
|
||||
| acadrust::EntityType::MText(_)
|
||||
if grip_id == 0 =>
|
||||
{
|
||||
Some(ParametricRef::point(handle, 0))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The friendly, user-facing constraint types — the "what button did they
|
||||
/// click" vocabulary, one layer above the `cadkernel_constraints` primitives each maps
|
||||
/// onto (that mapping is `constraint_map`, a later stage; see the design
|
||||
@@ -576,6 +615,19 @@ impl super::Scene {
|
||||
!self.hidden_parametric_constraints.contains(&(scope, id))
|
||||
}
|
||||
|
||||
pub fn should_display_parametric_constraint(
|
||||
&self,
|
||||
scope: ParametricScope,
|
||||
id: ConstraintId,
|
||||
related_entity_selected: bool,
|
||||
display_mode: i16,
|
||||
) -> bool {
|
||||
self.is_parametric_constraint_visible(scope, id)
|
||||
&& (self.shown_parametric_constraints.contains(&(scope, id))
|
||||
|| display_mode & 1 != 0
|
||||
|| (display_mode & 2 != 0 && related_entity_selected))
|
||||
}
|
||||
|
||||
pub fn set_parametric_constraint_visibility(
|
||||
&mut self,
|
||||
scope: ParametricScope,
|
||||
@@ -601,8 +653,10 @@ impl super::Scene {
|
||||
for id in &ids {
|
||||
if visible {
|
||||
self.hidden_parametric_constraints.remove(&(scope, *id));
|
||||
self.shown_parametric_constraints.insert((scope, *id));
|
||||
} else {
|
||||
self.hidden_parametric_constraints.insert((scope, *id));
|
||||
self.shown_parametric_constraints.remove(&(scope, *id));
|
||||
}
|
||||
}
|
||||
ids.len()
|
||||
@@ -613,40 +667,155 @@ impl super::Scene {
|
||||
&self,
|
||||
scope: ParametricScope,
|
||||
handles: &[Handle],
|
||||
settings: &crate::app::settings::AutoConstrainSettings,
|
||||
) -> Vec<(ConstraintKind, Vec<ParametricRef>)> {
|
||||
use cadkernel::geom2d::{
|
||||
infer_constraints, Arc, Circle, ConstraintEndpoint, InferredConstraint, Line,
|
||||
ParametricPrimitive, Tolerance,
|
||||
infer_constraints_with_settings, Arc, Circle, ConstraintEndpoint, InferenceKind,
|
||||
InferenceSettings, InferredConstraint, Line, ParametricPrimitive,
|
||||
};
|
||||
struct Source {
|
||||
handle: Handle,
|
||||
primitive: ParametricPrimitive,
|
||||
whole: ParametricRef,
|
||||
endpoints: [ParametricRef; 2],
|
||||
}
|
||||
let mut sources = Vec::new();
|
||||
for handle in handles {
|
||||
let primitive = match self.document.get_entity(*handle) {
|
||||
Some(acadrust::EntityType::Line(line)) => ParametricPrimitive::Line(Line {
|
||||
start: [line.start.x, line.start.y],
|
||||
end: [line.end.x, line.end.y],
|
||||
match self.document.get_entity(*handle) {
|
||||
Some(acadrust::EntityType::Line(line)) => sources.push(Source {
|
||||
handle: *handle,
|
||||
primitive: ParametricPrimitive::Line(Line {
|
||||
start: [line.start.x, line.start.y],
|
||||
end: [line.end.x, line.end.y],
|
||||
}),
|
||||
whole: ParametricRef::whole(*handle),
|
||||
endpoints: [
|
||||
ParametricRef::point(*handle, 0),
|
||||
ParametricRef::point(*handle, 1),
|
||||
],
|
||||
}),
|
||||
Some(acadrust::EntityType::Circle(circle)) => ParametricPrimitive::Circle(Circle {
|
||||
centre: [circle.center.x, circle.center.y],
|
||||
radius: circle.radius,
|
||||
Some(acadrust::EntityType::Circle(circle)) => sources.push(Source {
|
||||
handle: *handle,
|
||||
primitive: ParametricPrimitive::Circle(Circle {
|
||||
centre: [circle.center.x, circle.center.y],
|
||||
radius: circle.radius,
|
||||
}),
|
||||
whole: ParametricRef::whole(*handle),
|
||||
endpoints: [
|
||||
ParametricRef::center(*handle),
|
||||
ParametricRef::center(*handle),
|
||||
],
|
||||
}),
|
||||
Some(acadrust::EntityType::Arc(arc)) => ParametricPrimitive::Arc(Arc {
|
||||
centre: [arc.center.x, arc.center.y],
|
||||
radius: arc.radius,
|
||||
start_angle: arc.start_angle,
|
||||
end_angle: arc.end_angle,
|
||||
Some(acadrust::EntityType::Arc(arc)) => sources.push(Source {
|
||||
handle: *handle,
|
||||
primitive: ParametricPrimitive::Arc(Arc {
|
||||
centre: [arc.center.x, arc.center.y],
|
||||
radius: arc.radius,
|
||||
start_angle: arc.start_angle,
|
||||
end_angle: arc.end_angle,
|
||||
}),
|
||||
whole: ParametricRef::whole(*handle),
|
||||
endpoints: [
|
||||
ParametricRef::point(*handle, 0),
|
||||
ParametricRef::point(*handle, 1),
|
||||
],
|
||||
}),
|
||||
Some(acadrust::EntityType::LwPolyline(polyline)) => {
|
||||
let Some(world) = crate::entities::curve::lwpolyline_world_xy(polyline) else {
|
||||
continue;
|
||||
};
|
||||
let count = world.vertices.len();
|
||||
for index in 0..count {
|
||||
let next = index + 1;
|
||||
if next >= count && !world.is_closed {
|
||||
break;
|
||||
}
|
||||
if world.vertices[index].bulge.abs() > 1e-9 {
|
||||
continue;
|
||||
}
|
||||
let a = world.vertices[index].location;
|
||||
let b = world.vertices[next % count].location;
|
||||
sources.push(Source {
|
||||
handle: *handle,
|
||||
primitive: ParametricPrimitive::Line(Line {
|
||||
start: [a.x, a.y],
|
||||
end: [b.x, b.y],
|
||||
}),
|
||||
whole: ParametricRef::segment(*handle, index),
|
||||
endpoints: [
|
||||
ParametricRef::point(*handle, index as i32),
|
||||
ParametricRef::point(*handle, (next % count) as i32),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
Some(acadrust::EntityType::Polyline2D(polyline)) => {
|
||||
let count = polyline.vertices.len();
|
||||
for index in 0..count {
|
||||
let next = index + 1;
|
||||
if next >= count && !polyline.is_closed() {
|
||||
break;
|
||||
}
|
||||
if polyline.vertices[index].bulge.abs() > 1e-9 {
|
||||
continue;
|
||||
}
|
||||
let a = polyline.vertices[index].location;
|
||||
let b = polyline.vertices[next % count].location;
|
||||
sources.push(Source {
|
||||
handle: *handle,
|
||||
primitive: ParametricPrimitive::Line(Line {
|
||||
start: [a.x, a.y],
|
||||
end: [b.x, b.y],
|
||||
}),
|
||||
whole: ParametricRef::segment(*handle, index),
|
||||
endpoints: [
|
||||
ParametricRef::point(*handle, index as i32),
|
||||
ParametricRef::point(*handle, (next % count) as i32),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
sources.push((*handle, primitive));
|
||||
}
|
||||
}
|
||||
let primitives: Vec<_> = sources.iter().map(|(_, primitive)| *primitive).collect();
|
||||
let primitives: Vec<_> = sources.iter().map(|source| source.primitive).collect();
|
||||
let marker = |endpoint| match endpoint {
|
||||
ConstraintEndpoint::Start => 0,
|
||||
ConstraintEndpoint::End => 1,
|
||||
ConstraintEndpoint::Start => 0usize,
|
||||
ConstraintEndpoint::End => 1usize,
|
||||
};
|
||||
let kind = |kind| match kind {
|
||||
crate::app::settings::AutoConstraintKind::Coincident => InferenceKind::Coincident,
|
||||
crate::app::settings::AutoConstraintKind::Collinear => InferenceKind::Collinear,
|
||||
crate::app::settings::AutoConstraintKind::Parallel => InferenceKind::Parallel,
|
||||
crate::app::settings::AutoConstraintKind::Perpendicular => {
|
||||
InferenceKind::Perpendicular
|
||||
}
|
||||
crate::app::settings::AutoConstraintKind::Tangent => InferenceKind::Tangent,
|
||||
crate::app::settings::AutoConstraintKind::Concentric => InferenceKind::Concentric,
|
||||
crate::app::settings::AutoConstraintKind::Horizontal => InferenceKind::Horizontal,
|
||||
crate::app::settings::AutoConstraintKind::Vertical => InferenceKind::Vertical,
|
||||
crate::app::settings::AutoConstraintKind::Equal => InferenceKind::Equal,
|
||||
};
|
||||
let inference_settings = InferenceSettings {
|
||||
priority: settings
|
||||
.priority
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|candidate| settings.enabled.contains(candidate))
|
||||
.map(kind)
|
||||
.collect(),
|
||||
distance_tolerance: settings.distance_tolerance,
|
||||
angle_tolerance_radians: settings.angle_tolerance_deg.to_radians(),
|
||||
tangent_must_share_point: settings.tangent_must_share_point,
|
||||
perpendicular_must_intersect: settings.perpendicular_must_intersect,
|
||||
};
|
||||
let mut mapped: Vec<_> =
|
||||
infer_constraints(&primitives, Tolerance::new(1e-6), 0.5_f64.to_radians())
|
||||
infer_constraints_with_settings(&primitives, &inference_settings)
|
||||
.into_iter()
|
||||
.filter(|relation| {
|
||||
!matches!(relation, InferredConstraint::Coincident { first, second, .. }
|
||||
if sources[*first].handle == sources[*second].handle)
|
||||
})
|
||||
.map(|relation| match relation {
|
||||
InferredConstraint::Coincident {
|
||||
first,
|
||||
@@ -656,53 +825,57 @@ impl super::Scene {
|
||||
} => (
|
||||
ConstraintKind::Coincident,
|
||||
vec![
|
||||
ParametricRef::point(sources[first].0, marker(first_endpoint)),
|
||||
ParametricRef::point(sources[second].0, marker(second_endpoint)),
|
||||
sources[first].endpoints[marker(first_endpoint)],
|
||||
sources[second].endpoints[marker(second_endpoint)],
|
||||
],
|
||||
),
|
||||
InferredConstraint::Collinear { first, second } => (
|
||||
ConstraintKind::Colinear,
|
||||
vec![
|
||||
ParametricRef::whole(sources[first].0),
|
||||
ParametricRef::whole(sources[second].0),
|
||||
sources[first].whole,
|
||||
sources[second].whole,
|
||||
],
|
||||
),
|
||||
InferredConstraint::Concentric { first, second } => (
|
||||
ConstraintKind::Concentric,
|
||||
vec![
|
||||
ParametricRef::center(sources[first].0),
|
||||
ParametricRef::center(sources[second].0),
|
||||
ParametricRef::center(sources[first].handle),
|
||||
ParametricRef::center(sources[second].handle),
|
||||
],
|
||||
),
|
||||
InferredConstraint::Parallel { first, second } => (
|
||||
ConstraintKind::Parallel,
|
||||
vec![
|
||||
ParametricRef::whole(sources[first].0),
|
||||
ParametricRef::whole(sources[second].0),
|
||||
sources[first].whole,
|
||||
sources[second].whole,
|
||||
],
|
||||
),
|
||||
InferredConstraint::Perpendicular { first, second } => (
|
||||
ConstraintKind::Perpendicular,
|
||||
vec![
|
||||
ParametricRef::whole(sources[first].0),
|
||||
ParametricRef::whole(sources[second].0),
|
||||
sources[first].whole,
|
||||
sources[second].whole,
|
||||
],
|
||||
),
|
||||
InferredConstraint::Horizontal { entity } => (
|
||||
ConstraintKind::Horizontal,
|
||||
vec![ParametricRef::whole(sources[entity].0)],
|
||||
vec![sources[entity].whole],
|
||||
),
|
||||
InferredConstraint::Vertical { entity } => (
|
||||
ConstraintKind::Vertical,
|
||||
vec![ParametricRef::whole(sources[entity].0)],
|
||||
vec![sources[entity].whole],
|
||||
),
|
||||
InferredConstraint::Tangent { first, second } => (
|
||||
ConstraintKind::Tangent,
|
||||
vec![
|
||||
ParametricRef::whole(sources[first].0),
|
||||
ParametricRef::whole(sources[second].0),
|
||||
sources[first].whole,
|
||||
sources[second].whole,
|
||||
],
|
||||
),
|
||||
InferredConstraint::Equal { first, second } => (
|
||||
ConstraintKind::Equal,
|
||||
vec![sources[first].whole, sources[second].whole],
|
||||
),
|
||||
})
|
||||
.collect();
|
||||
if let Some(existing) = self.parametric_constraint_set(scope) {
|
||||
@@ -806,6 +979,7 @@ impl super::Scene {
|
||||
scope: ParametricScope,
|
||||
vp_size: (f32, f32),
|
||||
show_values: bool,
|
||||
display_mode: i16,
|
||||
) -> Vec<(ConstraintId, iced::Point, [f32; 2], String, bool)> {
|
||||
let Some(set) = self.parametric_constraint_set(scope) else {
|
||||
return Vec::new();
|
||||
@@ -827,7 +1001,13 @@ impl super::Scene {
|
||||
set.constraints
|
||||
.iter()
|
||||
.filter(|c| c.enabled)
|
||||
.filter(|c| self.is_parametric_constraint_visible(scope, c.id))
|
||||
.filter(|c| {
|
||||
let selected = c
|
||||
.refs
|
||||
.iter()
|
||||
.any(|reference| self.selected.contains(&reference.entity));
|
||||
self.should_display_parametric_constraint(scope, c.id, selected, display_mode)
|
||||
})
|
||||
.filter_map(|c| {
|
||||
let (anchor, outward) = glyph_placement(&self.document, c)?;
|
||||
let screen = crate::scene::pick::grip::project_rte(
|
||||
@@ -869,9 +1049,15 @@ impl super::Scene {
|
||||
scope: ParametricScope,
|
||||
vp_size: (f32, f32),
|
||||
show_values: bool,
|
||||
display_mode: i16,
|
||||
p: iced::Point,
|
||||
) -> Option<ConstraintId> {
|
||||
let placements = self.constraint_glyph_placements_screen(scope, vp_size, show_values);
|
||||
let placements = self.constraint_glyph_placements_screen(
|
||||
scope,
|
||||
vp_size,
|
||||
show_values,
|
||||
display_mode,
|
||||
);
|
||||
let glyphs: Vec<(iced::Point, [f32; 2], String, bool)> = placements
|
||||
.iter()
|
||||
.map(|(_, point, direction, label, is_conflicting)| {
|
||||
@@ -1158,6 +1344,24 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arc_grips_drive_center_start_and_end_but_not_midpoint() {
|
||||
let handle = h(8);
|
||||
let arc = acadrust::EntityType::Arc(acadrust::entities::Arc::from_coords(
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
5.0,
|
||||
0.0,
|
||||
std::f64::consts::PI,
|
||||
));
|
||||
|
||||
assert_eq!(driven_ref_for_grip(&arc, handle, 0), Some(ParametricRef::center(handle)));
|
||||
assert_eq!(driven_ref_for_grip(&arc, handle, 1), Some(ParametricRef::point(handle, 0)));
|
||||
assert_eq!(driven_ref_for_grip(&arc, handle, 2), Some(ParametricRef::point(handle, 1)));
|
||||
assert_eq!(driven_ref_for_grip(&arc, handle, 3), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parameter_usage_finds_every_constraint_driven_by_the_named_parameter() {
|
||||
let mut scene = super::super::Scene::new();
|
||||
@@ -1266,7 +1470,11 @@ mod tests {
|
||||
);
|
||||
|
||||
let inferred =
|
||||
scene.inferred_parametric_constraints(ParametricScope::ModelSpace, &[first, second]);
|
||||
scene.inferred_parametric_constraints(
|
||||
ParametricScope::ModelSpace,
|
||||
&[first, second],
|
||||
&crate::app::settings::AutoConstrainSettings::default(),
|
||||
);
|
||||
|
||||
assert!(!inferred.iter().any(|(kind, refs)| {
|
||||
*kind == ConstraintKind::Horizontal && *refs == [ParametricRef::whole(first)]
|
||||
@@ -1277,7 +1485,7 @@ mod tests {
|
||||
assert!(inferred
|
||||
.iter()
|
||||
.any(|(kind, _)| *kind == ConstraintKind::Coincident));
|
||||
assert!(inferred
|
||||
assert!(!inferred
|
||||
.iter()
|
||||
.any(|(kind, _)| *kind == ConstraintKind::Colinear));
|
||||
}
|
||||
@@ -1315,4 +1523,16 @@ mod tests {
|
||||
assert!(!scene.is_parametric_constraint_visible(ParametricScope::ModelSpace, geometric));
|
||||
assert!(scene.is_parametric_constraint_visible(ParametricScope::ModelSpace, dimensional));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hidden_constraint_stays_hidden_when_related_entity_is_selected() {
|
||||
let mut scene = super::super::Scene::new();
|
||||
let scope = ParametricScope::ModelSpace;
|
||||
let id = scene
|
||||
.parametric_constraint_set_mut(scope)
|
||||
.add(ConstraintKind::Horizontal, vec![ParametricRef::whole(h(1))], None);
|
||||
scene.hidden_parametric_constraints.insert((scope, id));
|
||||
|
||||
assert!(!scene.should_display_parametric_constraint(scope, id, true, 2));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user