IFCExportUtils Methods Determine Door and Window Area

Last week we discussed the SpatialElementGeometryCalculator sample for calculating gross and net wall areas that in turn led to the discovery of the FindInserts method to retrieve all openings in a wall.

Both of these were prompted by the Revit API discussion forum thread on door/window areas raised by Phillip Miller of Kiwi Codes Solutions Ltd.

Another question that arose in that thread regards the meaning of the built-in parameter HOST_AREA_COMPUTED and how to best calculate the area of a door or window family instance.

By the way, here are some previous discussions on HOST_AREA_COMPUTED regarding walls:

In the thread on door/window areas, Phillip wonders about its meaning applied to family instances:

Question: I need to determine the cut areas of windows and doors in a wall. I thought this would be a simple matter of grabbing the BIP HOST_AREA_COMPUTED as with my testing that was returning the correct area. It turns out that this is not the case from further testing.

Please refer to the attached RVT file with two doors inserted into a wall. They look similar but one is reporting through RevitLookup 4m2 and the other 2m2.

Why is this? What actually is HOST_AREA_COMPUTED reporting? What is the most reliable way of getting the cut area of windows and doors?

Answer: We initially avoided looking at the family instance areas directly – by analysing the wall with and without its openings instead – leaving two other interesting topics open for further exploration:

Peter (pjohan13) jumped in and answered:

Even though you solved the problem (congratulations on that by the way) I might be able to provide some additional info on the subject.

During the last couple of months I've been working with something related. In my case the specific surface area of each window/door is the main interest and I have thus been digging into this topic quite a bit.

My findings suggest that the value of HOST_AREA_COMPUTED in regards to family instances is, what I can best explain as a sum of the geometric objects 'overlapping' the wall geometry in all 3 axes.

Terrible as that explanation might be, consider the following:

A simple 1000 x 1000 mm window family containing only a sheet of glass with a thickness of 100 mm will report an area of 1.2 square meters, equivalent to what could be measured in 2D plan, section and elevation views. If the geometry is copied in the family so that it contains two sweets of glass, HOST_AREA_COMPUTED will report the area of both, adding up to 2.4 square meters. This assumption can be supported by snooping a window hosted by a curtain wall. As the curtain wall has no actual depth/thickness, only one overlapping area exist, e.g. as defined by points the X and Z axes.

Whether I'm right or not doesn't really affect anything, but I thought it was worth sharing anyway   :-)

An easy approach to access the actual surface/opening area that I've found extremely useful resides in the Autodesk.Revit.DB.IFC namespace, more specifically in the ExporterIFCUtils class that has provided other interesting features in the past. The methods GetInstanceCutoutFromWall and ComputeAreaOfCurveLoops are able to handle every oddly shaped window I have ever fed them. And especially the latter is handy in a very wide context   :-)

Unfortunately I have no sample code related to HOST_AREA_COMPUTED analysis. My findings on the parameter value are based on manual analysis including family editing, measuring, snooping and comparing through a series of small steps. When I eventually stumbled upon the IFCExportUtils methods, that settled my quest for clarity.

The way I have used them, generally, is pretty straightforward. I attached a very simple sample implementing both methods to display the surface areas of selected windows/doors/curtain panels. I've also attached a sample project containing the earlier mentioned 1000 x 1000 mm single sheet window and a 'zig-zag'-shaped window intended to challenge the method a bit – which it doesn't   :-)

One beauty of the GetInstanceCutoutFromWall method is that it returns the opening boundary as a curve loop and thus provides additional possibilities related to windows/door. For instance, the perimeter is available through curve lengths or sealant area can be calculated by adding a CurveLoop.CreateViaOffset to the list fed into ComputeAreaOfCurveLoops, etc.

I've tested the methods on windows intersecting the vertical join between two different wall types and windows intersecting the horizontal join in stacked walls, in each case with consistent outcome.

Ideally the GetAreaCutoutFromWall would instead be named *FromHost to cover skylights as well. But with solid performance like this I can't really ask for more, except maybe the values exposed directly by the FamilyInstance class or curtain wall's orientation vector updating accordingly when flipped   a different discussion, maybe   :-)

I hope that the sample can be of use and look forward to seeing more on this matter on The Building Coder.

