"""Read-only Revit 2026 model-identity diagnostics for a Dynamo Python node. Optional Dynamo input: IN[0]: output directory or complete .json file path The script intentionally performs no Revit transaction and changes no model data. OUT contains a compact summary plus the complete report. """ import datetime import json import os import platform import sys import tempfile import traceback import clr clr.AddReference("RevitAPI") clr.AddReference("RevitServices") from Autodesk.Revit.DB import ModelPathUtils, StorageType # noqa: E402 from RevitServices.Persistence import DocumentManager # noqa: E402 CHECKED_PARAMETER_NAMES = ("LB_ModelId", "LB_ProjectId") def safe_text(value): if value is None: return None try: return str(value) except Exception: return None def element_id_text(element_id): if element_id is None: return None try: return str(element_id.Value) except Exception: try: return str(element_id.IntegerValue) except Exception: return safe_text(element_id) def forge_type_id_text(value): if value is None: return None try: return value.TypeId except Exception: return safe_text(value) def parameter_value(parameter): result = { "hasValue": False, "raw": None, "display": None, } try: result["hasValue"] = bool(parameter.HasValue) except Exception: pass try: storage_type = parameter.StorageType if storage_type == StorageType.String: result["raw"] = parameter.AsString() elif storage_type == StorageType.Integer: result["raw"] = int(parameter.AsInteger()) elif storage_type == StorageType.Double: result["raw"] = float(parameter.AsDouble()) elif storage_type == StorageType.ElementId: result["raw"] = element_id_text(parameter.AsElementId()) except Exception as error: result["readError"] = safe_text(error) try: result["display"] = parameter.AsValueString() except Exception: pass return result def describe_parameter(parameter): definition = None try: definition = parameter.Definition except Exception: pass name = None if definition is not None: try: name = definition.Name except Exception: pass is_shared = False try: is_shared = bool(parameter.IsShared) except Exception: pass shared_guid = None if is_shared: try: shared_guid = str(parameter.GUID) except Exception: pass data_type = None group_type = None if definition is not None: try: data_type = forge_type_id_text(definition.GetDataType()) except Exception: pass try: group_type = forge_type_id_text(definition.GetGroupTypeId()) except Exception: pass unit_type = None try: unit_type = forge_type_id_text(parameter.GetUnitTypeId()) except Exception: pass try: storage_type = str(parameter.StorageType) except Exception: storage_type = None try: is_read_only = bool(parameter.IsReadOnly) except Exception: is_read_only = None try: user_modifiable = bool(parameter.UserModifiable) except Exception: user_modifiable = None return { "name": name, "parameterId": element_id_text(getattr(parameter, "Id", None)), "isShared": is_shared, "sharedGuid": shared_guid, "storageType": storage_type, "dataTypeId": data_type, "groupTypeId": group_type, "unitTypeId": unit_type, "isReadOnly": is_read_only, "userModifiable": user_modifiable, "value": parameter_value(parameter), } def sorted_parameters(element): parameters = [] try: parameters = [describe_parameter(parameter) for parameter in element.Parameters] except Exception: return [] return sorted( parameters, key=lambda parameter: ( (parameter.get("name") or "").casefold(), parameter.get("parameterId") or "", ), ) def named_parameter_occurrences(element, parameter_name): result = [] try: parameters = element.GetParameters(parameter_name) if parameters is not None: result = [describe_parameter(parameter) for parameter in parameters] except Exception: parameter = None try: parameter = element.LookupParameter(parameter_name) except Exception: pass if parameter is not None: result = [describe_parameter(parameter)] return result def loaded_assembly_versions(): result = {} try: from System import AppDomain for assembly in AppDomain.CurrentDomain.GetAssemblies(): try: name = assembly.GetName() simple_name = str(name.Name) if simple_name in ( "DynamoCore", "DynamoCoreWpf", "DynamoRevitDS", "RevitAPI", "RevitServices", ): result[simple_name] = str(name.Version) except Exception: continue except Exception: pass return dict(sorted(result.items())) def get_cloud_identity(document): result = {"isModelInCloud": False} try: result["isModelInCloud"] = bool(document.IsModelInCloud) except Exception: return result if not result["isModelInCloud"]: return result try: model_path = document.GetCloudModelPath() result["userVisiblePath"] = ModelPathUtils.ConvertModelPathToUserVisiblePath( model_path ) for property_name, output_name in ( ("GetProjectGUID", "projectGuid"), ("GetModelGUID", "modelGuid"), ): try: result[output_name] = str(getattr(model_path, property_name)()) except Exception: pass except Exception as error: result["readError"] = safe_text(error) return result def get_worksharing_identity(document): result = {"isWorkshared": False} try: result["isWorkshared"] = bool(document.IsWorkshared) except Exception: return result if not result["isWorkshared"]: return result try: model_path = document.GetWorksharingCentralModelPath() result["centralModelPath"] = ModelPathUtils.ConvertModelPathToUserVisiblePath( model_path ) except Exception as error: result["readError"] = safe_text(error) return result def resolve_output_path(configured_path, report_name): timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") default_directory = os.path.join(tempfile.gettempdir(), "leistungsbilanz-dynamo") raw_path = safe_text(configured_path) if raw_path is None or not raw_path.strip(): directory = default_directory file_path = os.path.join(directory, report_name + "-" + timestamp + ".json") else: expanded = os.path.abspath(os.path.expandvars(os.path.expanduser(raw_path.strip()))) if expanded.lower().endswith(".json"): file_path = expanded directory = os.path.dirname(file_path) else: directory = expanded file_path = os.path.join(directory, report_name + "-" + timestamp + ".json") if not directory: directory = os.getcwd() if not os.path.isdir(directory): os.makedirs(directory) return file_path def write_json(file_path, payload): temporary_path = file_path + ".tmp" with open(temporary_path, "w", encoding="utf-8", newline="\n") as output: json.dump(payload, output, ensure_ascii=False, indent=2, sort_keys=True) output.write("\n") os.replace(temporary_path, file_path) def get_input(index, default=None): values = globals().get("IN", []) try: value = values[index] return default if value is None else value except Exception: return default def build_report(): document = DocumentManager.Instance.CurrentDBDocument if document is None: raise RuntimeError("No active Revit document is available.") project_information = document.ProjectInformation if project_information is None: raise RuntimeError("The active document has no Project Information element.") application = document.Application checked_parameters = { name: named_parameter_occurrences(project_information, name) for name in CHECKED_PARAMETER_NAMES } warnings = [] for name in CHECKED_PARAMETER_NAMES: occurrences = checked_parameters[name] populated = [ parameter for parameter in occurrences if parameter.get("value", {}).get("raw") not in (None, "") ] if not occurrences: warnings.append(name + " is not bound to Project Information.") elif not populated: warnings.append(name + " exists but has no value on Project Information.") elif len(occurrences) > 1: warnings.append(name + " occurs more than once; use a shared-parameter GUID later.") return { "reportSchemaVersion": 1, "generatedAt": datetime.datetime.now(datetime.timezone.utc).isoformat(), "readOnly": True, "environment": { "revitVersionNumber": safe_text(getattr(application, "VersionNumber", None)), "revitVersionName": safe_text(getattr(application, "VersionName", None)), "revitSubVersionNumber": safe_text( getattr(application, "SubVersionNumber", None) ), "pythonImplementation": safe_text(getattr(sys.implementation, "name", None)), "pythonVersion": platform.python_version(), "assemblies": loaded_assembly_versions(), }, "document": { "title": safe_text(document.Title), "pathName": safe_text(document.PathName), "isFamilyDocument": bool(document.IsFamilyDocument), "cloud": get_cloud_identity(document), "worksharing": get_worksharing_identity(document), }, "modelIdentityCandidates": { "projectInformationUniqueId": safe_text(project_information.UniqueId), "projectInformationElementId": element_id_text(project_information.Id), "checkedProjectParameters": checked_parameters, }, "projectInformationParameters": sorted_parameters(project_information), "warnings": warnings, } try: report = build_report() output_path = resolve_output_path(get_input(0), "model-identity") write_json(output_path, report) OUT = { "ok": True, "filePath": output_path, "projectInformationUniqueId": report["modelIdentityCandidates"][ "projectInformationUniqueId" ], "warnings": report["warnings"], "report": report, } except Exception as error: OUT = { "ok": False, "error": safe_text(error), "traceback": traceback.format_exc(), }