#RTCNA leftover – know your StorageType

A question came up at RTC about why a certain parameter could not be set with the API. Turns out that it was a parameter that stored an ElementId. In this case, trying to set the parameter with a string for the name of the desired element does not work. And to make it confusing, it fails silently with no warning, error, or exception.

When the parameter was set with an ElementId, all was well. See the example below where we need to use the ID of the Phase named “Existing” instead of just setting the parameter to the string “Existing”.

public void setPhase()
{
    Document doc = this.ActiveUIDocument.Document;
    Parameter phaseParam = doc.ActiveView.get_Parameter(BuiltInParameter.VIEW_PHASE);
    using (Transaction t = new Transaction(doc, "Set Phase"))
    {
        t.Start();
        
        // doesn't work, because phaseParam stores an ElementId, not a string
        // see the Parameter.StorageType Property for more info
        // phaseParam.Set("Existing");
        
        phaseParam.Set(new FilteredElementCollector(doc).OfClass(typeof(Phase)).FirstOrDefault(q => q.Name == "Existing").Id);
        
        t.Commit();
    }
}

#RTCNA wish 10 granted! Material replacement

Amy asked if the API can be used to find and replace materials

This implementation does that for wall types and could be extended for other objects where the material is stored in a different manner.

BEFORE

 before

AFTER

after


public void replaceMaterials()
{
    Document doc = this.ActiveUIDocument.Document;
    
    List<Tuple<string,string>> replacements = new List<Tuple<string, string>>();
    // old materal listed first, then new material that will replace it
    replacements.Add(new Tuple<string, string>("Brick, Common", "Brick, Orange"));
    replacements.Add(new Tuple<string, string>("Concrete Masonry Units", "Cherry"));

    using (Transaction t = new Transaction(doc, "Replace Materials"))
    {
        t.Start();
        foreach (WallType wt in new FilteredElementCollector(doc).OfClass(typeof(WallType)).Cast<WallType>())
        {
            CompoundStructure cs = wt.GetCompoundStructure();
            if (cs == null)
                continue;

            int idx = 0;
            foreach (CompoundStructureLayer layer in cs.GetLayers())
            {
                foreach (Tuple<string, string> tup in replacements)
                {
                    Element oldMaterial = new FilteredElementCollector(doc).OfClass(typeof(Material))
                        .FirstOrDefault(q => q.Name == tup.Item1);
                    Element newMaterial =  new FilteredElementCollector(doc).OfClass(typeof(Material))
                        .FirstOrDefault(q => q.Name == tup.Item2);
                    
                    if (layer.MaterialId == oldMaterial.Id)
                        cs.SetMaterialId(idx, newMaterial.Id);
                }
                idx++;
            }
            wt.SetCompoundStructure(cs);
        }
        t.Commit();
    }
}

#RTCNA wish 8 granted! Delete unused elevation markers

Want to get rid of these?

Capture

public void deleteUnusedElevations()
{
    Document doc = this.ActiveUIDocument.Document;
    using (Transaction t = new Transaction(doc, "Delete unused elevations"))
    {
        t.Start();
        doc.Delete(new FilteredElementCollector(doc)
                   .OfClass(typeof(ElevationMarker))
                   .Cast<ElevationMarker>()
                   .Where(q => !q.HasElevations())
                   .Select(q => q.Id).ToList());
        t.Commit();
    }
}

#RTCNA wish 7 granted! Isolated 3d view for each workset

Create a 3d view for each workset, and in that view isolate the display of the elements in that workset.

Note that to find worksets we use a FilteredWorksetCollector (not a FilteredElementCollector) and a WorksetKindFilter to get only the user worksets (don’t want view, family, project standard worksets).

