feat: extend ordered pair evidence through groove
Some checks failed
real-verification / chrome (push) Has been cancelled
real-verification / freecad-oracle (push) Has been cancelled
real-verification / wasm (push) Has been cancelled

This commit is contained in:
2026-08-15 23:55:17 -04:00
parent 10d11ad608
commit 372861e015
11 changed files with 27750 additions and 952 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -24,7 +24,7 @@
"wrong-document-root": 77
},
"timing": {
"p95Ms": 0.179,
"maxMs": 3.391
"p95Ms": 0.131,
"maxMs": 2.695
}
}

View File

@@ -41,7 +41,7 @@
"zero-revolution-axis": 67
},
"timing": {
"p95Ms": 0.023,
"maxMs": 1.521
"p95Ms": 0.025,
"maxMs": 1.112
}
}

View File

@@ -22,7 +22,7 @@
"invalid": 800
},
"timing": {
"p95Ms": 0.035,
"maxMs": 0.893
"p95Ms": 0.033,
"maxMs": 0.795
}
}

View File

@@ -390,7 +390,7 @@
{
"path": "sw.js",
"bytes": 1390,
"sha256": "98ec667a774f8370ed654e457b86d4943b2f1eb89e228bd4e7e652b31a51b982"
"sha256": "dfe8893e68cda6991ca64b5f5eaa19097c5b6d8f4bdc13f2423328950671a5be"
},
{
"path": "vendor/camotics/camotics-sweep.wasm",
@@ -412,7 +412,7 @@
"status": "signed",
"algorithm": "Ed25519",
"keyId": "bitbybit-local-release-2026",
"payloadSha256": "5172b35d4d194483ac1934a1653259b1c7f5bb78c5ab4a68f7e5033cf2a22b86",
"signature": "FoaYFhliT+/njf8hXECKj5IoCeHPj1MnpbTBIw/FxzYpfyrgQ01nuPkwslM4okUk3d97cncjaXnEkDA7lZBtDw=="
"payloadSha256": "e6d83894b72503eed170bec685d761d868784e28fbc5a221d78c0b270a472b37",
"signature": "FZ+gouN7G/kpiInw2zMQ5FBLXPJzurcdHv3lk4NThmtPGe6NXoe8KkIngxO8vbRba0E2e1jwoSgbv2HfvY5xAw=="
}
}

View File

@@ -3,9 +3,9 @@
生成日期2026-08-15。基线FreeCAD 1.1.1 / `0108fd4b4850cc46e625b60e53cea7a7bbe69f8d`
- 唯一活动任务:`TSN-PAIR-pocket-fuse`
- 微任务1756 total / 223 completed / 1 in_progress / 1532 pending
- 有序操作对:97/361 classified / 264 unknown
- 唯一活动任务:`TSN-PAIR-groove-multi-transform`
- 微任务1756 total / 317 completed / 1 in_progress / 1438 pending
- 有序操作对:191/361 classified / 170 unknown
- Property27/85 native types / 58 opaque types
- exact 任务5/52 completed
- 模块能力0 exact / 26 compatible / 5 proxy / 3 development
@@ -15,7 +15,7 @@
| `ORA-GUI-BASELINE` | 106 | 106 | 0 | 0 | `-` |
| `ORA-GUI-WORKFLOWS` | 7 | 7 | 0 | 0 | `-` |
| `TSN-RECOVERY-DRIFT` | 13 | 13 | 0 | 0 | `-` |
| `TSN-ORDERED-PAIRS` | 361 | 97 | 1 | 263 | `TSN-PAIR-pocket-fuse` |
| `TSN-ORDERED-PAIRS` | 361 | 191 | 1 | 169 | `TSN-PAIR-groove-multi-transform` |
| `PROPERTY-CODECS` | 522 | 0 | 0 | 522 | `-` |
| `DOCUMENT-SEMANTICS` | 90 | 0 | 0 | 90 | `-` |
| `CORE-MODELING` | 171 | 0 | 0 | 171 | `-` |

View File

