ANTORR Academia / Diseño Computacional BIM
Automatización BIM · Dynamo · Python · Geotecnia

MSE Full Height en Revit 2026:
distribución y ajuste de paneles con Dynamo y Python

Modelar un muro de contención convencional en Revit es trivial: un muro de sistema, altura fija, geometría ortogonal. Modelar un sistema de paneles MSE Full Height a lo largo de un trazado vial curvo de 400 metros, con alturas variables por topografía, juntas de dilatación de 2 cm y la restricción de que cada panel ocupa exactamente el espacio disponible sin solaparse ni dejar huecos — eso es un problema de geometría computacional que ningún nodo nativo de Dynamo resuelve directamente.

Este artículo documenta el sistema completo que desarrollé para ANTORR BIM: dos scripts Dynamo con ocho nodos Python, aproximadamente 620 líneas de código, y una familia paramétrica con doce parámetros de instancia. El resultado es un flujo de trabajo reproducible que toma una Model Line en Revit como input y produce paneles correctamente posicionados, orientados y parametrizados a lo largo de cualquier trazado recto o curvo.

Muro MSE Full Height: distribución de paneles prefabricados a lo largo de un trazado curvo con alturas variables Muro MSE Full Height: distribución de paneles prefabricados a lo largo de un trazado curvo con alturas variables
Figura 1. Sección típica de muro MSE Full Height: paneles prefabricados de altura variable distribuidos sobre un trazado con curvatura. La geometría de cuerda-arco introduce discrepancias entre ancho nominal del panel y ancho proyectado en planta.
Demostración del sistema. Ejecución de los scripts MSE_Distribucion.dyn y MSE_AjustePaneles.dyn en Autodesk Revit 2026. Distribución automática de paneles Full Height sobre un trazado curvo. (Video en loop / GIF)

Marco teórico: MSE, BIM y diseño computacional

Los muros mecánicamente estabilizados (MSE) son estructuras de contención compuestas por un relleno granular reforzado mediante elementos metálicos o geosintéticos y un revestimiento frontal de paneles prefabricados. Según la Federal Highway Administration [1], estos sistemas constituyen la tecnología geotécnica de contención más ampliamente adoptada en infraestructura vial moderna por su eficiencia constructiva y tolerancia a asentamientos diferenciales.

La tipología Full Height se distingue de los sistemas segmentales (paneles cruciformes apilados) en que cada panel abarca la totalidad de la altura libre del muro en un punto dado. Esta característica introduce una complejidad geométrica específica: las dimensiones del panel no son constantes, sino función del perfil topográfico del terreno. En un trazado recto con talud uniforme, esto se reduce a una interpolación lineal. En un trazado curvo con talud variable, el problema requiere algoritmos de distribución óptima, geometría de cuerda-arco e interpolación multi-tramo.

La norma ISO 19650 [2] establece el marco de gestión de información BIM durante el ciclo de vida del activo construido. En proyectos de infraestructura geotécnica, la aplicación de BIM presenta desafíos derivados de la naturaleza paramétrica de elementos como los paneles MSE. Autodesk Revit no incorpora herramientas nativas para la distribución automática de familias sobre curvas arbitrarias, lo que justifica el desarrollo de soluciones programadas mediante Dynamo y la API de Revit.

Dynamo es el entorno de programación visual integrado en Revit que combina programación nodal con scripts Python a través del motor CPython3. Su acceso a la API transaccional de Revit mediante TransactionManager permite operaciones de creación, transformación y parametrización de elementos que los nodos nativos no cubren. El sistema documentado aquí implementa cuatro algoritmos principales: distribución óptima con paneles de ajuste, interpolación lineal por tramos de alturas, geometría cuerda-arco con tabla de parametrización de 1000 puntos, y junta adaptativa en función de curvatura del trazado.

"El problema de distribuir paneles MSE Full Height en curva no es un problema de Revit — es un problema de geometría computacional. Dynamo y Python son el puente entre el trazado geométrico y el modelo BIM."

Prerrequisitos: familia paramétrica MSE

Antes de ejecutar cualquier script, el modelo Revit debe contener una familia de categoría Modelos Genéricos (Generic Models) con exactamente doce parámetros de instancia de nombres case-sensitive. Estos parámetros son el contrato entre los scripts Dynamo y el modelo — cualquier discrepancia de nombre o tipo de dato silencia la escritura sin generar errores visibles.

Parámetro Tipo Dato Nodo que Escribe Descripción
1LargoDouble (Long.)D-6.1 / A-3.1Ancho horizontal del panel — cuerda en tramos curvos
2AlturaDouble (Long.)D-6.2 / A-3.1Altura Full Height interpolada por topografía
3ID_TramoStringD-6.3Identificador del tramo: T-01, T-02…
4Posicion_EnTramoDoubleD-6.4Distancia del centro del panel al inicio del tramo
5Total_PanelesStringD-6.5ID del tramo (para schedules y filtros)
6EsPanelAjusteInteger (0/1)D-6.6 / A-3.11 = panel de ajuste, 0 = panel estándar
7Angulo_RotacionDoubleD-6.7Ángulo tangente al trazado en la posición del panel
8BloqueadoInteger (0/1)D-6.8Flag de bloqueo para edición manual posterior
9Viga CoronaInteger (0/1)D-6.9Número secuencial del panel en el tramo
10Coord_XDouble (Long.)D-6.10Coordenada X absoluta (sistema Revit en pies)
11Coord_YDouble (Long.)D-6.10Coordenada Y absoluta
12Radio_CurvaDouble (Long.)D-6.10Radio de curvatura del trazado en la posición del panel

Convención de prefijos: Los nodos del script MSE_Distribucion se identifican con prefijo D- (ej: D-3.1 Distribucion) y los del script MSE_AjustePaneles con A- (ej: A-3.1 AjusteCuerda), evitando ambigüedad al referenciar nodos entre scripts.

Configuración del entorno

Siete ajustes previos determinan si los scripts funcionarán correctamente. El más crítico es Geometry Scaling: Extra Large — sin él, Dynamo trunca las coordenadas de trazados superiores a 100 metros y los paneles aparecen desplazados varios kilómetros. El segundo en importancia es limpiar el Element Binding antes de cada nueva ejecución; sin este paso, una re-ejecución del script sobre el mismo tramo mueve los paneles de otros tramos ya colocados.

