Skip to content

Repository files navigation

Vertex Flow

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

Table of Contents

About

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.

How It Works

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.

HowItWorks

The first integration is with Revit, but you can integrate with any Autodesk products, as well as other industry tools.

Installation

Azure Cosmos DB Emulator

  1. Install the latest version of Azure Cosmos DB Emulator
  2. Start the Azure Cosmos DB Emulator

Revit Addin

  1. Install Revit 2022
  2. Open the VertexFlow.addin file and replace the Assembly section with your output directory
  3. Open the VertexFlow.RevitAddin.csproj file and replace the DestinationFolder in the target AfterBuild section

Once you build the solution and launch Revit, you should find the Vertex Flow pannel in the Add-Ins tab.

Show Screenshot

RevitVertexFlowAddinPanel

Note: If you don't want to install Revit, just unload the src/VertexFlow.RevitAddin project from the solution.

Unity Plugin

  1. Create Assembly Definition in the Assets/Plugins/VertexFlow folder with VertexFlow name

  2. Copy following libraries to the Assets/Plugins/VertexFlow/libs directory

    • VertexFlow.Core.dll
    • VertexFlow.SDK.dll
    • VertexFlow.SDK.Extensions.dll
    • VertexFlow.SDK.Listeners.dll
  3. Create signalr.ps1 file in the Assets/Plugins/VertexFlow/libs folder

    signalr.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
  4. Check if NuGet CLI is installed locally

  5. Execute the following command in PowerShell from the Assets/Plugins/VertexFlow/libs directory to import the target .dll files

    ./signalr.ps1
Additional Links

Examples

You will find a complete example in the samples/VertexFlow.SDK.Sample project.

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 Vector3 from VertexFlow.Core.Structs

Send Mesh Data

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(){
...}

Get Mesh Data

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.");}}

Listen Mesh Data

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 & MeshUpdated will provide only the id of the mesh as a parameter, while OnMeshCreated & OnMeshUpdated will provide the full mesh data.

Custom Json Serializer

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/JsonSerializers directory.

How To Use

Make sure the src/VertexFlow.WebAPI project is running to be able to transfer mesh data.

Export Geometry From Revit

You will find a complete example in the src/VertexFlow.RevitAddin project.

  1. Create a class to store mesh data
  2. Extract Mesh from an Element
  3. Construct Mesh Data from the extracted Mesh
  4. Send the Mesh Data

Note:MeshDataConstructor mirrors geometry along the X-axis due to the Unity coordinate system. This approach avoids any transformation on the Unity side. 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.

Import Mesh To Unity

Once you've configured your unity project:

  1. Create a class to store mesh data

    UnityMesh
    usingUnityEngine;usingVertexFlow.Core.Models;publicclassUnityMesh:MeshData<Vector3>{}
  2. Implement the MeshCreator class

    MeshCreator
    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();}}
  3. Implement the UnityMeshProvider class

    UnityMeshProvider
    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);}}
  4. Use the UnityMeshProvider class as following

    App
    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();}}

Import Mesh To Unigine

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 API has to run from the main thread. You will find more information here.

Optimizations

You will find two custom json serializers in the benchmarks/VertexFlow.SDK.Benchmark/JsonSerializers directory.

You can optimize performance and memory usage by writing a custom json serializer.

Benchmarks

You will find all benchmarks in the benchmarks/VertexFlow.SDK.Benchmark project.

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

Get All Async

| 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 |

Send All Async

| 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.Benchmark project.

License

Usage is provided under the MIT License.

About

BIM to real-time 3D

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages