avMergeEdgesCmd

This is a simple but optimised Maya API plugin which I needed to write to allow me to create a single piece mesh from several adjoining meshes which came from my Marvellous Designer to Quad Mesh converter tool

It assumes a single combined mesh of multiple patches whose edges touch, but are not sewn. 

The tool adds the vertices along touching edges such that a poly merge creates a single seamless mesh.

It is highly optimised so I can use it with 1m+ poly models


I have display->polygons->borderEdges on here to show there are no internal gaps between the patches



Confusing image, but this shows the vertices of one face and how the tool has added the 2 lower central vertices to the face whose vertices are shown with yellow points. This means that a polygon merge can stitch the patches together without moving points.

The reason adding extra points in is important is that it gives a better control when a poly merge is applied.

The mesh can then be cleaned up more easily by collapsing edges.

This helps create a better triangulated sim mesh as well as a render mesh which is continuous.

A continuous render mesh is helpful for displacements and sub surface scattering for obvious reasons






To use, select a combined mesh such as the one above and run:

avMergeEdges();

select the mesh,

use polyMergeVertex / editMesh->merge

adjust the merge's distance value until you see no edges (go display->polygons->border edges to see border edges)




Ive attached the code along with a version compiled for Maya 2013 x64 windows 







//
// ==========================================================================
// Copyright 2012 Adam Vanner adamvanner@gmail.com
//
// Provided as is, with no warranty.
// Free to distribute, copy and modify
// ==========================================================================
//
// avMergeEdgesCmd
// 
// For more info, see: https://sites.google.com/site/adamvanner/scripts-and-tools/avmergeedgescmd
// 
// Add vertices along edges which are aligned with other edges within the same mesh such that when a poly merge is applied, the mesh is seamless without moving vertices
// 
// Usage:
//    select a mesh containing several combined touching patches
//    >> avMergeEdgesCmd()

//   options:
//     you can limit the working selection by selecting border edges instead of the mesh
//     
//     you can pass in two doubles as arguments. These set the Positional and Angular Tolerance
//     >> avMergeEdgesCmd(tolerance = 0.01, angularTolerance = 0.95)
//
//     The Tolerance is in edge parametric space and so runs between 0.0 and 1.0. A value of 0.0 means points must be precisely on the edge for it to add a point
//     Angular tolerance is used via a dot product to check whether edges touch by checking whether they are parallel. In other words, by default edges must be 95% parallel

#include <math.h>
#include <maya/MIOStream.h>
#include <maya/MPxCommand.h>
#include <maya/MStatus.h>
#include <maya/MObject.h>
#include <maya/MFnPlugin.h>
#include <maya/MString.h>
#include <maya/MArgList.h>
#include <maya/MGlobal.h>
#include <maya/MPoint.h>
#include <maya/MVector.h>
#include <maya/MVectorArray.h>
#include <maya/MMatrix.h>
#include <maya/MDagPath.h>

#include <maya/MSelectionList.h>
#include <maya/MItSelectionList.h>
#include <maya/MItMeshVertex.h>
#include <maya/MItMeshEdge.h>
#include <maya/MFnMesh.h>
#include <maya/MPoint.h>
#include <maya/MPointArray.h>
#include <maya/MFloatPointArray.h>
#include <maya/MFnSingleIndexedComponent.h>
#include <maya/MFnMeshData.h>
#include <vector>
#include <algorithm>
#include <time.h>

#define MCHECKERR(stat, msg) \
if( stat != MS::kSuccess) \
{ cerr << MString("Line: ") + __LINE__ << " " <<msg << " " << stat<< "\n"; \
return stat; }


//////////////////////
// Class Definition //
//////////////////////

class avMergeEdgesCmd : public MPxCommand {
public:

avMergeEdgesCmd();
virtual ~avMergeEdgesCmd();

virtual MStatus doIt( const MArgList& args );

static void* creator();
};


MString getProgressBar()
{
MString result;
MStatus status;
status = MGlobal::executeCommand("$tmp = $gMainProgressBar", result);
if( status != MS::kSuccess)
{
cerr <<"cant get progress bar\n";
return "";
}
return result;
}

MStatus progressStart( MString progressBar, MString message, int steps)
{
MString cmd = "progressBar -e -beginProgress -isInterruptable true -status \""+message+MString("\" -maxValue ")+ steps+" \""+progressBar+"\"";
MStatus status =  MGlobal::executeCommand(cmd);
if( status != MS::kSuccess)
{
cerr <<"cant progressStart\n";
}
return status;
}

MStatus progressEnd( MString progressBar)
{
MString cmd = "progressBar -e -endProgress \""+progressBar+"\"";
MStatus status =MGlobal::executeCommand(cmd);
if( status != MS::kSuccess)
{
cerr <<"cant progressEnd\n";
}
return status;
}