ConfiguraciónValorUbicaciónJustificación
1Geometry ScalingExtra LargeDynamo → SettingsTrazados >100 m producen truncamiento sin esta config
2Element BindingLimpiar por tramoDynamo → SettingsSin limpieza, re-ejecuciones afectan tramos anteriores
3Motor PythonCPython3Nodo Python → EngineScripts usan clr.AddReference para API de Revit
4Modo EjecuciónManualBarra DynamoEvita ejecución involuntaria al modificar parámetros
5Trazado1 Model Line / tramoRevit → Model LineNo usar Detail Line — no tiene geometría accesible desde API
6Nivel BaseMenor elevaciónRevit → LevelsD-5.1 Colocar selecciona el nivel más bajo automáticamente
7Familia MSECargada en proyectoRevit → Load FamilyDebe aparecer en el selector D-1.6 Familia
Vista general del canvas del script MSE_Distribucion.dyn con los 8 grupos funcionales en Dynamo
Figura 2. Vista general del canvas del script MSE_Distribucion.dyn. Los 8 grupos funcionales se identifican por color de fondo: rosado (Inputs), verde (Geometría, Distribución, Colocación), oscuro (Extractores), amarillo-verde (Alturas, Posiciones), gris claro (Parámetros) y naranja (Rotación).

Script MSE_Distribucion.dyn — 61 nodos, 8 grupos

MSE_Distribucion es el script principal. Toma una Model Line como trazado y produce instancias de la familia MSE posicionadas, orientadas y parametrizadas a lo largo de toda su longitud. Los 61 nodos se organizan en ocho grupos visuales en el canvas de Dynamo, cada uno con una responsabilidad específica y un color de fondo diferenciador.

Grupo 1: Inputs — Parámetros de entrada del usuario · Fondo rosado · Todos marcados Is Input para Dynamo Player

Doce nodos configurables por el usuario, los únicos que requieren intervención manual antes de ejecutar el script. En Dynamo Player aparecen como campos editables. El nodo más relevante es D-1.10 ParseInflexion, el único nodo Python del grupo: convierte dos cadenas CSV (distancias y alturas de inflexión topográfica) en listas numéricas que alimentan el algoritmo de interpolación de alturas.

D-1.1

Trazado

Select Model Element
InputON
Conecta aD-2.1 Curva

D-1.2

Altura

Number Slider
Rango0.1 – 4.0 m
Step0.01
Default2.40 m

D-1.3

Junta

Number Slider
Rango0 – 0.05 m
Step0.005
Default0.02 m

D-1.4

PanelAlInicio

Boolean
DefaultFalse
EfectoPosiciona ajuste

D-1.5

IDTramo

String Input
DefaultT-01
Escribe enID_Tramo

D-1.6

Familia

Family Types
InputON
Conecta aD-5.1 Colocar

D-1.7

Ancho

Number Slider
Rango0.5 – 1.25 m
Default1.25 m

D-1.8

DistInflexion

String Input
InputON
Default0, 8, 16
Conecta aD-1.10 → IN[0]

D-1.9

AltInflexion

String Input
InputON
Default2.40, 3.00, 1.80
Conecta aD-1.10 → IN[1]

D-1.10

ParseInflexion

Python Script · CPython3
Entradas2 (IN[0], IN[1])
Salida[dist[], alt[]]
Líneas3

D-1.11

DistLista

List.GetItemAtIndex
Index0
Sale haciaD-3B → IN[2]

D-1.12

AltLista

List.GetItemAtIndex
Index1
Sale haciaD-3B → IN[3]
Grupo 2: Geometría — Extrae la curva y longitud del trazado · Fondo verde

Tres nodos que convierten el elemento modelo seleccionado en datos geométricos utilizables: la curva Dynamo y su longitud en metros. La longitud alimenta simultáneamente al algoritmo de distribución (D-3.1), al algoritmo de alturas (D-3B) y al nodo Watch informativo.

D-2.1

Curva

CurveElement.Curve
EntradaD-1.1 Trazado
SalidaCurva geométrica

D-2.2

Longitud

Curve.Length
SalidaDouble (metros)
Conecta aD-3.1, D-3B, Watch

D-CB

Code Block

Code Block
Contenidod;
FunciónReenvía curva → D-6.10
Grupo 3: Distribución — Algoritmo de distribución óptima de paneles · Fondo verde

Un único nodo Python de 70 líneas que contiene la lógica de distribución. Calcula N_std = floor((L + junta) / (ancho + junta)), determina si existe residuo, y decide si colocar 0, 1 o 2 paneles de ajuste. El panel de ajuste nunca es menor que AMIN = 0.50 m y su ancho se redondea a múltiplos de STEP = 0.05 m. La salida es una lista de tres sublistas: [anchos, N_total, es_ajuste].

Extractores G3 — 3 nodos List.GetItemAtIndex que desempaquetan la salida de D-3.1 · Fondo oscuro

D-E1

Anchos

List.GetItemAtIndex
Index0
Sale haciaD-3B, D-4.1, D-6.1

D-E2

NTotal

List.GetItemAtIndex
Index1
Sale haciaRange, D-6.7

D-E3

EsAjuste

List.GetItemAtIndex
Index2
Sale haciaD-6.8 Bloqueado
Grupo 3B: Alturas — Interpolación de alturas por puntos de inflexión con suavizado ±5 cm · Fondo amarillo-verde

El nodo D-3B interpola la altura de cada panel entre los puntos de inflexión topográfica definidos por el usuario en D-1.8/D-1.9. El algoritmo calcula el centro acumulado de cada panel, localiza el segmento de inflexión correspondiente, interpola linealmente y aplica un suavizado: la diferencia máxima de altura entre paneles consecutivos es de ±5 cm (STEP = 0.05 m), lo que garantiza transiciones constructivamente viables entre paneles de distintas alturas.

D-3B

Alturas

Python Script · CPython3
Entradas6 (anchos, junta, dist_infl, alt_infl, L, alt_default)
SalidaLista de alturas [m]
Líneas52
ConstantesHMIN=0.10, STEP=0.05
Grupo 4: Posiciones y Ángulos — Coordenadas 2D y ángulo tangente de cada panel · Fondo amarillo-verde

D-4.1 CoordenadasPanel itera sobre la lista de anchos, calcula el punto de inserción de cada panel como PointAtSegmentLength(centro_acumulado) y el ángulo tangente como atan2(t.Y, t.X) donde t es el vector tangente en ese punto. Si la API de Revit no puede calcular la tangente (casos degenerados en geometría compleja), un fallback por diferencia finita entre dos puntos cercanos garantiza un ángulo de rotación razonable.

D-4.1

CoordenadasPanel

Python Script · CPython3
Entradas3 (anchos, junta, curva)
Salida[puntos, distancias, ángulos]
Líneas43

D-4.E1 / D-4.E2 / D-4.E3

Extractores G4

List.GetItemAtIndex
Index 0Puntos → D-5.1
Index 1Distancias → D-6.4
Index 2Ángulos → D-7.1
Grupo 5: Colocación — Creación de instancias NewFamilyInstance en el modelo Revit · Fondo verde

D-5.1 Colocar es el único nodo que escribe geometría en el modelo. Selecciona automáticamente el nivel de menor elevación del proyecto, convierte las coordenadas Dynamo (metros) a pies con el factor FT = 3.28083989501312, y crea cada instancia con doc.Create.NewFamilyInstance(xyz, fam_type, level, StructuralType.NonStructural) dentro de una transacción. La salida es la lista de instancias, que se distribuye simultáneamente hacia todos los nodos del Grupo 6 y el nodo D-7.1 Rotar.

