Selecting a Face in a Linked File

Here is a chance to look at an interesting method that we never discussed yet, ConvertToStableRepresentation, and the hidden information that it provides access to.
It does what it says, converting a Reference to a stable string representation.

Reference and Stable Representation

A reference provides a possibility to identify a piece of geometry, even though geometry is transient, not persistent, memory only, generated on the fly.
Still, we sometimes need to identify a piece of it and remember which piece it was.
For instance, to

dimension the distance


between two parallel walls
,
we need to identify which wall face we are measuring from.
This can be achieved using references.

A stable representation can be used to preserve and restore a reference later in the same Revit session or even in a different session in the same document.
The ParseFromStableRepresentation method is used to restore the reference.
The representation is based on the internal Revit structure and is not intended to be parsed by anyone else except ParseFromStableRepresentation.

Here is a rather unexpected use of this method and the undocumented internals of the stable string representation:

Face Selection in Linked File

Some Revit API functionality is limited to the current project and will not work for linked files.
Currently, this most heavily affects the geometric analysis, and methods like FindReferencesWithContextByDirection and its new optimised and simplified Revit 2013 wrapper class ReferenceIntersector.

Another affected area is the interactive element selection, which led to the following

question
by

Valentin Louzeau
:

Question: I’m working on a plugin and asking the user to select a face, but it does not work in linked files.
I found something using ObjectType.PointOnElement for selection but it doesn’t seem to work for my needs.
Is there an easy way to do that or do you already have a solution?

Answer: Nope, sorry, there is no easy way to work on a face in a linked file, and I am not aware of any solution for this.

Response: I found a solution using the method ConvertToStableRepresentation on the Reference class.

This string contains the ref document unique ID, its name, and the ID of the picked element in this document.
You can get its position in the ref doc and if you insert origin to origin, the element has the same position in the two documents.

Here is an example of a stable representation for a reference to a picked face on a wall in a linked file:

  • “7c25d827-3c9c-4cca-b3a0-dd28ee09a289-0002e3a1:0:RVTLINK/7c25d827-3c9c-4cca-b3a0-dd28ee09a289-0002e3a0:161224:5:SURFACE”

From this, the wall element id “161224” can be extracted.

Here is some code in which I tested making use of this:


