Introduction
Parsing and interpreting the PRC structure can be complex, as described in the PRC Basics documentation.
This article explains how to traverse a PRC model file using the built-in Visitor pattern sample included with HOOPS Exchange.
To simplify the setup process, we provide a lightweight workbench project on GitHub that is specifically prepared for traversal experiments and rapid prototyping. Using this project, you can focus on implementing traversal logic.
The sample code introduced in this article is reusable and can be integrated into your own applications for model analysis, retrieval, and modification workflows.
Instructions
Workbench Project Preparation
Prepare a workbench project for the Visitor pattern examples.
- Download the workbench project from GitHub
- Create a local folder named
ImportTraverseExport. - Copy the downloaded project files into the
ImportTraverseExportfolder. - Open
_VS2022.batin a text editor. - Edit the file to match your local environment. In particular, update the HOOPS Exchange installation directory.
- Save the file and close the editor.
- Launch the project by running
_VS2022.bat. - Verify that the solution opens successfully in Visual Studio 2022.
- Build the solution and confirm that it compiles without errors before proceeding.
The workbench project is preconfigured for the examples in this article and can be used as a starting point for your own traversal experiments and prototypes.
Porting the Visitor Pattern
Several HOOPS Exchange sample projects use the Visitor pattern. The Collision sample provides a good starting point for implementing model traversal.
-
Copy the
visitorfolder from:<HOOPS_Exchange SDK dir>\samples\advanced_functions\Collisioninto theImportTraverseExportproject folder. -
Return to the project in Visual Studio.
-
Under Header Files , create a new filter named
visitorand add all header files from the copiedvisitorfolder.
-
Under Source Files , create a new filter named
visitorand add all.cppfiles from the copiedvisitorfolder.
-
Build the project and verify that all visitor source files are compiled successfully.
Implementing the Tree Traversal Function
Next, implement a function that performs model traversal using the Visitor framework.
-
Add a
traverseModelFile()function toImportExport.cpp.... #include <sstream> #include "visitor/VisitorContainer.h" #include "visitor/VisitorTree.h" ... void traverseModelFile(A3DAsmModelFile* pModelFile) { // Prepare Visitor container A3DVisitorContainer sA3DVisitorContainer(CONNECT_TRANSFO); sA3DVisitorContainer.SetTraverseInstance(true); // Prepare Tree traverse visitor and set to the container A3DTreeVisitor *pA3DTreeVisitor = new A3DTreeVisitor(&sA3DVisitorContainer); sA3DVisitorContainer.push(pA3DTreeVisitor); // Prepare model file connector and call Traverse A3DModelFileConnector sModelFileConnector(pModelFile); A3DStatus sStatus = sModelFileConnector.Traverse(&sA3DVisitorContainer); } // ###################################################################################################################### #ifdef _MSC_VER int wmain(A3DInt32 iArgc, A3DUniChar** ppcArgv) ... -
Modify the conversion workflow so that the traversal function is called after the model file is imported and before it is exported.
... A3DExport sExport(acDstFileName); // see A3DSDKInternalConvert.hxx for import and export detailed parameters // perform conversion CHECK_RET(sHoopsExchangeLoader.Import(sImport)); traverseModelFile(sHoopsExchangeLoader.m_psModelFile); CHECK_RET(sHoopsExchangeLoader.Export(sExport)); ... -
Build the project
-
Set a breakpoint in
TreeTraverse.cppand verify that the traversal logic is executed.
Creating a Derived Visitor Class
The Visitor pattern sample already provides a complete traversal implementation that visits model entities from the root node to the leaf nodes.
Although you can add custom processing directly to A3DTreeVisitor , creating derived visitor classes is recommended. This approach allows traversal logic to remain separate from application-specific functionality and enables the same traversal framework to be reused for different use cases.
With this design, you only need to implement your own processing logic and do not need to write a traversal algorithm for each use case.
-
In Visual Studio, add a new C++ class to the project.
-
Create a class named
MyTreeVisitorusingA3DTreeVisitoras the base class.
Use the following settings:-
Class name:
MyTreeVisitor -
Base class:
A3DTreeVisitor -
Access:
public
Visual Studio will generate the following files:-
MyTreeVisitor.h -
MyTreeVisitor.cpp
-
-
Add custom visitor methods to
MyTreeVisitor.#pragma once #include "visitor/VisitorTree.h" class MyTreeVisitor : public A3DTreeVisitor { public: MyTreeVisitor(A3DVisitorContainer* psContainer = nullptr) : A3DTreeVisitor(psContainer) {}; ~MyTreeVisitor() = default; virtual A3DStatus visitEnter(const A3DProductOccurrenceConnector& sConnector) override; virtual A3DStatus visitLeave(const A3DProductOccurrenceConnector& sConnector) override; }; -
Implement the visitor methods in
MyTreeVisitor.cpp.#include "MyTreeVisitor.h" #include "visitor/VisitorTree.h" A3DStatus MyTreeVisitor::visitEnter(const A3DProductOccurrenceConnector& sConnector) { A3DStatus iRet = A3DTreeVisitor::visitEnter(sConnector); // My processes return iRet; } A3DStatus MyTreeVisitor::visitLeave(const A3DProductOccurrenceConnector& sConnector) { A3DStatus iRet = A3D_SUCCESS; // My processes iRet = A3DTreeVisitor::visitLeave(sConnector); return iRet; } -
Include
MyTreeVisitor.hinImportExport.cpp.... #include "visitor/VisitorContainer.h" #include "MyTreeVisitor.h" ... -
Replace
A3DTreeVisitorwithMyTreeVisitorin thetraverseModelFile()function.... // Prepare Tree traverse visitor and set to the container MyTreeVisitor* pMyTreeVisitor = new MyTreeVisitor(&sA3DVisitorContainer); sA3DVisitorContainer.push(pMyTreeVisitor); ... -
Build the project.
-
Set breakpoints in the
MyTreeVisitormethods and verify that the visitor is called during traversal.
A3DTreeVisitor traverses the model hierarchy recursively, from the root model file (pModelFile ) down to the leaf entities such as Product Occurrences , Part Definitions , and Representation Items .
The visitEnter() method is called when traversal enters a node, and visitLeave() is called when traversal exits a node. Because the Visitor pattern uses method overloading, the same method names are used for different node types, with the connector type determining which overload is invoked.
In the example above, custom processing is implemented for Product Occurrence nodes by overriding the methods that take an A3DProductOccurrenceConnector argument.
The following table shows the mapping between model node types and connector classes.
| Node type | Connector |
|---|---|
| A3DAsmModelFile | A3DModelFileConnector |
| A3DAsmProductOccurrence | A3DProductOccurrenceConnector |
| A3DAsmPartDefinition | A3DPartConnector |
| A3DRiRepresentationItem | A3DRiConnector |
| A3DRiBrepModel | A3DRiBrepModelConnector |
| A3DRiSet | A3DRiSetConnector |
| A3DRiPolyBrepModel | A3DPolyRiBrepModelConnector |
Logging Component Names
As a simple example of a custom visitor, add code that logs the names of Product Occurrences during traversal.
-
Add the following code in
MyTreeVisitor.h.... virtual A3DStatus visitLeave(const A3DProductOccurrenceConnector& sConnector) override; private: int m_iLevel = 0; }; -
Add the following code in
MyTreeVisitor.cpp.A3DStatus MyTreeVisitor::visitEnter(const A3DProductOccurrenceConnector& sConnector) { A3DStatus iRet = A3DTreeVisitor::visitEnter(sConnector); // Increment level m_iLevel++; // Get the ProductOccurrence (PO) const A3DEntity* pEntity = sConnector.GetA3DEntity(); A3DAsmProductOccurrence* pPO = (A3DAsmProductOccurrence*)pEntity; // Get RootBaseData of the PO A3DRootBaseData sRootBaseData; A3D_INITIALIZE_DATA(A3DRootBaseData, sRootBaseData); A3DRootBaseGet(pPO, &sRootBaseData); // Get the PO name A3DUniChar acName[256]; if (sRootBaseData.m_pcName) A3DMiscUTF8ToUTF16(sRootBaseData.m_pcName, acName); else wcscpy_s(acName, _T("NO_NAME")); // Show the PO name with level for (int i = 0; i < m_iLevel; i++) _tprintf(_T("+ ")); _tprintf(_T("%s\n"), acName); A3DRootBaseGet(nullptr, &sRootBaseData); return iRet; } virtual A3DStatus visitLeave(const A3DProductOccurrenceConnector& sConnector) override { A3DStatus iRet = A3D_SUCCESS; // Decrement level m_iLevel--; iRet = A3DTreeVisitor::visitLeave(sConnector); return iRet; } -
Build the project
-
Verify that Product Occurrence name are displayed in the console
In the example above, the Landing Gear model from the sample data set is used.
"$(HEXCHANGE_INSTALL_DIR)\samples\data\catiaV5\CV5_Landing Gear Model\_LandingGear.CATProduct"
The output reflects the Product Occurrence hierarchy of the assembly. The indentation level corresponds to the traversal depth within the model tree.
Getting Component Visibility
If you open the Landing_Gear_Assy model in HOOPS Demo Viewer (HDV), you will notice that some components are hidden by default.
Determining component visibility can be more complicated than checking a single visibility flag because visibility is inherited through the assembly hierarchy. A component may be hidden because one of its parent nodes is hidden.
The Visitor pattern sample handles this by using the A3DVisitorColorMaterials visitor, which computes cascaded display attributes such as color, material, and visibility during traversal.
The following visitors are available and can be combined through the visitor container.
| Flag | Visitor class | Member |
|---|---|---|
| CONNECT_TRANSFO | A3DVisitorTransfo | Transform |
| CONNECT_COLORS | A3DVisitorColorMaterials | Color, material, visibility |
| CONNECT_MESH | A3DVisitorTessellation | Tessallation |
| CONNECT_BREP | A3DVisitorBrep | B-rep |
-
Enable the color and visibility visitor by adding
CONNECT_COLORSto the visitor container.... void traverseModelFile(A3DAsmModelFile\* pModelFile) { // Prepare Visitor container A3DVisitorContainer sA3DVisitorContainer(CONNECT_TRANSFO | CONNECT_COLORS); sA3DVisitorContainer.SetTraverseInstance(true); ... -
Add code to retrieve the visibility state in
MyTreeVisitor.cpp.#include "MyTreeVisitor.h" #include "visitor/VisitorCascadedAttribute.h" #include "visitor/CascadedAttributeConnector.h" A3DStatus MyTreeVisitor::visitEnter(const A3DProductOccurrenceConnector& sConnector) { ... _tprintf(_T("%s"), acName); A3DRootBaseGet(nullptr, &sRootBaseData); A3DVisitorColorMaterials *pA3DCascadedVisitor = static_cast<A3DVisitorColorMaterials*>(m_psContainer->GetVisitorByName("CascadedAttribute")); if (pA3DCascadedVisitor) { ColorMaterialsConnector sColorConnector(nullptr); pA3DCascadedVisitor->GetColorMaterialConnector(sColorConnector); if (sColorConnector.IsShow()) _tprintf(_T("\n")); else _tprintf(_T(" (Hidden)\n")); } return iRet; } ... -
Build the project
-
Run the application and verify that hidden components are marked with
(Hidden)in the console output.
The visibility state returned by
ColorMaterialsConnectorreflects the effective visibility after inheritance from parent nodes has been applied.
Getting Instance Transformation
If you inspect the model in HOOPS Demo Viewer (HDV), you will notice that some components are instantiated multiple times within the assembly.
Each instance can have its own transformation, even when multiple instances reference the same part definition. The Visitor pattern can be used to retrieve the effective transformation of each instance during traversal.Add code to get the transformation of each instance.
The transformation visitor is already enabled in the visitor container:
A3DVisitorContainer sA3DVisitorContainer(CONNECT_TRANSFO | CONNECT_COLORS);
-
Add an override for
A3DPartConnectorinMyTreeVisitor.h... virtual A3DStatus visitEnter(const A3DProductOccurrenceConnector& sConnector) override; virtual A3DStatus visitEnter(const A3DPartConnector& sConnector) override; virtual A3DStatus visitLeave(const A3DProductOccurrenceConnector& sConnector) override; ... -
Retrieve the global transformation in
MyTreeVisitor.cpp#include "MyTreeVisitor.h" #include "visitor/VisitorCascadedAttribute.h" #include "visitor/CascadedAttributeConnector.h" #include "visitor/VisitorTransfo.h" #include "visitor/TransfoConnector.h" #include <memory> ... A3DStatus MyTreeVisitor::visitEnter(const A3DPartConnector& sConnector) { A3DStatus iRet = A3DTreeVisitor::visitEnter(sConnector); // Get transform connector via transform visitor A3DVisitorTransfo* psVisitorTransfo = static_cast<A3DVisitorTransfo*>(m_psContainer->GetVisitorByName("Transformation")); if (psVisitorTransfo) { std::unique_ptr<A3DTransfoConnector> pConnector(psVisitorTransfo->GetTransfoConnector()); A3DMatrix4x4 sTransfo; pConnector->GetGlobalTransfo(sTransfo); for (unsigned int i = 0; i < m_iLevel; i++) _tprintf(_T("+ ")); _tprintf(_T(" (%.3f, %.3f, %.3f)\n"), sTransfo.m_adM[12], sTransfo.m_adM[13], sTransfo.m_adM[14]); } return iRet; } -
Build the project
-
Run the application and verify that different instances of the same part report different locations.
The reported coordinates correspond to the translation components of the global transformation matrix.
When creating the visitor container, the call
sA3DVisitorContainer.SetTraverseInstance(true)
instructs the traversal to visit every instance in the assembly hierarchy.
When set tofalse, only the first occurrence of each instanced part is traversed.
The Collision sample provides another practical example of tree traversal using the Visitor pattern.
A3DCollisionCompute requires two groups of Representation Items to perform collision detection. To prepare these inputs, the sample uses A3DFlattenVisitor , a class derived from A3DTreeVisitor , to collect visible Representation Items together with their identifiers and effective transformations.
A3DFlattenVisitor achieves this by overriding the visitEnter() methods for both A3DPartConnector and A3DRiConnector .
Download the Completed Project
The completed sample project described in this article is available on GitHub:
You can use this project as a reference implementation or as a starting point for your own Visitor pattern experiments and model traversal applications.











