feat(plugin): InteractiveCommand hook for click-to-place (API v2)
Plugins could dispatch commands but not register interactive (click-to- place) tools — the gap mf4633 flagged on #100 for Storm Sewer's SS_INLET / SS_PIPE. Add an InteractiveCommand trait + CommandStep to ocs_plugin_api and HostApi::start_interactive; a host adapter bridges it to the internal CadCommand, so a plugin tool drives the host's point-collection flow. Hybrid by construction: the same command works by clicking in the viewport AND by feeding coordinates over --serve (run "CMD x,y x,y"). Adding a HostApi method changes the contract vtable, so API_VERSION bumps to 2 — v1 plugin binaries are now refused at load. Part of the #100 extensibility epic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
8d3d0c30cc
commit
931eb908a9
5 changed files with 141 additions and 4 deletions
|
|
@ -29,6 +29,38 @@ pub trait BuiltinPlugin: Send + Sync {
|
|||
fn dispatch(&self, host: &mut dyn HostApi, cmd: &str) -> bool;
|
||||
}
|
||||
|
||||
/// A point-driven interactive command a plugin starts via
|
||||
/// [`HostApi::start_interactive`]. The host shows the prompt, collects points —
|
||||
/// clicked in the viewport, or fed as coordinates over the `--serve` automation
|
||||
/// API — and commits the entities the command yields, exactly like a built-in
|
||||
/// tool. This is the plugin-facing slice of the host's command machinery; it
|
||||
/// covers click-to-place placement without exposing the host's internal command
|
||||
/// trait.
|
||||
pub trait InteractiveCommand: Send {
|
||||
/// Prompt for the next point.
|
||||
fn prompt(&self) -> String;
|
||||
/// A point was supplied (clicked or typed `x,y[,z]`). Returns the next step.
|
||||
fn on_point(&mut self, pt: [f64; 3]) -> CommandStep;
|
||||
/// Enter pressed with no point — e.g. to finish a multi-point command.
|
||||
fn on_enter(&mut self) -> CommandStep {
|
||||
CommandStep::Cancel
|
||||
}
|
||||
}
|
||||
|
||||
/// The outcome of an [`InteractiveCommand`] step.
|
||||
pub enum CommandStep {
|
||||
/// Need another point; keep the command active.
|
||||
NeedPoint,
|
||||
/// Commit an entity to the document and keep collecting points.
|
||||
Commit(EntityType),
|
||||
/// Commit an entity and end the command.
|
||||
CommitAndEnd(EntityType),
|
||||
/// End the command without committing.
|
||||
Done,
|
||||
/// Cancel the command.
|
||||
Cancel,
|
||||
}
|
||||
|
||||
/// Export a `BuiltinPlugin` from a `cdylib` so the host can load it at runtime.
|
||||
///
|
||||
/// Emits the two C symbols the loader looks for: `ocs_plugin_api_version`
|
||||
|
|
@ -90,6 +122,10 @@ pub trait HostApi {
|
|||
fn push_output(&mut self, msg: &str);
|
||||
fn push_error(&mut self, msg: &str);
|
||||
|
||||
/// Start a plugin-defined interactive (click-to-place) command on the active
|
||||
/// tab. The host drives it through its normal point-collection flow.
|
||||
fn start_interactive(&mut self, command: Box<dyn InteractiveCommand>);
|
||||
|
||||
// ── Per-tab plugin state (object-safe; use the typed helpers below) ──────
|
||||
fn plugin_state_any(&self, plugin_id: &str) -> Option<&(dyn Any + Send + Sync)>;
|
||||
fn plugin_state_any_mut(&mut self, plugin_id: &str)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
//! Plugin identity and capability declaration.
|
||||
|
||||
/// Host plugin API version. Bump when the host runtime surface breaks
|
||||
/// compatibility.
|
||||
pub const API_VERSION: u32 = 1;
|
||||
/// compatibility. v2 added `HostApi::start_interactive` (the
|
||||
/// `InteractiveCommand` hook) — a vtable change, so v1 binaries are refused.
|
||||
pub const API_VERSION: u32 = 2;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct ApiVersion {
|
||||
|
|
|
|||
Loading…
Reference in a new issue