A Closer Look at the Code
Let's now take a closer look at the implementation details for the tasks you have added during this lesson.
The first of these tasks deals with asking the user to select a set of rooms.
IList<Reference> rooms =
sel.PickObjects(
ObjectType.Element,
roomPickFilter,
"Select target rooms for duplicate furniture group");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!