Editar techos con la API de Revit
Te explicamos cómo hacerlo aplicando programación
En numerosos proyectos tendremos que realizar el modelo de los acabados, lo cual puede ser una tarea laboriosa según la complejidad del proyecto.
La interfaz de Revit no tiene ninguna herramienta que permita generar acabados automáticamente utilizando la información las habitaciones, sin embargo mediante la programación se pueden realizar de forma sencilla soluciones para generar muros y suelos, tal y como mostramos con el nodo del Modelical Dynamo Package Wall Finishes by room.
El problema está cuando llegamos a los techos, después de revisar la documentación de la API y buscar soluciones en la web, nos encontramos con la siguiente situación de partida:
- La API de Revit no permite crear techos.
- La API de Revit “no permite” editar el boceto de los techos.
Con esta publicación enseñaremos al lector a modificar el perímetro de los techos, aprovechando el perímetro de una habitación. Vamos a utilizar como base de este aprendizaje un pequeño ejemplo:
Supongamos que, después de haber creado manualmente todos los techos de nuestro modelo, en la última actualización del proyecto se ha producido un desajuste al modificarse la arquitectura.
Teniendo miles de techos, actualizar los contornos sería una tarea tediosa, por suerte esta tarea también es potencialmente automatizable. Empecemos con un techo y su habitación.

