Introduction
Parsing and interpreting the PRC structure is non-trivial in some cases, as described in our documentation for PRC Basics.
In this article, we are going to describe traversing a PRC model file using a builtin Visitor pattern sample. Based on the ImportExport sample project, you will be able to make a prototype easily, which can retrieve and update the model file. This sample is reusable and you can port the code into your application even if you don’t know the details of the traversing manner.
Instructions
Workbench project preparation
Prepare a workbench project based on the ImportExport sample.
- Copy ImportExport folder from
<HOOPS_Exchange_Publish SDK dir>\samples\exchange\exchangesourceand paste into your project folder - Rename the copied folder name to ImportTraverseExport
- Copy
HOOPSExchangePublishSample.propsfile from<HOOPS_Exchange_Publish SDK dir>\samplesand past in theImportTraverseExport/folder - Copy
common.hppfile from<HOOPS_Exchange_Publish SDK dir>\samples\exchange\exchangesource and paste into the project folder - Start Visual Studio 2015 and open the ImportTraverseExport project
- Save the solution as
ImportTraverseExport.slnand close Visual Studio - Create a file named
VS2015.batand open it in your preferred text editor - Edit
VS2015.batso that it opens the solution by specifyingHEXCHANGE_INSTALL_DIRenvironment variable
SET HEXCHANGE_INSTALL_DIR=C:\SDK\HOOPS_Exchange_Publish_2020_SP1
CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe" ImportTraverseExport.sln
C:\SDK\HOOPS_Exchange_Publish_2020_SP1 is your HOOPS Exchange install dir
9. Save and close the VS2015.bat file
10. Open the solution using VS2015.bat
11. Add $(HEXCHANGE_INSTALL_DIR)\include in the Additional Include Directories of C/C++ → General page in Project Property
- Open
ImportExport.cppand edit like below
...
#include "common.hpp"
#include <sstream>
...
//
// ### INITIALIZE HOOPS EXCHANGE
//
std::wstringstream bin_dir;
#ifdef _DEBUG
std::wstring buffer;
buffer.resize(_MAX_PATH * 2);
if (GetEnvironmentVariable(L"HEXCHANGE_INSTALL_DIR", &buffer[0], static_cast<DWORD>(buffer.size())))
{
bin_dir << buffer.data() << L"/bin/win64\0";
}
#else
bin_dir << L"";
#endif
A3DSDKHOOPSExchangeLoader sHoopsExchangeLoader(bin_dir.str().data());
...
- Build the project and verify that the import and export process works properly
To run this application, it is necessary to specify source and destination CAD file names to the Command Arguments of Debugging page of the project property, i.e.
"$(HEXCHANGE_INSTALL_DIR)\samples\data\step\helloworld.stp" "C:\temp\helloworld.prc"
Porting Visitor pattern
Several sample projects use the Visitor pattern, and the Collision sample provides a good starting point for our goals.
- Copy the
visitor/folder from<HOOPS_Exchange_Publish SDK dir>\samples\exchange\exchangesource\Collisionand past in the ImportTraverseExport folder - Return to the project in Visual Studio.
- Create a new folder named
visitor/under Header Files and add all header files in thevisitorfolder

- Create a new folder named
visitor/under Source Files and add all cpp files in thevisitorfolder

Implement tree traverse function
Implement a function which calls the visitor pattern.
- Create
traverseModelFile()function inImportExport.cpp
...
#include <sstream>
#include "visitor/VisitorContainer.h"
#include "visitor/VisitorTree.h"
static MY_CHAR acSrcFileName[_MAX_PATH * 2];
static MY_CHAR acDstFileName[_MAX_PATH * 2];
static MY_CHAR acLogFileName[_MAX_PATH * 2];
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)
...
- Split the Convert process into Import and Export so that the traverse function can be called after importing
...
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
- Make a breakpoint in
TreeTraverse.cppand verify the model file is traversed
Derived class creation
Thanks to the visitor pattern sample, you can access the model file from the root to the leaves in the correct manner. Though you can add your own retrieving and updating processes in it, we recommend you to make derived classes of A3DTreeVisitor, so that you can divide code into common traverse and various use cases. You don’t need to write a traverse algorithm for each use case.

