Model federation

In the tutorial so far, we have worked with a single imported IFC model. In some workflows, it is desirable to work with multiple imported models. Typically, with a BIM workflow, the whole building model may be split by discipline, for instance, architecture and MEP.

In this section, we discuss one way to implement a federated workflow. By way of example, we will take the previous architectural model and add to it an MEP model.

We will take this architectural model:

../../_images/architectural_model.png

…and combine it with this MEP model:

../../_images/mep_model.png

…to produce this federated model:

../../_images/federated_model.png

User interface

From a UI perspective, we’re going to use the Model Browser to allow the user to select a node and merge the imported model into that node. Similar functionality exists in the HOOPS Demo Viewer and you can test out that functionality using the sample models accompanying this tutorial.

Core functionality

Merging models is accomplished by using an option in the SetLocation function:

HPS::Exchange::ImportOptionsKit options;
options.SetLocation(mergePath);
notifier = HPS::Exchange::File::Import(filename, options);

This option is used to tell HOOPS Visualize to import a model into a specific location in an existing component hierarchy instead of creating a new root CADModel. The rest of the functionality, described in remaining part of this section, is wrapper code to allow the user to select a location to load in the model.

Component hierarchy and Exchange PRC data model

Before we continue to discuss the implementation, lets take a short detour to discuss the component hierarchy and its relation to the Exchange PRC data model. This will provide some background on a step which will make the implementation simpler.

../../_images/prc_data_model.png

When an IFC model is loaded into a HOOPS Visualize/Exchange application, there are different representations of the data, including the geometry.

  • PRC data model – a complete representation of the IFC data in HOOPS Exchange

  • Visualize scene-graph - a hierarchical representation of the model used for visualization

  • Component model – a representation of the PRC model in Visualize, which bridges the scene-graph and PRC data model.

A more detailed description of the component hierarchy can be found here.

What we can see from the diagram is that all models imported into HOOPS Visualize via Exchange are represented as a PRC model. So, when we’re merging IFC files into the same view, what we’re actually doing is merging PRC models.

Using PRC as a template

If you note the model browser image, it contains different nodes for architecture and MEP and it is into these nodes that we merged our two models. The starting point for this was an empty PRC product structure, which was loaded prior to the other two models. This creates a container into which the other models can be merged.

The structure is defined in the sample file.

Implementation

The main steps in the implementation are:

  1. To enable the user to select a target node in the Model Browser

  2. To ask the user to select a model to load

  3. To load the selected model into the selected destination and update the model

Selecting a model node

The steps are similar, in fact, simpler than the previous work we did to add a selected node to the bill of materials. In this case, we just want to retrieve the path of the selected node.

Define a new context menu item then add a handler, as shown below:

void ModelTreeCtrl::OnMbContextAddmodel()
{
	HPS::ComponentPath mergePath = contextItem->GetPath();

	CHPSDoc *pDoc = GetCHPSDoc();

	pDoc->GetCHPSView()->OnFileOpenAndMerge(mergePath);

}

Selecting a model

The code for selecting a model to merge is similar to the standard OnFileOpen command. However, as we’re only working with HOOPS Exchange-supported files for this tutorial, we can simplify it.

