Revit Links

Revit Links

Revit documents can have links to various external files, including other Revit documents. These types of links in the Revit API are represented by the RevitLinkType and RevitLinkInstance classes. The RevitLinkType class represents another Revit Document ("link") brought into the current one ("host"), while the RevitLinkInstance class represents an instance of a RevitLinkType.

Creating New Links

To create a new Revit link, use the static RevitLinkType.Create() method which will create a new Revit link type and load the linked document and the static RevitLinkInstance.Create() method to place an instance of the link in the model. The RevitLinkType.Create() method requires a document (which will be the host), a ModelPath to the file to be linked, and a RevitLinkOptions object. The RevitLinkOptions class represents options for creating and loading a Revit link. Options include whether or not Revit will store a relative or absolute path to the linked file and the workset configuration. The WorksetConfiguration class is used to specify which, if any, worksets will be opened when creating the link. Note that the relative or absolute path determines how Revit will store the path, but the ModelPath passed into the Create() method needs a complete path to find the linked document initially.

The following example demonstrates the use of RevitLinkType.Create(). The variable pathName is the full path to the file on disk to be linked.

Code Region: Create new Revit Link

public ElementId CreateRevitLink(Document doc, string pathName)
{
    FilePath path = new FilePath(pathName);
    RevitLinkOptions options = new RevitLinkOptions(false);
    // Create new revit link storing absolute path to file
    RevitLinkLoadResult result = RevitLinkType.Create(doc, path, options);
    return (result.ElementId);
}

Once the RevitLinkType is created, instances can be added to the document. In the following example, two instances of a RevitLinkType are added, offset by 100'. Until a RevitLinkInstance is created, the Revit link will show up in the Manage Links window, but the elements of the linked file will not be visible in any views.

Code Region: Create new Revit Link Instance

public void CreateLinkInstances(Document doc, ElementId linkTypeId)
{
    // Create revit link instance at origin
    RevitLinkInstance.Create(doc, linkTypeId);
    RevitLinkInstance instance2 = RevitLinkInstance.Create(doc, linkTypeId);
    // Offset second instance by 100 feet
    Location location = instance2.Location;
    location.Move(new XYZ(0, -100, 0));
}
The examples above work with files on the local disk. Below is a more complex example involving a link to a model on Revit server.

Code Region: Create new Revit Link to a model located on Revit server

public static void CreateLinkToServerModel(UIApplication uiApp)
{
    UIDocument uiDoc = uiApp.ActiveUIDocument;
    Document doc = uiDoc.Document;

    // Try to get the server path for the particular model on the server
    Application application = uiApp.Application;
    String hostId = application.GetRevitServerNetworkHosts().First();
    String rootFolder = "|";
    ModelPath serverPath = FindWSAPIModelPathOnServer(application, hostId, rootFolder, "Wall pin model for updaters.rvt");

    using (Transaction t = new Transaction(doc, "Create link"))
    {
        t.Start();
        RevitLinkOptions options = new RevitLinkOptions(false);

        RevitLinkLoadResult result = RevitLinkType.Create(doc, serverPath, options);

        RevitLinkInstance.Create(doc, result.ElementId);
        t.Commit();
    }
}

private static ModelPath FindWSAPIModelPathOnServer(Application app, string hostId, string folderName, string fileName)
{
    // Connect to host to find list of available models (the "/contents" flag)
    XmlDictionaryReader reader = GetResponse(app, hostId, folderName + "/contents");
    bool found = false;

    // Look for the target model name in top level folder
    List<String> folders = new List<String>();
    while (reader.Read())
    {
        // Save a list of subfolders, if found
        if (reader.NodeType == XmlNodeType.Element && reader.Name == "Folders")
        {
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Folders")
                    break;

                if (reader.NodeType == XmlNodeType.Element && reader.Name == "Name")
                {
                    reader.Read();
                    folders.Add(reader.Value);
                }
            }
        }
        // Check for a matching model at this folder level
        if (reader.NodeType == XmlNodeType.Element && reader.Name == "Models")
        {
            found = FindModelInServerResponseJson(reader, fileName);
            if (found)
                break;
        }
    }

    reader.Close();

    // Build the model path to match the found model on the server
    if (found)
    {
        // Server URLs use "|" for folder separation, Revit API uses "/"
        String folderNameFragment = folderName.Replace('|', '/');

        // Add trailing "/" if not present
        if (!folderNameFragment.EndsWith("/"))
            folderNameFragment += "/";

        // Build server path
        ModelPath modelPath = new ServerPath(hostId, folderNameFragment + fileName);
        return modelPath;
    }
    else
    {
        // Try subfolders
        foreach (String folder in folders)
        {
            ModelPath modelPath = FindWSAPIModelPathOnServer(app, hostId, folder, fileName);
            if (modelPath != null)
                return modelPath;
        }
    }

    return null;
}

