Use shared unit formatters across property and dynamic views

This commit is contained in:
Marcel committed 2026-09-17 08:30:20 +02:00
1 parent ca0ee37d26
commit fca96b57de
10 files changed
+78 -65

No files matched your search

+10 -12
View File
@@ -346,26 +346,24 @@ pub(super) fn dyn_component_value(
) && relative ) && relative
&& !comma_cartesian; && !comma_cartesian;
match f.component { match f.component {
DynComponent::X if relative => format!("{:.4}", if wh { dx.abs() } else { dx }), DynComponent::X if relative => crate::entities::common::format_length(if wh { dx.abs() } else { dx }),
DynComponent::Y if relative => format!("{:.4}", if wh { dy.abs() } else { dy }), DynComponent::Y if relative => crate::entities::common::format_length(if wh { dy.abs() } else { dy }),
DynComponent::Z if relative => "0.0000".to_string(), DynComponent::Z if relative => crate::entities::common::format_length(0.0),
DynComponent::X => format!("{:.4}", p.x), DynComponent::X => crate::entities::common::format_length(p.x),
DynComponent::Y => format!("{:.4}", p.y), DynComponent::Y => crate::entities::common::format_length(p.y),
DynComponent::Z => format!("{:.4}", p.z), DynComponent::Z => crate::entities::common::format_length(p.z),
// Scaled by the role so a diameter box reads twice the radius. // Scaled by the role so a diameter box reads twice the radius.
DynComponent::Distance => { DynComponent::Distance => {
format!( crate::entities::common::format_length(
"{:.4}", (dx * dx + dy * dy).sqrt() * f.role.value_scale() as f64,
(dx * dx + dy * dy).sqrt() * f.role.value_scale() as f64
) )
} }
// Shared rule: unsigned magnitude of the short angle, so CW (below the // Shared rule: unsigned magnitude of the short angle, so CW (below the
// reference axis) reads positive (e.g. 30°, not -30°/330°). The // reference axis) reads positive (e.g. 30°, not -30°/330°). The
// committed value stays signed (see dyn_resolve_point). // committed value stays signed (see dyn_resolve_point).
DynComponent::Angle => { DynComponent::Angle => {
format!( crate::entities::common::format_angle(
"{:.1}", (crate::command::dyn_display_angle_deg(dy.atan2(dx) as f32) as f64).to_radians(),
crate::command::dyn_display_angle_deg(dy.atan2(dx) as f32)
) )
} }
// Typed-only scalar — no geometric value to track when empty. // Typed-only scalar — no geometric value to track when empty.
+14 -4
View File
@@ -213,6 +213,12 @@ impl OpenCADStudio {
let i = self.active_tab; let i = self.active_tab;
let tab = &self.tabs[i]; let tab = &self.tabs[i];
// Dynamic input is built before the status bar and properties panel,
// so seed its shared formatter here rather than relying on either of
// those later views to have done it for this drawing/thread.
crate::entities::common::set_unit_context(
crate::entities::common::UnitContext::from_header(&tab.scene.document.header),
);
let thumbnail_capture_clean = self.thumbnail_capture_clean; let thumbnail_capture_clean = self.thumbnail_capture_clean;
let theme_text = self.active_theme.palette().background.base.text; let theme_text = self.active_theme.palette().background.base.text;
let viewcube_text_color = [theme_text.r, theme_text.g, theme_text.b, theme_text.a]; let viewcube_text_color = [theme_text.r, theme_text.g, theme_text.b, theme_text.a];
@@ -1068,15 +1074,15 @@ bg={bg_ms:.1}ms n={view_count}"
(None, _) if rectangle_values.is_some() => { (None, _) if rectangle_values.is_some() => {
let (width, height) = rectangle_values.unwrap(); let (width, height) = rectangle_values.unwrap();
match f.role { match f.role {
crate::command::DynRole::Width => format!("{width:.4}"), crate::command::DynRole::Width => crate::entities::common::format_length(width),
crate::command::DynRole::Height => format!("{height:.4}"), crate::command::DynRole::Height => crate::entities::common::format_length(height),
_ => String::new(), _ => String::new(),
} }
} }
// An angle step with a command-supplied live value // An angle step with a command-supplied live value
// (ARC span / direction) shows it in degrees. // (ARC span / direction) shows it in degrees.
(None, Some(lv)) if f.component == DynComponent::Angle => { (None, Some(lv)) if f.component == DynComponent::Angle => {
format!("{lv:.1}") crate::entities::common::format_angle(lv.to_radians())
} }
(None, Some(lv)) (None, Some(lv))
if matches!( if matches!(
@@ -1084,7 +1090,11 @@ bg={bg_ms:.1}ms n={view_count}"
DynComponent::Scalar | DynComponent::Distance DynComponent::Scalar | DynComponent::Distance
) => ) =>
{ {
format!("{lv:.4}") if f.component == DynComponent::Distance {
crate::entities::common::format_length(lv)
} else {
lv.to_string()
}
} }
_ => dyn_component_value( _ => dyn_component_value(
f, f,
+11 -11
View File
@@ -3,8 +3,8 @@ use crate::t;
use crate::command::EntityTransform; use crate::command::EntityTransform;
use crate::entities::common::{ use crate::entities::common::{
center_grip, edit_angle_prop as edit_angle, edit_prop as edit, parse_f64, ro_prop as ro, center_grip, edit_angle_prop as edit_angle, edit_prop as edit, format_angle, format_area,
square_grip, format_length, parse_f64, ro_prop as ro, square_grip,
}; };
use crate::entities::traits::RenderConvertible; use crate::entities::traits::RenderConvertible;
use crate::scene::convert::acad_to_render::{extrusion_wall_tris, RenderEntity, RenderObject}; use crate::scene::convert::acad_to_render::{extrusion_wall_tris, RenderEntity, RenderObject};
@@ -197,21 +197,21 @@ fn properties(arc: &Arc) -> Vec<PropSection> {
vec![PropSection { vec![PropSection {
title: t!("Geometry").into_owned(), title: t!("Geometry").into_owned(),
props: vec![ props: vec![
ro(t!("Start X").as_ref(), "start_x", format!("{sx:.4}")), ro(t!("Start X").as_ref(), "start_x", format_length(sx)),
ro(t!("Start Y").as_ref(), "start_y", format!("{sy:.4}")), ro(t!("Start Y").as_ref(), "start_y", format_length(sy)),
ro(t!("Start Z").as_ref(), "start_z", format!("{sz:.4}")), ro(t!("Start Z").as_ref(), "start_z", format_length(sz)),
edit(t!("Center X").as_ref(), "center_x", cwx), edit(t!("Center X").as_ref(), "center_x", cwx),
edit(t!("Center Y").as_ref(), "center_y", cwy), edit(t!("Center Y").as_ref(), "center_y", cwy),
edit(t!("Center Z").as_ref(), "center_z", cwz), edit(t!("Center Z").as_ref(), "center_z", cwz),
ro(t!("End X").as_ref(), "end_x", format!("{ex:.4}")), ro(t!("End X").as_ref(), "end_x", format_length(ex)),
ro(t!("End Y").as_ref(), "end_y", format!("{ey:.4}")), ro(t!("End Y").as_ref(), "end_y", format_length(ey)),
ro(t!("End Z").as_ref(), "end_z", format!("{ez:.4}")), ro(t!("End Z").as_ref(), "end_z", format_length(ez)),
edit(t!("Radius").as_ref(), "radius", arc.radius), edit(t!("Radius").as_ref(), "radius", arc.radius),
edit_angle(t!("Start angle").as_ref(), "start_angle", sa.to_degrees()), edit_angle(t!("Start angle").as_ref(), "start_angle", sa.to_degrees()),
edit_angle(t!("End angle").as_ref(), "end_angle", ea.to_degrees()), edit_angle(t!("End angle").as_ref(), "end_angle", ea.to_degrees()),
ro(t!("Total angle").as_ref(), "total_angle", format!("{total_angle:.2}")), ro(t!("Total angle").as_ref(), "total_angle", format_angle(total_angle.to_radians())),
ro(t!("Arc length").as_ref(), "arc_length", format!("{arc_length:.4}")), ro(t!("Arc length").as_ref(), "arc_length", format_length(arc_length)),
ro(t!("Area").as_ref(), "area", format!("{area:.4}")), ro(t!("Area").as_ref(), "area", format_area(area)),
ro(t!("Normal X").as_ref(), "normal_x", format!("{:.4}", arc.normal.x)), ro(t!("Normal X").as_ref(), "normal_x", format!("{:.4}", arc.normal.x)),
ro(t!("Normal Y").as_ref(), "normal_y", format!("{:.4}", arc.normal.y)), ro(t!("Normal Y").as_ref(), "normal_y", format!("{:.4}", arc.normal.y)),
ro(t!("Normal Z").as_ref(), "normal_z", format!("{:.4}", arc.normal.z)), ro(t!("Normal Z").as_ref(), "normal_z", format!("{:.4}", arc.normal.z)),
+16 -16
View File
@@ -9,8 +9,8 @@ use crate::t;
use crate::command::EntityTransform; use crate::command::EntityTransform;
use crate::entities::common::{ use crate::entities::common::{
center_grip, edit_prop as edit, oriented_triangle_grip, parse_f64, ro_prop as ro, center_grip, edit_prop as edit, format_area, format_length, oriented_triangle_grip, parse_f64,
square_grip, ro_prop as ro, square_grip,
}; };
use crate::entities::traits::RenderConvertible; use crate::entities::traits::RenderConvertible;
use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject}; use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject};
@@ -163,30 +163,30 @@ fn properties(ell: &Ellipse) -> Vec<PropSection> {
vec![PropSection { vec![PropSection {
title: t!("Geometry").into_owned(), title: t!("Geometry").into_owned(),
props: vec![ props: vec![
ro(t!("Start X").as_ref(), "start_x", format!("{:.4}", start.x)), ro(t!("Start X").as_ref(), "start_x", format_length(start.x)),
ro(t!("Start Y").as_ref(), "start_y", format!("{:.4}", start.y)), ro(t!("Start Y").as_ref(), "start_y", format_length(start.y)),
ro(t!("Start Z").as_ref(), "start_z", format!("{:.4}", start.z)), ro(t!("Start Z").as_ref(), "start_z", format_length(start.z)),
edit(t!("Center X").as_ref(), "center_x", ell.center.x), edit(t!("Center X").as_ref(), "center_x", ell.center.x),
edit(t!("Center Y").as_ref(), "center_y", ell.center.y), edit(t!("Center Y").as_ref(), "center_y", ell.center.y),
edit(t!("Center Z").as_ref(), "center_z", ell.center.z), edit(t!("Center Z").as_ref(), "center_z", ell.center.z),
ro(t!("End X").as_ref(), "end_x", format!("{:.4}", end.x)), ro(t!("End X").as_ref(), "end_x", format_length(end.x)),
ro(t!("End Y").as_ref(), "end_y", format!("{:.4}", end.y)), ro(t!("End Y").as_ref(), "end_y", format_length(end.y)),
ro(t!("End Z").as_ref(), "end_z", format!("{:.4}", end.z)), ro(t!("End Z").as_ref(), "end_z", format_length(end.z)),
edit(t!("Major radius").as_ref(), "major_r", r_major), edit(t!("Major radius").as_ref(), "major_r", r_major),
edit(t!("Minor radius").as_ref(), "minor_r", r_minor), edit(t!("Minor radius").as_ref(), "minor_r", r_minor),
edit(t!("Radius ratio").as_ref(), "ratio", ell.minor_axis_ratio), edit(t!("Radius ratio").as_ref(), "ratio", ell.minor_axis_ratio),
edit(t!("Start angle").as_ref(), "start_angle", start_angle), edit(t!("Start angle").as_ref(), "start_angle", start_angle),
edit(t!("End angle").as_ref(), "end_angle", end_angle), edit(t!("End angle").as_ref(), "end_angle", end_angle),
ro(t!("Major axis vector X").as_ref(), "major_x", format!("{:.4}", major_vec.x)), ro(t!("Major axis vector X").as_ref(), "major_x", format_length(major_vec.x)),
ro(t!("Major axis vector Y").as_ref(), "major_y", format!("{:.4}", major_vec.y)), ro(t!("Major axis vector Y").as_ref(), "major_y", format_length(major_vec.y)),
ro(t!("Major axis vector Z").as_ref(), "major_z", format!("{:.4}", major_vec.z)), ro(t!("Major axis vector Z").as_ref(), "major_z", format_length(major_vec.z)),
ro(t!("Minor axis vector X").as_ref(), "minor_x", format!("{:.4}", minor_vec.x)), ro(t!("Minor axis vector X").as_ref(), "minor_x", format_length(minor_vec.x)),
ro(t!("Minor axis vector Y").as_ref(), "minor_y", format!("{:.4}", minor_vec.y)), ro(t!("Minor axis vector Y").as_ref(), "minor_y", format_length(minor_vec.y)),
ro(t!("Minor axis vector Z").as_ref(), "minor_z", format!("{:.4}", minor_vec.z)), ro(t!("Minor axis vector Z").as_ref(), "minor_z", format_length(minor_vec.z)),
ro(t!("Area").as_ref(), "area", format!("{:.4}", props.area)), ro(t!("Area").as_ref(), "area", format_area(props.area)),
ro(t!("Start parameter").as_ref(), "start_param", format!("{:.4}", ell.start_parameter)), ro(t!("Start parameter").as_ref(), "start_param", format!("{:.4}", ell.start_parameter)),
ro(t!("End parameter").as_ref(), "end_param", format!("{:.4}", ell.end_parameter)), ro(t!("End parameter").as_ref(), "end_param", format!("{:.4}", ell.end_parameter)),
ro(t!("Length").as_ref(), "length", format!("{:.4}", props.perimeter)), ro(t!("Length").as_ref(), "length", format_length(props.perimeter)),
ro(t!("Normal X").as_ref(), "normal_x", format!("{:.4}", ell.normal.x)), ro(t!("Normal X").as_ref(), "normal_x", format!("{:.4}", ell.normal.x)),
ro(t!("Normal Y").as_ref(), "normal_y", format!("{:.4}", ell.normal.y)), ro(t!("Normal Y").as_ref(), "normal_y", format!("{:.4}", ell.normal.y)),
ro(t!("Normal Z").as_ref(), "normal_z", format!("{:.4}", ell.normal.z)), ro(t!("Normal Z").as_ref(), "normal_z", format!("{:.4}", ell.normal.z)),
+4 -4
View File
@@ -428,8 +428,8 @@ fn properties(h: &Hatch) -> Vec<PropSection> {
title: t!("Geometry").into_owned(), title: t!("Geometry").into_owned(),
props: vec![ props: vec![
edit(t!("Elevation").as_ref(), "elevation", h.elevation), edit(t!("Elevation").as_ref(), "elevation", h.elevation),
ro(t!("Area").as_ref(), "area", format!("{:.4}", area)), ro(t!("Area").as_ref(), "area", crate::entities::common::format_area(area)),
ro(t!("Cumulative area").as_ref(), "cumulative_area", format!("{:.4}", area)), ro(t!("Cumulative area").as_ref(), "cumulative_area", crate::entities::common::format_area(area)),
], ],
}, },
PropSection { PropSection {
@@ -537,8 +537,8 @@ fn properties(h: &Hatch) -> Vec<PropSection> {
title: t!("Geometry").into_owned(), title: t!("Geometry").into_owned(),
props: vec![ props: vec![
edit(t!("Elevation").as_ref(), "elevation", h.elevation), edit(t!("Elevation").as_ref(), "elevation", h.elevation),
ro(t!("Area").as_ref(), "area", format!("{:.4}", area)), ro(t!("Area").as_ref(), "area", crate::entities::common::format_area(area)),
ro(t!("Cumulative area").as_ref(), "cumulative_area", format!("{:.4}", area)), ro(t!("Cumulative area").as_ref(), "cumulative_area", crate::entities::common::format_area(area)),
], ],
}, },
]; ];
+9 -9
View File
@@ -3,8 +3,8 @@ use crate::t;
use crate::command::EntityTransform; use crate::command::EntityTransform;
use crate::entities::common::{ use crate::entities::common::{
center_grip, edit_prop as edit, oriented_triangle_grip, parse_f64, ro_prop as ro, center_grip, edit_prop as edit, format_direction, format_length, oriented_triangle_grip,
square_grip, parse_f64, ro_prop as ro, square_grip,
}; };
use crate::entities::traits::RenderConvertible; use crate::entities::traits::RenderConvertible;
use crate::scene::convert::acad_to_render::{extrusion_wall_tris, RenderEntity, RenderObject}; use crate::scene::convert::acad_to_render::{extrusion_wall_tris, RenderEntity, RenderObject};
@@ -187,7 +187,7 @@ fn properties(line: &Line) -> Vec<PropSection> {
"centerline_end_extension", "centerline_end_extension",
association.end_extension, association.end_extension,
), ),
ro(t!("Length").as_ref(), "length", format!("{:.4}", line.length())), ro(t!("Length").as_ref(), "length", format_length(line.length())),
ro( ro(
"Associative", "Associative",
"centerline_associative", "centerline_associative",
@@ -199,7 +199,7 @@ fn properties(line: &Line) -> Vec<PropSection> {
let dx = line.end.x - line.start.x; let dx = line.end.x - line.start.x;
let dy = line.end.y - line.start.y; let dy = line.end.y - line.start.y;
let dz = line.end.z - line.start.z; let dz = line.end.z - line.start.z;
let angle = dy.atan2(dx).to_degrees().rem_euclid(360.0); let angle = dy.atan2(dx);
vec![PropSection { vec![PropSection {
title: t!("Geometry").into_owned(), title: t!("Geometry").into_owned(),
props: vec![ props: vec![
@@ -209,11 +209,11 @@ fn properties(line: &Line) -> Vec<PropSection> {
edit(t!("End X").as_ref(), "end_x", line.end.x), edit(t!("End X").as_ref(), "end_x", line.end.x),
edit(t!("End Y").as_ref(), "end_y", line.end.y), edit(t!("End Y").as_ref(), "end_y", line.end.y),
edit(t!("End Z").as_ref(), "end_z", line.end.z), edit(t!("End Z").as_ref(), "end_z", line.end.z),
ro(t!("Delta X").as_ref(), "delta_x", format!("{dx:.4}")), ro(t!("Delta X").as_ref(), "delta_x", format_length(dx)),
ro(t!("Delta Y").as_ref(), "delta_y", format!("{dy:.4}")), ro(t!("Delta Y").as_ref(), "delta_y", format_length(dy)),
ro(t!("Delta Z").as_ref(), "delta_z", format!("{dz:.4}")), ro(t!("Delta Z").as_ref(), "delta_z", format_length(dz)),
ro(t!("Length").as_ref(), "length", format!("{:.4}", line.length())), ro(t!("Length").as_ref(), "length", format_length(line.length())),
ro(t!("Angle").as_ref(), "angle", format!("{angle:.2}")), ro(t!("Angle").as_ref(), "angle", format_direction(angle)),
edit(t!("Normal X").as_ref(), "normal_x", line.normal.x), edit(t!("Normal X").as_ref(), "normal_x", line.normal.x),
edit(t!("Normal Y").as_ref(), "normal_y", line.normal.y), edit(t!("Normal Y").as_ref(), "normal_y", line.normal.y),
edit(t!("Normal Z").as_ref(), "normal_z", line.normal.z), edit(t!("Normal Z").as_ref(), "normal_z", line.normal.z),
+3 -3
View File
@@ -3,7 +3,7 @@ use crate::t;
use crate::command::EntityTransform; use crate::command::EntityTransform;
use crate::entities::common::{ use crate::entities::common::{
edit_prop as edit, format_length, parse_f64, ro_prop as ro, square_grip, edit_prop as edit, format_area, format_length, parse_f64, ro_prop as ro, square_grip,
stepper_prop as stepper, VARIES_LABEL, stepper_prop as stepper, VARIES_LABEL,
}; };
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, RenderConvertible}; use crate::entities::traits::{Grippable, PropertyEditable, Transformable, RenderConvertible};
@@ -705,8 +705,8 @@ impl PropertyEditable for Polyline2D {
edit(t!("End segment width").as_ref(), "pl2_seg_end_w", seg_end_w), edit(t!("End segment width").as_ref(), "pl2_seg_end_w", seg_end_w),
global_width_prop, global_width_prop,
edit(t!("Elevation").as_ref(), "pl2_elevation", self.elevation), edit(t!("Elevation").as_ref(), "pl2_elevation", self.elevation),
ro(t!("Area").as_ref(), "pl2_area", format!("{area:.4}")), ro(t!("Area").as_ref(), "pl2_area", format_area(area)),
ro(t!("Length").as_ref(), "pl2_length", format!("{length:.4}")), ro(t!("Length").as_ref(), "pl2_length", format_length(length)),
], ],
}, },
PropSection { PropSection {
+3 -3
View File
@@ -301,9 +301,9 @@ impl PropertyEditable for RasterImage {
edit(t!("Position X").as_ref(), "ri_ox", self.insertion_point.x), edit(t!("Position X").as_ref(), "ri_ox", self.insertion_point.x),
edit(t!("Position Y").as_ref(), "ri_oy", self.insertion_point.y), edit(t!("Position Y").as_ref(), "ri_oy", self.insertion_point.y),
edit(t!("Position Z").as_ref(), "ri_oz", self.insertion_point.z), edit(t!("Position Z").as_ref(), "ri_oz", self.insertion_point.z),
ro(t!("Rotation").as_ref(), "ri_rotation", format!("{:.4}", rotation_deg)), ro(t!("Rotation").as_ref(), "ri_rotation", crate::entities::common::format_angle(rotation_deg.to_radians())),
ro(t!("Width").as_ref(), "ri_width", format!("{:.4}", self.width())), ro(t!("Width").as_ref(), "ri_width", crate::entities::common::format_length(self.width())),
ro(t!("Height").as_ref(), "ri_height", format!("{:.4}", self.height())), ro(t!("Height").as_ref(), "ri_height", crate::entities::common::format_length(self.height())),
ro(t!("Scale").as_ref(), "ri_scale", format!("{:.4}", scale)), ro(t!("Scale").as_ref(), "ri_scale", format!("{:.4}", scale)),
], ],
}, },
+2 -2
View File
@@ -234,8 +234,8 @@ impl PropertyEditable for Underlay {
edit(t!("Scale X").as_ref(), "ul_sx", self.x_scale), edit(t!("Scale X").as_ref(), "ul_sx", self.x_scale),
edit(t!("Scale Y").as_ref(), "ul_sy", self.y_scale), edit(t!("Scale Y").as_ref(), "ul_sy", self.y_scale),
edit(t!("Scale Z").as_ref(), "ul_sz", self.z_scale), edit(t!("Scale Z").as_ref(), "ul_sz", self.z_scale),
ro(t!("Width").as_ref(), "ul_width", format!("{:.4}", width)), ro(t!("Width").as_ref(), "ul_width", crate::entities::common::format_length(width)),
ro(t!("Height").as_ref(), "ul_height", format!("{:.4}", height)), ro(t!("Height").as_ref(), "ul_height", crate::entities::common::format_length(height)),
edit_angle(t!("Rotation").as_ref(), "ul_rot", self.rotation.to_degrees()), edit_angle(t!("Rotation").as_ref(), "ul_rot", self.rotation.to_degrees()),
], ],
}, },
+6 -1
View File
@@ -3069,7 +3069,12 @@ impl DynInputCanvas {
fn box_content(b: &DynBox) -> String { fn box_content(b: &DynBox) -> String {
match b.role { match b.role {
DynRole::Angle => format!("{}\u{00B0}", b.value), // Live angle values already come from `format_angle`, which adds
// the drawing's appropriate marker (°, g, r, DMS, or bearing).
// A locked value is the user's raw numeric buffer and retains the
// legacy degree marker while it is being edited.
DynRole::Angle if b.locked => format!("{}\u{00B0}", b.value),
DynRole::Angle => b.value.clone(),
_ if b.label.is_empty() => b.value.clone(), _ if b.label.is_empty() => b.value.clone(),
_ => format!("{}{}", b.label, b.value), _ => format!("{}{}", b.label, b.value),
} }