import json import os import tempfile import FreeCAD as App def version_text(): return ".".join(str(value) for value in App.Version()[:3]) def placement_json(placement): axis = placement.Rotation.Axis base = placement.Base return { "position": [float(base.x), float(base.y), float(base.z)], "axis": [float(axis.x), float(axis.y), float(axis.z)], "angleDegrees": float(placement.Rotation.Angle * 180.0 / 3.141592653589793), } def support_json(value): result = [] for obj, sub_elements in value: subs = [sub_elements] if isinstance(sub_elements, str) else list(sub_elements) result.append({ "document": obj.Document.Name, "object": obj.Name, "subElements": [str(sub) for sub in subs if str(sub)], }) return result def engine_json(obj): attacher = obj.Attacher implemented = list(attacher.ImplementedModes) return { "typeId": obj.TypeId, "attacherType": attacher.AttacherType, "completeModeList": list(attacher.CompleteModeList), "implementedModes": implemented, "modeInfo": { mode: { "modeIndex": int(attacher.getModeInfo(mode)["ModeIndex"]), "referenceCombinations": attacher.getModeInfo(mode)["ReferenceCombinations"], } for mode in implemented }, } def object_state(obj): suggestion = obj.Attacher.suggestModes() return { "typeId": obj.TypeId, "mapMode": str(obj.MapMode), "support": support_json(obj.AttachmentSupport), "placement": placement_json(obj.Placement), "state": [str(value) for value in obj.State], "status": str(obj.getStatusString()), "positionBySupport": bool(obj.positionBySupport()), "suggestion": { "message": suggestion["message"], "bestFitMode": suggestion["bestFitMode"], "allApplicableModes": suggestion["allApplicableModes"], "referenceTypes": suggestion["references_Types"], }, } document = App.newDocument("AttachmentModeOracle") source_body = document.addObject("PartDesign::Body", "SourceBody") source_box = source_body.newObject("PartDesign::AdditiveBox", "SourceBox") source_box.Length = 4 source_box.Width = 5 source_box.Height = 6 document.recompute() plane_body = document.addObject("PartDesign::Body", "PlaneBody") datum_plane = plane_body.newObject("PartDesign::Plane", "DatumPlane") datum_plane.AttachmentSupport = [(source_box, "Face1")] datum_plane.MapMode = "FlatFace" line_body = document.addObject("PartDesign::Body", "LineBody") datum_line = line_body.newObject("PartDesign::Line", "DatumLine") datum_line.AttachmentSupport = [(source_box, ("Vertex1", "Vertex2"))] datum_line.MapMode = "TwoPointLine" point_body = document.addObject("PartDesign::Body", "PointBody") datum_point = point_body.newObject("PartDesign::Point", "DatumPoint") datum_point.AttachmentSupport = [(source_box, "Vertex1")] datum_point.MapMode = "Vertex" sketch_body = document.addObject("PartDesign::Body", "SketchBody") sketch = sketch_body.newObject("Sketcher::SketchObject", "Sketch") sketch.AttachmentSupport = [(source_box, "Face1")] sketch.MapMode = "FlatFace" invalid_plane_body = document.addObject("PartDesign::Body", "InvalidPlaneBody") invalid_plane = invalid_plane_body.newObject("PartDesign::Plane", "InvalidPlane") invalid_plane.AttachmentSupport = [(source_box, "Vertex1")] invalid_plane.MapMode = "Vertex" invalid_line_body = document.addObject("PartDesign::Body", "InvalidLineBody") invalid_line = invalid_line_body.newObject("PartDesign::Line", "InvalidLine") invalid_line.AttachmentSupport = [(source_box, "Face1")] invalid_line.MapMode = "FlatFace" invalid_point_body = document.addObject("PartDesign::Body", "InvalidPointBody") invalid_point = invalid_point_body.newObject("PartDesign::Point", "InvalidPoint") invalid_point.AttachmentSupport = [(source_box, "Face1")] invalid_point.MapMode = "FlatFace" document.recompute() engine_objects = { "plane": datum_plane, "line": datum_line, "point": datum_point, "sketch": sketch, } engines = {name: engine_json(obj) for name, obj in engine_objects.items()} success_initial = {name: object_state(obj) for name, obj in engine_objects.items()} failures = { obj.Name: { "typeId": obj.TypeId, "mapMode": str(obj.MapMode), "support": support_json(obj.AttachmentSupport), "state": [str(value) for value in obj.State], "status": str(obj.getStatusString()), } for obj in [invalid_plane, invalid_line, invalid_point] } source_box.Height = 9 document.recompute() success_edited = {name: object_state(obj) for name, obj in engine_objects.items()} with tempfile.TemporaryDirectory(prefix="freecad-attachment-mode-") as temp_dir: path = os.path.join(temp_dir, "AttachmentModeOracle.FCStd") document.saveAs(path) App.closeDocument(document.Name) reopened = App.openDocument(path) reopened.recompute() roundtrip = { name: object_state(reopened.getObject(object_name)) for name, object_name in { "plane": "DatumPlane", "line": "DatumLine", "point": "DatumPoint", "sketch": "Sketch", }.items() } roundtrip_failures = { object_name: { "mapMode": str(reopened.getObject(object_name).MapMode), "state": [str(value) for value in reopened.getObject(object_name).State], "status": str(reopened.getObject(object_name).getStatusString()), } for object_name in ["InvalidPlane", "InvalidLine", "InvalidPoint"] } App.closeDocument(reopened.Name) all_modes = engines["plane"]["completeModeList"] implemented_union = sorted({mode for engine in engines.values() for mode in engine["implementedModes"]}) report = { "schemaVersion": 1, "baselineId": "freecad-1.1.1-attachment-mode-oracle", "freecadVersion": version_text(), "gitCommit": "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d", "status": "pass", "tolerance": 1e-7, "registry": { "modeCount": len(all_modes), "modes": all_modes, "implementedUnionCount": len(implemented_union), "unimplementedModes": [mode for mode in all_modes if mode not in implemented_union], }, "engines": engines, "success": {"initial": success_initial, "edited": success_edited, "roundtrip": roundtrip}, "failures": {"initial": failures, "roundtrip": roundtrip_failures}, } print("FREECAD_ATTACHMENT_MODE_RESULT=" + json.dumps(report, sort_keys=True, separators=(",", ":")))