Aligned dimensional constraint: Point & line, 2Lines, value and property parity

DCALIGNED opens with `[Object/Point & line/2Lines] <Object>`. Point & line
asks `Specify constraint point or [Line] <Line>:` then `Select line:` (or the
line first and `Select constraint point:`), measures perpendicular to the
line from the constraint point to the line end nearest to it, and stores a
directed distance with the line as direction reference. 2Lines asks
`Select first line:` and `Select second line to make parallel:`, adds a
Parallel constraint solved with the first line pinned, then measures on the
solved geometry between the nearest ends. Unsupported line picks report the
reference's message; the same measurement again reports `The constraint
already exists on the selected objects.` and ends the command; an invalid
expression reports both reference lines and asks again.

The offered default and seeded expression carry twelve significant digits;
a typed value moves the second point (or the other object) while the first
point and its measured line stay, negative values keep their sign in the
parameter and use their magnitude, zero folds the points together, and the
dynamic dimension's origins follow every solve. The lock mark sits after the
text, a selected dynamic dimension shows triangle grips at its points and the
text square, and its Properties show the Constraint section (Name and
Expression editable) plus Text rotation.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ramox81andClaude Fable 5.1 committed 2026-09-21 14:52:37 +03:00
1 parent 971abe64a4
commit 6969c19746
8 files changed
+855 -64

No files matched your search

+120
View File
@@ -2292,6 +2292,28 @@ impl OpenCADStudio {
}
_ => entity_type_label(entity),
};
// A dynamic dimension shows only its constraint and text
// rotation, as the reference does.
let title = match crate::scene::parametric_constraints::dynamic_dimension_constraint(
&self.tabs[i].scene.parametric_constraints,
handle,
) {
Some((set, constraint)) => {
sections = dynamic_dimension_sections(
&self.tabs[i].scene,
set,
constraint,
sections,
);
match entity {
acadrust::EntityType::Dimension(
acadrust::entities::Dimension::Aligned(_),
) => t!("Aligned Dimensional Constraint").into_owned(),
_ => t!("Linear Dimensional Constraint").into_owned(),
}
}
None => title,
};
ui::PropertiesPanel {
choice_combos: sections
.iter()
@@ -2739,6 +2761,26 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={}
}
}
}
if self.tabs[i].scene.is_dynamic_dimension(handle) {
// A dynamic dimension shows the reference's grips: a
// triangle at each constraint point aimed at the other
// one, and the text square — no dimension line grip.
let points: Vec<glam::DVec3> = entity_grips
.iter()
.filter(|grip| grip.id <= 1)
.map(|grip| grip.world)
.collect();
entity_grips.retain(|grip| grip.id != 2);
for grip in &mut entity_grips {
if grip.id <= 1 {
let other = points.get(1 - grip.id).copied().unwrap_or(grip.world);
let dir = (other - grip.world).normalize_or(glam::DVec3::X);
*grip = crate::entities::common::oriented_triangle_grip(
grip.id, grip.world, dir,
);
}
}
}
entity_grips.extend(crate::scene::model::solid_history::primitive_grips(
&self.tabs[i].scene.document,
handle,
@@ -3572,6 +3614,84 @@ fn set_row(sections: &mut [crate::scene::model::object::PropSection], field: &st
/// Replace a row's value with an arbitrary control (editable field, dropdown,
/// colour picker …) rather than plain read-only text.
/// A dynamic dimension's Properties as the reference shows them: the
/// Constraint rows, then only the text rotation of the dimension itself.
fn dynamic_dimension_sections(
scene: &crate::scene::Scene,
set: &crate::scene::parametric_constraints::ParametricConstraintSet,
constraint: &crate::scene::parametric_constraints::ParametricConstraint,
sections: Vec<crate::scene::model::object::PropSection>,
) -> Vec<crate::scene::model::object::PropSection> {
use crate::scene::model::object::{PropSection, PropValue, Property};
use crate::scene::named_parameters::DrivingValue;
let table = if set.local_parameters.is_empty() {
scene.named_parameters()
} else {
&set.local_parameters
};
let (name, expression, value) = match &constraint.driving_param {
Some(DrivingValue::Named(name)) => (
name.clone(),
table
.iter()
.find(|parameter| &parameter.name == name)
.map(|parameter| parameter.source.clone())
.unwrap_or_default(),
table.resolve(name).ok(),
),
Some(DrivingValue::Literal(value)) => (String::new(), format!("{value}"), Some(*value)),
None => (String::new(), String::new(), None),
};
let row = |label: &str, field: &'static str, value: PropValue| Property {
label: label.to_string(),
field,
value,
};
let text_rotation = sections
.iter()
.flat_map(|section| section.props.iter())
.find(|property| property.field == "text_rotation")
.cloned();
let mut result = vec![PropSection {
title: t!("Constraint").into_owned(),
props: vec![
row(
t!("Constraint Form").as_ref(),
"dyn_constraint_form",
PropValue::ReadOnly("Dynamic".to_string()),
),
row(
t!("Reference").as_ref(),
"dyn_constraint_reference",
PropValue::ReadOnly(t!("No").into_owned()),
),
row(t!("Name").as_ref(), "dyn_constraint_name", PropValue::EditText(name)),
row(
t!("Expression").as_ref(),
"dyn_constraint_expression",
PropValue::EditText(expression),
),
row(
t!("Value").as_ref(),
"dyn_constraint_value",
PropValue::ReadOnly(value.map(|v| format!("{v:.4}")).unwrap_or_default()),
),
row(
t!("Description").as_ref(),
"dyn_constraint_description",
PropValue::ReadOnly(String::new()),
),
],
}];
if let Some(text_rotation) = text_rotation {
result.push(PropSection {
title: t!("Text").into_owned(),
props: vec![text_rotation],
});
}
result
}
fn set_row_value(
sections: &mut [crate::scene::model::object::PropSection],
field: &str,