# How-to: Split a multi-body part into assembly model

**URL:** <https://forum.techsoft3d.com/t/how-to-split-a-multi-body-part-into-assembly-model/1459>\
**Category:** HOOPS Exchange\
**Tags:** code, how-to, knowledge, product-structure\
**Created:** [July 20, 2021, 4:00am UTC](https://forum.techsoft3d.com/t/how-to-split-a-multi-body-part-into-assembly-model/1459 "2021-07-20T04:00:00Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Toshi-TS3D](https://sea2.discourse-cdn.com/flex020/user_avatar/forum.techsoft3d.com/toshi-ts3d/32/885_2.png) [@Toshi-TS3D](https://forum.techsoft3d.com/u/Toshi-TS3D)\
**Post date:** [July 20, 2021, 4:00am UTC](https://forum.techsoft3d.com/t/how-to-split-a-multi-body-part-into-assembly-model/1459/1 "2021-07-20T04:00:00Z")

</div>

## Introduction

In this article, we’re going to describe updating the PRC model file structure using the example of splitting a multi-body part into an assembly model.

If you import helloworld.stp in \<HOOPS\_Exchange\_Publish SDK dir\>\samples\data\step using HOOPS Demo Viewer (HDV), you will see the part has 10 bodies. Through this article, you can split the multi-body part into an assembly model, including multiple parts.

 ![img_30](https://us1.discourse-cdn.com/flex020/uploads/techsoft3d/original/1X/1d020d81a24975c5b0cead5b9c0d8e26137406ba.png)

## Instructions

### Workbench project preparation

1. Refer [How-to: Traverse PRC model file using Visitor pattern for rapid prototyping](https://forum.techsoft3d.com/t/how-to-traverse-a-prc-model-file-using-visitor-pattern-for-rapid-prototyping/1456) article and create a workbench project (form [Workbench project preparation] to [Implement tree traverse function])

### Derived class creation

Create a derived class of A3DTreeVisitor for this use case.

1. Create a derived class of A3DTreeVisitor including `visitEnter` method for `A3DPartConnector`

```auto
...
static MY_CHAR acLogFileName[_MAX_PATH * 2];

class bodyOperationVisitor: public A3DTreeVisitor
{
public:
	bodyOperationVisitor(A3DVisitorContainer* psContainer = NULL) : A3DTreeVisitor(psContainer) {};
	~bodyOperationVisitor() {};

public:
	virtual A3DStatus visitEnter(const A3DPartConnector& sConnector) override
	{
		A3DStatus iRet = A3DTreeVisitor::visitEnter(sConnector);

		// My processes

		return iRet;
	}
};

void traverseModelFile(A3DAsmModelFile* pModelFile)
...

```

1. Use the derived `bodyOperationVisitor` class instead of `A3DTessVisitor` in the `traverseModelFile` function

```auto
...
	// Prepare body operation visitor and set to the container
	bodyOperationVisitor *pBodyOpVisitor = new bodyOperationVisitor(&sA3DVisitorContainer);
	sA3DVisitorContainer.push(pBodyOpVisitor);
...

```

1. Build the project
2. Create break points in the `bodyOperationVisitor` class and verify that the class is called during run time

Thanks to the `bodyOperationVisitor`, you can add inquiry and update functions while model file traversing.

Implementing functions and timing for splitting multi body part will be the following image:

 ![img_33](https://us1.discourse-cdn.com/flex020/uploads/techsoft3d/original/1X/fca2280e215e9fabdfabe8e83ec7fd54b805da48.png)

### Detecting a multi body part and splitting the parts

If `m_uiRepItemsSize` of the Part Definition is more than 1, it means you have a multi body part.

1. Add code to detect multi body part and split into individual parts

```auto
...
public:
	virtual A3DStatus visitEnter(const A3DPartConnector& sConnector) override
	{
		A3DStatus iRet = A3DTreeVisitor::visitEnter(sConnector);

		// Get the PartDefiniiton data
		A3DAsmPartDefinitionData sData = sConnector.m_sPartData;

		// Detect multi body part
		if (1 < sData.m_uiRepItemsSize)
		{
			// It is multi body part
		}
		return iRet;
	}
...

```

1. Split the multi body into new parts

```auto
...
private:
	int m_iBodyCnt;
	A3DAsmPartDefinition** m_ppPart;

public:
	virtual A3DStatus visitEnter(const A3DPartConnector& sConnector) override
	{
		A3DStatus iRet = A3DTreeVisitor::visitEnter(sConnector);

		// Get the PartDefiniiton data
		A3DAsmPartDefinitionData sData = sConnector.m_sPartData;

		// Detect multi body part
		m_iBodyCnt = 0;
		if (1 < sData.m_uiRepItemsSize)
		{
			m_iBodyCnt = sData.m_uiRepItemsSize;

			// Create new Parts for each body
			m_ppPart = new A3DAsmPartDefinition*[m_iBodyCnt];
			
			for (A3DUns32 ui = 0; ui < m_iBodyCnt; ui++)
			{
				// Create a new part and set body
				A3DAsmPartDefinitionData sNewPartData;
				A3D_INITIALIZE_DATA(A3DAsmPartDefinitionData, sNewPartData);
				sNewPartData.m_uiRepItemsSize = 1;
				sNewPartData.m_ppRepItems = &sData.m_ppRepItems[ui];

				iRet = A3DAsmPartDefinitionCreate(&sNewPartData, &m_ppPart[ui]);
			}
		}
		return iRet;
	}
...

```

### Creating an assembly model file

In the above code, when a multi body part is detected, the `m_iBodyCnt` member variable is set. The first `visitLeave` of Product Occurrence after setting the body count will be the Product Occurrence of the target part.

1. Add `visitLeave` method of Product Occurrence and create an assembly model including the split parts when `m_iBodyCnt` is greater than 1.

```auto
...
private:
	int m_iBodyCnt;
	A3DAsmPartDefinition** m_ppPart;
	A3DAsmProductOccurrence* m_pAssyPO = NULL;
...
	virtual A3DStatus visitLeave(const A3DProductOccurrenceConnector& sConnector) override
	{
		A3DStatus iRet = A3D_SUCCESS;

		// Get the ProductOccurrence (PO)
		const A3DEntity* pEntity = sConnector.GetA3DEntity();
		A3DAsmProductOccurrence* pPO = (A3DAsmProductOccurrence*)pEntity;

		// Get PO data
		A3DAsmProductOccurrenceData sData = sConnector.m_sProductOccurrenceData;

		// Get RootBaseData of the PO
		A3DRootBaseData sRootBaseData;
		A3D_INITIALIZE_DATA(A3DRootBaseData, sRootBaseData);
		A3DRootBaseGet(pPO, &sRootBaseData);

		// If multi body part
		if (m_iBodyCnt)
		{
			// Create new POs of the new parts
			A3DAsmProductOccurrence **ppPO;
			ppPO = new A3DAsmProductOccurrence*[m_iBodyCnt];

			for (A3DUns32 ui = 0; ui < m_iBodyCnt; ui++)
			{
				// Create new PO
				A3DAsmProductOccurrenceData sPOData;
				A3D_INITIALIZE_DATA(A3DAsmProductOccurrenceData, sPOData);
				sPOData.m_dUnit = sData.m_dUnit;
				sPOData.m_pPart = m_ppPart[ui];

				iRet = A3DAsmProductOccurrenceCreate(&sPOData, &ppPO[ui]);

				// Set part name (org name - id)
				char name[1024];
				sprintf_s(name, sizeof(name), "%s - %d", sRootBaseData.m_pcName, ui + 1);
				A3DRootBaseData sNewBaseData;
				A3D_INITIALIZE_DATA(A3DRootBaseData, sNewBaseData);
				sNewBaseData.m_pcName = name;

				iRet = A3DRootBaseSet(ppPO[ui], &sNewBaseData);
			}

			// Create a new assembly PO for split parts
			A3DAsmProductOccurrenceData sNewPOData;
			A3D_INITIALIZE_DATA(A3DAsmProductOccurrenceData, sNewPOData);
			sNewPOData.m_uiPOccurrencesSize = m_iBodyCnt;
			sNewPOData.m_dUnit = sData.m_dUnit;
			sNewPOData.m_ppPOccurrences = ppPO;
			sNewPOData.m_pLocation = sData.m_pLocation;

			iRet = A3DAsmProductOccurrenceCreate(&sNewPOData, &m_pAssyPO);

			iRet = A3DRootBaseSet(m_pAssyPO, &sRootBaseData);

		}

		iRet = A3DTreeVisitor::visitLeave(sConnector);
		return iRet;
	}
...

```

1. Add `visitLeave` method of model file and create a new model file when an assembly model is created in the previous process.

```auto
...
static MY_CHAR acLogFileName[_MAX_PATH * 2];
static A3DAsmModelFile *s_pNewModelFile = NULL;
class bodyOperationVisitor : public A3DTreeVisitor
{
...
	virtual A3DStatus visitLeave(const A3DModelFileConnector& sConnector) override
	{
		A3DStatus iRet = A3D_SUCCESS;

		const A3DEntity* pEntity = sConnector.GetA3DEntity();
		A3DAsmModelFile* pModelFile = (A3DAsmModelFile*)pEntity;

		A3DAsmModelFileData sData = sConnector.m_sModelFileData;

		A3DRootBaseData sRootBaseData;
		A3D_INITIALIZE_DATA(A3DRootBaseData, sRootBaseData);
		A3DRootBaseGet(pModelFile, &sRootBaseData);

		if (m_pAssyPO)
		{
			// Create assemly pModelFile
			A3DAsmModelFileData sModelFileData = sConnector.m_sModelFileData;
			sModelFileData.m_bUnitFromCAD = sData.m_bUnitFromCAD;
			sModelFileData.m_dUnit = sData.m_dUnit;
			sModelFileData.m_eModellerType = kA3DModellerPrc;
			sModelFileData.m_uiPOccurrencesSize = 1;
			sModelFileData.m_ppPOccurrences = &m_pAssyPO;

			iRet = A3DAsmModelFileCreate(&sModelFileData, &s_pNewModelFile);

			// Set root made data of original model file
			iRet = A3DRootBaseSet(s_pNewModelFile, &sRootBaseData);
			
			m_pAssyPO = NULL;
		}

		return iRet;
	}
...

```

> If you update a node of model file, it is necessary to update it at end of traversing (`visitLeave` ) . If you update a node at beginning of traversing (`visitEnter` ), it affect its child nodes traversing and cause of application crash.

### Replacing the model file

Apart from the imported model file, a new model file (`s_pNewModelFile`) is created in the above code.

1. After traversing the model file, replace the original model file when the new model file is created.

```auto
...
	traverseModelFile(sHoopsExchangeLoader.m_psModelFile);

	if (NULL != s_pNewModelFile)
	{
		A3DStatus iRet = A3DAsmModelFileDelete(sHoopsExchangeLoader.m_psModelFile);

		sHoopsExchangeLoader.m_psModelFile = s_pNewModelFile;
		
		s_pNewModelFile = NULL;
	}
...

```

1. Build the project
2. Verify that multi body part is split into assembly model

 ![img_31](https://us1.discourse-cdn.com/flex020/uploads/techsoft3d/original/1X/5e555f94cd37f47ac55ec36a8606d99d99e67e92.png)

### Multi body parts in assembly model

The above code just supports multi body part model file. If you open the \_LandingGear.CATProduct in the ‘\<HOOPS\_Exchange\_Publish SDK dir\>\samples\data\catiaV5\CV5\_Landing Gear Model’ directory using the HDV, you will see some multi body parts in the assembly model file.

 ![img_32](https://us1.discourse-cdn.com/flex020/uploads/techsoft3d/original/1X/4955a9c90747c1dc810318255b8337a42a083e43.png)

In an assembly model file, though it is necessary to replace detected multi body part (step 4 in below image) to assembly model (step 6), the timing (step 13) is a bit complicated.

 ![img_34](https://us1.discourse-cdn.com/flex020/uploads/techsoft3d/original/1X/d5138cc151a36ef4f6c5710652dada75228495c3.png)

1. Prepare member array of struct to keep target POs and assembly models

```auto
...
#include <sstream>
#include <vector>
...
class bodyOperationVisitor : public A3DTreeVisitor
{
...
	A3DAsmProductOccurrence* m_pAssyPO = NULL;
	struct SplitPart
	{
		const A3DAsmProductOccurrence *parentPO;
		A3DAsmProductOccurrence *oldPartPO;
		A3DAsmProductOccurrence *newAssyPO;
	};
	std::vector<SplitPart> m_aSplitPart;
...

```

1. Try to get father PO to check whether the model file is part model file or assembly model file

```auto
...
	virtual A3DStatus visitLeave(const A3DProductOccurrenceConnector& sConnector) override
	{
		...
		// If multi body part
		if (m_iBodyCnt)
		{
			...
			iRet = A3DAsmProductOccurrenceCreate(&sNewPOData, &m_pAssyPO);

			iRet = A3DRootBaseSet(m_pAssyPO, &sRootBaseData);
			
			// Access father PO
			const A3DAsmProductOccurrence *pParentPO = sConnector.GetProductOccurrenceFather();

			// If father PO isn't null, it is assembly model file 
			if (NULL != pParentPO)
			{
				// Keep the target POs
				SplitPart splitPart;
				splitPart.parentPO = sConnector.GetProductOccurrenceFather();
				splitPart.oldPartPO = pPO;
				splitPart.newAssyPO = m_pAssyPO;

				m_aSplitPart.push_back(splitPart);

				m_pAssyPO = NULL;
			}
			// No father PO maens that it is part model file

		}
		...

```

1. Replace multi body part to assembly model in proper timing

```auto
...
	virtual A3DStatus visitLeave(const A3DProductOccurrenceConnector& sConnector) override
	{
		...
		// If multi body part
		if (m_iBodyCnt)
		{
			...
		}
		else
		{
			// Check whether current PO is in a targets
			for (int i = 0; i < m_aSplitPart.size(); i++)
			{
				SplitPart splitPart = m_aSplitPart[i];

				if (pPO == splitPart.parentPO)
				{
					// Find target multi body part PO and replace to assembly PO
					for (A3DUns32 ui = 0; ui < sData.m_uiPOccurrencesSize; ui++)
					{
						if (sData.m_ppPOccurrences[ui] == splitPart.oldPartPO)
						{
							sData.m_ppPOccurrences[ui] = splitPart.newAssyPO;
							break;
						}
					}

					iRet = A3DAsmProductOccurrenceEdit(&sData, pPO);
				}
			}
		}

		iRet = A3DTreeVisitor::visitLeave(sConnector);
		return iRet;
	}
...

```

1. Build the project
2. Verify that multi body parts in the assembly model are split into assembly models

 ![img_35](https://us1.discourse-cdn.com/flex020/uploads/techsoft3d/original/1X/1a65a25a666f5bb95c1f43c0a21ae3f6c6ba16f9.jpeg)

### Detecting part instances

If you look at converted model in the model browser, you will see some components are instanced.

 ![img_36](https://us1.discourse-cdn.com/flex020/uploads/techsoft3d/original/1X/1ae4fb6250754072bd441e70025c39b27992adc1.png)

To manage part instances, each part instance points abstract product occurrence so called “prototype“.

 ![img_38](https://us1.discourse-cdn.com/flex020/uploads/techsoft3d/original/1X/c81027d079da8d21b3811f95eef67b793f68c9df.png)

1. Add code to get prototype info

```auto
...
	virtual A3DStatus visitLeave(const A3DProductOccurrenceConnector& sConnector) override
	{
		A3DStatus iRet = A3D_SUCCESS;
		...
		// If multi body part
		if (m_iBodyCnt)
		{
			// Get prototype
			A3DAsmProductOccurrence *pProtoPO = sData.m_pPrototype;
			A3DAsmProductOccurrenceData sProtoData;
			A3DRootBaseData sProtoBaseData;
			if (NULL != pProtoPO)
			{
				A3D_INITIALIZE_DATA(A3DAsmProductOccurrenceData, sProtoData);
				iRet = A3DAsmProductOccurrenceGet(pProtoPO, &sProtoData);

				A3D_INITIALIZE_DATA(A3DRootBaseData, sProtoBaseData);
				iRet = A3DRootBaseGet(pProtoPO, &sProtoBaseData);
			}

			// Create new POs of the new parts
			...

```

1. Build the project
2. Create a break point and verify that prototype info is received

 ![img_39](https://us1.discourse-cdn.com/flex020/uploads/techsoft3d/original/1X/19461104869b739312fa886e8285b3589bd4dc06.png)

1. Keep the prototype pointer and instance transform in a member variable and detect part instance

```auto
..
	struct SplitPart
	{
		const A3DAsmProductOccurrence *parentPO;
		A3DAsmProductOccurrence *oldPartPO;
		A3DAsmProductOccurrence *newAssyPO;
		A3DAsmProductOccurrence *protoPO;
		A3DMiscTransformation *instanceTransform;
		A3DRootBaseData instanceBaseData;
	};
	...
virtual A3DStatus visitLeave(const A3DProductOccurrenceConnector& sConnector) override
	{
		A3DStatus iRet = A3D_SUCCESS;
		...
		// If multi body part
		if (m_iBodyCnt)
		{
			...
			if (NULL != pProtoPO)
			{
				A3D_INITIALIZE_DATA(A3DAsmProductOccurrenceData, sProtoData);
				iRet = A3DAsmProductOccurrenceGet(pProtoPO, &sProtoData);

				A3D_INITIALIZE_DATA(A3DRootBaseData, sProtoBaseData);
				iRet = A3DRootBaseGet(pProtoPO, &sProtoBaseData);

				// Chenck whether the same part is detected
				for (int i = 0; i < m_aSplitPart.size(); i++)
				{
					if (pProtoPO == m_aSplitPart[i].protoPO)
					{
						// Keep the target POs
						SplitPart splitPart;
						splitPart.parentPO = sConnector.GetProductOccurrenceFather();
						splitPart.oldPartPO = pPO;
						splitPart.newAssyPO = m_aSplitPart[i].newAssyPO;
						splitPart.instanceTransform = sData.m_pLocation;
						splitPart.instanceBaseData = sRootBaseData;

						m_aSplitPart.push_back(splitPart);

						return iRet;
					}
				}
			}
			...
			// Create a new assembly PO for split parts
			A3DAsmProductOccurrenceData sNewPOData;
			A3D_INITIALIZE_DATA(A3DAsmProductOccurrenceData, sNewPOData);
			sNewPOData.m_uiPOccurrencesSize = m_iBodyCnt;
			sNewPOData.m_dUnit = sData.m_dUnit;
			sNewPOData.m_ppPOccurrences = ppPO;
			// sNewPOData.m_pLocation = sData.m_pLocation; <= comment out this line
			...
			// If father PO isn't null, it is assembly model file 
			if (NULL != pParentPO)
			{
				if (NULL != pProtoPO)
				{
					// Set prototype's RootBaseData
					iRet = A3DRootBaseSet(m_pAssyPO, &sProtoBaseData);
				}
				
				// Keep the target POs
				SplitPart splitPart;
				splitPart.parentPO = sConnector.GetProductOccurrenceFather();
				splitPart.oldPartPO = pPO;
				splitPart.newAssyPO = m_pAssyPO;
				splitPart.protoPO = pProtoPO;
				splitPart.instanceTransform = sData.m_pLocation;
				splitPart.instanceBaseData = sRootBaseData;
				...

```

1. Build the project
2. Create a break and verify that part instance is detected

 ![img_37](https://us1.discourse-cdn.com/flex020/uploads/techsoft3d/original/1X/99ce0b7478bc1a1b6839cdc556b925806485b98e.png)

### Replacement using sub-assembly prototype

Multi body part should be replaced using sub-assembly prototype like below image

 ![img_40](https://us1.discourse-cdn.com/flex020/uploads/techsoft3d/original/1X/2d6d6a1d250e182f741fdd750180f5b345951c63.png)

1. Update part replacement code

```auto
...
	virtual A3DStatus visitLeave(const A3DProductOccurrenceConnector& sConnector) override
	{
		A3DStatus iRet = A3D_SUCCESS;
		...
		else
		{
			// Check whether current PO is in a targets
			for (int i = 0; i < m_aSplitPart.size(); i++)
			{
				SplitPart splitPart = m_aSplitPart[i];

				if (pPO == splitPart.parentPO)
				{
					// Find target multi body part PO and replace to assembly PO
					for (A3DUns32 ui = 0; ui < sData.m_uiPOccurrencesSize; ui++)
					{
						if (sData.m_ppPOccurrences[ui] == splitPart.oldPartPO)
						{
							// Create new sub-assembly instance
							A3DAsmProductOccurrenceData sAssyInstData;
							A3D_INITIALIZE_DATA(A3DAsmProductOccurrenceData, sAssyInstData);
							sAssyInstData.m_pLocation = splitPart.instanceTransform;
							sAssyInstData.m_pPrototype = splitPart.newAssyPO;

							A3DAsmProductOccurrence *pAssyInstPO;
							iRet = A3DAsmProductOccurrenceCreate(&sAssyInstData, &pAssyInstPO);

							// Set RootBaseData of the instance
							iRet = A3DRootBaseSet(pAssyInstPO, &splitPart.instanceBaseData);

							// Replace part instance to sub-assembly instance 
							sData.m_ppPOccurrences[ui] = pAssyInstPO;
							break;
						}
					}

					iRet = A3DAsmProductOccurrenceEdit(&sData, pPO);
				}
			}
		}
		...

```

1. Build the project
2. Verify that multi body parts in the assembly model are split into assembly models

Here is full source code of ImportExport.cpp for this article

```auto
/ ***********************************************************************************************************************
*
* Copyright (c) 2017 by Tech Soft 3D, LLC.
* The information contained herein is confidential and proprietary to Tech Soft 3D, LLC., and considered a trade secret
* as defined under civil and criminal statutes. Tech Soft 3D shall pursue its civil and criminal remedies in the event
* of unauthorized use or misappropriation of its trade secrets. Use of this information by anyone other than authorized
* employees of Tech Soft 3D, LLC. is granted only under a written non-disclosure agreement, expressly prescribing the
* scope and manner of such use.
*
*********************************************************************************************************************** /

/*
* This sample demonstrates how to load a model and export it as a model file of a different format. The chosen
* format is determined by the file extension of the output file name.
*/

#define INITIALIZE_A3D_API
#include <A3DSDKIncludes.h>

#include "common.hpp"
#include <sstream>
#include <vector>

#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];
static A3DAsmModelFile *s_pNewModelFile = NULL;

class bodyOperationVisitor : public A3DTreeVisitor
{
public:
	bodyOperationVisitor(A3DVisitorContainer* psContainer = NULL) : A3DTreeVisitor(psContainer) {};
	~bodyOperationVisitor() {};

private:
	int m_iBodyCnt;
	A3DAsmPartDefinition** m_ppPart;
	A3DAsmProductOccurrence* m_pAssyPO = NULL;
	struct SplitPart
	{
		const A3DAsmProductOccurrence *parentPO;
		A3DAsmProductOccurrence *oldPartPO;
		A3DAsmProductOccurrence *newAssyPO;
		A3DAsmProductOccurrence *protoPO;
		A3DMiscTransformation *instanceTransform;
		A3DRootBaseData instanceBaseData;
	};
	std::vector<SplitPart> m_aSplitPart;

public:
	virtual A3DStatus visitEnter(const A3DPartConnector& sConnector) override
	{
		A3DStatus iRet = A3DTreeVisitor::visitEnter(sConnector);

		// Get the PartDefiniiton data
		A3DAsmPartDefinitionData sData = sConnector.m_sPartData;

		// Detect multi body part
		m_iBodyCnt = 0;
		if (1 < sData.m_uiRepItemsSize)
		{
			m_iBodyCnt = sData.m_uiRepItemsSize;

			// Create new Parts for each body
			m_ppPart = new A3DAsmPartDefinition*[m_iBodyCnt];

			for (A3DUns32 ui = 0; ui < m_iBodyCnt; ui++)
			{
				// Create a new part and set body
				A3DAsmPartDefinitionData sNewPartData;
				A3D_INITIALIZE_DATA(A3DAsmPartDefinitionData, sNewPartData);
				sNewPartData.m_uiRepItemsSize = 1;
				sNewPartData.m_ppRepItems = &sData.m_ppRepItems[ui];

				iRet = A3DAsmPartDefinitionCreate(&sNewPartData, &m_ppPart[ui]);
			}
		}
		return iRet;
	}

	virtual A3DStatus visitLeave(const A3DProductOccurrenceConnector& sConnector) override
	{
		A3DStatus iRet = A3D_SUCCESS;

		// Get the ProductOccurrence (PO)
		const A3DEntity* pEntity = sConnector.GetA3DEntity();
		A3DAsmProductOccurrence* pPO = (A3DAsmProductOccurrence*)pEntity;

		// Get PO data
		A3DAsmProductOccurrenceData sData = sConnector.m_sProductOccurrenceData;

		// Get RootBaseData of the PO
		A3DRootBaseData sRootBaseData;
		A3D_INITIALIZE_DATA(A3DRootBaseData, sRootBaseData);
		A3DRootBaseGet(pPO, &sRootBaseData);

		// If multi body part
		if (m_iBodyCnt)
		{
			// Get prototype
			A3DAsmProductOccurrence *pProtoPO = sData.m_pPrototype;
			A3DAsmProductOccurrenceData sProtoData;
			A3DRootBaseData sProtoBaseData;
			if (NULL != pProtoPO)
			{
				A3D_INITIALIZE_DATA(A3DAsmProductOccurrenceData, sProtoData);
				iRet = A3DAsmProductOccurrenceGet(pProtoPO, &sProtoData);

				A3D_INITIALIZE_DATA(A3DRootBaseData, sProtoBaseData);
				iRet = A3DRootBaseGet(pProtoPO, &sProtoBaseData);

				// Chenck whether the same part is detected
				for (int i = 0; i < m_aSplitPart.size(); i++)
				{
					if (pProtoPO == m_aSplitPart[i].protoPO)
					{
						// Keep the target POs
						SplitPart splitPart;
						splitPart.parentPO = sConnector.GetProductOccurrenceFather();
						splitPart.oldPartPO = pPO;
						splitPart.newAssyPO = m_aSplitPart[i].newAssyPO;
						splitPart.instanceTransform = sData.m_pLocation;
						splitPart.instanceBaseData = sRootBaseData;

						m_aSplitPart.push_back(splitPart);

						return iRet;
					}
				}
			}

			// Create new POs of the new parts
			A3DAsmProductOccurrence **ppPO;
			ppPO = new A3DAsmProductOccurrence*[m_iBodyCnt];

			for (A3DUns32 ui = 0; ui < m_iBodyCnt; ui++)
			{
				// Create new PO
				A3DAsmProductOccurrenceData sPOData;
				A3D_INITIALIZE_DATA(A3DAsmProductOccurrenceData, sPOData);
				sPOData.m_dUnit = sData.m_dUnit;
				sPOData.m_pPart = m_ppPart[ui];

				iRet = A3DAsmProductOccurrenceCreate(&sPOData, &ppPO[ui]);

				// Set part name (org name - id)
				char name[1024];
				sprintf_s(name, sizeof(name), "%s - %d", sRootBaseData.m_pcName, ui + 1);
				A3DRootBaseData sNewBaseData;
				A3D_INITIALIZE_DATA(A3DRootBaseData, sNewBaseData);
				sNewBaseData.m_pcName = name;

				iRet = A3DRootBaseSet(ppPO[ui], &sNewBaseData);
			}

			// Create a new assembly PO for split parts
			A3DAsmProductOccurrenceData sNewPOData;
			A3D_INITIALIZE_DATA(A3DAsmProductOccurrenceData, sNewPOData);
			sNewPOData.m_uiPOccurrencesSize = m_iBodyCnt;
			sNewPOData.m_dUnit = sData.m_dUnit;
			sNewPOData.m_ppPOccurrences = ppPO;
			// sNewPOData.m_pLocation = sData.m_pLocation;

			iRet = A3DAsmProductOccurrenceCreate(&sNewPOData, &m_pAssyPO);

			iRet = A3DRootBaseSet(m_pAssyPO, &sRootBaseData);

			// Access father PO
			const A3DAsmProductOccurrence *pParentPO = sConnector.GetProductOccurrenceFather();

			// If father PO isn't null, it is assembly model file 
			if (NULL != pParentPO)
			{
				if (NULL != pProtoPO)
				{
					// Set prototype's RootBaseData
					iRet = A3DRootBaseSet(m_pAssyPO, &sProtoBaseData);
				}

				// Keep the target POs
				SplitPart splitPart;
				splitPart.parentPO = sConnector.GetProductOccurrenceFather();
				splitPart.oldPartPO = pPO;
				splitPart.newAssyPO = m_pAssyPO;
				splitPart.protoPO = pProtoPO;
				splitPart.instanceTransform = sData.m_pLocation;
				splitPart.instanceBaseData = sRootBaseData;

				m_aSplitPart.push_back(splitPart);

				m_pAssyPO = NULL;
			}
			// No father PO maens that it is part model file

		}
		else
		{
			// Check whether current PO is in a targets
			for (int i = 0; i < m_aSplitPart.size(); i++)
			{
				SplitPart splitPart = m_aSplitPart[i];

				if (pPO == splitPart.parentPO)
				{
					// Find target multi body part PO and replace to assembly PO
					for (A3DUns32 ui = 0; ui < sData.m_uiPOccurrencesSize; ui++)
					{
						if (sData.m_ppPOccurrences[ui] == splitPart.oldPartPO)
						{
							// Create new sub-assembly instance
							A3DAsmProductOccurrenceData sAssyInstData;
							A3D_INITIALIZE_DATA(A3DAsmProductOccurrenceData, sAssyInstData);
							sAssyInstData.m_pLocation = splitPart.instanceTransform;
							sAssyInstData.m_pPrototype = splitPart.newAssyPO;

							A3DAsmProductOccurrence *pAssyInstPO;
							iRet = A3DAsmProductOccurrenceCreate(&sAssyInstData, &pAssyInstPO);

							// Set RootBaseData of the instance
							iRet = A3DRootBaseSet(pAssyInstPO, &splitPart.instanceBaseData);

							// Replace part instance to sub-assembly instance 
							sData.m_ppPOccurrences[ui] = pAssyInstPO;
							break;
						}
					}

					iRet = A3DAsmProductOccurrenceEdit(&sData, pPO);
				}
			}
		}

		iRet = A3DTreeVisitor::visitLeave(sConnector);
		return iRet;
	}

	virtual A3DStatus visitLeave(const A3DModelFileConnector& sConnector) override
	{
		A3DStatus iRet = A3D_SUCCESS;

		const A3DEntity* pEntity = sConnector.GetA3DEntity();
		A3DAsmModelFile* pModelFile = (A3DAsmModelFile*)pEntity;

		A3DAsmModelFileData sData = sConnector.m_sModelFileData;

		A3DRootBaseData sRootBaseData;
		A3D_INITIALIZE_DATA(A3DRootBaseData, sRootBaseData);
		A3DRootBaseGet(pModelFile, &sRootBaseData);

		if (m_pAssyPO)
		{
			// Create assemly pModelFile
			A3DAsmModelFileData sModelFileData = sConnector.m_sModelFileData;
			sModelFileData.m_bUnitFromCAD = sData.m_bUnitFromCAD;
			sModelFileData.m_dUnit = sData.m_dUnit;
			sModelFileData.m_eModellerType = kA3DModellerPrc;
			sModelFileData.m_uiPOccurrencesSize = 1;
			sModelFileData.m_ppPOccurrences = &m_pAssyPO;

			iRet = A3DAsmModelFileCreate(&sModelFileData, &s_pNewModelFile);

			// Set root made data of original model file
			iRet = A3DRootBaseSet(s_pNewModelFile, &sRootBaseData);

			m_pAssyPO = NULL;
		}

		return iRet;
	}
};

void traverseModelFile(A3DAsmModelFile* pModelFile)
{
	// Prepare Visitor container
	A3DVisitorContainer sA3DVisitorContainer(CONNECT_TRANSFO);
	sA3DVisitorContainer.SetTraverseInstance(true);

	// Prepare Tree traverse visitor and set to the container
	bodyOperationVisitor *pBodyOpVisitor = new bodyOperationVisitor(&sA3DVisitorContainer);
	sA3DVisitorContainer.push(pBodyOpVisitor);

	// Prepare model file connector and call Traverse
	A3DModelFileConnector sModelFileConnector(pModelFile);
	A3DStatus sStatus = sModelFileConnector.Traverse(&sA3DVisitorContainer);

}

//######################################################################################################################
#ifdef _MSC_VER
int wmain(A3DInt32 iArgc, A3DUniChar** ppcArgv)
#else
int main(A3DInt32 iArgc, A3DUTF8Char** ppcArgv)
#endif
{
	//
	// ### COMMAND LINE ARGUMENTS
	//

	if (iArgc < 2)
	{
		MY_PRINTF2("Usage:\n %s [input CAD file] [output CAD file] [output LOG file]\n", ppcArgv[0]);
		MY_PRINTF(" Default output CAD file is [input CAD file].prc\n");
		MY_PRINTF(" Default output LOG file is [output CAD file]_Log.txt\n\n");
		return A3D_ERROR;
	}

	if (iArgc > 1) MY_STRCPY(acSrcFileName, ppcArgv[1]);
	else MY_STRCPY(acSrcFileName, DEFAULT_INPUT_CAD);
	if (iArgc > 2) MY_STRCPY(acDstFileName, ppcArgv[2]);
	else MY_SPRINTF(acDstFileName, "%s.prc", acSrcFileName);
	if (iArgc > 3) MY_STRCPY(acLogFileName, ppcArgv[3]);
	else MY_SPRINTF(acLogFileName, "%s_Log.txt", acDstFileName);
	GetLogFile(acLogFileName); // Initialize log file

	//
	// ### 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());
	CHECK_RET(sHoopsExchangeLoader.m_eSDKStatus);

	// Uncomment these lines to track memory leaks
	//CHECK_RET(A3DDllSetCallbacksMemory(CheckMalloc, CheckFree));
	CHECK_RET(A3DDllSetCallbacksReport(PrintLogMessage, PrintLogWarning, PrintLogError));

	//
	// ### PROCESS SAMPLE CODE
	//

	// specify input file
	A3DImport sImport(acSrcFileName); // see A3DSDKInternalConvert.hxx for import and export detailed parameters

									  // specify output file
	A3DExport sExport(acDstFileName); // see A3DSDKInternalConvert.hxx for import and export detailed parameters

									  // perform conversion
	CHECK_RET(sHoopsExchangeLoader.Import(sImport));

	traverseModelFile(sHoopsExchangeLoader.m_psModelFile);

	if (NULL != s_pNewModelFile)
	{
		A3DStatus iRet = A3DAsmModelFileDelete(sHoopsExchangeLoader.m_psModelFile);

		sHoopsExchangeLoader.m_psModelFile = s_pNewModelFile;

		s_pNewModelFile = NULL;
	}

	CHECK_RET(sHoopsExchangeLoader.Export(sExport));

	//
	// ### TERMINATE HOOPS EXCHANGE
	//

	// Check memory allocations
	//if (siCheckMallocCounter != 0)
	//	fprintf(GetLogFile(), "stiMallocCounter=%d", siCheckMallocCounter);

	return A3D_SUCCESS;
}

```
