How to run Revit to Speckle conversions in C#

Dear Speckle founders, employees, and community,

I am not sure if this is something I should take here or at a Revit/.NET forum, but I’m trying my luck.

It seems like that Revit is loading DLLs asyncronously. This works nice if you do all the processing in the UI thread. However, some of the Speckle operations are exposed as Task and when you call those, it seems like the DLL doesn’t load in time and you get a MissingMethodException.

Example code:

        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            return Task.Run(async () => await ExecuteAsync(commandData)).Result;
        }

        public async Task<Result> ExecuteAsync(ExternalCommandData commandData)
        {
            Logger.Info("Executing command. Oh well.");

            UIApplication uiapp = commandData.Application;
            Document doc = uiapp.ActiveUIDocument.Document;

            /**
             * Get the converter. We use the built-in Speckle "magic" to load the correct DLL
             */
            var kit = KitManager.GetDefaultKit();
            var converter = kit.LoadConverter(VersionConstants.App);
            converter.SetContextDocument(doc);

            /**
             * Convert all selected elements to speckle "bases"
             */
            IList<Element> selectedElements = GetSelectedElements(uiapp);
            IList<Base> speckleBases = ConvertRevitElementsToSpeckleBases(selectedElements, converter);

            /**
             * Stash them all into one base object
             */
            var commitObject = new Base();
            commitObject["@all"] = speckleBases;

            /**
             * We cannot serialize the object directly as the detachable objects of the base will be "consumed"
             * by the transport. Thus, we pass the entire object to a memory transport and serialize the results after.
             *
             * We should, however, implement our own transport.
             * https://speckle.community/t/using-speckle-transports-serialization-to-transfer-data-to-a-custom-endpoint/1547/3
             */
            var memoryTransport = new MemoryTransport();
            await Operations.Send(commitObject, new List<ITransport>() { memoryTransport });
            var data = JsonConvert.SerializeObject(memoryTransport.Objects);

            var response = await Http.PostJsonString(PostUrl, data);
            Logger.Info(response);
            return Result.Succeeded;

        }

This throws the following exception:

MissingMethodException: Method not found: 'System.Threading.Tasks.Task`1<System.String> Speckle.Core.Api.Operations.Send(Speckle.Core.Models.Base, System.Collections.Generic.List`1<Speckle.Core.Transports.ITransport>, Boolean, System.Action`1<System.Collections.Concurrent.ConcurrentDictionary`2<System.String,Int32>>, System.Action`2<System.String,System.Exception>)'.

Any of you have a clue what is going on?

The issue had nothing to do with Task and async as far as I know. I forgot to include the Speckle.Objects library and that could have caused the issue.

Can you confirm that I would need:

  • Speckle.Core
  • Speckle.Objects
  • Speckle.Revit.API

To run Revit to Speckle conversions on Revit 2022?

1 Like

Hey Håkon!
Actually, to convert objects you don’t need a reference to Objects. This is because Speckle.Core is capable of dynamically loading kits. This feature is there to support 3rd party kits as well.
See how our ConnectorBindingsRevit.Send does it:

Where the crucial lines are:

var kit = KitManager.GetDefaultKit();
var converter = kit.LoadConverter(ConnectorRevitUtils.RevitAppName);
converter.SetContextDocument(CurrentDoc.Document);
var conversionResult = converter.ConvertToSpeckle(revitElement);

You could of course have a direct dependency on the converter and object classes if you wanted, but would lose the flexibility of supporting multiple kits.

Any other questions just ask! (I’ll edit your post title for clarity :slight_smile: )

I’m confused. Mostly because after including Speckle.Objects things started working again. I’ve run into some undeterministic behaviour at some point and I’m trying to figure out what is wrong. Hence my post about the Task and async. Thanks for correcting the title.

Where exactly are the Revit converter kits located? In our Revit 2021 package we include the Speckle.Objects.Converter.Revit2021 package but for 2022 no such package exist. We use Speckle.Revit.API instead. Is this where the kits we need are?

Hi @hawkaa! Quick one: Speckle.Revit.API should not be included in the plugin “build”, as it’s just a nuget with revit’s api dlls. This might conflict with the original revit ones, so make sure they’re not in the plugin folder.

The way kits work is not straightforward: it depends on whether the user has installed Speckle; if so, the dlls are going to be located in %appdata%/Speckle/Kits (if i’m not mistaken). That’s where Speckle Core will look for them to load them dynamically.

If your plugin is self contained and references directly Speckle.Objects and the associated Revit converter, you will probably bypass the dynamic loading mechanism. In this case Speckle Core does look through already loaded assemblies in memory to see if it finds any suitable converter, in which case it should be picked up (if already referenced in a project).

There’s also some weirdo gotchas in the way .NET loads assemblies sometimes - when actually needed IIRC, and some strange referencing hacks that basically “touch” the code w/o doing anything (to ensure loading) might be needed or explain the strange behaviour.

Anyway, let us know what’s not working now and we’ll try and help out!

2 Likes

Just to further clarify something, Objects is the kit which contains the converters for all the installed connectors. When you install connectors, their respective converters will all be added to \AppData\Roaming\Speckle\Kits\Objects as dim pointed out.

objects kit folder

If you directly reference Objects in your plugin, using the KitManager to load up Objects will actually pick up the Objects dll in your plugin instead of the one in your Speckle\Kits\Objects folder. This means it won’t have references to all your installed converters because it will be looking within your plugin for the converters instead of in the Speckle\Kits\Objects. You will need to reference the converters themselves within your plugin for this to work as expected.

This MissingMethodException from your initial post looks like it is for Operation.Send which is part of Speckle.Core. You definitely need to reference Speckle.Core within your plugin to send and receive! I’m a bit confused by this error though since you successfully used the KitManager which is also part of Core :thinking:

1 Like

Thank you guys! I need to have a second look at the dependencies and how we package our plugin.

There’s also some weirdo gotchas in the way .NET loads assemblies sometimes - when actually needed IIRC, and some strange referencing hacks that basically “touch” the code w/o doing anything (to ensure loading) might be needed or explain the strange behaviour.

Yes. I experienced the same. If you have any examples on how to do this, I’m listening!

Heya!

Not sure if this is exactly what you’re looking for, but we do this in the DesktopUI to preload some styling assemblies before the Speckle Connector is launched. This just creates some assets from the styling, colours, and behaviours packages we need to force loading of the assemblies before launch.

You can also go the explicit route like they do here in DynamoRevit:

1 Like