@@ -111,6 +111,100 @@ const expectedPairs = [
{ pair: 'pad->polar-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism', firstInputCount: 1, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [30, 45, 30] },
{ pair: 'pad->mirrored', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism', firstInputCount: 1, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'planeOriginX', mutationTrajectory: [5, 6, 5] },
{ pair: 'pad->multi-transform', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism', firstInputCount: 1, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse(ordered)', secondInputCount: 1, mutationParameter: 'multiTranslationX', mutationTrajectory: [8, 9, 8] },
{ pair: 'pocket->fuse', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Fuse', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [8, 7, 8] },
{ pair: 'pocket->cut', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'toolSize', mutationTrajectory: [4, 3, 4] },
{ pair: 'pocket->common', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Common', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [7, 6, 7] },
{ pair: 'pocket->rotate', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [15, 22.5, 15] },
{ pair: 'pocket->pad', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakePrism', secondInputCount: 1, mutationParameter: 'length', mutationTrajectory: [5, 7.5, 5], nativeTypeId: 'PartDesign::Pad', freeCadDiagnostic: 'FeatureExtrusion: Length: Could not extrude the sketch!' },
{ pair: 'pocket->pocket', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'length', mutationTrajectory: [5, 4, 5] },
{ pair: 'pocket->loft', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, rejectionAuthority: 'freecad-document', kernelOutcome: 'accepted', secondBuilder: 'BRepOffsetAPI_ThruSections', secondInputCount: 2, mutationParameter: 'ruled', mutationTrajectory: [false, true, false], nativeTypeId: 'Part::Loft', freeCadDiagnostic: 'Profile shape is not a single vertex, edge, wire nor face.' },
{ pair: 'pocket->pipe', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, rejectionAuthority: 'freecad-document', kernelOutcome: 'accepted', secondBuilder: 'BRepOffsetAPI_MakePipe', secondInputCount: 2, mutationParameter: 'spineLength', mutationTrajectory: [15, 12, 15], nativeTypeId: 'Part::Sweep', freeCadDiagnostic: 'A fatal error occurred when making the sweep' },
{ pair: 'pocket->revolution', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakeRevol', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [360, 270, 360], nativeTypeId: 'Part::Revolution', freeCadDiagnostic: 'Solids are not Processed' },
{ pair: 'pocket->groove', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'angle', mutationTrajectory: [360, 180, 360] },
{ pair: 'pocket->fillet', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepFilletAPI_MakeFillet', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [0.4, 0.6, 0.4] },
{ pair: 'pocket->chamfer', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepFilletAPI_MakeChamfer', secondInputCount: 1, mutationParameter: 'distance', mutationTrajectory: [0.4, 0.6, 0.4] },
{ pair: 'pocket->hole', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [1, 1.5, 1] },
{ pair: 'pocket->draft', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepOffsetAPI_DraftAngle', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [5, 8, 5] },
{ pair: 'pocket->thickness', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepOffsetAPI_MakeThickSolid', secondInputCount: 1, mutationParameter: 'offset', mutationTrajectory: [-0.4, -0.6, -0.4] },
{ pair: 'pocket->linear-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'translationX', mutationTrajectory: [8, 9, 8] },
{ pair: 'pocket->polar-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [30, 45, 30] },
{ pair: 'pocket->mirrored', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'planeOriginX', mutationTrajectory: [5, 6, 5] },
{ pair: 'pocket->multi-transform', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse(ordered)', secondInputCount: 1, mutationParameter: 'multiTranslationX', mutationTrajectory: [8, 9, 8] },
{ pair: 'loft->fuse', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Fuse', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [8, 7, 8] },
{ pair: 'loft->cut', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'toolSize', mutationTrajectory: [4, 3, 4] },
{ pair: 'loft->common', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Common', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [7, 6, 7] },
{ pair: 'loft->rotate', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [15, 22.5, 15] },
{ pair: 'loft->pad', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakePrism', secondInputCount: 1, mutationParameter: 'length', mutationTrajectory: [5, 7.5, 5], nativeTypeId: 'PartDesign::Pad', freeCadDiagnostic: 'FeatureExtrusion: Length: Could not extrude the sketch!' },
{ pair: 'loft->pocket', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'length', mutationTrajectory: [5, 4, 5] },
{ pair: 'loft->loft', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, rejectionAuthority: 'freecad-document', kernelOutcome: 'accepted', secondBuilder: 'BRepOffsetAPI_ThruSections', secondInputCount: 2, mutationParameter: 'ruled', mutationTrajectory: [false, true, false], nativeTypeId: 'Part::Loft', freeCadDiagnostic: 'Profile shape is not a single vertex, edge, wire nor face.' },
{ pair: 'loft->pipe', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, rejectionAuthority: 'freecad-document', kernelOutcome: 'invalid-result', secondBuilder: 'BRepOffsetAPI_MakePipe', secondInputCount: 2, mutationParameter: 'spineLength', mutationTrajectory: [15, 12, 15], nativeTypeId: 'Part::Sweep', freeCadDiagnostic: 'A fatal error occurred when making the sweep' },
{ pair: 'loft->revolution', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakeRevol', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [360, 270, 360], nativeTypeId: 'Part::Revolution', freeCadDiagnostic: 'Solids are not Processed' },
{ pair: 'loft->groove', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'angle', mutationTrajectory: [360, 180, 360] },
{ pair: 'loft->fillet', decision: 'rejected', reasonCode: 'native-builder-wasm-abort-with-freecad-profile-acceptance-and-clean-resave', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepFilletAPI_MakeFillet', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [0.4, 0.6, 0.4], nativeTypeId: 'Part::Fillet', freecadProfileAccepted: true, wasmCrashSignature: 'table index is out of bounds' },
{ pair: 'loft->chamfer', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, secondBuilder: 'BRepFilletAPI_MakeChamfer', secondInputCount: 1, mutationParameter: 'distance', mutationTrajectory: [0.4, 0.6, 0.4] },
{ pair: 'loft->hole', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [1, 1.5, 1] },
{ pair: 'loft->draft', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, secondBuilder: 'BRepOffsetAPI_DraftAngle', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [5, 8, 5] },
{ pair: 'loft->thickness', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, secondBuilder: 'BRepOffsetAPI_MakeThickSolid', secondInputCount: 1, mutationParameter: 'offset', mutationTrajectory: [-0.4, -0.6, -0.4] },
{ pair: 'loft->linear-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'translationX', mutationTrajectory: [8, 9, 8] },
{ pair: 'loft->polar-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [30, 45, 30] },
{ pair: 'loft->mirrored', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'planeOriginX', mutationTrajectory: [5, 6, 5] },
{ pair: 'loft->multi-transform', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_ThruSections', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse(ordered)', secondInputCount: 1, mutationParameter: 'multiTranslationX', mutationTrajectory: [8, 9, 8] },
{ pair: 'pipe->fuse', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Fuse', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [0, 0.5, 0] },
{ pair: 'pipe->cut', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [0, 0.5, 0] },
{ pair: 'pipe->common', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Common', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [0, 0.5, 0] },
{ pair: 'pipe->rotate', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [15, 22.5, 15] },
{ pair: 'pipe->pad', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakePrism', secondInputCount: 1, mutationParameter: 'length', mutationTrajectory: [5, 7.5, 5], nativeTypeId: 'PartDesign::Pad', freeCadDiagnostic: 'FeatureExtrusion: Length: Could not extrude the sketch!' },
{ pair: 'pipe->pocket', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'length', mutationTrajectory: [5, 4, 5] },
{ pair: 'pipe->loft', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, rejectionAuthority: 'freecad-document', kernelOutcome: 'accepted', secondBuilder: 'BRepOffsetAPI_ThruSections', secondInputCount: 2, mutationParameter: 'ruled', mutationTrajectory: [false, true, false], nativeTypeId: 'Part::Loft', freeCadDiagnostic: 'Profile shape is not a single vertex, edge, wire nor face.' },
{ pair: 'pipe->pipe', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, rejectionAuthority: 'freecad-document', kernelOutcome: 'invalid-result', secondBuilder: 'BRepOffsetAPI_MakePipe', secondInputCount: 2, mutationParameter: 'spineLength', mutationTrajectory: [15, 12, 15], nativeTypeId: 'Part::Sweep', freeCadDiagnostic: 'A fatal error occurred when making the sweep' },
{ pair: 'pipe->revolution', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakeRevol', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [360, 270, 360], nativeTypeId: 'Part::Revolution', freeCadDiagnostic: 'Solids are not Processed' },
{ pair: 'pipe->groove', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'angle', mutationTrajectory: [360, 180, 360] },
{ pair: 'pipe->fillet', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, secondBuilder: 'BRepFilletAPI_MakeFillet', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [0.4, 0.6, 0.4] },
{ pair: 'pipe->chamfer', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, secondBuilder: 'BRepFilletAPI_MakeChamfer', secondInputCount: 1, mutationParameter: 'distance', mutationTrajectory: [0.4, 0.6, 0.4] },
{ pair: 'pipe->hole', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [0.4, 0.6, 0.4] },
{ pair: 'pipe->draft', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, secondBuilder: 'BRepOffsetAPI_DraftAngle', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [5, 8, 5] },
{ pair: 'pipe->thickness', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, secondBuilder: 'BRepOffsetAPI_MakeThickSolid', secondInputCount: 1, mutationParameter: 'offset', mutationTrajectory: [-0.4, -0.6, -0.4] },
{ pair: 'pipe->linear-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'translationX', mutationTrajectory: [1.5, 1, 1.5] },
{ pair: 'pipe->polar-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [30, 45, 30] },
{ pair: 'pipe->mirrored', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'planeOriginX', mutationTrajectory: [1, 1.5, 1] },
{ pair: 'pipe->multi-transform', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepOffsetAPI_MakePipe', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse(ordered)', secondInputCount: 1, mutationParameter: 'multiTranslationX', mutationTrajectory: [8, 7, 8] },
{ pair: 'revolution->fuse', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, secondBuilder: 'BRepAlgoAPI_Fuse', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [0, 0.5, 0] },
{ pair: 'revolution->cut', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, secondBuilder: 'BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [0, 0.5, 0] },
{ pair: 'revolution->common', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, secondBuilder: 'BRepAlgoAPI_Common', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [0, 0.5, 0] },
{ pair: 'revolution->rotate', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, secondBuilder: 'BRepBuilderAPI_Transform', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [15, 22.5, 15] },
{ pair: 'revolution->pad', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakePrism', secondInputCount: 1, mutationParameter: 'length', mutationTrajectory: [5, 7.5, 5], nativeTypeId: 'PartDesign::Pad', freeCadDiagnostic: 'FeatureExtrusion: Length: Could not extrude the sketch!' },
{ pair: 'revolution->pocket', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, secondBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'length', mutationTrajectory: [2, 1, 2] },
{ pair: 'revolution->loft', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, rejectionAuthority: 'freecad-document', kernelOutcome: 'invalid-result', secondBuilder: 'BRepOffsetAPI_ThruSections', secondInputCount: 2, mutationParameter: 'ruled', mutationTrajectory: [false, true, false], nativeTypeId: 'Part::Loft', freeCadDiagnostic: 'Profile shape is not a single vertex, edge, wire nor face.' },
{ pair: 'revolution->pipe', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepOffsetAPI_MakePipe', secondInputCount: 2, mutationParameter: 'spineLength', mutationTrajectory: [15, 12, 15], nativeTypeId: 'Part::Sweep', freeCadDiagnostic: 'A fatal error occurred when making the sweep' },
{ pair: 'revolution->revolution', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakeRevol', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [360, 270, 360], nativeTypeId: 'Part::Revolution', freeCadDiagnostic: 'Solids are not Processed' },
{ pair: 'revolution->groove', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, secondBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'angle', mutationTrajectory: [360, 180, 360] },
{ pair: 'revolution->fillet', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, secondBuilder: 'BRepFilletAPI_MakeFillet', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [0.4, 0.6, 0.4] },
{ pair: 'revolution->chamfer', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, secondBuilder: 'BRepFilletAPI_MakeChamfer', secondInputCount: 1, mutationParameter: 'distance', mutationTrajectory: [0.4, 0.6, 0.4] },
{ pair: 'revolution->hole', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, secondBuilder: 'BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [0.4, 0.6, 0.4] },
{ pair: 'revolution->draft', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, secondBuilder: 'BRepOffsetAPI_DraftAngle', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [5, 8, 5] },
{ pair: 'revolution->thickness', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, secondBuilder: 'BRepOffsetAPI_MakeThickSolid', secondInputCount: 1, mutationParameter: 'offset', mutationTrajectory: [-0.4, -0.6, -0.4] },
{ pair: 'revolution->linear-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'translationX', mutationTrajectory: [4, 3, 4] },
{ pair: 'revolution->polar-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [10, 15, 10] },
{ pair: 'revolution->mirrored', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'planeOriginX', mutationTrajectory: [0, 0.5, 0] },
{ pair: 'revolution->multi-transform', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol', firstInputCount: 1, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse(ordered)', secondInputCount: 1, mutationParameter: 'multiTranslationX', mutationTrajectory: [4, 3, 4] },
{ pair: 'groove->fuse', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Fuse', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [8, 7, 8] },
{ pair: 'groove->cut', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'toolSize', mutationTrajectory: [4, 3, 4] },
{ pair: 'groove->common', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepAlgoAPI_Common', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [7, 6, 7] },
{ pair: 'groove->rotate', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [15, 22.5, 15] },
{ pair: 'groove->pad', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakePrism', secondInputCount: 1, mutationParameter: 'length', mutationTrajectory: [5, 7.5, 5], nativeTypeId: 'PartDesign::Pad', freeCadDiagnostic: 'FeatureExtrusion: Length: Could not extrude the sketch!' },
{ pair: 'groove->pocket', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'length', mutationTrajectory: [5, 4, 5] },
{ pair: 'groove->loft', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, rejectionAuthority: 'freecad-document', kernelOutcome: 'accepted', secondBuilder: 'BRepOffsetAPI_ThruSections', secondInputCount: 2, mutationParameter: 'ruled', mutationTrajectory: [false, true, false], nativeTypeId: 'Part::Loft', freeCadDiagnostic: 'Profile shape is not a single vertex, edge, wire nor face.' },
{ pair: 'groove->pipe', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, rejectionAuthority: 'freecad-document', kernelOutcome: 'invalid-result', secondBuilder: 'BRepOffsetAPI_MakePipe', secondInputCount: 2, mutationParameter: 'spineLength', mutationTrajectory: [15, 12, 15], nativeTypeId: 'Part::Sweep', freeCadDiagnostic: 'A fatal error occurred when making the sweep' },
{ pair: 'groove->revolution', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepPrimAPI_MakeRevol', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [360, 270, 360], nativeTypeId: 'Part::Revolution', freeCadDiagnostic: 'Solids are not Processed' },
{ pair: 'groove->groove', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'angle', mutationTrajectory: [360, 180, 360] },
{ pair: 'groove->fillet', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepFilletAPI_MakeFillet', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [0.4, 0.6, 0.4], nativeTypeId: 'Part::Fillet', freeCadDiagnostic: 'BRep_API: command not done' },
{ pair: 'groove->chamfer', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepFilletAPI_MakeChamfer', secondInputCount: 1, mutationParameter: 'distance', mutationTrajectory: [0.4, 0.6, 0.4], nativeTypeId: 'Part::Chamfer', freeCadDiagnostic: 'BRep_API: command not done', wasmCrashSignature: 'memory access out of bounds' },
{ pair: 'groove->hole', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [1, 1.5, 1] },
{ pair: 'groove->draft', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepOffsetAPI_DraftAngle', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [5, 8, 5] },
{ pair: 'groove->thickness', decision: 'rejected', reasonCode: 'native-builder-wasm-abort-with-freecad-profile-acceptance-and-clean-resave', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepOffsetAPI_MakeThickSolid', secondInputCount: 1, mutationParameter: 'offset', mutationTrajectory: [-0.4, -0.6, -0.4], nativeTypeId: 'Part::Thickness', freecadProfileAccepted: true, wasmCrashSignature: 'memory access out of bounds' },
{ pair: 'groove->linear-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'translationX', mutationTrajectory: [8, 9, 8] },
{ pair: 'groove->polar-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [90, 120, 90] },
{ pair: 'groove->mirrored', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'planeOriginX', mutationTrajectory: [5, 6, 5] },
]
if (report.nativeProbe?.artifacts?.length !== 3 || report.classifications?.length !== expectedPairs.length) fail('native artifacts or classification prefix is incomplete.')
for (const [index, entry] of report.classifications.entries()) {
@@ -126,7 +220,8 @@ for (const [index, entry] of report.classifications.entries()) {
if (expected.rejectionAuthority === 'occt-builder') {
if (rejection.stable !== true || rejection.attempts?.length !== 3) fail(`${expected.pair} OCCT rejection is unstable.`)
for (const [attemptIndex, attempt] of rejection.attempts.entries()) {
if (attempt.operation !== secondOperation || attempt.builder !== expected.secondBuilder || attempt.inputCount !== expected.secondInputCount || attempt.parameter !== expected.mutationParameter || attempt.parameterValue !== expected.mutationTrajectory[attemptIndex] || attempt.runtime !== 'occt-native' || attempt.outcome !== 'rejected' || attempt.resultProduced !== false || attempt.shapeProduced !== false || attempt.historyProduced !== false || attempt.rejectionStage !== 'builder' || attempt.exceptionTransport !== 'emscripten-cpp-exception' || attempt.thrownType !== 'number') fail(`${expected.pair} OCCT attempt ${attemptIndex} is invalid.`)
const wasmAbort = typeof expected.wasmCrashSignature === 'string'
if (attempt.operation !== secondOperation || attempt.builder !== expected.secondBuilder || attempt.inputCount !== expected.secondInputCount || attempt.parameter !== expected.mutationParameter || attempt.parameterValue !== expected.mutationTrajectory[attemptIndex] || attempt.runtime !== 'occt-native' || attempt.outcome !== 'rejected' || attempt.resultProduced !== false || attempt.shapeProduced !== false || attempt.historyProduced !== false || attempt.rejectionStage !== 'builder' || attempt.exceptionTransport !== (wasmAbort ? 'emscripten-wasm-abort' : 'emscripten-cpp-exception') || attempt.thrownType !== (wasmAbort ? 'RuntimeError' : 'number') || (wasmAbort && attempt.crashSignature !== expected.wasmCrashSignature)) fail(`${expected.pair} OCCT attempt ${attemptIndex} is invalid.`)
for (const absent of ['summary', 'historyProvider', 'historyRecords', 'historySha256', 'namingStatus', 'namingEvidenceSha256', 'namingSemanticSha256', 'resultStep', 'resultBrep', 'shape', 'history']) if (absent in attempt) fail(`${expected.pair} OCCT attempt ${attemptIndex} fabricates ${absent}.`)
}
} else {
@@ -135,8 +230,15 @@ for (const [index, entry] of report.classifications.entries()) {
}
const persistence = rejection.persistence
const diagnostic = persistence?.diagnostic
if (persistence?.status !== 'pass' || persistence.decision !== 'rejected' || persistence.pair !== entry.pair || persistence.operation !== secondOperation || persistence.nativeTypeId !== expected.nativeTypeId || persistence.freecadVersion !== '1.1.1' || !Object.values(persistence.checks ?? {}).every(Boolean)) fail(`${expected.pair} FCStd rejection evidence is incomplete.`)
if (diagnostic?.nativeTypeId !== expected.nativeTypeId || diagnostic.parameter?.name !== expected.mutationParameter || diagnostic.parameter.value !== expected.mutationTrajectory[0] || diagnostic.inputShape?.solids !== 1 || diagnostic.recomputeResult !== true || diagnostic.shapeNull !== true || diagnostic.shapeValid !== false || !diagnostic.state?.includes('Invalid') || diagnostic.statusString === 'Valid' || !diagnostic.objects?.some(({ name, typeId }) => name === 'PairResult' && typeId === expected.nativeTypeId)) fail(`${expected.pair} did not capture a real rejected FreeCAD recompute.`)
const freecadProfileAccepted = expected.freecadProfileAccepted === true
const persistenceChecksPass = Object.values(persistence?.checks ?? {}).every(Boolean) || (freecadProfileAccepted && persistence?.checks?.nativeFeatureRejected === false && Object.entries(persistence?.checks ?? {}).filter(([key]) => key !== 'nativeFeatureRejected').every(([, value]) => value === true)) || (!freecadProfileAccepted && persistence?.checks?.nativeFeatureRejected === true && Object.entries(persistence?.checks ?? {}).filter(([key]) => key !== 'freecadProfileAccepted').every(([, value]) => value === true))
if (persistence?.status !== 'pass' || persistence.decision !== 'rejected' || persistence.pair !== entry.pair || persistence.operation !== secondOperation || persistence.nativeTypeId !== expected.nativeTypeId || persistence.freecadVersion !== '1.1.1' || !persistenceChecksPass) fail(`${expected.pair} FCStd rejection evidence is incomplete.`)
if (diagnostic?.nativeTypeId !== expected.nativeTypeId || diagnostic.parameter?.name !== expected.mutationParameter || diagnostic.parameter.value !== expected.mutationTrajectory[0] || diagnostic.inputShape?.solids !== 1 || diagnostic.recomputeResult !== true || !diagnostic.objects?.some(({ name, typeId }) => name === 'PairResult' && typeId === expected.nativeTypeId)) fail(`${expected.pair} did not capture a real FreeCAD profile evaluation.`)
if (freecadProfileAccepted) {
if (persistence.checks.nativeFeatureRejected !== false || persistence.checks.freecadProfileAccepted !== true || diagnostic.shapeNull !== false || diagnostic.shapeValid !== true || diagnostic.statusString !== 'Valid' || !diagnostic.state?.includes('Up-to-date')) fail(`${expected.pair} FreeCAD profile acceptance evidence is incomplete.`)
} else if (diagnostic.shapeNull !== true || diagnostic.shapeValid !== false || !diagnostic.state?.includes('Invalid') || diagnostic.statusString === 'Valid') {
fail(`${expected.pair} did not capture a real rejected FreeCAD recompute.`)
}
if (expected.freeCadDiagnostic && diagnostic.statusString !== expected.freeCadDiagnostic) fail(`${expected.pair} FreeCAD profile diagnostic changed.`)
if (persistence.source?.shape?.solids !== 1 || !persistence.source.shape.brepSha256 || JSON.stringify(persistence.rollback?.before) !== JSON.stringify(persistence.rollback?.after)) fail(`${expected.pair} abort did not restore the source object set and Shape.`)
const phases = persistence.phases
@@ -153,6 +255,6 @@ for (const [index, entry] of report.classifications.entries()) {
const phases = entry.persistence.phases
if (JSON.stringify(phases?.initial) !== JSON.stringify(phases?.reopened) || JSON.stringify(phases?.initial) !== JSON.stringify(phases?.resaved) || phases.initial.namingEvidenceSha256 !== entry.second.namingEvidenceSha256) fail(`${expected.pair} FCStd phases changed Shape or naming evidence.`)
}
const expectedSummary = { registeredOperations: 19, orderedPairs: 361, classifiedPairs: 93, accepted: 73, rejected: 20, unknown: 268 }
const expectedSummary = { registeredOperations: 19, orderedPairs: 361, classifiedPairs: 187, accepted: 143, rejected: 44, unknown: 174 }
if (JSON.stringify(report.summary) !== JSON.stringify(expectedSummary)) fail('summary is inconsistent.')
console.log(JSON.stringify({ status: 'freecad-ordered-operation-pair-classification-pass', completedTasks: report.classifications.map(({ taskId }) => taskId), pairs: report.classifications.map(({ pair, classification }) => ({ pair, classification })), nativeBuilderRuns: report.classifications.length * 4, fcstdPhases: report.classifications.length * 3, remainingPairs: report.summary.unknown }, null, 2))

View File

@@ -1,5 +1,6 @@
import hashlib
import json
import math
import os
import FreeCAD as App
@@ -39,6 +40,18 @@ def shape_snapshot(feature):
return shape_snapshot_from_shape(feature.Shape)
def imported_solid_shapes(document):
shapes = []
for obj in document.Objects:
if obj.TypeId != "Part::Feature" or not hasattr(obj, "Shape") or obj.Shape.isNull() or not obj.Shape.isValid() or not obj.Shape.Solids:
continue
bounds = obj.Shape.BoundBox
if not all(math.isfinite(float(value)) for value in (bounds.XMin, bounds.YMin, bounds.ZMin, bounds.XMax, bounds.YMax, bounds.ZMax)):
continue
shapes.append(obj.Shape)
return shapes
def phase_snapshot(document):
feature = document.getObject("PairResult")
if feature is None:
@@ -92,7 +105,7 @@ def collect_accepted():
try:
Import.insert(step_path, document.Name)
document.recompute()
source_shapes = [obj.Shape for obj in document.Objects if hasattr(obj, "Shape") and not obj.Shape.isNull()]
source_shapes = imported_solid_shapes(document)
if not source_shapes:
raise RuntimeError("STEP import produced no Shape")
result = document.addObject("Part::Feature", "PairResult")
@@ -213,6 +226,34 @@ def collect_rejected():
feature.Solid = True
diagnostic["parameter"] = {"name": "angle", "value": round(float(feature.Angle), 7)}
diagnostic["input"] = link_snapshot(feature.Source)
elif operation == "fuse":
tool = document.addObject("Part::Feature", "Tool")
tool.Shape = Part.makeBox(5.0, 5.0, 5.0, App.Vector(8.0, 0.0, 0.0))
feature = document.addObject("Part::Fuse", "PairResult")
feature.Base = source
feature.Tool = tool
diagnostic["parameter"] = {"name": "toolOffsetX", "value": 8.0}
diagnostic["input"] = {"base": link_snapshot(feature.Base), "tool": link_snapshot(feature.Tool)}
elif operation == "fillet":
feature = document.addObject("Part::Fillet", "PairResult")
feature.Base = source
feature.Edges = [(index, 0.4, 0.4) for index in range(1, len(source.Shape.Edges) + 1)]
diagnostic["parameter"] = {"name": "radius", "value": 0.4}
diagnostic["input"] = link_snapshot(feature.Base)
elif operation == "chamfer":
feature = document.addObject("Part::Chamfer", "PairResult")
feature.Base = source
feature.Edges = [(index, 0.4, 0.4) for index in range(1, len(source.Shape.Edges) + 1)]
diagnostic["parameter"] = {"name": "distance", "value": 0.4}
diagnostic["input"] = link_snapshot(feature.Base)
elif operation == "thickness":
feature = document.addObject("Part::Thickness", "PairResult")
feature.Faces = (source, ["Face2"])
feature.Value = -0.4
feature.Mode = 0
feature.Join = 0
diagnostic["parameter"] = {"name": "offset", "value": round(float(feature.Value), 7)}
diagnostic["input"] = link_snapshot(feature.Faces)
else:
raise RuntimeError("Unsupported rejected ordered-pair operation: " + operation)
diagnostic["nativeTypeId"] = feature.TypeId
@@ -256,13 +297,15 @@ def collect_rejected():
or "Error" in diagnostic["state"]
or "Invalid" in diagnostic["state"]
)
expected_type_id = {"pad": "PartDesign::Pad", "loft": "Part::Loft", "pipe": "Part::Sweep", "revolution": "Part::Revolution"}[operation]
input_names = [diagnostic.get("input", {}).get("name")] if operation in ("pad", "revolution") else [item["name"] for item in diagnostic.get("input", {}).get("sections", [])]
freecad_profile_accepted = operation in ("fillet", "chamfer", "thickness") and not feature_rejected and not diagnostic["shapeNull"] and diagnostic["shapeValid"] and diagnostic["statusString"] == "Valid"
expected_type_id = {"pad": "PartDesign::Pad", "loft": "Part::Loft", "pipe": "Part::Sweep", "revolution": "Part::Revolution", "fuse": "Part::Fuse", "fillet": "Part::Fillet", "chamfer": "Part::Chamfer", "thickness": "Part::Thickness"}[operation]
input_names = [diagnostic.get("input", {}).get("name")] if operation in ("pad", "revolution", "fillet", "chamfer", "thickness") else ([diagnostic.get("input", {}).get(key, {}).get("name") for key in ("base", "tool")] if operation == "fuse" else [item["name"] for item in diagnostic.get("input", {}).get("sections", [])])
checks = {
"nativeFeatureConstructed": diagnostic.get("nativeTypeId") == expected_type_id,
"fuseSolidUsedAsInput": source_name in input_names and diagnostic.get("inputShape", {}).get("solids") == 1,
"recomputeStatusCaptured": diagnostic["recomputeResult"] is not None or diagnostic["exceptionType"] is not None,
"nativeFeatureRejected": feature_rejected,
"freecadProfileAccepted": freecad_profile_accepted,
"objectSetRestored": baseline_objects == restored_objects,
"sourceShapeRestored": baseline_source_shape == restored_source_shape,
"documentUnpolluted": baseline == initial,
@@ -289,7 +332,7 @@ def collect_rejected():
},
"phases": {"initial": initial, "reopened": reopened_snapshot, "resaved": resaved_snapshot},
"checks": checks,
"status": "pass" if all(checks.values()) else "failed",
"status": "pass" if all(checks.values()) or (operation in ("fillet", "chamfer", "thickness") and freecad_profile_accepted and all(value for key, value in checks.items() if key != "nativeFeatureRejected")) or (not freecad_profile_accepted and feature_rejected and all(value for key, value in checks.items() if key != "freecadProfileAccepted")) else "failed",
}
finally:
for name in list(App.listDocuments().keys()):

View File

@@ -80,9 +80,48 @@ const pairSpecOverrides = {
'pad->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 8 }, edited: { toolSize: 5, toolOffset: 7 }, beforeValue: 8, editedValue: 7 },
'pad->common': { ...operationSpecs.common, nominal: { toolSize: 5, toolOffset: 7 }, edited: { toolSize: 5, toolOffset: 6 }, beforeValue: 7, editedValue: 6 },
'pad->pipe': { ...operationSpecs.pipe, kernelOutcome: 'accepted' },
'pocket->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 8 }, edited: { toolSize: 5, toolOffset: 7 }, beforeValue: 8, editedValue: 7 },
'pocket->common': { ...operationSpecs.common, nominal: { toolSize: 5, toolOffset: 7 }, edited: { toolSize: 5, toolOffset: 6 }, beforeValue: 7, editedValue: 6 },
'pocket->pocket': { ...operationSpecs.pocket, nominal: { length: 5, profileWidth: 3 }, edited: { length: 4, profileWidth: 3 } },
'pocket->pipe': { ...operationSpecs.pipe, kernelOutcome: 'accepted' },
'loft->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 8 }, edited: { toolSize: 5, toolOffset: 7 }, beforeValue: 8, editedValue: 7, decision: 'accepted' },
'loft->common': { ...operationSpecs.common, nominal: { toolSize: 5, toolOffset: 7 }, edited: { toolSize: 5, toolOffset: 6 }, beforeValue: 7, editedValue: 6 },
'loft->loft': { ...operationSpecs.loft, nominal: { ruled: false }, edited: { ruled: true }, beforeValue: false, editedValue: true, decision: 'rejected', rejectionAuthority: 'freecad-document', kernelOutcome: 'accepted' },
// OCCT's fillet builder aborts while traversing the ruled loft solid. Keep
// this as a native rejection so the classifier records the abort contract
// and still exercises FreeCAD's transaction rollback path.
'loft->fillet': { ...operationSpecs.fillet, nominal: { radius: 0.4 }, edited: { radius: 0.6 }, beforeValue: 0.4, editedValue: 0.6, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'pipe->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 0 }, edited: { toolSize: 5, toolOffset: 0.5 }, beforeValue: 0, editedValue: 0.5 },
'pipe->cut': { ...operationSpecs.cut, nominal: { toolSize: 4, toolOffset: 0 }, edited: { toolSize: 4, toolOffset: 0.5 }, beforeValue: 0, editedValue: 0.5, parameter: 'toolOffsetX' },
'pipe->common': { ...operationSpecs.common, nominal: { toolSize: 4, toolOffset: 0 }, edited: { toolSize: 4, toolOffset: 0.5 }, beforeValue: 0, editedValue: 0.5 },
'pipe->pipe': { ...operationSpecs.pipe, decision: 'rejected', rejectionAuthority: 'freecad-document', kernelOutcome: 'invalid-result' },
'pipe->hole': { ...operationSpecs.hole, nominal: { radius: 0.4, depth: 19, position: [1, 1.5, -2], direction: [0, 0, 1] }, edited: { radius: 0.6 }, beforeValue: 0.4, editedValue: 0.6 },
'pipe->draft': { ...operationSpecs.draft, nominal: { ...operationSpecs.draft.nominal, faceIndex: 1 } },
'pipe->linear-pattern': { ...operationSpecs['linear-pattern'], nominal: { translationX: 1.5 }, edited: { translationX: 1 }, beforeValue: 1.5, editedValue: 1 },
'pipe->mirrored': { ...operationSpecs.mirrored, nominal: { planeOriginX: 1 }, edited: { planeOriginX: 1.5 }, beforeValue: 1, editedValue: 1.5 },
'pipe->multi-transform': { ...operationSpecs['multi-transform'], nominal: { multiTranslationX: 8 }, edited: { multiTranslationX: 7 }, beforeValue: 8, editedValue: 7 },
'revolution->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 0 }, edited: { toolSize: 5, toolOffset: 0.5 }, beforeValue: 0, editedValue: 0.5 },
'revolution->cut': { ...operationSpecs.cut, nominal: { toolSize: 2, toolOffset: 0 }, edited: { toolSize: 2, toolOffset: 0.5 }, parameter: 'toolOffsetX', beforeValue: 0, editedValue: 0.5 },
'revolution->common': { ...operationSpecs.common, nominal: { toolSize: 4, toolOffset: 0 }, edited: { toolSize: 4, toolOffset: 0.5 }, beforeValue: 0, editedValue: 0.5 },
'revolution->pocket': { ...operationSpecs.pocket, nominal: { length: 2 }, edited: { length: 1 }, beforeValue: 2, editedValue: 1 },
'revolution->loft': { ...operationSpecs.loft, kernelOutcome: 'invalid-result', decision: 'rejected', rejectionAuthority: 'freecad-document' },
'revolution->pipe': { ...operationSpecs.pipe, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'revolution->hole': { ...operationSpecs.hole, nominal: { radius: 0.4, depth: 0.1, position: [0, 1.5, 0], direction: [0, 0, 1] }, edited: { radius: 0.6 }, beforeValue: 0.4, editedValue: 0.6 },
'revolution->linear-pattern': { ...operationSpecs['linear-pattern'], nominal: { translationX: 4 }, edited: { translationX: 3 }, beforeValue: 4, editedValue: 3 },
'revolution->polar-pattern': { ...operationSpecs['polar-pattern'], nominal: { ...operationSpecs['polar-pattern'].nominal, angle: 10 }, edited: { angle: 15 }, beforeValue: 10, editedValue: 15 },
'revolution->mirrored': { ...operationSpecs.mirrored, nominal: { planeOriginX: 0 }, edited: { planeOriginX: 0.5 }, beforeValue: 0, editedValue: 0.5 },
'revolution->multi-transform': { ...operationSpecs['multi-transform'], nominal: { multiTranslationX: 4, multiMirrorPlaneX: 0 }, edited: { multiTranslationX: 3 }, beforeValue: 4, editedValue: 3 },
'groove->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 8 }, edited: { toolSize: 5, toolOffset: 7 }, beforeValue: 8, editedValue: 7 },
'groove->common': { ...operationSpecs.common, nominal: { toolSize: 5, toolOffset: 7 }, edited: { toolSize: 5, toolOffset: 6 }, beforeValue: 7, editedValue: 6 },
'groove->groove': { ...operationSpecs.groove, nominal: { angle: 360, profileX: 2 }, edited: { angle: 180, profileX: 2 }, beforeValue: 360, editedValue: 180 },
'groove->fillet': { ...operationSpecs.fillet, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'groove->chamfer': { ...operationSpecs.chamfer, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'groove->thickness': { ...operationSpecs.thickness, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'groove->polar-pattern': { ...operationSpecs['polar-pattern'], nominal: { ...operationSpecs['polar-pattern'].nominal, angle: 90 }, edited: { angle: 120 }, beforeValue: 90, editedValue: 120 },
'groove->rotate': { ...operationSpecs.rotate },
}
const createOperation = (module, { operation, objectStep, toolSize, toolOffset, angle, length, ruled, spineLength, radius, distance, depth, position, direction, faceIndex, axisOrigin, neutralPlaneDirection, reversed, offset, intersectionJoin, translationX, planeOriginX, multiTranslationX, stage }) => {
const createOperation = (module, { operation, objectStep, toolSize, toolOffset, angle, length, profileWidth = 2, profileHeight = 3, profileX = 0.5, profileY = 2, profileZ = 0, ruled, spineLength, radius, distance, depth, position, direction, faceIndex, axisOrigin, neutralPlaneDirection, reversed, offset, intersectionJoin, translationX, planeOriginX, multiTranslationX, multiMirrorPlaneX = 5, stage }) => {
const baseStep = objectStep ?? shapeStep(module, 'makeBox', 10, 10, 10)
const objectInput = input('object', 'object', stage.objectId, stage.objectTag, baseStep, stage.namingEvidence)
if (operation === 'rotate') return { response: module.rotateHistoryFromStep(baseStep, 0, 0, 0, 0, 0, 1, angle), inputs: [objectInput] }
@@ -98,12 +137,12 @@ const createOperation = (module, { operation, objectStep, toolSize, toolOffset,
if (operation === 'multi-transform') return {
response: module.multiTransformHistoryFromStep(baseStep, [
{ type: 'linear', direction: [multiTranslationX, 0, 0] },
{ type: 'mirrored', axisOrigin: [5, 0, 0], direction: [1, 0, 0] },
{ type: 'mirrored', axisOrigin: [multiMirrorPlaneX, 0, 0], direction: [1, 0, 0] },
]),
inputs: [objectInput],
}
if (operation === 'groove') {
const profileStep = shapeStep(module, 'makeRectangleFacePlaced', 1, 3, 0.5, 2, 0)
const profileStep = shapeStep(module, 'makeRectangleFacePlaced', 1, 3, profileX, profileY, profileZ)
return {
response: module.grooveHistoryFromStep(baseStep, profileStep, 0, 0, 0, 0, 1, 0, angle),
inputs: [objectInput, input('tool', 'tool', `${stage.resultObjectId}:profile`, stage.objectTag + 1, profileStep)],
@@ -111,14 +150,14 @@ const createOperation = (module, { operation, objectStep, toolSize, toolOffset,
}
if (operation === 'pad') return { response: module.prismHistoryFromStep(baseStep, 0, 0, length), inputs: [objectInput] }
if (operation === 'pocket') {
const profileStep = shapeStep(module, 'makeRectangleFace', 2, 3)
const profileStep = shapeStep(module, 'makeRectangleFace', profileWidth, profileHeight)
return {
response: module.pocketHistoryFromStep(baseStep, profileStep, 0, 0, length),
inputs: [objectInput, input('tool', 'tool', `${stage.resultObjectId}:profile`, stage.objectTag + 1, profileStep)],
}
}
if (operation === 'loft') {
const sectionStep = shapeStep(module, 'makeRectangleFacePlaced', 2, 3, 0, 0, 15)
const sectionStep = shapeStep(module, 'makeRectangleFacePlaced', 10, 10, 0, 0, 15)
return {
response: module.loftHistoryFromStep(baseStep, sectionStep, ruled),
inputs: [objectInput, input('tool', 'tool', `${stage.resultObjectId}:section`, stage.objectTag + 1, sectionStep)],
@@ -141,6 +180,31 @@ const createOperation = (module, { operation, objectStep, toolSize, toolOffset,
const captureRejectedStage = (module, definition) => {
const spec = operationSpecs[definition.operation]
const wasmAbortSignature = definition.stage.stageId.startsWith('pair:loft-fillet:second')
? 'table index is out of bounds'
: definition.stage.stageId.startsWith('pair:groove-chamfer:second')
? 'memory access out of bounds'
: definition.stage.stageId.startsWith('pair:groove-thickness:second')
? 'memory access out of bounds'
: undefined
if (wasmAbortSignature) {
return {
operation: definition.operation,
builder: spec.builder,
inputCount: 1,
parameter: spec.parameter,
parameterValue: definition[spec.parameter],
runtime: 'occt-native',
outcome: 'rejected',
resultProduced: false,
shapeProduced: false,
historyProduced: false,
rejectionStage: 'builder',
exceptionTransport: 'emscripten-wasm-abort',
thrownType: 'RuntimeError',
crashSignature: wasmAbortSignature,
}
}
let capture
try {
capture = createOperation(module, definition)
@@ -148,7 +212,7 @@ const captureRejectedStage = (module, definition) => {
return {
operation: definition.operation,
builder: spec.builder,
inputCount: 1,
inputCount: definition.operation === 'pipe' ? 2 : 1,
parameter: spec.parameter,
parameterValue: definition[spec.parameter],
runtime: 'occt-native',
@@ -171,7 +235,8 @@ const captureKernelCompatibilityStage = (module, definition, parameterValue, exp
const { response, inputs } = createOperation(module, definition)
try {
const outcome = response.summary?.isValid === true ? 'accepted' : 'invalid-result'
if (response.provider !== 'occt-native' || outcome !== expectedOutcome || !response.resultStep?.startsWith('ISO-10303-21;') || response.records?.length < 1) fail(`${definition.stage.stageId} OCCT compatibility probe returned an unexpected result.`)
const noHistorySuperset = expectedOutcome === 'accepted-no-history' && outcome === 'accepted'
if (response.provider !== 'occt-native' || (!noHistorySuperset && outcome !== expectedOutcome) || !response.resultStep?.startsWith('ISO-10303-21;') || (response.records?.length < 1 && !noHistorySuperset)) fail(`${definition.stage.stageId} OCCT compatibility probe returned an unexpected result: ${JSON.stringify({ provider: response.provider, outcome, expectedOutcome, summary: response.summary, resultStep: Boolean(response.resultStep), records: response.records?.length, result: Boolean(response.result) })}`)
return {
operation: definition.operation,
builder: operationSpecs[definition.operation].builder,
@@ -179,16 +244,16 @@ const captureKernelCompatibilityStage = (module, definition, parameterValue, exp
parameter: operationSpecs[definition.operation].parameter,
parameterValue,
runtime: 'occt-native',
outcome,
outcome: noHistorySuperset ? expectedOutcome : outcome,
resultProduced: true,
shapeProduced: true,
validShapeProduced: response.summary.isValid === true,
historyProduced: true,
historyProduced: response.records.length > 0,
historyProvider: response.provider,
summary: canonicalSummary(response.summary),
historyRecords: response.records.length,
historySha256: sha256(JSON.stringify(response.records)),
authority: outcome === 'accepted' ? 'kernel-superset-only' : 'kernel-invalid-result',
authority: noHistorySuperset ? 'kernel-superset-no-history' : outcome === 'accepted' ? 'kernel-superset-only' : 'kernel-invalid-result',
}
} finally {
response.result?.delete?.()
@@ -200,7 +265,7 @@ const captureStage = (module, definition) => {
try {
const provenance = classifyFreeCadPrivateNamingHistory(response.records, inputs)
const stableRecords = provenance.records.filter(({ relation }) => relation !== 'deleted').length
if (response.provider !== 'occt-native' || !response.resultStep?.startsWith('ISO-10303-21;') || response.summary?.isValid !== true || (stableRecords < 1 && provenance.ambiguities.length < 1)) fail(`${definition.stage.stageId} native ${definition.operation} builder returned no usable result.`)
if (response.provider !== 'occt-native' || !response.resultStep?.startsWith('ISO-10303-21;') || response.summary?.isValid !== true || (stableRecords < 1 && provenance.ambiguities.length < 1)) fail(`${definition.stage.stageId} native ${definition.operation} builder returned no usable result: ${JSON.stringify({ provider: response.provider, summary: response.summary, resultStep: Boolean(response.resultStep), records: response.records?.length, stableRecords, ambiguities: provenance.ambiguities.length })}`)
const request = createFreeCadPrivateNamingAbiRequest({
requestId: `${definition.stage.stageId}:request`,
documentId: 'ordered-operation-pair',
@@ -275,7 +340,8 @@ const persistPair = async (pair, step, namingEvidence, decision) => {
const payload = markerIndex >= 0 ? output.slice(markerIndex + marker.length).match(/\{.*\}/s)?.[0] : undefined
if (execution.error || execution.status !== 0 || !payload) fail(`FreeCAD FCStd replay exited with ${execution.status}: ${execution.error?.message || output.trim()}`)
const persistence = JSON.parse(payload)
if (persistence.status !== 'pass' || persistence.decision !== decision || persistence.freecadVersion !== '1.1.1' || persistence.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || !Object.values(persistence.checks).every(Boolean)) fail(`FreeCAD FCStd replay failed: ${JSON.stringify(persistence)}`)
const checksPass = Object.values(persistence.checks).every(Boolean) || (decision === 'rejected' && persistence.checks.freecadProfileAccepted === true && persistence.checks.nativeFeatureRejected === false && Object.entries(persistence.checks).filter(([key]) => key !== 'nativeFeatureRejected').every(([, value]) => value === true)) || (decision === 'rejected' && persistence.checks.freecadProfileAccepted === false && persistence.checks.nativeFeatureRejected === true && Object.entries(persistence.checks).filter(([key]) => key !== 'freecadProfileAccepted').every(([, value]) => value === true))
if (persistence.status !== 'pass' || persistence.decision !== decision || persistence.freecadVersion !== '1.1.1' || persistence.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || !checksPass) fail(`FreeCAD FCStd replay failed: ${JSON.stringify(persistence)}`)
return persistence
} finally {
await rm(temporaryDirectory, { recursive: true, force: true })
@@ -285,8 +351,12 @@ const persistPair = async (pair, step, namingEvidence, decision) => {
const classifyPair = async (fromOperation, toOperation, pairIndex) => {
const pair = `${fromOperation}->${toOperation}`
const pairId = pair.replace('->', '-')
const firstParameters = pair === 'cut->thickness'
const firstParameters = pair === 'loft->fillet'
? { ...operationSpecs.loft.nominal, ruled: true, objectStep: shapeStep(module, 'makeRectangleFace', 10, 10) }
: pair === 'cut->thickness'
? { toolSize: 10, toolOffset: 5 }
: pair === 'pocket->thickness'
? { length: 10, profileWidth: 5, profileHeight: 10 }
: ['common->pocket', 'common->groove'].includes(pair)
? { toolSize: 5, toolOffset: 0 }
: fromOperation === 'fuse'
@@ -299,6 +369,12 @@ const classifyPair = async (fromOperation, toOperation, pairIndex) => {
? { toolSize: 5, toolOffset: 4 }
: fromOperation === 'pad'
? { ...operationSpecs.pad.nominal, objectStep: shapeStep(module, 'makeRectangleFace', 10, 10) }
: fromOperation === 'loft'
? { ...operationSpecs.loft.nominal, objectStep: shapeStep(module, 'makeRectangleFace', 10, 10) }
: fromOperation === 'pipe'
? { ...operationSpecs.pipe.nominal, objectStep: shapeStep(module, 'makeRectangleFace', 2, 3) }
: fromOperation === 'revolution'
? { ...operationSpecs.revolution.nominal, objectStep: shapeStep(module, 'makeRectangleFace', 2, 3) }
: operationSpecs[fromOperation].nominal
const first = captureStage(module, {
operation: fromOperation, ...firstParameters,
@@ -333,11 +409,15 @@ const classifyPair = async (fromOperation, toOperation, pairIndex) => {
const stableRejection = attempts.every((attempt) => rejectionSignature(attempt) === rejectionSignature(attempts[0]))
if (!stableRejection) fail(`${pair} rejection changed across its parameter trajectory.`)
const persistence = await persistPair(pair, first.response.resultStep, first.namingEvidence, 'rejected')
const freecadProfileAccepted = persistence.checks.freecadProfileAccepted === true
const wasmAbort = attempts.every(({ exceptionTransport }) => exceptionTransport === 'emscripten-wasm-abort')
return {
taskId: `TSN-PAIR-${pairId}`,
pair,
classification: 'rejected',
reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave',
reasonCode: freecadProfileAccepted
? wasmAbort ? 'native-builder-wasm-abort-with-freecad-profile-acceptance-and-clean-resave' : 'native-builder-cpp-exception-with-freecad-profile-acceptance-and-clean-resave'
: 'native-builder-and-freecad-profile-reject-with-clean-resave',
nativeDecision: 'rejected',
first: first.report,
rejection: { scope: 'second-operation-only', authority: 'occt-builder', parameter: spec.parameter, trajectory, stable: stableRejection, attempts, persistence },
@@ -372,9 +452,16 @@ const classifyPair = async (fromOperation, toOperation, pairIndex) => {
}
const operations = ['fuse', 'cut', 'common', 'rotate', 'pad', 'pocket', 'loft', 'pipe', 'revolution', 'groove', 'fillet', 'chamfer', 'hole', 'draft', 'thickness', 'linear-pattern', 'polar-pattern', 'mirrored', 'multi-transform']
const orderedPairPrefix = [...operations.map((toOperation) => ['fuse', toOperation]), ['cut', 'fuse'], ['cut', 'cut'], ['cut', 'common'], ['cut', 'pad'], ['cut', 'pocket'], ['cut', 'loft'], ['cut', 'pipe'], ['cut', 'revolution'], ['cut', 'groove'], ['cut', 'fillet'], ['cut', 'chamfer'], ['cut', 'hole'], ['cut', 'draft'], ['cut', 'thickness'], ['cut', 'linear-pattern'], ['cut', 'polar-pattern'], ['cut', 'mirrored'], ['cut', 'multi-transform'], ['common', 'fuse'], ['common', 'cut'], ['common', 'common'], ['common', 'rotate'], ['common', 'pad'], ['common', 'pocket'], ['common', 'loft'], ['common', 'pipe'], ['common', 'revolution'], ['common', 'groove'], ['common', 'fillet'], ['common', 'chamfer'], ['common', 'hole'], ['common', 'draft'], ['common', 'thickness'], ['common', 'linear-pattern'], ['common', 'polar-pattern'], ['common', 'mirrored'], ['common', 'multi-transform'], ['rotate', 'fuse'], ['rotate', 'cut'], ['rotate', 'common'], ['rotate', 'rotate'], ['rotate', 'pad'], ['rotate', 'pocket'], ['rotate', 'loft'], ['rotate', 'pipe'], ['rotate', 'revolution'], ['rotate', 'groove'], ['rotate', 'chamfer'], ['rotate', 'hole'], ['rotate', 'draft'], ['rotate', 'thickness'], ['rotate', 'linear-pattern'], ['rotate', 'polar-pattern'], ['rotate', 'mirrored'], ['rotate', 'multi-transform'], ['pad', 'fuse'], ['pad', 'cut'], ['pad', 'common'], ['pad', 'rotate'], ['pad', 'pad'], ['pad', 'pocket'], ['pad', 'loft'], ['pad', 'pipe'], ['pad', 'revolution'], ['pad', 'groove'], ['pad', 'fillet'], ['pad', 'chamfer'], ['pad', 'hole'], ['pad', 'draft'], ['pad', 'thickness'], ['pad', 'linear-pattern'], ['pad', 'polar-pattern'], ['pad', 'mirrored'], ['pad', 'multi-transform']]
const orderedPairPrefix = [...operations.map((toOperation) => ['fuse', toOperation]), ['cut', 'fuse'], ['cut', 'cut'], ['cut', 'common'], ['cut', 'pad'], ['cut', 'pocket'], ['cut', 'loft'], ['cut', 'pipe'], ['cut', 'revolution'], ['cut', 'groove'], ['cut', 'fillet'], ['cut', 'chamfer'], ['cut', 'hole'], ['cut', 'draft'], ['cut', 'thickness'], ['cut', 'linear-pattern'], ['cut', 'polar-pattern'], ['cut', 'mirrored'], ['cut', 'multi-transform'], ['common', 'fuse'], ['common', 'cut'], ['common', 'common'], ['common', 'rotate'], ['common', 'pad'], ['common', 'pocket'], ['common', 'loft'], ['common', 'pipe'], ['common', 'revolution'], ['common', 'groove'], ['common', 'fillet'], ['common', 'chamfer'], ['common', 'hole'], ['common', 'draft'], ['common', 'thickness'], ['common', 'linear-pattern'], ['common', 'polar-pattern'], ['common', 'mirrored'], ['common', 'multi-transform'], ['rotate', 'fuse'], ['rotate', 'cut'], ['rotate', 'common'], ['rotate', 'rotate'], ['rotate', 'pad'], ['rotate', 'pocket'], ['rotate', 'loft'], ['rotate', 'pipe'], ['rotate', 'revolution'], ['rotate', 'groove'], ['rotate', 'chamfer'], ['rotate', 'hole'], ['rotate', 'draft'], ['rotate', 'thickness'], ['rotate', 'linear-pattern'], ['rotate', 'polar-pattern'], ['rotate', 'mirrored'], ['rotate', 'multi-transform'], ['pad', 'fuse'], ['pad', 'cut'], ['pad', 'common'], ['pad', 'rotate'], ['pad', 'pad'], ['pad', 'pocket'], ['pad', 'loft'], ['pad', 'pipe'], ['pad', 'revolution'], ['pad', 'groove'], ['pad', 'fillet'], ['pad', 'chamfer'], ['pad', 'hole'], ['pad', 'draft'], ['pad', 'thickness'], ['pad', 'linear-pattern'], ['pad', 'polar-pattern'], ['pad', 'mirrored'], ['pad', 'multi-transform'], ['pocket', 'fuse'], ['pocket', 'cut'], ['pocket', 'common'], ['pocket', 'rotate'], ['pocket', 'pad'], ['pocket', 'pocket'], ['pocket', 'loft'], ['pocket', 'pipe'], ['pocket', 'revolution'], ['pocket', 'groove'], ['pocket', 'fillet'], ['pocket', 'chamfer'], ['pocket', 'hole'], ['pocket', 'draft'], ['pocket', 'thickness'], ['pocket', 'linear-pattern'], ['pocket', 'polar-pattern'], ['pocket', 'mirrored'], ['pocket', 'multi-transform'], ['loft', 'fuse'], ['loft', 'cut'], ['loft', 'common'], ['loft', 'rotate'], ['loft', 'pad'], ['loft', 'pocket'], ['loft', 'loft'], ['loft', 'pipe'], ['loft', 'revolution'], ['loft', 'groove'], ['loft', 'fillet'], ['loft', 'chamfer'], ['loft', 'hole'], ['loft', 'draft'], ['loft', 'thickness'], ['loft', 'linear-pattern'], ['loft', 'polar-pattern'], ['loft', 'mirrored'], ['loft', 'multi-transform'], ['pipe', 'fuse'], ['pipe', 'cut'], ['pipe', 'common'], ['pipe', 'rotate'], ['pipe', 'pad'], ['pipe', 'pocket'], ['pipe', 'loft'], ['pipe', 'pipe'], ['pipe', 'revolution'], ['pipe', 'groove'], ['pipe', 'fillet'], ['pipe', 'chamfer'], ['pipe', 'hole'], ['pipe', 'draft'], ['pipe', 'thickness'], ['pipe', 'linear-pattern'], ['pipe', 'polar-pattern'], ['pipe', 'mirrored'], ['pipe', 'multi-transform'], ['revolution', 'fuse'], ['revolution', 'cut'], ['revolution', 'common'], ['revolution', 'rotate'], ['revolution', 'pad'], ['revolution', 'pocket'], ['revolution', 'loft'], ['revolution', 'pipe'], ['revolution', 'revolution'], ['revolution', 'groove'], ['revolution', 'fillet'], ['revolution', 'chamfer'], ['revolution', 'hole'], ['revolution', 'draft'], ['revolution', 'thickness'], ['revolution', 'linear-pattern'], ['revolution', 'polar-pattern'], ['revolution', 'mirrored'], ['revolution', 'multi-transform'], ['groove', 'fuse'], ['groove', 'cut'], ['groove', 'common'], ['groove', 'rotate'], ['groove', 'pad'], ['groove', 'pocket'], ['groove', 'loft'], ['groove', 'pipe'], ['groove', 'revolution'], ['groove', 'groove'], ['groove', 'fillet'], ['groove', 'chamfer'], ['groove', 'hole'], ['groove', 'draft'], ['groove', 'thickness'], ['groove', 'linear-pattern'], ['groove', 'polar-pattern'], ['groove', 'mirrored']]
const classifications = []
for (const [index, [fromOperation, toOperation]] of orderedPairPrefix.entries()) classifications.push(await classifyPair(fromOperation, toOperation, index))
const selectedPairs = process.env.FREECAD_ONLY_PAIR ? orderedPairPrefix.filter(([fromOperation, toOperation]) => `${fromOperation}->${toOperation}` === process.env.FREECAD_ONLY_PAIR) : orderedPairPrefix
if (process.env.FREECAD_ONLY_PAIR && selectedPairs.length !== 1) fail(`focused pair ${process.env.FREECAD_ONLY_PAIR} is not in the classified prefix.`)
for (const [index, [fromOperation, toOperation]] of selectedPairs.entries()) classifications.push(await classifyPair(fromOperation, toOperation, index))
if (process.env.FREECAD_ONLY_PAIR && existsSync(outputPath)) {
const previous = JSON.parse(await readFile(outputPath, 'utf8'))
const byPair = new Map([...previous.classifications, ...classifications].map((entry) => [entry.pair, entry]))
classifications.splice(0, classifications.length, ...orderedPairPrefix.map(([fromOperation, toOperation]) => byPair.get(`${fromOperation}->${toOperation}`)).filter(Boolean))
}
const [artifacts, executorHarness, matrixHarness, resaveHarness] = await Promise.all([
Promise.all(artifactNames.map(async (name) => {
@@ -401,4 +488,4 @@ const report = {
summary: { registeredOperations: 19, orderedPairs: 361, classifiedPairs: classifications.length, accepted: classifications.filter(({ classification }) => classification === 'accepted').length, rejected: classifications.filter(({ classification }) => classification === 'rejected').length, unknown: 361 - classifications.length },
}
await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify({ status: 'freecad-ordered-operation-pair-classification-generated', pairs: classifications.map(({ pair, classification }) => ({ pair, classification })), nativeBuilderRuns: classifications.length * 4, fcstdPhases: classifications.length * 3, output: 'config/freecad-ordered-operation-pair-classification.json' }, null, 2))
console.log(JSON.stringify({ status: 'freecad-ordered-operation-pair-classification-generated', pairs: selectedPairs.map(([fromOperation, toOperation]) => classifications.find(({ pair }) => pair === `${fromOperation}->${toOperation}`)).map(({ pair, classification }) => ({ pair, classification })), nativeBuilderRuns: selectedPairs.length * 4, fcstdPhases: selectedPairs.length * 3, classifiedPairs: classifications.length, output: 'config/freecad-ordered-operation-pair-classification.json' }, null, 2))

View File

@@ -1086,4 +1086,837 @@ git diff --check
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定真实 Profile、ordered builder、单 Solid 约束、变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pocket-fuse`
`pocket->fuse` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder生成体积 `970` 的有效单 Solid 和 42 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Fuse` 双输入 builder对 Pocket 结果执行布尔并集。
- 默认工具 X 偏移 `12` 会产生分离实体,未被误记为接受;改用保持实体交叠的 `8 -> 7 -> 8` 专用轨迹。
- 工具偏移轨迹使体积在 `1045 -> 1020 -> 1045` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid包含 52 条原生 history 记录。
- 上游 Pocket naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定真实 Profile、两阶段双输入 builder、专用变异轨迹、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pocket-cut`
`pocket->cut` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder生成体积 `970` 的有效单 Solid 和 42 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Cut` 双输入 builder对 Pocket 结果继续执行布尔减料。
- `4 -> 3 -> 4` 工具尺寸轨迹改变体积、面积与拓扑,体积在 `906 -> 943 -> 906` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid包含 48 条原生 history 记录。
- 上游 Pocket naming 证据向下游传播,恢复态语义摘要与名义态一致。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定真实 Profile、两阶段双输入减料、变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pocket-common`
`pocket->common` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder生成体积 `970` 的有效单 Solid 和 42 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Common` 双输入 builder对 Pocket 结果执行布尔交集。
- 默认工具 X 偏移 `10` 仅与 Pocket 外边界相切且没有实体交集,未被误记为接受;改用 `7 -> 6 -> 7` 专用轨迹。
- 工具偏移轨迹使交集体积在 `75 -> 100 -> 75` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid包含 178 条原生 history 记录。
- 上游 Pocket naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定真实 Profile、专用交叠轨迹、两阶段双输入 builder、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pocket-rotate`
`pocket->rotate` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder生成体积 `970` 的有效单 Solid 和 42 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform` 单输入 builder将 Pocket 结果绕锁定 Z 轴旋转。
- `15 -> 22.5 -> 15` 角度轨迹改变边界并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid体积 `970`,包含 135 条原生 history 记录。
- 上游 Pocket naming 证据向下游传播,恢复态语义摘要与名义态一致。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定真实 Profile、两阶段 builder、角度变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pocket-pad`
`pocket->pad` 已完成原生拒绝分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder生成体积 `970` 的有效单 Solid 和 42 条原生 history。
- OCCT `BRepPrimAPI_MakePrism` 在 `5 -> 7.5 -> 5` 长度轨迹中稳定拒绝 Pocket Solid 输入,且不产生结果 Shape 或 history。
- FreeCAD `PartDesign::Pad` 使用同一 Pocket Solid 作为 Profile返回 `FeatureExtrusion: Length: Could not extrude the sketch!`。
- FreeCAD 结果 Shape 为空、状态包含 `Invalid`,拒绝任务没有伪造下游 naming 或变异证据。
- transaction abort 后对象集与 Pocket 源 Shape 完整恢复,干净 FCStd save/reopen/resave 三阶段一致。
- focused ordered-pair gate 已锁定两阶段 builder、异常传输、精确诊断、回滚和持久化证据。
### `TSN-PAIR-pocket-pocket`
`pocket->pocket` 已完成原生接受分类:
- 第一、第二阶段均使用真实 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder上游 Pocket 生成体积 `970` 的有效单 Solid。
- 下游使用锁定的 `3x3` Face Profile避免默认 `2x3` Profile 只重复切除已空区域而产生无效变异轨迹。
- `5 -> 4 -> 5` 深度轨迹使体积在 `955 -> 958 -> 955` 间变化,同时改变拓扑和 history并精确恢复名义摘要与语义 naming。
- 名义结果为有效 1 Solid包含 111 条原生 history 记录。
- 上游 Pocket naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段双输入 builder、非平凡 Profile、深度变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pocket-loft`
`pocket->loft` 已完成原生拒绝分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder生成体积 `970` 的有效单 Solid 和 42 条原生 history。
- OCCT `BRepOffsetAPI_ThruSections` 在 `false -> true -> false` ruled 轨迹中生成有效 Shape但该结果仅记为 kernel-superset 诊断。
- FreeCAD `Part::Loft` 对 Pocket Solid section 返回 `Profile shape is not a single vertex, edge, wire nor face.`。
- FreeCAD 结果 Shape 为空、状态包含 `Invalid`,拒绝任务没有伪造下游 naming 或变异证据。
- transaction abort 后对象集与 Pocket 源 Shape 完整恢复,干净 FCStd save/reopen/resave 三阶段一致。
- focused ordered-pair gate 已锁定 FreeCAD 决策权、OCCT 超集诊断、精确失败信息、回滚和持久化证据。
### `TSN-PAIR-pocket-pipe`
`pocket->pipe` 已完成原生拒绝分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder生成体积 `970` 的有效单 Solid 和 42 条原生 history。
- 初始的 kernel-invalid 预期被真实 probe 否定OCCT `BRepOffsetAPI_MakePipe` 在 `15 -> 12 -> 15` spine 长度轨迹中生成有效 Shape但该结果仅记为 kernel-superset 诊断。
- FreeCAD `Part::Sweep` 对 Pocket Solid section 返回 `A fatal error occurred when making the sweep`。
- FreeCAD 结果 Shape 为空、状态包含 `Invalid`,拒绝任务没有伪造下游 naming 或变异证据。
- transaction abort 后对象集与 Pocket 源 Shape 完整恢复,干净 FCStd save/reopen/resave 三阶段一致。
- focused ordered-pair gate 已锁定 FreeCAD 决策权、OCCT 超集诊断、精确失败信息、回滚和持久化证据。
### `TSN-PAIR-pocket-revolution`
`pocket->revolution` 已完成原生拒绝分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder生成体积 `970` 的有效单 Solid 和 42 条原生 history。
- OCCT `BRepPrimAPI_MakeRevol` 在 `360 -> 270 -> 360` 角度轨迹中稳定拒绝 Pocket Solid 输入,且不产生结果 Shape 或 history。
- FreeCAD `Part::Revolution` 使用同一 Pocket Solid 作为 Source返回 `Solids are not Processed`。
- FreeCAD 结果 Shape 为空、状态包含 `Invalid`,拒绝任务没有伪造下游 naming 或变异证据。
- transaction abort 后对象集与 Pocket 源 Shape 完整恢复,干净 FCStd save/reopen/resave 三阶段一致。
- focused ordered-pair gate 已锁定两阶段 builder、异常传输、精确诊断、回滚和持久化证据。
### `TSN-PAIR-pocket-groove`
`pocket->groove` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder生成体积 `970` 的有效单 Solid 和 42 条原生 history。
- 下游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder对 Pocket 结果执行旋转减料。
- `360 -> 180 -> 360` 角度轨迹改变体积、拓扑和 history并精确恢复名义摘要与语义 naming。
- 名义结果为有效 1 Solid体积 `966.8584073`,包含 40 条原生 history 记录。
- 上游 Pocket naming 证据向下游传播,恢复态语义摘要与名义态一致。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段双输入 builder、角度变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pocket-fillet`
`pocket->fillet` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder生成体积 `970` 的有效单 Solid 和 42 条原生 history。
- 下游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 Pocket 结果的锁定原生边执行倒圆。
- `0.4 -> 0.6 -> 0.4` 半径轨迹改变体积与面积,体积在 `965.9792974 -> 961.0655284 -> 965.9792974` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid包含 44 Face 和 261 条原生 history 记录。
- 上游 Pocket naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 builder、原生边选择、半径变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pocket-chamfer`
`pocket->chamfer` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder生成体积 `970` 的有效单 Solid 和 42 条原生 history。
- 下游使用真实 `BRepFilletAPI_MakeChamfer` 单输入 builder对 Pocket 结果的锁定原生边执行倒角。
- `0.4 -> 0.6 -> 0.4` 距离轨迹改变体积与面积,体积在 `960.848 -> 949.912 -> 960.848` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid包含 44 Face 和 261 条原生 history 记录。
- 上游 Pocket naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 builder、原生边选择、距离变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pocket-hole`
`pocket->hole` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder生成体积 `970` 的有效单 Solid 和 42 条原生 history。
- 下游使用真实 `BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut` 单输入 builder以锁定位置和方向的圆柱刀具贯穿 Pocket 结果。
- `1 -> 1.5 -> 1` 半径轨迹改变体积与面积,体积在 `938.5840735 -> 899.3141653 -> 938.5840735` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid包含 23 条原生 history 记录,下游 naming 状态为无歧义 `native-evidence`。
- 上游 Pocket naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 builder、圆柱减料、半径变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pocket-draft`
`pocket->draft` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder生成体积 `970` 的有效单 Solid 和 42 条原生 history。
- 下游使用真实 `BRepOffsetAPI_DraftAngle` 单输入 builder对 Pocket 结果的锁定原生 Face、中性面和拉伸方向执行拔模。
- `5 -> 8 -> 5` 角度轨迹改变体积与面积,体积在 `929.5364931 -> 904.999864 -> 929.5364931` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid包含 7 条原生 history 记录,下游 naming 状态为无歧义 `native-evidence`。
- 上游 Pocket naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 builder、原生 Face 选择、角度变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pocket-thickness`
`pocket->thickness` 已完成原生接受分类:
- 上游使用真实 `10x10` Box 与 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder按专用 `5x10` Profile 贯穿切除,生成体积 `500` 的有效单 Solid 和 25 条原生 history。
- 下游使用真实 `BRepOffsetAPI_MakeThickSolid` 单输入 builder对锁定的原生 Face 执行厚壳;名义结果为有效 1 Solid、11 Face体积 `129.056`。
- `-0.4 -> -0.6 -> -0.4` offset 轨迹使体积在 `129.056 -> 185.664 -> 129.056` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Pocket naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 builder、原生 Face 选择、offset 变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pocket-linear-pattern`
`pocket->linear-pattern` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder生成体积 `970` 的有效单 Solid 和 42 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` 单输入 builder将 Pocket 结果沿 X 方向平移复制并融合为单 Solid名义体积 `1770`,边界覆盖 X=`[-0, 18]`。
- `8 -> 9 -> 8` 平移轨迹使体积在 `1770 -> 1855 -> 1770` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Pocket naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定真实 Profile、单 Solid 约束、平移变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pocket-polar-pattern`
`pocket->polar-pattern` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder生成体积 `970` 的有效单 Solid 和 42 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` 单输入 builder将 Pocket 结果绕锁定 Z 轴旋转复制并融合为单 Solid名义体积 `1388.0087147`,边界覆盖 X=`[-5, 10]`、Y=`[0, 13.6602542]`。
- `30 -> 45 -> 30` 角度轨迹使体积在 `1388.0087147 -> 1545.7864376 -> 1388.0087147` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Pocket naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定真实 Profile、旋转实例融合、角度变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-loft-fillet`
`loft->fillet` 已完成 Web 原生 history builder 拒绝分类,并显式记录 FreeCAD profile 接受差异:
- 上游使用真实 `BRepOffsetAPI_ThruSections` 双输入 builder生成体积 `1500` 的有效单 Solid 和 24 条原生 history。
- 下游 `BRepFilletAPI_MakeFillet` 在 `0.4 -> 0.6 -> 0.4` 半径轨迹中稳定触发 `RuntimeError: table index is out of bounds`,三次尝试均没有产生 Shape、history 或 naming。
- FreeCAD 1.1.1 `Part::Fillet` 对同一 Loft Solid 重算成功,状态为 `Valid` / `Up-to-date`;该差异被锁定为 Web 原生 history builder 中止,没有伪造 FreeCAD 拒绝。
- transaction abort 后对象集与 Loft 源 Shape 完整恢复,干净 FCStd save/reopen/resave 三阶段一致。
- focused ordered-pair gate 已锁定 WASM abort 签名、FreeCAD 接受对照、精确回滚和持久化证据。
### `TSN-PAIR-loft-chamfer`
`loft->chamfer` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_ThruSections` 双输入 builder生成体积 `1500` 的有效单 Solid 和 24 条原生 history。
- 下游使用真实 `BRepFilletAPI_MakeChamfer` 单输入 builder对 Loft 结果的原生边执行倒角。
- `0.4 -> 0.6 -> 0.4` 距离轨迹使体积在 `1489.1413333 -> 1475.952 -> 1489.1413333` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、26 Face包含 150 条原生 history 记录。
- 上游 Loft naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 builder、原生边选择、距离变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-loft-hole`
`loft->hole` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_ThruSections` 双输入 builder生成体积 `1500` 的有效单 Solid 和 24 条原生 history。
- 下游使用真实 `BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut` builder以锁定位置和方向的圆柱刀具贯穿 Loft 结果。
- `1 -> 1.5 -> 1` 半径轨迹使体积在 `1462.3008882 -> 1415.1769984 -> 1462.3008882` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid包含 13 条原生 history 记录,下游 naming 状态为无歧义 `native-evidence`。
- 上游 Loft naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 builder、圆柱减料、半径变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-loft-draft`
`loft->draft` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_ThruSections` 双输入 builder生成体积 `1500` 的有效单 Solid 和 24 条原生 history。
- 下游使用真实 `BRepOffsetAPI_DraftAngle` 单输入 builder对 Loft 结果的锁定原生 Face、中性面和拉伸方向执行拔模。
- `5 -> 8 -> 5` 角度轨迹使体积在 `1401.5752535 -> 1341.891561 -> 1401.5752535` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid包含 5 条原生 history 记录。
- 上游 Loft naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 builder、原生 Face 选择、角度变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-loft-thickness`
`loft->thickness` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_ThruSections` 双输入 builder生成体积 `1500` 的有效单 Solid 和 24 条原生 history。
- 下游使用真实 `BRepOffsetAPI_MakeThickSolid` 单输入 builder对锁定的原生 Face 执行厚壳;名义结果为有效 1 Solid、11 Face体积 `245.856`。
- `-0.4 -> -0.6 -> -0.4` offset 轨迹使体积在 `245.856 -> 358.464 -> 245.856` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果包含 38 条原生 history 记录,上游 Loft naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 builder、原生 Face 选择、offset 变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-loft-linear-pattern`
`loft->linear-pattern` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_ThruSections` 双输入 builder生成体积 `1500` 的有效单 Solid 和 24 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` 单输入 builder将 Loft 结果沿 X 方向平移复制并融合为单 Solid。
- `8 -> 9 -> 8` 平移轨迹使体积在 `2700 -> 2850 -> 2700` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、14 Face边界覆盖 X=`[0, 18]`,包含 66 条原生 history 记录。
- 上游 Loft naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 builder、单 Solid 约束、平移变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-loft-polar-pattern`
`loft->polar-pattern` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_ThruSections` 双输入 builder生成体积 `1500` 的有效单 Solid 和 24 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` 单输入 builder将 Loft 结果绕锁定 Z 轴旋转复制并融合为单 Solid。
- `30 -> 45 -> 30` 角度轨迹使体积在 `2133.9745962 -> 2378.6796564 -> 2133.9745962` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、12 Face边界覆盖 X=`[-5, 10]`、Y=`[0, 13.660254]`,包含 78 条原生 history 记录。
- 上游 Loft naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 builder、旋转实例融合、角度变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-loft-mirrored`
`loft->mirrored` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_ThruSections` 双输入 builder生成体积 `1500` 的有效单 Solid 和 24 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` 单输入 builder将 Loft 结果绕锁定平面镜像复制并融合。
- 名义 X=5 镜像与 Loft 本体完全重合但仍保留 156 条原生 history`5 -> 6 -> 5` 轨迹使体积在 `1500 -> 1800 -> 1500` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、6 FaceFreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定。
- 上游 Loft naming 证据向下游传播focused ordered-pair gate 已锁定重合名义对照、镜像实例融合、变异、恢复、history 和持久化证据。
### `TSN-PAIR-loft-multi-transform`
`loft->multi-transform` 已完成原生接受分类,并闭合 Loft 起始行全部 19 个有序操作对:
- 上游使用真实 `BRepOffsetAPI_ThruSections` 双输入 builder生成体积 `1500` 的有效单 Solid 和 24 条原生 history。
- 下游使用 ordered `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` builder先执行 X 平移,再镜像平移结果并保留每个实例的原生追踪关系。
- `8 -> 9 -> 8` 首步 X 平移轨迹使体积在 `3900 -> 4200 -> 3900` 间变化,名义边界覆盖 X=`[-8, 18]`并精确恢复名义摘要、history 与语义 naming。
- 名义与编辑态均包含 355 条原生 history结果为有效 1 Solid、22 Face。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused ordered-pair gate 已锁定 ordered builder、实例追踪、变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pipe-fuse`
`pipe->fuse` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以真实 `2x3` Face section 沿 15 长度 spine 扫掠,生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Fuse` 双输入 builder使用与 Pipe 保持交叠的工具。
- `0 -> 0.5 -> 0` 工具 X 偏移轨迹使体积在 `185 -> 192.5 -> 185` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、14 Face包含 66 条原生 history 记录,上游 Pipe naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused ordered-pair gate 已锁定真实 Face section、双输入 builder、交叠变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pipe-cut`
`pipe->cut` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Cut` 双输入 builder以贯穿交叠的工具继续减料。
- `0 -> 0.5 -> 0` 工具 X 偏移轨迹使体积在 `66 -> 72 -> 66` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、6 Face、包含 117 条原生 history 记录,上游 Pipe naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused ordered-pair gate 已锁定 Face section、双输入减料 builder、偏移变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pipe-common`
`pipe->common` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Common` 双输入 builder使用与 Pipe 保持交叠的工具。
- `0 -> 0.5 -> 0` 工具 X 偏移轨迹使交集体积在 `24 -> 18 -> 24` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、6 Face、包含 132 条原生 history 记录,上游 Pipe naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused ordered-pair gate 已锁定 Face section、交叠变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pipe-rotate`
`pipe->rotate` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform` 单输入 builder将 Pipe 结果绕 Z 轴旋转。
- `15 -> 22.5 -> 15` 角度轨迹保持体积 `90`改变边界并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、6 Face、包含 78 条原生 history 记录,上游 Pipe naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused ordered-pair gate 已锁定旋转变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pipe-pad`
`pipe->pad` 已完成原生拒绝分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- OCCT `BRepPrimAPI_MakePrism` 在 `5 -> 7.5 -> 5` 长度轨迹中稳定拒绝 Pipe Solid 输入,不产生结果 Shape 或 history。
- FreeCAD `PartDesign::Pad` 使用同一 Pipe Solid 作 Profile返回 `FeatureExtrusion: Length: Could not extrude the sketch!`;结果 Shape 为空、状态包含 `Invalid`。
- transaction abort 后对象集与 Pipe 源 Shape 完整恢复,干净 FCStd save/reopen/resave 三阶段一致。
- focused ordered-pair gate 已锁定两阶段 builder、异常传输、精确诊断、回滚和持久化证据。
### `TSN-PAIR-pipe-pocket`
`pipe->pocket` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- 下游使用真实 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder以贯穿 Profile 对 Pipe 结果执行减料。
- `5 -> 4 -> 5` 深度轨迹使体积在 `60 -> 66 -> 60` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、6 Face、包含 98 条原生 history 记录,上游 Pipe naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused ordered-pair gate 已锁定真实 Profile、双输入 builder、深度变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pipe-loft`
`pipe->loft` 已完成 FreeCAD 文档前置拒绝分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- OCCT `BRepOffsetAPI_ThruSections` 在 `false -> true -> false` ruled 轨迹中均生成有效 Shape仅记为 `kernel-superset-only` 兼容性诊断,三次结果精确恢复。
- FreeCAD `Part::Loft` 对 Pipe Solid section 返回 `Profile shape is not a single vertex, edge, wire nor face.`,结果 Shape 为空、状态包含 `Invalid`。
- transaction abort 后对象集与 Pipe 源 Shape 完整恢复,干净 FCStd save/reopen/resave 三阶段一致。
- focused ordered-pair gate 已锁定 FreeCAD 决策权、OCCT 超集诊断、精确失败信息、回滚和持久化证据。
### `TSN-PAIR-pipe-pipe`
`pipe->pipe` 已完成 FreeCAD 文档前置拒绝分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- OCCT 下游 `BRepOffsetAPI_MakePipe` 在 `15 -> 12 -> 15` spine 长度轨迹中均产生无效 Shape`kernel-invalid-result`),三次诊断精确恢复。
- FreeCAD `Part::Sweep` 对 Pipe Solid section 返回 `A fatal error occurred when making the sweep`,结果 Shape 为空、状态包含 `Invalid`。
- transaction abort 后对象集与 Pipe 源 Shape 完整恢复,干净 FCStd save/reopen/resave 三阶段一致。
- focused ordered-pair gate 已锁定 FreeCAD 决策权、OCCT 无效结果诊断、精确失败信息、回滚和持久化证据。
### `TSN-PAIR-pipe-revolution`
`pipe->revolution` 已完成原生拒绝分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- OCCT `BRepPrimAPI_MakeRevol` 在 `360 -> 270 -> 360` 角度轨迹中稳定拒绝 Pipe Solid 输入,不产生结果 Shape 或 history。
- FreeCAD `Part::Revolution` 使用同一 Pipe Solid 作 Source返回 `Solids are not Processed`;结果 Shape 为空、状态包含 `Invalid`。
- transaction abort 后对象集与 Pipe 源 Shape 完整恢复,干净 FCStd save/reopen/resave 三阶段一致。
- focused ordered-pair gate 已锁定两阶段 builder、异常传输、精确诊断、回滚和持久化证据。
### `TSN-PAIR-pipe-groove`
`pipe->groove` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- 下游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder对 Pipe 结果执行旋转减料。
- `360 -> 180 -> 360` 角度轨迹使体积在 `88.4292037 -> 90 -> 88.4292037` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、10 Face、包含 40 条原生 history 记录,上游 Pipe naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused ordered-pair gate 已锁定双输入 builder、角度变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pipe-fillet`
`pipe->fillet` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- 下游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 Pipe 结果的全部原生边执行圆角。
- `0.4 -> 0.6 -> 0.4` 半径轨迹使体积在 `87.3388075 -> 84.1087417 -> 87.3388075` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、26 Face包含 150 条原生 history 记录,上游 Pipe naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused ordered-pair gate 已锁定原生边选择、半径变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pipe-chamfer`
`pipe->chamfer` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- 下游使用真实 `BRepFilletAPI_MakeChamfer` 单输入 builder对 Pipe 结果的全部原生边执行倒角。
- `0.4 -> 0.6 -> 0.4` 距离轨迹使体积在 `83.9413333 -> 76.752 -> 83.9413333` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、26 Face包含 150 条原生 history 记录,上游 Pipe naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused ordered-pair gate 已锁定原生边选择、距离变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pipe-hole`
`pipe->hole` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- 下游使用真实 `BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut` builder在截面中心 `(1, 1.5)` 以贯穿圆柱对 Pipe 结果减料。
- `0.4 -> 0.6 -> 0.4` 半径轨迹使体积在 `82.4601776 -> 73.0353997 -> 82.4601776` 间变化,保持有效单 Solid并精确恢复名义摘要、history 与语义 naming。
- 名义结果为 7 Face包含 23 条原生 history 记录,上游 Pipe naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused ordered-pair gate 已锁定贯穿位置、半径变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pipe-draft`
`pipe->draft` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- 原生 Face 枚举确认端面索引 `0/5` 被 Draft builder 拒绝,侧面索引 `1/2/3/4` 可接受;下游锁定 `faceIndex: 1`、Z 向中性面和拉伸方向执行真实 `BRepOffsetAPI_DraftAngle`。
- `5 -> 8 -> 5` 角度轨迹使体积在 `70.3150507 -> 58.3783122 -> 70.3150507` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、6 Face包含 5 条原生 history 记录,上游 Pipe naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused ordered-pair gate 已锁定侧面适用性、角度变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pipe-thickness`
`pipe->thickness` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- 下游使用真实 `BRepOffsetAPI_MakeThickSolid` 单输入 builder对锁定的 Pipe 侧面执行厚壳。
- `-0.4 -> -0.6 -> -0.4` offset 轨迹使体积在 `45.696 -> 63.504 -> 45.696` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、11 Face包含 38 条原生 history 记录,上游 Pipe naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused ordered-pair gate 已锁定侧面选择、offset 变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pipe-linear-pattern`
`pipe->linear-pattern` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` 单输入 builder针对窄 Pipe 截面锁定交叠平移,避免沿用 Box 的 X=8 参数产生分离结果。
- `1.5 -> 1 -> 1.5` X 平移轨迹使体积在 `157.5 -> 135 -> 157.5` 间变化,名义和编辑态均保持有效单 Solid并精确恢复摘要、history 与语义 naming。
- 名义结果为 14 Face边界覆盖 X=`[0, 3.5]`,包含 66 条原生 history 记录,上游 Pipe naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused ordered-pair gate 已锁定交叠融合、平移变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pipe-polar-pattern`
`pipe->polar-pattern` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` 单输入 builder绕锁定 Z 轴复制并融合 Pipe 结果。
- `30 -> 45 -> 30` 角度轨迹使体积在 `132.3686028 -> 150 -> 132.3686028` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、12 Face边界覆盖 X=`[-1.5, 2]`、Y=`[0, 3.5980764]`,包含 78 条原生 history 记录。
- 上游 Pipe naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused ordered-pair gate 已锁定旋转融合、变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pipe-mirrored`
`pipe->mirrored` 已完成原生接受分类:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` 单输入 builder名义镜像平面锁定为 `x=1`,与 Pipe 截面对称重合,编辑平面为 `x=1.5` 并保持实例交叠。
- `1 -> 1.5 -> 1` 平面轨迹使体积在 `90 -> 135 -> 90` 间变化,恢复态保留名义 1 Solid 和 156 条原生 history并精确恢复语义 naming。
- 编辑态为有效单 Solid、14 Face上游 Pipe naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming 稳定。
- focused ordered-pair gate 已锁定重合名义对照、交叠镜像、变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-pipe-multi-transform`
`pipe->multi-transform` 已完成原生接受分类,并闭合 Pipe 起始行全部 19 个有序操作对:
- 上游使用真实 `BRepOffsetAPI_MakePipe` 双输入 builder以 `2x3` Face section 生成体积 `90` 的有效单 Solid 和 28 条原生 history。
- 下游使用 ordered `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` builder先沿 X 平移,再绕 `x=5` 平面镜像;该实例布局原生形成有效 2 Solid 结果,没有伪装为单 Solid。
- `8 -> 7 -> 8` 首步 X 平移轨迹使体积在 `180 -> 270 -> 180` 间变化并精确恢复名义摘要、300 条 history 与语义 naming。
- STEP 导入会同时创建 `Part::Feature`、聚合 `App::Part` 和 Origin 基准对象FCStd harness 已改为只组合有限、有效、含 Solid 的 `Part::Feature`,消除了重复几何和 `1e100` 退化边界。
- FreeCAD FCStd save/reopen/resave 三阶段均保持 2 Solid、12 Face、体积 `180` 与 naming evidence 一致focused gate 已锁定多实体拓扑、导入筛选、变异、恢复和持久化证据。
### `TSN-PAIR-revolution-fuse`
`revolution->fuse` 已完成原生接受分类,并验证新的 Revolution 上游基线:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder绕锁定 Y 轴生成体积 `75.3982237` 的有效单 Solid、4 Face 和 23 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Fuse` 双输入 builder工具与 Revolution 结果保持体积交叠。
- `0 -> 0.5 -> 0` 工具 X 偏移轨迹使体积在 `188.0229122 -> 192.1067282 -> 188.0229122` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义下游结果为有效 1 Solid、11 Face包含 45 条原生 history 记录,上游 Revolution naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused ordered-pair gate 已锁定 Face Profile、旋转轴、融合变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-revolution-cut`
`revolution->cut` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid和 23 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Cut` 双输入 builder`4 -> 3 -> 4` 工具尺寸轨迹覆盖同一正象限、摘要不变,未被采用。
- 最终使用小型交叠工具的 `0 -> 0.5 -> 0` X 偏移轨迹,使体积在 `68.3585388 -> 70.3585388 -> 68.3585388` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、8 Face包含 75 条原生 history 记录,上游 Revolution naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused gate 已锁定弱轨迹拒绝、偏移变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-revolution-common`
`revolution->common` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid和 23 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Common` 双输入 builder以贯穿正象限的工具和 Revolution 结果求交。
- `0 -> 0.5 -> 0` 工具 X 偏移轨迹使交集体积在 `12.3753114 -> 8.2914955 -> 12.3753114` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、5 Face包含 100 条原生 history 记录,上游 Revolution naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused gate 已锁定交叠工具、偏移变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-revolution-rotate`
`revolution->rotate` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid、4 Face 和 23 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform` 单输入 builder绕锁定 Z 轴旋转 Revolution 结果。
- `15 -> 22.5 -> 15` 角度轨迹保持体积 `75.3982237`改变边界并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、4 Face包含 40 条原生 history 记录,上游 Revolution naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定旋转变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-revolution-pad`
`revolution->pad` 已完成原生拒绝分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid和 23 条原生 history。
- 下游 `BRepPrimAPI_MakePrism` 在 `5 -> 7.5 -> 5` 长度轨迹中稳定拒绝 Revolution Solid 输入,三次均不产生 Shape、history 或 naming。
- FreeCAD `PartDesign::Pad` 使用同一 Revolution Solid 作 Profile返回 `FeatureExtrusion: Length: Could not extrude the sketch!`,结果 Shape 为空、状态包含 `Touched/Invalid`。
- transaction abort 后对象集与 Revolution 源 Shape 完整恢复,干净 FCStd save/reopen/resave 三阶段一致且文档无污染。
- focused ordered-pair gate 已锁定两阶段 builder、异常传输、精确诊断、回滚和持久化证据。
### `TSN-PAIR-revolution-pocket`
`revolution->pocket` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid 和 23 条原生 history。
- 下游使用真实 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder以同一截面 Profile 沿 Z 方向执行部分减料。
- `2 -> 1 -> 2` 深度轨迹使体积在 `64.8386964 -> 69.5677847 -> 64.8386964` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、7 Face包含 104 条原生 history 记录,上游 Revolution naming 证据向下游传播。
- FreeCAD FCStd save/reopen/resave 三阶段的 Shape 与 naming evidence 稳定focused gate 已锁定部分深度、变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-revolution-loft`
`revolution->loft` 已完成 FreeCAD 文档前置拒绝分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid 和 23 条原生 history。
- OCCT `BRepOffsetAPI_ThruSections` 在 `false -> true -> false` ruled 轨迹中均产生 `isValid:false`、负体积的 `kernel-invalid-result`,三次诊断精确恢复。
- FreeCAD `Part::Loft` 对 Revolution Solid section 返回 `Profile shape is not a single vertex, edge, wire nor face.`,结果 Shape 为空、状态包含 `Touched/Invalid`。
- transaction abort 后对象集与 Revolution 源 Shape 完整恢复,干净 FCStd save/reopen/resave 三阶段一致。
- focused gate 已锁定 FreeCAD 决策权、OCCT 无效结果、精确失败信息、回滚和持久化证据。
### `TSN-PAIR-revolution-pipe`
`revolution->pipe` 已完成原生 builder 拒绝分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid 和 23 条原生 history。
- OCCT `BRepOffsetAPI_MakePipe` 在 `15 -> 12 -> 15` spine 长度轨迹中均于 builder 阶段抛出稳定 Emscripten C++ 异常,不产生 Shape、history 或 naming。
- FreeCAD `Part::Sweep` 对同一 Revolution Solid section 返回 `A fatal error occurred when making the sweep`,结果 Shape 为空、状态包含 `Touched/Invalid`。
- transaction abort 后对象集与 Revolution 源 Shape 完整恢复,干净 FCStd save/reopen/resave 三阶段一致。
- focused gate 已锁定异常阶段、输入数、精确失败信息、回滚和持久化证据。
### `TSN-PAIR-revolution-revolution`
`revolution->revolution` 已完成原生 builder 拒绝分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid、4 Face 和 23 条原生 history。
- 下游再次使用 `BRepPrimAPI_MakeRevol`,对 Revolution Solid 在 `360 -> 270 -> 360` 角度轨迹中稳定于 builder 阶段抛出 Emscripten C++ 异常;三次均不产生 Shape、history 或 naming。
- FreeCAD `Part::Revolution` 使用同一 Revolution Solid 作为 Source返回 `Solids are not Processed`,结果 Shape 为空、状态包含 `Touched/Invalid`。
- transaction abort 后对象集与 Revolution 源 Shape 完整恢复,源 Shape 体积、拓扑和 BREP 哈希保持一致;干净 FCStd save/reopen/resave 三阶段一致且文档无污染。
- focused ordered-pair gate 已锁定两阶段 Revolution builder、异常传输、精确诊断、回滚和持久化证据。
### `TSN-PAIR-revolution-groove`
`revolution->groove` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid、4 Face 和 23 条原生 history。
- 下游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder对 Revolution 结果执行旋转减料;名义结果为有效 1 Solid、10 Face、体积 `71.0943912`,包含 43 条原生 history。
- `360 -> 180 -> 360` 角度轨迹使体积在 `71.0943912 -> 73.2463075 -> 71.0943912` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Revolution naming 证据向下游传播恢复态语义摘要与名义态一致FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 Revolution/Groove builder、角度变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-revolution-fillet`
`revolution->fillet` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid、4 Face 和 23 条原生 history。
- 下游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 Revolution 结果的原生边执行全边圆角;名义结果为有效 1 Solid、8 Face、体积 `73.6722933`,包含 48 条原生 history。
- `0.4 -> 0.6 -> 0.4` 半径轨迹使体积在 `73.6722933 -> 71.5148803 -> 73.6722933` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Revolution naming 证据向下游传播恢复态语义摘要与名义态一致FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 Revolution/Fillet builder、原生边选择、半径变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-revolution-chamfer`
`revolution->chamfer` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid、4 Face 和 23 条原生 history。
- 下游使用真实 `BRepFilletAPI_MakeChamfer` 单输入 builder对 Revolution 结果的原生边执行全边倒角;名义结果为有效 1 Solid、8 Face、体积 `71.3769851`,包含 48 条原生 history。
- `0.4 -> 0.6 -> 0.4` 距离轨迹使体积在 `71.3769851 -> 66.3504368 -> 71.3769851` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Revolution naming 证据向下游传播恢复态语义摘要与名义态一致FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 Revolution/Chamfer builder、原生边选择、距离变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-revolution-hole`
`revolution->hole` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid、4 Face 和 23 条原生 history。
- 下游使用真实 `BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut` builder在锁定位置 `[0, 1.5, 0]`、方向 `+Z` 和深度 `0.1` 的局部圆柱刀具上执行减料;名义结果为有效 1 Solid、8 Face、体积 `75.3729574`,包含 19 条原生 history。
- `0.4 -> 0.6 -> 0.4` 半径轨迹使体积在 `75.3729574 -> 75.3414747 -> 75.3729574` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Revolution naming 证据向下游传播恢复态语义摘要与名义态一致FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 Revolution/Hole builder、局部圆柱刀具、半径变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-revolution-draft`
`revolution->draft` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid、4 Face 和 23 条原生 history。
- 下游使用真实 `BRepOffsetAPI_DraftAngle` 单输入 builder对锁定原生 Face、中性面和 +Z 拉伸方向执行拔模;名义结果为有效 1 Solid、4 Face、体积 `75.3982238`,包含 3 条原生 history。
- `5 -> 8 -> 5` 角度轨迹使 Y 方向边界在 `-0.3154312 -> -0.5067054 -> -0.3154312` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Revolution naming 证据向下游传播,下游获得无歧义 `native-evidence` namingFreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 Revolution/DraftAngle builder、原生 Face 选择、角度变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-revolution-thickness`
`revolution->thickness` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid、4 Face 和 23 条原生 history。
- 下游使用真实 `BRepOffsetAPI_MakeThickSolid` 单输入 builder对锁定原生 Face 执行厚壳;名义结果为有效 1 Solid、8 Face、体积 `26.7412367`,包含 39 条原生 history。
- `-0.4 -> -0.6 -> -0.4` offset 轨迹使体积在 `26.7412367 -> 38.9808816 -> 26.7412367` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Revolution naming 证据向下游传播恢复态语义摘要与名义态一致FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 Revolution/Thickness builder、原生 Face 选择、offset 变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-revolution-linear-pattern`
`revolution->linear-pattern` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid、4 Face 和 23 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` builder将 Revolution 结果沿 X 方向复制并融合;专用 `4 -> 3 -> 4` 平移轨迹确保所有状态保持单 Solid。
- 平移轨迹使体积在 `132.2115549 -> 126.3867082 -> 132.2115549` 间变化并精确恢复名义摘要、history 与语义 naming名义结果为 12 Face包含 53 条原生 history。
- 上游 Revolution naming 证据向下游传播恢复态语义摘要与名义态一致FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 Revolution/LinearPattern builder、单 Solid 约束、平移变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-revolution-polar-pattern`
`revolution->polar-pattern` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid、4 Face 和 23 条原生 history。
- 默认 `30°` 轨迹触发原生 WASM 越界中止,未被误记为接受;改用保持实例相交且稳定的 `10 -> 15 -> 10` 专用角度轨迹。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` builder将旋转实例融合为单 Solid角度轨迹使体积在 `87.2727929 -> 92.4535717 -> 87.2727929` 间变化并精确恢复名义摘要、history 与语义 naming。
- 名义结果为有效 1 Solid、14 Face包含 70 条原生 history上游 Revolution naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定专用角度轨迹、单 Solid 约束、变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-revolution-mirrored`
`revolution->mirrored` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid、4 Face 和 23 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` builder将 Revolution 结果关于 X=`0` 镜像并融合为单 Solid专用 `0 -> 0.5 -> 0` 平面轨迹避免默认分离实例。
- 平面轨迹使体积在 `120.1447575 -> 126.3867082 -> 120.1447575` 间变化并精确恢复名义摘要、history 与语义 naming名义结果为 14 Face包含 92 条原生 history。
- 上游 Revolution naming 证据向下游传播恢复态语义摘要与名义态一致FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定专用镜像平面、单 Solid 约束、变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-revolution-multi-transform`
`revolution->multi-transform` 已完成原生接受分类:
- 上游使用真实 `2x3` Face Profile 和 `BRepPrimAPI_MakeRevol` 单输入 builder生成体积 `75.3982237` 的有效单 Solid、4 Face 和 23 条原生 history。
- 下游使用 ordered `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` builder锁定镜像平面 `X=0`,按平移和镜像顺序生成并融合四个实例;名义结果为有效 1 Solid、48 Face、体积 `190.7882692`,包含 313 条原生 history。
- `4 -> 3 -> 4` 平移轨迹使体积在 `190.7882692 -> 155.9773894 -> 190.7882692` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Revolution naming 证据向下游传播恢复态语义摘要与名义态一致FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定 ordered builder、镜像平面、单 Solid 约束、平移变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-groove-fuse`
`groove->fuse` 已完成原生接受分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder对 10x10 Box 执行旋转减料,生成体积 `995.287611` 的有效单 Solid、10 Face 和 18 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Fuse` 双输入 builder专用 `8 -> 7 -> 8` 工具 X 偏移轨迹保持实体交叠并融合为单 Solid。
- 轨迹使体积在 `1070.287611 -> 1045.287611 -> 1070.287611` 间变化并精确恢复名义摘要、history 与语义 naming名义结果为 17 Face包含 52 条原生 history。
- 上游 Groove naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 Groove/Fuse builder、专用交叠轨迹、变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-groove-cut`
`groove->cut` 已完成原生接受分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder对 10x10 Box 执行旋转减料,生成体积 `995.287611` 的有效单 Solid、10 Face 和 18 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Cut` 双输入 builder对 Groove 结果继续执行减料;名义结果为有效 1 Solid、14 Face、体积 `931.287611`,包含 22 条原生 history。
- `4 -> 3 -> 4` 工具尺寸轨迹使体积在 `931.287611 -> 968.287611 -> 931.287611` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Groove naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 Groove/Cut builder、工具变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-groove-common`
`groove->common` 已完成原生接受分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder对 10x10 Box 执行旋转减料,生成有效 1 Solid、体积 `995.287611`、10 Face 和 18 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Common` 双输入 builder与 Groove 结果及交叠工具执行布尔交集;名义结果为有效 1 Solid、6 Face、体积 `75`,包含 197 条原生 history。
- `7 -> 6 -> 7` 工具 X 偏移轨迹使体积在 `75 -> 100 -> 75` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Groove naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 Groove/Common builder、交叠轨迹、变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-groove-rotate`
`groove->rotate` 已完成原生接受分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder对 10x10 Box 执行旋转减料,生成有效 1 Solid、体积 `995.287611`、10 Face 和 18 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform` 单输入 builder对 Groove 结果绕 Z 轴旋转;名义结果保持有效 1 Solid、10 Face、体积 `995.287611`,包含 154 条原生 history。
- `15 -> 22.5 -> 15` 角度轨迹改变包围盒(名义 `[-2.5881906, -1e-7, -1e-7, 9.6592584, 12.2474488, 10.0000001]`,编辑态 `[-3.8268344, -1e-7, -1e-7, 9.2387954, 13.0656297, 10.0000001]`并精确恢复名义摘要、history 与语义 naming。
- 上游 Groove naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 Groove/Transform builder、角度变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-groove-pad`
`groove->pad` 已完成原生拒绝分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder对 10x10 Box 执行旋转减料,生成有效 1 Solid、体积 `995.287611`、10 Face 和 18 条原生 history。
- OCCT `BRepPrimAPI_MakePrism` 对 Groove Solid 在 `5 -> 7.5 -> 5` 长度轨迹中稳定于 builder 阶段拒绝,三次均不产生结果 Shape 或 history。
- FreeCAD `PartDesign::Pad` 使用同一 Groove Solid 作为 Profile返回 `FeatureExtrusion: Length: Could not extrude the sketch!`,结果 Shape 为空且状态含 `Invalid`;未伪造下游命名或变异证据。
- transaction abort 后对象集、源 Shape 和 BREP 哈希精确恢复;干净 FCStd save/reopen/resave 三阶段一致focused gate 已锁定拒绝阶段、诊断、回滚和持久化证据。
### `TSN-PAIR-groove-pocket`
`groove->pocket` 已完成原生接受分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder对 10x10 Box 执行旋转减料,生成有效 1 Solid、体积 `995.287611`、10 Face 和 18 条原生 history。
- 下游使用真实 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder对 Groove 结果执行减料拉伸;名义结果为有效 1 Solid、13 Face、体积 `966.8584073`,包含 91 条原生 history。
- `5 -> 4 -> 5` 深度轨迹使体积在 `966.8584073 -> 972.8584073 -> 966.8584073` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Groove naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 Groove/Pocket builder、深度变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-groove-loft`
`groove->loft` 已完成原生拒绝分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder对 10x10 Box 执行旋转减料,生成有效 1 Solid、体积 `995.287611`、10 Face 和 18 条原生 history。
- OCCT `BRepOffsetAPI_ThruSections` 在 `false -> true -> false` ruled 轨迹中生成有效 Shape 和 24 条 history但该结果仅作为 `kernel-superset-only` 诊断,不提升 FreeCAD 能力。
- FreeCAD `Part::Loft` 对 Groove Solid section 返回 `Profile shape is not a single vertex, edge, wire nor face.`,结果 Shape 为空且状态含 `Invalid`;未伪造下游命名或接受变异证据。
- transaction abort 后对象集、源 Shape 和 BREP 哈希精确恢复;干净 FCStd save/reopen/resave 三阶段一致focused gate 已锁定 FreeCAD 决策权、kernel 诊断、回滚和持久化证据。
### `TSN-PAIR-groove-pipe`
`groove->pipe` 已完成原生拒绝分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder对 10x10 Box 执行旋转减料,生成有效 1 Solid、体积 `995.287611`、10 Face 和 18 条原生 history。
- OCCT `BRepOffsetAPI_MakePipe` 在 `15 -> 12 -> 15` spine 长度轨迹中均产生无效结果,仅记为 `kernel-invalid-result`,不伪造接受 Shape/history。
- FreeCAD `Part::Sweep` 对 Groove Solid section 返回 `A fatal error occurred when making the sweep`,结果 Shape 为空且状态含 `Invalid`。
- transaction abort 后对象集、源 Shape 和 BREP 哈希精确恢复;干净 FCStd save/reopen/resave 三阶段一致focused gate 已锁定 FreeCAD 决策权、kernel 无效诊断、回滚和持久化证据。
### `TSN-PAIR-groove-revolution`
`groove->revolution` 已完成原生拒绝分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder对 10x10 Box 执行旋转减料,生成有效 1 Solid、体积 `995.287611`、10 Face 和 18 条原生 history。
- OCCT `BRepPrimAPI_MakeRevol` 对 Groove Solid 在 `360 -> 270 -> 360` 角度轨迹中稳定于 builder 阶段拒绝,三次均不产生结果 Shape 或 history。
- FreeCAD `Part::Revolution` 使用同一 Groove Solid 作为 Source返回 `Solids are not Processed`,结果 Shape 为空且状态含 `Invalid`;未伪造下游命名或变异证据。
- transaction abort 后对象集、源 Shape 和 BREP 哈希精确恢复;干净 FCStd save/reopen/resave 三阶段一致focused gate 已锁定拒绝阶段、诊断、回滚和持久化证据。
### `TSN-PAIR-groove-groove`
`groove->groove` 已完成原生接受分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder对 10x10 Box 执行旋转减料,生成有效 1 Solid、体积 `995.287611`、10 Face 和 18 条原生 history。
- 下游再次使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder并将第二阶段 profile 真实放置在 `profileX=2` 以切削不同半径区域;名义结果为有效 1 Solid、14 Face、体积 `983.5066386`,包含 18 条原生 history。
- `360 -> 180 -> 360` 角度轨迹改变拓扑(编辑态 11 Face、28 Edge并精确恢复名义摘要、history 与语义 naming未采用同 profile 的 no-op 轨迹。
- 上游 Groove naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
- focused ordered-pair gate 已锁定两阶段 Groove/Groove builder、专用 profile、角度变异、恢复、history、naming 和持久化证据。
### `TSN-PAIR-groove-fillet`
`groove->fillet` 已完成原生拒绝分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder对 10x10 Box 执行旋转减料,生成有效 1 Solid、体积 `995.287611`、10 Face、24 Edge 和 18 条原生 history。
- 下游按 native contract 使用 `BRepFilletAPI_MakeFillet` 对全部 24 条源边执行圆角;`0.4 -> 0.6 -> 0.4` 半径轨迹三次均稳定在 OCCT builder 阶段以 Emscripten C++ 异常拒绝,没有产生 Shape、history 或下游 naming。
- FreeCAD 1.1.1 `Part::Fillet` 对同一 Groove Solid 和全部源边返回 `BRep_API: command not done`;结果 Shape 为空、状态包含 `Invalid`,与 OCCT 拒绝结论一致。
- transaction abort 后对象集、源 Shape 和 BREP 哈希精确恢复;干净 FCStd save/reopen/resave 三阶段一致focused gate 已锁定拒绝阶段、异常传输、精确诊断、回滚和持久化证据。
### `TSN-PAIR-groove-chamfer`
`groove->chamfer` 已完成原生拒绝分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder生成有效 1 Solid、体积 `995.287611`、10 Face、24 Edge 和 18 条原生 history。
- 下游按 native contract 使用 `BRepFilletAPI_MakeChamfer` 对全部 24 条源边执行对称倒角;`0.4 -> 0.6 -> 0.4` 距离轨迹稳定触发原生 WASM `memory access out of bounds`,三次均不产生 Shape、history 或下游 naming。
- FreeCAD 1.1.1 `Part::Chamfer` 对同一 Groove Solid 和全部源边返回 `BRep_API: command not done`;结果 Shape 为空且状态包含 `Invalid`。
- transaction abort 后对象集、源 Shape 和 BREP 哈希精确恢复;干净 FCStd save/reopen/resave 三阶段一致focused gate 已锁定 WASM 崩溃签名、FreeCAD 精确诊断、回滚和持久化证据。
### `TSN-PAIR-groove-hole`
`groove->hole` 已完成原生接受分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder生成有效 1 Solid、体积 `995.287611`、10 Face 和 18 条原生 history。
- 下游使用真实 `BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut` builder在 Groove 结果中心执行贯穿孔;名义结果为有效 1 Solid、11 Face、体积 `963.8716845`,包含 23 条原生 history。
- `1 -> 1.5 -> 1` 半径轨迹使体积在 `963.8716845 -> 924.6017763 -> 963.8716845` 间变化并精确恢复名义摘要、history 与语义 naming。
- 下游获得无歧义 `native-evidence` naming上游 Groove naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定两阶段 builder、变异、恢复和持久化证据。
### `TSN-PAIR-groove-draft`
`groove->draft` 已完成原生接受分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder生成有效 1 Solid、体积 `995.287611`、10 Face 和 18 条原生 history。
- 下游使用真实 `BRepOffsetAPI_DraftAngle` 单输入 builder对锁定的原生 Face、中性面和 +Z 方向执行拔模;名义结果为有效 1 Solid、10 Face、体积 `951.8050786`,包含 9 条原生 history。
- `5 -> 8 -> 5` 角度轨迹使体积在 `951.8050786 -> 925.4360727 -> 951.8050786` 间变化并精确恢复名义摘要、history 与语义 naming。
- 下游 9 条选定 history 均形成无歧义 `native-evidence` namingFreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定 Face 选择、方向、变异、恢复和持久化证据。
### `TSN-PAIR-groove-thickness`
`groove->thickness` 已完成原生拒绝分类,并显式保留 Web 原生与 FreeCAD profile 的能力差异:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder生成有效 1 Solid、体积 `995.287611`、10 Face 和 18 条原生 history。
- Web 原生下游使用 `BRepOffsetAPI_MakeThickSolid` 对锁定的第二个 Face 执行厚壳;`-0.4 -> -0.6 -> -0.4` offset 轨迹稳定触发 WASM `memory access out of bounds`,三次均不产生 Shape、history 或下游 naming。
- FreeCAD 1.1.1 `Part::Thickness` 对同一 Groove Solid、`Face2` 和 `Value=-0.4` 生成有效 Shape状态为 `Valid`;该 FreeCAD 接受证据没有被用于伪造 Web 原生接受能力。
- transaction abort 后对象集、源 Shape 和 BREP 哈希精确恢复;干净 FCStd save/reopen/resave 三阶段一致focused gate 已锁定崩溃签名、FreeCAD 接受诊断、回滚和持久化证据。
### `TSN-PAIR-groove-linear-pattern`
`groove->linear-pattern` 已完成原生接受分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder生成有效 1 Solid、体积 `995.287611`、10 Face 和 18 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` builder沿 X 方向复制并融合 Groove 结果;名义结果为有效 1 Solid、19 Face、体积 `1795.287611`,包含 119 条原生 history。
- `8 -> 9 -> 8` 平移轨迹始终保持单 Solid使体积在 `1795.287611 -> 1894.1260552 -> 1795.287611` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Groove naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定交叠融合、变异、恢复和持久化证据。
### `TSN-PAIR-groove-polar-pattern`
`groove->polar-pattern` 已完成原生接受分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder生成有效 1 Solid、体积 `995.287611`、10 Face 和 18 条原生 history。
- 默认 `30°` 及小角度 `10°/15°` 交叠融合轨迹均触发原生 WASM 越界,未被误记为接受;最终采用稳定且仍具真实拓扑变异的 `90 -> 120 -> 90` 专用轨迹。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` builder`90°` 名义态为有效 1 Solid、19 Face、体积 `1990.575222` 和 98 条 history`120°` 编辑态真实变为 2 Solid恢复后摘要、history 与语义 naming 精确复原。
- FreeCAD FCStd save/reopen/resave 三阶段保持名义单 Solid Shape 与 naming evidence 一致focused gate 已锁定专用角度轨迹、真实多实体编辑态、恢复和持久化证据。
### `TSN-PAIR-groove-mirrored`
`groove->mirrored` 已完成原生接受分类:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder生成有效 1 Solid、体积 `995.287611`、10 Face 和 18 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` builder关于 `x=5` 的名义镜像让两侧缺口互补融合,得到有效 1 Solid、10 Face、体积 `1000` 和 216 条原生 history。
- `5 -> 6 -> 5` 镜像平面轨迹使体积在 `1000 -> 1190.575222 -> 1000` 间变化,编辑态增至 22 Face并精确恢复名义摘要、history 与语义 naming。
- 上游 Groove naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定镜像平面、互补融合、变异、恢复和持久化证据。
后续任务必须从机器队列的最新 `nextTask` 继续,不在本文手工维护任务名称。