PickObject to select room in current model or linked models?

PickObject to select room in current model or linked models?

juqing27
Contributor Contributor
4,213 Views
11 Replies
Message 1 of 12

PickObject to select room in current model or linked models?

juqing27
Contributor
Contributor

Hello Community,

 

This may be somehow old but I cannot find a good solution for my specific case here 🙂 I’m working on a Revit addin and would like to prompt user to select a room in model. The room can be from either the current model or the linked models. With the help from previous posts in the forum I’m able  to make some progress so far, but still none of the approaches is ideal. Here are what I’ve tried and the downside –

  1. PickObject(ObjectType.Element, roomSelectionFilter). This works great for rooms in current model, but won’t allow user to select rooms from linked models.
  2. PickObject(ObjectType.LinkedElement, roomSelectionFilter_linkedRoom). This is the opposite of 1.: works great for rooms in linked models, but won’t allow user to select rooms from current model.
  3. PickObject(ObjectType.PointOnElement). This one allows user to select elements in both current and linked models. However, it’s not limited to rooms  - or at least I haven’t come up with a proper ISelectionFilter to achieve so.

So, any ideas? I’m thinking if there’s a way to combine 1. and 2. in one pick, or maybe there can be a ISelectionFilter to filter out anything that is not a room for 3.? Appreciate your help!

0 Likes
Accepted solutions (1)
4,214 Views
11 Replies
Replies (11)
Message 2 of 12

jeremytammik
Autodesk
Autodesk

Please share the code for the two filters that you mention, roomSelectionFilter and roomSelectionFilter_linkedRoom.

 

Selection filters are very flexible, easy to understand and implement.

 

Therefore, the two can very probably be combined into one that performs a Boolean OR between the two types of filtering required.

 



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes
Message 3 of 12

juqing27
Contributor
Contributor

Thanks Jeremy! Edited my post to include the two filters I've tried. Thank you! - 

 

  • PickObject(ObjectType.Element, new RoomSelectionFilter(_doc)). This works great for rooms in current model, but won’t allow user to select rooms from linked models. Code for the filter - 

 

public class RoomSelectionFilter : ISelectionFilter
    {
        Document doc = null;

        public RoomSelectionFilter(Document document)
        {
            doc = document;
        }

        public bool AllowElement(Element elem)
        {
            if (elem.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Rooms)
            {
                return true;
            }
            return false;
        }

        public bool AllowReference(Reference reference, XYZ position)
        {
            //return false;
            RevitLinkInstance revitlinkinstance = doc.GetElement(reference) as RevitLinkInstance;
            Autodesk.Revit.DB.Document docLink = revitlinkinstance.GetLinkDocument();
            Element eRoomLink = docLink.GetElement(reference.LinkedElementId);
            if (eRoomLink.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Rooms)
            {
                return true;
            }
            return false;
        }
    }

 

  • PickObject(ObjectType.LinkedElement, new RoomSelectionFilter_linkedRoom(_doc)). This is the opposite of 1.: works great for rooms in linked models, but won’t allow user to select rooms from current model. Code for the filter - 

 

public class RoomSelectionFilter_Linked : ISelectionFilter
    {
        Document doc = null;

        public RoomSelectionFilter_Linked(Document document)
        {
            doc = document;
        }

        public bool AllowElement(Element elem)
        {
            return true;
        }

        public bool AllowReference(Reference reference, XYZ position)
        {
            //return false;
            RevitLinkInstance revitlinkinstance = doc.GetElement(reference) as RevitLinkInstance;
            Autodesk.Revit.DB.Document docLink = revitlinkinstance.GetLinkDocument();
            Element eRoomLink = docLink.GetElement(reference.LinkedElementId);
            if (eRoomLink.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Rooms)
            {
                return true;
            }
            return false;
        }
    }

 

  • PickObject(ObjectType.PointOnElement). This one allows user to select elements in both current and linked models. However, it’s not limited to rooms  - or at least I haven’t come up with a proper ISelectionFilter to achieve so.
0 Likes
Message 4 of 12

RPTHOMAS108
Mentor
Mentor

There is no perfect solution for this. Ideally there would be a separate flags argument for deciding where the items come from and then can do away with ObjectType.LinkedElement i.e.:

