Object Relationships

We already discussed several uses of the Document.Delete method to discover object relationships, e.g. the relationship between a

tag and the tagged element
, a

wall and its footing
,

host and its hosted elements
, the

title block of a sheet
, or
host elements and their openings for the purposes of

gross material quantity determination
.
Another use of it is to obtain the

unmodified element geometry
.

Here is a very neat little sample named ObjRel by Saikat Bhattacharya that shows how to use the method to determine generic objects relationships in a building model and display the results in a tree view.
The tree view is hosted by a .NET form, implemented by a class named Result.

The really neat trick of Saikat’s implementation lies in the helper method GetDependentsElementIds and its three lines of code that determine all object relationships in a generic fashion by creating a transaction, calling the Delete method to obtain the dependent element ids, and aborting the transaction to restore the original state:


ElementIdSet GetDependentsElementIds(
  Element e )
{
  Document doc = _app.ActiveDocument;
 
  doc.BeginTransaction();
 
  ElementIdSet ids = doc.Delete( e );
 
  doc.AbortTransaction();
 
  return ids;
}

This method is used by the two tree view helper methods DisplayNode and CreateRelationships to populate the tree view with all BIM elements having relationships with other objects in the model:

  • DisplayNode:
    This recursive method creates the tree nodes
    based on dependent element ids. Each node
    displays its element name, category and id.
  • CreateRelationships :
    Creates the tree node with the initial node,
    populates it, and maintains a list of elements
    already included in the nodes to avoid duplication.

Here is the implementation of these two and the tree view dialogue constructor calling them:


void DisplayNode( Element e, TreeNode node )
{
  string cat = (null == e.Category)
    ? "<category unknown>"
    : e.Category.Name;
 
  string label = string.Format( "{0}: {1} {2}",
    e.Name, cat, e.Id.Value );
 
  TreeNode father = node.Nodes.Add( label );
 
  // save element id to handle select event, 
  // cf. treeView1_AfterSelect:
 
  father.ImageKey = e.Id.Value.ToString();
 
  ElementIdSet ids = GetDependentsElementIds( e );
 
  try
  {
    if( null != ids && 1 < ids.Size )
    {
      Document doc = _app.ActiveDocument;
 
      foreach( ElementId id1 in ids )
      {
        ElementId id = id1;
        Element e2 = doc.get_Element( ref id );
        if( e2 != null )
        {
          if( !e2.Id.Equals( e.Id )
            && !_displayedElems.Contains( e2 ) )
          {
            _displayedElems.Insert( e2 );
            DisplayNode( e2, father );
          }
        }
      }
    }
  }
  catch( Exception ex )
  {
    MessageBox.Show( ex.Message.ToString() );
  }
}
 
void CreateRelationships()
{
  rootNode = new TreeNode( _app.ActiveDocument.Title );
  this.treeView1.Nodes.Add( rootNode );
 
  _displayedElems = new ElementSet();
 
  foreach( Element e in _elems )
  {
    if( !_displayedElems.Contains( e ) )
    {
      DisplayNode( e, rootNode );
    }
  }
}
 
public Result( ElementSet elems, Autodesk.Revit.Application app )
{
  _elems = elems;
  _app = app;
 
  InitializeComponent();
  CreateRelationships();
 
  treeView1.AfterSelect
    += new TreeViewEventHandler(
      treeView1_AfterSelect );
}

Here is the external command Execute mainline implementation.
It creates a set of all model elements with visible graphics and representing a valid physical part of the building model and sends it the Result dialogue constructor, which calls the CreateRelationships method which in turn uses DisplayNode and GetDependentsElementIds to determine and display all dependencies:


Application app = commandData.Application;
Document doc = app.ActiveDocument;
 
Autodesk.Revit.Geometry.Options opt
  = app.Create.NewGeometryOptions();
 
BuiltInCategory bicPreviewLegendComponent
  = BuiltInCategory.OST_PreviewLegendComponents;
 
int iBic = ( int ) bicPreviewLegendComponent;
 
try
{
  // select all model elements:
 
  ElementSet a = app.Create.NewElementSet();
 
  ElementIterator it = app.ActiveDocument.Elements;
 
  while( it.MoveNext() )
  {
    Element e = it.Current as Element;
 
    if( !( e is Symbol )
      && !( e is FamilyBase )
      && ( null != e.Category )
      && ( iBic != e.Category.Id.Value )
      && ( null != e.get_Geometry( opt ) ) )
    {
      a.Insert( e );
    }
  }
 
  // show the object relationship dialog
 
  Result res = new Result( a, app );
  res.ShowDialog();
}
catch( Exception ex )
{
  message = ex.Message;
}
return IExternalCommand.Result.Failed;

