Two Clicks to Create and Rotate Family Instances

public void createAndOrient()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = uidoc.Document;
    Application app = doc.Application;

    // Get the family symbol named "North Arrow 2"
    FamilySymbol famSym = new FilteredElementCollector(doc).OfClass(typeof(FamilySymbol)).Where(q => q.Name == "North Arrow 2").First() as FamilySymbol;

    // use a transaction group so that all the individual transactions are merged into a single entry in the Undo menu
    // this is optional
    using (TransactionGroup tg = new TransactionGroup(doc,"Create and Orient Instances"))
    {
        tg.Start();
        // create an infinite loop so user can create multiple instances in a single command
        // ESC when prompted to select a point will thrown an exception which is how the loop is exited
        while (true)
        {
            try
            {                
                XYZ pickPoint = uidoc.Selection.PickPoint("Click to specify instance location. ESC to stop placing instances.");

                FamilyInstance familyInstance = null;    
                // Create the instance with the default orientation
                // This is done in its own transaction so that the user can see the new instance when they are prompted for the orientation
                using (Transaction t = new Transaction(doc,"Place Instance"))
                {
                    t.Start();
                    familyInstance = doc.Create.NewFamilyInstance(pickPoint, famSym, doc.ActiveView);
                    t.Commit();
                }

                XYZ orientPoint = uidoc.Selection.PickPoint("Click to specify orientation. ESC to stop placing instances.");

                // Create a line between the two points
                // A transaction is not needed because the line is a transient element created in the application, not in the document
                Line orientLine = app.Create.NewLineBound(pickPoint, orientPoint);

                // Compute the angle between the vertical direction (XZY.BasisY) and the orientLine
                double angle = XYZ.BasisY.AngleTo(orientLine.Direction);

                // For diagnostics in Task dialog below
                double angleDegrees = angle * 180 / Math.PI;

                // AngleTo always returns the smaller angle between the two lines (for example, it will always return 10 degrees, never 350)
                // so if the orient point is to the left of the pick point, then correct the angle by subtracting it from 2PI (Revit measures angles in degrees)
                if (orientPoint.X < pickPoint.X)
                    angle = 2 * Math.PI - angle;

                // To show the need for angle corrections
                double angleDegreesCorrected = angle * 180 / Math.PI;
                //TaskDialog.Show("info","Angle directly from AngleTo = " + angleDegrees + "\n Angle after X correction = " + angleDegreesCorrected);

                // Create an axis in the Z direction 
                Line axis = app.Create.NewLineBound(pickPoint, new XYZ(pickPoint.X, pickPoint.Y, pickPoint.Z + 10));

                using (Transaction t = new Transaction(doc,"Orient Instance"))
                {
                    t.Start();
                    ElementTransformUtils.RotateElement(doc, familyInstance.Id, axis, -angle);
                    t.Commit();
                }
            }
            catch
            {
                // Get here when the user hits ESC when prompted for selection
                // "break" exits from the while loop
                break;
            }
        }
    // Consolidate all the transactions for the individual creation / rotation transactions into a single Undo item	
    tg.Assimilate();
    }
}
Advertisements

Catching Exceptions

As Arnost mentioned in a comment to an earlier post, the methods Selection.PickObject and Select.PickObjects throw exceptions when the user cancels the pick operation. This most commonly happens when the user presses the ESC key. Speaking of the ESC, did you read this http://www.nytimes.com/2012/10/07/magazine/who-made-that-escape-key.html?

If you run this macro and push ESC instead of making a selection, Revit will throw the exception, your code will not catch the exception, and you will see this:

Reference r = uidoc.Selection.PickObject(ObjectType.Element);
TaskDialog.Show("Thanks!", "You selected " + doc.GetElement(r).Name);

exception

Much better is for your code to handle the error by using a try/catch block. For example, this code displays a TaskDialog when the exception is thrown and then caught.

try
{
    Reference r = uidoc.Selection.PickObject(ObjectType.Element);
    TaskDialog.Show("Thanks!", "You selected " + doc.GetElement(r).Name);
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException exception)
{
    TaskDialog.Show("Oh No!", "You did not make a selection so PickObject threw an exception: " + exception.Message);
}

catch

Retrieving Elements Stored in a SelectionFilterElement

Building on this previous post, here is how to retrieve elements that are stored in a SelectionFilterElement. After getting the elements, the code below shows how to select them in the Revit UI.
beforeAfter

public void GetSelectionFilterElements()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = new UIDocument(doc);

    // Get the SelectionFilterElement named "My Selection Filter" with this LINQ statement
    SelectionFilterElement filter = (from f in new FilteredElementCollector(doc)
        .OfClass(typeof(SelectionFilterElement))
        .Cast<SelectionFilterElement>()
        where f.Name == "My Selection Filter" select f).First();

    // Get ids of elements in the filter
    ICollection<ElementId> filterIds = filter.GetElementIds();

    // Report # of elements to user
    TaskDialog.Show("Elements in filter", filterIds.Count.ToString());
    // ToString() must be used here because Count returns an integer and TaskDialog.Show requires a string
    // This hasn't been needed in other examples because when you concatenate a string with an integer
    // the whole thing automatically becomes a string. So ToString() would not be needed if I did:
    // "Number of elements is " + fitlerIds.Count

    // Now select the elements in "My Selection Filter" in the Revit UI
    // Create a SelElemenSet object
    SelElementSet selSet = SelElementSet.Create();
    foreach (ElementId id in filterIds) // loop through the elements in the filter
    {
        Element e = doc.GetElement(id); // get the element for each id
        selSet.Add(e); // add the element to the SelElementSet
    }
    // Use the SelElementSet to set the elements shown as selected in the Revit UI
    uidoc.Selection.Elements = selSet; 
    // Refresh the active view so that the change in selection higlighting is shown
    uidoc.RefreshActiveView();
}

Auto Generate 3D Walls from 2D Line floor plan (with a Selection pre-filter)

Darren asks at AUGI “Can Revit Architecture Auto Generate 3D from 2D floor plan?”

Here’s a little macro to do just that. Run the macro, select these 4 lines, and get these 4 walls.

walls from lines

Most of this should look familiar to code I have explained in previous posts.

The new piece is the ISelectionFilter. This applies a pre-filter to PickObjects so that when the user is prompted to select objects, the Select tool will only allow selections that pass the filter. In this case, the filter is defined so that it will return true only when ModelLines are selected. Nothing will be selected if the user clicks the mouse with the cursor over a wall, window, column, or anything else that it not a model line.

public void wallsFromLines()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = new UIDocument(doc);

    // Get "Level 1"
    Level level = (from l in new FilteredElementCollector(doc).OfClass(typeof(Level)) where l.Name == "Level 1" select l).First() as Level;

    // Selection will be restricted to lines only. Attempts to click on other objects will be ignored
    ISelectionFilter lineFilter = new LineSelectionFilter();

    IList<Reference> lineRefs = uidoc.Selection.PickObjects(ObjectType.Element, lineFilter, "Pick lines");

    using (Transaction t = new Transaction(doc,"Create Walls From Lines"))
    {
        t.Start(); 
        foreach (Reference reference in lineRefs)
        {
            ModelLine modelLine = doc.GetElement(reference) as ModelLine;
            doc.Create.NewWall(modelLine.GeometryCurve, level, false); // false indicates that the wall's Structural property is false
        }
        t.Commit();
    }
}
public class LineSelectionFilter : ISelectionFilter
{
    // determine if the element should be accepted by the filter
    public bool AllowElement(Element element)
    {
        // Convert the element to a ModelLine
        ModelLine line = element as ModelLine;
        // line is null if the element is not a model line
        if (line == null)
        {
            return false;
        }
        // return true if the line is a model line
        return true;
    }

    // references will never be accepeted by this filter, so always return false
    public bool AllowReference(Reference refer, XYZ point)
    {return false;}
}

In the real world you may want to do this with many walls and use a filter or some other mechanism instead of selecting them individually. You would also want something more sophisticated to specify the base constraint of the walls instead of having them all at Level 1. Model lines don’t have a Level property, but you can get their Z location from ModelLine.GeometryCurve.Origin and then find a level with the same elevation.

Save a Set of Elements with SelectionFilterElement

Here’s another great question from a reader! Is it possible to save a selection, with a name, and call upon it later?

Yes, it is.

public void SelectionFilter()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = new UIDocument(doc);

    // Prompt user to pick one or more elements
    IList<Reference> refList = uidoc.Selection.PickObjects(ObjectType.Element, "Pick elements to add to selection filter");

    // Create a transaction
    using (Transaction t = new Transaction(doc,"Create & Add To Selection Filter"))
    {
        t.Start(); // Start the transaction

        // Create a SelectionFilterElement
        SelectionFilterElement selFilter = SelectionFilterElement.Create(doc,"My Selection Filter");

        foreach (Reference r in refList) // Iterate through list of selected elements
        {
            // Add the ElementId of each selected element to the selection filter
            selFilter.AddSingle(r.ElementId);
        }

        // Commit the transaction
        t.Commit();
    }
}

After the filter is created it can be seen in the Filters dialog. It cannot be applied to a view but it can be used in other ways which is a a good topic for a future posts.

filterDialog

P.S. If you try to run this macro twice, it will fail the 2nd time because the filter names must be unique. The IsNameUnique method could be used to check for this case.

Find a Room’s Ceiling Height

Revit can compute some great info about Rooms – area, perimeter, and volume. But out-of-the-box Revit can’t tell you the height of the ceiling above a room. Fortunately the API lets us find this info. Pulling together some things that have been covered in previous posts, here is the code and a screenshot.

public void RoomCeilingHeight()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = new UIDocument(doc);

    // Prompt user to select a room
    Room room = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element)) as Room;

    // Get the location point of the room.
    // If you want to check other points for a room with non-uniform ceiling height
    // use Room.IsPointInRoom to make sure the points are in the room
    LocationPoint roomPoint = room.Location as LocationPoint;

    // The 3 inputs for ReferenceIntersector are:
    // A filter specifying that only ceilings will be reported
    // A FindReferenceTarget option indicating what types of objects to report
    // A 3d view (see http://wp.me/p2X0gy-2p for more info on how this works)
    ReferenceIntersector intersector = new ReferenceIntersector(
        new ElementCategoryFilter(BuiltInCategory.OST_Ceilings),
        FindReferenceTarget.All,
        (from v in new FilteredElementCollector(doc).OfClass(typeof(View3D)).Cast<View3D>() where v.IsTemplate == false && v.IsPerspective == false select v).First());

    // FindNearest finds the first item hit by the ray
    // XYZ.BasisZ shoots the ray "up"
    ReferenceWithContext rwC = intersector.FindNearest(roomPoint.Point, XYZ.BasisZ);

    // Report the data to the user. This information could also be used to set a "Ceiling Height" instance parameter
    if (rwC == null)
        TaskDialog.Show("Height", "no ceiling found");
    else
        TaskDialog.Show("Height",rwC.Proximity.ToString());
}
roomCeilingHeight

Getting info about rooms

A friendly member of the new BoostYourBIM fan club wrote with some suggestions that we might be able to some interesting things with rooms.

In the last example a basic filter was used to get all the doors. The other main way to get elements is with selection, which I will use here.

There are these two “using” statements to add for this code:

using Autodesk.Revit.DB.Architecture;
using Autodesk.Revit.UI.Selection;

And here is the code to select a room and show some basic information about it.

        public void RoomElements()
        {
            Document document = this.ActiveUIDocument.Document;
            UIDocument uidoc = new UIDocument(document);
            Reference reference = uidoc.Selection.PickObject(ObjectType.Element);
            Element element = document.GetElement(reference);
            Room room = element as Room;
            TaskDialog.Show("Room Data",room.Name + "\n" + room.Number);
        }

The UIDocument is a class that handles the “UI”, as opposed to the database (DB) of Revit. This primarily involves views and selection. The constructor creates a UIDocument from a DB Document.

The Reference class is used to reference elements in the documents. A popular thing to do with it is to get the element that it refers to, which is done on the following line.

Then the element is converted into a room using the “as” operator to do the cast conversion.

Finally, a Task Dialog is used to display the room name and number. As the image below shows, a “quirk” of the API is that room.Name includes the room name and number.

selectedRoom