fix(plan): show options on activation instead of flipping the view (#326)
PLAN acted like a ViewCube TOP click the moment it ran — already on Top meant an immediate flip to Bottom, and the options never appeared. It now prompts "[Current ucs / Ucs / World] <Current>:" right on activation and only changes the view once an option runs: Current = plan of the active UCS, World = world XY, Ucs = prompts for a named UCS (case-insensitive lookup). The new plan_snap goes straight to the plan view with no flip-to-opposite cube behaviour. KeywordCommand gains with_default(): a bare Enter at the verb step dispatches the default verb (PLAN's <Current>) instead of cancelling. Closes #326 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b998082c82
commit
b32a9b4083
2 changed files with 100 additions and 4 deletions
|
|
@ -394,10 +394,69 @@ impl OpenCADStudio {
|
|||
// PLAN — look straight down at the drawing (top view). The optional
|
||||
// World/Ucs/Current keyword is accepted; all map to the world top
|
||||
// view for now.
|
||||
cmd if cmd == "PLAN" || cmd.starts_with("PLAN ") => {
|
||||
return Some(Task::done(Message::ViewCubeSnapWorld(
|
||||
crate::scene::CubeRegion::Face(crate::scene::pipeline::viewcube::FACE_TOP),
|
||||
)));
|
||||
// PLAN — plan view of a UCS (#326). Shows its options right on
|
||||
// activation instead of flipping the view like a cube click; the
|
||||
// view only changes once an option (or the Current default) runs.
|
||||
"PLAN" => {
|
||||
use crate::command::KeywordCommand;
|
||||
let c = KeywordCommand::new(
|
||||
"PLAN",
|
||||
"PLAN [Current ucs / Ucs / World] <Current>:",
|
||||
vec![
|
||||
("Current ucs", "CURRENT", None),
|
||||
("Ucs", "UCS", Some("PLAN UCS ucs name:")),
|
||||
("World", "WORLD", None),
|
||||
],
|
||||
)
|
||||
.with_default("CURRENT");
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("PLAN ") => {
|
||||
let parts: Vec<&str> = cmd.splitn(3, ' ').collect();
|
||||
let sub = parts.get(1).map(|s| s.to_uppercase()).unwrap_or_default();
|
||||
match sub.as_str() {
|
||||
"" | "CURRENT" | "C" => {
|
||||
let r = self.tabs[i].scene.viewcube_ucs_mat();
|
||||
self.plan_snap(i, r);
|
||||
self.command_line.push_output("PLAN: current UCS plan view.");
|
||||
}
|
||||
"WORLD" | "W" => {
|
||||
self.plan_snap(i, glam::Mat4::IDENTITY);
|
||||
self.command_line.push_output("PLAN: world plan view.");
|
||||
}
|
||||
"UCS" | "U" => {
|
||||
let name = parts.get(2).map(|s| s.trim()).unwrap_or("");
|
||||
if name.is_empty() {
|
||||
self.command_line.push_info("Usage: PLAN UCS <name>");
|
||||
} else {
|
||||
let ucs = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.ucss
|
||||
.iter()
|
||||
.find(|u| u.name.eq_ignore_ascii_case(name))
|
||||
.cloned();
|
||||
match ucs {
|
||||
Some(u) => {
|
||||
let r = crate::app::helpers::UcsXform::from_ucs(&u)
|
||||
.rotation_mat();
|
||||
self.plan_snap(i, r);
|
||||
self.command_line.push_output(&format!(
|
||||
"PLAN: plan view of UCS '{}'.",
|
||||
u.name
|
||||
));
|
||||
}
|
||||
None => self
|
||||
.command_line
|
||||
.push_error(&format!("UCS '{name}' not found.")),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => self
|
||||
.command_line
|
||||
.push_info("Usage: PLAN [CURRENT / UCS <name> / WORLD]"),
|
||||
}
|
||||
}
|
||||
|
||||
"VIEW" => {
|
||||
|
|
@ -1152,4 +1211,25 @@ impl OpenCADStudio {
|
|||
}
|
||||
Some(self.finish_dispatch(cmd))
|
||||
}
|
||||
|
||||
/// Snap to the plan view of the UCS whose rotation is `r_ucs`, straight —
|
||||
/// no "already there → flip to the opposite face" cube behaviour (#326).
|
||||
fn plan_snap(&mut self, i: usize, r_ucs: glam::Mat4) {
|
||||
let eye_dir = r_ucs.transform_vector3(
|
||||
crate::scene::CubeRegion::Face(crate::scene::pipeline::viewcube::FACE_TOP)
|
||||
.snap_direction(),
|
||||
);
|
||||
if self.tabs[i].scene.active_viewport.is_some() {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.mutate_active_viewport_camera(|c| c.snap_to_face(eye_dir, r_ucs));
|
||||
} else {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.camera
|
||||
.borrow_mut()
|
||||
.snap_to_face(eye_dir, r_ucs);
|
||||
}
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -332,9 +332,18 @@ pub struct KeywordCommand {
|
|||
options: Vec<(&'static str, &'static str, Option<&'static str>)>,
|
||||
/// Set once a value-taking verb is chosen: `(keyword, value prompt)`.
|
||||
pending: Option<(&'static str, &'static str)>,
|
||||
/// Keyword a bare Enter dispatches at the verb step (`<Default>` prompts,
|
||||
/// e.g. PLAN's `<Current>`). `None` = Enter cancels, as before.
|
||||
default: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl KeywordCommand {
|
||||
/// Set the verb a bare Enter dispatches (shown as `<Default>` in prompts).
|
||||
pub fn with_default(mut self, kw: &'static str) -> Self {
|
||||
self.default = Some(kw);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
name: &'static str,
|
||||
prompt: &'static str,
|
||||
|
|
@ -345,6 +354,7 @@ impl KeywordCommand {
|
|||
prompt,
|
||||
options,
|
||||
pending: None,
|
||||
default: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -411,6 +421,12 @@ impl CadCommand for KeywordCommand {
|
|||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
// Bare Enter at the verb step runs the default verb when one is set.
|
||||
if self.pending.is_none() {
|
||||
if let Some(kw) = self.default {
|
||||
return CmdResult::Dispatch(format!("{} {kw}", self.name));
|
||||
}
|
||||
}
|
||||
CmdResult::Cancel
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue