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

Change the titleblock on a sheet

The titleblock is a FamilyInstance (just like a desk or door), so changing from one type to another is just a matter of finding the FamilyInstance (in this sample by looking in the active view for a FamilyInstance of the Titleblock category) and then changing its FamilySymbol.

public void changeTitleBlock()
{
    Document doc = this.ActiveUIDocument.Document;
    
    // find the titleblock in the active view
    FamilyInstance fi = new FilteredElementCollector(doc, doc.ActiveView.Id)
        .OfClass(typeof(FamilyInstance))
        .OfCategory(BuiltInCategory.OST_TitleBlocks)
        .FirstOrDefault() as FamilyInstance;
    
    // find the titleblock family symbol with the desired name
    FamilySymbol fs = new FilteredElementCollector(doc)
        .OfClass(typeof(FamilySymbol))
        .OfCategory(BuiltInCategory.OST_TitleBlocks)
        .FirstOrDefault(q => q.Name == "D 22 x 34 Horizontal") as FamilySymbol;           
    using (Transaction t = new Transaction(doc,"Change titleblock"))
    {
        t.Start();
        fi.Symbol = fs;
        t.Commit();
    }
}

A bug in the new DimensionSegment.TextPosition property

The Revit Sundial API enhancements, at least some of which are included in the current R2015 update, includes the new DimensionSegment.TextPosition property. Unfortunately, it incorrectly outputs the same position for all segments of a dimension. Autodesk is aware of the problem and hopefully it will be fixed soon.

unnamed

Document doc = this.ActiveUIDocument.Document;
UIDocument uidoc = this.ActiveUIDocument;
Dimension dim = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element)) as Dimension;
string d = "";
foreach (DimensionSegment dimseg in dim.Segments)
{
   d += dimseg.Value + Environment.NewLine + dimseg.TextPosition + Environment.NewLine + Environment.NewLine;
}
TaskDialog.Show("d",d);

Revit Exceptions are System Exceptions but the .NET run-time prefers the System namespace over Revit namespace

What’s wrong with this code? Why doesn’t it catch the exception thrown by SelectNewPrintDriver if the PDF995 is not installed?

try
{
pm.SelectNewPrintDriver(“PDF995″);
}
catch (InvalidOperationException)
{
TaskDialog.Show(“Error”, “Cannot find printer PDF995″);
return;
}

Because SelectNewPrintDriver throws Autodesk.Revit.Exceptions.InvalidOperationException, not System.InvalidOperationException. So I’d suggest that you include:

using Autodesk.Revit.Exceptions;

in your files so that the compiler will give this error and remind you to resolve the ambiguity about which exception you are trying to catch.
‘InvalidOperationException’ is an ambiguous reference between ‘System.InvalidOperationException’ and ‘Autodesk.Revit.Exceptions.InvalidOperationException’

UrlEncode for Revit Server API calls

The Revit Server SDK contains a Viewer app with the following code:

WebRequest request =
WebRequest.Create(
http://&#8221; +
tbxServerName.Text +
supportedVersions[cbxVersion.SelectedIndex, 1] +
info
);

Better would be to precede that creation of the WebRequest with this line to properly handle any special characters that might be in the path:

info = System.Web.HttpUtility.UrlEncode(info);

Don’t get tricked by Double Precision

There are plenty of good posts like these about the accuracy of numbers in Revit.

Without repeating the content of those posts, I will provide this reminder that even if you think a number will be 1, 0, -1, etc. it may be just slightly larger or smaller. And when you are debugging you might look at the value below for pf.Normal and think the Z value is exactly 0 and the surface is pointing exactly to the side.

Capture

 

But that would be a mistake, because the actual value of the Z value is not 0, as shown above.

#RTCEUR Wish 3: Scheduling adaptive point location values

Brian asked how to schedule the location value for placement points in adaptive components.

In a post at https://boostyourbim.wordpress.com/2013/02/14/set-adaptive-component-parameter-values-with-placement-point-coordinates/ I showed how user-defined parameters can be automatically updated with XYZ values of adaptive component points.

If instead what you’d like to do is schedule point parameters such as Measurement Type, Chord Length, and Measure From, the workflow could be something like this:

Capture

 

  1. Create user parameters for each point for each of these parameters
    1. Measurement Type 1
    2. Point Location 1
    3. Measurement Type 2
    4. Point Location 2
    5. etc
  2. Use the API to set these values for every family instance
  3. Use dynamic model update to:
    1. automatically update these user parameters any time the reference point parameters change
    2. automatically update the reference point parameters any time the user parameters change

