Use ODA SDKs with Unity (Part 2 of 2)

Alexander Borovikov

June 03, 2022

This is the second article in a series of articles about using ODA SDKs with Unity. In the previous article we prepared the ODA environment, read a .dwg file, and showed information from the file.

In this article we will visualize a .dwg file using the Unity engine to read a file, import mesh data from OdDbSolid objects, and visualize the data in Unity.

Read a File Containing Solids

In this article we will use a file named 8_Reductor Assembly.dwg that contains solids. Here it is visualized in ODA Viewer:

 

solids

 

The file contains 27 solids, and geometry from these solids can be converted to meshes.

Convert OdDbSolid to UnityEngine.Mesh

First collect the necessary entities from the .dwg file. Below is the code that reads a .dwg file, iterates through model space, and collects all OdDbSolid objects:

private OdDbDatabase ReadFile(string sFile)
{
  ExHostAppServices HostApp = new ExHostAppServices();
  return HostApp.readFile(
      sFile, true, false, FileShareMode.kShareDenyNo, "");
}

private OdDb3dSolidPtrArray CollectSolids(OdDbDatabase pDb)
{
  OdDb3dSolidPtrArray aSolids = new OdDb3dSolidPtrArray();

  OdDbBlockTableRecord modelSpace = pDb.getModelSpaceId()
      .safeOpenObject(OpenMode.kForRead) as OdDbBlockTableRecord;
  for (OdDbObjectIterator iterator = modelSpace.newIterator();
    !iterator.done();
    iterator.step())
  {
    OdDbEntity entity = iterator.entity();
    if (entity != null && entity.isKindOf(OdDb3dSolid.desc()))
    {
      aSolids.Add(entity as OdDb3dSolid);
    }
  }

  return aSolids;
}

The TD_Db.oddbGetObjectMesh method is used to convert OdDbSolid objects to mesh data (array of vertices and array of indices of vertices). See the Convert 3D Objects to a Faceted Mesh documentation article (login required) for more details.

private List<Mesh> ConvertToMesh(OdDb3dSolidPtrArray aSolids)
{
  List<Mesh> meshes = new List<Mesh>();

  MeshFaceterSettings convertionSettings = new MeshFaceterSettings
  {
    faceterDevSurface = 0.0,
    faceterDevNormal = 36,
    faceterMaxEdgeLength = 100000,
    faceterGridRatio = 0.0,
    faceterMaxGrid = 10000,
    faceterMinUGrid = 0,
    faceterMinVGrid = 0,
    faceterMeshType = 0
  };

  foreach (OdDb3dSolid solid in aSolids)
  {
    OdGePoint3dArray vertices = new OdGePoint3dArray();
    OdInt32Array indices = new OdInt32Array();
    OdGiFaceData facesData = new OdGiFaceData();

    TD_Db.oddbGetObjectMesh(
        solid, convertionSettings, vertices, indices, out facesData);
    meshes.Add(new Mesh()
    {
      vertices = ConvertToVectorArray(vertices),
      triangles = RemoveExtraIndices(indices)
    });
  }

  return meshes;
}

An array of vertices and an array of indices of vertices must be additionally converted from ODA data representations to Unity data representations.

private Vector3[] ConvertToVectorArray(OdGePoint3dArray vertices)
{
  return vertices.Select(pnt => new Vector3(
      Convert.ToSingle(pnt.x), Convert.ToSingle(pnt.z), 
          Convert.ToSingle(pnt.y))).ToArray();
}

 /* Array of faces returned by TD_Db.oddbGetObjectMesh contains
  * sets of data in the following format: n, V1, V2, .. , Vn,
  * where n is the number of vertices a face has.
  * 
  * For example, facesIndices may be as follow:
  * 3 1 6 8 3 4 5 7 3 2 1 3 ...
  * Currently, TD_Db.oddbGetObjectMesh() returns faces
  * only with 3 vertices.
  * 
  * UnityEngine.Mesh object takes indices of vertices
  * in reverse order and without number of indices in a sequence.
  * So, above example of indices must be converted to:
  *   8 6 1   7 5 4   3 1 2 ...
  * Sequence of triplets doesn't matter.
  */
private int[] RemoveExtraIndices(OdInt32Array facesIndices)
{
  // skip every 4th element and reverse the array
  return facesIndices.Where((iFaceIdx, iArrIndex) =>
      (iArrIndex % 4) != 0).Reverse().ToArray();
}

For every solid, a GameObject must be created. It must have two components: UnityEngine.MeshRenderer and UnityEngine.MeshFilter. All GameObjects are stored in an aGameObjects field.

private List<GameObject> aGameObjects;

private void ShowMesh(List<Mesh> aMesh)
{
  foreach (Mesh mesh in aMesh)
  {
    mesh.RecalculateNormals();
    mesh.RecalculateBounds();

    GameObject meshObject = new GameObject();
    var meshRenderer = meshObject.AddComponent<MeshRenderer>();

    var filter = meshObject.AddComponent<MeshFilter>();
    filter.mesh = mesh;

    aGameObjects.Add(meshObject);
  }
}

The color of an entity can be defined by an RGB value (byColor), index (byACI, byDGNIndex), color of a layer (byLayer), color of a block (byBlock), or one of the other color options. See the Methods of the Color Definition documentation article (login required) for more details. This example fills meshes with color in two cases: if a color is defined by a color index or if it uses the color of a layer.

private void ApplyColors(OdDb3dSolidPtrArray aSolids)
{
  for (int i = 0; i < aGameObjects.Count; i++)
  {
    GameObject meshObject = aGameObjects[i];
    Material material =
        meshObject.GetComponent<MeshRenderer>().material;
    material.color = GetColorFromSolid(aSolids[i]);
  }
}

private Color GetColorFromSolid(OdDb3dSolid solid)
{
  OdCmColor solidColor = solid.color();
  switch (solidColor.colorMethod())
  {
    case OdCmEntityColor.Items_ColorMethod.kByACI:
      {
        return new Color32(
            solidColor.red(), solidColor.green(),
            solidColor.blue(), 255);
      }
    case OdCmEntityColor.Items_ColorMethod.kByLayer:
      {
        OdDbLayerTableRecord layer =
            solid.layerId().safeOpenObject() as OdDbLayerTableRecord;
        OdCmColor layerColor = layer.color();
        return new Color32(layerColor.red(),layerColor.green(),
            layerColor.blue(), 255);
      }
    default:
      {
        Debug.LogFormat(
            "Filling a mesh by color method {0} is not implemented",
            solidColor.colorMethod().ToString());
        break;
      }
  }
  return new Color32();
}

Visualize Meshes in Unity

 

The following image shows the result of program execution.

 

meshes

 

Full Code

using System.Collections.Generic;
using UnityEngine;
using Teigha.Core;
using Teigha.TD;
using System;
using System.Linq;

class OdExSystemServices : RxSystemServicesImpl
{
  public OdExSystemServices()
  {
    Teigha.Core.Globals.odActivate(ActivationData.userInfo, ActivationData.userSignature);
  }
}

public class OdUnityEx : MonoBehaviour
{
  private List<GameObject> aGameObjects;

  // Start is called before the first frame update
  void Start()
  {
    aGameObjects = new List<GameObject>();

    MemoryManager mMan = MemoryManager.GetMemoryManager();
    MemoryTransaction mStartTrans = mMan.StartTransaction();
    try
    {
      OdExSystemServices systemServices = new OdExSystemServices();
      TD_Db.odInitialize(systemServices);

      LoadAndVisualizeSolids();
    }
    finally
    {
      mMan.StopTransaction(mStartTrans);
    }

    TD_Db.odUninitialize();
    Teigha.Core.Helpers.odUninit();
  }

  private void LoadAndVisualizeSolids()
  {
    string sFile = "Assets\\8_Reductor Assembly.dwg";
    OdDbDatabase pDb = ReadFile(sFile);

    OdDb3dSolidPtrArray aSolids = CollectSolids(pDb);
    List<Mesh> aMesh = ConvertToMesh(aSolids);

    ShowMesh(aMesh);
    ApplyColors(aSolids);
  }

  private OdDbDatabase ReadFile(string sFile)
  {
    ExHostAppServices HostApp = new ExHostAppServices();
    return HostApp.readFile(sFile, true, false, FileShareMode.kShareDenyNo, "");
  }

