feat: close builder mutation and roundtrip evidence gaps
This commit is contained in:
86
scripts/freecad_mutation_evidence.py
Normal file
86
scripts/freecad_mutation_evidence.py
Normal 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,
|
||||
}
|
||||
Reference in New Issue
Block a user