forked from 1b-agent/opencadstudio-upstream
Complete Wipeout host model coverage
Maps the placement, display and clip-boundary fields with rectangular and polygonal clip validation. staged_python_wipeout_lifecycle_over_real_ipc passes the full gate including DWG and DXF reopen with re-edit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
eae74c24dd
commit
96e3b54f08
10 files changed
+142
-12
No files matched your search
@@ -792,6 +792,7 @@ fn generate_entity_coverage(out_dir: &Path, registry: &TypeRegistry) {
|
|||||||
| ("Mesh", _)
|
| ("Mesh", _)
|
||||||
| ("Helix", _)
|
| ("Helix", _)
|
||||||
| ("RasterImage", _)
|
| ("RasterImage", _)
|
||||||
|
| ("Wipeout", _)
|
||||||
| (
|
| (
|
||||||
"MLine",
|
"MLine",
|
||||||
"flags"
|
"flags"
|
||||||
|
|||||||
@@ -401,6 +401,21 @@
|
|||||||
"fade",
|
"fade",
|
||||||
"clip_boundary",
|
"clip_boundary",
|
||||||
"file_path"
|
"file_path"
|
||||||
|
],
|
||||||
|
"Wipeout": [
|
||||||
|
"layer",
|
||||||
|
"insertion_point",
|
||||||
|
"u_vector",
|
||||||
|
"v_vector",
|
||||||
|
"size",
|
||||||
|
"flags",
|
||||||
|
"clipping_enabled",
|
||||||
|
"brightness",
|
||||||
|
"contrast",
|
||||||
|
"fade",
|
||||||
|
"clip_mode",
|
||||||
|
"clip_type",
|
||||||
|
"clip_boundary_vertices"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"readable": {
|
"readable": {
|
||||||
@@ -467,6 +482,11 @@
|
|||||||
"definition_handle",
|
"definition_handle",
|
||||||
"definition_reactor_handle",
|
"definition_reactor_handle",
|
||||||
"size"
|
"size"
|
||||||
|
],
|
||||||
|
"Wipeout": [
|
||||||
|
"class_version",
|
||||||
|
"definition_handle",
|
||||||
|
"definition_reactor_handle"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"aliases": {
|
"aliases": {
|
||||||
|
|||||||
@@ -829,6 +829,51 @@ fn validate_raster_image(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wipeout: a masking region placed like an image; the clip boundary (in
|
||||||
|
/// normalized image coordinates) defines the masked area.
|
||||||
|
#[cfg(feature = "host")]
|
||||||
|
fn validate_wipeout(
|
||||||
|
old: Option<&crate::host::acadrust::entities::Wipeout>,
|
||||||
|
new: &crate::host::acadrust::entities::Wipeout,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
use crate::host::acadrust::entities::WipeoutClipType;
|
||||||
|
if old == Some(new) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
finite_vector("Wipeout.insertion_point", &new.insertion_point)?;
|
||||||
|
finite_vector("Wipeout.u_vector", &new.u_vector)?;
|
||||||
|
finite_vector("Wipeout.v_vector", &new.v_vector)?;
|
||||||
|
let cross = new.u_vector.cross(&new.v_vector);
|
||||||
|
if cross.x.hypot(cross.y).hypot(cross.z) <= 1.0e-12 {
|
||||||
|
return Err("Wipeout.u_vector and v_vector must be nonzero and not parallel".into());
|
||||||
|
}
|
||||||
|
if !new.size.x.is_finite() || !new.size.y.is_finite() || new.size.x <= 0.0 || new.size.y <= 0.0 {
|
||||||
|
return Err("Wipeout.size must be finite and greater than zero".into());
|
||||||
|
}
|
||||||
|
if new.brightness > 100 || new.contrast > 100 || new.fade > 100 {
|
||||||
|
return Err("Wipeout brightness, contrast and fade must be within 0..=100".into());
|
||||||
|
}
|
||||||
|
if new.flags.bits() & !0x0f != 0 {
|
||||||
|
return Err("Wipeout.flags has unknown bits".into());
|
||||||
|
}
|
||||||
|
for (index, vertex) in new.clip_boundary_vertices.iter().enumerate() {
|
||||||
|
if !vertex.x.is_finite() || !vertex.y.is_finite() {
|
||||||
|
return Err(format!("Wipeout.clip_boundary_vertices[{index}] must be finite"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let count = new.clip_boundary_vertices.len();
|
||||||
|
match new.clip_type {
|
||||||
|
WipeoutClipType::Rectangular if count != 2 => {
|
||||||
|
return Err("a rectangular Wipeout needs exactly 2 clip boundary vertices".into());
|
||||||
|
}
|
||||||
|
WipeoutClipType::Polygonal if count < 3 => {
|
||||||
|
return Err("a polygonal Wipeout needs at least 3 clip boundary vertices".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")]
|
||||||
@@ -930,6 +975,7 @@ pub fn validate_new_canvas_entity(entity: &crate::host::EntityType) -> Result<()
|
|||||||
EntityType::Mesh(value) => validate_mesh(None, value)?,
|
EntityType::Mesh(value) => validate_mesh(None, value)?,
|
||||||
EntityType::Helix(value) => validate_helix(None, value)?,
|
EntityType::Helix(value) => validate_helix(None, value)?,
|
||||||
EntityType::RasterImage(value) => validate_raster_image(None, value)?,
|
EntityType::RasterImage(value) => validate_raster_image(None, value)?,
|
||||||
|
EntityType::Wipeout(value) => validate_wipeout(None, value)?,
|
||||||
EntityType::AttributeDefinition(value) => {
|
EntityType::AttributeDefinition(value) => {
|
||||||
finite_vector(
|
finite_vector(
|
||||||
"AttributeDefinition.insertion_point",
|
"AttributeDefinition.insertion_point",
|
||||||
@@ -1292,6 +1338,7 @@ pub fn validate_entity_mutation(
|
|||||||
(EntityType::Mesh(old), EntityType::Mesh(new)) => validate_mesh(Some(old), new)?,
|
(EntityType::Mesh(old), EntityType::Mesh(new)) => validate_mesh(Some(old), new)?,
|
||||||
(EntityType::Helix(old), EntityType::Helix(new)) => validate_helix(Some(old), new)?,
|
(EntityType::Helix(old), EntityType::Helix(new)) => validate_helix(Some(old), new)?,
|
||||||
(EntityType::RasterImage(old), EntityType::RasterImage(new)) => validate_raster_image(Some(old), new)?,
|
(EntityType::RasterImage(old), EntityType::RasterImage(new)) => validate_raster_image(Some(old), new)?,
|
||||||
|
(EntityType::Wipeout(old), EntityType::Wipeout(new)) => validate_wipeout(Some(old), new)?,
|
||||||
(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)) => {
|
||||||
@@ -1981,6 +2028,7 @@ mod tests {
|
|||||||
| "Mesh"
|
| "Mesh"
|
||||||
| "Helix"
|
| "Helix"
|
||||||
| "RasterImage"
|
| "RasterImage"
|
||||||
|
| "Wipeout"
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
assert!(
|
assert!(
|
||||||
@@ -2254,6 +2302,18 @@ mod tests {
|
|||||||
"RasterImage.fade".to_owned(),
|
"RasterImage.fade".to_owned(),
|
||||||
"RasterImage.clip_boundary".to_owned(),
|
"RasterImage.clip_boundary".to_owned(),
|
||||||
"RasterImage.file_path".to_owned(),
|
"RasterImage.file_path".to_owned(),
|
||||||
|
"Wipeout.insertion_point".to_owned(),
|
||||||
|
"Wipeout.u_vector".to_owned(),
|
||||||
|
"Wipeout.v_vector".to_owned(),
|
||||||
|
"Wipeout.size".to_owned(),
|
||||||
|
"Wipeout.flags".to_owned(),
|
||||||
|
"Wipeout.clipping_enabled".to_owned(),
|
||||||
|
"Wipeout.brightness".to_owned(),
|
||||||
|
"Wipeout.contrast".to_owned(),
|
||||||
|
"Wipeout.fade".to_owned(),
|
||||||
|
"Wipeout.clip_mode".to_owned(),
|
||||||
|
"Wipeout.clip_type".to_owned(),
|
||||||
|
"Wipeout.clip_boundary_vertices".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 **32 of 43** canvas kinds: 31
|
v7. The Python feature has explicit writes for **33 of 43** canvas kinds: 32
|
||||||
are creatable and `Insert` remains update-only. This is **not** 30 completed
|
are creatable and `Insert` remains update-only. This is **not** 33 completed
|
||||||
kinds: Tolerance, Shape, AttributeEntity, Hatch, MLine, Dimension, MultiLeader, Table, PolygonMesh, PolyfaceMesh and Mesh pass every completion gate.
|
kinds: Tolerance, Shape, AttributeEntity, Hatch, MLine, Dimension, MultiLeader, Table, PolygonMesh, PolyfaceMesh, Mesh and Wipeout pass every completion gate.
|
||||||
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 and Helix are mapped and integration-tested but blocked at W by acadrust persistence gaps (see the ledger). RasterImage is mapped and integration-tested, held short of `Complete` by unverifiable image-dictionary linkage. The other 11 canvas kinds have only `layer`
|
`acadrust` revision `7ea4247`. Leader is mapped and integration-tested but blocked at W by three acadrust persistence gaps (see the ledger). Leader and Helix are mapped and integration-tested but blocked at W by acadrust persistence gaps (see the ledger). RasterImage is mapped and integration-tested, held short of `Complete` by unverifiable image-dictionary linkage. The other 10 canvas kinds have only `layer`
|
||||||
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.
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ The last five may require new engine capabilities rather than only Python conver
|
|||||||
|
|
||||||
## Handoff start point
|
## Handoff start point
|
||||||
|
|
||||||
Continue with **Wipeout** (queue item 11). Leader is done except for its W
|
Continue with **Underlay** (queue item 12). Leader is done except for its W
|
||||||
gate; when the engine writes DXF group 340, reads group 213 and the DWG
|
gate; when the engine writes DXF group 340, reads group 213 and the DWG
|
||||||
R2010+ text-size question is decided, flip the canary assertions in
|
R2010+ text-size question is decided, flip the canary assertions in
|
||||||
`staged_python_leader_lifecycle_over_real_ipc` and mark it `Complete`.
|
`staged_python_leader_lifecycle_over_real_ipc` and mark it `Complete`.
|
||||||
@@ -165,3 +165,4 @@ exact queue above and update statuses with evidence after each increment.
|
|||||||
- Completed `PolygonMesh`, `PolyfaceMesh` and `Mesh` (queue items 6-8) with one shared `run_mesh_lifecycle` test driver and per-kind host validation. The generator now omits a sub-record's own `EntityCommon` from the scripted form. All three pass the full gate including DWG and DXF reopen with re-edit. The queue now continues with `Helix`.
|
- Completed `PolygonMesh`, `PolyfaceMesh` and `Mesh` (queue items 6-8) with one shared `run_mesh_lifecycle` test driver and per-kind host validation. The generator now omits a sub-record's own `EntityCommon` from the scripted form. All three pass the full gate including DWG and DXF reopen with re-edit. The queue now continues with `Helix`.
|
||||||
- Mapped `Helix` with a host-rebuilt spline. `run_mesh_lifecycle` gained per-format DXF expectations so an engine gap is pinned by a canary instead of hidden. All gates pass except W for `handedness` in DXF (reader ignores boolean group 290). The queue now continues with `RasterImage`.
|
- Mapped `Helix` with a host-rebuilt spline. `run_mesh_lifecycle` gained per-format DXF expectations so an engine gap is pinned by a canary instead of hidden. All gates pass except W for `handedness` in DXF (reader ignores boolean group 290). The queue now continues with `RasterImage`.
|
||||||
- Mapped `RasterImage`. The host reads the image file, creates and links the `ImageDefinition`, and derives `size` and the default clip boundary. The shared lifecycle driver passes every gate in OCS with a real PNG; `ACAD_IMAGE_DICT`/reactor creation is not implemented, so the kind stays integration-tested. The queue now continues with `Wipeout`.
|
- Mapped `RasterImage`. The host reads the image file, creates and links the `ImageDefinition`, and derives `size` and the default clip boundary. The shared lifecycle driver passes every gate in OCS with a real PNG; `ACAD_IMAGE_DICT`/reactor creation is not implemented, so the kind stays integration-tested. The queue now continues with `Wipeout`.
|
||||||
|
- Completed `Wipeout` (queue item 11) with rectangular and polygonal clip masks through the shared lifecycle driver; scripted creation matches OCS's native command. The queue now continues with `Underlay`.
|
||||||
@@ -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: **32 of 43 canvas kinds have explicit geometry writes** (31 creatable, plus update-only `Insert`), **11 kinds are complete**, and 11 remain layer-only. `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: **33 of 43 canvas kinds have explicit geometry writes** (32 creatable, plus update-only `Insert`), **12 kinds are complete**, and 10 remain layer-only. `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.
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ Completion gate codes: **C** = create through Python document model; **R** = rea
|
|||||||
| Mesh | **Complete** | Writable: `blend_crease`, `subdivision_level`, `vertices`, `faces`, `edges` (start, end, optional crease), `override_option`; `version` is read-only. Host validation requires at least 3 finite vertices, faces of at least 3 in-range indices, edges with distinct in-range endpoints and a finite non-negative crease, and a non-negative subdivision level, so out-of-range topology is rejected before mutation. `staged_python_mesh_lifecycle_over_real_ipc` passes the full gate: a quad with a creased edge, an added vertex and triangle plus subdivision level 1 in one transaction, canvas wires, seven rejection cases plus an atomic rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. |
|
| Mesh | **Complete** | Writable: `blend_crease`, `subdivision_level`, `vertices`, `faces`, `edges` (start, end, optional crease), `override_option`; `version` is read-only. Host validation requires at least 3 finite vertices, faces of at least 3 in-range indices, edges with distinct in-range endpoints and a finite non-negative crease, and a non-negative subdivision level, so out-of-range topology is rejected before mutation. `staged_python_mesh_lifecycle_over_real_ipc` passes the full gate: a quad with a creased edge, an added vertex and triangle plus subdivision level 1 in one transaction, canvas wires, seven rejection cases plus an atomic rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. |
|
||||||
| Helix | **Integration-tested / blocked at W by acadrust (DXF handedness)** | Writable generating parameters: `axis_base_point`, `start_point`, `axis_vector`, `radius` (the top radius; the base radius is the start point's distance from the axis), `turns`, `turn_height`, `handedness`, `constraint`. Read-only: `spline`, `major_version`, `maintenance_version`. The host rebuilds the derived NURBS spline with OCS's own helix builder on every scripted create or parameter edit (`normalize_scripted_helix`), so the spline always matches the parameters. Host validation requires finite positive radius, turns and turn height, a nonzero axis and a start point off the axis. `staged_python_helix_lifecycle_over_real_ipc` (shared `run_mesh_lifecycle`) passes C/R/E/D/U/I/V/P: creation with a derived spline ending at z = turns × turn height, a turns and handedness edit that regenerates it, canvas wires, seven rejection cases plus an atomic rollback, DWG reopen and re-edit, DXF reopen and re-edit, and three-step undo/redo. **W blocker (engine, pinned by a canary assertion):** the DXF reader parses boolean group 290 as an `i16` and never applies it, so a clockwise helix reopens counter-clockwise from DXF while DWG keeps it. Not `Complete` until the reader is fixed. |
|
| Helix | **Integration-tested / blocked at W by acadrust (DXF handedness)** | Writable generating parameters: `axis_base_point`, `start_point`, `axis_vector`, `radius` (the top radius; the base radius is the start point's distance from the axis), `turns`, `turn_height`, `handedness`, `constraint`. Read-only: `spline`, `major_version`, `maintenance_version`. The host rebuilds the derived NURBS spline with OCS's own helix builder on every scripted create or parameter edit (`normalize_scripted_helix`), so the spline always matches the parameters. Host validation requires finite positive radius, turns and turn height, a nonzero axis and a start point off the axis. `staged_python_helix_lifecycle_over_real_ipc` (shared `run_mesh_lifecycle`) passes C/R/E/D/U/I/V/P: creation with a derived spline ending at z = turns × turn height, a turns and handedness edit that regenerates it, canvas wires, seven rejection cases plus an atomic rollback, DWG reopen and re-edit, DXF reopen and re-edit, and three-step undo/redo. **W blocker (engine, pinned by a canary assertion):** the DXF reader parses boolean group 290 as an `i16` and never applies it, so a clockwise helix reopens counter-clockwise from DXF while DWG keeps it. Not `Complete` until the reader is fixed. |
|
||||||
| RasterImage | **Integration-tested** | Writable: `file_path` (creation only; a later change is rejected because the image definition owns it), `insertion_point`, `u_vector`, `v_vector` (world units per pixel), `flags`, `clipping_enabled`, `brightness`, `contrast`, `fade`, `clip_boundary`. Read-only: `size` (pixel size, taken from the file), `class_version`, `definition_handle`, `definition_reactor_handle`. Creating from a script requires a readable image file: the host reads its pixel size, creates the `ImageDefinition` object, links it, and sets `size` and a whole-image clip boundary from the file. Host validation requires finite, non-parallel `u`/`v` vectors, brightness/contrast/fade within 0..=100, known display flags, finite clip vertices, at least 2 clip vertices when clipping is enabled, and an existing definition. `staged_python_raster_image_lifecycle_over_real_ipc` (shared `run_mesh_lifecycle`, real PNG fixture) passes C/R/E/D/U/I/W/V/P inside OCS: creation with a host-made definition, placement/scale/brightness/contrast/fade and clip edit in one transaction, canvas wires, eight rejection cases plus an atomic rollback, DWG and DXF reopen with the definition link and file path intact, re-edit, and three-step undo/redo. **Why not `Complete`:** the host does not create an `ACAD_IMAGE_DICT` entry or `IMAGEDEF_REACTOR` for a scripted image, so other CAD applications may need to repair the linkage; that cannot be verified here. Editing an existing definition (file path, resolution) is out of scope. |
|
| RasterImage | **Integration-tested** | Writable: `file_path` (creation only; a later change is rejected because the image definition owns it), `insertion_point`, `u_vector`, `v_vector` (world units per pixel), `flags`, `clipping_enabled`, `brightness`, `contrast`, `fade`, `clip_boundary`. Read-only: `size` (pixel size, taken from the file), `class_version`, `definition_handle`, `definition_reactor_handle`. Creating from a script requires a readable image file: the host reads its pixel size, creates the `ImageDefinition` object, links it, and sets `size` and a whole-image clip boundary from the file. Host validation requires finite, non-parallel `u`/`v` vectors, brightness/contrast/fade within 0..=100, known display flags, finite clip vertices, at least 2 clip vertices when clipping is enabled, and an existing definition. `staged_python_raster_image_lifecycle_over_real_ipc` (shared `run_mesh_lifecycle`, real PNG fixture) passes C/R/E/D/U/I/W/V/P inside OCS: creation with a host-made definition, placement/scale/brightness/contrast/fade and clip edit in one transaction, canvas wires, eight rejection cases plus an atomic rollback, DWG and DXF reopen with the definition link and file path intact, re-edit, and three-step undo/redo. **Why not `Complete`:** the host does not create an `ACAD_IMAGE_DICT` entry or `IMAGEDEF_REACTOR` for a scripted image, so other CAD applications may need to repair the linkage; that cannot be verified here. Editing an existing definition (file path, resolution) is out of scope. |
|
||||||
| Wipeout | Layer-only | Requires clip and image-definition fixture. |
|
| Wipeout | **Complete** | Writable: `insertion_point`, `u_vector`, `v_vector` (the region's world extent), `size`, `flags`, `clipping_enabled`, `brightness`, `contrast`, `fade`, `clip_mode`, `clip_type` and `clip_boundary_vertices` (normalized image coordinates, -0.5..0.5). Read-only: `class_version`, `definition_handle`, `definition_reactor_handle`. Scripted creation matches OCS's native WIPEOUT command, which also leaves the definition link unset. Host validation requires finite non-parallel `u`/`v` vectors, a positive `size`, brightness/contrast/fade within 0..=100, known flags, finite clip vertices, exactly 2 vertices for a rectangular clip and at least 3 for a polygonal one. `staged_python_wipeout_lifecycle_over_real_ipc` (shared `run_mesh_lifecycle`) passes C/R/E/D/U/I/W/V/P: rectangular creation, a switch to a three-vertex polygonal inside-clip with a move in one transaction, canvas wires, eight rejection cases plus an atomic rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. |
|
||||||
| Underlay | Layer-only | Requires external definition/file fixture. |
|
| Underlay | Layer-only | Requires external definition/file fixture. |
|
||||||
| Viewport | Layer-only | Requires layout and view-state fixture. |
|
| Viewport | Layer-only | Requires layout and view-state fixture. |
|
||||||
| ViewBorder | Layer-only | Requires linked view records. |
|
| ViewBorder | Layer-only | Requires linked view records. |
|
||||||
|
|||||||
@@ -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 32 geometry converters. Other writable properties
|
canvas kinds outside its 33 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
|
||||||
@@ -91,7 +91,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 32-kind generated schema it contains only handle, kind, and layer;
|
outside the 33-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 32 mapped kinds.
|
the 33 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
|
||||||
|
|||||||
@@ -31,7 +31,8 @@
|
|||||||
"PolyfaceMesh",
|
"PolyfaceMesh",
|
||||||
"Mesh",
|
"Mesh",
|
||||||
"Helix",
|
"Helix",
|
||||||
"RasterImage"
|
"RasterImage",
|
||||||
|
"Wipeout"
|
||||||
],
|
],
|
||||||
"manual_kinds": ["Dimension"],
|
"manual_kinds": ["Dimension"],
|
||||||
"base_fields": ["handle", "layer", "owner_handle"],
|
"base_fields": ["handle", "layer", "owner_handle"],
|
||||||
@@ -351,6 +352,14 @@
|
|||||||
"definition_handle": { "read_only": true },
|
"definition_handle": { "read_only": true },
|
||||||
"definition_reactor_handle": { "read_only": true }
|
"definition_reactor_handle": { "read_only": true }
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Wipeout": {
|
||||||
|
"fields": {
|
||||||
|
"insertion_point": { "required": true },
|
||||||
|
"class_version": { "read_only": true },
|
||||||
|
"definition_handle": { "read_only": true },
|
||||||
|
"definition_reactor_handle": { "read_only": true }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4756,7 +4756,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert_eq!(emitted.len(), 32);
|
assert_eq!(emitted.len(), 33);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "experimental-host-model")]
|
#[cfg(feature = "experimental-host-model")]
|
||||||
|
|||||||
@@ -3493,6 +3493,45 @@ mod tests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn staged_python_wipeout_lifecycle_over_real_ipc() {
|
||||||
|
run_mesh_lifecycle(&MeshCase {
|
||||||
|
create: concat!(
|
||||||
|
"doc.create_entity('Wipeout', insertion_point=P(0.0, 0.0, 0.0),\n",
|
||||||
|
" u_vector=P(10.0, 0.0, 0.0), v_vector=P(0.0, 6.0, 0.0))\n"),
|
||||||
|
edit: concat!(
|
||||||
|
"w = doc.entities[HANDLE]\n",
|
||||||
|
"with doc.transaction('Edit wipeout'):\n",
|
||||||
|
" w.clip_type = 'Polygonal'\n",
|
||||||
|
" w.clip_boundary_vertices = [{'x': -0.5, 'y': -0.5}, {'x': 0.5, 'y': -0.5}, {'x': 0.0, 'y': 0.5}]\n",
|
||||||
|
" w.insertion_point = (2.0, 3.0, 0.0)\n",
|
||||||
|
" w.clip_mode = 'Inside'\n",
|
||||||
|
"doc.selection = [w]\n"),
|
||||||
|
rejects: &[
|
||||||
|
("'clip_boundary_vertices':[{'x':0.0,'y':0.0}]", "at least 3"),
|
||||||
|
("'clip_type':'Rectangular'", "exactly 2"),
|
||||||
|
("'u_vector':{'x':0.0,'y':0.0,'z':0.0}", "not parallel"),
|
||||||
|
("'brightness':200", "within 0..=100"),
|
||||||
|
("'size':{'x':0.0,'y':1.0}", "greater than zero"),
|
||||||
|
("'insertion_point':{'x':float('nan'),'y':0.0,'z':0.0}", "finite"),
|
||||||
|
("'definition_handle':5", "read-only"),
|
||||||
|
("'clip_type':'Circle'", "unsupported"),
|
||||||
|
],
|
||||||
|
is_kind: |entity| matches!(entity, EntityType::Wipeout(_)),
|
||||||
|
digest: |entity| match entity {
|
||||||
|
EntityType::Wipeout(w) => format!("{:?} {:?} n{} at{:.1},{:.1} u{:.1} v{:.1}", w.clip_type, w.clip_mode,
|
||||||
|
w.clip_boundary_vertices.len(), w.insertion_point.x, w.insertion_point.y, w.u_vector.x, w.v_vector.y),
|
||||||
|
_ => "wrong kind".into(),
|
||||||
|
},
|
||||||
|
reedit: |entity| if let EntityType::Wipeout(w) = entity { w.u_vector.x = 12.0; },
|
||||||
|
expect_created: "Rectangular Outside n2 at0.0,0.0 u10.0 v6.0",
|
||||||
|
expect_edited: "Polygonal Inside n3 at2.0,3.0 u10.0 v6.0",
|
||||||
|
expect_reedited: "Polygonal Inside n3 at2.0,3.0 u12.0 v6.0",
|
||||||
|
expect_edited_dxf: "",
|
||||||
|
expect_reedited_dxf: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[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