- Create a derived class of
A3DTreeVisitor
...
static MY_CHAR acLogFileName[_MAX_PATH * 2];
class myTreeVisitor: public A3DTreeVisitor
{
public:
myTreeVisitor(A3DVisitorContainer* psContainer = NULL) : A3DTreeVisitor(psContainer) {};
~myTreeVisitor() {};
public:
virtual A3DStatus visitEnter(const A3DProductOccurrenceConnector& sConnector) override
{
A3DStatus iRet = A3DTreeVisitor::visitEnter(sConnector);
// My processes
return iRet;
}
virtual A3DStatus visitLeave(const A3DProductOccurrenceConnector& sConnector) override
{
A3DStatus iRet = A3D_SUCCESS;
// My processes
iRet = A3DTreeVisitor::visitLeave(sConnector);
return iRet;
}
};
void traverseModelFile(A3DAsmModelFile* pModelFile)
...
- Use the derived
myTreeVisitorclass instead ofA3DTessVisitorin thetraverseModelFile()function
...
// Prepare Tree traverse visitor and set to the container
myTreeVisitor *pMyTreeVisitor = new myTreeVisitor(&sA3DVisitorContainer);
sA3DVisitorContainer.push(pMyTreeVisitor);
...
- Build the project
- Make breakpoints in the
myTreeVisitorclass and verify that the class is called
In A3DTreeVisitor, the model file is traversed recursively from the root (pModelFile) to leaves (Product Occurrence, Part definition, Representation Item, etc.). The visitEnter() method is called at the beginning of each node traversal and the visitLeave() is called at the end of traversal. Due to the nature of the visitor pattern, all model tree nodes use the same methods (visitEnter() and visitLeave()) by changing the connector type argument.
In the above sample, you can implement your own processes when Product Occurrence is traversed by setting as A3DProductOccurrenceConnector in the argument.
The following is a mapping of Node types to Connecter classes
| Node type | Connector |
|---|---|
| A3DAsmModelFile | A3DModelFileConnector |
| A3DAsmProductOccurrence | A3DProductOccurrenceConnector |
| A3DAsmPartDefinition | A3DPartConnector |
| A3DRiRepresentationItem | A3DRiConnector |
| A3DRiBrepModel | A3DRiBrepModelConnector |
| A3DRiSet | A3DRiSetConnector |
| A3DRiPolyBrepModel | A3DPolyRiBrepModelConnector |
Logging component names
- Add code to log component names in the console
...
private:
int m_iLevel = 0;
public:
virtual A3DStatus visitEnter(const A3DProductOccurrenceConnector& sConnector) override
{
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);
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 above image, we used the Landing Gear model in the sample folder.
"$(HEXCHANGE_INSTALL_DIR)\samples\data\catiaV5\CV5_Landing Gear Model_LandingGear.CATProduct"
Getting component visibility
If you open the Landing_Gear_Assy in HOOPS Demo Viewer (HDV), you will see a component is invisible by default.
The visibility parameter is complex because parent visibility affects the child components. This problem is managed by using a separate visitor (A3DVisitorColorMaterials) in the visitor pattern sample.
These are the following Visitor classes, and multiple visitors can be called via 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 |
- Add
CONNECT_COLORSto the visitor container
...
#include "visitor/VisitorTree.h"
#include "visitor/VisitorCascadedAttribute.h"
#include "visitor/CascadedAttributeConnector.h"
...
void traverseModelFile(A3DAsmModelFile* pModelFile)
{
// Prepare Visitor container
A3DVisitorContainer sA3DVisitorContainer(CONNECT_TRANSFO | CONNECT_COLORS);
sA3DVisitorContainer.SetTraverseInstance(true);
...
- Add code to access visibility info
...
virtual A3DStatus visitEnter(const A3DProductOccurrenceConnector& sConnector) override
{
...
_tprintf(_T("%s"), acName);
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
- Verify that
(Hidden)is added after the Product Occurrence name
Getting instance transformation
You will see in HDV that some components are used multiple times in the assembly model.
Add code to get the transformation of each instance.
The visitor to get a transformation is already assigned to the visitor container.
A3DVisitorContainer sA3DVisitorContainer(CONNECT_TRANSFO | CONNECT_COLORS);
- Add an override function of
A3DPartConnectorand get the part transform
class myTreeVisitor: public A3DTreeVisitor
{
...
virtual A3DStatus visitEnter(const A3DPartConnector& sConnector) override
{
A3DStatus iRet = A3DTreeVisitor::visitEnter(sConnector);
// Get transform connector via transform visitor
A3DVisitorTransfo* psVisitorTransfo = static_cast<A3DVisitorTransfo*>(m_psContainer->GetVisitorByName("Transformation"));
A3DTransfoConnector* pConnector = psVisitorTransfo->GetTransfoConnector();
A3DMatrix4x4 sTransfo;
pConnector->GetGlobalTransfo(sTransfo);
delete pConnector;
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
- Verify that part locations differ by instance
You will see
sA3DVisitorContainer.SetTraverseInstance(true)when creating the visitor container. Setting totruecauses the visitor to traverse each instance part.If set to
false, just the first part of the instance will be traversed.
If you refer to the Collision sample, you will see the usage of Tree traverse Visitor.
Hence,
A3DCollisionComputerequires two Representation Item groups to compute collision, and it is necessary to get a flatten array of Representation Items from the target model file.
A3DFlattenVisitoris a derived class ofA3DTreeVisitorand retrieves visibleRepresentation Items, including the identifier and transform overridingvisitEnterofA3DPartConnectorandA3DRiConnector.







