mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-29 21:36:52 -06:00
Properties: a rotation shows and is typed as the drawing measures directions (#1275)
AutoCAD's Properties shows a block reference's or text's rotation as it shows any direction: from ANGBASE, clockwise when ANGDIR is 1 (a block turned 2.23° shows as 87.77 in a drawing measured clockwise from north). CADCraft showed the raw angle from +X. Properties and Quick Properties now show it that way and read a typed rotation back the same way (`AngleSettings::shown`, `unshown`); properties.set still takes the angle from +X counterclockwise. Co-authored-by: Cody Vogt <18035288+codyvogt@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Cody Vogt
Claude Opus 5.5
parent
e635248b1e
commit
c400d6d48c
@@ -200,6 +200,20 @@ impl AngleSettings {
|
||||
let v = self.angbase + if self.clockwise { -a } else { a };
|
||||
v.is_finite().then_some(v)
|
||||
}
|
||||
|
||||
/// Direction `dir` (radians counterclockwise from +X) as the drawing shows directions, in
|
||||
/// radians: from ANGBASE in the ANGDIR sense, 0 up to a full turn. (A block turned 2.23°
|
||||
/// shows as 87.77 in a drawing measured clockwise from north, as in AutoCAD's Properties.)
|
||||
pub fn shown(&self, dir: f64) -> f64 {
|
||||
let a = if self.clockwise { self.angbase - dir } else { dir - self.angbase };
|
||||
a.rem_euclid(std::f64::consts::TAU)
|
||||
}
|
||||
|
||||
/// The direction (radians counterclockwise from +X) the drawing shows as `v` (radians): the
|
||||
/// inverse of [`AngleSettings::shown`].
|
||||
pub fn unshown(&self, v: f64) -> f64 {
|
||||
(self.angbase + if self.clockwise { -v } else { v }).rem_euclid(std::f64::consts::TAU)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a direction typed with the default angle settings (degrees, 0 = +X, counterclockwise):
|
||||
@@ -289,6 +303,22 @@ pub const DEG: f64 = PI / 180.0;
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn directions_show_from_angbase_in_the_angdir_sense() {
|
||||
let north_clockwise = AngleSettings { aunits: 0, angbase: 90f64.to_radians(), clockwise: true };
|
||||
// AutoCAD shows a block turned 2.22729505553431° as 87.77 in such a drawing.
|
||||
let shown = north_clockwise.shown(2.22729505553431f64.to_radians()).to_degrees();
|
||||
assert!((shown - 87.77270494446569).abs() < 1e-9, "{shown}");
|
||||
let back = north_clockwise.unshown(87.77270494446569f64.to_radians()).to_degrees();
|
||||
assert!((back - 2.22729505553431).abs() < 1e-9, "{back}");
|
||||
// The defaults show directions as they are.
|
||||
let plain = AngleSettings::default();
|
||||
assert!((plain.shown(0.5) - 0.5).abs() < 1e-12 && (plain.unshown(0.5) - 0.5).abs() < 1e-12);
|
||||
// Counterclockwise from a base of 30°: 45° shows as 15.
|
||||
let based = AngleSettings { aunits: 0, angbase: 30f64.to_radians(), clockwise: false };
|
||||
assert!((based.shown(45f64.to_radians()).to_degrees() - 15.0).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decimal_and_arch() {
|
||||
assert_eq!(format_distance(28.98524, 2, 4), "28.9852");
|
||||
|
||||
@@ -968,6 +968,8 @@ pub(crate) fn properties_section(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
if sel.len() == 1 {
|
||||
group(ui, "Geometry");
|
||||
let real = |v: f64| format!("{v:.4}");
|
||||
// Rotations show as the drawing measures directions (ANGBASE, ANGDIR).
|
||||
let angles = app.session.angle_settings();
|
||||
let num = |ui: &mut egui::Ui, label: &str, key: &str, shown: String, set: &mut Option<Value>, bad: &mut Option<String>, ids: &Vec<String>| {
|
||||
prop_row(ui, label, |ui| {
|
||||
if let Some(f) = number(edit_field(ui, ui.id().with((key, ids)), shown), bad) {
|
||||
@@ -1034,7 +1036,7 @@ pub(crate) fn properties_section(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
});
|
||||
num(ui, "Height", "height", len(tx.height), &mut set, &mut bad, &ids);
|
||||
num(ui, "Rotation", "rotation", real(tx.rotation.to_degrees()), &mut set, &mut bad, &ids);
|
||||
num(ui, "Rotation", "rotation", real(angles.shown(tx.rotation).to_degrees()), &mut set, &mut bad, &ids);
|
||||
num(ui, "Width factor", "widthFactor", real(tx.width_factor), &mut set, &mut bad, &ids);
|
||||
}
|
||||
EntityKind::MText(tx) => {
|
||||
@@ -1094,7 +1096,7 @@ pub(crate) fn properties_section(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
prop_row(ui, "Position X", |ui| read_only(ui, &len(i.insert.x)));
|
||||
prop_row(ui, "Position Y", |ui| read_only(ui, &len(i.insert.y)));
|
||||
prop_row(ui, "Position Z", |ui| read_only(ui, &len(i.insert.z)));
|
||||
num(ui, "Rotation", "rotation", real(i.rotation.to_degrees()), &mut set, &mut bad, &ids);
|
||||
num(ui, "Rotation", "rotation", real(angles.shown(i.rotation).to_degrees()), &mut set, &mut bad, &ids);
|
||||
num(ui, "Scale", "scale", real(i.scale.x), &mut set, &mut bad, &ids);
|
||||
if let Some((_, props)) = custom.filter(|(_, p)| !p.is_empty()) {
|
||||
group(ui, "Custom");
|
||||
@@ -1118,7 +1120,14 @@ pub(crate) fn properties_section(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(p) = set {
|
||||
if let Some(mut p) = set {
|
||||
// A rotation is typed as shown (from ANGBASE, in the ANGDIR sense); properties.set takes it
|
||||
// from +X counterclockwise.
|
||||
if let Some(r) = p.get_mut("rotation")
|
||||
&& let Some(v) = r.as_f64()
|
||||
{
|
||||
*r = json!(app.session.angle_settings().unshown(v.to_radians()).to_degrees());
|
||||
}
|
||||
let _ = app.run("properties.set", p);
|
||||
}
|
||||
report_bad(app, bad);
|
||||
|
||||
@@ -295,6 +295,8 @@ pub fn quick_properties(app: &mut CadApp, ctx: &egui::Context) {
|
||||
let kind = if ents.len() == 1 { Some(first.kind.clone()) } else { None };
|
||||
let ids: Vec<String> = sel.iter().map(|h| h.hex()).collect();
|
||||
let mut set: Option<(String, Value)> = None;
|
||||
// Rotations show as the drawing measures directions (ANGBASE, ANGDIR).
|
||||
let angles = app.session.angle_settings();
|
||||
let mut close = false;
|
||||
egui::Window::new(title)
|
||||
.id(egui::Id::new(("quick_properties", &st.sel)))
|
||||
@@ -373,7 +375,7 @@ pub fn quick_properties(app: &mut CadApp, ctx: &egui::Context) {
|
||||
}
|
||||
ui.end_row();
|
||||
num_row(ui, "Height", id("h"), tx.height, &mut set, "height");
|
||||
num_row(ui, "Rotation", id("rot"), tx.rotation.to_degrees(), &mut set, "rotation");
|
||||
num_row(ui, "Rotation", id("rot"), angles.shown(tx.rotation).to_degrees(), &mut set, "rotation");
|
||||
}
|
||||
Some(EntityKind::MText(tx)) => {
|
||||
ui.label(RichText::new(crate::tl!("Contents")).color(Tokens::get().text_dim));
|
||||
@@ -385,7 +387,7 @@ pub fn quick_properties(app: &mut CadApp, ctx: &egui::Context) {
|
||||
}
|
||||
Some(EntityKind::Insert(i)) => {
|
||||
ro_row(ui, "Name", i.block.clone());
|
||||
num_row(ui, "Rotation", id("rot"), i.rotation.to_degrees(), &mut set, "rotation");
|
||||
num_row(ui, "Rotation", id("rot"), angles.shown(i.rotation).to_degrees(), &mut set, "rotation");
|
||||
num_row(ui, "Scale", id("sc"), i.scale.x, &mut set, "scale");
|
||||
}
|
||||
Some(EntityKind::Dimension(dm)) => {
|
||||
@@ -407,7 +409,13 @@ pub fn quick_properties(app: &mut CadApp, ctx: &egui::Context) {
|
||||
});
|
||||
});
|
||||
ctx.data_mut(|dd| dd.insert_temp(sid, st));
|
||||
if let Some((k, v)) = set {
|
||||
if let Some((k, mut v)) = set {
|
||||
// A rotation is typed as shown; properties.set takes it from +X counterclockwise.
|
||||
if k == "rotation"
|
||||
&& let Some(f) = v.as_f64()
|
||||
{
|
||||
v = json!(angles.unshown(f.to_radians()).to_degrees());
|
||||
}
|
||||
let _ = app.run("properties.set", json!({ "handles": ids, k: v }));
|
||||
}
|
||||
if close {
|
||||
|
||||
Reference in New Issue
Block a user