public void ViewWorksetIso()
{
    Document doc = this.ActiveUIDocument.Document;
    if (!doc.IsWorkshared)
        return;
    
    // get the 3d view type which is needed when creating 3d views
    ViewFamilyType vft = new FilteredElementCollector(doc)
        .OfClass(typeof(ViewFamilyType))
        .Cast<ViewFamilyType>()
        .FirstOrDefault(q => q.ViewFamily == ViewFamily.ThreeDimensional);
    
    using (Transaction t = new Transaction(doc, "workset view isolation"))
    {
        t.Start();
        
        // loop through all worksets (but only User worksets)
        foreach (Workset wset in new FilteredWorksetCollector(doc).WherePasses(new WorksetKindFilter(WorksetKind.UserWorkset)))
        {
            // create a 3d view
            View3D view = View3D.CreateIsometric(doc, vft.Id);
            
            // set the name of the view to match the name of the workset
            view.Name = "WORKSET - " + wset.Name;
            
            // isolate elements in the view, using a filter to find elements only in this workset
            view.IsolateElementsTemporary(new FilteredElementCollector(doc).WherePasses(new ElementWorksetFilter(wset.Id)).Select(q => q.Id).ToList());
        }
        t.Commit();
    }
}

#RTCNA wish 6 granted! Create assemblies from groups

Here is how to create an assembly and assembly views from a group

public void assemblyFromGroup()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;
    
    // prompt the user to select a group
    Group group = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element)) as Group;
    
    View v3d = null;
    View partlist = null;
    View section = null;
    using (Transaction t = new Transaction(doc,"Create Assembly From Group"))
    {
        t.Start();
        
        // ungroup the group, getting the list of elements in the group
        List<ElementId> ids = group.UngroupMembers().ToList();
        
        // get the category of the first element in the group
        // a category needs to be specified when creating the assembly
        Category cat = doc.GetElement(ids.First()).Category;
        
        // create the assembly
        AssemblyInstance ai = AssemblyInstance.Create(doc, ids, cat.Id);
        
        // create the assembly views
        v3d = AssemblyViewUtils.Create3DOrthographic(doc, ai.Id);
        partlist = AssemblyViewUtils.CreatePartList(doc, ai.Id);
        section = AssemblyViewUtils.CreateDetailSection(doc, ai.Id, AssemblyDetailViewOrientation.DetailSectionA);
        t.Commit();
    }
    // activate the new views so they are visible when the command ends
    // The active view can only be changed when there is no open transaction
    uidoc.ActiveView = v3d;
    uidoc.ActiveView = partlist;
    uidoc.ActiveView = section;
}

#RTCNA wish 4 granted! Delete empty tags

Its great when a tiny bit of code can do something useful!

  • Filter for only elements in the active view: FilteredElementCollector(doc, doc.ActiveView.Id)
  • Get only tags with no text: Where(q => q.TagText == "")
  • Convert from Elements into a list of ElementIds (because that’s what Document.Delete() requires as an input): .Select(q => q.Id).ToList()

BEFORE

 before

AFTER

after

public void deleteEmptyTags()
{
    Document doc = this.ActiveUIDocument.Document;
    using (Transaction t = new Transaction(doc, "Delete empty tags"))
    {
        t.Start();
        doc.Delete(new FilteredElementCollector(doc, doc.ActiveView.Id)
             .OfClass(typeof(IndependentTag))
             .Cast<IndependentTag>()
             .Where(q => q.TagText == "")
             .Select(q => q.Id).ToList());
        t.Commit();
    }
}

#RTCNA Wish 3 granted! Join all walls and floors

Here’s how to find all wall/floor intersections and join their geometry. The first, simpler macro prompts the user to select to elements and then joins them. The 2nd one looks at each wall in the model and joins it to all intersecting floors.

BEFORE
before
AFTER
after

public void joinGeometryUserPick()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;
    
    // prompt user to select two elements
    Element e1 = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element));
    Element e2 = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element));
    
    // join the selected elements
    using (Transaction t = new Transaction(doc, "Join"))
    {
        t.Start();
        JoinGeometryUtils.JoinGeometry(doc, e1, e2);
        t.Commit();
    }
}

public void joinAllWallsFloors()
{
    Document doc = this.ActiveUIDocument.Document;
    using (Transaction t = new Transaction(doc, "Join All Walls/Floors"))
    {
        t.Start();
        foreach (Element wall in new FilteredElementCollector(doc)
             .OfClass(typeof(Wall)))
        {
            // get the bounding box of this wall
            BoundingBoxXYZ bbox = wall.get_BoundingBox(null);
            
            // create an outline from the min and max points of the bounding box
            Outline outline = new Outline(bbox.Min, bbox.Max);
            
            // find all floors that intersect the wall
            foreach (Element floor in new FilteredElementCollector(doc)
                 .OfClass(typeof(Floor))
                 .WherePasses(new BoundingBoxIntersectsFilter(outline)))
            {
                JoinGeometryUtils.JoinGeometry(doc, wall, floor);
            }
                     
        }
        t.Commit();
    }
}

