From 03cf0f75eb696cc13d3ce07979d1f98b85694558 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Thu, 15 Oct 2026 16:33:30 +0300 Subject: [PATCH] fix(dimension): radius text stands above its line, which runs under it A radius placed its text where it was clicked, on the line through the centre and the arc point, whatever DIMTAD said; outside the circle the line stopped at the text's near edge, so the text sat on the end of the line. DIMTAD now lifts aligned radius text off that line as it already does on a diameter, and an outside line runs on under the text to its far end. A text point already off the line (a file saved elsewhere) is kept as the text's middle. (#1675) Co-Authored-By: Claude Opus 5.5 --- src/entities/dimension.rs | 80 +++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/src/entities/dimension.rs b/src/entities/dimension.rs index 7e0e47339..736576627 100644 --- a/src/entities/dimension.rs +++ b/src/entities/dimension.rs @@ -3924,6 +3924,7 @@ fn tessellate_dimension_inner( dimtofl: style.map(|s| s.dimtofl).unwrap_or(false), text_position: text_layout.position, horizontal_text: text_layout.horizontal, + text_lifted: style.map(|s| s.dimtad).unwrap_or(1) != 0, text_movement: style.map(|s| s.dimtmove).unwrap_or(0), text_break: text_layout.break_box, leader_anchor: style @@ -4847,6 +4848,8 @@ struct DimLineParams { dimtofl: bool, text_position: Vec3, horizontal_text: bool, + /// DIMTAD lifts aligned text off its dimension line. + text_lifted: bool, text_movement: i16, text_break: Option, /// DIMTMOVE 1: the point the file stores is where the leader's hook starts, @@ -5531,6 +5534,14 @@ fn append_radial_leader( } else { add_segment(&mut g.dim_lines, tip, text_edge); } + } else if params.text_lifted { + // Text above or below the line: the line carries on out from the + // rim, under the whole text to its far end. + let along = normalized_or(direction, Vec3::X); + let reach = (text - tip).dot(along) + params.text_width * 0.5; + if reach > 0.0 { + add_segment(&mut g.dim_lines, tip, tip + along * reach); + } } else { let toward = normalized_or(text - tip, Vec3::X); let reach = ((text - tip).length() - params.text_width * 0.5).max(0.0); @@ -7893,18 +7904,47 @@ fn diameter_point_on_leader( point: Vector3, offset: f64, ) -> bool { - let (dx, dy) = ( - d.definition_point.x - d.angle_vertex.x, - d.definition_point.y - d.angle_vertex.y, - ); + point_on_line(d.angle_vertex, d.definition_point, point, offset) +} + +/// Whether `point` lies on the line through `a` and `b` rather than an +/// `offset` (a DIMTAD lift) away from it. +fn point_on_line(a: Vector3, b: Vector3, point: Vector3, offset: f64) -> bool { + let (dx, dy) = (b.x - a.x, b.y - a.y); let length = dx.hypot(dy); if length < 1e-12 { return true; } - let across = ((point.x - d.angle_vertex.x) * dy - (point.y - d.angle_vertex.y) * dx) / length; + let across = ((point.x - a.x) * dy - (point.y - a.y) * dx) / length; across.abs() < offset.abs() * 0.5 } +/// `point` on the line through `a` and `b`, lifted `offset` to the side the +/// line's readable text stands on (below for DIMTAD 4). +fn lift_off_line(a: Vector3, b: Vector3, point: Vector3, offset: f64, dimtad: i16) -> Vector3 { + let (dx, dy) = (b.x - a.x, b.y - a.y); + let len = (dx * dx + dy * dy).sqrt().max(1e-12); + // Match the readable orientation used by text_on_dim_line(). + let (mut nx, mut ny) = (dx / len, dy / len); + if nx < 0.0 || (nx == 0.0 && ny < 0.0) { + nx = -nx; + ny = -ny; + } + let (px, py) = (-ny, nx); + let sign = if dimtad == 4 { -1.0 } else { 1.0 }; + Vector3::new( + point.x + px * offset * sign, + point.y + py * offset * sign, + point.z, + ) +} + +/// DIMTOH / DIMTIH turn this dimension's text horizontal where it sits. +fn dimension_text_reads_horizontal(dim: &Dimension, style: Option<&DimStyle>) -> bool { + let outside = dimension_text_is_outside(dim, style); + style.is_some_and(|style| (outside && style.dimtoh) || (!outside && style.dimtih)) +} + fn dimension_text_pos_f64( dim: &Dimension, style: Option<&DimStyle>, @@ -7956,6 +7996,18 @@ fn dimension_text_pos_f64( let side = if point.x >= tip.x { 1.0 } else { -1.0 }; Vector3::new(point.x + side * (arrow + text_w * 0.5), point.y, point.z) } + // A radius places its text where it was clicked, on the line + // through the centre and the arc point; DIMTAD lifts aligned text + // off that line as it does on a diameter, and the line runs on + // under it. A point already off the line is the text's own + // middle, as a file saved elsewhere writes it. (#1675) + Dimension::Radius(d) + if dimtad != 0 + && !dimension_text_reads_horizontal(dim, style) + && point_on_line(d.angle_vertex, d.definition_point, point, perp_off) => + { + lift_off_line(d.angle_vertex, d.definition_point, point, perp_off, dimtad) + } // A diameter whose text the user dragged keeps the grip point on // the leader and lifts the text by the DIMTAD offset (#1323). // A stored point already standing off the leader is the text's @@ -7966,22 +8018,7 @@ fn dimension_text_pos_f64( && dimtad != 0 && diameter_point_on_leader(d, point, perp_off) => { - let dx = d.definition_point.x - d.angle_vertex.x; - let dy = d.definition_point.y - d.angle_vertex.y; - let len = (dx * dx + dy * dy).sqrt().max(1e-12); - // Match the readable orientation used by text_on_dim_line(). - let (mut nx, mut ny) = (dx / len, dy / len); - if nx < 0.0 || (nx == 0.0 && ny < 0.0) { - nx = -nx; - ny = -ny; - } - let (px, py) = (-ny, nx); - let perp_sign = if dimtad == 4 { -1.0 } else { 1.0 }; - Vector3::new( - point.x + px * perp_off * perp_sign, - point.y + py * perp_off * perp_sign, - point.z, - ) + lift_off_line(d.angle_vertex, d.definition_point, point, perp_off, dimtad) } _ => point, }; @@ -8248,6 +8285,7 @@ pub(crate) fn baked_large_radial_geometry( dimtofl: style.is_some_and(|style| style.dimtofl), text_position: text.position, horizontal_text: text.horizontal, + text_lifted: style.map(|style| style.dimtad).unwrap_or(1) != 0, text_movement: style.map(|style| style.dimtmove).unwrap_or(0), text_break: text.break_box, leader_anchor: None,