D-5.1

Colocar

Python Script · CPython3
Entradas2 (fam_type, puntos)
SalidaInstancias Revit (DSType)
Líneas37
Sale haciaG6 (×10), D-7.1
Grupo 6: Parámetros — 9 nodos SetParameterByName + 1 Python para coordenadas · Fondo gris claro

Diez nodos que escriben los doce parámetros de la familia. Los nueve primeros son nodos nativos Element.SetParameterByName; cada uno requiere un nodo String auxiliar con el nombre exacto del parámetro. El nodo D-6.10 Coordenadas es un script Python que escribe Coord_X, Coord_Y (desde Location.Point) y Radio_Curva (desde 1.0 / curva.CurvatureAtParameter(0.5)) en una sola transacción, devolviendo las instancias como passthrough para mantener la cadena de dependencias.

D-6.1

Largo

SetParameterByName
ParámetroLargo
DatoD-E1 Anchos

D-6.2

Altura

SetParameterByName
ParámetroAltura
DatoD-3B Alturas

D-6.3

ID_Tramo

SetParameterByName
ParámetroID_Tramo
DatoD-1.5 IDTramo

D-6.4

Posicion_EnTramo

SetParameterByName
ParámetroPosicion_EnTramo
DatoD-4.E2 Distancias

D-6.5

Total_Paneles

SetParameterByName
ParámetroTotal_Paneles
DatoD-1.5 IDTramo

D-6.6

EsPanelAjuste

SetParameterByName
ParámetroEsPanelAjuste
DatoD-E3 EsAjuste

D-6.7

Angulo_Rotacion

SetParameterByName
ParámetroAngulo_Rotacion
DatoD-4.E3 Ángulos

D-6.8

Bloqueado

SetParameterByName
ParámetroBloqueado
Dato0 (fijo)

D-6.9

Viga Corona

SetParameterByName
ParámetroViga Corona
DatoRange(0, N-1)

D-6.10

Coordenadas

Python Script · CPython3
EscribeCoord_X, Coord_Y, Radio_Curva
Líneas33
SalidaPassthrough → D-7.1

Automatización BIM avanzada

¿Tu proyecto de infraestructura requiere automatización paramétrica en Revit?

Desarrollamos scripts Dynamo y familias paramétricas a medida para proyectos de infraestructura vial, geotecnia y obras civiles. Desde distribución de elementos sobre trazados hasta extracción automática de cantidades por tramo.

Grupo 7: Rotación — Rotación absoluta por ángulo tangente · Is Output: ON · Fondo naranja

D-7.1 Rotar aplica una rotación absoluta, no relativa. El algoritmo calcula ang_diff = radians(deseado) - loc.Rotation, normaliza a [−π, π] y llama a ElementTransformUtils.RotateElement solo si |ang_diff| > 0.001 radianes. El doc.Regenerate() previo al bucle es crítico: sin él, el nodo lee posiciones pre-transacción y los ángulos calculados con las coordenadas reales de D-5.1 resultan inconsistentes.

D-7.1

Rotar

Python Script · CPython3
Entradas2 (instancias, ángulos)
SalidaInstancias rotadas
Líneas52
Is OutputON

Script MSE_AjustePaneles.dyn — Ajuste cuerda-arco

MSE_AjustePaneles es el script de corrección. Se ejecuta después de MSE_Distribucion, sobre un subconjunto de paneles seleccionados manualmente que requieren recalculación por geometría de curva. Su núcleo es el nodo A-3.1 AjusteCuerda: 328 líneas Python que implementan el ajuste completo en cinco fases.

Geometría de cuerda y arco en un muro MSE curvo: diferencia entre longitud de arco y cuerda proyectada en planta Geometría de cuerda y arco en un muro MSE curvo: diferencia entre longitud de arco y cuerda proyectada en planta
Figura 3. Geometría cuerda-arco en trazado curvo. El parámetro Largo del panel MSE debe corresponder a la longitud de cuerda (distancia entre extremos en planta), no a la longitud de arco, para que el panel físico cierre correctamente contra sus vecinos.

El problema que AjustePaneles resuelve: cuando MSE_Distribucion coloca paneles en una curva, el parámetro Largo se calcula sobre la longitud de arco del trazado. Pero un panel físico es un elemento recto — su ancho real en planta es la longitud de cuerda entre sus dos extremos, que es menor que el arco correspondiente. En curvas de radio pequeño (R < 50 m) la diferencia supera el milímetro constructivo y los paneles se solapan visualmente en el modelo.

Las cinco fases del algoritmo A-3.1:

A
Tabla arco-parámetro
Genera 1001 puntos equidistantes en parámetro normalizado [0,1], acumula distancias reales (Euclidiana entre puntos consecutivos) y construye una tabla de búsqueda bidireccional: arco→t y t→arco. Permite convertir cualquier longitud acumulada en un punto exacto sobre la curva con búsqueda binaria O(log N).
B
Junta adaptativa
Calcula el ángulo total girado en el segmento (atan2 de vectores tangentes) y estima la junta necesaria entre 2.0 cm (rectas) y 2.5 cm (curvas cerradas) proporcional al cambio de ángulo por panel.
C
Redistribución y gestión de instancias
Recalcula N con la junta adaptativa, y crea o elimina instancias según la diferencia entre N_nuevo y N_existente, copiando parámetros desde la instancia plantilla.
D
Alturas interpoladas
Interpola alturas entre el panel vecino anterior (PanelPrevio) y el posterior (PanelSiguiente), con suavizado de ±5 cm entre paneles consecutivos.
E
Posicionamiento por cuerda
Para cada panel, calcula el punto inicial (ri) y final (rf) del arco, obtiene el centroide geométrico, calcula la longitud de cuerda como distancia Euclidiana ri→rf, y asigna ese valor (no el arco) al parámetro Largo. El último panel usa la longitud exacta restante sin redondeo, cerrando perfectamente el segmento.
Grupo A-IN: Inputs — Selección de paneles, trazado y parámetros de ajuste · Fondo rosado

A-2.1

Paneles

Select Model Elements
InputON
Conecta aA-3.1 → IN[0]

A-2.2

Trazado

Select Model Element
InputON
Conecta aA-3.1 → IN[1]

A-2.3

LargoObjetivo

Number Slider
Rango0.5 – 1.25 m
Default1.25 m

A-2.4

PanelAjusteAlInicio

Boolean
DefaultFalse
Conecta aA-3.1 → IN[3]

A-2.5

PanelPrevio

Select Model Element
InputON
Conecta aA-3.1 → IN[4]

A-2.6

PanelSiguiente

Select Model Element
InputON
Conecta aA-3.1 → IN[5]
Grupo A-AC: Ajuste Cuerda — Algoritmo de cuerda-arco · Fondo verde · 328 líneas Python

