Wednesday, May 25, 2011

Using LINQ with the Revit API

LINQ (or Language Integrated Query) can be used to filter collections retrieved using the Revit API.

For example, say you wanted to retrieve a list of category names of all elements that exist in the active view. To achieve this without LINQ you would:
  1. Select all elements in the active view (using a FilteredElementCollector).
  2. Loop through each element and:
    • Check that category is not null.
    • Check that you haven't already stored this category name (you don't want to see the Doors category 100 times).
    • Store the category in a list.
  3. Sort the list so that it is in alphabetical order.

Using LINQ this can be reduced to:
  1. Select all elements in the active view (using a FilteredElementCollector).
  2. Select all category names from the elements.
  3. Ensure the list of category names contains only distinct items and is in alphabetical order.

using System.Linq;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;

namespace KRSP.DistinctCategories
{
    [Transaction(TransactionMode.Automatic)]
    public class Command : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            UIApplication app = commandData.Application;
            UIDocument doc = app.ActiveUIDocument;
            View view = doc.ActiveView;

            //select all elements in the active view
            FilteredElementCollector allInView = new FilteredElementCollector(doc.Document, view.Id);
            allInView.WhereElementIsNotElementType();

            //use LINQ to collect the category names of all elements whilst ensuring that category is not null
            var categoryNames = from elem in allInView
                             where elem.Category != null
                             select elem.Category.Name;

            //strip out duplicates and order the list of category names
            categoryNames = categoryNames.Distinct().OrderBy(catName => catName);

            //construct a string list of the collected category names
            string catList = "Categories:";
            foreach (var catName in categoryNames)
            {
                catList += "\n" + catName;
            }

            //display a dialog showing the results.
            TaskDialog td = new TaskDialog("INFO");
            td.MainInstruction = string.Format("{0} Categor{1} Found.", categoryNames.Count(), categoryNames.Count() == 1 ? "y" : "ies");
            td.MainContent = catList;
            td.Show();

            return Result.Succeeded;
        }
    }
}

Lets examine the LINQ statement above:
//use LINQ to collect the category names of all elements whilst ensuring that category is not null
            var categoryNames = from elem in allInView
                             where elem.Category != null
                             select elem.Category.Name;
There are three parts to the query:
  1. The "from/in" clause; this is where you define which collection you are querying. In this example we are querying the "elem" items inside the "allInView" FilteredElementCollector.
  2. The "where" clause; this clause is optional and allows us to conditionally filter the selection. In this example we are only selecting those elements whose categories are not null.
  3. The "select" clause; this is where we state what we want to retrieve from the collection. In this example we are retrieving the "Category.Name" of each "elem" item.
The results of running the above code on a simple model are shown below:

Monday, May 23, 2011

RFO Ribbon Addin

I recently uploaded the initial release of the RFO Ribbon Addin (thanks to Martijn for all the ground work). You can download it here: http://revitforum.org/showthread.php/1793-RFO-Ribbon-Add-In-Downloads.

The following explains the folder structure and files associated with the addin and how to add new commands to it.

