For Jeremy (Stable Reference Strings)
I didn't want to keep Hijacking the other thread so I thought I'd make a new one.
As requested by Jeremy, Here's a summary of my investigations into stable reference strings created using Reference.ConvertToStableRepresentation and the Voodoo Magic that they can perform.
A Reference String is a collection of UniqueIds combined with table indecies (delimeted by the ':' character). a reference string for a PlanarFace of a FamilyInstance Element (Accessed through GetSymbolGeometry() )looks like this:
66a421da-90cb-4df2-8efe-c312e4f78aa0-0003d32b:0:INSTANCE:a2177cbb-03d8-4416-a4d8-8ada6fc0165a-0003ebf0:78:SURFACE
The string obtained from the reference of the same face accessed using GetInstanceGeometry() will look like:
a2177cbb-03d8-4416-a4d8-8ada6fc0165a-0003ebf0:78:SURFACE
as you can see this is identical to the last 3 sections of the symbol geometry reference.
the SymbolGeometry reference looks to be 2 references of 3 tokens each
the structured data contained in the string can be used to reliably get at geometry objects that are not completely exposed by the API.
this is done by building a custom string using parts of an existing valid string obtained from the API.
The fist step is to tokenise the string:
string refString = myReference.ConvertToStableRepresentation(dbDoc); String[] refTokens = refString.Split(new char[] { ':' });
this will give you an array of string tokens, see below for my interpretation of each token's purpose, I could be wrong on some of these as I've just inferred this information through experimentation (symbol geometry string used as example).
token0: 66a421da-90cb-4df2-8efe-c312e4f78aa0-0003d32b - UniqueId of the FamilyInstance Element.
token1: 0 - Presumably an index into a collection within the FamilyInstance (I've not seen anything other than zero here so it could be that the collection always contains a single entry, or that other entries have some other special unknown use)
token2: INSTANCE - signals that the referenced geometry object belongs to A FamilyInstance Element.
token3: a2177cbb-03d8-4416-a4d8-8ada6fc0165a-0003ebf0 - UniqueId of the FamilySymbol Instance that describes the geometry of this FamilyInstance (multiple FamilyInstances that have identical Geometry will reference the same FamilySymbol Instance.
token4: 78 - the index of the referenced geometry object within the FamilySymbol Instance's geometry table. index positions 0 through 8 are reserved for the special reference planes that have been specified in the family editor (explained later).
token5: SURFACE - the type of geometry object referenced by this string.
I think the best way to try to follow the hierarchy is to read the tokens in reverse order as follows:
The referenced geometry object of type: SURFACE is at index: 78 of the FamilySymbol Instance with UniqueId: a2177cbb-03d8-4416-a4d8-8ada6fc0165a-0003ebf0 as represented by the FamilyInstance with UniqueId: 66a421da-90cb-4df2-8efe-c312e4f78aa0-0003d32b
It bothers me that references from geometry objects accessed through GetSymbolGeometry() specify the FamilyInstance while those from GetInstanceGeometry do not, yet the GetInstanceGeometry() objects will be correctly transformed into project coordinates according to the FamilyInstance, where the GetSymbolGeometry() objects are left in Family coordinate space. this seems a little backwards to me.
Another strange behaviour is that the ElementId property of the reference from an object returned by GetInstanceGeometry() does not even match the FamilyInstance it came from (it actually refers to the FamilySymbol Instance).
Now for the Voodoo Magic that I promised.
If you have a reference in SymbolGeometry format, (as obtained from a Dimension or through the Selection.PickObject method)and you need to get the geometry object in its correct position according to the FamilyInstace, you can tokenise the reference string as demonstrated above and then rebuild it as an InstanceGeometry reference string by discarding the first 3 tokens and joining the remaining 3 using ':' as the delimiter.
you then obtain the InstanceGeometry from the element linked to the original reference and loop through it until you find an object that has a stable reference string that exactly matches your custom made one.
public static Edge GetInstanceEdgeFromSymbolRef(Reference symbolRef, Autodesk.Revit.DB.Document dbDoc) { Edge instEdge = null; Options gOptions = new Options(); gOptions.ComputeReferences = true; gOptions.DetailLevel= ViewDetailLevel.Undefined; gOptions.IncludeNonVisibleObjects = false; Element elem = dbDoc.GetElement(symbolRef.ElementId); string stableRefSymbol = symbolRef.ConvertToStableRepresentation(dbDoc); string[] tokenList = stableRefSymbol.Split(new char[] { ':' }); string stableRefInst = tokenList[3] + ":" + tokenList[4] + ":" + tokenList[5]; GeometryElement geomElem = elem.get_Geometry(gOptions); foreach(GeometryObject geomElemObj in geomElem) { GeometryInstance geomInst = geomElemObj as GeometryInstance; if(geomInst != null) { GeometryElement gInstGeom = geomInst.GetInstanceGeometry(); foreach(GeometryObject gGeomObject in gInstGeom) { Solid solid = gGeomObject as Solid; if(solid != null) { foreach(Edge edge in solid.Edges) { string stableRef = edge.Reference.ConvertToStableRepresentation(dbDoc); if(stableRef == stableRefInst) { instEdge = edge; break; } } } if(instEdge != null) { // already found, exit early break; } } } if(instEdge != null) { // already found, exit early break; } } return instEdge; }
As I mentioned earlier the first 9 positions are occupied by the special references that have been allocated in the family editor the list is as follows:
0 = Left
1 = Center Left/Right
2 = Right
3 = Front
4 = Centre Front/Back
5 = Back
6 = Bottom
7 = Center Elevation
8 = Top
As you can see this sequence matches the order they appear in the family editor reference type drop-down list.
these references are handy for creating Dimensions but the API does not expose them fully (they exist in the element geometry but have the generic type of GeometryObject with no indication of their special purpose other than the index found in the reference string. to get one of these references all you need to do is get any geometry object from the FamilyInstance using GetSymbolGeometry and use it's reference string as a template to build a custom reference that points to the special reference.
you just need to tokenise the reference string and replace token4 with the index value from the list above for the reference you want. token5 should also be Replaced with "SURFACE" but I've found that you can safely leave out the last token and the reference string will still be valid.
to simplify things I created an enumeration for the indecies and a static method that simplifies the process:
public enum SpecialReferenceType { Left = 0, CenterLR = 1, Right = 2, Front = 3, CenterFB = 4, Back = 5, Bottom = 6, CenterElevation = 7, Top = 8 } public static Reference GetSpecialFamilyReference(FamilyInstance inst, SpecialReferenceType refType) { Reference indexRef = null; int idx = (int)refType; if(inst != null) { Document dbDoc = inst.Document; Options geomOptions = dbDoc.Application.Create.NewGeometryOptions(); if(geomOptions != null) { geomOptions.ComputeReferences = true; geomOptions.DetailLevel = ViewDetailLevel.Undefined; geomOptions.IncludeNonVisibleObjects = true; } GeometryElement gElement = inst.get_Geometry(geomOptions); GeometryInstance gInst = gElement.First() as GeometryInstance; String sampleStableRef = null; if(gInst != null) { GeometryElement gSymbol = gInst.GetSymbolGeometry(); if(gSymbol != null) { foreach(GeometryObject geomObj in gSymbol) { if(geomObj is Solid) { Solid solid = geomObj as Solid; if(solid.Faces.Size > 0) { Face face = solid.Faces.get_Item(0); sampleStableRef = face.Reference.ConvertToStableRepresentation(dbDoc); break; } } else if(geomObj is Curve) { Curve curve = geomObj as Curve; sampleStableRef = curve.Reference.ConvertToStableRepresentation(dbDoc); break; } else if(geomObj is Point) { Point point = geomObj as Point; sampleStableRef = point.Reference.ConvertToStableRepresentation(dbDoc); break; } } } if(sampleStableRef != null) { String[] refTokens = sampleStableRef.Split(new char[] { ':' }); String customStableRef = refTokens[0] + ":" + refTokens[1] + ":" + refTokens[2] + ":" + refTokens[3] + ":" + idx.ToString(); indexRef = Reference.ParseFromStableRepresentation(dbDoc, customStableRef); GeometryObject geoObj = inst.GetGeometryObjectFromReference(indexRef); if(geoObj != null) { String finalToken = ""; if(geoObj is Edge) { finalToken = ":LINEAR"; } if(geoObj is Face) { finalToken = ":SURFACE"; } customStableRef += finalToken; indexRef = Reference.ParseFromStableRepresentation(dbDoc, customStableRef); } else { indexRef = null; } } } else { throw new Exception("No Symbol Geometry found..."); } } return indexRef; }
There's a little bit of unnecessary code in that method as it was quickly cobbled together from the code I was using for research, sorry.
That's all I have for now. hopefully someone finds it usefull.