Selection in Link, Cancel in Export, Multithreading

Cool topics to wrap up this hot and exciting week:

Are You Using the Derived Analytical Model?

If so, please provide feedback on your experiences to Charlene Portante, Building Engineering User Experience Research Coordinator at Autodesk.

She is seeking feedback from developers regarding the Derived Analytical Model Revit API:

Are you a developer who interacts with the Revit API regarding the Derived Analytical Model?

If so, please take 5 minutes to provide your input. We would like to hear from you.

Our goal is to get a better understanding what functions developers are using and how they are using them.

Please feel free to forward this on to a developer you work with.

Please click here to provide input:

https://autodeskfeedback.az1.qualtrics.com/jfe/form/SV_9Xe2uafQg3X9xXf

Thank you!

Pick Room in Current Project or Linked Model

Richard RPThomas108 Thomas and I cooperated nicely and enjoyably to develop a novel solution for PickObject to select a room in current model or linked models:

Question: I’m working on a Revit add-in and would like to prompt the user to select a room in the model. The room can be from either the current project or the linked models. With the help from previous posts in the forum I’m able to make some progress so far, but still none of the approaches is ideal. Here are what I’ve tried and the downsides:

So, any ideas? I’m thinking if there’s a way to combine 1. and 2. in one pick, or maybe there can be an ISelectionFilter to filter out anything that is not a room for 3.? Appreciate your help!

Answer: To cut a long story short, we ended up implementing the latter suggestion, asking PickObject to pick a point and limiting the valid selection to room elements, either directly in the current model or in two steps in one of the linked models.

I cleaned up the solution originally implemented by Richard and added it to The Building Coder samples. Here is the diff to the previous version.

The selection filter looks like this:

  public class ElementInLinkSelectionFilter<T>
    : ISelectionFilter where T : Element
  {
    private Document _doc;

    public ElementInLinkSelectionFilter( Document doc )
    {
      _doc = doc;
    }

    public Document LinkedDocument { getprivate set; } = null;

    public bool LastCheckedWasFromLink
    {
      get { return null != LinkedDocument; }
    }

    public bool AllowElement( Element e )
    {
      return true;
    }

    public bool AllowReference( Reference r, XYZ p )
    {
      LinkedDocument = null;

      Element e = _doc.GetElement( r );

      if( e is RevitLinkInstance )
      {
        RevitLinkInstance li = e as RevitLinkInstance;

        LinkedDocument = li.GetLinkDocument();

        e = LinkedDocument.GetElement( r.LinkedElementId );
      }
      return e is T;
    }
  }

Here is the resulting external command for testing it:

  public Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements )
  {
    UIApplication uiapp = commandData.Application;
    UIDocument uidoc = uiapp.ActiveUIDocument;
    Document doc = uidoc.Document;
    Reference r;

    ElementInLinkSelectionFilter<Room> filter
      = new ElementInLinkSelectionFilter<Room>(
        doc );

    try
    {
      r = uidoc.Selection.PickObject(
        ObjectType.PointOnElement,
        filter,
        "Please pick a room in current project or linked model" );
    }
    catch( Autodesk.Revit.Exceptions.OperationCanceledException )
    {
      return Result.Cancelled;
    }

    Element e;

    if( filter.LastCheckedWasFromLink )
    {
      e = filter.LinkedDocument.GetElement(
        r.LinkedElementId );
    }
    else
    {
      e = doc.GetElement( r );
    }

    TaskDialog.Show( "Picked", e.Name );

    return Result.Succeeded;
  }

Richard confirms that it still works for him as well.

Determine Whether Custom Export was Cancelled

A quickie from the StackOverflow question on how to get info that Revit custom export of a view is cancelled:

Question: I used Revit custom export of a model for exporting a 3D view based on IExportContext. It works fine. But I found that the export process can be cancelled:

Custom export cancel

If the custom export is cancelled, a dialog box is shown:

Custom export cancel printing

I have 2 questions:

  1. How to get info that exporting was cancelled?
  2. Why is the name of the operation Printing?

Answers:

  1. Implement and handle the IExportContext IsCanceled method.
  2. Because the custom export is in fact a printing or exporting context, cf. the CustomExporter documentation: The Export method of this class triggers standard rendering or exporting process in Revit, but instead of displaying the result on screen or printer, the output is channelled through the given custom context that handles processing of the geometric as well as non-geometric information.

Multi-Threading with the Single-Threaded Revit API

Question: I have a quick question here: does Revit addin run as a child process of Revit or the addins run in the same process of Revit? Also, where in the code do we start the execution of an addin?

Answer: I believe that it is the same process. In fact, you have to be careful to keep execution of API from the addin code on the main thread of Revit. As to where execution begins, an addin is packaged as an application class and there are startup/shutdown methods. To quote: "You have to be careful to keep execution of API from the addin code on the main thread of Revit."

Question: Why do we need to be careful to keep the execution in the main thread?

Answer: You cannot call Revit API from multiple threads, because most of Revit code is a critical section. Weird corruptions and crashes; it's totally against the way Revit runs. It can be quite easy to accidentally let some code call Revit API from a non-main thread. I think it can happen when you have timers and UI containers that update asynchronously and need to update something in Revit. Here is an explantion of why the Revit API is never ever thread safe.

Response: Okay, I understand the thread-safety and critical section ideas. I think 'process' and 'thread' are different concepts. Processes are typically independent of each other, while threads exist as the subset of a process. Does it mean: Revit addin runs in the main thread of Revit, so addin and Revit are in the same process, they share the memory? This is the question we want to know.

Answer: Yes. Although, no, that is not entirely true. Revit addins can be multi-threaded. The Revit API can only be correctly accessed from the main thread. That is one reason we have external events that give addins a chance to safely call Revit API methods when another thread says they need to.

Question: Is it entirely true that Revit and the addin share the same memory?

Answer: The way I understand it is that an addin can spawn threads for computation or UI purposes, but calls to Revit API should be made from the same thread, the one that calls those Startup/OnDocumentOpen methods, etc. Edit: external events appear to be a special case. The Revit process owns the main thread which executes both code inside of Revit and the .NET code. You are able to see a call stack that starts in the addin and goes all the way down to Revit. Past the managed/unmanaged transition. In fact, there can be several such transitions.

Question: Is it entirely true that Revit and the addin share the same memory? Could you rephrase?

Answer: What is being attempted or prevented? Again, note that there is no multithreading in Revit.

Question: We are trying to understand the exact relationship between Revit and its addins, so we ask experts if an addin can snoop on Revit's memory and find secrets like service credentials.

Answer: I think that can be phrased as a general question for security experts: if you call code in a managed assembly, can it see memory inside of the caller? I think it is possible, but .NET is rather sophisticated about tracking trust level of the assemblies.

Response: Yes – I like that rephrase.

Response: Thank you! You got exactly what we want to know!

Answer: I can't add much, particularly not much about how managed code is restricted. At a low level, any process like the one that is running Revit.exe has a single address space. All threads and all DLLs that exist in the process "see" the same data. Yes, a given thread of execution can transition between native and managed, which, at higher level, each present a different world to the executing code. We know that the way physical memory is mapped into address space cannot be changing very much on these transitions, or when switching among threads, because remapping so often would be too slow. Security? Native code, in particular, could be trampling on anything at any time. It's the Wild Wild West. Exploits like Spectre feed on that freedom. Addins are simply DLLs, but well-behaved addins are limited by the managed architecture and, well, correctness. The so-called main or UI thread of Revit is usually in charge. That's the one your addin code is running in by default. Managed code running within an Addin is permitted to create managed side threads. There are a lot of things the code can make happen in these threads, including ending up in native code, but for correctness, such should not be calling arbitrary Revit APIs.

Beginner’s Guide To Abstraction

We end this week with a beautiful Beginner’s Guide To Abstraction presented by Jesse Duffield in a Pursuit of Laziness.

Nothing new for an experienced programmer, but beautifully put for less experienced coders.

The nice name of Jesse's blog may or may not be inspired by Larry Wall's three virtues.

Have a nice weekend!