OBJ Model Exporter with Transparency Support

Tuesday evening the moon was full, the rain finally eased up a bit, the sky became completely clear, and I spent a couple of hours outside on a hill with a beautiful view, a group of friends, and a nice hot fire.

Full moon

On Wednesday I finally got to play hockey again with my Autodesk colleagues after a long period of missing out on that. I hope all US Americans enjoyed a nice Independence Day.

The next days are filled with team meetings here in Neuchâtel, followed by our yearly Autodesk football tournament, taking place in Switzerland this year for the first time. Unfortunately, the date for that was fixed very late, and I had already made a prior appointment for a mountain tour this coming weekend, so I will not be able to participate unless the weather turns really bad and prohibits mountaineering. It's nice to have alternatives :-)

The OBJ model exporter I am working on went through an unexpected number of iterations from the base functionality through the first version, then adding support for colour and processing multiple solids per BIM element.

The last update forced me to realise that I really do need to support transparency as well, just as Rudolf Honke initially suggested.

Transparency in OBJ and Revit

Happily, the OBJ format and its associated MTL material libraries (details) do support this.

In MTL, the transparency is written using either a 'd' or a 'Tr' statement with values ranging from 0.0 to 1.0, where 1.0 is opaque. This is the record generated for the glass in the basic sample model:

newmtl 4B475A69
Ka 0.27734375 0.3515625 0.41015625
Kd 0.27734375 0.3515625 0.41015625
d 0.25

Revit material transparency values lie between 0 and 100, where 100 is completely transparent and 0 opaque. Here is the code that I am currently using to define a material library entry including transparency:

  const string _mtl_newmtl_d 
    = "newmtl {0}\r\n"
    + "Ka {1} {2} {3}\r\n"
    + "Kd {1} {2} {3}\r\n"
    + "d {4}";
 
  s.WriteLine( _mtl_newmtl_d, 
    name, 
    color.Red / 256.0, 
    color.Green / 256.0,
    color.Blue / 256.0, 
    (100 - transparency) / 100.0 );

Here is an impression of the resulting view in the Bonzai Engine driven online model viewer, which was the only viewer I was able to find so far supporting transparency:

Sample model with transparency

Mingling Triangles, Colours and Transparency

The first take of the exporter used a simple list of integers representing triples of vertex indices to store the triangular facets to export.

When I decided to add colour support to the exporter and realised that the mutual ordering of triangles and colour definitions for subsequent faces needs preserving, I decided to store the colours in the same list.

Since the Revit colour red, green and blue values are encoded in byte data, i.e. only range from 0 to 255, I can squeeze all three of them into a single integer like this:

  static int ColorToInt( Color color )
  {
    return ( (int) color.Red ) << 16
      | ( (int) color.Green ) << 8
      | (int) color.Blue;
  }

For each colour switch, I create a triple of integers using a -1 marker, followed by one integer holding the colour value and a final zero-valued integer to keep up the multiples of three.

When adding support for transparency, I made use of the fact that the maximum transparency value returned by Revit is 100. Left shifting that value by 24 bits results in 100 * 2^24 = 1677721600, which is still small enough to fit into a signed integer, whose maximum value is 2^32 - 1 = 4294967295. To put this shorter, you might also say that I make use of the fact that 100 is smaller than 127 = 2^7 - 1 :-)

I can thus encode transparency as well as colour in a single signed integer like this, including some assertions to ensure my thinking is not completely off track and Revit is sticking to its agreements:

  public static int ColorTransparencyToInt( 
    Color color,
    int transparency )
  {
    Debug.Assert( 0 <= transparency,
      "expected non-negative transparency" );
 
    Debug.Assert( 100 >= transparency,
      "expected transparency between 0 and 100" );
 
    uint trgb = ( (uint) transparency << 24 )
      | (uint) ColorToInt( color );
 
    Debug.Assert( int.MaxValue > trgb,
      "expected trgb smaller than max int" );
 
    return (int) trgb;
  }

These operations are reversed to extract the colour and transparency from the encoded integer like this:

  static Color IntToColor( int rgb )
  {
    return new Color(
      (byte) ( ( rgb & 0xFF0000 ) >> 16 ),
      (byte) ( ( rgb & 0xFF00 ) >> 8 ),
      (byte) ( rgb & 0xFF ) );
  }
 
  public static Color IntToColorTransparency( 
    int trgb, 
    out int transparency )
  {
    transparency = (int) ( ( ( (uint) trgb ) 
      & 0xFF000000 ) >> 24 );
 
    return IntToColor( trgb );
  }

Since the materials referenced by the OBJ file need a name, I use a similar technique to generate that as well:

  static string ColorString( Color color )
  {
    return color.Red.ToString( "X2" )
      + color.Green.ToString( "X2" )
      + color.Blue.ToString( "X2" );
  }
 
  public static string ColorTransparencyString(
    Color color,
    int transparency )
  {
    return transparency.ToString( "X2" )
      + ColorString( color );
  }

To find out what other exciting things happen to the data on its way out of the Revit model into the OBJ file, please have a look at the source code yourself.

Here is ObjExport3.zip including the entire source code, Visual Studio solution and add-in manifest for the updated OBJ exporter version 3 including transparency support.

As far as I can tell, that concludes the OBJ export effort for now. Next steps may include looking into viewing this on a mobile device and getting it uploaded to the cloud to make it available there. I did define an abstract interface for the exporter, albeit very simple, in order to be able to replace the implementation to address other targets. The future will tell where this will lead us.

Getting Started with the Revit 2013 API

Most of the suggestions provided for getting started with the Revit 2012 API and preparing for a hands on Revit API training are still valid for Revit 2013 as well.

Still, for the sake of convenience and completeness, Mikako Harada updated the various pointers to the available materials and published an overview for getting started with the Revit 2013 API that is well worth taking a gander at.