  private OdDb3dSolidPtrArray CollectSolids(OdDbDatabase pDb)
  {
    OdDb3dSolidPtrArray aSolids = new OdDb3dSolidPtrArray();

    OdDbBlockTableRecord modelSpace = pDb.getModelSpaceId().safeOpenObject(OpenMode.kForRead) as OdDbBlockTableRecord;
    for (OdDbObjectIterator iterator = modelSpace.newIterator(); !iterator.done(); iterator.step())
    {
      OdDbEntity entity = iterator.entity();
      if (entity != null && entity.isKindOf(OdDb3dSolid.desc()))
      {
        aSolids.Add(entity as OdDb3dSolid);
      }
    }

    return aSolids;
  }

  private List<Mesh> ConvertToMesh(OdDb3dSolidPtrArray aSolids)
  {
    List<Mesh> meshes = new List<Mesh>();

    MeshFaceterSettings convertionSettings = new MeshFaceterSettings
    {
      faceterDevSurface = 0.0,
      faceterDevNormal = 36,
      faceterMaxEdgeLength = 100000,
      faceterGridRatio = 0.0,
      faceterMaxGrid = 10000,
      faceterMinUGrid = 0,
      faceterMinVGrid = 0,
      faceterMeshType = 0
    };

    foreach (OdDb3dSolid solid in aSolids)
    {
      OdGePoint3dArray vertices = new OdGePoint3dArray();
      OdInt32Array indices = new OdInt32Array();
      OdGiFaceData facesData = new OdGiFaceData();

      TD_Db.oddbGetObjectMesh(solid, convertionSettings, vertices, indices, out facesData);
      meshes.Add(new Mesh()
      {
        vertices = ConvertToVectorArray(vertices),
        triangles = RemoveExtraIndices(indices)
      });
    }

    return meshes;
  }

  private Vector3[] ConvertToVectorArray(OdGePoint3dArray vertices)
  {
    return vertices.Select(pnt => new Vector3(
        Convert.ToSingle(pnt.x), Convert.ToSingle(pnt.z), Convert.ToSingle(pnt.y)))
            .ToArray();
  }

  /* Array of faces returned by TD_Db.oddbGetObjectMesh contains
   * sets of data in the following format: n, V1, V2, .. , Vn,
   * where n is the number of vertices a face has.
   * 
   * For example, facesIndices may be as follow:
   * 3 1 6 8 3 4 5 7 3 2 1 3 ...
   * Currently, TD_Db.oddbGetObjectMesh() returns faces only with 3 vertices.
   * 
   * UnityEngine.Mesh object takes indices of vertices in reverse order
   * and without number of indices in a sequence. So, above example of
   * indices must be converted to:
   *   8 6 1   7 5 4   3 1 2 ...
   * Sequence of triplets doesn't matter.
   */
  private int[] RemoveExtraIndices(OdInt32Array facesIndices)
  {
    return facesIndices.Where((iFaceIdx, iArrIndex) => (iArrIndex % 4) != 0).Reverse().ToArray();
  }

  private void ShowMesh(List<Mesh> aMesh)
  {
    foreach (Mesh mesh in aMesh)
    {
      mesh.RecalculateNormals();
      mesh.RecalculateBounds();

      GameObject meshObject = new GameObject();
      var meshRenderer = meshObject.AddComponent<MeshRenderer>();

      var filter = meshObject.AddComponent<MeshFilter>();
      filter.mesh = mesh;

      aGameObjects.Add(meshObject);
    }
  }

  private void ApplyColors(OdDb3dSolidPtrArray aSolids)
  {
    for (int i = 0; i < aGameObjects.Count; i++)
    {
      GameObject meshObject = aGameObjects[i];
      Material material = meshObject.GetComponent<MeshRenderer>().material;
      material.color = GetColorFromSolid(aSolids[i]);
    }
  }

  private Color GetColorFromSolid(OdDb3dSolid solid)
  {
    OdCmColor solidColor = solid.color();
    switch (solidColor.colorMethod())
    {
      case OdCmEntityColor.Items_ColorMethod.kByACI:
        {
          return new Color32(solidColor.red(), solidColor.green(), solidColor.blue(), 255);
        }
      case OdCmEntityColor.Items_ColorMethod.kByLayer:
        {
          OdDbLayerTableRecord layer = solid.layerId().safeOpenObject() as OdDbLayerTableRecord;
          OdCmColor layerColor = layer.color();
          return new Color32(layerColor.red(), layerColor.green(), layerColor.blue(), 255);
        }
      default:
        {
          Debug.LogFormat("Filling a mesh by color method {0} is not implemented",
              solidColor.colorMethod().ToString());
          break;
        }
    }
    return new Color32();
  }
}