218 lines
7.8 KiB
Python
218 lines
7.8 KiB
Python
import json
|
|
|
|
import FreeCAD as App
|
|
import Part
|
|
|
|
from freecad_mutation_evidence import capture_property_mutation
|
|
|
|
COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d"
|
|
|
|
|
|
def feature_status(case_id, feature, expected):
|
|
shape = feature.Shape
|
|
shape_null = bool(shape.isNull())
|
|
state = [str(value) for value in feature.State]
|
|
try:
|
|
status_string = str(feature.getStatusString())
|
|
except Exception:
|
|
status_string = ""
|
|
shape_valid = bool(shape.isValid()) if not shape_null else False
|
|
observed = "success" if not shape_null and shape_valid else "rejected" if "Invalid" in state or status_string != "Valid" else "accepted-empty"
|
|
return {
|
|
"id": case_id,
|
|
"typeId": feature.TypeId,
|
|
"expected": expected,
|
|
"observed": observed,
|
|
"passed": observed == expected,
|
|
"shapeNull": shape_null,
|
|
"shapeValid": shape_valid,
|
|
"shapeType": shape.ShapeType if not shape_null else "Null",
|
|
"solids": len(shape.Solids) if not shape_null else 0,
|
|
"faces": len(shape.Faces) if not shape_null else 0,
|
|
"edges": len(shape.Edges) if not shape_null else 0,
|
|
"vertices": len(shape.Vertexes) if not shape_null else 0,
|
|
"volume": float(shape.Volume) if not shape_null else 0.0,
|
|
"area": float(shape.Area) if not shape_null else 0.0,
|
|
"state": state,
|
|
"statusString": status_string,
|
|
}
|
|
|
|
|
|
def add_shape(document, name, shape):
|
|
feature = document.addObject("Part::Feature", name)
|
|
feature.Shape = shape
|
|
return feature
|
|
|
|
|
|
def rectangle_wire(x_min, y_min, x_max, y_max, z=0):
|
|
points = [
|
|
App.Vector(x_min, y_min, z),
|
|
App.Vector(x_max, y_min, z),
|
|
App.Vector(x_max, y_max, z),
|
|
App.Vector(x_min, y_max, z),
|
|
App.Vector(x_min, y_min, z),
|
|
]
|
|
return Part.makePolygon(points)
|
|
|
|
|
|
def success_extrusion():
|
|
document = App.newDocument("PartExtrusionSuccessOracle")
|
|
try:
|
|
profile = add_shape(document, "Profile", rectangle_wire(0, 0, 3, 2))
|
|
feature = document.addObject("Part::Extrusion", "Extrusion")
|
|
feature.Base = profile
|
|
feature.Dir = App.Vector(0, 0, 5)
|
|
feature.Solid = True
|
|
mutation = capture_property_mutation(document, feature, "Dir", App.Vector(0, 0, 7))
|
|
result = feature_status("part-extrusion-success", feature, "success")
|
|
result["mutation"] = mutation
|
|
return result
|
|
finally:
|
|
App.closeDocument(document.Name)
|
|
|
|
|
|
def success_revolution():
|
|
document = App.newDocument("PartRevolutionSuccessOracle")
|
|
try:
|
|
profile = add_shape(document, "Profile", rectangle_wire(2, 0, 4, 2))
|
|
feature = document.addObject("Part::Revolution", "Revolution")
|
|
feature.Source = profile
|
|
feature.Axis = App.Vector(0, 1, 0)
|
|
feature.Base = App.Vector(0, 0, 0)
|
|
feature.Angle = 360
|
|
feature.Solid = True
|
|
mutation = capture_property_mutation(document, feature, "Angle", 270.0)
|
|
result = feature_status("part-revolution-success", feature, "success")
|
|
result["mutation"] = mutation
|
|
return result
|
|
finally:
|
|
App.closeDocument(document.Name)
|
|
|
|
|
|
def success_loft():
|
|
document = App.newDocument("PartLoftSuccessOracle")
|
|
try:
|
|
first = add_shape(document, "SectionA", rectangle_wire(-2, -2, 2, 2, 0))
|
|
second = add_shape(document, "SectionB", rectangle_wire(-1, -1, 1, 1, 5))
|
|
feature = document.addObject("Part::Loft", "Loft")
|
|
feature.Sections = [first, second]
|
|
feature.Solid = True
|
|
feature.Ruled = False
|
|
feature.Closed = False
|
|
mutation = capture_property_mutation(document, feature, "Ruled", True)
|
|
result = feature_status("part-loft-success", feature, "success")
|
|
result["mutation"] = mutation
|
|
return result
|
|
finally:
|
|
App.closeDocument(document.Name)
|
|
|
|
|
|
def success_sweep():
|
|
document = App.newDocument("PartSweepSuccessOracle")
|
|
try:
|
|
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)]))
|
|
feature = document.addObject("Part::Sweep", "Sweep")
|
|
feature.Sections = [section]
|
|
feature.Spine = spine
|
|
feature.Solid = True
|
|
feature.Frenet = False
|
|
mutation = capture_property_mutation(document, feature, "Sections", [edited_section])
|
|
result = feature_status("part-sweep-success", feature, "success")
|
|
result["mutation"] = mutation
|
|
return result
|
|
finally:
|
|
App.closeDocument(document.Name)
|
|
|
|
|
|
def success_fillet():
|
|
document = App.newDocument("PartFilletSuccessOracle")
|
|
try:
|
|
base = add_shape(document, "Base", Part.makeBox(10, 8, 6))
|
|
feature = document.addObject("Part::Fillet", "Fillet")
|
|
feature.Base = base
|
|
feature.Edges = [(1, 1, 1)]
|
|
mutation = capture_property_mutation(document, feature, "Edges", [(1, 0.5, 0.5)])
|
|
result = feature_status("part-fillet-success", feature, "success")
|
|
result["mutation"] = mutation
|
|
return result
|
|
finally:
|
|
App.closeDocument(document.Name)
|
|
|
|
|
|
def success_chamfer():
|
|
document = App.newDocument("PartChamferSuccessOracle")
|
|
try:
|
|
base = add_shape(document, "Base", Part.makeBox(10, 8, 6))
|
|
feature = document.addObject("Part::Chamfer", "Chamfer")
|
|
feature.Base = base
|
|
feature.Edges = [(1, 1, 1)]
|
|
mutation = capture_property_mutation(document, feature, "Edges", [(1, 0.5, 0.5)])
|
|
result = feature_status("part-chamfer-success", feature, "success")
|
|
result["mutation"] = mutation
|
|
return result
|
|
finally:
|
|
App.closeDocument(document.Name)
|
|
|
|
|
|
def rejected_case(type_id):
|
|
name = type_id.split("::", 1)[1]
|
|
document = App.newDocument("Part{}FailureOracle".format(name))
|
|
try:
|
|
feature = document.addObject(type_id, name)
|
|
document.recompute()
|
|
result = feature_status("part-{}-missing-input".format(name.lower()), feature, "rejected")
|
|
result["inputClass"] = "missing-required-input"
|
|
return result
|
|
except Exception as error:
|
|
return {
|
|
"id": "part-{}-missing-input".format(name.lower()),
|
|
"typeId": type_id,
|
|
"inputClass": "missing-required-input",
|
|
"expected": "rejected",
|
|
"observed": "rejected",
|
|
"passed": True,
|
|
"errorType": type(error).__name__,
|
|
"error": str(error),
|
|
}
|
|
finally:
|
|
App.closeDocument(document.Name)
|
|
|
|
|
|
success_cases = [
|
|
success_extrusion(),
|
|
success_revolution(),
|
|
success_loft(),
|
|
success_sweep(),
|
|
success_fillet(),
|
|
success_chamfer(),
|
|
]
|
|
failure_cases = [rejected_case(type_id) for type_id in [
|
|
"Part::Extrusion",
|
|
"Part::Revolution",
|
|
"Part::Loft",
|
|
"Part::Sweep",
|
|
"Part::Fillet",
|
|
"Part::Chamfer",
|
|
]]
|
|
all_cases = success_cases + failure_cases
|
|
report = {
|
|
"schemaVersion": 1,
|
|
"baselineId": "freecad-1.1.1-part-builders-oracle",
|
|
"freecadVersion": ".".join(App.Version()[0:3]),
|
|
"gitCommit": COMMIT,
|
|
"status": "pass" if all(case.get("passed") for case in all_cases) else "failed",
|
|
"summary": {
|
|
"successCases": len(success_cases),
|
|
"successPassed": sum(1 for case in success_cases if case.get("passed")),
|
|
"failureCases": len(failure_cases),
|
|
"failurePassed": sum(1 for case in failure_cases if case.get("passed")),
|
|
"rejected": sum(1 for case in failure_cases if case.get("observed") == "rejected"),
|
|
"acceptedEmpty": sum(1 for case in failure_cases if case.get("observed") == "accepted-empty"),
|
|
},
|
|
"successCases": success_cases,
|
|
"failureCases": failure_cases,
|
|
}
|
|
print("FREECAD_PART_BUILDERS_ORACLE_RESULT=" + json.dumps(report, sort_keys=True))
|