Capture

This code does step 2 listed above. The approach for Step 3 is covered in the post linked above.
You will note that the Measurement Type is stored internally as an integer and we need to convert from that integer to the corresponding string (Chord Length, Segment Length…) so that we can get the length value from the parameter of the same name.

public void adapt()
{
    Document doc = this.ActiveUIDocument.Document;
    using (Transaction t = new Transaction(doc,"Set Adapt Comp Data"))
    {
        t.Start();
        foreach (FamilyInstance fi in new FilteredElementCollector(doc).OfClass(typeof(FamilyInstance)).Cast<FamilyInstance>())
        {
            int ctr = 1;
            foreach (ElementId pointId in AdaptiveComponentInstanceUtils.GetInstancePlacementPointElementRefIds(fi))
            {
                ReferencePoint referencePoint = doc.GetElement(pointId) as ReferencePoint;
                Parameter measurementTypeParam = referencePoint.get_Parameter("Measurement Type");
                if (measurementTypeParam == null)
                    continue;
                
                int i = measurementTypeParam.AsInteger();
                string s = "";
                double paramValue = 0;
                if (i == 1)
                    s = "Non-Normalized Curve Parameter";
                else if (i == 2)
                    s = "Normalized Curve Parameter";
                else if (i == 3)
                    s = "Segment Length";
                else if (i == 4)
                    s = "Normalized Segment Length";
                else if (i == 5)
                    s = "Chord Length";
                
                paramValue = referencePoint.get_Parameter(s).AsDouble();
                
                fi.get_Parameter("Measurement Type " + ctr).Set(s);
                fi.get_Parameter("Point " + ctr).Set(paramValue);
                
                ctr++;
            }
        }
        t.Commit();
    }
}

#RTCEUR Wish 2: Override element display by View Filters

To conclude this set of 3 posts showing how to override display of elements, this shows how to create a view filter for elements with a Comments instance parameter equal to “override”. The list of element ids is used to set this instance parameter. A view filter is created and set to use this rule, the view filter is applied to the view, and the graphics of this filter is overridden.


public void viewFilter()
{
    Document doc = this.ActiveUIDocument.Document;

    OverrideGraphicSettings ogs = new OverrideGraphicSettings();
    ogs.SetCutLineColor(new Color(255,255,0));
    ogs.SetCutLineWeight(6);
    ogs.SetProjectionLineColor(new Color(0,255,255));    
    ogs.SetProjectionLineWeight(6);
    
    IList<ElementId> catIds = new List<ElementId>();
    catIds.Add(doc.Settings.Categories.get_Item("Furniture").Id);
    catIds.Add(doc.Settings.Categories.get_Item("Casework").Id);
    
    IList<FilterRule> rules = new List<FilterRule>();
    ElementId commentParamId = new ElementId(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS);
    rules.Add(ParameterFilterRuleFactory.CreateEqualsRule(commentParamId, "override", true));
    using (Transaction t = new Transaction(doc,"View Filter"))
    {
        t.Start();
                        
        foreach (ElementId id in getIds(doc))
        {
            doc.GetElement(id).get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS).Set("override");
        }
        
        ParameterFilterElement filter = ParameterFilterElement.Create(doc, "New Filter", catIds, rules);
        doc.ActiveView.AddFilter(filter.Id);
        doc.ActiveView.SetFilterOverrides(filter.Id, ogs);
        t.Commit();
    }
}

#RTCEUR Wish 2: Override graphics by Element

Increasing the complexity a bit from the previous post, this sample shows how to Override Graphics by Element. In this case, the cut & project line weight and color are overridden in the active view for the specified elements.

public void overrideElements()
{
    Document doc = this.ActiveUIDocument.Document;
    OverrideGraphicSettings ogs = new OverrideGraphicSettings();
    ogs.SetCutLineColor(new Color(0,255,0));
    ogs.SetCutLineWeight(6);
    ogs.SetProjectionLineColor(new Color(0,0,255));
    ogs.SetProjectionLineWeight(6);
    using (Transaction t = new Transaction(doc,"Overrides"))
    {
        t.Start();
        foreach (ElementId id in getIds(doc))
        {
            doc.ActiveView.SetElementOverrides(id, ogs);
        }
        t.Commit();
    }
}

#RTCEUR API Wish 2: Override display by element ids (isolate)

@DigDesRev asked “so clash detection gives a report w/ element IDs, can we review the report and on 1 view, override the display settings?”

