forked from 1b-agent/opencadstudio-upstream
Add kernel solid operations and complete Solid3D (create and rigid move)
An additive HostApi::solid_operation builds cadkernel primitives and applies rigid transforms, verifying each result lifts back losslessly and refusing a payload it cannot lift. Exposed as doc.solids in Python; Solid3D, Body and Region are mapped read-only with the opaque payload preserved. The lifecycle test passes through the real runner with analytic volumes and exact extents as oracles, DWG and DXF reopen with re-edit, and undo restores exactly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
07fe739571
commit
04cd9e462d
18 files changed
+717
-13
No files matched your search
@@ -2532,6 +2532,9 @@ mod tests {
|
||||
| "ViewBorder"
|
||||
| "Light"
|
||||
| "Ole2Frame"
|
||||
| "Solid3D"
|
||||
| "Body"
|
||||
| "Region"
|
||||
)
|
||||
{
|
||||
assert!(
|
||||
|
||||
@@ -60,6 +60,33 @@ pub enum LogLevel {
|
||||
Error,
|
||||
}
|
||||
|
||||
/// A solid primitive the host can build with its geometry kernel. Lengths are
|
||||
/// in drawing units; `center`/`origin` are world coordinates.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum SolidPrimitive {
|
||||
/// Axis-aligned box from its center and full extents.
|
||||
Box { center: [f64; 3], size: [f64; 3] },
|
||||
/// Right triangular prism from its minimum corner and extents.
|
||||
Wedge { origin: [f64; 3], size: [f64; 3] },
|
||||
/// Cylinder standing on the plane through `center`.
|
||||
Cylinder { center: [f64; 3], radius: f64, height: f64 },
|
||||
Sphere { center: [f64; 3], radius: f64 },
|
||||
Torus { center: [f64; 3], major: f64, minor: f64 },
|
||||
/// Regular pyramid with `sides` base edges.
|
||||
Pyramid { center: [f64; 3], radius: f64, height: f64, sides: u32 },
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
pub enum SolidOperation {
|
||||
/// Build a new solid on `layer` (layer `0` when `None`).
|
||||
Create { primitive: SolidPrimitive, layer: Option<String> },
|
||||
/// Apply a rigid transform, given as a column-major 4x4 matrix, to an
|
||||
/// existing solid. Scaling and shear are refused.
|
||||
Transform { handle: Handle, matrix: [f64; 16] },
|
||||
}
|
||||
|
||||
/// Value of a host-managed drafting or document setting exposed to plugins.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum HostSettingValue {
|
||||
@@ -620,6 +647,13 @@ pub trait HostApi {
|
||||
fn set_selection(&mut self, _handles: &[Handle]) -> Result<(), String> {
|
||||
Err("selection writes are not supported by this host".to_owned())
|
||||
}
|
||||
|
||||
/// Run a kernel-backed solid operation and return the created or updated
|
||||
/// entity's handle. Refused, without any change, when the kernel cannot
|
||||
/// perform it losslessly (API v7, additive).
|
||||
fn solid_operation(&mut self, _operation: SolidOperation) -> Result<Handle, String> {
|
||||
Err("solid operations are not supported by this host".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
/// Simplified, read-only entity kind exposed by [`DocumentReader`].
|
||||
|
||||
@@ -470,6 +470,18 @@ impl HostApi for PluginHostApi {
|
||||
}
|
||||
}
|
||||
|
||||
fn solid_operation(&mut self, operation: crate::host::SolidOperation) -> Result<Handle, String> {
|
||||
match self.client.request(PluginRequest::SolidOperation { operation }) {
|
||||
Ok(PluginResponse::SolidResult(result)) => {
|
||||
if result.is_ok() { self.document_cache = OnceCell::new(); }
|
||||
result
|
||||
}
|
||||
Ok(PluginResponse::Error(error)) => Err(error),
|
||||
Ok(other) => Err(format!("unexpected solid 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,
|
||||
|
||||
@@ -149,6 +149,8 @@ pub enum PluginRequest {
|
||||
/// V7: synchronous selection read/write for the dispatch tab.
|
||||
GetSelection,
|
||||
SetSelection { handles: Vec<Handle> },
|
||||
/// V7 (additive): kernel-backed solid create or transform.
|
||||
SolidOperation { operation: crate::host::SolidOperation },
|
||||
}
|
||||
|
||||
/// Responses the host sends back for `PluginRequest`.
|
||||
@@ -180,6 +182,7 @@ pub enum PluginResponse {
|
||||
EntityTransactionResult(Result<(), String>),
|
||||
Selection(Vec<Handle>),
|
||||
SelectionResult(Result<(), String>),
|
||||
SolidResult(Result<Handle, String>),
|
||||
}
|
||||
|
||||
/// Messages sent from the host to the plugin runner.
|
||||
|
||||
@@ -107,5 +107,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)),
|
||||
}
|
||||
}
|
||||
@@ -784,6 +784,18 @@ impl HostApi for V4PluginHostApi {
|
||||
}
|
||||
}
|
||||
|
||||
fn solid_operation(&mut self, operation: crate::host::SolidOperation) -> Result<Handle, String> {
|
||||
match self.request(PluginRequest::SolidOperation { operation }) {
|
||||
Ok(PluginResponse::SolidResult(result)) => {
|
||||
if result.is_ok() { self.document_cache = OnceCell::new(); }
|
||||
result
|
||||
}
|
||||
Ok(PluginResponse::Error(error)) => Err(error),
|
||||
Ok(other) => Err(format!("unexpected solid 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,27 @@ mod tests {
|
||||
assert_eq!(decoded, changed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solid_operation_request_and_response_roundtrip() {
|
||||
use crate::host::{SolidOperation, SolidPrimitive};
|
||||
for operation in [
|
||||
SolidOperation::Create {
|
||||
primitive: SolidPrimitive::Pyramid { center: [1.0, 2.0, 3.0], radius: 4.0, height: 5.0, sides: 6 },
|
||||
layer: Some("SOLIDS".into()),
|
||||
},
|
||||
SolidOperation::Transform { handle: acadrust::Handle::new(7), matrix: [1.0; 16] },
|
||||
] {
|
||||
let bytes = bincode::serialize(&PluginRequest::SolidOperation { operation: operation.clone() }).unwrap();
|
||||
assert!(matches!(bincode::deserialize::<PluginRequest>(&bytes).unwrap(),
|
||||
PluginRequest::SolidOperation { operation: decoded } if decoded == operation));
|
||||
}
|
||||
for result in [Ok(acadrust::Handle::new(9)), Err("refused".to_owned())] {
|
||||
let bytes = bincode::serialize(&PluginResponse::SolidResult(result.clone())).unwrap();
|
||||
assert!(matches!(bincode::deserialize::<PluginResponse>(&bytes).unwrap(),
|
||||
PluginResponse::SolidResult(decoded) if decoded == result));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selection_request_and_response_roundtrip() {
|
||||
let request = PluginRequest::SetSelection { handles: vec![acadrust::Handle::new(9)] };
|
||||
|
||||
Reference in new issue
Block a user