Today is actually a bridging day here in Neuchâtel, following the Ascension Day holiday yesterday. I want to finish off a few urgent things before going on vacation for the next two weeks with my kids, so I am taking the opportunity to post this anyway.
I finally got around to porting the Building Coder sample code to the Revit 2010 API. I was leaving it in 2009 for as long as possible so as not to force anybody to update too early. On the other hand, I hope that this is not too late either, and that nobody has been waiting urgently for a 2010 port. Now, on this replacement machine I am working on, I no longer have Revit 2009 or Visual Studio 2005 installed, so I am forced to update in order to use the samples at all. Simply opening the solution file in Visual Studio 2008 and compiling fails, and we will discuss why and how to fix this. I actually expected the recompilation on 2010 to be completely trivial, but some issues did crop up which are worth mentioning.
Here is a list of the errors on simply opening the Visual Studio 2005 solution in Visual Studio 2008 and recompiling. For the sake of better readability, I removed the full path name of the source files. Copy to an editor to see the full line length:
------ Build started: Project: BuildingCoder, Configuration: Debug Any CPU ------ c:\WINDOWS\Microsoft.NET\Framework\v3.5\Csc.exe /noconfig /nowarn:1701,1702 /errorreport:prompt /warn:4 /define:DEBUG;TRACE /reference:"..\..\..\..\..\..\..\..\..\..\Program Files\Autodesk Revit Architecture 2010\Program\RevitAPI.dll" /reference:c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll /reference:c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll /reference:c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Windows.Forms.dll /debug+ /debug:full /optimize- /out:obj\Debug\BuildingCoder.dll /resource:obj\Debug\BuildingCoder.CmdLinkedFileElementsForm.resources /resource:obj\Debug\BuildingCoder.CmdWindowHandleForm.resources /target:library CmdAzimuth.cs CmdBoundingBox.cs CmdColumnRound.cs CmdLinkedFileElements.cs CmdLinkedFileElementsForm.cs CmdLinkedFileElementsForm.Designer.cs CmdListRailingTypes.cs CmdNestedInstanceGeo.cs CmdNewArea.cs CmdNewBeamTypeInstance.cs CmdNewColumnTypeInstance.cs CmdEditFloor.cs CmdFilterPerformance.cs CmdGetMaterials.cs CmdLinkedFiles.cs CmdListViews.cs CmdNewRailing.cs CmdOmniClassParams.cs CmdPlanTopology.cs CmdRoomWallAdjacency.cs CmdSlabBoundaryArea.cs CmdSlopedWall.cs CmdTransformedCoords.cs CmdWallLayerVolumes.cs CmdWallProfileArea.cs CmdListWalls.cs CmdRelationshipInverter.cs CmdSlabSides.cs CmdSlabBoundary.cs CmdWallDimensions.cs CmdWallLayers.cs CmdWallProfile.cs CmdWindowHandle.cs CmdWindowHandleForm.cs CmdWindowHandleForm.Designer.cs Creator.cs Properties\AssemblyInfo.cs Util.cs CmdWallNeighbours.cs CmdWallNeighbours.cs(64,26): error CS0029: Cannot implicitly convert type 'Autodesk.Revit.ElementArray' to 'System.Collections.Generic.List' CmdNewBeamTypeInstance.cs(83,14): error CS1502: The best overloaded method match for 'Autodesk.Revit.Document.LoadFamily(string, out Autodesk.Revit.Elements.Family)' has some invalid arguments CmdNewBeamTypeInstance.cs(83,40): error CS1620: Argument '2' must be passed with the 'out' keyword CmdNewColumnTypeInstance.cs(90,14): error CS1502: The best overloaded method match for 'Autodesk.Revit.Document.LoadFamily(string, out Autodesk.Revit.Elements.Family)' has some invalid arguments CmdNewColumnTypeInstance.cs(90,40): error CS1620: Argument '2' must be passed with the 'out' keyword CmdColumnRound.cs(35,44): error CS1061: 'Autodesk.Revit.Elements.Family' does not contain a definition for 'SolidForms' and no extension method 'SolidForms' accepting a first argument of type 'Autodesk.Revit.Elements.Family' could be found (are you missing a using directive or an assembly reference?) CmdColumnRound.cs(39,35): error CS1061: 'Autodesk.Revit.Elements.Sketch' does not contain a definition for 'CurveLoop' and no extension method 'CurveLoop' accepting a first argument of type 'Autodesk.Revit.Elements.Sketch' could be found (are you missing a using directive or an assembly reference?) CmdNestedInstanceGeo.cs(165,50): error CS1061: 'Autodesk.Revit.Elements.Family' does not contain a definition for 'Components' and no extension method 'Components' accepting a first argument of type 'Autodesk.Revit.Elements.Family' could be found (are you missing a using directive or an assembly reference?) Compile complete -- 8 errors, 0 warnings ========== Build: 0 succeeded or up-to-date, 1 failed, 0 skipped ==========
The first error CS0029 is reporting that an 'Autodesk.Revit.ElementArray' cannot be converted to a generic list of elements in CmdWallNeighbours. This makes perfect sense, because get_ElementsAtJoin now returns an instance of the custom Revit API collection class ElementArray instead of a standard generic collection class provided by .NET. Therefore, we also have to modify the following iteration over the collection returned. Actually, the only change required is to replace the property Count by Size.
The next four errors are trivial to fix, CS1502 and CS1620 in CmdNewBeamTypeInstance and CmdNewColumnTypeInstance. They are all four caused by the second argument of one of the overloads of the LoadFamily method changing from a reference to an output argument, so the keyword 'ref' needs to be replaced by 'out'.
The two CS1061 errors in CmdColumnRound occur in the method IsColumnRound, which was written to demonstrate an approach that can only be used in the Revit 2009 API. The classes used have been removed in the Revit 2010 API, so this method can no longer be compiled. Since it is not used anyway, I can simply comment it out.
The only serious change is required to fix the error CS1061 in CmdNestedInstanceGeo. There we use the Revit 2009 API property FamilyInstance.Symbol.Family.Components to obtain the nested family instances within the top level family instance. This is what the original code looks like:
ElementSet components = inst.Symbol.Family.Components;
n = components.Size;
The Components property has been removed in the Revit 2010 API, since we can iterate through the elements of a family just like any other document. Here is a note about this from in the description of the family API in the What's New section of the RevitAPI.chm help file:
"New document methods - Document.LoadFamily and Document.EditFamily - are introduced to bring families from the Family Editor to the Project environment and vice versa. These methods can be used to examine the contents of the family in terms of its elements, parameters and types; as a result, the properties of Family which access the contents have been removed (Family.SolidForms, Family.VoidForms, Family.Components, Family.LoadedSymbols, Family.Others)."
Therefore, we have to port the code from the 2009 to the 2010 API. We can use doc.EditFamily to obtain a Document instance for the family document, and then work with its elements using the standard document element access methods as follows:
Document fdoc = doc.EditFamily( inst.Symbol.Family ); List<RvtElement> components = new List<RvtElement>(); fdoc.get_Elements( typeof( FamilyInstance ), components ); n = components.Count;
Here is version 1.1.0.32 of the complete Visual Studio 2008 solution of The Building Coder sample code. This is the first version for Revit 2010, and the first one using Visual Studio 2008 instead of 2005.
RvtSamples Conversion from 2009 to 2010
As you probably know by now, I use RvtSamples to load the external commands defined by the Revit SDK samples into Revit. I also use it to provide access to all my additional sample commands by making use of the include functionality I implemented, which is now part of the standard SDK distribution.
The format used in the text file RvtSamples.txt used to drive RvtSamples changed from Revit 2009 to 2010 in order to add support for the ribbon Add-Ins tab and button images.
For Revit 2009, RvtSamples expected four lines of information for each entry to specify a hierarchical menu item definition for an external command. The entry for the first Building Coder command looks like this:
/&ADN/&Bc/List Walls List wall lengths and areas C:\...\BuildingCoder.dll BuildingCoder.CmdListWalls
This specifies that a menu entry ADN > Bc > List walls should be generated in the Revit menu system. Selecting it in the user interface launches the external command defined by the BuildingCoder.CmdListWalls class loaded from the specified assembly.
In Revit 2010, there is no longer a hierarchical menu system to install menu entries in. Instead, all external commands are located in the ribbon Add-Ins tab, either under the External Tools pull down button or in an additional pane defined by an external application. Optionally, buttons can be specified as well. Therefore, the new 2010 RvtSamples format for defining a similar entry uses seven lines which might look like this:
ADN Bc List Walls List wall lengths and areas LargeImage: Image: C:\...\BuildingCoder.dll BuildingCoder.CmdListWalls
I used regular expressions in the Visual Studio search and replace dialogue to convert from old format to the new one. I start the search and replace dialogue with Edit > Find and Replace > Quick Replace, and then enter the following regular search expression in 'Find what':
/&ADN/&Bc/{.*}\n{.*}\nIt specifies a line starting with "/&ADN/&Bc/" and followed by anything at all, which is tagged, followed by another line, whose contents are also tagged. The first tagged expression is the menu entry, the second is the description. I enter the following in 'Replace with':
ADN Bc\n\1\n\2\nLargeImage:\nImage:\n
That splits the menu entry text and description onto separate lines, followed by two additional lines for the optional large and small images.
Applying this search and replace action to the example shown above achieves the desired conversion, making it easy and comfortable to reliably convert the entire list of entries.
It was next on my agenda: to make my first revit api program. Now this posts makes me really worried, do i really need to rewrite it every year. That does not sound good.
Posted by: Priit | May 22, 2009 at 08:05
Dear Priit,
Glad to hear you are interested in getting started with the Revit API. Fact is, the API can change a little bit from release to release, but that should not be a cause for too much concern. Only small changes are made to the API from one release to the next. If you are not making any use of API functionality that changed, your add-in may run unmodified on several releases of Revit. It is however recommended to recompile fresh for each release. Mostly, this requires no or only very changes.
In this specific case, the addition of the new family API is an extremely big step forward for Revit programming, and it would have been surprising if it had not affected anything at all.
One command out of thirty-two in The Building Coder samples was affected by this change, and even the update required for that change was very small. Thirty-one of the thirty-two were not affected in any significant way, and twenty-seven out of the thirty-two commands were not affected at all.
Eight lines of code out of a total of 15.000 needed editing.
Feedback from large application developers indicates that porting even the most complex of applications requires no more than a few hours.
Cheers, Jeremy.
Posted by: Jeremy Tammik | May 22, 2009 at 08:53
Hello,
I sympathize with what you had to go through. My primary occupation is programming using Visual Studio. On certain occasions, I have had to upgrade solutions to newer versions of VS to add functionality that the older version did not have. Sadly, the VS "conversion wizard" can be very buggy, and on one occasion I was so stumped I had to open an MS ticket to get help because I couldn't figure out how to manually adjust the converted files so the projects would compile.
I think MS needs to thoroughly test the conversion wizard against against specific legacy versions of VS before allowing the wizard to run (for as of yet untested scenarios, the system should pop a dialog saying that conversion can't take place right now, but may be possible later as patches from Windows Update are issued).
Posted by: Stewart Engelman Domain Sales | July 30, 2009 at 23:11
Jeremy,
Sorry to dig this up, but how does one accomplish the equivalent of Family.LoadedSymbols within the context of the family's parent document? I'll do my best to explain.
For example, try manipulating the weld symbol:
If you use the EditFamily() method to enumerate through the family to retrieve the "Fillet" symbol the elementId will be (for example) 1000.
Using RvtMgdDbg if one snoops the weld family once loaded in a project you can see that the "Fillet" symbol is given an Id of (say) 800000.
So my question: How do you retrieve the loaded symbol's Id in the context of the parent document, not the family editor itself?
Posted by: Chris B | December 09, 2009 at 09:17
Dear Chris,
Filter or iterate the elements of whatever document it is that you need the element id in, identify the symbol you are looking for, and Bob's your uncle.
Cheers, Jeremy.
Posted by: Jeremy Tammik | December 10, 2009 at 02:56
Try as I might I'm still not having success finding the family's symbols in the parent document. For instance, I simply load up a new project (default structural template), drag in a weld symbol, select it, and run through the following code (I've trimmed the easy obvious parts):
foreach (Element elem in selection)
{
List weldSymbolList = new List();
Parameter botSymbol = elem.get_Parameter("Bottom Symbol");
ElementId botSymId = botSymbol.AsElementId();
Element tester = revitDoc.get_Element(ref botSymId);
AnnotationSymbol elementAS = elem as AnnotationSymbol;
FamilyInstance elementFI = elementAS.AsFamilyInstance;
Family fam = elementFI.Symbol.Family;
Document familyDoc = revitDoc.EditFamily(fam);
if (familyDoc != null && familyDoc.IsFamilyDocument == true)
{
ElementIterator itrFS = familyDoc.get_Elements(typeof(AnnotationSymbolType));
itrFS.Reset();
while (itrFS.MoveNext())
{
Element eleFam = itrFS.Current as Element;
if (eleFam != null)
{
weldSymbolList.Add(eleFam.Name);
}
}
ElementIterator itrParentDoc = revitDoc.Elements;
itrParentDoc.Reset();
while (itrParentDoc.MoveNext())
{
Element eleDoc= itrParentDoc.Current as Element;
if (eleDoc != null)
{
if (weldSymbolList.IndexOf(eleDoc.Name) != -1)
MessageBox.Show("Found " + eleDoc.Name + " : " + eleDoc.Id.Value);
}
}
}
And the weld symbols are never found in the iteration. What may I be missing? Your help is much appreciated!
Posted by: Chris B | December 11, 2009 at 18:03
Dear Chris,
I still have a hard time understanding what your difficulty is.
Maybe there is a misunderstanding here. Maybe it is just too simple. Maybe the term 'parent' document is causing confusion.
Does the following correctly describe your need?
1. Create a new project document.
2. Load a family document into the project document.
3. Determine the element ids of the family symbols in the project document context.
If so, then the Revit API Introduction lab Lab3_1_StandardFamiliesAndTypes does exactly what you need.
Cheers, Jeremy.
Posted by: Jeremy Tammik | December 15, 2009 at 03:17
Hi Jeremy,
From your answer, is it meant that we can load mass into curent project via revit API? I have search a correct function but seem it failed.
What I meant is:
1. create new project document
2. using revit api
i. retrieve selected mass family
ii.load into new project into selected level
iii.show in new project document
Thank you.
Posted by: abby | May 18, 2011 at 04:26
Dear Abby,
I do not think you can load a mass directly into the current project, but if you have packaged it as a family I see no reason why it should not be possible to load it.
Any standard family can be loaded into Revit, and instances of its symbols inserted into and displayed in the current project.
Cheers, Jeremy.
Posted by: Jeremy Tammik | May 18, 2011 at 07:20
Hi Jeremy.
Thank you for your reply. I managed to load mass into current project by package it as family.
What i have done is:
1. Create new project
2. In Revit API,
i. it will check whether current document is family.
ii. if not family, it shall create new family.
iii. then i'll create new mass and save it.
iv. then i load new created family into current document.
It seem ok and what i need is to view mass manually since it doesn't shows in floor plan.
Is is correct or there are better way to do that?
Thank you.
Posted by: abby | May 19, 2011 at 00:18
Dear Abby,
That sounds fine to me.
Cheers, Jeremy.
Posted by: Jeremy Tammik | May 19, 2011 at 03:52