feat: classify fillet ordered pair row
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-16 01:02:00 -04:00
parent 372861e015
commit 03339a6360
8 changed files with 6011 additions and 116 deletions

View File

@@ -24,12 +24,12 @@
], ],
"followUpProgress": "config/freecad-follow-up-task-progress.json" "followUpProgress": "config/freecad-follow-up-task-progress.json"
}, },
"nextTask": "TSN-PAIR-groove-multi-transform", "nextTask": "TSN-PAIR-chamfer-cut",
"summary": { "summary": {
"tasks": 1756, "tasks": 1756,
"completed": 317, "completed": 337,
"inProgress": 1, "inProgress": 1,
"pending": 1438 "pending": 1418
}, },
"milestones": [ "milestones": [
{ {
@@ -4206,181 +4206,235 @@
{ {
"id": "TSN-PAIR-groove-multi-transform", "id": "TSN-PAIR-groove-multi-transform",
"title": "Classify ordered operation pair groove->multi-transform", "title": "Classify ordered operation pair groove->multi-transform",
"status": "in_progress", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "groove->multi-transform", "pair": "groove->multi-transform",
"classification": "accepted",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builders-mutation-naming-and-fcstd-resave-pass"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-fuse", "id": "TSN-PAIR-fillet-fuse",
"title": "Classify ordered operation pair fillet->fuse", "title": "Classify ordered operation pair fillet->fuse",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->fuse", "pair": "fillet->fuse",
"classification": "accepted",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builders-mutation-naming-and-fcstd-resave-pass"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-cut", "id": "TSN-PAIR-fillet-cut",
"title": "Classify ordered operation pair fillet->cut", "title": "Classify ordered operation pair fillet->cut",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->cut", "pair": "fillet->cut",
"classification": "accepted",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builders-mutation-naming-and-fcstd-resave-pass"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-common", "id": "TSN-PAIR-fillet-common",
"title": "Classify ordered operation pair fillet->common", "title": "Classify ordered operation pair fillet->common",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->common", "pair": "fillet->common",
"classification": "accepted",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builders-mutation-naming-and-fcstd-resave-pass"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-rotate", "id": "TSN-PAIR-fillet-rotate",
"title": "Classify ordered operation pair fillet->rotate", "title": "Classify ordered operation pair fillet->rotate",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->rotate", "pair": "fillet->rotate",
"classification": "accepted",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builders-mutation-naming-and-fcstd-resave-pass"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-pad", "id": "TSN-PAIR-fillet-pad",
"title": "Classify ordered operation pair fillet->pad", "title": "Classify ordered operation pair fillet->pad",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->pad", "pair": "fillet->pad",
"classification": "rejected",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builder-and-freecad-profile-reject-with-clean-resave"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-pocket", "id": "TSN-PAIR-fillet-pocket",
"title": "Classify ordered operation pair fillet->pocket", "title": "Classify ordered operation pair fillet->pocket",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->pocket", "pair": "fillet->pocket",
"classification": "accepted",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builders-mutation-naming-and-fcstd-resave-pass"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-loft", "id": "TSN-PAIR-fillet-loft",
"title": "Classify ordered operation pair fillet->loft", "title": "Classify ordered operation pair fillet->loft",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->loft", "pair": "fillet->loft",
"classification": "rejected",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-pipe", "id": "TSN-PAIR-fillet-pipe",
"title": "Classify ordered operation pair fillet->pipe", "title": "Classify ordered operation pair fillet->pipe",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->pipe", "pair": "fillet->pipe",
"classification": "rejected",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-revolution", "id": "TSN-PAIR-fillet-revolution",
"title": "Classify ordered operation pair fillet->revolution", "title": "Classify ordered operation pair fillet->revolution",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->revolution", "pair": "fillet->revolution",
"classification": "rejected",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builder-and-freecad-profile-reject-with-clean-resave"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-groove", "id": "TSN-PAIR-fillet-groove",
"title": "Classify ordered operation pair fillet->groove", "title": "Classify ordered operation pair fillet->groove",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->groove", "pair": "fillet->groove",
"classification": "accepted",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builders-mutation-naming-and-fcstd-resave-pass"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-fillet", "id": "TSN-PAIR-fillet-fillet",
"title": "Classify ordered operation pair fillet->fillet", "title": "Classify ordered operation pair fillet->fillet",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->fillet", "pair": "fillet->fillet",
"classification": "rejected",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builder-and-freecad-profile-reject-with-clean-resave"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-chamfer", "id": "TSN-PAIR-fillet-chamfer",
"title": "Classify ordered operation pair fillet->chamfer", "title": "Classify ordered operation pair fillet->chamfer",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->chamfer", "pair": "fillet->chamfer",
"classification": "rejected",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builder-and-freecad-profile-reject-with-clean-resave"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-hole", "id": "TSN-PAIR-fillet-hole",
"title": "Classify ordered operation pair fillet->hole", "title": "Classify ordered operation pair fillet->hole",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->hole", "pair": "fillet->hole",
"classification": "accepted",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builders-mutation-naming-and-fcstd-resave-pass"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-draft", "id": "TSN-PAIR-fillet-draft",
"title": "Classify ordered operation pair fillet->draft", "title": "Classify ordered operation pair fillet->draft",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->draft", "pair": "fillet->draft",
"classification": "rejected",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builder-and-freecad-profile-reject-with-clean-resave"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-thickness", "id": "TSN-PAIR-fillet-thickness",
"title": "Classify ordered operation pair fillet->thickness", "title": "Classify ordered operation pair fillet->thickness",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->thickness", "pair": "fillet->thickness",
"classification": "rejected",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-and-freecad-profile-no-op-with-clean-resave"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-linear-pattern", "id": "TSN-PAIR-fillet-linear-pattern",
"title": "Classify ordered operation pair fillet->linear-pattern", "title": "Classify ordered operation pair fillet->linear-pattern",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->linear-pattern", "pair": "fillet->linear-pattern",
"classification": "accepted",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builders-mutation-naming-and-fcstd-resave-pass"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-fillet-polar-pattern", "id": "TSN-PAIR-fillet-polar-pattern",
"title": "Classify ordered operation pair fillet->polar-pattern", "title": "Classify ordered operation pair fillet->polar-pattern",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->polar-pattern", "pair": "fillet->polar-pattern",
"classification": "accepted",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builders-mutation-naming-and-fcstd-resave-pass"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
@@ -4394,27 +4448,33 @@
{ {
"id": "TSN-PAIR-fillet-multi-transform", "id": "TSN-PAIR-fillet-multi-transform",
"title": "Classify ordered operation pair fillet->multi-transform", "title": "Classify ordered operation pair fillet->multi-transform",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "fillet->multi-transform", "pair": "fillet->multi-transform",
"classification": "accepted",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builders-mutation-naming-and-fcstd-resave-pass"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-chamfer-fuse", "id": "TSN-PAIR-chamfer-fuse",
"title": "Classify ordered operation pair chamfer->fuse", "title": "Classify ordered operation pair chamfer->fuse",
"status": "pending", "status": "completed",
"dependencies": [ "dependencies": [],
"TSN-PROD-DRIFT-001"
],
"pair": "chamfer->fuse", "pair": "chamfer->fuse",
"classification": "accepted",
"evidence": [
"config/freecad-ordered-operation-pair-classification.json",
"native-builders-mutation-naming-and-fcstd-resave-pass"
],
"exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence" "exit": "native acceptance or rejection is recorded; accepted pairs include builder, mutation, naming, and resave evidence"
}, },
{ {
"id": "TSN-PAIR-chamfer-cut", "id": "TSN-PAIR-chamfer-cut",
"title": "Classify ordered operation pair chamfer->cut", "title": "Classify ordered operation pair chamfer->cut",
"status": "pending", "status": "in_progress",
"dependencies": [ "dependencies": [
"TSN-PROD-DRIFT-001" "TSN-PROD-DRIFT-001"
], ],

File diff suppressed because it is too large Load Diff

View File

@@ -390,7 +390,7 @@
{ {
"path": "sw.js", "path": "sw.js",
"bytes": 1390, "bytes": 1390,
"sha256": "dfe8893e68cda6991ca64b5f5eaa19097c5b6d8f4bdc13f2423328950671a5be" "sha256": "8b0477955514535bb11cef3733135b67422cc8c085b4331c3b9cb1a1adc9d852"
}, },
{ {
"path": "vendor/camotics/camotics-sweep.wasm", "path": "vendor/camotics/camotics-sweep.wasm",
@@ -412,7 +412,7 @@
"status": "signed", "status": "signed",
"algorithm": "Ed25519", "algorithm": "Ed25519",
"keyId": "bitbybit-local-release-2026", "keyId": "bitbybit-local-release-2026",
"payloadSha256": "e6d83894b72503eed170bec685d761d868784e28fbc5a221d78c0b270a472b37", "payloadSha256": "ad5f25aeb7e1382b3172d9919357a9831ac198841e54304a6789665cd21cda31",
"signature": "FZ+gouN7G/kpiInw2zMQ5FBLXPJzurcdHv3lk4NThmtPGe6NXoe8KkIngxO8vbRba0E2e1jwoSgbv2HfvY5xAw==" "signature": "c8xaS8E7BOThrQKOK9WlCLpMazuqlBZ6eMKbCTt+tzeC0b4TyEEaiVvjelUtFDjo5PK0fVYPeYISrS2895LTDA=="
} }
} }

View File

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

View File

@@ -205,6 +205,26 @@ const expectedPairs = [
{ 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->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->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] }, { 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] },
{ pair: 'groove->multi-transform', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', firstInputCount: 2, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse(ordered)', secondInputCount: 1, mutationParameter: 'multiTranslationX', mutationTrajectory: [8, 9, 8] },
{ pair: 'fillet->fuse', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, secondBuilder: 'BRepAlgoAPI_Fuse', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [8, 7, 8] },
{ pair: 'fillet->cut', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, secondBuilder: 'BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'toolSize', mutationTrajectory: [4, 3, 4] },
{ pair: 'fillet->common', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, secondBuilder: 'BRepAlgoAPI_Common', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [7, 6, 7] },
{ pair: 'fillet->rotate', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, secondBuilder: 'BRepBuilderAPI_Transform', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [15, 22.5, 15] },
{ pair: 'fillet->pad', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepFilletAPI_MakeFillet', 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: 'fillet->pocket', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, secondBuilder: 'BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'length', mutationTrajectory: [5, 4, 5] },
{ pair: 'fillet->loft', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, 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: 'fillet->pipe', decision: 'rejected', reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, 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: 'fillet->revolution', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepFilletAPI_MakeFillet', 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: 'fillet->groove', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, secondBuilder: 'BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut', secondInputCount: 2, mutationParameter: 'angle', mutationTrajectory: [360, 180, 360] },
{ pair: 'fillet->fillet', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepFilletAPI_MakeFillet', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [0.4, 0.6, 0.4], nativeTypeId: 'Part::Fillet', freeCadDiagnostic: 'There are no suitable edges for chamfer or fillet' },
{ pair: 'fillet->chamfer', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepFilletAPI_MakeChamfer', secondInputCount: 1, mutationParameter: 'distance', mutationTrajectory: [0.4, 0.6, 0.4], nativeTypeId: 'Part::Chamfer', freeCadDiagnostic: 'There are no suitable edges for chamfer or fillet' },
{ pair: 'fillet->hole', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, secondBuilder: 'BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut', secondInputCount: 1, mutationParameter: 'radius', mutationTrajectory: [1, 1.5, 1] },
{ pair: 'fillet->draft', decision: 'rejected', reasonCode: 'native-builder-and-freecad-profile-reject-with-clean-resave', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, rejectionAuthority: 'occt-builder', secondBuilder: 'BRepOffsetAPI_DraftAngle', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [5, 8, 5], nativeTypeId: 'PartDesign::Draft', freeCadDiagnostic: '', inputShapeTransfer: 'partdesign-feature-summary-exact-with-explicit-brep-drift' },
{ pair: 'fillet->thickness', decision: 'rejected', reasonCode: 'native-and-freecad-profile-no-op-with-clean-resave', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, rejectionAuthority: 'occt-no-op', kernelOutcome: 'no-op', secondBuilder: 'BRepOffsetAPI_MakeThickSolid', secondInputCount: 1, mutationParameter: 'offset', mutationTrajectory: [-0.4, -0.6, -0.4], nativeTypeId: 'Part::Thickness', freeCadDiagnostic: 'Valid', freecadProfileNoOp: true },
{ pair: 'fillet->linear-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'translationX', mutationTrajectory: [8, 9, 8] },
{ pair: 'fillet->polar-pattern', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse', secondInputCount: 1, mutationParameter: 'angle', mutationTrajectory: [30, 45, 30] },
{ pair: 'fillet->multi-transform', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepFilletAPI_MakeFillet', firstInputCount: 1, secondBuilder: 'BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse(ordered)', secondInputCount: 1, mutationParameter: 'multiTranslationX', mutationTrajectory: [8, 9, 8] },
{ pair: 'chamfer->fuse', decision: 'accepted', reasonCode: 'native-builders-mutation-naming-and-fcstd-resave-pass', firstBuilder: 'BRepFilletAPI_MakeChamfer', firstInputCount: 1, secondBuilder: 'BRepAlgoAPI_Fuse', secondInputCount: 2, mutationParameter: 'toolOffsetX', mutationTrajectory: [8, 7, 8] },
] ]
if (report.nativeProbe?.artifacts?.length !== 3 || report.classifications?.length !== expectedPairs.length) fail('native artifacts or classification prefix is incomplete.') 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()) { for (const [index, entry] of report.classifications.entries()) {
@@ -226,20 +246,31 @@ for (const [index, entry] of report.classifications.entries()) {
} }
} else { } else {
if (rejection.kernelRestoredExactly !== true || rejection.kernelAttempts?.length !== 3) fail(`${expected.pair} OCCT diagnostic trajectory is incomplete.`) if (rejection.kernelRestoredExactly !== true || rejection.kernelAttempts?.length !== 3) fail(`${expected.pair} OCCT diagnostic trajectory is incomplete.`)
for (const [attemptIndex, attempt] of rejection.kernelAttempts.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 !== expected.kernelOutcome || attempt.resultProduced !== true || attempt.shapeProduced !== true || attempt.validShapeProduced !== (expected.kernelOutcome === 'accepted') || attempt.historyProduced !== true || attempt.historyProvider !== 'occt-native' || attempt.summary?.isValid !== (expected.kernelOutcome === 'accepted') || attempt.historyRecords < 1 || !attempt.historySha256 || attempt.authority !== (expected.kernelOutcome === 'accepted' ? 'kernel-superset-only' : 'kernel-invalid-result')) fail(`${expected.pair} OCCT compatibility attempt ${attemptIndex} is invalid.`) for (const [attemptIndex, attempt] of rejection.kernelAttempts.entries()) {
const validKernelShape = expected.kernelOutcome !== 'invalid-result'
const expectedAuthority = expected.kernelOutcome === 'no-op' ? 'kernel-no-op' : expected.kernelOutcome === 'accepted' ? 'kernel-superset-only' : 'kernel-invalid-result'
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 !== expected.kernelOutcome || attempt.resultProduced !== true || attempt.shapeProduced !== true || attempt.validShapeProduced !== validKernelShape || attempt.historyProduced !== true || attempt.historyProvider !== 'occt-native' || attempt.summary?.isValid !== validKernelShape || attempt.historyRecords < 1 || !attempt.historySha256 || attempt.authority !== expectedAuthority || (expected.kernelOutcome === 'no-op' && JSON.stringify(attempt.summary) !== JSON.stringify(entry.first.summary))) fail(`${expected.pair} OCCT compatibility attempt ${attemptIndex} is invalid.`)
}
} }
const persistence = rejection.persistence const persistence = rejection.persistence
const diagnostic = persistence?.diagnostic const diagnostic = persistence?.diagnostic
const freecadProfileAccepted = expected.freecadProfileAccepted === true 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)) const freecadProfileNoOp = expected.freecadProfileNoOp === 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)) || (freecadProfileNoOp && persistence?.checks?.freecadProfileNoOp === true && persistence?.checks?.freecadProfileAccepted === false && persistence?.checks?.nativeFeatureRejected === false && Object.entries(persistence?.checks ?? {}).filter(([key]) => !['nativeFeatureRejected', 'freecadProfileAccepted'].includes(key)).every(([, value]) => value === true)) || (!freecadProfileAccepted && !freecadProfileNoOp && 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 (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 (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 (freecadProfileNoOp) {
if (diagnostic.shapeNull !== false || diagnostic.shapeValid !== true || diagnostic.statusString !== 'Valid' || !diagnostic.state?.includes('Up-to-date') || JSON.stringify(diagnostic.resultShape) !== JSON.stringify(diagnostic.inputShape)) fail(`${expected.pair} FreeCAD no-op evidence is incomplete.`)
} else 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.`) 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') { } 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.`) 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 (Object.hasOwn(expected, 'freeCadDiagnostic') && diagnostic.statusString !== expected.freeCadDiagnostic) fail(`${expected.pair} FreeCAD profile diagnostic changed.`)
if (expected.inputShapeTransfer === 'partdesign-feature-summary-exact-with-explicit-brep-drift') {
const input = diagnostic.input
if (input?.name !== 'DraftBase' || input.typeId !== 'PartDesign::Feature' || JSON.stringify(input.subelements) !== JSON.stringify(['Face1']) || input.sourceName !== persistence.source?.name || input.shapeSummaryTransferredExactly !== true || typeof input.sourceBrepSha256 !== 'string' || typeof input.draftBaseBrepSha256 !== 'string' || input.sourceBrepSha256 === input.draftBaseBrepSha256) fail(`${expected.pair} PartDesign input Shape transfer evidence is incomplete.`)
}
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.`) 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 const phases = persistence.phases
if (JSON.stringify(phases?.initial) !== JSON.stringify(phases?.reopened) || JSON.stringify(phases?.initial) !== JSON.stringify(phases?.resaved) || phases.initial.objects?.length !== persistence.rollback.before.objects.length || phases.initial.objects?.some(({ typeId }) => typeId === 'PartDesign::Body' || typeId === 'PartDesign::Pad' || typeId === 'Part::Loft' || typeId === 'Part::Revolution')) fail(`${expected.pair} polluted the clean FCStd save/reopen/resave chain.`) if (JSON.stringify(phases?.initial) !== JSON.stringify(phases?.reopened) || JSON.stringify(phases?.initial) !== JSON.stringify(phases?.resaved) || phases.initial.objects?.length !== persistence.rollback.before.objects.length || phases.initial.objects?.some(({ typeId }) => typeId === 'PartDesign::Body' || typeId === 'PartDesign::Pad' || typeId === 'Part::Loft' || typeId === 'Part::Revolution')) fail(`${expected.pair} polluted the clean FCStd save/reopen/resave chain.`)
@@ -255,6 +286,6 @@ for (const [index, entry] of report.classifications.entries()) {
const phases = entry.persistence.phases 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.`) 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: 187, accepted: 143, rejected: 44, unknown: 174 } const expectedSummary = { registeredOperations: 19, orderedPairs: 361, classifiedPairs: 207, accepted: 155, rejected: 52, unknown: 154 }
if (JSON.stringify(report.summary) !== JSON.stringify(expectedSummary)) fail('summary is inconsistent.') 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)) 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

@@ -246,6 +246,29 @@ def collect_rejected():
feature.Edges = [(index, 0.4, 0.4) for index in range(1, len(source.Shape.Edges) + 1)] 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["parameter"] = {"name": "distance", "value": 0.4}
diagnostic["input"] = link_snapshot(feature.Base) diagnostic["input"] = link_snapshot(feature.Base)
elif operation == "draft":
body = document.addObject("PartDesign::Body", "Body")
draft_base = body.newObject("PartDesign::Feature", "DraftBase")
draft_base.Shape = source.Shape.copy()
datum_plane = body.newObject("PartDesign::Plane", "DatumPlane")
datum_plane.AttachmentSupport = [(document.XY_Plane, "")]
datum_plane.MapMode = "FlatFace"
datum_line = body.newObject("PartDesign::Line", "DatumLine")
datum_line.AttachmentSupport = [(document.Z_Axis, "")]
datum_line.MapMode = "TwoPointLine"
feature = body.newObject("PartDesign::Draft", "PairResult")
feature.Base = (draft_base, ["Face1"])
feature.NeutralPlane = (datum_plane, [""])
feature.PullDirection = (datum_line, [""])
feature.Angle = 5.0
feature.Reversed = False
diagnostic["parameter"] = {"name": "angle", "value": round(float(feature.Angle), 7)}
diagnostic["input"] = link_snapshot(feature.Base)
diagnostic["input"]["sourceName"] = source.Name
draft_base_shape = shape_snapshot_from_shape(draft_base.Shape, include_brep=True)
diagnostic["input"]["shapeSummaryTransferredExactly"] = {key: value for key, value in draft_base_shape.items() if key != "brepSha256"} == {key: value for key, value in baseline_source_shape.items() if key != "brepSha256"}
diagnostic["input"]["sourceBrepSha256"] = baseline_source_shape["brepSha256"]
diagnostic["input"]["draftBaseBrepSha256"] = draft_base_shape["brepSha256"]
elif operation == "thickness": elif operation == "thickness":
feature = document.addObject("Part::Thickness", "PairResult") feature = document.addObject("Part::Thickness", "PairResult")
feature.Faces = (source, ["Face2"]) feature.Faces = (source, ["Face2"])
@@ -268,6 +291,8 @@ def collect_rejected():
diagnostic["state"] = [str(value) for value in feature.State] diagnostic["state"] = [str(value) for value in feature.State]
diagnostic["shapeNull"] = bool(feature.Shape.isNull()) diagnostic["shapeNull"] = bool(feature.Shape.isNull())
diagnostic["shapeValid"] = False if feature.Shape.isNull() else bool(feature.Shape.isValid()) diagnostic["shapeValid"] = False if feature.Shape.isNull() else bool(feature.Shape.isValid())
if not feature.Shape.isNull():
diagnostic["resultShape"] = shape_snapshot_from_shape(feature.Shape)
finally: finally:
document.abortTransaction() document.abortTransaction()
document.recompute() document.recompute()
@@ -297,12 +322,13 @@ def collect_rejected():
or "Error" in diagnostic["state"] or "Error" in diagnostic["state"]
or "Invalid" in diagnostic["state"] or "Invalid" in diagnostic["state"]
) )
freecad_profile_accepted = operation in ("fillet", "chamfer", "thickness") and not feature_rejected and not diagnostic["shapeNull"] and diagnostic["shapeValid"] and diagnostic["statusString"] == "Valid" freecad_profile_no_op = operation == "thickness" and not feature_rejected and not diagnostic["shapeNull"] and diagnostic["shapeValid"] and diagnostic["statusString"] == "Valid" and diagnostic.get("resultShape") == diagnostic.get("inputShape")
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] freecad_profile_accepted = operation in ("fillet", "chamfer", "thickness") and not freecad_profile_no_op and not feature_rejected and not diagnostic["shapeNull"] and diagnostic["shapeValid"] and diagnostic["statusString"] == "Valid"
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", [])]) expected_type_id = {"pad": "PartDesign::Pad", "loft": "Part::Loft", "pipe": "Part::Sweep", "revolution": "Part::Revolution", "fuse": "Part::Fuse", "fillet": "Part::Fillet", "chamfer": "Part::Chamfer", "draft": "PartDesign::Draft", "thickness": "Part::Thickness"}[operation]
input_names = [diagnostic.get("input", {}).get("sourceName", diagnostic.get("input", {}).get("name"))] if operation in ("pad", "revolution", "fillet", "chamfer", "draft", "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 = { checks = {
"nativeFeatureConstructed": diagnostic.get("nativeTypeId") == expected_type_id, "nativeFeatureConstructed": diagnostic.get("nativeTypeId") == expected_type_id,
"fuseSolidUsedAsInput": source_name in input_names and diagnostic.get("inputShape", {}).get("solids") == 1, "fuseSolidUsedAsInput": source_name in input_names and diagnostic.get("inputShape", {}).get("solids") == 1 and (operation != "draft" or diagnostic.get("input", {}).get("shapeSummaryTransferredExactly") is True),
"recomputeStatusCaptured": diagnostic["recomputeResult"] is not None or diagnostic["exceptionType"] is not None, "recomputeStatusCaptured": diagnostic["recomputeResult"] is not None or diagnostic["exceptionType"] is not None,
"nativeFeatureRejected": feature_rejected, "nativeFeatureRejected": feature_rejected,
"freecadProfileAccepted": freecad_profile_accepted, "freecadProfileAccepted": freecad_profile_accepted,
@@ -311,6 +337,8 @@ def collect_rejected():
"documentUnpolluted": baseline == initial, "documentUnpolluted": baseline == initial,
"documentStable": initial == reopened_snapshot and initial == resaved_snapshot, "documentStable": initial == reopened_snapshot and initial == resaved_snapshot,
} }
if freecad_profile_no_op:
checks["freecadProfileNoOp"] = True
return { return {
"schemaVersion": 1, "schemaVersion": 1,
"baselineId": "freecad-1.1.1-ordered-operation-pair-resave", "baselineId": "freecad-1.1.1-ordered-operation-pair-resave",
@@ -332,7 +360,7 @@ def collect_rejected():
}, },
"phases": {"initial": initial, "reopened": reopened_snapshot, "resaved": resaved_snapshot}, "phases": {"initial": initial, "reopened": reopened_snapshot, "resaved": resaved_snapshot},
"checks": checks, "checks": checks,
"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", "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 (freecad_profile_no_op and all(value for key, value in checks.items() if key not in ("nativeFeatureRejected", "freecadProfileAccepted"))) or (not freecad_profile_accepted and feature_rejected and all(value for key, value in checks.items() if key != "freecadProfileAccepted")) else "failed",
} }
finally: finally:
for name in list(App.listDocuments().keys()): for name in list(App.listDocuments().keys()):

