fix(dimension): complete angular workflow

This commit is contained in:
Hakan Seven committed 2026-08-25 18:55:25 +03:00
commit 649dfae337
35 files changed
+2084 -264

No files matched your search

+14
View File
@@ -274,6 +274,8 @@ pub fn property_int_code(field: &str) -> Option<i16> {
"dim_line_inside" => DIMSOXD,
"dim_units" => DIMLUNIT,
"dim_precision" => DIMDEC,
"dim_angle_units" => DIMAUNIT,
"dim_angle_precision" => DIMADEC,
"dim_decimal_separator" => DIMDSEP,
"dim_fractional_type" => DIMFRAC,
"dim_text_view_direction" => DIMTXTDIRECTION,
@@ -330,6 +332,9 @@ fn inherited_int(doc: &CadDocument, handle: Handle, code: i16) -> i16 {
DIMTOL => style.dimtol as i16,
DIMLIM => style.dimlim as i16,
DIMALT => style.dimalt as i16,
DIMAZIN => style.dimazin,
DIMAUNIT => style.dimaunit,
DIMADEC => style.dimadec,
DIMTFILL => style.dimtfill,
DIMTALN => 0,
_ => 0,
@@ -451,6 +456,8 @@ pub fn set_property(
"dim_tolerance_suppress_trailing_zeros" => Some((DIMTZIN, 8)),
"dim_alt_tolerance_suppress_leading_zeros" => Some((DIMALTTZ, 4)),
"dim_alt_tolerance_suppress_trailing_zeros" => Some((DIMALTTZ, 8)),
"dim_angle_suppress_leading_zeros" => Some((DIMAZIN, 1)),
"dim_angle_suppress_trailing_zeros" => Some((DIMAZIN, 2)),
_ => None,
};
if let Some((code, bit)) = bit_field {
@@ -624,6 +631,13 @@ pub fn set_property(
"desktop" => Some(6),
_ => trimmed.parse().ok(),
},
"dim_angle_units" => match trimmed.to_ascii_lowercase().as_str() {
"decimal degrees" => Some(0),
"degrees/minutes/seconds" => Some(1),
"gradians" => Some(2),
"radians" => Some(3),
_ => trimmed.parse().ok(),
},
"dim_fractional_type" => match trimmed.to_ascii_lowercase().as_str() {
"horizontal" => Some(0),
"diagonal" => Some(1),
+457 -75
View File
@@ -124,6 +124,18 @@ fn properties(dim: &Dimension) -> Vec<PropSection> {
],
}];
}
if matches!(dim, Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_)) {
return vec![PropSection {
title: t!("Misc").into_owned(),
props: vec![crate::scene::model::object::Property {
label: t!("Dimension style").into_owned(),
field: "style_name",
value: crate::scene::model::object::PropValue::PlainText(
dim.base().style_name.clone(),
),
}],
}];
}
let mut props = base_props(dim.base());
match dim {
Dimension::Aligned(d) => {
@@ -1064,6 +1076,12 @@ fn restore_dim_text_relative_position(
}
fn dimension_line_grip_position(dim: &Dimension) -> Option<DVec3> {
match dim {
Dimension::Angular2Ln(d) => return Some(dv3(&d.dimension_arc)),
Dimension::Angular3Pt(d) => return Some(dv3(&d.definition_point)),
Dimension::Arc(d) => return Some(dv3(&d.definition_point)),
_ => {}
}
let (first, second, defpt, ax, ay) = match dim {
Dimension::Linear(d) => (
d.first_point,
@@ -1117,6 +1135,14 @@ fn dimension_line_grip_position(dim: &Dimension) -> Option<DVec3> {
}
fn above_dimension_text_position(dim: &Dimension) -> Option<DVec3> {
if let Some((vertex, start, end, radius)) = angular_dimension_frame(dim) {
let angle = (start + end) * 0.5;
let direction = DVec3::new(angle.cos() as f64, angle.sin() as f64, 0.0);
return Some(
DVec3::new(vertex.x as f64, vertex.y as f64, vertex.z as f64)
+ direction * (radius as f64 + 1.0),
);
}
let center = dimension_line_grip_position(dim)?;
let (ax, ay) = match dim {
Dimension::Linear(d) => (d.rotation.cos(), d.rotation.sin()),
@@ -1179,11 +1205,12 @@ impl Grippable for Dimension {
center_grip(2, text),
],
Dimension::Angular2Ln(d) => vec![
square_grip(0, dv3(&d.angle_vertex)),
center_grip(1, dv3(&d.first_point)),
center_grip(2, dv3(&d.second_point)),
square_grip(0, dv3(&d.first_point)),
center_grip(1, dv3(&d.second_point)),
center_grip(2, dv3(&d.angle_vertex)),
center_grip(3, dv3(&d.definition_point)),
center_grip(4, text),
center_grip(4, dv3(&d.dimension_arc)),
center_grip(5, text),
],
Dimension::Angular3Pt(d) => vec![
square_grip(0, dv3(&d.angle_vertex)),
@@ -1231,7 +1258,8 @@ impl Grippable for Dimension {
let text_grip = match self {
Dimension::Linear(_) | Dimension::Aligned(_) => 3,
Dimension::Radius(_) | Dimension::Diameter(_) => 2,
Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => 4,
Dimension::Angular2Ln(_) => 5,
Dimension::Angular3Pt(_) => 4,
Dimension::Ordinate(_) => 3,
Dimension::Arc(d) => if d.has_leader { 6 } else { 4 },
Dimension::LargeRadial(_) => 4,
@@ -1274,10 +1302,11 @@ impl Grippable for Dimension {
_ => {}
},
Dimension::Angular2Ln(d) => match grip_id {
0 => apply_to_v3(&mut d.angle_vertex, &apply),
1 => apply_to_v3(&mut d.first_point, &apply),
2 => apply_to_v3(&mut d.second_point, &apply),
0 => apply_to_v3(&mut d.first_point, &apply),
1 => apply_to_v3(&mut d.second_point, &apply),
2 => apply_to_v3(&mut d.angle_vertex, &apply),
3 => apply_to_v3(&mut d.definition_point, &apply),
4 => apply_to_v3(&mut d.dimension_arc, &apply),
_ => {}
},
Dimension::Angular3Pt(d) => match grip_id {
@@ -1325,7 +1354,8 @@ impl Grippable for Dimension {
let (dim_line_grip, text_grip) = match self {
Dimension::Linear(_) | Dimension::Aligned(_) => (2, 3),
Dimension::Radius(_) | Dimension::Diameter(_) => (1, 2),
Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => (3, 4),
Dimension::Angular2Ln(_) => (4, 5),
Dimension::Angular3Pt(_) => (3, 4),
Dimension::Ordinate(_) => (0, 3),
Dimension::Arc(d) => (3, if d.has_leader { 6 } else { 4 }),
Dimension::LargeRadial(_) => (3, 4),
@@ -1393,7 +1423,8 @@ impl Grippable for Dimension {
let (_dim_line_grip, text_grip) = match self {
Dimension::Linear(_) | Dimension::Aligned(_) => (2, 3),
Dimension::Radius(_) | Dimension::Diameter(_) => (1, 2),
Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => (3, 4),
Dimension::Angular2Ln(_) => (4, 5),
Dimension::Angular3Pt(_) => (3, 4),
Dimension::Ordinate(_) => (0, 3),
Dimension::Arc(d) => (3, if d.has_leader { 6 } else { 4 }),
Dimension::LargeRadial(_) => (3, 4),
@@ -1440,7 +1471,8 @@ impl Grippable for Dimension {
let text_grip = match self {
Dimension::Linear(_) | Dimension::Aligned(_) => 3,
Dimension::Radius(_) | Dimension::Diameter(_) => 2,
Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => 4,
Dimension::Angular2Ln(_) => 5,
Dimension::Angular3Pt(_) => 4,
Dimension::Ordinate(_) => 3,
Dimension::Arc(d) => if d.has_leader { 6 } else { 4 },
Dimension::LargeRadial(_) => 4,
@@ -1682,7 +1714,7 @@ pub fn style_sections(
_ => choice_value("None", &["None", "Background", "Color"]),
};
vec![
let mut sections = vec![
PropSection {
title: t!("Lines & Arrows").into_owned(),
props: vec![
@@ -2009,19 +2041,19 @@ pub fn style_sections(
text(
t!("Decimal separator").as_ref(),
"dim_decimal_separator",
decimal_separator,
decimal_separator.clone(),
true,
),
text(
t!("Dim prefix").as_ref(),
t!("Prefix").as_ref(),
"dim_prefix",
dim_prefix,
dim_prefix.clone(),
true,
),
text(
t!("Dim suffix").as_ref(),
t!("Suffix").as_ref(),
"dim_suffix",
dim_suffix,
dim_suffix.clone(),
true,
),
text(
@@ -2312,7 +2344,81 @@ pub fn style_sections(
),
],
},
]
];
if matches!(dimension, Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_)) {
if let Some(text_section) = sections
.iter_mut()
.find(|section| section.title == t!("Text").as_ref())
{
text_section
.props
.retain(|property| property.field != "dim_fractional_type");
}
let angle_zero_suppression = int(ov::DIMAZIN, s.dimazin);
let angle_unit = int(ov::DIMAUNIT, s.dimaunit);
if let Some(primary_units) = sections
.iter_mut()
.find(|section| section.title == t!("Primary Units").as_ref())
{
primary_units.props = vec![
text(
t!("Decimal separator").as_ref(),
"dim_decimal_separator",
decimal_separator,
true,
),
text(t!("Prefix").as_ref(), "dim_prefix", dim_prefix, true),
text(t!("Suffix").as_ref(), "dim_suffix", dim_suffix, true),
choice(
t!("Angle format").as_ref(),
"dim_angle_units",
angular_unit_label(angle_unit),
&[
"Decimal degrees",
"Degrees/minutes/seconds",
"Gradians",
"Radians",
],
true,
),
choice(
t!("Suppress leading zeros").as_ref(),
"dim_angle_suppress_leading_zeros",
yes(angle_zero_suppression & 1 != 0),
&["Yes", "No"],
true,
),
choice(
t!("Suppress trailing zeros").as_ref(),
"dim_angle_suppress_trailing_zeros",
yes(angle_zero_suppression & 2 != 0),
&["Yes", "No"],
true,
),
property(
t!("Precision").as_ref(),
"dim_angle_precision",
PropValue::Choice {
selected: int(ov::DIMADEC, s.dimadec).to_string(),
options: (-1..=8).map(|value| value.to_string()).collect(),
},
),
];
}
sections.retain(|section| section.title != t!("Alternate Units").as_ref());
if let Some(tolerances) = sections
.iter_mut()
.find(|section| section.title == t!("Tolerances").as_ref())
{
tolerances
.props
.retain(|property| !property.field.starts_with("dim_alt_tolerance_"));
}
}
sections
}
fn property(label: &str, field: &'static str, value: PropValue) -> Property {
@@ -2396,6 +2502,15 @@ fn alternate_unit_label(value: i16) -> &'static str {
}
}
fn angular_unit_label(value: i16) -> &'static str {
match value {
1 => "Degrees/minutes/seconds",
2 => "Gradians",
3 => "Radians",
_ => "Decimal degrees",
}
}
fn tolerance_vertical_label(value: i16) -> &'static str {
match value {
0 => "Bottom",
@@ -2458,7 +2573,7 @@ use acadrust::{CadDocument, EntityType, Handle};
use crate::scene::convert::tess_util::aci_to_rgba;
use crate::scene::convert::tessellate::{
add_polyline, add_segment, append_arrow, arrow_from_block_with_deferred_hatch,
add_segment, append_arrow, arrow_from_block_with_deferred_hatch,
normalized_or, ArrowKind, DimGeom,
};
use crate::scene::model::wire_model::{SnapHint, WireModel};
@@ -3410,6 +3525,8 @@ fn dimtmove_leader_endpoints(dim: &Dimension) -> Option<(Vec3, Vec3)> {
}
Dimension::Radius(d) => lv(d.definition_point),
Dimension::Diameter(d) => (lv(d.angle_vertex) + lv(d.definition_point)) * 0.5,
Dimension::Angular2Ln(d) => lv(d.dimension_arc),
Dimension::Angular3Pt(d) => lv(d.definition_point),
_ => return None,
};
Some((anchor, lv(txt)))
@@ -3612,12 +3729,14 @@ fn dimension_geometry(
Some((vertex, start, end)) => append_angular_dimension(
&mut g,
vertex,
vertex + Vec3::new(start.cos(), start.sin(), 0.0) * vertex.distance(arc_point),
vertex + Vec3::new(end.cos(), end.sin(), 0.0) * vertex.distance(arc_point),
vertex,
vertex,
arc_point,
arrow1,
arrow2,
Some((start, end)),
params,
suppress,
),
// Parallel lines have no vertex and so no angle to draw; the
// extension lines alone say where the dimension was.
@@ -3628,15 +3747,29 @@ fn dimension_geometry(
}
}
Dimension::Angular3Pt(d) => {
let vertex = lv(d.angle_vertex);
let first = lv(d.first_point);
let second = lv(d.second_point);
let arc_point = lv(d.definition_point);
let explicit_sweep = two_line_angle_frame(
vertex,
first,
vertex,
second,
arc_point,
)
.map(|(_, start, end)| (start, end));
append_angular_dimension(
&mut g,
lv(d.angle_vertex),
lv(d.first_point),
lv(d.second_point),
lv(d.definition_point),
vertex,
first,
second,
arc_point,
arrow1,
arrow2,
None,
explicit_sweep,
params,
suppress,
);
}
Dimension::Ordinate(d) => {
@@ -3664,6 +3797,8 @@ fn dimension_geometry(
d.arc_start_parameter as f32,
d.arc_end_parameter as f32,
)),
params,
suppress,
);
if d.has_leader {
add_segment(
@@ -3924,14 +4059,13 @@ fn two_line_angle_frame(
arc_point: Vec3,
) -> Option<(Vec3, f32, f32)> {
let (u, v) = (a2 - a1, b2 - b1);
let denominator = u.x * v.y - u.y * v.x;
// Near-parallel: the crossing runs away to infinity and the sweep with it.
if denominator.abs() <= 1e-9 * u.length().max(v.length()).max(1.0) {
return None;
}
let w = b1 - a1;
let t = (w.x * v.y - w.y * v.x) / denominator;
let vertex = a1 + u * t;
let (t, _) = cadkernel::geom2d::line_line(
[a1.x as f64, a1.y as f64],
[u.x as f64, u.y as f64],
[b1.x as f64, b1.y as f64],
[v.x as f64, v.y as f64],
)?;
let vertex = a1 + u * t as f32;
// Two lines cross at four angles; the arc point picks one. Each line
// contributes its direction and its reverse, so try the four pairs and keep
@@ -3965,6 +4099,38 @@ fn two_line_angle_frame(
Some((vertex, start, end))
}
fn angular_dimension_frame(dim: &Dimension) -> Option<(Vec3, f32, f32, f32)> {
let (vertex, start, end, arc_point) = match dim {
Dimension::Angular2Ln(value) => {
let first_start = vec3_local(value.first_point);
let first_end = vec3_local(value.second_point);
let second_start = vec3_local(value.angle_vertex);
let second_end = vec3_local(value.definition_point);
let arc_point = vec3_local(value.dimension_arc);
let (vertex, start, end) = two_line_angle_frame(
first_start,
first_end,
second_start,
second_end,
arc_point,
)?;
(vertex, start, end, arc_point)
}
Dimension::Angular3Pt(value) => {
let vertex = vec3_local(value.angle_vertex);
let first = vec3_local(value.first_point);
let second = vec3_local(value.second_point);
let arc_point = vec3_local(value.definition_point);
let (vertex, start, end) =
two_line_angle_frame(vertex, first, vertex, second, arc_point)?;
(vertex, start, end, arc_point)
}
_ => return None,
};
let radius = vertex.distance(arc_point);
(radius > 1.0e-6).then_some((vertex, start, end, radius))
}
fn append_angular_dimension(
g: &mut DimGeom,
vertex: Vec3,
@@ -3974,6 +4140,8 @@ fn append_angular_dimension(
arrow1: &ArrowKind,
arrow2: &ArrowKind,
explicit_sweep: Option<(f32, f32)>,
params: DimLineParams,
suppress: SuppressFlags,
) {
let radius = vertex.distance(arc_point);
if radius <= 1e-6 {
@@ -3988,8 +4156,30 @@ fn append_angular_dimension(
let (start, mut end) = explicit_sweep.unwrap_or((measured_start, measured_end));
let dir1 = Vec3::new(start.cos(), start.sin(), 0.0);
let dir2 = Vec3::new(end.cos(), end.sin(), 0.0);
add_segment(&mut g.ext_lines, first, vertex + dir1 * radius);
add_segment(&mut g.ext_lines, second, vertex + dir2 * radius);
let arc_start = vertex + dir1 * radius;
let arc_end = vertex + dir2 * radius;
let extension = |origin: Vec3, endpoint: Vec3, direction: Vec3| {
if params.dimfxlon {
(
endpoint - direction * params.dimfxl.max(0.0),
endpoint + direction * params.dimexe,
)
} else {
let toward_arc = normalized_or(endpoint - origin, direction);
(
origin + toward_arc * params.dimexo,
endpoint + toward_arc * params.dimexe,
)
}
};
let (ext1_start, ext1_end) = extension(first, arc_start, dir1);
let (ext2_start, ext2_end) = extension(second, arc_end, dir2);
if !suppress.ext1 {
add_segment(&mut g.ext_lines, ext1_start, ext1_end);
}
if !suppress.ext2 {
add_segment(&mut g.ext_lines, ext2_start, ext2_end);
}
let mut delta = end - start;
// Wrap a negative sweep forwards, but leave a zero one alone: two rays that
@@ -4006,32 +4196,158 @@ fn append_angular_dimension(
delta = end - start;
}
let steps = 32;
let mut arc_pts = Vec::with_capacity((steps + 1) as usize);
for i in 0..=steps {
let t = i as f32 / steps as f32;
let a = start + delta * t;
arc_pts.push(vertex + Vec3::new(a.cos() * radius, a.sin() * radius, 0.0));
let arc_length = radius * delta.abs();
let arrows_outside = if params.ticks || params.arrow_len <= 1.0e-6 {
false
} else if arc_length < params.arrow_len * 2.0 {
true
} else if arc_length < params.text_width + params.arrow_len * 2.0 {
match params.dimatfit {
0 | 1 => true,
2 => false,
_ => params.text_width <= arc_length,
}
} else {
false
};
let draw_inside_line = !arrows_outside || params.dimtofl;
let arc_extension = if params.ticks { params.dimdle / radius } else { 0.0 };
let direction = delta.signum();
let draw_start = start - direction * arc_extension;
let draw_delta = delta + direction * arc_extension * 2.0;
let arc_pts = sample_angular_arc(vertex, radius, draw_start, draw_delta);
let steps = arc_pts.len().saturating_sub(1);
if draw_inside_line {
for index in 0..steps {
let t = (index as f32 + 0.5) / steps as f32;
if (t < 0.5 && suppress.dim1) || (t >= 0.5 && suppress.dim2) {
continue;
}
let a = arc_pts[index];
let b = arc_pts[index + 1];
let hidden_by_text = params.text_break.is_some_and(|(center, half_width, half_height)| {
let angle = draw_start + draw_delta * t;
let radial = Vec3::new(angle.cos(), angle.sin(), 0.0);
let tangent = Vec3::new(-angle.sin(), angle.cos(), 0.0);
let offset = center - (a + b) * 0.5;
offset.dot(tangent).abs() <= half_width
&& offset.dot(radial).abs() <= half_height
});
if !hidden_by_text {
add_segment(&mut g.dim_lines, a, b);
}
}
}
add_polyline(&mut g.dim_lines, &arc_pts);
if arc_pts.len() >= 2 {
if arrows_outside && !params.dimsoxd {
let stub_angle = (params.arrow_len * 2.0 / radius).min(std::f32::consts::FRAC_PI_2);
if !suppress.dim1 {
append_sampled_arc(
&mut g.dim_lines,
vertex,
radius,
start - direction * stub_angle,
start,
);
}
if !suppress.dim2 {
append_sampled_arc(
&mut g.dim_lines,
vertex,
radius,
end,
end + direction * stub_angle,
);
}
}
if let Some((text_center, half_width, _)) = params.text_break {
let text_angle = (text_center.y - vertex.y).atan2(text_center.x - vertex.x);
let into = (text_angle - start).rem_euclid(std::f32::consts::TAU);
if into > delta.abs() + 1.0e-6 {
let back_to_start = (start - text_angle).rem_euclid(std::f32::consts::TAU);
let forward_from_end = (text_angle - end).rem_euclid(std::f32::consts::TAU);
let text_gap = (half_width / radius).min(std::f32::consts::FRAC_PI_2);
if back_to_start <= forward_from_end && !suppress.dim1 {
append_sampled_arc(
&mut g.dim_lines,
vertex,
radius,
text_angle + text_gap,
start,
);
} else if !suppress.dim2 {
append_sampled_arc(
&mut g.dim_lines,
vertex,
radius,
end,
text_angle - text_gap,
);
}
}
}
let start_tangent = Vec3::new(-start.sin(), start.cos(), 0.0) * direction;
let end_tangent = Vec3::new(-end.sin(), end.cos(), 0.0) * direction;
let draw_arrows = !arrows_outside || !params.dimsoxd;
if draw_arrows && !suppress.dim1 {
append_arrow(
g,
arc_pts[0],
normalized_or(arc_pts[1] - arc_pts[0], Vec3::X),
arc_start,
if arrows_outside { -start_tangent } else { start_tangent },
arrow1,
);
let n = arc_pts.len();
}
if draw_arrows && !suppress.dim2 {
append_arrow(
g,
arc_pts[n - 1],
normalized_or(arc_pts[n - 2] - arc_pts[n - 1], Vec3::X),
arc_end,
if arrows_outside { end_tangent } else { -end_tangent },
arrow2,
);
}
}
fn append_sampled_arc(
lines: &mut Vec<[f32; 3]>,
vertex: Vec3,
radius: f32,
start: f32,
end: f32,
) {
for pair in sample_angular_arc(vertex, radius, start, end - start).windows(2) {
add_segment(lines, pair[0], pair[1]);
}
}
fn sample_angular_arc(vertex: Vec3, radius: f32, start: f32, sweep: f32) -> Vec<Vec3> {
if sweep.abs() <= 1.0e-6 {
return vec![vertex + Vec3::new(start.cos() * radius, start.sin() * radius, 0.0)];
}
let end = start + sweep;
let (from, to, reverse) = if sweep >= 0.0 {
(start, end, false)
} else {
(end, start, true)
};
let mut points: Vec<_> = cadkernel::geom2d::tessellate::arc(
[vertex.x as f64, vertex.y as f64],
radius as f64,
from as f64,
to as f64,
vertex.z as f64,
cadkernel::geom2d::tessellate::DEFAULT_SEGMENTS_PER_RADIAN,
)
.into_iter()
.map(|point| Vec3::new(point[0] as f32, point[1] as f32, point[2] as f32))
.collect();
if reverse {
points.reverse();
}
points
}
fn dimension_snap_pts(dim: &Dimension) -> Vec<(glam::DVec3, SnapHint)> {
let lv = |v: acadrust::types::Vector3| glam::DVec3::new(v.x, v.y, v.z);
let node = |v: acadrust::types::Vector3| (lv(v), SnapHint::Node);
@@ -4049,10 +4365,11 @@ fn dimension_snap_pts(dim: &Dimension) -> Vec<(glam::DVec3, SnapHint)> {
Dimension::Radius(d) => vec![node(d.angle_vertex), node(d.definition_point)],
Dimension::Diameter(d) => vec![node(d.angle_vertex), node(d.definition_point)],
Dimension::Angular2Ln(d) => vec![
node(d.angle_vertex),
node(d.first_point),
node(d.second_point),
node(d.angle_vertex),
node(d.definition_point),
node(d.dimension_arc),
],
Dimension::Angular3Pt(d) => vec![
node(d.angle_vertex),
@@ -4288,6 +4605,32 @@ fn dimension_text_is_outside(dim: &Dimension, style: Option<&DimStyle>) -> bool
let Some(style) = style else {
return false;
};
if let Some((vertex, start, end, radius)) = angular_dimension_frame(dim) {
if dim.base().text_user_positioned {
let text = vec3_local(dim.base().text_middle_point);
let angle = (text.y - vertex.y).atan2(text.x - vertex.x);
return (angle - start).rem_euclid(std::f32::consts::TAU)
> end - start + 1.0e-6;
}
if style.dimtix {
return false;
}
let scale = if style.dimscale > 1e-9 { style.dimscale } else { 1.0 };
let height = style.dimtxt * scale;
let gap = style.dimgap.abs() * scale;
let text_width = dimension_text_value(dim, Some(style))
.map(|value| value.chars().count() as f64 * height * 0.6 + gap * 2.0)
.unwrap_or(0.0);
let arrow = style.dimasz * scale;
let span = radius as f64 * (end - start).abs() as f64;
let insufficient = text_width + arrow * 2.0 > span;
return insufficient
&& match style.dimatfit {
0 | 2 => true,
1 | 3 => text_width > span,
_ => text_width > span,
};
}
let (first, second, axis) = match dim {
Dimension::Linear(d) => (
d.first_point,
@@ -4338,6 +4681,11 @@ fn dimension_text_natural_rotation(dim: &Dimension) -> f64 {
let dy = d.second_point.y - d.first_point.y;
dy.atan2(dx)
}
Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => angular_dimension_frame(dim)
.map(|(_, start, end, _)| {
((start + end) * 0.5 + std::f32::consts::FRAC_PI_2) as f64
})
.unwrap_or(0.0),
_ => 0.0,
};
// Clamp to (-π/2, π/2] so text never appears upside-down.
@@ -4822,54 +5170,62 @@ fn reduce_fraction(mut n: u64, mut d: u64) -> String {
}
}
/// Format an angular measurement (input in degrees as Dimension::measurement
/// returns for angular variants) honouring DIMAUNIT, DIMADEC, DIMAZIN.
/// Format an angular measurement in degrees using the angular style settings.
fn format_angular_value(measurement_deg: f64, style: Option<&DimStyle>) -> String {
let (aunit, adec, azin) = style
.map(|s| (s.dimaunit, s.dimadec, s.dimazin))
.unwrap_or((0, 2, 0));
let adec = adec.max(0) as usize;
let (aunit, adec, azin, decimal_separator) = style
.map(|s| {
let precision = if s.dimadec < 0 { s.dimdec } else { s.dimadec };
(s.dimaunit, precision, s.dimazin, s.dimdsep)
})
.unwrap_or((0, 2, 0, b'.' as i16));
let adec = adec.clamp(0, 8) as usize;
match aunit {
// 1 = Degrees / Minutes / Seconds
1 => format_dms(measurement_deg, adec, azin),
1 => format_dms(measurement_deg, adec, azin, decimal_separator),
// 2 = Gradians
2 => {
let g = measurement_deg / 0.9;
let raw = format!("{:.*}", adec, g);
format!("{}g", apply_angular_zero_suppression(&raw, azin))
format!(
"{}g",
swap_decimal_sep(&apply_angular_zero_suppression(&raw, azin), decimal_separator)
)
}
// 3 = Radians
3 => {
let r = measurement_deg.to_radians();
let raw = format!("{:.*}", adec, r);
format!("{}r", apply_angular_zero_suppression(&raw, azin))
format!(
"{}r",
swap_decimal_sep(&apply_angular_zero_suppression(&raw, azin), decimal_separator)
)
}
// 0 or unknown = Decimal Degrees
_ => {
let raw = format!("{:.*}", adec, measurement_deg);
format!("{}°", apply_angular_zero_suppression(&raw, azin))
format!(
"{}°",
swap_decimal_sep(&apply_angular_zero_suppression(&raw, azin), decimal_separator)
)
}
}
}
fn format_dms(deg: f64, sec_dec: usize, azin: i16) -> String {
fn format_dms(deg: f64, sec_dec: usize, azin: i16, decimal_separator: i16) -> String {
let sign = if deg < 0.0 { "-" } else { "" };
let abs = deg.abs();
let d = abs.floor();
let m_full = (abs - d) * 60.0;
let m = m_full.floor();
let s = (m_full - m) * 60.0;
let scale = 10_u64.pow(sec_dec.min(8) as u32) as f64;
let total_ticks = (deg.abs() * 3600.0 * scale).round();
let d = (total_ticks / (3600.0 * scale)).floor();
let remaining = total_ticks - d * 3600.0 * scale;
let m = (remaining / (60.0 * scale)).floor();
let s = (remaining - m * 60.0 * scale) / scale;
let s_str = format!("{:.*}", sec_dec, s);
let mut out = format!("{}{:.0}°{:.0}'{}\"", sign, d, m, s_str);
if azin & 4 != 0 {
// suppress 0° / 0' parts
if d == 0.0 {
out = out.trim_start_matches('0').to_string();
out = out.replacen("°", "", 1);
}
}
out
let s_str = swap_decimal_sep(
&apply_angular_zero_suppression(&s_str, azin),
decimal_separator,
);
format!("{}{:.0}°{:.0}'{}\"", sign, d, m, s_str)
}
/// Apply DIMZIN bit flags to a formatted linear value.
@@ -5119,6 +5475,32 @@ fn dimension_text_pos_f64(
dimtad,
)
}
Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => {
let Some((vertex, start, end, radius)) = angular_dimension_frame(dim) else {
return base.text_middle_point;
};
let span = radius as f64 * (end - start).abs() as f64;
let insufficient = text_w + arrow * 2.0 > span;
let move_outside = !dimtix
&& insufficient
&& match dimatfit {
0 | 2 => true,
1 | 3 => text_w > span,
_ => text_w > span,
};
let angle = if move_outside {
end as f64 + (text_w * 0.5 + dimgap + arrow) / (radius as f64).max(1.0e-12)
} else {
((start + end) * 0.5) as f64
};
let radial_offset = if dimtad == 4 { -perp_off } else { perp_off };
let text_radius = (radius as f64 + radial_offset).max(0.0);
Vector3::new(
vertex.x as f64 + angle.cos() * text_radius,
vertex.y as f64 + angle.sin() * text_radius,
vertex.z as f64,
)
}
_ => {
// Non-linear (radius / diameter / angular / ordinate): lift the
// natural mid point straight up by the style offset. A user-dragged