forked from 1b-agent/opencadstudio-upstream
fix(props): angle rows format via AUNITS, not LUNITS (#297)
Every angle row went through the linear formatter, so switching LUNITS
to Architectural showed a block's Rotation as feet-and-inches — and the
string wouldn't parse back, wedging the field until it was cleared and
retyped. Only the decimal/fractional LUNITS survived by accident.
New edit_angle row helper formats angles with format_angle
(AUNITS/AUPREC: decimal degrees, DMS, grads or radians), applied to all
17 angle rows (rotations, obliquing, arc start/end, hatch pattern
angle, multileader text rotation). parse_angle_deg converts every
AUNITS display form back to degrees ("30°"/"30d", DMS 30°15'20",
33.33g, 0.52r) and parse_f64 falls back to it, so the displayed value
round-trips through an edit.
Closes #297
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
51f07f1208
commit
b998082c82
11 files changed
+87
-29
No files matched your search
+3
-3
@@ -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<PropSection> {
|
||||
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}")),
|
||||
|
||||
@@ -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),
|
||||
|
||||
+60
-2
@@ -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<f64> {
|
||||
value.trim().parse::<f64>().ok()
|
||||
let t = value.trim();
|
||||
// Angle rows display via AUNITS (#297) — accept those formats back.
|
||||
t.parse::<f64>().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<f64> {
|
||||
let s = value.trim();
|
||||
if s.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if let Ok(v) = s.parse::<f64>() {
|
||||
return Some(v);
|
||||
}
|
||||
let lower = s.to_ascii_lowercase();
|
||||
if let Some(num) = lower.strip_suffix('g') {
|
||||
return num.trim().parse::<f64>().ok().map(|g| g * 0.9);
|
||||
}
|
||||
if let Some(num) = lower.strip_suffix('r') {
|
||||
return num.trim().parse::<f64>().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::<f64>().ok()? / 60.0;
|
||||
}
|
||||
let sp = spart.trim().trim_end_matches('"').trim();
|
||||
if !sp.is_empty() {
|
||||
total += sp.parse::<f64>().ok()? / 3600.0;
|
||||
}
|
||||
}
|
||||
Some(if neg { -total } else { total })
|
||||
}
|
||||
|
||||
/// Bulge → arc geometry for a polyline segment.
|
||||
|
||||
@@ -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<PropSection> {
|
||||
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",
|
||||
|
||||
@@ -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<PropSection> {
|
||||
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<PropSection> {
|
||||
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),
|
||||
|
||||
@@ -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<PropSection> {
|
||||
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",
|
||||
|
||||
@@ -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<PropSection> {
|
||||
},
|
||||
},
|
||||
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 {
|
||||
|
||||
@@ -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<PropSection> {
|
||||
),
|
||||
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",
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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<PropSection> {
|
||||
},
|
||||
},
|
||||
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),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user