Tiempo de lectura: ~ 1 minuto
182 vistas
Cuantificar elementos MEP por zonas personalizadas con Modelos Genéricos
# Revit API
Actualizado 26 de diciembre de 2024
Todos sabemos que la cuantificación de materiales en un proyecto es muy importante, ya que este valor por el P.U. es parte del presupuesto directo de un proyecto, los proyectos actualmente vienen implementando nuevas metodologías, herramientas, técnicas para la gestión y producción en obra, una de ellas son los trenes de trabajo en donde realizamos una sectorización con el fin de balancear los recursos tanto en Mano de obra, materiales, equipos y herramientas, por eso les presentamos un aplicativo que nos ayudara a cuantificar la cantidad de materiales en un determinado sector.
Empezaremos creando un nuevo proyecto en visual studio, la plantilla de Librería de Clases (.NET Framework) usando el lenguaje C#. A este proyecto lo nombraremos QuantityBox y usaremos .NET Framework 4.7.2. para Autodesk Revit 2019.
Iniciaremos nuestro proyecto según la publicación de Mi primer addin con la API de Revit y tendríamos el siguiente código.
QuantityBox.cs1// Revit API Namespaces 2using Autodesk.Revit.Attributes; 3using Autodesk.Revit.DB; 4using Autodesk.Revit.UI; 5using Autodesk.Revit.UI.Selection; 6 7namespace QuantityBox 8{ 9 public class CmdQuantityBox : IExternalCommand 10 { 11 public Result Execute(ExternalCommandData commandData, 12 ref string message, 13 ElementSet elements) 14 { 15 TaskDialog.show("Mi primer aplicativo", "Hola Mundo"); 16 return Result.Succeeded; 17 } 18 } 19}
Luego accederemos a la variable Documento, para poder trabajar con propiedades y métodos a lo largo de nuestro proyecto.
QuantityBox.cs1UIApplication uiApp = commandData.Application; 2UIDocument uiDoc = uiApp.ActiveUIDocument; 3Document doc = uiDoc.Document;
Ahora realizaremos la selección de un solo elemento que será el modelo genérico por medio del PickObject, el cual recibe como parámetro el ObjectType.Element y un mensaje para el usuario.
QuantityBox.cs1// Seleccionamos el modelo genérico 2ElementId genericModelElementId = uiDoc.Selection 3 .PickObject(ObjectType.Element, 4 "Seleccione un modelo generico") 5 .ElementId; 6Element genericModelElement = doc.GetElement(genericModelElementId);
Crearemos una condicional donde comprobaremos si la vista actual es diferente de una vista 3D. En caso la vista actual sea diferente a una vista 3D se muestra un mensaje al usuario y se cancela la ejecución del aplicativo, pero si la vista es la correcta nos permita correr el aplicativo.
QuantityBox.cs1// Comprobamos si estamos en una vista 3D 2if (doc.ActiveView.ViewType != ViewType.ThreeD) 3 { 4 TaskDialog.Show("Error", "Debes estar en una vista 3D"); 5 return Result.Cancelled; 6 }
Vamos a generar BoundingBox, el cual se generara desde la vista activa, que servirá para filtrar los elementos que estén dentro. Ahora generaremos un Outline indicándole el punto más bajo izquierda (bb.Min) y el punto más alto derecha (bb.Max).
QuantityBox.cs1// Obtenemos el BoundingBox del Generic Model 2BoundingBoxXYZ bb = genericModelElement.get_BoundingBox(doc.ActiveView); 3Outline outline = new Outline(bb.Min, bb.Max);
Entonces generaremos un BoundingBoxIntersectsFilter, el cual es una regla que filtra los elementos que están dentro de un BoundingBox.
QuantityBox.cs1 // Regla que filtra los elementos que estan dentro del bounding Box 2BoundingBoxIntersectsFilter bbfilter = new BoundingBoxIntersectsFilter(outline);
El BoundingBox va a seleccionar todos los elementos que estén dentro de este, incluido el modelo genérico, el cual no sera incluido en nuestro análisis, para eso usaremos el idsExclude de tal manera q excluyamos el modelo genérico.
QuantityBox.cs1// Lista de elementos que seran excluidos en este caso el generic model 2ICollection<ElementId> idsExclude = new List<ElementId>(); 3idsExclude.Add(genericModelElementId);
Ahora filtraremos los elementos a traves del FilteredElementCollector en la vista activa y procederemos a ejecutar el intersectedElements el cual realizará la acción de excluir el modelo genérico de los elementos filtrados y aparte que este dentro del BoundingBox.
1// Recollectar los elementos que cumplan con la regla 2FilteredElementCollector elementInCurrentViewCollector = new FilteredElementCollector(doc, doc.ActiveView.Id); 3List<Element> intersectedElements = elementInCurrentViewCollector 4 .Excluding(idsExclude) 5 .WherePasses(bbfilter) 6 .ToList();
Para asegurarnos que dentro del modelo genérico hay elementos realizaremos una comprobación, si la cantidad de elementos es igual a cero, se muestra un mensaje al usuario y se cancela la ejecución del aplicativo, caso contrario si es diferente de cero ejecutara el aplicativo.
QuantityBox.cs1// Comprobar si existen elementos dentro del boundingBox 2if (intersectedElements.Count == 0) 3{ 4 TaskDialog.Show("Warning", "No hay elementos dentro del area"); 5 return Result.Cancelled; 6}
Luego vamos a general un solido del modelo genérico.
QuantityBox.cs1// Create solid from generic model 2Solid genericModelSolid = GetSolidElement(doc, genericModelElement); 3 4// Metodo para obtener el solido de un elemento 5public static Solid GetSolidElement(Document doc, Element el) 6{ 7 Options option = doc.Application.Create.NewGeometryOptions(); 8 option.ComputeReferences = true; 9 option.IncludeNonVisibleObjects = true; 10 option.View = doc.ActiveView; 11 12 Solid solid = null; 13 GeometryElement geoEle = el.get_Geometry(option) as GeometryElement; 14 foreach (GeometryObject gObj in geoEle) 15 { 16 Solid geoSolid = gObj as Solid; 17 if (geoSolid != null && geoSolid.Volume != 0) 18 { 19 solid = geoSolid; 20 } 21 else if (gObj is GeometryInstance) 22 { 23 GeometryInstance geoInst = gObj as GeometryInstance; 24 GeometryElement geoElem = geoInst.SymbolGeometry; 25 foreach (GeometryObject gObj2 in geoElem) 26 { 27 Solid geoSolid2 = gObj2 as Solid; 28 if (geoSolid2 != null && geoSolid2.Volume != 0) 29 { 30 solid = geoSolid2; 31 } 32 } 33 } 34 } 35 return solid; 36}
QuantityBox.cs1// Revit API Namespaces 2using Autodesk.Revit.Attributes; 3using Autodesk.Revit.DB; 4using Autodesk.Revit.UI; 5using Autodesk.Revit.UI.Selection; 6 7namespace QuantityBox 8{ 9 public class CmdQuantityBox : IExternalCommand 10 { 11 public Result Execute(ExternalCommandData commandData, 12 ref string message, 13 ElementSet elements) 14 { 15 16 UIApplication uiApp = commandData.Application; 17 UIDocument uiDoc = uiApp.ActiveUIDocument; 18 Document doc = uiDoc.Document; 19 20 // Seleccionamos el modelo generico 21 ElementId genericModelElementId = uiDoc.Selection.PickObject(ObjectType.Element, "Seleccione un modelo generico").ElementId; 22 Element genericModelElement = doc.GetElement(genericModelElementId); 23 24 // Comprobamos si estamos en una vista 3D 25 if (doc.ActiveView.ViewType != ViewType.ThreeD) 26 { 27 TaskDialog.Show("Error", "Debes estar en una vista 3D"); 28 return Result.Cancelled; 29 } 30 31 // Obtenemos el BoundingBox del generic model 32 BoundingBoxXYZ bb = genericModelElement.get_BoundingBox(doc.ActiveView); 33 Outline outline = new Outline(bb.Min, bb.Max); // Min: Punto mas bajo izquierda - Max: Punto mas alto derecha 34 35 BoundingBoxIntersectsFilter bbfilter = new BoundingBoxIntersectsFilter(outline); // Regla que filtra los elementos que estan dentro del bounding Box 36 37 // Lista de elementos que seran excluidos en este caso el generic model 38 ICollection<ElementId> idsExclude = new List<ElementId>(); 39 idsExclude.Add(genericModelElementId); 40 41 // Recollectar los elementos que cumplan con la regla 42 FilteredElementCollector elementInCurrentViewCollector = new FilteredElementCollector(doc, doc.ActiveView.Id); 43 List<Element> intersectedElements = elementInCurrentViewCollector 44 .Excluding(idsExclude) 45 .WherePasses(bbfilter) 46 .ToList(); 47 48 // Comprobar si existen elementos dentro del boundingBox 49 if (intersectedElements.Count == 0) 50 { 51 TaskDialog.Show("Warning", "No hay elementos dentro del area"); 52 return Result.Cancelled; 53 } 54 // Create solid from generic model 55 Solid genericModelSolid = GetSolidElement(doc, genericModelElement); 56 57 return Result.Succeeded; 58 } 59 } 60}
Exportación a Excel
Para trabajar con la API de Excel tendremos que agregar una nueva referencia a nuestro proyecto que nos permitirá interactuar con métodos y propiedades de Microsoft Excel..
Luego agregamos el namespace de excel, usaremos una variable Excel para que se nos sea más fácil trabajar con los métodos de Microsoft.Office.Interop.Excel.
QuantityBox.cs1using Excel = Microsoft.Office.Interop.Excel;
Para trabajar con excel primero debemos acceder a la aplicación con el siguiente método.
QuantityBox.cs1Excel.Application xlApp = new Excel.Application();
Una vez ejecutado excel debemos decirle que como voy a crear la aplicación no sea visible y que no muestre ninguna alerta.
QuantityBox.cs1xlApp.Visible = false; // Hide Excel Aplication 2xlApp.DisplayAlerts = false; // Hide Excel Alert
Luego creamos el Workbook y accedemos al Worksheet que vendrían hacer espacio de trabajo y la hoja de excel.
QuantityBox.cs1Excel.Workbook xlWorkBook = xlApp.Workbooks.Add(Type.Missing); // Create a Workbook 2Excel.Worksheet xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
Definimos una variable entera, rowIndex, que será fila de inicio indicando de donde se proyectara la información extraída.
QuantityBox.cs1int rowIndex = 4; // row start
Mediante foreach, recorremos todos los elementos interceptados para obtener la información de cada uno para exportarlas a Excel.
Haremos los siguientes pasos para esta parte:
- Obtendremos es el solido del elemento en análisis.
- Creamos una comprobación si el solido es igual a nulo que continue con el siguiente elemento interceptado.
- Crearemos un solido, entre el solido del modelo genérico y el solido del elemento en análisis, utilizando el método BooleanOperationsUtils.ExecuteBooleanOperation obteniéndolo a traves de BooleanOperationsType.Intersect.
- Creamos una comprobación si existe un solido de la intersección.
- Calculamos el porcentaje del elemento, comparando el volumen de la parte interceptada versus el volumen total del elemento en análisis.
Obtendremos la información de elemento y comparándolo con el porcentaje interceptado, mediante el método getElementInformation.
QuantityBox.cs1private ElementExport getElementInformation(Element el, double elementPercentage) 2{ 3 BuiltInCategory category = (BuiltInCategory)el.Category.Id.IntegerValue; 4 5 string elementCategory = el.Category.Name; 6 double _lengthQuantity = 1; 7 string _sistemType = "No definido"; 8 9 switch (category) 10 { 11 //TUBERIAS 12 case BuiltInCategory.OST_PipeCurves: 13 _lengthQuantity = el.get_Parameter(BuiltInParameter.CURVE_ELEM_LENGTH).AsDouble(); 14 _sistemType = el.get_Parameter(BuiltInParameter.RBS_PIPING_SYSTEM_TYPE_PARAM).AsValueString(); 15 break; 16 //UNIONES DE TUNERIAS 17 case BuiltInCategory.OST_PipeFitting: 18 _sistemType = el.get_Parameter(BuiltInParameter.RBS_PIPING_SYSTEM_TYPE_PARAM).AsValueString(); 19 break; 20 //APARATOS SANITARIOS 21 case BuiltInCategory.OST_PlumbingFixtures: 22 _sistemType = el.get_Parameter(BuiltInParameter.RBS_PIPING_SYSTEM_TYPE_PARAM).AsValueString(); 23 break; 24 //DUCTOS 25 case BuiltInCategory.OST_DuctCurves: 26 _lengthQuantity = el.get_Parameter(BuiltInParameter.CURVE_ELEM_LENGTH).AsDouble(); 27 _sistemType = el.get_Parameter(BuiltInParameter.RBS_DUCT_SYSTEM_TYPE_PARAM).AsValueString(); 28 break; 29 //UNIONES DE DUCTOS 30 case BuiltInCategory.OST_DuctFitting: 31 _sistemType = el.get_Parameter(BuiltInParameter.RBS_DUCT_SYSTEM_TYPE_PARAM).AsValueString(); 32 break; 33 //TERMINALES DE AIRE 34 case BuiltInCategory.OST_DuctTerminal: 35 _sistemType = el.get_Parameter(BuiltInParameter.RBS_DUCT_SYSTEM_TYPE_PARAM).AsValueString(); 36 break; 37 //EQUIPOS MECANICOS 38 case BuiltInCategory.OST_MechanicalEquipment: 39 _sistemType = el.get_Parameter(BuiltInParameter.RBS_SYSTEM_CLASSIFICATION_PARAM).AsValueString() != null ? el.get_Parameter(BuiltInParameter.RBS_SYSTEM_CLASSIFICATION_PARAM).AsValueString() : "No definido"; 40 break; 41 //BANDEJAS DE CABLES 42 case BuiltInCategory.OST_CableTray: 43 _lengthQuantity = el.get_Parameter(BuiltInParameter.CURVE_ELEM_LENGTH).AsDouble(); 44 _sistemType = el.LookupParameter("SISTEMA").AsValueString() != null ? el.LookupParameter("SISTEMA").AsValueString() 45 : el.LookupParameter("SUB-SISTEMA").AsValueString() != null ? el.LookupParameter("SUB-SISTEMA").AsValueString() 46 : "No definido"; 47 break; 48 //TUBOS 49 case BuiltInCategory.OST_Conduit: 50 _lengthQuantity = el.get_Parameter(BuiltInParameter.CURVE_ELEM_LENGTH).AsDouble(); 51 _sistemType = el.LookupParameter("SISTEMA").AsValueString() != null ? el.LookupParameter("SISTEMA").AsValueString() 52 : el.LookupParameter("SUB-SISTEMA").AsValueString() != null ? el.LookupParameter("SUB-SISTEMA").AsValueString() 53 : "No definido"; 54 break; 55 //LUMINARIAS 56 case BuiltInCategory.OST_LightingFixtures: 57 _sistemType = el.LookupParameter("SISTEMA").AsValueString() != null ? el.LookupParameter("SISTEMA").AsValueString() 58 : el.LookupParameter("SUB-SISTEMA").AsValueString() != null ? el.LookupParameter("SUB-SISTEMA").AsValueString() 59 : "No definido"; 60 break; 61 //UNIONES DE BANDEJAS DE CABLES 62 case BuiltInCategory.OST_CableTrayFitting: 63 _sistemType = el.LookupParameter("SISTEMA").AsValueString() != null ? el.LookupParameter("SISTEMA").AsValueString() 64 : el.LookupParameter("SUB-SISTEMA").AsValueString() != null ? el.LookupParameter("SUB-SISTEMA").AsValueString() 65 : "No definido"; 66 break; 67 //UNIONES DE TUBOS 68 case BuiltInCategory.OST_ConduitFitting: 69 _sistemType = el.LookupParameter("SISTEMA").AsValueString() != null ? el.LookupParameter("SISTEMA").AsValueString() 70 : el.LookupParameter("SUB-SISTEMA").AsValueString() != null ? el.LookupParameter("SUB-SISTEMA").AsValueString() 71 : "No definido"; 72 break; 73 //DISPOSITIVOS DE DATOS 74 case BuiltInCategory.OST_DataDevices: 75 _sistemType = el.LookupParameter("SISTEMA").AsValueString() != null ? el.LookupParameter("SISTEMA").AsValueString() 76 : el.LookupParameter("SUB-SISTEMA").AsValueString() != null ? el.LookupParameter("SUB-SISTEMA").AsValueString() 77 : "No definido"; 78 break; 79 //DISPOSITIVOS DE COMUNICACIÓN 80 case BuiltInCategory.OST_CommunicationDevices: 81 _sistemType = el.LookupParameter("SISTEMA").AsValueString() != null ? el.LookupParameter("SISTEMA").AsValueString() 82 : el.LookupParameter("SUB-SISTEMA").AsValueString() != null ? el.LookupParameter("SUB-SISTEMA").AsValueString() 83 : "No definido"; 84 break; 85 } 86 87 return new ElementExport(elementCategory, _lengthQuantity.ToString(), _sistemType); 88}
Para identificar la categoría de los distintos elementos usamos el BuiltInCategory porque este parámetro es propio de Revit y tendrá un valor único indistintamente si usamos Revit en español, ingles u otro idioma. Para ubicar este parámetro debemos de tener instalado el RevitLookup y seguir los siguiente pasos.
Una vez extraída esta información lo exportaremos a excel con un método externo llamado exportToExcel.
QuantityBox.cs1private void exportToExcel(Excel.Worksheet xlWorkSheet, 2 ElementExport elementExport, 3 int rowIndex) 4{ 5 xlWorkSheet.Cells[rowIndex, 2] = elementExport.GetCategory(); 6 xlWorkSheet.Cells[rowIndex, 3] = elementExport.GetQuantity(); 7 xlWorkSheet.Cells[rowIndex, 4] = elementExport.GetSystem(); 8}
- Va aumentando el rowIndex++ para q siga la siguiente fila.
- por ultimo hacemos que el aplicativo sea visible.
QuantityBox.cs1foreach (Element intersectedElement in intersectedElements) 2{ 3 Solid intersectedSolid = GetSolidElement(doc, intersectedElement); 4 5 if (intersectedSolid == null) continue; // continuar con el siguiente elemento intersectado en caso no exista solido. 6 Solid intersectSolid = BooleanOperationsUtils.ExecuteBooleanOperation(intersectedSolid, genericModelSolid, BooleanOperationsType.Intersect); 7 8 if (intersectSolid == null) continue; 9 double solidPercentage = intersectSolid.Volume / intersectedSolid.Volume; 10 exportToExcel(xlWorkSheet, getElementInformation(intersectedElement, solidPercentage), rowIndex); 11 rowIndex++; 12} 13 14xlApp.Visible = true;
Definimos una clase ElementExport va a tener 3 propiedades: categoría, cantidad y el sistema, a su vez generamos un constructor, el constructor es el método que se ejecuta apenas cree una nueva variable.
QuantityBox.cs1public class ElementExport 2{ 3 private string _category; 4 private string _quantity; 5 private string _system; 6 7 public ElementExport(string category, string quantity, string system) 8 { 9 this._category = category; 10 this._quantity = quantity; 11 this._system = system; 12 } 13 14 public string GetCategory() 15 { 16 return _category; 17 } 18 19 public string GetQuantity() 20 { 21 return _quantity; 22 } 23 24 public string GetSystem() 25 { 26 return _system; 27 } 28 29}
Para ya terminar el aplicativo, nos faltaría agregar el archivo manifiesto
QuantityBox.cs1<?xml version="1.0" encoding="utf-8" standalone="no"?> 2<RevitAddIns> 3 <AddIn Type="Command"> 4 <Assembly>QuantityBox.dll</Assembly> 5 <AddInId>ec1d2f17-7d6f-475d-bdf5-ffb2218f0307</AddInId> 6 <FullClassName>QuantityBox.CmdQuantityBox</FullClassName> 7 <Text>QuantityBox</Text> 8 <VendorId>ADSK</VendorId> 9 <VendorDescription>Lambda Ingenieria e Innovacion, https://lambda.com.pe</VendorDescription> 10 <VisibilityMode>NotVisibleInFamily</VisibilityMode> 11 <Discipline>Any</Discipline> 12 </AddIn> 13</RevitAddIns>
Y por ultimo agregaremos a Visual Studio los eventos de compilación, los cuales despues de compilar un addin dentro de Revit, permite compilar los archivos .dll y .addin dentro de una caperta que Revit leera el manifiesto y podra cargar nuestro addin, podemos encontrar estos eventos en el siguiente link.
copy "$(ProjectDir)*.addin" "$(AppData)\Autodesk\REVIT\Addins\2020" copy "$(ProjectDir)bin\debug\*.dll" "$(AppData)\Autodesk\REVIT\Addins\2020"
Hasta este puntos hemos culminado con la creación del aplicativo en Visual Studio, solo procederiamos a compilar el aplicativo y hacer uso en el software de Revit.
Una vez realizado el aplicativo abrimos un proyecto de ejemplo en Revit, nos vamos a la pestaña de complementos/Herramientas externas/QualityBox.
Como producto final tenemos el metrado o cuantificación de los materiales que están dentro del modelo generico.
Por ultimo ya solo quedaria darle formato a la información extraida de manera rapida, eficiente y verídica.
Y eso es todo. Es un proceso que vale la pena intentarlo y probarlo, sumémonos todos para poder formar una comunidad de personas que tengan las ganas de ingresar a este nuevo mundo de la programación. Aqui les comparto un pequeño video del funcionamiento y como pueden descargar el código completo desde github.
Si necesitan más información sobre este increíble mundo de la automatización de procesos a través de la creación de aplicaciones en Autodesk Revit no duden en escribirme a mi LinkedIn Bryan Espinoza Allcca o dejar comentarios debajo de este post.
Suscribirse para recibir actualizaciones
Recibe semanalmente tutoriales, recursos, noticias sobre temas innovadores dentro del sector construcción y destaca profesionalmente.