forked from 1b-agent/opencadstudio-upstream
Merge pull request #1447 from ramox81:codex/diameter-constraint-parity
#1447 branched from the same commit as #1443 and rewrote the radial dimensional constraint a second time, so the two designs had to be reconciled rather than stacked. #1447's is the one kept: it covers DCRADIUS and DCDIAMETER through one command, places the dimension by the direction the location was picked in rather than storing a text point, and names the parameters `radN` / `diaN`. #1443's radial command result, its entity builder call and its follow-up code are gone; its own independent work stays — the ribbon's small-button grid and tool descriptions, the magnitude reading of a negative radius, and the centre anchor a parameter edit resizes a circle about. The DIMCONSTRAINT default follows #1443: running DCRADIUS or DCDIAMETER directly leaves it alone, and the Dispatch arm sets it when DIMCONSTRAINT is what asked for the command. The second commit's radial drawing — the dimension line broken around the text, the elbowed leader, the centre-mark and arrowhead rules — applied to every radius and diameter dimension, not just a constraint's, and three layout-parity tests said the old layout was the measured one. It is now scoped to a dimensional constraint's own dimension, as #1443 scoped its version, and ordinary radial dimensions draw as they did. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
889a19c362
9 files changed
+706
-415
No files matched your search
@@ -353,36 +353,37 @@ 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(
|
||||
/// The first free `d1`, `d2`, … name a new dimensional constraint takes.
|
||||
pub(crate) fn next_dimensional_parameter_name(
|
||||
table: &super::named_parameters::ParameterTable,
|
||||
prefix: &str,
|
||||
) -> String {
|
||||
(1..)
|
||||
.map(|n| format!("d{n}"))
|
||||
.find(|name| !table.contains(name))
|
||||
.unwrap_or_else(|| "d1".to_string())
|
||||
}
|
||||
|
||||
/// The first free `rad1`/`dia1`, `rad2`/`dia2`, … name a new radius or
|
||||
/// diameter constraint takes.
|
||||
pub(crate) fn next_radial_parameter_name(
|
||||
table: &super::named_parameters::ParameterTable,
|
||||
diameter: bool,
|
||||
) -> String {
|
||||
let prefix = if diameter { "dia" } else { "rad" };
|
||||
(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 {
|
||||
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 {
|
||||
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")
|
||||
(1..)
|
||||
.map(|n| format!("ang{n}"))
|
||||
.find(|name| !table.contains(name))
|
||||
.unwrap_or_else(|| "ang1".to_string())
|
||||
}
|
||||
|
||||
/// An angle as the reference displays it: a negative or over-full value is
|
||||
@@ -577,52 +578,6 @@ 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)> {
|
||||
@@ -659,6 +614,99 @@ fn angular_follow_points(
|
||||
}
|
||||
}
|
||||
|
||||
/// A radial constraint's circle or arc: its centre and radius now.
|
||||
fn radial_geometry(
|
||||
document: &acadrust::CadDocument,
|
||||
constraint: &ParametricConstraint,
|
||||
) -> Option<(glam::DVec3, f64)> {
|
||||
let reference = constraint.refs.first()?;
|
||||
let (center, radius) = match document.get_entity(reference.entity)? {
|
||||
acadrust::EntityType::Circle(circle) => (circle.center, circle.radius),
|
||||
acadrust::EntityType::Arc(arc) => (arc.center, arc.radius),
|
||||
_ => return None,
|
||||
};
|
||||
Some((glam::DVec3::new(center.x, center.y, center.z), radius))
|
||||
}
|
||||
|
||||
/// Rebuilds a dynamic radial dimension on its circle's current centre and
|
||||
/// radius, keeping the direction its dimension line was placed in; true
|
||||
/// when anything moved.
|
||||
fn dynamic_dimension_follow_radial(
|
||||
dimension: &mut acadrust::entities::Dimension,
|
||||
center: glam::DVec3,
|
||||
radius: f64,
|
||||
) -> bool {
|
||||
use acadrust::entities::Dimension;
|
||||
let to_dvec = |p: Vector3| glam::DVec3::new(p.x, p.y, p.z);
|
||||
let same = |a: Vector3, b: Vector3| (a - b).length_squared() < 1.0e-16;
|
||||
let text = dimension.base().user_text.clone();
|
||||
let (rebuilt, leader) = match &*dimension {
|
||||
Dimension::Radius(d) => {
|
||||
let direction = to_dvec(d.definition_point) - to_dvec(d.angle_vertex);
|
||||
let direction = direction
|
||||
.try_normalize()
|
||||
.unwrap_or(glam::DVec3::X);
|
||||
(
|
||||
crate::modules::annotate::radius_dim::radius_constraint_entity(
|
||||
center,
|
||||
radius,
|
||||
center + direction * radius.max(1.0e-9),
|
||||
text,
|
||||
),
|
||||
d.leader_length,
|
||||
)
|
||||
}
|
||||
Dimension::Diameter(d) => {
|
||||
let direction = to_dvec(d.angle_vertex) - to_dvec(d.definition_point);
|
||||
let direction = direction
|
||||
.try_normalize()
|
||||
.unwrap_or(glam::DVec3::X);
|
||||
(
|
||||
crate::modules::annotate::diameter_dim::diameter_constraint_entity(
|
||||
center,
|
||||
radius,
|
||||
center + direction * radius.max(1.0e-9),
|
||||
text,
|
||||
),
|
||||
d.leader_length,
|
||||
)
|
||||
}
|
||||
_ => return false,
|
||||
};
|
||||
let Some(acadrust::EntityType::Dimension(mut rebuilt)) = rebuilt else {
|
||||
return false;
|
||||
};
|
||||
let unchanged = match (&*dimension, &rebuilt) {
|
||||
(Dimension::Radius(old), Dimension::Radius(new)) => {
|
||||
same(old.angle_vertex, new.angle_vertex)
|
||||
&& same(old.definition_point, new.definition_point)
|
||||
}
|
||||
(Dimension::Diameter(old), Dimension::Diameter(new)) => {
|
||||
same(old.angle_vertex, new.angle_vertex)
|
||||
&& same(old.definition_point, new.definition_point)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if unchanged {
|
||||
return false;
|
||||
}
|
||||
match &mut rebuilt {
|
||||
Dimension::Radius(new) => new.leader_length = leader,
|
||||
Dimension::Diameter(new) => new.leader_length = leader,
|
||||
_ => {}
|
||||
}
|
||||
let base = dimension.base();
|
||||
let target = rebuilt.base_mut();
|
||||
target.common = base.common.clone();
|
||||
target.style_name = base.style_name.clone();
|
||||
target.text_rotation = base.text_rotation;
|
||||
target.attachment_point = base.attachment_point;
|
||||
target.line_spacing_style = base.line_spacing_style;
|
||||
target.line_spacing_factor = base.line_spacing_factor;
|
||||
*dimension = rebuilt;
|
||||
true
|
||||
}
|
||||
|
||||
/// Rebuilds a dynamic angular dimension on its sides' current positions,
|
||||
/// keeping its arc point; true when anything moved.
|
||||
fn dynamic_dimension_follow_angle(
|
||||
@@ -731,9 +779,9 @@ 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. A single whole reference can also be a line's length,
|
||||
// whose start is what stays, so the entity decides.
|
||||
// A whole circle or arc (a radius or diameter): its centre stays and the
|
||||
// value resizes it. This is what a parameter edit through -PARAMETERS or
|
||||
// Properties goes through; the command itself passes the anchor directly.
|
||||
if refs.len() == 1
|
||||
&& first.marker.is_none()
|
||||
&& matches!(
|
||||
@@ -2053,7 +2101,7 @@ impl super::Scene {
|
||||
String,
|
||||
Option<(Vector3, Vector3)>,
|
||||
Option<Vec<Vector3>>,
|
||||
Option<(Vector3, f64)>,
|
||||
Option<(glam::DVec3, f64)>,
|
||||
)> = Vec::new();
|
||||
// A reference (driven) constraint's parameter follows the geometry.
|
||||
let mut followed: Vec<(String, f64)> = Vec::new();
|
||||
@@ -2081,15 +2129,21 @@ impl super::Scene {
|
||||
constraint.kind,
|
||||
ConstraintKind::Angle | ConstraintKind::Angle3Point
|
||||
);
|
||||
// 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)?)
|
||||
})
|
||||
let radial = matches!(
|
||||
constraint.kind,
|
||||
ConstraintKind::Radius | ConstraintKind::Diameter
|
||||
);
|
||||
let radial_now = radial
|
||||
.then(|| radial_geometry(&self.document, constraint))
|
||||
.flatten();
|
||||
let measured = radial.map(|(_, radius)| radius);
|
||||
let measured = measured.or(points.filter(|_| !angular).map(|(first, second)| {
|
||||
let radial_measured = radial_now.map(|(_, radius)| {
|
||||
if constraint.kind == ConstraintKind::Diameter {
|
||||
radius * 2.0
|
||||
} else {
|
||||
radius
|
||||
}
|
||||
});
|
||||
let measured = points.filter(|_| !angular).map(|(first, second)| {
|
||||
let delta = second - first;
|
||||
let axis = match self.document.get_entity(*dimension) {
|
||||
Some(acadrust::EntityType::Dimension(
|
||||
@@ -2105,7 +2159,8 @@ impl super::Scene {
|
||||
}
|
||||
_ => delta.length(),
|
||||
}
|
||||
}));
|
||||
});
|
||||
let measured = radial_measured.or(measured);
|
||||
let (name, value, source) = match &constraint.driving_param {
|
||||
Some(DrivingValue::Named(name)) => {
|
||||
let value = match (reference, measured) {
|
||||
@@ -2147,9 +2202,11 @@ impl super::Scene {
|
||||
annotational,
|
||||
decimals,
|
||||
),
|
||||
points,
|
||||
// A radial dimension follows its own circle, not a pair
|
||||
// of constraint points.
|
||||
points.filter(|_| !radial),
|
||||
angle_points,
|
||||
radial,
|
||||
radial_now,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -2177,7 +2234,7 @@ impl super::Scene {
|
||||
}
|
||||
}
|
||||
if let Some((center, radius)) = radial {
|
||||
if dynamic_dimension_follow_radius(&mut dimension, center, radius) {
|
||||
if dynamic_dimension_follow_radial(&mut dimension, center, radius) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -2343,6 +2400,7 @@ impl super::Scene {
|
||||
ov::DIMEXO,
|
||||
ov::DIMEXE,
|
||||
ov::DIMASZ,
|
||||
ov::DIMTAD,
|
||||
ov::DIMTIH,
|
||||
ov::DIMTOH,
|
||||
] {
|
||||
@@ -2381,7 +2439,7 @@ impl super::Scene {
|
||||
continue;
|
||||
}
|
||||
let style_name = dimension.base().style_name.clone();
|
||||
let text_height = self
|
||||
let style = self
|
||||
.document
|
||||
.dim_styles
|
||||
.iter()
|
||||
@@ -2389,23 +2447,40 @@ impl super::Scene {
|
||||
style.name.eq_ignore_ascii_case(&style_name)
|
||||
|| (style_name.trim().is_empty()
|
||||
&& style.name.eq_ignore_ascii_case("Standard"))
|
||||
})
|
||||
});
|
||||
let text_height = style
|
||||
.map(|style| style.dimtxt)
|
||||
.filter(|height| *height > 1e-9)
|
||||
.unwrap_or(0.18);
|
||||
let size_of = |value: Option<f64>, fallback: f64| {
|
||||
value.filter(|size| *size > 1e-9).unwrap_or(fallback)
|
||||
};
|
||||
let arrow_size = size_of(style.map(|style| style.dimasz), text_height);
|
||||
let extension_over = size_of(style.map(|style| style.dimexe), text_height * 0.5);
|
||||
let extension_offset = size_of(style.map(|style| style.dimexo), text_height * 0.25);
|
||||
let scale = f64::from(wpp) * f64::from(DYNAMIC_DIMENSION_TEXT_PX) / text_height;
|
||||
use crate::entities::dim_override as ov;
|
||||
let xdata = &dimension.base().common.extended_data;
|
||||
let current = ov::real(xdata, ov::DIMSCALE);
|
||||
// Text gap, extension offset and overshoot and arrow size follow
|
||||
// the text height on screen (the reference's ratios), whatever
|
||||
// the style says in drawing units.
|
||||
// The gap hugs the text, so it follows the text's screen size.
|
||||
// Arrowheads and extension lines keep the style's size in
|
||||
// drawing units: dividing by the screen factor the style's
|
||||
// DIMSCALE re-applies leaves them unchanged as the view zooms.
|
||||
let sizes = [
|
||||
(ov::DIMGAP, text_height * 0.25),
|
||||
(ov::DIMEXO, text_height * 0.25),
|
||||
(ov::DIMEXE, text_height * 0.5),
|
||||
(ov::DIMASZ, text_height),
|
||||
(ov::DIMEXO, extension_offset / scale),
|
||||
(ov::DIMEXE, extension_over / scale),
|
||||
(ov::DIMASZ, arrow_size / scale),
|
||||
];
|
||||
// A radius or diameter constraint reads on its own dimension
|
||||
// line, which breaks around the text, so the text is centred on
|
||||
// it rather than lifted above it.
|
||||
let radial = matches!(
|
||||
dimension,
|
||||
acadrust::entities::Dimension::Radius(_)
|
||||
| acadrust::entities::Dimension::Diameter(_)
|
||||
);
|
||||
let centred = !radial || ov::int(xdata, ov::DIMTAD) == Some(0);
|
||||
let sizes_set = sizes.iter().all(|(code, size)| {
|
||||
ov::real(xdata, *code).is_some_and(|value| (value - size).abs() < 1e-12)
|
||||
});
|
||||
@@ -2418,9 +2493,21 @@ impl super::Scene {
|
||||
);
|
||||
let horizontal = angular
|
||||
|| (ov::int(xdata, ov::DIMTIH) == Some(1) && ov::int(xdata, ov::DIMTOH) == Some(1));
|
||||
if current.is_some_and(|value| (value - scale).abs() < 1e-9) && horizontal && sizes_set {
|
||||
if current.is_some_and(|value| (value - scale).abs() < 1e-9)
|
||||
&& horizontal
|
||||
&& sizes_set
|
||||
&& centred
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if radial && !centred {
|
||||
ov::set(
|
||||
&mut self.document,
|
||||
handle,
|
||||
ov::DIMTAD,
|
||||
Some(acadrust::xdata::XDataValue::Integer16(0)),
|
||||
);
|
||||
}
|
||||
ov::set(
|
||||
&mut self.document,
|
||||
handle,
|
||||
|
||||
Reference in new issue
Block a user