===================================== Accessing metadata and IFC properties ===================================== In the last section, we loaded an IFC model. This is sufficient if all you want to do is view the 3D model in a limited way. However, many BIM models contain useful information concerning both IFC data types and associated properties. These types and properties allow you to interact with the model more intelligently, both in what is seen visually, and in being able to extract useful information for downstream applications. In this section, we're going to learn how to extract metadata from the imported CAD file. We'll begin with a simple example to extract some metadata attached to the top of the component hierachy - the ``ModelFile`` object - and later develop this into a more general sample.Note that for this tutorial, we'll use the terms "metadata" and "properties" interchangeably. All objects derived from the `HPS::Component `_ class can have attached metadata, which is represented by a key-value pair. In this example, we are going to request metadata with the key "units" attached to the ``ModelFile`` object. Open *IFCUtils.cpp*, and find the following the code: .. literalinclude:: /source/IFCUtils.cpp :language: c :start-after: //! [get_units_string] :end-before: //! [get_units_string] Note how the value of the metadata for "units", is assigned to a string object of type `HPS::UTF8 `_, which is the basic string data type used by HOOPS Visualize. Metadata can have different value types. and we'll handle the different types later. Let's call this function from the Sandbox. For simplicity, we can use one of the User Code slots that are defined in ``CHPSView``. Scroll down to the ``OnUserCode1`` function, and you'll see this: .. code-block:: cpp void CHPSView::OnUserCode1() { HPS::View view = _canvas.GetFrontView(); HPS::Model model = view.GetAttachedModel(); HPS::SegmentKey modelKey = model.GetSegmentKey(); HPS::CADModel cadModel = GetCHPSDoc()->GetCADModel(); if (!cadModel.Empty()) { UTF8 unitsString = IFCUtils::GetUnitsString(cadModel); HPS::UTF8 utf8Name = cadModel.GetName(); CString cName(utf8Name.GetBytes()); MessageBox(CString("Units: ") + unitsString.GetBytes(), CString("Model File:") + cName); std::vector < std::pair> wsMetaData; IFCUtils::GetComponentMetaData(cadModel, wsMetaData); UTF8 allProps; for (auto const &wsd : wsMetaData) { allProps += wsd.first + ":" + wsd.second + "\n"; } MessageBox(CString(allProps.GetBytes()), CString("Model File") + cName); } } If you run the Sandbox, then click the User Code 1 button: .. image:: images/OnUserCode1.png ...the code above will run, and produce these dialogs with unit and properties data read from the model: .. image:: images/msgbox_units.png .. image:: images/msgbox_properties.png Notice that we are iterating through the component metadata to read the properties of this file. We've also added the name of the file to the title of the dialog. Like other properties, the file name is just metadata, but since asking for the name of a component is such a common operation, the convenience function ``GetName()`` is provided, which can be called directly. For this example, we are printing the units in a modal dialog, but you can obviously use whatever method is appropriate for your situation. Finally, we're going to look at obtaining all the metadata associated with a a component. We do this using the function `HPS::Component::GetAllMetaData `_. The units and name properties explained above were retrieved as string property values, but metadata can have several value types. The function below demonstrates how to handle the different value types. In this case, we're going to just translate them to type ``HPS::UTF8`` for display in the user interface: .. literalinclude:: /source/IFCUtils.cpp :language: c :start-after: //! [GetMetadataValueAsUTF8] :end-before: //! [GetMetadataValueAsUTF8] You can then assemble all the metadata values into a ``std::vector`` of pair values is easy using the ``GetComponentMetaData`` function: .. literalinclude:: /source/IFCUtils.cpp :language: c :start-after: //! [GetComponentMetaData] :end-before: //! [GetComponentMetaData] Isolating IFC storeys --------------------- Next, we're going to look at both handling IFC property values in particular and making use of them in the user interface. In particular, we will use the IFCBUILDINGSTOREY property attached to our model to allow the user to view different levels of the model. The examples in this section assume familiarity with adding controls to an MFC based application, but the same principles apply to any GUI you may be using. We'll begin by adding a new panel to the User Code table of the Ribbon Bar. We will use this panel to add additional controls later on. Having created an IFC panel, now add a Combox box to it as shown. Change its caption to **Floors** and its appearance to **Drop List**. Also for readability, let's change the ID name to something more appropriate. In this case, **ID_COMBO_FLOOR**. .. image:: images/floors_combo.png Having created the control, the next thing we want to do is populate it when we load an IFC file. Let's add a function to the the ``CView`` object to do this. In some cases you might consider associating IFC document information with the MFC document object, but for the sake of simplicity in this tutorial, we'll keep in in the CView. Add a new function to the CView called ``OnLoadedIFCFile()``. We will call this function from the ``CDocument::OnOpenDocument`` function when we load a new IFC file. .. literalinclude:: /source/codesnippets.cpp :language: c :start-after: //! [on_open_ifc_document] :end-before: //! [on_open_ifc_document] Once the document is opened, we can search for components which represent a building story, then use this information to populate a dropdown list. .. literalinclude:: /source/codesnippets.cpp :language: c :start-after: //! [on_loaded_ifc_file] :end-before: //! [on_loaded_ifc_file] In order to find the building stories, we will recursively walk down the component hierarchy - starting with the root CAD mode - until we find product occurrences with a property named "IFCBUILDINGSTOREY". Notice in the function below, we gather the component path of the selected product occurrence. The component path represents a unique, ordered set of components from a leaf node back up to the root, in this case a CAD model. More on ``HPS::ComponentPath`` can be found `here `_. The reason we are using component paths and not just the component is to allow use of ``Show``, ``Hide``, and ``Isolate`` functions all of which operate on paths. Just like the `KeyPath `_ entity of HOOPS Visualize can be used to define a unique instance of a graphics entity, so the component path defines a unique component instance. In this specific case, we know that an IFCBUILDINGSTOREY must be reprented by a product occurrence component, so we only need to search components of this type. .. literalinclude:: /source/codesnippets.cpp :language: c :start-after: //! [find_ifc_storeys] :end-before: //! [find_ifc_storeys] Having built up an array of component paths, we will use this to populate the dropdown list. The first entry in the list will be used to show all of the building. The other entries will represent one of the building storeys we found previously. .. code-block:: cpp void ifcPopulateFloorsDropDown(CMFCRibbonComboBox *pCombo, IFCSampleModel *pIFCModel) { // The first entry will be to show the entire model pCombo->AddItem(CString("All"),NULL); pCombo->SelectItem(0); // now add an entry for each int index = 0; for (auto path : pIFCModel->_storeyPaths) { //lets get the name of the component float elevation = pIFCModel->_storeyElevations[index]; Component front = path.Front(); HPS::UTF8 strFrontName = front.GetName(); CString csName(strFrontName.GetBytes()); CString csValue; csValue.Format(_T("- %f"), elevation); pCombo->AddItem(csName + csValue, index++); } } Finally, lets add an event handler for when the dropdown selection is changed. We call the Isolate function on the component path representing a floor. For the 'All' selection, we can call `HPS::CADModel:: ResetVisibility `_. .. literalinclude:: /source/codesnippets.cpp :language: c :start-after: //! [on_combo_floor] :end-before: //! [on_combo_floor] This is what the model looks like after isloating the foundations: .. image:: images/ifcmodel_foundations.png