View File

@@ -119,6 +119,13 @@ const pairSpecOverrides = {
'groove->thickness': { ...operationSpecs.thickness, 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->polar-pattern': { ...operationSpecs['polar-pattern'], nominal: { ...operationSpecs['polar-pattern'].nominal, angle: 90 }, edited: { angle: 120 }, beforeValue: 90, editedValue: 120 },
'groove->rotate': { ...operationSpecs.rotate }, 'groove->rotate': { ...operationSpecs.rotate },
'fillet->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 8 }, edited: { toolSize: 5, toolOffset: 7 }, beforeValue: 8, editedValue: 7 },
'fillet->common': { ...operationSpecs.common, nominal: { toolSize: 5, toolOffset: 7 }, edited: { toolSize: 5, toolOffset: 6 }, beforeValue: 7, editedValue: 6 },
'fillet->fillet': { ...operationSpecs.fillet, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'fillet->chamfer': { ...operationSpecs.chamfer, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'fillet->draft': { ...operationSpecs.draft, decision: 'rejected', rejectionAuthority: 'occt-builder' },
'fillet->thickness': { ...operationSpecs.thickness, decision: 'rejected', rejectionAuthority: 'occt-no-op', kernelOutcome: 'no-op' },
'chamfer->fuse': { ...operationSpecs.fuse, nominal: { toolSize: 5, toolOffset: 8 }, edited: { toolSize: 5, toolOffset: 7 }, beforeValue: 8, editedValue: 7 },
} }
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 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 }) => {
@@ -236,7 +243,8 @@ const captureKernelCompatibilityStage = (module, definition, parameterValue, exp
try { try {
const outcome = response.summary?.isValid === true ? 'accepted' : 'invalid-result' const outcome = response.summary?.isValid === true ? 'accepted' : 'invalid-result'
const noHistorySuperset = expectedOutcome === 'accepted-no-history' && outcome === 'accepted' 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) })}`) const noOp = expectedOutcome === 'no-op' && outcome === 'accepted' && JSON.stringify(canonicalSummary(response.summary)) === JSON.stringify(definition.inputSummary)
if (response.provider !== 'occt-native' || (!noHistorySuperset && !noOp && 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, inputSummary: definition.inputSummary, resultStep: Boolean(response.resultStep), records: response.records?.length, result: Boolean(response.result) })}`)
return { return {
operation: definition.operation, operation: definition.operation,
builder: operationSpecs[definition.operation].builder, builder: operationSpecs[definition.operation].builder,
@@ -244,7 +252,7 @@ const captureKernelCompatibilityStage = (module, definition, parameterValue, exp
parameter: operationSpecs[definition.operation].parameter, parameter: operationSpecs[definition.operation].parameter,
parameterValue, parameterValue,
runtime: 'occt-native', runtime: 'occt-native',
outcome: noHistorySuperset ? expectedOutcome : outcome, outcome: noHistorySuperset || noOp ? expectedOutcome : outcome,
resultProduced: true, resultProduced: true,
shapeProduced: true, shapeProduced: true,
validShapeProduced: response.summary.isValid === true, validShapeProduced: response.summary.isValid === true,
@@ -253,7 +261,7 @@ const captureKernelCompatibilityStage = (module, definition, parameterValue, exp
summary: canonicalSummary(response.summary), summary: canonicalSummary(response.summary),
historyRecords: response.records.length, historyRecords: response.records.length,
historySha256: sha256(JSON.stringify(response.records)), historySha256: sha256(JSON.stringify(response.records)),
authority: noHistorySuperset ? 'kernel-superset-no-history' : outcome === 'accepted' ? 'kernel-superset-only' : 'kernel-invalid-result', authority: noOp ? 'kernel-no-op' : noHistorySuperset ? 'kernel-superset-no-history' : outcome === 'accepted' ? 'kernel-superset-only' : 'kernel-invalid-result',
} }
} finally { } finally {
response.result?.delete?.() response.result?.delete?.()
@@ -340,7 +348,8 @@ const persistPair = async (pair, step, namingEvidence, decision) => {
const payload = markerIndex >= 0 ? output.slice(markerIndex + marker.length).match(/\{.*\}/s)?.[0] : undefined 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()}`) 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) const persistence = JSON.parse(payload)
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?.checks || typeof persistence.checks !== 'object') fail(`FreeCAD FCStd replay returned incomplete evidence: ${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.freecadProfileNoOp === true && persistence.checks.freecadProfileAccepted === false && persistence.checks.nativeFeatureRejected === false && Object.entries(persistence.checks).filter(([key]) => !['nativeFeatureRejected', 'freecadProfileAccepted'].includes(key)).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)}`) 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 return persistence
} finally { } finally {
@@ -384,25 +393,30 @@ const classifyPair = async (fromOperation, toOperation, pairIndex) => {
const secondDefinition = { const secondDefinition = {
operation: toOperation, operation: toOperation,
objectStep: first.response.resultStep, objectStep: first.response.resultStep,
inputSummary: first.report.summary,
...spec.nominal, ...spec.nominal,
stage: { stageId: `pair:${pairId}:second`, ordinal: 1, objectId: `pair:${pairId}:first`, objectTag: 1101 + pairIndex * 100, resultObjectId: `pair:${pairId}:second`, resultObjectTag: 1201 + pairIndex * 100, namingEvidence: first.namingEvidence }, stage: { stageId: `pair:${pairId}:second`, ordinal: 1, objectId: `pair:${pairId}:first`, objectTag: 1101 + pairIndex * 100, resultObjectId: `pair:${pairId}:second`, resultObjectTag: 1201 + pairIndex * 100, namingEvidence: first.namingEvidence },
} }
if (spec.decision === 'rejected') { if (spec.decision === 'rejected') {
try { try {
const trajectory = [spec.beforeValue, spec.editedValue, spec.beforeValue] const trajectory = [spec.beforeValue, spec.editedValue, spec.beforeValue]
if (spec.rejectionAuthority === 'freecad-document') { if (['freecad-document', 'occt-no-op'].includes(spec.rejectionAuthority)) {
const kernelAttempts = trajectory.map((value) => captureKernelCompatibilityStage(module, { ...secondDefinition, [spec.parameter]: value }, value, spec.kernelOutcome)) const kernelAttempts = trajectory.map((value) => captureKernelCompatibilityStage(module, { ...secondDefinition, [spec.parameter]: value }, value, spec.kernelOutcome))
const kernelRestoredExactly = JSON.stringify(kernelAttempts[0]) === JSON.stringify(kernelAttempts[2]) const kernelRestoredExactly = JSON.stringify(kernelAttempts[0]) === JSON.stringify(kernelAttempts[2])
if (!kernelRestoredExactly) fail(`${pair} OCCT diagnostic probe did not restore exactly.`) if (!kernelRestoredExactly) fail(`${pair} OCCT diagnostic probe did not restore exactly.`)
const persistence = await persistPair(pair, first.response.resultStep, first.namingEvidence, 'rejected') const persistence = await persistPair(pair, first.response.resultStep, first.namingEvidence, 'rejected')
const freecadProfileAccepted = persistence.checks.freecadProfileAccepted === true
const freecadProfileNoOp = persistence.checks.freecadProfileNoOp === true
return { return {
taskId: `TSN-PAIR-${pairId}`, taskId: `TSN-PAIR-${pairId}`,
pair, pair,
classification: 'rejected', classification: 'rejected',
reasonCode: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave', reasonCode: spec.rejectionAuthority === 'occt-no-op'
? freecadProfileAccepted ? 'native-builder-no-op-with-freecad-profile-acceptance-and-clean-resave' : freecadProfileNoOp ? 'native-and-freecad-profile-no-op-with-clean-resave' : 'native-builder-no-op-and-freecad-profile-reject-with-clean-resave'
: 'freecad-input-precondition-reject-with-kernel-diagnostic-and-clean-resave',
nativeDecision: 'rejected', nativeDecision: 'rejected',
first: first.report, first: first.report,
rejection: { scope: 'second-operation-only', authority: 'freecad-document', parameter: spec.parameter, trajectory, kernelAttempts, kernelRestoredExactly, persistence }, rejection: { scope: 'second-operation-only', authority: spec.rejectionAuthority, parameter: spec.parameter, trajectory, kernelAttempts, kernelRestoredExactly, persistence },
} }
} }
const attempts = trajectory.map((value) => captureRejectedStage(module, { ...secondDefinition, [spec.parameter]: value })) const attempts = trajectory.map((value) => captureRejectedStage(module, { ...secondDefinition, [spec.parameter]: value }))
@@ -453,6 +467,7 @@ 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 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'], ['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 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']]
orderedPairPrefix.push(['groove', 'multi-transform'], ['fillet', 'fuse'], ['fillet', 'cut'], ['fillet', 'common'], ['fillet', 'rotate'], ['fillet', 'pad'], ['fillet', 'pocket'], ['fillet', 'loft'], ['fillet', 'pipe'], ['fillet', 'revolution'], ['fillet', 'groove'], ['fillet', 'fillet'], ['fillet', 'chamfer'], ['fillet', 'hole'], ['fillet', 'draft'], ['fillet', 'thickness'], ['fillet', 'linear-pattern'], ['fillet', 'polar-pattern'], ['fillet', 'multi-transform'], ['chamfer', 'fuse'])
const classifications = [] const classifications = []
const selectedPairs = process.env.FREECAD_ONLY_PAIR ? orderedPairPrefix.filter(([fromOperation, toOperation]) => `${fromOperation}->${toOperation}` === process.env.FREECAD_ONLY_PAIR) : orderedPairPrefix 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.`) if (process.env.FREECAD_ONLY_PAIR && selectedPairs.length !== 1) fail(`focused pair ${process.env.FREECAD_ONLY_PAIR} is not in the classified prefix.`)