// This string is different for each RevitServer version
private static string s_revitServerVersion = "/RevitServerAdminRESTService2014/AdminRESTService.svc/";

private static XmlDictionaryReader GetResponse(Application app, string hostId, string info)
{
    // Create request	
    WebRequest request = WebRequest.Create("http://" + hostId + s_revitServerVersion + info);
    request.Method = "GET";

    // Add the information the request needs

    request.Headers.Add("User-Name", app.Username);
    request.Headers.Add("User-Machine-Name", app.Username);
    request.Headers.Add("Operation-GUID", Guid.NewGuid().ToString());

    // Read the response
    XmlDictionaryReaderQuotas quotas =
        new XmlDictionaryReaderQuotas();
    XmlDictionaryReader jsonReader =
        JsonReaderWriterFactory.CreateJsonReader(request.GetResponse().GetResponseStream(), quotas);

    return jsonReader;
}

private static bool FindModelInServerResponseJson(XmlDictionaryReader reader, string fileName)
{
    // Read through entries in this section
    while (reader.Read())
    {
        if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Models")
            break;

        if (reader.NodeType == XmlNodeType.Element && reader.Name == "Name")
        {
            reader.Read();
            String modelName = reader.Value;
            if (modelName.Equals(fileName))
            {
                // Match found, stop looping and return
                return true;
            }
        }
    }

    return false;
}

In the example below, the WorksetConfiguration is obtained, modified so that only one specified workset is opened and set back to the RevitLinkOptions prior to creating the new link.

Code Region: Create link with one workset open

public bool CreateRevitLinkWithOneWorksetOpen(Document doc, string pathName, string worksetName)
{
    FilePath path = new FilePath(pathName);
    RevitLinkOptions options = new RevitLinkOptions(true);

    // Get info on all the user worksets in the project prior to opening
    IList<WorksetPreview> worksets = WorksharingUtils.GetUserWorksetInfo(path);
    IList<WorksetId> worksetIds = new List<WorksetId>();
    // Find worksetName
    foreach (WorksetPreview worksetPrev in worksets)
    {
        if (worksetPrev.Name.CompareTo(worksetName) == 0)
        {
            worksetIds.Add(worksetPrev.Id);
            break;
        }
    }

    // close all worksets but the one specified
    WorksetConfiguration worksetConfig = new WorksetConfiguration(WorksetConfigurationOption.CloseAllWorksets);
    if (worksetIds.Count > 0)
    {
        worksetConfig.Open(worksetIds);
    }
    options.SetWorksetConfiguration(worksetConfig);

    RevitLinkLoadResult result = RevitLinkType.Create(doc, path, options);
    RevitLinkType type = doc.GetElement(result.ElementId) as RevitLinkType;
    return (result.LoadResult == RevitLinkLoadResultType.LinkLoaded);
}

Whether creating or loading a link, a RevitLinkLoadResults is returned. This class has a property to determine if the link was loaded. It also has an ElementId property that is the id of the created or loaded linked model.

Loading and Unloading Links

RevitLinkType has several methods related to loading links. Load(), LoadFrom() and Unload() allow a link to be loaded or unloaded, or to be loaded from a new location. These methods regenerate the document. The document's Undo history will be cleared by these methods. All transaction phases (e.g. transactions, transaction groups and sub-transactions) that were explicitly started must be finished prior to calling one of these methods.

The static method RevitLinkType.IsLoaded() will return whether or not the link is loaded.

Getting Link Information

Each RevitLinkType in a document can have one or more associated RevitLinkInstances. The RevitLinkInstance.GetLinkDocument() method returns a Document associated with the Revit link. This document cannot be modified, meaning that operations that require a transaction or modify the document's status in memory (such as Save and Close) cannot be performed.

The associated RevitLinkType for a RevitLinkInstance can be retrieved from the document using the ElementId obtained from the RevitLinkInstance.GetTypeId() method. The RevitLinkType for a linked file has several methods and properties related to nested links. A document that is linked in to another document may itself have links. The IsNested property returns true if the RevitLinkType is a nested link (i.e. it has some other link as a parent), or false if it is a top-level link. The method GetParentId() will get the id of the immediate parent of this link, while GetRootId() will return the id of the top-level link which this link is ultimately linked under. Both methods will return invalidElementId if this link is a top-level link. The method GetChildIds() will return the element ids of all links which are linked directly into this one.

For example, if C is linked into document B and B in turn is linked into document A, calling GetParentId() for the C link will return the id of document B and calling GetRootId() for the C link will return the id of document A. Calling GetChildIds() for document A will only return the id of B's link since C is not a direct link under A.

