Speckle连接器开发指南
本指南介绍如何使用Speckle提供的工具开发自己的3D应用连接器。
1、Speckle连接器开发简介
在开始编写自己的连接器之前,我们鼓励你遵循以下步骤:
- 确保你可以轻松地为你计划定位的宿主应用编写插件,否则下面的指南不会有很大帮助 😀
- 查看我们的路线图 看看我们是否已经在着手或计划
- 在社区论坛上发帖宣布你计划开发什么以及如何开发
- 考虑一下,如果你使连接器公开可用,则维护它是你自己的责任
- 检查 objects是否适合支持你未来的连接器,是否需要扩展或者你是否也想开发一个新的工具包
- 阅读我们关于 Base object、Kits、Transports 等的开发文档...
1.1 Speckle连接器解剖
连接器由以下部分组成:
- 用户界面
- 宿主应用和 UI 之间的绑定
- 特定于宿主应用的自定义逻辑,用于选择元素、在项目文件中保存发送者和接收者等
- 在宿主应用与Speckle几何体和 BIM 元素之间进行转换的转换器
出于本教程的目的,我们将使用 Revit、Rhino、AutoCAD 和 Civil 3D 连接器当前使用的名为 DesktopUI 的用户界面。 但是你当然可以创建自己的或使用任何与您集成的宿主应用提供的 - 可视化编程软件就是这种情况。
1.2 Speckle连接器开发入门
首先,按照为目标宿主应用编写插件的约定和要求,在你选择的 IDE 中创建一个 C# 项目。 在大多数情况下,你将创建一个 .NET Framework 类库项目。
为了与其他 Speckle 连接器保持一致,你应该将项目命名为 ConnectorAPP_NAME
,将程序集名称设置为 SpeckleConnectorAPP_NAME
,并将命名空间设置为 Speckle.ConnectorAPP_NAME
,其中 APP_NAME
是你的宿主应用的名称(例如 Tekla、Etabs...)。
要求:使用我们的 .NET SDK 的最低支持 .NET Framework 是 4.6.1
接下来你可以继续从 NuGet 添加以下包:
- Speckle.DesktopUI
- Speckle.Objects
通过安装这些包,Speckle.Core 和其他包也会自动添加。
2、添加 DesktopUI(旧)
重要⚠️:我们的默认用户界面正在改变! 🤩 在这里查看我们的新版本!
假设你要集成的宿主应用提供了一种通过命令或单击按钮启动插件的方法,可以使用以下代码启动 DesktopUI:
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);
}
你可以看到它是如何在 Rhino 和 Revit 中实现的。
现在,在你的宿主应用中,启动 Speckle 插件后,应该会看到这个窗口弹出:
2.1 添加事件绑定
上面刚实现的 UI 非常时尚,但暂时有点无用,因为它与宿主应用没有任何连接; 所以当点击发送按钮时,当用户想要更改选择或从哪里加载保存的流等时,它不知道该怎么做......
DesktopUI 带有一些 DummyBindings以便你可以对其进行测试,但让我们继续编写我们自己的。
创建一个名为 ConnectorBindingsAPP_NAME.cs
的类,并让它实现抽象类 ConnectorBindings.cs
。 代码看起来像下面这样:
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();
}
}
正如你可能已经猜到的那样,我们现在需要使用调用宿主应用的 API 以执行各种操作的逻辑来填充这些方法。 你不必实现每个方法,因为有些方法可能与你的宿主应用无关,但请确保它们得到妥善处理。 这是编写连接器最复杂的部分之一,需要对宿主应用的 API 有很好的理解和经验。
你可以在 Rhino 和 Revit 连接器实现中看到这是如何完成的。
在这个类中,你可能还想添加逻辑来处理从宿主应用触发的各种事件,例如 DocumentOpened
,并在文档保存了任何流时自动打开 UI。
绑定类完成后,你需要在我们启动 UI 时初始化的 Bootstrapper 中设置它。
修改如下代码:
Bootstrapper = new Bootstrapper();
为:
Bootstrapper = new Bootstrapper()
{
Bindings = new ConnectorBindingsAECApp()
};
现在应该可以看到 UI 使用你的自定义绑定逻辑响应各种用户操作。
3、添加 DesktopUI(新)
重要⚠️:这个新的用户界面目前正在进行 Beta 测试!
我们新的 DesktopUI 是使用 Avalonia 开发的,这是一个用于跨平台 UI 的 .NET 开源框架。 你可以通过打开 speckle-sharp/DesktopUI2/DesktopUI2.sln
中的解决方案来使用它的独立版本。 假设你要集成的宿主应用提供了一种通过命令或单击按钮启动插件的方法,你可以使用以下代码启动新的 DesktopUI:
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));
}
可以查看它是如何在 Rhino和 Revi 连接器中实现的。
现在,在你的宿主应用中,启动 Speckle 插件后,应该会看到这个窗口弹出:
3.1 添加事件绑定
同样,上面刚实现的 UI 非常时尚,但暂时有点无用,因为它与宿主应用没有任何连接; 所以当点击发送按钮时,当用户想要更改选择或从哪里加载保存的流等时,它不知道该怎么做......
DesktopUI 带有一些 DummyBindings 以便你可以对其进行测试,但让我们继续编写我们自己的。
创建一个名为 ConnectorBindingsAPP_NAME.cs
的类,并让它实现抽象类 ConnectorBindings.cs
。 代码看起来像下面这样:
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();
}
}
正如你可能已经猜到的那样,我们现在需要使用调用宿主应用的 API 以执行各种操作的逻辑来填充这些方法。 你不必实现每个方法,因为有些方法可能与你的宿主应用无关,但请确保异常得到妥善处理。 这是编写连接器最复杂的部分之一,需要对宿主应用的 API 有很好的理解和经验。
你可以在 Rhino 和 Revit 的连接器实现中看到这是如何完成的。
在这个类中,你可能还想添加逻辑来处理从宿主应用触发的各种事件,例如 DocumentOpened
,并在文档保存了任何流时自动打开 UI。
绑定类完成后,你需要在启动 UI 时在 MainWindow 构造函数中设置它。
修改如下代码:
var viewModel = new MainWindowViewModel();
为:
var viewModel = new MainWindowViewModel(Bindings);
现在应该可以看到 UI 使用你的自定义绑定逻辑响应各种用户操作。
4、添加报告支持
我们新的 DesktopUI 有一些方法可以更好地跟踪发送和接收操作期间发生的事情,这样我们就可以向用户呈现报告以更好地了解发生了什么。 要使用的类是在 Core 中定义的 ProgressReport
。
ProgressReport
有三个主要方法,你应该在转换和绑定中实现:
Log()
:用于记录任何有用的操作,例如正在使用的转换器版本、成功操作或跳过的元素。 你应该记录尽可能多的有用操作,在我们的连接器中,每个转换都会被记录下来。LogConversionError()
:用于跟踪转换期间发生的任何错误LogOperationError()
:用于在发送或接收时跟踪任何其他错误
4.1 将错误从转换器传递到 UI
重要:不要忘记这一步! 不这样做会导致报告不完整。
由于 Speckle 套件是可热插拔的,因此连接器或 UI 对它们没有任何直接依赖性。 因此,我们通常有 2 个 ProgressReport
类的实例,一个在转换器内部,一个在连接器/UI 中。
为确保你的报告包含所有内容,你需要在发送/接收转换结束时调用 connectorReport.Merge(converterReport)
将两者合并。
4.2 报告摘要
在报告的顶部,我们输出摘要,只有在记录的消息中使用了某些关键字时它才有效:转换、创建、更新、跳过、失败。 输出摘要的处理逻辑将来可能会改变,但目前有效。
因此,你的消息的格式应如下所示:
- “将曲线转换为梁”
- “创造的墙”
- “更新楼层”
- “跳过不支持的类型:{@object.GetType()}”
- “创建楼层失败:……”
5、添加自定义操作
新的 UI 还提供了注册自定义操作的可能性,这些操作将显示在每个已保存流的“选项菜单”中:
可以在 GetCustomStreamMenuItems
绑定方法中注册新操作,如下所示:
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 });
}
6、实现遥测的支持
遥测(telemetry)是连接器的一个可选方面,但它极大地帮助我们了解我们的技术是如何被使用的,以及我们的产品是否有用。 我们鼓励每个人在他们的连接器中添加它(并启用它)。 我们看到的使用越多,项目获得的资源就越多,更好的 Speckle 也将成为可能。
遥测服务 (matomo) 已作为参考添加到 Core 中,因此只需要:
- 使用连接器名称作为输入,调用
Setup.Init()
对其进行初始化 - 使用
Tracker.TrackPageView
跟踪主要操作
7、编写Speckle转换器
连接器正常工作的最后一个关键点是创建一个转换器。 转换器将负责在发送时将本地数据和几何图形从宿主应用转换为 Speckle格式,反之亦然。
出于本指南的目的,我们假设你的转换器将扩展 Objects Kit,但如果你愿意,也可以编写自己的工具包。
重要:连接器永远不应该直接引用 Converter 或 Kit。 这是因为套件是可交换的,并且具有直接引用会阻止此机制工作。
7.1 创建转换器项目
为转换器创建一个新的 C# 项目,我们建议使用带有 .NET Standard 2.0 的类库。
为了与其他 Speckle 转换器保持一致,你应该将项目命名为 ConverterAPP_NAME
,将程序集名称设置为 Objects.Converter.APP_NAME
,将命名空间设置为 Objects.Converter.APP_NAME
,其中 APP_NAME
是你的宿主应用的名称(例如 Tekla、Etabs... ).
然后添加对以下 NuGet 包的引用:
- Speckle.Objects
- 你的宿主应用的 API(最好是 NuGets包)
7.2 添加转换器逻辑
现在创建一个名为 ConverterAPP_NAME
的新类,并让它实现 ISpeckleConverter
接口。 它看起来像这样:
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();
}
}
你可能不需要实现所有这些方法,这实际上取决于你希望如何处理转换以及你正在处理的宿主应用的复杂程度。
我们将在下面详细介绍其中的一些方法和属性,你可以查看它们是如何在 Rhino 和 Revit 中实现的。
重要:请注意,GetServicedApplications() 返回的字符串值必须与用于 APP_NAME 的名称相匹配。 因此,如果你的转换器构建为 Objects.Converter.Tekla.dll ,则该方法应返回 new string[] { "Tekla"};
如果想将你的 APP_NAME
添加到 Core
中的应用程序列表中,我们当然可以这样做,只需提交 PR。
7.3 上下文文档
SetContextDocument
用于将文档从连接器和宿主应用 API 注入转换器(你的情况下它可能有不同的名称,或者甚至不存在!),但在 Revit 或 Rhino 等软件中,创建新对象是基础, 开始事务,获取单位等...
7.4 实现转换方法
转换器中最重要的方法是 ConvertToNative
和 ConvertToSpeckle
,它们将占用大部分时间和 API 的灵活技能。
请看看其他转换器是如何完成的,你很可能最终会得到一个带有处理各种几何和数据类型的函数列表的 switch
语句。
如果想支持嵌套元素或更新以前收到的对象,这些例程的逻辑会变得相当复杂,我们建议稍后添加此类功能。
重要:不要忘记在发送到 Speckle 时正确设置对象的单位,并在从 Speckle 接收时缩放传入的几何体。
7.5 新对象支持
如果你的目标宿主应用使用当前未在 Objects Kit 中定义的对象类型,并且你想为它们编写转换,我们很乐意扩展它!
请在进行 PR 之前先在论坛上取得联系,然后看看如何编写新对象。
7.6 加载转换器
在你的 ConnectorBindings
中的某个时刻,需要实现 SendStream
和 ReceiveStream
方法。 正是在这些方法中,我们将调用转换器的方法,然后向 Speckle 发送数据或从 Speckle 接收数据。
重要:连接器永远不应该直接引用 Converter 或 Kit。 这是因为套件是可交换的,并且具有直接引用会阻止此机制工作。
要调用转换器的 ConvertToNative
和 ConvertToSpeckle
方法(以及其他方法/属性),我们首先需要加载它。 为此,你应该使用 Core 中的 KitManager
,如下所示:
var kit = KitManager.GetDefaultKit();
var converter = kit.LoadConverter(APP_NAME);
// then stuff like converter.ConvertToNative(obj);
正如你在上面看到的那样,转换器应该已经实现了其他方便的方法,例如 CanConvertToSpeckle
。
8、结束语
我们希望本指南能让你更轻松地开始编写自己的连接器,我们真的很期待看到它! 由于这是一个相当高级的指南,我们鼓励你查看其他连接器是如何编写的,如果有任何其他问题,请联系我们。
原文链接:Writing Your Own Connector
BimAnt翻译整理,转载请标明出处