Folder Structure
The addin is structured such that the individual commands that appear on the ribbon are separate dll files (each separate command could be added to Revit as a stand alone addin with its own addin manifest file if you didn't want to use the RFO Ribbon Addin) this allows new commands to be added and commands to be updated easily without the need to recompile the core addin.

The root folder contains the following files:

FileDescription
Addins FolderContains "Vendor" subfolders.
RFO_Addins.xmlThis xml stores a list of all addins and whether or not they are "Enabled".
RibbonAddins.dllThis is the main app that creates the ribbon tab and loads individual addins.
Various ImagesThe thumbnails used on the ribbon.

RFO_Addins.xml
This file lists all available addins and whether each one is displayed on the ribbon not. The xml is divided into section by "Vendor". I use "Vendor" to represent individual contributers, I used the term "Vendor" as this matches what Autodesk uses in their addin manifest files.

The xml contains multiple "Vendor" tags which have a single attribute "Id". The "Id" attribute is equal to the "VendorId" attribute found in the Autodesk addin manifest files. You can obtain a Vendor Id from: http://www.autodesk.com/symbreg/startreg.htm. The "Id" attribute is used to identify each contributor and the folder containing their addins must have the same name as their "Id", additionally the xml file containing the information about their addins must also have the same name. For example; Contributor "ABCD" would have an xml file named "ABCD.xml" contianed within the "Addins/ABCD" subfolder.

Each "Vendor" tag contains one or more "Command" tags with two attributes; "Id" and "Enabled". The "Id" attribute is a GUID which is equal to the "AddInId" attribute found in the Autodesk addin manifest files. The "Id" must match the corresponding "Id" in the contributors xml file.The "Enabled" attribute tells the program whether or not to display this command on the ribbon, it can have a value of "True" or "False".

Individual Commands
Each command on the RFO Ribbon is loaded by a separate dll file and is located within a subfolder named after the contributors unique Id (or "VendorId"). For example; if a vendor with Id "ABCD" contributes a command "My Command" then the dll file would be located within a folder named "ABCD" within the "Addins" folder mentioned in the table above.

Each contributor also must provide an xml file named after their unique Id. The xml file also must reside in the subfolder named after their unique Id.

The xml file has one "Vendor" tag which has three attributes; "Id" is the contributors unique Id, "Name" is the name of the contributor and will appear on the ribbon panel containing their command(s), "Description" is a short description of the contributor and can contain contact information.

The "Vendor" tag contains one or more "Command" tags. Each "Command" tag has one attribute; "Id" is a GUID and should match the "Id" used in the main "RFO_Ribbon.xml" file.

Each "Command" tag contains seven tags describing the command:

  1. "Name" The name of the command, this name is not seen by the user but is used internally by Revit.
  2. "Assembly" The filename of the dll file associated with this command.
  3. "FullClassName" The fully qualified entry point to the command, usually this is the namespace and class name separated by periods.
  4. "Text" The text that appears on the command button.
  5. "Description" The basic tooltip for the command.
  6. "LongDescription" The extended tooltip that appears when hovering over the command for more than two seconds.
  7. "LargeImage" The thumbnail image that appears on the command button. This should be 32x32 pixels.


Add New Command
To add a new command is a two step process:

  1. Extract/copy the command files (including the contributor's xml file) into a subfolder of RFO Ribbon's "Addin" folder", the subfolder should have the same name as the contributor's Id.
  2. Update the RFO_Ribbon.xml to include the new "Vendor" tag and "Command" tags.


A future release of the ribbon app will automate the process of adding a new command.

Thursday, May 5, 2011

Debugging in Visual C# 2010 Express

When you try to debug a Revit add-in by selecting "Start Debugging" from the "Debug" menu (or by hitting the "F5" key) you will prompted with an error saying that "A project with an Output Type of Class Library cannot be started directly.".

While this is true as you can't run your add-in without it first being loaded into Revit it is also a bit frustrating. In the full paid version of Visual Studio there are options to specify the starting application for class libraries allowing you to debug your add-in, these options are missing from the Express versions. There is a workaround though, see below:

When you start a project, Visual C# 2010 Express automatically creates the files needed to create your application or add-in, one of these files is named "YourProject.csproj.user", by editing this file you will be able to specify the starting application for your add-in.

When you open this file in Visual C# it appears like this:
<?xml version="1.0" encoding="utf-8"?>
<project toolsversion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
</project>
Simply add a new "PropertyGroup" tag between the "Project" opening and closing tags so that the file now looks like this:
<?xml version="1.0" encoding="utf-8"?>
<project toolsversion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <propertygroup condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <startaction>Program</startaction>
    <startprogram>C:\Program Files\Autodesk\Revit Architecture 2012\Program\Revit.exe</startprogram>
  </propertygroup>
</project>
Close and then re-open your solution and you can now debug your add-in!

Note that the path shown in the xml above within the "StartProgram" tag relates to the default location for Revit Architecture 2012, you will need to change this path if you are using a different version or have installed in a different location.

Tuesday, May 3, 2011

Disallow Wall Joins

A short time ago a request was made via this post to be able to disallow wall joins on multiple walls at once at the click of a button (http://www.revitforum.org/showthread.php/676-walls-and-joins). At the time of the post this was not possible using the 2011 API, however the ability to do this via the API has been added to the 2012 suite of products!

I posted some source code in that thread showing how to accomplish the original request and am linking to a compiled version here: https://sites.google.com/site/krispcadresources/KRSPDisallowWallJoins.zip

The work is done with these lines of code:
//loop through each wall and disallow wall joins on both ends.
foreach (Wall wall in walls)
{
    WallUtils.DisallowWallJoinAtEnd(wall,0); //beginning of wall
    WallUtils.DisallowWallJoinAtEnd(wall, 1); //end of wall
}

Obviously there is a bit of work before this point to get the collection of the walls that you want to modify.

The add-in works like this; if there are some walls already selected it will disallow joins on those walls otherwise you will be prompted to choose either all walls in the view or all in the project (or cancel).

To install the add-in simply extract the *.dll and *.addin files to the Addins folder and you're good to go. The default location for Revit 2012 products on Windows XP is: C:\Documents and Settings\%username%\Application Data\Autodesk\REVIT\Addins\2012

Sunday, May 1, 2011

Hello World!

The first application to create when learning a new language is traditionally "Hello World". This is a very simple application that performs a single task, it displays the text "Hello World!"

This post shows step by step instructions to create a "Hello World" command using the Revit API. it is assumed that you have already installed Revit 2012 and Visual C# 2010 Express.
  1. In Visual C# 2010 Express start a new project by selecting "New Project..." from the "File" menu.
  2. From the "New Project" dialog select "Class Library" as the project type and change the "Name" to "HelloWorld" then click "OK".
  3. Visual C# will now create the files required for the new project and open the "Class1.cs" file in the text editor.
  4. Right click on the "References" object within the "Solution Explorer" window and select "Add Reference...". (If the Solution Explorer is not visible you can open it by selecting "Solution Explorer" from the "View" menu).
  5. In the "Add Reference" dialog change to the "Browse" tab and then browse to the "Program" folder within the Revit installation directory. (The default location for Revit Architecture 2012 on Windows XP is "C:\Program Files\Autodesk\Revit Architecture 2012\Program").
  6. Select both the "RevitAPI.dll" and "RevitAPIUI.dll" files and click "OK".
  7. Select both of the new references in the Solution Explorer and change the "Copy Local" parameter to "False" in the "Properties" window. (If the Properties window is not visible you can open it by selecting "Properties Window" from the "View" menu).
  8. Add the following two lines directly below the last using statement
    using Autodesk.Revit.Attributes;
    using Autodesk.Revit.UI;

    so that the top of the "Class1.cs" file now looks like this:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Autodesk.Revit.Attributes;
    using Autodesk.Revit.UI;
  9. Add a new blank line directly above the "public class Class1" line and enter the following line:
    [Transaction(TransactionMode.Automatic)]
  10. At the end of the "public class Class1" line add the following text:
    public class Class1 : IExternalCommand
  11. With the "IExternalCommand" text highlighted you should see a small blue underline at the start of the text, hover over this and click the button that appears then select the "Implement interface 'IExternalCommand'" option.
  12. Visual C# inserts the necessary methods required by the interface.
  13. Replace the "throw new NotImplementedException();" line with two new lines so that the "Execute" method now looks like this:
    public Result Execute(ExternalCommandData commandData, ref string message, Autodesk.Revit.DB.ElementSet elements)
    {
        TaskDialog.Show("TITLE", "Hello World!");
        return Result.Succeeded;
    }
  14. This is all that is required to create the *.dll file.
  15. Save the project. The default location on Windows XP is within the "Visual Studio 2010\Projects" folder in "My Documents".
  16. Build the *.dll file by selecting "Build Solution" from the "Build" menu. This will create the *.dll file in the "bin/Debug" folder within your project's folder.
  17. To tell Revit that there is a new add-in to load it is now necessary to create an addin manifest file.
  18. Still within Visual C#, right click on your project in the Solution Explorer and select "Add -> New Item...".
  19. Select "XML File" as the type and change the name to "HelloWorld.addin" then click "Add".
  20. In the new file, add new lines of text so that the contents now looks like this:
    
    
      
        C:\Documents and Settings\>%username%\My Documents\Visual Studio 2010\Projects\HelloWorld\HelloWorld\bin\Debug\HelloWorld.dll
        9c672c99-ed2a-4fb7-a05a-bbc7eb108ed3
        HelloWorld.Class1
        Hello World
        KRSP
        KrispCAD: krisp.design@gmail.com
      
    
  21. Select "Save HelloWorld.addin As..." from the "File" menu and save it to the "Addins" folder. (The default location for Revit Architecture 2012 on Windows XP is "C:\Documents and Settings\%username%\Application Data\Autodesk\REVIT\Addins\2012").
  22. The set up is now complete, launch Revit and you should find a "Hello World" command under the "External Commands" button on the "Add-Ins" tab of the Ribbon. Running the command will display the following dialog: