DCRADIUS: reference radial dimensional constraint with a dynamic radial dimension

Select arc or circle / Dimension text / dimension line location / value or
name prompts with a radN parameter, invalid-selection, miss, duplicate and
invalid-expression messages, negative values as magnitudes, a dynamic radial
dimension on the constraints layer drawn as the reference does (mid-radius
text on the center-to-rim line, no hook, no center mark, one text grip), the
stored text position following the radius, center-anchored resizing, the
Properties title and Constraint section, DIMCONSTRAINT's Radius default,
the small-button Dimensional ribbon grid and tooltip descriptions.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
ramox81andClaude Fable 5.1 committed 2026-09-22 13:09:47 +03:00
1 parent 64c020293a
commit aab42e2aa9
11 files changed
+516 -87

No files matched your search

+89 -11
View File
@@ -353,24 +353,36 @@ pub(crate) fn equal_size_follower(
Some(entity)
}
/// The first free `<prefix>1`, `<prefix>2`, … name a new constraint takes.
fn next_prefixed_parameter_name(
table: &super::named_parameters::ParameterTable,
prefix: &str,
) -> String {
(1..)
.map(|n| format!("{prefix}{n}"))
.find(|name| !table.contains(name))
.unwrap_or_else(|| format!("{prefix}1"))
}
/// The first free `d1`, `d2`, … name a new dimensional constraint takes.
pub(crate) fn next_dimensional_parameter_name(
table: &super::named_parameters::ParameterTable,
) -> String {
(1..)
.map(|n| format!("d{n}"))
.find(|name| !table.contains(name))
.unwrap_or_else(|| "d1".to_string())
next_prefixed_parameter_name(table, "d")
}
/// The first free `ang1`, `ang2`, … name a new angular constraint takes.
pub(crate) fn next_angular_parameter_name(
table: &super::named_parameters::ParameterTable,
) -> String {
(1..)
.map(|n| format!("ang{n}"))
.find(|name| !table.contains(name))
.unwrap_or_else(|| "ang1".to_string())
next_prefixed_parameter_name(table, "ang")
}
/// The first free `rad1`, `rad2`, … name a new radius constraint takes.
pub(crate) fn next_radial_parameter_name(
table: &super::named_parameters::ParameterTable,
) -> String {
next_prefixed_parameter_name(table, "rad")
}
/// An angle as the reference displays it: a negative or over-full value is
@@ -565,6 +577,52 @@ fn dynamic_dimension_follow_points(
true
}
/// A circle's or arc's center and radius.
fn radial_geometry(entity: &acadrust::EntityType) -> Option<(Vector3, f64)> {
match entity {
acadrust::EntityType::Circle(circle) => Some((circle.center_wcs(), circle.radius)),
acadrust::EntityType::Arc(arc) => Some((arc.center_wcs(), arc.radius)),
_ => None,
}
}
/// Moves a dynamic radial dimension onto its circle's current center and
/// radius: the dimension line keeps its direction and the text keeps its
/// distance beyond the circle, as in the reference; true when it moved.
fn dynamic_dimension_follow_radius(
dimension: &mut acadrust::entities::Dimension,
center: Vector3,
radius: f64,
) -> bool {
let acadrust::entities::Dimension::Radius(radial) = dimension else {
return false;
};
let to_dvec = |p: Vector3| glam::DVec3::new(p.x, p.y, p.z);
let to_vec = |p: glam::DVec3| Vector3::new(p.x, p.y, p.z);
let old_center = to_dvec(radial.angle_vertex);
let old_radius = (to_dvec(radial.definition_point) - old_center).length();
let center = to_dvec(center);
if (old_center - center).length_squared() < 1.0e-16 && (old_radius - radius).abs() < 1.0e-9 {
return false;
}
let toward_text = to_dvec(radial.base.text_middle_point) - old_center;
let direction = toward_text
.try_normalize()
.or_else(|| (to_dvec(radial.definition_point) - old_center).try_normalize())
.unwrap_or(glam::DVec3::X);
let beyond = toward_text.length() - old_radius;
let chord = center + direction * radius;
let text = center + direction * (radius + beyond);
radial.angle_vertex = to_vec(center);
radial.definition_point = to_vec(chord);
radial.base.definition_point = to_vec(chord);
radial.base.text_middle_point = to_vec(text);
radial.base.insertion_point = to_vec(text);
radial.leader_length = (text - chord).length();
radial.base.actual_measurement = radius;
true
}
/// The end points of a line reference: a line's two ends, or the picked
/// polyline segment's vertices.
fn line_ends(entity: &acadrust::EntityType, reference: ParametricRef) -> Option<(Vector3, Vector3)> {
@@ -673,6 +731,11 @@ pub(crate) fn dimensional_anchor_refs(
let Some(&first) = refs.first() else {
return Vec::new();
};
// A whole circle or arc (a radius or diameter): its center stays and the
// value resizes it.
if refs.len() == 1 && first.marker.is_none() {
return vec![ParametricRef::center(first.entity)];
}
// Three point references: a three-point angle `[first, vertex, second]`.
if refs.len() == 3
&& refs
@@ -1983,6 +2046,7 @@ impl super::Scene {
String,
Option<(Vector3, Vector3)>,
Option<Vec<Vector3>>,
Option<(Vector3, f64)>,
)> = Vec::new();
// A reference (driven) constraint's parameter follows the geometry.
let mut followed: Vec<(String, f64)> = Vec::new();
@@ -2010,7 +2074,15 @@ impl super::Scene {
constraint.kind,
ConstraintKind::Angle | ConstraintKind::Angle3Point
);
let measured = points.filter(|_| !angular).map(|(first, second)| {
// A radius: the circle's or arc's center and radius now.
let radial = (constraint.kind == ConstraintKind::Radius)
.then(|| {
let reference = constraint.refs.first()?;
radial_geometry(self.document.get_entity(reference.entity)?)
})
.flatten();
let measured = radial.map(|(_, radius)| radius);
let measured = measured.or(points.filter(|_| !angular).map(|(first, second)| {
let delta = second - first;
let axis = match self.document.get_entity(*dimension) {
Some(acadrust::EntityType::Dimension(
@@ -2026,7 +2098,7 @@ impl super::Scene {
}
_ => delta.length(),
}
});
}));
let (name, value, source) = match &constraint.driving_param {
Some(DrivingValue::Named(name)) => {
let value = match (reference, measured) {
@@ -2070,6 +2142,7 @@ impl super::Scene {
),
points,
angle_points,
radial,
));
}
}
@@ -2079,7 +2152,7 @@ impl super::Scene {
let _ = self.named_parameters.set(&name, &source);
}
}
for (handle, text, points, angle_points) in updates {
for (handle, text, points, angle_points, radial) in updates {
let Some(acadrust::EntityType::Dimension(mut dimension)) =
self.document.get_entity(handle).cloned()
else {
@@ -2096,6 +2169,11 @@ impl super::Scene {
changed = true;
}
}
if let Some((center, radius)) = radial {
if dynamic_dimension_follow_radius(&mut dimension, center, radius) {
changed = true;
}
}
if dimension.base().user_text.as_deref() != Some(text.as_str()) {
crate::entities::dimension::set_dimension_text_override(
dimension.base_mut(),
+8 -7
View File
@@ -996,14 +996,15 @@ fn resolved_target(params: &ParameterTable, constraint: &ParametricConstraint) -
// A dimensional distance keeps its sign in the parameter but measures
// its magnitude, as the reference does (`d1=-50` shortens the line to
// 50; `0` folds the points together).
if constraint.kind == ConstraintKind::Distance {
return Some(value.abs());
// A radius or diameter likewise (`rad1=-20` is a radius of 20); zero is
// no circle at all.
match constraint.kind {
ConstraintKind::Distance => Some(value.abs()),
ConstraintKind::Radius | ConstraintKind::Diameter => {
(value != 0.0).then(|| value.abs())
}
_ => Some(value),
}
let must_be_positive = matches!(
constraint.kind,
ConstraintKind::Radius | ConstraintKind::Diameter
);
(!must_be_positive || value > 0.0).then_some(value)
}
fn oriented_distance(target: f64, current_projection: f64) -> f64 {