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))