import hashlib import json import os import FreeCAD as App import Part FREECAD_COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d" OUTPUT_PATH = os.environ.get("FREECAD_PROPERTY_DIRECTION_SUCCESS_OUTPUT", "") ASSIGNMENTS = [ ("axisZTuple", (0.0, 0.0, 1.0), "tuple"), ("integerTuple", (1, 1, 2), "tuple"), ("nonUnitFloatTuple", (0.25, -0.5, 1.5), "tuple"), ("vectorObject", App.Vector(-0.125, 0.25, 1.0), "Base.Vector"), ("nearAxisTuple", (1.0e-9, -1.0e-9, 1.0), "tuple"), ("largeFiniteTuple", (1.0e6, -2.0e6, 3.0e6), "tuple"), ] def vector_value(obj, property_name): value = getattr(obj, property_name) return [float(value.x), float(value.y), float(value.z)] def object_snapshot(document): return [{"name": obj.Name, "typeId": obj.TypeId} for obj in document.Objects] def shape_snapshot(obj): shape = obj.Shape if shape.isNull(): return {"applicable": True, "isNull": True, "shapeType": "Null", "solids": 0, "faces": 0, "edges": 0, "vertices": 0} brep = shape.exportBrepToString().encode("utf-8") bounds = shape.BoundBox return { "applicable": True, "isNull": False, "valid": bool(shape.isValid()), "shapeType": shape.ShapeType, "solids": len(shape.Solids), "faces": len(shape.Faces), "edges": len(shape.Edges), "vertices": len(shape.Vertexes), "area": round(float(shape.Area), 9), "volume": round(float(shape.Volume), 9), "bounds": [round(float(value), 9) for value in (bounds.XMin, bounds.YMin, bounds.ZMin, bounds.XMax, bounds.YMax, bounds.ZMax)], "brepSha256": hashlib.sha256(brep).hexdigest(), } def host_snapshot(document, obj, property_name, recompute_result): return { "value": vector_value(obj, property_name), "propertyTypeId": obj.getTypeIdOfProperty(property_name), "propertyStatus": [str(item) for item in obj.getPropertyStatus(property_name)], "editorMode": [str(item) for item in obj.getEditorMode(property_name)], "objectState": [str(item) for item in obj.State], "statusString": str(obj.getStatusString()), "mustExecute": bool(obj.MustExecute), "recomputeResult": bool(recompute_result), "shape": shape_snapshot(obj), "objectSet": object_snapshot(document), } def touched_snapshot(obj, property_name): return { "value": vector_value(obj, property_name), "objectState": [str(item) for item in obj.State], "statusString": str(obj.getStatusString()), "mustExecute": bool(obj.MustExecute), } def setup_mirroring(document): source = document.addObject("Part::Box", "MirrorSource") source.Length = 2 source.Width = 3 source.Height = 4 host = document.addObject("Part::Mirroring", "DirectionMirror") host.Source = source host.Base = App.Vector(0, 0, 0) return host, "Normal", { "kind": "box-mirror", "source": {"name": source.Name, "typeId": source.TypeId, "dimensions": [2, 3, 4]}, "base": [0, 0, 0], "mirrorPlane": None, } def setup_projection(document): support = document.addObject("Part::Feature", "ProjectionSupport") support.Shape = Part.makePlane(40, 40, App.Vector(-20, -20, 0)) wire = document.addObject("Part::Feature", "ProjectionWire") points = [ App.Vector(-4, -3, -5), App.Vector(4, -3, -5), App.Vector(4, 3, -5), App.Vector(-4, 3, -5), App.Vector(-4, -3, -5), ] wire.Shape = Part.makePolygon(points) host = document.addObject("Part::ProjectOnSurface", "DirectionProjection") host.SupportFace = (support, ["Face1"]) host.Projection = [(wire, ["Wire1"])] host.Mode = "Edges" return host, "Direction", { "kind": "wire-to-planar-face", "support": {"name": support.Name, "typeId": support.TypeId, "subElements": ["Face1"]}, "projection": {"name": wire.Name, "typeId": wire.TypeId, "subElements": ["Wire1"], "z": -5}, "mode": "Edges", } def run_case(case_id, object_type_id, setup): document = App.newDocument("PropertyDirection" + case_id) try: obj, property_name, setup_report = setup(document) initial_recompute = document.recompute() default_value = vector_value(obj, property_name) phases = {"default": {"afterRecompute": host_snapshot(document, obj, property_name, initial_recompute)}} requested = {} for phase, value, input_kind in ASSIGNMENTS: requested[phase] = {"inputKind": input_kind, "value": [float(component) for component in value]} setattr(obj, property_name, value) touched = touched_snapshot(obj, property_name) recompute_result = document.recompute() phases[phase] = {"afterSet": touched, "afterRecompute": host_snapshot(document, obj, property_name, recompute_result)} setattr(obj, property_name, tuple(default_value)) restored_touched = touched_snapshot(obj, property_name) restored_recompute = document.recompute() phases["restored"] = {"afterSet": restored_touched, "afterRecompute": host_snapshot(document, obj, property_name, restored_recompute)} return { "caseId": case_id, "objectTypeId": object_type_id, "objectName": obj.Name, "propertyName": property_name, "mode": "direct-native-property-setter-with-valid-host-geometry", "setup": setup_report, "defaultValue": default_value, "requested": requested, "phases": phases, } finally: App.closeDocument(document.Name) cases = [ run_case("Mirroring", "Part::Mirroring", setup_mirroring), run_case("ProjectOnSurface", "Part::ProjectOnSurface", setup_projection), ] report = { "schemaVersion": 1, "status": "pass", "baselineId": "freecad-1.1.1-property-direction-success", "freecadVersion": ".".join(str(value) for value in App.Version()[:3]), "gitCommit": FREECAD_COMMIT, "propertyType": "App::PropertyDirection", "caseCount": len(cases), "cases": cases, } if not OUTPUT_PATH: raise RuntimeError("FREECAD_PROPERTY_DIRECTION_SUCCESS_OUTPUT is required") with open(OUTPUT_PATH, "w", encoding="utf-8") as handle: json.dump(report, handle, indent=2, sort_keys=True) handle.write("\n") print("FREECAD_PROPERTY_DIRECTION_SUCCESS_RESULT=" + json.dumps({"status": report["status"], "caseCount": report["caseCount"]}, sort_keys=True))