void CHPSView::OnFileOpenAndMerge(HPS::ComponentPath mergePath)
{
        CString filter = _T("");
        CString exchange_filter =
                _T("All CAD Files (*.3ds, *.3dxml, *.sat, *.sab, *_pd, *.model, *.dlv, *.exp, *.session, *.CATPart, *.CATProduct, *.CATShape, *.CATDrawing")
                _T(", *.cgr, *.dae, *.prt, *.prt.*, *.neu, *.neu.*, *.asm, *.asm.*, *.xas, *.xpr, *.arc, *.unv, *.mf1, *.prt, *.pkg, *.ifc, *.ifczip, *.igs, *.iges, *.ipt, *.iam")
                _T(", *.jt, *.kmz, *.prt, *.pdf, *.prc, *.x_t, *.xmt, *.x_b, *.xmt_txt, *.3dm, *.stp, *.step, *.stpz, *.stp.z, *.stl, *.par, *.asm, *.pwd, *.psm")
                _T(", *.sldprt, *.sldasm, *.sldfpp, *.asm, *.u3d, *.vda, *.wrl, *.vml, *.obj, *.xv3, *.xv0)|")
                _T("*.3ds;*.3dxml;*.sat;*.sab;*_pd;*.model;*.dlv;*.exp;*.session;*.catpart;*.catproduct;*.catshape;*.catdrawing")
                _T(";*.cgr;*.dae;*.prt;*.prt.*;*.neu;*.neu.*;*.asm;*.asm.*;*.xas;*.xpr;*.arc;*.unv;*.mf1;*.prt;*.pkg;*.ifc;*.ifczip;*.igs;*.iges;*.ipt;*.iam")
                _T(";*.jt;*.kmz;*.prt;*.pdf;*.prc;*.x_t;*.xmt;*.x_b;*.xmt_txt;*.3dm;*.stp;*.step;*.stpz;*.stp.z;*.stl;*.par;*.asm;*.pwd;*.psm")
                _T(";*.sldprt;*.sldasm;*.sldfpp;*.asm;*.u3d;*.vda;*.wrl;*.vml;*.obj;*.xv3;*.xv0;*.hsf|")
                _T("3D Studio Files (*.3ds)|*.3ds|")
                _T("3DXML Files (*.3dxml)|*.3dxml|")
                _T("ACIS SAT Files (*.sat, *.sab)|*.sat;*.sab|")
                _T("CADDS Files (*_pd)|*_pd|")
                _T("CATIA V4 Files (*.model, *.dlv, *.exp, *.session)|*.model;*.dlv;*.exp;*.session|")
                _T("CATIA V5 Files (*.CATPart, *.CATProduct, *.CATShape, *.CATDrawing)|*.catpart;*.catproduct;*.catshape;*.catdrawing|")
                _T("CGR Files (*.cgr)|*.cgr|")
                _T("Collada Files (*.dae)|*.dae|")
                _T("Creo (ProE) Files (*.prt, *.prt.*, *.neu, *.neu.*, *.asm, *.asm.*, *.xas, *.xpr)|*.prt;*.prt.*;*.neu;*.neu.*;*.asm;*.asm.*;*.xas;*.xpr|")
                _T("I-DEAS Files (*.arc, *.unv, *.mf1, *.prt, *.pkg)|*.arc;*.unv;*.mf1;*.prt;*.pkg|")
                _T("IFC Files (*.ifc, *.ifczip)|*.ifc;*.ifczip|")
                _T("IGES Files (*.igs, *.iges)|*.igs;*.iges|")
                _T("Inventor Files (*.ipt, *.iam)|*.ipt;*.iam|")
                _T("JT Files (*.jt)|*.jt|")
                _T("KMZ Files (*.kmz)|*.kmz|")
                _T("NX (Unigraphics) Files (*.prt)|*.prt|")
                _T("PDF Files (*.pdf)|*.pdf|")
                _T("PRC Files (*.prc)|*.prc|")
                _T("Parasolid Files (*.x_t, *.xmt, *.x_b, *.xmt_txt)|*.x_t;*.xmt;*.x_b;*.xmt_txt|")
                _T("Rhino Files (*.3dm)|*.3dm|")
                _T("STEP Files (*.stp, *.step, *.stpz, *.stp.z)|*.stp;*.step;*.stpz;*.stp.z|")
                _T("STL Files (*.stl)|*.stl|")
                _T("SolidEdge Files (*.par, *.asm, *.pwd, *.psm)|*.par;*.asm;*.pwd;*.psm|")
                _T("SolidWorks Files (*.sldprt, *.sldasm, *.sldfpp, *.asm)|*.sldprt;*.sldasm;*.sldfpp;*.asm|")
                _T("Universal 3D Files (*.u3d)|*.u3d|")
                _T("VDA Files (*.vda)|*.vda|")
                _T("VRML Files (*.wrl, *.vrml)|*.wrl;*.vrml|")
                _T("XVL Files (*.xv3, *.xv0)|*.xv0;*.xv3|");
        filter.Append(exchange_filter);
        CString all_files = _T("All Files (*.*)|*.*||");
        filter.Append(all_files);
        CFileDialog dlg(TRUE, _T(".ifc"), NULL, OFN_HIDEREADONLY, filter, NULL);
        if (dlg.DoModal() == IDOK)
        {
                GetDocument()->OnMergeExchangeModel(dlg.GetPathName(), mergePath );
        }
}

Once a file has been loaded it calls the function OnMergeExchangeModel (see below).

