Editing Elements in Worksets

Overview

Users working in teams can encounter usability issues with Revit API add-ins beyond what a single user might experience. In particular, how an add-in is designed can prevent or create editing conflicts. For example, if an add-in attempts to edit thousands of elements, all of those elements will need to be checked out to the local user and will be unavailable to other users until a synchronize with central is performed. Or some of the elements may be checked out to other users and unavailable to be edited. This is important to keep in mind when making changes to a workshared model from the API.

The basic model editing workflow goes like this:

Action Example Why this is important
The user changes some elements in the model User drags a wall These changes are "user changes". The user must borrow these elements to make the change.
Revit regenerates additional data in the model as needed Joined walls move, floor updates, roof updates, room updates, room tags check if they're still in their rooms These changes are "system changes". Even though they are changed, they are still available to other users.

Most API changes are "user changes" and are treated the same as if the local user made the changes manually. This is the case whether called from an External Command, a macro, or an event. The one exception is that changes made from updaters are treated as system changes. .

Element Ownership

One way to address worksharing issues that may arise from attempting to edit an element in a workshared document is to set up FailureHandlingOptions for the transaction used to edit the element. This allows for catching and suppressing editing errors automatically and rollback the changes as shown below:

Code Region: Suppressing worksharing errors

public static void TryToEditGeometricElement(Element elem, bool useFailureHandlingOpts)
{
    Document doc = elem.Document;
    using (Transaction t = MakeTransaction(doc, "Move element", useFailureHandlingOpts))
    {
        t.Start();
        ElementTransformUtils.MoveElement(doc, elem.Id, new XYZ(1, 1, 0));
        t.Commit();
    }
}

private static Transaction MakeTransaction(Document doc, string name, bool useFailureHandlingOpts)
{
    Transaction t = new Transaction(doc, name);
    if (useFailureHandlingOpts)
    {
        FailureHandlingOptions opts = t.GetFailureHandlingOptions();
        opts.SetClearAfterRollback(true);
        opts.SetFailuresPreprocessor(new WorksharingErrorsPreprocessor());
        t.SetFailureHandlingOptions(opts);
    }

    return t;
}

The WorksharingUtils class can be used to modify element and workset ownership. The CheckoutElements() method obtains ownership for the current user of as many specified elements as possible, while the CheckoutWorksets() method does the same for worksets. These methods are useful for attempting to checkout elements prior to performing edits. The RelinquishOwnership() method relinquishes elements and worksets owned by the current user based on the specified RelinquishOptions.

For best performance, checkout all elements or worksets and relinquish items in one big call, rather than many small calls.

Note: When checking out an element, Revit may check out additional elements that are needed to make the requested element editable. For example, if an element is in a group, Revit will checkout the entire group.

The following example tries to checkout the given element prior to editing and issues a message to the user if there is an issue.

Code Region: Checkout elements

public static bool AttemptToCheckoutInAdvance(Element element)
{
    Document doc = element.Document;
    String categoryName = element.Category.Name;
            
    // Checkout attempt
    ICollection<ElementId> checkedOutIds = WorksharingUtils.CheckoutElements(doc, new ElementId[] { element.Id });

    // Confirm checkout
    bool checkedOutSuccessfully = checkedOutIds.Contains(element.Id);

    if (!checkedOutSuccessfully)
    {
        TaskDialog.Show("Element is not checked out", "Cannot edit the " + categoryName + " element - " +
                        "it was not checked out successfully and may be checked out to another.");
        return false;
    }

    // If element is updated in central or deleted in central, it is not editable
    ModelUpdatesStatus updatesStatus = WorksharingUtils.GetModelUpdatesStatus(doc, element.Id);
    if (updatesStatus == ModelUpdatesStatus.DeletedInCentral || updatesStatus == ModelUpdatesStatus.UpdatedInCentral)
    {
        TaskDialog.Show("Element is not up to date", "Cannot edit the " + categoryName + " element - " +
                        "it is not up to date with central, but it is checked out.");
        return false;
    }

    return true;
}

The next example demonstrates checking out all the view worksets.

Code Region: Checkout worksets

void CheckoutAllViewWorksets(Document doc)
{
    FilteredWorksetCollector collector = new FilteredWorksetCollector(doc);

    // find all view worksets
    collector.OfKind(WorksetKind.ViewWorkset);
    ICollection<WorksetId> viewworksets = collector.ToWorksetIds();
    ICollection<WorksetId> checkoutworksets = WorksharingUtils.CheckoutWorksets(doc, viewworksets);
    TaskDialog.Show("Checked out worksets", "Number of worksets checked out: " + checkoutworksets.Count);
}