RevitLinkType also has a PathType property which indicates if the path to the external file reference is relative to the host file's location (or to the central model's location if the host is workshared), an absolute path to a location on disk or the network, or if the path is to a Revit Server location.

The AttachmentType property of RevitLinkType indicates if the link is an attachment or an overlay. "Attachment" links are considered to be part of their parent link and will be brought along if their parent is linked into another document. "Overlay" links are only visible when their parent is opened directly.

The following example gets all RevitLinkInstances in the document and displays some information on them.

Code Region: Getting Link information

public void GetAllRevitLinkInstances(Document doc)
{
    FilteredElementCollector collector = new FilteredElementCollector(doc);
    collector.OfClass(typeof(RevitLinkInstance));
    StringBuilder linkedDocs = new StringBuilder();
    foreach (Element elem in collector)
    {
        RevitLinkInstance instance = elem as RevitLinkInstance;
        Document linkDoc = instance.GetLinkDocument();
        linkedDocs.AppendLine("FileName: " + Path.GetFileName(linkDoc.PathName));
        RevitLinkType type = doc.GetElement(instance.GetTypeId()) as RevitLinkType;
        linkedDocs.AppendLine("Is Nested: " + type.IsNestedLink.ToString());
    }

    TaskDialog.Show("Revit Links in Document", linkedDocs.ToString());
}

Link Geometry

Shared coordinates

The RevitLinkType methods SavePositions() and HasSaveablePositions() support saving shared coordinates changes back to the linked document. Use HasSaveablePositions() to determine if the link has shared positioning changes which can be saved. Call SavePositions() to save shared coordinates changes back to the linked document. SavePositions() requires an instance of the ISaveSharedCoordinatesCallback interface to resolve situations when Revit encounters modified links. The interface's GetSaveModifiedLinksOption() method determines whether Revit should save the link, not save the link, or discard shared positioning entirely.

While SavePositions() does not clear the document's undo history, it cannot be undone since it saves the link's shared coordinates changes to disk.

Conversion of geometric references

The Reference class has members related to linked files that allow conversion between Reference objects which reference only the contents of the link and Reference objects which reference the host. This allows an application, for example, to look at the geometry in the link, find the needed face, and convert the reference to that face into a reference in the host suitable for use to place a face-based instance. Also, these Reference members make it possible to obtain a reference in the host (e.g. from a dimension or family) and convert it to a reference in the link suitable for use in Element.GetGeometryObjectFromReference().

The Reference. LinkedElementId property represents the id of the top-level element in the linked document that is referred to by this reference, or InvalidElementId for references that do not refer to an element in a linked RVT file. The Reference . CreateLinkReference() method uses a RevitLinkInstance to create a Reference from a Reference in a Revit link. And the Reference. CreateReferenceInLink() method creates a Reference in a Revit Link from a Reference in the host file

Picking elements in links

The Selection methods PickObject() and PickObjects() allow the selection of objects in Revit links. To allow the user to select elements in linked files, use the ObjectType.LinkedElement enumeration value for the first parameter of the PickObject() or PickObjects(). Note that this option allows for selection of elements in links only, not in the host document.

In the example below, an ISelectionFilter is used to allow only walls to be selected in linked files.

Code Region: Selecting Elements in Linked File

public void SelectElementsInLinkedDoc(Autodesk.Revit.DB.Document document)
{
    UIDocument uidoc = new UIDocument(document);
    Selection choices = uidoc.Selection;
    // Pick one wall from Revit link
    WallInLinkSelectionFilter wallFilter = new WallInLinkSelectionFilter();
    Reference elementRef = choices.PickObject(ObjectType.LinkedElement, wallFilter, "Select a wall in a linked document");
    if (elementRef != null)
    {
        TaskDialog.Show("Revit", "Element from link document selected.");
    }
}

// This filter allows selection of only a certain element type in a link instance.
class WallInLinkSelectionFilter : ISelectionFilter
{
    private RevitLinkInstance m_currentInstance = null;

    public bool AllowElement(Element e)
    {
        // Accept any link instance, and save the handle for use in AllowReference()
        m_currentInstance = e as RevitLinkInstance;
        return (m_currentInstance != null);
    }

    public bool AllowReference(Reference refer, XYZ point)
    {
        if (m_currentInstance == null)
            return false;

        // Get the handle to the element in the link
        Document linkedDoc = m_currentInstance.GetLinkDocument();
        Element elem = linkedDoc.GetElement(refer.LinkedElementId);

        // Accept the selection if the element exists and is of the correct type
        return elem != null && elem is Wall;
    }
}