The simplest way to visualize these elements is with Revit’s ‘Temporary Hide/Isolate’ tool

public void isolateElements()
{
    Document doc = this.ActiveUIDocument.Document;    
    using (Transaction t = new Transaction(doc,"Isolate"))
    {
        t.Start();
        doc.ActiveView.IsolateElementsTemporary(getIds(doc));
        t.Commit();
    }
}

getIds() can be whatever function you want to read a clash detection report from disk, find elements in the Revit model, etc.

#RTCEUR Wish #1: #Revit Change Tracking

Dieter asked “What about a tool that can help you tracking changes in your #Revit model. E.g. select elements that were affected by an indicated change. Eg after updating model from RSA. Maybe a dialog showing the history”

If you want to log every change made to your Revit model and what elements are affected by that change, the DocumentChanged event is what you need.

The code below outputs the time, user name, transaction name, and all added, modified, and deleted elements for each transaction.

For simple annotation operations, the output is relatively straightforward:

10/30/2014 3:58:43 PM,harry_000,Edit Text,ADDED,1074184(Text Notes: 2.5mm Arial),,DELETED,,MODIFIED,
10/30/2014 3:58:54 PM,harry_000,Add Left Arc Leader,ADDED,,DELETED,,MODIFIED,1074184(Text Notes: 2.5mm Arial),
10/30/2014 3:59:02 PM,harry_000,Detail Lines,ADDED,1074193(Lines: Detail Lines),,DELETED,,MODIFIED,
10/30/2014 3:59:11 PM,harry_000,Place Dimensions,ADDED,1074208(Dimensions: Feet & Inches),,DELETED,,MODIFIED,

For other transactions (such as creating a column or moving a grid), the output shows that Revit is making a whole lot of changes.

10/30/2014 4:01:05 PM,harry_000,Structural Column,ADDED,1074223(Structural Columns: L127X127X9.5),1074224(Analytical Columns),1074245(Analytical Nodes),1074246(Analytical Nodes),1074247(Analytical Nodes),1074248(Analytical Nodes),,DELETED,,MODIFIED,102467(AutoJoin Tracker Element),102521(Element to track Analytical Sets during regeneration),103632(GCS Tracker),132466(Hubs Tracker),

10/30/2014 4:01:54 PM,harry_000,Nudge Right,ADDED,,DELETED,,MODIFIED,9745(System Data),86962(System Data),102467(AutoJoin Tracker Element),103632(GCS Tracker),132466(Hubs Tracker),195239(Grids: 2),414482(Structural Columns: M_1000),414483(Analytical Columns),414562(Analytical Nodes),414564(Analytical Nodes),428588(Walls: SIP 202mm Wall – conc clad),428745(Walls: SIP 202mm Wall – conc clad),457479(Windows: Standard),485432(Windows: Standard),554066(Constraints: Alignment),554069(Constraints: Alignment),670283(Dimensions: Feet & Inches),760918(M_1000),857235(<Area Boundary>: Model Lines),857236(<Area Boundary>: Model Lines),857239(),940871(Window Tags: M_Window Tag),940919(Window Tags: M_Window Tag),941385(Dimensions: Feet & Inches),

For a real application you might want to write this data to a database instead of a text file, and build a viewer to get more info about the added and modified elements based on their element ids.

void ControlledApplication_DocumentChanged(object sender, Autodesk.Revit.DB.Events.DocumentChangedEventArgs e)
{
    string file = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "CommandLogging.csv");
    using (StreamWriter sw = new StreamWriter(file, true))
    {
        foreach (string transactionName in e.GetTransactionNames())
        {
            sw.Write(DateTime.Now + "," + Utils.username + "," + transactionName);
            sw.Write(",ADDED," + idCollectionToString(e.GetDocument(), e.GetAddedElementIds()));
            sw.Write(",DELETED," + idCollectionToString(e.GetDocument(), e.GetDeletedElementIds()));
            sw.Write(",MODIFIED," + idCollectionToString(e.GetDocument(), e.GetModifiedElementIds()));
            sw.Write(Environment.NewLine);
        }
    }
}

private string idCollectionToString(Document doc, ICollection<ElementId> coll)
{
    string ret = "";
    foreach (ElementId id in coll)
    {
        Element e = doc.GetElement(id);
        string name = "";
        if (e != null)
        {
            string catName = "";
            if (e.Category != null)
                catName = e.Category.Name;
            if (e.Name != "" && catName != "")
                catName += ": ";
            name = "(" + catName + e.Name + ")";
        }
        ret += id.IntegerValue.ToString() + name + ",";
    }
    return ret;
}

