Standardize parametric constraints and interoperability

This commit is contained in:
Hakan Seven committed 2026-09-15 03:19:52 +03:00
1 parent 753df67ee4
commit d738ebfcc6
41 files changed
+10038 -7847

No files matched your search

+129 -127
View File
@@ -1,13 +1,13 @@
use acadrust::entities::Dimension;
use acadrust::objects::{
AssocDimensionAssociation, AssocDimensionReference, AssociativeData,
AssociativeObject, ObjectType,
AssocDimensionAssociation, AssocDimensionReference, AssociativeData, AssociativeObject,
ObjectType,
};
use acadrust::types::{Handle, Vector3};
use acadrust::EntityType;
use cadkernel::geom2d::{
angle_within_arc, arc as tessellate_arc, arc_span, closest_point, Arc as KernelArc,
BulgeArc, Circle as KernelCircle, Curve as KernelCurve, DEFAULT_SEGMENTS_PER_RADIAN,
angle_within_arc, arc as tessellate_arc, arc_span, closest_point, Arc as KernelArc, BulgeArc,
Circle as KernelCircle, Curve as KernelCurve, DEFAULT_SEGMENTS_PER_RADIAN,
};
use cadkernel::space::Plane;
use std::f64::consts::TAU;
@@ -25,8 +25,7 @@ pub(crate) fn polyline_arc_point_marker(segment: i32) -> i32 {
}
fn polyline_arc_segment_from_point_marker(marker: i32) -> Option<i32> {
(marker <= POLYLINE_ARC_POINT_MARKER_BASE)
.then_some(POLYLINE_ARC_POINT_MARKER_BASE - marker)
(marker <= POLYLINE_ARC_POINT_MARKER_BASE).then_some(POLYLINE_ARC_POINT_MARKER_BASE - marker)
}
#[derive(Clone, Copy, Debug)]
@@ -158,11 +157,13 @@ pub(crate) fn radial_source_at(
entity: &EntityType,
point: Vector3,
) -> Option<RadialSourceGeometry> {
radial_candidates(entity).into_iter().min_by(|first, second| {
first
.distance_squared_to(dpoint(point))
.total_cmp(&second.distance_squared_to(dpoint(point)))
})
radial_candidates(entity)
.into_iter()
.min_by(|first, second| {
first
.distance_squared_to(dpoint(point))
.total_cmp(&second.distance_squared_to(dpoint(point)))
})
}
fn radial_source_for_marker(entity: &EntityType, marker: i32) -> Option<RadialSourceGeometry> {
@@ -177,14 +178,16 @@ fn radial_source_matching(
radius: f64,
chord: Vector3,
) -> Option<RadialSourceGeometry> {
radial_candidates(entity).into_iter().min_by(|first, second| {
let score = |candidate: &RadialSourceGeometry| {
point_distance_squared(candidate.center_world(), center)
+ (candidate.radius - radius).powi(2)
+ candidate.distance_squared_to(dpoint(chord)) * 1e-6
};
score(first).total_cmp(&score(second))
})
radial_candidates(entity)
.into_iter()
.min_by(|first, second| {
let score = |candidate: &RadialSourceGeometry| {
point_distance_squared(candidate.center_world(), center)
+ (candidate.radius - radius).powi(2)
+ candidate.distance_squared_to(dpoint(chord)) * 1e-6
};
score(first).total_cmp(&score(second))
})
}
fn point_distance_squared(first: Vector3, second: Vector3) -> f64 {
@@ -235,9 +238,10 @@ fn polyline_arc_center(entity: &EntityType, segment: usize) -> Option<Vector3> {
EntityType::LwPolyline(polyline) => {
let count = polyline.vertices.len();
let first = *polyline.vertices.get(segment)?;
let second = *polyline
.vertices
.get(next_segment_index(count, polyline.is_closed, segment)?)?;
let second =
*polyline
.vertices
.get(next_segment_index(count, polyline.is_closed, segment)?)?;
bulge_center_world(
[first.location.x, first.location.y],
[second.location.x, second.location.y],
@@ -249,9 +253,10 @@ fn polyline_arc_center(entity: &EntityType, segment: usize) -> Option<Vector3> {
EntityType::Polyline2D(polyline) => {
let count = polyline.vertices.len();
let first = polyline.vertices.get(segment)?;
let second = polyline
.vertices
.get(next_segment_index(count, polyline.is_closed(), segment)?)?;
let second =
polyline
.vertices
.get(next_segment_index(count, polyline.is_closed(), segment)?)?;
bulge_center_world(
[first.location.x, first.location.y],
[second.location.x, second.location.y],
@@ -267,20 +272,24 @@ fn polyline_arc_center(entity: &EntityType, segment: usize) -> Option<Vector3> {
/// Ordered, named points for an entity (line start/end, polyline vertices,
/// ...), indexed by the same non-negative GsMarker convention
/// `AssocDimensionReference::main_gs_marker` uses. Promoted to
/// `pub(crate)` so `sketch::constraint_set` can address constraint
/// `pub(crate)` so the parametric solver can address constraint
/// endpoints the same way associative dimensions already address theirs,
/// rather than inventing a second sub-element scheme.
pub(crate) fn source_points(entity: &EntityType) -> Vec<Vector3> {
match entity {
EntityType::Line(line) => vec![line.start, line.end],
EntityType::Ray(ray) => vec![ray.base_point],
EntityType::Arc(arc) => vec![arc.start_point_wcs(), arc.end_point_wcs()],
EntityType::Circle(_) => Vec::new(),
EntityType::Spline(spline) => crate::entities::spline::nurbs3(spline)
.map(|curve| {
let (start, end) = curve.domain();
[curve.point_at_knot(start), curve.point_at_knot(end)].into_iter()
.map(|point| Vector3::new(point[0], point[1], point[2])).collect()
}).unwrap_or_default(),
[curve.point_at_knot(start), curve.point_at_knot(end)]
.into_iter()
.map(|point| Vector3::new(point[0], point[1], point[2]))
.collect()
})
.unwrap_or_default(),
EntityType::LwPolyline(polyline) => polyline
.vertices
.iter()
@@ -345,8 +354,7 @@ fn source_marker(entity: &EntityType, point: Vector3) -> Option<i32> {
.into_iter()
.enumerate()
.min_by(|(_, first), (_, second)| {
point_distance_squared(*first, point)
.total_cmp(&point_distance_squared(*second, point))
point_distance_squared(*first, point).total_cmp(&point_distance_squared(*second, point))
})
.map(|(index, _)| index as i32)
}
@@ -360,9 +368,7 @@ fn resolve_reference(scene: &Scene, reference: &AssocDimensionReference) -> Opti
let angle = radial.start_angle + sweep * reference.osnap_distance.clamp(0.0, 1.0);
return Some(radial.point_at_angle(angle));
}
if let Some(segment) =
polyline_arc_segment_from_point_marker(reference.main_gs_marker)
{
if let Some(segment) = polyline_arc_segment_from_point_marker(reference.main_gs_marker) {
let radial = radial_source_for_marker(entity, segment)?;
let sweep = positive_sweep(radial.start_angle, radial.end_angle);
let angle = radial.start_angle + sweep * reference.osnap_distance.clamp(0.0, 1.0);
@@ -381,9 +387,7 @@ fn resolve_reference(scene: &Scene, reference: &AssocDimensionReference) -> Opti
}
if reference.main_gs_marker == -2 {
return match entity {
EntityType::Circle(circle) => {
Some(circle.point_at_angle_wcs(reference.osnap_distance))
}
EntityType::Circle(circle) => Some(circle.point_at_angle_wcs(reference.osnap_distance)),
EntityType::Arc(arc) => Some(arc.point_at_angle_wcs(reference.osnap_distance)),
_ => None,
};
@@ -399,14 +403,17 @@ fn resolve_reference(scene: &Scene, reference: &AssocDimensionReference) -> Opti
);
let curve = circle_curve(circle);
let stored_parameter = curve.parameter_at([stored.0, stored.1]) * TAU;
let parameter = if reference.osnap_distance.abs() > 1e-12
|| stored_parameter.abs() <= 1e-12
let parameter = if reference.osnap_distance.abs() > 1e-12 || stored_parameter.abs() <= 1e-12
{
reference.osnap_distance
} else {
stored_parameter
};
let parameter = if parameter.is_finite() { parameter } else { 0.0 };
let parameter = if parameter.is_finite() {
parameter
} else {
0.0
};
let point = curve.point_at(parameter / TAU);
return Some(ocs_point(
point[0],
@@ -494,27 +501,13 @@ fn signed_angle_delta(value: f64) -> f64 {
(value + std::f64::consts::PI).rem_euclid(TAU) - std::f64::consts::PI
}
fn angle_about_plane(
plane: Plane,
center: Vector3,
point: Vector3,
) -> f64 {
let delta = [
point.x - center.x,
point.y - center.y,
point.z - center.z,
];
let dot = |axis: [f64; 3]| {
delta[0] * axis[0] + delta[1] * axis[1] + delta[2] * axis[2]
};
fn angle_about_plane(plane: Plane, center: Vector3, point: Vector3) -> f64 {
let delta = [point.x - center.x, point.y - center.y, point.z - center.z];
let dot = |axis: [f64; 3]| delta[0] * axis[0] + delta[1] * axis[1] + delta[2] * axis[2];
dot(plane.y_axis).atan2(dot(plane.x_axis))
}
fn point_on_radial_circle(
radial: RadialSourceGeometry,
radius: f64,
angle: f64,
) -> Vector3 {
fn point_on_radial_circle(radial: RadialSourceGeometry, radius: f64, angle: f64) -> Vector3 {
vector3(radial.plane.point_at([
radial.center[0] + radius * angle.cos(),
radial.center[1] + radius * angle.sin(),
@@ -522,9 +515,7 @@ fn point_on_radial_circle(
}
fn plane_from_normal(origin: Vector3, normal: Vector3) -> Plane {
let (x_axis, y_axis) = crate::scene::view::transform::ocs_axes((
normal.x, normal.y, normal.z,
));
let (x_axis, y_axis) = crate::scene::view::transform::ocs_axes((normal.x, normal.y, normal.z));
Plane::from_axes(
[origin.x, origin.y, origin.z],
[x_axis.0, x_axis.1, x_axis.2],
@@ -574,35 +565,52 @@ pub(crate) fn constraint_from_associative_dimension(
document: &acadrust::CadDocument,
handle: Handle,
) -> Option<(
super::sketch_constraints::ConstraintKind,
Vec<super::sketch_constraints::SketchRef>,
super::parametric_constraints::ConstraintKind,
Vec<super::parametric_constraints::ParametricRef>,
super::named_parameters::DrivingValue,
)> {
use super::named_parameters::DrivingValue;
use super::sketch_constraints::{ConstraintKind, SketchRef};
use super::parametric_constraints::{ConstraintKind, ParametricRef};
let EntityType::Dimension(dimension) = document.get_entity(handle)? else { return None };
let EntityType::Dimension(dimension) = document.get_entity(handle)? else {
return None;
};
let association = document.objects.values().find_map(|object| {
let ObjectType::Associative(object) = object else { return None };
let AssociativeData::DimensionAssociation(association) = &object.data else { return None };
let ObjectType::Associative(object) = object else {
return None;
};
let AssociativeData::DimensionAssociation(association) = &object.data else {
return None;
};
(association.dimension == handle && association.associativity != 0).then_some(association)
})?;
let point = |index: usize| {
let reference = association.references.get(index)?.first()?;
Some(SketchRef::point(*reference.xrefs.first()?, reference.main_gs_marker))
Some(ParametricRef::point(
*reference.xrefs.first()?,
reference.main_gs_marker,
))
};
let whole = |index: usize| {
let reference = association.references.get(index)?.first()?;
Some(SketchRef::whole(*reference.xrefs.first()?))
Some(ParametricRef::whole(*reference.xrefs.first()?))
};
let measurement = DrivingValue::Literal(dimension.measurement());
match dimension {
Dimension::Aligned(_) => Some((ConstraintKind::Distance, vec![point(0)?, point(1)?], measurement)),
Dimension::Aligned(_) => Some((
ConstraintKind::Distance,
vec![point(0)?, point(1)?],
measurement,
)),
Dimension::Linear(linear) => {
let direction = [linear.rotation.cos().abs(), linear.rotation.sin().abs()];
let kind = if direction[1] <= 1e-9 { ConstraintKind::DistanceX }
else if direction[0] <= 1e-9 { ConstraintKind::DistanceY }
else { return None };
let kind = if direction[1] <= 1e-9 {
ConstraintKind::DistanceX
} else if direction[0] <= 1e-9 {
ConstraintKind::DistanceY
} else {
return None;
};
Some((kind, vec![point(0)?, point(1)?], measurement))
}
Dimension::Radius(_) | Dimension::LargeRadial(_) => {
@@ -610,16 +618,24 @@ pub(crate) fn constraint_from_associative_dimension(
}
Dimension::Diameter(_) => Some((ConstraintKind::Diameter, vec![whole(0)?], measurement)),
Dimension::Angular2Ln(_) => {
let mut sources: Vec<Handle> = association.references.iter().flatten()
.filter_map(|reference| reference.xrefs.first().copied()).collect();
let mut sources: Vec<Handle> = association
.references
.iter()
.flatten()
.filter_map(|reference| reference.xrefs.first().copied())
.collect();
sources.sort_unstable();
sources.dedup();
let [first, second] = sources.as_slice() else { return None };
Some((ConstraintKind::Angle,
vec![SketchRef::whole(*first), SketchRef::whole(*second)], measurement))
let [first, second] = sources.as_slice() else {
return None;
};
Some((
ConstraintKind::Angle,
vec![ParametricRef::whole(*first), ParametricRef::whole(*second)],
measurement,
))
}
Dimension::Arc(_) => Some((ConstraintKind::ArcLength, vec![whole(0)?], measurement)),
Dimension::Angular3Pt(_) | Dimension::Ordinate(_) => None,
Dimension::Arc(_) | Dimension::Angular3Pt(_) | Dimension::Ordinate(_) => None,
}
}
@@ -716,8 +732,7 @@ impl Scene {
dimension: Handle,
sources: Vec<Option<DimensionAssociationSource>>,
) {
let Some(EntityType::Dimension(dimension_entity)) =
self.document.get_entity(dimension)
let Some(EntityType::Dimension(dimension_entity)) = self.document.get_entity(dimension)
else {
return;
};
@@ -803,22 +818,20 @@ impl Scene {
return;
}
let reference = |source: Handle,
marker: i32,
parameter: f64,
osnap_type: u8,
point: Vector3| AssocDimensionReference {
class_name: "AcDbOsnapPointRef".to_string(),
osnap_type,
xrefs: vec![source],
main_subent_type: 1,
main_gs_marker: marker,
osnap_distance: parameter,
osnap_point: point,
..AssocDimensionReference::default()
};
let mut references: [Vec<AssocDimensionReference>; 4] =
std::array::from_fn(|_| Vec::new());
let reference =
|source: Handle, marker: i32, parameter: f64, osnap_type: u8, point: Vector3| {
AssocDimensionReference {
class_name: "AcDbOsnapPointRef".to_string(),
osnap_type,
xrefs: vec![source],
main_subent_type: 1,
main_gs_marker: marker,
osnap_distance: parameter,
osnap_point: point,
..AssocDimensionReference::default()
}
};
let mut references: [Vec<AssocDimensionReference>; 4] = std::array::from_fn(|_| Vec::new());
let mut associativity = 0;
for (index, resolved) in resolved.into_iter().enumerate() {
if index >= references.len() {
@@ -873,12 +886,8 @@ impl Scene {
}
}
pub(crate) fn infer_dimension_sources(
&self,
dimension: Handle,
) -> Vec<Option<Handle>> {
let Some(EntityType::Dimension(entity)) = self.document.get_entity(dimension)
else {
pub(crate) fn infer_dimension_sources(&self, dimension: Handle) -> Vec<Option<Handle>> {
let Some(EntityType::Dimension(entity)) = self.document.get_entity(dimension) else {
return Vec::new();
};
let radial_data = match entity {
@@ -906,18 +915,11 @@ impl Scene {
.entities()
.filter(|candidate| candidate.common().handle != dimension)
.filter_map(|candidate| {
let radial = radial_source_matching(
candidate,
center,
radius,
chord,
)?;
let radial = radial_source_matching(candidate, center, radius, chord)?;
let center_error = point_distance_squared(radial.center_world(), center);
let radius_error = (radial.radius - radius).powi(2);
(center_error + radius_error <= tolerance * tolerance).then_some((
center_error + radius_error,
candidate.common().handle,
))
(center_error + radius_error <= tolerance * tolerance)
.then_some((center_error + radius_error, candidate.common().handle))
})
.min_by(|first, second| first.0.total_cmp(&second.0))
.map(|(_, handle)| handle);
@@ -946,8 +948,7 @@ impl Scene {
&mut self,
changes: &[(Handle, ChangeKind)],
) -> Vec<(Handle, ChangeKind)> {
let changed: rustc_hash::FxHashSet<_> =
changes.iter().map(|(handle, _)| *handle).collect();
let changed: rustc_hash::FxHashSet<_> = changes.iter().map(|(handle, _)| *handle).collect();
if changed.is_empty() {
return Vec::new();
}
@@ -966,7 +967,12 @@ impl Scene {
.references
.iter()
.flatten()
.any(|reference| reference.xrefs.iter().any(|handle| changed.contains(handle)))
.any(|reference| {
reference
.xrefs
.iter()
.any(|handle| changed.contains(handle))
})
.then_some(association.clone())
})
.collect();
@@ -989,9 +995,7 @@ impl Scene {
let entity = self.document.get_entity(source)?;
let segment = match reference.main_gs_marker {
-3 => 0,
POLYLINE_ARC_CENTER_MARKER => {
reference.osnap_distance.round().max(0.0) as i32
}
POLYLINE_ARC_CENTER_MARKER => reference.osnap_distance.round().max(0.0) as i32,
_ => return None,
};
radial_source_for_marker(entity, segment)
@@ -1144,10 +1148,7 @@ impl Scene {
let old_dim_radius = old_center.distance(&old_definition);
let radial_offset = old_dim_radius - old_source_radius;
let old_mid = arc.arc_start_parameter
+ positive_sweep(
arc.arc_start_parameter,
arc.arc_end_parameter,
) * 0.5;
+ positive_sweep(arc.arc_start_parameter, arc.arc_end_parameter) * 0.5;
let old_definition_angle =
angle_about_plane(old_plane, old_center, old_definition);
let definition_angle_offset =
@@ -1208,8 +1209,7 @@ impl Scene {
arc.base.insertion_point = definition;
}
if arc.has_leader {
arc.first_leader_point =
point_on_radial_circle(radial, dim_radius, middle);
arc.first_leader_point = point_on_radial_circle(radial, dim_radius, middle);
arc.second_leader_point = second_leader;
}
arc.base.actual_measurement = arc.measurement();
@@ -1228,7 +1228,9 @@ impl Scene {
fn linear_measurement(linear: &acadrust::entities::DimensionLinear) -> f64 {
let plane = plane_from_normal(linear.first_point, linear.base.normal);
let first = plane.project(dpoint(linear.first_point)).unwrap_or([0.0; 2]);
let first = plane
.project(dpoint(linear.first_point))
.unwrap_or([0.0; 2]);
let second = plane.project(dpoint(linear.second_point)).unwrap_or(first);
let axis = [linear.rotation.cos(), linear.rotation.sin()];
((second[0] - first[0]) * axis[0] + (second[1] - first[1]) * axis[1]).abs()