IFC Model Operations

Roman Chizhdenko

August 18, 2023

The IfcModelOps (IFC Model Operations) API within the IfcCore module provides a set of functions and classes that allows you to remove IfcProduct representation subtrees, clone IfcProduct subtrees inside a model and from one model to another (with nested items according to the compose relations), and merge models.

To support general operations during model content processing within common objects (IfcBuidling/IfcStorey/etc), use methods from the IfcModelOpsFiller helper class. Many of them provide the needed structure of created items: prepareModel, createBuildingStorey, addRelAggregates, addRelContainedInSpatialStructure, etc.

Merge Models

IfcModelOps supports federated models based on functionality of the following:

OdDAIObjectId OdIfc։։ModelOps::ifcCombineModels(const OdDAI::ModelPtr sourceModel, const OdDAI::ModelPtr targetModel, MergeLevel level = MergeLevel::Building); 
OdIfc::ModelOps::IfcModelProcessor::ifcCombineWith(const OdDAI::ModelPtr sourceModel, OdIfc::ModelOps::MergeLevel level);

 example, to append two or more IfcBuilding instances under an IfcSite of an existing IFC model or file:
 

wchar_t* fileNames[] = {
  L"C:/path/to/source_1.ifc",
  L"C:/path/to/source_2.ifc",
  L"C:/path/to/combined_model.ifc"
  };

  OdStaticRxObject< OdIfcHostAppServices > svcs;
  OdIfcFilePtr pIfcFile = svcs.createDatabase(kScmUndefined);

  OdResult res = pIfcFile->readFile(fileNames[0]);

  if (res != eOk)
  {
    return;
  }


  OdIfcModelPtr pSrcModel = pIfcFile->getModel();

  if (pSrcModel.isNull())
  {
    odPrintConsoleString(L"Error getting model: %s\n", fileNames[0]);
  }

  OdIfcFilePtr pIfcFile2 = svcs.createDatabase(kScmUndefined);
  res = pIfcFile2->readFile(fileNames[1]);
  if (res != eOk)
  {
    return;
  }

  // access mode is RW
  OdIfcModelPtr pTargetModel = pIfcFile2->getModel(sdaiRW);

  if (pTargetModel.isNull())
  {
    odPrintConsoleString(L"Error getting model: %s\n", fileNames[1]);
  }

  OdIfc::ModelOps::IfcModelProcessor modelProcessor(pTargetModel);

  odPrintConsoleString(L"\nMerging from %ls to %ls...\n", OdString(sourceFile).c_str(), OdString(m_ifcDestFile).c_str());

  OdDAIObjectId clonedItem = modelProcessor.ifcCombineWith(pSourceModel, OdIfc::ModelOps::MergeLevel::Site);

  OdString pathToSave = fileNames[2];

  if (pIfcFile2->writeFile(pathToSave) == eOk)
  {
    odPrintConsoleString(L"Combined model items have been written to file %ls\n", pathToSave.c_str());
  }

A more complex model merge is located in Tutorial 14 of the ExIfcTutorials example module. The file generated by the tutorial contains all data from a number of source files merged under the site level to the target model that is associated with the target file. Also, OdIfc::ModelOps::IfcModelProcessor supports changing the position of appended items by specifying the target offset in the target model's units and appropriate scale:


OdGePoint3d targetOffset = OdGePoint3d(10.0, 20.0, 30.0);
modelProcessor.setTargetOffset(targetOffset);

An important feature of the ModelOps namespace methods is support of models with various length and area units and automatic conversion of geometric representations to one common unit that is based on the target model. The functions provide units conversion when merging several models to one when an item is cloned from one model to another, etc.

Append New Content


You can process objects that are on lower levels of hierarchy, such as IfcBuildingElement, IfcBuildingElementProxy, and so on. You can clone an item from a source model to another one with the necessary destination object. Here are some useful methods of IfcModelOpsFiller and IfcModelProcessor:

OdDAIObjectId IfcModelProcessor::ifcCloneTo(const OdDAIObjectId& sourceProduct, const OdDAIObjectId& targetProduct);
OdDAIObjectId IfcModelProcessor::cloneContainedInRelAggregate(const OdDAIObjectId& sourceProduct, const OdDAIObjectId& target);

OdDAIObjectId IfcModelProcessor::cloneContainedInSpatialStructure(const OdDAIObjectId& sourceProduct, const OdDAIObjectId& target);
OdIfc::OdIfcInstancePtr IfcModelOpsFiller::createBuildingStorey(const OdString& name,
  const OdAnsiString& compositionType,  const OdDAIObjectId localPlacementId);

OdIfc::OdIfcInstancePtr OdIfcModelFiller::addRelAggregates(OdIfc::OdIfcInstancePtr pRelating, OdIfc::OdIfcInstancePtr pRelated);
OdIfc::OdIfcInstancePtr OdIfcModelFiller::addRelContainedInSpatialStructure(OdIfc::OdIfcInstancePtr pRelating, OdIfc::OdIfcInstancePtr pRelated);

For example, you can implement a clone operation of an IfcProduct with a known OdDbHandle to a new IfcBuildingStorey item in an existing model, which would be saved with new content:

 

IFC Model

 

wchar_t* fileNames[] = {
  L"C:/path/to/source.ifc",
  L"C:/path/to/target.ifc",
  };

  OdStaticRxObject< OdIfcHostAppServices > svcs;
  OdIfcFilePtr pIfcFile = svcs.createDatabase(kScmUndefined);

  OdResult res = pIfcFile->readFile(fileNames[0]);

  if (res != eOk)
  {
    return;
  }

  OdIfcModelPtr pSrcModel = pIfcFile->getModel();

  if (pSrcModel.isNull())
  {
    odPrintConsoleString(L"Error getting model: %s\n", fileNames[0]); 
    return;
  }

  OdIfcFilePtr pIfcFile2 = svcs.createDatabase(kScmUndefined);
  res = pIfcFile2->readFile(fileNames[1]);
  if (res != eOk)
  {
    return;
  }

  OdIfcModelPtr pTargetModel = pIfcFile2->getModel(sdaiRW);

  if (pTargetModel.isNull())
  {
    odPrintConsoleString(L"Error getting model: %s\n", fileNames[1]);
    return;
  }

  std::unique_ptr<IfcModelOpsFiller> modelFiller = std::unique_ptr<IfcModelOpsFiller>(new IfcModelOpsFiller(static_cast<OdIfcModelPtr>(pTargetModel)));

  OdDbHandle sourceInstance = OdDbHandle(SOURCE_ID);
  auto clonableEntity = pSrcModel->getEntityInstance(sourceInstance);

  OdIfc::OdIfcEntityPtr pClonableEntInstance = clonableEntity.openObject();
  if (pClonableEntInstance.isNull())
  {
    odPrintConsoleString(L"\nSource model doesn't contain entity with specified Id\n");
    return;
  }

  OdDAIObjectId targetEntity = OdDAIObjectId::kNull;
  auto targetEntityPresent = false;
  auto buildingPresent = true;

  OdDAIObjectId buildingId = OdDAIObjectId::kNull;

The code below detects the presence of target entities for the cloned item within an existing model, and if not found, uses methods of IfcModelFiller to create all the needed content dynamically:

if (pTargetModel->getEntityExtent("ifcbuildingstorey")->getMemberCount())
  {
    targetEntity = pTargetModel->getEntityExtent("ifcbuildingstorey")->getArray()[0];
    targetEntityPresent = true;
  } 

  // we need existing ifcownerhistory to create other objects
  if (targetEntityPresent)
  {
    OdDAIObjectId history;
    targetEntity.openObject()->getAttr("ownerhistory") >> history;

    modelFiller->setOwnerHistory(history.openObject());
  }
  // create minimal amount of model items from the plate
  else
  {
    modelFiller->prepareModel(pIfcFile2);
    buildingPresent = false;
  }

With the methods of IfcModelFiller, the code below appends the appropriate entities of the IfcBuildingStorey type that would affect target elements for the clone operations:

  OdIfc::OdIfcInstancePtr storey = OdIfc::OdIfcInstancePtr();
  if (targetEntityPresent)
  {
    OdDAIObjectId location = OdDAIObjectId::kNull;

    if (pTargetModel->getEntityExtent("ifcbuilding")->getMemberCount())
    {
      buildingId = pTargetModel->getEntityExtent("ifcbuilding")->getArray()[0];

      if (buildingId.openObject()->getAttr("objectplacement") >> location)
      {
        storey = modelFiller->createBuildingStorey("New Storey", "ELEMENT", location);
      }
    }

  }
  else //previously self created building without storeys
  {
    storey = modelFiller->createBuildingStorey("New Storey", "ELEMENT", modelFiller->getBuildingPlacement());
  }

