The Building Coder

Traversing and Exporting all MEP System Graphs

The Forge DevCon last week completed successfully.

I had a full body 3D scan created there in the Shapify Booth and explained on The 3D Web Coder how I used sed to flip the axes of the resulting OBJ model.

This week, I am sitting in the Autodesk offices at One Market in San Francisco, supporting the fourth Cloud Accelerator taking place here.

One of the projects we are working on is from USC, the University of Southern California Facilities Management CAD Services.

One of its goals is to interact with Revit MEP systems in the Forge viewer.

That requires traversing the MEP systems in the Revit model to store, recreate and represent their graph structures in the viewer.

Below, I present the Revit add-in that I have started implementing to generate and supply that information:

Before getting to that, here are a couple of pictures from the past weekend.

Saturday, I crossed the Golden Gate bridge and the Marin headlands to Sausalito:

Golden Gate Sausalito Walk

Sunday, I participated in the ecstatic dance event (FB) in their great new location and enjoyed the views from the Buena Vista and Presidio parks:

Buena Vista and Presidio

Revit MEP System Traversal

Today, I present a simple Revit add-in that analyses all MEP systems in the model and determines the graph structure representing the connections between the systems elements.

I discussed a simple MEP system traversal in 2013.

The graph information required in this case, however, requires the more advanced traversal algorithms determining the correct order of the individual system elements in the direction of the flow implemented by the TraverseSystem SDK sample (2010, 2011) for mechanical systems and the AdnRme sample (GitHub repo) for electrical ones.

TraverseAllSystems Revit Add-in

I implemented a simple C# .NET Revit API add-in that performs the following steps:

Here is the main implementation file of the external command:

 [TransactionTransactionMode.ReadOnly )]
 public class Command : IExternalCommand
 {
   /// <summary>
   /// Return true to include this system in the 
   /// exported system graphs.
   /// </summary>
   static bool IsDesirableSystemPredicate( MEPSystem s )
   {
     return s is MechanicalSystem || s is PipingSystem
       && !s.Name.Equals( "unassigned" )
       && 1 < s.Elements.Size;
   }

   /// <summary>
   /// Create a and return the path of a random temporary directory.
   /// </summary>
   static string GetTemporaryDirectory()
   {
     string tempDirectory = Path.Combine(
       Path.GetTempPath(), Path.GetRandomFileName() );

     Directory.CreateDirectory( tempDirectory );

     return tempDirectory;
   }

   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;

     FilteredElementCollector allSystems
       = new FilteredElementCollector( doc )
         .OfClass( typeofMEPSystem ) );

     int nAllSystems = allSystems.Count<Element>();

     IEnumerable<MEPSystem> desirableSystems
       = allSystems.Cast<MEPSystem>().Where<MEPSystem>(
         s => IsDesirableSystemPredicate( s ) );

     int nDesirableSystems = desirableSystems
       .Count<Element>();

     string outputFolder = GetTemporaryDirectory();

     int n = 0;

     foreachMEPSystem system in desirableSystems )
     {
       Debug.Print( system.Name );

       FamilyInstance root = system.BaseEquipment;

       // Traverse the system and dump the 
       // traversal graph into an XML file

       TraversalTree tree = new TraversalTree( system );

       if( tree.Traverse() )
       {
         string filename = system.Id.IntegerValue.ToString();

         filename = Path.ChangeExtension(
           Path.Combine( outputFolder, filename ), "xml" );

         tree.DumpIntoXML( filename );

         // Uncomment to preview the 
         // resulting XML structure

         //Process.Start( fileName );

         ++n;
       }
     }

     string main = string.Format(
       "{0} XML files generated in {1} ({2} total"
       + "systems, {3} desirable):",
       n, outputFolder, nAllSystems,
       nDesirableSystems );

     List<string> system_list = desirableSystems
       .Select<Elementstring>( e =>
         string.Format( "{0}({1})", e.Id, e.Name ) )
       .ToList<string>();

     system_list.Sort();

     string detail = string.Join( ", ",
       system_list.ToArray<string>() );

     TaskDialog dlg = new TaskDialog( n.ToString()
       + " Systems" );

     dlg.MainInstruction = main;
     dlg.MainContent = detail;

     dlg.Show();

     return Result.Succeeded;
   }
 }

I ran the command in the RME advanced sample project included with the standard Revit installation, rme_advanced_sample_project.rvt.

The result of doing so looks like this:

TraverseAllSystems result in rme_advanced_sample_project.rvt

It contains 240 MEP systems, 51 of which were deemed 'desirable' by the IsDesirableSystemPredicate method, of which only 35 produced any interesting graph data, exported to individual XML files in a random temporary directory.

Download

The current state of this project is available from the TraverseAllSystems GitHub repository, and the version discussed above is release 2017.0.0.1.

To Do

The next step will be to implement a Forge viewer extension displaying a custom panel in the user interface hosting a tree view of the MEP system graphs and implementing two-way linking and selection functionality back and forth between the tree view nodes and the 2D and 3D viewer elements.

We also need to figgure out how to transport the graph information from the Revit add-in to the Forge viewer.

Presumably, we will encode it in JSON instead of XML, to start with, to make it easier to handle directly in JavaScript, e.g. by implementing a viewer extension making use of jstree to interact with the graph.

Here are some of the storage options:

These options can obviously be combined, and even all implemented at once.

Probaly, the easiest way to transport the data from the BIM to the Forge platform will be to store it in shared parameter data on Revit elements.

Then it will be automatically included and handled by the standard Forge translation process for Revit RVT files.

Thanks to Mustafa Salaheldin

One last important point before closing.

In the past weeks, Mustafa Salaheldin has answered more cases on the Revit API forum than any other person before him ever was able to do in the past:

Mustafa Salaheldin ranking

Nobody ever reached that ranking before.

I cannot even imagine how he does it.

Thank you very much, Mustafa!

It is extremely appreciated by the entire community!