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

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!

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.

#RTCNA Wish 6: Save all groups to file

In response to a tweet asking to save all groups, I responded that there is no API for Save Group. That’s true, but all hope is not lost.

This video shows how the API can be used to generate a text file listing all model groups. Then this text file is used as input to a journal file that saves each group to its own file.

#RTCNA Wish 5: Delete (almost) all revisions

Scott sent a tweet about deleting all revisions from a project. The tricky part is that, just like how the UI grays out the Delete button when there is only one revision in the project, we can’t use the API to delete the last revision.
Capture
This code uses the Revit 2015 API because the Revision class is new to 2015.

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

    // get list of all revisions in the document
    ICollection<ElementId> elementIds = new FilteredElementCollector(doc)
        .OfClass(typeof(Revision))
        .ToElementIds();
    
    // Remove the first revision from the list
    // Revit must have one revision in the document, so we can't delete them all
    elementIds.Remove(elementIds.First());

    using (Transaction t = new Transaction(doc,"Delete Revisions"))
    {
        t.Start();
        doc.Delete(elementIds);
        t.Commit();
    }
}

#RTNCA Wish 4: Dimension creation

Last night at the API Open House, we talked about the power and limitations of the Dimensions API.

This example shows how to create dimensions that reference two parallel faces. The video shows running the command in a locked 3D view and selecting the end faces of beams. It is also necessary to set the view’s workplane so that the direction of the dimension lies in this plane.

More automated creation of these dimensions could be achieved by programatically getting the faces from the beams so that the user need only select the beam instead of the 3 faces shown in this sample.

public void CreateDimFromFaces()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;
    Reference r1 = uidoc.Selection.PickObject(ObjectType.Face, "Pick face for dimension reference");
    Reference r2 = uidoc.Selection.PickObject(ObjectType.Face, "Pick face for dimension reference");
    Reference r3 = uidoc.Selection.PickObject(ObjectType.Face, "Pick face for view work plane");
    ReferenceArray ra = new ReferenceArray();
    ra.Append(r1);
    ra.Append(r2);
    Element e = doc.GetElement(r2);
    Location l = e.Location;
    LocationCurve lc = l as LocationCurve;
    Curve curve = lc.Curve;
    Line line = curve as Line;
    
    using (Transaction t = new Transaction(doc,"dim"))
    {
        t.Start();
        doc.ActiveView.SketchPlane = SketchPlane.Create(doc, r3);
        Dimension dim = doc.Create.NewDimension(doc.ActiveView, line, ra);
        t.Commit();
    }
}

#RTCNA Wish 3: Copy rooms from one phase to another

Matt and some others asked how to “Copy All Rooms/Spaces to all phases in project”. This example shows how we can specify a “source phase” and create new rooms for each of those rooms in a “target phase”.

public void copyRoomPhase()
{
    string sourcePhaseName = "New Construction";
    string targetPhaseName = "Existing";
    
    Document doc = this.ActiveUIDocument.Document;
    
    Phase targetPhase = new FilteredElementCollector(doc)
        .OfClass(typeof(Phase))
        .Cast<Phase>()
        .FirstOrDefault(q => q.Name == targetPhaseName);

    Phase sourcePhase = new FilteredElementCollector(doc)
        .OfClass(typeof(Phase))
        .Cast<Phase>()
        .FirstOrDefault(q => q.Name == sourcePhaseName);
    
    IList<Element> roomList = new FilteredElementCollector(doc)
        .OfClass(typeof(SpatialElement))
        .OfCategory(BuiltInCategory.OST_Rooms).ToList();
    
    PlanTopologySet planTopologies = null;
    using (Transaction t = new Transaction(doc,"get topology"))
    {
        t.Start();
        planTopologies = doc.PlanTopologies;
        t.Commit();
    }

    foreach (Element sourceRoom in roomList)
    {
        XYZ pointInSourceRoom = ((LocationPoint)(sourceRoom.Location)).Point;
        
        Parameter phaseParam = sourceRoom.get_Parameter("Phase");
        if (doc.GetElement(phaseParam.AsElementId()).Name != sourcePhaseName)
            continue;
        
        Room newRoom = null;
        using (Transaction t = new Transaction(doc,"New Room"))
        {
            t.Start();
            newRoom = doc.Create.NewRoom(targetPhase);
            t.Commit();
        }
        
        bool found = false;
        
        foreach (PlanTopology pt in planTopologies)
        {
            if (found)
                break;
            
            foreach (PlanCircuit pc in pt.Circuits.Cast<PlanCircuit>())
            {
                using (Transaction t = new Transaction(doc,"Place Room"))
                {
                    t.Start();
                    Room placedNewRoom = doc.Create.NewRoom(newRoom,pc);
                    doc.Regenerate();
                    
                    if (placedNewRoom.IsPointInRoom(pointInSourceRoom))
                    {
                        t.Commit();
                        found = true;
                        break;
                    }
                    else
                    {
                        t.RollBack();
                    }
                }
            }
        }
    }
}

