#BIMTHOUGHTS talks #RTCNA, @BoostYourBIM, and more

If you weren’t able to be at RTCNA last month, or want to hear about some of the classes you may not have been able to attend, you are in luck because Bill Debevc and the bimthoughts.com team have published Part 1 of their RTC Recap.

At 11:25 in the podcast, Bill talks about attending the Boost Your BIM “You can be a Revit Programmer” course, how SharpDevelop and writing macros makes the API more accessible, and the my role in the early days of Charles River Software and Revit Technology Corporation.

Have #Revit files to upgrade?

Moving to Revit 2016?

Bulk File Upgrader will save you lots of time and tedium. It has a 5-star rating at the Autodesk store and has been used by many happy Revit users. Autodesk has selected is as a Featured App because of its increasing popularity as more people with 100s or 1000s of files are looking to get them all saved in the new version.

https://apps.exchange.autodesk.com/RVT/en/Detail/Index?id=appstore.exchange.autodesk.com%3abulkfileupgrader_windows32and64%3aen

Capture

#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 9 granted! Place instances of multiple families

Jason asked if the API can be used to “select a bunch of families from a folder and automatically place them in an .rvt

NOTE: For this code to run, you will need to add a reference to the SharpDevelop project as follows: Project – Add Reference – select System.Windows.Forms

