forked from 1b-agent/opencadstudio-upstream
Map Ole2Frame as update-only with the embedded object preserved
Frame corners and the aspect lock are scriptable; the opaque storage and envelope are excluded and refused on edit. The audit embeds a real PNG through OCS's own builder and proves it survives DWG and DXF byte-for-byte. Python creation stays a follow-up. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
1 parent
f1813b3ebe
commit
e583da83db
10 files changed
+149
-11
No files matched your search
@@ -793,6 +793,7 @@ fn generate_entity_coverage(out_dir: &Path, registry: &TypeRegistry) {
|
||||
| ("Helix", _)
|
||||
| ("RasterImage", _)
|
||||
| ("Wipeout", _)
|
||||
| ("Ole2Frame", _)
|
||||
| ("Underlay", _)
|
||||
| ("Viewport", _)
|
||||
| ("ViewBorder", _)
|
||||
|
||||
@@ -486,6 +486,13 @@
|
||||
"shadow_map_softness",
|
||||
"photometric_mode",
|
||||
"photometric_data"
|
||||
],
|
||||
"Ole2Frame": [
|
||||
"layer",
|
||||
"upper_left_corner",
|
||||
"lower_right_corner",
|
||||
"lock_aspect",
|
||||
"is_paper_space"
|
||||
]
|
||||
},
|
||||
"readable": {
|
||||
@@ -590,6 +597,12 @@
|
||||
],
|
||||
"Light": [
|
||||
"class_version"
|
||||
],
|
||||
"Ole2Frame": [
|
||||
"version",
|
||||
"source_application",
|
||||
"ole_object_type",
|
||||
"dwg_mode"
|
||||
]
|
||||
},
|
||||
"aliases": {
|
||||
|
||||
@@ -1223,6 +1223,28 @@ fn validate_basic_entity(
|
||||
}
|
||||
}
|
||||
|
||||
/// Ole2Frame: only the frame placement is scriptable; the embedded object
|
||||
/// storage is never rewritten.
|
||||
#[cfg(feature = "host")]
|
||||
fn validate_ole_frame(
|
||||
old: Option<&crate::host::acadrust::entities::Ole2Frame>,
|
||||
new: &crate::host::acadrust::entities::Ole2Frame,
|
||||
) -> Result<(), String> {
|
||||
if old == Some(new) {
|
||||
return Ok(());
|
||||
}
|
||||
finite_vector("Ole2Frame.upper_left_corner", &new.upper_left_corner)?;
|
||||
finite_vector("Ole2Frame.lower_right_corner", &new.lower_right_corner)?;
|
||||
let (a, b) = (new.upper_left_corner, new.lower_right_corner);
|
||||
if (a.x - b.x).abs() <= 1.0e-12 || (a.y - b.y).abs() <= 1.0e-12 {
|
||||
return Err("Ole2Frame corners must span a nonzero width and height".into());
|
||||
}
|
||||
if new.lock_aspect > 1 {
|
||||
return Err("Ole2Frame.lock_aspect must be 0 or 1".into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate newly created geometry for the kinds whose mapped fields have
|
||||
/// host checks. Called by the Python add path before an entity enters the document.
|
||||
#[cfg(feature = "host")]
|
||||
@@ -1348,6 +1370,7 @@ pub fn validate_new_canvas_entity(entity: &crate::host::EntityType) -> Result<()
|
||||
return Err("Insert array counts must be greater than zero".into());
|
||||
}
|
||||
}
|
||||
EntityType::Ole2Frame(value) => validate_ole_frame(None, value)?,
|
||||
EntityType::AttributeDefinition(value) => {
|
||||
finite_vector(
|
||||
"AttributeDefinition.insertion_point",
|
||||
@@ -1716,6 +1739,12 @@ pub fn validate_entity_mutation(
|
||||
(EntityType::Viewport(old), EntityType::Viewport(new)) => validate_viewport(Some(old), new)?,
|
||||
(EntityType::ViewBorder(old), EntityType::ViewBorder(new)) => validate_view_border(Some(old), new)?,
|
||||
(EntityType::Light(old), EntityType::Light(new)) => validate_light(Some(old), new)?,
|
||||
(EntityType::Ole2Frame(old), EntityType::Ole2Frame(new)) => {
|
||||
validate_ole_frame(Some(old), new)?;
|
||||
if old.storage != new.storage || old.envelope != new.envelope {
|
||||
return Err("Ole2Frame embedded storage cannot change".into());
|
||||
}
|
||||
}
|
||||
(EntityType::MLine(old), EntityType::MLine(new)) => validate_mline(Some(old), new)?,
|
||||
(EntityType::Leader(old), EntityType::Leader(new)) => validate_leader(Some(old), new)?,
|
||||
(EntityType::AttributeDefinition(old), EntityType::AttributeDefinition(new)) => {
|
||||
@@ -2502,6 +2531,7 @@ mod tests {
|
||||
| "Viewport"
|
||||
| "ViewBorder"
|
||||
| "Light"
|
||||
| "Ole2Frame"
|
||||
)
|
||||
{
|
||||
assert!(
|
||||
@@ -2787,6 +2817,10 @@ mod tests {
|
||||
"Wipeout.clip_mode".to_owned(),
|
||||
"Wipeout.clip_type".to_owned(),
|
||||
"Wipeout.clip_boundary_vertices".to_owned(),
|
||||
"Ole2Frame.upper_left_corner".to_owned(),
|
||||
"Ole2Frame.lower_right_corner".to_owned(),
|
||||
"Ole2Frame.lock_aspect".to_owned(),
|
||||
"Ole2Frame.is_paper_space".to_owned(),
|
||||
"Underlay.underlay_type".to_owned(),
|
||||
"Underlay.definition_handle".to_owned(),
|
||||
"Underlay.insertion_point".to_owned(),
|
||||
|
||||
@@ -7,13 +7,13 @@ Build a general Python document model over the OCS host API for all **43 canvas
|
||||
As of 20 September 2026, the working branch is `plugin/host-model-api` at
|
||||
`a8dd3640` plus the Leader increment, published as draft PR
|
||||
[#1391](https://github.com/HakanSeven12/OpenCADStudio/pull/1391). Host API is
|
||||
v7. The Python feature has explicit writes for **37 of 43** canvas kinds: 36
|
||||
are creatable (the legacy `Polyline` is update-only). This is **not** 37 completed
|
||||
v7. The Python feature has explicit writes for **38 of 43** canvas kinds: 36
|
||||
are creatable (the legacy `Polyline` and `Ole2Frame` are update-only). This is **not** 38 completed
|
||||
kinds: Tolerance, Shape, AttributeEntity, Hatch, MLine, Dimension, MultiLeader, Table, PolygonMesh, PolyfaceMesh, Mesh, Wipeout, Point, Line, Circle, Arc, Ellipse, LwPolyline, Spline, Text, MText, Ray, XLine, Solid and Face3D pass every completion gate; Insert now passes every gate too; the audited Polyline (update-only by decision), Polyline2D and Polyline3D pass every gate that their format allows.
|
||||
AttributeDefinition passes its host and real IPC lifecycle but remains short of
|
||||
`Complete` because the previously pinned CAD codec dropped optional ATTDEF
|
||||
fields during DXF reads; revalidate that blocker against the current
|
||||
`acadrust` revision `7ea4247`. Leader is mapped and integration-tested but blocked at W by three acadrust persistence gaps (see the ledger). Leader, Helix, Underlay, ViewBorder and Light are mapped and integration-tested but blocked at W by acadrust persistence gaps, and RasterImage and Viewport are held short of `Complete` by unverifiable or missing oracles (see the ledger). RasterImage is mapped and integration-tested, held short of `Complete` by unverifiable image-dictionary linkage. The other 6 canvas kinds (SectionSymbol, Region, Body, Solid3D, Surface, Ole2Frame) have only `layer` and are recorded as blocked
|
||||
`acadrust` revision `7ea4247`. Leader is mapped and integration-tested but blocked at W by three acadrust persistence gaps (see the ledger). Leader, Helix, Underlay, ViewBorder and Light are mapped and integration-tested but blocked at W by acadrust persistence gaps, and RasterImage and Viewport are held short of `Complete` by unverifiable or missing oracles (see the ledger). RasterImage is mapped and integration-tested, held short of `Complete` by unverifiable image-dictionary linkage. The other 5 canvas kinds (SectionSymbol, Region, Body, Solid3D, Surface) have only `layer` and are recorded as blocked
|
||||
writes through the document model. All 43 have raw typed snapshots. There are
|
||||
three internal records and two opaque fallbacks outside the 43 canvas kinds.
|
||||
|
||||
@@ -175,3 +175,4 @@ exact queue above and update statuses with evidence after each increment.
|
||||
- Audited `AttributeDefinition` against acadrust `5b682ed` with a full-field comparison through both formats. Everything passes in DWG. DXF keeps only tag, prompt, value, insertion point, height and rotation because the reader handles eight group codes; alignment, width factor, oblique angle, attribute flags, field length, generation flags, lock and text style reopen as defaults. The kind stays integration-tested and the gap is pinned by canary expectations; the fix belongs in cadcodec's `read_attdef`. The shared driver's block fixture now also serves `MAKEBLOCK` scripts.
|
||||
- Decided the two open audit questions. **Insert is creatable** by naming an existing block (existence, ordinary-block and no-cycle checks; attribute records stay with the AttributeEntity flow), which completes the kind. **Legacy Polyline is update-only** with a hint pointing scripts at Polyline2D and Polyline3D, because both formats convert it on save. The generator gained an `update_only_message` for such kinds, integer coordinates are accepted in Insert scales, and the driver gained `MAKEPOLYLINE` and `MAKEBLOCK` fixtures.
|
||||
- Wrote up the cadcodec DXF reader gaps as draft issues in [cadcodec-reader-gaps.md](cadcodec-reader-gaps.md) (nothing filed): ATTDEF, boolean groups read through `as_i16()`, UNDERLAY rotation units, LEADER, DRAWINGVIEW/SECTIONLINE dispatch and TABLE merged ranges. Verifying them corrected an earlier attribution: the Spline "DXF drops weights" finding was an OCS bug (the writer emits weights only for a rational spline), now fixed by setting `flags.rational` from the weights, so Spline is complete.
|
||||
- Mapped `Ole2Frame` as update-only (queue item 21, previously blocked). OCS already builds real embedded frames (`build_embedded_ole`), which supplied the fixture the blocker was waiting for. Frame corners and the aspect lock are scriptable; the opaque storage and envelope are excluded and refused on edit. The audit proves the embedded picture survives DWG and DXF unchanged. Creation from Python remains a follow-up. The remaining blocked kinds are SectionSymbol, Region, Body, Solid3D and Surface.
|
||||
@@ -1,6 +1,6 @@
|
||||
# OCS canvas kind coverage ledger
|
||||
|
||||
Use with [the completion plan](plugin-host-model-completion-plan.md). This ledger is current as of 20 September 2026: **37 of 43 canvas kinds have explicit geometry writes** (36 creatable, plus update-only legacy `Polyline`), **26 kinds are complete**, and 6 remain layer-only (one of them, SectionSymbol, blocked). `Mapped` means at least one non-layer field is exposed for Python writes; it does **not** mean the kind passes the completion gate. `Update-only` means Python creation is unavailable. `Layer-only` means raw inspection and canvas layer edits exist, but geometry is not mapped. Record actual evidence or a blocking issue before changing any status to `Complete`.
|
||||
Use with [the completion plan](plugin-host-model-completion-plan.md). This ledger is current as of 20 September 2026: **38 of 43 canvas kinds have explicit geometry writes** (36 creatable, plus update-only legacy `Polyline` and `Ole2Frame`), **26 kinds are complete**, and 5 remain layer-only and blocked (SectionSymbol, Region, Body, Solid3D, Surface). `Mapped` means at least one non-layer field is exposed for Python writes; it does **not** mean the kind passes the completion gate. `Update-only` means Python creation is unavailable. `Layer-only` means raw inspection and canvas layer edits exist, but geometry is not mapped. Record actual evidence or a blocking issue before changing any status to `Complete`.
|
||||
|
||||
Completion gate codes: **C** = create through Python document model; **R** = read; **E** = edit defining geometry and another property; **D** = delete; **U** = undo/redo and atomic failure; **I** = real IPC plus GUI/canvas pick or observable behavior; **W** = DWG and DXF save/reopen; **V** = invalid-input checks and untouched-field/reference preservation; **P** = build and load the in-tree bundled plugin with a manifest matching the host toolchain and acadrust source. A kind is `Complete` only when **C/R/E/D/U/I/W/V/P** all pass and its property catalog matches implementation. `Blocked` requires a specific dependency or failing fixture.
|
||||
|
||||
@@ -48,7 +48,7 @@ Completion gate codes: **C** = create through Python document model; **R** = rea
|
||||
| Body | **Blocked** | Not mapped. The entity is an opaque ACIS payload (`acis_data` SAT/SAB text or binary) plus derived `wires`, `silhouettes` and, for solids, a history record. A script can neither produce a valid body nor edit one, because every geometric change needs the CAD kernel to rewrite the payload, and the handover forbids deserializing and rewriting opaque ACIS data without a proven lossless path. The only lossless edit today is the layer change the document model already supports. Adding this kind needs a host-side, kernel-backed API (create a primitive, or apply a transform, then regenerate `acis_data` with its wires and silhouettes) with a DWG and DXF round-trip that preserves the payload and history byte-for-byte for untouched bodies. |
|
||||
| Solid3D | **Blocked** | Not mapped. The entity is an opaque ACIS payload (`acis_data` SAT/SAB text or binary) plus derived `wires`, `silhouettes` and, for solids, a history record. A script can neither produce a valid body nor edit one, because every geometric change needs the CAD kernel to rewrite the payload, and the handover forbids deserializing and rewriting opaque ACIS data without a proven lossless path. The only lossless edit today is the layer change the document model already supports. Adding this kind needs a host-side, kernel-backed API (create a primitive, or apply a transform, then regenerate `acis_data` with its wires and silhouettes) with a DWG and DXF round-trip that preserves the payload and history byte-for-byte for untouched bodies. |
|
||||
| Surface | **Blocked** | Not mapped. The entity is an opaque ACIS payload (`acis_data` SAT/SAB text or binary) plus derived `wires`, `silhouettes` and, for solids, a history record. A script can neither produce a valid body nor edit one, because every geometric change needs the CAD kernel to rewrite the payload, and the handover forbids deserializing and rewriting opaque ACIS data without a proven lossless path. The only lossless edit today is the layer change the document model already supports. Adding this kind needs a host-side, kernel-backed API (create a primitive, or apply a transform, then regenerate `acis_data` with its wires and silhouettes) with a DWG and DXF round-trip that preserves the payload and history byte-for-byte for untouched bodies. |
|
||||
| Ole2Frame | **Blocked** | Not mapped. A valid OLE frame carries a `StructuredStoragePayload` (a compound-file image of the embedded object) that a script cannot supply, so creation is impossible, and frame-geometry edits (`upper_left_corner`, `lower_right_corner`, `lock_aspect`) would be an update-only path that has no real embedded-object fixture to prove storage and aspect persistence through DWG and DXF. Adding it needs a fixture builder for a minimal valid OLE storage plus a proven round-trip of the payload; until then the kind keeps raw snapshot access and layer edits. |
|
||||
| Ole2Frame | **Update-only (all applicable gates pass)** | Writable: `upper_left_corner`, `lower_right_corner`, `lock_aspect`, `is_paper_space`. Read-only: `version`, `source_application`, `ole_object_type`, `dwg_mode`. The embedded object `storage` and the frame `envelope` are opaque, excluded from the Python form, refused by the mutation check (`Ole2Frame embedded storage cannot change`) and preserved untouched. Host validation requires finite corners spanning a nonzero width and height and `lock_aspect` of 0 or 1. Creation from a script is refused with an explanatory message, because a valid frame needs a real embedded object; the host's own `build_embedded_ole` builds one from an image. `audit_python_ole2frame_lifecycle_over_real_ipc` (shared driver; the fixture embeds a real 4x3 PNG through `add_embedded_image`) passes E/D/U/I/W/V/P: a corner and aspect-lock transaction, canvas geometry, rejection of a zero-size frame, a bad aspect lock, non-finite corners, a read-only `version` and an attempt to write `storage`, an atomic rollback, **DWG and DXF reopen with the 119-byte embedded picture byte-for-byte intact (extracted through `extract_presentation`)**, re-edit, and two-step undo/redo. Not `Complete` because Python cannot create a frame. A create path (`file_path` of an image, built with `build_embedded_ole`) would need a manual-kind converter like Dimension and is the natural follow-up. |
|
||||
|
||||
After each increment, update the row with test names, fixture paths, exact
|
||||
passing commands, the OCS commit hash, and any manual GUI result. Host and
|
||||
|
||||
@@ -35,7 +35,7 @@ attached attribute and sequence records. Those fields report `transaction_geomet
|
||||
catalog. All 43 canvas kinds also support nonempty `layer` changes through
|
||||
the same undoable transaction; internal and opaque variants remain read-only
|
||||
through the document model. The adapter accepts only `layer` patches for
|
||||
canvas kinds outside its 37 geometry converters. Other writable properties
|
||||
canvas kinds outside its 38 geometry converters. Other writable properties
|
||||
still report `type_conversion_only`; broader CAD range and cross-property
|
||||
checks remain outstanding.
|
||||
The host's `entity_snapshot` helper serializes any typed entity as a detached
|
||||
@@ -92,7 +92,7 @@ Input tokens are bound to the drawing tab that requested the pick. Polling a
|
||||
token from another tab returns no result and does not consume it.
|
||||
|
||||
Python `doc.entities[handle]` returns a descriptor for every entity. For kinds
|
||||
outside the 37-kind generated schema it contains only handle, kind, and layer;
|
||||
outside the 38-kind generated schema it contains only handle, kind, and layer;
|
||||
`layer` is editable on canvas kinds. `doc.coverage()` enumerates the
|
||||
catalog; `doc.coverage("Line")` and `line.coverage` return one entry. For
|
||||
covered kinds, the coverage object lists actual readable and editable
|
||||
|
||||
@@ -94,7 +94,7 @@ The host-owned catalog classifies all 48 entity variants and tracks every
|
||||
traced field, including fields marked `unmapped`. Query it with
|
||||
`doc.coverage()` or `doc.coverage("Line")`; each row includes source field,
|
||||
type, access, and current validation status. Geometry edits remain limited to
|
||||
the 37 mapped kinds.
|
||||
the 38 mapped kinds.
|
||||
For any entity, `entity.snapshot` returns a detached dictionary of its raw
|
||||
CAD fields, including `snapshot["common"]` and kind-specific nested values.
|
||||
It can inspect an unmapped kind; editing the dictionary does
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
"Underlay",
|
||||
"Viewport",
|
||||
"ViewBorder",
|
||||
"Light"
|
||||
"Light",
|
||||
"Ole2Frame"
|
||||
],
|
||||
"manual_kinds": [
|
||||
"Dimension"
|
||||
@@ -780,6 +781,32 @@
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"Ole2Frame": {
|
||||
"update_only": true,
|
||||
"update_only_message": "Ole2Frame cannot be created from a script: its embedded object storage is opaque; existing frames can be moved, resized and aspect-locked, and the stored object is preserved untouched",
|
||||
"fields": {
|
||||
"storage": {
|
||||
"exclude": true,
|
||||
"_comment": "Opaque compound-file payload; snapshot-readable and preserved untouched."
|
||||
},
|
||||
"envelope": {
|
||||
"exclude": true,
|
||||
"_comment": "Opaque frame envelope; preserved untouched."
|
||||
},
|
||||
"version": {
|
||||
"read_only": true
|
||||
},
|
||||
"source_application": {
|
||||
"read_only": true
|
||||
},
|
||||
"ole_object_type": {
|
||||
"read_only": true
|
||||
},
|
||||
"dwg_mode": {
|
||||
"read_only": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4780,7 +4780,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
assert_eq!(emitted.len(), 37);
|
||||
assert_eq!(emitted.len(), 38);
|
||||
}
|
||||
|
||||
#[cfg(feature = "experimental-host-model")]
|
||||
|
||||
+63
-1
@@ -3212,7 +3212,28 @@ mod tests {
|
||||
.map(|(x, y, z)| acadrust::types::Vector3::new(x, y, z));
|
||||
host.add_entity(EntityType::Polyline(acadrust::entities::Polyline::from_points(points.to_vec())));
|
||||
}
|
||||
let update_only = create_script.contains("MAKEINSERT") || make_polyline;
|
||||
let make_ole = create_script.contains("MAKEOLE");
|
||||
if make_ole {
|
||||
// An Ole2Frame is update-only: the host embeds a real picture.
|
||||
let mut png = std::io::Cursor::new(Vec::new());
|
||||
image::RgbaImage::from_pixel(4, 3, image::Rgba([10, 120, 200, 255]))
|
||||
.write_to(&mut png, image::ImageFormat::Png)
|
||||
.unwrap();
|
||||
let picture = crate::io::ole_embed::EmbeddedImage {
|
||||
bytes: png.into_inner(),
|
||||
pixel_width: 4,
|
||||
pixel_height: 3,
|
||||
name: "audit.png".into(),
|
||||
};
|
||||
crate::io::ole_embed::add_embedded_image(
|
||||
host.document_mut(),
|
||||
&picture,
|
||||
acadrust::types::Vector3::new(10.0, 10.0, 0.0),
|
||||
20.0,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let update_only = create_script.contains("MAKEINSERT") || make_polyline || make_ole;
|
||||
if update_only || create_script.contains("MAKEBLOCK") {
|
||||
let next = host.document().next_handle();
|
||||
let (record_handle, block_handle, end_handle) = (Handle::new(next), Handle::new(next + 1), Handle::new(next + 2));
|
||||
@@ -4387,6 +4408,47 @@ mod tests {
|
||||
assert!(last(&host).contains("Polyline2D or Polyline3D"), "{}", last(&host));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audit_python_ole2frame_lifecycle_over_real_ipc() {
|
||||
run_mesh_lifecycle(&MeshCase {
|
||||
create: "# MAKEOLE\n",
|
||||
edit: concat!(
|
||||
"e = doc.entities[HANDLE]\n",
|
||||
"with doc.transaction('Edit'):\n",
|
||||
" e.upper_left_corner = (12.0, 40.0, 0.0)\n",
|
||||
" e.lower_right_corner = (42.0, 10.0, 0.0)\n",
|
||||
" e.lock_aspect = 1\n",
|
||||
"doc.selection = [e]\n"),
|
||||
rejects: &[
|
||||
("'upper_left_corner':{'x':42.0,'y':40.0,'z':0.0}", "nonzero width and height"),
|
||||
("'lock_aspect':2", "0 or 1"),
|
||||
("'lower_right_corner':{'x':float('nan'),'y':0.0,'z':0.0}", "finite"),
|
||||
("'version':3", "read-only"),
|
||||
("'storage':{}", "outside the editable schema"),
|
||||
],
|
||||
is_kind: |entity| matches!(entity, EntityType::Ole2Frame(_)),
|
||||
digest: |entity| match entity {
|
||||
EntityType::Ole2Frame(v) => {
|
||||
let picture = match acadrust::entities::extract_presentation(&v.encoded_payload()) {
|
||||
Some(acadrust::entities::OlePresentation::Raster(bytes)) => bytes.len(),
|
||||
_ => 0,
|
||||
};
|
||||
format!("ul{:.1},{:.1} lr{:.1},{:.1} lock{} picture{}", v.upper_left_corner.x, v.upper_left_corner.y,
|
||||
v.lower_right_corner.x, v.lower_right_corner.y, v.lock_aspect, picture)
|
||||
}
|
||||
_ => "wrong kind".into(),
|
||||
},
|
||||
reedit: |entity| if let EntityType::Ole2Frame(v) = entity { v.lower_right_corner.x = 50.0; },
|
||||
expect_created: "ul10.0,25.0 lr30.0,10.0 lock0 picture119",
|
||||
expect_edited: "ul12.0,40.0 lr42.0,10.0 lock1 picture119",
|
||||
expect_reedited: "ul12.0,40.0 lr50.0,10.0 lock1 picture119",
|
||||
expect_edited_dxf: "",
|
||||
expect_reedited_dxf: "",
|
||||
expect_edited_dwg: "",
|
||||
expect_reedited_dwg: "",
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn staged_python_point_pick_and_cancel_over_real_ipc() {
|
||||
let Some(plugin_path) = std::env::var_os("OCS_TEST_PYTHON_PLUGIN") else {
|
||||
|
||||
Reference in new issue
Block a user