forked from 1b-agent/opencadstudio-upstream
Audit AttributeDefinition through both formats; pin the DXF reader gap
Compares every writable ATTDEF field through DWG and DXF. DWG round-trips the complete state; acadrust's DXF reader handles only eight group codes, so alignment, width factor, oblique angle, attribute flags, field length, generation flags, lock and text style reopen as defaults. Pinned by canary expectations and recorded in the ledger. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
1 parent
bd43ed7e91
commit
cae188dd08
3 files changed
+70
-4
No files matched your search
@@ -172,3 +172,4 @@ exact queue above and update statuses with evidence after each increment.
|
||||
- Recorded `SectionSymbol` (queue item 15) as **blocked**: it needs a section view style and view representation the host cannot create, and its raw point-count fields have no documented derivation. Mapped `Light` (item 16); every gate passes except DXF `cast_shadows`, which the reader drops (pinned by a canary). The queue now continues with `Region`.
|
||||
- Recorded `Region`, `Body`, `Solid3D`, `Surface` (queue items 17-20) and `Ole2Frame` (item 21) as **blocked**. The ACIS kinds need a host-side kernel-backed create/transform API that regenerates the payload, wires and silhouettes; the OLE frame needs a valid embedded-storage fixture. No opaque payload is rewritten. With this the ordered queue is complete: 12 kinds are fully complete, Leader, Helix, Underlay, ViewBorder and Light are mapped but blocked at DWG/DXF persistence by acadrust gaps, RasterImage and Viewport lack an oracle or linkage, and six kinds are blocked. Next work is the engine fixes behind the canary assertions, the mapped-kind audit of the 17 pre-existing kinds, and merging `origin/main`.
|
||||
- Audited the 17 pre-existing mapped kinds through the shared real-runner driver (`audit_python_*`). The audit found and fixed genuine validation gaps: Ellipse had none, Text and MText accepted a zero height, Arc accepted non-finite angles, Point and Line accepted infinite thickness, and most basic kinds had no creation-time validation. A shared `validate_basic_entity` now covers Point, Line, Circle, Arc, Ellipse, Text, MText, LwPolyline, Polyline, Polyline2D, Polyline3D and Spline, refusing only problems an edit introduces so already-invalid legacy values stay editable. Twelve of the 17 kinds now pass every gate (Point, Line, Circle, Arc, Ellipse, LwPolyline, Text, MText, Ray, XLine, Solid, Face3D). Four expose persistence findings pinned by canaries (Polyline reopens as Polyline3D; Polyline2D reopens as LwPolyline in DXF; Polyline3D loses elevation in DWG; Spline loses weights in DXF), and Insert is update-only with all applicable gates passing. The driver gained per-format DWG expectations and a host-made block fixture for Insert.
|
||||
- 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.
|
||||
@@ -25,7 +25,7 @@ Completion gate codes: **C** = create through Python document model; **R** = rea
|
||||
| Insert | **Update-only (all gates pass)** | Exercised: `insert_point`, `x_scale`, `y_scale`, `rotation`, `column_count`, `column_spacing`. `audit_python_insert_lifecycle_over_real_ipc` passes E/D/U/I/W/V/P with a host-made block and insert: a six-property transaction edit, canvas wires, rejection of a zero scale, non-finite rotation and point, a zero array count, a zero normal and a `block_name` change (read-only), an atomic rollback, DWG and DXF reopen with re-edit, and undo/redo. Not `Complete` because Python cannot create an Insert (creation and block-reference semantics are an open decision: permanent update-only, or a create path that names an existing block). |
|
||||
| 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 / DXF blocker pending revalidation** | Host commit `5b854bd1` maps block ownership plus every stable ATTDEF field except the nested `embedded_mtext` payload, which is snapshot-readable and preserved untouched. `staged_python_attribute_definition_lifecycle_over_real_ipc` passes C/R/E/D/U/I/V/P with a real block record, Block/BlockEnd markers and linked Insert: high-level creation inside the block, readback, grouped placement/default/rotation/width edits, selection, rendered text, atomic tag/height/style/owner rejection, deletion persistence, and three-step undo/redo. DWG reopens with the full edited state. DXF reopens identity, ownership, tag, prompt, value, placement, height and rotation, and OCS no longer double-converts ATTDEF/ATTRIB rotation. **W remains open:** acadrust `568a12c` dropped optional ATTDEF fields including width factor while reading DXF. Rerun the regression against the currently merged `7ea4247` revision and update this row with the result. `embedded_mtext` mutation remains unsupported pending linked-layout validation. |
|
||||
| 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. |
|
||||
|
||||
+68
-3
@@ -3201,7 +3201,7 @@ mod tests {
|
||||
// Insert is update-only: the host makes a block and one insert, and the
|
||||
// "create" script only has to exist.
|
||||
let update_only = create_script.contains("MAKEINSERT");
|
||||
if update_only {
|
||||
if update_only || create_script.contains("MAKEBLOCK") {
|
||||
let next = host.document().next_handle();
|
||||
let (record_handle, block_handle, end_handle) = (Handle::new(next), Handle::new(next + 1), Handle::new(next + 2));
|
||||
let mut record = acadrust::tables::BlockRecord::new("AUDITBLK");
|
||||
@@ -3217,8 +3217,11 @@ mod tests {
|
||||
end.common.handle = end_handle;
|
||||
end.common.owner_handle = record_handle;
|
||||
host.document_mut().add_entity(EntityType::BlockEnd(end)).unwrap();
|
||||
let insert = acadrust::entities::Insert::new("AUDITBLK", acadrust::types::Vector3::new(1.0, 2.0, 0.0));
|
||||
host.add_entity(EntityType::Insert(insert));
|
||||
if update_only {
|
||||
let insert = acadrust::entities::Insert::new("AUDITBLK", acadrust::types::Vector3::new(1.0, 2.0, 0.0));
|
||||
host.add_entity(EntityType::Insert(insert));
|
||||
}
|
||||
create_script = create_script.replace("BLOCKRECORD", &record_handle.value().to_string());
|
||||
}
|
||||
let paper = host.document().block_records.iter().find(|r| r.is_paper_space()).map(|r| r.handle.value());
|
||||
let layer0 = host.document().layers.iter().find(|l| l.name == "0").map(|l| l.handle.value());
|
||||
@@ -4241,6 +4244,68 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audit_python_attribute_definition_lifecycle_over_real_ipc() {
|
||||
run_mesh_lifecycle(&MeshCase {
|
||||
create: concat!(
|
||||
"# MAKEBLOCK\n",
|
||||
"doc.create_entity('AttributeDefinition', owner_handle=BLOCKRECORD, tag='PART_NO', prompt='Part number',\n",
|
||||
" default_value='PN-001', insertion_point=P(1.0, 2.0, 0.0), height=2.5, width_factor=1.25, oblique_angle=0.1,\n",
|
||||
" rotation=0.25, horizontal_alignment='Center', vertical_alignment='Top', alignment_point=P(3.0, 4.0, 0.0),\n",
|
||||
" flags={'invisible': True, 'constant': False, 'verify': True, 'preset': False, 'locked_position': False, 'annotative': False},\n",
|
||||
" field_length=12, text_generation_flags=2, lock_position=True)\n"),
|
||||
edit: concat!(
|
||||
"e = doc.entities[HANDLE]\n",
|
||||
"with doc.transaction('Edit'):\n",
|
||||
" e.insertion_point = (4.0, 5.0, 0.0)\n",
|
||||
" e.default_value = 'PN-002'\n",
|
||||
" e.prompt = 'Serial'\n",
|
||||
" e.height = 3.0\n",
|
||||
" e.width_factor = 0.8\n",
|
||||
" e.rotation = 0.5\n",
|
||||
" e.oblique_angle = 0.2\n",
|
||||
" e.horizontal_alignment = 'Right'\n",
|
||||
" e.vertical_alignment = 'Middle'\n",
|
||||
" e.flags = {'invisible': False, 'constant': True, 'verify': False, 'preset': True, 'locked_position': False, 'annotative': False}\n",
|
||||
" e.field_length = 20\n",
|
||||
"doc.selection = [e]\n"),
|
||||
rejects: &[
|
||||
("'tag':'BAD TAG'", "whitespace"),
|
||||
("'height':0.0", "greater than zero"),
|
||||
("'width_factor':0.0", "width_factor"),
|
||||
("'text_style':'Missing'", "does not exist"),
|
||||
("'owner_handle':999999", "read-only"),
|
||||
("'normal':{'x':0.0,'y':0.0,'z':0.0}", "nonzero"),
|
||||
("'rotation':float('nan')", "finite"),
|
||||
("'horizontal_alignment':'Nowhere'", "unsupported"),
|
||||
],
|
||||
is_kind: |entity| matches!(entity, EntityType::AttributeDefinition(_)),
|
||||
digest: |entity| match entity {
|
||||
EntityType::AttributeDefinition(v) => format!(
|
||||
"{}|{}|{} ins{:.1},{:.1} al{:.1},{:.1} h{:.1} r{:.2} wf{:.2} ob{:.2} {:?}/{:?} f{}{}{}{} fl{} tg{} lock{}",
|
||||
v.tag, v.prompt, v.default_value, v.insertion_point.x, v.insertion_point.y,
|
||||
v.alignment_point.x, v.alignment_point.y, v.height, v.rotation, v.width_factor, v.oblique_angle,
|
||||
v.horizontal_alignment, v.vertical_alignment,
|
||||
u8::from(v.flags.invisible), u8::from(v.flags.constant), u8::from(v.flags.verify), u8::from(v.flags.preset),
|
||||
v.field_length, v.text_generation_flags, u8::from(v.lock_position)),
|
||||
_ => "wrong kind".into(),
|
||||
},
|
||||
reedit: |entity| if let EntityType::AttributeDefinition(v) = entity { v.default_value = "PN-003".into(); },
|
||||
expect_created: "PART_NO|Part number|PN-001 ins1.0,2.0 al3.0,4.0 h2.5 r0.25 wf1.25 ob0.10 Center/Top f1010 fl12 tg2 lock1",
|
||||
expect_edited: "PART_NO|Serial|PN-002 ins4.0,5.0 al3.0,4.0 h3.0 r0.50 wf0.80 ob0.20 Right/Middle f0101 fl20 tg2 lock1",
|
||||
expect_reedited: "PART_NO|Serial|PN-003 ins4.0,5.0 al3.0,4.0 h3.0 r0.50 wf0.80 ob0.20 Right/Middle f0101 fl20 tg2 lock1",
|
||||
// BLOCKER: acadrust's DXF ATTDEF reader handles only groups 1, 2, 3,
|
||||
// 10, 40, 50, 280 and 101, so the alignment point and alignments,
|
||||
// width factor, oblique angle, attribute flags, field length,
|
||||
// generation flags, lock and text style (groups 7, 11, 41, 51, 70-74,
|
||||
// 210, 280 lock) reopen as defaults. The writer emits all of them.
|
||||
expect_edited_dxf: "PART_NO|Serial|PN-002 ins4.0,5.0 al0.0,0.0 h3.0 r0.50 wf1.00 ob0.00 Left/Baseline f0000 fl0 tg0 lock0",
|
||||
expect_reedited_dxf: "PART_NO|Serial|PN-003 ins4.0,5.0 al0.0,0.0 h3.0 r0.50 wf1.00 ob0.00 Left/Baseline f0000 fl0 tg0 lock0",
|
||||
expect_edited_dwg: "",
|
||||
expect_reedited_dwg: "",
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn staged_python_point_pick_and_cancel_over_real_ipc() {
|
||||
let Some(plugin_path) = std::env::var_os("OCS_TEST_PYTHON_PLUGIN") else {
|
||||
|
||||
Reference in new issue
Block a user