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.

Importante
Los siguientes pasos que se realizaran se explicaran de manera clara y resumida para el aplicativo que crearemos, pero si tienen dudas sobre primeros pasos de creación de un proyecto hasta la creación del interfaz los invito a revisar mi publicación de Mi primer addin con la API de Revit para su mejor entendimiento.
QuantityBox.cs
1// 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.cs
1UIApplication 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.

Importante
El método PickObject nos devuelve una Referencia, el cual nos servirá para obtener el Id del Elemento.
QuantityBox.cs
1// 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.cs
1// 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.cs
1// 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.cs
1 // 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.cs
1// 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.cs
1// 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.cs
1// 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.cs
1// 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}

estructura del proyecto - Revit API

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..

Agregar exportación a 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.cs
1using Excel = Microsoft.Office.Interop.Excel;

Para trabajar con excel primero debemos acceder a la aplicación con el siguiente método.

QuantityBox.cs
1Excel.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.cs
1xlApp.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.cs
1Excel.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.cs
1int 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:

  1. Obtendremos es el solido del elemento en análisis.
  2. Creamos una comprobación si el solido es igual a nulo que continue con el siguiente elemento interceptado.
  3. 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.
  4. Creamos una comprobación si existe un solido de la intersección.
  5. Calculamos el porcentaje del elemento, comparando el volumen de la parte interceptada versus el volumen total del elemento en análisis.
  6. Obtendremos la información de elemento y comparándolo con el porcentaje interceptado, mediante el método getElementInformation.

    QuantityBox.cs
    1private 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.

    buildcategory con revit lookup

  7. Una vez extraída esta información lo exportaremos a excel con un método externo llamado exportToExcel.

    QuantityBox.cs
    1private 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}
  8. Va aumentando el rowIndex++ para q siga la siguiente fila.
  9. por ultimo hacemos que el aplicativo sea visible.
QuantityBox.cs
1foreach (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.cs
1public 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.cs
1<?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"
Importante
Si tienes dudas en los dos últimos pasos los invito a revisar la publicación de Mi primer addin con la API de Revit para entender de manera más clara estos procesos para el aplicativo.

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.

Compilar proyeto con la API 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.

Ejecutar complemento

Como producto final tenemos el metrado o cuantificación de los materiales que están dentro del modelo generico.

Exportación de información

Por ultimo ya solo quedaria darle formato a la información extraida de manera rapida, eficiente y verídica.

Tablas dinámicas

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.

Comparte este artículo

Suscribirse para recibir actualizaciones

Recibe semanalmente tutoriales, recursos, noticias sobre temas innovadores dentro del sector construcción y destaca profesionalmente.