Initial commit

This commit is contained in:
Sebastian authored and Hakan Seven committed 2026-06-25 00:01:36 +03:00
1 parent 125b89d8da
commit dcfaed0182
26 files changed
+2080 -143

No files matched your search

+1 -1
View File
@@ -560,7 +560,7 @@ mod tests {
fn save_then_open_round_trips() {
let mut app = OpenCADStudio::new_for_test();
let path = std::env::temp_dir().join("ocs_automation_test.dxf");
let p = path.to_string_lossy();
let p = path.to_string_lossy().replace('\\', "\\\\");
app.automation_op(r#"{"op":"new"}"#);
assert_eq!(
app.automation_op(&format!(r#"{{"op":"save","path":"{p}"}}"#))["ok"],
+17 -1
View File
@@ -1980,7 +1980,7 @@ impl OpenCADStudio {
// their ribbon tabs into the ribbon. Skipped under test/wasm.
#[cfg(all(not(target_arch = "wasm32"), not(test)))]
{
for (id, res) in crate::plugin::external::load_at_startup() {
for (id, res) in crate::plugin::external::load_at_startup(&mut app) {
if let Err(e) = res {
app.command_line
.push_error(&format!("Plugin '{id}' failed to load: {e}"));
@@ -2000,6 +2000,22 @@ impl OpenCADStudio {
Self::new()
}
/// Install `cmd` as the active interactive command for tab `tab`.
pub(crate) fn set_active_command(
&mut self,
tab: usize,
cmd: Box<dyn crate::command::CadCommand>,
) {
if let Some(t) = self.tabs.get_mut(tab) {
t.active_cmd = Some(cmd);
}
}
/// Push an error message from the plugin runtime to the command line.
pub(crate) fn push_plugin_error(&mut self, msg: &str) {
self.command_line.push_error(msg);
}
#[cfg(test)]
pub(crate) fn command_history_info(&self) -> Vec<String> {
use crate::ui::command_line::EntryKind;
+83
View File
@@ -260,6 +260,89 @@ impl crate::command::CadCommand for PluginInteractiveAdapter {
}
}
/// Bridges an out-of-process plugin's interactive command to the host's
/// `CadCommand`. Events are sent over IPC and the returned `CommandStep` is
/// translated into a `CmdResult`. Prompt and object-pick mode are cached and
/// refreshed after each event.
pub(crate) struct PluginProcessInteractiveAdapter {
pub process: std::sync::Arc<ocs_plugin_api::process::PluginProcess>,
pub command_id: u64,
prompt: Option<String>,
needs_entity_pick: Option<bool>,
}
impl PluginProcessInteractiveAdapter {
pub(crate) fn new(
process: std::sync::Arc<ocs_plugin_api::process::PluginProcess>,
command_id: u64,
) -> Self {
let prompt = process.get_prompt(command_id).ok();
let needs_entity_pick = process.needs_entity_pick(command_id).ok();
Self {
process,
command_id,
prompt,
needs_entity_pick,
}
}
fn refresh(&mut self) {
self.prompt = self.process.get_prompt(self.command_id).ok();
self.needs_entity_pick = self.process.needs_entity_pick(self.command_id).ok();
}
}
impl crate::command::CadCommand for PluginProcessInteractiveAdapter {
fn name(&self) -> &'static str {
"PLUGIN"
}
fn prompt(&self) -> String {
self.prompt.clone().unwrap_or_default()
}
fn on_point(&mut self, pt: glam::Vec3) -> crate::command::CmdResult {
use ocs_plugin_api::ipc::protocol::InteractiveEvent;
let result = self
.process
.interactive_event(
self.command_id,
InteractiveEvent::Point([pt.x as f64, pt.y as f64, pt.z as f64]),
)
.map(plugin_step_to_result)
.unwrap_or(crate::command::CmdResult::Cancel);
self.refresh();
result
}
fn on_enter(&mut self) -> crate::command::CmdResult {
use ocs_plugin_api::ipc::protocol::InteractiveEvent;
let result = self
.process
.interactive_event(self.command_id, InteractiveEvent::Enter)
.map(plugin_step_to_result)
.unwrap_or(crate::command::CmdResult::Cancel);
self.refresh();
result
}
fn needs_entity_pick(&self) -> bool {
self.needs_entity_pick.unwrap_or(false)
}
fn on_entity_pick(&mut self, handle: Handle, pt: glam::Vec3) -> crate::command::CmdResult {
use ocs_plugin_api::ipc::protocol::InteractiveEvent;
let result = self
.process
.interactive_event(
self.command_id,
InteractiveEvent::ObjectPick {
handle,
pt: [pt.x as f64, pt.y as f64, pt.z as f64],
},
)
.map(plugin_step_to_result)
.unwrap_or(crate::command::CmdResult::Cancel);
self.refresh();
result
}
}
fn plugin_step_to_result(
step: ocs_plugin_api::host::CommandStep,
) -> crate::command::CmdResult {