本指南介紹如何使用Speckle提供的工具開發自己的3D應用連接配接器。
推薦:使用 NSDT場景編輯器 快速搭建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、結束語
我們希望本指南能讓你更輕松地開始編寫自己的連接配接器,我們真的很期待看到它! 由于這是一個相當進階的指南,我們鼓勵你檢視其他連接配接器是如何編寫的,如果有任何其他問題,請聯系我們。
原文連結:http://www.bimant.com/blog/speckle-connectors-dev-guide/