An experiment to extract data from Revit as fast as possible.
We are exploring
- Quickly extracting Revit geometry definitions (without tesselation)
- Fast grouping of similar geometry
- Fast detection of changed geometry
- Performing processing asynchronously via Revit idle events.
For some high-level thoughts on performance optimization in software see https://github.com/ara3d/revit-fast-exporter/blob/main/notes-on-performance.md.
Currently the early coding experiments are being done using the Bowerbird plug-in. Bowerbird is a tool to allow C# code to be edited and re-loaded dynamically in a running instance of Revit. Bowerbird scans a directory for code, and recompiles and reloads it whenever a file is changed, added, or deleted.
The current code experiments can be found at https://github.com/ara3d/bowerbird/blob/main/Ara3D.Bowerbird.RevitSamples/SampleRevitCommands.cs.
Detecting changes in geometric data is a hard problem to do rigorously. Hashing techniques reduce a high-dimensional data set to a lower-dimensionality data set, and invariably different objects can result in the same hash code. When two objects have the same hash-code a detailed comparison has to be done on the geometry.
We do not recommend computing a mesh, as this is an expensive operation, so we propose using the geometry definitions.
Despite what it may appear the GetHashCode
function of various Revit elements (including GeometryObject
) does not actually create a hash based on the value of the geometry, but rather of
the address of a native pointer. This means that GetHashCode
is definitely not going to be consistent between Revit sessions, and maybe not even between the same geometry object.
A geometry abstract syntax tree (GAST) is a novel method of succinctly expressing BIM or CAD geometric data that can be used for fast detection of similar geometry, and separation of different geometry.
An abstract syntax tree (AST) is a data structure used in computer science to represent the structure of a program or code snippet. It is a tree representation of the abstract syntactic structure of text (often source code) written in a formal language. The syntax is "abstract" in the sense that it does not represent every detail appearing in the real syntax, but rather just the structural or content-related details.
We wanted an algorithm to efficiently group a set of 3D geometric representations: where items in the same group have a high degree of possibility of being the same, and where distinct groups are known to be different.
The 3D representation of BIM elements stored within the native structures of Revit, or expressed as IFC files, are usually not stored as meshes, instead the usual representation is a tree of geometric objects and/or operations.
Comping a GAST and storing it as a string is a much faster operation than performing tessellation, which is the commonly used method for comparing geometric similarity.
Some early code for generating a GAST can be found at: https://github.com/ara3d/bowerbird/blob/main/Ara3D.Bowerbird.RevitSamples/GeometryAbstractSyntaxTree.cs.
- How quickly can a GAST be computed even in extremely degenerate cases?
- How reliable is it that GASTs will be different for different objects?
- How similar are distance metrics applied to GASTs are to distance metrics applied to the resulting meshes?
For some clarification on terminology see: https://github.com/ara3d/revit-fast-exporter/blob/main/notes-on-async.md.
Access to the Revit API can only be done through the main thread.
To enable code to appear non-blocking to a user we can use the UIApplication.OnIdling
event and a queue to store work in some quickly consumed tasks.
An open question is whether we can perform a substantial enough amount of work in a short enough period of time, to still have a positive user experience
and to not cause the application to appear to lag or stutter.
Some code examples are being used to experiment with the OnIdling event: https://github.com/ara3d/bowerbird/blob/main/Ara3D.Bowerbird.RevitSamples/SampleRevitCommands.cs
Revit.Async is a confusingly named tool to simplify the programming pattern of communicating with the Revit API outside of the so-called "Revit Context". The Revit context is a loosely defined terms, that specifies if Revit is in the correct state to receive API requests.
For example, if you are on a background thread, or responding to a UI event in a modeless WPF Window you are probably not in a valid Revit context. If you are correctly responding to a Revit ExternalEvent or the Revit UIApplication.Idling event then you are in a valid Revit context.
To determine if you are in a valid Revit context see this link.
Revit.Async allows the user to await
on an async
task which is executed within a valid Revit context.
The Speckle team uses code derived from the Revit.Async package.
- See code here.
- See discussion of PR here
To improve performance of Revit export, a technique we can apply is to filter data to assure we only include specific kinds of data the user cares about, or a filter to exclude specific kinds of data we definitely don't care about.
This can increase the performance of export, as well as the performance of change detection.
No code has been written to do this yet. It is not a hard problem computationally, but it requires significant consideration to be given to the design of the workflow and UI.
The goal was to perform a non-trivial task on a set of objects during Idling
events. In this experiment I extract geometry information (but without tessellating meshes)
from every element in the scene. The total running time of the algorithm 2.8 seconds but is amortized over a total of 40 seconds. We demonstrate that we can still navigate,
select, and even add or remove objects in the Revit scene.
Find code at: https://github.com/ara3d/bowerbird/blob/main/Ara3D.Bowerbird.RevitSamples/SampleRevitCommands.cs