We have an inbuilt AutoCAD command CONVTOSOLID, but this command is very limited and 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, then convert this mesh object to a solid using the convertToSolid API.
Before we get into this, you might want to know commands and their meaning while working on Meshes.
You can find a 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.
What is a vertex array?
An array of vertex points that makes each face.
What is a 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 the 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 is a hole.
Code:
void test4()
{
AcDbObjectId entId;
ads_name ent_name;
AcGePoint3d pickPnt;
if (acedEntSel(_T("\n Pick a PFACEn"), 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; // Unused?
AcArray<AcDbObjectId> materialArray; // Unused?
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; // Unused?
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(); // Unused?
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;
}

Leave a Reply