OutsideLink = 1

WithinLink = 2

Both = 1 + 2

 

You could try a looping strategy such as below where you click once for a link or room and then if link click again for a room in the link. I would not advise using pickpoint as you would require an active work plane and depending on the depth of this within the view you may not be picking on an element you believe you are picking. You can achieve the same as below in C# with while loop.

 

 

 Public Class CatSelFilter
        Implements Selection.ISelectionFilter
        Public Property Categories As List(Of Integer)
        Public Function AllowElement(elem As Element) As Boolean Implements Selection.ISelectionFilter.AllowElement
            If elem.Category Is Nothing Then Return False Else
            Return Categories.Contains(elem.Category.Id.IntegerValue)
        End Function
        Public Function AllowReference(reference As Reference, position As XYZ) As Boolean Implements Selection.ISelectionFilter.AllowReference
            Return True
        End Function
    End Class


    Public Function TObj81(ByVal commandData As Autodesk.Revit.UI.ExternalCommandData,
ByRef message As String, ByVal elements As Autodesk.Revit.DB.ElementSet) As Result

        Dim doc As Document = commandData.Application.ActiveUIDocument.Document
        Dim uiDoc As UIDocument = commandData.Application.ActiveUIDocument
        Dim R As Reference = Nothing
        Dim SelType As Selection.ObjectType = Selection.ObjectType.Element
        Dim Cfilt As New CatSelFilter
        Cfilt.Categories = {CInt(BuiltInCategory.OST_Rooms), CInt(BuiltInCategory.OST_RvtLinks)}.ToList

back:
        Try
            R = uiDoc.Selection.PickObject(SelType, Cfilt)
        Catch ex As Exception
        End Try
        If R Is Nothing Then Return Result.Cancelled Else

        Dim RvtLnk As RevitLinkInstance = TryCast(doc.GetElement(R), RevitLinkInstance)
        If RvtLnk Is Nothing = False AndAlso SelType = Selection.ObjectType.Element Then
            SelType = Selection.ObjectType.LinkedElement
            GoTo back
        End If

        Dim El As Element = Nothing
        If SelType = Selection.ObjectType.LinkedElement Then
            El = RvtLnk.GetLinkDocument.GetElement(R.LinkedElementId)
        Else
            El = doc.GetElement(R)
        End If

        Return Result.Succeeded

    End Function

 

 

0 Likes
Message 5 of 12

juqing27
Contributor
Contributor

Thanks @RPTHOMAS108 ! Pick (a room or link) - decide whether a room or a link is selected - prompt another pick if a link is selected in the first one seems to be a good workaround at this time.

 

