Revit API My First Plug-In Training
Revit API My First Plug-In Training
Revit API My First Plug-In Training
2.
Visual C# Express will create a default code project for you and display the code in the code window.
3.
4.
Add references:
In the Solution Explorer window on the right-hand side of the Visual C# Express window, right-click References and click
Add Reference
5.
Click the Browse tab in the Add Reference dialog and browse to the Revit product installation sub-folder. (The subfolder path depends on where you have installed Revit Architecture 201x. The default path is C:\Program Files\Autodesk\Revit
Architecture 201x\Program*).
* The path may vary depending on the flavour of Autodesk Revit you are using.
You will add two reference files from this folder. Select RevitAPI.dll, hold the Ctrl key and select RevitAPIUI.dll, and then click
OK. Now the two interface DLL files are referenced in your project. All the Revit APIs are exposed by these interface files and
your project can use all of those available APIs from them.
6.
In the Solution Explorer window you saw in step 5, click RevitAPI under Reference node. In the Properties window, click
Copy Local property, and then click the drop-down list, select False. Repeat the same steps to change RevitAPIUI's Copy
Local property value to False.
7.
8.
In the displaying Project Property form, click Application tab on the left-hand side of the window, click the drop-down
list below Target framework, and then click the .NET Framework 3.5 option in the list.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
The following message box appears to ask your confirmation. Click Yes to confirm the change.
using System;
using System.Collections.Generic;
using System.Linq;
using
using
using
using
using
using
Autodesk.Revit.DB;
Autodesk.Revit.DB.Architecture;
Autodesk.Revit.UI;
Autodesk.Revit.UI.Selection;
Autodesk.Revit.ApplicationServices;
Autodesk.Revit.Attributes;
[TransactionAttribute(TransactionMode.Manual)]
[RegenerationAttribute(RegenerationOption.Manual)]
public class Lab1PlaceGroup : IExternalCommand
{
public Result Execute(
ExternalCommandData commandData,
ref string message,
ElementSet elements)
{
//Get application and document objects
UIApplication uiApp = commandData.Application;
Document doc = uiApp.ActiveUIDocument.Document;
//Define a Reference object to accept the pick result.
Reference pickedRef = null;
//Pick a group
Selection sel = uiApp.ActiveUIDocument.Selection;
pickedRef = sel.PickObject(ObjectType.Element, "Please select a group");
Element elem = pickedRef.Element;
Group group = elem as Group;
//Pick a point
XYZ point = sel.PickPoint("Please pick a point to place group");
//Place the group
Transaction trans = new Transaction(doc);
49.
50.
51.
52.
53.
54.
55.
trans.Start("Lab");
doc.Create.PlaceGroup(point, group.GroupType);
trans.Commit();
return Result.Succeeded;
}
}
Don't worry about the details of the code for now, you'll come back to this shortly in the next couple of lessons.
11.
12.
Note: If you are working with Revit 2012 API, you will see a warning stating that 'Autodesk.Revit.DB.Reference.Element' is
obsolete. At this point, don't worry about this warning. We will address what the code needs to be changed to, in Lesson 3.
If you are working with Revit 2013 and higher API, you will need to replace the following line of code:
Thats it! You have just written your first plug-in for Autodesk Revit.
Before you actually work with the plug-in in Revit, you will need to do one more step, which is to write an
AddIn manifest.
An AddIn manifest is a file located in a specific location checked by Revit when the application starts. The
manifest includes information used by Revit to load and run the plug-in.
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
Depending on what version you are using you may need to change the path here to match your Lab1PlaceGroup.dll location on
your computer:
C:\test\Lab1PlaceGroup\Lab1PlaceGroup\bin\Release\Lab1PlaceGroup.dll
11.
For example, here is the setting in Save As dialog in Windows 7 for Revit Architecture 2011.
2.
3.
Load your plug-in into Revit and allow the plug-in to communicate with Revit:
Inside Revit on the Add-Ins ribbon tab, click the External Tools drop-down list, then click Lab1PlaceGroup. This will start your
plug-in.
4.
5.
Pick a point in another room, for example in Room 2. You should see the group copied to this location. The center of
the new group is the point you selected.
Congratulations! You have just written your first plug-in for Autodesk Revit. You will be reviewing the code in
detail in Lesson 3.
Before you move on to the next lessons, let us go back to some of the things we skipped over earlier, starting
with basics concept about programming, and the benefits it can bring to your day-to-day work.
Additional Topics
Introduction to Programming
The C# code you have just executed to copy a group is only 30 lines long. Here you see a small amount of
code working in a similar way to the internal Revit command, Create Similar. Software programming allows
you to capture the logic of a particular functionality once and then reap the benefits over and over again,
every time you want to perform this functionality.
What is Programming?
A simple answer to this question is: Computer programming is the process of creating a sequence of
instructions to tell the computer to do something. You can look at your program as a sequence of instructions.
During the course of the upcoming lessons, you will look at the various lines and blocks of code and look at
them all in the context of being instructions for a computer.
If you were to explain what computers are to a young child, you might say: a computer is a tool which follows
instructions you provide. Programming is one way of giving instructions to the computer. Internally, a
computer sees these instructions encoded as a series of numbers (also called machine code). The humanreadable instructions you saw at the beginning of this lesson is called source code and the computer converts
these instructions to machine code which it can then read and execute. A sequence of such instructions (or
code), written to perform a specific task, is called a program and a collection of such programs and related
data is called a software. Autodesk Revit is one such software product.
Source code can be written in different languages, just as humans use different languages to communicate
between ourselves. The language you will be using in this guide is called C# (pronounced C-Sharp).
What is an API?
API is the acronym for Application Programming Interface: the way a software programmer can communicate
with a software product. For instance, the Revit API is the way programmers can work with Revit, and it
establishes what functionality a software programmer can use within Revit. Such as the Revit API allows you
to write instructions for Revit to execute one after the other.
To put this slightly differently: commercial software companies, such as Autodesk, often distribute a set of
libraries which you can use in your own program to interact with a particular software product, such as
Autodesk Revit, and extend its functionality. This set of libraries is known as the software products API.
The type of program you write to interact with a software product and extend its functionality will depend upon
how the API has been designed and what has been exposed (through APIs) for you to work with.
What is a Plug-in?
A software plug-in is a type of program module (or file) that adds functionality to a software product, usually in
the form of a command automating a task or some customization of the products behavior. When you talk
about a plug-in for Revit and you will also hear the term Add-In used for this product we mean a module
containing code that makes use of the Revit API. Revit loads such plug-ins and uses them to adjust its
behavior under certain conditions, such as when a particular command is executed by the user of the plug-in.
The Visual C# Express interface also contains a text editor and interface designer. These are displayed in the
main window depending on the type of file being edited. The text editor is where you will enter the C# code for
your Revit plug-in. This editor provides advanced features such as IntelliSense and collapsible code sections
along with the more classic text-editing features such as bookmarks and the display of line numbers.
IntelliSense is an extremely valuable feature of the Visual Studio family that greatly improves programmer
productivity: it automatically provides suggestions for the code being written based on the objects available
and the letters that are being typed.
Clearly one of the key features of Visual C# Express is its ability to build C# code into an executable file.
During the build process, the language compiler performs various checks and analyses on the code. One
such check is to ensure the code conforms to the syntactical rules of the C# language. The compiler also
performs various other checks, such as whether a variable has been appropriately defined or not. Detected
errors are reported via the Error List window, typically found at the bottom of the main window.
2.
3.
Your blank project was created, containing a few standard project references to core .NET components along with a
blank C# class file. Its this file that gets displayed in the text editor window.
4.
Saving the solution created physical files representing the contents of your class library project on the computers hard
drive, allowing you to open and edit it at another time in the future.
5.
This blank project, as created by Visual C# Express, did not automatically make use of the Revit API. For it to do so,
you added project references to the interface DLLs in Revit describing its API, RevitAPI.dll and RevitAPIUI.dll.
While developing code, its a good idea to build the solution from time to time, to check whether errors have
been introduced in the code. The code does not necessarily have to be complete or functional when building
the solution. This approach can help avoid potentially lengthy troubleshooting once the code is complete, and
has the side benefit of automatically saving any edited source files before the build starts.
To build a solution inside Visual C# Express, select Build Solution from the Debug pull-down menu.
Tip: As a shortcut, you can use the Function key F6 to directly build the solution without accessing the menu.
If the build process was successful, you would see a Build Succeeded status in the bottom left corner of the
Visual C# Express interface.
A quick recap: in this lesson you took a brief look at what happens when you build a project, as well as some
background information on C# and Visual C# Express. You reviewed the steps you had taken in the previous
lesson to build your basic Revit plug-in, putting it in the context of your learning about programming.
Additional Topics
What is .NET?
The .NET Framework is a software framework that sits on top of the Microsoft Windows operating system*
and provides the underlying platform, libraries and services for all .NET applications. The services generally
include memory management, garbage collection, common type system, class libraries, etc.
* Subsets of .NET are also available on other operating systems, whether via the open source Mono project
or via Microsoft Silverlight, but these are not topics for this guide: you will focus solely on the use of .NET
in the context of Microsoft Windows.
Common Language Runtime (CLR) This is the agent (or execution engine) in the .NET Framework responsible for
managing the execution of code. Which is why code written to target this runtime is also known as managed code. All managed
code runs under the supervision of the CLR, but what does this mean? The CLR manages code by providing core services
such as memory management (which includes automatically releasing the computers memory for reuse on other tasks when it
is no longer needed), error (or exception) handling, managing the use of multiple threads of execution and ensuring rules
around the use of different types of object are adhered to. The CLR is really the foundation of the .NET Framework.
2.
.NET Framework Class Library As the name suggests, this is a library or collection of object types that can be used
from your own code when developing .NET applications. These .NET applications are targeted for Windows (whether
command-prompt based or with a graphical user interface), the web or mobile devices. This library is available to all languages
using the .NET Framework.
As mentioned above, the CLR improves code robustness by making sure the executing code conforms to a
common type system (CTS). The CTS ensures that all .NET (or managed) code irrespective of the
language uses a similar set of object types and can work together in the same environment. It is this feature
that makes it possible for you to write applications in the development language of your choice and yet make
use of components/code written by programmers using other .NET languages.
Building Executables
When you built your code into a DLL, it is compiled to Common Intermediate Language (CIL also known as
MSIL) code using a language-specific compiler. CIL is a CPU-independent set of instructions that can be
executed by the CLR on Windows operating systems. CIL is typically portable across 32- and 64-bit systems
and even to some extent to non-Windows operating systems. The CIL code generated from your C#
source code is then packaged into a .NET assembly. Such an assembly is a library of CIL code stored in
Portable Executable (PE) format (which contains both the CIL and its associated metadata). Assemblies can
either be process assemblies (EXEs) or library assemblies (DLLs).
During the course of this guide, Revit plug-ins are compiled into library assembly files (DLLs) which are then
loaded and executed from within Revits memory space.
Running Executables
Autodesk.Revit.DB;
Autodesk.Revit.DB.Architecture;
Autodesk.Revit.UI;
Autodesk.Revit.UI.Selection;
Autodesk.Revit.ApplicationServices;
Autodesk.Revit.Attributes;
Namespaces
The first few lines of code start with the using keyword. When working with different library assemblies in
your C# project, its quite common for classes to share the same name there might be multiple versions of
the Point class in different libraries, for instance. To avoid naming conflicts, .NET provides you with the
concept of namespaces. A namespace is a way to organize classes by grouping them together in a logical
way and to help you identity the class you wish to use.
To give you an example, lets say there are two boys named John living in the same neighborhood. One
possible way of uniquely identifying one of the Johns might be to use his last name. But if both Johns have
the same last name, such as Smith, this is no longer enough to uniquely identify them. You now need to use
an additional identifier, such as the school at which they study. If this was put into .NET syntax, you might use
SchoolA.Smith.John for one and SchoolB.Smith.John for the other.
Its in this way that youre able to identify the use of specific classes in C#. The using keyword gives you direct
access to the classes contained in the included namespace, which saves the effort of having to enter the full
namespace when typing the names of classes. It also causes the namespaces various classes to be added
to the list presented by the IntelliSense feature.
The using keyword is a directive telling the C# compiler to provide access to the contents of a specified
namespace from the code in this source file.
Attributes
Lets look at the following block of code:
[TransactionAttribute(TransactionMode.Manual)]
[RegenerationAttribute(RegenerationOption.Manual)]
public class Lab1PlaceGroup : IExternalCommand
{
}
Well start by talking about the first two lines inside the square brackets.
[TransactionAttribute(TransactionMode.Manual)]
[RegenerationAttribute(RegenerationOption.Manual)]
These two lines are attributes that help control the transaction and regeneration behavior of your command.
1.
Transaction Attribute a transaction attribute declares the way the transaction should work. It could be either Manual
or Automatic. Here, you have set the attribute to Manual.
So what is a transaction? Transactions are objects that capture the changes to the Revit model. Changes to the Revit model
can only be performed when there is an active transaction to do so. Transactions can either be committed which means that
their changes are written/recorded into the model or rolled-back which means their changes are undone.
The value of TransactionMode.Manual for the TransactionAttribute requests that Revit not create a transaction automatically
(besides the one Revit creates for any external command to help roll back any changes performed should the external
command fail). This setting also implies that you are responsible for creating, naming, committing or aborting your transactions.
2.
Regeneration Attribute this attribute applies only to Revit 2011 products. A regeneration attribute sets the timing
for Revit to draw graphical objects to the computer screen. It could be either Manual or Automatic. Here, you set have the
attribute to Manual.
RegenerationOption.Manual requests that Revit not regenerate (or refresh) the model on the screen after each change made to
it. Instead you tell Revit when the model needs to be drawn or regenerated during the command execution process. If you set
this as Automatic, Revit will try to draw every time something is drawn.
IExternalCommand
The line after the declaration of our attributes declares the class.
public class Lab1PlaceGroup : IExternalCommand
{
}
A class can be thought of as a type that can be used to describe a representation of a thing or an individual
object. A class can be considered the template that describes the details of an object and is used to create
individual objects. In this way classes can help define the common characteristics of objects of their type: the
objects attributes (properties) and behaviors (methods). For more details on understanding classes see
Additional Topics.
The public keyword states that the class should be accessible from other classes in this plug-in as well in
other Visual Studio projects which reference the plug-in DLL containing this class. The name
Lab1PlaceGroup is the name of your class.
Next in the declaration statement, after the class name, you see a colon followed by IExternalCommand.
Placing a colon after your class name followed by IExternalCommand tells the C# compiler that you want your
class to implement an interface called IExternalCommand (which if stated with the complete namespace
is actually Autodesk.Revit.UI.IExternalCommand).
From the Revit plug-in perspective, this denotes the class as the implementation of an external command:
Revit will be able to use this class to execute a command when it gets triggered from the products user
interface. In simpler words, by declaring a class that implements IExternalCommand, Revit can recognize this
as a command that you have defined. Interfaces are like classes except that they contain unimplemented
methods. Interface names start with an I, as is the case with IExternalCommand.
As your new class Lab1PlaceGroup implements the IExternalCommand interface from the Revit API, it needs
to provide implementations for all the unimplemented methods defined by this interface. A common question
when working with interfaces is this: once Ive decided to implement an interface, how do I know what
methods need to be implemented?
Thankfully Visual C# Express comes to the rescue, helping create the basic skeleton implementation of the
method(s) that need to be implemented in the interface. If you right-click on the IExternalCommand word in
the Code Editor window in Visual C# Express, you see an option in the context menu entitled Implement
Interface. If you click on this further another sub-menu is provided, Implement Interface Explicitly. Clicking
on this creates a blank method implementation for you to fill in.
The class in a Revit plug-in that implements this interface is known as the entry point for that plug-in: its the
class that Revit will attempt to find and call the Execute() method upon. Putting it another way, when a Revit
user clicks on a command in the Revit user interface listed under the External Tools drop-down button on the
Add-Ins tab, the code in the Execute() method is run (executed) from the corresponding class which
implements this IExternalCommand interface.
[TransactionAttribute(TransactionMode.Manual)]
[RegenerationAttribute(RegenerationOption.Manual)]
public class Lab1PlaceGroup : IExternalCommand
{
public Result Execute(
ExternalCommandData commandData,
ref string message,
ElementSet elements)
{
}
}
Any block of code in a class which performs a particular task (or action) is called a method. The method
declaration starts with the word public in this case. You already know what public implies. For more details
on understanding methods see Additional Topics.
This method returns a Result (in fact an Autodesk.Revit.UI.Result) rather than being declared void (i.e. not
returning anything). The Result you returned from the Execute() method will tell Revit whether the command
execution has succeeded, failed or been cancelled. If the command does not succeed, any changes it made
will be reversed (Revit will cause the transaction that was used to make them to be rolled back).
The Execute() method has three parameters: commandData, message and elements. Lets take a closer
look at what each of these parameters refer to:
1.
commandData is of type ExternalCommandData and provides you with API access to the Revit application. The
application object in turn provides you with access to the document that is active in the user interface and its corresponding
database. All Revit data (including that of the model) is accessed via this commandData parameter.
2.
message is a string parameter with the additional ref keyword, which means it can be modified within the method
implementation. This parameter can be set in the external command when the command fails or is cancelled. When this
message gets set and the Execute() method returns a failure or cancellation result an error dialog is displayed by Revit with
this message text included.
3.
elements is a parameter of type ElementSet which allows you to choose elements to be highlighted on screen should
the external command fail or be cancelled.
Lets now look at the code inside in the Execute() method. This is the actual set of instructions which uses the
Revit API to perform certain tasks when your command is executed. In Lesson 1 you saw how the Revit API
can be used to add an external command which asks the user to select a group and a target location before
going ahead and copying the group to that location.
Now lets look at the code, line-by-line:
//Get application and document objects
UIApplication uiApp = commandData.Application;
In the first line, you use the commandData parameter that was passed into the Execute() method to access
the Application property of this object, which provides you with access to the Revit application. For more
details on understanding properties and reviewing the main Revit API classes and the correlation between
them, see the Additional Topics.
Variables
To be able to use the Application property you just retrieved from the commandData parameter, you created
a container variable for the object named uiApp of type UIApplication. You then assigned the value of
commandData.Application to it for later use in your program. When youre declaring a variable you create a
named location for a value which can be accessed later on within the same block of code. Variables can be
named according to your wishes, as long as the name is unique in that code-block and is not a reserved word
(such as the using keyword mentioned earlier).
Document doc = uiApp.ActiveUIDocument.Document;
The uiApp variable (which contains the Revit Application object) provides access to the active document in
the Revit user interface via the ActiveUIDocument property. In the above line of code in just one line you
directly accessed the database of the active document (this database is represented by the Document class).
You stored this Document object in a variable named doc.
Object Selection
Lets look at how you prompted users to select Groups using the API.
//Define a Reference object to accept the pick result.
Reference pickedRef = null;
You started by creating an empty variable named pickedRef of type Reference and set its initial value to be
null (which literally means nothing). By doing this you created an empty container in which to subsequently
store a Reference object. Reference is a class which can contain elements from a Revit model associated
with valid geometry.
//Pick a group
Selection sel = uiApp.ActiveUIDocument.Selection;
pickedRef = sel.PickObject(ObjectType.Element, "Please select a group");
Element elem = pickedRef.Element;
Group group = elem as Group;
Next you accessed the current user selection using the API. The user selection from the user interface is
represented by the Selection property on the ActiveUIDocument object: you placed this Selection object
into a variable named sel of type Selection. This Selection object provides you with a method named
PickObject(). As the methods name suggests, it shifts focus to the user interface and prompts the user to
select an object. The parameters of this method allow you to specify the type of element you want the user to
select (you can specify if you are expecting users to select a face, an element, an edge, etc.) along with the
message the user will see in the lower left corner of the Revit user interface while the plug-in waits for the
selection to occur.
As the selected Group object has geometry data associated with it, it was safe to place it in the pickedRef
variable you declared previously. You then used the references Element property to gain access to the
references associated element: in this case you assigned its value to a variable named elem, of type
Element. As you are expecting the elem object to be of type Group, in the last line of the above code snippet
you performed a cast, allowing us to treat the elem variable as a Group via the variable named group.
Note: If you are working with Revit 2012 API, the following line of code might throw a warning stating that
'Autodesk.Revit.DB.Reference.Element' is obsolete.
Element elem = pickedRef.Element;
To get rid of this warning in Revit 2012, please replace the line of code from above with the following
Element elem = doc.GetElement(pickedRef);
Note: If you are working with Revit 2013 and higher API, the above mentioned change will need to be done to
be able to build successfully and run the code.
Casting
In the manufacturing world, the term casting refers to the act of setting a given material into a mold to shape it
into an object of a particular form. Similarly, in the programming world, casting means the act of trying to set a
value of one type into another. Casting asks the language compiler to consider a value in a different way. In
the last line of your code snippet, you are essentially casting the Element (which is actually a Group selected
by the user) specifically into the Group type. The as operator in C# will cause the compiler to check the
actual type of the object being cast: if it is incompatible with the target type, the value returned by the operator
will be null.
Next you asked the user to pick a target point for the selected group to be copied to.
//Pick a point
XYZ point = sel.PickPoint("Please pick a point to place group");
For this, you once again made use of the variable named sel, of type Selection the one you used
previously to gain access to the PickObject() method to call the PickPoint() method. The parameter for the
PickPoint() method is simply the message to be shown in the bottom left corner of the screen while the plugin waits for the user to select a point. The point selected was returned as an object of type XYZ, which you
placed in a variable named point.
//Place the group
Transaction trans = new Transaction(doc);
trans.Start("Lab");
doc.Create.PlaceGroup(point, group.GroupType);
trans.Commit();
We have already mentioned Transactions in the context of the Revit API. As you have set the transaction
attribute to manual, Revit expects your plug-in to create and manage its own Transaction objects to make
changes to the model. You needed to create your own transaction object to encapsulate the creation of your
new group. You started by declaring a Transaction variable named trans to which you assigned a new
instance of the Transaction class, created using the new keyword. The constructor (a special method used
to create a new instance of a class) of the Transaction class expects the active documents database object
to be passed in as a parameter, so that the Transaction knows with which document it will be associated. You
then needed to start the transaction by calling its Start() method, allowing you to make changes to the Revit
model. In this case, we named the transaction "Lab", however it can be any string value you choose.
The aim of this initial plug-in is to place a selected group at a location selected by the user. To perform this
task, you used the PlaceGroup() method from the active documents database object under the creationrelated methods made accessible via its Create property. This Create property makes it possible to add new
instances of elements such as Groups to the Revit model. The PlaceGroup() method, as expected,
required you to pass in the location at which you wanted to place your group, as well as the type (used in the
context of Revit, rather than C#) of the group selected by the user.
Finally, you committed the transaction object using the Commit() method. This ensured the changes
encapsulated by the transaction were successfully written to the Revit model.
return Result.Succeeded;
As you may recall, the Execute() method the entry point for your Revit plug-in expects a Result to be
returned. It is this Result which informs Revit whether the command completed successfully, whether it failed
or was cancelled. At this point, assuming all went well with the code, you passed back a Result of
Succeeded.
Well done you have made it to the end of a lesson containing a great deal of information!
You covered a range of new topics in this lesson, including some basics of object oriented programming
defining what class is, what an object is you looked at some fundamentals of the C# language and also took
a first close look at the framework code needed by a Revit plug-in. You were then introduced the main
classes of the Revit API and saw them being used in the code you created in Lesson 1. Finally, you looked at
a number of different classes and methods from the Revit API which enabled you to work with user-selected
objects and points and also helped you to create new instances of groups.
Additional Topics
Object Oriented Programming
In the previous lesson, you learned about how a program is a sequence of instructions which tells the
computer how to perform one or more tasks. Simple programs tend to consist of a sequence or list of
instructions operating on variables data representing whats happening in the program as it executes. But,
with increasing complexity, such linear sequences of instructions (an approach also often referred to as
procedural programming) can be difficult to manage and ends up not being well-suited for the task for which
the code was written.
To help address the complexity problem, Object-Oriented Programming (OOP) was developed to allow
source code to be structured differently, enabling componentization and reuse. OOP encourages you to
develop discrete, reusable units of programming logic which are aimed at modeling objects from the problem
domain and the relationships between them. A good example of this is a map. A map of a city can be
considered as a model or a simplified representation of the city itself, providing a useful abstraction of the
information you need to get around it.
The key to good object-oriented design is to model the elements of the problem domain as individual objects
displaying the relevant behavior of the originals and the relationships between them.
Classes
A class can be thought of as a type which can be used to describe a representation of a thing or an individual
object. A class can be considered the template that describes the details of an object and is used to create
individual objects. In this way classes can help define the common characteristics of objects of their type: the
objects attributes (properties) and behaviors (methods).
An object is an instance of a class. These are the building blocks of Object-Oriented Programming and can be
thought of as variables - often quite complex ones which encapsulate both data and behavior.
To provide an example, the relationship between a class and an object is similar to the concept of family and
family instances in Revit. You could consider a wall system family as a class. It is in this file that the basic
parameters of a wall are described. When you create an instance of this wall system family inside the model,
it can be thought of as an object. The wall family is therefore the blueprint or template for all walls in the
building model. Each wall instance has the set of parameters defined in the template but may have quite
different parameter values: they might have different fire-ratings and locations, for instance.
How do you define a class in C#?
The following code snippet shows an example of a simple class declaration in C#. While describing this, you
will also start looking more closely at the syntax of the C# language.
public class Point
{
private int x, y;
public void setLocation(int x, int y)
{
this.x = x;
this.y = y;
// Do some calculations next
//
}
}
The first word, public, sets the accessibility of the class. The use of public means that any programmer can
gain access to this class in their own application. Other options would be internal (if only to be used within
this project) or private (if only to be used in the context in which it is declared).
The next word, class, defines what follows as a class declaration. The next word, Point, defines the name of
your class.
On the next line you have a {. These are called braces and come in sets of two: for every opening brace ({)
you must have a matching closing brace (}). The compiler will complain, otherwise, and often with a quite
obscure error (as it attempts to interpret the following code in the wrong context). These braces help create
blocks of code. In this case, the start and end braces signify the start and end of the code defining your class.
Variable Declarations
The next line defines two data members: these are member variables to be used in your class and help
contain data which might be used in different portions of the code in the class. Data members allow you to
share data between multiple blocks of code in the class. You define data members in one place and can then
use them in different places.
private int x, y;
The line starts with the information on the scope of access for these data members, which in this case is
private. This means these variables can only be used and accessed within the class.
The word int refers to the integer type, which tells the compiler that you want to reserve some space to store
some whole numbers. The most common types handled by C# are floating point (i.e. decimal) numbers,
integer numbers and text (known as strings). x and y are names given to help identify the data members. C#
code is made up of statements, each of which ends with a semi-colon (;).
Methods
The following block of code is called a method:
public void setLocation(int x, int y)
{
this.x = x;
this.y = y;
Any block of code in a class which performs a particular task (or action) is called a method. The method
declaration starts with the word public in this case. You already know what public implies.
The next word is void. This is where you specify what the method is expected to return after performing the
task(s) inside it. For example, if a method performs some calculations, it will be expected to return the result
of those calculations. Its at this point that you specify the type of this result in this case void, which is the
same as saying it doesnt return anything. This means that when you call the method you cannot assign its
results to a variable, as there are no results being returned.
The word setLocation is the name you use to identify and make use of this method.
The portion of the statement between the brackets () contains the methods parameters. A parameter (or set
of parameters) is the input provided to a method for it to use and work on. Parameters can also be used to
return additional data to the caller of the method, but in this case youre keeping things simple and only
specifying that data will be passed into it.
The next line has an opening brace which means this is the start of the block of code defining the
implementation of the method. Inside the method, you can see the following line:
this.x = x;
this.y = y;
The word this identifies the current instance of your class. In this line, you are accessing the member variable
x of this instance and so use the statement this.x. The = is called the assignment operator and means that
the container on the left side of the operator will have its value changed to that specified on the right side. In
this case, you are setting the value of the x member of the current instance of your Point class (using this) to
the value passed into the method via the parameter x.
The next statement follows the same principle, but setting the y data member.
Lines starting with // are ignored by C#. This is one way of adding comments to your code to describe its
implementation in plain language or if you have portions of the code which should be ignored during
compilation.
Finally, the closing brace signifies the end of the block of code for this methods implementation.
If you look back at the code for the class, youll see a subsequent brace. This denotes the end of the class
(and has a different indentation to make it more readable).
Now you have a simple class defined using C#. The next question is typically
How do you create objects of this class?
In the first statement, you define a new variable named pt which is defined to be of type Point and use the
assignment operator to set its initial value to be a new instance of the Point class, created using the new
keyword. The second statement shows how to access the setLocation() method, passing in some parameter
values (for x and y).
Properties
A core feature of C# and other .NET languages is the ability for classes to have properties. A property is
simply a way of allowing access to an item of member data. It is good practice to keep member variables (or
data variables) private to that class: properties allow you to expose this data in a controlled way (as you can
then change the underlying class implementation without it impacting the way your class is used).
Extending the code snippet, lets see how a property can be added to a same class.
public class Point
{
private int x, y;
public void setLocation(int x, int y)
{
this.x = x;
this.y = y;
// Do some calculations next
//
}
public int X
{
set
{
this.x = value;
}
get
{
return this.x;
}
}
}
The above code, in bold, shows how you can expose a property from your class, whether for use in your own
project or consumption by other programmers in their projects.
The property declaration starts with the much-discussed public keyword. The next word specifies that the
property is of integer type and is named X.
The get part of the property declaration describes what happens when someone reads the value of the
property and the set part does the same for when someone writes to or sets the value of the property.
You have already seen the use of this.x to access a data member of the current class instance (or object),
but the value keyword in the set part is something new: this provides you with the information being passed
into the property, a bit like the parameters of a method. During set you simply take this value and assign it to
your internal data member.
When someone attempts to access your property for a particular Point object, the get part of the property
executes and the return statement passes back the value of your internal data member.
Lets now see how this property can be used:
Point pt = new Point();
pt.setLocation(10, 10);
// getting the value of X property
int xCoord = pt.X;
// setting the value of X property
pt.X = 20;
You start by creating a new Point object, named pt, as you saw previously.
The lines in bold show you how to read (or get) the X property of your pt object, assigning its value to a
variable xCoord, and then how to write (or set) it to a new value 20.
Here are these classes listed with their complete namespaces and a view of how they can be accessed as
properties of other classes (i.e. how you would navigate from one to the other in your code).
Locate the C# project file (*.csproj) in your project folder, which in this case, would be the Lab1PlaceGroup.csproj.
Open the file:
Start Notepad.exe from the Windows Start menu. On Notepads File menu, click Open. Navigate to the file and open it.
Look for the first <PropertyGroup> tag. (Note - you will see this tag in multiple times).
At the end of this tag, i.e. right before the close tag </PropertyGroup>, insert the two lines with the <StartAction> and
<StartProgram> node (highlighted in bold). The Lab1PlaceGroup.csproj file should look like this:
<PropertyGroup>
...
<StartAction>Program</StartAction>
<StartProgram>C:\Program Files\Autodesk\Revit Architecture
201x\Program\Revit.exe</StartProgram>
</PropertyGroup>
However, this functionality isnt exposed through the Express IDE and is why we perform the additional
step of adding the settings to the .csproj file.
To start your debugging session, simply open the Debug menu and select the Start Debugging option, or just
hit F5. This will launch Revit from your debugger. The flavor of Revit that gets launched will depend on the
path you have provided in the tag in the .csproj file that we edited manually at the beginning of this lesson.
You have to launch Revit in this way so that your debugger can hook into the Revit process to monitor the
execution of the code you compiled into your plug-in DLL. Because weve placed the AddIn manifest in the
correct location, Revit will automatically load our plug-in.
If you have the Visual C# Express Immediate window open when you do this, you will see a lot of text
scrolling up that window. Some of that text looks a little scary because it includes the word error. Dont worry
about that. This text is informational only it doesnt mean theres a problem with Revit or with your plug-in.
Remember that the DLL you loaded in Lesson 1 was in the bin\Release folder. When you are building a final
version of your plug-in DLL that you want to give to your users and customers, Visual Studio will build a
release version. Visual Studio makes various optimizations to the compiled code in a release build so that it
will run faster and take up less memory. When you want to debug your code, Visual Studio will create a
debug version of your plug-in DLL. The debug version isnt optimized for speed/memory, and also includes
additional information that the debugger uses to tell you about what is happening when the code runs.
Note: If you are using Visual Studio 2010 Professional (and not the Express Edition), there is an alternative
way of attaching the Visual Studio debugger to running instance of Revit. Please read more on this in this
article: How to: Attach to a Running Process.
Now, open the Hotel.rvt Project file.
Breakpoints
Your plug-in DLL is now ready to debug. But before you run your Lab1PlaceGroup command, you have to
tell the debugger you want it to stop when it is executing your code. You do this using a breakpoint.
In Visual C# Express, double-click on Lab1.cs in the Solution Explorer to display your code and click
anywhere in the line:
UIApplication uiApp = commandData.Application;
Then select Toggle Breakpoint from the Debug menu (or hit F9).
That line will now be highlighted in red and have a red circle in the margin next to it to indicate that youve set
a breakpoint for this line:
When Revit calls these methods in your code, the debugger will stop at these lines and wait for you to tell it
what to do.
Now youre ready to step through your code. The Visual C# Express debug menu gives you three ways to
step through your code: Step Into; Step Over; and Step Out. Youll mostly be using Step Over this
executes the next line of code (the line highlighted in yellow) in the debugger, and then moves to the next line.
If the line of code to be executed is a method call, then Step Over executes the entire method. If you also
want to execute the called method a line at a time, you can use Step Into; and you can use Step Out to move
back up (out of the method) to the code you were originally debugging.
As well as in the Debug menu, you should also see Step Into, Step Over and Step Out icons on a toolbar, and
each also has a corresponding hot key (F8, Shift+F8, and Ctrl+Shift+F8).
Click on the Step Over icon on the toolbar now. The debugger moves to the next line of code (it ignores
comments).
You can now hover the mouse over the text uiApp and a tooltip is displayed showing the value of the
variable.
You can click on the + sign to drill down to the various properties and check their values.
To display variable values, you can also right click on the variable in your code and select Add Watch. This
displays the variable and its value in the Watch window at the bottom of your IDE.
Keep stepping through the code, hovering over variable values and properties to see how they change as the
code executes. If at any point you want to stop line-by-line debugging, just hit F5 to Continue to the next
breakpoint you had set which in our case was set to the sel.PickPoint() line.
Stepping through this line by clicking F10, makes Revit the foreground application and prompts you to select
the point where you want the Furniture group to be copied to. Let us select the approximate center of the
adjacent room now. As soon as the point is selected, Visual C# Express now becomes the foreground
application with the next line of code highlighted in yellow with the debugger waiting for us to either check the
value of the selected point or continue line by line debugging or just jump to the next breakpoint by clicking
F5. At this point, we can check the XYZ value of the selected point by just hovering the mouse over the point
variable (which is the container/variable we created to store the target point for the furniture group to be
copied to).
If you hit F5 now, you will find that the debugger steps through all the remaining code, and makes Revit the
foreground application again with the copy operation successfully completed.
Once youve finished experimenting, select Stop Debugging from the Debug menu to close Revit and end
your debugging session.
To avoid incorrectly selecting an element other than a group while using your command, the user would have to move the
cursor very carefully until the group is highlighted and then click to select it. The command should manage this situation more
cleanly: while the message presented to the user gives information about the problem in the code, this is not at all helpful for
the user of the plug-in.
Plug-in Solution
To reduce the chances of the user incorrectly selecting a non-group element, you will add a selection filter. A selection filter
limits the types of object being selected during the user interaction. When you ask the user to select a group, only groups will
be highlighted when hovered over in the Revit user interface. This will help guide users to select a group, even if they havent
seen the prompt displayed on the status bar.
2.
Plug-in Solution
To prevent this error message from being displayed, you will add code to catch the exception and handle it. If the exception is
caused by the user right-clicking or hitting the Esc key, you will simply terminate the current command. For other exceptions,
you will present the error message to the user via the standard Revit error dialog.
Although the above should be enough to prevent these particular errors from being generated, you will also add an exception
handling mechanism to deal with other possible errors, should they occur.
Youll start by adding your selection filter to highlight groups during the PickObject() call. Youll then add an
exception handler to avoid the obscure error message, should the user still select something other than a
group.
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
{
return false;
}
}
In the code window, replace this line:
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
All exceptions that might occur during execution of the above code will be caught and handled gracefully by this exception
handling mechanism.
After the above two steps, you have completed your code for this lesson. The complete code for this lesson is also provided for
download at the top of this lesson. It can be useful to see the complete code to compare your results and ensure they are
correct.
22.
23.
2.
3.
Start the command as you did in Lesson 1. (In the Revit Add-Ins ribbon tab, click the External Tools drop-down list,
and then Lab1PlaceGroup to start executing your plug-ins command.)
4.
Move the cursor over Room1. Irrespective of the geometry being hovered over, only the furniture group should be
highlighted. Click to select the furniture group.
5.
You are now asked to select a point and it is expected you will do so by left-clicking. To test your exception handler,
right-click instead. The command should end without an error message. Similarly, instead of right-clicking, if you hit the Esc
key, the command should also end cleanly.
Each form has specific functionality and has a specific parameter signature. The ObjectType argument
specifies the type of element that you would like selected this is common across all four forms.
In Object-Oriented Programming, a method with the same name can take different numbers and types of
parameters. This concept is known as overloading. In your original code, you called PickObject() this way:
pickedRef = sel.PickObject(ObjectType.Element, "Please select a group");
This corresponds to the third overload in the above list:
This overload takes two arguments: an object type and a prompt string. As the argument names indicate, the
prompt string will be displayed in the status bar. The user will be allowed to select an element of the specified
ObjectType.
In the code you wrote in this lesson, a selection filter is needed to constrain the range of types that can be
selected. You did this by using the fourth overload in the list:
This overload expects you to pass in three arguments: an object type, a selection filter, and a prompt string.
The second argument should be an instance of the class that implements the ISelectionFilter interface. We
introduced the concept of an interface in Lesson 3, when talking about the IExternalCommand interface. In
this lesson you created the GroupPickFilter class which implements the ISelectionFilter interface to tell Revit
which elements and references can be selected.
The ISelectionFilter interface has two methods to be implemented: AllowElement() and AllowReference():
you needed to implement both of these in your GroupPickFilter class. The function parameter signatures of
AllowElement() and AllowReference() are specified in the Revit API. The signatures of these two methods are
shown in the following code.
Here is the skeleton for the new GroupPickFilter class:
public class GroupPickFilter : ISelectionFilter
{
public bool AllowElement(Element e)
{
//insert your code here
}
public bool AllowReference(Reference r, XYZ p)
{
//insert your code here
}
}
During the selection process, when the cursor is hovering over an element, this element will be passed into
the AllowElement() method. The AllowElement() method allows you to examine the element and determine
whether it should be highlighted or not. If you return true from this method, the element can be highlighted
and selected. If you return false, the element can be neither highlighted nor selected.
Its in the implementation of AllowElement() that you decided whether or not the element should be selectable
by checking its category. This is the line of code that checks if the given elements category type is of model
group:
return (e.Category.Id.IntegerValue.Equals(
(int)BuiltInCategory.OST_IOSModelGroups));
e.Category.Id.IntegerValue gets the integer value of the category ID from the element e passed in as a
parameter to AllowElement().
BuiltInCategory.OST_IOSModelGroups refers to a number identifying the built-in model group category,
which we retrieve from the BuiltInCategory collection of fixed integer values.
The BuiltInCategory collection is called an enumeration and is declared using the enum keyword in the C#
language. An enumeration is a way of associating a human-readable string of characters with an underlying
integer: you can think of an enum as a list of integers, each of which has an associated string that can be
used instead of the number itself. OST_IOSModelGroups is a particular value in this BuiltInCategory
enumeration. In this case it actually refers to the integer value -2000095, although you never really need to
know this and it would be bad programming practice to use this number directly in your code.
Since members of an enum are actually integers, you performed a cast to convert
BuildingCategory.OST_IOSModelGroups into an integer to be able to compare it with the category ID value. If
the category ID of the element passed into AllowElement() equals the model groups category ID, then this
element is a group and so should be selectable.
As AllowElement() returns a Boolean value (which is either true or false), it was possible to simplify the
code into a single line in your method implementation:
public bool AllowElement(Element e)
{
return (e.Category.Id.IntegerValue.Equals(
(int)BuiltInCategory.OST_IOSModelGroups));
}
The second method that you needed to implement is AllowReference(). During the selection process, if the
cursor is hovering over a reference, this reference will be passed into the AllowReference() method. Once
again, if you return true from this method, then the reference can be highlighted and selected. If you return
false, the reference can be neither highlighted nor selected. The AllowReference() method also needs to be
implemented for the ISelectionFilter to be complete, but as you dont care about references in your plug-in,
you simply return false from it:
public bool AllowReference(Reference r, XYZ p)
{
return false;
}
Error Handling
When a program needs to interact with its user but doesnt anticipate something the user does, it could lead
to the program crashing or terminating unexpectedly. .NET provides an exception handling mechanism that
can catch these and other errors in execution. You started by placing your code within the braces of a try
block. This tells C# that you wanted the CLR (Command Language Runtime) to watch for exceptions (which
may be errors or other atypical events) and report them to the subsequent catch block. You can think of the
try statement as something that checks for exceptions, and, as soon as it finds one, stops executing the
current block of code and throws it outside of itself to be caught below. The catch block for the type of
exception that is thrown will handle it: its possible to have multiple catch blocks if different behaviors are
needed for certain exceptions.
In this lesson, you added two catch blocks. The first is related to the case where the user might hit Esc or
right-click their mouse during selection of a group or point: this causes an OperationCanceledException
exception to be thrown by the PickObject() or PickPoint() method. Using the following catch block, you simply
returned Result.Cancelled from your command implementation, as you believe the logical behavior for the
user to see at this point is the command to be cancelled:
//If user right click or press ESC button, handle the exception
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
return Result.Cancelled;
}
There are other exceptions that might occur during execution of your code, such as when you added a new
group to the model via the PlaceGroup() method (although the possibility of this failing is very low). Such
errors tend to happen for reasons outside of your control, such as the computer being low on memory. To
make sure all such possibilities are considered, a second, more general catch block will handle any .NET
exception. The argument of this catch block is System.Exception (which is shortened to Exception as you
are using the System namespace in your code), which is the base class for all exceptions in .NET (you could
cast any .NET exception to this type, should you wish). When this more general catch block executes, you
want the command to fail: you place the message contained in your exception (which you access via its
Message property) in the message parameter to the Execute() method and then return Result.Failed. This
tells Revit the command has failed and causes it to display the exception information in its standard error
dialog.
//Catch other error.
catch (Exception ex)
{
message = ex.Message;
return Result.Failed;
}
Well done! You have just written a more robust plug-in by improving the selection functionality and
anticipating the possibility of something unexpected happening with the use of your plug-in, which will, in turn,
improve the user experience. While implementing these new functionalities you discovered more
programming concepts: overloading, enumerations and using a try-catch block for error handling.
A big part of this process will be to find the room containing a particular group. For the sake of simplicity, you
will assume that the selected group is completely enclosed by a room: it should not span two or more rooms.
You will then look for the room that contains the groups center point. You will calculate the center point of that
room and the copied groups target location at a fixed displacement from it. Finally, you will place the copy of
the selected group at this location.
Here are the tasks you implemented in the previous lessons:
a.
b.
c.
To implement the proposed enhancements, there are new tasks (in bold) to be added to the list above. Task
b in the above list is no longer needed, as the group will now be placed using different logic.
a.
b.
c.
d.
e.
f.
g.
As you can see from the above list, youre going to need to calculate the center point for both the Group and
the Room objects. To do so, you will calculate the center point of their respective bounding boxes. As an
example, in the following picture the dashed blue line is the bounding box of a selected group, while the blue
dot in the middle is its center point.
Both the Group and the Room classes provide a BoundingBox property. They do so because they are
derived from the Element class, and the Element class has this property. The Group and Room classes are
said to inherit this property from their parent class. Because of this shared ancestry, you can implement a
function which works for both the Group and the Room objects (and for any Element object, for that matter).
An Elements BoundingBox property returns a BoundingBoxXYZ object containing the minimum and
maximum points of its geometry. You can calculate the center point from these two points by taking the point
halfway between them.
To get the room that contains the selected group, you first need to get all rooms in the model and then go
through each one, checking whether it contains the selected groups center point. You will calculate the center
point of the room containing the group in the same way as you did for the group itself.
3.
4.
5.
Re-open the C# project that you have created in Lesson 5 in Visual C# Express, if you have closed it.
b. Calculate the center of the selected group:
Type the following code fragment inside the class Lab1PlaceGroup, making sure it is outside the Execute() method. The code
defines a new method, GetElementCenter(), which takes an Element as a parameter and returns its center.
6.
7.
8.
9.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
Back in the Execute() method, after the line, GetElementCenter(), which you added in the last step, type the
lines of code highlighted below in bold. The new statement calls your new GetRoomOfGroup() method to
find the room containing the center of the selected group.
// Get the group's center point
XYZ origin = GetElementCenter(group);
// Get the room that the picked group is located in
Room room = GetRoomOfGroup(doc, origin);
4.
5.
6.
7.
8.
9.
10.
11.
12.
d. Calculate the center of the room and e. Display the x, y and z coordinate of the center of the room in a dialog
box:
Type the following code fragment inside the command class, once again making sure the code is outside any existing methods.
The code defines a new GetRoomCenter() method, which takes a Room and as the name suggests returns its center
point. You use the previously defined GetElementCenter() to calculate this, but you modify the Z coordinate of the point you
return to make sure its on the floor of the room.
13.
14.
15.
Remove or comment out (using two forward slashes: //) the following line, which was your former step b. Your new
group will be placed relative to the center of the original groups room, so you dont need the user to select anything else, at this
stage.
17.
18.
//pick a point
XYZ point = sel.PickPoint("Please pick a point");
19.
f. Calculate the target group location based on the rooms center and g. Place the copy of the group at the
target location:
Remove or comment out the current PlaceGroup() call in the Execute() method and replace it with the following lines in bold.
Your new group will be placed at a displacement of (13.12, 0, 0) in feet from the center point of the original groups room (13.12
feet is the width of the two rooms and therefore the horizontal distance between their center points). As both sourceCenter
and new XYZ(13.12,2,0) are of type XYZ, they can be added together to get the new location coordinates.
20.
21.
22.
23.
24.
doc.Create.PlaceGroup(point, group.GroupType);
// Calculate the new group's position
XYZ groupLocation = sourceCenter + new XYZ(13.12, 0, 0);
doc.Create.PlaceGroup(groupLocation, group.GroupType);
This completes your code for this lesson. The complete code for this lesson is also provided for download at the top of this
lesson. It can be useful to see the complete code to compare your results and ensure they are correct.
25.
26.
2.
3.
4.
Click the Close button. Following this, a new group should be inserted into Room 2. Because the displacement of (13.12,0,0) is
the vector from the center of Room 1 to the center of Room 2, the group appears to be copied from Room 1 to the same
relative location in Room 2.
In the implementation of the GetElementCenter() method, you started by accessing the BoundingBox
property of the Element passed in, storing its value in a variable named bounding.
BoundingBoxXYZ bounding = elem.get_BoundingBox(null);
The BoundingBox property is slightly unusual in that it takes a parameter: the view for which the bounding box
is to be calculated. If this parameter is null, the property returns the bounding box of the model geometry. If a
property of a class takes one or more parameters, the get_ prefix is needed before the property name to read
the property value. This prefix isn't needed if the property doesn't take any parameters: you can just use the
property name.
The returned BoundingBoxXYZ contains the coordinates of the minimum and maximum extents of the
Element's geometry. The center point is calculated by taking the average (or mid-point) of these two points.
For the sake of clarity, you stored this in another variable named center.
XYZ center = (bounding.Max + bounding.Min) * 0.5;
Let's now take a closer look at the implementation of the GetRoomOfGroup() method. In this method, you
started by retrieving all the rooms in the document, going through them to find the room that contains the
group. The FilteredElementCollector class helped you with this task: it collects elements of a certain type
from the document provided. That's why you needed to pass a document parameter to the
GetRoomOfGroup() method, so it can be used there.
FilteredElementCollector collector = new FilteredElementCollector(doc);
The collector object is now used to filter the elements in the document. In the next step you added a filter
requesting that only rooms be collected.
collector.OfCategory(BuiltInCategory.OST_Rooms);
You added your category filter to the collector using the OfCategory() method. Once the filter was applied,
the collector only provided access to rooms. The FilteredElementCollector class provides several methods to
add filters (and multiple methods can be applied at the same time for more complex requirements). More
information on adding filters is provided later in this lesson in the Additional Topics section.
You then iterated through each room in the collector using a foreach expression. The code between the
braces is repeatedly executed on each of the elements found by the collector. While you know these
elements will be rooms, at this stage you accessed them as generic elements, as that's how the
FilteredElementCollector provides access to them.
foreach (Element elem in collector)
{
//code between braces pair executed repetitively.
}
The elem variable represents the current element in the collector. So when the code in the body of the
foreach statement gets executed for the first time, the elem variable contains the first room. When the code in
the body of the foreach statement is executed again, this time the elem variable contains the second room.
And so on until you have executed the code against each of the rooms collected from the document.
As mentioned before, the collector provides you with access to each element it contains an instance of the
generic Element class. As you know your elements will actually be instances of the Room class, you needed
to cast them to that type before being able to access the functionality you needed from them (i.e. the
IsPointInRoom() method).
room = elem as Room;
As you may recall, the as keyword first checks the actual type of the object before performing the type
conversion: if the object is not of type Room, the variable will be set to null. Even though you fully expected
the collector only to return rooms, it's still good practice to double-check that the room variable contains a
valid room, just in case.
if (room != null)
The above if statement performs a conditional operation. If the condition provided between the brackets
evaluates to true, the subsequent code block gets executed. An optional else clause can be used to execute
different code when the condition evaluates to false (although this particular statement does not have one).
The if statement is a very important programming concept, so we have provided more information on its use
in the Additional Topics section.
As implied by its name, the IsPointInRoom() method judged whether the specified point is inside the
boundaries of the room. If it was inside, the method returned true, otherwise false. You called
IsPointInRoom() on each room: as soon as it returned true for a particular room, you knew you had found the
one that contained your point and so you did not need to check the others. You then used a break statement
to escape the iteration, even though there may well have been rooms that had not yet been checked. The
break statement stops execution of code in the enclosing loop (in this case the foreach) and starts executing
the code following it.
if (room.IsPointInRoom(point))
{
break;
}
On completion of the loop, the room variable either contains the room in which the point was found if
IsPointInRoom() succeeded for it or the last room in the list of rooms, otherwise. In either case, the contents
of this variable gets returned as the result of the GetRoomOfGroup() method.
return room;
Let's now take a closer look at the implementation of the GetRoomCenter() method. This function is internally
going to make use once again of your GetElementCenter() method, modifying the returned point to make
it at the same level as the floor of the room.
The first line of actual code in the implementation of the GetRoomCenter() stores the center point of your
Room's geometry in a variable, named boundCenter:
// Get the room center point.
XYZ boundCenter = GetElementCenter(room);
In order to make sure this point is adjusted to be at the elevation of the Room, you needed to access the
Room's location point. The location point of a Room is always on its floor, so you determined its elevation by
checking the location's Z coordinate.
The location of a particular room is at the intersection of the lines of the cross displayed by Revit when the
room is selected.
You accessed this point via the Room's Location property. As a Room is located at a particular point, the
value returned from the Location property is actually a LocationPoint object. But the Location property as
it is shared with other Elements returns a more generic Location object. To get access to the point
information stored in the property, you therefore needed to cast its value to be a LocationPoint. You stored
the result of type conversion i.e. the room's location in a variable of type LocationPoint.
LocationPoint locPt = (LocationPoint)room.Location;
This cast does not use the as keyword, as you saw before. The as keyword checks the type of an object
before casting it to the target type. The above code does not perform this check and is appropriate for cases
where you know the underlying type of the object. As you know that instances of the Room class always
return a LocationPoint from their Location property, it's quite safe to use it here. If the property somehow
could not be treated as a LocationPoint (hypothetically speaking), this operation would cause an
InvalidCastException to be thrown.
To get the modified point to return, you took the X and Y coordinates from your Room's center point and the Z
coordinate from its LocationPoint and used these three values to create a new XYZ point.
XYZ roomCenter =
new XYZ(boundCenter.X, boundCenter.Y, locPt.Point.Z);
Finally, you returned the adjusted point from the GetRoomCenter() method.
That's it for your new methods. Let's now take a look at the code you inserted into your command's Execute()
method.
Now that you have your methods in place to do the actual calculations, it's a fairly simple matter of calling
them, one after the other. You can see a progression in the data that is effectively passed from one to the
other.
// Get the group's center point
XYZ origin = GetElementCenter(group);
// Get the room that the picked group is located in
Room room = GetRoomOfGroup(doc, origin);
// Get the room's center point
XYZ sourceCenter = GetRoomCenter(room);
You started by getting the center of your group using GetElementCenter(), placing it in the origin variable of
type XYZ. This was passed into the GetRoomOfGroup() method along with the active document to help
with the room collection process, which returned the containing Room object to be stored in the room
variable. This was then passed to the GetRoomCenter() method to determine the center of this room, which
was stored in the sourceCenter variable.
The sourceCenter variable of type XYZ containing the center of the Room where the Group selected by the
user resides then needed to be displayed to the user in a dialog box.
Each coordinate X, Y and Z of the XYZ class is of type double. This is the type you use to represent a
double-precision, floating-point number. It's not important to worry about what the terms in italics actually
mean: suffice it to say that a variable of type double uses 64-bits of data to hold an extremely accurate
decimal number (to an accuracy of 15-16 digits).
To show these values in a dialog box, they need to be converted to a string. sourceCenter.X returns the
point's X coordinate as a double, which can then be converted to a string type using the ToString() method.
The + operator concatenates (joins together) two strings. The "\r\n" string represents a line break (it's a
combination of a carriage return (\r) followed by a linefeed (\n), two characters that hark back to the days of
the typewriter). This allowed you to split your string across multiple lines in the dialog box.
string coords =
"X = " + sourceCenter.X.ToString() + "\r\n" +
"Y = " + sourceCenter.Y.ToString() + "\r\n" +
"Z = " + sourceCenter.Z.ToString();
The TaskDialog class allows you to display a standardized dialog box inside the Revit user interface. It's
possible to add code to customize the dialog's controls prior to its display, but in this example you just used
the Show() method to display your message. The first parameter is the dialog's title and the second is the
main message.
TaskDialog.Show("Source room Center", coords);
This wraps up the coding part of Lesson 6, before you move on to the next lesson, we will discuss a couple
topics more in depth: Filtering with FilteredElementCollector and the if Statement.
Additional Topics
Filtering with FilteredElementCollector
In this lesson, you requested the contents of the Revit model to be filtered using the OfCategory() method:
collector.OfCategory(BuiltInCategory.OST_Rooms);
Element access and filtering is extremely important to Revit programming. The FilteredElementCollector
class provides several methods to add filters. The OfClass() method adds a class filter, so that the collected
elements are all instances of the specified class. The OfCategoryId() method adds a filter such that the
elements collected will all have the specified category identifiers. The combined filter can be added by the
WherePassed() method. The use of some or all of these methods allows you to extract or filter out elements
for a wide range of scenarios. This helps avoid iterating through very large sets of elements often a very
time-consuming and resource-intensive process. You can find more information about filtering in "Chapter 6
Filtering" of the Revit API Developer Guide in the Revit SDK.
The if Statement
The ability to execute or evaluate different blocks of code in different scenarios is a core part of all
programming languages. In C# this is most commonly achieved using the if statement.
The if statement allows you to execute statements (or sets of statements) conditionally based on the value of
an expression which evaluates to a Boolean (i.e. true or false).
In the following example, a Boolean variable, named flagCheck, is set to true and then checked in the
following if statement. The output is: The flag is set to true.
bool flagCheck = true;
if (flagCheck)
{
Console.WriteLine("The flag is set to true.");
}
else
{
Console.WriteLine("The flag is set to false.");
}
The expression in the parentheses is evaluated to be true. Then the Console.WriteLine("The Boolean flag
is set to true."); statement is executed. After executing the if statement, control is transferred to the next
statement. The else clause is not executed in this example.
If you wish to execute more than one statement, multiple statements can be conditionally executed by
including them in blocks using {}.
Your main task for this lesson is to prompt the user to select target rooms, information that is needed to place
the copies of the selected group. There are a couple of choices here: one way to do this would be to keep the
user selection related functionality together at the beginning, making sure that the user has entered all the
required information before you start your real work. Another would be to have the group information
calculated first and then you add your new tasks of selecting multiple rooms. Here we will choose the latter
option. As you really dont need to display the room coordinates to the user in the final plug-in, youll remove
that task and add your new tasks after that (in bold, below).
a.
b.
c.
d.
e.
f.
Now that you have the overall plan in place, lets look at the steps needed to implement this new functionality.
Re-open the C# project that you have created in Lesson 5 in Visual C# Express, if you have closed it.
2.
e. Prompt the user to select the rooms to which the group will be copied:
Start by adding a new type of filter class outside the implementation of the Lab1PlaceGroup class.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
/*
string coords =
"X = " + sourceCenter.X.ToString() + "\r\n" +
"Y = " + sourceCenter.Y.ToString() + "\r\n" +
"Z = " + sourceCenter.Z.ToString();
*/
Beneath that add the code to prompt the user to select rooms, making use of your new RoomPickFilter class.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
f. Place the group at the center point of each of the selected room:
You'll start this task with the implementation of a new method inside the Lab1PlaceGroup class. Add the following to
Lab1PlaceGroup class:
/// Copy the group to each of the provided rooms. The position
/// at which the group should be placed is based on the target
/// room's center point: it should have the same offset from
/// this point as the original had from the center of its room
public void PlaceFurnitureInRooms(
Document doc,
IList<Reference> rooms,
XYZ sourceCenter,
GroupType gt,
XYZ groupOrigin)
{
XYZ offset = groupOrigin - sourceCenter;
XYZ offsetXY = new XYZ(offset.X, offset.Y, 0);
foreach (Reference r in rooms)
32.
33.
34.
35.
36.
37.
38.
39.
40.
{
Room roomTarget = r.Element as Room;
if (roomTarget != null)
{
XYZ roomCenter = GetRoomCenter(roomTarget);
Group group =
doc.Create.PlaceGroup(roomCenter + offsetXY, gt);
}
}
}
Note: If you are working with Revit 2013 and higher API, the following line of code in the code snippet above
PlaceFurnitureInRooms(
doc, rooms, sourceCenter,
group.GroupType, origin);
The resulting code fragment should now look like this:
41.
42.
The steps to run the command are similar to those used in prior lessons.
1.
2.
3.
4.
5.
Select multiple rooms to where you would like the selected group to be copied.
The selected group should be copied to the same relative position in each of the selected rooms.
You have already used PickObject() and PickPoint() in previous lessons, to select a single object (i.e. a
Group) and a point, respectively. Based on the naming convention of these methods, let's now discover what
other selection methods are available to you. If you open the RevitAPI.chm file (the Revit API Help
Documentation) and search with the keyword *Pick*, you will find a list of methods, one of which is called
PickObjects(). As you can see from the description of this method, it is exactly what you're looking for:
As you can see, there are five different forms of PickObjects() (you may recall from looking at the PickObject()
method that this is known as function overloading). As with your use of PickObject(), it's the fourth version
that appears to be of most interest, as it allows us to use a selection filter and to display a prompt string to the
user.
Click on the link for the fourth method to show you the details of this overload:
As you can see from the method's signature, it returns an IList of Reference objects. IList is a generic list
class defined in the System.Collections.Generic namespace which can be specialized to contain a specific
type of object in this case instances of the Revit API's Reference class. You therefore declared a variable of
type IList to which you assigned the results of the call to the PickObjects() method.
Before going ahead and calling this method, you needed to implement another selection filter, similar to the
one you created previously for Group selection.
Here's the definition of your selection filter class limiting selection to Room objects:
public class RoomPickFilter : ISelectionFilter
{
public bool AllowElement(Element e)
{
return (e.Category.Id.IntegerValue.Equals(
(int)BuiltInCategory.OST_Rooms));
}
public bool AllowReference(Reference r, XYZ p)
{
return false;
}
}
This class is very similar to the one you created previously: the changes are with the name this one is called
RoomPickFilter and the category ID upon which to filter (OST_Rooms).
Back in the Execute() method, you created a RoomPickFilter before calling the PickObjects() method.
RoomPickFilter roomPickFilter = new RoomPickFilter();
In the next statement, which is spread over multiple lines, you declared your reference list and assigned the
results of the PickObjects() call to it:
IList<Reference> rooms =
sel.PickObjects(
ObjectType.Element,
roomPickFilter,
"Select target rooms for duplicate furniture group");
The first line contains the left-hand side of the assignment, specifying the name (rooms) and the type
(IList<Reference>) of your variable. The subsequent lines call the function, passing the required parameters
to it.
The second task in this lesson was to place a new group in each of the selected rooms. This involved looping
through each of the rooms, calculating the center point and placing a new group relative to that point.
Here's the new method performing that task:
public void PlaceFurnitureInRooms(
Document doc,
IList<Reference> rooms,
XYZ sourceCenter,
GroupType gt,
XYZ groupOrigin)
{
XYZ offset = groupOrigin - sourceCenter;
XYZ offsetXY = new XYZ(offset.X, offset.Y, 0);
foreach (Reference r in rooms)
{
Room roomTarget = r.Element as Room;
if (roomTarget != null)
{
XYZ roomCenter = GetRoomCenter(roomTarget);
Group group =
doc.Create.PlaceGroup(roomCenter + offsetXY, gt);
}
}
}
In the lines directly after the method's signature where you defined the parameters being passed in you
defined two variables, offset and offsetXY. The offset variable holds the difference between the group's
origin and the center of the room in which the group is located. This gives you an offset you can use to
position each of the new groups relative to its target room's center. As you want the group to maintain the
same level as the target room, you only care about X and Y offsets. This is the offset that you applied when
placing your new groups.
The next statement was your foreach loop. You took each item from the list of rooms, in turn, and did
something with it.
Inside the foreach loop, you first performed a cast on each Reference from the list, to be able to treat it as a
Room. You did this using the "as" keyword, which as you know will return null if the object is not actually a
Room. It's for this reason that you then used an if statement to make sure you actually have a Room to work
with before proceeding (i.e. if it is not null, as "!=" means "is not equal to").
Assuming you have a valid Room, you then used the GetRoomCenter() method from the previous lesson to
get the room's center, stored it in the roomCenter variable, and placed the group at the appropriate offset
from the center of the target room (roomCenter + offsetXY).
With your new PlaceFurnitureInRoom() method implemented, it remained simply to update the Execute()
method to make use of it.
Instead of your previous call to PlaceGroup(), you changed the code to call PlaceFurnitureInRoom(), passing
in the required parameters.
// Place furniture in each of the rooms
Transaction trans = new Transaction(doc);
trans.Start("Lab");
PlaceFurnitureInRooms(
doc, rooms, sourceCenter,
group.GroupType, origin);
trans.Commit();
This brings you to the end of this lesson and to the practical section of this guide.
Congratulations! You have just completed all the lessons in this guide. You have completed the journey from
being a Revit product user to learning the basics of programming, getting a first look at the Revit API and
creating your first real-world plug-in for Revit.
All that remains is to take a look at some of the resources you will find useful during your onwards journey
with the Autodesk Revit API. We wish you all the very best!
Autodesk Revit LookUp is a tool that helps analyze the currently running instance of Revit, the documents loaded in it
and the elements (including content) contained in the project. In other words, its a great tool for taking the lid off the Revit
database and understanding whats going on. The tool comes with complete source code which can also be used to
understand how to access and analyze model information programmatically. Its most commonly used as a standalone tool for
inspecting the Revit model, though: you certainly dont need the source code for it to be useful.
2.
Add-In Manager is a tool that helps add external commands and applications to Revit without having to manually edit
manifest files (as you did in lesson 1). The tool has its own dialog that allows you to browse to the folder containing the plug-in
DLL, to specify the command name and to provide a plug-in description. Using this information, you can load commands
directly into Revit or save the plug-in information to the manifest file for future loading. This also helps avoid typing errors when
working with manifest files that can lead to unexpected results when attempting to load a plug-in.
Resources Online
Autodesk Revit Developer Center
The Autodesk Revit Developer Center is a great resource for those working with the Autodesk Revit API. As
well as listing recent API additions and improvements, it aggregates the various other resources available to
you. Highlights of this webpage include:
1.
2.
DevTVs, self-paced video tutorials which show you how to create your own plug-ins using the Revit API. These can be
viewed online or downloaded for later viewing at your convenience.
3.
Webcast recordings for the Revit API. They cover the APIs of Autodesk Revit Architecture, Autodesk Revit Structure,
Autodesk Revit MEP as well as the Autodesk Revit Family API. These courses introduce basic Revit programming concepts
and are a great way to learn more about using the Revit API: view the list.
Autodesk Revit product downloads, these can be installed and used without activation for 30 days from the day of
installation
Autodesk Revit Architecture
Autodesk Revit Structure
Autodesk Revit MEP
2.
3.
4.
This brings you to the end of this guide. We hope this has proven to be a helpful beginning to your journey
with the Revit API. We wish you all the very best in your future API endeavors.