#RTCNA Wish 2 granted: Find pinned elements

Steve wishes for an easy way to use the API to find pinned elements. Wish granted!

public void findPinned()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = this.ActiveUIDocument.Document;
    
    // create new SelElementSet to store elements that will be selected
    SelElementSet selSet = SelElementSet.Create();
    
    // find all elements in the active view that are pinned
    foreach (Element e in new FilteredElementCollector(doc, doc.ActiveView.Id).WhereElementIsNotElementType().Where(q => q.Pinned))
    {
        // add each of these elements to the set
        selSet.Add(e);
    }
    
    // select all elements in the set
    uidoc.Selection.Elements = selSet;
    
    // refresh the view so that the newly selected elements are highlighted
    uidoc.RefreshActiveView();
    
    TaskDialog.Show("Elements Pinned","Elements pinned in view '" + doc.ActiveView.Name + "' = " + selSet.Size.ToString());
}

RTCNA Wish #1: Replace detail groups with detail components

Michael McCune sent a wish via Twitter to “replace detail group instances with a detail component”.

That’s a good wish, and one that can almost be completely granted via the API as shown in this video.

The limitation is that the API provides no way to identify the rotation angle of a group instance. For any element in a subclass of the Instance class (such as a family instance, import instance, or Revit link instance) the API provides GetTransform() which contains the data needed to determine the instance’s rotation angle. The AssemblyInstance class also implements GetTransform().

Unfortunately, the Group class has no such method. As a result, while we could rotate the newly created detail components, there is no way to query the detail group instance to determine how much each detail component should be rotated.

public void detailGroupToComponent()
{
    Document doc = this.ActiveUIDocument.Document;
    
    // create a dictionary to store the mapping between detail groups and detail component
    // the NewLine character is used to delimit the family name and family type name
    Dictionary<string, string> groupComponentDict = new Dictionary<string, string>();
    groupComponentDict.Add("DG1","DetailComponent1" + Environment.NewLine + "Type 1");
    groupComponentDict.Add("filled regions","DetailComponent2" + Environment.NewLine + "Type 2");
    
    using (Transaction t = new Transaction(doc,"Detail Groups -> Detail Components"))
    {
        t.Start();    
        
        // find all group instances in the document with the Detail Groups category
        IEnumerable<ElementId> groups =  new FilteredElementCollector(doc)
            .OfClass(typeof(Group))
            .OfCategory(BuiltInCategory.OST_IOSDetailGroups).ToElementIds();
        
        foreach (ElementId id in groups)
        {
            Group g = doc.GetElement(id) as Group;
            
            // get the XYZ location of the group instance
            Location location = g.Location;
            LocationPoint lp = location as LocationPoint;
            XYZ xyz = lp.Point;
            
            // get the view that owns the group instance
            ElementId ownerviewId = g.OwnerViewId;
            View ownerview = doc.GetElement(ownerviewId) as View;
            
            // lookup the name of the family that will replace this group instance
            string familyNameType = groupComponentDict[g.Name];
            
            // split the family name / family type string
            string[] split = familyNameType.Split(new string[] {Environment.NewLine}, StringSplitOptions.None);
            string famName = split[0];
            string typeName = split[1];
            
            // find the FamilySymbol (which is the API name for the Family Type)
            FamilySymbol fs = new FilteredElementCollector(doc)
                .OfClass(typeof(FamilySymbol))
                .Cast<FamilySymbol>()
                .FirstOrDefault(q => q.Family.Name == famName && q.Name == typeName);

            // delete the group instance
            doc.Delete(g.Id);
            
            // create an instance of the detail component
            doc.Create.NewFamilyInstance(xyz, fs, ownerview);
        }
        t.Commit();
    }
}

Get your #RTCNA Revit API Wishes Ready!

Like last year, during the upcoming few days of Revit Technology Conference North America, please tweet your Revit API questions and wishes to me at twitter.com/BoostYourBIM. I will do my best to reply to as many as possible with code, videos, and blog posts showing how we can use the API to make Revit better.

Revit disappears after startup with a Windows 8.1 Clean Boot

Has anyone ever seen something like this?

I was having some problems with Windows (like Windows Explorer freezing when I try to create a new folder). So I did a Clean Boot and used shexview.exe to disable the non-Microsoft entries.

These changes seem to have fixed the problems I was having with Chrome and Windows Explorer, but now I can’t run Revit. Revit starts, the startup page briefly appears, then the Revit session dissapears. Does anyone know what might be happening? I’d really appreciate your help!