#RTCNA Wish 2 granted! Create view for every level for every view type

Jason gets another wish granted, this time to “Create a view for every level for every view type in your project file

public void createViewsForViewTypes()
{
    Document doc = this.ActiveUIDocument.Document;
    using (Transaction t = new Transaction(doc, "Create Views"))
    {
        t.Start();
        foreach (ViewFamilyType vft in new FilteredElementCollector(doc)
                 .OfClass(typeof(ViewFamilyType))
                 .Cast<ViewFamilyType>()
                 .Where(q => q.ViewFamily == ViewFamily.FloorPlan))
        {
            foreach (Level level in new FilteredElementCollector(doc)
                     .OfClass(typeof(Level)))
            {
                ViewPlan newview = ViewPlan.Create(doc, vft.Id, level.Id);
                newview.Name = level.Name + "-" + vft.Name;
            }
        }
        t.Commit();
    }
}

#RTCNA Wish 1 granted! Export wall type layer information

Jason wished that the API could be used to “create a data dump of wall types to show the different layers and assigned materials

Two things to consider: Curtain and stacked walls don’t have layers, and <By Category> has no material element ID

public void exportWallTypeStructure()
{
    Document doc = this.ActiveUIDocument.Document;
    
    // set name of output file
    string output = Path.Combine(Path.GetTempPath(), Path.GetFileNameWithoutExtension(doc.PathName) + "-walls.txt");
    
    // create file for writing, overwriting an existing file with the same name if it exists
    using (StreamWriter sw = new StreamWriter(output, false))
    {
        // look at all basic wall types (not stacked & curtain walls)
        foreach (WallType wt in new FilteredElementCollector(doc).OfClass(typeof(WallType)).Cast<WallType>().Where(q => q.Kind == WallKind.Basic))
        {
            sw.Write(wt.Name + ","); // write the name of the wall type
            
            // iterate through every layer in the wall
            foreach (CompoundStructureLayer layer in wt.GetCompoundStructure().GetLayers())
            {
                Element material = doc.GetElement(layer.MaterialId);
                
                // handle case when ID is -1 because layer does not have a material assigned
                string mname = "<By Category>";
                if (material != null)
                    mname = material.Name;
                
                sw.Write(mname + " = " + layer.Width + ",");
            }
            sw.Write(Environment.NewLine);
        }
    }
    Process.Start(output); //open the output file
}

Sample Output:
Generic - 6",<By Category> = 0.5,
Interior - 5" Partition (2-hr),Gypsum Wall Board = 0.0520833333333333,Gypsum Wall Board = 0.0520833333333333,Metal Stud Layer = 0.208333333333333,Gypsum Wall Board = 0.0520833333333333,Gypsum Wall Board = 0.0520833333333333,
Exterior - Brick on Mtl. Stud,Brick, Common = 0.302083333333333,Air = 0.25,Air Infiltration Barrier = 0,Plywood, Sheathing = 0.0625,Metal Stud Layer = 0.5,Vapor Retarder = 0,Gypsum Wall Board = 0.0416666666666667,
Generic - 8",<By Category> = 0.666666666666667,
Exterior - Brick on CMU,Brick, Common = 0.302083333333333,Air = 0.25,Rigid insulation = 0.25,Damp-proofing = 0,Concrete Masonry Units = 0.635416666666667,Metal Furring = 0.135416666666667,Gypsum Wall Board = 0.0520833333333333,
Generic - 12",<By Category> = 1,
Generic - 5",<By Category> = 0.416666666666667,
Generic - 12" Masonry,Concrete Masonry Units = 0.96875,
Generic - 8" Masonry,Concrete Masonry Units = 0.635416666666667,
Generic - 6" Masonry,Concrete Masonry Units = 0.46875,