A-3.1

AjusteCuerda

Python Script · CPython3
Entradas6 (paneles, trazado, largo, ajuste, prev, next)
SalidaResumen + detalle por panel
Líneas328
FasesA→B→C→D→E

A-W

Resultado

Watch
Is OutputON
MuestraResumen + detalle

Código fuente Python — Fichas técnicas

El procedimiento para todos los nodos es idéntico: doble clic sobre el nodo Python para abrir el editor, Ctrl+A para seleccionar el código por defecto, Delete para borrarlo, pegar el código de la ficha correspondiente y cerrar el editor.

D-1.10 ParseInflexion CPython3 2 entradas 3 líneas
Parsea dos strings CSV separados por coma a listas numéricas float. Sin dependencias externas.
Ver código fuente
dist = [float(x.strip()) for x in IN[0].split(",")]
alt  = [float(x.strip()) for x in IN[1].split(",")]
OUT  = [dist, alt]
D-3.1 Distribucion CPython3 4 entradas 70 líneas
Calcula N_std = floor((L+junta)/(ancho+junta)). Si hay residuo: 1 panel de ajuste si cabe en [AMIN, ANCHO]; si no, 2 paneles de ajuste repartidos simétricamente. Constantes: AMIN=0.50 m, STEP=0.05 m. Retorna [anchos, N_total, es_ajuste].
Ver código fuente — 70 líneas
import math
L = IN[0];  junta = IN[1]
ref = "Inicio" if IN[2] else "Final"
ANCHO = IN[3];  AMIN = 0.50;  STEP = 0.05
modulo = ANCHO + junta
N_std  = int(math.floor((L + junta) / modulo))
if N_std < 1: N_std = 1
ocupado = N_std * ANCHO + (N_std - 1) * junta
residuo  = L - ocupado
if abs(residuo) < 0.001:
    anchos   = [ANCHO] * N_std
    es_ajuste = [False] * N_std
elif residuo > 0:
    ancho_especial = residuo - junta
    if ancho_especial > ANCHO:
        N_std += 1
        ocupado = N_std * ANCHO + (N_std - 1) * junta
        residuo = L - ocupado
        ancho_especial = residuo - junta
    if ancho_especial >= AMIN and ancho_especial <= ANCHO:
        if ref == "Final":
            anchos    = [ANCHO] * N_std + [ancho_especial]
            es_ajuste = [False] * N_std + [True]
        else:
            anchos    = [ancho_especial] + [ANCHO] * N_std
            es_ajuste = [True] + [False] * N_std
    elif ancho_especial > 0.001:
        N_std -= 1
        espacio = L - N_std * ANCHO - N_std * junta
        p1 = round(round(espacio / 2.0 / STEP) * STEP, 4)
        p2 = round(espacio - p1 - junta, 4)
        if p1 < AMIN: p1 = AMIN; p2 = round(espacio - p1 - junta, 4)
        if p2 < AMIN: p2 = AMIN; p1 = round(espacio - p2 - junta, 4)
        if p1 >= AMIN and p2 >= AMIN and p1 <= ANCHO and p2 <= ANCHO:
            if ref == "Final":
                anchos    = [ANCHO] * N_std + [p1, p2]
                es_ajuste = [False] * N_std + [True, True]
            else:
                anchos    = [p1, p2] + [ANCHO] * N_std
                es_ajuste = [True, True] + [False] * N_std
        else:
            anchos = [ANCHO] * (N_std + 1); es_ajuste = [False] * (N_std + 1)
    else:
        anchos = [ANCHO] * N_std; es_ajuste = [False] * N_std
else:
    anchos = [ANCHO] * N_std; es_ajuste = [False] * N_std
N_total = len(anchos)
OUT = [anchos, N_total, es_ajuste]
D-3B Alturas CPython3 6 entradas 52 líneas
Interpola la altura de cada panel entre los puntos de inflexión topográfica. Si solo hay 2 puntos y son iguales, asigna la altura default uniforme. Suavizado: máx ±0.05 m entre paneles consecutivos. Constantes: HMIN=0.10 m, STEP=0.05 m.
Ver código fuente — 52 líneas
import math
anchos = IN[0];  junta = IN[1]
dist_infl = list(IN[2]);  alt_infl = list(IN[3])
L = IN[4];  alt_default = IN[5]
HMIN = 0.10;  STEP = 0.05
dist_infl[-1] = L
if len(dist_infl) == 2 and abs(alt_infl[0] - alt_infl[1]) < 0.001:
    alturas = [round(round(alt_default / STEP) * STEP, 4)] * len(anchos)
else:
    centros = [];  acum = 0.0
    for a in anchos:
        centros.append(acum + a / 2.0)
        acum = acum + a + junta
    alturas_obj = []
    for centro in centros:
        if   centro <= dist_infl[0]:  h = alt_infl[0]
        elif centro >= dist_infl[-1]: h = alt_infl[-1]
        else:
            for j in range(len(dist_infl) - 1):
                if dist_infl[j] <= centro <= dist_infl[j+1]:
                    t = (centro - dist_infl[j]) / (dist_infl[j+1] - dist_infl[j])
                    h = alt_infl[j] + t * (alt_infl[j+1] - alt_infl[j])
                    break
        h_round = round(h / STEP) * STEP
        if h_round < HMIN: h_round = HMIN
        alturas_obj.append(round(h_round, 4))
    alturas = [alturas_obj[0]]
    for i in range(1, len(alturas_obj)):
        h_prev = alturas[i-1];  h_obj = alturas_obj[i]
        diff = h_obj - h_prev
        if   abs(diff) <= STEP + 0.001: alturas.append(h_obj)
        elif diff > 0: alturas.append(round(h_prev + STEP, 4))
        else:
            nueva = round(h_prev - STEP, 4)
            if nueva < HMIN: nueva = HMIN
            alturas.append(nueva)
OUT = alturas
D-4.1 CoordenadasPanel CPython3 3 entradas 43 líneas
Para cada panel: punto = PointAtSegmentLength(centro_acumulado), ángulo = atan2(t.Y, t.X) con fallback por diferencia finita si la tangente no está disponible. Retorna [puntos, distancias, ángulos].
Ver código fuente — 43 líneas
import math
anchos = IN[0];  junta = IN[1];  curva = IN[2]
puntos = [];  distancias = [];  angulos = []
acum = 0.0;  L = curva.Length
for i in range(len(anchos)):
    a = anchos[i]
    centro = max(acum + a / 2.0, 0.001)
    centro = min(centro, L - 0.001)
    distancias.append(centro)
    try:    pt = curva.PointAtSegmentLength(centro); puntos.append(pt)
    except: puntos.append(curva.StartPoint)
    try:
        t = curva.TangentAtSegmentLength(centro)
        angulos.append(math.degrees(math.atan2(t.Y, t.X)))
    except:
        s0 = max(acum, L * 0.001);  s1 = min(acum + a, L * 0.999)
        try:
            p0 = curva.PointAtSegmentLength(s0)
            p1 = curva.PointAtSegmentLength(s1)
            angulos.append(math.degrees(math.atan2(p1.Y - p0.Y, p1.X - p0.X)))
        except: angulos.append(0.0)
    acum += a
    if i < len(anchos) - 1: acum += junta
