Plugin BIM pyRevit 22 Mar 2026 12 min lectura

🏗️ Cubicación Automática en Revit con pyRevit y Python

Calcular cubicaciones en Revit siempre fue un proceso que me molestó. Exportabas a Excel, filtrabas a mano, y si el modelo cambiaba — cosa que siempre pasa — volvías a empezar. En este artículo te muestro cómo construí un script que resuelve eso en 10 segundos.

2 h

Proceso manual

10 s

Con el plugin

720×

Más rápido

El Problema Real

El flujo clásico de cubicación en cualquier oficina de proyectos se ve más o menos así: abres una vista en Revit, exportas una planilla de cantidades, la abres en Excel, filtras por tipo de material, calculas los pesos con fórmulas que alguien hizo hace tres años y nadie sabe si son correctas, y al final generas un reporte.

El problema no es que sea lento — aunque lo es. El problema es que el modelo y la planilla se desconectan desde el momento en que exportas. Cualquier cambio en el modelo requiere repetir todo el proceso desde cero.

⚠️ El costo real

En un proyecto de mediana envergadura, un ciclo completo de cubicación manual puede tomar entre 2 y 4 horas. Si el modelo cambia dos veces a la semana — lo cual es normal — estás gastando casi un día completo en extraer datos que ya existen en el modelo. La solución no es trabajar más rápido. Es no hacer ese trabajo en absoluto.

Qué Hace el Script

El script extrae directamente desde el modelo Revit, a través de su API, toda la información necesaria para generar una cubicación completa de acero y hormigón. Sin exportar nada, sin abrir Excel, sin fórmulas manuales.

  • Detecta el material automáticamente — lee la propiedad de material de cada elemento estructural y determina si es acero u hormigón.
  • Extrae el volumen real — usa el parámetro de volumen calculado por Revit, no una estimación.
  • Calcula el peso con densidad real — aplica 7.850 kg/m³ para acero y 2.400 kg/m³ para hormigón por defecto, con valores configurables.
  • Clasifica el acero por Nominal Weight — Liviana, Media, Pesada y Extrapesada según el peso lineal del perfil.
  • Agrega factor de conexiones — un porcentaje configurable que se suma al peso total de acero.
  • Muestra el reporte en consola — tablas interactivas en pyRevit, sin abrir ningún archivo externo.

Prerrequisitos

  • Revit 2022 o superior — el script fue probado en 2024 y 2025.
  • pyRevit 4.8+ — descárgalo desde github.com/eirannejad/pyRevit.
  • Un modelo estructural activo — con elementos de categoría Framing o Columns con materiales asignados.
💡 Nota

No necesitas saber Python para usar este script. El tutorial te explica dónde poner cada archivo. Si quieres entender cómo funciona el código, las secciones de explicación son para ti.

Paso a Paso

1
Crea la estructura de carpetas
pyRevit convierte carpetas en botones automáticamente. La estructura de nombres es el único "framework" que necesitas entender.
AG_Tools.extension/ ← tu extensión principal
  Cubicación.tab/ ← pestaña en el ribbon de Revit
    Reportes.panel/ ← panel de botones
      Cubicacion.pushbutton/ ← el botón
        script.py ← tu código va aquí
        icon.png ← ícono 32×32 px (opcional)
2
Crea el archivo script.py
Dentro de la carpeta Cubicacion.pushbutton/, crea un archivo llamado exactamente script.py y pega el código completo de la siguiente sección.

Código Completo

Script listo para producción. Cópialo directamente en tu script.py:

Python · pyRevit
# -*- coding: utf-8 -*-
# Cubicación Automática — Andrés Gallo P.
# atelijudesign.com

import clr
clr.AddReference('RevitAPI')
clr.AddReference('RevitAPIUI')

from Autodesk.Revit.DB import (
    FilteredElementCollector,
    BuiltInCategory,
    BuiltInParameter,
    UnitUtils,
    UnitTypeId
)
from pyrevit import revit, DB, forms, script