[TransactionAttribute( TransactionMode.Manual )]
public class SelectFaceInLinkedFile : IExternalCommand
{
  public Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements )
  {
    UIApplication uiapp = commandData.Application;
 
    PlanarFace Plan = SelectFace( uiapp );
 
    return Result.Succeeded;
  }
 
  public static PlanarFace SelectFace(
    UIApplication uiapp )
  {
    Document doc = uiapp.ActiveUIDocument.Document;
 
    // get all ref doc. 
 
    IEnumerable<Document> doc2
      = GetLinkedDocuments( doc );
 
    // get the ref of a selected plane
 
    Selection sel = uiapp.ActiveUIDocument.Selection;
 
    Reference pickedRef = sel.PickObject(
      ObjectType.PointOnElement,
      "Please select a Face" );
 
    Element elem = doc.GetElement(
      pickedRef.ElementId );
 
    // get the true position picked 
    // in the active document
 
    XYZ pos = pickedRef.GlobalPoint;
 
    // get the ID of the element containing the 
    // face you picked in the active document 
    // and in its host document
 
    string s = pickedRef
      .ConvertToStableRepresentation( doc );
 
    string[] tab_str = s.Split( ':' );
 
    string id = tab_str[tab_str.Length - 3];
 
    int ID;
    Int32.TryParse( id, out ID );
 
    Type et = elem.GetType();
 
    if( typeof( RevitLinkType ) == et
      || typeof( RevitLinkInstance ) == et
      || typeof( Instance ) == et )
    {
      foreach( Document d in doc2 )
      {
        if( elem.Name.Contains( d.Title ) )
        {
          Element element = d.GetElement(
            new ElementId( ID ) );
 
          Options ops = new Options();
          ops.ComputeReferences = true;
 
          // write the name of the element and the 
          // number of solids in this only for 
          // control to show the possibilities
 
          MessageBox.Show( element.Name,
            element.get_Geometry( ops )
              .Objects.Size.ToString() );
 
          GeometryObject obj
            = element.get_Geometry( ops )
              .Objects.get_Item( 0 );
 
          // test all surfaces of solids in the 
          // element and return the one containing 
          // the picked point as a planarface to 
          // build my sketchplan
 
          foreach( GeometryObject obj2 in
            element.get_Geometry( ops ).Objects )
          {
            if( obj2.GetType() == typeof( Solid ) )
            {
              Solid solid2 = obj2 as Solid;
              foreach( Face face2 in solid2.Faces )
              {
                try
                {
                  if( face2.Project( pos )
                    .XYZPoint.DistanceTo( pos ) == 0 )
                  {
                    return face2 as PlanarFace;
                  }
                }
                catch( NullReferenceException )
                {
                }
              }
            }
          }
        }
      }
    }
    return null;
  }
 
  // this part is not mine, i found it on the internet, 
  // i don't anderstand all the code
 
  public static IEnumerable<ExternalFileReference>
    GetLinkedFileReferences( Document _document )
  {
    var collector = new FilteredElementCollector(
      _document );
 
    var linkedElements = collector
      .OfClass( typeof( RevitLinkType ) )
      .Select( x => x.GetExternalFileReference() )
      .ToList();
 
    return linkedElements;
  }
 
  public static IEnumerable<Document>
    GetLinkedDocuments( Document _document )
  {
    var linkedfiles = GetLinkedFileReferences(
      _document );
 
    var linkedFileNames = linkedfiles
      .Select( x => ModelPathUtils
        .ConvertModelPathToUserVisiblePath(
          x.GetAbsolutePath() ) ).ToList();
 
    return _document.Application.Documents
      .Cast<Document>()
      .Where( doc => linkedFileNames.Any(
        fileName => doc.PathName.Equals( fileName ) ) );
  }
}

Here is
SelectFaceInLinkedFile.zip containing
Valentin’s sample code and Visual Studio solution.

Many thanks to Valentin for the novel use of this method and its undocumented embedded information.

This is obviously all undocumented internal stuff that is not guaranteed to work at all in any way, and actually is guaranteed to change at some point in the future (as everything must change) with no prior warning whatsoever, so use at your own risk or just ponder and enjoy.


Comments

