A cross-platform library that allows you to transfer BIM data directly into your application and maintain a live link between them.
Video Demonstration
RevitToUnigineAndUnity.mp4
Vertex Flow is designed to speed up your building information modeling (BIM) workflows. No longer necessary to juggle multiple tools and take dozens of steps to prepare and optimize BIM data for creating 3D experiences.
Vertex Flow SDK can help you create real-time BIM applications by building on top of any 3D engine.
Vertex Flow makes the process of bringing BIM models into real-time 3D extremely simple by unlocking the ability to stream data directly into your application.
The first integration is with Revit, but you can integrate with any Autodesk products, as well as other industry tools.
- Install the latest version of Azure Cosmos DB Emulator
- Start the Azure Cosmos DB Emulator
- Install Revit 2022
- Open the
VertexFlow.addinfile and replace theAssemblysection with your output directory - Open the
VertexFlow.RevitAddin.csprojfile and replace theDestinationFolderin the targetAfterBuildsection
Once you build the solution and launch Revit, you should find the Vertex Flow pannel in the Add-Ins tab.
Note: If you don't want to install Revit, just unload the
src/VertexFlow.RevitAddinproject from the solution.
Create
Assembly Definitionin theAssets/Plugins/VertexFlowfolder withVertexFlownameCopy following libraries to the
Assets/Plugins/VertexFlow/libsdirectory- VertexFlow.Core.dll
- VertexFlow.SDK.dll
- VertexFlow.SDK.Extensions.dll
- VertexFlow.SDK.Listeners.dll
Create
signalr.ps1file in theAssets/Plugins/VertexFlow/libsfoldersignalr.ps1
$srcVersion="3.1.20"$stjVersion="4.7.2"$target="netstandard2.0"$outDir=".\temp"$pluginDir=".\" nuget install Microsoft.AspNetCore.SignalR.Client -Version $srcVersion-OutputDirectory $outDir nuget install System.Text.Json -Version $stjVersion-OutputDirectory $outDir$packages=Get-ChildItem-Path $outDirforeach ($pin$packages) { $dll=Get-ChildItem-Path "$($p.FullName)\lib\$($target)\*.dll"if (!($dll-eq$null)) { $d=$dll[0] if (!(Test-Path"$($pluginDir)\$($d.Name)")) { Move-Item-Path $d.FullName-Destination $pluginDir } } } Remove-Item$outDir-Recurse
Check if NuGet CLI is installed locally
Execute the following command in PowerShell from the
Assets/Plugins/VertexFlow/libsdirectory to import the target .dll files./signalr.ps1
You will find a complete example in the
samples/VertexFlow.SDK.Sampleproject.
First of all, create a class to store mesh data.
usingVertexFlow.Core.Models;publicstructCustomVector3{publicfloatX{get;set;}publicfloatY{get;set;}publicfloatZ{get;set;}}publicclassCustomMesh:MeshData<CustomVector3>{}You can use default
Vector3fromVertexFlow.Core.Structs
IMeshFlow<TMeshData> provides methods for adding SendAsync or replacing UpdateAsync existing meshes in a database.
staticasyncTaskMain(){usingvarvertexFlow=newVertexFlow("https://localhost:5001");varmeshFlow=vertexFlow.CreateMeshFlow<CustomMesh>();varmeshData=GetMeshData();// Adds the mesh data to a database.// Note: Will fail if there already is a mesh data with the same id.awaitmeshFlow.SendAsync(meshData);// Updates the mesh data in a database.// Note: Will add or replace any mesh data with the specified id.awaitmeshFlow.UpdateAsync(meshData.Id,meshData);}privatestaticCustomMeshGetMeshData(){
...}IMeshStore<TMeshData> provides methods for reading GetAsync, GetAllAsync, or deleting DeleteAsync existing meshes from a database.
staticasyncTaskMain(){usingvarvertexFlow=newVertexFlow("https://localhost:5001");varmeshStore=vertexFlow.CreateMeshStore<CustomMesh>();foreach(varmeshinawaitmeshStore.GetAllAsync()){Console.WriteLine($"Mesh '{mesh.Id}' downloaded.");}}IMeshFlowListener invokes MeshCreated or MeshUpdated in response to mesh data changes in a database.
usingVertexFlow.SDK.Listeners;staticasyncTaskMain(){usingvarvertexFlow=newVertexFlow("https://localhost:5001");usingvarmeshFlowListener=vertexFlow.CreateMeshFlowListener();// Occurs when new mesh data has been added to a database.meshFlowListener.MeshCreated+=(sender,meshId)=>{Console.WriteLine($"Mesh '{meshId}' created.");};// Occurs when new mesh data has been updated in a database.meshFlowListener.MeshUpdated+=(sender,meshId)=>{Console.WriteLine($"Mesh '{meshId}' updated.");};// Starts listening for changes in a database.awaitmeshFlowListener.StartAsync();
...// Stops listening for changes in a database.
await meshFlowListener.StopAsync();}Use VertexFlow.SDK.Extensions to automate the process of loading mesh data on adding OnMeshCreated or updating OnMeshUpdated a database.
usingSystem.Net.Http;usingVertexFlow.SDK.Listeners;usingVertexFlow.SDK.Extensions;staticasyncTaskMain(){usingvarvertexFlow=newVertexFlow("https://localhost:5001");varmeshStore=vertexFlow.CreateMeshStore<CustomMesh>();usingvarmeshFlowListener=awaitvertexFlow.CreateMeshFlowListener().WithStore(meshStore).OnMeshCreated(mesh =>Console.WriteLine($"Mesh '{mesh.Id}' created.")).OnMeshUpdated(mesh =>Console.WriteLine($"Mesh '{mesh.Id}' updated.")).ContinueOnCapturedContext(false).StartAsync(exception =>thrownewHttpRequestException(exception.Message));
...
await meshFlowListener.StopAsync();}Note:
MeshCreated&MeshUpdatedwill provide only the id of the mesh as a parameter, whileOnMeshCreated&OnMeshUpdatedwill provide the full mesh data.
You can control how the mesh data is encoded into JSON. Once you've implemented the IJsonSerializer interface, you can pass the implementaton to the CreateMeshFlow and CreateMeshStore methods.
classCustomSerializer:IJsonSerializer{publicTask<HttpContent>SerializeAsync<T>(Tdata,CancellationTokencancellationToken){
...}publicTask<T>DeserializeAsync<T>(HttpContenthttpContent,CancellationTokencancellationToken){
...}}staticclassProgram{staticasyncTaskMain(){usingvarvertexFlow=newVertexFlow("https://localhost:5001");varcustomSerializer=newCustomSerializer();varmeshFlow=vertexFlow.CreateMeshFlow<CustomMesh>(customSerializer);varmeshStore=vertexFlow.CreateMeshStore<CustomMesh>(customSerializer);
...}}You will find two custom json serializers in the
benchmarks/VertexFlow.SDK.Benchmark/JsonSerializersdirectory.
Make sure the src/VertexFlow.WebAPI project is running to be able to transfer mesh data.
You will find a complete example in the
src/VertexFlow.RevitAddinproject.
- Create a class to store mesh data
- Extract
Meshfrom anElement - Construct
Mesh Datafrom the extractedMesh - Send the
Mesh Data
Note:
MeshDataConstructormirrors geometry along theX-axisdue to theUnitycoordinate system. This approach avoids any transformation on theUnityside. But if you want to use this geometry in different 3D engines at the same time, it takes a trade-off to decide where to manually mirror it back.
Once you've configured your unity project:
Create a class to store mesh data
UnityMesh
usingUnityEngine;usingVertexFlow.Core.Models;publicclassUnityMesh:MeshData<Vector3>{}
Implement the
MeshCreatorclassMeshCreator
usingUnityEngine;publicclassMeshCreator{privatereadonlyMaterial_meshMaterial;privatereadonlyTransform_meshContainer;publicMeshCreator(TransformmeshContainer,MaterialmeshMaterial){_meshMaterial=meshMaterial;_meshContainer=meshContainer;// Rotate due to Revit coordinate system._meshContainer.rotation=Quaternion.Euler(-90,0,0);}publicMeshFilterCreateMesh(UnityMeshmeshData){vargameObj=newGameObject(meshData.Id);// Sets mesh data to the game object.varmeshFilter=gameObj.AddComponent<MeshFilter>();SetMeshData(meshFilter.mesh,meshData);// Sets default material.gameObj.AddComponent<MeshRenderer>().sharedMaterial=_meshMaterial;// Sets parent to the game object.gameObj.transform.SetParent(_meshContainer,false);returnmeshFilter;}publicvoidRebuildMesh(Meshmesh,UnityMeshdata){mesh.Clear();SetMeshData(mesh,data);}privatevoidSetMeshData(Meshmesh,UnityMeshdata){mesh.vertices=data.Vertices;mesh.triangles=data.Triangles;if(data.Normals.Length==data.Vertices.Length){mesh.normals=data.Normals;}else{mesh.RecalculateNormals();}mesh.Optimize();}}
Implement the
UnityMeshProviderclassUnityMeshProvider
usingSystem;usingSystem.Collections.Generic;usingSystem.Threading.Tasks;usingUnityEngine;usingVertexFlow.SDK.Extensions;usingVertexFlow.SDK.Interfaces;usingVertexFlow.SDK.Listeners;usingVertexFlow.SDK.Listeners.Interfaces;publicclassUnityMeshProvider:IDisposable{privatereadonlyMeshCreator_meshCreator;privatereadonlyDictionary<string,MeshFilter>_meshes;privatereadonlyIMeshStore<UnityMesh>_meshStore;privatereadonlyIMeshFlowListener_meshFlowListener;privatereadonlyVertexFlow.SDK.VertexFlow_vertexFlow;publicUnityMeshProvider(TransformmeshContainer,MaterialdefaultMaterial){_meshes=newDictionary<string,MeshFilter>();_meshCreator=newMeshCreator(meshContainer,defaultMaterial);_vertexFlow=newVertexFlow.SDK.VertexFlow("https://localhost:5001");_meshStore=_vertexFlow.CreateMeshStore<UnityMesh>();_meshFlowListener=_vertexFlow.CreateMeshFlowListener().WithStore(_meshStore).OnMeshCreated(CreateMesh).OnMeshUpdated(RebuildMesh);}publicasyncTaskStartMeshFlowListenerAsync(){await_meshFlowListener.StartAsync();}publicasyncTaskLoadAllMeshesAsync(){foreach(varmeshDatainawait_meshStore.GetAllAsync()){CreateMesh(meshData);}}publicasyncTaskStopMeshFlowListenerAsync(){await_meshFlowListener.StopAsync();}publicvoidDispose(){_meshFlowListener.Dispose();_vertexFlow.Dispose();}privatevoidCreateMesh(UnityMeshmeshData){_meshes.Add(meshData.Id,_meshCreator.CreateMesh(meshData));}privatevoidRebuildMesh(UnityMeshmeshData){_meshCreator.RebuildMesh(_meshes[meshData.Id].mesh,meshData);}}
Use the
UnityMeshProviderclass as followingApp
usingExtensions;usingUnityEngine;publicclassApp:MonoBehaviour{[SerializeField]privateMaterial_meshMaterial;[SerializeField]privateTransform_meshContainer;privateUnityMeshProvider_unityMeshProvider;privatevoidAwake(){_unityMeshProvider=newUnityMeshProvider(_meshContainer,_meshMaterial);}privatevoidOnEnable(){_unityMeshProvider.StartMeshFlowListenerAsync().Forget();}privatevoidStart(){_unityMeshProvider.LoadAllMeshesAsync().Forget();}privatevoidOnDisable(){_unityMeshProvider.StopMeshFlowListenerAsync().Forget();}privatevoidOnDestroy(){_unityMeshProvider.Dispose();}}
For Unigine, all steps are the same as for Unity, except the UnigineMesh and MeshCreator classes.
UnigineMesh
usingUnigine;usingVertexFlow.Core.Models;publicclassUnigineMesh:MeshData<vec3>{}MeshCreator
usingUnigine;namespaceUnigineApp{publicclassMeshCreator{privatereadonlyvec3_transformVertex;publicMeshCreator(){_transformVertex=newvec3(-0.1f,0.1f,0.1f);}publicObjectMeshDynamicCreateMesh(UnigineMeshmeshData){varmesh=newObjectMeshDynamic{MeshName=meshData.Id};mesh.SetMaterial("mesh_base","*");SetMeshData(mesh,meshData);returnmesh;}publicvoidRebuildMesh(ObjectMeshDynamicmesh,UnigineMeshdata){mesh.ClearVertex();mesh.ClearIndices();mesh.ClearSurfaces();SetMeshData(mesh,data);mesh.FlushVertex();mesh.FlushIndices();}privatevoidSetMeshData(ObjectMeshDynamicmesh,UnigineMeshdata){// Allocate space in index and vertex buffers.mesh.AllocateIndices(data.Triangles.Length);mesh.AllocateVertex(data.Vertices.Length);// Add vertices.for(vari=0;i<data.Vertices.Length;i++){mesh.AddVertex(data.Vertices[i]*_transformVertex);}// Add indices for created vertices.for(vari=data.Triangles.Length-1;i>=0;i--){mesh.AddIndex(data.Triangles[i]);}// Calculate tangent vectors.mesh.UpdateTangents();// Optimize vertex and index buffers, if necessary.mesh.UpdateIndices();// Calculate a mesh bounding box.mesh.UpdateBounds();}}}Note: Unlike Unity, when the Unigine app's Main method is invoked, SynchronizationContext.Current will return null. That means that when you invoke an asynchronous method, it will not return to the main thread, but the
Unigine APIhas to run from the main thread. You will find more information here.
You will find two custom json serializers in the
benchmarks/VertexFlow.SDK.Benchmark/JsonSerializersdirectory.
You can optimize performance and memory usage by writing a custom json serializer.
You will find all benchmarks in the
benchmarks/VertexFlow.SDK.Benchmarkproject.
The benchmarks were run on the dataset with realistic mesh data. The tests compare the JsonSerializer in the Newtonsoft.Json namespace (used by default) with two custom serializers based on JsonSerializer in the System.Text.Json namespace.
Environment
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19041.1165 (2004/May2020Update/20H1) Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores .NET SDK=5.0.301 [Host] : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT DefaultJob : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT
| Method | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | | ------------------------------- |---------:|---------:|---------:|------:|----------:|----------:|----------:|----------:| | Newtonsoft_Stream | 150.1 ms | 2.88 ms | 2.41 ms | 1.00 | 3000.0000 | 1000.0000 | - | 24 MB | | SystemTextJson_Stream | 114.2 ms | 2.02 ms | 1.89 ms | 0.76 | 400.0000 | 200.0000 | - | 4 MB | | SystemTextJson_RecyclableStream | 113.9 ms | 1.61 ms | 1.51 ms | 0.76 | 400.0000 | 200.0000 | - | 4 MB |
| Method | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | |-------------------------------- |---------:|---------:|---------:|------:|----------:|----------:|----------:|----------:| | Newtonsoft_Stream | 933.5 ms | 7.35 ms | 6.88 ms | 1.00 | 2000.0000 | 1000.0000 | 1000.0000 | 17 MB | | SystemTextJson_Stream | 934.8 ms | 11.53 ms | 10.22 ms | 1.00 | 1000.0000 | 1000.0000 | 1000.0000 | 7 MB | | SystemTextJson_RecyclableStream | 931.6 ms | 6.41 ms | 5.68 ms | 1.00 | - | - | - | 1 MB |
Note: Make sure your database contains data before running the
VertexFlow.SDK.Benchmarkproject.
Usage is provided under the MIT License.

