Skip to content

LibraStack/VertexFlow

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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 $outDir
    foreach ($p in $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.

using VertexFlow.Core.Models;

public struct CustomVector3
{
    public float X { get; set; }
    public float Y { get; set; }
    public float Z { get; set; }
}

public class CustomMesh : 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.

static async Task Main()
{
    using var vertexFlow = new VertexFlow("https://localhost:5001");

    var meshFlow = vertexFlow.CreateMeshFlow<CustomMesh>();
    
    var meshData = GetMeshData();
    
    // Adds the mesh data to a database.
    // Note: Will fail if there already is a mesh data with the same id.
    await meshFlow.SendAsync(meshData);
    
    // Updates the mesh data in a database.
    // Note: Will add or replace any mesh data with the specified id.
    await meshFlow.UpdateAsync(meshData.Id, meshData);
}

private static CustomMesh GetMeshData()
{
    ...
}

Get Mesh Data

IMeshStore<TMeshData> provides methods for reading GetAsync, GetAllAsync, or deleting DeleteAsync existing meshes from a database.

static async Task Main()
{
    using var vertexFlow = new VertexFlow("https://localhost:5001");

    var meshStore = vertexFlow.CreateMeshStore<CustomMesh>();

    foreach (var mesh in await meshStore.GetAllAsync())
    {
        Console.WriteLine($"Mesh '{mesh.Id}' downloaded.");
    }
}

Listen Mesh Data

IMeshFlowListener invokes MeshCreated or MeshUpdated in response to mesh data changes in a database.

using VertexFlow.SDK.Listeners;

static async Task Main()
{
    using var vertexFlow = new VertexFlow("https://localhost:5001");
            
    using var meshFlowListener = 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.
    await meshFlowListener.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.

using System.Net.Http;
using VertexFlow.SDK.Listeners;
using VertexFlow.SDK.Extensions;

static async Task Main()
{
    using var vertexFlow = new VertexFlow("https://localhost:5001");
            
    var meshStore = vertexFlow.CreateMeshStore<CustomMesh>();

    using var meshFlowListener = await vertexFlow
        .CreateMeshFlowListener()
        .WithStore(meshStore)
        .OnMeshCreated(mesh => Console.WriteLine($"Mesh '{mesh.Id}' created."))
        .OnMeshUpdated(mesh => Console.WriteLine($"Mesh '{mesh.Id}' updated."))
        .ContinueOnCapturedContext(false)
        .StartAsync(exception => throw new HttpRequestException(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.

class CustomSerializer : IJsonSerializer
{
    public Task<HttpContent> SerializeAsync<T>(T data, CancellationToken cancellationToken)
    {
        ...
    }

    public Task<T> DeserializeAsync<T>(HttpContent httpContent, CancellationToken cancellationToken)
    {
        ...
    }
}

static class Program
{
    static async Task Main()
    {
        using var vertexFlow = new VertexFlow("https://localhost:5001");

        var customSerializer = new CustomSerializer();

        var meshFlow = vertexFlow.CreateMeshFlow<CustomMesh>(customSerializer);
        var meshStore = 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
    using UnityEngine;
    using VertexFlow.Core.Models;
    
    public class UnityMesh : MeshData<Vector3>
    {
    }
  2. Implement the MeshCreator class

    MeshCreator
    using UnityEngine;
    
    public class MeshCreator
    {
        private readonly Material _meshMaterial;
        private readonly Transform _meshContainer;
    
        public MeshCreator(Transform meshContainer, Material meshMaterial)
        {
            _meshMaterial = meshMaterial;
            _meshContainer = meshContainer;
        
            // Rotate due to Revit coordinate system.
            _meshContainer.rotation = Quaternion.Euler(-90, 0, 0);
        }
    
        public MeshFilter CreateMesh(UnityMesh meshData)
        {
            var gameObj = new GameObject(meshData.Id);
    
            // Sets mesh data to the game object.
            var meshFilter = 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);
    
            return meshFilter;
        }
    
        public void RebuildMesh(Mesh mesh, UnityMesh data)
        {
            mesh.Clear();
            SetMeshData(mesh, data);
        }
        
        private void SetMeshData(Mesh mesh, UnityMesh data)
        {
            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
    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using UnityEngine;
    using VertexFlow.SDK.Extensions;
    using VertexFlow.SDK.Interfaces;
    using VertexFlow.SDK.Listeners;
    using VertexFlow.SDK.Listeners.Interfaces;
    
    public class UnityMeshProvider : IDisposable
    {
        private readonly MeshCreator _meshCreator;
        private readonly Dictionary<string, MeshFilter> _meshes;
    
        private readonly IMeshStore<UnityMesh> _meshStore;
        private readonly IMeshFlowListener _meshFlowListener;
        private readonly VertexFlow.SDK.VertexFlow _vertexFlow;
    
        public UnityMeshProvider(Transform meshContainer, Material defaultMaterial)
        {
            _meshes = new Dictionary<string, MeshFilter>();
            _meshCreator = new MeshCreator(meshContainer, defaultMaterial);
    
            _vertexFlow = new VertexFlow.SDK.VertexFlow("https://localhost:5001");
            _meshStore = _vertexFlow.CreateMeshStore<UnityMesh>();
            _meshFlowListener = _vertexFlow
                .CreateMeshFlowListener()
                .WithStore(_meshStore)
                .OnMeshCreated(CreateMesh)
                .OnMeshUpdated(RebuildMesh);
        }
    
        public async Task StartMeshFlowListenerAsync()
        {
            await _meshFlowListener.StartAsync();
        }
    
        public async Task LoadAllMeshesAsync()
        {
            foreach (var meshData in await _meshStore.GetAllAsync())
            {
                CreateMesh(meshData);
            }
        }
    
        public async Task StopMeshFlowListenerAsync()
        {
            await _meshFlowListener.StopAsync();
        }
    
        public void Dispose()
        {
            _meshFlowListener.Dispose();
            _vertexFlow.Dispose();
        }
    
        private void CreateMesh(UnityMesh meshData)
        {
            _meshes.Add(meshData.Id, _meshCreator.CreateMesh(meshData));
        }
    
        private void RebuildMesh(UnityMesh meshData)
        {
            _meshCreator.RebuildMesh(_meshes[meshData.Id].mesh, meshData);
        }
    }
  4. Use the UnityMeshProvider class as following

    App
    using Extensions;
    using UnityEngine;
    
    public class App : MonoBehaviour
    {
        [SerializeField] private Material _meshMaterial;
        [SerializeField] private Transform _meshContainer;
    
        private UnityMeshProvider _unityMeshProvider;
    
        private void Awake()
        {
            _unityMeshProvider = new UnityMeshProvider(_meshContainer, _meshMaterial);
        }
    
        private void OnEnable()
        {
            _unityMeshProvider.StartMeshFlowListenerAsync().Forget();
        }
    
        private void Start()
        {
            _unityMeshProvider.LoadAllMeshesAsync().Forget();
        }
    
        private void OnDisable()
        {
            _unityMeshProvider.StopMeshFlowListenerAsync().Forget();
        }
    
        private void OnDestroy()
        {
            _unityMeshProvider.Dispose();
        }
    }

Import Mesh To Unigine

For Unigine, all steps are the same as for Unity, except the UnigineMesh and MeshCreator classes.

UnigineMesh
using Unigine;
using VertexFlow.Core.Models;

public class UnigineMesh : MeshData<vec3>
{
}
MeshCreator
using Unigine;

namespace UnigineApp
{
    public class MeshCreator
    {
        private readonly vec3 _transformVertex;

        public MeshCreator()
        {
            _transformVertex = new vec3(-0.1f, 0.1f, 0.1f);
        }
        
        public ObjectMeshDynamic CreateMesh(UnigineMesh meshData)
        {
            var mesh = new ObjectMeshDynamic
            {
                MeshName = meshData.Id
            };
            mesh.SetMaterial("mesh_base", "*");
		
            SetMeshData(mesh, meshData);
            
            return mesh;
        }
        
        public void RebuildMesh(ObjectMeshDynamic mesh, UnigineMesh data)
        {
            mesh.ClearVertex();
            mesh.ClearIndices();
            mesh.ClearSurfaces();

            SetMeshData(mesh, data);
		
            mesh.FlushVertex();
            mesh.FlushIndices();
        }
        
        private void SetMeshData(ObjectMeshDynamic mesh, UnigineMesh data)
        {
            // Allocate space in index and vertex buffers.
            mesh.AllocateIndices(data.Triangles.Length);
            mesh.AllocateVertex(data.Vertices.Length);
		
            // Add vertices.
            for (var i = 0; i < data.Vertices.Length; i++)
            {
                mesh.AddVertex(data.Vertices[i] * _transformVertex);
            }
		
            // Add indices for created vertices.
            for (var i = 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.

Releases

No releases published

Packages

No packages published

Languages