Is it possible to use ON_Brep::CreateMesh() concurrently?

Hi,
I am trying to create a function that converts a large number of extrusions (14K) to meshes.
I managed to do that in realistic timing and get a ON_SimpleArray<ON_Mesh*> after slicing the extrusions and convert them concurrently in the following way for each thread:

for (int i = 0; i < slicedObjectsAttributesChecked.Count(); i++)
{
CRhinoObjRef objRef = CRhinoObjRef(slicedObjectsAttributesChecked[i]);
const ON_Geometry* geometry = objRef.Geometry();
const ON_Extrusion* extrusionCast = ON_Extrusion::Cast(geometry);
   if (nullptr != extrusionCast)
   {
	  ON_Brep* brep = extrusionCast->BrepForm();			
	  if (brep)
	  {
		ON_SimpleArray<ON_Mesh*> list(brep->m_F.Count());
		int meshCount = brep->CreateMesh(mp, list);
		if (meshCount == brep->m_F.Count())
		{
			for (int i = 0; i < list.Count(); i++)
			{
				if (nullptr != list[i])
				{
					slicedMeshes.Append(list[i]);
				}
			}
		}
                progressBarPosition->fetch_add(1);//atomic variable for progress bar
		delete brep;
		continue;
	  }			
    }
}

It seemed to give correct results after an extensive set of tests. One thing however that seemed not to work well is that the time consumed to finish all the threads was increasing after each time I was calling the multithreaded conversion. The following shows the growth pattern in seconds:

1.45894
2.05183
4.96986
7.17207
9.13605
8.82918
9.21310
10.74482
9.30600
9.20454
9.02471
9.55440
9.16980
9.64565
9.86206
9.63683
9.59136

It seems that after reaching the 9 to 10 seconds (always for the same job of 14K extrusions) the time did not grow any further. My first thought was a probable memory leak but after many days of checking the code it seemed that this was not the case. I went a bit deeper and I realised that the function ON_Brep::CreateMesh() is not thread safe:

*/  
Description:
    Calculates polygon mesh approximation of the brep
    and appends one mesh for each face to the mesh_list[]
    array.
  Parameters:
    mp - [in] meshing parameters
    mesh_list - [out] meshes are appended to this array.
  Returns:
    Number of meshes appended to mesh_list[] array.
  Note:
    This function is not thread safe.  
  */
  int CreateMesh( 
    const ON_MeshParameters& mp,
    ON_SimpleArray<ON_Mesh*>& mesh_list
    ) const;
  1. Could it be that this non thread safe function (used concurrently) was not deallocating the memory properly causing this time growth?
  2. Is there really any workaround to make it run concurrently?
  3. Is there any alternative option to convert lots of extrusions into ON_Mesh?

Thanks!

There is a function to directly create a mesh from an extrusion without having to convert to brep. See ON_Extrusion::CreateMesh

1 Like

Thanks a lot, for the quick answer!
I would like to examine if the Rhino project contains breps as well:

// Try casting as brep object 
const ON_Brep* brepCast = ON_Brep::Cast(geometry);
if (nullptr != brepCast)
{		
	std::unique_ptr<ON_Brep> brep(brepCast->BrepForm());
	if (brep)
	{
		ON_SimpleArray<ON_Mesh*> list(brep->m_F.Count());
		int meshCount = brep->CreateMesh(mp, list);<------------------------------HERE

		if (meshCount == brep->m_F.Count())
		{
			for (int i = 0; i < list.Count(); i++)
			{
				if (nullptr != list[i])
				{
					projectMeshes.Append(list[i]);
				}						
			}
		}

Since CreateMesh is not thread safe, is there any other option than CreateMesh(mp, list) to get the ON_SimpleArray<ON_Mesh*> out of ON_Brep*?
Thanks!

1 Like

Any ideas on how to convert a ON_Brep to ON_SimpleArray<ON_Mesh*> concurrently?

I am trying to figure out if running the brep->CreateMesh(mp, list) in async mode would be risky to use in my case.
I first slice the scene objects and then I run async each slice of objects. In this way the threads do not have any shared data. Each thread has its own slice of data:

bool LayerState::ObjectsToMeshes(ON_SimpleArray<CRhinoObject*>& objectsAttributesChecked, ON_SimpleArray<ON_Mesh*>& meshesCSR)
{

	int objCount = objectsAttributesChecked.Count();

	int concurrency = CalculateCONCURRENCY(objectsAttributesChecked.Count());

	std::vector< ON_SimpleArray<CRhinoObject*> > slicedObjectsAttributesChecked = ObjectSlicer(concurrency, objCount, objectsAttributesChecked);

	std::vector< std::shared_future<ON_SimpleArray<ON_Mesh*>> > objectsAttributesFutures;

	// Run async
	for (int i = 0; i < concurrency; i++)
	{

		std::shared_future< ON_SimpleArray<ON_Mesh*> > future = std::async(std::launch::async, &LayerState::GetMeshFromObject, this, slicedObjectsAttributesChecked[i]);

		objectsAttributesFutures.push_back(future);
	}

	for (int i = 0; i < concurrency; i++)
	{
		ON_SimpleArray<ON_Mesh*> mtResults = objectsAttributesFutures[i].get();

		for (int j = 0; j < mtResults.Count(); j++)
		{
			meshesCSR.Append(mtResults[j]);
		}
	}

	return true;
}

This is the actual function that converts breps to mesh:

ON_SimpleArray<ON_Mesh*> LayerState::GetMeshFromObject(ON_SimpleArray< CRhinoObject*>& slicedObjectsAttributesChecked)
{
	ON_SimpleArray<ON_Mesh*> slicedMeshesCSR;

	ON_MeshParameters mp = RRTV8SetQualityMesh(FALSE);

	ON_MassProperties MassProp;

	bool isObjectGeometryProcessedSuccessfully;

	for (int i = 0; i < slicedObjectsAttributesChecked.Count(); i++)
	{
		isObjectGeometryProcessedSuccessfully = false;

		CRhinoObjRef objRef = CRhinoObjRef(slicedObjectsAttributesChecked[i]);

		const ON_Geometry* geometry = objRef.Geometry();

		// Try casting as brep object
		const ON_Brep* brepCast = ON_Brep::Cast(geometry);

		if (nullptr != brepCast)
		{
			ON_Brep* brep = brepCast->BrepForm();

			if (nullptr != brep)
			{

				ON_SimpleArray<ON_Mesh*> list(brep->m_F.Count());

				int meshCount = brep->CreateMesh(mp, list);

				if (meshCount == brep->m_F.Count())
				{
					for (int i = 0; i < list.Count(); i++)
					{
						if (nullptr != list[i])
						{
							slicedMeshesCSR.Append(list[i]);
						}
					}
				}

				isObjectGeometryProcessedSuccessfully = true;

				delete brep;

				return slicedMeshesCSR;
			}
		}
	}
}

Testing so far shows accurate results (which could be a coincidence?) but running times are growing which seems to be a bad memory de-allocation (or something else?). Any thoughts on this? Is there any alternative to convert brep to mesh concurrently?