Please note that this sample is a bit lazy. Normally I would not recommend unit conversion until 'the last moment', e.g. before printing values in the task dialog. Approaches like the one illustrated in the sample could obviously lead to some unintentional 'ft * m' scenarios causing unexpected outcome.

Many thanks to Peter for his research and sample material!

Implementation

I cleaned up his sample code for publication in the ExporterIfcUtilsWinArea GitHub repository.

As always, the Revit database length unit is imperial feet, and we are interested in seeing the resulting area in square metres, so an approximate conversion factor will come in handy:

  const double _square_feet_to_square_metres
    = 0.09290304;

The algorithm implemented by Peter in the GetInstanceSurfaceAreaMetric method goes like this:

Here is the implementation of that:

  /// <summary>
  /// Return surface area of given family instance
  /// in square metres.
  /// </summary>
  static double GetInstanceSurfaceAreaMetric(
    FamilyInstance familyInstance )
  {
    double area_sq_ft = 0;
 
    Wall wall = familyInstance.Host as Wall;
 
    if( null != wall )
    {
      if( wall.WallType.Kind == WallKind.Curtain )
      {
        area_sq_ft = familyInstance.get_Parameter(
          BuiltInParameter.HOST_AREA_COMPUTED )
            .AsDouble();
      }
      else
      {
        Document doc = familyInstance.Document;
        XYZ basisY = XYZ.BasisY;
 
        // using I = Autodesk.Revit.DB.IFC;
 
        CurveLoop curveLoop = I.ExporterIFCUtils
          .GetInstanceCutoutFromWall( doc, wall,
            familyInstance, out basisY );
 
        IList<CurveLoop> loops
          = new List<CurveLoop>( 1 );
 
        loops.Add( curveLoop );
 
        area_sq_ft = I.ExporterIFCUtils
          .ComputeAreaOfCurveLoops( loops );
      }
    }
    else
    {
      double width
        = familyInstance.Symbol.get_Parameter(
          BuiltInParameter.FAMILY_WIDTH_PARAM )
            .AsDouble();
 
      double height
        = familyInstance.Symbol.get_Parameter(
          BuiltInParameter.FAMILY_HEIGHT_PARAM )
            .AsDouble();
 
      area_sq_ft = width * height;
    }
    return _square_feet_to_square_metres * area_sq_ft;
  }

The element selection and result reporting is handled like this by the external command mainline Execute method:

  public Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements )
  {
    UIApplication uiapp = commandData.Application;
    UIDocument uidoc = uiapp.ActiveUIDocument;
    Application app = uiapp.Application;
    Document doc = uidoc.Document;
 
    Selection sel = uidoc.Selection;
 
    StringBuilder sb = new StringBuilder();
    double areaTotal = 0;
 
    IEnumerable<ElementId> elementIds = sel.GetElementIds();
 
    foreach( ElementId elementId in elementIds )
    {
      FamilyInstance fi = doc.GetElement( elementId )
        as FamilyInstance;
 
      if( null != fi )
      {
        double areaMetric =
            GetInstanceSurfaceAreaMetric( fi );
        areaTotal += areaMetric;
 
        double areaRound = Math.Round( areaMetric, 2 );
 
        sb.AppendLine();
        sb.Append( "ElementId: " + fi.Id.IntegerValue );
        sb.Append( "  Name: " + fi.Name );
        sb.AppendLine( "  Area: " + areaRound + " m2" );
      }
    }
    int count = elementIds.Count<ElementId>();
 
    double areaPrintFriendly = Math.Round( areaTotal, 2 );
 
    sb.AppendLine( "\nTotal area: "
      + areaPrintFriendly + " m2" );
 
    TaskDialog taskDialog = new TaskDialog(
      "Selection Area" );
 
    taskDialog.MainInstruction = "Elements selected: "
      + count;
 
    taskDialog.MainContent = sb.ToString();
 
    taskDialog.Show();
 
    return Result.Succeeded;
  }

Sample Model

Peter's sample model contains three different kinds of windows:

ExporterIFCUtils sample model

The sample code calculates the following resulting areas for it:

ExporterIFCUtils sample result

Download

The entire source code, Visual Studio solution and add-in manifest is provided in the ExporterIfcUtilsWinArea GitHub repository, and the version discussed above is release 2015.0.0.1.