OUT = [puntos, distancias, angulos]
D-5.1 Colocar CPython3 2 entradas 37 líneas
NewFamilyInstance en el nivel de menor elevación del proyecto. Convierte coordenadas Dynamo (metros) a Revit (pies) con FT=3.28083989501312. Retorna instancias envueltas con ToDSType(True).
Ver código fuente — 37 líneas
import clr
clr.AddReference('RevitAPI'); clr.AddReference('RevitServices')
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Structure import StructuralType
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
fam_type = UnwrapElement(IN[0])
puntos   = IN[1] if isinstance(IN[1], list) else [IN[1]]
FT = 3.28083989501312
levels = FilteredElementCollector(doc).OfClass(Level).ToElements()
level  = sorted(levels, key=lambda lv: lv.Elevation)[0]
z = level.Elevation
TransactionManager.Instance.EnsureInTransaction(doc)
instancias = []
for pt in puntos:
    if pt is None: instancias.append(None); continue
    try:
        xyz  = XYZ(pt.X * FT, pt.Y * FT, z)
        inst = doc.Create.NewFamilyInstance(xyz, fam_type, level, StructuralType.NonStructural)
        instancias.append(inst)
    except: instancias.append(None)
TransactionManager.Instance.TransactionTaskDone()
clr.AddReference('RevitNodes')
import Revit; clr.ImportExtensions(Revit.Elements)
OUT = [inst.ToDSType(True) if inst is not None else None for inst in instancias]
D-6.10 Coordenadas CPython3 2 entradas 33 líneas
Escribe Coord_X, Coord_Y desde Location.Point y Radio_Curva desde 1/CurvatureAtParameter(0.5). Passthrough: devuelve IN[0] para mantener cadena de dependencias en el canvas.
Ver código fuente — 33 líneas
import clr
clr.AddReference('RevitAPI'); clr.AddReference('RevitServices')
from Autodesk.Revit.DB import *
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc   = DocumentManager.Instance.CurrentDBDocument
elems = UnwrapElement(IN[0]);  curva = IN[1]
if not isinstance(elems, list): elems = [elems]
TransactionManager.Instance.EnsureInTransaction(doc)
for elem in elems:
    loc = elem.Location
    if loc is None: continue
    pt = loc.Point
    x_param = elem.LookupParameter("Coord_X")
    y_param = elem.LookupParameter("Coord_Y")
    r_param = elem.LookupParameter("Radio_Curva")
    if x_param: x_param.Set(pt.X)
    if y_param: y_param.Set(pt.Y)
    if r_param:
        try:    r_param.Set(1.0 / curva.CurvatureAtParameter(0.5))
        except: r_param.Set(0.0)
TransactionManager.Instance.TransactionTaskDone()
OUT = IN[0]
D-7.1 Rotar CPython3 2 entradas 52 líneas
Rotación ABSOLUTA: ang_diff = radians(deseado) − loc.Rotation. Normaliza a [−π, π]. Llama RotateElement solo si |diff| > 0.001 rad. Requiere doc.Regenerate() previo para leer posiciones post-transacción.
Ver código fuente — 52 líneas
import clr, math
clr.AddReference('RevitAPI'); clr.AddReference('RevitServices')
from Autodesk.Revit.DB import *
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc     = DocumentManager.Instance.CurrentDBDocument
elems   = UnwrapElement(IN[0]);  angulos = IN[1]
if not isinstance(elems,   list): elems   = [elems]
if not isinstance(angulos, list): angulos = [angulos]
resultados = []
TransactionManager.Instance.EnsureInTransaction(doc)
doc.Regenerate()
for i in range(len(elems)):
    elem = elems[i]
    if elem is None: resultados.append(None); continue
    if i >= len(angulos): resultados.append(elem); continue
    ang_deseado = angulos[i]
    try:
        loc = elem.Location
        if loc is None: resultados.append(elem); continue
        pt   = loc.Point
        axis = Line.CreateBound(XYZ(pt.X, pt.Y, pt.Z), XYZ(pt.X, pt.Y, pt.Z + 1.0))
        ang_actual = loc.Rotation
        ang_diff   = math.radians(ang_deseado) - ang_actual
        while ang_diff >  math.pi: ang_diff -= 2 * math.pi
        while ang_diff < -math.pi: ang_diff += 2 * math.pi
        if abs(ang_diff) > 0.001:
            ElementTransformUtils.RotateElement(doc, elem.Id, axis, ang_diff)
        resultados.append(elem)
    except: resultados.append(elem)
TransactionManager.Instance.TransactionTaskDone()
OUT = resultados
A-3.1 AjusteCuerda CPython3 6 entradas 328 líneas
FASE A: tabla arco 1001 pts. FASE B: junta adaptativa 2.0–2.5 cm. FASE C: redistribución + crear/eliminar instancias. FASE D: alturas interpoladas. FASE E: posicionar por cuerda, Largo = cuerda (no arco), último panel sin snap. Constantes: FT=3.28084, STEP_M=0.05, AMIN_M=0.50, JUNTA_MIN=0.020, JUNTA_MAX=0.025, N_FULL=1000, HSTEP=0.05.
Ver código fuente — 328 líneas
import clr, math
clr.AddReference('RevitAPI')
clr.AddReference('RevitServices')
clr.AddReference('RevitNodes')
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Structure import StructuralType
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
import Revit
clr.ImportExtensions(Revit.Elements)
doc            = DocumentManager.Instance.CurrentDBDocument
paneles_dy     = IN[0] if isinstance(IN[0], list) else [IN[0]]
curva_elem     = UnwrapElement(IN[1])
largo_obj_m    = float(IN[2])
ajuste_inicio  = bool(IN[3])
panel_prev_dy  = IN[4]
panel_next_dy  = IN[5]
FT       = 3.28083989501312
MI       = 1.0 / FT
STEP_M   = 0.05
AMIN_M   = 0.50
JUNTA_MIN_M = 0.020
JUNTA_MAX_M = 0.025
def snap(v):
    return round(round(v / STEP_M) * STEP_M, 4)
def copy_params(src, dst):
    for pname in ['Largo','Altura','ID_Tramo','Total_Paneles',
                   'EsPanelAjuste','Angulo_Rotacion','Bloqueado','Viga Corona']:
        try:
            ps = src.LookupParameter(pname)
            pd = dst.LookupParameter(pname)
            if ps and pd and not pd.IsReadOnly:
                st = ps.StorageType
                if   st == StorageType.Double:  pd.Set(ps.AsDouble())
                elif st == StorageType.Integer: pd.Set(ps.AsInteger())
                elif st == StorageType.String:  pd.Set(ps.AsString() or '')
        except:
            pass