Obtener el perímetro de la habitación
Lo primero que hacemos es obtener el contorno de la habitación. Para ello podemos utilizar el método GetBoundarySegments de la clase SpatialElement.
Este método nos devuelve una sublista de listas de segmentos, siendo la primera sublista el contorno exterior de la habitación y las siguientes los contornos interiores.
Document currentDocument = ActiveUIDocument.Document;
ElementId roomId = new ElementId(207717);
Element room = currentDocument.GetElement(roomId);
//get room boundary
SpatialElementBoundaryOptions options =
new SpatialElementBoundaryOptions();
options.SpatialElementBoundaryLocation =
SpatialElementBoundaryLocation.Finish;
IList<IList<ltBoundarySegment>> roomBoundary =
(room as SpatialElement).GetBoundarySegments(options);
Vemos que en este ejemplo la habitación se ha localizado mediante el id (haremos lo mismo con el techo), esto es porque en este ejercicio no pretendemos identificar qué techo corresponde a qué habitación, eso se lo dejamos al lector.
Obtener el boceto del techo
Ahora obtenemos el boceto del techo que queremos editar. En la API de Revit podemos alcanzarlo de la siguiente forma:
ElementId ceilingId = new ElementId(210549);
Element ceiling = currentDocument.GetElement(ceilingId);
//get ceiling sketch
ElementClassFilter filter =
new ElementClassFilter(typeof(Sketch));
ElementId sketchId =
ceiling.GetDependentElements(filter).First();
Sketch ceilingSketch =
currentDocument.GetElement(sketchId) as Sketch;
CurveArrArray ceilingProfile = ceilingSketch.Profile;
Esta instancia de la clase CurveArrArray es una matriz que contiene a su vez submatrices que almacenan las líneas del boceto del techo, separadas por bucles, de forma equivalente al resultado obtenido anteriormente para la habitación.
El problema está en que los elementos de esta instancia no pueden ser modificados, si lo intentamos recibiremos una excepción del tipo “InvalidOperationException […] Collection is read-only”.
Obtener las líneas editables
Si bien la API de Revit no permite modificar directamente el boceto, sí hay una cosa que se puede hacer, obtener las líneas que forman techo y modificarlas.
filter = new ElementClassFilter(typeof(CurveElement));
IEnumerable curves = ceiling.GetDependentElements(filter)
.Select(id => currentDocument.GetElement(id));
IEnumerable modelLines = curves
.Where(e => e is ModelLine).Cast();//target
if(curves.Count() != modelLines.Count())
throw new Exception("The ceiling contains" +
"non straight lines");
En este caso lo que hemos obtenido es una lista de las líneas que forman el techo, sin la estructura de bucles.
La pregunta sería cómo podemos cambiar las líneas que componen el techo y no romperlo en el intento. Para ello tenemos que tener en cuenta dos condiciones particulares:
- Podemos cambiar una línea por otra y podemos eliminar líneas, pero no podemos añadirlas.
- Podemos modificar los bucles internos y podemos eliminarlos, excepto si dichas modificaciones entran en conflicto con la primera condición. Tampoco podremos añadir nuevos bucles.
Esto implica que si el techo original tenía 7 lados en su perímetro exterior el nuevo perímetro deberá tener 7 o menos lados. Además el nuevo techo podrá tener igual o menor número de bucles internos y estos bucles internos podrán tener igual o menos aristas que las que tenían originalmente.
En este ejemplo existe otra limitación que puede ser solventada por el usuario: el techo y la habitación deben estar formados por líneas rectas.
En este punto podemos llegar a la conclusión de que puede ser interesante preservar la estructura del boceto de techo que habíamos conseguido en el apartado anterior, pero utilizando estas líneas de modelo que acabamos de conseguir en su lugar. Para ello podemos hacer lo siguiente:
IList<IList<ModelLine>> editableSketch =
new List<IList<ModelLine>>();
foreach (CurveArray loop in ceilingProfile) {
List<ModelLine> newLoop = new List<ModelLine>();
foreach (Curve edge in loop) {
foreach (ModelLine modelLine in modelLines) {
Curve currentLine = ((modelLine as ModelLine)
.Location as LocationCurve).Curve;
if (currentLine.Intersect(edge)
== SetComparisonResult.Equal){
newLoop.Add(modelLine);
break;
}
editableSketch.Add(newLoop);
}
}
}
Lo que está haciendo este código es recorrer todos las líneas de los bucles del boceto de techo (variable ceilingProfile) y buscar la misma línea en nuestra lista de líneas de modelo (variable modelLine), una vez que la encuentra la almacena en una nueva estructura (variable editableSketch) que respeta la estructura del boceto almacenando las líneas editables.
Ajustar los datos
Para evitar errores el código debería comprobar que se cumplen las condiciones mencionadas en el apartado anterior, antes de editar el techo.
El siguiente código es una simplificación que:
- Ordena el contorno de la habitación y del techo, dejando como primer elemento el perímetro exterior, y ordenando el resto de mayor a menor número de aristas.
- Comprueba el número de bucles y el número de aristas.
//sort room boundary
IList<BoundarySegment> roomPerimeter = roomBoundary[0];
roomBoundary = roomBoundary.Skip(1)
.OrderByDescending(s => s.Count()).ToList();
roomBoundary.Insert(0, roomPerimeter);
//sort ceiling boundary
IList<ModelLine> ceilingPerimeter = editableSketch[0];
editableSketch = editableSketch.Skip(1)
.OrderByDescending(s => s.Count()).ToList();
editableSketch.Insert(0, ceilingPerimeter);
//compare the number of loops and edges
if(roomBoundary.Count() > editableSketch.Count()
|| Enumerable.Range(0, Math.Min(roomBoundary.Count(),
editableSketch.Count()))
.Any(i => roomBoundary[].Count()>editableSketch[].Count()))
{
throw new Exception(
"The ceiling's sketch cannot be" +
" adapted to the room's shape.");
}
Editar el techo
Ya tenemos el contorno actual del techo y el contorno de la habitación, que es el que queremos conseguir.
Ahora modificamos las líneas del techo para que coincidan con las de la habitación, haciendo uso de la propiedad Location de la clase ModelLine según se ve en el siguiente código:
using (Transaction t = new Transaction(currentDocument,
"Edit ceiling sketch"))
{
t.Start();
for(int i = 0; i < editableSketch.Count(); i++)
{
IList<ModelLine> ceilingLoop = editableSketch[];
if(i < roomBoundary.Count())
{
//edit the current loop
IList<BoundarySegment> roomLoop = roomBoundary[];
for(int j = 0; j < ceilingLoop.Count(); j++)
{
ModelLine currentEdge = ceilingLoop[];
if(j < roomLoop.Count())
{
//edit the current edge
BoundarySegment roomEdge = roomLoop[];
Curve newEdge = roomEdge.GetCurve().Clone();
if(!(newEdge is Line))
throw new Exception(
"The room contains non straight lines");
(currentEdge.Location as LocationCurve).Curve =
newEdge;
}
else
{
//remove the current edge
currentDocument.Delete(currentEdge.Id);
}
}
}
else
{
//remove the current loop
foreach(ModelLine line in ceilingLoop)
{
currentDocument.Delete(line.Id);
}
}
}
t.Commit();
}
Es importante hacer hincapié en el uso del método Clone de la clase Curve utilizado para replicar la línea de la habitación. Si no se clonara, la línea que define el perímetro de la habitación estaría ligada internamente con la línea que delimita el techo, llevando a errores cuando el usuario haga modificaciones en estos elementos desde la interfaz de Revit.
Como algunos lectores ya se habrán dado cuenta, este código permite modificar el boceto de más elementos aparte de los techos, sin embargo otras categorías sí cuentan con métodos propios para crear y modificar su geometría, recomendamos consultar la documentación de la API de Revit antes de caer en soluciones más complejas de lo necesario.
Conclusión
- La API de Revit no permite editar el boceto de los techos, pero existen trucos para conseguirlo.
Puede parecer que este ejemplo tiene demasiadas limitaciones, sin embargo es la base sobre la que pudimos desarrollar en Modelical un add-in como el siguiente:
Referencias
Debemos dar las gracias a Joe Ye y su artículo Change the boundary of floors/slabs, fue la clave para alcanzar nuestro objetivo.
Autor: Adrián Sánz