It’s Almost #RTCEUR Wishlist Time!

RTC Europe starts tomorrow, and I’m looking forward to my class “Maintaining Order & Preventing Chaos with the Revit API” on Friday morning.

But more importantly, another RTC means another chance for you to send your RTC Wish List ideas and get some free API code written while I’m in Dublin!

So tweet your best ideas for little Revit API apps to https://twitter.com/BoostYourBIM and I look forward to seeing what you dream up. Suggestions from folks who are actually at RTC of course get first priority.

Getting linked file data w/out opening the RVT

Here’s how to get information about linked files (DWG, RVT, etc) without having to first open the file

public void GetRVTLinks()
{
    ModelPath mp = new FilePath(@"C:\Users\harry_000\Documents\testFile.rvt");
    TransmissionData td = TransmissionData.ReadTransmissionData(mp);
    string s = "ExternalFileReferenceType\tPathType\tFile Path" + Environment.NewLine;
    foreach (ElementId id in td.GetAllExternalFileReferenceIds())
    {
        ExternalFileReference extRef = td.GetLastSavedReferenceData(id);
        s += extRef.ExternalFileReferenceType.ToString() + "\t" +
             extRef.PathType.ToString() + "\t" + 
            ModelPathUtils.ConvertModelPathToUserVisiblePath(extRef.GetPath()) + Environment.NewLine;
    }
    
    string outputfile = Path.Combine(Path.GetTempPath(),"links.txt");
    using (StreamWriter sw = new StreamWriter(outputfile,false))
    {
        sw.Write(s);
    }
    
    Process.Start(outputfile);
}

Deleting (almost) all Viewport Types in 2014

A friend who is still running Revit 2014 on their Windows NT box noticed that yesterday’s post works only in 2015 because it uses the newfangled ElementType.FamilyName Property.

So that you can continue living happily in the Stone Age, here is a different way to find the Viewport Types based on the presence of the Viewport Type-specific parameters.

Also, while testing this Revit kept giving me a nonsensical “Deleting all open views in a project is not allowed” error, even though I was never trying to delete all open views in the project. It seems that Revit 2014 & 2015 really really really love Viewport 1 and don’t want it to be deleted. So I added some code to leave it alone.

public void deleteUnusedViewportTypesFor2014()
{
    Document doc = this.ActiveUIDocument.Document;
    
    // get all viewport types
    // this approach to find the Viewport Types can be used in 2014 
    // whereas the ElementType.FamilyName property exists in 2015 only
    IList<ElementType> viewportTypes = new FilteredElementCollector(doc).OfClass(typeof(ElementType)).Cast<ElementType>()
        .Where(q =>
               q.get_Parameter("Title") != null &&
               q.get_Parameter("Show Title") != null &&
               q.get_Parameter("Show Extension Line") != null &&
               q.get_Parameter("Line Weight") != null &&
               q.get_Parameter("Color") != null &&
               q.get_Parameter("Line Pattern") != null
              ).ToList();

    // create list of ElementIds from list of ElementType objects
    IList<ElementId> viewportTypeIds = new List<ElementId>();
    foreach (Element viewportType in viewportTypes)
    {
        viewportTypeIds.Add(viewportType.Id);
    }
    
    // create list of Viewport Type Element Ids sorted by the integer value of the element ids
    List<ElementId> toDelete = viewportTypeIds.OrderBy(q => q.IntegerValue).ToList();
    
    // Remove the first element in this list so that "Viewport 1" will not be deleted.
    // There seems to be a bug in Revit that prevents deleting "Viewport 1".
    // Even if this viewport is not being used, attempting to delete it results in an error "Deleting all open views in a project is not allowed."
    toDelete.RemoveAt(0);
    
    // remove from this list all viewport types that are in use
    foreach (Element viewport in new FilteredElementCollector(doc).OfClass(typeof(Viewport)))
    {
        ElementId typeId = viewport.GetTypeId();
        toDelete.Remove(typeId);
    }
    
    // do the deletion
    using (Transaction t = new Transaction(doc,"Delete Unused Viewport Types"))
    {
        t.Start();
           doc.Delete(toDelete);
        t.Commit();
    }
}

Deleting Unused Viewport Types

If there are unused Viewport Types that you can’t get rid of with Purge Unused, here is an API approach to try. The ElementType class has many subclasses but not one for ViewportType so the FamilyName property is used instead.

