# Writing Your Own Connector (Outdated)

Have a dream for a connector that doesn't yet exist? We have all the tools ready for you to start developing your own!

We are very enthusiastic about community connectors and would love to help you bring them to life. To get started, check out the guide below. Feel free to reach out with any questions or calls for more contributors to the project on our forum (opens new window).

# Before you begin

Before you begin writing your own connector, we encourage you to follow the steps below:

  1. make sure you are comfortable writing plugins for the host application you are planning to target, otherwise the guide below will not be of great help 😅
  2. post on the community forum (opens new window) announcing what you are planning to develop and how
  3. consider that if you make your connector publicly available, it's going to be your own responsibility to maintain it
  4. check whether the objects (opens new window) kit is fit to support your future connector, if it might need to be extended or if you might want to develop a new kit as well
  5. read our dev docs on Base object, Kits, Transports etc...

# Anatomy of a connector

Connectors are made of the following parts:

  • a User Interface
  • bindings between the host application and the UI
  • custom logic specific to the host application (for selecting elements, saving senders and receivers in the project file etc)
  • a converter to convert between the host application and Speckle geometry and BIM elements

For the purpose of this tutorial, we'll be using a user interface called DesktopUI currently in use by our Revit, Rhino, AutoCAD and Civil 3D connectors. But you can of course create your own or use whatever the host application you are integrating with provides - that's the case of visual programming software.

# Getting started

To get started, create a C# project in your IDE of choice by following the conventions and requirements for writing plugins for the host application you are targeting. In most cases you'll be creating a .NET Framework class library project.

To be consistent with other Speckle connectors you should name your project ConnectorAPP_NAME, set the Assembly name to SpeckleConnectorAPP_NAME and the namespace to Speckle.ConnectorAPP_NAME where APP_NAME is the name of your host application (eg Tekla, Etabs...).

Requirements

The minimum supported .NET Framework for using our .NET SDK is 4.6.1

Then you can proceed to add the following packages from NuGet:

  • Speckle.DesktopUI
  • Speckle.Objects

By installing these packages,Speckle.Core and other packages will be also added automatically.

# Adding DesktopUI (old)

IMPORTANT ⚠️

Our default User Interface is changing! 🤩 See our new version here, and add your feedback on the forum (opens new window)!

Assuming the host application you are integrating with provides a way to launch plugins via command or by clicking a button, you can insatiate and launch the DesktopUI with the code below:

public static Bootstrapper Bootstrapper { get; set; }

internal void StartOrShowPanel()
{
  if (Bootstrapper != null)
  {
    Bootstrapper.ShowRootView();
    return;
  }

  Bootstrapper = new Bootstrapper();

  if (Application.Current != null)
    new StyletAppLoader() { Bootstrapper = Bootstrapper };
  else
    new DesktopUI.App(Bootstrapper);

  Bootstrapper.Start(Application.Current);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

You can see how it's been implemented in Rhino (opens new window) and Revit (opens new window).

Now, in your host application, after launching the Speckle plugin you should see this window pop up:

# Adding the Bindings

The UI we just launched is quite sleek, but also a bit useless for the time being, as it doesn't have any connection to the host application; so it wouldn't know what to do when the Send button is clicked, when the user wants to change selection or where to load saved streams from etc...

DesktopUI comes with some DummyBindings (opens new window) so that you can test it, but let's go ahead and write our own.

Create a class named something like ConnectorBindingsAPP_NAME.cs and have it implement the abstract class ConnectorBindings.cs. It'll look something like the code below:

public class ConnectorBindingsAECApp : ConnectorBindings
  {
    public override void AddNewStream(StreamState state)
    {
      throw new NotImplementedException();
    }

    public override string GetActiveViewName()
    {
      throw new NotImplementedException();
    }

    public override string GetDocumentId()
    {
      throw new NotImplementedException();
    }

    public override string GetDocumentLocation()
    {
      throw new NotImplementedException();
    }

    public override string GetFileName()
    {
      throw new NotImplementedException();
    }

    public override string GetHostAppName()
    {
      throw new NotImplementedException();
    }

    public override List<string> GetObjectsInView()
    {
      throw new NotImplementedException();
    }

    public override List<string> GetSelectedObjects()
    {
      throw new NotImplementedException();
    }

    public override List<ISelectionFilter> GetSelectionFilters()
    {
      throw new NotImplementedException();
    }

    public override List<StreamState> GetStreamsInFile()
    {
      throw new NotImplementedException();
    }

    public override void PersistAndUpdateStreamInFile(StreamState state)
    {
      throw new NotImplementedException();
    }

    public override Task<StreamState> ReceiveStream(StreamState state)
    {
      throw new NotImplementedException();
    }

    public override void RemoveStreamFromFile(string streamId)
    {
      throw new NotImplementedException();
    }

    public override void SelectClientObjects(string args)
    {
      throw new NotImplementedException();
    }

    public override Task<StreamState> SendStream(StreamState state)
    {
      throw new NotImplementedException();
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

As you might have guessed, we now need to populate these methods with logic that calls the host app API to perform the various actions. You don't have to implement each method as some might not be relevant for your host application, but make sure they are handled gracefully. This is one of the most complicated parts of writing a connector and requires a very good understanding and experience with the host app API.

You can see how that's been done in Rhino (opens new window) and Revit (opens new window) (where it's split in multiple partial classes).

In this class you might also want to add the logic to handle various events triggered from the host application such as DocumentOpened and automatically open the UI if the document has any streams saved.

Once the binding class is complete you need to set it in the Bootstrapper we initialized when launching the UI.

Change:

Bootstrapper = new Bootstrapper();
1

To something like:

 Bootstrapper = new Bootstrapper()
 {
     Bindings = new ConnectorBindingsAECApp()
 };
1
2
3
4

You should now see the UI responding to various user actions with your custom binding logic.

# Adding DesktopUI (new)

IMPORTANT ⚠️

This new User Interface is currently in beta testing, add your feedback on the forum (opens new window)!

Our new DesktopUI is written using Avalonia (opens new window), a .NET open source framework for cross-platform UIs. You can play around with a standalone version of it by opening the solution in speckle-sharp/DesktopUI2/DesktopUI2.sln. Assuming the host application you are integrating with provides a way to launch plugins via command or by clicking a button, you can insatiate and launch the new DesktopUI with the code below:

public static Window MainWindow { get; private set; }

public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure<DesktopUI2.App>()
  .UsePlatformDetect()
  .With(new SkiaOptions { MaxGpuResourceSizeBytes = 8096000 })
  .With(new Win32PlatformOptions { AllowEglInitialization = true, EnableMultitouch = false })
  .LogToTrace()
  .UseReactiveUI();

protected override Result Command()
{
  CreateOrFocusSpeckle();
  return Result.Success;
}

public static void CreateOrFocusSpeckle()
{
  if (MainWindow == null)
  {
    BuildAvaloniaApp().Start(AppMain, null);
  }

  MainWindow.Show();
}

private static void AppMain(Application app, string[] args)
{
  var viewModel = new MainWindowViewModel();
  MainWindow = new MainWindow
  {
    DataContext = viewModel
  };

  Task.Run(() => app.Run(MainWindow));
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

You can see how it's been implemented in Rhino (opens new window) and Revit (opens new window).

Now, in your host application, after launching the Speckle plugin you should see this window pop up:

# Adding the Bindings

The UI we just launched is quite sleek, but also a bit useless for the time being, as it doesn't have any connection to the host application; so it wouldn't know what to do when the Send button is clicked, when the user wants to change selection or where to load saved streams from etc...

DesktopUI comes with some DummyBindings (opens new window) so that you can test it, but let's go ahead and write our own.

Create a class named something like ConnectorBindingsAPP_NAME.cs and have it implement the abstract class ConnectorBindings.cs. It'll look something like the code below:

public class ConnectorBindingsAECApp : ConnectorBindings
  {
    public override string GetActiveViewName()
    {
      throw new NotImplementedException();
    }

    public override List<MenuItem> GetCustomStreamMenuItems()
    {
      throw new NotImplementedException();
    }

    public override string GetDocumentId()
    {
      throw new NotImplementedException();
    }

    public override string GetDocumentLocation()
    {
      throw new NotImplementedException();
    }

    public override string GetFileName()
    {
      throw new NotImplementedException();
    }

    public override string GetHostAppName()
    {
      throw new NotImplementedException();
    }

    public override List<string> GetObjectsInView()
    {
      throw new NotImplementedException();
    }

    public override List<string> GetSelectedObjects()
    {
      throw new NotImplementedException();
    }

    public override List<ISelectionFilter> GetSelectionFilters()
    {
      throw new NotImplementedException();
    }

    public override List<StreamState> GetStreamsInFile()
    {
      throw new NotImplementedException();
    }

    public override Task<StreamState> ReceiveStream(StreamState state, ProgressViewModel progress)
    {
      throw new NotImplementedException();
    }

    public override void SelectClientObjects(string args)
    {
      throw new NotImplementedException();
    }

    public override Task SendStream(StreamState state, ProgressViewModel progress)
    {
      throw new NotImplementedException();
    }

    public override void WriteStreamsToFile(List<StreamState> streams)
    {
      throw new NotImplementedException();
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

As you might have guessed, we now need to populate these methods with logic that calls the host app API to perform the various actions. You don't have to implement each method as some might not be relevant for your host application, but make sure exceptions are handled gracefully. This is one of the most complicated parts of writing a connector and requires a very good understanding and experience with the host app API.

You can see how that's been done in Rhino (opens new window) and Revit (opens new window) (where it's split in multiple partial classes).

In this class you might also want to add the logic to handle various events triggered from the host application such as DocumentOpened and automatically open the UI if the document has any streams saved.

Once the binding class is complete you need to set it in the MainWindow constructor when launching the UI.

Change:

 var viewModel = new MainWindowViewModel();
1

To something like:

 var viewModel = new MainWindowViewModel(Bindings);
1

You should now see the UI responding to various user actions with your custom binding logic.

# Adding support for Reports

Our new DesktopUI has methods to better track what happens during send and receive operations, so that we can present a report to the user to better understand what happened. The class being used is ProgressReport defined in Core (opens new window).

It has three main methods that you should implement in your conversions and bindings:

# Passing errors from the converter to the UI

IMPORTANT

Don't forget this step! Not doing so will result in incomplete reports.

Since Speckle kits are hot swappable, the connectors or UI don't have any direct dependency on them. Therefore, we'd typically have 2 instances of a ProgressReport class, one inside the converter (opens new window) and one in the connector/UI (opens new window).

To make sure your reports include everything, you need to merge the two at the end of a send/receive conversion by calling: connectorReport.Merge(converterReport); like demonstrated here (opens new window).

# Report summary

At the top of a report we're outputting a summary, it only works if some keywords are used in the messages being logged: converted, created, updated, skipped ,failed; see the logic here (opens new window). It might change in the future, but works for now.

Therefore your messages should be formatted like this:

  • "Converted Curve to Beam"
  • "Created Wall"
  • "Updated Floor"
  • "Skipped not supported type: {@object.GetType()}"
  • "Failed to create Floor: ..."

# Adding custom actions

The new UI also offers the possibility of registering custom actions that will show up in the "options menu" of each saved stream:

You can register new actions in your GetCustomStreamMenuItems bindings method like so:

public override List<MenuItem> GetCustomStreamMenuItems()
{
  var menuItems = new List<MenuItem>
  {
    new MenuItem { Header="Test link", Icon="Home", Action =OpenLink},
    new MenuItem { Header="More items", Icon="List", Items = new List<MenuItem>
    {
      new MenuItem { Header="Sub item 1", Icon="Account" },
      new MenuItem { Header="Sub item 2", Icon="Clock" },
    }
    },
  };
  return menuItems;
}

public void OpenLink(StreamState state)
{
  //to open urls in .net core you must set UseShellExecute = true
  Process.Start(new ProcessStartInfo(state.ServerUrl) { UseShellExecute = true });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Implementing telemetry

Telemetry is an optional aspects of a connector, but it massively helps us understand how our tech is being used and if our products are useful or not. We encourage everyone adding it (and enabling it) in their connectors. The more usage we see, the more resources the project will get and a Better Speckle will be possible.

The telemetry service (matomo) is already added as a reference in Core, so you will just need to:

# Writing the Converter

Last crucial bit for your connector to work properly is to create a converter. The converter will take care of converting native data and geometry from your host application to Speckle when sending and vice-versa when receiving.

For the purpose of this guide, we'll assume your converter will be extending the Objects Kit, but you can also write your own kit if you so wish.

IMPORTANT

The Connector should never have a direct references to the Converter or Kit. This is because Kits are swappable and having a direct reference would prevent this mechanism from working.

# Creating the Converter Project

Create a new C# project for the converter, we recommend using a class library with .NET Standard 2.0.

To be consistent with other Speckle converters you should name your project ConverterAPP_NAME, set the Assembly name to Objects.Converter.APP_NAME and the namespace to Objects.Converter.APP_NAME where APP_NAME is the name of your host application (eg Tekla, Etabs...).

Then add references to the following NuGet packages:

  • Speckle.Objects
  • Your host app API (preferably as NuGets)

# Adding the Converter logic

Now create a new class named ConverterAPP_NAME and have it implement the ISpeckleConverter interface. It'll look something like this:

 public class ConverterAECApp : ISpeckleConverter
  {
    public string Description => throw new NotImplementedException();

    public string Name => throw new NotImplementedException();

    public string Author => throw new NotImplementedException();

    public string WebsiteOrEmail => throw new NotImplementedException();

    public HashSet<Exception> ConversionErrors => throw new NotImplementedException();

    public bool CanConvertToNative(Base @object)
    {
      throw new NotImplementedException();
    }

    public bool CanConvertToSpeckle(object @object)
    {
      throw new NotImplementedException();
    }

    public object ConvertToNative(Base @object)
    {
      throw new NotImplementedException();
    }

    public List<object> ConvertToNative(List<Base> objects)
    {
      throw new NotImplementedException();
    }

    public Base ConvertToSpeckle(object @object)
    {
      throw new NotImplementedException();
    }

    public List<Base> ConvertToSpeckle(List<object> objects)
    {
      throw new NotImplementedException();
    }

    public IEnumerable<string> GetServicedApplications()
    {
      throw new NotImplementedException();
    }

    public void SetContextDocument(object doc)
    {
      throw new NotImplementedException();
    }

    public void SetContextObjects(List<ApplicationPlaceholderObject> objects)
    {
      throw new NotImplementedException();
    }

    public void SetPreviousContextObjects(List<ApplicationPlaceholderObject> objects)
    {
      throw new NotImplementedException();
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

You might not need to implement all these methods, it really depends on how you want to handle the conversions and on how complex is the host app you're dealing with.

We'll detail a few of these methods and properties below, and you can see how they have been implemented for Rhino (opens new window) and Revit (opens new window).

IMPORTANT

Please note that it's fundamental that the string value(s) returned by GetServicedApplications() matches the name used for APP_NAME. So if your converter is built as Objects.Converter.Tekla.dll , the method should return new string[] { "Tekla"};

If you'd like to add your APP_NAME to the list of applications in Core (opens new window), we can certainly do so, just submit a PR.

# The Context Document

The SetContextDocument is used to inject the Document from the connector and host app API to the converter ( it might have a different name in your case, or not even exist!), but in software as Revit or Rhino it's fundamental to create new objects, start transactions, get units etc...

# The Convert methods

The most important methods in a converter are ConvertToNative and ConvertToSpeckle, and are what will take most of your time and API flexing skills.

Please have a look at how things are done in other converters, you will most likely end up having a switch statement with a list of functions that handle the various geometry and data types.

The logic of these routines can get quite complicated if you want to support nested elements or update previously received objects, and we recommend adding such functionalities later.

IMPORTANT

Don't forget to properly set the units of your objects when sending to Speckle and to scale incoming geometry when receiving from Speckle.

For any questions feel free to write on the community forum (opens new window)!

# New Objects

If the host app you're targeting uses object types not currently defined in the Objects Kit and you'd like to write conversions for them, we are happy to extend it!

Please get in touch on the forum before making a PR, then have a look at how to write new objects.

# Loading the Converter

At some point in your ConnectorBindings you'll need to implement the SendStream and ReceiveStream methods. It's within these that we will be invoking methods of the converter and then either send or receive data to/from Speckle.

IMPORTANT

The Connector should never have a direct references to the Converter or Kit. This is because Kits are swappable and having a direct reference would prevent this mechanism from working.

To invoke the ConvertToNative and ConvertToSpeckle methods (and other methods / properties as well) of the converter, we first need to load it. To do so, you should use the KitManager in Core as follows:

var kit = KitManager.GetDefaultKit();
var converter = kit.LoadConverter(APP_NAME);
// then stuff like converter.ConvertToNative(obj);
1
2
3

The converter, as you might have seen above should have implemented other handy methods such as CanConvertToSpeckle

For more information on how to implement the send/receive bindings, use our Connectors' implementations as reference, and (for receive) see Traversal docs.

# Publishing the Connector

We currently don't have mechanisms to publish third party connectors via Manager, but if you'd like to do so please write on the community forum (opens new window) and we'll work out a solution! You are of course free to develop your own installer / deployment mechanism.

# Conclusion

We hope this guide will make it easier for you to start writing your own connector and we are really looking forward to seeing it! As this is a quite high level guide we encourage you to check out how our other connectors have been written and ping us with additional questions if you have any.