panel_prev  = UnwrapElement(panel_prev_dy) if panel_prev_dy else None
panel_next  = UnwrapElement(panel_next_dy) if panel_next_dy else None
def get_altura_m(elem):
    p = elem.LookupParameter('Altura')
    return (p.AsDouble() * MI) if p else None
paneles_rv   = [UnwrapElement(p) for p in paneles_dy if p is not None]
revit_curve  = curva_elem.GeometryCurve
L_ft_tot     = revit_curve.Length
p0_raw  = revit_curve.GetEndParameter(0)
p1_raw  = revit_curve.GetEndParameter(1)
p_range = p1_raw - p0_raw
def to_norm(p_raw):
    return max(0.0, min(1.0, (p_raw - p0_raw) / p_range))
def eval_norm(t):
    return revit_curve.Evaluate(max(0.00001, min(0.99999, t)), True)
def panel_t(elem):
    try:
        return to_norm(revit_curve.Project(elem.Location.Point).Parameter)
    except:
        return 0.0
def get_largo_ft(elem):
    p = elem.LookupParameter('Largo')
    return p.AsDouble() if p else largo_obj_m * FT
def get_z(elem):
    try:
        return elem.Location.Point.Z
    except:
        return 0.0
N_FULL   = 1000
ts_full  = [k / float(N_FULL) for k in range(N_FULL + 1)]
pts_full = [eval_norm(t) for t in ts_full]
arc_full = [0.0]
for k in range(1, N_FULL + 1):
    dx = pts_full[k].X - pts_full[k-1].X
    dy = pts_full[k].Y - pts_full[k-1].Y
    arc_full.append(arc_full[-1] + math.sqrt(dx*dx + dy*dy))
L_curve_ft = arc_full[-1]
JUNTA_M  = JUNTA_MIN_M
JUNTA_FT = JUNTA_M * FT
def t_to_arc(t_val):
    t_val = max(0.0, min(1.0, t_val))
    lo, hi = 0, N_FULL
    while lo < hi - 1:
        mid = (lo + hi) // 2
        if ts_full[mid] <= t_val: lo = mid
        else: hi = mid
    if ts_full[hi] == ts_full[lo]: return arc_full[lo]
    frac = (t_val - ts_full[lo]) / (ts_full[hi] - ts_full[lo])
    return arc_full[lo] + frac * (arc_full[hi] - arc_full[lo])
def arc_to_t(s_ft):
    s_ft = max(0.0, min(L_curve_ft, s_ft))
    lo, hi = 0, N_FULL
    while lo < hi - 1:
        mid = (lo + hi) // 2
        if arc_full[mid] <= s_ft: lo = mid
        else: hi = mid
    if arc_full[hi] == arc_full[lo]: return ts_full[lo]
    frac = (s_ft - arc_full[lo]) / (arc_full[hi] - arc_full[lo])
    return ts_full[lo] + frac * (ts_full[hi] - ts_full[lo])
def rv_at_arc(s_ft):
    return eval_norm(arc_to_t(s_ft))
def panel_arc(elem):
    return t_to_arc(panel_t(elem))
paneles_rv.sort(key=panel_arc)
N_exist  = len(paneles_rv)
template = paneles_rv[0]
z_nivel  = get_z(template)
s_c0 = panel_arc(paneles_rv[0])
s_cN = panel_arc(paneles_rv[-1])
s_seg_ini = s_c0 - get_largo_ft(paneles_rv[0])  / 2.0
s_seg_fin = s_cN + get_largo_ft(paneles_rv[-1]) / 2.0
if s_seg_fin - s_seg_ini < largo_obj_m * FT * 0.5:
    OUT = ['ERROR: segmento demasiado pequeno (' + str(round((s_seg_fin-s_seg_ini)*MI,3)) + 'm). Verifica la seleccion de paneles y el trazado.']
