Complete automatic constraint behavior

This commit is contained in:
ramox81 committed 2026-09-15 11:47:28 +03:00
1 parent d738ebfcc6
commit 66be530655
25 files changed
+1494 -102

No files matched your search

+215 -3
View File
@@ -1549,6 +1549,44 @@ type SolveResult = (
)>,
);
fn retained_line_length(entity: &EntityType, segment: Option<usize>) -> Option<f64> {
match (entity, segment) {
(EntityType::Line(line), None) => Some((line.end - line.start).length()),
(EntityType::LwPolyline(polyline), Some(index)) => {
let world = crate::entities::curve::lwpolyline_world_xy(polyline)?;
let a = world.vertices.get(index)?.location;
let b = if let Some(vertex) = world.vertices.get(index + 1) {
vertex.location
} else if world.is_closed {
world.vertices.first()?.location
} else {
return None;
};
Some((b - a).length())
}
(EntityType::Polyline2D(polyline), Some(index)) => {
let a = polyline.vertices.get(index)?.location;
let b = if let Some(vertex) = polyline.vertices.get(index + 1) {
vertex.location
} else if polyline.is_closed() {
polyline.vertices.first()?.location
} else {
return None;
};
Some((b - a).length())
}
_ => None,
}
}
fn retained_radius(entity: &EntityType) -> Option<f64> {
match entity {
EntityType::Circle(circle) => Some(circle.radius),
EntityType::Arc(arc) => Some(arc.radius),
_ => None,
}
}
/// Rebuilds `set`'s entire `cadkernel_constraints::System` from current document
/// geometry, solves it, and returns the resulting entity states for every
/// handle whose registered coordinates actually moved beyond floating-point
@@ -1564,6 +1602,9 @@ fn solve_scope(
document: &acadrust::CadDocument,
drawing_params: &ParameterTable,
set: &ParametricConstraintSet,
driven_refs: &[ParametricRef],
retain_size: bool,
retained_before: &HashMap<Handle, std::sync::Arc<EntityType>>,
) -> Option<SolveResult> {
let params = if set.local_parameters.is_empty() {
drawing_params
@@ -1597,6 +1638,81 @@ fn solve_scope(
return None;
}
// Preserve current line lengths and radii for this solve when
// CONSTRAINTSOLVEMODE requests it. These temporary equations guide the
// solution without becoming persistent dimensional constraints.
if retain_size {
for (handle, geom) in &cache {
match geom {
EntityGeom::Line(line) => {
let current_length = {
let store = sys.store();
let dx = store.get(line.p2.x) - store.get(line.p1.x);
let dy = store.get(line.p2.y) - store.get(line.p1.y);
(dx * dx + dy * dy).sqrt()
};
let length = retained_before
.get(handle)
.and_then(|entity| retained_line_length(entity, None))
.unwrap_or(current_length);
let target = sys.add_param(length, true);
sys.add_constraint(Rc::new(P2PDistance::new(line.p1, line.p2, target)));
}
EntityGeom::Polyline {
points,
straight,
closed,
} => {
for index in 0..straight.len() {
if !straight[index] {
continue;
}
let Some(a) = points.get(index).copied() else {
continue;
};
let b = if let Some(point) = points.get(index + 1).copied() {
Some(point)
} else if *closed {
points.first().copied()
} else {
None
};
let Some(b) = b else { continue };
let current_length = {
let store = sys.store();
let dx = store.get(b.x) - store.get(a.x);
let dy = store.get(b.y) - store.get(a.y);
(dx * dx + dy * dy).sqrt()
};
let length = retained_before
.get(handle)
.and_then(|entity| retained_line_length(entity, Some(index)))
.unwrap_or(current_length);
let target = sys.add_param(length, true);
sys.add_constraint(Rc::new(P2PDistance::new(a, b, target)));
}
}
EntityGeom::Circle(circle) => {
let radius = retained_before
.get(handle)
.and_then(|entity| retained_radius(entity))
.unwrap_or_else(|| sys.store().get(circle.rad));
let target = sys.add_param(radius, true);
sys.add_constraint(Rc::new(Equal::new(circle.rad, target, 1.0)));
}
EntityGeom::Arc(arc) => {
let radius = retained_before
.get(handle)
.and_then(|entity| retained_radius(entity))
.unwrap_or_else(|| sys.store().get(arc.circle.rad));
let target = sys.add_param(radius, true);
sys.add_constraint(Rc::new(Equal::new(arc.circle.rad, target, 1.0)));
}
_ => {}
}
}
}
// Arc rules (this module's doc comment): keep every registered arc's
// `start`/`end` points consistent with its center/radius/angle,
// unconditionally — the kernel adds these the moment an arc
@@ -1633,6 +1749,33 @@ fn solve_scope(
)));
}
// A point explicitly edited by a grip, STRETCH, or Properties is the
// driver's target for this solve. Pin its current coordinates for this
// solve only so constrained neighbours respond while the chosen point
// stays at the exact cursor or typed coordinate.
for reference in driven_refs {
if !set
.constraints
.iter()
.any(|constraint| constraint.refs.iter().any(|r| r.entity == reference.entity))
{
continue;
}
let Some(point) =
resolve_constraint_point(document, &mut sys, &mut cache, *reference)
else {
continue;
};
let (x, y) = {
let store = sys.store();
(store.get(point.x), store.get(point.y))
};
let target_x = sys.add_param(x, true);
let target_y = sys.add_param(y, true);
sys.add_constraint(Rc::new(Equal::new(point.x, target_x, 1.0)));
sys.add_constraint(Rc::new(Equal::new(point.y, target_y, 1.0)));
}
// Ellipse rules (same motivation as arc rules, different mechanism):
// `cadkernel_constraints::geo::Ellipse` stores `focus1` as an absolute point, not an
// offset from `center`. If some constraint (e.g. Concentric) pulls only
@@ -2151,9 +2294,43 @@ 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.
#[allow(dead_code)]
pub(crate) fn refresh_parametric_constraints(
&mut self,
changes: &[(Handle, ChangeKind)],
) -> Vec<(Handle, ChangeKind)> {
self.refresh_parametric_constraints_with_policy(changes, &[], false)
}
#[allow(dead_code)]
pub(crate) fn refresh_parametric_constraints_with_driven(
&mut self,
changes: &[(Handle, ChangeKind)],
driven_refs: &[ParametricRef],
) -> Vec<(Handle, ChangeKind)> {
self.refresh_parametric_constraints_with_policy(changes, driven_refs, false)
}
pub(crate) fn refresh_parametric_constraints_with_policy(
&mut self,
changes: &[(Handle, ChangeKind)],
driven_refs: &[ParametricRef],
retain_size: bool,
) -> Vec<(Handle, ChangeKind)> {
self.refresh_parametric_constraints_with_originals(
changes,
driven_refs,
retain_size,
&[],
)
}
pub(crate) fn refresh_parametric_constraints_with_originals(
&mut self,
changes: &[(Handle, ChangeKind)],
driven_refs: &[ParametricRef],
retain_size: bool,
retained_originals: &[(Handle, EntityType)],
) -> Vec<(Handle, ChangeKind)> {
// An erased entity takes its constraints with it instead of leaving
// dangling references. Done first,
@@ -2185,6 +2362,25 @@ impl Scene {
}
}
let retained_before: HashMap<Handle, std::sync::Arc<EntityType>> = if retain_size {
let mut retained: HashMap<Handle, std::sync::Arc<EntityType>> = self
.undo_recording
.as_ref()
.into_iter()
.flat_map(|recording| recording.before.iter())
.filter_map(|(handle, entity)| {
entity
.as_ref()
.map(|entity| (*handle, std::sync::Arc::clone(entity)))
})
.collect();
for (handle, entity) in retained_originals {
retained.insert(*handle, std::sync::Arc::new(entity.clone()));
}
retained
} else {
HashMap::new()
};
let mut result = Vec::new();
for i in 0..self.parametric_constraints.len() {
let touched = changes.iter().any(|(handle, _)| {
@@ -2200,6 +2396,9 @@ impl Scene {
&self.document,
&self.named_parameters,
&self.parametric_constraints[i],
driven_refs,
retain_size,
&retained_before,
) else {
continue;
};
@@ -2224,10 +2423,17 @@ impl Scene {
pub(crate) fn solve_parametric_constraints_preview(
&self,
touched: &[Handle],
driven_refs: &[ParametricRef],
retain_size: bool,
retained_originals: &[(Handle, EntityType)],
) -> Vec<(Handle, EntityType)> {
if self.parametric_constraints.is_empty() || touched.is_empty() {
return Vec::new();
}
let retained_before: HashMap<Handle, std::sync::Arc<EntityType>> = retained_originals
.iter()
.map(|(handle, entity)| (*handle, std::sync::Arc::new(entity.clone())))
.collect();
let mut result = Vec::new();
for set in &self.parametric_constraints {
let is_touched = touched
@@ -2236,8 +2442,14 @@ impl Scene {
if !is_touched {
continue;
}
let Some((solved, _dof, _conflicts)) =
solve_scope(&self.document, &self.named_parameters, set)
let Some((solved, _dof, _conflicts)) = solve_scope(
&self.document,
&self.named_parameters,
set,
driven_refs,
retain_size,
&retained_before,
)
else {
continue;
};
@@ -2515,7 +2727,7 @@ mod tests {
}
let b_before = scene.document.get_entity(b).cloned();
let solved = scene.solve_parametric_constraints_preview(&[a]);
let solved = scene.solve_parametric_constraints_preview(&[a], &[], false, &[]);
assert_eq!(
scene.document.get_entity(b),