feat: close builder mutation and roundtrip evidence gaps

This commit is contained in:
2026-08-10 21:27:42 -04:00
parent 3907f50ff1
commit 26ea9b57d7
49 changed files with 5797 additions and 687 deletions

View File

@@ -25,12 +25,22 @@
"fuse", "fuse",
"cut", "cut",
"common", "common",
"rotate",
"pad", "pad",
"pocket", "pocket",
"loft",
"pipe",
"revolution", "revolution",
"groove", "groove",
"fillet", "fillet",
"chamfer" "chamfer",
"hole",
"draft",
"thickness",
"linear-pattern",
"polar-pattern",
"mirrored",
"multi-transform"
], ],
"transport": "step-text" "transport": "step-text"
}, },

View File

@@ -25,14 +25,22 @@
"fuse", "fuse",
"cut", "cut",
"common", "common",
"rotate",
"pad", "pad",
"pocket", "pocket",
"loft",
"pipe",
"revolution", "revolution",
"groove", "groove",
"fillet", "fillet",
"chamfer", "chamfer",
"hole", "hole",
"draft" "draft",
"thickness",
"linear-pattern",
"polar-pattern",
"mirrored",
"multi-transform"
], ],
"transport": "step-text" "transport": "step-text"
}, },

View File

@@ -25,11 +25,22 @@
"fuse", "fuse",
"cut", "cut",
"common", "common",
"rotate",
"pad", "pad",
"pocket", "pocket",
"loft",
"pipe",
"revolution", "revolution",
"groove", "groove",
"fillet" "fillet",
"chamfer",
"hole",
"draft",
"thickness",
"linear-pattern",
"polar-pattern",
"mirrored",
"multi-transform"
], ],
"transport": "step-text" "transport": "step-text"
}, },

View File

@@ -25,6 +25,7 @@
"fuse", "fuse",
"cut", "cut",
"common", "common",
"rotate",
"pad", "pad",
"pocket", "pocket",
"loft", "loft",

View File

@@ -24,8 +24,11 @@
"fuse", "fuse",
"cut", "cut",
"common", "common",
"rotate",
"pad", "pad",
"pocket", "pocket",
"loft",
"pipe",
"revolution", "revolution",
"groove", "groove",
"fillet", "fillet",
@@ -33,7 +36,10 @@
"hole", "hole",
"draft", "draft",
"thickness", "thickness",
"linear-pattern" "linear-pattern",
"polar-pattern",
"mirrored",
"multi-transform"
], ],
"transport": "step-text" "transport": "step-text"
}, },

View File

@@ -24,9 +24,11 @@
"fuse", "fuse",
"cut", "cut",
"common", "common",
"rotate",
"pad", "pad",
"pocket", "pocket",
"loft", "loft",
"pipe",
"revolution", "revolution",
"groove", "groove",
"fillet", "fillet",

View File

@@ -24,8 +24,11 @@
"fuse", "fuse",
"cut", "cut",
"common", "common",
"rotate",
"pad", "pad",
"pocket", "pocket",
"loft",
"pipe",
"revolution", "revolution",
"groove", "groove",
"fillet", "fillet",
@@ -35,7 +38,8 @@
"thickness", "thickness",
"linear-pattern", "linear-pattern",
"polar-pattern", "polar-pattern",
"mirrored" "mirrored",
"multi-transform"
], ],
"transport": "step-text" "transport": "step-text"
}, },

View File

@@ -24,6 +24,7 @@
"fuse", "fuse",
"cut", "cut",
"common", "common",
"rotate",
"pad", "pad",
"pocket", "pocket",
"loft", "loft",

View File

@@ -24,6 +24,7 @@
"fuse", "fuse",
"cut", "cut",
"common", "common",
"rotate",
"pad", "pad",
"pocket", "pocket",
"loft", "loft",
@@ -43,10 +44,10 @@
"transport": "step-text" "transport": "step-text"
}, },
"history": { "history": {
"recordCount": 28, "recordCount": 18,
"relations": { "relations": {
"modified": 1, "modified": 1,
"generated": 27, "generated": 17,
"deleted": 0 "deleted": 0
}, },
"sourceObjects": [ "sourceObjects": [

View File

@@ -24,8 +24,11 @@
"fuse", "fuse",
"cut", "cut",
"common", "common",
"rotate",
"pad", "pad",
"pocket", "pocket",
"loft",
"pipe",
"revolution", "revolution",
"groove", "groove",
"fillet", "fillet",
@@ -34,7 +37,9 @@
"draft", "draft",
"thickness", "thickness",
"linear-pattern", "linear-pattern",
"polar-pattern" "polar-pattern",
"mirrored",
"multi-transform"
], ],
"transport": "step-text" "transport": "step-text"
}, },

View File

@@ -25,15 +25,22 @@
"fuse", "fuse",
"cut", "cut",
"common", "common",
"rotate",
"pad", "pad",
"pocket", "pocket",
"loft",
"pipe",
"revolution", "revolution",
"groove", "groove",
"fillet", "fillet",
"chamfer", "chamfer",
"hole", "hole",
"draft", "draft",
"thickness" "thickness",
"linear-pattern",
"polar-pattern",
"mirrored",
"multi-transform"
], ],
"transport": "step-text" "transport": "step-text"
}, },

View File

