forked from 1b-agent/opencadstudio-upstream
feat(python): command runner and modify wrappers (doc.command, doc.modify)
HostApi::run_command drives the real OCS command through the same primitives the automation channel uses, synchronously on the host thread: run a line, start a command and answer point/text/token/entity/selection/enter prompts, or cancel (closing anything the command opened). Refused while another command is active, for a denylist (QUIT, NEW, OPEN, SAVE*, PY_* ...) and off the active tab. Python: doc.command, doc.start_command and doc.modify (offset, trim, extend, fillet, move, copy, rotate, scale, mirror, erase). Real-IPC audit checks exact geometry for every wrapper plus guards and undo; IPC and Python unit tests; docs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
795aa922b4
commit
1b1785eefb
13 files changed
+867
-1
No files matched your search
@@ -482,6 +482,18 @@ impl HostApi for PluginHostApi {
|
||||
}
|
||||
}
|
||||
|
||||
fn run_command(&mut self, request: crate::host::CommandRequest) -> Result<crate::host::CommandOutcome, String> {
|
||||
match self.client.request(PluginRequest::RunCommand { request }) {
|
||||
Ok(PluginResponse::CommandResult(result)) => {
|
||||
self.document_cache = OnceCell::new();
|
||||
result
|
||||
}
|
||||
Ok(PluginResponse::Error(error)) => Err(error),
|
||||
Ok(other) => Err(format!("unexpected command response: {other:?}")),
|
||||
Err(error) => Err(error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn table_operation(&mut self, operation: crate::host::TableOperation) -> Result<Handle, String> {
|
||||
match self.client.request(PluginRequest::TableOperation { operation }) {
|
||||
Ok(PluginResponse::TableResult(result)) => {
|
||||
|
||||
@@ -157,6 +157,8 @@ pub enum PluginRequest {
|
||||
SolidOperation { operation: crate::host::SolidOperation },
|
||||
/// V7 (additive): drawing table record create/modify/rename/delete.
|
||||
TableOperation { operation: crate::host::TableOperation },
|
||||
/// V7 (additive): drive an OCS command.
|
||||
RunCommand { request: crate::host::CommandRequest },
|
||||
}
|
||||
|
||||
/// Responses the host sends back for `PluginRequest`.
|
||||
@@ -192,6 +194,7 @@ pub enum PluginResponse {
|
||||
SelectionResult(Result<(), String>),
|
||||
SolidResult(Result<Handle, String>),
|
||||
TableResult(Result<Handle, String>),
|
||||
CommandResult(Result<crate::host::CommandOutcome, String>),
|
||||
}
|
||||
|
||||
/// Messages sent from the host to the plugin runner.
|
||||
|
||||
@@ -111,5 +111,6 @@ pub fn handle_plugin_request(
|
||||
SetSelection { handles } => PluginResponse::SelectionResult(host.set_selection(&handles)),
|
||||
SolidOperation { operation } => PluginResponse::SolidResult(host.solid_operation(operation)),
|
||||
TableOperation { operation } => PluginResponse::TableResult(host.table_operation(operation)),
|
||||
RunCommand { request } => PluginResponse::CommandResult(host.run_command(request)),
|
||||
}
|
||||
}
|
||||
@@ -796,6 +796,18 @@ impl HostApi for V4PluginHostApi {
|
||||
}
|
||||
}
|
||||
|
||||
fn run_command(&mut self, request: crate::host::CommandRequest) -> Result<crate::host::CommandOutcome, String> {
|
||||
match self.request(PluginRequest::RunCommand { request }) {
|
||||
Ok(PluginResponse::CommandResult(result)) => {
|
||||
self.document_cache = OnceCell::new();
|
||||
result
|
||||
}
|
||||
Ok(PluginResponse::Error(error)) => Err(error),
|
||||
Ok(other) => Err(format!("unexpected command response: {other:?}")),
|
||||
Err(error) => Err(error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn table_operation(&mut self, operation: crate::host::TableOperation) -> Result<Handle, String> {
|
||||
match self.request(PluginRequest::TableOperation { operation }) {
|
||||
Ok(PluginResponse::TableResult(result)) => {
|
||||
|
||||
@@ -105,6 +105,43 @@ mod tests {
|
||||
assert_eq!(decoded, changed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_request_and_outcome_roundtrip() {
|
||||
use crate::host::{CommandOutcome, CommandRequest};
|
||||
for request in [
|
||||
CommandRequest::Run { line: "LINE 0,0 1,1".into() },
|
||||
CommandRequest::Start { name: "OFFSET".into() },
|
||||
CommandRequest::Point { point: [1.0, 2.0, 3.0] },
|
||||
CommandRequest::Text { text: "2".into() },
|
||||
CommandRequest::Token { text: "R".into() },
|
||||
CommandRequest::Entity { handle: acadrust::Handle::new(9), point: [0.0; 3] },
|
||||
CommandRequest::Selection,
|
||||
CommandRequest::Enter,
|
||||
CommandRequest::Cancel,
|
||||
] {
|
||||
let bytes = bincode::serialize(&PluginRequest::RunCommand { request: request.clone() }).unwrap();
|
||||
assert!(matches!(bincode::deserialize::<PluginRequest>(&bytes).unwrap(),
|
||||
PluginRequest::RunCommand { request: decoded } if decoded == request));
|
||||
}
|
||||
let outcome = CommandOutcome {
|
||||
status: "waiting_input".into(),
|
||||
blocked_by: Some("command".into()),
|
||||
command: "OFFSET".into(),
|
||||
prompt: "Select object".into(),
|
||||
accepts: vec!["entity".into()],
|
||||
options: vec!["M".into()],
|
||||
entities: 4,
|
||||
added: -1,
|
||||
unconsumed: vec!["x".into()],
|
||||
error: Some("e".into()),
|
||||
};
|
||||
for result in [Ok(outcome), Err("refused".to_owned())] {
|
||||
let bytes = bincode::serialize(&PluginResponse::CommandResult(result.clone())).unwrap();
|
||||
assert!(matches!(bincode::deserialize::<PluginResponse>(&bytes).unwrap(),
|
||||
PluginResponse::CommandResult(decoded) if decoded == result));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_operation_request_and_response_roundtrip() {
|
||||
use crate::host::{LayerConfig, TableOperation};
|
||||
|
||||
Reference in new issue
Block a user