diff --git a/src/entities/arc.rs b/src/entities/arc.rs index c6f7c8511..24dab99e7 100644 --- a/src/entities/arc.rs +++ b/src/entities/arc.rs @@ -3,7 +3,7 @@ use truck_modeling::{builder, Point3}; use crate::command::EntityTransform; use crate::entities::common::{ - center_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip, + center_grip, edit_angle_prop as edit_angle, edit_prop as edit, parse_f64, ro_prop as ro, square_grip, }; use crate::entities::traits::TruckConvertible; use crate::scene::convert::acad_to_truck::{extrusion_wall_tris, TruckEntity, TruckObject}; @@ -164,8 +164,8 @@ fn properties(arc: &Arc) -> Vec { ro("End Y", "end_y", format!("{ey:.4}")), ro("End Z", "end_z", format!("{ez:.4}")), edit("Radius", "radius", arc.radius), - edit("Start angle", "start_angle", sa.to_degrees()), - edit("End angle", "end_angle", ea.to_degrees()), + edit_angle("Start angle", "start_angle", sa.to_degrees()), + edit_angle("End angle", "end_angle", ea.to_degrees()), ro("Total angle", "total_angle", format!("{total_angle:.2}")), ro("Arc length", "arc_length", format!("{arc_length:.4}")), ro("Area", "area", format!("{area:.4}")), diff --git a/src/entities/attribute.rs b/src/entities/attribute.rs index 75a81cf64..ef636964e 100644 --- a/src/entities/attribute.rs +++ b/src/entities/attribute.rs @@ -5,7 +5,7 @@ use acadrust::entities::{AttributeDefinition, AttributeEntity}; use acadrust::types::Vector3; use crate::command::EntityTransform; -use crate::entities::common::{edit_prop as edit, parse_f64, ro_prop as ro, square_grip}; +use crate::entities::common::{edit_angle_prop as edit_angle, edit_prop as edit, parse_f64, ro_prop as ro, square_grip}; use crate::entities::text_support::{ layout_mtext, resolve_dxf_special_chars, resolve_text_style, text_local_bounds, MTextRenderOpts, MTextVAnchor, ResolvedTextStyle, @@ -468,9 +468,9 @@ impl PropertyEditable for AttributeDefinition { }, }, edit("Height", "att_h", self.height), - edit("Rotation", "att_rot", self.rotation.to_degrees()), + edit_angle("Rotation", "att_rot", self.rotation.to_degrees()), edit("Width factor", "att_wf", self.width_factor), - edit("Obliquing", "att_ob", self.oblique_angle.to_degrees()), + edit_angle("Obliquing", "att_ob", self.oblique_angle.to_degrees()), edit("Text alignment X", "att_ax", self.alignment_point.x), edit("Text alignment Y", "att_ay", self.alignment_point.y), edit("Text alignment Z", "att_az", self.alignment_point.z), @@ -739,9 +739,9 @@ impl PropertyEditable for AttributeEntity { bool_yn(self.flags.annotative), ), edit("Height", "atte_h", self.height), - edit("Rotation", "atte_rot", self.rotation.to_degrees()), + edit_angle("Rotation", "atte_rot", self.rotation.to_degrees()), edit("Width factor", "atte_wf", self.width_factor), - edit("Obliquing", "atte_ob", self.oblique_angle.to_degrees()), + edit_angle("Obliquing", "atte_ob", self.oblique_angle.to_degrees()), edit("Text alignment X", "atte_ax", self.alignment_point.x), edit("Text alignment Y", "atte_ay", self.alignment_point.y), edit("Text alignment Z", "atte_az", self.alignment_point.z), diff --git a/src/entities/common.rs b/src/entities/common.rs index 2443aea4c..53ff08e6d 100644 --- a/src/entities/common.rs +++ b/src/entities/common.rs @@ -94,7 +94,6 @@ pub fn format_length(value: f64) -> String { } /// Format an angle (input in radians) using AUNITS / AUPREC. -#[allow(dead_code)] pub fn format_angle(value_rad: f64) -> String { let ctx = unit_context(); let prec = ctx.auprec.max(0) as usize; @@ -184,6 +183,18 @@ pub fn triangle_grip(id: usize, world: glam::DVec3) -> GripDef { } } +/// Editable ANGLE row: displays via AUNITS/AUPREC. Angle rows used the +/// LINEAR formatter, so LUNITS=Architectural showed a block rotation as +/// feet-and-inches and the string wouldn't parse back (#297). Value in +/// DEGREES, matching every angle call site. +pub fn edit_angle_prop(label: &'static str, field: &'static str, value_deg: f64) -> Property { + Property { + label: label.into(), + field, + value: PropValue::EditText(format_angle(value_deg.to_radians())), + } +} + pub fn edit_prop(label: &'static str, field: &'static str, value: f64) -> Property { Property { label: label.into(), @@ -230,7 +241,54 @@ pub fn stepper_prop( } pub fn parse_f64(value: &str) -> Option { - value.trim().parse::().ok() + let t = value.trim(); + // Angle rows display via AUNITS (#297) — accept those formats back. + t.parse::().ok().or_else(|| parse_angle_deg(t)) +} + +/// Parse an angle string the panel displayed via AUNITS back to DEGREES: +/// "30", "30°"/"30d", DMS "30°15'20.5\"", grads "33.33g", radians "0.52r". +pub fn parse_angle_deg(value: &str) -> Option { + let s = value.trim(); + if s.is_empty() { + return None; + } + if let Ok(v) = s.parse::() { + return Some(v); + } + let lower = s.to_ascii_lowercase(); + if let Some(num) = lower.strip_suffix('g') { + return num.trim().parse::().ok().map(|g| g * 0.9); + } + if let Some(num) = lower.strip_suffix('r') { + return num.trim().parse::().ok().map(f64::to_degrees); + } + // Degrees with optional DMS parts: [-]D(°|d)[M'[S"]] + let mut rest = s; + let neg = rest.starts_with('-'); + if neg { + rest = &rest[1..]; + } + let i = rest.find(['°', 'd', 'D'])?; + let d: f64 = rest[..i].trim().parse().ok()?; + let mut total = d; + let tail = rest[i..] + .trim_start_matches(['°', 'd', 'D']) + .trim(); + if !tail.is_empty() { + let (mpart, spart) = match tail.find('\'') { + Some(j) => (&tail[..j], &tail[j + 1..]), + None => (tail, ""), + }; + if !mpart.trim().is_empty() { + total += mpart.trim().parse::().ok()? / 60.0; + } + let sp = spart.trim().trim_end_matches('"').trim(); + if !sp.is_empty() { + total += sp.parse::().ok()? / 3600.0; + } + } + Some(if neg { -total } else { total }) } /// Bulge → arc geometry for a polyline segment. diff --git a/src/entities/dimension.rs b/src/entities/dimension.rs index a30819e60..0b6f47594 100644 --- a/src/entities/dimension.rs +++ b/src/entities/dimension.rs @@ -7,7 +7,7 @@ use glam::{DVec3, Vec3}; use crate::command::EntityTransform; use crate::entities::common::{ - center_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip, + center_grip, edit_angle_prop as edit_angle, edit_prop as edit, parse_f64, ro_prop as ro, square_grip, }; use crate::entities::traits::{Grippable, PropertyEditable, Transformable}; use crate::scene::model::object::{GripApply, GripDef, PropSection}; @@ -78,7 +78,7 @@ fn properties(dim: &Dimension) -> Vec { d.second_point, d.definition_point, )); - props.push(edit("Rotation (deg)", "rotation", d.rotation.to_degrees())); + props.push(edit_angle("Rotation", "rotation", d.rotation.to_degrees())); props.push(edit( "Ext Rotation (deg)", "ext_line_rotation", diff --git a/src/entities/hatch.rs b/src/entities/hatch.rs index bdc4f3ad8..2d820ce3f 100644 --- a/src/entities/hatch.rs +++ b/src/entities/hatch.rs @@ -2,7 +2,7 @@ use acadrust::entities::{BoundaryEdge, Hatch}; use glam::Vec3; use crate::command::EntityTransform; -use crate::entities::common::{center_grip, circle_grip, edit_prop as edit, parse_f64, ro_prop as ro}; +use crate::entities::common::{center_grip, circle_grip, edit_angle_prop as edit_angle, edit_prop as edit, parse_f64, ro_prop as ro}; use crate::entities::traits::{FallbackTess, Grippable, PropertyEditable, Transformable}; use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property}; use crate::scene::convert::tess_util::{arc_segments, arc_signed_span, wire_chord_tol, FallbackGeometry}; @@ -250,7 +250,7 @@ fn properties(h: &Hatch) -> Vec { field: "gradient_color_2", value: PropValue::ColorChoice(grad_c2), }, - edit("Angle", "pattern_angle", g.angle.to_degrees()), + edit_angle("Angle", "pattern_angle", g.angle.to_degrees()), ro("Centered", "gradient_centered", centered), ], }, @@ -359,7 +359,7 @@ fn properties(h: &Hatch) -> Vec { type_row, pattern_name_row, ro("Annotative", "annotative", String::new()), - edit("Angle", "pattern_angle", h.pattern_angle.to_degrees()), + edit_angle("Angle", "pattern_angle", h.pattern_angle.to_degrees()), edit("Scale", "pattern_scale", h.pattern_scale), edit("Origin X", "origin_x", origin_x), edit("Origin Y", "origin_y", origin_y), diff --git a/src/entities/insert.rs b/src/entities/insert.rs index c793f5741..10a81ff69 100644 --- a/src/entities/insert.rs +++ b/src/entities/insert.rs @@ -4,7 +4,7 @@ use acadrust::{EntityType, Handle}; use glam::Vec3; use crate::command::EntityTransform; -use crate::entities::common::{edit_prop as edit, parse_f64, ro_prop as ro, square_grip}; +use crate::entities::common::{edit_angle_prop as edit_angle, edit_prop as edit, parse_f64, ro_prop as ro, square_grip}; use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property}; use crate::scene::model::wire_model::WireModel; @@ -53,7 +53,7 @@ fn properties(ins: &Insert) -> Vec { title: "Misc".into(), props: vec![ ro("Name", "block", ins.block_name.clone()), - edit("Rotation", "rotation", ins.rotation.to_degrees()), + edit_angle("Rotation", "rotation", ins.rotation.to_degrees()), ro( "Annotative", "annotative", diff --git a/src/entities/mtext.rs b/src/entities/mtext.rs index 537de57a5..ec65fa9f6 100644 --- a/src/entities/mtext.rs +++ b/src/entities/mtext.rs @@ -2,7 +2,7 @@ use acadrust::entities::{AttachmentPoint, DrawingDirection, MText}; use crate::command::EntityTransform; use crate::entities::common::{ - edit_prop as edit, num_prop as num_row, ro_prop as ro, square_grip, triangle_grip, + edit_angle_prop as edit_angle, edit_prop as edit, num_prop as num_row, ro_prop as ro, square_grip, triangle_grip, }; use crate::entities::text_support::{ layout_mtext, resolve_text_style, GlyphBox, MTextColumns, MTextRenderOpts, MTextVAnchor, @@ -275,7 +275,7 @@ fn properties(t: &MText, text_style_names: &[String]) -> Vec { }, }, edit("Text height", "height", t.height), - edit("Rotation", "rotation", t.rotation.to_degrees()), + edit_angle("Rotation", "rotation", t.rotation.to_degrees()), edit("Line space factor", "line_spacing", t.line_spacing_factor), edit("Line space distance", "line_space_distance", line_space_distance), Property { diff --git a/src/entities/multileader.rs b/src/entities/multileader.rs index ba64adeb3..b3a3fd473 100644 --- a/src/entities/multileader.rs +++ b/src/entities/multileader.rs @@ -31,7 +31,7 @@ use glam::DVec3; use crate::command::EntityTransform; use crate::entities::common::{ - center_grip, edit_prop as edit, num_prop as num_row, ro_prop as ro, square_grip, triangle_grip, + center_grip, edit_angle_prop as edit_angle, edit_prop as edit, num_prop as num_row, ro_prop as ro, square_grip, triangle_grip, }; use crate::entities::traits::TruckConvertible; use crate::scene::convert::acad_to_truck::{TruckEntity, TruckObject}; @@ -663,7 +663,7 @@ fn properties(ml: &MultiLeader) -> Vec { ), edit("Width", "text_width", ctx.text_width), edit("Height", "text_height", ml.text_height), - edit("Rotation", "text_rotation", ctx.text_rotation.to_degrees()), + edit_angle("Rotation", "text_rotation", ctx.text_rotation.to_degrees()), edit("Line space factor", "line_spacing", ctx.line_spacing_factor), edit( "Line space distance", diff --git a/src/entities/shape.rs b/src/entities/shape.rs index 583f75579..b8771f6ee 100644 --- a/src/entities/shape.rs +++ b/src/entities/shape.rs @@ -8,7 +8,7 @@ use acadrust::entities::Shape; use crate::command::EntityTransform; -use crate::entities::common::{edit_prop as edit, ro_prop as ro, square_grip}; +use crate::entities::common::{edit_angle_prop as edit_angle, edit_prop as edit, ro_prop as ro, square_grip}; use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible}; use crate::scene::convert::acad_to_truck::{TruckEntity, TruckObject}; use crate::scene::model::object::{GripApply, GripDef, PropSection, Property, PropValue}; @@ -149,9 +149,9 @@ impl PropertyEditable for Shape { edit("Insert Y", "shp_iy", self.insertion_point.y), edit("Insert Z", "shp_iz", self.insertion_point.z), edit("Size", "shp_sz", self.size), - edit("Rotation", "shp_rot", self.rotation.to_degrees()), + edit_angle("Rotation", "shp_rot", self.rotation.to_degrees()), edit("Width Factor", "shp_xs", self.relative_x_scale), - edit("Oblique Angle", "shp_ob", self.oblique_angle.to_degrees()), + edit_angle("Oblique Angle", "shp_ob", self.oblique_angle.to_degrees()), edit("Normal X", "shp_nx", self.normal.x), edit("Normal Y", "shp_ny", self.normal.y), edit("Normal Z", "shp_nz", self.normal.z), diff --git a/src/entities/text.rs b/src/entities/text.rs index 900e93a09..c6fd3614c 100644 --- a/src/entities/text.rs +++ b/src/entities/text.rs @@ -2,7 +2,7 @@ use acadrust::entities::{Text, TextHorizontalAlignment as HA, TextVerticalAlignm use crate::command::EntityTransform; use crate::entities::common::{ - edit_prop as edit, num_prop as num_row, parse_f64, ro_prop as ro, square_grip, + edit_angle_prop as edit_angle, edit_prop as edit, num_prop as num_row, parse_f64, ro_prop as ro, square_grip, }; use crate::entities::text_support::{ resolve_dxf_special_chars, resolve_text_style, text_local_bounds, @@ -325,9 +325,9 @@ fn properties(t: &Text, text_style_names: &[String]) -> Vec { }, }, edit("Height", "height", t.height), - edit("Rotation", "rotation", t.rotation.to_degrees()), + edit_angle("Rotation", "rotation", t.rotation.to_degrees()), edit("Width factor", "width_factor", t.width_factor), - edit("Obliquing", "oblique_angle", t.oblique_angle.to_degrees()), + edit_angle("Obliquing", "oblique_angle", t.oblique_angle.to_degrees()), num_row("Text alignment X", "align_x", ax, align_editable), num_row("Text alignment Y", "align_y", ay, align_editable), num_row("Text alignment Z", "align_z", az, align_editable), diff --git a/src/entities/underlay.rs b/src/entities/underlay.rs index 9c78ac896..0346b55af 100644 --- a/src/entities/underlay.rs +++ b/src/entities/underlay.rs @@ -7,7 +7,7 @@ use acadrust::entities::{Underlay, UnderlayDisplayFlags}; use crate::command::EntityTransform; -use crate::entities::common::{center_grip, edit_prop as edit, ro_prop as ro, square_grip}; +use crate::entities::common::{center_grip, edit_angle_prop as edit_angle, edit_prop as edit, ro_prop as ro, square_grip}; use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible}; use crate::scene::convert::acad_to_truck::{TruckEntity, TruckObject}; use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property}; @@ -189,7 +189,7 @@ impl PropertyEditable for Underlay { edit("Scale Z", "ul_sz", self.z_scale), ro("Width", "ul_width", format!("{:.4}", width)), ro("Height", "ul_height", format!("{:.4}", height)), - edit("Rotation", "ul_rot", self.rotation.to_degrees()), + edit_angle("Rotation", "ul_rot", self.rotation.to_degrees()), ], }, PropSection {