The LINQ “Select” statement to get rid of extraneous looping

In the previous post, I included this code to show how to build a list of element id’s that could then be used with Document.Delete

It finds all ImportInstance elements that are not linked, and then iterates through those elements in the foreach loop to create a list of their IDs.

    IList<ElementId> toDelete = new List<ElementId>();
    foreach (ImportInstance ii in new FilteredElementCollector(doc)
         .OfClass(typeof(ImportInstance))
         .Cast<ImportInstance>()
         .Where(i => i.IsLinked == false))
        {
            toDelete.Add(ii.Id);
        }   

But that foreach loop looks ugly (to me) and so I wanted to share a simplification that allows you to get rid of that loop and build this list of IDs directly.

The LINQ Select statement takes a lambda expression (in this case an expression taking a type ImportInstance represented as i). It returns whatever you want it to, in this case the ElementId returned from the i.Id property.

    IList<ElementId> toDelete =  new FilteredElementCollector(doc)
        .OfClass(typeof(ImportInstance))
        .Cast<ImportInstance>()
        .Where(i => i.IsLinked == false)
        .Select(i => i.Id)
        .ToList();

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();
}

Scrubbing Out Dimension Styles

selectAllInstance

Over at AUGI a good question was asked about how to get undesired dimension styles out of a project.

Dimensions in sketches and unplaced groups are at least part of the problem because right-clicking on a dimension and choosing “Select All Instances – In Entire Project” doesn’t select them.

Here is a macro that handles both dimensions in sketches and dimensions in unplaced groups. The dimensions in sketches are found as part of a normal filter to find all dimensions in the project. Dimensions in unplaced groups needed to be treated specially because an instance of the group needs to be created before the dimension in the group can be changed.

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

    // Prompt user to select a dimension with an unwanted style
    Dimension selected = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element), "Select dimension with style to scrub.") as Dimension;
    // Get name of the dimension style of this dimension
    string toScrub = selected.DimensionType.Name;

    // Find the dimension style named "Default" which will be used to replace the unwanted style
    // There must be a style named "Default" for this command to work
    DimensionType defaultType = (from v in new FilteredElementCollector(doc)
                                                        .OfClass(typeof(DimensionType))
                                                        .Cast<DimensionType>()
                                                        where v.Name == "Default" select v).First();

    using (Transaction t = new Transaction(doc,"Change dimension style to default"))
    {
        t.Start();
        // loop through all dimensions with the unwanted style
        foreach (Dimension dimension in (from v in new FilteredElementCollector(doc).OfClass(typeof(Dimension)).Cast<Dimension>()
                              where v.DimensionType.Name == toScrub select v))
        {
            // change the dimension's style to the default style
            dimension.DimensionType = defaultType;
        }
        t.Commit();
    }

    // Dimensions in unplaced groups will not be found by the code above.
    // So we need to place an instance of each of these groups, find any dimensions in the group, and change their style if needed
    foreach (GroupType groupType in new FilteredElementCollector(doc).OfClass(typeof(GroupType)))
    {
        // if there are already instances of the group then skip to the next group type
         if (groupType.Groups.Size > 0)
             continue;
         using (Transaction t = new Transaction(doc,"Change dimension style in unplaced groups"))
         {
            // use this flag to track if any dimensions have been changed
            bool flag = false;
            t.Start();

            // place an instance of the group
            Group newGroup = doc.Create.PlaceGroup(XYZ.Zero, groupType);

            // loop through all members in the group
            foreach (ElementId id in newGroup.GetMemberIds())
            {
                  // identify any dimensions in the group
                  Dimension dimension = doc.GetElement(id) as Dimension;
                  if (dimension != null)
                  {
                      // check if this dimension has the unwanted type
                      if (dimension.DimensionType.Name = toScrub)
                      {
                          // change the dimension type to the default type
                          dimension.DimensionType = defaultType;
                          flag = true;
                      }
                  }
            }
            doc.Delete(newGroup.Id); // delete the newly placed group
            if (flag)
                  t.Commit(); // commit this transaction if any dimensions were changed
            else
                  t.RollBack(); // no dimensions were changed, so throw away the entire transaction
         }
    }    
}

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 3d views with one lovely long laborious LINQ and Lamda line

Language-Integrated Query (LINQ) and Lamda expressions are great ways to do a whole lot without writing much code.

Here is some code that improves on the previous post by excluding the view templates.

public void ThreeDViewsWithLinq()
{
    Document doc = this.ActiveUIDocument.Document;
    IList<View3D> viewList = new FilteredElementCollector(doc).OfClass(typeof(View3D)).Cast<View3D>().Where(v => v.IsTemplate == false).ToList();
    string views = "";
    foreach (View3D view in viewList)
    {
        views += view.Name + "-" + view.IsPerspective + "\n";
    }
    TaskDialog.Show("3D Views", views);
}

Here are the pieces that go together to define the objects found by the FilteredElementCollector.

  • OfClass(typeof(View3D)) has been shown in previous examples as a way to restrict the filter to 3d views
  • Cast<View3D>() converts the items from generic Element objects to View3D objects. Later on, when we want to check the IsPerspective property, the objects will need to be View3D objects (because IsPerspective is a property of View3D, not a property of Element). Also, to do the lamda expression that evaluates a view property (IsTemplate) we need to cast to the View class (or a sub-class such as View3D).
  • Where(v => v.IsTemplate == false) is the lamda expression. Microsoft defines a lambda expression as ” an anonymous function that you can use to create delegates or expression tree types”. Whatever. I’d rather say it is a way to include only objects that pass some logical statement. In this case, the IsTemplate property of the views must be false.
  • ToList() converts this bunch of view3D objects into a list

You don’t need to understand all the computer science stuff behind LINQ and lambda expressions to use them. The line of code above can be copied and reused as you need, changing View3D to some other class and “IsTemplate == false” to some other statement.

3dlinq