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 <noreply@anthropic.com>
This commit is contained in:
Felix RiestraandClaude Sonnet 5 committed 2026-09-20 16:43:25 +02:00
1 parent f1813b3ebe
commit e583da83db
10 files changed
+149 -11

No files matched your search

+1
View File
@@ -793,6 +793,7 @@ fn generate_entity_coverage(out_dir: &Path, registry: &TypeRegistry) {
| ("Helix", _) | ("Helix", _)
| ("RasterImage", _) | ("RasterImage", _)
| ("Wipeout", _) | ("Wipeout", _)
| ("Ole2Frame", _)
| ("Underlay", _) | ("Underlay", _)
| ("Viewport", _) | ("Viewport", _)
| ("ViewBorder", _) | ("ViewBorder", _)
@@ -486,6 +486,13 @@
"shadow_map_softness", "shadow_map_softness",
"photometric_mode", "photometric_mode",
"photometric_data" "photometric_data"
],
"Ole2Frame": [
"layer",
"upper_left_corner",
"lower_right_corner",
"lock_aspect",
"is_paper_space"
] ]
}, },
"readable": { "readable": {
@@ -590,6 +597,12 @@
], ],
"Light": [ "Light": [
"class_version" "class_version"
],
"Ole2Frame": [
"version",
"source_application",
"ole_object_type",
"dwg_mode"
] ]
}, },
"aliases": { "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 /// 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. /// host checks. Called by the Python add path before an entity enters the document.
#[cfg(feature = "host")] #[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()); return Err("Insert array counts must be greater than zero".into());
} }
} }
EntityType::Ole2Frame(value) => validate_ole_frame(None, value)?,
EntityType::AttributeDefinition(value) => { EntityType::AttributeDefinition(value) => {
finite_vector( finite_vector(
"AttributeDefinition.insertion_point", "AttributeDefinition.insertion_point",
@@ -1716,6 +1739,12 @@ pub fn validate_entity_mutation(
(EntityType::Viewport(old), EntityType::Viewport(new)) => validate_viewport(Some(old), new)?, (EntityType::Viewport(old), EntityType::Viewport(new)) => validate_viewport(Some(old), new)?,
(EntityType::ViewBorder(old), EntityType::ViewBorder(new)) => validate_view_border(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::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::MLine(old), EntityType::MLine(new)) => validate_mline(Some(old), new)?,
(EntityType::Leader(old), EntityType::Leader(new)) => validate_leader(Some(old), new)?, (EntityType::Leader(old), EntityType::Leader(new)) => validate_leader(Some(old), new)?,
(EntityType::AttributeDefinition(old), EntityType::AttributeDefinition(new)) => { (EntityType::AttributeDefinition(old), EntityType::AttributeDefinition(new)) => {
@@ -2502,6 +2531,7 @@ mod tests {
| "Viewport" | "Viewport"
| "ViewBorder" | "ViewBorder"
| "Light" | "Light"
| "Ole2Frame"
) )
{ {
assert!( assert!(
@@ -2787,6 +2817,10 @@ mod tests {
"Wipeout.clip_mode".to_owned(), "Wipeout.clip_mode".to_owned(),
"Wipeout.clip_type".to_owned(), "Wipeout.clip_type".to_owned(),
"Wipeout.clip_boundary_vertices".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.underlay_type".to_owned(),
"Underlay.definition_handle".to_owned(), "Underlay.definition_handle".to_owned(),
"Underlay.insertion_point".to_owned(), "Underlay.insertion_point".to_owned(),
+4 -3
View File
@@ -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 As of 20 September 2026, the working branch is `plugin/host-model-api` at
`a8dd3640` plus the Leader increment, published as draft PR `a8dd3640` plus the Leader increment, published as draft PR
[#1391](https://github.com/HakanSeven12/OpenCADStudio/pull/1391). Host API is [#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 v7. The Python feature has explicit writes for **38 of 43** canvas kinds: 36
are creatable (the legacy `Polyline` is update-only). This is **not** 37 completed 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. 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 AttributeDefinition passes its host and real IPC lifecycle but remains short of
`Complete` because the previously pinned CAD codec dropped optional ATTDEF `Complete` because the previously pinned CAD codec dropped optional ATTDEF
fields during DXF reads; revalidate that blocker against the current 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 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. 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. - 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. - 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. - 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.
+2 -2
View File
@@ -1,6 +1,6 @@
# OCS canvas kind coverage ledger # 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. 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. | | 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. | | 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. | | 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 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 passing commands, the OCS commit hash, and any manual GUI result. Host and
+2 -2
View File
@@ -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 catalog. All 43 canvas kinds also support nonempty `layer` changes through
the same undoable transaction; internal and opaque variants remain read-only the same undoable transaction; internal and opaque variants remain read-only
through the document model. The adapter accepts only `layer` patches for 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 still report `type_conversion_only`; broader CAD range and cross-property
checks remain outstanding. checks remain outstanding.
The host's `entity_snapshot` helper serializes any typed entity as a detached 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. token from another tab returns no result and does not consume it.
Python `doc.entities[handle]` returns a descriptor for every entity. For kinds 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 `layer` is editable on canvas kinds. `doc.coverage()` enumerates the
catalog; `doc.coverage("Line")` and `line.coverage` return one entry. For catalog; `doc.coverage("Line")` and `line.coverage` return one entry. For
covered kinds, the coverage object lists actual readable and editable covered kinds, the coverage object lists actual readable and editable
+1 -1
View File
@@ -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 traced field, including fields marked `unmapped`. Query it with
`doc.coverage()` or `doc.coverage("Line")`; each row includes source field, `doc.coverage()` or `doc.coverage("Line")`; each row includes source field,
type, access, and current validation status. Geometry edits remain limited to 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 For any entity, `entity.snapshot` returns a detached dictionary of its raw
CAD fields, including `snapshot["common"]` and kind-specific nested values. CAD fields, including `snapshot["common"]` and kind-specific nested values.
It can inspect an unmapped kind; editing the dictionary does It can inspect an unmapped kind; editing the dictionary does
+28 -1
View File
@@ -36,7 +36,8 @@
"Underlay", "Underlay",
"Viewport", "Viewport",
"ViewBorder", "ViewBorder",
"Light" "Light",
"Ole2Frame"
], ],
"manual_kinds": [ "manual_kinds": [
"Dimension" "Dimension"
@@ -780,6 +781,32 @@
"required": true "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
}
}
} }
} }
} }
+1 -1
View File
@@ -4780,7 +4780,7 @@ mod tests {
); );
} }
} }
assert_eq!(emitted.len(), 37); assert_eq!(emitted.len(), 38);
} }
#[cfg(feature = "experimental-host-model")] #[cfg(feature = "experimental-host-model")]
+63 -1
View File
@@ -3212,7 +3212,28 @@ mod tests {
.map(|(x, y, z)| acadrust::types::Vector3::new(x, y, z)); .map(|(x, y, z)| acadrust::types::Vector3::new(x, y, z));
host.add_entity(EntityType::Polyline(acadrust::entities::Polyline::from_points(points.to_vec()))); 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") { if update_only || create_script.contains("MAKEBLOCK") {
let next = host.document().next_handle(); let next = host.document().next_handle();
let (record_handle, block_handle, end_handle) = (Handle::new(next), Handle::new(next + 1), Handle::new(next + 2)); 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)); 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] #[test]
fn staged_python_point_pick_and_cancel_over_real_ipc() { fn staged_python_point_pick_and_cancel_over_real_ipc() {
let Some(plugin_path) = std::env::var_os("OCS_TEST_PYTHON_PLUGIN") else { let Some(plugin_path) = std::env::var_os("OCS_TEST_PYTHON_PLUGIN") else {