fix(rotate): apply a typed angle right after the centre (#159)

ROTATE's reference-point step prompts "or skip: type angle directly", but
on_text_input only handled the later Angle step. So picking a centre and
typing the angle did nothing, and the next Enter cancelled the command —
the objects never rotated, which reads as the rotate "erasing" / losing
them.

Accept a typed angle at the reference-point step too, rotating directly
about the centre (the reference point stays optional). Added an automation
regression test that rotates a line 90° with centre + angle only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-25 19:58:45 +03:00
1 parent c599c2ba09
commit 1a78e6c7ff
2 files changed
+43 -15

No files matched your search

+23
View File
@@ -518,6 +518,29 @@ mod tests {
assert!(app.tabs[i].active_cmd.is_none(), "command must have finished");
}
#[test]
fn rotate_by_typed_angle_after_center() {
// ROTATE: after picking the centre, typing the angle directly must
// rotate the selection (the reference point is optional, as the prompt
// says). Before the fix this did nothing and the command cancelled, so
// the objects never rotated. Regression for #159.
let mut app = OpenCADStudio::new_for_test();
app.automation_op(r#"{"op":"new"}"#);
app.automation_op(r#"{"op":"run","cmd":"LINE 0,0 10,0"}"#);
app.automation_op(r#"{"op":"select","type":"Line"}"#);
// Centre (0,0) then 90° — no reference point.
app.automation_op(r#"{"op":"run","cmd":"ROTATE 0,0 90"}"#);
let q = app.automation_op(r#"{"op":"query","type":"Line"}"#);
assert_eq!(q["count"], 1, "the line must survive the rotate");
let ex = q["entities"][0]["end"][0].as_f64().unwrap();
let ey = q["entities"][0]["end"][1].as_f64().unwrap();
// (10,0) rotated 90° about the origin → (0,10).
assert!(
ex.abs() < 1e-3 && (ey - 10.0).abs() < 1e-3,
"line end after ROTATE 90 = ({ex}, {ey})"
);
}
#[test]
fn save_then_open_round_trips() {
let mut app = OpenCADStudio::new_for_test();
+20 -15
View File
@@ -120,21 +120,26 @@ impl CadCommand for RotateCommand {
}
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
if let Step::Angle { center, .. } = &self.step {
// The value already carries the correct sign (the dynamic-input
// layer applies the cursor's side for a bare magnitude).
let deg: f32 = text.trim().replace(',', ".").parse().ok()?;
let center = *center;
defaults::set_rotate_angle(deg);
return Some(CmdResult::TransformSelected(
self.handles.clone(),
EntityTransform::Rotate {
center,
angle_rad: deg.to_radians(),
},
));
}
None
// A typed angle rotates directly about the centre. Accept it at the
// reference-point step too (the prompt offers "skip: type angle
// directly") — otherwise typing the angle there did nothing and the
// command cancelled on the next Enter, so the objects never rotated.
let center = match &self.step {
Step::RefPoint { center } => *center,
Step::Angle { center, .. } => *center,
Step::Center => return None,
};
// The value already carries the correct sign (the dynamic-input
// layer applies the cursor's side for a bare magnitude).
let deg: f32 = text.trim().replace(',', ".").parse().ok()?;
defaults::set_rotate_angle(deg);
Some(CmdResult::TransformSelected(
self.handles.clone(),
EntityTransform::Rotate {
center,
angle_rad: deg.to_radians(),
},
))
}
fn on_preview_wires(&mut self, pt: DVec3) -> Vec<WireModel> {