12 responses to “Selecting a Face in a Linked File”

  1. +1 for

    this part is not mine, i found it on the internet,
    i don’t anderstand all the code

  2. Dear Maxence,
    Thank you! All praise goes to Valentin :-)
    Cheers, Jeremy.

  3. Jeremy,
    In a Revit architectural model we are able to retreive the walls that make up the boundaries of a space via BoundarySegment. When in a mechanical model with a linked architectural file the boundarysegment only returns the linked file id. We were hoping to use the ConverttoStableRepresentation code above to get to the linked element data but the boundarysegment only returns the link file without the id of the surface.
    Any ideas on how we can get the id of walls that make up the boundaries of a space that are in a linked file?
    We learn alot from you blog. Please keep it coming!
    Thanks so much,

  4. Dear Ted,
    Thank you very much for your appreciation. I am glad it helps.
    Nope, sorry, I have no off-hand solution for the issue you describe. You can of course open the linked file and start analysing its geometry yourself.
    Rest assured that both user and API access to geometry in linked files is being continuously enhanced, though, so we are getting there…
    Cheers, Jeremy.

  5. Hi Jeremy,
    if you include the following (in a vb.net version)
    If elem.Name.Contains(d.Title) Then
    Dim rli As Instance = TryCast(elem, Instance)
    If rli Is Nothing Then Continue For
    Dim t As Transform = rli.GetTransform
    pos = t.Inverse.OfPoint(pos)
    then you can get the face regardless of the location/rotation of the revitlinkinstance. The critical lines are the last two. (The first line matches
    if( elem.Name.Contains( d.Title ) )
    in the above code.

  6. Rolando Avatar
    Rolando

    Hi Jeremy. Could you please give some example code about the use of Reference.CreateLinkReference(RvtLink) to place a face based family in a linked model.
    Thanks

  7. Paul Marsland Avatar
    Paul Marsland

    Jeremy
    Like Rolando, I have been toying (unsuccessfully) with CreateLinkReference since it appeared in the API, documentation and examples of how this is implemented are virtually non existent. I would greatly appreciate a code example showing how a face based family can be attached to a face in a linked file.
    I eagerly await a response
    Paul M

  8. Dear Rolando,
    Thank you for asking about this and sorry for the delay. Your wish is my command :-)
    http://thebuildingcoder.typepad.com/blog/2014/07/createlinkreference-sample-code.html
    Cheers, Jeremy.

  9. Dear Paul,
    Thank you for adding your request to Rolando’s.
    That really got me going:
    http://thebuildingcoder.typepad.com/blog/2014/07/createlinkreference-sample-code.html
    Cheers, Jeremy.

  10. Paul Marsland Avatar
    Paul Marsland

    Jeremy
    Where I am having trouble using this is where I prompt the user to select a ceiling in a linked model (in my case I am specifically interested in ceiling grids as the API in 2014 does not allow the user to select a horizontal face when using PromptForFamilyInstancePlacement).
    I am prompting the user to select a Ceiling grid in a linked file using:
    Reference eRefLink = choices.PickObject(ObjectType.LinkedElement, CeilingFilter, “Select a Ceiling Grid in a linked document”);
    The reference returned (eRefLink) has the CreateLinkReference method exposed to it (ie: it is listed automatically in Visual Studio) but it is the selected element eRefLink.LinkedElementId for which I need a LinkReference. I want to pass the reference to:
    NewFamilyInstance(face, position, direction, symbol).
    All works fine in the host document using:
    PickObject(ObjectType.Face, “Select a Face in this document”);
    From above I can pass the reference returned directly to NewFamilyInstance. I extracted all the ElementId’s using both methods (whilst the model is the host and as a linked file) and the Id returned from the face picked in the host exactly matches the Id when extracted using eRefLink.LinkedElementId.
    I know I must just be missing a few steps but cannot work out what.
    Any help on this would be greatly appreciated as its the last piece of my jigsaw.
    regards
    Paul Marsland

  11. Paul Marsland Avatar
    Paul Marsland

    Jeremy
    I tried getting the code posted at: http://thebuildingcoder.typepad.com/blog/links/
    to function. First I tried modifying it to work with a ceiling but received the “Reference direction is parallel to face normal at insertion point” error.
    So I tried using the code exactly as is for the wall but received the same error.
    I had attempted to use another snippet posted previously to you blog by Valentin Louzeau at:
    http://thebuildingcoder.typepad.com/blog/2012/05/selecting-a-face-in-a-linked-file.html
    But I could never get this code to work because regardless of what orientation I used I always received the same “Reference direction is parallel to face normal at insertion point” error.|
    In every case if I open the link file directly I can insert to the ceiling or wall without any problems.
    regards
    Paul Marsland

  12. Dear Paul,
    The first link you point out is very unspecific, so I don’t know exactly what you mean.
    I think the best way forward would be for you to submit a simple, minimal, reproducible case so that we know exactly what we are talking about and can monitor the progress we make resolving it:
    http://thebuildingcoder.typepad.com/blog/about-the-author.html#1b
    You could either submit it as an ADN case, if you are a member, or post it to the Autodesk community Revit API discussion forum, and I will escalate it to an ADN case for you. That will provide a handy container for the associated files.
    Thank you!
    Cheers, Jeremy.

Leave a Reply to Jeremy TammikCancel reply

Discover more from Autodesk Developer Blog

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

Continue reading