public void placeMultipleFamilies()
{
    Document doc = this.ActiveUIDocument.Document;
    
    if (!(doc.ActiveView is ViewPlan))
    {
        TaskDialog.Show("Error","Command must be run from a plan view");
        return;
    }
    
    // use standard Windows File Open dialog to select files
    System.Windows.Forms.OpenFileDialog opendialog = new System.Windows.Forms.OpenFileDialog();
    // let user select multiple files
    opendialog.Multiselect = true;
    // set default directory
    opendialog.InitialDirectory = doc.Application.GetLibraryPaths().Values.First();
    opendialog.Filter = "Revit Families|*.rfa";
    System.Windows.Forms.DialogResult result = opendialog.ShowDialog();
    if (result == System.Windows.Forms.DialogResult.Cancel)
        return;
    
    Level level = doc.ActiveView.GenLevel;
    
    using (Transaction t = new Transaction(doc, "Place families"))
    {
        t.Start();
        double x = 0;
        double y = 0;
        foreach (string file in opendialog.FileNames)
        {
            // load the family
            Family f = null;
            doc.LoadFamily(file, out f);
            
            if (f == null)
                continue;
            
            // get a family symbol (aka family type) from the family
            FamilySymbol fs = f.Symbols.Cast<FamilySymbol>().FirstOrDefault();
            
            doc.Create.NewFamilyInstance(new XYZ(x, y, 0), fs, level, StructuralType.NonStructural);
             
            // increment coordinates for placing next instance
            x = x + 10;
            if (x > 50)
            {
                x = 0;
                y = 10;
            }
        }
        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 5 granted! Toposurface from Sokkia SDR file

Ken asked “Can an api help to use Sokkia survey data collection files (.sdr) directly into Revit topo’s without 3rd party conversions?”

There may be some tweaks needed to how the XYZ values are read from the SDR file, but this seems to come close

RESULTS FROM SAMPLE FILE

Capture

POINTS EXTRACTED FROM SDR FILE

(168.400000000, 88.900000000, 270.000000000)
(274.500000000, 91.300000000, 120.000000000)
(3101.000000000, 91.000000000, 93.300000000)
(471.300000000, 89.100000000, 272.000000000)
(591.100000000, 89.100000000, 246.000000000)
(6101.000000000, 89.200000000, 246.000000000)
(777.300000000, 89.400000000, 246.000000000)
(841.800000000, 90.300000000, 16.700000000)
(936.100000000, 89.600000000, 241.000000000)
(121.000000000, 90.100000000, 317.000000000)
(136.400000000, 88.700000000, 308.000000000)
public void topoFromSdrFile()
{
    Document doc = this.ActiveUIDocument.Document;
    List<XYZ> points = new List<XYZ>();
    using (StreamReader sr = new StreamReader(@"C:\Users\harry_000\Desktop\USPS-Eville.sdr"))
    {
        string line = "";
        while ((line = sr.ReadLine()) != null)
        {
            if (line.Contains("-") || line.Contains(":"))
                continue;
            if (line.Length < 40)
                continue;
            line = line.Substring(11);
            double x = 0;
            Double.TryParse(line.Substring(0, 5), out x);
            double y = 0;
            Double.TryParse(line.Substring(11, 4), out y);
            double z = 0;
            Double.TryParse(line.Substring(21,4), out z);
            if (x != 0 && y != 0 && z != 0)
                points.Add(new XYZ(x, y, z));
        }
    }
    
    // write to a file the results of parsing the input file for testing/validation
    string output = Path.Combine(Path.GetTempPath(),"points.txt");
    using (StreamWriter sw = new StreamWriter(output, false))
    {
        foreach (XYZ xyz in points)
        {
            sw.WriteLine(xyz.ToString());
        }
    }
    Process.Start(output);
    
    // create the toposurface
    using (Transaction t = new Transaction(doc, "create topo"))
    {
        t.Start();
        TopographySurface.Create(doc, points);
        t.Commit();
    }
}

#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 not granted (sorry!)

A request was made to “Turn off revision in a revision schedule in project. If I am working on RFI 2 when RFI 1 has not been submitted, i don’t want to confuse the Contractor, so would be nice if I can turn it off”

Because I think there is value is seeing how to figure out if something can or can’t be done, here is how I checked the CanFilter value of a titleblock revision schedule. Because it returned ‘false’ I don’t think this wish is grantable :(

public void filterRevisionSchedule()
{
    Document doc = this.ActiveUIDocument.Document;
    foreach (ViewSchedule vs in new FilteredElementCollector(doc)
             .OfClass(typeof(ViewSchedule))
             .Cast<ViewSchedule>().Where(q => q.IsTitleblockRevisionSchedule))
    {
        TaskDialog.Show("Can filter?", vs.Definition.CanFilter().ToString());
    }
}

#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,

Its Time to Grant Your #RTCNA Wishes!

Like in past years, while I am here at RTC I’ll be posting solutions API wishes that you send in during the conference. Here’s some examples from past years:

Please leave a comment here and I’ll try to make your API dreams come true!

What would you like to learn to do with the Revit API?

Revit Technology Conference 2015 in Washington, DC is only 6 weeks away! In addition to all the wonderful classes, social events, and general greatness of RTC, I will be teaching two Revit API labs:

  1. You Can Be a Revit Programmer! – A hands-on class covering basic concepts in Revit API development.
  2. Advanced Concepts in the Revit API

Whether or not you will be at RTC, I’d love to know what you’d like to have included in either of these courses. I’ll use some of your suggestions in the course, and others for future blog posts.

Also, if you have taken a lab at RTC, AU, or elsewhere, what has made it great (or not so great)?

MoveElement silently projects translation vector onto sketch plane

I was trying to use  ElementTransformUtils.MoveElement to move a model line. The line would move, but not in the direction specified by the vector I was using for MoveElement. WHY?!

It happens because the line can only move in its sketch plane. This makes perfect sense now, but it can be strange when you tell the API to move the line by (10, 10, 0) and it moves somewhere else without any error.

Ready for Revit 2016? New bulk upgrader tool now available

Save a ton of time getting your Revit files re-saved in 2016 with the Bulk File Upgrader tool, now available for Revit 2016 at the Autodesk Exchange

https://apps.exchange.autodesk.com/RVT/en/Detail/Index?id=appstore.exchange.autodesk.com%3abulkfileupgrader_windows32and64%3aen

Make a utility function for version compatibility

Are you getting ready for Revit 2016 and now need to change dozens of hundreds of calls to “get_Parameter” like this?

doc.ProjectInformation.get_Parameter(paramName)

And do you want your program to continue working with Revit 2013/2014?

If so, the way to go might be a utility function like this, which contains the conditional compilation needed to handle both old and new versions of the API.

public static Parameter getParam(Element e, string paramname)
{
#if RELEASE2013 || RELEASE2014
  return e.get_Parameter(paramname);
#else
  return e.GetOrderedParameters().FirstOrDefault(q => q.Definition.Name == paramname);
#endif
}

Then your code to get a parameter becomes:

Utils.getParam(doc.ProjectInformation, paramName)

More on the topic of writing code to support multiple versions of Revit at:

Zooming a new view

If you are creating a new view and then want to use the UIView.Zoom method, you need to make the new view the active view and then refresh the view. If you don’t, the attempt to zoom will be futile.

public void createNewViewAndZoom()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;
    ViewFamilyType vft3d = new FilteredElementCollector(doc).OfClass(typeof(ViewFamilyType)).Cast<ViewFamilyType>().FirstOrDefault(q => q.ViewFamily == ViewFamily.ThreeDimensional);
    View view = null;
    using (Transaction t = new Transaction(doc, "make views"))
    {
        t.Start();
        view = View3D.CreateIsometric(doc, vft3d.Id);
        t.Commit();
    }
    zoom(uidoc, view);            
}

private void zoom(UIDocument uidoc, View view)
{
    // YOU NEED THESE TWO LINES OR THE ZOOM WILL NOT HAPPEN!
    uidoc.ActiveView = view;
    uidoc.RefreshActiveView();
    
    UIView uiview = uidoc.GetOpenUIViews().Cast<UIView>().FirstOrDefault(q => q.ViewId == view.Id);
    uiview.Zoom(5);
}