View File

@@ -1919,4 +1919,186 @@ git diff --check
- `5 -> 6 -> 5` 镜像平面轨迹使体积在 `1000 -> 1190.575222 -> 1000` 间变化,编辑态增至 22 Face并精确恢复名义摘要、history 与语义 naming。 - `5 -> 6 -> 5` 镜像平面轨迹使体积在 `1000 -> 1190.575222 -> 1000` 间变化,编辑态增至 22 Face并精确恢复名义摘要、history 与语义 naming。
- 上游 Groove naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定镜像平面、互补融合、变异、恢复和持久化证据。 - 上游 Groove naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定镜像平面、互补融合、变异、恢复和持久化证据。
### `TSN-PAIR-groove-multi-transform`
`groove->multi-transform` 已完成原生接受分类,并闭合 Groove 起始行的全部 19 个有序操作对:
- 上游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder生成有效 1 Solid、体积 `995.287611`、10 Face 和 18 条原生 history。
- 下游使用 ordered `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` builder依次执行 X 平移和关于 `x=5` 的镜像并融合;名义结果为有效 1 Solid、24 Face、体积 `2600`,包含 525 条原生 history。
- `8 -> 9 -> 8` 首步 X 平移轨迹使体积在 `2600 -> 2797.6768884 -> 2600` 间变化,编辑态增至 40 Face并精确恢复名义摘要、history 与语义 naming。
- 上游 Groove naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定 ordered builder、变异、恢复和持久化证据。
### `TSN-PAIR-fillet-fuse`
`fillet->fuse` 已完成原生接受分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Fuse` 双输入 builder并以相交工具形成有效 1 Solid名义结果为 33 Face、体积 `1071.3666774`,包含 79 条原生 history。
- `8 -> 7 -> 8` 工具 X 偏移轨迹使体积在 `1071.3666774 -> 1046.4010137 -> 1071.3666774` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Fillet naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定相交融合、变异、恢复和持久化证据。
### `TSN-PAIR-fillet-cut`
`fillet->cut` 已完成原生接受分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Cut` 双输入 builder对 Fillet 结果执行减料;名义结果为有效 1 Solid、31 Face、体积 `932.1027009`,包含 42 条原生 history。
- `4 -> 3 -> 4` 工具尺寸轨迹使体积在 `932.1027009 -> 969.0683646 -> 932.1027009` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Fillet naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定减料变异、恢复和持久化证据。
### `TSN-PAIR-fillet-common`
`fillet->common` 已完成原生接受分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Common` 双输入 builder以相交工具求 Fillet 结果的布尔交集;名义结果为有效 1 Solid、10 Face、体积 `74.5643421`,包含 360 条原生 history。
- `7 -> 6 -> 7` 工具 X 偏移轨迹使体积在 `74.5643421 -> 99.5300058 -> 74.5643421` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Fillet naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定有体积交集、变异、恢复和持久化证据。
### `TSN-PAIR-fillet-rotate`
`fillet->rotate` 已完成原生接受分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform` 单输入 builder 绕 Z 轴旋转 Fillet 结果;名义结果保持有效 1 Solid、26 Face 和体积 `995.9653557`,包含 338 条原生 history。
- `15 -> 22.5 -> 15` 角度轨迹改变包围盒(名义 `[-2.4982926, 0.0898978, -1e-7, 9.5693604, 12.1575509, 10.0000001]`,编辑态 `[-3.7042092, 0.1226251, -1e-7, 9.1161702, 12.9430046, 10.0000001]`并精确恢复名义摘要、history 与语义 naming。
- 上游 Fillet naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定刚体变换、恢复和持久化证据。
### `TSN-PAIR-fillet-pad`
`fillet->pad` 已完成原生拒绝分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- OCCT `BRepPrimAPI_MakePrism` 对 Fillet Solid 在 `5 -> 7.5 -> 5` 长度轨迹中稳定于 builder 阶段拒绝,三次均不产生结果 Shape 或 history。
- FreeCAD `PartDesign::Pad` 使用同一 Fillet Solid 作为 Profile返回 `FeatureExtrusion: Length: Could not extrude the sketch!`,结果 Shape 为空且状态含 `Invalid`;未伪造下游 naming 或接受变异证据。
- transaction abort 后对象集、源 Shape 和 BREP 哈希精确恢复;干净 FCStd save/reopen/resave 三阶段一致focused gate 已锁定拒绝阶段、诊断、回滚和持久化证据。
### `TSN-PAIR-fillet-pocket`
`fillet->pocket` 已完成原生接受分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- 下游使用真实 `BRepPrimAPI_MakePrism+BRepAlgoAPI_Cut` 双输入 builder对 Fillet 结果执行减料拉伸;名义结果为有效 1 Solid、28 Face、体积 `966.2980048`,包含 100 条原生 history。
- `5 -> 4 -> 5` 深度轨迹使体积在 `966.2980048 -> 972.2636685 -> 966.2980048` 间变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Fillet naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定减料拉伸、恢复和持久化证据。
### `TSN-PAIR-fillet-loft`
`fillet->loft` 已完成原生拒绝分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- OCCT `BRepOffsetAPI_ThruSections` 在 `false -> true -> false` ruled 轨迹中生成有效 1 Solid 和 24 条 history但该结果仅作为 `kernel-superset-only` 诊断,不提升 FreeCAD 能力。
- FreeCAD `Part::Loft` 对 Fillet Solid section 返回 `Profile shape is not a single vertex, edge, wire nor face.`,结果 Shape 为空且状态含 `Invalid`;未伪造下游 naming 或接受变异证据。
- transaction abort 后对象集、源 Shape 和 BREP 哈希精确恢复;干净 FCStd save/reopen/resave 三阶段一致focused gate 已锁定 FreeCAD 决策权、kernel 诊断、回滚和持久化证据。
### `TSN-PAIR-fillet-pipe`
`fillet->pipe` 已完成原生拒绝分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- OCCT `BRepOffsetAPI_MakePipe` 在 `15 -> 12 -> 15` spine 长度轨迹中均产生无效零体积 Shape 和 28 条 history仅记为 `kernel-invalid-result`,不伪造接受 Shape。
- FreeCAD `Part::Sweep` 对 Fillet 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-fillet-revolution`
`fillet->revolution` 已完成原生拒绝分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- OCCT `BRepPrimAPI_MakeRevol` 对 Fillet Solid 在 `360 -> 270 -> 360` 角度轨迹中稳定于 builder 阶段拒绝,三次均不产生结果 Shape 或 history。
- FreeCAD `Part::Revolution` 使用同一 Fillet Solid 作为 Source返回 `Solids are not Processed`,结果 Shape 为空且状态含 `Invalid`;未伪造下游 naming 或接受变异证据。
- transaction abort 后对象集、源 Shape 和 BREP 哈希精确恢复;干净 FCStd save/reopen/resave 三阶段一致focused gate 已锁定拒绝阶段、精确诊断、回滚和持久化证据。
### `TSN-PAIR-fillet-groove`
`fillet->groove` 已完成原生接受分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- 下游使用真实 `BRepPrimAPI_MakeRevol+BRepAlgoAPI_Cut` 双输入 builder 对 Fillet 结果执行旋转减料;名义结果为有效 1 Solid、30 Face、体积 `991.2529668`,包含 18 条原生 history。
- `360 -> 180 -> 360` 角度轨迹使拓扑从 30 Face 变为 27 Face 后恢复并精确恢复名义摘要、history 与语义 naming。
- 上游 Fillet naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定旋转减料、变异、恢复和持久化证据。
### `TSN-PAIR-fillet-fillet`
`fillet->fillet` 已完成原生拒绝分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- 第二个 OCCT `BRepFilletAPI_MakeFillet` 对已圆角 Solid 在 `0.4 -> 0.6 -> 0.4` 半径轨迹中稳定于 builder 阶段拒绝,三次均不产生结果 Shape 或 history。
- FreeCAD `Part::Fillet` 使用同一 Fillet Solid 作为 Base返回 `There are no suitable edges for chamfer or fillet`,结果 Shape 为空且状态含 `Invalid`;未伪造下游 naming 或接受变异证据。
- transaction abort 后对象集、源 Shape 和 BREP 哈希精确恢复;干净 FCStd save/reopen/resave 三阶段一致focused gate 已锁定拒绝阶段、精确诊断、回滚和持久化证据。
### `TSN-PAIR-fillet-chamfer`
`fillet->chamfer` 已完成原生拒绝分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- OCCT `BRepFilletAPI_MakeChamfer` 对 Fillet Solid 在 `0.4 -> 0.6 -> 0.4` 距离轨迹中稳定于 builder 阶段拒绝,三次均不产生结果 Shape 或 history。
- FreeCAD `Part::Chamfer` 使用同一 Fillet Solid 作为 Base返回 `There are no suitable edges for chamfer or fillet`,结果 Shape 为空且状态含 `Invalid`;未伪造下游 naming 或接受变异证据。
- transaction abort 后对象集、源 Shape 和 BREP 哈希精确恢复;干净 FCStd save/reopen/resave 三阶段一致focused gate 已锁定拒绝阶段、精确诊断、回滚和持久化证据。
### `TSN-PAIR-fillet-hole`
`fillet->hole` 已完成原生接受分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- 下游使用真实 `BRepPrimAPI_MakeCylinder+BRepAlgoAPI_Cut` builder 对 Fillet 结果执行中心贯穿孔;名义结果为有效 1 Solid、27 Face、体积 `964.5494292`,包含 23 条原生 history。
- `1 -> 1.5 -> 1` 半径轨迹使体积在 `964.5494292 -> 925.279521 -> 964.5494292` 间变化并精确恢复名义摘要、history 与语义 naming。
- 下游获得无歧义 `native-evidence` naming上游 Fillet 证据已传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定。
### `TSN-PAIR-fillet-draft`
`fillet->draft` 已完成原生拒绝分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- 只读原生适用性扫描枚举 Fillet 结果的全部 26 个 Face在锁定的 +Z 拉伸方向与 XY 中性面下均无有效 Draft Shape没有用选面 fallback 伪造接受。
- OCCT `BRepOffsetAPI_DraftAngle` 在 `5 -> 8 -> 5` 角度轨迹中稳定于 builder 阶段拒绝,三次均不产生结果 Shape 或 history。
- FreeCAD `PartDesign::Draft` 通过事务内 Body、DatumPlane、DatumLine 和 `PartDesign::Feature` Base 评估同一 Fillet 几何Base 的有效性、拓扑数、体积、面积和边界摘要精确一致,同时显式记录 FreeCAD 元素映射导致的 BREP 重序列化哈希差异。
- FreeCAD 结果合同为空状态字符串、空 Shape 和 `Touched/Invalid`transaction abort 后 Body 及所有临时对象消失,顶层源 Shape 的 BREP 哈希精确恢复,干净 FCStd save/reopen/resave 三阶段一致。
### `TSN-PAIR-fillet-thickness`
`fillet->thickness` 已完成原生 no-op 拒绝分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- 只读原生扫描确认:全部 26 个 Face 在 `-0.4/-0.6` offset 下均返回与输入同摘要的 Shape6 个主面的 `0.4/0.6` 正向 offset 只产生无效 Shape 或 builder 拒绝,没有可用的真实变异轨迹。
- OCCT `BRepOffsetAPI_MakeThickSolid` 在 `-0.4 -> -0.6 -> -0.4` 轨迹中每次均返回有效 Shape 和 1 条 history但有效性、Solid/Face/Edge/Vertex、体积、面积、边界与 history 哈希全部不变,因此被显式分类为 `kernel-no-op`,不冒充接受。
- FreeCAD `Part::Thickness` 对同一 Fillet Solid 和 `Face2` 返回 `Valid/Up-to-date` 且 Shape 非空,但结果 Shape 摘要与输入逐字段相同,因此同样记为 `freecadProfileNoOp`。
- transaction abort 后对象集、源 Shape 和 BREP 哈希精确恢复;干净 FCStd save/reopen/resave 三阶段一致focused gate 已锁定双 no-op、回滚和持久化证据。
### `TSN-PAIR-fillet-linear-pattern`
`fillet->linear-pattern` 已完成原生接受分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` builder 沿 X 方向复制并融合 Fillet 结果;名义结果为有效 1 Solid、42 Face、体积 `1794.8665943`,包含 266 条原生 history。
- `8 -> 9 -> 8` 平移轨迹使体积在 `1794.8665943 -> 1894.7292492 -> 1794.8665943` 间变化X 上界从 18 变为 19 后恢复并精确恢复名义摘要、history 与语义 naming。
- 上游 Fillet naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定交叠融合、变异、恢复和持久化证据。
### `TSN-PAIR-fillet-polar-pattern`
`fillet->polar-pattern` 已完成原生接受分类:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- 下游使用真实 `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` builder 绕 Z 轴旋转复制并融合 Fillet 结果30 度名义结果为有效 1 Solid、46 Face、体积 `1417.6209625`,包含 234 条原生 history。
- `30 -> 45 -> 30` 角度轨迹使体积在 `1417.6209625 -> 1580.485276 -> 1417.6209625` 间变化面积和边界也同步变化并精确恢复名义摘要、history 与语义 naming。
- 上游 Fillet naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定旋转融合、变异、恢复和持久化证据。
### `TSN-PAIR-fillet-multi-transform`
`fillet->multi-transform` 已完成原生接受分类,并闭合 Fillet 起始行中尚需分类的全部有序操作对:
- 上游使用真实 `BRepFilletAPI_MakeFillet` 单输入 builder对 10x10x10 Box 的全部边执行圆角,生成有效 1 Solid、26 Face、体积 `995.9653557` 和 150 条原生 history。
- 下游使用 ordered `BRepBuilderAPI_Transform+BRepAlgoAPI_Fuse` builder依次执行 X 平移、关于 `x=5` 的镜像并融合;名义结果为有效 1 Solid、58 Face、体积 `2593.7678329`,包含 1103 条原生 history。
- `8 -> 9 -> 8` 首步 X 平移轨迹使体积在 `2593.7678329 -> 2793.4931426 -> 2593.7678329` 间变化,边界从 `[-8, 18]` 扩展为 `[-9, 19]` 后恢复并精确恢复名义摘要、history 与语义 naming。
- 上游 Fillet naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定 ordered builder、变异、恢复和持久化证据。
### `TSN-PAIR-chamfer-fuse`
`chamfer->fuse` 已完成原生接受分类:
- 上游使用真实 `BRepFilletAPI_MakeChamfer` 单输入 builder对 10x10x10 Box 的全部边执行倒角,生成有效 1 Solid、26 Face、体积 `990.7413333` 和 150 条原生 history。
- 下游使用真实 `BRepAlgoAPI_Fuse` 双输入 builder以相交工具融合 Chamfer 结果;名义结果为有效 1 Solid、33 Face、体积 `1066.6586667`,包含 78 条原生 history。
- `8 -> 7 -> 8` 工具 X 偏移轨迹使体积在 `1066.6586667 -> 1041.7386667 -> 1066.6586667` 间变化X 上界从 13 变为 12 后恢复并精确恢复名义摘要、history 与语义 naming。
- 上游 Chamfer naming 证据向下游传播FreeCAD FCStd save/reopen/resave 三阶段 Shape 与 naming evidence 稳定focused gate 已锁定相交融合、变异、恢复和持久化证据。
后续任务必须从机器队列的最新 `nextTask` 继续,不在本文手工维护任务名称。 后续任务必须从机器队列的最新 `nextTask` 继续,不在本文手工维护任务名称。