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

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

View File

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