We have an inbuilt AutoCAD command CONVTOSOLID, but this command is very limited doesn’t work effectively on
Polyfacemesh <AcDbPolyFaceMesh> entities.
We will create an in-memory subDMesh <AcDbSubDMesh> object from pfmesh [read as Polyfacemesh], first we will extract vertex and face information from pfmesh and use this information in constructing a subDMesh, convert this mesh object solid using convertToSolid API.
Before we get in to this, if you must know commands and their meaning while working on Meshs.
You can find full glossary at MeshPrimitives, a quick reference for the reader is below:
DIVMESHBOXWIDTH: Sets the number of subdivisions for the width of a mesh box along the Y axis. DIVMESHBOXHEIGHT : Sets the number of subdivisions for the height of a mesh box along the Z axis. DIVMESHCONEAXIS : Sets the number of subdivisions around the perimeter of the mesh cone base. DIVMESHCONEHEIGHT : Sets the number of subdivisions between the base and the point or top of the mesh cone. DIVMESHCONEBASE : Sets the number of subdivisions between the perimeter and the center point of the mesh cone base. DIVMESHCYLAXIS : Sets the number of subdivisions around the perimeter of the mesh cylinder base. DIVMESHCYLHEIGHT : Sets the number of subdivisions between the base and the top of the mesh cylinder. DIVMESHCYLBASE : Sets the number of radial subdivisions from the center of the mesh cylinder base to its perimeter. DIVMESHPYRLENGTH : Sets the number of subdivisions along each dimension of a mesh pyramid base. DIVMESHPYRHEIGHT : Sets the number of subdivisions between the base and the top of the mesh pyramid. DIVMESHPYRBASE : Sets the number of radial subdivisions between the center of the mesh pyramid base and its perimeter. DIVMESHSPHEREAXIS : Sets the number of radial subdivisions around the axis endpoint of the mesh sphere. DIVMESHSPHEREHEIGHT : Sets the number of subdivisions between the two axis endpoints of the mesh sphere. DIVMESHWEDGELENGTH : Sets the number of subdivisions for the length of a mesh wedge along the X axis. DIVMESHWEDGEWIDTH : Sets the number of subdivisions for the width of the mesh wedge along the Y axis. DIVMESHWEDGEHEIGHT : Sets the number of subdivisions for the height of the mesh wedge along the Z axis. DIVMESHWEDGESLOPE : Sets the number of subdivisions in the slope that extends from the apex of the wedge to the edge of the base. DIVMESHWEDGEBASE : Sets the number of subdivisions between the midpoint of the perimeter of triangular dimension of the mesh wedge. DIVMESHTORUSSECTION : Sets the number of subdivisions in the profile that sweeps the path of a mesh torus. DIVMESHTORUSPATH : Sets the number of subdivisions in the path that is swept by the profile of a mesh torus
We have an API
AcDbSubDMesh::setSubDMesh which creates Mesh entity for a given vertex array and face array, first we will try to understand about vertex array and face array
What is vertex array ?
An array of vertex points that makes each face.
What is Face array ?
The face Array defines an array of faces, and each face is defined by a set of numbers. The first number specifies the number of vertices in the face, the following numbers are the indices of the vertices making up the face.
Face array F[] = {N0,a0,...,aN0,N1,b0,...,bN1} Where N0 is Number of vertices in that particular face and a0,...,aN0 are the corresponding vertex indices in the Vertex array
To illustrate :
Face array F[] for the above faces would be - {3, 0, 1, 2, -3, 3, 4, 5, 3, 6, 7, 8}; A negative before a Number of vertices <for e.g.,-3> indicates the face is lying inside or a hole.
Code:
void test4()
{
AcDbObjectId entId;
ads_name ent_name;
AcGePoint3d pickPnt;
if (acedEntSel(_T("\n Pick a PFACE\n"), ent_name,
asDblArray(pickPnt)) != RTNORM) return;
if (!eOkVerify(acdbGetObjectId(entId, ent_name))) return;
AcDbSmartObjectPointer<AcDbPolyFaceMesh> pFace(entId, AcDb::kForWrite);
// polyface mesh
if (!eOkVerify(pFace.openStatus())) return;
AcGePoint3dArray vertexArray;
AcArray<Adesk::Int32> faceArray;
AcArray<AcCmColor> colorArray;
AcArray<AcDbObjectId> materialArray;
Acad::ErrorStatus es = GetPolyFaceMeshData(pFace, vertexArray, faceArray);
if (es != eOk)
return;
/*Mesh Type ":
Specifies the type of mesh to be used in the conversion. (FACETERMESHTYPE system variable)
0 : Smooth Mesh Optimized. Sets the shape of the mesh faces to adapt to the shape of the mesh object.
1 : Mostly Quads. Sets the shape of the mesh faces to be mostly quadrilateral.
2 : Triangles. Sets the shape of the mesh faces to be mostly triangular.*/
int faceterMeshType = 0;
int nSmoothLevel = 0;
int nMaxFaces = 0;
resbuf rb;
/*
This variable sets the default level of smoothness that is applied to
mesh that is created as a result of conversion from another object with the MESHSMOOTH command.
The value cannot be greater than the value of SMOOTHMESHMAXLEV.
*/
if (acedGetVar(_T("FACETERSMOOTHLEV"), &rb) == RTNORM)
{
nSmoothLevel = rb.resval.rint;
nSmoothLevel = (nSmoothLevel < 0 ? 0 : nSmoothLevel);
}
if (acedGetVar(_T("FACETERMESHTYPE"), &rb) == RTNORM)
{
faceterMeshType = rb.resval.rint;
}
int newSubdLevel = (faceterMeshType == 0) ? nSmoothLevel : 0;
int oldSubdLevel = nSmoothLevel;
// If oldSubdivLevel > SmoothMeshMaxLev, we need to reset oldSubdivLevel
// value to SmoothMeshMaxLev.
// If newSubdivLevel > SmoothMeshMaxLev, we need to reset newSubdivLevel
// value to SmoothMeshMaxLev.
//The valid range is from 1 to 255. The recommended range is 1 - 5.
//Use this limit to prevent creating extremely dense meshes that might affect program performance.
if (acedGetVar(_T("SMOOTHMESHMAXLEV"), &rb) == RTNORM)
{
int nMaxLevel = rb.resval.rint;
if (nMaxLevel >= 0 && oldSubdLevel > nMaxLevel)
oldSubdLevel = nMaxLevel;
if (nMaxLevel >= 0 && newSubdLevel > nMaxLevel)
newSubdLevel = nMaxLevel;
}
/*We create an in-memory mesh and convert mesh to solid*/
AcDbSubDMesh* pSubDMesh = nullptr;
if (!eOkVerify(CreateSubdMesh(vertexArray, faceArray, newSubdLevel, pSubDMesh)))
return;
pSubDMesh->setPropertiesFrom(pFace);
/*Create A solid and Convert SubDMesh to Solid*/
AcDb3dSolid* pMeshSolid = new AcDb3dSolid();
AcDbObjectId meshSolId;
/*Now convert to Solid using CONVTOSOLID API*/
/*We will restrict to polygonal solid, we set false for smoothness, so not to abuse CPU*/
pSubDMesh->convertToSolid(false, false, pMeshSolid);
postToDatabase(NULL, pMeshSolid, meshSolId);
/*Delete Pface */
if (pFace)
{
pFace->erase();
}
}
Helper Methods
/*Get the Mesh data, not accounted for Color and Material of Faces*/
Acad::ErrorStatus GetPolyFaceMeshData(AcDbPolyFaceMesh* pMesh,
AcGePoint3dArray& vertexArray,
AcArray<Adesk::Int32>& faceArray)
{
if (pMesh == NULL)
return eNullObjectPointer;
const bool dbResident = (pMesh->database() != NULL);
AcDbObjectIterator* pIter = pMesh->vertexIterator();
if (pIter == NULL)
return eNullObjectPointer;
Acad::ErrorStatus es;
AcGePoint3d pt;
AcDbVertex* pVtxObj = NULL;
AcDbPolyFaceMeshVertex *pVertex = NULL;
AcDbFaceRecord *pFaceRec = NULL;
int nFaces = pMesh->numFaces();
for (; !pIter->done(); pIter->step())
{
if (acdbHostApplicationServices()->userBreak())
{
delete pIter;
return eUserBreak;
}
if (dbResident)
{
es = acdbOpenObject(pVtxObj, pIter->objectId(), AcDb::kForRead);
if (es != Acad::eOk)
continue;
}
else
{
pVtxObj = (AcDbVertex*)pIter->entity();
}
// Build vertex list
if ((pVertex = AcDbPolyFaceMeshVertex::cast(pVtxObj)) != NULL)
{
pt = pVertex->position();
vertexArray.append(pt);
}// Build face list
else if ((pFaceRec = AcDbFaceRecord::cast(pVtxObj)) != NULL)
{
int count = 0;
Adesk::Int16 indices[4], newIndex =0;
for (int i = 0; i < 4; i++)
{
pFaceRec->getVertexAt(i, indices[i]);
if (indices[i] == 0)
break;
count++;
}
// Quick triangle test by checking if the first vertex
// is the same as the last vertex
if (count == 4 && (abs(indices[0]) == abs(indices[3])))
count = 3;
// Per face record can be a triangle or quad
if (count > 2)
{
faceArray.append(count);
for (int i = 0; i < count; i++)
{
newIndex = abs(indices[i]) - 1;
faceArray.append(newIndex);
}
}
}
if (dbResident)
pVtxObj->close();
}
delete pIter;
return eOk;
}
Acad::ErrorStatus CreateSubdMesh(AcGePoint3dArray& vertexArray,
AcArray<Adesk::Int32>& faceArray,
int nSubdivLevel,
AcDbSubDMesh*& poly)
{
if (vertexArray.length() <= 0 || faceArray.length() <= 0)
return Acad::eCreateFailed;
poly = new AcDbSubDMesh();
ASSERT(poly != NULL);
if (poly == NULL)
return Acad::eCreateFailed;
Acad::ErrorStatus es = poly->setSubDMesh(vertexArray, faceArray, nSubdivLevel);
return es;
}