forked from 1b-agent/opencadstudio-upstream
feat(python): validated, undoable layer table (doc.layers)
Adds the additive v7 TableOperation request and HostApi::table_operation for layer create/modify/rename/delete/set-current. Refusals are decided before the undo step is recorded. Python wrapper, real-IPC audit with DWG/DXF persistence, IPC round-trip test, Python unit test and docs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6b8056cfeb
commit
b7b6e2cef5
12 files changed
+681
No files matched your search
@@ -85,6 +85,25 @@ pub enum SolidBoolean {
|
||||
Intersect,
|
||||
}
|
||||
|
||||
/// A change to a drawing table record (API v7, additive). The host validates
|
||||
/// the request, records one undo step and refuses without changing anything
|
||||
/// when it cannot honour it; the result is the record's handle.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum TableOperation {
|
||||
/// Create a layer. `config.name` must be unused and valid.
|
||||
LayerCreate { config: LayerConfig },
|
||||
/// Change only the properties `config` sets on an existing layer.
|
||||
LayerModify { config: LayerConfig },
|
||||
/// Rename a layer; entities on it follow. Layer `0` and `Defpoints` stay.
|
||||
LayerRename { from: String, to: String },
|
||||
/// Delete a layer. A layer that still holds objects is refused unless
|
||||
/// `erase_objects` is set, which erases them with the layer. Layer `0`,
|
||||
/// `Defpoints` and the current layer are never deleted.
|
||||
LayerDelete { name: String, erase_objects: bool },
|
||||
/// Make an existing layer current.
|
||||
LayerSetCurrent { name: String },
|
||||
}
|
||||
|
||||
/// A kernel operation on ACIS-backed solids (API v7, additive). The host owns
|
||||
/// the geometry: a script never sees or rewrites the payload.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
@@ -690,6 +709,12 @@ pub trait HostApi {
|
||||
fn solid_operation(&mut self, _operation: SolidOperation) -> Result<Handle, String> {
|
||||
Err("solid operations are not supported by this host".to_owned())
|
||||
}
|
||||
|
||||
/// Create, change, rename, delete or select a drawing table record (API v7,
|
||||
/// additive). Returns the record's handle; a refusal changes nothing.
|
||||
fn table_operation(&mut self, _operation: TableOperation) -> Result<Handle, String> {
|
||||
Err("table operations are not supported by this host".to_owned())
|
||||
}
|
||||
/// Add a layer to the active document with full initial properties.
|
||||
/// If an optional property in `config` is `None`, standard CAD defaults are applied.
|
||||
/// Returns `None` if the layer already exists or `config.name` is invalid.
|
||||
|
||||
@@ -482,6 +482,18 @@ impl HostApi for PluginHostApi {
|
||||
}
|
||||
}
|
||||
|
||||
fn table_operation(&mut self, operation: crate::host::TableOperation) -> Result<Handle, String> {
|
||||
match self.client.request(PluginRequest::TableOperation { operation }) {
|
||||
Ok(PluginResponse::TableResult(result)) => {
|
||||
if result.is_ok() { self.document_cache = OnceCell::new(); }
|
||||
result
|
||||
}
|
||||
Ok(PluginResponse::Error(error)) => Err(error),
|
||||
Ok(other) => Err(format!("unexpected table operation response: {other:?}")),
|
||||
Err(error) => Err(error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_selection(&mut self, handles: &[Handle]) -> Result<(), String> {
|
||||
match self.client.request(PluginRequest::SetSelection { handles: handles.to_vec() }) {
|
||||
Ok(PluginResponse::SelectionResult(result)) => result,
|
||||
|
||||
@@ -155,6 +155,8 @@ pub enum PluginRequest {
|
||||
SetSelection { handles: Vec<Handle> },
|
||||
/// V7 (additive): kernel-backed solid create or transform.
|
||||
SolidOperation { operation: crate::host::SolidOperation },
|
||||
/// V7 (additive): drawing table record create/modify/rename/delete.
|
||||
TableOperation { operation: crate::host::TableOperation },
|
||||
}
|
||||
|
||||
/// Responses the host sends back for `PluginRequest`.
|
||||
@@ -189,6 +191,7 @@ pub enum PluginResponse {
|
||||
Selection(Vec<Handle>),
|
||||
SelectionResult(Result<(), String>),
|
||||
SolidResult(Result<Handle, String>),
|
||||
TableResult(Result<Handle, String>),
|
||||
}
|
||||
|
||||
/// Messages sent from the host to the plugin runner.
|
||||
|
||||
@@ -110,5 +110,6 @@ pub fn handle_plugin_request(
|
||||
GetSelection => PluginResponse::Selection(host.selection()),
|
||||
SetSelection { handles } => PluginResponse::SelectionResult(host.set_selection(&handles)),
|
||||
SolidOperation { operation } => PluginResponse::SolidResult(host.solid_operation(operation)),
|
||||
TableOperation { operation } => PluginResponse::TableResult(host.table_operation(operation)),
|
||||
}
|
||||
}
|
||||
@@ -796,6 +796,18 @@ impl HostApi for V4PluginHostApi {
|
||||
}
|
||||
}
|
||||
|
||||
fn table_operation(&mut self, operation: crate::host::TableOperation) -> Result<Handle, String> {
|
||||
match self.request(PluginRequest::TableOperation { operation }) {
|
||||
Ok(PluginResponse::TableResult(result)) => {
|
||||
if result.is_ok() { self.document_cache = OnceCell::new(); }
|
||||
result
|
||||
}
|
||||
Ok(PluginResponse::Error(error)) => Err(error),
|
||||
Ok(other) => Err(format!("unexpected table operation response: {other:?}")),
|
||||
Err(error) => Err(error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_selection(&mut self, handles: &[Handle]) -> Result<(), String> {
|
||||
match self.request(PluginRequest::SetSelection { handles: handles.to_vec() }) {
|
||||
Ok(PluginResponse::SelectionResult(result)) => result,
|
||||
|
||||
@@ -105,6 +105,35 @@ mod tests {
|
||||
assert_eq!(decoded, changed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_operation_request_and_response_roundtrip() {
|
||||
use crate::host::{LayerConfig, TableOperation};
|
||||
for operation in [
|
||||
TableOperation::LayerCreate {
|
||||
config: LayerConfig {
|
||||
name: "Walls".into(),
|
||||
color: Some(acadrust::types::Color::Index(5)),
|
||||
frozen: Some(true),
|
||||
description: Some("d".into()),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
TableOperation::LayerModify { config: LayerConfig { name: "Walls".into(), off: Some(true), ..Default::default() } },
|
||||
TableOperation::LayerRename { from: "A".into(), to: "B".into() },
|
||||
TableOperation::LayerDelete { name: "A".into(), erase_objects: true },
|
||||
TableOperation::LayerSetCurrent { name: "A".into() },
|
||||
] {
|
||||
let bytes = bincode::serialize(&PluginRequest::TableOperation { operation: operation.clone() }).unwrap();
|
||||
assert!(matches!(bincode::deserialize::<PluginRequest>(&bytes).unwrap(),
|
||||
PluginRequest::TableOperation { operation: decoded } if decoded == operation));
|
||||
}
|
||||
for result in [Ok(acadrust::Handle::new(9)), Err("refused".to_owned())] {
|
||||
let bytes = bincode::serialize(&PluginResponse::TableResult(result.clone())).unwrap();
|
||||
assert!(matches!(bincode::deserialize::<PluginResponse>(&bytes).unwrap(),
|
||||
PluginResponse::TableResult(decoded) if decoded == result));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solid_operation_request_and_response_roundtrip() {
|
||||
use crate::host::{SolidOperation, SolidPrimitive};
|
||||
|
||||
Reference in new issue
Block a user