Merge pull request #1332 from ramox81/codex/concentric-constraint-parity

# Conflicts:
#	src/entities/arc.rs
#	src/entities/ellipse.rs
#	src/modules/parametric/mod.rs
#	src/scene/parametric_solve.rs
#	src/ui/overlay.rs
This commit is contained in:
Hakan Seven committed 2026-09-17 17:24:14 +03:00
commit 16c417f81d
17 files changed
+692 -52

No files matched your search

+13 -1
View File
@@ -454,11 +454,17 @@ impl<'a> GroupBuilder<'a> {
return Some(*existing);
}
let segment_midpoint = ParametricRef::point(handle, marker).segment_midpoint_index();
let segment_center = ParametricRef::point(handle, marker).segment_center_index();
let polyline = matches!(
self.document.get_entity(handle),
Some(EntityType::LwPolyline(_) | EntityType::Polyline2D(_))
);
let (curve_id, point_type) = if let Some(segment) = segment_midpoint {
let (curve_id, point_type) = if let Some(segment) = segment_center {
(
self.segment_node(handle, segment)?,
implicit_point_type::CENTER,
)
} else if let Some(segment) = segment_midpoint {
(
self.segment_node(handle, segment)?,
implicit_point_type::MID,
@@ -551,6 +557,9 @@ impl<'a> GroupBuilder<'a> {
if r.segment_midpoint_index().is_some() {
return self.point_node(r.entity, r.marker?);
}
if r.segment_center_index().is_some() {
return self.point_node(r.entity, r.marker?);
}
if let Some(segment) = r.segment_index() {
return self.segment_node(r.entity, segment);
}
@@ -2079,6 +2088,9 @@ pub(super) fn native_constraint_set(
implicit_point_type::MID => {
ParametricRef::segment_midpoint(curve.entity, segment)
}
implicit_point_type::CENTER => {
ParametricRef::segment_center(curve.entity, segment)
}
_ => continue,
}
} else {
+43 -7
View File
@@ -14,8 +14,8 @@ use acadrust::types::{Handle, Vector3};
/// ordered per-entity-type point list when non-negative (0/1 = a line's
/// start/end, ...), or names a special case when negative (-3 = a
/// circle/arc's center; -2 = a bounded curve's midpoint). Polyline segment
/// and segment-midpoint references use private negative ranges so they stay
/// distinct from vertex markers.
/// segment-midpoint, and curved-segment-center references use private
/// negative ranges so they stay distinct from vertex markers.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ParametricRef {
pub entity: Handle,
@@ -28,6 +28,7 @@ pub struct ParametricRef {
const POLYLINE_SEGMENT_MARKER_BASE: i32 = -1_000_000;
const POLYLINE_SEGMENT_MIDPOINT_MARKER_BASE: i32 = -2_000_000;
const POLYLINE_SEGMENT_CENTER_MARKER_BASE: i32 = -3_000_000;
const ELLIPSE_MAJOR_AXIS_MARKER: i32 = -4;
const ELLIPSE_MINOR_AXIS_MARKER: i32 = -5;
const TEXT_BASELINE_MARKER: i32 = -6;
@@ -88,10 +89,25 @@ impl ParametricRef {
pub fn segment_midpoint_index(self) -> Option<usize> {
let marker = self.marker?;
(marker <= POLYLINE_SEGMENT_MIDPOINT_MARKER_BASE)
(marker <= POLYLINE_SEGMENT_MIDPOINT_MARKER_BASE
&& marker > POLYLINE_SEGMENT_CENTER_MARKER_BASE)
.then(|| (POLYLINE_SEGMENT_MIDPOINT_MARKER_BASE - marker) as usize)
}
/// Select the center of one curved polyline segment.
pub fn segment_center(entity: Handle, index: usize) -> Self {
Self {
entity,
marker: Some(POLYLINE_SEGMENT_CENTER_MARKER_BASE - index as i32),
}
}
pub fn segment_center_index(self) -> Option<usize> {
let marker = self.marker?;
(marker <= POLYLINE_SEGMENT_CENTER_MARKER_BASE)
.then(|| (POLYLINE_SEGMENT_CENTER_MARKER_BASE - marker) as usize)
}
/// Select the displayed baseline of a Text or MText entity.
pub fn text_baseline(entity: Handle) -> Self {
Self {
@@ -503,7 +519,7 @@ impl ParametricConstraintSet {
/// endpoint's equivalent of `dimension_assoc::resolve_reference`, restricted
/// to the marker conventions constraint endpoints actually use (whole-entity
/// `None`, an ordinary `source_points()` index, the `-3` center case, or a
/// bounded curve/segment midpoint).
/// bounded curve/segment midpoint, or a curved polyline-segment center).
///
/// Solver-side registration reads raw entity fields directly. This helper is
/// for UI-side consumers that need the current world-space position.
@@ -524,12 +540,20 @@ pub(crate) fn resolve_point(entity: &acadrust::EntityType, marker: i32) -> Optio
let point = curve.point_at(0.5);
return Some(Vector3::new(point[0], point[1], point[2]));
}
let segment = ParametricRef {
let reference = ParametricRef {
entity: Handle::NULL,
marker: Some(marker),
};
if let Some(segment) = reference.segment_center_index() {
let planar = crate::entities::curve::entity_curve(entity)?;
let curve = planar.curve.segments().into_iter().nth(segment)?;
let cadkernel::geom2d::Curve::Arc(arc) = curve else {
return None;
};
let point = planar.plane.point_at(arc.centre);
return Some(Vector3::new(point[0], point[1], point[2]));
}
.segment_midpoint_index();
if let Some(segment) = segment {
if let Some(segment) = reference.segment_midpoint_index() {
let planar = crate::entities::curve::entity_curve(entity)?;
let curve = planar.curve.segments().into_iter().nth(segment)?;
let point = planar.plane.point_at(curve.point_at(0.5));
@@ -831,6 +855,18 @@ fn glyph_placement_for_reference(
let anchor = (start + end) * 0.5;
return Some((anchor, segment_normal(start, end)));
}
if let Some(segment) = r.segment_center_index() {
let planar = crate::entities::curve::entity_curve(entity)?;
let curve = planar.curve.segments().into_iter().nth(segment)?;
let on_arc = planar.plane.point_at(curve.point_at(0.5));
let cadkernel::geom2d::Curve::Arc(arc) = curve else {
return None;
};
let center = planar.plane.point_at(arc.centre);
let center = Vector3::new(center[0], center[1], center[2]);
let on_arc = Vector3::new(on_arc[0], on_arc[1], on_arc[2]);
return Some((on_arc, on_arc - center));
}
if let Some(segment) = r.segment_index() {
let anchor = resolve_point(
entity,
+205 -1
View File
@@ -513,6 +513,12 @@ fn resolve_constraint_point(
if let Some(point) = geometry.point_for_marker(marker) {
return Some(point);
}
if let Some(segment) = reference
.segment_center_index()
.and_then(|index| geometry.arc_segment(index))
{
return Some(segment.arc.circle.center);
}
let midpoint_line = reference
.segment_midpoint_index()
@@ -794,6 +800,175 @@ fn refs_share_supported_plane(document: &acadrust::CadDocument, refs: &[Parametr
plane_z.is_some()
}
fn concentric_reference_geometry(
document: &acadrust::CadDocument,
reference: ParametricRef,
) -> Option<(Vector3, cadkernel::space::Plane)> {
let marker = reference.marker?;
if marker != -3 && reference.segment_center_index().is_none() {
return None;
}
let entity = document.get_entity(reference.entity)?;
let center = super::parametric_constraints::resolve_point(entity, marker)?;
let plane = crate::entities::curve::entity_curve(entity)?.plane;
Some((center, plane))
}
fn concentric_refs_share_plane(
document: &acadrust::CadDocument,
refs: &[ParametricRef],
) -> bool {
let [first, second] = refs else {
return false;
};
if first == second {
return false;
}
let Some((first_center, first_plane)) = concentric_reference_geometry(document, *first) else {
return false;
};
let Some((second_center, second_plane)) = concentric_reference_geometry(document, *second)
else {
return false;
};
let (Some(first_normal), Some(second_normal)) = (first_plane.normal(), second_plane.normal())
else {
return false;
};
let parallel = (first_normal[0] * second_normal[0]
+ first_normal[1] * second_normal[1]
+ first_normal[2] * second_normal[2])
.abs()
>= 1.0 - 1.0e-9;
let points = [
first_plane.origin,
second_plane.origin,
[first_center.x, first_center.y, first_center.z],
[second_center.x, second_center.y, second_center.z],
];
let tolerance = cadkernel::space::coplanarity_tolerance(&points);
parallel
&& first_plane
.distance_to(second_plane.origin)
.is_some_and(|distance| distance.abs() <= tolerance)
&& first_plane
.distance_to([second_center.x, second_center.y, second_center.z])
.is_some_and(|distance| distance.abs() <= tolerance)
}
fn ref_is_driven(driven: &[ParametricRef], reference: ParametricRef) -> bool {
driven.iter().any(|candidate| {
candidate.entity == reference.entity
&& (candidate.marker == reference.marker || candidate.marker.is_none())
})
}
fn apply_spatial_concentric_constraints(
document: &acadrust::CadDocument,
set: &ParametricConstraintSet,
driven_refs: &[ParametricRef],
initial_fixed_refs: &[ParametricRef],
results: &mut Vec<(Handle, EntityType)>,
) {
let current_entity = |handle: Handle, results: &[(Handle, EntityType)]| {
results
.iter()
.rev()
.find(|(candidate, _)| *candidate == handle)
.map(|(_, entity)| entity.clone())
.or_else(|| document.get_entity(handle).cloned())
};
let mut handles = Vec::<Handle>::new();
let mut relations = Vec::new();
let mut fixed = Vec::new();
let mut points = Vec::new();
for constraint in set.constraints.iter().filter(|constraint| {
constraint.enabled
&& constraint.kind == ConstraintKind::Concentric
&& !refs_share_supported_plane(document, &constraint.refs)
&& concentric_refs_share_plane(document, &constraint.refs)
}) {
let [first, second] = constraint.refs.as_slice() else {
continue;
};
if first.entity == second.entity {
continue;
}
let (Some(first_entity), Some(second_entity)) = (
current_entity(first.entity, results),
current_entity(second.entity, results),
) else {
continue;
};
let (Some(first_marker), Some(second_marker)) = (first.marker, second.marker) else {
continue;
};
let (Some(first_center), Some(second_center)) = (
super::parametric_constraints::resolve_point(&first_entity, first_marker),
super::parametric_constraints::resolve_point(&second_entity, second_marker),
) else {
continue;
};
let first_index = handles
.iter()
.position(|handle| *handle == first.entity)
.unwrap_or_else(|| {
handles.push(first.entity);
handles.len() - 1
});
let second_index = handles
.iter()
.position(|handle| *handle == second.entity)
.unwrap_or_else(|| {
handles.push(second.entity);
handles.len() - 1
});
let first_point = [first_center.x, first_center.y, first_center.z];
let second_point = [second_center.x, second_center.y, second_center.z];
relations.push((first_index, first_point, second_index, second_point));
points.extend([first_point, second_point]);
if ref_is_driven(driven_refs, *first) || ref_is_driven(initial_fixed_refs, *first) {
fixed.push(first_index);
}
if ref_is_driven(driven_refs, *second) || ref_is_driven(initial_fixed_refs, *second) {
fixed.push(second_index);
}
}
if relations.is_empty() {
return;
}
fixed.sort_unstable();
fixed.dedup();
let tolerance = cadkernel::space::coplanarity_tolerance(&points).max(MOVE_EPS);
let Some(translations) = cadkernel::space::solve_rigid_point_coincidence(
handles.len(),
&relations,
&fixed,
tolerance,
) else {
return;
};
for (handle, translation) in handles.into_iter().zip(translations) {
let translation = glam::DVec3::from_array(translation);
if translation.length() <= tolerance {
continue;
}
let Some(mut entity) = current_entity(handle, results) else {
continue;
};
crate::scene::view::dispatch::apply_transform(
&mut entity,
&crate::command::EntityTransform::Translate(translation),
);
if let Some((_, current)) = results.iter_mut().find(|(candidate, _)| *candidate == handle) {
*current = entity;
} else {
results.push((handle, entity));
}
}
}
fn resolved_target(params: &ParameterTable, constraint: &ParametricConstraint) -> Option<f64> {
let value = constraint.driving_param.as_ref()?.resolve(params).ok()?;
if !value.is_finite() {
@@ -1982,6 +2157,7 @@ fn polyline_ref_order(
}
let position = if let Some(index) = reference.segment_index()
.or_else(|| reference.segment_midpoint_index())
.or_else(|| reference.segment_center_index())
{
2 * index + 1
} else {
@@ -2133,7 +2309,13 @@ fn solve_scope(
let has_smooth = constraints.iter()
.any(|constraint| constraint.enabled && constraint.kind == ConstraintKind::Smooth);
if cache.is_empty() && !has_smooth {
let has_spatial_concentric = constraints.iter().any(|constraint| {
constraint.enabled
&& constraint.kind == ConstraintKind::Concentric
&& !refs_share_supported_plane(document, &constraint.refs)
&& concentric_refs_share_plane(document, &constraint.refs)
});
if cache.is_empty() && !has_smooth && !has_spatial_concentric {
return None;
}
@@ -3013,6 +3195,13 @@ fn solve_scope(
}
}
apply_smooth_constraints(document, set, &mut results);
apply_spatial_concentric_constraints(
document,
set,
driven_refs,
initial_fixed_refs,
&mut results,
);
Some((results, dof, conflicts))
}
@@ -3064,6 +3253,21 @@ impl Scene {
}
return Ok(());
}
if kind == ConstraintKind::Concentric {
if !concentric_refs_share_plane(&self.document, refs) {
return Err(
"Concentric requires two supported circular curves on the same plane.",
);
}
if !refs_share_supported_plane(&self.document, refs) {
if refs[0].entity == refs[1].entity {
return Err(
"Concentric cannot move two curved segments of the same object on this plane.",
);
}
return Ok(());
}
}
if !refs_share_supported_plane(&self.document, refs) {
return Err(
"Constraint references must be supported entities on the same world-XY plane.",