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 –
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!
Solved! Go to Solution.
Solved by RPTHOMAS108. Go to Solution.
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.
Thanks Jeremy! Edited my post to include the two filters I've tried. Thank you! -
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;
}
}
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;
}
}
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
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
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
@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
@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
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.
This is great! Thanks for combining the solution decently into one click!
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
Thanks Jeremy,
Yes that is much cleaner and still works from the test I've done.
Regards
Richard