else:
    L_seg_ft = s_seg_fin - s_seg_ini
    L_seg_m  = L_seg_ft * MI
    try:
        tang_ini = eval_norm(arc_to_t(s_seg_ini + 0.001))
        tang_mid = eval_norm(arc_to_t(s_seg_ini + L_seg_ft * 0.5))
        tang_fin = eval_norm(arc_to_t(s_seg_fin - 0.001))
        pt_ini   = rv_at_arc(s_seg_ini)
        pt_fin   = rv_at_arc(s_seg_fin)
        dx1 = tang_mid.X - tang_ini.X; dy1 = tang_mid.Y - tang_ini.Y
        dx2 = tang_fin.X - tang_mid.X; dy2 = tang_fin.Y - tang_mid.Y
        ang1 = math.degrees(math.atan2(dy1, dx1))
        ang2 = math.degrees(math.atan2(dy2, dx2))
        n_est = max(1, L_seg_m / (largo_obj_m + JUNTA_MIN_M))
        delta_ang_total = abs(ang2 - ang1)
        delta_ang_per_panel = delta_ang_total / n_est
        factor = min(1.0, delta_ang_per_panel / 10.0)
        JUNTA_M  = JUNTA_MIN_M + factor * (JUNTA_MAX_M - JUNTA_MIN_M)
        JUNTA_FT = JUNTA_M * FT
    except:
        JUNTA_M  = JUNTA_MIN_M
        JUNTA_FT = JUNTA_M * FT
    def rv_at_local(s_local_ft):
        return rv_at_arc(s_seg_ini + max(0.0, min(L_seg_ft, s_local_ft)))
    modulo = largo_obj_m + JUNTA_M
    N_std  = int(math.floor((L_seg_m + JUNTA_M) / modulo))
    if N_std < 1: N_std = 1
    ocupado = N_std * largo_obj_m + (N_std - 1) * JUNTA_M
    residuo = L_seg_m - ocupado
    ancho_ult = residuo - JUNTA_M
    if abs(residuo) < 0.001:
        anchos    = [largo_obj_m] * N_std
        es_ajuste = [False] * N_std
    elif ancho_ult >= AMIN_M and ancho_ult <= largo_obj_m:
        if ajuste_inicio:
            anchos    = [snap(ancho_ult)] + [largo_obj_m] * N_std
            es_ajuste = [True] + [False] * N_std
        else:
            anchos    = [largo_obj_m] * N_std + [snap(ancho_ult)]
            es_ajuste = [False] * N_std + [True]
    elif ancho_ult > 0.001:
        N2  = N_std - 1
        esp = L_seg_m - N2 * largo_obj_m - N2 * JUNTA_M
        p1v = snap(esp / 2.0)
        p2v = snap(esp - p1v - JUNTA_M)
        if p1v >= AMIN_M and p2v >= AMIN_M:
            if ajuste_inicio:
                anchos    = [p1v, p2v] + [largo_obj_m] * N2
                es_ajuste = [True, True] + [False] * N2
            else:
                anchos    = [largo_obj_m] * N2 + [p1v, p2v]
                es_ajuste = [False] * N2 + [True, True]
        else:
            anchos    = [largo_obj_m] * N_std
            es_ajuste = [False] * N_std
    else:
        anchos    = [largo_obj_m] * N_std
        es_ajuste = [False] * N_std
    N_nuevo = len(anchos)
    nuevos_creados = 0
    eliminados     = 0
    fam_type = template.Symbol
    TransactionManager.Instance.EnsureInTransaction(doc)
    doc.Regenerate()
    if N_nuevo > N_exist:
        levels = FilteredElementCollector(doc).OfClass(Level).ToElements()
        level  = sorted(levels, key=lambda lv: lv.Elevation)[0]
        pt_mid = rv_at_arc(s_seg_ini + L_seg_ft / 2.0)
        pt_tmp = XYZ(pt_mid.X, pt_mid.Y, z_nivel)
        for _ in range(N_nuevo - N_exist):
            inst = doc.Create.NewFamilyInstance(pt_tmp, fam_type, level, StructuralType.NonStructural)
            copy_params(template, inst)
            paneles_rv.append(inst)
            nuevos_creados += 1
    elif N_nuevo < N_exist:
        sobrantes  = paneles_rv[N_nuevo:]
        eliminados = len(sobrantes)
        for e in sobrantes:
            doc.Delete(e.Id)
        paneles_rv = paneles_rv[:N_nuevo]
    doc.Regenerate()
    h_ini = get_altura_m(panel_prev) if panel_prev else get_altura_m(paneles_rv[0])
    h_fin = get_altura_m(panel_next) if panel_next else get_altura_m(paneles_rv[-1])
    if h_ini is None: h_ini = get_altura_m(paneles_rv[0])
    if h_fin is None: h_fin = get_altura_m(paneles_rv[-1])
    if h_ini is None: h_ini = 2.4
    if h_fin is None: h_fin = h_ini
    HSTEP = 0.05
    def snap_h(v):
        return round(round(v / HSTEP) * HSTEP, 4)
    N = N_nuevo
    alturas_obj = []
    for idx in range(N):
        t_h = idx / max(1, N - 1)
        h   = h_ini + t_h * (h_fin - h_ini)
        alturas_obj.append(snap_h(h))
    alturas_final = [alturas_obj[0]]
    for idx in range(1, N):
        diff = alturas_obj[idx] - alturas_final[-1]
        if abs(diff) <= HSTEP + 0.001:
            alturas_final.append(alturas_obj[idx])
        elif diff > 0:
            alturas_final.append(round(alturas_final[-1] + HSTEP, 4))
        else:
            alturas_final.append(round(alturas_final[-1] - HSTEP, 4))
    resultados = []
    acum_ft    = 0.0
    acum_check_ft = sum(a * FT + JUNTA_FT for a in anchos) - JUNTA_FT
    last_exact_ft = L_seg_ft - (acum_check_ft - anchos[-1] * FT)
    for i, elem in enumerate(paneles_rv):
        if i == len(paneles_rv) - 1:
            a_ft = max(AMIN_M * FT, last_exact_ft)
        else:
            a_ft = anchos[i] * FT
        ri = rv_at_local(acum_ft)
        rf = rv_at_local(acum_ft + a_ft)
        cx = (ri.X + rf.X) / 2.0
        cy = (ri.Y + rf.Y) / 2.0
        dx_m    = (rf.X - ri.X) * MI
        dy_m    = (rf.Y - ri.Y) * MI
        cuerda_m = math.sqrt(dx_m*dx_m + dy_m*dy_m)
        ang_deg  = math.degrees(math.atan2(dy_m, dx_m))
        try:
            loc = elem.Location
            pt  = loc.Point
            delta = XYZ(cx - pt.X, cy - pt.Y, z_nivel - pt.Z)
            if delta.GetLength() > 0.00001:
                ElementTransformUtils.MoveElement(doc, elem.Id, delta)
            lp = elem.LookupParameter('Largo')
            if lp:
                largo_set = cuerda_m if i == len(paneles_rv)-1 else snap(cuerda_m)
                lp.Set(largo_set * FT)
            lpa = elem.LookupParameter('EsPanelAjuste')
            if lpa and not lpa.IsReadOnly: lpa.Set(1 if es_ajuste[i] else 0)
            lh = elem.LookupParameter('Altura')
            if lh and not lh.IsReadOnly and i < len(alturas_final):
                lh.Set(alturas_final[i] * FT)
            doc.Regenerate()
            loc2 = elem.Location
            pt2  = loc2.Point
            axis = Line.CreateBound(pt2, XYZ(pt2.X, pt2.Y, pt2.Z + 1.0))
            ang_diff = math.radians(ang_deg) - loc2.Rotation
            while ang_diff >  math.pi: ang_diff -= 2 * math.pi
            while ang_diff < -math.pi: ang_diff += 2 * math.pi
            if abs(ang_diff) > 0.0005:
                ElementTransformUtils.RotateElement(doc, elem.Id, axis, ang_diff)
            tag   = ' [AJUSTE]' if es_ajuste[i] else ''
            h_str = str(alturas_final[i]) + 'm' if i < len(alturas_final) else ''
            resultados.append('P' + str(i) + tag + ': ' + str(snap(cuerda_m)) + 'm ' + str(round(ang_deg,1)) + 'deg h=' + h_str)
        except Exception as ex:
            resultados.append('P' + str(i) + ' ERROR: ' + str(ex)[:80])
        acum_ft += a_ft + JUNTA_FT
    TransactionManager.Instance.TransactionTaskDone()
    resumen = ('Seg: ' + str(round(L_seg_m,3)) + 'm | '
               + str(N_nuevo) + ' paneles (+' + str(nuevos_creados)
               + ' nuevos, -' + str(eliminados) + ' elim) | '
               + str(largo_obj_m) + 'm | Ajuste al '
               + ('Inicio' if ajuste_inicio else 'Final'))
    OUT = [resumen] + resultados

Implementación en Dynamo Player — Flujo de trabajo

El flujo de trabajo completo para un tramo de muro MSE sigue seis pasos secuenciales. El paso 2 — limpiar el Element Binding — es el que más se omite y el que más problemas genera. Sin él, la segunda ejecución del script moverá los paneles del primer tramo ya colocado.

