import json import FreeCAD as App import Part FREECAD_COMMIT = "0108fd4b4850cc46e625b60e53cea7a7bbe69f8d" def object_set(document): return [{"name": obj.Name, "typeId": obj.TypeId} for obj in document.Objects] def area_snapshot(measure): value = measure.Area return { "raw": str(value), "numeric": round(float(value.Value), 9), "unit": str(value.Unit), "elements": [ {"object": obj.Name, "subElements": list(sub_elements)} for obj, sub_elements in measure.Elements ], "state": [str(item) for item in measure.State], "statusString": str(measure.getStatusString()), } def exception_snapshot(callback): try: callback() except Exception as error: return {"type": type(error).__name__, "message": str(error)} return None def active_transaction_snapshot(): active = App.getActiveTransaction() if not active: return {"name": "", "id": 0} return {"name": str(active[0]), "id": int(active[1])} def run(): document = App.newDocument("PropertyAreaFailure") try: box = document.addObject("Part::Box", "AreaBox") box.Length = 10 box.Width = 5 box.Height = 2 cylinder = document.addObject("Part::Cylinder", "AreaCylinder") cylinder.Radius = 3 cylinder.Height = 4 point = document.addObject("Part::Feature", "AreaPoint") point.Shape = Part.Vertex(App.Vector(0, 0, 0)) line = document.addObject("Part::Feature", "AreaLine") line.Shape = Part.makeLine(App.Vector(0, 0, 0), App.Vector(4, 0, 0)) measure = document.addObject("Measure::MeasureArea", "AreaProbe") measure.Elements = [(box, ["Face1"])] document.recompute() document.UndoMode = 1 initial = area_snapshot(measure) initial_objects = object_set(document) failures = [] for case_id, source, sub_element, shape_type in [ ("unsupported-point", point, "Vertex1", "Vertex"), ("unsupported-line", line, "Edge1", "Edge"), ]: measure.Elements = [(box, ["Face1"])] document.recompute() before = area_snapshot(measure) before_objects = object_set(document) assignment_exception = exception_snapshot(lambda: setattr(measure, "Elements", [(source, [sub_element])])) recompute_exception = None recompute_result = None try: recompute_result = bool(document.recompute()) except Exception as error: recompute_exception = {"type": type(error).__name__, "message": str(error)} after = area_snapshot(measure) after_objects = object_set(document) measure.Elements = [(box, ["Face1"])] document.recompute() recovered = area_snapshot(measure) failures.append({ "id": case_id, "source": {"name": source.Name, "typeId": source.TypeId, "subElement": sub_element, "shapeType": shape_type}, "expectedDiagnostic": "Cannot calculate area", "assignmentException": assignment_exception, "recomputeException": recompute_exception, "recomputeResult": recompute_result, "before": before, "after": after, "recovered": recovered, "beforeObjects": before_objects, "afterObjects": after_objects, "objectsPreserved": before_objects == after_objects, "valuePreserved": before["numeric"] == after["numeric"], "recoveredExactly": before["numeric"] == recovered["numeric"] and before["elements"] == recovered["elements"], }) measure.Elements = [(box, ["Face1"])] document.recompute() disabled_before = area_snapshot(measure) disabled_objects_before = object_set(document) editor_read_only_exception = exception_snapshot(lambda: setattr(measure, "Area", "20 mm^2")) editor_read_only_after = area_snapshot(measure) measure.setPropertyStatus("Area", "Immutable") immutable_status = [str(item) for item in measure.getPropertyStatus("Area")] immutable_exception = exception_snapshot(lambda: setattr(measure, "Area", "30 mm^2")) immutable_after = area_snapshot(measure) measure.setPropertyStatus("Area", "-Immutable") restored_status = [str(item) for item in measure.getPropertyStatus("Area")] restored_editor_mode = [str(item) for item in measure.getEditorMode("Area")] measure.Elements = [] measure.Elements = [(box, ["Face1"])] document.recompute() disabled_restored = area_snapshot(measure) disabled_objects_after = object_set(document) disabled = { "classification": "editor-readonly-python-mutable-until-immutable", "editorReadOnlyRequested": "20 mm^2", "immutableRequested": "30 mm^2", "propertyStatus": ["24", "27"], "editorMode": [str(item) for item in measure.getEditorMode("Area")], "editorReadOnlyException": editor_read_only_exception, "editorReadOnlyAfter": editor_read_only_after, "pythonBypassesEditorReadOnly": editor_read_only_exception is None and editor_read_only_after["numeric"] == 20, "immutableStatus": immutable_status, "immutableException": immutable_exception, "immutableAfter": immutable_after, "before": disabled_before, "restored": disabled_restored, "restoredStatus": restored_status, "restoredEditorMode": restored_editor_mode, "immutableValuePreserved": editor_read_only_after == immutable_after, "valueRestored": disabled_before["numeric"] == disabled_restored["numeric"] and disabled_before["elements"] == disabled_restored["elements"], "objectsPreserved": disabled_objects_before == disabled_objects_after, } document.openTransaction("property-area-cancel") transaction_before = area_snapshot(measure) transaction_objects_before = object_set(document) measure.Elements = [(cylinder, ["Face1"])] document.recompute() transaction_edited = area_snapshot(measure) transaction_pending_after_edit = bool(document.HasPendingTransaction) transaction_active_after_edit = active_transaction_snapshot() document.abortTransaction() transaction_after_abort = area_snapshot(measure) document.recompute() transaction_after_recompute = area_snapshot(measure) transaction_objects_after = object_set(document) return { "schemaVersion": 1, "status": "pass", "baselineId": "freecad-1.1.1-property-area-failure", "freecadVersion": ".".join(str(value) for value in App.Version()[:3]), "gitCommit": FREECAD_COMMIT, "object": {"name": measure.Name, "typeId": measure.TypeId}, "property": {"name": "Area", "typeId": measure.getTypeIdOfProperty("Area")}, "input": {"name": "Elements", "typeId": measure.getTypeIdOfProperty("Elements")}, "initial": initial, "failures": failures, "disabled": disabled, "transaction": { "before": transaction_before, "edited": transaction_edited, "afterAbort": transaction_after_abort, "afterRecompute": transaction_after_recompute, "objectsBefore": transaction_objects_before, "objectsAfter": transaction_objects_after, "undoMode": int(document.UndoMode), "pendingAfterEdit": transaction_pending_after_edit, "pendingAfterAbort": bool(document.HasPendingTransaction), "activeAfterEdit": transaction_active_after_edit, "activeAfterAbort": active_transaction_snapshot(), "restored": transaction_before["numeric"] == transaction_after_recompute["numeric"] and transaction_before["elements"] == transaction_after_recompute["elements"], "objectsRestored": transaction_objects_before == transaction_objects_after, }, "cancellationBoundary": {"supported": False, "classification": "synchronous-measure-recompute", "reason": "no-native-cancel-hook", "replacement": "abort-active-document-transaction"}, "documentIntegrity": {"initialObjects": initial_objects, "finalObjects": object_set(document), "objectsPreserved": initial_objects == object_set(document), "objectCount": len(document.Objects)}, } finally: App.closeDocument(document.Name) print("FREECAD_PROPERTY_AREA_FAILURE_RESULT=" + json.dumps(run(), sort_keys=True))