Unit Conversion and Exporting to IFC

Ilya Kovaldov

April 30, 2020

Sometimes members have questions about unit conversion when exporting to an .ifc file using the TB_IfcExport module. This article contains an example of working with units and exporting them to an .ifc file.

Create a Project Unit Assignment

The OdIfcExportImpl::createDefaultUnits() function creates an IfcUnitAssignment. This is a long list of units that translates from internal BimRv units to known units.

Conversion-Based Unit

For example, consider the algorithm for creating a conversion-based unit using length units.

1. Get the internal units using UnitsTracking Manager and FormatOptions:

OdBmUnitsTrackingPtr pUnitsTracking = m_database->getAppInfo(OdBm::ManagerType::UnitsTracking);
OdBmUnitsElemPtr pUnitsElem = pUnitsTracking->getUnitsElemId().safeOpenObject();
OdBmAUnitsPtr pAUnits = pUnitsElem->getUnits();
OdBmFormatOptionsPtrArray aFormatOptions;
pAUnits->getFormatOptionsArr(aFormatOptions);

OdDAIObjectId lenSIBaseUnit;
{
  bool lenConversionBased = false;
  bool lenUseDefault = false;

  OdAnsiString lenConvName;
  OdAnsiString lenUnitType = "LENGTHUNIT";
  OdAnsiString lenPrefix;
  OdAnsiString lenUnitName = "METRE";

  OdBmFormatOptionsPtr pFormatOptions = aFormatOptions[OdBm::UnitType::UT_Length];

  switch (pFormatOptions->getDisplayUnits())
  {
  case OdBm::DisplayUnitType::DUT_METERS:
  case OdBm::DisplayUnitType::DUT_METERS_CENTIMETERS:
    break;
  case OdBm::DisplayUnitType::DUT_CENTIMETERS:
    lenPrefix = "CENTI";
    break;
  case OdBm::DisplayUnitType::DUT_MILLIMETERS:
    lenPrefix = "MILLI";
    break;
  case OdBm::DisplayUnitType::DUT_DECIMAL_FEET:
  case OdBm::DisplayUnitType::DUT_FEET_FRACTIONAL_INCHES:
  {
    if (exportToCOBIE)
      lenConvName = "foot";
    else
      lenConvName = "FOOT";
    lenConversionBased = true;
  }
  break;
  case OdBm::DisplayUnitType::DUT_FRACTIONAL_INCHES:
  case OdBm::DisplayUnitType::DUT_DECIMAL_INCHES:
  {
    if (exportToCOBIE)
      lenConvName = "inch";
    else
      lenConvName = "INCH";
  }
  lenConversionBased = true;
  break;
  default:
  {
    //Couldn't find display unit type conversion -- assuming foot
    if (exportToCOBIE)
      lenConvName = "foot";
    else
      lenConvName = "FOOT";
    lenConversionBased = true;
    lenUseDefault = true;
  }
  break;
  }

2. Create a base SI unit instance and SI unit with prefix instance if necessary:

OdDAIObjectId lenSIUnit = OdInstanceExporter::createSIUnit(m_model, lenUnitType, lenPrefix, 
    lenUnitName);
  if (lenPrefix.isEmpty())
    lenSIBaseUnit = lenSIUnit;
  else
    lenSIBaseUnit = OdInstanceExporter::createSIUnit(m_model, lenUnitType, NULL, lenUnitName);

3. Get the length scale factor using BmUnitUtils:

double lengthScaleFactor = OdBmUnitUtils::convertFromInternalUnits(1.0, lenUseDefault ? 
  OdBm::DisplayUnitType::DUT_DECIMAL_FEET : pFormatOptions->getDisplayUnits());

// If BIM Units differ from SI Units, for example Inches or Foots using for length measure, creating // Base Conversion Unit. First getting Conversion Scale Factor using OdBmUnitUtils:

if (lenConversionBased)
{
  double lengthSIScaleFactor = OdBmUnitUtils::convertFromInternalUnits(1.0, 
    OdBm::DisplayUnitType::DUT_METERS) / lengthScaleFactor;

4. Create a dimensional dexponents instance (one dimension for length):

OdDAIObjectId lenDims = OdInstanceExporter::createDimensionalExponents(m_model, 1, 0, 0,
   0, 0, 0, 0); 

5. Create a measure-with unit, which is an object that determines which unit to convert (SI length unit) and how to convert (lengthSIScaleFactor ratio):

ODIFC_CREATE_AS_RATIO_MEASURE(supVal, lengthSIScaleFactor)
OdDAIObjectId lenConvFactor = OdInstanceExporter::createMeasureWithUnit(m_model, supVal, lenSIUnit);

6. Create a unit of measure based on conversions whose attributes are a dimensional exponent object and measure-with unit object, created before.


lenSIUnit = OdInstanceExporter::createConversionBasedUnit(m_model, lenDims, lenUnitType, 
  lenConvName, lenConvFactor);

7. Add the SI unit object to a UnitSet array:

unitSet.push_back(lenSIUnit);

8. Add the SI unit and scale factors to a units cache:

OdExporterCacheManager::getUnitsCache()->addUnit(OdBm::UnitType::UT_Length,
  lenSIUnit,lengthScaleFactor, 0.0);

Expression Unit

For example, consider the algorithm for creating an expression unit using mass density units.

1. Create a mass SI unit:

OdDAIObjectId massSIUnit;
{
  massSIUnit = createSIUnit(OdBm::UnitType::UT_Mass, "MASSUNIT", "GRAM", "KILO", 
    OdBm::DisplayUnitType::DUT_UNDEFINED);
  unitSet.push_back(massSIUnit);

}

2. Create derived unit elements using kilograms to the power of 1 and meters to the power of -3. Attributes of the derived unit elements are SI unit objects created above: mass SI Unit and length SI unit.

OdDAIObjectIds elements;
elements.push_back(OdInstanceExporter::createDerivedUnitElement(m_model, massSIUnit, 1));
elements.push_back(OdInstanceExporter::createDerivedUnitElement(m_model, lenSIBaseUnit, -3));

3. Create a kg/(m^3) derived unit using derived unit elements:

OdDAIObjectId massDensityUnit = OdInstanceExporter::createDerivedUnit(m_model, elements, "MASSDENSITYUNIT", NULL);

4. Add the created derived unit and scale factor to project a UnitSet array into the units cache:

unitSet.push_back(massDensityUnit);
double massDensityFactor = OdBmUnitUtils::convertFromInternalUnits(1.0, OdBm::DisplayUnitType::DUT_KILOGRAMS_PER_CUBIC_METER);
OdExporterCacheManager::getUnitsCache()->addUnit(OdBm::UnitType::UT_MassDensity, massDensityUnit, 
  massDensityFactor, 0.0);

5. Repeat in the same way for filling other units: area, volume, plane angle, time, frequency, temperature, etc.

6. Finally, after filling the UnitSet array, create a project UnitAssignment instance:

OdInstanceExporter::createUnitAssignment(m_model, unitSet);

Use UnitsCache and UnitUtils for Unit Conversion in the Export Process

In the process of exporting objects, convert the units of measurement obtained from BimRv API to IFC project units. A cache and utilities for using this cache are created for these purposes. Consider their application in the next example of a level export.

// …
// some code to fill levels array from database 
// …

OdBmLevelPtr level = levels[ii];
double elev = level->getElevation(); // elevation in internal units

1. Convert elevation from BimRv units to IFC units using ScaleLength() util:

double elevation = OdUnitUtil::ScaleLength(elev);

2. Use the converted elevation to create local placement of a building storey and a building storey:

OdGeVector3d orig(0.0, 0.0, elevation);
OdDAIObjectId placement = OdExporterUtil::createLocalPlacement(m_model, buildingPlacement, &orig, 
  NULL, NULL);
OdDAIObjectId buildingStorey = OdInstanceExporter::createBuildingStorey(m_model, level, 
  OdExporterCacheManager::getOwnerHistoryHandle(),
  bsObjectType, placement, "ELEMENT", elevation);

Export Result

As a result of the export, a file contains the following lines.

1. Length units (METRE in SI and FOOT in BimRv database):

#21=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#22=IFCDIMENSIONALEXPONENTS(1,0,0,0,0,0,0);
#23=IFCMEASUREWITHUNIT(IFCRATIOMEASURE(0.30480000000000035),#21);
#24=IFCCONVERSIONBASEDUNIT(#22,.LENGTHUNIT.,'FOOT',#23);

2. Mass and mass density units:

#37=IFCSIUNIT(*,.MASSUNIT.,.KILO.,.GRAM.);
#38=IFCDERIVEDUNITELEMENT(#37,1);
#39=IFCDERIVEDUNITELEMENT(#21,-3);
#40=IFCDERIVEDUNIT((#38,#39),.MASSDENSITYUNIT.,'');

3. Project units assignment:

#73=IFCUNITASSIGNMENT((#24,#28,#32,#36,#37,#40,#41,#42,#44,#48,#52,#53,#54,#55,#56,#57,#58,#59,#64,#67,#68,#72));

4. Building storey:

#76=IFCCARTESIANPOINT((0.00000000000000000,0.00000000000000000,-800.00000000000000));
#77=IFCAXIS2PLACEMENT3D(#76,$,$);
#78=IFCLOCALPLACEMENT(#16,#77);
#79=IFCBUILDINGSTOREY('1i_YYDONP3AgUe36R0EAL1',#20,'Foundation','','8mm Head',#78,$,'Foundation',.ELEMENT.,-800.00000000000068);