We can use the following simple model to see the relationships displayed by this tool.
Here is a 3D view showing the physical building model:

Simple house 3D view

The model also includes a few annotation elements which are only visible in plan view:

Simple house plan view

Here is the result of running the ObjRel command on this model and then fully expanding the tree view nodes to display all of the nested relationships detected:

Simple house object relationships

As you can see, a number of relationships between various objects have been determined and are displayed, and many of the types of relationships are ones which we had not previously explicitly noted.

Here is the complete
ObjRel
source code and Visual Studio solution including the sample model we used.

Many thanks to Saikat for this brilliant idea of making such generic use of the Delete method to determine all object relationships and the neat little sample to demonstrate its use so succinctly and powerfully!


Comments

12 responses to “Object Relationships”

  1. Hi Jeremy,
    This isn’t directly related to this, but I couldn’t find a better place to post…
    I’m trying to figure out if there is a way to change the workplane of a workplane based family via API. I can find the workplane parameter but all I can get out of it is the string value and can’t set it. I also don’t see any methods to rehost to a new workplane. Is this possible?
    Thanks,
    Steve

  2. Dear Steve,
    It sounds to me as if it is not possible, unfortunately.
    Cheers, Jeremy.

  3. Ok, thanks for the confirmation at least…

  4. Ning Zhou Avatar
    Ning Zhou

    hi Jeremy,
    does ObjRel have updated version say 2012? somehow i cannot make it 100% work, i must miss something somewhere.
    many thanks.
    Ning

  5. Dear Ning,
    No, sorry, I have not updated it nor heard of anyone else doing so. People continue making use of this basic technique, though, so it should certainly work:
    http://thebuildingcoder.typepad.com/blog/2012/10/the-temporary-transaction-trick-for-gross-slab-data.html
    http://thebuildingcoder.typepad.com/blog/2012/11/temporary-transaction-trick-touchup.html
    What problem do you observe?
    Cheers, Jeremy.

  6. Ning Zhou Avatar
    Ning Zhou

    thanks Jeremy for your quick reply, and links are very helpful!
    temporary transaction trick is great, but i noticed that group instance(s) is perhaps an exception(?), for instance, if i delete a level, then group instance(s) based on that level won’t be listed in the “deleted” element list, well, i have to use separate group filter to add these group instances into the “deleted” element list, perhaps i missed something?
    Cheers,
    Ning

  7. Ning Zhou Avatar
    Ning Zhou

    furthermore, if i rehost that group instance to another level, then all IDs are changed (both group ID and group member IDs), even unique IDs are changed too, any tricks to deal w/ group in API, especially when rehost multiple group instances to another level? thanks, Ning

  8. Dear Ning,
    Thank you for your appreciation. I am glad it helped.
    Oh dear, yes, the groups may well be an exception. Glad to hear you know how to handle it.
    Cheers, Jeremy.

  9. Dear Ning,
    Wow, all the unique ids are changed, even the members’ ones? That sounds pretty spectacular.
    Nope, I am not aware of any such tricks, but it sounds as if a toolkit for dealing with this would be pretty useful.
    Cheers, Jeremy.

  10. Ning Zhou Avatar
    Ning Zhou

    thanks Jeremy, i figured out that if not a single member of group instance is sit on the level to be deleted, then it really doesn’t matter if group instance is sit on that level or not, well, this also solved the (unique) ID issue as i mentioned in previous message, because i do not have to rehost group instances before processing them (ungroup and swap as you described before).

  11. Dear Ning,
    Thank you for the good news! Glad that you found at least a partial solution.
    So what happens to the group instance on a level that is deleted? Do you simply move it to a different level, or what? Before or after deleting the level?
    Cheers, Jeremy.

  12. Ning Zhou Avatar
    Ning Zhou

    thanks Jeremy,
    2 cases of group instance sit on the level to be deleted:
    1) if no member sits on that level then that group instance will remain even after level is already deleted
    2) if at least one member sits on that level then that group instance will be deleted too after level is deleted
    so basically i only have to check if any member of that group instance sits on that level, won’t have to rehost group instance at all as i previously did which will eventually changed (unique) IDs before i start using ungroup and swap.
    Cheers,
    Ning

Leave a Reply to Ning ZhouCancel reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading