fix(dimension): harden jogged radius workflow

This commit is contained in:
Hakan Seven committed 2026-08-27 08:09:02 +03:00
commit bc750c6d76
71 files changed
+5834 -860

No files matched your search

+415 -9
View File
@@ -146,6 +146,11 @@ fn properties(dim: &Dimension) -> Vec<PropSection> {
"jog_angle",
d.jog_angle.to_degrees(),
),
ro(
t!("Measurement").as_ref(),
"measurement",
format!("{:.4}", d.measurement()),
),
],
}];
}
@@ -216,6 +221,16 @@ fn properties(dim: &Dimension) -> Vec<PropSection> {
],
}];
}
if let Dimension::Arc(d) = dim {
return vec![PropSection {
title: t!("Misc").into_owned(),
props: vec![Property {
label: t!("Dimension style").into_owned(),
field: "style_name",
value: PropValue::PlainText(d.base.style_name.clone()),
}],
}];
}
let mut props = base_props(dim.base());
match dim {
Dimension::Aligned(d) => {
@@ -2638,6 +2653,33 @@ pub fn style_sections(
}
}
if matches!(dimension, Dimension::Arc(_)) {
if let Some(lines) = sections
.iter_mut()
.find(|section| section.title == t!("Lines & Arrows").as_ref())
{
let symbol = match int(ov::DIMARCSYM, s.dimarcsym) {
1 => "Above dimension text",
2 => "None",
_ => "Preceding dimension text",
};
lines.props.insert(
3,
choice(
t!("Arc length symbol").as_ref(),
"dim_arc_symbol",
symbol,
&[
"Preceding dimension text",
"Above dimension text",
"None",
],
true,
),
);
}
}
if matches!(dimension, Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_)) {
if let Some(text_section) = sections
.iter_mut()
@@ -2914,7 +2956,7 @@ use crate::scene::convert::tessellate::{
add_polyline, add_segment, append_arrow, arrow_from_block_with_deferred_hatch,
normalized_or, ArrowKind, DimGeom,
};
use crate::scene::model::wire_model::{SnapHint, WireModel};
use crate::scene::model::wire_model::{SnapHint, TangentGeom, WireModel};
fn apply_dimension_breaks(
document: &CadDocument,
@@ -3078,6 +3120,50 @@ fn tessellate_dimension_inner(
view_aabb: Option<[f32; 4]>,
world_per_pixel: Option<f32>,
) -> Vec<WireModel> {
if let Dimension::Arc(arc) = dim {
if let Some((local_arc, normal)) = arc_dimension_in_ocs(arc) {
let mut wires = tessellate_dimension_inner(
document,
handle,
&Dimension::Arc(local_arc),
selected,
entity_color,
line_weight_px,
anno_scale,
selected_set,
active_viewport,
bg_color,
None,
world_per_pixel,
);
for wire in &mut wires {
map_wire_ocs_to_wcs(wire, normal);
}
return wires;
}
}
if let Dimension::LargeRadial(radial) = dim {
if let Some((local_radial, normal)) = large_radial_dimension_in_ocs(radial) {
let mut wires = tessellate_dimension_inner(
document,
handle,
&Dimension::LargeRadial(local_radial),
selected,
entity_color,
line_weight_px,
anno_scale,
selected_set,
active_viewport,
bg_color,
None,
world_per_pixel,
);
for wire in &mut wires {
map_wire_ocs_to_wcs(wire, normal);
}
return wires;
}
}
let name = handle.value().to_string();
// (Baked-block fast path moved up into scene::tessellate_entity so the
// recursive call goes through the LOD ladder, not the kernel path.)
@@ -3445,9 +3531,6 @@ fn tessellate_dimension_inner(
// DIMUPT governs interactive creation-time text placement; saved
// geometry already carries the resulting position.
let _ = s.dimupt;
// Per-entity arc-symbol and jog-angle values are consumed by their
// native dimension variants; the resolved style values remain the
// creation defaults.
let _ = (s.dimarcsym, s.dimjogang);
// DIMUNIT is the obsolete pre-R2000 linear unit format; DIMLUNIT
// supersedes it. Read but not honoured.
@@ -3674,6 +3757,46 @@ fn tessellate_dimension_inner(
fill_tris_low: Vec::new(),
});
if let Some(symbol) = style.and_then(|style| {
arc_length_symbol_points(dim, Some(style), dim_txt, dim_scale, style.dimarcsym)
}) {
let mut points = Vec::new();
add_polyline(&mut points, &symbol);
wires.push(WireModel {
point_marker: None,
taper_widths: Vec::new(),
pattern_stations: Vec::new(),
world_width: 0.0,
depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false,
fill_is_2d_solid: false,
render_instance: None,
pick_tris: Vec::new(),
pick_tris_low: Vec::new(),
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: name.clone(),
points,
points_low: Vec::new(),
color: if selected { WireModel::SELECTED } else { text_color },
selected,
aci: 0,
pattern_length: 0.0,
pattern: [0.0; 8],
line_weight_px: 1.0,
snap_pts: vec![],
tangent_geoms: vec![],
key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
fill_tris: vec![],
fill_tris_low: Vec::new(),
});
}
// DIMTFILL: 0=none, 1=drawing background (mask), 2=DIMTFILLCLR.
if let Some(s) = style {
if s.dimtfill == 1 || s.dimtfill == 2 {
@@ -3975,6 +4098,57 @@ fn text_fill_rect(
let p4 = corner(-hx, hy);
Some(vec![p1, p2, p3, p1, p3, p4])
}
fn arc_length_symbol_points(
dim: &Dimension,
style: Option<&DimStyle>,
text_height: f64,
dim_scale: f64,
symbol_position: i16,
) -> Option<Vec<Vec3>> {
if !matches!(dim, Dimension::Arc(_)) || symbol_position == 2 {
return None;
}
let value = dimension_text_value(dim, style)?;
if value.is_empty() || text_height <= 1.0e-12 {
return None;
}
let position = dimension_text_pos_f64(dim, style, text_height, dim_scale);
let rotation = dimension_text_rotation(dim, style);
let (sin_rotation, cos_rotation) = rotation.sin_cos();
let text_width = value.chars().count() as f64 * text_height * 0.6;
let symbol_width = text_height * 0.62;
let symbol_height = text_height * 0.20;
let (center_x, center_y) = if symbol_position == 1 {
(0.0, text_height * 0.72)
} else {
(-(text_width * 0.5 + symbol_width * 0.70), text_height * 0.02)
};
let transform = |x: f64, y: f64| {
let local_x = center_x + x;
let local_y = center_y + y;
Vec3::new(
(position.x + local_x * cos_rotation - local_y * sin_rotation) as f32,
(position.y + local_x * sin_rotation + local_y * cos_rotation) as f32,
position.z as f32,
)
};
let steps = 8usize;
Some(
(0..=steps)
.map(|index| {
let t = index as f64 / steps as f64;
let x = (t - 0.5) * symbol_width;
let normalized = x / (symbol_width * 0.5);
let y = symbol_height * (1.0 - normalized * normalized);
transform(x, y)
})
.collect(),
)
}
struct SuppressFlags {
ext1: bool,
ext2: bool,
@@ -4210,6 +4384,7 @@ fn dimension_geometry(
}
}
Dimension::Arc(d) => {
let explicit_sweep = arc_dimension_angles(d);
append_angular_dimension(
&mut g,
lv(d.center_point),
@@ -4218,10 +4393,7 @@ fn dimension_geometry(
lv(d.definition_point),
arrow1,
arrow2,
d.is_partial.then_some((
d.arc_start_parameter as f32,
d.arc_end_parameter as f32,
)),
explicit_sweep,
params,
suppress,
);
@@ -4633,6 +4805,195 @@ fn two_line_angle_frame(
Some((vertex, start, end))
}
pub(crate) fn arc_dimension_angles(dimension: &DimensionArc) -> Option<(f32, f32)> {
let raw = dimension.arc_end_parameter - dimension.arc_start_parameter;
let mut sweep = raw.rem_euclid(std::f64::consts::TAU);
if sweep <= 1.0e-12 && raw.abs() > 1.0e-12 {
sweep = std::f64::consts::TAU;
}
if sweep > 1.0e-12 {
let start = dimension.arc_start_parameter as f32;
return Some((start, start + sweep as f32));
}
let start = (dimension.first_extension_point.y - dimension.center_point.y)
.atan2(dimension.first_extension_point.x - dimension.center_point.x);
let end = (dimension.second_extension_point.y - dimension.center_point.y)
.atan2(dimension.second_extension_point.x - dimension.center_point.x);
let sweep = (end - start).rem_euclid(std::f64::consts::TAU);
(sweep > 1.0e-12).then_some((start as f32, (start + sweep) as f32))
}
pub(crate) fn arc_dimension_in_ocs(
dimension: &DimensionArc,
) -> Option<(DimensionArc, Vector3)> {
let length = dimension.base.normal.length();
if !length.is_finite() || length <= 1.0e-12 {
return None;
}
let normal = dimension.base.normal / length;
if (normal - Vector3::UNIT_Z).length() <= 1.0e-12 {
return None;
}
let normal_tuple = (normal.x, normal.y, normal.z);
let to_ocs = |point: Vector3| {
let point = crate::scene::view::transform::wcs_point_to_ocs(
(point.x, point.y, point.z),
normal_tuple,
);
Vector3::new(point.0, point.1, point.2)
};
let mut local = dimension.clone();
local.center_point = to_ocs(dimension.center_point);
let elevation = local.center_point.z;
let on_plane = |point: Vector3| {
let mut point = to_ocs(point);
point.z = elevation;
point
};
local.definition_point = on_plane(dimension.definition_point);
local.first_extension_point = on_plane(dimension.first_extension_point);
local.second_extension_point = on_plane(dimension.second_extension_point);
local.first_leader_point = on_plane(dimension.first_leader_point);
local.second_leader_point = on_plane(dimension.second_leader_point);
local.base.definition_point = local.definition_point;
local.base.text_middle_point = on_plane(dimension.base.text_middle_point);
local.base.insertion_point = on_plane(dimension.base.insertion_point);
local.base.normal = Vector3::UNIT_Z;
Some((local, normal))
}
pub(crate) fn large_radial_dimension_in_ocs(
dimension: &DimensionLargeRadial,
) -> Option<(DimensionLargeRadial, Vector3)> {
let length = dimension.base.normal.length();
if !length.is_finite() || length <= 1.0e-12 {
return None;
}
let normal = dimension.base.normal / length;
if (normal - Vector3::UNIT_Z).length() <= 1.0e-12 {
return None;
}
let normal_tuple = (normal.x, normal.y, normal.z);
let to_ocs = |point: Vector3| {
let point = crate::scene::view::transform::wcs_point_to_ocs(
(point.x, point.y, point.z),
normal_tuple,
);
Vector3::new(point.0, point.1, point.2)
};
let mut local = dimension.clone();
local.definition_point = to_ocs(dimension.definition_point);
let elevation = local.definition_point.z;
let on_plane = |point: Vector3| {
let mut point = to_ocs(point);
point.z = elevation;
point
};
local.chord_point = on_plane(dimension.chord_point);
local.override_center = on_plane(dimension.override_center);
local.jog_point = on_plane(dimension.jog_point);
local.base.definition_point = local.definition_point;
local.base.text_middle_point = on_plane(dimension.base.text_middle_point);
local.base.insertion_point = on_plane(dimension.base.insertion_point);
local.base.normal = Vector3::UNIT_Z;
Some((local, normal))
}
fn map_wire_ocs_to_wcs(wire: &mut WireModel, normal: Vector3) {
let normal_tuple = (normal.x, normal.y, normal.z);
let map = |x: f64, y: f64, z: f64| {
crate::scene::view::transform::ocs_point_to_wcs((x, y, z), normal_tuple)
};
let map_split = |high: &mut Vec<[f32; 3]>, low: &mut Vec<[f32; 3]>| {
let mut mapped_high = Vec::with_capacity(high.len());
let mut mapped_low = Vec::with_capacity(high.len());
for (index, point) in high.iter().enumerate() {
if point[0].is_nan() {
mapped_high.push(*point);
mapped_low.push([0.0; 3]);
continue;
}
let residual = low.get(index).copied().unwrap_or([0.0; 3]);
let point = map(
point[0] as f64 + residual[0] as f64,
point[1] as f64 + residual[1] as f64,
point[2] as f64 + residual[2] as f64,
);
let (xh, xl) = WireModel::split_ds(point.0);
let (yh, yl) = WireModel::split_ds(point.1);
let (zh, zl) = WireModel::split_ds(point.2);
mapped_high.push([xh, yh, zh]);
mapped_low.push([xl, yl, zl]);
}
*high = mapped_high;
*low = mapped_low;
};
map_split(&mut wire.points, &mut wire.points_low);
map_split(&mut wire.fill_tris, &mut wire.fill_tris_low);
map_split(&mut wire.pick_tris, &mut wire.pick_tris_low);
wire.text_verts = crate::scene::model::wire_model::map_text_verts(
&wire.text_verts,
map,
);
for (point, _) in &mut wire.snap_pts {
let mapped = map(point.x, point.y, point.z);
*point = DVec3::new(mapped.0, mapped.1, mapped.2);
}
for point in &mut wire.key_vertices {
let mapped = map(point[0], point[1], point[2]);
*point = [mapped.0, mapped.1, mapped.2];
}
if let Some(marker) = &mut wire.point_marker {
let origin = map(marker.origin.x, marker.origin.y, marker.origin.z);
let vector = |value: DVec3| {
let mapped = map(value.x, value.y, value.z);
DVec3::new(mapped.0, mapped.1, mapped.2)
};
marker.origin = DVec3::new(origin.0, origin.1, origin.2);
marker.normal = vector(marker.normal).normalize_or(DVec3::Z);
marker.axis_x = vector(marker.axis_x).normalize_or(DVec3::X);
marker.axis_y = vector(marker.axis_y).normalize_or(DVec3::Y);
}
for tangent in &mut wire.tangent_geoms {
match tangent {
TangentGeom::Line { p1, p2 } => {
let first = map(p1[0] as f64, p1[1] as f64, p1[2] as f64);
let second = map(p2[0] as f64, p2[1] as f64, p2[2] as f64);
*p1 = [first.0 as f32, first.1 as f32, first.2 as f32];
*p2 = [second.0 as f32, second.1 as f32, second.2 as f32];
}
TangentGeom::Circle { center, radius } => {
let center = map(
center[0] as f64,
center[1] as f64,
center[2] as f64,
);
let (x_axis, y_axis) = crate::scene::view::transform::ocs_axes(normal_tuple);
*tangent = TangentGeom::PlanarCircle {
center: [center.0, center.1, center.2],
axis_x: [x_axis.0, x_axis.1, x_axis.2],
axis_y: [y_axis.0, y_axis.1, y_axis.2],
radius: *radius as f64,
};
}
TangentGeom::PlanarCircle { center, axis_x, axis_y, .. }
| TangentGeom::Arc { center, axis_x, axis_y, .. } => {
let mapped = map(center[0], center[1], center[2]);
let map_vector = |axis: [f64; 3]| {
let mapped = map(axis[0], axis[1], axis[2]);
[mapped.0, mapped.1, mapped.2]
};
*center = [mapped.0, mapped.1, mapped.2];
*axis_x = map_vector(*axis_x);
*axis_y = map_vector(*axis_y);
}
}
}
wire.aabb = WireModel::UNBOUNDED_AABB;
}
fn angular_dimension_frame(dim: &Dimension) -> Option<(Vec3, f32, f32, f32)> {
let (vertex, start, end, arc_point) = match dim {
Dimension::Angular2Ln(value) => {
@@ -4659,6 +5020,12 @@ fn angular_dimension_frame(dim: &Dimension) -> Option<(Vec3, f32, f32, f32)> {
two_line_angle_frame(vertex, first, vertex, second, arc_point)?;
(vertex, start, end, arc_point)
}
Dimension::Arc(value) => {
let vertex = vec3_local(value.center_point);
let arc_point = vec3_local(value.definition_point);
let (start, end) = arc_dimension_angles(value)?;
(vertex, start, end, arc_point)
}
_ => return None,
};
let radius = vertex.distance(arc_point);
@@ -5249,7 +5616,7 @@ 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)
Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) | Dimension::Arc(_) => angular_dimension_frame(dim)
.map(|(_, start, end, _)| {
((start + end) * 0.5 + std::f32::consts::FRAC_PI_2) as f64
})
@@ -6183,6 +6550,45 @@ pub(crate) fn baked_dimension_text_entity(
Some(ent)
}
pub(crate) fn baked_arc_length_symbol_points(
dim: &Dimension,
document: &CadDocument,
anno_scale: f64,
) -> Vec<Vector3> {
if !matches!(dim, Dimension::Arc(_)) {
return Vec::new();
}
let style_name = dim.base().style_name.as_str();
let style = document.dim_styles.iter().find(|style| {
style.name.eq_ignore_ascii_case(style_name)
|| (style_name.trim().is_empty() && style.name.eq_ignore_ascii_case("Standard"))
});
let dim_scale = style
.map(|style| {
if style.dimscale > 1.0e-6 {
style.dimscale
} else {
anno_scale
}
})
.unwrap_or(1.0);
let text_height = style
.map(|style| style.dimtxt * dim_scale)
.unwrap_or(2.5 * dim_scale);
let symbol_position = crate::entities::dim_override::int(
&dim.base().common.extended_data,
crate::entities::dim_override::DIMARCSYM,
)
.or_else(|| style.map(|style| style.dimarcsym))
.unwrap_or(0);
arc_length_symbol_points(dim, style, text_height, dim_scale, symbol_position)
.unwrap_or_default()
.into_iter()
.map(|point| Vector3::new(point.x as f64, point.y as f64, point.z as f64))
.collect()
}
pub(crate) fn dimension_text_grip_position(
dim: &Dimension,
document: &CadDocument,