Melbourne Day Two

On the second day of the Revit API training here in Melbourne, we addressed a number of further interesting issues, both basic and beyond. Our sample code ended up demonstrating the following functionality:

Besides that, we looked at extensible storage, and I compiled an updated version of the RstLink sample for Revit Structure 2012.

I also received interesting comments on a previous post on finding the Revit parent window that is worthwhile sharing here.

After the training, I went for a quick climb with Rob at Hardrock, marvelling at yet another grading system.

Hardrock climbing

Here in Australia I went for the easy grades 16 to 18. No idea what they compare to in European or American grading systems.

After that, I had a sandwich in the Siglo roof-top bar on 161 Spring Street above the Melbourne Supper Club in the same house. These two places share an extremely sympathetic anonymous entrance, a simple brown wooden door with no sign whatsoever. If no-one told you they are there, you would never find them.

Placing a Duct

One thing we talked about was creating new elements in the model.

As a very simple example, we created the code to place an HVAC duct:

[Transaction( TransactionMode.Manual )]
public class Command3 : IExternalCommand
{
  public Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements )
  {
    UIApplication uiapp = commandData.Application;
    UIDocument uidoc = uiapp.ActiveUIDocument;
    Document doc = uidoc.Document;
 
    XYZ p, q;
 
    try
    {
      Selection sel = uidoc.Selection;
      p = sel.PickPoint( "Start point: " );
      q = sel.PickPoint( "End point: " );
    }
    catch( RvtOperationCanceledException )
    {
      return Result.Cancelled;
    }
 
    DuctType ductType
      = new FilteredElementCollector( doc )
        .OfClass( typeof( DuctType ) )
        .Cast<DuctType>()
        .FirstOrDefault<DuctType>();
 
    if( null == ductType )
    {
      message = "No duct type found.";
      return Result.Failed;
    }
 
    //Duct duct = new Duct(); // OO approach, not supported
    //Duct duct = Duct.Create( doc );
 
    Transaction tx = new Transaction( doc );
    tx.Start( "Add Duct" );
 
    Duct duct = doc.Create.NewDuct( p, q, ductType );
 
    tx.Commit();
 
    return Result.Succeeded;
  }
}

We also played with creating a mechanical system. That needs some fittings a well, because their connectors provide the required duct system type information.

Look at the AutoRoute and AvoidObstruction SDK samples for more details.

Retrieving Unique Geometry Vertices

We then explored geometry extraction, and especially the identification of unique points with in it, since this bring up a number of issues regarding units and precision etc.

Here is a simple command that prompts me to select an element and accesses its geometry. It processes it as well towards the end; ignore that for the moment:

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;
 
  Element e;
 
  try
  {
    Reference r = uidoc.Selection.PickObject(
      ObjectType.Element,
      "Please pick an element." );
 
    e = doc.get_Element( r.ElementId );
  }
  catch( RvtOperationCanceledException )
  {
    return Result.Cancelled;
  }
 
  Options opt = app.Create.NewGeometryOptions();
 
  GeometryElement geo = e.get_Geometry( opt );
 
  Solid solid = null;
 
  foreach( GeometryObject obj in geo.Objects )
  {
    GeometryInstance inst = obj as GeometryInstance;
 
    if( null != inst )
    {
      geo = inst.GetSymbolGeometry();
      break;
    }
  }
 
  foreach( GeometryObject obj in geo.Objects )
  {
    solid = obj as Solid;
 
    if( null != solid )
    {
      break;
    }
  }
 
  if( null == solid )
  {
    message = "Unable to access element solid.";
    return Result.Failed;
  }

  Dictionary<XYZ, int> corners = GetCorners( solid );
 
  int n = corners.Count;
 
  Debug.Print( "{0} corners found:", n );
 
  foreach( XYZ p in corners.Keys )
  {
    Debug.Print( PointString( p ) );
  }
 
  return Result.Succeeded;
}

The next step we looked at is identification of all unique vertices in the geometry. For this, we need to somehow implement a fuzzy method to distinguish between points that really are different, but detect that points that are nearly the same should in fact be treated as identical. We can achieve this by implementing an XYZ equality comparer, e.g. like this:

class XyzEqualityComparer : IEqualityComparer<XYZ>
{
  const double _sixteenthInchInFeet 
    = 1.0 / ( 16.0 * 12.0 );
 
  public bool Equals( XYZ p, XYZ q )
  {
    return p.IsAlmostEqualTo( q, 
      _sixteenthInchInFeet );
  }
 
  public int GetHashCode( XYZ p )
  {
    return PointString( p ).GetHashCode();
  }
}

As an almost-equal tolerance we are using a rather large value, because Revit uses this pretty rough number in several places itself, e.g. to limit the limit the minimum line length.

You must be careful to also define a hash code that does not return different values for points that you wish to compare equal, or every single point will be considered different, possibly even when a point is compared with itself. In this case we are using our two-decimal string representation to define a hash code, so many points that are considered different by the comparison operator will actually generate the same hash code. In a perfect world, the hash code generator would be a bit more appropriately chosen to match the equality comparison precision.

We can easily implement a short and sweet method to extract all the unique corner vertices of the solid geometry by using the vertices themselves as keys in a dictionary based on our equality comparer like this:

Dictionary<XYZ,int> GetCorners( Solid solid )
{
  Dictionary<XYZ, int> corners 
    = new Dictionary<XYZ, int>( 
      new XyzEqualityComparer() );
 
  foreach( Face f in solid.Faces )
  {
    foreach( EdgeArray ea in f.EdgeLoops )
    {
      foreach( Edge e in ea )
      {
        XYZ p = e.AsCurveFollowingFace( f )
          .get_EndPoint( 0 );
 
        if( !corners.ContainsKey( p ) )
        {
          corners[p] = 0;
        }
        ++corners[p];
      }
    }
  }
  return corners;
}

The dictionary returned includes a count of how many times each vertex was encountered.

This is something very important that I have looked at numerous times in the past. I already presented the fundamentals for handling this when looking at nested instance geometry, which also implements a GetVertices method similar to the above, and reused it for toposurface point classification and accessing the top faces of a sloped wall.

Extensible Storage

Here is the documentation and sample code from my Autodesk University 2011 class and lab on this topic, which says it all:

Revit Structure Link 2012

Long overdue, I finally updated the RstLink sample for Revit 2012 and AutoCAD 2012. I provided a brief description of the analytical link sample two years ago, and updated the sample last year for Revit Structure 2011. Now here is RstLink2012.zip containing the entire Visual Studio solution for the following projects:

In fact, most of these are provided for both C# and VB, so it ends up including the following projects:

Revit Parent Window

Finally, to wrap up this information overflow, Victor Chekalin, or Виктор Чекалин, offered a very useful and interesting suggestion for a new and easier way to obtain the Revit parent window:

  1. Add a reference to the AdWindows assembly, located at the same place as the RevitAPI assembly, and set its Copy Local property to false.
  2. Get the Revit main window handle using the static property ApplicationWindow of the ComponentManager class in the Autodesk.Windows namespace.

Now, you can use only one Property – ComponentManager.ApplicationWindow – instead two methods – get current process and main window handle.