The last lines of the journal file are:

Jrn.Command “Internal” , “Show or hide recent files , ID_STARTUP_PAGE”
‘ 0:< ::1:: Delta VM: Avail -26 -> 134216371 MB, Used 217 MB; RAM: Avail -3 -> 5496 MB, Used +2 -> 310 MB
‘C 09-Jun-2014 22:19:25.709; 0:< <-processShellCommand
‘ 0:< ->DesktopMFCApp::doStartupWarnings
‘C 09-Jun-2014 22:19:25.710; 0:< manage licensing
‘ 0:< ::1:: Delta VM: Avail -85 -> 134216287 MB, Used +8 -> 226 MB, Peak +0 -> 226 MB; RAM: Avail -47 -> 5450 MB, Used +34 -> 345 MB, Peak +29 -> 345 MB
‘C 09-Jun-2014 22:19:28.617; 0:< License checkout failed: 20
‘C 09-Jun-2014 22:19:28.618; 0:< License cleanup skipped

Process Monitor shows:

Capture

UPDATE:  This checkbox for FLEXnet needed to be turned back on. Problem solved thanks to the suggestion from Luke Johnson.

Capture

Maintaining order & preventing #Revit chaos

In a few weeks I will be speaking at Revit Technology Conference in Chicago about how the Revit API can help you maintain order and prevent chaos in your Revit adventures.

Techniques such as “using Worksets as a security guard” can work, but when you have 100s or 1000s of person-months of effort invested in your 400MB RVT file, you might want a much more complete set of tools to monitor, audit, and protect your model.

It would be great to have your input to shape this course (and related posts on this blog). Please leave your comments on this post or contact me at http://boostyourbim.wordpress.com/contact/

Keep those macro names unique

If you try to create a macro named “Line” the result will be an error like:
‘ThisApplication.Line()’ is a ‘method’, which is not valid in the given context (CS0119)”

The red squiggly line indicating the error is misleading. The problem here is not really on line 38. The problem is that the name of your macro defines a method called Line which does not have a CreateBound method. Then the compiler gets confused when you try to call the CreateBound method on the Autodesk.Revit.DB.Line class.

So keep those macro names unique to avoid this problem. Renaming the macro to Line1, LineTest, or anything like that will solve the problem.

Capture

File Version Check & Reporting Tool enhanced for 2015

The File Version Check tool has been updated and enhanced for Revit 2015. It provides a cancellable dialog before upgrading files from a previous version and produces reports about the

Download the 2015 installer at http://gdurl.com/eWec/download
This trial version works with all files up to 50MB. Contact Boost Your BIM to upgrade.

Finding those constraining dimensions

Steve asks a lot of good questions and it is nice that the API is often able to help answer them. Recently he asked “Where are the Constraining Dimensions?

Here’s how to use the Dimension.IsLocked property to find those constraining dimensions:

Capture

For the entries where a segment of a multi-segment dimension is locked (such as #s 30, 31, 32), every reference of every segment is listed instead of just listing the two references related to the locked segment. This happens because while the Dimension class does has a References property, the DimensionSegement class has no such property. So the output is all references of the “parent” dimension instead of the two references of the relevant segment.

Capture

public void getConstraints()
{
    Document doc = this.ActiveUIDocument.Document;
    string data = "";
    int ctr = 1;
    foreach (Dimension d in new FilteredElementCollector(doc)
             .OfClass(typeof(Dimension))
             .Cast<Dimension>())
    {                
        if (d.NumberOfSegments == 0 && d.IsLocked)
        {
            data += ctr + ": id = " + d.Id.IntegerValue + " " + getReferenceNames(doc, d.References) + Environment.NewLine;
            ctr++;
        }
        else if (d.NumberOfSegments > 1)
        {
            foreach (DimensionSegment ds in d.Segments)
            {
                if (ds.IsLocked)
                {
                    data += ctr + ": id = " + d.Id.IntegerValue + " " + getReferenceNames(doc, d.References) + " - segment" + Environment.NewLine;
                    ctr++;
                }
            }
        }
    }
    if (data == "")
        data = "No locked dimensions";
    
    TaskDialog td = new TaskDialog("Locked Dimensions");
    td.MainContent = data;
    td.Show();
}

private string getReferenceNames(Document doc, ReferenceArray ra)
{
    string ret = "";
    foreach (Reference r in ra)
    {
        Element e = doc.GetElement(r);
        if (e.Category != null)
            ret += "(" + e.Category.Name + ") ";
        ret += e.Name + " & ";
    }
    
    // trim trailing " & "
    ret = ret.Remove(ret.Length - 3);
    
    return ret;
}