# ─── CONFIGURACIÓN ──────────────────────────────────────────────
DENSIDAD_ACERO    = 7850   # kg/m³
DENSIDAD_HORMIGON = 2400   # kg/m³
FACTOR_CONEXIONES = 0.05   # 5% por defecto — ajústalo según proyecto

# Clasificación Nominal Weight (kg/m)
CLASES_NW = [
    (0,   20,  "Liviana"),
    (20,  40,  "Media"),
    (40,  80,  "Pesada"),
    (80,  9999, "Extrapesada"),
]

# ─── FUNCIONES AUXILIARES ────────────────────────────────────────
def m3_a_kg(volumen_m3, densidad):
    """Convierte volumen en m³ a kilogramos."""
    return volumen_m3 * densidad

def clasificar_nw(peso_lineal_kg_m):
    """Clasifica un perfil por Nominal Weight según su peso lineal."""
    for min_nw, max_nw, nombre in CLASES_NW:
        if min_nw <= peso_lineal_kg_m < max_nw:
            return nombre
    return "Sin clasificar"

def obtener_volumen_m3(elemento):
    """Extrae el volumen del elemento en m³ desde la API de Revit."""
    try:
        param = elemento.get_Parameter(BuiltInParameter.HOST_VOLUME_COMPUTED)
        if param and param.HasValue:
            # Revit almacena en pies³ — convertimos a m³
            return UnitUtils.ConvertFromInternalUnits(
                param.AsDouble(),
                UnitTypeId.CubicMeters
            )
    except:
        pass
    return 0.0

def obtener_nombre_material(elemento):
    """Obtiene el nombre del material estructural del elemento."""
    try:
        param = elemento.get_Parameter(BuiltInParameter.STRUCTURAL_MATERIAL_PARAM)
        if param and param.HasValue:
            mat_id = param.AsElementId()
            material = elemento.Document.GetElement(mat_id)
            if material:
                return material.Name.upper()
    except:
        pass
    return ""

def es_acero(nombre_material):
    """Determina si el material es acero por palabras clave."""
    keywords = ["ACERO", "STEEL", "A36", "A572", "A992", "ER70", "METAL"]
    return any(k in nombre_material for k in keywords)

def es_hormigon(nombre_material):
    """Determina si el material es hormigón por palabras clave."""
    keywords = ["HORMIGON", "HORMIGÓN", "CONCRETE", "HA", "H20", "H25", "H30"]
    return any(k in nombre_material for k in keywords)

# ─── RECOLECCIÓN DE ELEMENTOS ────────────────────────────────────
doc = revit.doc

categorias = [
    BuiltInCategory.OST_StructuralFraming,    # vigas, diagonales
    BuiltInCategory.OST_StructuralColumns,    # columnas
    BuiltInCategory.OST_StructuralFoundation  # fundaciones
]

elementos = []
for cat in categorias:
    collector = FilteredElementCollector(doc)\
        .OfCategory(cat)\
        .WhereElementIsNotElementType()
    elementos.extend(list(collector))

# ─── PROCESAMIENTO ───────────────────────────────────────────────
resultado_acero   = {}   # {clase_nw: [pesos]}
total_hormigon_m3 = 0.0
elementos_sin_mat = 0

for elem in elementos:
    volumen = obtener_volumen_m3(elem)
    if volumen <= 0:
        continue

    nombre_mat = obtener_nombre_material(elem)

    if es_acero(nombre_mat):
        # Peso lineal para clasificación NW
        try:
            param_largo = elem.get_Parameter(BuiltInParameter.STRUCTURAL_FRAME_CUT_LENGTH)
            largo_m = UnitUtils.ConvertFromInternalUnits(
                param_largo.AsDouble(), UnitTypeId.Meters
            ) if param_largo and param_largo.HasValue else 1.0
        except:
            largo_m = 1.0

        peso_total_kg = m3_a_kg(volumen, DENSIDAD_ACERO)
        peso_lineal   = peso_total_kg / largo_m if largo_m > 0 else 0
        clase_nw      = clasificar_nw(peso_lineal)

        if clase_nw not in resultado_acero:
            resultado_acero[clase_nw] = []
        resultado_acero[clase_nw].append(peso_total_kg)

    elif es_hormigon(nombre_mat):
        total_hormigon_m3 += volumen

    else:
        elementos_sin_mat += 1