Flujo de trabajo en 6 pasos para implementar MSE Full Height con Dynamo Player en Revit 2026 Flujo de trabajo en 6 pasos para implementar MSE Full Height con Dynamo Player en Revit 2026
Figura 4. Flujo de trabajo completo para un tramo MSE. Los pasos 3 y 4 corresponden a la ejecución de los scripts; el paso 2 (limpiar Element Binding) es el más frecuentemente omitido y el que causa el 80 % de los errores de re-ejecución.
01
Dibujar Model Line del tramo
Trazar la Model Line que define el trazado del muro. Un tramo = una línea. No usar Detail Lines — no tienen geometría accesible desde la API de Revit.
02
Limpiar Element Binding en Dynamo
Dynamo Settings → Manage Node and Custom Packages → Element Binding → borrar entradas del tramo anterior.
⚠ Sin este paso, la re-ejecución afecta tramos ya colocados.
03
Ejecutar MSE_Distribucion en Dynamo Player
Configurar los inputs del Grupo 1 (Trazado, Altura, Junta, PanelAlInicio, IDTramo, Familia, Ancho, Inflexiones) y ejecutar. Verificar el output Watch «Longitud del trazado».
04
Ejecutar MSE_AjustePaneles sobre tramos curvos
Seleccionar los paneles del segmento curvo, el trazado correspondiente, y los paneles vecinos anterior y posterior. Verificar output «Resultado» para confirmar distribución final.
05
Inspeccionar en vista 3D
Verificar alineación, orientación y alturas en vista 3D. Comparar con el diseño de referencia. Los parámetros EsPanelAjuste y Bloqueado permiten filtrar paneles en schedules.
06
Repetir desde paso 1 con nuevo IDTramo
Cambiar el valor de D-1.5 IDTramo (T-02, T-03…) antes de ejecutar el siguiente tramo. El IDTramo se escribe en el parámetro ID_Tramo de cada instancia y es el selector principal en schedules.

Referencia de constantes

Las siguientes constantes están embebidas en los scripts y no son configurables desde Dynamo Player. Para modificarlas es necesario editar el código fuente en el editor del nodo Python correspondiente.

ConstanteValorNodo IDScriptDescripción
AMIN0.50 mD-3.1DistribuciónAncho mínimo de panel de ajuste
STEP0.05 mD-3.1DistribuciónRedondeo de anchos a múltiplos de 5 cm
HMIN0.10 mD-3BDistribuciónAltura mínima de panel
FT3.28083989501312D-5.1, D-7.1, A-3.1AmbosFactor metros → pies (unidad interna Revit)
STEP_M0.05 mA-3.1AjusteRedondeo anchos en AjusteCuerda
AMIN_M0.50 mA-3.1AjusteAncho mínimo en AjusteCuerda
JUNTA_MIN_M0.020 mA-3.1AjusteJunta mínima (trazados rectos)
JUNTA_MAX_M0.025 mA-3.1AjusteJunta máxima (curvas cerradas)
N_FULL1000A-3.1AjustePuntos en tabla arco-parámetro
HSTEP0.05 mA-3.1AjusteRedondeo alturas en AjusteCuerda

Solución de problemas

SíntomaCausa probableSolución
Paneles no aparecen en el modelo Element Binding anterior activo Limpiar Element Binding y re-ejecutar el script
Paneles truncados o desplazados kilómetros Geometry Scaling insuficiente Dynamo Settings → Geometry Scaling: Extra Large
Error en D-5.1 al ejecutar Familia no cargada en el proyecto Verificar que la familia MSE aparece seleccionada en D-1.6 Familia
Todas las alturas son iguales Mismo valor en todos los puntos de inflexión Verificar D-1.9 AltInflexion: los valores deben ser distintos para activar la interpolación
Panel de ajuste en lado equivocado PanelAlInicio con valor incorrecto Invertir el booleano D-1.4 (Distribución) o A-2.4 (Ajuste)
A-3.1 reporta "ERROR: segmento demasiado pequeño" Selección de paneles incompleta Seleccionar TODOS los paneles del segmento curvo antes de ejecutar
Rotaciones erróneas en varios paneles Tangente indefinida en geometría compleja Fallback automático en D-4.1 — verificar geometría del trazado
Parámetros no se escriben (sin error) Nombre de parámetro con mayúscula incorrecta Los nombres son case-sensitive. Verificar exactitud: "Coord_X" ≠ "coord_x"
Herramienta interactiva

Calculadora de Distribución de Paneles MSE

Configura la longitud del trazado, ancho estándar y junta para obtener la distribución óptima de paneles. Replica el algoritmo D-3.1 documentado en este artículo.

Parámetros del tramo
25.0 m
1.25 m
0.020 m
Resultado de distribución
Distribución visual

* Replica el algoritmo D-3.1 Distribucion con constantes AMIN=0.50 m y STEP=0.05 m. Los resultados son idénticos a la ejecución del nodo Python en Dynamo.

Cómo citar este artículo

APA 7.ª edición

Torres, E. (2026, abril 1). MSE Full Height en Revit 2026: distribución y ajuste de paneles con Dynamo y Python. ANTORR Ingeniería S.A.S. https://antorr.co/academia/mse-full-height-dynamo-revit.html

Chicago 17.ª edición

Torres, Emil. "MSE Full Height en Revit 2026: distribución y ajuste de paneles con Dynamo y Python." ANTORR Academia, 1 de abril de 2026. https://antorr.co/academia/mse-full-height-dynamo-revit.html

Referencias bibliográficas y técnicas

  1. Torres, E. (2026, abril 1). MSE Full Height en Revit 2026: distribución y ajuste de paneles con Dynamo y Python. ANTORR Ingeniería S.A.S. https://antorr.co/academia/mse-full-height-dynamo-revit.html [artículo citado]
  2. Berg, R. R., Christopher, B. R., & Samtani, N. C. (2009). Mechanically stabilized earth walls and reinforced soil slopes design & construction guidelines (FHWA-NHI-10-024). Federal Highway Administration. https://www.fhwa.dot.gov/engineering/geotech/pubs/nhi10024/
  3. ISO 19650-1:2018. Organization and digitization of information about buildings and civil engineering works, including building information modelling (BIM) — Information management using BIM. Part 1: Concepts and principles. International Organization for Standardization.
  4. ISO 19650-2:2018. Information management using BIM. Part 2: Delivery phase of the assets. International Organization for Standardization.
  5. AASHTO. (2007). LRFD bridge design specifications (4th ed.). American Association of State Highway and Transportation Officials.
  6. Terzidis, K. (2006). Algorithmic architecture. Elsevier.
  7. Autodesk Inc. (2025). Dynamo Primer — A guide to visual programming for design. Autodesk. https://primer.dynamobim.org
  8. Autodesk Inc. (2025). Revit API Developer's Guide 2026. Autodesk Developer Network. https://www.revitapidocs.com
  9. Eastman, C., Teicholz, P., Sacks, R., & Liston, K. (2011). BIM Handbook: A guide to building information modeling for owners, managers, designers, engineers, and contractors (2nd ed.). John Wiley & Sons.
Siguiente paso

¿Tu proyecto requiere automatización
paramétrica en Revit o Dynamo?

Desarrollamos scripts Dynamo a medida para infraestructura vial, geotecnia y obras civiles. Desde distribución de elementos sobre trazados hasta extracción de cantidades por tramo y generación automática de fichas técnicas por panel.

Atendemos proyectos en todo Colombia — Cartagena, Barranquilla, Bogotá y más. · proyectos@antorr.co · +57 310 432 0600