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 <noreply@anthropic.com>
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", _)
|
| ("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(),
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
@@ -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 {
|
||||||
|
|||||||
Reference in new issue
Block a user