# ─── CÁLCULO DE TOTALES ──────────────────────────────────────────
total_acero_kg   = sum(sum(p) for p in resultado_acero.values())
total_con_conx   = total_acero_kg * (1 + FACTOR_CONEXIONES)
total_hormigon_kg = m3_a_kg(total_hormigon_m3, DENSIDAD_HORMIGON)

# ─── GENERACIÓN DEL REPORTE ──────────────────────────────────────
output = script.get_output()
output.close_others()
output.set_title("Cubicación Estructural — {}".format(doc.Title))

output.print_md("# 📊 Reporte de Cubicación Estructural")
output.print_md("**Proyecto:** {}".format(doc.Title))
output.print_md("**Elementos procesados:** {}".format(len(elementos)))
if elementos_sin_mat > 0:
    output.print_md("⚠️ **Elementos sin material asignado:** {}".format(elementos_sin_mat))

# Tabla acero por clase NW
output.print_md("---")
output.print_md("## 🔩 Acero Estructural")

tabla_acero = [["Clase NW", "N° Elementos", "Peso (kg)", "Peso (ton)"]]
for clase in ["Liviana", "Media", "Pesada", "Extrapesada"]:
    if clase in resultado_acero:
        pesos = resultado_acero[clase]
        total = sum(pesos)
        tabla_acero.append([
            clase,
            str(len(pesos)),
            "{:,.1f}".format(total),
            "{:,.2f}".format(total / 1000)
        ])

output.print_table(
    table_data=tabla_acero[1:],
    title="Clasificación por Nominal Weight",
    columns=tabla_acero[0]
)

output.print_md("**Subtotal acero:** {:,.1f} kg  ({:,.2f} ton)".format(
    total_acero_kg, total_acero_kg / 1000))
output.print_md("**Factor conexiones ({:.0f}%):** + {:,.1f} kg".format(
    FACTOR_CONEXIONES * 100, total_acero_kg * FACTOR_CONEXIONES))
output.print_md("### ✅ Total acero c/conexiones: {:,.1f} kg  ({:,.2f} ton)".format(
    total_con_conx, total_con_conx / 1000))

# Tabla hormigón
output.print_md("---")
output.print_md("## 🏗️ Hormigón Estructural")
output.print_md("**Volumen total:** {:,.2f} m³".format(total_hormigon_m3))
output.print_md("**Peso total:** {:,.1f} kg  ({:,.2f} ton)".format(
    total_hormigon_kg, total_hormigon_kg / 1000))

output.print_md("---")
output.print_md("*Generado con pyRevit · atelijudesign.com*")
3
Configura los valores de tu proyecto
Al inicio del script hay una sección de configuración. Ajusta el porcentaje de conexiones según las especificaciones de tu proyecto. El 5% es un valor estándar conservador.
Python · Configuración
# ─── CONFIGURACIÓN ──────────────────────────────
DENSIDAD_ACERO    = 7850   # kg/m³ — no cambiar
DENSIDAD_HORMIGON = 2400   # kg/m³ — ajusta si tu especificación difiere
FACTOR_CONEXIONES = 0.05   # 5% estándar
                           # 0.08 = 8% para proyectos industriales
                           # 0.12 = 12% para estructuras complejas
4
Agrega tu extensión a pyRevit
Abre Revit → pestaña pyRevit → Settings → Custom Extensions. Agrega la ruta a tu carpeta AG_Tools.extension. Luego haz clic en Reload.
5
Abre un modelo y ejecuta el script
Con un modelo estructural abierto, busca la pestaña "Cubicación" en el ribbon. Haz clic en el botón "Cubicacion". El reporte aparece en la consola de pyRevit en segundos.

Resultado Esperado

Si el modelo tiene materiales correctamente asignados, el reporte se ve así en la consola de pyRevit:

// Output consola pyRevit

📊 Reporte de Cubicación Estructural
Proyecto: Proyecto_Arqueros_Rev3
Elementos procesados: 847

