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
|
// PLAN — look straight down at the drawing (top view). The optional
|
||||||
// World/Ucs/Current keyword is accepted; all map to the world top
|
// World/Ucs/Current keyword is accepted; all map to the world top
|
||||||
// view for now.
|
// view for now.
|
||||||
cmd if cmd == "PLAN" || cmd.starts_with("PLAN ") => {
|
// PLAN — plan view of a UCS (#326). Shows its options right on
|
||||||
return Some(Task::done(Message::ViewCubeSnapWorld(
|
// activation instead of flipping the view like a cube click; the
|
||||||
crate::scene::CubeRegion::Face(crate::scene::pipeline::viewcube::FACE_TOP),
|
// 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" => {
|
"VIEW" => {
|
||||||
|
|
@ -1152,4 +1211,25 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
Some(self.finish_dispatch(cmd))
|
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>)>,
|
options: Vec<(&'static str, &'static str, Option<&'static str>)>,
|
||||||
/// Set once a value-taking verb is chosen: `(keyword, value prompt)`.
|
/// Set once a value-taking verb is chosen: `(keyword, value prompt)`.
|
||||||
pending: Option<(&'static str, &'static str)>,
|
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 {
|
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(
|
pub fn new(
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
prompt: &'static str,
|
prompt: &'static str,
|
||||||
|
|
@ -345,6 +354,7 @@ impl KeywordCommand {
|
||||||
prompt,
|
prompt,
|
||||||
options,
|
options,
|
||||||
pending: None,
|
pending: None,
|
||||||
|
default: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -411,6 +421,12 @@ impl CadCommand for KeywordCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self) -> CmdResult {
|
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
|
CmdResult::Cancel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue