Sending data from Revit to Speckle programmatically

Hi community,

I just started learning Speckle, so I hope you can point me in the right direction.

I am a C# developer, and I am currently working on creating an add-in for Revit (2024).
My goal is to send particular elements (or the whole current document) to a Speckle Server/stream. This works fine when I use the User interface of the provided Revit connector.
Now I would like to send elements not using the UI of the connector, but doing this programmatically inside of a Revit add-in (using C#).

Revit allows to access most of their stuff (elements, current project, linked documents, etc…) using the RevitAPI. So I was thinking I could create a new Revit converter kit instance inside the add-in codee, and then use this to convert the Revit elements into Speckle objects.

Here is what I got so far:

//Create a revit converter kit instance
var kit = KitManager.GetDefaultKit();
var converter = kit.LoadConverter(Objects.Converter.Revit.ConverterRevit.RevitAppName);
converter.SetContextDocument(revit_doc);

//Example for converting a particular element - fails with a
//NullReferenceException - the private variable revitDocumentAggregateCache
//is null inside ConverterRevit
var collector = new FilteredElementCollector(Project.MainDocument);
var rev_el = collector.OfCategory(BuiltInCategory.OST_MEPSpaces)
    .FirstOrDefault();
var sp_el = converter.ConvertToSpeckle(rev_el);

//Example for the whole document - the conversion result does not contain any elements
var speckle_doc = converter.ConvertToSpeckle(revit_doc);

//Finally, send the document or the element to Speckle
Helpers.Send("https://speckle.xyz/streams/1225379f6a", speckle_doc);
//Helpers.Send("https://speckle.xyz/streams/1225379f6a", sp_el);

I have two issues here. When I try to convert the whole Autodesk.Revit.DB.Document into a Speckle object, I don’t get an exception, but also the document does not contain any elements.
When I try to convert a particular element (in this case a MEPSpace, but I get the same issue for a Room or a Wall etc.), I get a NullReferenceException.

I tried to debug the issue in the Speckle connector source code - it looks like the ConverterRevit class has a private variable called revitDocumentAggregateCache, which is null in my case.
Judging on the implementation of the current Revit connector (which is thankfully open source), it looks like that variable is somehow injected when the UI is created. I must have missed something here.

So my question is basically: Is there a way to programmatically convert Revit objects (or the whole document) into Speckle objects inside a Revit plugin?

Thanks for your support!

Hi @andreas.lennartz,

Thanks for reaching out about this. The issue that you’ve brough up is a very bad developer experience thing that we are aware and plan to fix (although I couldn’t tell you when). Our current architecture forces the converters to be instantiated via parameterless constructors which forces the dependencies (such as the aggregate cache) to be add via “Initialize” methods.

We use the SetContextDocument method for a lot of this initialization. You can see here all the things that we are expecting to be passed into this method.

To fix your current issue, you would need to “inject” the dependency via the “SetContextDocument” method. Something like this.

//Create a revit converter kit instance
var kit = KitManager.GetDefaultKit();
var converter = kit.LoadConverter(Objects.Converter.Revit.ConverterRevit.RevitAppName);
converter.SetContextDocument(revit_doc);

// NEW CODE
//
var docProvider = new UIDocumentProvider(ConnectorBindingsRevit.RevitApp);
var cache = new RevitDocumentAggregateCache(docProvider);
converter.SetContextDocument(cache);
//
// potentially need to inject other objects such via setContextDocument such as the 
// TransactionManager
//
// END NEW CODE

//Example for converting a particular element - fails with a
//NullReferenceException - the private variable revitDocumentAggregateCache
//is null inside ConverterRevit
var collector = new FilteredElementCollector(Project.MainDocument);
var rev_el = collector.OfCategory(BuiltInCategory.OST_MEPSpaces)
    .FirstOrDefault();
var sp_el = converter.ConvertToSpeckle(rev_el);

//Example for the whole document - the conversion result does not contain any elements
var speckle_doc = converter.ConvertToSpeckle(revit_doc);

//Finally, send the document or the element to Speckle
Helpers.Send("https://speckle.xyz/streams/1225379f6a", speckle_doc);
//Helpers.Send("https://speckle.xyz/streams/1225379f6a", sp_el);

Again, I know this isn’t very explicit and it doesn’t make a ton of sense, so hopefully we can get around to actually doing some constructor-based injection. But for now, this should move you along. Let me know if you have any further questions about this.

Hi @connor ,
thanks a lot for your quick reply and your help!
Extending the code with the instances of the UIDocumentProvider/RevitDocumentAggregateCache did the trick.

Unfortunately, getting there was a little bit harder than expected - originally, I just referenced the publicly provided nuget packages (Speckle.Core, Speckle.Objects and Objects.Converter.Revit2024) in my Revit-add-in. This worked for the original code, but the objects that you added are not exposed in the packages. E.g. UIDocumentProvider lives in the namespace ConnectorRevit.Storage, but Objects.Converter.Revit2024 does not expose a *.Storage namespace at all.

So I ended up in cloning the whole speckl-sharp project, and building all of it. (Initially I only wanted to rebuild the the connectors locally, but they had dependencies, so I needed to rebuild the whole project). Then referencing the project in my add-in worked, and now I was able to access the classes.

Now I was able to convert a single Revit element into a speckle object using the converter.ConvertToSpeckle, it now worked flawless. Thanks!

In the next step, I wanted to convert a whole Revit Document - turns out, that is also not that straightforward. Luckily, your Revit Connector code is open-source, so I basically copied the whole code that is also called from your UI. Turns out that a few more things need to be done, and that your code contained already many things that I could reuse. (e.g. you already had methods like GetEverything(.) available, but I needed to convert them from private into public in order to access them).

I have two final questions here: What would be the best/recommended way to get access to this classes (UIDocumentProvider, ConnectorBindingsRevit, etc.), and to reference the Speckle Revit API in my C# project?
What are your current plans to improve/document the exposed “Revit Connector API” - e.g. do you plan that I should be easier accessible by other programmers in the future, or is it main purpose only to support the functionalities offered by the User Interface?

Thanks a lot for your support,
Andreas

@andreas.lennartz,

Yeah, that is a lot of work. You can make things a bit easier but probably not as easy as you’d like it to be. If you look at the link that I sent in my previous message, you’ll see that a lot of the types that the converter expects to be passed are interfaces. These interfaces are defined in a nuget package that, I believe, is already referenced by the converter call RevitSharedResources.

The answer to you first question is that you have two options.

  1. You could make your own implementations of these interface to pass to the converter.
  2. You can just copy the interface implementation that we have in the connector code and pass that to the converter.

To answer your second question: the connector code isn’t release as a nuget because it’s not really designed with customization in mind. Also, we want to be able to change pretty much anything in there without having to worry about breaking other people’s code. That is unlikely to change anytime soon.

@connor,
Thanks a lot for your support. That clarifies it a lot.

Creating an own implementation of the interfaces might by a good option (e.g. if we want to have the cache persisted locally in order to improve the transfer speed), and copying the interface definition for now might be a good way to go forward. I will test this approach and see if I then can only reference the nuget packages in my project, without the need to have the source code integrated. Thanks for clarifying this!

I have another, only partially related question: I have an IFC file which I load via C# directly. I use the XBim.Essential/XBim.Geometry packages, and this will create me an IFCStore object.
Is there an easy way to upload this object (or, alternatively the raw file without the IFC packages) to Speckle programatically? E.g. is there already some kind of ifcstore object to speckle object converter kit? Or some rest endpoint that I can use to upload the raw file?
I know that I can upload the file in the UI, but I would like to send the IFC without user interaction to speckle.

@connor
Sorry for bothering, just wanted to follow up on my IFC question.
I have create a new thread for this 2 weeks ago:

Unfortunately, I didn’t get any responses yet.
Do you have any ideas what would be the best way to upload an IFC file into specke via C# code?