─────────────────────────────────────────────
🔩 Acero Estructural — Clasificación por NW
─────────────────────────────────────────────
Clase NW        N° Elementos   Peso (kg)    Peso (ton)
Liviana              124         8.420,3        8,42
Media                298        42.180,7       42,18
Pesada               201        89.340,2       89,34
Extrapesada           48        38.920,1       38,92

Subtotal acero:          178.861,3 kg  (178,86 ton)
Factor conexiones (5%):  +  8.943,1 kg
✅ Total c/conexiones:   187.804,4 kg  (187,80 ton)

─────────────────────────────────────────────
🏗️ Hormigón Estructural
─────────────────────────────────────────────
Volumen total:    1.240,50 m³
Peso total:     2.977.200 kg  (2.977,20 ton)

Cómo Funciona el Código

Detección de material

La función obtener_nombre_material() lee el parámetro STRUCTURAL_MATERIAL_PARAM de cada elemento, obtiene el ID del material asignado y consulta su nombre en el documento. Las funciones es_acero() y es_hormigon() buscan palabras clave en ese nombre — por eso es importante que tus materiales en Revit tengan nombres descriptivos.

Extracción del volumen

Revit calcula el volumen real de cada elemento estructural y lo almacena en el parámetro HOST_VOLUME_COMPUTED. La API lo entrega en pies cúbicos — la conversión a m³ la hace UnitUtils.ConvertFromInternalUnits(). Este valor ya considera la geometría exacta del perfil, no una aproximación.

✅ Por qué esto es más preciso que Excel

El volumen calculado por Revit considera los cortes, uniones y modificaciones de geometría que aplicaste al modelo. Una tabla de Excel con fórmulas de largo × área de sección no hace eso. La diferencia puede ser de hasta 3–5% en estructuras con muchas uniones.

Clasificación Nominal Weight

El script calcula el peso lineal de cada elemento dividiendo el peso total por el largo de corte estructural. Con ese valor clasifica el perfil en una de las cuatro categorías estándar. Esto es útil para separar las cotizaciones por tipo de perfil, que en Chile generalmente tienen precios distintos por categoría NW.

Antes vs. Después: La Diferencia Real

Tiempo invertido por tarea en el flujo manual versus el flujo automatizado:

Flujo Manual (~2 horas)

  • • Exportar schedule a Excel
  • • Filtrar y clasificar manualmente
  • • Buscar densidades de materiales
  • • Calcular pesos con fórmulas
  • • Repetir si el modelo cambia

Con el Plugin (~10 segundos)

  • • Presionar el botón en Revit
  • • Revisar tablas en la consola
  • • Listo ✓

Troubleshooting

El botón no aparece en el ribbon

Verifica que el nombre de tu carpeta termine exactamente en .extension, .tab, .panel y .pushbutton. Un espacio de más o una mayúscula incorrecta hace que pyRevit no lo reconozca. Luego haz Reload.

Elementos sin material asignado

Si el reporte muestra elementos sin material, abre el modelo, selecciona esos elementos y asigna el material estructural correcto en las propiedades. El script avisa cuántos tienen ese problema.

Error de conversión de unidades (Revit < 2022)

En versiones anteriores a 2022, UnitTypeId no existe. Reemplaza la conversión por DisplayUnitType.DUT_CUBIC_METERS, que es el equivalente en versiones más antiguas.

Conclusiones Clave

  • El modelo es la fuente de verdad. Si el modelo cambia, el reporte también. Sin exportar nada, sin versiones desactualizadas.
  • La detección por palabras clave es frágil si los materiales no tienen nombres descriptivos. Estandariza los nombres de materiales en tu plantilla de proyecto.
  • pyRevit hace que la automatización sea accesible. Sin compilar DLLs ni configurar Visual Studio. Solo Python y la estructura de carpetas correcta.
  • Cuando el proceso tarda 10 segundos, puedes iterar. Cuando tarda 2 horas, evitas hacerlo. Esa diferencia cambia cómo diseñas.

Comparte este artículo

Volver al Blog ¿Preguntas? Contáctame