public void deleteUnusedViewportTypes()
{
    Document doc = this.ActiveUIDocument.Document;
    
    // get all viewport types
    IList<ElementType> viewportTypes = new FilteredElementCollector(doc).OfClass(typeof(ElementType)).Cast<ElementType>().Where(q => q.FamilyName == "Viewport").ToList();

    // create list of ElementIds from list of ElementType objects
    IList<ElementId> viewportTypeIds = new List<ElementId>();
    foreach (Element viewportType in viewportTypes)
    {
        viewportTypeIds.Add(viewportType.Id);
    }
    
    // remove from this list all viewport types that are in use
    foreach (Element viewport in new FilteredElementCollector(doc).OfClass(typeof(Viewport)))
    {
        ElementId typeId = viewport.GetTypeId();
        viewportTypeIds.Remove(typeId);
    }
    
    // do the deletion
    using (Transaction t = new Transaction(doc,"Delete Unused Viewport Types"))
    {
        t.Start();
        doc.Delete(viewportTypeIds);
        t.Commit();
    }
}

EDIT: Here is a new version with some additional diagnostic information and the opportunity for the user to abort after seeing the list of viewport types to be deleted.

public void deleteUnusedViewportTypes()
{
    Document doc = this.ActiveUIDocument.Document;
    
    // get all viewport types
    IList<ElementType> viewportTypes = new FilteredElementCollector(doc).OfClass(typeof(ElementType)).Cast<ElementType>().Where(q => q.FamilyName == "Viewport").ToList();

    // create list of ElementIds from list of ElementType objects
    IList<ElementId> viewportTypeIds = new List<ElementId>();
    // do not try to delete Viewport 1, as it is a special viewport type that Revit does not want deleted
    foreach (Element viewportType in viewportTypes.Where(q => q.Name != "Viewport 1"))
    {
        viewportTypeIds.Add(viewportType.Id);
    }
    
    // remove from this list all viewport types that are in use
    foreach (Element viewport in new FilteredElementCollector(doc).OfClass(typeof(Viewport)))
    {
        ElementId typeId = viewport.GetTypeId();
        viewportTypeIds.Remove(typeId);
    }
    
    // generate list of names of viewports to be deleted
    string viewports = "";
    foreach (ElementId id in viewportTypeIds)
    {
        viewports += doc.GetElement(id).Name + Environment.NewLine;
    }
    
    // show Task Dialog with opportunity for user to cancel the operation
    TaskDialog td = new TaskDialog("Viewports");
    td.MainInstruction = "The following viewports will be deleted";
    td.MainContent = viewports;
    td.CommonButtons = TaskDialogCommonButtons.Ok | TaskDialogCommonButtons.Cancel;
    if (td.Show() != TaskDialogResult.Ok)
        return;
    
    string idsNotDeleted = "";
    // do the deletion
    using (Transaction t = new Transaction(doc,"Delete Unused Viewport Types"))
    {
        t.Start();
        
        foreach (ElementId id in viewportTypeIds)
        {
            try
            {
                doc.Delete(id);
            }
            catch // deletion failed - get the name of the element that could not be deleted
            {
                Element eNotDeleted = doc.GetElement(id);
                idsNotDeleted += eNotDeleted.Name + " " + id.IntegerValue.ToString();
            }
        }
        
        t.Commit();
    }
    
    if (idsNotDeleted != "")
    {
        TaskDialog tdError = new TaskDialog("Error");
        tdError.MainInstruction = "The following elements were not able to be deleted";
        tdError.MainContent = idsNotDeleted;
        tdError.Show();
    }
}

Thanks for voting me an #RTCNA Top 10 Speaker!

Thank you to everyone who attended my class “Maintaining order & preventing chaos with the Revit API” at RTCNA 2014. I am honored to have been voted a Top Ten speaker for the 2nd consecutive year!

If you haven’t been to RTC yet, I highly recommend attending the Europe, Australia, and/or North America editions. RTC Europe will be in Dublin at the end of October – I’m looking forward to being there and hope you will be there too.

Get room data for elements in RVT links

A Revit guru asked:

“Have you ever shared room data across Revit links successfully? If you have 10 linked projects from different authors / disciplines, and the official room data only exists in one of them (say architectural), have you figured out a way to create a cross discipline room schedule?”

For example, your walls & rooms are in rooms.rvt. Furniture is in furniture.rvt which is linked into rooms.rvt. How do you figure out the room for each piece of furniture?

With the API, of course!