forked from 1b-agent/opencadstudio-upstream
refactor(commands): unified StepInput → feed_command step router (F0)
Introduce the single place every input source funnels through to drive the active command's step machine: a `StepInput` vocabulary (one variant per `CadCommand::on_*` method) and `OpenCADStudio::feed_command`, which routes the input to the matching `on_*` and applies the result. Sources keep their own classification (raw input → which StepInput); only the routing is centralized. Migrate the headless automation feeder onto it: feed_active_cmd builds a StepInput, the terminator sends StepInput::Enter. No behavior change — automation_ops_round_trip still passes. Also fixes headless inline-argument loss for plugin commands (#162): run_headless now offers the whole line to plugin dispatch first (plugins parse their own inline args, e.g. `HC_PIPE 2B 2C 1.25 0.013`), falling back to the first-word + fed-tokens path for built-in interactive tools. First phase of routing all input sources (command line, dynamic input, plugin API, viewport clicks) through one driver. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
69ea050124
commit
5fdd212592
3 files changed
+96
-25
No files matched your search
+33
-24
@@ -372,12 +372,35 @@ impl OpenCADStudio {
|
||||
let _ = self.dispatch_command(cmd);
|
||||
return;
|
||||
}
|
||||
// Plugin commands parse their own inline arguments from the whole line
|
||||
// (e.g. `HC_PIPE 2B 2C 1.25 0.013`), so offer the full command to plugin
|
||||
// dispatch first. A built-in interactive tool matches only its bare name
|
||||
// (`LINE`), so the full line is not a plugin command and falls through to
|
||||
// the first-word + fed-tokens path below. (#162)
|
||||
if crate::plugin::try_dispatch(self, i, cmd) {
|
||||
// The plugin either committed immediately (inline args consumed) or
|
||||
// installed an interactive command — feed any remaining tokens and
|
||||
// finish it on Enter, as for a built-in tool.
|
||||
self.finish_headless_interactive(&tokens);
|
||||
return;
|
||||
}
|
||||
let _ = self.dispatch_command(tokens[0]);
|
||||
if self.tabs[i].active_cmd.is_none() {
|
||||
// Not an interactive tool — an inline-argument command.
|
||||
let _ = self.dispatch_command(cmd);
|
||||
return;
|
||||
}
|
||||
self.finish_headless_interactive(&tokens);
|
||||
}
|
||||
|
||||
/// Feed `tokens[1..]` to the active interactive command as points / option
|
||||
/// keywords, then terminate it as if Enter were pressed (LINE / PLINE finish
|
||||
/// on Enter). No-op when no command is active.
|
||||
fn finish_headless_interactive(&mut self, tokens: &[&str]) {
|
||||
let i = self.active_tab;
|
||||
if self.tabs[i].active_cmd.is_none() {
|
||||
return;
|
||||
}
|
||||
self.last_point = None;
|
||||
for tok in &tokens[1..] {
|
||||
if self.tabs[i].active_cmd.is_none() {
|
||||
@@ -386,17 +409,15 @@ impl OpenCADStudio {
|
||||
self.feed_active_cmd(tok);
|
||||
}
|
||||
// Terminate a still-open command (LINE / PLINE finish on Enter).
|
||||
if self.tabs[i].active_cmd.is_some() {
|
||||
if let Some(r) = self.tabs[i].active_cmd.as_mut().map(|c| c.on_enter()) {
|
||||
let _ = self.apply_cmd_result(r);
|
||||
}
|
||||
}
|
||||
let _ = self.feed_command(crate::command::StepInput::Enter);
|
||||
}
|
||||
|
||||
/// Feed one token to the active command. When the command is picking an
|
||||
/// existing entity, the token is a hex handle; otherwise a coordinate point
|
||||
/// or an option keyword.
|
||||
/// Classify one headless token into a [`StepInput`] and route it through the
|
||||
/// shared [`OpenCADStudio::feed_command`]. When the command is picking an
|
||||
/// existing entity the token is a hex handle; otherwise it is a coordinate
|
||||
/// point or an option keyword / value.
|
||||
fn feed_active_cmd(&mut self, token: &str) {
|
||||
use crate::command::StepInput;
|
||||
let i = self.active_tab;
|
||||
// Object-pick step: the token is a handle (as returned by `query`).
|
||||
if self.tabs[i]
|
||||
@@ -419,13 +440,7 @@ impl OpenCADStudio {
|
||||
)
|
||||
})
|
||||
.unwrap_or(glam::Vec3::ZERO);
|
||||
if let Some(r) = self.tabs[i]
|
||||
.active_cmd
|
||||
.as_mut()
|
||||
.map(|c| c.on_entity_pick(handle, pt.as_dvec3()))
|
||||
{
|
||||
let _ = self.apply_cmd_result(r);
|
||||
}
|
||||
let _ = self.feed_command(StepInput::EntityPick(handle, pt.as_dvec3()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -436,15 +451,9 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
self.last_point = Some(pt);
|
||||
if let Some(r) = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt.as_dvec3())) {
|
||||
let _ = self.apply_cmd_result(r);
|
||||
}
|
||||
} else if let Some(r) = self.tabs[i]
|
||||
.active_cmd
|
||||
.as_mut()
|
||||
.and_then(|c| c.on_text_input(token))
|
||||
{
|
||||
let _ = self.apply_cmd_result(r);
|
||||
let _ = self.feed_command(StepInput::Point(pt.as_dvec3()));
|
||||
} else {
|
||||
let _ = self.feed_command(StepInput::Text(token.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+30
-1
@@ -1,9 +1,38 @@
|
||||
use super::{Message, OpenCADStudio};
|
||||
use crate::command::CmdResult;
|
||||
use crate::command::{CmdResult, StepInput};
|
||||
use acadrust::Handle;
|
||||
use iced::Task;
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// Drive the active command's step machine with one [`StepInput`], then
|
||||
/// apply the result. This is the single entry point every input source
|
||||
/// (command line, headless, dynamic input, plugin API, viewport) funnels
|
||||
/// through, so the routing from input → `on_*` method → `apply_cmd_result`
|
||||
/// lives in exactly one place. No-op when no command is active.
|
||||
pub(super) fn feed_command(&mut self, input: StepInput) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
let result: Option<CmdResult> = {
|
||||
let Some(cmd) = self.tabs[i].active_cmd.as_mut() else {
|
||||
return Task::none();
|
||||
};
|
||||
match input {
|
||||
StepInput::Point(p) => Some(cmd.on_point(p)),
|
||||
StepInput::Text(s) => cmd.on_text_input(&s),
|
||||
StepInput::EntityPick(h, p) => Some(cmd.on_entity_pick(h, p)),
|
||||
StepInput::StructurePick(h, p) => Some(cmd.on_structure_pick(h, p)),
|
||||
StepInput::SelectionComplete(hs) => Some(cmd.on_selection_complete(hs)),
|
||||
StepInput::Tangent(o, p) => Some(cmd.on_tangent_point(o, p)),
|
||||
StepInput::EditorClosed(c) => Some(cmd.on_editor_closed(c)),
|
||||
StepInput::Enter => Some(cmd.on_enter()),
|
||||
StepInput::Escape => Some(cmd.on_escape()),
|
||||
}
|
||||
};
|
||||
match result {
|
||||
Some(r) => self.apply_cmd_result(r),
|
||||
None => Task::none(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn apply_cmd_result(&mut self, result: CmdResult) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
match result {
|
||||
|
||||
@@ -46,6 +46,39 @@ pub enum TangentObject {
|
||||
Circle { center: DVec3, radius: f64 },
|
||||
}
|
||||
|
||||
/// One unit of input to the active command's step machine.
|
||||
///
|
||||
/// Every input source — the GUI command line, the headless automation feeder,
|
||||
/// dynamic input, the plugin API, and the viewport (clicks / picks / selection
|
||||
/// / tangent) — translates its raw input into one of these and routes it
|
||||
/// through `OpenCADStudio::feed_command`, so a single place drives the command
|
||||
/// regardless of where the step came from.
|
||||
///
|
||||
/// Variants are wired up incrementally as each source is migrated onto
|
||||
/// `feed_command`; `#[allow(dead_code)]` covers those not yet constructed.
|
||||
#[allow(dead_code)]
|
||||
pub enum StepInput {
|
||||
/// A coordinate (`on_point`).
|
||||
Point(DVec3),
|
||||
/// A typed token: keyword, option letter, distance, or value
|
||||
/// (`on_text_input`).
|
||||
Text(String),
|
||||
/// An object pick (`on_entity_pick`).
|
||||
EntityPick(Handle, DVec3),
|
||||
/// A sub-structure pick — vertex / edge / face (`on_structure_pick`).
|
||||
StructurePick(Handle, DVec3),
|
||||
/// A completed selection set (`on_selection_complete`).
|
||||
SelectionComplete(Vec<Handle>),
|
||||
/// A tangent-snap target (`on_tangent_point`).
|
||||
Tangent(TangentObject, DVec3),
|
||||
/// The text / MText editor closed, with its commit flag (`on_editor_closed`).
|
||||
EditorClosed(bool),
|
||||
/// Advance / finish the current step — Enter, or Space-as-Enter (`on_enter`).
|
||||
Enter,
|
||||
/// Cancel the command (`on_escape`).
|
||||
Escape,
|
||||
}
|
||||
|
||||
// ── Result token ──────────────────────────────────────────────────────────
|
||||
|
||||
/// Returned by every `CadCommand` method to tell main.rs what to do.
|
||||
|
||||
Reference in new issue
Block a user