{ "Uuid": "d7b7f9df-f7e3-47ac-ab3d-802ed44c5515", "IsCustomNode": false, "Description": "", "Name": "01_sheets_from_excel", "ElementResolver": { "ResolutionMap": {} }, "Inputs": [], "Outputs": [], "Nodes": [ { "ConcreteType": "PythonNodeModels.PythonNode, PythonNodeModels", "Code": "# -*- coding: utf-8 -*-\r\n# 01 — Crear hojas desde tabla (Excel / CSV importado en Dynamo con Data.ImportExcel).\r\n#\r\n# IN[0] : lista de listas (primera fila = cabeceras). Columnas requeridas:\r\n# SheetNumber, SheetName\r\n# Origen típico: Data.ImportCSV (CSV) o Data.ImportExcel (.xlsx + nombre de hoja).\r\n# IN[1] : FamilySymbol del cartucho, o salida del nodo \"Family Types\" de Dynamo (se desempaqueta).\r\n# IN[2] : dry_run (opcional) True = no crea, solo valida cabeceras y filas.\r\n#\r\n# Salida OUT: [ lista de ViewSheet creadas, lista de str con errores/advertencias ]\r\n# En dry_run: [ [], ['DRY RUN', 'N filas válidas', ...] ]\r\n#\r\n# Notas: IronPython (Dynamo clásico) y CPython 3 (Revit 2025+). Probar en copia de proyecto.\r\n# CPython: IN y tablas de Excel pueden llegar como StackValue (sin len()); se normalizan abajo.\r\n\r\nimport sys\r\n\r\nimport clr\r\nclr.AddReference('RevitAPI')\r\nclr.AddReference('RevitServices')\r\nfrom Autodesk.Revit.DB import ElementId, FilteredElementCollector, ViewSheet, FamilySymbol\r\nfrom RevitServices.Persistence import DocumentManager\r\nfrom RevitServices.Transactions import TransactionManager\r\n\r\n_PY3 = sys.version_info[0] >= 3\r\n\r\n\r\ndef _ustr(value):\r\n if value is None:\r\n return ''\r\n if _PY3:\r\n return str(value).strip()\r\n if isinstance(value, unicode):\r\n return value.strip()\r\n return unicode(value).strip()\r\n\r\n\r\ndef _exc_str(ex):\r\n if _PY3:\r\n return str(ex)\r\n return unicode(ex)\r\n\r\n\r\ndef _as_bool_dry_run(val):\r\n \"\"\"\r\n True solo si el usuario pide dry run. Evita el bug bool('False')==True en Python\r\n (texto desde Excel u otros nodos).\r\n \"\"\"\r\n if val is None:\r\n return False\r\n if isinstance(val, bool):\r\n return val\r\n if isinstance(val, int):\r\n return bool(val)\r\n s = _ustr(val).lower()\r\n if s in ('false', 'falso', '0', 'no', 'n', '', 'off'):\r\n return False\r\n if s in ('true', 'verdadero', '1', 'yes', 'si', 'sí', 'on', 'dry', 'dry_run'):\r\n return True\r\n try:\r\n return bool(int(float(s)))\r\n except Exception:\r\n return bool(val)\r\n\r\n\r\ndef _to_py_list(obj, max_index=256):\r\n \"\"\"Convierte ICollection / StackValue / iterable de Dynamo a list Python.\"\"\"\r\n if obj is None:\r\n return []\r\n if isinstance(obj, (list, tuple)):\r\n return list(obj)\r\n try:\r\n return list(obj)\r\n except TypeError:\r\n pass\r\n out = []\r\n for i in range(max_index):\r\n try:\r\n out.append(obj[i])\r\n except Exception:\r\n break\r\n return out\r\n\r\n\r\ndef _normalize_table(data):\r\n \"\"\"Lista de filas; cada fila como lista (ImportExcel en CPython).\"\"\"\r\n rows = _to_py_list(data)\r\n return [_to_py_list(r) for r in rows]\r\n\r\n\r\ndef _dynamo_in_ports():\r\n \"\"\"Entradas del nodo; evita len(IN) cuando IN es StackValue (CPython3).\"\"\"\r\n raw = globals().get('IN', [])\r\n ports = _to_py_list(raw, max_index=32)\r\n if len(ports) == 0 and raw is not None:\r\n tmp = []\r\n for i in range(32):\r\n try:\r\n tmp.append(raw[i])\r\n except Exception:\r\n break\r\n ports = tmp\r\n return ports\r\n\r\n\r\ndef _cell(row, key, headers):\r\n if key not in headers:\r\n return None\r\n i = headers[key]\r\n if i >= len(row):\r\n return None\r\n v = row[i]\r\n if v is None or v == '':\r\n return None\r\n return _ustr(v)\r\n\r\n\r\ndef _unwrap_family_symbol(obj, document):\r\n \"\"\"Acepta FamilySymbol o envoltorio del nodo Family Types (Dynamo / CPython).\"\"\"\r\n if obj is None:\r\n return None\r\n if isinstance(obj, FamilySymbol):\r\n return obj\r\n inner = getattr(obj, 'InternalElement', None)\r\n if isinstance(inner, FamilySymbol):\r\n return inner\r\n eid = getattr(obj, 'Id', None)\r\n if eid is not None:\r\n try:\r\n if not isinstance(eid, ElementId):\r\n if hasattr(eid, 'IntegerValue'):\r\n eid = ElementId(eid.IntegerValue)\r\n else:\r\n eid = ElementId(int(eid))\r\n el = document.GetElement(eid)\r\n if isinstance(el, FamilySymbol):\r\n return el\r\n except Exception:\r\n pass\r\n return None\r\n\r\n\r\ndoc = DocumentManager.Instance.CurrentDBDocument\r\n_ports = _dynamo_in_ports()\r\ndry_run = False\r\nif len(_ports) > 2 and _ports[2] is not None:\r\n dry_run = _as_bool_dry_run(_ports[2])\r\n\r\nrows = _normalize_table(_ports[0]) if len(_ports) > 0 else []\r\ntitle_sym = _unwrap_family_symbol(_ports[1] if len(_ports) > 1 else None, doc)\r\n\r\ncreated = []\r\nmessages = []\r\n\r\nif not rows or len(rows) < 2:\r\n OUT = [[], ['Error: IN[0] debe tener cabecera + al menos una fila de datos.']]\r\nelse:\r\n header_row = rows[0]\r\n headers = {}\r\n for idx, h in enumerate(header_row):\r\n if h is None or h == '':\r\n continue\r\n name = _ustr(h).lstrip('\\ufeff')\r\n if name:\r\n headers[name] = idx\r\n\r\n if 'SheetNumber' not in headers or 'SheetName' not in headers:\r\n OUT = [[], ['Error: faltan columnas SheetNumber o SheetName.']]\r\n elif title_sym is None or not isinstance(title_sym, FamilySymbol):\r\n OUT = [[], ['Error: IN[1] debe ser un FamilySymbol de title block cargado.']]\r\n else:\r\n data_rows = rows[1:]\r\n existing_numbers = set()\r\n for vs in FilteredElementCollector(doc).OfClass(ViewSheet).ToElements():\r\n existing_numbers.add(_ustr(vs.SheetNumber))\r\n\r\n messages.append(\r\n 'Info: dry_run={0}, filas_en_tabla={1}, cabeceras={2}'.format(\r\n dry_run, len(data_rows), ', '.join(sorted(headers.keys()))))\r\n\r\n if dry_run:\r\n ok = 0\r\n for r in data_rows:\r\n num = _cell(r, 'SheetNumber', headers)\r\n nam = _cell(r, 'SheetName', headers)\r\n if not num or not nam:\r\n messages.append('Dry run: fila omitida (SheetNumber/SheetName vacíos).')\r\n continue\r\n if num in existing_numbers:\r\n messages.append('Dry run: número ya existe en el modelo — ' + num)\r\n continue\r\n ok += 1\r\n messages.insert(0, 'DRY RUN: {0} fila(s) válida(s) para crear (sin duplicados en modelo).'.format(ok))\r\n OUT = [[], messages]\r\n else:\r\n TransactionManager.Instance.EnsureInTransaction(doc)\r\n try:\r\n if not title_sym.IsActive:\r\n title_sym.Activate()\r\n doc.Regenerate()\r\n\r\n for r in data_rows:\r\n num = _cell(r, 'SheetNumber', headers)\r\n nam = _cell(r, 'SheetName', headers)\r\n if not num or not nam:\r\n messages.append('Fila omitida: SheetNumber o SheetName vacíos.')\r\n continue\r\n if num in existing_numbers:\r\n messages.append('Omitido: ya existe hoja con número ' + num)\r\n continue\r\n try:\r\n # Revit 2024+: Create(doc, titleBlockId) solo; número y nombre después.\r\n # Versiones antiguas: Create(doc, id, number, name) en una llamada.\r\n try:\r\n vs = ViewSheet.Create(doc, title_sym.Id, num, nam)\r\n except Exception:\r\n vs = ViewSheet.Create(doc, title_sym.Id)\r\n vs.SheetNumber = num\r\n vs.Name = nam\r\n created.append(vs)\r\n existing_numbers.add(num)\r\n except Exception as ex:\r\n messages.append('Fila {0}: {1}'.format(num, _exc_str(ex)))\r\n finally:\r\n TransactionManager.Instance.TransactionTaskDone()\r\n\r\n non_info = [m for m in messages if not _ustr(m).startswith('Info:')]\r\n if created:\r\n if len(non_info) == 0:\r\n messages.append('OK: {0} hoja(s) creada(s).'.format(len(created)))\r\n else:\r\n messages.insert(1, 'Parcial: {0} hoja(s) creada(s); revisa avisos.'.format(len(created)))\r\n else:\r\n if len(non_info) == 0:\r\n messages.append(\r\n 'No se creó ninguna hoja (0 filas útiles o tabla vacía). Revisa Excel y duplicados.')\r\n else:\r\n messages.insert(\r\n 1,\r\n 'Ninguna hoja nueva: revisa mensajes (duplicados, celdas vacías, error API).')\r\n\r\n OUT = [created, messages]\r\n# --- RÜM: mensaje de cierre (URL en rum_platform_url.py) ---\r\ntry:\r\n import sys as _rum_sys\r\n _rum_root = r'c:\\RUM_Platform\\RUM_Tools\\Dynamo_Routines'\r\n if _rum_root not in _rum_sys.path:\r\n _rum_sys.path.insert(0, _rum_root)\r\n import rum_finalize as _rum_fin\r\n OUT = _rum_fin.apply(OUT)\r\nexcept Exception:\r\n pass", "Engine": "CPython3", "VariableInputPorts": true, "Id": "0d18990eac7c48e9a529f3d1f63cb71e", "NodeType": "PythonScriptNode", "Inputs": [ { "Id": "0fe7e02e61554f43b8b8d403da316b28", "Name": "IN[0]", "Description": "Input #0", "UsingDefaultValue": false, "Level": 2, "UseLevels": false, "KeepListStructure": false }, { "Id": "5a93b68350e14495b9dd40ce03f47211", "Name": "IN[1]", "Description": "Input #1", "UsingDefaultValue": false, "Level": 2, "UseLevels": false, "KeepListStructure": false }, { "Id": "1b44c3518d5f49b697e3e2c0d921c9c1", "Name": "IN[2]", "Description": "Input #2", "UsingDefaultValue": false, "Level": 2, "UseLevels": false, "KeepListStructure": false } ], "Outputs": [ { "Id": "9c2af7dcddea4c9dbb4de76f672180f2", "Name": "OUT", "Description": "Result of the python script", "UsingDefaultValue": false, "Level": 2, "UseLevels": false, "KeepListStructure": false } ], "Replication": "Disabled", "Description": "Runs an embedded Python script." }, { "ConcreteType": "CoreNodeModels.Input.Filename, CoreNodeModels", "Id": "23caef1ad90249449f1a983a7a2f198b", "NodeType": "ExtensionNode", "Inputs": [], "Outputs": [ { "Id": "272a2fe57bfd452cbaa690019a961ad1", "Name": "", "Description": "File Path", "UsingDefaultValue": false, "Level": 2, "UseLevels": false, "KeepListStructure": false } ], "Replication": "Disabled", "Description": "Allows you to select a file on the system and returns its file path", "HintPath": "C:\\RUM_Platform\\RUM_Tools\\Dynamo_Routines\\01_sheets_from_excel\\RUM_template_sheets.xlsx", "InputValue": ".\\RUM_template_sheets.xlsx" }, { "ConcreteType": "Dynamo.Graph.Nodes.ZeroTouch.DSFunction, DynamoCore", "Id": "ec4dd2d362e04ab98cd7babaeb3c11cc", "NodeType": "FunctionNode", "Inputs": [ { "Id": "8fec46a5da4f4b68aad8e589940c82b1", "Name": "file", "Description": "var", "UsingDefaultValue": false, "Level": 2, "UseLevels": false, "KeepListStructure": false }, { "Id": "7bc9d9d1cedc4f8c876f39071957e92a", "Name": "sheetName", "Description": "string", "UsingDefaultValue": false, "Level": 2, "UseLevels": false, "KeepListStructure": false }, { "Id": "cce8d1bf748e4e75871e3facf8d34c36", "Name": "readAsStrings", "Description": "bool\nDefault value : false", "UsingDefaultValue": true, "Level": 2, "UseLevels": false, "KeepListStructure": false }, { "Id": "417a8c34b029420c9d896751d7f1947f", "Name": "showExcel", "Description": "bool\nDefault value : true", "UsingDefaultValue": true, "Level": 2, "UseLevels": false, "KeepListStructure": false } ], "Outputs": [ { "Id": "91537318aa13445aa9168c17f09d74d9", "Name": "var[][]", "Description": "var[][]", "UsingDefaultValue": false, "Level": 2, "UseLevels": false, "KeepListStructure": false } ], "FunctionSignature": "DSOffice.Data.ImportExcel@var,string,bool,bool", "Replication": "Auto", "Description": "Data.ImportExcel (file: var, sheetName: string, readAsStrings: bool = false, showExcel: bool = true): var[][]" }, { "ConcreteType": "DSRevitNodesUI.FamilyTypes, DSRevitNodesUI", "SelectedIndex": 4, "SelectedString": "AN_PLANO_90X60:AN_PIE DE PLANO", "Id": "7153d68f970a4ddd9b0d3099f0d262ad", "NodeType": "ExtensionNode", "Inputs": [], "Outputs": [ { "Id": "80e5ef3fe9214ffab66efe70a314371a", "Name": "Family Type", "Description": "The selected Family Type", "UsingDefaultValue": false, "Level": 2, "UseLevels": false, "KeepListStructure": false } ], "Replication": "Disabled", "Description": "All family types available in the document." }, { "ConcreteType": "CoreNodeModels.Input.StringInput, CoreNodeModels", "Id": "e96f85cc36334ecaaddba440131bf7c9", "NodeType": "StringInputNode", "Inputs": [], "Outputs": [ { "Id": "5e6cfee3577d43eabd8699f8b87379f0", "Name": "", "Description": "String", "UsingDefaultValue": false, "Level": 2, "UseLevels": false, "KeepListStructure": false } ], "Replication": "Disabled", "Description": "Creates a string", "InputValue": "Hojas" }, { "ConcreteType": "CoreNodeModels.Input.FileObject, CoreNodeModels", "Id": "b8344f24f30c4658af5831d90fc59e45", "NodeType": "ExtensionNode", "Inputs": [ { "Id": "b6dd668e3ed54171b3392792a02846f1", "Name": "path", "Description": "Path to the file.", "UsingDefaultValue": false, "Level": 2, "UseLevels": false, "KeepListStructure": false } ], "Outputs": [ { "Id": "985c22f6579b47c69f4a162f0d5cdfcf", "Name": "file", "Description": "File object", "UsingDefaultValue": false, "Level": 2, "UseLevels": false, "KeepListStructure": false } ], "Replication": "Disabled", "Description": "Creates a file object from the given path" } ], "Connectors": [ { "Start": "272a2fe57bfd452cbaa690019a961ad1", "End": "b6dd668e3ed54171b3392792a02846f1", "Id": "e7dd1b20a7084800b12c7e691a550cbb", "IsHidden": "False" }, { "Start": "91537318aa13445aa9168c17f09d74d9", "End": "0fe7e02e61554f43b8b8d403da316b28", "Id": "a8897cbca50342f0afd626ea4283632d", "IsHidden": "False" }, { "Start": "80e5ef3fe9214ffab66efe70a314371a", "End": "5a93b68350e14495b9dd40ce03f47211", "Id": "6a472476d9fe489e934bd8d38097befe", "IsHidden": "False" }, { "Start": "5e6cfee3577d43eabd8699f8b87379f0", "End": "7bc9d9d1cedc4f8c876f39071957e92a", "Id": "6eac21a13b764e2786344d52e8047323", "IsHidden": "False" }, { "Start": "985c22f6579b47c69f4a162f0d5cdfcf", "End": "8fec46a5da4f4b68aad8e589940c82b1", "Id": "60f8522dd1fa40f5994afda4f5aeb06e", "IsHidden": "False" } ], "Dependencies": [], "NodeLibraryDependencies": [ { "Name": "RUM_template_sheets.xlsx", "ReferenceType": "External", "Nodes": [ "23caef1ad90249449f1a983a7a2f198b" ] } ], "EnableLegacyPolyCurveBehavior": true, "Thumbnail": "", "GraphDocumentationURL": null, "ExtensionWorkspaceData": [ { "ExtensionGuid": "28992e1d-abb9-417f-8b1b-05e053bee670", "Name": "Properties", "Version": "3.3", "Data": {} }, { "ExtensionGuid": "DFBD9CC0-DB40-457A-939E-8C8555555A9D", "Name": "Generative Design", "Version": "8.2", "Data": {} } ], "Author": "", "Linting": { "activeLinter": "None", "activeLinterId": "7b75fb44-43fd-4631-a878-29f4d5d8399a", "warningCount": 0, "errorCount": 0 }, "Bindings": [], "View": { "Dynamo": { "ScaleFactor": 1.0, "HasRunWithoutCrash": true, "IsVisibleInDynamoLibrary": true, "Version": "3.3.0.6316", "RunType": "Manual", "RunPeriod": "1000" }, "Camera": { "Name": "_Background Preview", "EyeX": -18.535983562469482, "EyeY": 23.32427215576172, "EyeZ": 49.833648681640625, "LookX": 12.0, "LookY": -13.0, "LookZ": -58.0, "UpX": 0.0, "UpY": 1.0, "UpZ": 0.0 }, "ConnectorPins": [], "NodeViews": [ { "Id": "0d18990eac7c48e9a529f3d1f63cb71e", "Name": "RÜM", "IsSetAsInput": false, "IsSetAsOutput": false, "Excluded": false, "ShowGeometry": true, "X": 862.6165069225223, "Y": 530.3814713301231 }, { "Id": "23caef1ad90249449f1a983a7a2f198b", "Name": "File Path", "IsSetAsInput": false, "IsSetAsOutput": false, "Excluded": false, "ShowGeometry": true, "X": -10.506607086610131, "Y": 271.8683854915066 }, { "Id": "ec4dd2d362e04ab98cd7babaeb3c11cc", "Name": "Data.ImportExcel", "IsSetAsInput": false, "IsSetAsOutput": false, "Excluded": false, "ShowGeometry": true, "X": 514.6944472774601, "Y": 387.99581750267964 }, { "Id": "7153d68f970a4ddd9b0d3099f0d262ad", "Name": "Familia de Plano/Solapa", "IsSetAsInput": false, "IsSetAsOutput": false, "Excluded": false, "ShowGeometry": true, "X": 10.564313048078589, "Y": 564.7072190868648 }, { "Id": "e96f85cc36334ecaaddba440131bf7c9", "Name": "String", "IsSetAsInput": false, "IsSetAsOutput": false, "Excluded": false, "ShowGeometry": true, "X": 105.56461860239096, "Y": 419.78534754212455 }, { "Id": "b8344f24f30c4658af5831d90fc59e45", "Name": "File From Path", "IsSetAsInput": false, "IsSetAsOutput": false, "Excluded": false, "ShowGeometry": true, "X": 277.8292992058231, "Y": 272.72604025597525 } ], "Annotations": [], "X": -21.441106418272568, "Y": 46.3787850021107, "Zoom": 0.6574300927052009 } }