Loading and merging the model

OnMergeExchangeModel

The code for OnMergeExchangeModel is similar to the sandbox OnOpenDocument, however, as we are merging the loaded file into the existing model, we do not want to create a new CADModel.

BOOL CHPSDoc::OnMergeExchangeModel(LPCTSTR lpszPathName, HPS::ComponentPath mergePath)
{
        CStringA ansiPath(lpszPathName);
        const char *ext = ::strrchr(ansiPath, '.');
        if (!ext)
                return FALSE;
        ext++;
        // Flush any existing highlights
        GetCHPSView()->GetCanvas().GetWindowKey().GetHighlightControl().UnhighlightEverything();
        HPS::Exchange::ImportOptionsKit options;
        options.SetLocation(mergePath);
        bool success = MergeExchangeFile(lpszPathName, options);
        if (success)
        {
                CHPSApp * app = (CHPSApp *)AfxGetApp();
                app->addToRecentFileList(lpszPathName);
                CHPSView * cview = GetCHPSView();
                cview->UpdatePlanes();
                if (_cadModel.Empty() && _defaultCamera.Empty())
                        cview->GetCanvas().GetFrontView().ComputeFitWorldCamera(_defaultCamera);
                if (!_stricmp(ext, "ifc"))
                {
                        HPS::Type objectType = _cadModel.Type();
                        cview->OnLoadedIFCFile(this, TRUE);
                }
        }

        PostMessage(GetCHPSFrame()->GetSafeHwnd(), WM_MFC_SANDBOX_INITIALIZE_BROWSERS, 0, 0);
        return TRUE;
}

MergeExchangeFile

Finally, instead of calling the function ImportExchangeFile, we call MergeExchangeFile. Note that this in the call to this function we pass the target merge path. Again, the code for this function is very close to that of ImportExchangeFile, but we do not want to destroy the existing CAD file.

bool CHPSDoc::MergeExchangeFile(LPCTSTR lpszPathName, HPS::Exchange::ImportOptionsKit const & options)
{
        bool success = false;
        HPS::Exchange::ImportNotifier notifier;
        HPS::IOResult status = HPS::IOResult::Failure;
        std::string message;
        try
        {
                HPS::Exchange::ImportOptionsKit ioOpts = options;
                ioOpts.SetBRepMode(HPS::Exchange::BRepMode::BRepAndTessellation);
                ioOpts.SetIFCMetadata(true);
                ioOpts.SetAttributes(true);
                ioOpts.SetIFCOwnerHistoryOptimization(false);
                HPS::UTF8 filename(lpszPathName);
                HPS::Exchange::File::Format format = HPS::Exchange::File::GetFormat(filename);
                HPS::UTF8Array selectedConfig;
                HPS::Exchange::ConfigurationArray configs;
                if (format == HPS::Exchange::File::Format::CATIAV4
                        && !ioOpts.ShowConfiguration(selectedConfig)
                        && !(configs = HPS::Exchange::File::GetConfigurations(filename)).empty())
                {
                        // If this is a CATIA V4 file w/ configurations a configuration must be specified to perform the import.
                        // Since no configuration was specified, we'll pick the first one and import it, otherwise, the import will throw an exception.
                        getConfiguration(configs, selectedConfig);
                        ioOpts.SetConfiguration(selectedConfig);
                }
                CHPSExchangeProgressDialog dlg(this, notifier, filename);
                notifier = HPS::Exchange::File::Import(filename, ioOpts);
                dlg.DoModal();
                success = dlg.WasImportSuccessful();
                status = notifier.Status();
        }
        catch (HPS::IOException const & ex)
        {
                status = ex.result;
                message = ex.what();
        }
        if (status != HPS::IOResult::Success)
        {
                CAtlString str;
                if (status == HPS::IOResult::Failure)
                        str.Format(_T("Error loading file %s:\n\n\t%s"), lpszPathName, std::wstring(message.begin(), message.end()).data());
                else
                        str = getErrorString(status, lpszPathName);
                GetCHPSFrame()->MessageBox(str.GetString(), _T("File import error"), MB_ICONERROR | MB_OK);
        }
        return success;
}

Final note

With these changes, we are finished. The code is very similar to existing import code. All we needed to do is load the model into an existing CAD model instead of replacing the old one. We also used a PRC file as a template to create an empty structure in which to load models.