To construct IFC relationships as IfcRelDecompose|IfcRelAggregates according to the appropriate schema, use the following IfcModelFiller methods:

 if (!storey.isNull())
  {
    if (!buildingPresent)
    {
      OdIfc::OdIfcEntityPtr pRelAggrStorey = modelFiller->addRelAggregates(modelFiller->getBuilding().openObject(), storey);
    }
    else
    {
      OdIfc::OdIfcEntityPtr pRelAggrStorey = modelFiller->addRelAggregates(buildingId.openObject(), storey);
    }
  }

Most important is using IfcModelProcessor methods to specify the location and to call the clone operation:

 OdIfc::ModelOps::IfcModelProcessor modelProcessor(pTargetModel);

  modelProcessor.setTargetLocation(OdGePoint3d(0.0, 0.0, 0.0));
  OdDAIObjectId clonedItem = modelProcessor.ifcCloneTo(clonableEntity, storey->id());
  OdDAI::ApplicationInstancePtr clonedObject = clonedItem.openObject();

  if (!clonedItem.isNull())
  {
    if (clonableEntity.openObject()->isKindOf("ifcbuildingelement"))
    {
      OdIfc::OdIfcEntityPtr pRelSpatial = modelFiller->addRelContainedInSpatialStructure(storey, clonedObject);
    }
    else
    {
      OdIfc::OdIfcEntityPtr pRelAggr = modelFiller->addRelAggregates(storey, clonedObject);
    }
  }

  OdString pathToSave = fileNames[2];

  if (pIfcFile2->writeFile(pathToSave) == eOk)
  {
    odPrintConsoleString(L"Combined model items have been written to file %ls\n", pathToSave.c_str());
  }

A more advanced example of sequential processing of IfcBuildingElement source can be found in Tutorial 13 of the ExIfcTutorials example module. The file generated by the tutorial contains copies of the source entity that are distributed with placement of target model storeys.

Remove Existing Items

The remove functionality is implemented as part of the ModelOps module:

OdResult OdIfc::ModelOps::removeFromSpatialStructure(const OdDAIObjectId& productToRemove);
OdResult OdIfc::ModelOps::removeFromRelAggregates(const OdDAIObjectId& productToRemove);

The most general case is provided with the following method:

OdResult OdIfc::ModelOps::removeRepresentationFrom(const OdDAIObjectId& productToRemove);

For example, to decrease the number of storeys in a model, remove them using the removeRepresentationFrom or removeFromRelAggregates methods. The source model contains the next geometric representation:

 

IFC model remove storeys

 

To remove an IfcBuilding storey with a known ID from a model, you can use the next code snippet:

wchar_t *fileNames[] = {
  L"C:/path/to/source.ifc",
  L"C:/path/to/target.ifc",
  };

  odPrintConsoleString(OD_T("Open file %ls\n"), fileNames[0]);
  OdIfcFilePtr pIfcFile = pHostApp->readFile(fileNames[0]);  

  OdIfcModelPtr pModel = pIfcFile->getModel(sdaiRW);

  if (pModel.isNull())
  {
    odPrintConsoleString(L"Error getting source model\n"); 
    return;
  }

  OdDbHandle itemToRemove = 60; // IfcBuildingStorey ID   

  auto remEnt = pModel->getEntityInstance(itemToRemove);
  if (remEnt.isNull())
  {
    odPrintConsoleString(L"Error getting source entity\n"); 
    return;
  }

  OdIfc::OdIfcInstancePtr pRemEntInstance = remEnt.openObject();
  if (!pRemEntInstance.isNull())
  {
    OdAnsiString typeName = pRemEntInstance->typeName();
      if ((typeName == OdAnsiString("ifcbuildingstorey")))
    {
      OdIfc::ModelOps::removeFromRelAggregates(remEnt);
      auto pathToWrite = fileNames[1];
      if (pIfcFile->writeFile(pathToWrite) == eOk)
      {
        odPrintConsoleString(L"\nModified model has been written to file %ls\n", pathToWrite.c_str());
      }
    }  
  }

 

ifc model result

 

An example that uses clone and remove operations for replicating IfcBuilding storeys within one model can be found in Tutorial 12 of the ExIfcTutorials example module. The code in this tutorial clones the specified non-top IfcBuildingStorey N times with a vertical offset, and moves an upper floor to another IfcBuildingStorey at the top of the new building. Then the content with the updated model is written to an output file. The result is below:

 

IFC Model storeys