diff --git a/src/app/view/controls.rs b/src/app/view/controls.rs index 2f333e099..f2127339f 100644 --- a/src/app/view/controls.rs +++ b/src/app/view/controls.rs @@ -346,26 +346,24 @@ pub(super) fn dyn_component_value( ) && relative && !comma_cartesian; match f.component { - DynComponent::X if relative => format!("{:.4}", if wh { dx.abs() } else { dx }), - DynComponent::Y if relative => format!("{:.4}", if wh { dy.abs() } else { dy }), - DynComponent::Z if relative => "0.0000".to_string(), - DynComponent::X => format!("{:.4}", p.x), - DynComponent::Y => format!("{:.4}", p.y), - DynComponent::Z => format!("{:.4}", p.z), + DynComponent::X if relative => crate::entities::common::format_length(if wh { dx.abs() } else { dx }), + DynComponent::Y if relative => crate::entities::common::format_length(if wh { dy.abs() } else { dy }), + DynComponent::Z if relative => crate::entities::common::format_length(0.0), + DynComponent::X => crate::entities::common::format_length(p.x), + DynComponent::Y => crate::entities::common::format_length(p.y), + DynComponent::Z => crate::entities::common::format_length(p.z), // Scaled by the role so a diameter box reads twice the radius. DynComponent::Distance => { - format!( - "{:.4}", - (dx * dx + dy * dy).sqrt() * f.role.value_scale() as f64 + crate::entities::common::format_length( + (dx * dx + dy * dy).sqrt() * f.role.value_scale() as f64, ) } // Shared rule: unsigned magnitude of the short angle, so CW (below the // reference axis) reads positive (e.g. 30°, not -30°/330°). The // committed value stays signed (see dyn_resolve_point). DynComponent::Angle => { - format!( - "{:.1}", - crate::command::dyn_display_angle_deg(dy.atan2(dx) as f32) + crate::entities::common::format_angle( + (crate::command::dyn_display_angle_deg(dy.atan2(dx) as f32) as f64).to_radians(), ) } // Typed-only scalar — no geometric value to track when empty. diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index e239808b9..86d4f5bbf 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -213,6 +213,12 @@ impl OpenCADStudio { let i = self.active_tab; 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 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]; @@ -1068,15 +1074,15 @@ bg={bg_ms:.1}ms n={view_count}" (None, _) if rectangle_values.is_some() => { let (width, height) = rectangle_values.unwrap(); match f.role { - crate::command::DynRole::Width => format!("{width:.4}"), - crate::command::DynRole::Height => format!("{height:.4}"), + crate::command::DynRole::Width => crate::entities::common::format_length(width), + crate::command::DynRole::Height => crate::entities::common::format_length(height), _ => String::new(), } } // An angle step with a command-supplied live value // (ARC span / direction) shows it in degrees. (None, Some(lv)) if f.component == DynComponent::Angle => { - format!("{lv:.1}") + crate::entities::common::format_angle(lv.to_radians()) } (None, Some(lv)) if matches!( @@ -1084,7 +1090,11 @@ bg={bg_ms:.1}ms n={view_count}" 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( f, diff --git a/src/entities/arc.rs b/src/entities/arc.rs index e4f35e9cb..3c354c20c 100644 --- a/src/entities/arc.rs +++ b/src/entities/arc.rs @@ -3,8 +3,8 @@ use crate::t; use crate::command::EntityTransform; use crate::entities::common::{ - center_grip, edit_angle_prop as edit_angle, edit_prop as edit, parse_f64, ro_prop as ro, - square_grip, + center_grip, edit_angle_prop as edit_angle, edit_prop as edit, format_angle, format_area, + format_length, parse_f64, ro_prop as ro, square_grip, }; use crate::entities::traits::RenderConvertible; use crate::scene::convert::acad_to_render::{extrusion_wall_tris, RenderEntity, RenderObject}; @@ -197,21 +197,21 @@ fn properties(arc: &Arc) -> Vec { vec![PropSection { title: t!("Geometry").into_owned(), props: vec![ - ro(t!("Start X").as_ref(), "start_x", format!("{sx:.4}")), - ro(t!("Start Y").as_ref(), "start_y", format!("{sy:.4}")), - ro(t!("Start Z").as_ref(), "start_z", format!("{sz:.4}")), + ro(t!("Start X").as_ref(), "start_x", format_length(sx)), + ro(t!("Start Y").as_ref(), "start_y", format_length(sy)), + ro(t!("Start Z").as_ref(), "start_z", format_length(sz)), edit(t!("Center X").as_ref(), "center_x", cwx), edit(t!("Center Y").as_ref(), "center_y", cwy), edit(t!("Center Z").as_ref(), "center_z", cwz), - ro(t!("End X").as_ref(), "end_x", format!("{ex:.4}")), - ro(t!("End Y").as_ref(), "end_y", format!("{ey:.4}")), - ro(t!("End Z").as_ref(), "end_z", format!("{ez:.4}")), + ro(t!("End X").as_ref(), "end_x", format_length(ex)), + ro(t!("End Y").as_ref(), "end_y", format_length(ey)), + ro(t!("End Z").as_ref(), "end_z", format_length(ez)), edit(t!("Radius").as_ref(), "radius", arc.radius), edit_angle(t!("Start angle").as_ref(), "start_angle", sa.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!("Arc length").as_ref(), "arc_length", format!("{arc_length:.4}")), - ro(t!("Area").as_ref(), "area", format!("{area:.4}")), + ro(t!("Total angle").as_ref(), "total_angle", format_angle(total_angle.to_radians())), + ro(t!("Arc length").as_ref(), "arc_length", format_length(arc_length)), + 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 Y").as_ref(), "normal_y", format!("{:.4}", arc.normal.y)), ro(t!("Normal Z").as_ref(), "normal_z", format!("{:.4}", arc.normal.z)), diff --git a/src/entities/ellipse.rs b/src/entities/ellipse.rs index 6530f9e38..47ef9d58e 100644 --- a/src/entities/ellipse.rs +++ b/src/entities/ellipse.rs @@ -9,8 +9,8 @@ use crate::t; use crate::command::EntityTransform; use crate::entities::common::{ - center_grip, edit_prop as edit, oriented_triangle_grip, parse_f64, ro_prop as ro, - square_grip, + center_grip, edit_prop as edit, format_area, format_length, oriented_triangle_grip, parse_f64, + ro_prop as ro, square_grip, }; use crate::entities::traits::RenderConvertible; use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject}; @@ -163,30 +163,30 @@ fn properties(ell: &Ellipse) -> Vec { vec![PropSection { title: t!("Geometry").into_owned(), props: vec![ - ro(t!("Start X").as_ref(), "start_x", format!("{:.4}", start.x)), - ro(t!("Start Y").as_ref(), "start_y", format!("{:.4}", start.y)), - ro(t!("Start Z").as_ref(), "start_z", format!("{:.4}", start.z)), + ro(t!("Start X").as_ref(), "start_x", format_length(start.x)), + ro(t!("Start Y").as_ref(), "start_y", format_length(start.y)), + 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 Y").as_ref(), "center_y", ell.center.y), 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 Y").as_ref(), "end_y", format!("{:.4}", end.y)), - ro(t!("End Z").as_ref(), "end_z", format!("{:.4}", end.z)), + ro(t!("End X").as_ref(), "end_x", format_length(end.x)), + ro(t!("End Y").as_ref(), "end_y", format_length(end.y)), + ro(t!("End Z").as_ref(), "end_z", format_length(end.z)), edit(t!("Major radius").as_ref(), "major_r", r_major), edit(t!("Minor radius").as_ref(), "minor_r", r_minor), edit(t!("Radius ratio").as_ref(), "ratio", ell.minor_axis_ratio), edit(t!("Start angle").as_ref(), "start_angle", start_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 Y").as_ref(), "major_y", format!("{:.4}", major_vec.y)), - ro(t!("Major axis vector Z").as_ref(), "major_z", format!("{:.4}", major_vec.z)), - ro(t!("Minor axis vector X").as_ref(), "minor_x", format!("{:.4}", minor_vec.x)), - ro(t!("Minor axis vector Y").as_ref(), "minor_y", format!("{:.4}", minor_vec.y)), - ro(t!("Minor axis vector Z").as_ref(), "minor_z", format!("{:.4}", minor_vec.z)), - ro(t!("Area").as_ref(), "area", format!("{:.4}", props.area)), + 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_length(major_vec.y)), + 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_length(minor_vec.x)), + 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_length(minor_vec.z)), + 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!("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 Y").as_ref(), "normal_y", format!("{:.4}", ell.normal.y)), ro(t!("Normal Z").as_ref(), "normal_z", format!("{:.4}", ell.normal.z)), diff --git a/src/entities/hatch.rs b/src/entities/hatch.rs index 0acf8ebde..3be4da77d 100644 --- a/src/entities/hatch.rs +++ b/src/entities/hatch.rs @@ -428,8 +428,8 @@ fn properties(h: &Hatch) -> Vec { title: t!("Geometry").into_owned(), props: vec![ edit(t!("Elevation").as_ref(), "elevation", h.elevation), - ro(t!("Area").as_ref(), "area", format!("{:.4}", area)), - ro(t!("Cumulative area").as_ref(), "cumulative_area", format!("{:.4}", area)), + ro(t!("Area").as_ref(), "area", crate::entities::common::format_area(area)), + ro(t!("Cumulative area").as_ref(), "cumulative_area", crate::entities::common::format_area(area)), ], }, PropSection { @@ -537,8 +537,8 @@ fn properties(h: &Hatch) -> Vec { title: t!("Geometry").into_owned(), props: vec![ edit(t!("Elevation").as_ref(), "elevation", h.elevation), - ro(t!("Area").as_ref(), "area", format!("{:.4}", area)), - ro(t!("Cumulative area").as_ref(), "cumulative_area", format!("{:.4}", area)), + ro(t!("Area").as_ref(), "area", crate::entities::common::format_area(area)), + ro(t!("Cumulative area").as_ref(), "cumulative_area", crate::entities::common::format_area(area)), ], }, ]; diff --git a/src/entities/line.rs b/src/entities/line.rs index 0f4e9fdfb..d434a58d3 100644 --- a/src/entities/line.rs +++ b/src/entities/line.rs @@ -3,8 +3,8 @@ use crate::t; use crate::command::EntityTransform; use crate::entities::common::{ - center_grip, edit_prop as edit, oriented_triangle_grip, parse_f64, ro_prop as ro, - square_grip, + center_grip, edit_prop as edit, format_direction, format_length, oriented_triangle_grip, + parse_f64, ro_prop as ro, square_grip, }; use crate::entities::traits::RenderConvertible; use crate::scene::convert::acad_to_render::{extrusion_wall_tris, RenderEntity, RenderObject}; @@ -187,7 +187,7 @@ fn properties(line: &Line) -> Vec { "centerline_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( "Associative", "centerline_associative", @@ -199,7 +199,7 @@ fn properties(line: &Line) -> Vec { let dx = line.end.x - line.start.x; let dy = line.end.y - line.start.y; 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 { title: t!("Geometry").into_owned(), props: vec![ @@ -209,11 +209,11 @@ fn properties(line: &Line) -> Vec { 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 Z").as_ref(), "end_z", line.end.z), - ro(t!("Delta X").as_ref(), "delta_x", format!("{dx:.4}")), - ro(t!("Delta Y").as_ref(), "delta_y", format!("{dy:.4}")), - ro(t!("Delta Z").as_ref(), "delta_z", format!("{dz:.4}")), - ro(t!("Length").as_ref(), "length", format!("{:.4}", line.length())), - ro(t!("Angle").as_ref(), "angle", format!("{angle:.2}")), + ro(t!("Delta X").as_ref(), "delta_x", format_length(dx)), + ro(t!("Delta Y").as_ref(), "delta_y", format_length(dy)), + ro(t!("Delta Z").as_ref(), "delta_z", format_length(dz)), + ro(t!("Length").as_ref(), "length", format_length(line.length())), + ro(t!("Angle").as_ref(), "angle", format_direction(angle)), 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 Z").as_ref(), "normal_z", line.normal.z), diff --git a/src/entities/polyline.rs b/src/entities/polyline.rs index fcbdedfa6..86038dde8 100644 --- a/src/entities/polyline.rs +++ b/src/entities/polyline.rs @@ -3,7 +3,7 @@ use crate::t; use crate::command::EntityTransform; 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, }; 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), global_width_prop, edit(t!("Elevation").as_ref(), "pl2_elevation", self.elevation), - ro(t!("Area").as_ref(), "pl2_area", format!("{area:.4}")), - ro(t!("Length").as_ref(), "pl2_length", format!("{length:.4}")), + ro(t!("Area").as_ref(), "pl2_area", format_area(area)), + ro(t!("Length").as_ref(), "pl2_length", format_length(length)), ], }, PropSection { diff --git a/src/entities/raster_image.rs b/src/entities/raster_image.rs index b3ceab826..028c31d80 100644 --- a/src/entities/raster_image.rs +++ b/src/entities/raster_image.rs @@ -301,9 +301,9 @@ impl PropertyEditable for RasterImage { 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 Z").as_ref(), "ri_oz", self.insertion_point.z), - ro(t!("Rotation").as_ref(), "ri_rotation", format!("{:.4}", rotation_deg)), - ro(t!("Width").as_ref(), "ri_width", format!("{:.4}", self.width())), - ro(t!("Height").as_ref(), "ri_height", format!("{:.4}", self.height())), + ro(t!("Rotation").as_ref(), "ri_rotation", crate::entities::common::format_angle(rotation_deg.to_radians())), + ro(t!("Width").as_ref(), "ri_width", crate::entities::common::format_length(self.width())), + ro(t!("Height").as_ref(), "ri_height", crate::entities::common::format_length(self.height())), ro(t!("Scale").as_ref(), "ri_scale", format!("{:.4}", scale)), ], }, diff --git a/src/entities/underlay.rs b/src/entities/underlay.rs index b7450d8c6..3db6bf7e0 100644 --- a/src/entities/underlay.rs +++ b/src/entities/underlay.rs @@ -234,8 +234,8 @@ impl PropertyEditable for Underlay { 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 Z").as_ref(), "ul_sz", self.z_scale), - ro(t!("Width").as_ref(), "ul_width", format!("{:.4}", width)), - ro(t!("Height").as_ref(), "ul_height", format!("{:.4}", height)), + ro(t!("Width").as_ref(), "ul_width", crate::entities::common::format_length(width)), + 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()), ], }, diff --git a/src/ui/overlay.rs b/src/ui/overlay.rs index 37274b4a8..e9da97a99 100644 --- a/src/ui/overlay.rs +++ b/src/ui/overlay.rs @@ -3069,7 +3069,12 @@ impl DynInputCanvas { fn box_content(b: &DynBox) -> String { 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(), _ => format!("{}{}", b.label, b.value), }