MStatus progressStep( MString progressBar)
{
MString cmd = "progressBar -e -s 1 \""+progressBar+"\"";
MStatus status =MGlobal::executeCommand(cmd);
if( status != MS::kSuccess)
{cerr <<"cant progressStep\n";
}
return status;
}

bool progressIsCancelled( MString progressBar)
{
MString cmd = "progressBar -q -ic \""+progressBar+"\"";
double result;
MStatus status =MGlobal::executeCommand(cmd,result);
if( status != MS::kSuccess)
{cerr <<"cant progressIsCancelled\n";
}
return result == 1.0;
}

//////////////////////////
// Class Implementation //
//////////////////////////


avMergeEdgesCmd::~avMergeEdgesCmd() {}

avMergeEdgesCmd::avMergeEdgesCmd() {}

void* avMergeEdgesCmd::creator()
{
return new avMergeEdgesCmd;
}



struct avSplitGroup
{
MIntArray edgeIndices;
MVectorArray splitPoints;
};

#define MAX_SPLITS 8192

MStatus mergeEdges(MDagPath meshPath, MObject component, double tol, double anglularTolerance)
{
MStatus status;

MVectorArray points;

// convert selected edge list to vertices
MGlobal::executeCommand("select `polyListComponentConversion -tv`");
MSelectionList vertSel; MGlobal::getActiveSelectionList(vertSel);
MItSelectionList iter( vertSel );
MDagPath objectPath; MObject vertComponent;
if( vertSel.length() && !iter.isDone())
status = iter.getDagPath( objectPath, vertComponent );
MFnMesh mesh(meshPath.node(&status));

MItMeshVertex vertexIter( objectPath, vertComponent, &status );
MCHECKERR(status,"cant create vertex iterator0")

uint vcount = vertexIter.count();
points.setLength(vcount);
for ( uint i=0 ; !vertexIter.isDone(); vertexIter.next(),i++ ) 
points[i] = vertexIter.position();
double paramMin = tol, paramMax = 1.0-tol;


uint nMeshPoints = mesh.numVertices();

MItMeshEdge edgeIter( meshPath, component, &status );
MCHECKERR(status,"cant create edge iterator")

uint nEdges = edgeIter.count();


uint nPoints = points.length();


MVectorArray edgeP0, edgeP1; MIntArray edgeIdx; edgeP0.setLength(nEdges); edgeP1.setLength(nEdges);edgeIdx.setLength(nEdges);

for (uint c=0 ; !edgeIter.isDone(); edgeIter.next(),c++ ) 
{
edgeP0[c] = edgeIter.point(0);
edgeP1[c] = edgeIter.point(1);
MFnSingleIndexedComponent edgeComp(edgeIter.edge());
edgeIdx[c] =  edgeComp.element(0);
}

// make a sparse array containing pointers - each index will refer to a specific number of splits
avSplitGroup**splitDataSparse = (avSplitGroup**)calloc(MAX_SPLITS, sizeof(avSplitGroup*));
uint maxSplits = 0, nSplitGroups=0, numFoundSplits=0;


for (uint c=0 ; c < nEdges; c++ ) 
{
// prepare edge data
MVector A = edgeP0[c];
MVector B = edgeP1[c];

MVector AB = B-A;
double lenAB = AB.length(), invLenAB = 1.0/lenAB; 
MVector ABunit = AB.normal();

//clock_t start = clock();
std::vector<double> paramsV;
// find verts that are close enough to the edge and store the parameter where they hit
for( uint i=0; i < nPoints; i++ )
if( (A- points[i]).length() < lenAB) // ignore anything over the length of the edge away from the first point this distance
{
MVector &P = points[i];
MVector PA =  P - A;
MVector PAu = PA.normal();
double t= ( ABunit * PA) * invLenAB;
if(t > paramMin && t < paramMax  && fabs( ABunit * PAu) > anglularTolerance )
paramsV.push_back(t);
}
uint psize = static_cast<uint>(paramsV.size());

if(psize)
{
// get the existing split index or make a new one
avSplitGroup* splitInfo;
if(splitDataSparse[psize])
splitInfo = splitDataSparse[psize];
else
{
splitInfo = splitDataSparse[psize] = new avSplitGroup;
maxSplits = psize >= maxSplits ? psize : maxSplits;
nSplitGroups++;
}
splitInfo->edgeIndices.append(edgeIdx[c]);

// insert points must be sorted otheriwse this wont work
std::sort(paramsV.begin(),paramsV.end());
numFoundSplits += psize;
for (uint i=0; i < psize; i++)
splitInfo->splitPoints.append(A + AB * paramsV[i]); // where the point should be
}
}


int doneP =0;

uint handled = 0;
for( uint ns=0; ns <= nSplitGroups+1; ns++)
if( splitDataSparse[ns])
{
avSplitGroup *splitInfo = splitDataSparse[ns];

status = mesh.subdivideEdges(splitInfo->edgeIndices,ns);
doneP += splitInfo->edgeIndices.length() * ns;
if( status != MS::kSuccess)
{
cerr << "subdivideEdges  problem\n";
break;
}
handled++;
}

// now set the new points correctly
status = mesh.updateSurface();
MCHECKERR(status,"updateSurface failed")
MFnMesh newMesh;
MFnMeshData meshDataFn;
// silly trick
MObject newMeshObj = meshDataFn.create();
newMesh.copy(mesh.object(), newMeshObj);
MCHECKERR(status,"cant create new mesh")

uint newMeshPoints = newMesh.numVertices();

uint cVertex = nMeshPoints;
int doneC=0, doneG=0;
for( uint ns=0; ns <= nSplitGroups+1; ns++)
if( splitDataSparse[ns])
{
avSplitGroup *splitInfo = splitDataSparse[ns];
//cerr << "found group " << ns <<"\n";
uint spl = splitInfo->splitPoints.length();
for( uint j =0; j < spl; j++)
{
status = newMesh.setPoint(cVertex, splitInfo->splitPoints[j]);
if( status != MS::kSuccess)
{
MString isInRange;
if( cVertex < newMeshPoints)
isInRange = "Yes";
else
isInRange = "No";
cerr << "no verex exists: " << cVertex <<" .. is in range: "<<isInRange <<"\n"; 
break;
}
cVertex++;
doneC += 1;
}
doneG++;
delete splitInfo;
}

free(splitDataSparse);

if(  numFoundSplits -doneC)
cerr << "Missing " << numFoundSplits -doneC << " splits\n";
status = newMesh.updateSurface();
MCHECKERR(status,"updateSurface failed")

status = mesh.copyInPlace(meshDataFn.object());
MCHECKERR(status,"copy failed")

return status;

}



