From 358103c1e453f2b8d57ce068070b770d4aa69165 Mon Sep 17 00:00:00 2001 From: yztink Date: Wed, 23 Sep 2026 11:33:37 -0700 Subject: [PATCH] feat(plugin): wire on_cursor_move and preview wires through IPC plugin runner --- crates/ocs_plugin_api/src/ipc/protocol.rs | 7 +++++ crates/ocs_plugin_api/src/process.rs | 31 +++++++++++++++++++++++ crates/ocs_plugin_api/src/runner.rs | 30 ++++++++++++++++++++++ src/app/plugin_host.rs | 20 +++++++++++++++ 4 files changed, 88 insertions(+) diff --git a/crates/ocs_plugin_api/src/ipc/protocol.rs b/crates/ocs_plugin_api/src/ipc/protocol.rs index 5d86fe6b2..70d7513df 100644 --- a/crates/ocs_plugin_api/src/ipc/protocol.rs +++ b/crates/ocs_plugin_api/src/ipc/protocol.rs @@ -23,6 +23,7 @@ use crate::ribbon::owned::{OwnedPluginManifest, OwnedRibbonGroup}; pub use acadrust::xdata::{ExtendedDataRecord, XDataValue}; pub use acadrust::{CadDocument, EntityType, Handle}; +pub use crate::host::PreviewWire; /// Events the host forwards to an active plugin `InteractiveCommand`. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -83,6 +84,11 @@ pub enum HostRequest { }, /// V7: release an interactive command after completion or cancellation. DropInteractive { command_id: u64 }, + /// V7: cursor move preview update during an interactive command. + CursorMove { + command_id: u64, + pt: [f64; 3], + }, } /// Responses the plugin runner sends back for `HostRequest`. @@ -95,6 +101,7 @@ pub enum HostResponse { Manifest(OwnedPluginManifest), Error(String), CodeExecutionResult(crate::host::ExecutionResult), + PreviewWires(Vec), } /// Requests the plugin runner sends to the host. diff --git a/crates/ocs_plugin_api/src/process.rs b/crates/ocs_plugin_api/src/process.rs index 5f8086a08..161ff97a1 100644 --- a/crates/ocs_plugin_api/src/process.rs +++ b/crates/ocs_plugin_api/src/process.rs @@ -222,6 +222,7 @@ fn base_max_floor(base: Duration, kind: &'static str) -> Duration { "GetManifest" | "GetRibbon" => Duration::from_secs(5), "Dispatch" => Duration::from_secs(10), "InteractiveEvent" | "GetPrompt" | "NeedsEntityPick" => Duration::from_secs(2), + "CursorMove" => Duration::from_millis(500), "ExecuteCode" => execute_code_timeout(), _ => Duration::from_secs(1), }; @@ -238,6 +239,7 @@ pub(crate) fn request_kind(req: &HostRequest) -> &'static str { HostRequest::NeedsEntityPick { .. } => "NeedsEntityPick", HostRequest::ExecuteCode { .. } => "ExecuteCode", HostRequest::DropInteractive { .. } => "DropInteractive", + HostRequest::CursorMove { .. } => "CursorMove", HostRequest::Shutdown => "Shutdown", } } @@ -681,6 +683,35 @@ impl PluginProcess { } } + /// Ask the plugin process for real-time preview wires for the cursor position. + pub fn on_cursor_move( + &self, + command_id: u64, + pt: [f64; 3], + ) -> Result, PluginError> { + if let Some(v4) = &self.v4 { + let resp = v4.call( + &mut NullHost, + HostRequest::CursorMove { command_id, pt }, + &mut |_| {}, + )?; + match resp { + HostResponse::PreviewWires(w) => Ok(w), + other => Err(PluginError::UnexpectedResponse(Box::new(other))), + } + } else { + self.call_simple( + HostRequest::CursorMove { command_id, pt }, + "CursorMove", + "cursor_move", + |resp| match resp { + HostResponse::PreviewWires(w) => Ok(w), + other => Err(Box::new(other)), + }, + ) + } + } + pub fn is_alive(&self) -> bool { if let Some(v4) = &self.v4 { return v4.is_alive(); diff --git a/crates/ocs_plugin_api/src/runner.rs b/crates/ocs_plugin_api/src/runner.rs index 1c7627f9d..ddf1758cb 100644 --- a/crates/ocs_plugin_api/src/runner.rs +++ b/crates/ocs_plugin_api/src/runner.rs @@ -243,6 +243,21 @@ fn handle_host_request( None => HostResponse::Error(format!("unknown interactive command {command_id}")), } } + HostRequest::CursorMove { command_id, pt } => { + let result = { + let mut registry = interactive.borrow_mut(); + registry.get_mut(&command_id).map(|cmd| { + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + cmd.on_cursor_move(pt) + })) + }) + }; + match result { + Some(Ok(wires)) => HostResponse::PreviewWires(wires), + Some(Err(_)) => HostResponse::PreviewWires(Vec::new()), + None => HostResponse::PreviewWires(Vec::new()), + } + } HostRequest::ExecuteCode { .. } => { HostResponse::Error("ExecuteCode requires V4 protocol".to_string()) } @@ -366,6 +381,21 @@ fn handle_host_request_v4( None => Some(HostResponse::Error(format!("unknown interactive command {command_id}"))), } } + HostRequest::CursorMove { command_id, pt } => { + let result = { + let mut registry = interactive.borrow_mut(); + registry.get_mut(&command_id).map(|cmd| { + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + cmd.on_cursor_move(pt) + })) + }) + }; + match result { + Some(Ok(wires)) => Some(HostResponse::PreviewWires(wires)), + Some(Err(_)) => Some(HostResponse::PreviewWires(Vec::new())), + None => Some(HostResponse::PreviewWires(Vec::new())), + } + } HostRequest::ExecuteCode { command_id, source, diff --git a/src/app/plugin_host.rs b/src/app/plugin_host.rs index 7bcf9a62a..198d68679 100644 --- a/src/app/plugin_host.rs +++ b/src/app/plugin_host.rs @@ -2842,6 +2842,26 @@ impl crate::command::CadCommand for PluginProcessInteractiveAdapter { self.refresh(); result } + fn on_preview_wires(&mut self, pt: glam::DVec3) -> Vec { + let raw_wires = self + .process + .on_cursor_move(self.command_id, [pt.x as f64, pt.y as f64, pt.z as f64]) + .unwrap_or_default(); + + raw_wires + .into_iter() + .filter(|w| w.points.len() >= 2) + .map(|w| { + let color = w.color.unwrap_or(crate::scene::model::wire_model::WireModel::CYAN); + crate::scene::model::wire_model::WireModel::solid_f64( + "rubber_band".into(), + w.points, + color, + false, + ) + }) + .collect() + } } fn plugin_step_to_result(step: ocs_plugin_api::host::CommandStep) -> crate::command::CmdResult {