Frustratingly - there is no direct way to get the Analytical Node Ids from an Analytical Member (it really seems like in the API there should be some really simple method like AnalyticalMember.EndNode(int) where you could get the Analytical Node that is at the i and j ends of an Analytical Member). There is the Hub Connection Manager available for Analytical Nodes that makes getting the Analytical Members associated to a Node pretty direct, but there isn't the inverse to go from Member to Node, and even with the Hub Connection Manager - you cannot tell whether a particular Node is at the i or j end of a member (Note that when you might have 30,000 Analytical Members and 45,000 Analytical Nodes - its not really efficient to have to query all 45,000 Nodes to then query their Hub Connection Manager just to find which two nodes belong to a particular member). Bottom line - you end up needing to do some geometric comparisons at some point taking the AnalyticalNode.Position (AnalyticalNodes are Reference Points) and then also taking the AnalyticalMember geometry line and getting the end points - then finding where the ReferencePoint XYZ coordinates match the EndPoint XYZ coordinates.
In the process of doing this, I've come across a puzzling inconsistency with the new Analytical Members vs Analytical Nodes. It seems that somewhere in the guts of Revit - the geometry curve end point coordinates that get reported get mysteriously rounded, but the ReferencePoint coordinates do not. See image below - clearly the AnalyticalNode ReferencePoint X Coordinate is 100.000002115674, but somehow the AnalyticalMember Line.EndPoint(0) sees it's X coordinate as 100.000000000. I'm no expert - but that seems bad to me even though it's very small numbers, any operation looking for A==B is not going to have any success unless someone knew ahead of time that they needed to go on and round down to 5 decimal places before doing a comparison.
If anyone (specifically anyone who works at Autodesk and wants to investigate and/or fix this) wants to check it out - the small file below has some Analytical Members in it with this exact problem with coordinate rounding. I'd still like to have a direct method to find which Nodes belong to a selected member, but with current capabilities the geometric comparisions seem to be the best route, but the coordinate rounding accuracy makes this a little weird. Note that this also makes for some head scratching when nodes show themselves as "Unconnected" but when looking at the geometry of the Analytical Members everything points to things should be connected (because the member geometry reported doest match the node coordinates in the model)
I, on the other hand, am neither an expert on structural engineering nor Dynamo, but I can certainly address one aspect that you mention:
> clearly the first X Coordinate is 100.000002115674, but somehow the other thing sees it's X coordinate as 100.000000000... that seems bad to me even though it's very small numbers; any operation looking for A==B is not going to have any success unless someone knew ahead of time that they needed to go on and round down to 5 decimal places before doing a comparison.
Yes.
In fact, anyone who has some expertise in these matter DOES already know that they need to perform some rounding operation when comparing ANY and ALL real floating-point numbers:
https://duckduckgo.com/?q=comparing+floating+point+number
That is standard. Check out the numerous explanation on the need for fuzz by The Building Coder:
https://www.google.com/search?q=fuzz&as_sitesearch=thebuildingcoder.typepad.com
You should consider that the same ReferencePoint element can be associated with multiple element ends. So if those ends are not perfectly meeting (numerically) then which member end should the node be assigned to? I assume for small distances that Revit considers to be the same in terms of single node then the position may be averaged. Regardless that number you refer to is less than a thousandth of a mm (fitness for purpose). As noted by Jeremy and elsewhere the double structure has inherent issues at the minor decimal places so should not be used for direct equality comparisons.
I don't know the purpose of your development but I would focus less on reference points and more on distances between ends of curves etc. The only advantage of a reference point to the analytical model appears to be to indicate if one member is connected to another. If we knew the minimum proximity required for an end to be considered connected to something else then that would be all we needed.
However you could create some extension methods to get the reference point from the end of an analytical member. In the below I set up bounding box filter local to an end and filter for objects of ReferencePoint. I then verify that the point found has a connector to the original member.
<Extension>
Public Function GetAnalyticalNodeAtEnd(AM As [Structure].AnalyticalMember, Idx As Integer) As ReferencePoint
Dim FEC As New FilteredElementCollector(AM.Document)
Dim EP As XYZ = AM.GetCurve.GetEndPoint(Idx)
Dim Dx As Double = 1 / 304.8
Dim Min As XYZ = EP - New XYZ(Dx, Dx, Dx)
Dim Max As XYZ = EP + New XYZ(Dx, Dx, Dx)
Dim OL As New Outline(Min, Max)
Dim BBInside As New BoundingBoxIsInsideFilter(OL)
Dim BBInt As New BoundingBoxIntersectsFilter(OL)
Dim LorF As New LogicalOrFilter(BBInt, BBInside)
Dim Els As List(Of ReferencePoint) =
FEC.OfCategory(BuiltInCategory.OST_AnalyticalNodes).WherePasses(LorF).OfType(Of ReferencePoint).ToList
Dim Out As ReferencePoint = Els.FirstOrDefault(Function(k) k.GetHub.ConnectsMember(AM.Id))
Return Out
End Function
<Extension>
Public Function GetHub(RP As ReferencePoint) As [Structure].Hub
Dim HB As [Structure].Hub = RP.Document.GetElement(RP.GetHubId)
Return HB
End Function
<Extension>
Public Function ConnectsMember(H As [Structure].Hub, ID As ElementId) As Boolean
Dim CM As ConnectorManager = H.GetHubConnectorManager
Dim CS As ConnectorSet = CM.Connectors
For ia = 0 To CS.Size - 1
Dim Ca As Connector = CS(ia)
Dim CSb As ConnectorSet = Ca.AllRefs
For ib = 0 To CSb.Size - 1
Dim Cb As Connector = CSb(ib)
If Cb.Owner.Id = ID Then
Return True
End If
Next
Next
Return False
End Function
public static class AnalyticalExtensions
{
public static ReferencePoint GetAnalyticalNodeAtEnd(this AnalyticalMember AM, int Idx)
{
FilteredElementCollector FEC = new FilteredElementCollector(AM.Document);
XYZ EP = AM.GetCurve().GetEndPoint(Idx);
double Dx = 1.0 / 304.8;
XYZ Min = EP - new XYZ(Dx, Dx, Dx);
XYZ Max = EP + new XYZ(Dx, Dx, Dx);
Outline OL = new Outline(Min, Max);
BoundingBoxIsInsideFilter BBInside = new BoundingBoxIsInsideFilter(OL);
BoundingBoxIntersectsFilter BBInt = new BoundingBoxIntersectsFilter(OL);
LogicalOrFilter LorF = new LogicalOrFilter(BBInt, BBInside);
List<ReferencePoint> Els = FEC.OfCategory(BuiltInCategory.OST_AnalyticalNodes).WherePasses(LorF).OfType<ReferencePoint>().ToList();
ReferencePoint Out = Els.FirstOrDefault(k => k.GetHub().ConnectsMember(AM.Id));
return Out;
}
public static Hub GetHub(this ReferencePoint RP)
{
Hub HB = (Hub) RP.Document.GetElement(RP.GetHubId());
return HB;
}
public static bool ConnectsMember(this Hub H, ElementId ID)
{
ConnectorManager CM = H.GetHubConnectorManager();
ConnectorSet CS = CM.Connectors;
ConnectorSetIterator CSitr = CS.ForwardIterator();
CSitr.Reset();
while (CSitr.MoveNext())
{
Connector Ca = (Connector) CSitr.Current;
ConnectorSet CSb = Ca.AllRefs;
ConnectorSetIterator CSitrB = CSb.ForwardIterator();
CSitrB.Reset();
while (CSitrB.MoveNext())
{
Connector Cb = (Connector) CSitrB.Current;
if (Cb.Owner.Id == ID)
{
return true;
}
}
}
return false;
}
}
I get that the Analytical Point can be associated to multiple points. It’s just that in this case - it’s not. And you cannot have an analytical member without an analytical node - so in my mind it stands to reason that at each end of an Analytical member (I,J) there must be an Analytical Node with the same end coordinates. But apparently with the fuzzy math stuff per Jeremy’s response- even when a singular Analytical Member is created and two Analytical Nodes are generated - the Node and the Member End Coordinates are not necessarily 100.0000000% the same.
The reason why this matters is that the Analytical Nodes carry a nice little bit of information about their connectivity. What I am writing is a tool to repair the connectivity in Revit. Even though the AnalyticalMember sees its X coordinate as 100.0000000 - that small diff in the Analytical Node X Coordinate (which is down into the fuzzy math nether regions) is enough for the Node to think it is unconnected inside of Revit.
It seems strange that Revit would consider the item unconnected for such a negligible distance.
If you have three ends meeting at a node then is it possible to average the coords for those and redefine the curve ends to that same averaged point in order to join them? You may get an issue trying to make such minor changes, historically that was an issue (small curve tolerance affecting minor adjustments). I think elsewhere at some point they masked internally the exception you got regarding short curve tolerance so you may find the change isn't occurring if it is minor. If that is an issue you can make two movements one off target and then back (hopefully that isn't required.)
I also don't know what significance the nodes play when you export i.e, you may have two nodes close together but perhaps the end result is they will be packed because the analysis program/exporter considers them the same. It is also possible the analysis exporter only considers the member ends and not the reference points.
If I were creating an analytical exporter I would just create a node list containing common points. I would iterate the members to associate each end to a node by proximity. I have to break each member into sub members add the intersections along the member. However in the end my inputs for geometry are:
Numbered nodes list: N0, N1, N2...Nn
Member instances list: M0(N0, N1), M1(N0, N2)
Super members: (M0, M2, M3)
So I'm probably making my own judgements about connectivity anyway and not necessarily needing the Revit version of connectivity. Robot may be different because they have internal discussion and get closer integration perhaps.
I am definitely no expert in this as you say. Novice at best. That said - in my ignorance - there are certain expectations I have that seem to be not being met.
For Instance - Analytical Nodes are "Children" of Analytical Members (the Node is created upon the creation of the member) - so my expectation is that the child would adopt the properties of the parent (in real life we all know this is not the case - often to much frustration). But in the case of the Analytical Node XYZ coordinates and the Analytical Member End Point XYZ coordinates - this is not happening. In the attached example file - there are only 2 analytical members and 4 analytical nodes. From the image below taken from that file - you can see that the one member appears as if it should be connected to the other member - and looking at the member end point coordinates they all have the EXACT SAME X and Z coordinates (so they should be connected). But the Analytical Node has that "fuzzy" error in that it's X coordinate is 0.000002115674 ft (1/39388 inches) off of being exactly on the line - so it's reporting itself as "Unconnected".
My entire use case here is that when we get an analytical model from Revit - we are going to take that model over to some analytical software (i.e. SAP2000) and we'd like to fix connectivity issues in Revit ahead of time and "clean" the data to be more well-rounded numbers. With this issue - when I set a View Filter to colorize all "unconnected" nodes so I can see where I potentially have repairs to make - the model (which in this case is over 30,000 members and 40,000 nodes) lights up like a Christmas tree. And these "unconnected" nodes do truly act unconnected - they are not "Sticky" to the member they really practically should be attached to.
So to your point about fuzzy math - is it possible that in the creation of the Analytical Nodes and Analytical Members in Revit and the checking of Connectivity that perhaps the fuzzy math is somehow getting ignored within Revit where it perhaps should not be being ignored?
I'm aware of that - but my point is that that the inverse of that should also be possible to go from Member to Nodes instead of having to go from Nodes to Members. Essentially - every Analytical Member has a Start and End Point which are at Analytical Nodes - so in practice every Analytical Member Should be a "HUB" that contains 2 Analytical Nodes - one at the Start and one at the End.
In a model with 40,000 nodes - selecting a single analytical member and then looping through 40,000 nodes and checking the Hub and Connecetor manager for that particular Analytical Member is really an inefficient way to go about finding which 2 nodes belong to that member - and even after all that you still would not know which node was at the member Start Point and which was at the End Point until you do some geometric comparisons.
in your plugin you collect dictionary or class with points and their analytical members. And when you need to get point from analytical member you can apply to your dictionary
I don't think this would work very well. The Analytical Nodes are a bit transient - if two Nodes are moved to the same coordinate, then one Node ceases to exist and then both members show as being connected to the same (remaining Node). So a Dictionary solution on the front end of a plugin would need to be rewritten/updated after every single operation because as Analytical Member positions are adjusted - the Nodes they connect to often change as initial unconnected Nodes may get overlapped and combined to single Nodes.
When you transfer those insignificant differences through to the analysis program are you finding that they are maintained there as imperfections also?
I think also the issue regarding ReferencePoints could be a bit misleading to your task of closing gaps anyway i.e. The ReferencePoint will confirm one member is connected to another but it doesn't tell you if one member should be connected to another (small gap but large enough to not share a hub). You only get an idea of that by solving geometrically by end proximity to curve or other end point.
The ReferencePoint is it's own element so if it is your solution or an internal RevitAPI solution it will involve iterating such items to find a match. Sounds better to iterate the ReferencePoints and find the related members rather than the other way around perhaps.
in the specific case of the illustration and the 2 member example file i attached - no - the insignificant differences wouldnt matter because when we transfer to analysis we are taking the member endpoint geometries, not the analytical node geometries. And the member geometries are such that the member endpoint actually does lie on (is supported by) the other analytical member (the Analytical member is connected at that endpoint because the member Curve Endpoint lies on the other member, but the Associated Analytical Node says "Unconnected" because it is infinitesimally not in the same XYZ location as it's Associated Analytical Member endpoint and is thus NOT connected to the intersecting Analytical Member).
The problem is that when you look at the Revit Analytical model - the "connectivity" of the model is communicated via the Analytical Nodes - not via the members. And the Analytical Nodes are "Supposed" to always represent some endpoint of a member (you cant have an analytical node sitting somewhere all by itself - Analytical Nodes are created at Analytical Member endpoints - just like you cant have an Analytical Member without having Analytical Nodes). Most of the time - this works fine. On the job i am currently looking at - it seems that there are literally thousands of instances where this is the case. So when trying to repair the "Connectivity" of the model - you literally cant tell or trust whether the Analytical Members are actually Unconnected (i.e. the Analytical node is several inches or feet away from the Intersecting Analytical Member) or whether the Analytical Members are "psuedo-connected" (Endpoint of analytical member is on the intersecting member but the node is a hair's width off of the intersecting member).
I get that the Connectivity can be misleading sometimes (i.e. two purlins connect to each other but miss connecting to the girder) - but that is not the problem here. The problem is that the analytical member endpoint XYZ is every so amazingly a small number NOT the same XYZ as the Analytical Node - and thus it causes all sorts of headaches both with my scripts (due to me assuming they would be exactly the same numbers because they should represent exactly the same location) and with visually triaging the connectivity via View Filters.
I see what you mean. It definitely should not be that pedantic about it.
Hello @jeremy_tammik what is the status of the AnalyticalModelSelector that has been around since 2011? The example in the RevitAPI seems to have been updated to use the 2023 'AnalyticalMember' terminology but it doesn't appear to work.
In the below example the R0, R1 and R2 are the same.
Private Function Obj_230327b(ByVal commandData As Autodesk.Revit.UI.ExternalCommandData,
ByRef message As String, ByVal elements As Autodesk.Revit.DB.ElementSet) As Result
Dim UIApp As UIApplication = commandData.Application
Dim UIDoc As UIDocument = commandData.Application.ActiveUIDocument
If UIDoc Is Nothing Then Return Result.Cancelled Else
Dim IntDoc As Document = UIDoc.Document
Dim R As Reference = Nothing
Try
R = UIDoc.Selection.PickObject(Selection.ObjectType.Element)
Catch ex As Exception
Return Result.Cancelled
End Try
Dim AM As AnalyticalMember = TryCast(IntDoc.GetElement(R), AnalyticalMember)
If AM Is Nothing Then Return Result.Cancelled Else
'Dim XX = AnalyticalNodeData.GetAnalyticalNodeData(AM)
Dim C As Curve = AM.GetCurve
Dim AMS As New AnalyticalModelSelector(C)
AMS.CurveSelector = AnalyticalCurveSelector.WholeCurve
Dim R0 As Reference = AM.GetReference(AMS)
AMS.CurveSelector = AnalyticalCurveSelector.StartPoint
Dim R1 As Reference = AM.GetReference(AMS)
AMS.CurveSelector = AnalyticalCurveSelector.EndPoint
Dim R2 As Reference = AM.GetReference(AMS)
Dim RP = IntDoc.GetElement(R1)
Return Result.Succeeded
End Function
Also I do see this phenomenon of leaving nodes (ReferencePoints) behind if the member is disconnected whilst zoomed in. If you zoom in quite far and disconnect the node you get the below which is only corrected if you reconnect the member after zooming out. It is probably more of a product/graphics issue than an API issue to be fair (although it might explain the odd numerical values reported). It is probably a case of movement is too small to regenerate the node, the largest gap I achieved was around 0.2mm which is still significantly insignificant for an analytical model in my view.
Revit (via the API) will let me move nodes using ElementTransformUtils very infinitesimally small amounts (as in the 0.00000211 etc ft required to correct the particular node I showed in the example).
I wrote up a small Dynamo script last night that went through every node and checked to see how close it was to the nearest 1/2” clean dimension in the X and Y coordinates then corrected the nodes as needed (very useful little script - though it did take hours to run through 40,000 nodes).
I get that 0.2mm is insignificant from an analytical point of view - except that sometimes that 0.2mm might make something show “connected” that might get exported “unconnected” or vice versa cause the user to work hard to connect something that appears unconnected.
My use case is that I have a in-house plugin for analytical model manipulation that allows me to adjust selected ends of members to explicit X, Y, and/or Z coordinates, or to extend selected members to extend one end in a particular vector direction to the intersect with a specified plane or datum. To avoid having to always interpret whether we want the I or J end of of the member to be adjusted - I use selection of Member(s) plus node(s) to denote which end of the member should be adjusted using the selected transform method. Hence the need to pair up nodes and member curve ends and the confusion when XYZ member end does not equal XYZ node .
The tool was made 4 years ago (and updated to 2023 a year ago) and this is the first time I’ve seen this issue - new analytical model engine…new quirks???
Can't find what you're looking for? Ask the community or share your knowledge.