MStatus avMergeEdgesCmd::doIt( const MArgList& args)
//
// Description:
// Plugin command to test Selection List Iterator.
//
//
{
MStatus status;

double tollerance = 0.01;
double angularTollerance = 0.95;

if(args.length() == 2 )
{
MStatus stat;
tollerance = args.asDouble(0,&stat);

if(stat != MS::kSuccess)
{
MGlobal::displayWarning("avMergeEdgesCmdCmd: argument 1 must be double (tollerance),.. ignored");
tollerance = 0.01;
}
angularTollerance = args.asDouble(1,&stat);
if(stat != MS::kSuccess)
{
MGlobal::displayWarning("avMergeEdgesCmdCmd: argument must be double (angularTollerance),.. ignored");
angularTollerance = 0.95;
}
}

// Create an iterator for the active selection list
//
MSelectionList slist;
MGlobal::getActiveSelectionList( slist );
MItSelectionList iter( slist );

if (iter.isDone()) {
cerr << "Nothing selected\n";
return MS::kFailure;
}


MDagPath objectPath;
MObject component;

for ( ; !iter.isDone(); iter.next() ) {
status = iter.getDagPath( objectPath, component );



if (objectPath.hasFn(MFn::kMesh))
{
// if no components are selected, select all edges
if(!component.hasFn(MFn::kMeshEdgeComponent))
{
status = iter.getDagPath( objectPath );
MFnMesh tmesh(objectPath);
MCHECKERR(status,"cant init mesh set")
uint ne = tmesh.numEdges(&status);
MCHECKERR(status,"cant get edges")

char cmd[1024];
sprintf(cmd,"hilite %s; select %s.e[0:%d];polySelectConstraint -pp 3; refresh(); ",tmesh.name().asChar(),tmesh.name().asChar(),ne );
MGlobal::executeCommand(cmd);
MSelectionList tempEdgeSel; MGlobal::getActiveSelectionList(tempEdgeSel);
MItSelectionList teiter( tempEdgeSel );
MDagPath teobjectPath;
if( tempEdgeSel.length() && !teiter.isDone())
status = teiter.getDagPath( objectPath, component );
MCHECKERR(status,"cant get dagpath")
}

status = mergeEdges(objectPath, component, tollerance, angularTollerance);
}
else {
cerr << "Selected object is not a polygon\n";
return MS::kFailure;
}
}
return status;
}





//////////////////////////////////
// Register command with system //
//////////////////////////////////


MStatus initializePlugin( MObject obj )
{
MStatus   status;
MFnPlugin plugin( obj, PLUGIN_COMPANY, "3.0", "Any");

status = plugin.registerCommand( "avMergeEdges", avMergeEdgesCmd::creator );
if (!status) {
status.perror("registerCommand");
return status;
}


return status;
}

MStatus uninitializePlugin( MObject obj )
{
MStatus   status;
MFnPlugin plugin( obj );

status = plugin.deregisterCommand( "avMergeEdges" );
if (!status) {
status.perror("deregisterCommand");
return status;
}

return status;
}


ċ
avMergeEdgesCmd.cpp
(12k)
Adam Vanner,
10 Oct 2012, 13:48
ċ
avMergeEdgesCmd.mll
(35k)
Adam Vanner,
10 Oct 2012, 13:50
Comments