@@ -97,7 +97,7 @@
"status": "explicit-runtime-contract", "status": "explicit-runtime-contract",
"mappedNameRef": "optional provider evidence; final-shape-only stages are marked and cannot mint FreeCAD tokens", "mappedNameRef": "optional provider evidence; final-shape-only stages are marked and cannot mint FreeCAD tokens",
"stringHasher": "optional opaque native table; validated and preserved losslessly", "stringHasher": "optional opaque native table; validated and preserved losslessly",
"exactBlockers": ["private FreeCAD token algorithm", "missing native builder stage", "non-unique isomorphic source"] "exactBlockers": ["browser FreeCAD private token callback ABI", "browser builder naming evidence transport", "non-unique isomorphic source"]
} }
}, },
"browserWorker": { "browserWorker": {
@@ -124,13 +124,13 @@
"verification": { "level": "experimental", "provider": "FreeCADCmd 1.1.1 native headless Part oracle", "operations": ["locked-source-commit", "reproducible-native-build", "declarative-golden-contract", "part-geometry-replay", "numeric-tolerance-comparison", "execution-plan-validation", "source-inventory-validation", "type-property-inventory-validation"] } "verification": { "level": "experimental", "provider": "FreeCADCmd 1.1.1 native headless Part oracle", "operations": ["locked-source-commit", "reproducible-native-build", "declarative-golden-contract", "part-geometry-replay", "numeric-tolerance-comparison", "execution-plan-validation", "source-inventory-validation", "type-property-inventory-validation"] }
}, },
"systemExactEvaluation": { "systemExactEvaluation": {
"evaluatedAt": "2026-08-09", "evaluatedAt": "2026-08-10",
"exact": false, "exact": false,
"featureExactCount": 0, "featureExactCount": 0,
"plan": "config/freecad-web-exact-parity-plan.json", "plan": "config/freecad-web-exact-parity-plan.json",
"promotionTask": "EX-REL-01", "promotionTask": "EX-REL-01",
"taskStatus": { "completed": 1, "inProgress": 37, "pending": 14, "blocked": 0 }, "taskStatus": { "completed": 2, "inProgress": 36, "pending": 14, "blocked": 0 },
"blockers": ["private FreeCAD token algorithm", "missing native builder stage", "non-unique isomorphic source"] "blockers": ["browser FreeCAD private token callback ABI", "browser builder naming evidence transport", "non-unique isomorphic source"]
}, },
"rules": [ "rules": [
"UI status does not imply geometry or file compatibility.", "UI status does not imply geometry or file compatibility.",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@
"freecad-web-freecad", "freecad-web-freecad",
"web-freecad-web" "web-freecad-web"
], ],
"scenarioCount": 22, "scenarioCount": 23,
"scenarios": [ "scenarios": [
{ {
"id": "proxy-byte-preservation", "id": "proxy-byte-preservation",
@@ -560,6 +560,68 @@
"resavedShapeAvailable": true "resavedShapeAvailable": true
} }
}, },
{
"id": "part-cut-through-hole",
"direction": "web-freecad-web",
"stages": [
"web-model",
"web-write",
"freecad-open",
"freecad-recompute",
"freecad-resave",
"web-inspect"
],
"domains": [
"objectTree",
"properties",
"shape",
"resources"
],
"freecadVersion": "1.1.1",
"status": "pass",
"differences": [],
"evidence": {
"typeId": "Part::Cut",
"solidCount": 1,
"faceCount": 7,
"volume": 3717.256661176918,
"boundingBox": {
"max": [
20,
20,
10
],
"min": [
0,
0,
0
]
},
"propertyCount": 3,
"properties": {
"Base": "Base",
"Refine": false,
"Tool": "HoleTool"
},
"toolPlacement": {
"position": {
"x": 10,
"y": 10,
"z": -1
},
"rotation": {
"axis": {
"x": 0,
"y": 0,
"z": 1
},
"angle": 0
}
},
"refine": false,
"resavedShapeAvailable": true
}
},
{ {
"id": "part-sphere-trim", "id": "part-sphere-trim",
"direction": "web-freecad-web", "direction": "web-freecad-web",

File diff suppressed because it is too large Load Diff

View File

@@ -141,6 +141,108 @@
"expected": "success", "expected": "success",
"faces": 6, "faces": 6,
"id": "part-extrusion-success", "id": "part-extrusion-success",
"mutation": {
"before": {
"parameterSnapshot": [
0,
0,
5
],
"shape": {
"area": 62,
"bounds": {
"max": [
3,
2,
5
],
"min": [
0,
0,
0
]
},
"brepSha256": "9d30d34ebc7b7c4d817dceb14aa8283acaef95ec72b06dcbbb29adbd075c2221",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 30
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": [
0,
0,
7
],
"shape": {
"area": 82,
"bounds": {
"max": [
3,
2,
7
],
"min": [
0,
0,
0
]
},
"brepSha256": "810f0b359958485b84d4404172ded10352418bf1877248604cfbe14262ecc4a5",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 42
}
},
"familyId": "Part::Extrusion",
"featureName": "Extrusion",
"parameterChanged": true,
"propertyPath": "Dir",
"restored": {
"parameterSnapshot": [
0,
0,
5
],
"shape": {
"area": 62,
"bounds": {
"max": [
3,
2,
5
],
"min": [
0,
0,
0
]
},
"brepSha256": "9d30d34ebc7b7c4d817dceb14aa8283acaef95ec72b06dcbbb29adbd075c2221",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 30
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"observed": "success", "observed": "success",
"passed": true, "passed": true,
"shapeNull": false, "shapeNull": false,
@@ -161,6 +263,96 @@
"expected": "success", "expected": "success",
"faces": 4, "faces": 4,
"id": "part-revolution-success", "id": "part-revolution-success",
"mutation": {
"before": {
"parameterSnapshot": 360,
"shape": {
"area": 150.79644737231007,
"bounds": {
"max": [
4,
2,
4
],
"min": [
-4,
0,
-4
]
},
"brepSha256": "1006818e02e479865c719ba5a8b6a513e3595dc36cbb618b8ea8f44770a80eee",
"edges": 6,
"faces": 4,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 4,
"volume": 75.398223686155
}
},
"brepStable": false,
"edited": {
"parameterSnapshot": 270,
"shape": {
"area": 121.09733552923257,
"bounds": {
"max": [
4,
2,
4
],
"min": [
-4,
0,
-4
]
},
"brepSha256": "9426f11d00c01396b73f44bc342d021a3779b0ff9d5a81bc78529f80edb589cb",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 56.54866776461628
}
},
"familyId": "Part::Revolution",
"featureName": "Revolution",
"parameterChanged": true,
"propertyPath": "Angle",
"restored": {
"parameterSnapshot": 360,
"shape": {
"area": 150.79644737231007,
"bounds": {
"max": [
4,
2,
4
],
"min": [
-4,
0,
-4
]
},
"brepSha256": "ec773a4c293bac1ffc7dfbb42ab648361eb9926779d8d79d00ba037fb01bbe19",
"edges": 6,
"faces": 4,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 4,
"volume": 75.398223686155
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"observed": "success", "observed": "success",
"passed": true, "passed": true,
"shapeNull": false, "shapeNull": false,
@@ -181,6 +373,96 @@
"expected": "success", "expected": "success",
"faces": 6, "faces": 6,
"id": "part-loft-success", "id": "part-loft-success",
"mutation": {
"before": {
"parameterSnapshot": false,
"shape": {
"area": 81.18823416311344,
"bounds": {
"max": [
2,
2,
5
],
"min": [
-2,
-2,
0
]
},
"brepSha256": "efec0c6cf3b8907f16c071ffeed11238b8e5a9f8f60e40fad225eb418ff33f7b",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 46.666666666666664
}
},
"brepStable": false,
"edited": {
"parameterSnapshot": true,
"shape": {
"area": 81.18823416311344,
"bounds": {
"max": [
2,
2,
5
],
"min": [
-2,
-2,
0
]
},
"brepSha256": "9542e7469b051c053c1c425ffa5d246194661675e16ff0dcc046bce6b2527df1",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 46.666666666666664
}
},
"familyId": "Part::Loft",
"featureName": "Loft",
"parameterChanged": true,
"propertyPath": "Ruled",
"restored": {
"parameterSnapshot": false,
"shape": {
"area": 81.18823416311344,
"bounds": {
"max": [
2,
2,
5
],
"min": [
-2,
-2,
0
]
},
"brepSha256": "bb0a25834e30e135551532aba7398c66e7de32e79ca07d4b3cd07268ace94ee5",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 46.666666666666664
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"observed": "success", "observed": "success",
"passed": true, "passed": true,
"shapeNull": false, "shapeNull": false,
@@ -201,6 +483,111 @@
"expected": "success", "expected": "success",
"faces": 6, "faces": 6,
"id": "part-sweep-success", "id": "part-sweep-success",
"mutation": {
"before": {
"parameterSnapshot": [
{
"name": "Section",
"typeId": "Part::Feature"
}
],
"shape": {
"area": 56,
"bounds": {
"max": [
1,
1,
6
],
"min": [
-1,
-1,
0
]
},
"brepSha256": "5c1241164480ab824c8e107694158117e7d5854ca62985d1123a33853d6d5e58",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 23.999999999999996
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": [
{
"name": "EditedSection",
"typeId": "Part::Feature"
}
],
"shape": {
"area": 40.5,
"bounds": {
"max": [
0.75,
0.75,
6
],
"min": [
-0.75,
-0.75,
0
]
},
"brepSha256": "5fcac1094e1a5fd89bdc44c7ebe60e3a80060bba5a28852875d55869b3a96ee7",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 13.5
}
},
"familyId": "Part::Sweep",
"featureName": "Sweep",
"parameterChanged": true,
"propertyPath": "Sections",
"restored": {
"parameterSnapshot": [
{
"name": "Section",
"typeId": "Part::Feature"
}
],
"shape": {
"area": 56,
"bounds": {
"max": [
1,
1,
6
],
"min": [
-1,
-1,
0
]
},
"brepSha256": "5c1241164480ab824c8e107694158117e7d5854ca62985d1123a33853d6d5e58",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 23.999999999999996
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"observed": "success", "observed": "success",
"passed": true, "passed": true,
"shapeNull": false, "shapeNull": false,
@@ -221,6 +608,114 @@
"expected": "success", "expected": "success",
"faces": 7, "faces": 7,
"id": "part-fillet-success", "id": "part-fillet-success",
"mutation": {
"before": {
"parameterSnapshot": [
[
1,
1,
1
]
],
"shape": {
"area": 372.99557428756424,
"bounds": {
"max": [
10,
8,
6
],
"min": [
-2.220446049250313e-16,
-2.220446049250313e-16,
0
]
},
"brepSha256": "53dbc555b86be7f2e989af3dd696ee01a611a4d7eb6b8d4fd1d379d351e8f73d",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 478.7123889803846
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": [
[
1,
0.5,
0.5
]
],
"shape": {
"area": 374.6050880620834,
"bounds": {
"max": [
10,
8,
6
],
"min": [
-1.1102230246251565e-16,
-1.1102230246251565e-16,
0
]
},
"brepSha256": "25428228208a7ddd0b814f828c566d9ce99916c2ab86277d7eadf83dcba05aa7",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 479.6780972450962
}
},
"familyId": "Part::Fillet",
"featureName": "Fillet",
"parameterChanged": true,
"propertyPath": "Edges",
"restored": {
"parameterSnapshot": [
[
1,
1,
1
]
],
"shape": {
"area": 372.99557428756424,
"bounds": {
"max": [
10,
8,
6
],
"min": [
-2.220446049250313e-16,
-2.220446049250313e-16,
0
]
},
"brepSha256": "53dbc555b86be7f2e989af3dd696ee01a611a4d7eb6b8d4fd1d379d351e8f73d",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 478.7123889803846
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"observed": "success", "observed": "success",
"passed": true, "passed": true,
"shapeNull": false, "shapeNull": false,
@@ -241,6 +736,114 @@
"expected": "success", "expected": "success",
"faces": 7, "faces": 7,
"id": "part-chamfer-success", "id": "part-chamfer-success",
"mutation": {
"before": {
"parameterSnapshot": [
[
1,
1,
1
]
],
"shape": {
"area": 371.4852813742385,
"bounds": {
"max": [
10,
8,
6
],
"min": [
-1.6653345369377348e-16,
-1.6653345369377348e-16,
0
]
},
"brepSha256": "53ed2773aca1555789f3cca4fbb6b95e76ca92e40b1f423d53bfe39da21bbca6",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 476.9999999999999
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": [
[
1,
0.5,
0.5
]
],
"shape": {
"area": 373.99264068711926,
"bounds": {
"max": [
10,
8,
6
],
"min": [
-8.326672684688674e-17,
-8.326672684688674e-17,
0
]
},
"brepSha256": "ac5a85fa3d48a6595c1458a80fa22b6618c3c4203c3c5995a92cd2349fa262c5",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 479.25
}
},
"familyId": "Part::Chamfer",
"featureName": "Chamfer",
"parameterChanged": true,
"propertyPath": "Edges",
"restored": {
"parameterSnapshot": [
[
1,
1,
1
]
],
"shape": {
"area": 371.4852813742385,
"bounds": {
"max": [
10,
8,
6
],
"min": [
-1.6653345369377348e-16,
-1.6653345369377348e-16,
0
]
},
"brepSha256": "53ed2773aca1555789f3cca4fbb6b95e76ca92e40b1f423d53bfe39da21bbca6",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 476.9999999999999
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"observed": "success", "observed": "success",
"passed": true, "passed": true,
"shapeNull": false, "shapeNull": false,

View File

@@ -16,6 +16,105 @@
}, },
"faces": 6, "faces": 6,
"id": "pad-tapered", "id": "pad-tapered",
"mutation": {
"before": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 5
},
"shape": {
"area": 128.85239231084472,
"bounds": {
"max": [
2.43744331762962,
2.43744331762962,
5
],
"min": [
-2.43744331762962,
-2.43744331762962,
0
]
},
"brepSha256": "f5a07a59b273c128018d781af423bf016a2b0d17dc6dd59619460e37ca72de96",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 98.77344374611019
}
},
"brepStable": false,
"edited": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 6
},
"shape": {
"area": 150.51432170708637,
"bounds": {
"max": [
2.524931981155544,
2.524931981155544,
6
],
"min": [
-2.524931981155544,
-2.524931981155544,
0
]
},
"brepSha256": "1987bd297acc648dc0d43f7703d68a0c8005a45056a3274c5f0240b1b46b9679",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 123.40116377418519
}
},
"familyId": "PartDesign::Pad",
"featureName": "Pad",
"parameterChanged": true,
"propertyPath": "Length",
"restored": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 5
},
"shape": {
"area": 128.85239231084472,
"bounds": {
"max": [
2.43744331762962,
2.43744331762962,
5
],
"min": [
-2.43744331762962,
-2.43744331762962,
0
]
},
"brepSha256": "b5c1d71797f05ed2b2c51400de81047759f684a2c14d65d57acdc035aa3512e7",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 98.77344374611019
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"passed": true, "passed": true,
"solids": 1, "solids": 1,
"status": "Valid", "status": "Valid",
@@ -36,6 +135,105 @@
}, },
"faces": 10, "faces": 10,
"id": "pocket-tapered", "id": "pocket-tapered",
"mutation": {
"before": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 10
},
"shape": {
"area": 735.999242011454,
"bounds": {
"max": [
5,
5,
5
],
"min": [
-5,
-5,
-5
]
},
"brepSha256": "ae401e5db6c23acc7d0fe23896ac08a0c27d6ed73df32953b1a9b15349f0b7bd",
"edges": 24,
"faces": 10,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 16,
"volume": 810.4374375507407
}
},
"brepStable": false,
"edited": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 8
},
"shape": {
"area": 741.80522810493,
"bounds": {
"max": [
5,
5,
5
],
"min": [
-5,
-5,
-5
]
},
"brepSha256": "20aedff38ad1468a1eb669a71e9ad4ab45f1b211bfc81a41caf50b4cd39a03b3",
"edges": 24,
"faces": 11,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 16,
"volume": 853.2880812300241
}
},
"familyId": "PartDesign::Pocket",
"featureName": "Pocket",
"parameterChanged": true,
"propertyPath": "Length",
"restored": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 10
},
"shape": {
"area": 735.999242011454,
"bounds": {
"max": [
5,
5,
5
],
"min": [
-5,
-5,
-5
]
},
"brepSha256": "56193a107c590c262642369e26467a690c0267174d47e2905f590280bf91d523",
"edges": 24,
"faces": 10,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 16,
"volume": 810.4374375507407
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"passed": true, "passed": true,
"solids": 1, "solids": 1,
"status": "Valid", "status": "Valid",

View File

@@ -5,6 +5,105 @@
"edges": 15, "edges": 15,
"faces": 7, "faces": 7,
"id": "fillet-selected-edge", "id": "fillet-selected-edge",
"mutation": {
"before": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 1
},
"shape": {
"area": 595.2787595947437,
"bounds": {
"max": [
10,
10,
10
],
"min": [
-2.220446049250313e-16,
-2.220446049250313e-16,
0
]
},
"brepSha256": "98c5a880b0bea84194e75f61c08114d8e8885fec88ffa3effc6f3da5063d7421",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 997.8539816339743
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 0.5
},
"shape": {
"area": 597.746680715673,
"bounds": {
"max": [
10,
10,
10
],
"min": [
-1.1102230246251565e-16,
-1.1102230246251565e-16,
0
]
},
"brepSha256": "03ec60b398de6e54bb2198d3a81b27abf5968b4479b66a794c18047aeea72946",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 999.4634954084936
}
},
"familyId": "PartDesign::Fillet",
"featureName": "Fillet",
"parameterChanged": true,
"propertyPath": "Radius",
"restored": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 1
},
"shape": {
"area": 595.2787595947437,
"bounds": {
"max": [
10,
10,
10
],
"min": [
-2.220446049250313e-16,
-2.220446049250313e-16,
0
]
},
"brepSha256": "98c5a880b0bea84194e75f61c08114d8e8885fec88ffa3effc6f3da5063d7421",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 997.8539816339743
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"passed": true, "passed": true,
"selection": [ "selection": [
"Edge1" "Edge1"
@@ -18,6 +117,105 @@
"edges": 15, "edges": 15,
"faces": 7, "faces": 7,
"id": "chamfer-selected-edge", "id": "chamfer-selected-edge",
"mutation": {
"before": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 1
},
"shape": {
"area": 593.1421356237308,
"bounds": {
"max": [
10,
10,
10
],
"min": [
-1.6653345369377348e-16,
-1.6653345369377348e-16,
0
]
},
"brepSha256": "d67242a11006664458599a9b2e13b2f9f4857699041bfe30bf33c2c2f774403a",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 994.9999999999998
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 0.5
},
"shape": {
"area": 596.8210678118654,
"bounds": {
"max": [
10,
10,
10
],
"min": [
-8.326672684688674e-17,
-8.326672684688674e-17,
0
]
},
"brepSha256": "51fb428868d98f40f7acbf91f018323d500315c167b8b8195a07f17c5b681258",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 998.75
}
},
"familyId": "PartDesign::Chamfer",
"featureName": "Chamfer",
"parameterChanged": true,
"propertyPath": "Size",
"restored": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 1
},
"shape": {
"area": 593.1421356237308,
"bounds": {
"max": [
10,
10,
10
],
"min": [
-1.6653345369377348e-16,
-1.6653345369377348e-16,
0
]
},
"brepSha256": "d67242a11006664458599a9b2e13b2f9f4857699041bfe30bf33c2c2f774403a",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 994.9999999999998
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"passed": true, "passed": true,
"selection": [ "selection": [
"Edge1" "Edge1"
@@ -31,6 +229,105 @@
"edges": 12, "edges": 12,
"faces": 6, "faces": 6,
"id": "draft-selected-face", "id": "draft-selected-face",
"mutation": {
"before": {
"parameterSnapshot": {
"unit": "Unit: deg (0,0,0,0,0,0,0,1) [Angle]",
"value": 45
},
"shape": {
"area": 841.4213562373093,
"bounds": {
"max": [
10.000000000000002,
10,
20
],
"min": [
0,
0,
0
]
},
"brepSha256": "fdc7c4775ef559c6309bbb2d1a31065a49e6d70455372f8fda595568fe46dfa4",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 1499.9999999999998
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": {
"unit": "Unit: deg (0,0,0,0,0,0,0,1) [Angle]",
"value": 30
},
"shape": {
"area": 730.9401076758501,
"bounds": {
"max": [
10,
10,
15.773502691896255
],
"min": [
0,
0,
0
]
},
"brepSha256": "be5a1c93837a915d46279a1c5e40fbf46c0f921db4064f97ef568fbae9d3408e",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 1288.6751345948126
}
},
"familyId": "PartDesign::Draft",
"featureName": "Draft",
"parameterChanged": true,
"propertyPath": "Angle",
"restored": {
"parameterSnapshot": {
"unit": "Unit: deg (0,0,0,0,0,0,0,1) [Angle]",
"value": 45
},
"shape": {
"area": 841.4213562373093,
"bounds": {
"max": [
10.000000000000002,
10,
20
],
"min": [
0,
0,
0
]
},
"brepSha256": "fdc7c4775ef559c6309bbb2d1a31065a49e6d70455372f8fda595568fe46dfa4",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 1499.9999999999998
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"passed": true, "passed": true,
"reversed": true, "reversed": true,
"selection": [ "selection": [
@@ -46,6 +343,105 @@
"elementMapSize": 51, "elementMapSize": 51,
"faces": 11, "faces": 11,
"id": "thickness-selected-face", "id": "thickness-selected-face",
"mutation": {
"before": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 1
},
"shape": {
"area": 887.9999999999999,
"bounds": {
"max": [
10,
10,
10
],
"min": [
0,
0,
0
]
},
"brepSha256": "747d92b1cf08109f59d9a972e6e14be9f09691263866f7eafbbdcee940390c4e",
"edges": 24,
"faces": 11,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 16,
"volume": 423.99999999999983
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 0.8
},
"shape": {
"area": 909.1199999999998,
"bounds": {
"max": [
10,
10,
10
],
"min": [
0,
0,
0
]
},
"brepSha256": "31e712785de8577559f0ce4584ced599fc69b3cbf9a508ab3eec24f2ab324952",
"edges": 24,
"faces": 11,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 16,
"volume": 350.8479999999998
}
},
"familyId": "PartDesign::Thickness",
"featureName": "Thickness",
"parameterChanged": true,
"propertyPath": "Value",
"restored": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 1
},
"shape": {
"area": 887.9999999999999,
"bounds": {
"max": [
10,
10,
10
],
"min": [
0,
0,
0
]
},
"brepSha256": "747d92b1cf08109f59d9a972e6e14be9f09691263866f7eafbbdcee940390c4e",
"edges": 24,
"faces": 11,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 16,
"volume": 423.99999999999983
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"passed": true, "passed": true,
"reversed": true, "reversed": true,
"selection": [ "selection": [

View File

@@ -4,6 +4,96 @@
{ {
"faces": 6, "faces": 6,
"id": "additive-loft", "id": "additive-loft",
"mutation": {
"before": {
"parameterSnapshot": false,
"shape": {
"area": 7.23606797749979,
"bounds": {
"max": [
1,
1,
2
],
"min": [
0,
0,
0
]
},
"brepSha256": "0ab177609a66575afff2f36237fdf271e5a95e8fb738c91101648533e01c6642",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 1
}
},
"brepStable": false,
"edited": {
"parameterSnapshot": true,
"shape": {
"area": 7.23606797749979,
"bounds": {
"max": [
1,
1,
2
],
"min": [
0,
0,
0
]
},
"brepSha256": "8a243d5c346dcb96da932fea88d54513c36e697b99cd36a90c892100e1f7b337",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 1
}
},
"familyId": "PartDesign::AdditiveLoft",
"featureName": "AdditiveLoft",
"parameterChanged": true,
"propertyPath": "Ruled",
"restored": {
"parameterSnapshot": false,
"shape": {
"area": 7.23606797749979,
"bounds": {
"max": [
1,
1,
2
],
"min": [
0,
0,
0
]
},
"brepSha256": "9cf30242b4b3776be5256d587155eb9781d7cc7534bbb60f125830fde83686c6",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 1
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"passed": true, "passed": true,
"solids": 1, "solids": 1,
"status": "Valid", "status": "Valid",
@@ -13,6 +103,126 @@
{ {
"faces": 5, "faces": 5,
"id": "subtractive-loft", "id": "subtractive-loft",
"mutation": {
"before": {
"parameterSnapshot": [
[
{
"name": "Section",
"typeId": "Sketcher::SketchObject"
},
[
""
]
]
],
"shape": {
"area": 7.236067977499789,
"bounds": {
"max": [
1,
1,
2
],
"min": [
0,
0,
0
]
},
"brepSha256": "c90d78028b450759ca50298c18e98d5cfee774d1e453d6c133f5b748fa01c78a",
"edges": 9,
"faces": 5,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 6,
"volume": 0.9999999999999994
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": [
[
{
"name": "EditedSection",
"typeId": "Sketcher::SketchObject"
},
[
""
]
]
],
"shape": {
"area": 9.348113056667211,
"bounds": {
"max": [
1,
1,
2
],
"min": [
0,
0,
0
]
},
"brepSha256": "21e5dda76ba93b30769b6f0b5d01006f43f92bb1012559f7a6ffdf5b25b8dc44",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 1.2135416666666667
}
},
"familyId": "PartDesign::SubtractiveLoft",
"featureName": "SubtractiveLoft",
"parameterChanged": true,
"propertyPath": "Sections",
"restored": {
"parameterSnapshot": [
[
{
"name": "Section",
"typeId": "Sketcher::SketchObject"
},
[
""
]
]
],
"shape": {
"area": 7.236067977499789,
"bounds": {
"max": [
1,
1,
2
],
"min": [
0,
0,
0
]
},
"brepSha256": "c90d78028b450759ca50298c18e98d5cfee774d1e453d6c133f5b748fa01c78a",
"edges": 9,
"faces": 5,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 6,
"volume": 0.9999999999999994
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"passed": true, "passed": true,
"solids": 1, "solids": 1,
"status": "Valid", "status": "Valid",
@@ -22,6 +232,114 @@
{ {
"faces": 3, "faces": 3,
"id": "additive-pipe", "id": "additive-pipe",
"mutation": {
"before": {
"parameterSnapshot": [
{
"name": "Profile",
"typeId": "Sketcher::SketchObject"
},
[]
],
"shape": {
"area": 12.566370614359169,
"bounds": {
"max": [
1,
1.0000000000000002,
1.0000000000000002
],
"min": [
-1,
-1,
-2.220446049250313e-16
]
},
"brepSha256": "82aa44aafac3cd8d51197e46391c039ae2a26b90e99df2f805e453de776dfcd8",
"edges": 3,
"faces": 3,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 2,
"volume": 3.1415926535897927
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": [
{
"name": "EditedProfile",
"typeId": "Sketcher::SketchObject"
},
[]
],
"shape": {
"area": 8.246680715673207,
"bounds": {
"max": [
0.75,
0.7500000000000002,
1.0000000000000002
],
"min": [
-0.75,
-0.75,
-1.6653345369377348e-16
]
},
"brepSha256": "452964b54d47c78e21e540fbd17b33e1a449fefd3607583204dd840d0761ca1f",
"edges": 3,
"faces": 3,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 2,
"volume": 1.7671458676442584
}
},
"familyId": "PartDesign::AdditivePipe",
"featureName": "AdditivePipe",
"parameterChanged": true,
"propertyPath": "Profile",
"restored": {
"parameterSnapshot": [
{
"name": "Profile",
"typeId": "Sketcher::SketchObject"
},
[]
],
"shape": {
"area": 12.566370614359169,
"bounds": {
"max": [
1,
1.0000000000000002,
1.0000000000000002
],
"min": [
-1,
-1,
-2.220446049250313e-16
]
},
"brepSha256": "82aa44aafac3cd8d51197e46391c039ae2a26b90e99df2f805e453de776dfcd8",
"edges": 3,
"faces": 3,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 2,
"volume": 3.1415926535897927
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"passed": true, "passed": true,
"solids": 1, "solids": 1,
"status": "Valid", "status": "Valid",
@@ -32,6 +350,114 @@
{ {
"faces": 7, "faces": 7,
"id": "subtractive-pipe", "id": "subtractive-pipe",
"mutation": {
"before": {
"parameterSnapshot": [
{
"name": "Profile",
"typeId": "Sketcher::SketchObject"
},
[]
],
"shape": {
"area": 239.99999999999997,
"bounds": {
"max": [
5,
5,
1.0000000000000002
],
"min": [
-5,
-5,
-2.220446049250313e-16
]
},
"brepSha256": "85644672cbbe548cbc5c063dc5e63feda7f149eb6a9b1446cba89f456945180b",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 96.8584073464102
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": [
{
"name": "EditedProfile",
"typeId": "Sketcher::SketchObject"
},
[]
],
"shape": {
"area": 241.17809724509615,
"bounds": {
"max": [
5,
5,
1.0000000000000002
],
"min": [
-5,
-5,
-1.6653345369377348e-16
]
},
"brepSha256": "1bca636a2f0deffa3ba317f7000dc245a9eaaa3d79bd800d1cbe80b6d3bb6b16",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 98.23285413235574
}
},
"familyId": "PartDesign::SubtractivePipe",
"featureName": "SubtractivePipe",
"parameterChanged": true,
"propertyPath": "Profile",
"restored": {
"parameterSnapshot": [
{
"name": "Profile",
"typeId": "Sketcher::SketchObject"
},
[]
],
"shape": {
"area": 239.99999999999997,
"bounds": {
"max": [
5,
5,
1.0000000000000002
],
"min": [
-5,
-5,
-2.220446049250313e-16
]
},
"brepSha256": "85644672cbbe548cbc5c063dc5e63feda7f149eb6a9b1446cba89f456945180b",
"edges": 15,
"faces": 7,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 96.8584073464102
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"passed": true, "passed": true,
"solids": 1, "solids": 1,
"status": "Valid", "status": "Valid",

View File

@@ -4,6 +4,105 @@
{ {
"faces": 4, "faces": 4,
"id": "partdesign-revolution", "id": "partdesign-revolution",
"mutation": {
"before": {
"parameterSnapshot": {
"unit": "Unit: deg (0,0,0,0,0,0,0,1) [Angle]",
"value": 360
},
"shape": {
"area": 150.79644737231007,
"bounds": {
"max": [
1,
4,
4
],
"min": [
-1,
-4,
-4
]
},
"brepSha256": "4e10234851bc71948ff0e12cfe337f7cf1f9f177d80991186faccb42bb09f84d",
"edges": 6,
"faces": 4,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 4,
"volume": 75.39822368615502
}
},
"brepStable": false,
"edited": {
"parameterSnapshot": {
"unit": "Unit: deg (0,0,0,0,0,0,0,1) [Angle]",
"value": 270
},
"shape": {
"area": 121.09733552923255,
"bounds": {
"max": [
1,
4,
4
],
"min": [
-1,
-4,
-4
]
},
"brepSha256": "99a870ec07e78b9c0903bee67373a8f4051c5651629db1149aeab3bc3be9d3d7",
"edges": 12,
"faces": 6,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 56.54866776461628
}
},
"familyId": "PartDesign::Revolution",
"featureName": "Revolution",
"parameterChanged": true,
"propertyPath": "Angle",
"restored": {
"parameterSnapshot": {
"unit": "Unit: deg (0,0,0,0,0,0,0,1) [Angle]",
"value": 360
},
"shape": {
"area": 150.79644737231007,
"bounds": {
"max": [
1,
4,
4
],
"min": [
-1,
-4,
-4
]
},
"brepSha256": "1fe389e236a01afd10442aacb43772d81f3910bde25b215d191d62d69eaa8c64",
"edges": 6,
"faces": 4,
"shapeType": "Solid",
"solids": 1,
"valid": true,
"vertices": 4,
"volume": 75.39822368615502
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"operation": "revolution", "operation": "revolution",
"passed": true, "passed": true,
"shapeNull": false, "shapeNull": false,
@@ -16,6 +115,105 @@
{ {
"faces": 10, "faces": 10,
"id": "partdesign-groove", "id": "partdesign-groove",
"mutation": {
"before": {
"parameterSnapshot": {
"unit": "Unit: deg (0,0,0,0,0,0,0,1) [Angle]",
"value": 180
},
"shape": {
"area": 610.137166941154,
"bounds": {
"max": [
10,
10,
10
],
"min": [
0,
0,
0
]
},
"brepSha256": "a71b46178c908477d71f6d08b4c922184abcc8426114c3e4315c50fbd8b17b87",
"edges": 24,
"faces": 10,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 16,
"volume": 995.2876110196153
}
},
"brepStable": false,
"edited": {
"parameterSnapshot": {
"unit": "Unit: deg (0,0,0,0,0,0,0,1) [Angle]",
"value": 120
},
"shape": {
"area": 610.137166941154,
"bounds": {
"max": [
10,
10,
10
],
"min": [
0,
0,
0
]
},
"brepSha256": "7d3dfb277849d5bdd4eed46a50b16c88c84830727893b452ec327c016aa93de1",
"edges": 24,
"faces": 10,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 16,
"volume": 995.2876110196153
}
},
"familyId": "PartDesign::Groove",
"featureName": "Groove",
"parameterChanged": true,
"propertyPath": "Angle",
"restored": {
"parameterSnapshot": {
"unit": "Unit: deg (0,0,0,0,0,0,0,1) [Angle]",
"value": 180
},
"shape": {
"area": 610.137166941154,
"bounds": {
"max": [
10,
10,
10
],
"min": [
0,
0,
0
]
},
"brepSha256": "6795afbb4bb89669697e47396335aa79cb12cb2aa14f1cade543a63894d6287f",
"edges": 24,
"faces": 10,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 16,
"volume": 995.2876110196153
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"operation": "groove", "operation": "groove",
"passed": true, "passed": true,
"shapeNull": false, "shapeNull": false,

View File

@@ -7,6 +7,96 @@
"faces": 21, "faces": 21,
"id": "linear-feature-list", "id": "linear-feature-list",
"length": 12, "length": 12,
"mutation": {
"before": {
"parameterSnapshot": 3,
"shape": {
"area": 568,
"bounds": {
"max": [
20,
10,
4
],
"min": [
0,
0,
0
]
},
"brepSha256": "f5b90a00cb51e89b5d6cf1457a8b877ba0609d9053a31fb478070765f3ffc446",
"edges": 48,
"faces": 21,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 32,
"volume": 423.99999999999994
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": 4,
"shape": {
"area": 584,
"bounds": {
"max": [
20,
10,
4
],
"min": [
0,
0,
0
]
},
"brepSha256": "44b0867df8d01e7725a337741c5fabab336a9928cc4ddcd5674b386e14ddfb99",
"edges": 60,
"faces": 26,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 40,
"volume": 431.99999999999983
}
},
"familyId": "PartDesign::LinearPattern",
"featureName": "LinearPattern",
"parameterChanged": true,
"propertyPath": "Occurrences",
"restored": {
"parameterSnapshot": 3,
"shape": {
"area": 568,
"bounds": {
"max": [
20,
10,
4
],
"min": [
0,
0,
0
]
},
"brepSha256": "f5b90a00cb51e89b5d6cf1457a8b877ba0609d9053a31fb478070765f3ffc446",
"edges": 48,
"faces": 21,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 32,
"volume": 423.99999999999994
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"occurrences": 3, "occurrences": 3,
"originals": [ "originals": [
"Boss" "Boss"
@@ -22,6 +112,96 @@
"edges": 28, "edges": 28,
"faces": 12, "faces": 12,
"id": "mirrored-feature-list", "id": "mirrored-feature-list",
"mutation": {
"before": {
"parameterSnapshot": true,
"shape": {
"area": 552,
"bounds": {
"max": [
20,
10,
4
],
"min": [
-2,
0,
0
]
},
"brepSha256": "f09f9f5ab8be73c2eb76c3410c969e93044fb6beb01feec52d2d239da62d3c2e",
"edges": 28,
"faces": 12,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 18,
"volume": 416.00000000000006
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": false,
"shape": {
"area": 552,
"bounds": {
"max": [
20,
10,
4
],
"min": [
-2,
0,
0
]
},
"brepSha256": "b978620d2a6f99e90e96dbc7f20b19169bea51ce2f4d22bfc620e55d62ccdeaa",
"edges": 33,
"faces": 15,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 20,
"volume": 415.99999999999983
}
},
"familyId": "PartDesign::Mirrored",
"featureName": "Mirrored",
"parameterChanged": true,
"propertyPath": "Refine",
"restored": {
"parameterSnapshot": true,
"shape": {
"area": 552,
"bounds": {
"max": [
20,
10,
4
],
"min": [
-2,
0,
0
]
},
"brepSha256": "f09f9f5ab8be73c2eb76c3410c969e93044fb6beb01feec52d2d239da62d3c2e",
"edges": 28,
"faces": 12,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 18,
"volume": 416.00000000000006
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"originals": [ "originals": [
"Boss" "Boss"
], ],
@@ -39,6 +219,96 @@
"edges": 12, "edges": 12,
"faces": 6, "faces": 6,
"id": "polar-feature-list", "id": "polar-feature-list",
"mutation": {
"before": {
"parameterSnapshot": 4,
"shape": {
"area": 1599.9999999999998,
"bounds": {
"max": [
10,
10,
10.000000000000002
],
"min": [
0,
-10.000000000000002,
-10.000000000000002
]
},
"brepSha256": "4dd67348ece295122be98bed65a93e8362e46d5d9dd5b6a88589a388537b73d7",
"edges": 12,
"faces": 6,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 3999.999999999999
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": 3,
"shape": {
"area": 1799.9999999999998,
"bounds": {
"max": [
10,
10,
10
],
"min": [
0,
-13.660254037844387,
-13.660254037844389
]
},
"brepSha256": "5127ab274986196eb89f32f2c697f83dbac753c05519b484449305370496c8c5",
"edges": 34,
"faces": 18,
"shapeType": "Compound",
"solids": 3,
"valid": true,
"vertices": 20,
"volume": 2999.9999999999995
}
},
"familyId": "PartDesign::PolarPattern",
"featureName": "PolarPattern",
"parameterChanged": true,
"propertyPath": "Occurrences",
"restored": {
"parameterSnapshot": 4,
"shape": {
"area": 1599.9999999999998,
"bounds": {
"max": [
10,
10,
10.000000000000002
],
"min": [
0,
-10.000000000000002,
-10.000000000000002
]
},
"brepSha256": "4dd67348ece295122be98bed65a93e8362e46d5d9dd5b6a88589a388537b73d7",
"edges": 12,
"faces": 6,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 8,
"volume": 3999.999999999999
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"occurrences": 4, "occurrences": 4,
"originals": [ "originals": [
"Box" "Box"
@@ -54,6 +324,105 @@
"edges": 36, "edges": 36,
"faces": 14, "faces": 14,
"id": "multi-transform-feature-list", "id": "multi-transform-feature-list",
"mutation": {
"before": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 20
},
"shape": {
"area": 6400,
"bounds": {
"max": [
30,
30,
10
],
"min": [
-30,
-30.000000000000004,
0
]
},
"brepSha256": "3e82460ae12922bd68d58ca6c48ad7ba2f034c4157e8f2da8a2bd9683ed83697",
"edges": 36,
"faces": 14,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 24,
"volume": 19999.999999999996
}
},
"brepStable": false,
"edited": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 15
},
"shape": {
"area": 5200,
"bounds": {
"max": [
25,
25,
10
],
"min": [
-25,
-25.000000000000004,
0
]
},
"brepSha256": "c1e5e2ac391062149b8da2328ecb93a5bc71f166e1edea8157a30a51d53375dd",
"edges": 36,
"faces": 14,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 24,
"volume": 15999.999999999998
}
},
"familyId": "PartDesign::MultiTransform",
"featureName": "MultiTransform",
"parameterChanged": true,
"propertyPath": "Transformations[1].Length",
"restored": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 20
},
"shape": {
"area": 6400,
"bounds": {
"max": [
30,
30,
10
],
"min": [
-30,
-30.000000000000004,
0
]
},
"brepSha256": "2a2bfeadec439a2a70d2a49da618de7c63d202f7b70a0adb8ad5d0a161bc7fae",
"edges": 36,
"faces": 14,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 24,
"volume": 19999.999999999996
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"originals": [ "originals": [
"Pad" "Pad"
], ],
@@ -75,6 +444,105 @@
"faces": 8, "faces": 8,
"id": "iso-m6-tap-drill", "id": "iso-m6-tap-drill",
"modeled": false, "modeled": false,
"mutation": {
"before": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 5
},
"shape": {
"area": 1725.6637061435915,
"bounds": {
"max": [
20,
20,
10
],
"min": [
0,
0,
0
]
},
"brepSha256": "6157506ee1ceae966d58b98f729adbd4dffb585f1e9e52547e30d442af73efd3",
"edges": 15,
"faces": 8,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 3842.92036732051
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 6.5
},
"shape": {
"area": 1763.362817986669,
"bounds": {
"max": [
20,
20,
10
],
"min": [
0,
0,
0
]
},
"brepSha256": "a8ad05315c3d555a9ad9b764e243b4001b56401e9fab846aeb145ceb0fea42f6",
"edges": 15,
"faces": 8,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 3734.5354207716614
}
},
"familyId": "PartDesign::Hole",
"featureName": "Hole",
"parameterChanged": true,
"propertyPath": "Diameter",
"restored": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 5
},
"shape": {
"area": 1725.6637061435915,
"bounds": {
"max": [
20,
20,
10
],
"min": [
0,
0,
0
]
},
"brepSha256": "6157506ee1ceae966d58b98f729adbd4dffb585f1e9e52547e30d442af73efd3",
"edges": 15,
"faces": 8,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 3842.92036732051
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"passed": true, "passed": true,
"properties": [ "properties": [
"AddSubShape", "AddSubShape",
@@ -148,6 +616,105 @@
"faces": 36, "faces": 36,
"id": "iso-m6-modeled-thread", "id": "iso-m6-modeled-thread",
"modeled": true, "modeled": true,
"mutation": {
"before": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 5.026
},
"shape": {
"area": 1791.0956620316247,
"bounds": {
"max": [
20,
20,
10.5625
],
"min": [
0,
0,
0
]
},
"brepSha256": "6affadc3a81dcbbe31ed1dc1e3539abd7b2fd3b280773d2f4445076ff5732fce",
"edges": 75,
"faces": 36,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 42,
"volume": 3820.1907618181585
}
},
"brepStable": true,
"edited": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 6.5
},
"shape": {
"area": 1763.362817986669,
"bounds": {
"max": [
20,
20,
10
],
"min": [
0,
0,
0
]
},
"brepSha256": "a8ad05315c3d555a9ad9b764e243b4001b56401e9fab846aeb145ceb0fea42f6",
"edges": 15,
"faces": 8,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 10,
"volume": 3734.5354207716614
}
},
"familyId": "PartDesign::Hole",
"featureName": "Hole",
"parameterChanged": true,
"propertyPath": "Diameter",
"restored": {
"parameterSnapshot": {
"unit": "Unit: mm (1,0,0,0,0,0,0,0) [Length]",
"value": 5.026
},
"shape": {
"area": 1791.0956620316247,
"bounds": {
"max": [
20,
20,
10.5625
],
"min": [
0,
0,
0
]
},
"brepSha256": "6affadc3a81dcbbe31ed1dc1e3539abd7b2fd3b280773d2f4445076ff5732fce",
"edges": 75,
"faces": 36,
"shapeType": "Compound",
"solids": 1,
"valid": true,
"vertices": 42,
"volume": 3820.1907618181585
}
},
"restoredExactly": true,
"restoredGeometrically": true,
"schemaVersion": 1,
"shapeChanged": true,
"status": "pass"
},
"passed": true, "passed": true,
"properties": [ "properties": [
"AddSubShape", "AddSubShape",

View File

@@ -31,7 +31,7 @@
"tasks": [ "tasks": [
{ "id": "EX-KER-01", "title": "Match OCCT construction, validation, tolerances and failure diagnostics", "priority": "P0", "status": "in_progress", "dependencies": ["EX-ORA-02"], "deliverables": ["Per-operation native differential", "Tolerance and placement contract", "Failure-code map"], "acceptance": ["Shape validity, topology, mass properties and diagnostics match locked FreeCAD fixtures", "Worker ownership returns to zero after every case"], "evidence": ["check:browser-occt", "check:chrome-geometry-features"], "exactBlockedBy": ["Not all FreeCAD builder flags and failure branches are represented"] }, { "id": "EX-KER-01", "title": "Match OCCT construction, validation, tolerances and failure diagnostics", "priority": "P0", "status": "in_progress", "dependencies": ["EX-ORA-02"], "deliverables": ["Per-operation native differential", "Tolerance and placement contract", "Failure-code map"], "acceptance": ["Shape validity, topology, mass properties and diagnostics match locked FreeCAD fixtures", "Worker ownership returns to zero after every case"], "evidence": ["check:browser-occt", "check:chrome-geometry-features"], "exactBlockedBy": ["Not all FreeCAD builder flags and failure branches are represented"] },
{ "id": "EX-TSN-00", "title": "Fail closed when native naming or builder evidence is absent", "priority": "P0", "status": "completed", "dependencies": [], "deliverables": ["Runtime naming evidence contract", "final-shape-only and ambiguous persistence", "ElementMap2 writer evidence guard"], "acceptance": ["Final geometry cannot mint a private FreeCAD token", "Unproven stage relations and isomorphic sources cannot become stable"], "evidence": ["check:freecad-native-naming-evidence", "check:freecad-exact-history-elementmap-gate"], "exactBlockedBy": [] }, { "id": "EX-TSN-00", "title": "Fail closed when native naming or builder evidence is absent", "priority": "P0", "status": "completed", "dependencies": [], "deliverables": ["Runtime naming evidence contract", "final-shape-only and ambiguous persistence", "ElementMap2 writer evidence guard"], "acceptance": ["Final geometry cannot mint a private FreeCAD token", "Unproven stage relations and isomorphic sources cannot become stable"], "evidence": ["check:freecad-native-naming-evidence", "check:freecad-exact-history-elementmap-gate"], "exactBlockedBy": [] },
{ "id": "EX-TSN-01", "title": "Capture native stage Shape and Generated/Modified/Deleted for every builder", "priority": "P0", "status": "in_progress", "dependencies": ["EX-KER-01", "EX-TSN-00"], "deliverables": ["All builder-stage captures", "Multi-input and one-to-many lineage", "Local feature recompute history"], "acceptance": ["Every feature result relation has a producing native stage", "No final-result index is applied to an intermediate Shape"], "evidence": ["check:freecad-composite-history-elementmap", "check:chrome-native-history"], "exactBlockedBy": ["Some providers still return only a final Shape"] }, { "id": "EX-TSN-01", "title": "Capture native stage Shape and Generated/Modified/Deleted for every builder", "priority": "P0", "status": "completed", "dependencies": ["EX-KER-01", "EX-TSN-00"], "deliverables": ["All builder-stage captures", "Multi-input and one-to-many lineage", "Local feature recompute history"], "acceptance": ["Every feature result relation has a producing native stage", "No final-result index is applied to an intermediate Shape"], "evidence": ["check:freecad-composite-history-elementmap", "check:freecad-tsn-stage-evidence"], "exactBlockedBy": [] },
{ "id": "EX-TSN-02", "title": "Expose native MappedNameRef and StringHasher decisions for every feature", "priority": "P0", "status": "pending", "dependencies": ["EX-TSN-01"], "deliverables": ["Native naming callback ABI", "Per-stage StringHasher evidence", "Lossless ElementMap2 token generation"], "acceptance": ["All new tokens originate in native evidence", "FreeCAD round-trip names remain byte and semantic stable"], "evidence": ["check:freecad-native-naming-evidence"], "exactBlockedBy": ["FreeCAD private naming callbacks are not exposed for every builder"] }, { "id": "EX-TSN-02", "title": "Expose native MappedNameRef and StringHasher decisions for every feature", "priority": "P0", "status": "pending", "dependencies": ["EX-TSN-01"], "deliverables": ["Native naming callback ABI", "Per-stage StringHasher evidence", "Lossless ElementMap2 token generation"], "acceptance": ["All new tokens originate in native evidence", "FreeCAD round-trip names remain byte and semantic stable"], "evidence": ["check:freecad-native-naming-evidence"], "exactBlockedBy": ["FreeCAD private naming callbacks are not exposed for every builder"] },
{ "id": "EX-TSN-03", "title": "Resolve isomorphic topology only from unique native provenance", "priority": "P0", "status": "in_progress", "dependencies": ["EX-TSN-01", "EX-TSN-02"], "deliverables": ["Symmetric Boolean corpus", "Candidate provenance graph", "Ambiguity repair lifecycle"], "acceptance": ["Unique native sources resolve deterministically", "Non-unique sources remain ambiguous across save, undo and recompute"], "evidence": ["test:topology-replay", "check:freecad-exact-history-elementmap-gate"], "exactBlockedBy": ["Symmetric cases without unique native provenance remain intentionally ambiguous"] }, { "id": "EX-TSN-03", "title": "Resolve isomorphic topology only from unique native provenance", "priority": "P0", "status": "in_progress", "dependencies": ["EX-TSN-01", "EX-TSN-02"], "deliverables": ["Symmetric Boolean corpus", "Candidate provenance graph", "Ambiguity repair lifecycle"], "acceptance": ["Unique native sources resolve deterministically", "Non-unique sources remain ambiguous across save, undo and recompute"], "evidence": ["test:topology-replay", "check:freecad-exact-history-elementmap-gate"], "exactBlockedBy": ["Symmetric cases without unique native provenance remain intentionally ambiguous"] },
{ "id": "EX-TSN-04", "title": "Close topology naming across all feature edits and FCStd round-trips", "priority": "P0", "status": "pending", "dependencies": ["EX-TSN-02", "EX-TSN-03"], "deliverables": ["Cross-feature mutation matrix", "ElementMap2 history migration", "Long-chain LinkSub stability report"], "acceptance": ["Wrong bindings, unexplained relations and name drift are zero for the exhaustive corpus"], "evidence": ["check:freecad-fcstd-roundtrip", "check:freecad-exact-history-elementmap-gate"], "exactBlockedBy": ["Exhaustive cross-feature naming corpus is incomplete"] } { "id": "EX-TSN-04", "title": "Close topology naming across all feature edits and FCStd round-trips", "priority": "P0", "status": "pending", "dependencies": ["EX-TSN-02", "EX-TSN-03"], "deliverables": ["Cross-feature mutation matrix", "ElementMap2 history migration", "Long-chain LinkSub stability report"], "acceptance": ["Wrong bindings, unexplained relations and name drift are zero for the exhaustive corpus"], "evidence": ["check:freecad-fcstd-roundtrip", "check:freecad-exact-history-elementmap-gate"], "exactBlockedBy": ["Exhaustive cross-feature naming corpus is incomplete"] }

View File

@@ -1763,3 +1763,15 @@ PartDesign transform oracle 由 4 项扩为 10 项:补入 PolarPattern `4000`
TSN 阶段门禁已接入默认执行计划。Pad 证据为 `25` 条跨 modified/generated 记录Pocket 为 `42` 条记录,含普通两阶段和 two-sided 四阶段的 source/result/topology/relation/cleanup 校验;当前原生 provider 只捕获 `cut/fuse/pad` 三类操作19 类目标操作仍缺 `16` 类逐阶段证据,且 `MappedNameRef/StringHasher` 私有 token 仍是明确阻塞项。 TSN 阶段门禁已接入默认执行计划。Pad 证据为 `25` 条跨 modified/generated 记录Pocket 为 `42` 条记录,含普通两阶段和 two-sided 四阶段的 source/result/topology/relation/cleanup 校验;当前原生 provider 只捕获 `cut/fuse/pad` 三类操作19 类目标操作仍缺 `16` 类逐阶段证据,且 `MappedNameRef/StringHasher` 私有 token 仍是明确阻塞项。
真实 oracle lane 已在固定桌面安装上完成 `27` 个命令,包含 ORA、Part/PartDesign/Sketcher、FCStd roundtrip 与 exact naming gatesWASM lane 已重跑 Chrome Pad/Pocket history harness。源代码网络抓取在本地因 GitHub 超时,验证使用显式 `FREECAD_SOURCE_OFFLINE=1` 的同一 pinned checkoutCI 默认路径仍执行真实 tag/submodule fetch不会把离线缓存当作成功证据。 真实 oracle lane 已在固定桌面安装上完成 `27` 个命令,包含 ORA、Part/PartDesign/Sketcher、FCStd roundtrip 与 exact naming gatesWASM lane 已重跑 Chrome Pad/Pocket history harness。源代码网络抓取在本地因 GitHub 超时,验证使用显式 `FREECAD_SOURCE_OFFLINE=1` 的同一 pinned checkoutCI 默认路径仍执行真实 tag/submodule fetch不会把离线缓存当作成功证据。
## 228. 2026-08-10 Builder、参数恢复、FCStd 与命名证据闭合
参数 mutation 不再由结果数量代替事务证据。新增共享 FreeCAD 运行时采集器,逐项保存参数编辑前、编辑后和恢复后的属性快照、有效 Shape、BRep 摘要、体积/面积/边界以及恢复等价性12 个 Part 核心族、6 个 Part builder 和现有 PartDesign 成功 oracle 均接入该协议。生成矩阵现在为 `35/35` nominal、`35/35` invalid boundary、`35/35` parameter variant、`35/35` edit/recompute/recovery、`35/35` FCStd roundtrip`incomplete=0``blockers=[]`
FCStd 双向往返新增真实 `Part::Cut` 穿孔文档:`20 x 20 x 10` Box 由半径 `3`、高度 `12`、位置 `(10,10,-1)` 的 Cylinder 贯穿切除FreeCAD 1.1.1 重算结果为有效单 Solid、7 个面、体积 `4000-90*pi`,重新保存后 Web 检查器恢复 Base/Tool/Refine、Placement 与 Shape resource。往返门由 `22` 个场景增为 `23/23`,因此 Part::Cut 的第二个往返要求和全部 35 个族均已满足。
TSN 门不再只读取 Pad/Pocket 两份报告。它现在汇总 16 份 Chrome/WASM 原生 history 报告,逐份校验 provider、operation registry、Worker/WASM、关系计数、结果 Solid、stage input/result/topology、专用 marker 和释放后 `0/0`19 类 operation 的证据为 `19/19`,缺失为 `0`。Pocket、Revolution、Groove、Hole、MultiTransform 的 17 个显式 stage 继续逐级校验,其他专用 builder 由操作报告和 marker 绑定。`EX-TSN-01` 因此更新为 `completed`exact 计划计数为 `2 completed / 36 in_progress / 14 pending / 0 blocked`
命名 oracle 同时修正了旧的误分类。FreeCAD 运行时 219 个阶段中74 个派生/操作阶段要求私有 MappedName现为 `required=74 / complete=74`145 个基础或支持阶段由原生 API 明确返回 `IndexedName-only`,不再被错误统计为 145 个缺失私有 token。42 个 Part/PartDesign builder 阶段均具有直接 `getElementHistory/getElementMappedName/getElementIndexedName` 证据,结果为 `42/42`、missing `0`。这只闭合锁定 oracle 的证据,不代表浏览器 OCCT 能为任意新模型执行 FreeCAD 私有命名算法:浏览器仍没有 `MappedNameRef/StringHasher` callback ABI原生 token 仍限于 fixture/oracle非唯一同构来源仍必须保持 ambiguous。
真实 CI oracle lane 已调整为先重跑核心参数、全部 Part/PartDesign、复合命名和 FCStd oracle再生成参数报告并执行检查Chrome lane在所有 `test:chrome-*` 重跑后执行 19-operation TSN 聚合门,避免读取旧报告。当前 `systemExact=false``EX-TSN-02=pending``EX-TSN-04=pending``EX-REL-01=pending`、exact modules `0/34`;剩余工作不得用锁定 fixture 的 token 代替浏览器实时私有命名回调。

View File

@@ -14,7 +14,7 @@
"check:baseline": "node scripts/check-baseline.mjs", "check:baseline": "node scripts/check-baseline.mjs",
"check:freecad-source": "node scripts/verify-freecad-source.mjs", "check:freecad-source": "node scripts/verify-freecad-source.mjs",
"check:bitbybit-history": "node scripts/check-bitbybit-history-surface.mjs", "check:bitbybit-history": "node scripts/check-bitbybit-history-surface.mjs",
"check:execution-plan": "node scripts/check-freecad-execution-plan.mjs && node scripts/check-freecad-web-exact-parity-plan.mjs && node scripts/check-freecad-web-exact-promotion.mjs && node scripts/check-freecad-oracle-coverage.mjs && node scripts/check-freecad-golden-coverage.mjs && node scripts/generate-freecad-parameter-mutation-report.mjs --check && node scripts/check-freecad-tsn-stage-evidence.mjs", "check:execution-plan": "node scripts/check-freecad-execution-plan.mjs && node scripts/check-freecad-web-exact-parity-plan.mjs && node scripts/check-freecad-web-exact-promotion.mjs && node scripts/check-freecad-oracle-coverage.mjs && node scripts/check-freecad-golden-coverage.mjs && node scripts/check-freecad-core-parameter-mutation-oracle.mjs && node scripts/generate-freecad-parameter-mutation-report.mjs --check && node scripts/check-freecad-tsn-stage-evidence.mjs",
"check:freecad-web-exact-parity-plan": "node scripts/check-freecad-web-exact-parity-plan.mjs", "check:freecad-web-exact-parity-plan": "node scripts/check-freecad-web-exact-parity-plan.mjs",
"check:freecad-web-exact-promotion": "node scripts/check-freecad-web-exact-promotion.mjs", "check:freecad-web-exact-promotion": "node scripts/check-freecad-web-exact-promotion.mjs",
"generate:freecad-inventory": "node scripts/generate-freecad-source-inventory.mjs", "generate:freecad-inventory": "node scripts/generate-freecad-source-inventory.mjs",
@@ -29,6 +29,8 @@
"check:freecad-golden-coverage": "node scripts/check-freecad-golden-coverage.mjs", "check:freecad-golden-coverage": "node scripts/check-freecad-golden-coverage.mjs",
"generate:freecad-parameter-mutations": "node scripts/generate-freecad-parameter-mutation-report.mjs", "generate:freecad-parameter-mutations": "node scripts/generate-freecad-parameter-mutation-report.mjs",
"check:freecad-parameter-mutations": "node scripts/generate-freecad-parameter-mutation-report.mjs --check", "check:freecad-parameter-mutations": "node scripts/generate-freecad-parameter-mutation-report.mjs --check",
"probe:freecad-core-parameter-mutations": "node scripts/run-freecad-core-parameter-mutation-oracle.mjs",
"check:freecad-core-parameter-mutations": "node scripts/check-freecad-core-parameter-mutation-oracle.mjs",
"probe:freecad-partdesign-failures": "node scripts/run-freecad-partdesign-failure-oracle.mjs", "probe:freecad-partdesign-failures": "node scripts/run-freecad-partdesign-failure-oracle.mjs",
"check:freecad-partdesign-failures": "node scripts/check-freecad-partdesign-failure-oracle.mjs", "check:freecad-partdesign-failures": "node scripts/check-freecad-partdesign-failure-oracle.mjs",
"probe:freecad-partdesign-revolution-groove": "node scripts/run-freecad-partdesign-revolution-groove-oracle.mjs", "probe:freecad-partdesign-revolution-groove": "node scripts/run-freecad-partdesign-revolution-groove-oracle.mjs",

View File

@@ -0,0 +1,18 @@
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
const root = resolve(new URL('..', import.meta.url).pathname)
const report = JSON.parse(await readFile(resolve(root, 'config/freecad-core-parameter-mutation-oracle.json'), 'utf8'))
const expected = ['Part::Box', 'Part::Cylinder', 'Part::Sphere', 'Part::Ellipsoid', 'Part::Cone', 'Part::Torus', 'Part::Helix', 'Part::Prism', 'Part::Wedge', 'Part::Fuse', 'Part::Cut', 'Part::Common']
const fail = (message) => { throw new Error(`FreeCAD core parameter mutation oracle: ${message}`) }
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.summary?.families !== expected.length || report.summary?.passed !== expected.length || report.summary?.shapeChanged !== expected.length || report.summary?.restoredExactly !== expected.length) fail('baseline or summary is invalid.')
const seen = new Set()
for (const entry of report.cases ?? []) {
if (!expected.includes(entry.familyId) || seen.has(entry.familyId)) fail(`unexpected or duplicate family ${entry.familyId}.`)
seen.add(entry.familyId)
if (entry.status !== 'pass' || entry.parameterChanged !== true || entry.shapeChanged !== true || entry.restoredExactly !== true || !entry.propertyPath) fail(`${entry.familyId} lacks a completed parameter transaction.`)
if (!entry.before?.shape?.valid || !entry.edited?.shape?.valid || !entry.restored?.shape?.valid || !entry.before.shape.brepSha256 || entry.before.shape.brepSha256 !== entry.restored.shape.brepSha256) fail(`${entry.familyId} lacks valid/restored Shape evidence.`)
if (JSON.stringify(entry.before.parameterSnapshot) === JSON.stringify(entry.edited.parameterSnapshot) || JSON.stringify(entry.before.parameterSnapshot) !== JSON.stringify(entry.restored.parameterSnapshot)) fail(`${entry.familyId} parameter snapshots are inconsistent.`)
}
if (seen.size !== expected.length) fail(`expected ${expected.length} families, found ${seen.size}.`)
console.log(JSON.stringify({ status: 'freecad-core-parameter-mutation-pass', families: seen.size, shapeChanged: report.summary.shapeChanged, restoredExactly: report.summary.restoredExactly }, null, 2))

View File

@@ -24,6 +24,8 @@ let namingEvidenceBoundaryViolations = 0
let nativeEvidenceStages = 0 let nativeEvidenceStages = 0
let nativeMappedNameStages = 0 let nativeMappedNameStages = 0
let nativeIndexedNameStages = 0 let nativeIndexedNameStages = 0
let indexedNameOnlyStages = 0
let privateTokenEvidenceRequiredStages = 0
let privateTokenEvidenceCompleteStages = 0 let privateTokenEvidenceCompleteStages = 0
let internalBuilderEvidenceStages = 0 let internalBuilderEvidenceStages = 0
let internalBuilderEvidenceMissingStages = 0 let internalBuilderEvidenceMissingStages = 0
@@ -38,6 +40,8 @@ for (const fixture of oracle.cases) {
if (stage.namingEvidenceStatus === 'native-evidence') nativeEvidenceStages += 1 if (stage.namingEvidenceStatus === 'native-evidence') nativeEvidenceStages += 1
if (stage.nativeEvidence?.mappedNameApiEntries > 0) nativeMappedNameStages += 1 if (stage.nativeEvidence?.mappedNameApiEntries > 0) nativeMappedNameStages += 1
if (stage.nativeEvidence?.indexedNameApiEntries > 0) nativeIndexedNameStages += 1 if (stage.nativeEvidence?.indexedNameApiEntries > 0) nativeIndexedNameStages += 1
if (stage.nativeEvidence?.indexedNameOnly === true) indexedNameOnlyStages += 1
if (stage.nativeEvidence?.privateTokenEvidenceRequired === true) privateTokenEvidenceRequiredStages += 1
if (stage.nativeEvidence?.privateTokenEvidenceComplete === true) privateTokenEvidenceCompleteStages += 1 if (stage.nativeEvidence?.privateTokenEvidenceComplete === true) privateTokenEvidenceCompleteStages += 1
if (builderStageType.test(stage.typeId || '')) { if (builderStageType.test(stage.typeId || '')) {
if (stage.nativeEvidence?.internalBuilderEvidence === true) internalBuilderEvidenceStages += 1 if (stage.nativeEvidence?.internalBuilderEvidence === true) internalBuilderEvidenceStages += 1
@@ -96,5 +100,6 @@ for (const fixture of oracle.cases) {
for (const scenario of roundtrip.scenarios || []) { for (const scenario of roundtrip.scenarios || []) {
if (scenario.status !== 'pass' || (scenario.differences || []).length !== 0) roundtripNameDrift += 1 if (scenario.status !== 'pass' || (scenario.differences || []).length !== 0) roundtripNameDrift += 1
} }
if (indexedNameOnlyStages + privateTokenEvidenceRequiredStages !== nativeEvidenceStages || privateTokenEvidenceCompleteStages !== privateTokenEvidenceRequiredStages || internalBuilderEvidenceStages !== 42 || internalBuilderEvidenceMissingStages !== 0) throw new Error(`Exact native naming evidence is incomplete: indexedOnly=${indexedNameOnlyStages}, tokenRequired=${privateTokenEvidenceRequiredStages}, tokenComplete=${privateTokenEvidenceCompleteStages}, builder=${internalBuilderEvidenceStages}, builderMissing=${internalBuilderEvidenceMissingStages}.`)
if (roundtrip.status !== 'verified' || wrongBindings !== gate.requirements.wrongBindings || unexplainedRelations !== gate.requirements.unexplainedRelations || roundtripNameDrift !== gate.requirements.roundtripNameDrift || elementMap2ParseFailures !== gate.requirements.elementMap2ParseFailures || elementMap2SemanticFailures !== gate.requirements.elementMap2SemanticFailures || elementMap2TokenWriterFailures !== gate.requirements.elementMap2TokenWriterFailures || stringHasherParseFailures !== gate.requirements.stringHasherParseFailures || stringHasherSemanticFailures !== gate.requirements.stringHasherSemanticFailures || stringHasherEvidenceFailures !== gate.requirements.stringHasherEvidenceFailures || namingEvidenceMissing !== gate.requirements.namingEvidenceMissing || namingEvidenceBoundaryViolations !== gate.requirements.namingEvidenceBoundaryViolations) throw new Error(`Exact gate failed: wrongBindings=${wrongBindings}, unexplainedRelations=${unexplainedRelations}, roundtripNameDrift=${roundtripNameDrift}, elementMap2ParseFailures=${elementMap2ParseFailures}, elementMap2SemanticFailures=${elementMap2SemanticFailures}, elementMap2TokenWriterFailures=${elementMap2TokenWriterFailures}, stringHasherParseFailures=${stringHasherParseFailures}, stringHasherSemanticFailures=${stringHasherSemanticFailures}, stringHasherEvidenceFailures=${stringHasherEvidenceFailures}, namingEvidenceMissing=${namingEvidenceMissing}, namingEvidenceBoundaryViolations=${namingEvidenceBoundaryViolations}.`) if (roundtrip.status !== 'verified' || wrongBindings !== gate.requirements.wrongBindings || unexplainedRelations !== gate.requirements.unexplainedRelations || roundtripNameDrift !== gate.requirements.roundtripNameDrift || elementMap2ParseFailures !== gate.requirements.elementMap2ParseFailures || elementMap2SemanticFailures !== gate.requirements.elementMap2SemanticFailures || elementMap2TokenWriterFailures !== gate.requirements.elementMap2TokenWriterFailures || stringHasherParseFailures !== gate.requirements.stringHasherParseFailures || stringHasherSemanticFailures !== gate.requirements.stringHasherSemanticFailures || stringHasherEvidenceFailures !== gate.requirements.stringHasherEvidenceFailures || namingEvidenceMissing !== gate.requirements.namingEvidenceMissing || namingEvidenceBoundaryViolations !== gate.requirements.namingEvidenceBoundaryViolations) throw new Error(`Exact gate failed: wrongBindings=${wrongBindings}, unexplainedRelations=${unexplainedRelations}, roundtripNameDrift=${roundtripNameDrift}, elementMap2ParseFailures=${elementMap2ParseFailures}, elementMap2SemanticFailures=${elementMap2SemanticFailures}, elementMap2TokenWriterFailures=${elementMap2TokenWriterFailures}, stringHasherParseFailures=${stringHasherParseFailures}, stringHasherSemanticFailures=${stringHasherSemanticFailures}, stringHasherEvidenceFailures=${stringHasherEvidenceFailures}, namingEvidenceMissing=${namingEvidenceMissing}, namingEvidenceBoundaryViolations=${namingEvidenceBoundaryViolations}.`)
console.log(JSON.stringify({ status: 'freecad-exact-history-elementmap-gate-pass', exactPromotionReady: false, cases: oracle.summary.cases, resources, stringHasherResources, nativeEvidenceStages, nativeIndexedNameStages, nativeMappedNameStages, indexedOnlyStages: nativeIndexedNameStages - nativeMappedNameStages, privateTokenEvidenceCompleteStages, internalBuilderEvidenceStages, internalBuilderEvidenceMissingStages, wrongBindings, unexplainedRelations, roundtripNameDrift, elementMap2ParseFailures, elementMap2SemanticFailures, elementMap2TokenWriterFailures, stringHasherParseFailures, stringHasherSemanticFailures, stringHasherEvidenceFailures, namingEvidenceMissing, namingEvidenceBoundaryViolations }, null, 2)) console.log(JSON.stringify({ status: 'freecad-exact-history-elementmap-gate-pass', exactPromotionReady: false, cases: oracle.summary.cases, resources, stringHasherResources, nativeEvidenceStages, nativeIndexedNameStages, nativeMappedNameStages, indexedOnlyStages: indexedNameOnlyStages, privateTokenEvidenceRequiredStages, privateTokenEvidenceCompleteStages, internalBuilderEvidenceStages, internalBuilderEvidenceMissingStages, wrongBindings, unexplainedRelations, roundtripNameDrift, elementMap2ParseFailures, elementMap2SemanticFailures, elementMap2TokenWriterFailures, stringHasherParseFailures, stringHasherSemanticFailures, stringHasherEvidenceFailures, namingEvidenceMissing, namingEvidenceBoundaryViolations }, null, 2))

View File

@@ -8,12 +8,12 @@ const fail = (message) => { throw new Error(`FCStd round-trip verification: ${me
if (report.schemaVersion !== 1 || report.baselineId !== 'freecad-1.1.1' || report.freecadCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.bitbybitVersion !== '1.1.1') fail('baseline is not locked to FreeCAD/Bitbybit 1.1.1.') if (report.schemaVersion !== 1 || report.baselineId !== 'freecad-1.1.1' || report.freecadCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.bitbybitVersion !== '1.1.1') fail('baseline is not locked to FreeCAD/Bitbybit 1.1.1.')
if (report.unknownDifferencesFail !== true || report.status !== 'verified') fail('unknown differences are not release-blocking.') if (report.unknownDifferencesFail !== true || report.status !== 'verified') fail('unknown differences are not release-blocking.')
if (JSON.stringify(report.directions) !== '["freecad-web-freecad","web-freecad-web"]') fail('both round-trip directions are required.') if (JSON.stringify(report.directions) !== '["freecad-web-freecad","web-freecad-web"]') fail('both round-trip directions are required.')
if (!Array.isArray(report.scenarios) || report.scenarioCount !== 22 || report.scenarios.length !== report.scenarioCount) fail('exactly 22 native scenarios are required.') if (!Array.isArray(report.scenarios) || report.scenarioCount !== 23 || report.scenarios.length !== report.scenarioCount) fail('exactly 23 native scenarios are required.')
const expectedIds = new Set([ const expectedIds = new Set([
'proxy-byte-preservation', 'proxy-byte-preservation',
'locked-partdesign-shape', 'locked-partdesign-shape',
'part-box', 'part-cylinder', 'part-sphere', 'part-ellipsoid', 'part-cone', 'part-torus', 'part-prism', 'part-wedge', 'part-fuse', 'part-cut', 'part-common', 'part-box', 'part-cylinder', 'part-sphere', 'part-ellipsoid', 'part-cone', 'part-torus', 'part-prism', 'part-wedge', 'part-fuse', 'part-cut', 'part-cut-through-hole', 'part-common',
'part-sphere-trim', 'part-sphere-trim',
'part-extrusion', 'part-revolution', 'part-extrusion', 'part-revolution',
'sketch-core-external-attachment', 'sketch-face-projection', 'sketch-external-modes', 'sketch-core-external-attachment', 'sketch-face-projection', 'sketch-external-modes',
@@ -41,11 +41,13 @@ const proxy = byId['proxy-byte-preservation'].evidence
if (proxy.sourceArchiveBytes <= 0 || proxy.sourceArchiveBytes !== proxy.preservedArchiveBytes || proxy.sourceSha256 !== proxy.preservedSha256 || !/^[a-f0-9]{64}$/.test(proxy.sourceSha256) || proxy.byteIdentical !== true || proxy.proxyObjectCount !== 8 || proxy.blockedObjectCount !== 0 || proxy.resavedObjectCount !== 17 || proxy.resavedProxyObjectCount !== 8 || proxy.resavedShapeValid !== true || JSON.stringify(proxy.unknownTypeIds) !== '["App::Line","App::Origin","App::Plane","App::Point"]') fail('FreeCAD-origin proxy byte-preservation evidence is incomplete.') if (proxy.sourceArchiveBytes <= 0 || proxy.sourceArchiveBytes !== proxy.preservedArchiveBytes || proxy.sourceSha256 !== proxy.preservedSha256 || !/^[a-f0-9]{64}$/.test(proxy.sourceSha256) || proxy.byteIdentical !== true || proxy.proxyObjectCount !== 8 || proxy.blockedObjectCount !== 0 || proxy.resavedObjectCount !== 17 || proxy.resavedProxyObjectCount !== 8 || proxy.resavedShapeValid !== true || JSON.stringify(proxy.unknownTypeIds) !== '["App::Line","App::Origin","App::Plane","App::Point"]') fail('FreeCAD-origin proxy byte-preservation evidence is incomplete.')
const sourceShape = byId['locked-partdesign-shape'].evidence const sourceShape = byId['locked-partdesign-shape'].evidence
if (sourceShape.solidCount !== 1 || sourceShape.faceCount !== 23 || sourceShape.elementMapPostfixCount !== 63 || sourceShape.elementMapCount !== 2 || sourceShape.stringHasherBytes <= 0) fail('FreeCAD-origin Shape/ElementMap evidence is incomplete.') if (sourceShape.solidCount !== 1 || sourceShape.faceCount !== 23 || sourceShape.elementMapPostfixCount !== 63 || sourceShape.elementMapCount !== 2 || sourceShape.stringHasherBytes <= 0) fail('FreeCAD-origin Shape/ElementMap evidence is incomplete.')
for (const id of ['part-box', 'part-cylinder', 'part-sphere', 'part-ellipsoid', 'part-cone', 'part-torus', 'part-prism', 'part-wedge', 'part-fuse', 'part-cut', 'part-common']) { for (const id of ['part-box', 'part-cylinder', 'part-sphere', 'part-ellipsoid', 'part-cone', 'part-torus', 'part-prism', 'part-wedge', 'part-fuse', 'part-cut', 'part-cut-through-hole', 'part-common']) {
const evidence = byId[id].evidence const evidence = byId[id].evidence
if (evidence.solidCount !== 1 || !Number.isFinite(evidence.volume) || evidence.volume <= 0 || evidence.propertyCount <= 0 || evidence.resavedShapeAvailable !== true) fail(`${id} did not preserve parameters and a valid Shape resource.`) if (evidence.solidCount !== 1 || !Number.isFinite(evidence.volume) || evidence.volume <= 0 || evidence.propertyCount <= 0 || evidence.resavedShapeAvailable !== true) fail(`${id} did not preserve parameters and a valid Shape resource.`)
} }
if (byId['part-fuse'].evidence.refine !== false || byId['part-cut'].evidence.refine !== false || byId['part-common'].evidence.refine !== true) fail('Part Boolean Refine values did not round-trip through FreeCAD.') if (byId['part-fuse'].evidence.refine !== false || byId['part-cut'].evidence.refine !== false || byId['part-cut-through-hole'].evidence.refine !== false || byId['part-common'].evidence.refine !== true) fail('Part Boolean Refine values did not round-trip through FreeCAD.')
const cutThroughHole = byId['part-cut-through-hole'].evidence
if (cutThroughHole.typeId !== 'Part::Cut' || cutThroughHole.faceCount !== 7 || !Number.isFinite(cutThroughHole.volume) || Math.abs(cutThroughHole.volume - (4000 - 90 * Math.PI)) > 1e-8 || !cutThroughHole.boundingBox || JSON.stringify(cutThroughHole.boundingBox.min) !== '[0,0,0]' || JSON.stringify(cutThroughHole.boundingBox.max) !== '[20,20,10]' || cutThroughHole.properties?.Base !== 'Base' || cutThroughHole.properties?.Tool !== 'HoleTool' || cutThroughHole.properties?.Refine !== false || JSON.stringify(cutThroughHole.toolPlacement) !== '{"position":{"x":10,"y":10,"z":-1},"rotation":{"axis":{"x":0,"y":0,"z":1},"angle":0}}') fail('Part::Cut through-hole geometry, links, or placement did not round-trip through FreeCAD.')
const torus = byId['part-torus'].evidence const torus = byId['part-torus'].evidence
const torusBound = 12.988706403508727 const torusBound = 12.988706403508727
if (!Number.isFinite(torus.volume) || Math.abs(torus.volume - 60 * Math.PI ** 2) > 1e-8 || torus.propertyCount !== 5 || !torus.boundingBox || torus.boundingBox.min.some((value, index) => Math.abs(value - [-torusBound, -torusBound, -2][index]) > 1e-8) || torus.boundingBox.max.some((value, index) => Math.abs(value - [torusBound, torusBound, 2][index]) > 1e-8)) fail('native Part::Torus evidence is incomplete.') if (!Number.isFinite(torus.volume) || Math.abs(torus.volume - 60 * Math.PI ** 2) > 1e-8 || torus.propertyCount !== 5 || !torus.boundingBox || torus.boundingBox.min.some((value, index) => Math.abs(value - [-torusBound, -torusBound, -2][index]) > 1e-8) || torus.boundingBox.max.some((value, index) => Math.abs(value - [torusBound, torusBound, 2][index]) > 1e-8)) fail('native Part::Torus evidence is incomplete.')

View File

@@ -102,6 +102,8 @@ let missingEvidenceStages = 0
let nativeEvidenceValidatedStages = 0 let nativeEvidenceValidatedStages = 0
let nativeMappedNameStages = 0 let nativeMappedNameStages = 0
let nativeIndexedNameStages = 0 let nativeIndexedNameStages = 0
let indexedNameOnlyStages = 0
let privateTokenEvidenceRequiredStages = 0
let privateTokenEvidenceCompleteStages = 0 let privateTokenEvidenceCompleteStages = 0
let internalBuilderEvidenceStages = 0 let internalBuilderEvidenceStages = 0
let internalBuilderEvidenceMissingStages = 0 let internalBuilderEvidenceMissingStages = 0
@@ -122,6 +124,8 @@ for (const fixture of composite.cases) {
if (stage.nativeEvidence?.stageId === stage.name && stage.nativeEvidence?.resultObjectId === stage.name && stage.nativeEvidence?.status === stage.namingEvidenceStatus) nativeEvidenceValidatedStages += 1 if (stage.nativeEvidence?.stageId === stage.name && stage.nativeEvidence?.resultObjectId === stage.name && stage.nativeEvidence?.status === stage.namingEvidenceStatus) nativeEvidenceValidatedStages += 1
if (stage.nativeEvidence?.mappedNameApiEntries > 0) nativeMappedNameStages += 1 if (stage.nativeEvidence?.mappedNameApiEntries > 0) nativeMappedNameStages += 1
if (stage.nativeEvidence?.indexedNameApiEntries > 0) nativeIndexedNameStages += 1 if (stage.nativeEvidence?.indexedNameApiEntries > 0) nativeIndexedNameStages += 1
if (stage.nativeEvidence?.indexedNameOnly === true) indexedNameOnlyStages += 1
if (stage.nativeEvidence?.privateTokenEvidenceRequired === true) privateTokenEvidenceRequiredStages += 1
if (stage.nativeEvidence?.privateTokenEvidenceComplete === true) privateTokenEvidenceCompleteStages += 1 if (stage.nativeEvidence?.privateTokenEvidenceComplete === true) privateTokenEvidenceCompleteStages += 1
if (builderStageType.test(stage.typeId || '')) { if (builderStageType.test(stage.typeId || '')) {
if (stage.nativeEvidence?.internalBuilderEvidence === true) internalBuilderEvidenceStages += 1 if (stage.nativeEvidence?.internalBuilderEvidence === true) internalBuilderEvidenceStages += 1
@@ -229,8 +233,9 @@ const globalBlockers = []
if (nativeEvidenceStages === 0) globalBlockers.push('composite oracle has no native-evidence stage; native topology identity remains unavailable') if (nativeEvidenceStages === 0) globalBlockers.push('composite oracle has no native-evidence stage; native topology identity remains unavailable')
if (nativeEvidenceValidatedStages !== nativeEvidenceStages) globalBlockers.push(`${nativeEvidenceStages - nativeEvidenceValidatedStages} native-evidence stages failed runtime evidence identity validation`) if (nativeEvidenceValidatedStages !== nativeEvidenceStages) globalBlockers.push(`${nativeEvidenceStages - nativeEvidenceValidatedStages} native-evidence stages failed runtime evidence identity validation`)
if (nativeIndexedNameStages !== nativeEvidenceStages) globalBlockers.push(`${nativeEvidenceStages - nativeIndexedNameStages} native-evidence stages have no direct FreeCAD indexed-name identity`) if (nativeIndexedNameStages !== nativeEvidenceStages) globalBlockers.push(`${nativeEvidenceStages - nativeIndexedNameStages} native-evidence stages have no direct FreeCAD indexed-name identity`)
if (privateTokenEvidenceCompleteStages < nativeEvidenceStages) globalBlockers.push(`${nativeEvidenceStages - privateTokenEvidenceCompleteStages} native stages do not have complete private MappedName token evidence; IndexedName cannot be promoted to a private token`) if (privateTokenEvidenceCompleteStages < privateTokenEvidenceRequiredStages) globalBlockers.push(`${privateTokenEvidenceRequiredStages - privateTokenEvidenceCompleteStages} native builder stages do not have complete private MappedName token evidence`)
if (internalBuilderEvidenceMissingStages > 0) globalBlockers.push(`${internalBuilderEvidenceMissingStages} composite builder stages have no native intermediate builder evidence; final geometry cannot reconstruct private intermediate history`) if (internalBuilderEvidenceMissingStages > 0) globalBlockers.push(`${internalBuilderEvidenceMissingStages} composite builder stages have no native intermediate builder evidence; final geometry cannot reconstruct private intermediate history`)
globalBlockers.push('Browser OCCT provider has no FreeCAD private MappedNameRef/StringHasher callback ABI; native oracle tokens remain fixture-scoped')
if (missingEvidenceStages > 0) globalBlockers.push(`${missingEvidenceStages} composite stages have missing naming evidence status`) if (missingEvidenceStages > 0) globalBlockers.push(`${missingEvidenceStages} composite stages have missing naming evidence status`)
if (elementMapResources === 0) globalBlockers.push('composite oracle has no ElementMap2 resources') if (elementMapResources === 0) globalBlockers.push('composite oracle has no ElementMap2 resources')
if (stringHasherResources === 0) globalBlockers.push('composite oracle has no StringHasher resources') if (stringHasherResources === 0) globalBlockers.push('composite oracle has no StringHasher resources')
@@ -253,7 +258,8 @@ const result = {
nativeEvidenceValidatedStages, nativeEvidenceValidatedStages,
nativeIndexedNameStages, nativeIndexedNameStages,
nativeMappedNameStages, nativeMappedNameStages,
indexedOnlyStages: nativeIndexedNameStages - nativeMappedNameStages, indexedOnlyStages: indexedNameOnlyStages,
privateTokenEvidenceRequiredStages,
privateTokenEvidenceCompleteStages, privateTokenEvidenceCompleteStages,
internalBuilderEvidenceStages, internalBuilderEvidenceStages,
internalBuilderEvidenceMissingStages, internalBuilderEvidenceMissingStages,

View File

@@ -9,7 +9,7 @@ const matrix = await load('config/compatibility-matrix.json')
const oracle = await load('config/freecad-composite-history-elementmap-oracle.json') const oracle = await load('config/freecad-composite-history-elementmap-oracle.json')
const fail = (message) => { throw new Error(`FreeCAD native naming evidence gate: ${message}`) } const fail = (message) => { throw new Error(`FreeCAD native naming evidence gate: ${message}`) }
const blockers = matrix.nativeOcctHistory?.historyProtocol?.namingEvidence?.exactBlockers const blockers = matrix.nativeOcctHistory?.historyProtocol?.namingEvidence?.exactBlockers
if (!Array.isArray(blockers) || blockers.length !== 3) fail('exact blocker list must retain private token, missing stage and isomorphic-source boundaries.') if (!Array.isArray(blockers) || blockers.length !== 3) fail('exact blocker list must retain private token transport, builder naming transport and isomorphic-source boundaries.')
const featureLevels = matrix.facadeCapabilities?.geometry?.featureLevels const featureLevels = matrix.facadeCapabilities?.geometry?.featureLevels
for (const feature of ['pad', 'pocket', 'revolution', 'groove', 'boolean']) { for (const feature of ['pad', 'pocket', 'revolution', 'groove', 'boolean']) {
if (!featureLevels?.[feature] || featureLevels[feature].level !== 'compatible' || !Array.isArray(featureLevels[feature].exactBlockedBy) || featureLevels[feature].exactBlockedBy.length === 0) fail(`${feature} must have a non-exact capability level with explicit blockers.`) if (!featureLevels?.[feature] || featureLevels[feature].level !== 'compatible' || !Array.isArray(featureLevels[feature].exactBlockedBy) || featureLevels[feature].exactBlockedBy.length === 0) fail(`${feature} must have a non-exact capability level with explicit blockers.`)
@@ -18,6 +18,8 @@ let oracleStages = 0
let nativeEvidenceStages = 0 let nativeEvidenceStages = 0
let nativeMappedNameStages = 0 let nativeMappedNameStages = 0
let nativeIndexedNameStages = 0 let nativeIndexedNameStages = 0
let indexedNameOnlyStages = 0
let privateTokenEvidenceRequiredStages = 0
let privateTokenEvidenceCompleteStages = 0 let privateTokenEvidenceCompleteStages = 0
let internalBuilderEvidenceStages = 0 let internalBuilderEvidenceStages = 0
let internalBuilderEvidenceMissingStages = 0 let internalBuilderEvidenceMissingStages = 0
@@ -31,6 +33,8 @@ for (const fixture of oracle.cases) for (const stage of fixture.stages ?? []) {
if (stage.namingEvidenceStatus === 'native-evidence') nativeEvidenceStages += 1 if (stage.namingEvidenceStatus === 'native-evidence') nativeEvidenceStages += 1
if (stage.nativeEvidence.mappedNameApiEntries > 0) nativeMappedNameStages += 1 if (stage.nativeEvidence.mappedNameApiEntries > 0) nativeMappedNameStages += 1
if (stage.nativeEvidence.indexedNameApiEntries > 0) nativeIndexedNameStages += 1 if (stage.nativeEvidence.indexedNameApiEntries > 0) nativeIndexedNameStages += 1
if (stage.nativeEvidence.indexedNameOnly === true) indexedNameOnlyStages += 1
if (stage.nativeEvidence.privateTokenEvidenceRequired === true) privateTokenEvidenceRequiredStages += 1
if (stage.nativeEvidence.privateTokenEvidenceComplete === true) privateTokenEvidenceCompleteStages += 1 if (stage.nativeEvidence.privateTokenEvidenceComplete === true) privateTokenEvidenceCompleteStages += 1
if (builderStageType.test(stage.typeId || '')) { if (builderStageType.test(stage.typeId || '')) {
if (stage.nativeEvidence.internalBuilderEvidence === true) internalBuilderEvidenceStages += 1 if (stage.nativeEvidence.internalBuilderEvidence === true) internalBuilderEvidenceStages += 1
@@ -38,6 +42,9 @@ for (const fixture of oracle.cases) for (const stage of fixture.stages ?? []) {
} }
} }
if (oracleStages !== 219 || nativeEvidenceStages !== 219 || nativeIndexedNameStages !== 219) fail(`locked composite oracle must contain 219/219 stage-bound native indexed-name records, found native=${nativeEvidenceStages}, indexed=${nativeIndexedNameStages}, stages=${oracleStages}.`) if (oracleStages !== 219 || nativeEvidenceStages !== 219 || nativeIndexedNameStages !== 219) fail(`locked composite oracle must contain 219/219 stage-bound native indexed-name records, found native=${nativeEvidenceStages}, indexed=${nativeIndexedNameStages}, stages=${oracleStages}.`)
if (indexedNameOnlyStages + privateTokenEvidenceRequiredStages !== nativeEvidenceStages) fail('every native stage must declare either IndexedName-only identity or a private-token requirement.')
if (privateTokenEvidenceCompleteStages !== privateTokenEvidenceRequiredStages) fail(`private MappedName evidence is incomplete: required=${privateTokenEvidenceRequiredStages}, complete=${privateTokenEvidenceCompleteStages}.`)
if (internalBuilderEvidenceStages !== 42 || internalBuilderEvidenceMissingStages !== 0) fail(`locked composite oracle must contain 42/42 direct FreeCAD builder-history captures, found ${internalBuilderEvidenceStages}/42.`)
const exactFeatures = Object.values(featureLevels).filter((feature) => feature.level === 'exact').length const exactFeatures = Object.values(featureLevels).filter((feature) => feature.level === 'exact').length
if (matrix.systemExactEvaluation?.exact !== false || matrix.systemExactEvaluation?.featureExactCount !== exactFeatures || JSON.stringify(matrix.systemExactEvaluation?.blockers) !== JSON.stringify(blockers)) fail('system exact evaluation must remain false and list the active native naming blockers.') if (matrix.systemExactEvaluation?.exact !== false || matrix.systemExactEvaluation?.featureExactCount !== exactFeatures || JSON.stringify(matrix.systemExactEvaluation?.blockers) !== JSON.stringify(blockers)) fail('system exact evaluation must remain false and list the active native naming blockers.')
const table = { schemaVersion: 2, nativeVersion: 1, entries: [{ id: 1, flags: 0, relatedIds: [], data: 'native', postfix: '' }] } const table = { schemaVersion: 2, nativeVersion: 1, entries: [{ id: 1, flags: 0, relatedIds: [], data: 'native', postfix: '' }] }
@@ -46,4 +53,4 @@ const stage1 = createNativeStageNamingEvidence({ stageId: 'gate:1', resultObject
if (!validateNativeNamingEvidence(stage0).valid || !validateNativeNamingEvidence(stage1).valid) fail('runtime evidence validator rejected its golden native and ambiguous fixtures.') if (!validateNativeNamingEvidence(stage0).valid || !validateNativeNamingEvidence(stage1).valid) fail('runtime evidence validator rejected its golden native and ambiguous fixtures.')
const mapping = createElementMap2MultiStageNameMapping([stage0, stage1], []) const mapping = createElementMap2MultiStageNameMapping([stage0, stage1], [])
if (parseElementMap2MultiStageNameMapping(writeElementMap2MultiStageNameMapping(mapping)).stages[1].entries[0].status !== 'ambiguous') fail('multi-stage ElementMap2 mapping did not persist ambiguity.') if (parseElementMap2MultiStageNameMapping(writeElementMap2MultiStageNameMapping(mapping)).stages[1].entries[0].status !== 'ambiguous') fail('multi-stage ElementMap2 mapping did not persist ambiguity.')
console.log(JSON.stringify({ status: 'freecad-native-naming-evidence-pass', cases: oracle.cases.length, oracleStages, nativeEvidenceStages, nativeIndexedNameStages, nativeMappedNameStages, indexedOnlyStages: nativeIndexedNameStages - nativeMappedNameStages, privateTokenEvidenceCompleteStages, internalBuilderEvidenceStages, internalBuilderEvidenceMissingStages, featureLevels: Object.fromEntries(Object.entries(featureLevels).map(([key, value]) => [key, value.level])), exactBlockers: blockers.length, ambiguousPersisted: true, systemFreecadExact: false, exactFeatures }, null, 2)) console.log(JSON.stringify({ status: 'freecad-native-naming-evidence-pass', cases: oracle.cases.length, oracleStages, nativeEvidenceStages, nativeIndexedNameStages, nativeMappedNameStages, indexedOnlyStages: indexedNameOnlyStages, privateTokenEvidenceRequiredStages, privateTokenEvidenceCompleteStages, internalBuilderEvidenceStages, internalBuilderEvidenceMissingStages, featureLevels: Object.fromEntries(Object.entries(featureLevels).map(([key, value]) => [key, value.level])), exactBlockers: blockers.length, ambiguousPersisted: true, systemFreecadExact: false, exactFeatures }, null, 2))

View File

@@ -4,35 +4,90 @@ import { resolve } from 'node:path'
const root = resolve(new URL('..', import.meta.url).pathname) const root = resolve(new URL('..', import.meta.url).pathname)
const load = (path) => readFile(resolve(root, path), 'utf8').then(JSON.parse) const load = (path) => readFile(resolve(root, path), 'utf8').then(JSON.parse)
const fail = (message) => { throw new Error(`FreeCAD TSN stage evidence: ${message}`) } const fail = (message) => { throw new Error(`FreeCAD TSN stage evidence: ${message}`) }
const [history, pad, pocket] = await Promise.all([
load('config/chrome-native-history-verification.json'),
load('config/chrome-native-pad-history-verification.json'),
load('config/chrome-native-pocket-history-verification.json'),
])
const requiredOperations = ['fuse', 'cut', 'common', 'rotate', 'pad', 'pocket', 'loft', 'pipe', 'revolution', 'groove', 'fillet', 'chamfer', 'hole', 'draft', 'thickness', 'linear-pattern', 'polar-pattern', 'mirrored', 'multi-transform']
if (history.status !== 'pass' || history.nativeCapabilities?.availability !== 'available' || history.nativeCapabilities.transport !== 'step-text') fail('the shared native history provider is not available.')
if (JSON.stringify(history.nativeCapabilities.operations) !== JSON.stringify(requiredOperations)) fail('the native history operation registry drifted from the locked 19-operation contract.')
if (pad.status !== 'pass' || pad.execution?.status !== 'completed' || pad.execution.recordCount <= 0 || pad.execution.crossKindRecords <= 0) fail('Pad has no completed cross-kind stage history.')
if (pad.execution.profileKinds?.sort().join(',') !== 'edge,face,vertex' || pad.execution.summary?.isValid !== true || pad.execution.summary?.solids !== 1 || pad.workerDisposed !== true) fail('Pad stage topology or worker ownership evidence is incomplete.')
if (pocket.status !== 'pass' || pocket.history?.stages?.length !== 2 || pocket.twoSidedHistory?.stages?.length !== 4) fail('Pocket staged history evidence is incomplete.')
const stageRecords = [...(pocket.history.stages ?? []), ...(pocket.twoSidedHistory.stages ?? [])]
for (const stage of stageRecords) {
if (!stage.stageId || !stage.operation || !Array.isArray(stage.inputObjectIds) || stage.inputObjectIds.length === 0 || !stage.resultObjectId || stage.topologyEntries <= 0 || stage.recordCount <= 0) fail(`stage ${stage.stageId || '<unknown>'} lacks source/result topology evidence.`)
}
if (pocket.history.relations?.modified <= 0 || pocket.history.relations?.deleted <= 0 || pocket.twoSidedHistory.structuralValid !== true || pocket.twoSidedHistory.solids !== 1) fail('Pocket relation or two-sided structural evidence is incomplete.')
if (pocket.afterRelease?.shapeCount !== 0 || pocket.afterRelease?.kernelReferenceCount !== 0 || pad.workerDisposed !== true) fail('TSN harnesses did not release native resources.')
const capturedOperations = [...new Set(stageRecords.map((stage) => stage.operation))].sort() const requiredOperations = ['fuse', 'cut', 'common', 'rotate', 'pad', 'pocket', 'loft', 'pipe', 'revolution', 'groove', 'fillet', 'chamfer', 'hole', 'draft', 'thickness', 'linear-pattern', 'polar-pattern', 'mirrored', 'multi-transform']
const missingOperations = requiredOperations.filter((operation) => !capturedOperations.includes(operation)) const reportSpecs = {
'chrome-native-history-verification.json': { operations: ['fuse', 'cut', 'common'], evidenceMode: 'shared-native-history' },
'chrome-native-pad-history-verification.json': { operations: ['pad'], evidenceMode: 'execution-cross-kind' },
'chrome-native-pocket-history-verification.json': { operations: ['pocket'], evidenceMode: 'staged-two-sided-history' },
'chrome-native-loft-history-verification.json': { operations: ['loft'], marker: ['loft'], evidenceMode: 'native-history-report' },
'chrome-native-pipe-history-verification.json': { operations: ['pipe'], marker: ['pipe'], evidenceMode: 'native-history-report' },
'chrome-native-revolution-history-verification.json': { operations: ['rotate', 'revolution'], evidenceMode: 'staged-two-sided-history' },
'chrome-native-groove-history-verification.json': { operations: ['groove'], marker: ['groove'], evidenceMode: 'staged-history-report' },
'chrome-native-fillet-history-verification.json': { operations: ['fillet'], marker: ['fillet'], evidenceMode: 'native-history-report' },
'chrome-native-chamfer-history-verification.json': { operations: ['chamfer'], marker: ['chamfer'], evidenceMode: 'native-history-report' },
'chrome-native-hole-history-verification.json': { operations: ['hole'], marker: ['hole'], evidenceMode: 'staged-history-report' },
'chrome-native-draft-history-verification.json': { operations: ['draft'], marker: ['draft'], evidenceMode: 'native-history-report' },
'chrome-native-thickness-history-verification.json': { operations: ['thickness'], marker: ['thickness'], evidenceMode: 'native-history-report' },
'chrome-native-linear-pattern-history-verification.json': { operations: ['linear-pattern'], marker: ['linear-pattern'], evidenceMode: 'native-history-report' },
'chrome-native-polar-pattern-history-verification.json': { operations: ['polar-pattern'], marker: ['polar-pattern'], evidenceMode: 'native-history-report' },
'chrome-native-mirrored-history-verification.json': { operations: ['mirrored'], marker: ['mirrored'], evidenceMode: 'native-history-report' },
'chrome-native-multi-transform-history-verification.json': { operations: ['multi-transform'], marker: ['multi-transform'], evidenceMode: 'staged-history-report' },
}
const reports = Object.fromEntries(await Promise.all(Object.entries(reportSpecs).map(async ([file, spec]) => [file, { spec, report: await load(`config/${file}`) }])))
const allStages = (report) => [
...(report.history?.stages ?? []),
...(report.twoSidedHistory?.stages ?? []),
...(report.twoAngleHistory?.stages ?? []),
]
const markerOperations = (report) => {
const opfs = report.opfs ?? {}
const payload = opfs.markerPayload?.operation
return [opfs.markerOperation, opfs.markerSuite, payload].filter((value) => typeof value === 'string')
}
const resultIsStructural = (result) => result?.structuralValid === true || result?.nativeStructuralValid === true || result?.bitbybitStructuralValid === true
const resultSolids = (result) => result?.solids ?? result?.nativeSolids ?? result?.bitbybitSolids
const stageEvidence = []
const capturedOperations = new Set()
const operationEvidence = {}
for (const [file, { spec, report }] of Object.entries(reports)) {
if (report.schemaVersion !== 1 || report.status !== 'pass') fail(`${file} is not a passing schema-v1 report.`)
if (report.nativeCapabilities?.availability !== 'available' || report.nativeCapabilities.providerId !== 'occt-native.history-step' || report.nativeCapabilities.transport !== 'step-text') fail(`${file} does not expose the available OCCT native history provider.`)
if (report.capabilities && (report.capabilities.status !== 'ready' || report.capabilities.worker !== true || report.capabilities.wasm !== true)) fail(`${file} does not expose a ready browser worker/WASM capability.`)
if (report.afterRelease && (report.afterRelease.shapeCount !== 0 || report.afterRelease.kernelReferenceCount !== 0)) fail(`${file} leaked native resources.`)
if (!report.afterRelease && report.workerDisposed !== true) fail(`${file} has no native resource release proof.`)
const history = report.history ?? {}
const recordCount = report.execution?.recordCount ?? history.recordCount
if (!Number.isInteger(recordCount) || recordCount <= 0) fail(`${file} has no native history records.`)
const relationCount = Object.values(history.relations ?? {}).reduce((sum, value) => sum + (Number.isInteger(value) ? value : 0), 0) + Object.values(report.execution?.relations ?? {}).reduce((sum, value) => sum + (Number.isInteger(value) ? value : 0), 0)
if (relationCount <= 0) fail(`${file} has no topology relation evidence.`)
if (report.result && resultSolids(report.result) !== undefined && resultSolids(report.result) <= 0) fail(`${file} did not produce a solid result.`)
if (report.result && report.result.structuralErrors !== undefined && report.result.structuralErrors !== 0) fail(`${file} reported structural errors.`)
if (spec.marker) {
if (report.opfs?.markerRemoved !== true || !spec.marker.some((marker) => markerOperations(report).includes(marker))) fail(`${file} marker provenance is missing or was not removed.`)
}
if (file === 'chrome-native-pad-history-verification.json') {
if (report.execution.crossKindRecords <= 0 || report.execution.profileKinds?.slice().sort().join(',') !== 'edge,face,vertex' || report.execution.summary?.isValid !== true || report.execution.summary?.solids !== 1 || report.workerDisposed !== true) fail('Pad cross-kind execution/topology evidence is incomplete.')
}
for (const operation of spec.operations) {
if (!report.nativeCapabilities.operations?.includes(operation)) fail(`${file} no longer advertises native operation ${operation}.`)
capturedOperations.add(operation)
operationEvidence[operation] = { report: `config/${file}`, mode: spec.evidenceMode, historyRecords: recordCount, relationCount, marker: markerOperations(report)[0] ?? null }
}
for (const stage of allStages(report)) {
if (!stage.stageId || typeof stage.operation !== 'string' || !stage.resultObjectId || stage.topologyEntries <= 0 || !Array.isArray(stage.inputObjectIds) || !Number.isInteger(stage.recordCount) || stage.recordCount < 0 || (stage.operation !== 'hole' && stage.inputObjectIds.length === 0) || (stage.operation !== 'hole' && stage.recordCount <= 0)) fail(`${file} contains an incomplete native stage capture.`)
stageEvidence.push({ report: `config/${file}`, stageId: stage.stageId, operation: stage.operation, inputObjectCount: stage.inputObjectIds.length, resultObjectId: stage.resultObjectId, topologyEntries: stage.topologyEntries, recordCount: stage.recordCount })
capturedOperations.add(stage.operation)
operationEvidence[stage.operation] ??= { report: `config/${file}`, mode: 'native-stage', historyRecords: stage.recordCount, relationCount: 0, marker: null }
}
}
const missingOperations = requiredOperations.filter((operation) => !capturedOperations.has(operation))
if (missingOperations.length > 0) fail(`missing native evidence for: ${missingOperations.join(', ')}`)
const history = reports['chrome-native-history-verification.json'].report
if (JSON.stringify(history.nativeCapabilities.operations) !== JSON.stringify(requiredOperations)) fail('the native history operation registry drifted from the locked 19-operation contract.')
console.log(JSON.stringify({ console.log(JSON.stringify({
status: 'freecad-tsn-stage-evidence-pass', status: 'freecad-tsn-stage-evidence-pass',
exactPromotionReady: false, exactPromotionReady: false,
provider: history.nativeCapabilities.providerId, provider: history.nativeCapabilities.providerId,
requiredBuilderOperations: requiredOperations.length, requiredBuilderOperations: requiredOperations.length,
capturedStageOperations: capturedOperations, capturedStageOperations: [...capturedOperations].sort(),
capturedBuilderOperationCount: capturedOperations.length, capturedBuilderOperationCount: capturedOperations.size,
missingBuilderOperations: missingOperations, missingBuilderOperations: missingOperations,
pad: { records: pad.execution.recordCount, crossKindRecords: pad.execution.crossKindRecords, relations: pad.execution.relations }, reports: Object.fromEntries(Object.entries(operationEvidence).sort(([a], [b]) => a.localeCompare(b))),
pocket: { records: pocket.history.recordCount, stages: pocket.history.stages.length, twoSidedStages: pocket.twoSidedHistory.stages.length, relations: pocket.history.relations }, nativeStages: stageEvidence,
blockers: [`${missingOperations.length} of ${requiredOperations.length} builder operations still need explicit native stage captures`, 'FreeCAD private MappedNameRef/StringHasher token evidence remains unavailable for the browser provider'], blockers: ['FreeCAD private MappedNameRef/StringHasher token evidence remains unavailable for the browser provider'],
}, null, 2)) }, null, 2))

View File

@@ -10,6 +10,12 @@ import Sketcher
FREECAD_COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d" FREECAD_COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d"
BUILDER_STAGE_TYPES = {
"Part::Fuse", "Part::Cut", "Part::Common", "Part::Extrusion", "Part::Revolution", "Part::Loft", "Part::Sweep", "Part::Fillet", "Part::Chamfer",
"PartDesign::Pad", "PartDesign::Pocket", "PartDesign::Revolution", "PartDesign::Groove", "PartDesign::AdditiveLoft", "PartDesign::SubtractiveLoft",
"PartDesign::AdditivePipe", "PartDesign::SubtractivePipe", "PartDesign::Fillet", "PartDesign::Chamfer", "PartDesign::Draft", "PartDesign::Thickness",
"PartDesign::Mirrored", "PartDesign::MultiTransform", "PartDesign::LinearPattern", "PartDesign::PolarPattern", "PartDesign::Hole",
}
def version_text(): def version_text():
@@ -97,6 +103,7 @@ def stage_report(obj):
native_evidence_complete = True native_evidence_complete = True
mapped_name_entries = 0 mapped_name_entries = 0
indexed_name_entries = 0 indexed_name_entries = 0
history_entry_count = 0
for kind, count in (("Face", len(shape.Faces)), ("Edge", len(shape.Edges)), ("Vertex", len(shape.Vertexes))): for kind, count in (("Face", len(shape.Faces)), ("Edge", len(shape.Edges)), ("Vertex", len(shape.Vertexes))):
for index in range(1, count + 1): for index in range(1, count + 1):
name = "%s%d" % (kind, index) name = "%s%d" % (kind, index)
@@ -109,6 +116,7 @@ def stage_report(obj):
except Exception: except Exception:
indexed, indexed_ids = "", [] indexed, indexed_ids = "", []
history = history_entry(obj, name) history = history_entry(obj, name)
history_entry_count += len(history)
if not mapped and not indexed: if not mapped and not indexed:
native_evidence_complete = False native_evidence_complete = False
if mapped: if mapped:
@@ -133,8 +141,14 @@ def stage_report(obj):
"source": "FreeCAD 1.1.1 runtime API", "source": "FreeCAD 1.1.1 runtime API",
"mappedNameApiEntries": mapped_name_entries, "mappedNameApiEntries": mapped_name_entries,
"indexedNameApiEntries": indexed_name_entries, "indexedNameApiEntries": indexed_name_entries,
# FreeCAD exposes MappedName only for derived/builder results. Primitive
# and support stages legitimately expose IndexedName without a private
# token; record that boundary explicitly instead of treating it as a
# failed token capture.
"privateTokenEvidenceRequired": mapped_name_entries > 0,
"privateTokenEvidenceComplete": mapped_name_entries == len(native_mapped_names) and mapped_name_entries > 0, "privateTokenEvidenceComplete": mapped_name_entries == len(native_mapped_names) and mapped_name_entries > 0,
"internalBuilderEvidence": False, "indexedNameOnly": mapped_name_entries == 0 and indexed_name_entries == len(native_mapped_names) and indexed_name_entries > 0,
"internalBuilderEvidence": obj.TypeId in BUILDER_STAGE_TYPES and history_entry_count > 0 and native_evidence_complete,
"reason": None if native_evidence_complete else "One or more subshapes had no mapped or indexed name from the native API.", "reason": None if native_evidence_complete else "One or more subshapes had no mapped or indexed name from the native API.",
} }
return { return {

View File

@@ -0,0 +1,67 @@
import json
import FreeCAD as App
from freecad_mutation_evidence import capture_property_mutation
COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d"
def primitive_case(type_id, properties, property_name, edited_value):
document = App.newDocument("{}MutationOracle".format(type_id.replace("::", "")))
try:
feature = document.addObject(type_id, "Feature")
for name, value in properties.items():
setattr(feature, name, value)
mutation = capture_property_mutation(document, feature, property_name, edited_value)
return mutation
finally:
App.closeDocument(document.Name)
def boolean_case(type_id):
document = App.newDocument("{}MutationOracle".format(type_id.replace("::", "")))
try:
base = document.addObject("Part::Box", "Base")
base.Length, base.Width, base.Height = 10.0, 9.0, 8.0
tool = document.addObject("Part::Cylinder", "Tool")
tool.Radius, tool.Height = 2.0, 12.0
tool.Placement.Base = App.Vector(5.0, 4.5, -2.0)
feature = document.addObject(type_id, "Feature")
feature.Base, feature.Tool = base, tool
return capture_property_mutation(document, feature, "Radius", 2.5, target=tool, property_path="Tool.Radius")
finally:
App.closeDocument(document.Name)
cases = [
primitive_case("Part::Box", {"Length": 10.0, "Width": 8.0, "Height": 6.0}, "Length", 12.0),
primitive_case("Part::Cylinder", {"Radius": 3.0, "Height": 8.0, "Angle": 360.0}, "Radius", 3.5),
primitive_case("Part::Sphere", {"Radius": 4.0}, "Radius", 4.5),
primitive_case("Part::Ellipsoid", {"Radius1": 2.0, "Radius2": 4.0, "Radius3": 0.0, "Angle1": -90.0, "Angle2": 90.0, "Angle3": 360.0}, "Radius1", 2.5),
primitive_case("Part::Cone", {"Radius1": 4.0, "Radius2": 2.0, "Height": 8.0, "Angle": 360.0}, "Height", 10.0),
primitive_case("Part::Torus", {"Radius1": 10.0, "Radius2": 2.0, "Angle1": -180.0, "Angle2": 180.0, "Angle3": 270.0}, "Radius2", 2.5),
primitive_case("Part::Helix", {"Pitch": 3.0, "Height": 6.0, "Radius": 2.0, "Angle": 0.0, "SegmentLength": 0.0}, "Radius", 2.5),
primitive_case("Part::Prism", {"Polygon": 6, "Circumradius": 2.0, "Height": 10.0, "FirstAngle": 10.0, "SecondAngle": -5.0}, "Height", 12.0),
primitive_case("Part::Wedge", {"Xmin": 0.0, "Ymin": 0.0, "Zmin": 0.0, "Z2min": 0.0, "X2min": 0.0, "Xmax": 10.0, "Ymax": 10.0, "Zmax": 10.0, "Z2max": 8.0, "X2max": 8.0}, "X2max", 7.0),
boolean_case("Part::Fuse"),
boolean_case("Part::Cut"),
boolean_case("Part::Common"),
]
report = {
"schemaVersion": 1,
"baselineId": "freecad-1.1.1-core-parameter-mutation-oracle",
"freecadVersion": ".".join(str(value) for value in App.Version()[:3]),
"gitCommit": COMMIT,
"status": "pass" if all(case["status"] == "pass" for case in cases) else "failed",
"summary": {
"families": len(cases),
"passed": sum(1 for case in cases if case["status"] == "pass"),
"shapeChanged": sum(1 for case in cases if case["shapeChanged"]),
"restoredExactly": sum(1 for case in cases if case["restoredExactly"]),
},
"cases": cases,
}
print("FREECAD_CORE_PARAMETER_MUTATION_RESULT=" + json.dumps(report, sort_keys=True, separators=(",", ":")))

View File

@@ -3,6 +3,7 @@ import json
import FreeCAD as App import FreeCAD as App
import Part import Part
from freecad_mutation_evidence import capture_property_mutation
COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d" COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d"
@@ -62,8 +63,10 @@ def success_extrusion():
feature.Base = profile feature.Base = profile
feature.Dir = App.Vector(0, 0, 5) feature.Dir = App.Vector(0, 0, 5)
feature.Solid = True feature.Solid = True
document.recompute() mutation = capture_property_mutation(document, feature, "Dir", App.Vector(0, 0, 7))
return feature_status("part-extrusion-success", feature, "success") result = feature_status("part-extrusion-success", feature, "success")
result["mutation"] = mutation
return result
finally: finally:
App.closeDocument(document.Name) App.closeDocument(document.Name)
@@ -78,8 +81,10 @@ def success_revolution():
feature.Base = App.Vector(0, 0, 0) feature.Base = App.Vector(0, 0, 0)
feature.Angle = 360 feature.Angle = 360
feature.Solid = True feature.Solid = True
document.recompute() mutation = capture_property_mutation(document, feature, "Angle", 270.0)
return feature_status("part-revolution-success", feature, "success") result = feature_status("part-revolution-success", feature, "success")
result["mutation"] = mutation
return result
finally: finally:
App.closeDocument(document.Name) App.closeDocument(document.Name)
@@ -94,8 +99,10 @@ def success_loft():
feature.Solid = True feature.Solid = True
feature.Ruled = False feature.Ruled = False
feature.Closed = False feature.Closed = False
document.recompute() mutation = capture_property_mutation(document, feature, "Ruled", True)
return feature_status("part-loft-success", feature, "success") result = feature_status("part-loft-success", feature, "success")
result["mutation"] = mutation
return result
finally: finally:
App.closeDocument(document.Name) App.closeDocument(document.Name)
@@ -104,14 +111,17 @@ def success_sweep():
document = App.newDocument("PartSweepSuccessOracle") document = App.newDocument("PartSweepSuccessOracle")
try: try:
section = add_shape(document, "Section", rectangle_wire(-1, -1, 1, 1, 0)) section = add_shape(document, "Section", rectangle_wire(-1, -1, 1, 1, 0))
edited_section = add_shape(document, "EditedSection", rectangle_wire(-0.75, -0.75, 0.75, 0.75, 0))
spine = add_shape(document, "Spine", Part.makePolygon([App.Vector(0, 0, 0), App.Vector(0, 0, 6)])) spine = add_shape(document, "Spine", Part.makePolygon([App.Vector(0, 0, 0), App.Vector(0, 0, 6)]))
feature = document.addObject("Part::Sweep", "Sweep") feature = document.addObject("Part::Sweep", "Sweep")
feature.Sections = [section] feature.Sections = [section]
feature.Spine = spine feature.Spine = spine
feature.Solid = True feature.Solid = True
feature.Frenet = False feature.Frenet = False
document.recompute() mutation = capture_property_mutation(document, feature, "Sections", [edited_section])
return feature_status("part-sweep-success", feature, "success") result = feature_status("part-sweep-success", feature, "success")
result["mutation"] = mutation
return result
finally: finally:
App.closeDocument(document.Name) App.closeDocument(document.Name)
@@ -123,8 +133,10 @@ def success_fillet():
feature = document.addObject("Part::Fillet", "Fillet") feature = document.addObject("Part::Fillet", "Fillet")
feature.Base = base feature.Base = base
feature.Edges = [(1, 1, 1)] feature.Edges = [(1, 1, 1)]
document.recompute() mutation = capture_property_mutation(document, feature, "Edges", [(1, 0.5, 0.5)])
return feature_status("part-fillet-success", feature, "success") result = feature_status("part-fillet-success", feature, "success")
result["mutation"] = mutation
return result
finally: finally:
App.closeDocument(document.Name) App.closeDocument(document.Name)
@@ -136,8 +148,10 @@ def success_chamfer():
feature = document.addObject("Part::Chamfer", "Chamfer") feature = document.addObject("Part::Chamfer", "Chamfer")
feature.Base = base feature.Base = base
feature.Edges = [(1, 1, 1)] feature.Edges = [(1, 1, 1)]
document.recompute() mutation = capture_property_mutation(document, feature, "Edges", [(1, 0.5, 0.5)])
return feature_status("part-chamfer-success", feature, "success") result = feature_status("part-chamfer-success", feature, "success")
result["mutation"] = mutation
return result
finally: finally:
App.closeDocument(document.Name) App.closeDocument(document.Name)

View File

@@ -4,6 +4,8 @@ import FreeCAD as App
import Part import Part
import Sketcher import Sketcher
from freecad_mutation_evidence import capture_property_mutation
def version_text(): def version_text():
return ".".join(str(value) for value in App.Version()[:3]) return ".".join(str(value) for value in App.Version()[:3])
@@ -46,7 +48,9 @@ def tapered_pad():
pad.Type = "Length" pad.Type = "Length"
pad.Length = 5.0 pad.Length = 5.0
pad.TaperAngle = 5.0 pad.TaperAngle = 5.0
mutation = capture_property_mutation(document, pad, "Length", 6.0)
result = shape_result("pad-tapered", document, pad) result = shape_result("pad-tapered", document, pad)
result["mutation"] = mutation
App.closeDocument(document.Name) App.closeDocument(document.Name)
return result return result
@@ -69,7 +73,9 @@ def tapered_pocket():
pocket.Type = "Length" pocket.Type = "Length"
pocket.Length = 10.0 pocket.Length = 10.0
pocket.TaperAngle = 2.0 pocket.TaperAngle = 2.0
mutation = capture_property_mutation(document, pocket, "Length", 8.0)
result = shape_result("pocket-tapered", document, pocket) result = shape_result("pocket-tapered", document, pocket)
result["mutation"] = mutation
App.closeDocument(document.Name) App.closeDocument(document.Name)
return result return result

View File

@@ -2,6 +2,8 @@ import json
import FreeCAD as App import FreeCAD as App
from freecad_mutation_evidence import capture_property_mutation
def version_text(): def version_text():
return ".".join(str(value) for value in App.Version()[:3]) return ".".join(str(value) for value in App.Version()[:3])
@@ -38,7 +40,9 @@ def selected_fillet():
feature = body.newObject("PartDesign::Fillet", "Fillet") feature = body.newObject("PartDesign::Fillet", "Fillet")
feature.Base = (box, ["Edge1"]) feature.Base = (box, ["Edge1"])
feature.Radius = 1 feature.Radius = 1
mutation = capture_property_mutation(document, feature, "Radius", 0.5)
result = shape_result("fillet-selected-edge", document, feature) result = shape_result("fillet-selected-edge", document, feature)
result["mutation"] = mutation
result["selection"] = ["Edge1"] result["selection"] = ["Edge1"]
App.closeDocument(document.Name) App.closeDocument(document.Name)
return result return result
@@ -49,7 +53,9 @@ def selected_chamfer():
feature = body.newObject("PartDesign::Chamfer", "Chamfer") feature = body.newObject("PartDesign::Chamfer", "Chamfer")
feature.Base = (box, ["Edge1"]) feature.Base = (box, ["Edge1"])
feature.Size = 1 feature.Size = 1
mutation = capture_property_mutation(document, feature, "Size", 0.5)
result = shape_result("chamfer-selected-edge", document, feature) result = shape_result("chamfer-selected-edge", document, feature)
result["mutation"] = mutation
result["selection"] = ["Edge1"] result["selection"] = ["Edge1"]
App.closeDocument(document.Name) App.closeDocument(document.Name)
return result return result
@@ -76,7 +82,9 @@ def selected_draft():
document.recompute() document.recompute()
if "Invalid" in feature.State: if "Invalid" in feature.State:
feature.Reversed = False feature.Reversed = False
mutation = capture_property_mutation(document, feature, "Angle", 30.0)
result = shape_result("draft-selected-face", document, feature) result = shape_result("draft-selected-face", document, feature)
result["mutation"] = mutation
result["selection"] = ["Face{}".format(top_face + 1)] result["selection"] = ["Face{}".format(top_face + 1)]
result["reversed"] = bool(feature.Reversed) result["reversed"] = bool(feature.Reversed)
App.closeDocument(document.Name) App.closeDocument(document.Name)
@@ -91,7 +99,9 @@ def selected_thickness():
feature.Reversed = True feature.Reversed = True
feature.Mode = 0 feature.Mode = 0
feature.Join = 0 feature.Join = 0
mutation = capture_property_mutation(document, feature, "Value", 0.8)
result = shape_result("thickness-selected-face", document, feature) result = shape_result("thickness-selected-face", document, feature)
result["mutation"] = mutation
result["selection"] = ["Face1"] result["selection"] = ["Face1"]
result["reversed"] = bool(feature.Reversed) result["reversed"] = bool(feature.Reversed)
result["elementMapSize"] = int(feature.Shape.ElementMapSize) result["elementMapSize"] = int(feature.Shape.ElementMapSize)

View File

@@ -6,6 +6,8 @@ import Part
import Sketcher import Sketcher
import TestSketcherApp import TestSketcherApp
from freecad_mutation_evidence import capture_property_mutation
def version_text(): def version_text():
return ".".join(str(value) for value in App.Version()[:3]) return ".".join(str(value) for value in App.Version()[:3])
@@ -38,7 +40,9 @@ def additive_loft():
feature = body.newObject("PartDesign::AdditiveLoft", "AdditiveLoft") feature = body.newObject("PartDesign::AdditiveLoft", "AdditiveLoft")
feature.Profile = profile feature.Profile = profile
feature.Sections = [section] feature.Sections = [section]
mutation = capture_property_mutation(document, feature, "Ruled", True)
result = shape_result("additive-loft", document, feature) result = shape_result("additive-loft", document, feature)
result["mutation"] = mutation
App.closeDocument(document.Name) App.closeDocument(document.Name)
return result return result
@@ -59,10 +63,17 @@ def subtractive_loft():
section.AttachmentSupport = (document.XZ_Plane, [""]) section.AttachmentSupport = (document.XZ_Plane, [""])
document.recompute() document.recompute()
TestSketcherApp.CreateRectangleSketch(section, (0, 1), (1, 1)) TestSketcherApp.CreateRectangleSketch(section, (0, 1), (1, 1))
edited_section = body.newObject("Sketcher::SketchObject", "EditedSection")
edited_section.MapMode = "FlatFace"
edited_section.AttachmentSupport = (document.XZ_Plane, [""])
document.recompute()
TestSketcherApp.CreateRectangleSketch(edited_section, (0, 1), (0.75, 0.75))
feature = body.newObject("PartDesign::SubtractiveLoft", "SubtractiveLoft") feature = body.newObject("PartDesign::SubtractiveLoft", "SubtractiveLoft")
feature.Profile = profile feature.Profile = profile
feature.Sections = [section] feature.Sections = [section]
mutation = capture_property_mutation(document, feature, "Sections", [edited_section])
result = shape_result("subtractive-loft", document, feature) result = shape_result("subtractive-loft", document, feature)
result["mutation"] = mutation
App.closeDocument(document.Name) App.closeDocument(document.Name)
return result return result
@@ -70,6 +81,8 @@ def subtractive_loft():
def pipe_sketches(document, body): def pipe_sketches(document, body):
profile = body.newObject("Sketcher::SketchObject", "Profile") profile = body.newObject("Sketcher::SketchObject", "Profile")
TestSketcherApp.CreateCircleSketch(profile, (0, 0), 1) TestSketcherApp.CreateCircleSketch(profile, (0, 0), 1)
edited_profile = body.newObject("Sketcher::SketchObject", "EditedProfile")
TestSketcherApp.CreateCircleSketch(edited_profile, (0, 0), 0.75)
spine = body.newObject("Sketcher::SketchObject", "Spine") spine = body.newObject("Sketcher::SketchObject", "Spine")
spine.MapMode = "FlatFace" spine.MapMode = "FlatFace"
spine.AttachmentSupport = (document.XZ_Plane, [""]) spine.AttachmentSupport = (document.XZ_Plane, [""])
@@ -78,17 +91,19 @@ def pipe_sketches(document, body):
spine.addConstraint(Sketcher.Constraint("Coincident", 0, 1, -1, 1)) spine.addConstraint(Sketcher.Constraint("Coincident", 0, 1, -1, 1))
spine.addConstraint(Sketcher.Constraint("PointOnObject", 0, 2, -2)) spine.addConstraint(Sketcher.Constraint("PointOnObject", 0, 2, -2))
spine.addConstraint(Sketcher.Constraint("DistanceY", 0, 1, 0, 2, 1)) spine.addConstraint(Sketcher.Constraint("DistanceY", 0, 1, 0, 2, 1))
return profile, spine return profile, edited_profile, spine
def additive_pipe(): def additive_pipe():
document = App.newDocument("AdditivePipeOracle") document = App.newDocument("AdditivePipeOracle")
body = document.addObject("PartDesign::Body", "Body") body = document.addObject("PartDesign::Body", "Body")
profile, spine = pipe_sketches(document, body) profile, edited_profile, spine = pipe_sketches(document, body)
feature = body.newObject("PartDesign::AdditivePipe", "AdditivePipe") feature = body.newObject("PartDesign::AdditivePipe", "AdditivePipe")
feature.Profile = profile feature.Profile = profile
feature.Spine = spine feature.Spine = spine
mutation = capture_property_mutation(document, feature, "Profile", edited_profile)
result = shape_result("additive-pipe", document, feature) result = shape_result("additive-pipe", document, feature)
result["mutation"] = mutation
result["transition"] = str(feature.Transition) result["transition"] = str(feature.Transition)
App.closeDocument(document.Name) App.closeDocument(document.Name)
return result return result
@@ -97,7 +112,7 @@ def additive_pipe():
def subtractive_pipe(): def subtractive_pipe():
document = App.newDocument("SubtractivePipeOracle") document = App.newDocument("SubtractivePipeOracle")
body = document.addObject("PartDesign::Body", "Body") body = document.addObject("PartDesign::Body", "Body")
profile, spine = pipe_sketches(document, body) profile, edited_profile, spine = pipe_sketches(document, body)
pad_sketch = body.newObject("Sketcher::SketchObject", "PadSketch") pad_sketch = body.newObject("Sketcher::SketchObject", "PadSketch")
TestSketcherApp.CreateRectangleSketch(pad_sketch, (-5, -5), (10, 10)) TestSketcherApp.CreateRectangleSketch(pad_sketch, (-5, -5), (10, 10))
pad = body.newObject("PartDesign::Pad", "Pad") pad = body.newObject("PartDesign::Pad", "Pad")
@@ -107,7 +122,9 @@ def subtractive_pipe():
feature = body.newObject("PartDesign::SubtractivePipe", "SubtractivePipe") feature = body.newObject("PartDesign::SubtractivePipe", "SubtractivePipe")
feature.Profile = profile feature.Profile = profile
feature.Spine = spine feature.Spine = spine
mutation = capture_property_mutation(document, feature, "Profile", edited_profile)
result = shape_result("subtractive-pipe", document, feature) result = shape_result("subtractive-pipe", document, feature)
result["mutation"] = mutation
result["transition"] = str(feature.Transition) result["transition"] = str(feature.Transition)
App.closeDocument(document.Name) App.closeDocument(document.Name)
return result return result

View File

@@ -4,6 +4,8 @@ import FreeCAD as App
import Part import Part
import Sketcher import Sketcher
from freecad_mutation_evidence import capture_property_mutation
COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d" COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d"
@@ -47,7 +49,8 @@ def run_revolution():
feature.ReferenceAxis = (sketch, ["H_Axis"]) feature.ReferenceAxis = (sketch, ["H_Axis"])
feature.Angle = 360 feature.Angle = 360
document.recompute() document.recompute()
return {"id": "partdesign-revolution", "operation": "revolution", **status(feature)} mutation = capture_property_mutation(document, feature, "Angle", 270.0)
return {"id": "partdesign-revolution", "operation": "revolution", "mutation": mutation, **status(feature)}
except Exception as error: except Exception as error:
return {"id": "partdesign-revolution", "operation": "revolution", "passed": False, "error": str(error)} return {"id": "partdesign-revolution", "operation": "revolution", "passed": False, "error": str(error)}
finally: finally:
@@ -74,7 +77,8 @@ def run_groove():
feature.ReferenceAxis = (sketch, ["H_Axis"]) feature.ReferenceAxis = (sketch, ["H_Axis"])
feature.Angle = 180 feature.Angle = 180
document.recompute() document.recompute()
return {"id": "partdesign-groove", "operation": "groove", **status(feature)} mutation = capture_property_mutation(document, feature, "Angle", 120.0)
return {"id": "partdesign-groove", "operation": "groove", "mutation": mutation, **status(feature)}
except Exception as error: except Exception as error:
return {"id": "partdesign-groove", "operation": "groove", "passed": False, "error": str(error)} return {"id": "partdesign-groove", "operation": "groove", "passed": False, "error": str(error)}
finally: finally:

View File

@@ -4,6 +4,8 @@ import FreeCAD as App
import Part import Part
import Sketcher import Sketcher
from freecad_mutation_evidence import capture_property_mutation
def version_text(): def version_text():
return ".".join(str(value) for value in App.Version()[:3]) return ".".join(str(value) for value in App.Version()[:3])
@@ -50,7 +52,9 @@ def feature_linear_pattern():
pattern.Direction = (document.X_Axis, [""]) pattern.Direction = (document.X_Axis, [""])
pattern.Occurrences = 3 pattern.Occurrences = 3
pattern.Length = 12 pattern.Length = 12
mutation = capture_property_mutation(document, pattern, "Occurrences", 4)
result = shape_result("linear-feature-list", document, pattern) result = shape_result("linear-feature-list", document, pattern)
result["mutation"] = mutation
result.update({ result.update({
"transformMode": pattern.TransformMode, "transformMode": pattern.TransformMode,
"originals": [item.Name for item in pattern.Originals], "originals": [item.Name for item in pattern.Originals],
@@ -79,7 +83,9 @@ def feature_mirrored():
mirrored.TransformMode = "Features" mirrored.TransformMode = "Features"
mirrored.Originals = [boss] mirrored.Originals = [boss]
mirrored.MirrorPlane = (document.YZ_Plane, [""]) mirrored.MirrorPlane = (document.YZ_Plane, [""])
mutation = capture_property_mutation(document, mirrored, "Refine", False)
result = shape_result("mirrored-feature-list", document, mirrored) result = shape_result("mirrored-feature-list", document, mirrored)
result["mutation"] = mutation
result.update({ result.update({
"transformMode": mirrored.TransformMode, "transformMode": mirrored.TransformMode,
"originals": [item.Name for item in mirrored.Originals], "originals": [item.Name for item in mirrored.Originals],
@@ -105,7 +111,9 @@ def feature_polar_pattern():
pattern.Occurrences = 4 pattern.Occurrences = 4
pattern.Refine = True pattern.Refine = True
body.addObject(pattern) body.addObject(pattern)
mutation = capture_property_mutation(document, pattern, "Occurrences", 3)
result = shape_result("polar-feature-list", document, pattern) result = shape_result("polar-feature-list", document, pattern)
result["mutation"] = mutation
result.update({ result.update({
"transformMode": pattern.TransformMode, "transformMode": pattern.TransformMode,
"originals": [item.Name for item in pattern.Originals], "originals": [item.Name for item in pattern.Originals],
@@ -149,7 +157,9 @@ def feature_multi_transform():
polar.Occurrences = 4 polar.Occurrences = 4
body.addObject(polar) body.addObject(polar)
multi.Transformations = [mirrored, linear, polar] multi.Transformations = [mirrored, linear, polar]
mutation = capture_property_mutation(document, multi, "Length", 15.0, target=linear, property_path="Transformations[1].Length")
result = shape_result("multi-transform-feature-list", document, multi) result = shape_result("multi-transform-feature-list", document, multi)
result["mutation"] = mutation
result.update({ result.update({
"transformMode": multi.TransformMode, "transformMode": multi.TransformMode,
"originals": [item.Name for item in multi.Originals], "originals": [item.Name for item in multi.Originals],
@@ -255,7 +265,9 @@ def iso_hole(modeled):
hole.ThreadDirection = "Left" if modeled else "Right" hole.ThreadDirection = "Left" if modeled else "Right"
hole.ThreadDepthType = "Dimension" hole.ThreadDepthType = "Dimension"
hole.ThreadDepth = 6 hole.ThreadDepth = 6
mutation = capture_property_mutation(document, hole, "Diameter", 6.5)
result = shape_result(case_id, document, hole) result = shape_result(case_id, document, hole)
result["mutation"] = mutation
result.update({ result.update({
"threaded": bool(hole.Threaded), "threaded": bool(hole.Threaded),
"modeled": bool(hole.ModelThread), "modeled": bool(hole.ModelThread),

View File

@@ -0,0 +1,86 @@
import hashlib
def _json_value(value):
if value is None or isinstance(value, (bool, int, float, str)):
return value
if hasattr(value, "Value") and hasattr(value, "Unit"):
return {"value": float(value.Value), "unit": str(value.Unit)}
if hasattr(value, "x") and hasattr(value, "y") and hasattr(value, "z"):
return [float(value.x), float(value.y), float(value.z)]
if hasattr(value, "Name") and hasattr(value, "TypeId"):
return {"name": str(value.Name), "typeId": str(value.TypeId)}
if isinstance(value, (list, tuple)):
return [_json_value(item) for item in value]
return str(value)
def _shape_snapshot(feature):
shape = feature.Shape
if shape.isNull():
raise RuntimeError("{} produced a null Shape".format(feature.Name))
if not shape.isValid():
raise RuntimeError("{} produced an invalid Shape".format(feature.Name))
brep = shape.exportBrepToString().encode("utf-8")
bounds = shape.BoundBox
return {
"brepSha256": hashlib.sha256(brep).hexdigest(),
"shapeType": str(shape.ShapeType),
"valid": True,
"solids": len(shape.Solids),
"faces": len(shape.Faces),
"edges": len(shape.Edges),
"vertices": len(shape.Vertexes),
"volume": float(shape.Volume),
"area": float(shape.Area),
"bounds": {
"min": [float(bounds.XMin), float(bounds.YMin), float(bounds.ZMin)],
"max": [float(bounds.XMax), float(bounds.YMax), float(bounds.ZMax)],
},
}
def capture_property_mutation(document, feature, property_name, edited_value, target=None, property_path=None):
target = target or feature
original_value = getattr(target, property_name)
before_parameter = _json_value(original_value)
document.recompute()
before_shape_object = feature.Shape.copy()
before_shape = _shape_snapshot(feature)
try:
setattr(target, property_name, edited_value)
document.recompute()
edited_parameter = _json_value(getattr(target, property_name))
if before_parameter == edited_parameter:
raise RuntimeError("{} mutation did not change {}".format(feature.TypeId, property_path or property_name))
edited_shape = _shape_snapshot(feature)
finally:
setattr(target, property_name, original_value)
document.recompute()
restored_shape_object = feature.Shape
restored_shape = _shape_snapshot(feature)
restored_parameter = _json_value(getattr(target, property_name))
if restored_parameter != before_parameter:
raise RuntimeError("{} did not restore {}".format(feature.TypeId, property_path or property_name))
restored_equivalent = bool(restored_shape_object.isEqual(before_shape_object))
geometry_equivalent = (restored_shape["shapeType"], restored_shape["solids"], restored_shape["faces"], restored_shape["edges"], restored_shape["vertices"], round(restored_shape["volume"], 7), round(restored_shape["area"], 7)) == (before_shape["shapeType"], before_shape["solids"], before_shape["faces"], before_shape["edges"], before_shape["vertices"], round(before_shape["volume"], 7), round(before_shape["area"], 7))
if not restored_equivalent and not geometry_equivalent:
raise RuntimeError("{} did not restore the original geometry".format(feature.TypeId))
return {
"schemaVersion": 1,
"familyId": str(feature.TypeId),
"featureName": str(feature.Name),
"propertyPath": property_path or property_name,
"status": "pass",
"before": {"parameterSnapshot": before_parameter, "shape": before_shape},
"edited": {"parameterSnapshot": edited_parameter, "shape": edited_shape},
"restored": {"parameterSnapshot": restored_parameter, "shape": restored_shape},
"parameterChanged": True,
"shapeChanged": edited_shape["brepSha256"] != before_shape["brepSha256"],
"restoredExactly": restored_equivalent or geometry_equivalent,
"brepStable": restored_shape["brepSha256"] == before_shape["brepSha256"],
"restoredGeometrically": True,
}

View File

@@ -49,6 +49,14 @@ const addVariant = (familyId, snapshot, source) => {
bucket.parameterVariants.set(stable(snapshot), { snapshot, source }) bucket.parameterVariants.set(stable(snapshot), { snapshot, source })
bucket.sources.add(source) bucket.sources.add(source)
} }
const addMutation = (mutation, source) => {
if (!mutation || typeof mutation !== 'object') return
const familyId = familyByType.get(mutation.familyId)
if (!familyId || mutation.status !== 'pass' || mutation.parameterChanged !== true || mutation.shapeChanged !== true || mutation.restoredExactly !== true) return
addRecord(familyId, 'editRecovery', { transaction: `native-parameter:${mutation.featureName}:${mutation.propertyPath}`, ...mutation }, source)
addVariant(familyId, mutation.before.parameterSnapshot, `${source}#before`)
addVariant(familyId, mutation.edited.parameterSnapshot, `${source}#edited`)
}
const manifestCases = async (manifestPath, entryKey, directory) => { const manifestCases = async (manifestPath, entryKey, directory) => {
const manifest = await load(manifestPath) const manifest = await load(manifestPath)
const base = resolve(root, directory) const base = resolve(root, directory)
@@ -81,11 +89,16 @@ const addOracleCases = async (file, successKey, failureKey) => {
for (const key of [successKey, failureKey]) for (const fixture of report[key] || []) { for (const key of [successKey, failureKey]) for (const fixture of report[key] || []) {
const familyId = familyByType.get(fixture.typeId) const familyId = familyByType.get(fixture.typeId)
if (!familyId) continue if (!familyId) continue
if (key === successKey) addRecord(familyId, 'nominal', { id: fixture.id, typeId: fixture.typeId }, source) if (key === successKey) {
addRecord(familyId, 'nominal', { id: fixture.id, typeId: fixture.typeId }, source)
addMutation(fixture.mutation, source)
}
else addRecord(familyId, 'invalid', { id: fixture.id, typeId: fixture.typeId }, source) else addRecord(familyId, 'invalid', { id: fixture.id, typeId: fixture.typeId }, source)
} }
} }
await addOracleCases('config/freecad-part-builders-oracle.json', 'successCases', 'failureCases') await addOracleCases('config/freecad-part-builders-oracle.json', 'successCases', 'failureCases')
const coreMutationOracle = await load('config/freecad-core-parameter-mutation-oracle.json')
for (const mutation of coreMutationOracle.cases || []) addMutation(mutation, 'config/freecad-core-parameter-mutation-oracle.json')
for (const file of ['config/freecad-partdesign-failure-oracle.json', 'config/freecad-partdesign-revolution-groove-oracle.json', 'config/freecad-partdesign-transform-oracle.json']) { for (const file of ['config/freecad-partdesign-failure-oracle.json', 'config/freecad-partdesign-revolution-groove-oracle.json', 'config/freecad-partdesign-transform-oracle.json']) {
const report = await load(file) const report = await load(file)
for (const fixture of report.cases || []) { for (const fixture of report.cases || []) {
@@ -93,18 +106,21 @@ for (const file of ['config/freecad-partdesign-failure-oracle.json', 'config/fre
if (!familyId) continue if (!familyId) continue
if (fixture.passed === true && fixture.status === 'Valid' && fixture.shapeValid !== false && fixture.solids > 0) addRecord(familyId, 'nominal', { id: fixture.id, typeId: fixture.typeId }, file) if (fixture.passed === true && fixture.status === 'Valid' && fixture.shapeValid !== false && fixture.solids > 0) addRecord(familyId, 'nominal', { id: fixture.id, typeId: fixture.typeId }, file)
if (fixture.passed === true && (fixture.observed === 'rejected' || fixture.state?.includes('Invalid'))) addRecord(familyId, 'invalid', { id: fixture.id, typeId: fixture.typeId }, file) if (fixture.passed === true && (fixture.observed === 'rejected' || fixture.state?.includes('Invalid'))) addRecord(familyId, 'invalid', { id: fixture.id, typeId: fixture.typeId }, file)
addMutation(fixture.mutation, file)
} }
} }
const baseOracle = await load('config/freecad-partdesign-base-oracle.json') const baseOracle = await load('config/freecad-partdesign-base-oracle.json')
for (const fixture of baseOracle.cases || []) { for (const fixture of baseOracle.cases || []) {
const familyId = fixture.id.startsWith('pad-') ? 'PartDesign::Pad' : fixture.id.startsWith('pocket-') ? 'PartDesign::Pocket' : undefined const familyId = fixture.id.startsWith('pad-') ? 'PartDesign::Pad' : fixture.id.startsWith('pocket-') ? 'PartDesign::Pocket' : undefined
if (familyId && fixture.passed === true && fixture.status === 'Valid' && fixture.solids > 0) addRecord(familyId, 'nominal', { id: fixture.id, typeId: familyId, outcome: { volume: fixture.volume, faces: fixture.faces } }, 'config/freecad-partdesign-base-oracle.json') if (familyId && fixture.passed === true && fixture.status === 'Valid' && fixture.solids > 0) addRecord(familyId, 'nominal', { id: fixture.id, typeId: familyId, outcome: { volume: fixture.volume, faces: fixture.faces } }, 'config/freecad-partdesign-base-oracle.json')
if (familyId) addMutation(fixture.mutation, 'config/freecad-partdesign-base-oracle.json')
} }
for (const file of ['config/freecad-partdesign-loft-oracle.json', 'config/freecad-partdesign-dressup-oracle.json']) { for (const file of ['config/freecad-partdesign-loft-oracle.json', 'config/freecad-partdesign-dressup-oracle.json']) {
const report = await load(file) const report = await load(file)
for (const fixture of report.cases || []) { for (const fixture of report.cases || []) {
const familyId = familyByType.get(fixture.typeId) const familyId = familyByType.get(fixture.typeId)
if (familyId && fixture.passed === true && fixture.status === 'Valid' && fixture.solids > 0) addRecord(familyId, 'nominal', { id: fixture.id, typeId: fixture.typeId, outcome: { volume: fixture.volume, faces: fixture.faces } }, file) if (familyId && fixture.passed === true && fixture.status === 'Valid' && fixture.solids > 0) addRecord(familyId, 'nominal', { id: fixture.id, typeId: fixture.typeId, outcome: { volume: fixture.volume, faces: fixture.faces } }, file)
addMutation(fixture.mutation, file)
} }
} }

View File

@@ -0,0 +1,29 @@
import { spawnSync } from 'node:child_process'
import { existsSync } from 'node:fs'
import { writeFile } from 'node:fs/promises'
import { resolve } from 'node:path'
const root = resolve(new URL('..', import.meta.url).pathname)
const executable = process.env.FREECAD_CMD || resolve(root, '.cache/freecad/install-desktop/bin/FreeCADCmd')
if (!existsSync(executable)) throw new Error(`FreeCAD core parameter mutation executable is missing: ${executable}`)
const sysroot = resolve(root, '.cache/freecad/sysroot')
const execution = spawnSync(executable, ['--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), resolve(root, 'scripts/freecad-core-parameter-mutation-oracle.py')], {
cwd: root,
encoding: 'utf8',
timeout: 180_000,
maxBuffer: 20 * 1024 * 1024,
env: {
...process.env,
PYTHONPATH: `${resolve(sysroot, 'usr/lib/python3/dist-packages')}${process.env.PYTHONPATH ? `:${process.env.PYTHONPATH}` : ''}`,
LD_LIBRARY_PATH: `${resolve(sysroot, 'usr/lib/x86_64-linux-gnu')}${process.env.LD_LIBRARY_PATH ? `:${process.env.LD_LIBRARY_PATH}` : ''}`,
MATPLOTLIBRC: resolve(sysroot, 'usr/share/matplotlib/mpl-data/matplotlibrc'),
MPLBACKEND: 'Agg',
},
})
const output = `${execution.stdout || ''}\n${execution.stderr || ''}`
const marker = 'FREECAD_CORE_PARAMETER_MUTATION_RESULT='
const line = output.split(/\r?\n/).find((candidate) => candidate.includes(marker))
if (execution.error || execution.status !== 0 || !line) throw new Error(`FreeCAD core parameter mutation oracle failed with status ${execution.status}: ${execution.error?.message || output.trim()}`)
const report = JSON.parse(line.slice(line.indexOf(marker) + marker.length))
await writeFile(resolve(root, 'config/freecad-core-parameter-mutation-oracle.json'), `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify(report, null, 2))

View File

@@ -258,6 +258,56 @@ try {
if (!shape || !primitiveResaved.shapeResources.some((resource) => resource.path === shape.path && resource.status === 'available')) fail(`FreeCAD resave did not produce an available ${objectName}.Shape resource.`) if (!shape || !primitiveResaved.shapeResources.some((resource) => resource.path === shape.path && resource.status === 'available')) fail(`FreeCAD resave did not produce an available ${objectName}.Shape resource.`)
} }
const throughHoleDocument: DocumentSnapshot = {
id: 'fcstd-native-part-cut-through-hole',
label: 'FCStd native Part Cut through hole',
version: 1,
dirty: false,
readOnly: false,
units: 'mm',
tree: ['Base', 'HoleTool', 'CutThroughHole'].map((id) => ({ id, label: id, type: 'feature', state: 'up-to-date' })),
objects: [
{ id: 'Base', typeId: 'Part::Box', properties: [
{ name: 'Length', label: 'Length', group: 'Box', scope: 'data', type: 'App::PropertyLength', value: 20 },
{ name: 'Width', label: 'Width', group: 'Box', scope: 'data', type: 'App::PropertyLength', value: 20 },
{ name: 'Height', label: 'Height', group: 'Box', scope: 'data', type: 'App::PropertyLength', value: 10 },
] },
{ id: 'HoleTool', typeId: 'Part::Cylinder', properties: [
{ name: 'Radius', label: 'Radius', group: 'Cylinder', scope: 'data', type: 'App::PropertyLength', value: 3 },
{ name: 'Height', label: 'Height', group: 'Cylinder', scope: 'data', type: 'App::PropertyLength', value: 12 },
{ name: 'Angle', label: 'Angle', group: 'Cylinder', scope: 'data', type: 'App::PropertyAngle', value: 360 },
{ name: 'Placement', label: 'Placement', group: 'Base', scope: 'data', type: 'App::PropertyPlacement', value: { position: { x: 10, y: 10, z: -1 }, rotation: { axis: { x: 0, y: 0, z: 1 }, angle: 0 } } },
] },
{ id: 'CutThroughHole', typeId: 'Part::Cut', properties: [
{ name: 'Base', label: 'Base', group: 'Boolean', scope: 'data', type: 'App::PropertyLink', value: 'Base' },
{ name: 'Tool', label: 'Tool', group: 'Boolean', scope: 'data', type: 'App::PropertyLink', value: 'HoleTool' },
{ name: 'Refine', label: 'Refine shape', group: 'Boolean', scope: 'data', type: 'App::PropertyBool', value: false },
] },
],
dependencies: [
{ sourceId: 'CutThroughHole', targetId: 'Base', relation: 'link', propertyName: 'Base' },
{ sourceId: 'CutThroughHole', targetId: 'HoleTool', relation: 'link', propertyName: 'Tool' },
],
recompute: { generation: 0, status: 'idle', objectStates: { Base: 'up-to-date', HoleTool: 'up-to-date', CutThroughHole: 'up-to-date' }, dirtyObjects: [], order: [], errors: [] },
}
const throughHolePath = join(temporaryDirectory, 'native-part-cut-through-hole.fcstd')
const throughHoleResavedPath = join(temporaryDirectory, 'native-part-cut-through-hole-resaved.fcstd')
await writeFile(throughHolePath, serializeFcstdMetadataArchive(throughHoleDocument))
const throughHoleResult = runFreecadProbe(throughHolePath, 'CutThroughHole', {
FREECAD_FCSTD_RECOMPUTE: '1',
FREECAD_FCSTD_PROPERTIES: 'Base,Tool,Refine',
FREECAD_FCSTD_RESAVE_PATH: throughHoleResavedPath,
})
const throughHoleExpectedVolume = 4000 - 90 * Math.PI
if (throughHoleResult.freecadVersion !== '1.1.1' || throughHoleResult.shapeNull !== false || throughHoleResult.shapeValid !== true || throughHoleResult.shapeError !== null || throughHoleResult.solidCount !== 1 || throughHoleResult.faceCount !== 7 || !near(throughHoleResult.volume, throughHoleExpectedVolume, 1e-8) || !throughHoleResult.boundingBox || JSON.stringify(throughHoleResult.boundingBox.min) !== '[0,0,0]' || JSON.stringify(throughHoleResult.boundingBox.max) !== '[20,20,10]') fail(`unexpected native Part::Cut through-hole result: ${JSON.stringify(throughHoleResult)}`)
if (throughHoleResult.properties.Base !== 'Base' || throughHoleResult.properties.Tool !== 'HoleTool' || throughHoleResult.properties.Refine !== false || Object.keys(throughHoleResult.properties).length !== 3) fail(`native Part::Cut through-hole links did not restore: ${JSON.stringify(throughHoleResult.properties)}`)
const throughHoleResaved = inspectFcstdArchive(new Uint8Array(await readFile(throughHoleResavedPath)))
const throughHoleObject = throughHoleResaved.objects.find((object) => object.name === 'CutThroughHole') ?? fail('FreeCAD resave is missing CutThroughHole.')
const throughHoleShape = throughHoleObject.properties.find((property) => property.name === 'Shape')?.shapeResource
const throughHolePlacement = throughHoleResaved.objects.find((object) => object.name === 'HoleTool')?.properties.find((property) => property.name === 'Placement')
if (!throughHoleShape || !throughHoleResaved.shapeResources.some((resource) => resource.path === throughHoleShape.path && resource.status === 'available')) fail('FreeCAD resave did not produce the CutThroughHole.Shape resource.')
if (!throughHolePlacement || JSON.stringify(decodeFcstdPropertyValue(throughHolePlacement).value) !== '{"position":{"x":10,"y":10,"z":-1},"rotation":{"axis":{"x":0,"y":0,"z":1},"angle":0}}') fail('Web inspector did not recover the through-hole tool placement.')
const sphereTrimPath = join(temporaryDirectory, 'native-sphere-trim.fcstd') const sphereTrimPath = join(temporaryDirectory, 'native-sphere-trim.fcstd')
const sphereTrimResavedPath = join(temporaryDirectory, 'native-sphere-trim-resaved.fcstd') const sphereTrimResavedPath = join(temporaryDirectory, 'native-sphere-trim-resaved.fcstd')
const sphereTrimProperties = { Radius: 5, Angle1: -45, Angle2: 45, Angle3: 120 } const sphereTrimProperties = { Radius: 5, Angle1: -45, Angle2: 45, Angle3: 120 }
@@ -658,6 +708,18 @@ try {
resavedShapeAvailable: primitiveResaved.shapeResources.some((resource) => resource.path === `${oracleCase.objectName}.Shape.brp` && resource.status === 'available'), resavedShapeAvailable: primitiveResaved.shapeResources.some((resource) => resource.path === `${oracleCase.objectName}.Shape.brp` && resource.status === 'available'),
}) })
}), }),
scenario('part-cut-through-hole', 'web-freecad-web', ['objectTree', 'properties', 'shape', 'resources'], {
typeId: throughHoleObject.typeId,
solidCount: throughHoleResult.solidCount,
faceCount: throughHoleResult.faceCount,
volume: throughHoleResult.volume,
boundingBox: throughHoleResult.boundingBox,
propertyCount: Object.keys(throughHoleResult.properties).length,
properties: throughHoleResult.properties,
toolPlacement: decodeFcstdPropertyValue(throughHolePlacement).value,
refine: throughHoleResult.properties.Refine,
resavedShapeAvailable: true,
}),
scenario('part-sphere-trim', 'web-freecad-web', ['properties', 'shape', 'resources'], { scenario('part-sphere-trim', 'web-freecad-web', ['properties', 'shape', 'resources'], {
solidCount: sphereTrimResult.solidCount, solidCount: sphereTrimResult.solidCount,
faceCount: sphereTrimResult.faceCount, faceCount: sphereTrimResult.faceCount,

View File

@@ -22,8 +22,8 @@ const execution = spawnSync(executable, ['--python-path', resolve(sysroot, 'usr/
}) })
const output = `${execution.stdout || ''}\n${execution.stderr || ''}` const output = `${execution.stdout || ''}\n${execution.stderr || ''}`
const marker = 'FREECAD_PART_BUILDERS_ORACLE_RESULT=' const marker = 'FREECAD_PART_BUILDERS_ORACLE_RESULT='
const line = output.split(/\r?\n/).find((candidate) => candidate.startsWith(marker)) const line = output.split(/\r?\n/).find((candidate) => candidate.includes(marker))
if (execution.error || execution.status !== 0 || !line) throw new Error(`FreeCAD Part builders oracle failed with status ${execution.status}: ${execution.error?.message || output.trim()}`) if (execution.error || execution.status !== 0 || !line) throw new Error(`FreeCAD Part builders oracle failed with status ${execution.status}: ${execution.error?.message || output.trim()}`)
const report = JSON.parse(line.slice(marker.length)) const report = JSON.parse(line.slice(line.indexOf(marker) + marker.length))
await writeFile(resolve(root, 'config/freecad-part-builders-oracle.json'), `${JSON.stringify(report, null, 2)}\n`) await writeFile(resolve(root, 'config/freecad-part-builders-oracle.json'), `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify(report, null, 2)) console.log(JSON.stringify(report, null, 2))

View File

@@ -24,8 +24,8 @@ const execution = spawnSync(executable, ['--python-path', resolve(sysroot, 'usr/
}) })
const output = `${execution.stdout || ''}\n${execution.stderr || ''}` const output = `${execution.stdout || ''}\n${execution.stderr || ''}`
const marker = 'FREECAD_PARTDESIGN_BASE_RESULT=' const marker = 'FREECAD_PARTDESIGN_BASE_RESULT='
const line = output.split(/\r?\n/).find((candidate) => candidate.startsWith(marker)) const line = output.split(/\r?\n/).find((candidate) => candidate.includes(marker))
if (execution.error || execution.status !== 0 || !line) throw new Error(`FreeCAD PartDesign base oracle failed with status ${execution.status}: ${execution.error?.message || output.trim()}`) if (execution.error || execution.status !== 0 || !line) throw new Error(`FreeCAD PartDesign base oracle failed with status ${execution.status}: ${execution.error?.message || output.trim()}`)
const report = JSON.parse(line.slice(marker.length)) const report = JSON.parse(line.slice(line.indexOf(marker) + marker.length))
await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`) await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify(report, null, 2)) console.log(JSON.stringify(report, null, 2))

View File

@@ -22,8 +22,8 @@ const execution = spawnSync(executable, ['--python-path', resolve(sysroot, 'usr/
}) })
const output = `${execution.stdout || ''}\n${execution.stderr || ''}` const output = `${execution.stdout || ''}\n${execution.stderr || ''}`
const marker = 'FREECAD_PARTDESIGN_DRESSUP_RESULT=' const marker = 'FREECAD_PARTDESIGN_DRESSUP_RESULT='
const line = output.split(/\r?\n/).find((candidate) => candidate.startsWith(marker)) const line = output.split(/\r?\n/).find((candidate) => candidate.includes(marker))
if (execution.error || execution.status !== 0 || !line) throw new Error(`FreeCAD PartDesign dress-up oracle failed with status ${execution.status}: ${execution.error?.message || output.trim()}`) if (execution.error || execution.status !== 0 || !line) throw new Error(`FreeCAD PartDesign dress-up oracle failed with status ${execution.status}: ${execution.error?.message || output.trim()}`)
const report = JSON.parse(line.slice(marker.length)) const report = JSON.parse(line.slice(line.indexOf(marker) + marker.length))
await writeFile(resolve(root, 'config/freecad-partdesign-dressup-oracle.json'), `${JSON.stringify(report, null, 2)}\n`) await writeFile(resolve(root, 'config/freecad-partdesign-dressup-oracle.json'), `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify(report, null, 2)) console.log(JSON.stringify(report, null, 2))

View File

@@ -23,8 +23,8 @@ const execution = spawnSync(executable, ['--python-path', resolve(sysroot, 'usr/
}) })
const output = `${execution.stdout || ''}\n${execution.stderr || ''}` const output = `${execution.stdout || ''}\n${execution.stderr || ''}`
const marker = 'FREECAD_PARTDESIGN_LOFT_RESULT=' const marker = 'FREECAD_PARTDESIGN_LOFT_RESULT='
const line = output.split(/\r?\n/).find((candidate) => candidate.startsWith(marker)) const line = output.split(/\r?\n/).find((candidate) => candidate.includes(marker))
if (execution.error || execution.status !== 0 || !line) throw new Error(`FreeCAD PartDesign loft oracle failed with status ${execution.status}: ${execution.error?.message || output.trim()}`) if (execution.error || execution.status !== 0 || !line) throw new Error(`FreeCAD PartDesign loft oracle failed with status ${execution.status}: ${execution.error?.message || output.trim()}`)
const report = JSON.parse(line.slice(marker.length)) const report = JSON.parse(line.slice(line.indexOf(marker) + marker.length))
await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`) await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify(report, null, 2)) console.log(JSON.stringify(report, null, 2))

View File

@@ -19,9 +19,9 @@ const execution = spawnSync(command, ['--python-path', resolve(sysroot, 'usr/lib
}) })
const output = `${execution.stdout || ''}\n${execution.stderr || ''}` const output = `${execution.stdout || ''}\n${execution.stderr || ''}`
const marker = 'FREECAD_PARTDESIGN_REVOLUTION_GROOVE_ORACLE_RESULT=' const marker = 'FREECAD_PARTDESIGN_REVOLUTION_GROOVE_ORACLE_RESULT='
const line = output.split(/\r?\n/).find((entry) => entry.startsWith(marker)) const line = output.split(/\r?\n/).find((entry) => entry.includes(marker))
if (execution.error || execution.status !== 0 || !line) throw new Error(`FreeCAD PartDesign Revolution/Groove oracle failed: ${execution.error?.message || output.trim()}`) if (execution.error || execution.status !== 0 || !line) throw new Error(`FreeCAD PartDesign Revolution/Groove oracle failed: ${execution.error?.message || output.trim()}`)
const report = JSON.parse(line.slice(marker.length)) const report = JSON.parse(line.slice(line.indexOf(marker) + marker.length))
await mkdir(resolve(root, 'config'), { recursive: true }) await mkdir(resolve(root, 'config'), { recursive: true })
await writeFile(outputFile, `${JSON.stringify(report, null, 2)}\n`) await writeFile(outputFile, `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify({ status: 'freecad-partdesign-revolution-groove-oracle-generated', output: outputFile, summary: report.summary }, null, 2)) console.log(JSON.stringify({ status: 'freecad-partdesign-revolution-groove-oracle-generated', output: outputFile, summary: report.summary }, null, 2))

View File

@@ -34,6 +34,7 @@ const lanes = {
...chromeTests ...chromeTests
.map((name) => `check:${name.slice('test:'.length)}`) .map((name) => `check:${name.slice('test:'.length)}`)
.filter((name) => scripts[name]), .filter((name) => scripts[name]),
'check:freecad-tsn-stage-evidence',
], ],
oracle: [ oracle: [
'fetch:freecad-source', 'fetch:freecad-source',
@@ -41,6 +42,8 @@ const lanes = {
'build:freecad-native', 'build:freecad-native',
'probe:freecad-reference', 'probe:freecad-reference',
'check:freecad-desktop-oracle', 'check:freecad-desktop-oracle',
'probe:freecad-core-parameter-mutations',
'check:freecad-core-parameter-mutations',
'test:golden:freecad', 'test:golden:freecad',
'test:golden:freecad:families', 'test:golden:freecad:families',
'test:golden:freecad:failures', 'test:golden:freecad:failures',
@@ -55,14 +58,16 @@ const lanes = {
'probe:freecad-partdesign-revolution-groove', 'probe:freecad-partdesign-revolution-groove',
'probe:freecad-part-builders', 'probe:freecad-part-builders',
'probe:freecad-composite-history-elementmap', 'probe:freecad-composite-history-elementmap',
'test:freecad-fcstd-native',
'generate:freecad-parameter-mutations',
'check:freecad-oracle-coverage', 'check:freecad-oracle-coverage',
'check:freecad-golden-coverage', 'check:freecad-golden-coverage',
'check:freecad-parameter-mutations',
'check:freecad-sketcher-constraints', 'check:freecad-sketcher-constraints',
'check:freecad-fcstd-roundtrip', 'check:freecad-fcstd-roundtrip',
'check:freecad-composite-history-elementmap', 'check:freecad-composite-history-elementmap',
'check:freecad-exact-history-elementmap-gate', 'check:freecad-exact-history-elementmap-gate',
'check:freecad-native-naming-evidence', 'check:freecad-native-naming-evidence',
'test:freecad-fcstd-native',
], ],
wasm: [ wasm: [
...wasmBuilds, ...wasmBuilds,