fix(dimension): make DIMTAD/DIMTVP/DIMJUST drive text placement (#94)
Dimension text position ignored the style: the renderer returned the
saved text_middle_point whenever it was non-zero, which it always is for
app-created dims, so DIMTAD ("text over the dimension line" / ISO) and
friends had no effect — text could only be moved by grips.
Use the new acadrust text_user_positioned flag: when the text wasn't
user-placed, compute the position from the style (DIMTAD/DIMTVP/DIMJUST)
anchored on the dimension line (definition_point); when it was (grip drag
or properties edit, both now set the flag), keep the saved point.
Imported drawings keep their fidelity via the flag round-tripping through
DXF/DWG.
Also fixes linear/aligned text anchoring at the measured midpoint instead
of the actual dimension line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
68a3efa83d
commit
006dbe4631
2 changed files with 108 additions and 92 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -56,7 +56,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
|||
[[package]]
|
||||
name = "acadrust"
|
||||
version = "0.3.4"
|
||||
source = "git+https://github.com/HakanSeven12/acadrust?branch=main#677ffce9b3622a788af4edcef08647f3480170ce"
|
||||
source = "git+https://github.com/HakanSeven12/acadrust?branch=main#b4ab9ffedfae44b5731301a94c849a3c8f695105"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
|
|
|
|||
|
|
@ -203,9 +203,20 @@ fn apply_base_prop(base: &mut DimensionBase, field: &str, value: &str) -> bool {
|
|||
base.style_name = value.to_string();
|
||||
true
|
||||
}
|
||||
"text_x" => assign_f64(value, &mut base.text_middle_point.x),
|
||||
"text_y" => assign_f64(value, &mut base.text_middle_point.y),
|
||||
"text_z" => assign_f64(value, &mut base.text_middle_point.z),
|
||||
// Editing the text position in the properties panel pins it to a
|
||||
// user-defined location (stops following DIMTAD). See #94.
|
||||
"text_x" => {
|
||||
base.text_user_positioned = true;
|
||||
assign_f64(value, &mut base.text_middle_point.x)
|
||||
}
|
||||
"text_y" => {
|
||||
base.text_user_positioned = true;
|
||||
assign_f64(value, &mut base.text_middle_point.y)
|
||||
}
|
||||
"text_z" => {
|
||||
base.text_user_positioned = true;
|
||||
assign_f64(value, &mut base.text_middle_point.z)
|
||||
}
|
||||
"text_rotation" => assign_f64(value, &mut base.text_rotation),
|
||||
"horizontal_direction" => assign_f64(value, &mut base.horizontal_direction),
|
||||
"line_spacing_factor" => assign_f64(value, &mut base.line_spacing_factor),
|
||||
|
|
@ -649,6 +660,9 @@ impl Grippable for Dimension {
|
|||
};
|
||||
if grip_id == text_grip {
|
||||
apply_to_v3(&mut self.base_mut().text_middle_point, &apply);
|
||||
// Dragging the text grip pins it to a user-defined location, so it
|
||||
// no longer follows the style (DIMTAD). See #94.
|
||||
self.base_mut().text_user_positioned = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2455,23 +2469,104 @@ fn vec3_local(v: Vector3, off: [f64; 3]) -> Vec3 {
|
|||
)
|
||||
}
|
||||
|
||||
/// Style-driven text anchor on the dimension line: a point on the line through
|
||||
/// `defpt` parallel to (`ax`,`ay`), slid along it per DIMJUST and lifted by
|
||||
/// `perp_off` toward the side the dimension line sits on.
|
||||
fn text_on_dim_line(
|
||||
first: Vector3,
|
||||
second: Vector3,
|
||||
defpt: Vector3,
|
||||
ax: f64,
|
||||
ay: f64,
|
||||
dimjust: i16,
|
||||
perp_off: f64,
|
||||
) -> Vector3 {
|
||||
let px = -ay;
|
||||
let py = ax;
|
||||
// Side of the measured points the dimension line sits on.
|
||||
let off = (defpt.x - first.x) * px + (defpt.y - first.y) * py;
|
||||
let perp_sign = if off >= 0.0 { 1.0 } else { -1.0 };
|
||||
// Along-axis positions of the extension points relative to the dim line.
|
||||
let t1 = (first.x - defpt.x) * ax + (first.y - defpt.y) * ay;
|
||||
let t2 = (second.x - defpt.x) * ax + (second.y - defpt.y) * ay;
|
||||
// DIMJUST: 0=centred, 1/3=near first ext, 2/4=near second ext.
|
||||
let along = match dimjust {
|
||||
1 | 3 => t1,
|
||||
2 | 4 => t2,
|
||||
_ => (t1 + t2) * 0.5,
|
||||
};
|
||||
let bx = defpt.x + ax * along;
|
||||
let by = defpt.y + ay * along;
|
||||
Vector3::new(
|
||||
bx + px * perp_off * perp_sign,
|
||||
by + py * perp_off * perp_sign,
|
||||
defpt.z,
|
||||
)
|
||||
}
|
||||
|
||||
fn dimension_text_pos_f64(dim: &Dimension, style: Option<&DimStyle>, text_height: f64) -> Vector3 {
|
||||
let base = dim.base();
|
||||
|
||||
// DIMTAD: 0=centred (on the line), 1=above. 2/3/4 fall back to "above".
|
||||
let dimtad = style.map(|s| s.dimtad).unwrap_or(1);
|
||||
let dimgap = style.map(|s| s.dimgap).unwrap_or(0.0);
|
||||
let dimjust = style.map(|s| s.dimjust).unwrap_or(0);
|
||||
// DIMTVP vertical-position multiplier (units of dimtxt). Only honoured when
|
||||
// DIMTAD == 0; offsets text perpendicular to the dim line.
|
||||
let dimtvp = style.map(|s| s.dimtvp).unwrap_or(0.0);
|
||||
let perp_off = if dimtad == 0 {
|
||||
dimtvp * text_height
|
||||
} else {
|
||||
text_height * 0.5 + dimgap
|
||||
};
|
||||
|
||||
// Explicit per-entity override (text dragged to a custom location): the
|
||||
// saved point wins. Otherwise the dimension style governs placement.
|
||||
let use_saved = base.text_user_positioned && {
|
||||
let p = base.text_middle_point;
|
||||
p.x * p.x + p.y * p.y + p.z * p.z > 1e-16
|
||||
};
|
||||
if use_saved {
|
||||
return base.text_middle_point;
|
||||
}
|
||||
|
||||
match dim {
|
||||
Dimension::Linear(d) => {
|
||||
let ax = d.rotation.cos();
|
||||
let ay = d.rotation.sin();
|
||||
text_on_dim_line(
|
||||
d.first_point,
|
||||
d.second_point,
|
||||
d.definition_point,
|
||||
ax,
|
||||
ay,
|
||||
dimjust,
|
||||
perp_off,
|
||||
)
|
||||
}
|
||||
Dimension::Aligned(d) => {
|
||||
let dx = d.second_point.x - d.first_point.x;
|
||||
let dy = d.second_point.y - d.first_point.y;
|
||||
let len = (dx * dx + dy * dy).sqrt().max(1e-12);
|
||||
text_on_dim_line(
|
||||
d.first_point,
|
||||
d.second_point,
|
||||
d.definition_point,
|
||||
dx / len,
|
||||
dy / len,
|
||||
dimjust,
|
||||
perp_off,
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
// Non-linear (radius / diameter / angular / ordinate): keep the
|
||||
// saved text point when present for fidelity, else lift the natural
|
||||
// mid point straight up by the style offset.
|
||||
let p = base.text_middle_point;
|
||||
if p.x * p.x + p.y * p.y + p.z * p.z > 1e-16 {
|
||||
return p;
|
||||
}
|
||||
let mid = match dim {
|
||||
Dimension::Aligned(d) => Vector3::new(
|
||||
(d.first_point.x + d.second_point.x) * 0.5,
|
||||
(d.first_point.y + d.second_point.y) * 0.5,
|
||||
(d.first_point.z + d.second_point.z) * 0.5,
|
||||
),
|
||||
Dimension::Linear(d) => Vector3::new(
|
||||
(d.first_point.x + d.second_point.x) * 0.5,
|
||||
(d.first_point.y + d.second_point.y) * 0.5,
|
||||
(d.first_point.z + d.second_point.z) * 0.5,
|
||||
),
|
||||
Dimension::Radius(d) => Vector3::new(
|
||||
(d.angle_vertex.x + d.definition_point.x) * 0.5,
|
||||
(d.angle_vertex.y + d.definition_point.y) * 0.5,
|
||||
|
|
@ -2485,90 +2580,11 @@ fn dimension_text_pos_f64(dim: &Dimension, style: Option<&DimStyle>, text_height
|
|||
Dimension::Angular2Ln(d) => d.dimension_arc,
|
||||
Dimension::Angular3Pt(d) => d.definition_point,
|
||||
Dimension::Ordinate(d) => d.leader_endpoint,
|
||||
_ => base.text_middle_point,
|
||||
};
|
||||
|
||||
// DIMTAD: 0=centred (on the line), 1=above (offset perpendicular), 2=outside,
|
||||
// 3=JIS. We honour 0 and 1; 2/3 fall back to "above".
|
||||
let dimtad = style.map(|s| s.dimtad).unwrap_or(1);
|
||||
let dimgap = style.map(|s| s.dimgap).unwrap_or(0.0);
|
||||
// DIMJUST horizontal placement on the dim line (only meaningful for
|
||||
// linear/aligned dims). 0=centred, 1=near first ext, 2=near second ext,
|
||||
// 3=above first ext (perpendicular text), 4=above second ext.
|
||||
let dimjust = style.map(|s| s.dimjust).unwrap_or(0);
|
||||
// DIMTVP vertical-position multiplier (units of dimtxt). Only honoured
|
||||
// when DIMTAD == 0; offsets text perpendicular to the dim line.
|
||||
let dimtvp = style.map(|s| s.dimtvp).unwrap_or(0.0);
|
||||
|
||||
// Need axis + perp_sign (toward "above").
|
||||
let (axis_x, axis_y, perp_sign, p1, p2) = match dim {
|
||||
Dimension::Linear(d) => {
|
||||
let ax = d.rotation.cos();
|
||||
let ay = d.rotation.sin();
|
||||
let px = -ay;
|
||||
let py = ax;
|
||||
let off = (d.definition_point.x - d.first_point.x) * px
|
||||
+ (d.definition_point.y - d.first_point.y) * py;
|
||||
(
|
||||
ax,
|
||||
ay,
|
||||
if off >= 0.0 { 1.0 } else { -1.0 },
|
||||
d.first_point,
|
||||
d.second_point,
|
||||
)
|
||||
Vector3::new(mid.x, mid.y + perp_off * perp_sign_default(), mid.z)
|
||||
}
|
||||
Dimension::Aligned(d) => {
|
||||
let dx = d.second_point.x - d.first_point.x;
|
||||
let dy = d.second_point.y - d.first_point.y;
|
||||
let len = (dx * dx + dy * dy).sqrt().max(1e-12);
|
||||
let ax = dx / len;
|
||||
let ay = dy / len;
|
||||
let px = -ay;
|
||||
let py = ax;
|
||||
let off = (d.definition_point.x - d.first_point.x) * px
|
||||
+ (d.definition_point.y - d.first_point.y) * py;
|
||||
(
|
||||
ax,
|
||||
ay,
|
||||
if off >= 0.0 { 1.0 } else { -1.0 },
|
||||
d.first_point,
|
||||
d.second_point,
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
// Non-linear: only DIMTAD offset applies; no horizontal shift along axis.
|
||||
let off_perp = if dimtad == 0 {
|
||||
dimtvp * text_height
|
||||
} else {
|
||||
text_height * 0.5 + dimgap
|
||||
};
|
||||
return Vector3::new(mid.x, mid.y + off_perp * perp_sign_default(), mid.z);
|
||||
}
|
||||
};
|
||||
|
||||
// Horizontal slide along the dim axis to honour DIMJUST. Slide endpoints
|
||||
// are the dim-line endpoints (projection of p1/p2 onto the dim line),
|
||||
// approximated here as the def-points themselves (we don't have axis-
|
||||
// projected d1/d2 here without more plumbing).
|
||||
let along_offset = match dimjust {
|
||||
1 => (p1.x - mid.x) * axis_x + (p1.y - mid.y) * axis_y,
|
||||
2 => (p2.x - mid.x) * axis_x + (p2.y - mid.y) * axis_y,
|
||||
3 => (p1.x - mid.x) * axis_x + (p1.y - mid.y) * axis_y,
|
||||
4 => (p2.x - mid.x) * axis_x + (p2.y - mid.y) * axis_y,
|
||||
_ => 0.0,
|
||||
};
|
||||
|
||||
// Perpendicular offset: DIMTAD 0 → DIMTVP * dimtxt, else above-line gap.
|
||||
let perp_offset = if dimtad == 0 {
|
||||
dimtvp * text_height * perp_sign
|
||||
} else {
|
||||
(text_height * 0.5 + dimgap) * perp_sign
|
||||
};
|
||||
|
||||
Vector3::new(
|
||||
mid.x + axis_x * along_offset + (-axis_y) * perp_offset,
|
||||
mid.y + axis_y * along_offset + (axis_x) * perp_offset,
|
||||
mid.z,
|
||||
)
|
||||
}
|
||||
|
||||
fn perp_sign_default() -> f64 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue