Sometimes its not your code (try a different Revit version)

An API developer asked why this code didn’t work to place a face-based family instance on a face in an RVT Link.

public void PlaceFaceBasedFamilyinLink()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = uidoc.Document;
    
    ReferenceIntersector ri = new ReferenceIntersector((View3D)doc.ActiveView);
    ri.FindReferencesInRevitLinks = true;
    Reference r = ri.FindNearest(XYZ.Zero,XYZ.BasisZ.Negate()).GetReference();

    Family fam = new FilteredElementCollector(doc).OfClass(typeof(Family)).Cast<Family>().FirstOrDefault(q => q.Name == "Family2");
    
    using (Transaction t = new Transaction(doc,"place instance"))
    {
        t.Start();
        doc.Create.NewFamilyInstance(r, r.GlobalPoint, XYZ.BasisY, fam.Symbols.Cast<FamilySymbol>().First());
        t.Commit();
    }        
}

After investigating for a while in 2014 and wondering why this error occurred:

Capture

I decided to try the same code in 2015 where I found it to work just fine. This doesn’t help me get it working if I need a Revit 2014 solution, but at least it lets me stop spending time looking for a problem in my code that doesn’t exist.

Advertisements

How To Find Elements

A reader asked how to count the number of doors in a model. Here is a 5 line macro:

  1. Get the document
  2. Create a FilteredElementCollector for this document
  3. Create a list of doors using this collector. There are 3 methods applied to the collector
    1. OfClass restricts the collector to only FamilyInstance elements
    2. OfCategory restricts the collector to doors
    3. ToList converts the output of the collector into a list
  4. Create an integer variable to store the count of the list
  5. Display the count to the user.
public void CountDoors()
{
    Document doc = this.ActiveUIDocument.Document;
    FilteredElementCollector collector = new FilteredElementCollector(doc);
    List<Element> doorList = collector.OfClass(typeof(FamilyInstance)).OfCategory(BuiltInCategory.OST_Doors).ToList();
    int doorCount = doorList.Count;
    TaskDialog.Show("Door Count", "Your projects has " + doorCount + " doors");
}

He also asked how to count the number of doors in a view. This should be possible to do for the active view by changing the 2nd line to:

collector = new FilteredElementCollector(doc, doc.ActiveView.Id);

This version of the constructor constructs a new FilteredElementCollector that will search and filter the visible elements in a view. But the documentation is a bit vague about what exactly will be found. It says:

“Elements that will be passed by the collector have graphics that may be visible in the input view. Some elements may still be hidden because they are obscured by other elements.”

When I run make this change and run the macro in a ceiling plan where the doors are not visible, it correctly says there are 0 doors.

But when the active view is Level 2 Plan or East Elevation, where no doors are visible, the collector finds them anyway. So, specifying the view in the FilteredElementCollector constructor will not always produce the desired result.

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

Finding distances to objects with ReferenceIntersector

Here’s a different way to find the position of each door in a wall using the ReferenceIntersector class to shoot a ray in a specified direction from a given point. ReferenceIntersector returns a list with information about the objects hit by the ray and the distance from the element to the ray’s origin.
refintersector

Here’s the source. I’ve tried to be generous with the comments. Let me know if it needs more description.

public void FindDoorsInWallWithReferenceInteresector()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = new UIDocument(doc);
    Wall wall = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element)) as Wall;

    LocationCurve locCurve = wall.Location as LocationCurve;
    Curve curve = locCurve.Curve;
    // Find the direction of the tangent to the curve at the curve's midpoint
    // ComputeDerivates finds 3 vectors that describe the curve at the specified point
    // BasisX is the tangent vector
    // "true" means that the point on the curve will be described with a normalized value
    // 0 = one end of the curve, 0.5 = middle, 1.0 = other end of the curve
    XYZ curveTangent = curve.ComputeDerivatives(0.5,true).BasisX;
    // XYZ coordinate of one of the wall's endpoints
    XYZ wallEnd = curve.get_EndPoint(0);

    // Filter to find only doors
    ElementCategoryFilter filter = new ElementCategoryFilter(BuiltInCategory.OST_Doors);

    // ReferenceIntersector uses the specified filter, find elements, in a 3d view (which is the active view for this example)
    ReferenceIntersector intersector = new ReferenceIntersector(filter, FindReferenceTarget.Element, (View3D)doc.ActiveView);

    string doorDistanceInfo = "";
    // ReferenceIntersector.Find shoots a ray in the specified direction from the specified point
    // it returns a list of ReferenceWithContext objects
    foreach (ReferenceWithContext refWithContext in intersector.Find(wallEnd,curveTangent))
    {
        // Each ReferenceWithContext object specifies the distance from the ray's origin to the object (proximity)
        // and the reference of the element hit by the ray
        double proximity = refWithContext.Proximity;
        FamilyInstance door = doc.GetElement(refWithContext.GetReference()) as FamilyInstance;
        doorDistanceInfo += door.Symbol.Family.Name + " - " + door.Name + " = " + proximity + "\n";
    }
    TaskDialog.Show("ReferenceIntersector", doorDistanceInfo);
}

Resolving duplicate tags Step 2 – finding only family instances

Running the command results in this output. But there are only 6 doors in the model (3 added by each user), so where do the other 7 come from?

typesinstances

Lets look at these 2 lines in more detail:

FilteredElementCollector collector = new FilteredElementCollector(documnent);
IList<Element> elements = collector.OfCategory(BuiltInCategory.OST_Doors).ToList();

The FilteredElementCollector is the class that the Revit API uses to filter and collect elements in the model. The first line is a constructor that creates a new FilteredElementCollector.

The next line uses this collector to create a list of elements that belong to the built-in category OST_Doors. But this does more than get the 6 door instances that we want. It also collects the 7 door family types that are loaded in the model, hence the 13 elements that are in the list.

To restrict the filter to find only family instances, the FilteredElementCollector.OfClass method can be used and the that line re-written to:

IList<Element> elements = collector.OfCategory(BuiltInCategory.OST_Doors).OfClass(typeof(FamilyInstance)).ToList();

With this change, the list will have only the 6 family instances of doors, which is what we want for the next step.