Complete Coincident command behavior

This commit is contained in:
ramox81 committed 2026-09-15 14:00:26 +03:00
1 parent dfe8b77cca
commit ba2b371269
13 files changed
+1183 -207

No files matched your search

+277 -43
View File
@@ -45,6 +45,7 @@ enum EntityGeom {
Polyline {
points: Rc<Vec<GPoint>>,
straight: Rc<Vec<bool>>,
arcs: Rc<Vec<Option<PolylineArc>>>,
closed: bool,
},
Circle(GCircle),
@@ -61,6 +62,12 @@ enum EntityGeom {
},
}
#[derive(Clone, Copy)]
struct PolylineArc {
arc: GArc,
sweep: f64,
}
impl EntityGeom {
/// The `cadkernel_constraints::geo::Point` for a marker on this entity — `None` for
/// a marker this entity type/value doesn't support (see
@@ -91,6 +98,7 @@ impl EntityGeom {
points,
straight,
closed,
..
} = self
else {
return None;
@@ -108,6 +116,13 @@ impl EntityGeom {
};
Some(GLine { p1, p2 })
}
fn arc_segment(&self, index: usize) -> Option<PolylineArc> {
let EntityGeom::Polyline { arcs, .. } = self else {
return None;
};
arcs.get(index).copied().flatten()
}
}
/// `Tangent`/`Normal` only ever care about "a whole circle-shaped curve" or
@@ -131,8 +146,14 @@ fn as_circle_or_line(g: EntityGeom, reference: ParametricRef) -> CircleOrLine {
EntityGeom::Spline { .. } => CircleOrLine::Other,
EntityGeom::Polyline { .. } => reference
.segment_index()
.and_then(|index| g.line_segment(index))
.map(CircleOrLine::Line)
.and_then(|index| {
g.line_segment(index)
.map(CircleOrLine::Line)
.or_else(|| {
g.arc_segment(index)
.map(|segment| CircleOrLine::Circle(segment.arc.circle))
})
})
.unwrap_or(CircleOrLine::Other),
}
}
@@ -182,42 +203,114 @@ fn register_entity(
sys.add_param(table.insertion_point.x, false),
sys.add_param(table.insertion_point.y, false),
))),
EntityType::LwPolyline(polyline) => Some(EntityGeom::Polyline {
points: Rc::new(
super::dimension_assoc::source_points(entity)
.into_iter()
.map(|point| {
GPoint::new(sys.add_param(point.x, false), sys.add_param(point.y, false))
EntityType::LwPolyline(polyline) => {
let source = super::dimension_assoc::source_points(entity);
let points: Vec<_> = source
.iter()
.map(|point| {
GPoint::new(sys.add_param(point.x, false), sys.add_param(point.y, false))
})
.collect();
let arcs = (0..polyline.vertices.len())
.map(|index| {
let next = if index + 1 < source.len() {
index + 1
} else if polyline.is_closed {
0
} else {
return None;
};
let bulge = polyline.vertices[index].bulge;
let shape = cadkernel::geom2d::BulgeArc::from_bulge(
[source[index].x, source[index].y],
[source[next].x, source[next].y],
bulge,
)?;
Some(PolylineArc {
arc: GArc {
circle: GCircle {
center: GPoint::new(
sys.add_param(shape.center[0], false),
sys.add_param(shape.center[1], false),
),
rad: sys.add_param(shape.radius, false),
},
start: points[index],
end: points[next],
start_angle: sys.add_param(shape.start_angle, false),
end_angle: sys.add_param(shape.start_angle + shape.sweep, false),
},
sweep: shape.sweep,
})
.collect(),
),
straight: Rc::new(
polyline
.vertices
.iter()
.map(|vertex| vertex.bulge.abs() <= 1e-9)
.collect(),
),
closed: polyline.is_closed,
}),
EntityType::Polyline2D(polyline) => Some(EntityGeom::Polyline {
points: Rc::new(
super::dimension_assoc::source_points(entity)
.into_iter()
.map(|point| {
GPoint::new(sys.add_param(point.x, false), sys.add_param(point.y, false))
})
.collect();
Some(EntityGeom::Polyline {
points: Rc::new(points),
straight: Rc::new(
polyline
.vertices
.iter()
.map(|vertex| vertex.bulge.abs() <= 1e-9)
.collect(),
),
arcs: Rc::new(arcs),
closed: polyline.is_closed,
})
}
EntityType::Polyline2D(polyline) => {
let source = super::dimension_assoc::source_points(entity);
let points: Vec<_> = source
.iter()
.map(|point| {
GPoint::new(sys.add_param(point.x, false), sys.add_param(point.y, false))
})
.collect();
let arcs = (0..polyline.vertices.len())
.map(|index| {
let next = if index + 1 < source.len() {
index + 1
} else if polyline.is_closed() {
0
} else {
return None;
};
let bulge = polyline.vertices[index].bulge;
let shape = cadkernel::geom2d::BulgeArc::from_bulge(
[source[index].x, source[index].y],
[source[next].x, source[next].y],
bulge,
)?;
Some(PolylineArc {
arc: GArc {
circle: GCircle {
center: GPoint::new(
sys.add_param(shape.center[0], false),
sys.add_param(shape.center[1], false),
),
rad: sys.add_param(shape.radius, false),
},
start: points[index],
end: points[next],
start_angle: sys.add_param(shape.start_angle, false),
end_angle: sys.add_param(shape.start_angle + shape.sweep, false),
},
sweep: shape.sweep,
})
.collect(),
),
straight: Rc::new(
polyline
.vertices
.iter()
.map(|vertex| vertex.bulge.abs() <= 1e-9)
.collect(),
),
closed: polyline.is_closed(),
}),
})
.collect();
Some(EntityGeom::Polyline {
points: Rc::new(points),
straight: Rc::new(
polyline
.vertices
.iter()
.map(|vertex| vertex.bulge.abs() <= 1e-9)
.collect(),
),
arcs: Rc::new(arcs),
closed: polyline.is_closed(),
})
}
EntityType::Line(l) => {
let p1 = GPoint::new(
sys.add_param(l.start.x, false),
@@ -439,6 +532,42 @@ fn resolve_constraint_point(
)));
return Some(point);
}
if let Some(segment) = reference
.segment_midpoint_index()
.and_then(|index| geometry.arc_segment(index))
{
let (start, end) = {
let store = sys.store();
(
store.get(segment.arc.start_angle),
store.get(segment.arc.end_angle),
)
};
let parameter_value = (start + end) * 0.5;
let parameter = sys.add_param(parameter_value, false);
sys.add_constraint(Rc::new(CenterOfGravity::new(
parameter,
vec![segment.arc.start_angle, segment.arc.end_angle],
vec![0.5, 0.5],
)));
let value = cadkernel_constraints::geo::Curve::value(
&segment.arc,
sys.store(),
parameter_value,
0.0,
None,
);
let point = GPoint::new(sys.add_param(value.x, false), sys.add_param(value.y, false));
let curve = Rc::new(segment.arc);
sys.add_constraint(Rc::new(CurveValue::new(
point,
point.x,
curve.clone(),
parameter,
)));
sys.add_constraint(Rc::new(CurveValue::new(point, point.y, curve, parameter)));
return Some(point);
}
if marker != -2 {
return None;
}
@@ -935,8 +1064,22 @@ fn build_constraint(
}
EntityGeom::Polyline { .. } => b
.segment_index()
.and_then(|index| geom.line_segment(index))
.map(|line| vec![Rc::new(PointOnLine::new(p, line)) as Rc<dyn Constraint>])
.and_then(|index| {
geom.line_segment(index)
.map(|line| {
vec![Rc::new(PointOnLine::new(p, line)) as Rc<dyn Constraint>]
})
.or_else(|| {
geom.arc_segment(index).map(|segment| {
let zero = sys.add_param(0.0, true);
vec![Rc::new(P2CDistance::new(
segment.arc.circle,
p,
zero,
)) as Rc<dyn Constraint>]
})
})
})
.unwrap_or_default(),
EntityGeom::Point(_) => Vec::new(),
}
@@ -1564,6 +1707,7 @@ fn solve_scope(
document: &acadrust::CadDocument,
drawing_params: &ParameterTable,
set: &ParametricConstraintSet,
driven_refs: &[ParametricRef],
) -> Option<SolveResult> {
let params = if set.local_parameters.is_empty() {
drawing_params
@@ -1604,9 +1748,8 @@ fn solve_scope(
// its endpoint. Not tracked in `owner`: these are solver-internal
// bookkeeping, never redundant with anything a user-facing constraint
// could name, so there's no `ConstraintId` for them to report against.
for geom in cache.values() {
let EntityGeom::Arc(arc) = geom else { continue };
let curve = Rc::new(*arc);
let add_arc_rules = |sys: &mut System, arc: GArc, sweep: Option<f64>| {
let curve = Rc::new(arc);
sys.add_constraint(Rc::new(CurveValue::new(
arc.start,
arc.start.x,
@@ -1631,6 +1774,25 @@ fn solve_scope(
curve,
arc.end_angle,
)));
if let Some(sweep) = sweep {
let target = sys.add_param(sweep, true);
sys.add_constraint(Rc::new(Difference::new(
arc.start_angle,
arc.end_angle,
target,
)));
}
};
for geom in cache.values() {
match geom {
EntityGeom::Arc(arc) => add_arc_rules(&mut sys, *arc, None),
EntityGeom::Polyline { arcs, .. } => {
for segment in arcs.iter().flatten() {
add_arc_rules(&mut sys, segment.arc, Some(segment.sweep));
}
}
_ => {}
}
}
// Ellipse rules (same motivation as arc rules, different mechanism):
@@ -1667,6 +1829,66 @@ fn solve_scope(
sys.add_constraint(Rc::new(Difference::new(el.center.y, el.focus1.y, dy)));
}
// The first ordered Coincident selection is the anchor for this solve.
// Pin either its addressed point or every defining parameter of its whole
// curve temporarily; the relation remains persistent, while these pins do
// not enter the drawing's constraint graph.
for reference in driven_refs {
if !set
.constraints
.iter()
.any(|constraint| constraint.refs.iter().any(|item| item.entity == reference.entity))
{
continue;
}
let params_to_pin = if reference.marker.is_some() && reference.segment_index().is_none() {
resolve_constraint_point(document, &mut sys, &mut cache, *reference)
.map(|point| vec![point.x, point.y])
.unwrap_or_default()
} else {
match resolve_ref(document, &mut sys, &mut cache, *reference) {
Some(EntityGeom::Point(point)) => vec![point.x, point.y],
Some(EntityGeom::Line(line))
| Some(EntityGeom::Ray(line))
| Some(EntityGeom::XLine(line)) => {
vec![line.p1.x, line.p1.y, line.p2.x, line.p2.y]
}
Some(EntityGeom::Polyline { points, .. }) => points
.iter()
.flat_map(|point| [point.x, point.y])
.collect(),
Some(EntityGeom::Circle(circle)) => {
vec![circle.center.x, circle.center.y, circle.rad]
}
Some(EntityGeom::Arc(arc)) => vec![
arc.circle.center.x,
arc.circle.center.y,
arc.circle.rad,
arc.start_angle,
arc.end_angle,
],
Some(EntityGeom::Ellipse(ellipse)) => vec![
ellipse.center.x,
ellipse.center.y,
ellipse.focus1.x,
ellipse.focus1.y,
ellipse.radmin,
],
Some(EntityGeom::Spline { curve, .. }) => curve
.poles
.iter()
.flat_map(|point| [point.x, point.y])
.collect(),
None => Vec::new(),
}
};
for parameter in params_to_pin {
let value = sys.store().get(parameter);
let target = sys.add_param(value, true);
sys.add_constraint(Rc::new(Equal::new(parameter, target, 1.0)));
}
}
let partitions = sys.partition();
for sub in &partitions {
solve_dl(sub, sys.store_mut());
@@ -1683,7 +1905,9 @@ fn solve_scope(
.map(|g| match g {
EntityGeom::Point(_) => 2,
EntityGeom::Line(_) | EntityGeom::Ray(_) | EntityGeom::XLine(_) => 4,
EntityGeom::Polyline { points, .. } => points.len() * 2,
EntityGeom::Polyline { points, arcs, .. } => {
points.len() * 2 + arcs.iter().flatten().count() * 5
}
EntityGeom::Circle(_) => 3,
// Raw param count (center×2, rad, start×2, end×2, both
// angles) — same "raw, not netted against its own
@@ -2151,9 +2375,18 @@ impl Scene {
/// the resulting `(Handle, ChangeKind::Modified)` entries the same way
/// `refresh_associative_dimensions`/`_hatches` do, for `bump_entities`
/// to fold into its own `changes` vec.
#[cfg(test)]
pub(crate) fn refresh_parametric_constraints(
&mut self,
changes: &[(Handle, ChangeKind)],
) -> Vec<(Handle, ChangeKind)> {
self.refresh_parametric_constraints_with_driven(changes, &[])
}
pub(crate) fn refresh_parametric_constraints_with_driven(
&mut self,
changes: &[(Handle, ChangeKind)],
driven_refs: &[ParametricRef],
) -> Vec<(Handle, ChangeKind)> {
// An erased entity takes its constraints with it instead of leaving
// dangling references. Done first,
@@ -2200,6 +2433,7 @@ impl Scene {
&self.document,
&self.named_parameters,
&self.parametric_constraints[i],
driven_refs,
) else {
continue;
};
@@ -2237,7 +2471,7 @@ impl Scene {
continue;
}
let Some((solved, _dof, _conflicts)) =
solve_scope(&self.document, &self.named_parameters, set)
solve_scope(&self.document, &self.named_parameters, set, &[])
else {
continue;
};