Still hoping @jeremytammik  could advise on how (or if it's possible) to build a ISelectionFilter that allows room room both current model and linked models. 🙂

 

Best,

Qing

 

 

0 Likes
Message 6 of 12

jeremytammik
Autodesk
Autodesk

Dear Richard,

 

Thank you for your good advice and solution. It looks nice and clean to me, very convincing.

 

I was hoping that something like ObjectType.PointOnElement could be used and the selection filter expanded to accept a point on either a room element or on a room element within a linked file.

 

I'm still not convinced that it cannot be done.

 

Have you tried that out? 

 

Qing Ju, have you?

 

Cheers,

 

Jeremy

 



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes
Message 7 of 12

juqing27
Contributor
Contributor

@jeremytammik @RPTHOMAS108 I'd love to try the route @jeremytammik suggest (PickObject(ObjectType.PointOnElement) with a proper selection filter, the 3rd approach in my original post 🙂 but I'm pretty new to Revit API and don't seem to be able to make the filter work for rooms from both current model and linked model... Can you show me some general idea, or point me to any similar example?

 

Thank you both!

 

Qing

0 Likes
Message 8 of 12

RPTHOMAS108
Mentor
Mentor

@jeremytammik you are correct .PointOnElement does provide a better approach thanks for highlighting this to me.

 

@juqing27 I have included below an Implementation of ISelectionFilter using generics. I've included this in VB but should be easy to convert to C#.

 

 Public Class ElementInLinkSelectionFilter(Of T As Element)
        Implements Selection.ISelectionFilter
        Private IntDoc As Document
        Private IntLastCheckedWasFromLink As Boolean = False
        Private IntLinkDoc As Document = Nothing

        Public Sub New(Doc As Document)
            IntDoc = Doc
        End Sub

        Public ReadOnly Property LinkDocument As Document
            Get
                Return IntLinkDoc
            End Get
        End Property
        Public ReadOnly Property LastCheckedWasFromLink As Boolean
            Get
                Return IntLastCheckedWasFromLink
            End Get
        End Property

        Public Function AllowElement(elem As Element) As Boolean Implements Selection.ISelectionFilter.AllowElement
            Return True
        End Function
        Public Function AllowReference(reference As Reference, position As XYZ) As Boolean Implements Selection.ISelectionFilter.AllowReference

            Dim El As Element = IntDoc.GetElement(reference)
            Dim RvtLnkInst As RevitLinkInstance = TryCast(El, RevitLinkInstance)
            If RvtLnkInst Is Nothing = False Then
                IntLastCheckedWasFromLink = True

                Dim LnkDoc As Document = RvtLnkInst.GetLinkDocument
                Dim ClsInst As T = TryCast(LnkDoc.GetElement(reference.LinkedElementId), T)
                If ClsInst Is Nothing = False Then
                    IntLinkDoc = RvtLnkInst.GetLinkDocument
                    Return True
                End If
            Else
                IntLastCheckedWasFromLink = False
                IntLinkDoc = Nothing
                Dim ClsInst As T = TryCast(El, T)
                If ClsInst Is Nothing = False Then
                    Return True
                End If
            End If

            Return False
        End Function
    End Class

ISelectionFilter implementation.

 

Public Function TObj82(ByVal commandData As Autodesk.Revit.UI.ExternalCommandData,
ByRef message As String, ByVal elements As Autodesk.Revit.DB.ElementSet) As Result

        Dim doc As Document = commandData.Application.ActiveUIDocument.Document
        Dim uiDoc As UIDocument = commandData.Application.ActiveUIDocument
        Dim R As Reference = Nothing
        Dim SelType As Selection.ObjectType = Selection.ObjectType.PointOnElement
        Dim Cfilt As New ElementInLinkSelectionFilter(Of Room)(doc)

        Try
            R = uiDoc.Selection.PickObject(SelType, Cfilt)
        Catch ex As Exception
        End Try
        If R Is Nothing Then Return Result.Cancelled Else

        Dim El As Element = Nothing
        If Cfilt.LastCheckedWasFromLink Then
            El = Cfilt.LinkDocument.GetElement(R.LinkedElementId)
        Else
            El = doc.GetElement(R)
        End If
        TaskDialog.Show("Picked", El.Name)

        Return Result.Succeeded
    End Function

Usage

 

0 Likes
Message 9 of 12

RPTHOMAS108
Mentor
Mentor
Accepted solution

Above in C#

 

 

public class ElementInLinkSelectionFilter<T> : ISelectionFilter where T : Element
		{
			private Document IntDoc;
			private bool IntLastCheckedWasFromLink = false;

			private Document IntLinkDoc = null;
			public ElementInLinkSelectionFilter(Document Doc)
			{
				IntDoc = Doc;
			}

			public Document LinkDocument
			{
				get { return IntLinkDoc; }
			}
			public bool LastCheckedWasFromLink
			{
				get { return IntLastCheckedWasFromLink; }
			}

			public bool AllowElement(Element elem)
			{
				return true;
			}
			public bool AllowReference(Reference reference, XYZ position)
			{

				Element El = IntDoc.GetElement(reference);
				RevitLinkInstance RvtLnkInst = El as RevitLinkInstance;
				if (RvtLnkInst == null == false)
				{
					IntLastCheckedWasFromLink = true;

					Document LnkDoc = RvtLnkInst.GetLinkDocument();
					T ClsInst = LnkDoc.GetElement(reference.LinkedElementId) as T;
					if (ClsInst == null == false)
					{
						IntLinkDoc = RvtLnkInst.GetLinkDocument();
						return true;
					}
				}
				else
				{
					IntLastCheckedWasFromLink = false;
					IntLinkDoc = null;
					T ClsInst = El as T;
					if (ClsInst == null == false)
					{
						return true;
					}
				}

				return false;
			}
		}

 

 

Implementation of ISelectionFilter

 

 

public Result TObj82(Autodesk.Revit.UI.ExternalCommandData commandData, ref string message, Autodesk.Revit.DB.ElementSet elements)
		{

			Document doc = commandData.Application.ActiveUIDocument.Document;
			UIDocument uiDoc = commandData.Application.ActiveUIDocument;
			Reference R = null;
			ObjectType SelType = ObjectType.PointOnElement;
			ElementInLinkSelectionFilter<Room> Cfilt = new ElementInLinkSelectionFilter<Room>(doc);

			try
			{
				R = uiDoc.Selection.PickObject(SelType, Cfilt);
			}
			catch (Exception)
			{
			}
			if (R == null)
				return Result.Cancelled;

			Element El = null;
			if (Cfilt.LastCheckedWasFromLink)
			{
				El = Cfilt.LinkDocument.GetElement(R.LinkedElementId);
			}
			else
			{
				El = doc.GetElement(R);
			}
			TaskDialog.Show("Picked", El.Name);

			return Result.Succeeded;
		}

 

 

Usage

 

I've extracted the LinkedDocument and determined the nature of the reference within the ISelectionFilter. It may be preferable to determine again these things outside. I just don't like going through the same process twice but since the filter is called when you hover over an element and not when you select an element it may be wise to move getting the linked document and determining again if the reference is from a link outside of ISelectionFilter.

 

You can also simplify the null checking of the above converted by using '!=' etc.

 

Message 10 of 12

juqing27
Contributor
Contributor

This is great! Thanks for combining the solution decently into one click! 

0 Likes
Message 11 of 12

jeremytammik
Autodesk
Autodesk

Very nice indeed!

 

Thank you for putting that together.

 

This is really worth saving, I think, so I cleaned it up and added it to The Building Coder samples.

 

Here is the diff to the previous version:

 

https://github.com/jeremytammik/the_building_coder_samples/compare/2021.0.149.2.../2021.0.150.0

 

Here is my version of your selection filter:

 

public class ElementInLinkSelectionFilter<T> : ISelectionFilter where T : Element
{
  private Document _doc;

  public ElementInLinkSelectionFilter( Document doc )
  {
    _doc = doc;
  }

  public Document LinkedDocument { get; private set; } = null;

  public bool LastCheckedWasFromLink
  {
    get { return null != LinkedDocument; }
  }

  public bool AllowElement( Element e )
  {
    return true;
  }

  public bool AllowReference( Reference r, XYZ p )
  {
    LinkedDocument = null;

    Element e = _doc.GetElement( r );

    if( e is RevitLinkInstance )
    { 
      RevitLinkInstance li = e as RevitLinkInstance;

      LinkedDocument = li.GetLinkDocument();

      e = LinkedDocument.GetElement( r.LinkedElementId );
    }
    return e is T;
  }
}

 

Here is the resulting external command for testing it:

 

public Result Execute(
  ExternalCommandData commandData,
  ref string message,
  ElementSet elements )
{
  UIApplication uiapp = commandData.Application;
  UIDocument uidoc = uiapp.ActiveUIDocument;
  Document doc = uidoc.Document;
  Reference r;

  ElementInLinkSelectionFilter<Room> filter
    = new ElementInLinkSelectionFilter<Room>(
      doc );

  try
  {
    r = uidoc.Selection.PickObject( 
      ObjectType.PointOnElement, 
      filter,
      "Please pick a room in current project or linked model" );
  }
  catch( Autodesk.Revit.Exceptions.OperationCanceledException )
  {
    return Result.Cancelled;
  }

  Element e;

  if( filter.LastCheckedWasFromLink )
  {
    e = filter.LinkedDocument.GetElement( 
      r.LinkedElementId );
  }
  else
  {
    e = doc.GetElement( r );
  }

  TaskDialog.Show( "Picked", e.Name );

  return Result.Succeeded;
}

 

Please let us know whether this works for you as well.

 

Thank you again, and cheers,

 

Jeremy

 



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 12 of 12

RPTHOMAS108
Mentor
Mentor

Thanks Jeremy,

 

Yes that is much cleaner and still works from the test I've done.

 

Regards

Richard

0 Likes