An additive HostApi::solid_operation builds cadkernel primitives and applies rigid transforms, verifying each result lifts back losslessly and refusing a payload it cannot lift. Exposed as doc.solids in Python; Solid3D, Body and Region are mapped read-only with the opaque payload preserved. The lifecycle test passes through the real runner with analytic volumes and exact extents as oracles, DWG and DXF reopen with re-edit, and undo restores exactly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
45 KiB
OCS canvas kind coverage ledger
Use with the completion plan. This ledger is current as of 20 September 2026: 41 of 43 canvas kinds have explicit geometry writes (37 creatable, plus update-only legacy Polyline, Ole2Frame, Body and Region), 27 kinds are complete, and 2 remain layer-only (SectionSymbol blocked, Surface unblocked by design). 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.
| Kind | Baseline | Gate evidence / blocker |
|---|---|---|
| Point | Complete | Exercised: location, thickness. Rejects non-finite location, thickness and axis angle and a zero normal, which the audit found were previously accepted (thickness and normal) or unchecked at creation. audit_python_point_lifecycle_over_real_ipc (shared run_mesh_lifecycle, real runner) passes C/R/E/D/U/I/W/V/P: creation, a two-or-more-property transaction edit with the entity's common data, identity and selection preserved, canvas wires, invalid-input rejection with an atomic multi-entity rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. The audit added the shared validate_basic_entity (creation must be valid; an edit is refused only for problems it introduces, so already-invalid legacy values stay editable). |
| Line | Complete | Exercised: end, thickness. Existing endpoint, undo, selection and pick coverage plus the second-property edit the earlier row asked for. Rejects non-finite endpoints and thickness and a zero normal (thickness was previously accepted). audit_python_line_lifecycle_over_real_ipc (shared run_mesh_lifecycle, real runner) passes C/R/E/D/U/I/W/V/P: creation, a two-or-more-property transaction edit with the entity's common data, identity and selection preserved, canvas wires, invalid-input rejection with an atomic multi-entity rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. The audit added the shared validate_basic_entity (creation must be valid; an edit is refused only for problems it introduces, so already-invalid legacy values stay editable). |
| Circle | Complete | Exercised: radius, center. Rejects a non-positive or non-finite radius, non-finite center and thickness, and a zero normal. audit_python_circle_lifecycle_over_real_ipc (shared run_mesh_lifecycle, real runner) passes C/R/E/D/U/I/W/V/P: creation, a two-or-more-property transaction edit with the entity's common data, identity and selection preserved, canvas wires, invalid-input rejection with an atomic multi-entity rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. The audit added the shared validate_basic_entity (creation must be valid; an edit is refused only for problems it introduces, so already-invalid legacy values stay editable). |
| Arc | Complete | Exercised: end_angle, radius. Rejects a non-positive radius, non-finite angles (previously accepted), center and thickness, and a zero normal. audit_python_arc_lifecycle_over_real_ipc (shared run_mesh_lifecycle, real runner) passes C/R/E/D/U/I/W/V/P: creation, a two-or-more-property transaction edit with the entity's common data, identity and selection preserved, canvas wires, invalid-input rejection with an atomic multi-entity rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. The audit added the shared validate_basic_entity (creation must be valid; an edit is refused only for problems it introduces, so already-invalid legacy values stay editable). |
| Ellipse | Complete | Exercised: minor_axis_ratio, end_parameter. The audit found Ellipse had no validation at all; it now requires a nonzero major axis, a ratio within (0, 1], finite parameters and a nonzero normal. audit_python_ellipse_lifecycle_over_real_ipc (shared run_mesh_lifecycle, real runner) passes C/R/E/D/U/I/W/V/P: creation, a two-or-more-property transaction edit with the entity's common data, identity and selection preserved, canvas wires, invalid-input rejection with an atomic multi-entity rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. The audit added the shared validate_basic_entity (creation must be valid; an edit is refused only for problems it introduces, so already-invalid legacy values stay editable). |
| Polyline | Update-only (decision) | Decision (2026-09-20): the legacy Polyline (3-D vertices) is not creatable from Python, because both file formats save it as Polyline3D and it would silently change kind on the first save. Creating one now fails with a message that names Polyline2D and Polyline3D. Existing Polyline entities remain editable: audit_python_polyline_lifecycle_over_real_ipc (host-made fixture) passes E/D/U/I/V with vertex and closed edits, canvas wires, rejection of short and non-finite vertex lists, an atomic rollback and three-step undo/redo, and pins the reopen behavior (both DWG and DXF return a Polyline3D with the vertices and closure intact). Scripts should create Polyline2D or Polyline3D. |
| Polyline2D | Integration-tested / kind changes on DXF save | Exercised: vertices, closed, thickness. Passes every gate in DWG. DXF reopens a 2-D polyline as an LwPolyline (vertices, closure and width survive, but the kind changes and thickness is not part of that record's digest here); pinned by a canary expectation. audit_python_polyline2d_lifecycle_over_real_ipc (shared run_mesh_lifecycle, real runner) passes C/R/E/D/U/I/W/V/P: creation, a two-or-more-property transaction edit with the entity's common data, identity and selection preserved, canvas wires, invalid-input rejection with an atomic multi-entity rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. The audit added the shared validate_basic_entity (creation must be valid; an edit is refused only for problems it introduces, so already-invalid legacy values stay editable). |
| Polyline3D | Integration-tested / blocked at W by the DWG format (elevation) | Exercised: vertices, elevation. Passes every gate in DXF; DWG does not store a 3-D polyline's elevation, so it reopens as 0 (pinned by a canary). Vertices persist. audit_python_polyline3d_lifecycle_over_real_ipc (shared run_mesh_lifecycle, real runner) passes C/R/E/D/U/I/W/V/P: creation, a two-or-more-property transaction edit with the entity's common data, identity and selection preserved, canvas wires, invalid-input rejection with an atomic multi-entity rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. The audit added the shared validate_basic_entity (creation must be valid; an edit is refused only for problems it introduces, so already-invalid legacy values stay editable). |
| LwPolyline | Complete | Exercised: vertices (with a bulge), is_closed, constant_width. Rejects fewer than 2 vertices, non-finite locations, bulges and widths, a negative constant width and a zero normal. audit_python_lwpolyline_lifecycle_over_real_ipc (shared run_mesh_lifecycle, real runner) passes C/R/E/D/U/I/W/V/P: creation, a two-or-more-property transaction edit with the entity's common data, identity and selection preserved, canvas wires, invalid-input rejection with an atomic multi-entity rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. The audit added the shared validate_basic_entity (creation must be valid; an edit is refused only for problems it introduces, so already-invalid legacy values stay editable). |
| Spline | Complete | Exercised: control_points, weights. Validation covers degree, control-point count, knot count and order, weights and finite points. The host sets flags.rational whenever weights are present, because the DXF writer emits group 41 only for a rational spline; the audit first showed weights vanishing in DXF and traced it to that missing flag (an OCS bug, not a cadcodec one), now fixed. audit_python_spline_lifecycle_over_real_ipc (shared run_mesh_lifecycle, real runner) passes C/R/E/D/U/I/W/V/P with DWG and DXF reopen, re-edit, atomic rollback and three-step undo/redo. |
| Text | Complete | Exercised: text, rotation, height. Rejects a non-positive height (previously accepted), a non-positive width factor, non-finite placement and angles, and a zero normal. Style and alignment fields are mapped but not individually exercised. audit_python_text_lifecycle_over_real_ipc (shared run_mesh_lifecycle, real runner) passes C/R/E/D/U/I/W/V/P: creation, a two-or-more-property transaction edit with the entity's common data, identity and selection preserved, canvas wires, invalid-input rejection with an atomic multi-entity rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. The audit added the shared validate_basic_entity (creation must be valid; an edit is refused only for problems it introduces, so already-invalid legacy values stay editable). |
| MText | Complete | Exercised: text, rectangle_width, rotation. Rejects a non-positive height (previously accepted), a negative width, non-finite placement, a non-positive line-spacing factor and a zero normal. Attachment, columns and background fields are mapped but not individually exercised. audit_python_mtext_lifecycle_over_real_ipc (shared run_mesh_lifecycle, real runner) passes C/R/E/D/U/I/W/V/P: creation, a two-or-more-property transaction edit with the entity's common data, identity and selection preserved, canvas wires, invalid-input rejection with an atomic multi-entity rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. The audit added the shared validate_basic_entity (creation must be valid; an edit is refused only for problems it introduces, so already-invalid legacy values stay editable). |
| Ray | Complete | Exercised: base_point, direction. Unit-direction and finite-point validation already existed; the audit added the full DWG and DXF reopen and re-edit gate. audit_python_ray_lifecycle_over_real_ipc (shared run_mesh_lifecycle, real runner) passes C/R/E/D/U/I/W/V/P: creation, a two-or-more-property transaction edit with the entity's common data, identity and selection preserved, canvas wires, invalid-input rejection with an atomic multi-entity rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. The audit added the shared validate_basic_entity (creation must be valid; an edit is refused only for problems it introduces, so already-invalid legacy values stay editable). |
| XLine | Complete | Exercised: base_point, direction. Same as Ray. audit_python_xline_lifecycle_over_real_ipc (shared run_mesh_lifecycle, real runner) passes C/R/E/D/U/I/W/V/P: creation, a two-or-more-property transaction edit with the entity's common data, identity and selection preserved, canvas wires, invalid-input rejection with an atomic multi-entity rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. The audit added the shared validate_basic_entity (creation must be valid; an edit is refused only for problems it introduces, so already-invalid legacy values stay editable). |
| Solid | Complete | Exercised: fourth_corner, thickness. Finite corners, thickness and normal validation already existed; the audit added the full gate. audit_python_solid_lifecycle_over_real_ipc (shared run_mesh_lifecycle, real runner) passes C/R/E/D/U/I/W/V/P: creation, a two-or-more-property transaction edit with the entity's common data, identity and selection preserved, canvas wires, invalid-input rejection with an atomic multi-entity rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. The audit added the shared validate_basic_entity (creation must be valid; an edit is refused only for problems it introduces, so already-invalid legacy values stay editable). |
| Face3D | Complete | Exercised: third_corner, invisible_edges. Finite corners and known edge bits validation already existed; the audit added the full gate. audit_python_face3d_lifecycle_over_real_ipc (shared run_mesh_lifecycle, real runner) passes C/R/E/D/U/I/W/V/P: creation, a two-or-more-property transaction edit with the entity's common data, identity and selection preserved, canvas wires, invalid-input rejection with an atomic multi-entity rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. The audit added the shared validate_basic_entity (creation must be valid; an edit is refused only for problems it introduces, so already-invalid legacy values stay editable). |
| Insert | Complete | Decision (2026-09-20): Insert is creatable. A script names an existing block with block_name and a placement; the host does not generate attribute records, because attributes stay with the AttributeEntity flow that already creates them from the block's ATTDEFs. Writable: block_name (fixed after creation; the mutation check refuses a different block), insert_point, x_scale, y_scale, z_scale, rotation, normal, column_count, row_count, column_spacing, row_spacing. Creation and rename rules: the block must exist, must not be model or paper space, and must not make a block contain itself directly or through nested inserts; scales must be finite and nonzero, array counts positive, and the name non-empty. An edit that keeps the block name is exempt from the block checks so a legacy insert of a missing block stays editable. audit_python_insert_lifecycle_over_real_ipc (shared driver, host-made block) passes C/R/E/D/U/I/W/V/P: creation by block name, a six-property transaction edit, canvas wires, rejection of zero scale, non-finite rotation and point, zero array count, zero normal and an unknown block, an atomic rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. audit_python_insert_creation_rules_over_real_ipc covers unknown block, model-space block, bad scale, zero count, empty name, direct self-insertion and indirect nesting cycles. Not covered: MINSERT (dwg_minsert) and attached-attribute creation, which stay excluded and preserved. |
| Tolerance | Complete | staged_python_tolerance_lifecycle_over_real_ipc passes C/R/E/D/U/I/W/V/P with the staged API v7 plugin: high-level create/read/transaction edit/delete; insertion, direction, and text edits; live canvas selection; grouped undo/redo; invalid direction and missing style atomic rollback; exact untouched common/style/private-field preservation; and edited DWG plus DXF reopen. Host-owned coverage exposes placement, direction, normal, text, style name, height and gap; style handle is read-only and the undocumented DWG short remains unmapped. tolerance_geometry_and_style_references_are_validated and the existing Tolerance tessellation tests cover reference and rendered-frame behavior. |
| Shape | Complete | staged_python_shape_lifecycle_over_real_ipc passes C/R/E/D/U/I/W/V/P with a generated parser-valid SHX file and a real shape-file TextStyle: high-level create/read/transaction edit/delete; insertion, size, rotation and width-factor edits; live canvas selection; grouped undo/redo; invalid size and missing style rollback; exact identity/style/common-field preservation; and DWG plus DXF reopen. Each live and reopened document renders the three-point SHX glyph rather than the placeholder. Host-owned reference binding writes the stable style handle needed by DWG. The DXF fallback now searches all is_shape_file styles, preserving named-style glyph resolution after reopen. |
| AttributeDefinition | Integration-tested / blocked at W by acadrust (DXF ATTDEF reader) | Host commit 5b854bd1 maps block ownership plus every stable ATTDEF field except the nested embedded_mtext payload, which is snapshot-readable and preserved untouched. Two staged tests cover it. staged_python_attribute_definition_lifecycle_over_real_ipc passes with a real block record, Block/BlockEnd markers and a linked Insert. The 2026-09-20 audit audit_python_attribute_definition_lifecycle_over_real_ipc (shared run_mesh_lifecycle, host-made block) compares every writable field through both formats: creation with non-default tag, prompt, value, placement, alignment point, height, rotation, width factor, oblique angle, alignments, all attribute flags, field length, generation flags and lock; a nine-property transaction edit; canvas wires; eight rejection cases (whitespace tag, non-positive height and width factor, missing style, owner change, zero normal, non-finite rotation, unknown alignment) plus an atomic rollback; DWG reopen with the complete edited state and re-edit; deletion; and three-step undo/redo. W blocker, revalidated on acadrust 5b682ed: the DXF writer emits every ATTDEF group, but the reader handles only groups 1, 2, 3, 10, 40, 50, 280 and 101, so a DXF reopen keeps tag, prompt, value, insertion point, height and rotation and resets the alignment point, both alignments, width factor, oblique angle, all attribute flags (invisible, constant, verify, preset), field length, generation flags, lock and text style to defaults. This is now pinned by canary expectations for both the edit and the re-edit. Not Complete until the reader is extended; the fix belongs in cadcodec's read_attdef. embedded_mtext mutation remains unsupported pending linked-layout validation. |
| AttributeEntity | Complete | OCS commit cf2a9235 and adapter commit 9c6b5c3 make nested ATTRIB records addressable by handle while retaining canonical storage in Insert.attributes. Host validation binds tag to a definition in the Insert's block, validates placement/text geometry and style references, keeps owner and definition handles read-only, and preserves embedded MTEXT. staged_python_attribute_entity_lifecycle_over_real_ipc passes C/R/E/D/U/I/W/V/P with a real block, ATTDEF and Insert: high-level create/read/transaction edit/delete, parent selection, rendered text, atomic invalid tag/height/owner rejection, edited and deleted DWG/DXF reopen, and three-step undo/redo. nested_insert_attribute_crud_uses_parent_storage_and_undo additionally proves the child never becomes an orphan flat entity in the host document. |
| Hatch | Complete | OCS commit 98b56ef6 and adapter commit a9b5de8 expose solid and patterned Hatches with elevation/normal, complete pattern definitions, pattern controls, style, association state, boundary paths and seed points. Nested tagged boundary edges use the reusable {kind, value} adapter form. Host validation requires closed finite loops, validates line/arc/ellipse/polyline/spline payloads and stored pattern lines, resolves associative boundary handles, and preserves gradient and MPOLYGON payloads as explicitly unsupported. staged_python_hatch_lifecycle_over_real_ipc passes C/R/E/D/U/I/W/V/P: Python solid creation, patterned boundary edit, live render-model pattern/boundary checks, selection, atomic invalid scale/association/open-loop/unmapped-property rejection, edited and deleted DWG/DXF reopen, post-reopen edits, and three-step undo/redo. |
| Leader | Integration-tested / blocked at W by acadrust | Fields mapped writable: vertices (≥2 finite points, not all coincident), annotation_handle, creation_type, path_type, dimension_style, arrow_enabled, hookline_enabled, hookline_direction, text_height, text_width, normal, horizontal_direction, block_offset, annotation_offset. Read-only: origin, dimension_gap, arrowhead_type, arrow_size, byblock_color. override_color is writable through the new generator Color form ({'kind': 'Rgb', 'value': {'r','g','b'}}, Index, ByLayer, ByBlock, None). Unmapped but preserved on edit: the dwg_unknown_* fields. Host reference binding validates that annotation_handle exists, is not the leader itself, matches creation_type (Text/MText, Tolerance, Insert; NoAnnotation forbids a handle) and that dimension_style exists; nothing is silently defaulted. staged_python_leader_lifecycle_over_real_ipc passes C/R/E/D/U/I/V/P against acadrust 7ea4247: linked Text annotation, path/height/arrow/offset transaction edit with untouched-field preservation, GUI pick grouping (scripted selection stays exact), seven single-edit rejections plus a cross-entity atomic rollback, four-step undo/redo, real render linework, DWG and DXF reopen and re-edit. Erasing a leader also erases its annotation (scene leader grouping); undo restores both. W blockers (engine, recorded as canary assertions in the test): (1) the DXF writer never emits group 340, so a DXF save unlinks the annotation; (2) DXF reads drop annotation_offset (group 213 is written but not reassembled); (3) DWG R2010+ does not store text_height, text_width or hookline_enabled (acadrust writes them only for R13–R2007 / R13–R14); (4) neither writer stores override_color, so it reopens as ByLayer in DWG and DXF. Not Complete until the engine round-trips (1) and (2); (3) needs a decision to document or make read-only. |
| MLine | Complete | Writable: vertices (raw vertex dicts; a script may pass only position), flags (integer, unknown bits rejected), justification, normal, scale_factor, style_name. Read-only: start_point, style_handle, style_element_count. Host validation requires ≥2 finite non-coincident vertices, ≥3 for a closed multiline, a finite nonzero scale and normal, an existing non-empty MLineStyle, and per-vertex segments that are either empty or match the style element count. Vertex direction/miter, segment layout and per-element miter offsets (justification and scale included) are host-derived by normalize_scripted_mline on every scripted create or edit, reusing the drawing command's rebuild_mline_geometry/sync_mline_element_parameters; dash and fill cuts survive an edit unless a vertex moves. staged_python_mline_lifecycle_over_real_ipc passes C/R/E/D/U/I/W/V/P: create against the Standard style, a three-vertex/scale/justification transaction edit with untouched-field preservation, corner miter widening and element offsets checked, real mline_lines linework before and after DWG and DXF reopen, nine single-edit rejections plus a cross-entity atomic rollback, re-edit after reopen in both formats, deletion, and three-step undo/redo. Known limit: switching to a style with a different element count is rejected until the script also clears the vertices' segments; only the rejection is covered by the integration test. |
| Dimension | Complete (all 9 subtypes) | EntityType::Dimension wraps a nine-variant enum, so the adapter uses a hand-written flat model (dimension_model.rs.inc, wired in through the manifest's manual_kinds) and the coverage catalog lists its properties through the policy's synthetic section. Python sees subtype (fixed at creation), the shared base fields (text, style_name, normal, text_middle_point, attachment_point, text_rotation, horizontal_direction, flip_arrow1/2, text_user_positioned) and only the fields of that subtype; a field from another subtype is rejected, not ignored. actual_measurement and the base definition point are host-derived (normalize_scripted_dimension, the same recompute grip edits use). Unmapped but preserved: user_text, insertion_point, block_name, version and DWG flags, style overrides in XDATA. Per-subtype status, all verified by staged_python_dimension_lifecycle_over_real_ipc (C/R/E/D/U/I/W/V/P; measurement and canvas wires checked for each, DWG and DXF reopen recorded per subtype): Linear (10), Aligned (5), Radius (5), Diameter (10), Angular3Pt (90°), Angular2Ln (90°), Ordinate (5), Arc (5·π/2), LargeRadial (6) — every one creates, reads, derives the right measurement, renders wires, and keeps its measurement through DWG and DXF. Edits (Linear second point/text/attachment, Radius definition point) update the measurement, nine rejection cases plus a cross-entity atomic rollback and three create-time rejections leave the document untouched, and post-reopen re-edit passes in both formats. Not covered: text formatting overrides, arrowheads and other DIMSTYLE overrides stored in XDATA, the anonymous *D block. The subtype-specific field meanings for the angular subtypes follow the DXF group-code layout rather than the acadrust convenience constructors. |
| MultiLeader | Complete | Mapped through the generic generator, which gained support for Color, LineWeight, mixed unit/payload enums, fixed [f64; N] arrays, bitflags-2 newtypes and per-type module paths for this kind. Writable: content_type, path_type, line_color, line_weight, enable_landing, enable_dogleg, dogleg_length, arrowhead_size, text_color, text_frame, the four text attachments, attachment direction/point, text_alignment, text_angle_type, text_direction_negative, scale_factor, enable_annotation_scale, extend_leader_to_text, block content fields, the reference handles (style_handle, text_style_handle, arrowhead_handle, line_type_handle, block_content_handle) and context, which merges as a patch so a script can change text_string without resetting leader roots. Read-only: dwg_version, property_override_flags, text_align_in_ipe and the entity-level text_height (neither writer stores it; set context.text_height). Excluded but preserved: arrowhead_overrides, block_attributes. Host validation checks finite/positive scale, height, dogleg and arrow sizes, finite context geometry and transform, non-empty leader lines, content-type/context consistency, and that every referenced handle exists as the right object kind. staged_python_multileader_lifecycle_over_real_ipc passes C/R/E/D/U/I/W/V/P: MText creation with a three-point leader root, a partial-context edit that replaces the path (four points), text, text location, dogleg and colour while preserving scale/content/common fields, canvas wires, eleven rejection cases plus a duplicate-handle atomic rollback, DWG and DXF reopen (text, path, dogleg, context.text_height, colour) with post-reopen re-edit, and three-step undo/redo. Not covered: block-content MultiLeaders (no block fixture yet), tolerance content, and style-defined defaults. |
| Table | Complete | Mapped through the generic generator (now also handling tuples and usize, with the generic LegacyBorderOverrides<T> fields excluded and preserved). Writable: insertion_point, horizontal_direction, normal, table_style_handle, rows, columns, merged_ranges, break_spacing, break_flow_direction, break_options. Read-only: block_name, block_record_handle, data_version, value_flags, the override flags, name, description, field_handles, base_style, break_data, break_ranges, legacy_style_override. Creation yields the engine default 3x3 grid; scripts read rows/columns, modify or extend them and assign them back (rows and columns replace wholesale, so copy an existing row dict when adding one). Host validation requires a non-empty grid, positive finite row heights and column widths, every row to have exactly one cell per column, finite cell and content geometry, merged ranges inside the grid and non-overlapping, and existing table-style, cell text-style and cell block handles. normalize_scripted_table mirrors merged_ranges onto the origin cell's merge_width/height on every scripted change to the ranges. staged_python_table_lifecycle_over_real_ipc passes C/R/E/D/U/I/W/V/P: default-grid creation, a row append, column-width edit, a text cell, a three-column merge and break spacing in one transaction with untouched rows/columns/style/common fields preserved, canvas wires, ten rejection cases plus an atomic rollback, DWG and DXF reopen (row count, width, cell text, insertion) with re-edit, and three-step undo/redo. Format notes: DWG restores merged_ranges but not per-cell merge dimensions; DXF restores the cell dimensions but its reader does not rebuild merged_ranges (recorded by a canary assertion); both keep the merge. Not covered: the anonymous table block is not regenerated, cell styles and formats beyond text, linked data and field content. |
| PolygonMesh | Complete | Writable: flags, m_vertex_count, n_vertex_count, m_smooth_density, n_smooth_density, smooth_type, elevation, normal, vertices (each {location, flags}; a vertex's own common data is not scripted and defaults). Host validation requires M and N of at least 2, exactly M×N vertices, non-negative densities, finite elevation and locations, and a nonzero normal, so inconsistent counts are refused before any mutation. staged_python_polygon_mesh_lifecycle_over_real_ipc (shared run_mesh_lifecycle) passes C/R/E/D/U/I/W/V/P: a 3×3 grid, a vertex move plus density and cubic smoothing in one transaction, canvas wires, seven rejection cases plus an atomic rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. |
| PolyfaceMesh | Complete | Writable: elevation, flags, normal, start_width, end_width, smooth_surface, thickness, vertices (location, flags, bulge, widths, tangent, id) and faces (four indices, flags, optional colour). seqend_handle is read-only and preserved. Host validation requires at least 3 finite vertices and 1 face, every face index within the vertex count (a negative index hides that edge, 0 marks an unused corner), at least 3 indices per face, and finite widths, thickness and elevation. staged_python_polyface_mesh_lifecycle_over_real_ipc passes the full gate: two triangles, a vertex move plus a new vertex and a hidden-edge face in one transaction, canvas wires, six 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. |
| 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 | 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 | Integration-tested / blocked at W by acadrust (DXF rotation units) | Writable: underlay_type and definition_handle (both fixed by the definition: creation names an existing UnderlayDefinition, and a mismatched type or missing definition is rejected), insertion_point, x_scale, y_scale, z_scale, rotation, normal, flags, contrast, fade, clip_boundary_vertices, clip_inverted. The file path and page live on the definition object, which scripts cannot create or list yet; they reference one the drawing already owns. Host validation requires finite nonzero scales, a finite rotation, a nonzero normal, contrast/fade within 0..=100, known flags, finite clip vertices (none, or at least 2) and an existing definition of the same type. staged_python_underlay_lifecycle_over_real_ipc (shared run_mesh_lifecycle with a fixture PDF definition) passes C/R/E/D/U/I/V/P and DWG W: creation, a placement/scale/rotation/contrast/fade/clip transaction, canvas wires, eight rejection cases plus an atomic rollback, DWG reopen and re-edit, and three-step undo/redo. W blocker (engine, pinned by canary assertions): DXF stores the rotation in degrees but the reader returns it unconverted, so 0.5 rad reopens as 28.6 and each further DXF save-reopen compounds it (1641.4 after a second), silently corrupting the rotation. Not Complete until the reader converts degrees back to radians. Not covered: creating definitions from a file path, DWF/DGN fixtures. |
| Viewport | Integration-tested | Writable: center, width, height, status, view_center, view_direction, view_target, lens_length, front_clip_z, back_clip_z, view_height, twist_angle, custom_scale, frozen_layers (layer handles), render_mode, circle_sides, ucs_icon_visible. Read-only: id, snap/grid settings, UCS fields, clip_boundary_handle, style_sheet, shade-plot, background, visual-style and sun handles, lighting, ambient_color, elevation. Reference rules: the owner must be a paper-space layout block (a model-space viewport is rejected) and every frozen-layer handle must be an existing layer. Host validation requires finite centers, positive width, height, view height, lens length and scale, a nonzero view direction, finite clip planes and twist, and at least 3 circle sides. staged_python_viewport_lifecycle_over_real_ipc (shared run_mesh_lifecycle, paper-space owner supplied by the driver) passes C/R/E/D/U/V/W/P: creation in the paper-space block, a move/resize/view-height/twist/frozen-layer transaction, seven rejection cases plus an atomic rollback, DWG and DXF reopen with re-edit, and three-step undo/redo. Why not Complete: the I gate lacks a canvas oracle because a paper-space viewport only draws on its layout tab, which the driver does not activate; viewport id uniqueness within a layout, clip boundary entities and view-to-scale synchronization are not handled by the host. |
| ViewBorder | Integration-tested / blocked at W by acadrust (DXF kind) | Writable: min, max, center (2-D pairs), scale, rotation_angle, active_viewport and scale_handle (both required at creation). version is read-only. Reference rules: active_viewport must be an existing Viewport entity and scale_handle an existing Scale object. Host validation requires finite bounds with min below max on both axes, a positive scale and a finite rotation. staged_python_view_border_lifecycle_over_real_ipc (shared run_mesh_lifecycle, driver-made viewport and scale fixtures) passes C/R/E/D/U/I/V/P and DWG W: creation, a resize/recenter/scale/rotation transaction, canvas wires, seven rejection cases plus an atomic rollback, DWG reopen and re-edit, and three-step undo/redo. W blocker (engine, pinned by a canary): a DXF DRAWINGVIEW reopens as a different, opaque entity kind rather than as a ViewBorder, so only DWG restores the typed record. Not Complete until DXF loading restores the typed entity. Not covered: the linked view representation and drawing-view definition records. |
| SectionSymbol | Blocked | Not mapped. A valid section mark needs a SectionViewStyle and a ViewRep view-representation object that a script cannot create through the host API, and the entity stores raw point counts and flag words (raw_point_count_90, raw_flags_90, raw_point_record_count, raw_view_symbol_70) whose relationship to points is undocumented in acadrust and is written back verbatim by both writers, so the host cannot keep them consistent after a scripted edit without guessing. Adding the kind needs (1) host-side creation or lookup of a section style and view representation, and (2) a confirmed derivation of the raw counts from a real DWG produced by another CAD application. The kind keeps raw snapshot access and layer edits. |
| Light | Integration-tested / blocked at W by acadrust (DXF cast_shadows) |
Writable: name, light_type (1 distant, 2 point, 3 spot), position, target, status, light_color, plot_glyph, intensity, the attenuation type and limits, hotspot_angle, falloff_angle, cast_shadows, shadow_type, shadow_map_size, shadow_map_softness, photometric_mode, photometric_data; class_version is read-only. Host validation requires a non-empty name, a known light type, finite placement, a spot or distant target that differs from the position, non-negative intensity, angles and limits, an attenuation start not above its end, a spot hotspot not above its falloff, and a non-negative shadow map size. staged_python_light_lifecycle_over_real_ipc (shared run_mesh_lifecycle) passes C/R/E/D/U/I/V/P and DWG W: a spot light with an RGB colour, an intensity/target/position/shadow transaction that preserves the colour and untouched fields, the canvas glyph, eight rejection cases plus an atomic rollback, DWG reopen and re-edit, and three-step undo/redo. W blocker (engine, pinned by a canary): the DXF reader does not restore cast_shadows, which reopens false; colour, placement and intensity persist. Not covered: photometric web data edits and sun/skylight objects. |
| Region | Mapped read-only; transform untested | Mapped like Solid3D and accepted by doc.solids.transform, which lifts the payload with kernel_acis_body, but no Region fixture exercises it yet. Creation from curve handles (the REGION command's add_region_model) is the next operation to add. |
| Body | Integration-tested (rigid move only) | Mapped read-only like Solid3D (same payload container). audit_python_body_transform_over_real_ipc moves a Body carrying a kernel box through doc.solids.translate, checks the exact extent, DWG reopen with a losslessly lifting payload and the exact volume, and the explanatory refusal of create_entity('Body'). Scripts cannot create a Body, and a non-solid (sheet or wire) body that does not lift is refused untouched. |
| Solid3D | Complete (create + rigid move) | New in this session: an additive HostApi::solid_operation (SolidOperation::Create and Transform, plus a SolidPrimitive enum) carried by PluginRequest::SolidOperation / PluginResponse::SolidResult through both IPC clients, the server and HostSession, and a Python doc.solids API: box, wedge, cylinder, sphere, torus, pyramid, transform, translate, rotate, mirror. The host builds bodies with cadkernel, and every result goes through solid_to_sat, which lifts its own output back and refuses anything lossy. A transform first lifts the existing payload with kernel_acis_body and is refused, leaving the payload untouched, when that is lossy. Only rigid moves and mirrors are accepted (scaling and shear are refused). Regenerated wires follow the body, silhouettes are cleared and history_handle is dropped. Python sees uid, point_of_reference and history_handle read-only; acis_data, wires and silhouettes are excluded and preserved; creation through create_entity is refused with a message pointing at doc.solids. audit_python_solid3d_lifecycle_over_real_ipc passes C/R/E/D/U/I/W/V/P through the real runner with the kernel as oracle: all six primitives match their analytic volumes (box, wedge and cube exactly, curved solids within 2 percent of the tessellated volume), a translate, a rotate and a mirror keep the volume and move the extent exactly and keep 12 edge wires, 13 refusals (zero and non-finite sizes, a torus with minor above major, too few pyramid sides, an empty layer, an unknown primitive, a wrong number count, a scaling matrix, a non-finite offset, a payload the kernel cannot lift, a missing entity) change nothing and leave no undo step, DWG and DXF reopen with the payload lifting losslessly and the same volume and extent, a reopened payload moves again, and undo restores the original solid exactly. Not covered: booleans (about 23 s each in a debug build; release timing needed first), solids that carry parametric history, and AutoCAD acceptance of the written payload, which cannot be tested here. |
| Surface | Blocked, unblocked by design | Not mapped yet, and the most involved: a surface carries surface_data beside acis_data, and only a plane keeps its kind when regenerated. Plan: planes first, then extruded and revolved from a profile. See cadkernel-body-path.md. |
| Ole2Frame | Update-only (all applicable gates pass) | Writable: upper_left_corner, lower_right_corner, lock_aspect, is_paper_space. Read-only: version, source_application, ole_object_type, dwg_mode. The embedded object storage and the frame envelope are opaque, excluded from the Python form, refused by the mutation check (Ole2Frame embedded storage cannot change) and preserved untouched. Host validation requires finite corners spanning a nonzero width and height and lock_aspect of 0 or 1. Creation from a script is refused with an explanatory message, because a valid frame needs a real embedded object; the host's own build_embedded_ole builds one from an image. audit_python_ole2frame_lifecycle_over_real_ipc (shared driver; the fixture embeds a real 4x3 PNG through add_embedded_image) passes E/D/U/I/W/V/P: a corner and aspect-lock transaction, canvas geometry, rejection of a zero-size frame, a bad aspect lock, non-finite corners, a read-only version and an attempt to write storage, an atomic rollback, DWG and DXF reopen with the 119-byte embedded picture byte-for-byte intact (extracted through extract_presentation), re-edit, and two-step undo/redo. Not Complete because Python cannot create a frame. A create path (file_path of an image, built with build_embedded_ole) would need a manual-kind converter like Dimension and is the natural follow-up. |
After each increment, update the row with test names, fixture paths, exact passing commands, the OCS commit hash, and any manual GUI result. Host and adapter changes now share the same repository commit. If a gate is untested, leave it pending. Do not raise the headline completion count from converter coverage alone.