diff options
author | Ken Hughes <khughes@pacific.edu> | 2005-10-20 01:24:18 +0400 |
---|---|---|
committer | Ken Hughes <khughes@pacific.edu> | 2005-10-20 01:24:18 +0400 |
commit | 7d325f1ed42dcacaa0aed85da7e311571e5bfa19 (patch) | |
tree | 94d0abeea5b9dde7a68aa2616cc6206eb11439b2 /source | |
parent | b83f3183c0bd2000c871af6f45bf072c51545a64 (diff) |
- New additions to Mesh module
- new methods from NMesh (transform, getFromObject, findEdges)
- new methods for deleting groups of verts, edges and faces
- new methods for accessing mesh editing tools: fill, flipNormals,
recalcNormals, remDoubles, smooth, subdivide, toSphere
- Added PVertType to Types module (not my favorite name; any suggestions?)
Diffstat (limited to 'source')
-rw-r--r-- | source/blender/python/api2_2x/Mesh.c | 1433 | ||||
-rw-r--r-- | source/blender/python/api2_2x/Mesh.h | 6 | ||||
-rw-r--r-- | source/blender/python/api2_2x/Types.c | 3 | ||||
-rw-r--r-- | source/blender/python/api2_2x/Types.h | 3 | ||||
-rw-r--r-- | source/blender/python/api2_2x/doc/Mesh.py | 205 | ||||
-rw-r--r-- | source/blender/python/api2_2x/doc/Types.py | 5 |
6 files changed, 1585 insertions, 70 deletions
diff --git a/source/blender/python/api2_2x/Mesh.c b/source/blender/python/api2_2x/Mesh.c index 14e0248a551..ae982e5d42e 100644 --- a/source/blender/python/api2_2x/Mesh.c +++ b/source/blender/python/api2_2x/Mesh.c @@ -40,6 +40,7 @@ #include "DNA_oops_types.h" #include "DNA_space_types.h" #include "DNA_curve_types.h" +#include "DNA_meta_types.h" #include "BDR_editface.h" /* make_tfaces */ #include "BDR_vpaint.h" @@ -48,7 +49,7 @@ #include "BIF_editdeform.h" #include "BIF_editkey.h" /* insert_meshkey */ #include "BIF_editview.h" -#include "BIF_space.h" /* allqueue */ +#include "BIF_editmesh.h" #include "BKE_deform.h" #include "BKE_mesh.h" @@ -63,12 +64,14 @@ #include "BKE_utildefines.h" #include "BKE_depsgraph.h" #include "BSE_edit.h" /* for void countall(); */ +#include "BKE_curve.h" /* copy_curve(); */ #include "BLI_arithb.h" #include "BLI_blenlib.h" #include "blendef.h" #include "mydevice.h" +#include "butspace.h" /* for mesh tools */ #include "Object.h" #include "Key.h" #include "Image.h" @@ -77,6 +80,8 @@ #include "constant.h" #include "gen_utils.h" +#define MESH_TOOLS /* add access to mesh tools */ + /* EXPP Mesh defines */ #define MESH_SMOOTHRESH 30 @@ -90,6 +95,14 @@ #define MESH_HASMCOL 1 #define MESH_HASVERTUV 2 +#define MESH_TOOL_TOSPHERE 0 +#define MESH_TOOL_VERTEXSMOOTH 1 +#define MESH_TOOL_FLIPNORM 2 +#define MESH_TOOL_SUBDIV 3 +#define MESH_TOOL_REMDOUB 4 +#define MESH_TOOL_FILL 5 +#define MESH_TOOL_RECALCNORM 6 + /************************************************************************ * * internal utilities @@ -103,10 +116,8 @@ typedef struct SrchEdges { unsigned int v[2]; /* indices for verts */ unsigned char swap; /* non-zero if verts swapped */ -#if 0 unsigned int index; /* index in original param list of this edge */ /* (will be used by findEdges) */ -#endif } SrchEdges; typedef struct SrchFaces { @@ -114,6 +125,12 @@ typedef struct SrchFaces { unsigned char order; /* order of original verts, bitpacked */ } SrchFaces; +typedef struct FaceEdges { + unsigned int v[2]; /* search key (vert indices) */ + unsigned int index; /* location in edge list */ + unsigned char sel; /* selection state */ +} FaceEdges; + /* * compare edges by vertex indices */ @@ -170,40 +187,32 @@ int mface_comp( const void *va, const void *vb ) } /* - * update the DAG for all objects linked to this mesh + * compare edges by vertex indices */ -static void mesh_update( Mesh * mesh ) +int faceedge_comp( const void *va, const void *vb ) { - allqueue( REDRAWVIEW3D, 1); - Object_updateDag( (void *) mesh ); + const unsigned int *a = ((FaceEdges *)va)->v; + const unsigned int *b = ((FaceEdges *)vb)->v; + + /* compare first index for differences */ + + if (a[0] < b[0]) return -1; + else if (a[0] > b[0]) return 1; + + /* if first indices equal, compare second index for differences */ + + else if (a[1] < b[1]) return -1; + else return (a[1] > b[1]); } /* - * Since all faces must have 3 or 4 verts, we can't have v3 or v4 be zero. - * If that happens during the deletion, we have to shuffle the vertices - * around; otherwise it can cause an Eeekadoodle or worse. - */ - -static void eeek_fix( MFace *tmpface , int len4 ) -{ - if( len4 && !tmpface->v4 ) { - unsigned int tmp = tmpface->v1; - tmpface->v1 = tmpface->v4; - tmpface->v4 = tmpface->v3; - tmpface->v3 = tmpface->v2; - tmpface->v2 = tmp; - } else if( !tmpface->v3 ) { - unsigned int tmp = tmpface->v1; - tmpface->v1 = tmpface->v2; - tmpface->v2 = tmpface->v3; - if( !len4 ) { - tmpface->v3 = tmp; - } else { - tmpface->v3 = tmpface->v4; - tmpface->v4 = tmp; - } - } + * update the DAG for all objects linked to this mesh + */ + +static void mesh_update( Mesh * mesh ) +{ + Object_updateDag( (void *) mesh ); } #ifdef CHECK_DVERTS /* not clear if this code is needed */ @@ -239,6 +248,347 @@ static void check_dverts(Mesh *me, int old_totvert) } #endif +/* + * delete vertices from mesh, then delete edges/keys/faces which used those + * vertices + * + * Deletion is done by "smart compaction"; groups of verts/edges/faces which + * remain in the list are copied to new list instead of one at a time. Since + * Blender has no realloc we would have to copy things anyway, so there's no + * point trying to fill empty entries with data from the end of the lists. + * + * vert_table is a lookup table for mapping old verts to new verts (after the + * vextex list has deleted vertices removed). Each entry contains the + * vertex's new index. + */ + +static void delete_verts( Mesh *mesh, unsigned int *vert_table, int to_delete ) +{ + /* + * (1) allocate vertex table (initialize contents to 0) + * (2) mark each vertex being deleted in vertex table (= UINT_MAX) + * (3) update remaining table entries with "new" vertex index (after + * compaction) + * (4) allocate new vertex list + * (5) do "smart copy" of vertices from old to new + * * each moved vertex is entered into vertex table: if vertex i is + * moving to index j in new list + * vert_table[i] = j; + * (6) if keys, do "smart copy" of keys + * (7) process edge list + * update vert index + * delete edges which delete verts + * (7) allocate new edge list + * (8) do "smart copy" of edges + * (9) allocate new face list + * (10) do "smart copy" of face + */ + unsigned int *tmpvert; + int i; + char state; + MVert *newvert, *srcvert, *dstvert; + int count; + + newvert = (MVert *)MEM_mallocN( + sizeof( MVert )*( mesh->totvert-to_delete ), "MVerts" ); + + /* + * do "smart compaction" of the table; find and copy groups of vertices + * which are not being deleted + */ + + dstvert = newvert; + srcvert = mesh->mvert; + tmpvert = vert_table; + count = 0; + state = 1; + for( i = 0; i < mesh->totvert; ++i, ++tmpvert ) { + switch( state ) { + case 0: /* skipping verts */ + if( *tmpvert == UINT_MAX ) { + ++count; + } else { + srcvert = mesh->mvert + i; + count = 1; + state = 1; + } + break; + case 1: /* gathering verts */ + if( *tmpvert != UINT_MAX ) { + ++count; + } else { + if( count ) { + memcpy( dstvert, srcvert, sizeof( MVert ) * count ); + dstvert += count; + } + count = 1; + state = 0; + } + } + } + + /* if we were gathering verts at the end of the loop, copy those */ + if( state && count ) + memcpy( dstvert, srcvert, sizeof( MVert ) * count ); + + /* delete old vertex list, install the new one, update vertex count */ + + MEM_freeN( mesh->mvert ); + mesh->mvert = newvert; + mesh->totvert -= to_delete; +} + +static void delete_edges( Mesh *mesh, unsigned int *vert_table, int to_delete ) +{ + int i; + MEdge *tmpedge; + + /* if not given, then mark and count edges to be deleted */ + if( !to_delete ) { + tmpedge = mesh->medge; + for( i = mesh->totedge; i-- ; ++tmpedge ) + if( vert_table[tmpedge->v1] == UINT_MAX || + vert_table[tmpedge->v2] == UINT_MAX ) { + tmpedge->v1 = UINT_MAX; + ++to_delete; + } + } + + /* if there are edges to delete, handle it */ + if( to_delete ) { + MEdge *newedge, *srcedge, *dstedge; + int count, state; + + /* allocate new edge list and populate */ + newedge = (MEdge *)MEM_mallocN( + sizeof( MEdge )*( mesh->totedge-to_delete ), "MEdges" ); + + /* + * do "smart compaction" of the edges; find and copy groups of edges + * which are not being deleted + */ + + dstedge = newedge; + srcedge = mesh->medge; + tmpedge = srcedge; + count = 0; + state = 1; + for( i = 0; i < mesh->totedge; ++i, ++tmpedge ) { + switch( state ) { + case 0: /* skipping edges */ + if( tmpedge->v1 == UINT_MAX ) { + ++count; + } else { + srcedge = tmpedge; + count = 1; + state = 1; + } + break; + case 1: /* gathering edges */ + if( tmpedge->v1 != UINT_MAX ) { + ++count; + } else { + if( count ) { + memcpy( dstedge, srcedge, sizeof( MEdge ) * count ); + dstedge += count; + } + count = 1; + state = 0; + } + } + /* if edge is good, update vertex indices */ + } + + /* copy any pending good edges */ + if( state && count ) + memcpy( dstedge, srcedge, sizeof( MEdge ) * count ); + + /* delete old vertex list, install the new one, update vertex count */ + MEM_freeN( mesh->medge ); + mesh->medge = newedge; + mesh->totedge -= to_delete; + } + + /* if vertices were deleted, update edge's vertices */ + if( vert_table ) { + tmpedge = mesh->medge; + for( i = mesh->totedge; i--; ++tmpedge ) { + tmpedge->v1 = vert_table[tmpedge->v1]; + tmpedge->v2 = vert_table[tmpedge->v2]; + } + } +} + +/* + * Since all faces must have 3 or 4 verts, we can't have v3 or v4 be zero. + * If that happens during the deletion, we have to shuffle the vertices + * around; otherwise it can cause an Eeekadoodle or worse. If there are + * texture faces as well, they have to be shuffled as well. + * + * (code borrowed from test_index_face() in mesh.c, but since we know the + * faces already have correct number of vertices, this is a little faster) + */ + +static void eeek_fix( MFace *mface, TFace *tface, int len4 ) +{ + /* if 4 verts, then neither v3 nor v4 can be zero */ + if( len4 ) { + if( !mface->v3 || !mface->v4 ) { + SWAP( int, mface->v1, mface->v3 ); + SWAP( int, mface->v2, mface->v4 ); + if( tface ) { + SWAP( float, tface->uv[0][0], tface->uv[2][0] ); + SWAP( float, tface->uv[0][1], tface->uv[2][1] ); + SWAP( float, tface->uv[1][0], tface->uv[3][0] ); + SWAP( float, tface->uv[1][1], tface->uv[3][1] ); + SWAP( unsigned int, tface->col[0], tface->col[2] ); + SWAP( unsigned int, tface->col[1], tface->col[3] ); + } + } + } else if( !mface->v3 ) { + /* if 2 verts, then just v3 cannot be zero (v4 MUST be zero) */ + SWAP( int, mface->v1, mface->v2 ); + SWAP( int, mface->v2, mface->v3 ); + if( tface ) { + SWAP( float, tface->uv[0][0], tface->uv[1][0] ); + SWAP( float, tface->uv[0][1], tface->uv[1][1] ); + SWAP( float, tface->uv[2][0], tface->uv[1][0] ); + SWAP( float, tface->uv[2][1], tface->uv[1][1] ); + SWAP( unsigned int, tface->col[0], tface->col[1] ); + SWAP( unsigned int, tface->col[1], tface->col[2] ); + } + } +} + +static void delete_faces( Mesh *mesh, unsigned int *vert_table, int to_delete ) +{ + int i; + MFace *tmpface; + TFace *tmptface; + + /* if there are faces to delete, handle it */ + if( to_delete ) { + MFace *newface, *srcface, *dstface; + TFace *newtface = NULL, *srctface, *dsttface; + char state; + int count; + + newface = (MFace *)MEM_mallocN( ( mesh->totface-to_delete ) + * sizeof( MFace ), "MFace" ); + if( mesh->tface ) + newtface = (TFace *)MEM_mallocN( ( mesh->totface-to_delete ) + * sizeof( TFace ), "TFace" ); + + /* + * do "smart compaction" of the faces; find and copy groups of faces + * which are not being deleted + */ + + dstface = newface; + srcface = mesh->mface; + tmpface = srcface; + dsttface = newtface; + srctface = mesh->tface; + tmptface = srctface; + + count = 0; + state = 1; + for( i = 0; i < mesh->totface; ++i ) { + switch( state ) { + case 0: /* skipping faces */ + if( tmpface->v1 == UINT_MAX ) { + ++count; + } else { + srcface = tmpface; + srctface = tmptface; + count = 1; + state = 1; + } + break; + case 1: /* gathering faces */ + if( tmpface->v1 != UINT_MAX ) { + ++count; + } else { + if( count ) { + memcpy( dstface, srcface, sizeof( MFace ) * count ); + dstface += count; + if( newtface ) { + memcpy( dsttface, srctface, sizeof( TFace ) + * count ); + dsttface += count; + } + } + count = 1; + state = 0; + } + } + ++tmpface; + ++tmptface; + } + + /* if we were gathering faces at the end of the loop, copy those */ + if ( state && count ) { + memcpy( dstface, srcface, sizeof( MFace ) * count ); + if( newtface ) + memcpy( dsttface, srctface, sizeof( TFace ) * count ); + } + + /* delete old face list, install the new one, update face count */ + + MEM_freeN( mesh->mface ); + mesh->mface = newface; + mesh->totface -= to_delete; + if( newtface ) { + MEM_freeN( mesh->tface ); + mesh->tface = newtface; + } + } + + /* if vertices were deleted, update face's vertices */ + if( vert_table ) { + tmpface = mesh->mface; + tmptface = mesh->tface; + for( i = mesh->totface; i--; ) { + int len4 = tmpface->v4; + tmpface->v1 = vert_table[tmpface->v1]; + tmpface->v2 = vert_table[tmpface->v2]; + tmpface->v3 = vert_table[tmpface->v3]; + tmpface->v4 = vert_table[tmpface->v4]; + + eeek_fix( tmpface, tmptface, len4 ); + + ++tmpface; + if( mesh->tface ) + ++tmptface; + } + } +} + +/* + * fill up vertex lookup table with old-to-new mappings + * + * returns the number of vertices marked for deletion + */ + +static unsigned int make_vertex_table( unsigned int *vert_table, int count ) +{ + int i; + unsigned int *tmpvert = vert_table; + unsigned int to_delete = 0; + unsigned int new_index = 0; + + /* fill the lookup table with old->new index mappings */ + for( i = count; i; --i, ++tmpvert ) { + if( *tmpvert == UINT_MAX ) { + ++to_delete; + } else { + *tmpvert = new_index; + ++new_index; + } + } + return to_delete; +} + /************************************************************************ * * Color attributes @@ -936,8 +1286,8 @@ static PyObject *MVert_CreatePyObject( Mesh *mesh, int i ) static PyObject *PVert_CreatePyObject( MVert *vert ) { - BPy_MVert *obj = PyObject_NEW( BPy_MVert, &PVert_Type ); MVert *newvert; + BPy_MVert *obj = (BPy_MVert *)PyObject_NEW( BPy_MVert, &PVert_Type ); if( !obj ) return EXPP_ReturnPyObjError( PyExc_RuntimeError, @@ -1198,10 +1548,11 @@ static PyObject *MVertSeq_extend( BPy_MVertSeq * self, PyObject *args ) tmpvert = &newvert[mesh->totvert]; for( i = 0; i < len; ++i ) { float co[3]; - tmp = PySequence_Fast_GET_ITEM( args, i ); + tmp = PySequence_GetItem( args, i ); if( VectorObject_Check( tmp ) ) { if( ((VectorObject *)tmp)->size != 3 ) { MEM_freeN( newvert ); + Py_DECREF ( tmp ); Py_DECREF ( args ); return EXPP_ReturnPyObjError( PyExc_ValueError, "expected vector of size 3" ); @@ -1225,10 +1576,12 @@ static PyObject *MVertSeq_extend( BPy_MVertSeq * self, PyObject *args ) if( !ok ) { MEM_freeN( newvert ); Py_DECREF ( args ); + Py_DECREF ( tmp ); return EXPP_ReturnPyObjError( PyExc_ValueError, "expected tuple triplet of floats" ); } } + Py_DECREF ( tmp ); /* add the coordinate to the new list */ memcpy( tmpvert->co, co, sizeof(co) ); @@ -1270,7 +1623,7 @@ static PyObject *MVertSeq_extend( BPy_MVertSeq * self, PyObject *args ) /* add data for new vertices */ fp = (float *)((char *)currkey->data + - mesh->key->elemsize*mesh->totvert ); + (mesh->key->elemsize*mesh->totvert)); tmpvert = mesh->mvert + mesh->totvert; for( i = newlen - mesh->totvert; i > 0; --i ) { VECCOPY(fp, tmpvert->co); @@ -1294,9 +1647,100 @@ static PyObject *MVertSeq_extend( BPy_MVertSeq * self, PyObject *args ) return EXPP_incr_ret( Py_None ); } +static PyObject *MVertSeq_delete( BPy_MVertSeq * self, PyObject *args ) +{ + unsigned int *vert_table; + int vert_delete, face_count; + int i; + Mesh *mesh = self->mesh; + MFace *tmpface; + + Py_INCREF( args ); /* so we can safely DECREF later */ + + /* accept a sequence (lists or tuples) also */ + if( PySequence_Size( args ) == 1 ) { + PyObject *tmp = PyTuple_GET_ITEM( args, 0 ); + if( PySequence_Check ( tmp ) ) { + Py_DECREF( args ); /* release previous reference */ + args = tmp; /* PyTuple_GET_ITEM returns new ref */ + } + } + + /* allocate vertex lookup table */ + vert_table = (unsigned int *)MEM_callocN( + mesh->totvert*sizeof( unsigned int ), "vert_table" ); + + /* get the indices of vertices to be removed */ + for( i = PySequence_Size( args ); i--; ) { + PyObject *tmp = PySequence_GetItem( args, i ); + int index; + if( BPy_MVert_Check( tmp ) ) { + if( (void *)self->mesh != ((BPy_MVert*)tmp)->data ) { + MEM_freeN( vert_table ); + Py_DECREF( args ); + Py_DECREF( tmp ); + return EXPP_ReturnPyObjError( PyExc_ValueError, + "MVert belongs to a different mesh" ); + } + index = ((BPy_MVert*)tmp)->index; + } + else if( PyInt_CheckExact( tmp ) ) + index = PyInt_AsLong ( tmp ); + else { + MEM_freeN( vert_table ); + Py_DECREF( args ); + Py_DECREF( tmp ); + return EXPP_ReturnPyObjError( PyExc_TypeError, + "expected a sequence of ints or MVerts" ); + } + Py_DECREF( tmp ); + if( index < 0 || index >= mesh->totvert ) { + MEM_freeN( vert_table ); + Py_DECREF( args ); + return EXPP_ReturnPyObjError( PyExc_ValueError, + "array index out of range" ); + } + vert_table[index] = UINT_MAX; + } + + /* delete things, then clean up and return */ + + vert_delete = make_vertex_table( vert_table, mesh->totvert ); + if( vert_delete ) + delete_verts( mesh, vert_table, vert_delete ); + + /* calculate edges to delete, fix vertex indices */ + delete_edges( mesh, vert_table, 0 ); + + /* + * find number of faces which contain any of the deleted vertices, + * and mark them, then delete them + */ + tmpface = mesh->mface; + face_count=0; + for( i = mesh->totface; i--; ++tmpface ) { + if( vert_table[tmpface->v1] == UINT_MAX || + vert_table[tmpface->v2] == UINT_MAX || + vert_table[tmpface->v3] == UINT_MAX || + vert_table[tmpface->v4] == UINT_MAX ) { + tmpface->v1 = UINT_MAX; + ++face_count; + } + } + delete_faces( mesh, vert_table, face_count ); + + /* clean up and exit */ + MEM_freeN( vert_table ); + mesh_update ( mesh ); + Py_DECREF( args ); + return EXPP_incr_ret( Py_None ); +} + static struct PyMethodDef BPy_MVertSeq_methods[] = { {"extend", (PyCFunction)MVertSeq_extend, METH_VARARGS, "add vertices to mesh"}, + {"delete", (PyCFunction)MVertSeq_delete, METH_VARARGS, + "delete vertices to mesh"}, {NULL, NULL, 0, NULL} }; @@ -1740,6 +2184,7 @@ static PyObject *MEdgeSeq_item( BPy_MEdgeSeq * self, int i ) return MEdge_CreatePyObject( self->mesh, i ); } + static PySequenceMethods MEdgeSeq_as_sequence = { ( inquiry ) MEdgeSeq_len, /* sq_length */ ( binaryfunc ) 0, /* sq_concat */ @@ -1841,11 +2286,12 @@ static PyObject *MEdgeSeq_extend( BPy_MEdgeSeq * self, PyObject *args ) /* verify the param list and get a total count of number of edges */ new_edge_count = 0; for( i = 0; i < len; ++i ) { - tmp = PySequence_Fast_GET_ITEM( args, i ); + tmp = PySequence_GetItem( args, i ); /* not a tuple of MVerts... error */ if( !PyTuple_Check( tmp ) || EXPP_check_sequence_consistency( tmp, &MVert_Type ) != 1 ) { + Py_DECREF( tmp ); Py_DECREF( args ); return EXPP_ReturnPyObjError( PyExc_ValueError, "expected sequence of MVert tuples" ); @@ -1854,10 +2300,13 @@ static PyObject *MEdgeSeq_extend( BPy_MEdgeSeq * self, PyObject *args ) /* not the right number of MVerts... error */ nverts = PyTuple_Size( tmp ); if( nverts < 2 || nverts > 4 ) { + Py_DECREF( tmp ); Py_DECREF( args ); return EXPP_ReturnPyObjError( PyExc_ValueError, "expected 2 to 4 MVerts per tuple" ); } + Py_DECREF( tmp ); + if( nverts == 2 ) ++new_edge_count; /* if only two vert, then add only edge */ else @@ -1872,12 +2321,13 @@ static PyObject *MEdgeSeq_extend( BPy_MEdgeSeq * self, PyObject *args ) len = PySequence_Size( args ); tmppair = newpair; for( i = 0; i < len; ++i ) { - tmp = PySequence_Fast_GET_ITEM( args, i ); + tmp = PySequence_GetItem( args, i ); nverts = PyTuple_Size( tmp ); /* get copies of vertices */ for(j = 0; j < nverts; ++j ) e[j] = (BPy_MVert *)PyTuple_GET_ITEM( tmp, j ); + Py_DECREF( tmp ); if( nverts == 2 ) nverts = 1; /* again, two verts give just one edge */ @@ -2016,9 +2466,154 @@ static PyObject *MEdgeSeq_extend( BPy_MEdgeSeq * self, PyObject *args ) return EXPP_incr_ret( Py_None ); } +static PyObject *MEdgeSeq_delete( BPy_MEdgeSeq * self, PyObject *args ) +{ + Mesh *mesh = self->mesh; + MEdge *srcedge; + MFace *srcface; + unsigned int *vert_table, *del_table, *edge_table; + int i, len; + int face_count, edge_count, vert_count; + + Py_INCREF( args ); /* so we can safely DECREF later */ + + /* accept a sequence (lists or tuples) also */ + if( PySequence_Size( args ) == 1 ) { + PyObject *tmp = PyTuple_GET_ITEM( args, 0 ); + if( PySequence_Check ( tmp ) ) { + Py_DECREF( args ); /* release previous reference */ + args = tmp; /* PyTuple_GET_ITEM returns new ref */ + } + } + + /* see how many args we need to parse */ + len = PySequence_Size( args ); + edge_table = (unsigned int *)MEM_callocN( len*sizeof( unsigned int ), + "edge_table" ); + + /* get the indices of edges to be removed */ + for( i = len; i--; ) { + PyObject *tmp = PySequence_GetItem( args, i ); + if( BPy_MEdge_Check( tmp ) ) + edge_table[i] = ((BPy_MEdge *)tmp)->index; + else if( PyInt_CheckExact( tmp ) ) + edge_table[i] = PyInt_AsLong ( tmp ); + else { + MEM_freeN( edge_table ); + Py_DECREF( tmp ); + Py_DECREF( args ); + return EXPP_ReturnPyObjError( PyExc_TypeError, + "expected a sequence of ints or MEdges" ); + } + Py_DECREF( tmp ); + + /* if index out-of-range, throw exception */ + if( edge_table[i] >= (unsigned int)mesh->totedge ) { + MEM_freeN( edge_table ); + Py_DECREF( args ); + return EXPP_ReturnPyObjError( PyExc_ValueError, + "array index out of range" ); + } + } + + /* + * build two tables: first table marks vertices which belong to an edge + * which is being deleted + */ + del_table = (unsigned int *)MEM_callocN( + mesh->totvert*sizeof( unsigned int ), "vert_table" ); + + /* + * Borrow a trick from editmesh code: for each edge to be deleted, mark + * its vertices as well. Then go through face list and look for two + * consecutive marked vertices. + */ + + /* mark each edge that's to be deleted */ + srcedge = mesh->medge; + for( i = len; i--; ) { + unsigned int idx = edge_table[i]; + del_table[srcedge[idx].v1] = UINT_MAX; + del_table[srcedge[idx].v2] = UINT_MAX; + srcedge[idx].v1 = UINT_MAX; + } + + /* + * second table is used for vertices which become orphaned (belong to no + * edges) and need to be deleted; it's also the normal lookup table for + * old->new vertex indices + */ + + vert_table = (unsigned int *)MEM_mallocN( + mesh->totvert*sizeof( unsigned int ), "vert_table" ); + + /* assume all edges will be deleted (fills with UINT_MAX) */ + memset( vert_table, UCHAR_MAX, mesh->totvert*sizeof( unsigned int ) ); + + /* unmark vertices of each "good" edge; count each "bad" edge */ + edge_count = 0; + for( i = mesh->totedge; i--; ++srcedge ) + if( srcedge->v1 != UINT_MAX ) + vert_table[srcedge->v1] = vert_table[srcedge->v2] = 0; + else + ++edge_count; + + /* + * find faces which no longer have all edges + */ + + face_count = 0; + srcface = mesh->mface; + for( i = 0; i < mesh->totface; ++i, ++srcface ) { + int len = srcface->v4 ? 4 : 3; + unsigned int id[4]; + int del; + + id[0] = del_table[srcface->v1]; + id[1] = del_table[srcface->v2]; + id[2] = del_table[srcface->v3]; + id[3] = del_table[srcface->v4]; + + del = ( id[0] == UINT_MAX && id[1] == UINT_MAX ) || + ( id[1] == UINT_MAX && id[2] == UINT_MAX ); + if( !del ) { + if( len == 3 ) + del = ( id[2] == UINT_MAX && id[0] == UINT_MAX ); + else + del = ( id[2] == UINT_MAX && id[3] == UINT_MAX ) || + ( id[3] == UINT_MAX && id[0] == UINT_MAX ); + } + if( del ) { + srcface->v1 = UINT_MAX; + ++face_count; + } + } + + /* fix the vertex lookup table, if any verts to delete, do so now */ + vert_count = make_vertex_table( vert_table, mesh->totvert ); + if( vert_count ) + delete_verts( mesh, vert_table, vert_count ); + + /* delete faces which have a deleted edge */ + delete_faces( mesh, vert_table, face_count ); + + /* now delete the edges themselves */ + delete_edges( mesh, vert_table, edge_count ); + + /* clean up and return */ + MEM_freeN( del_table ); + MEM_freeN( vert_table ); + MEM_freeN( edge_table ); + Py_DECREF( args ); + mesh_update ( mesh ); + return EXPP_incr_ret( Py_None ); +} + static struct PyMethodDef BPy_MEdgeSeq_methods[] = { {"extend", (PyCFunction)MEdgeSeq_extend, METH_VARARGS, "add edges to mesh"}, + {"delete", (PyCFunction)MEdgeSeq_delete, METH_VARARGS, + "delete edges from mesh"}, {NULL, NULL, 0, NULL} }; @@ -2297,7 +2892,7 @@ static int MFace_setImage( BPy_MFace *self, PyObject *value ) } /* - * get face's texture flags + * get face's texture flag */ static PyObject *MFace_getFlag( BPy_MFace *self ) @@ -2318,7 +2913,7 @@ static PyObject *MFace_getFlag( BPy_MFace *self ) } /* - * set face's texture flags + * set face's texture flag */ static int MFace_setFlag( BPy_MFace *self, PyObject *value ) @@ -2949,7 +3544,7 @@ static PyObject *MFaceSeq_extend( BPy_MEdgeSeq * self, PyObject *args ) /* verify the param list and get a total count of number of edges */ new_face_count = 0; for( i = 0; i < len; ++i ) { - tmp = PySequence_Fast_GET_ITEM( args, i ); + tmp = PySequence_GetItem( args, i ); /* not a tuple of MVerts... error */ if( !PyTuple_Check( tmp ) || @@ -2983,12 +3578,20 @@ static PyObject *MFaceSeq_extend( BPy_MEdgeSeq * self, PyObject *args ) MFace tmpface; unsigned int vert[4]={0,0,0,0}; unsigned char order[4]={0,1,2,3}; - tmp = PySequence_Fast_GET_ITEM( args, i ); + tmp = PySequence_GetItem( args, i ); nverts = PyTuple_Size( tmp ); if( nverts == 2 ) /* again, ignore 2-vert tuples */ break; + /* get copies of vertices */ +#if 0 + for( j = 0; j < nverts; ++j ) { + BPy_MVert *e = (BPy_MVert *)PyTuple_GET_ITEM( tmp, j ); + vert[j] = e->index; + } +#endif + /* * go through some contortions to guarantee the third and fourth * vertices are not index 0 @@ -3004,7 +3607,9 @@ static PyObject *MFaceSeq_extend( BPy_MEdgeSeq * self, PyObject *args ) e = (BPy_MVert *)PyTuple_GET_ITEM( tmp, 3 ); tmpface.v4 = e->index; } - eeek_fix( &tmpface, nverts==4 ); + Py_DECREF( tmp ); + + eeek_fix( &tmpface, NULL, nverts==4 ); vert[0] = tmpface.v1; vert[1] = tmpface.v2; vert[2] = tmpface.v3; @@ -3150,6 +3755,7 @@ static PyObject *MFaceSeq_extend( BPy_MEdgeSeq * self, PyObject *args ) tmpface->v2 = tmppair->v[index[1]]; tmpface->v3 = tmppair->v[index[2]]; tmpface->v4 = tmppair->v[index[3]]; + tmpface->flag = 0; mesh->totface++; ++tmpface; @@ -3166,9 +3772,218 @@ static PyObject *MFaceSeq_extend( BPy_MEdgeSeq * self, PyObject *args ) return EXPP_incr_ret( Py_None ); } +struct fourEdges +{ + FaceEdges *v[4]; +}; + +static PyObject *MFaceSeq_delete( BPy_MFaceSeq * self, PyObject *args ) +{ + unsigned int *face_table; + int i, len; + Mesh *mesh = self->mesh; + MFace *tmpface; + int face_count; + int edge_also = 0; + + /* check for valid inputs */ + + if( PySequence_Size( args ) != 2 || + !PyArg_ParseTuple( args, "iO", &edge_also, &args ) ) + return EXPP_ReturnPyObjError( PyExc_TypeError, + "expected and int and a sequence of ints or MFaces" ); + + /* see how many args we need to parse */ + len = PySequence_Size( args ); + if( len < 1 ) { + Py_DECREF( args ); + return EXPP_ReturnPyObjError( PyExc_TypeError, + "sequence must contain at least one int or MFace" ); + } + + face_table = (unsigned int *)MEM_callocN( len*sizeof( unsigned int ), + "face_table" ); + + /* get the indices of faces to be removed */ + for( i = len; i--; ) { + PyObject *tmp = PySequence_GetItem( args, i ); + if( BPy_MEdge_Check( tmp ) ) + face_table[i] = ((BPy_MEdge *)tmp)->index; + else if( PyInt_CheckExact( tmp ) ) + face_table[i] = PyInt_AsLong ( tmp ); + else { + MEM_freeN( face_table ); + Py_DECREF( tmp ); + Py_DECREF( args ); + return EXPP_ReturnPyObjError( PyExc_TypeError, + "expected a sequence of ints or MFaces" ); + } + Py_DECREF( tmp ); + + /* if index out-of-range, throw exception */ + if( face_table[i] >= (unsigned int)mesh->totface ) { + MEM_freeN( face_table ); + Py_DECREF( args ); + return EXPP_ReturnPyObjError( PyExc_ValueError, + "array index out of range" ); + } + } + + if( edge_also ) { + /* + * long version + * + * (1) build sorted table of all edges + * (2) construct face->edge lookup table for all faces + * face->e1 = mesh->medge[i] + * (3) (delete sorted table) + * (4) mark all edges as live + * (5) mark all edges for deleted faces as dead + * (6) mark all edges for remaining faces as live + * (7) delete all dead edges + * (8) (delete face lookup table) + * + */ + + FaceEdges *edge_table, *tmp_et; + MEdge *tmpedge; + FaceEdges **face_edges; + FaceEdges **tmp_fe; + struct fourEdges *fface; + int edge_count; + + edge_table = MEM_mallocN( mesh->totedge*sizeof( FaceEdges ), + "edge_table" ); + + tmpedge = mesh->medge; + tmp_et = edge_table; + + for( i = 0; i < mesh->totedge; ++i ) { + if( tmpedge->v1 < tmpedge->v2 ) { + tmp_et->v[0] = tmpedge->v1; + tmp_et->v[1] = tmpedge->v2; + } else { + tmp_et->v[0] = tmpedge->v2; + tmp_et->v[1] = tmpedge->v1; + } + tmp_et->index = i; + tmp_et->sel = 1; /* select each edge */ + ++tmpedge; + ++tmp_et; + } + + /* sort the edge pairs */ + qsort( edge_table, mesh->totedge, sizeof(FaceEdges), faceedge_comp ); + + /* build face translation table, lookup edges */ + face_edges = MEM_callocN( 4*sizeof(FaceEdges*)*mesh->totface, + "face_edges" ); + + tmp_fe = face_edges; + tmpface = mesh->mface; + for( i = mesh->totface; i--; ++tmpface ) { + FaceEdges *ptrs[4]; + unsigned int verts[4]; + int j,k; + FaceEdges target; + int len=tmpface->v4 ? 4 : 3; + + ptrs[3] = NULL; + verts[0] = tmpface->v1; + verts[1] = tmpface->v2; + verts[2] = tmpface->v3; + if(len == 4) + verts[3] = tmpface->v4; + for( j = 0; j < len; ++j ) { + k = (j+1) % len; + if( verts[j] < verts[k] ) { + target.v[0] = verts[j]; + target.v[1] = verts[k]; + } else { + target.v[0] = verts[k]; + target.v[1] = verts[j]; + } + ptrs[j] = bsearch( &target, edge_table, mesh->totedge, + sizeof(FaceEdges), faceedge_comp ); + } + for( j = 0; j < 4; ++j, ++tmp_fe ) + *tmp_fe = ptrs[j]; + } + + /* for each face, deselect each edge */ + tmpface = mesh->mface; + face_count = 0; + for( i = len; i--; ) { + if( tmpface[face_table[i]].v1 != UINT_MAX ) { + fface = (void *)face_edges; + fface += face_table[i]; + fface->v[0]->sel = 0; + fface->v[1]->sel = 0; + fface->v[2]->sel = 0; + if( fface->v[3] ) + fface->v[3]->sel = 0; + tmpface[face_table[i]].v1 = UINT_MAX; + ++face_count; + } + } + + /* for each face, deselect each edge */ + tmpface = mesh->mface; + fface = (struct fourEdges *)face_edges; + for( i = mesh->totface; i--; ++tmpface, ++fface ) { + if( tmpface->v1 != UINT_MAX ) { + FaceEdges (*face)[4]; + face = (void *)face_edges; + face += face_table[i]; + fface->v[0]->sel = 1; + fface->v[1]->sel = 1; + fface->v[2]->sel = 1; + if( fface->v[3] ) + fface->v[3]->sel = 1; + } + } + + /* now mark the selected edges for deletion */ + + edge_count = 0; + for( i = 0; i < mesh->totedge; ++i ) { + if( !edge_table[i].sel ) { + mesh->medge[edge_table[i].index].v1 = UINT_MAX; + ++edge_count; + } + } + + if( edge_count ) + delete_edges( mesh, NULL, edge_count ); + + MEM_freeN( face_edges ); + MEM_freeN( edge_table ); + } else { + /* mark faces to delete */ + tmpface = mesh->mface; + face_count = 0; + for( i = len; i--; ) + if( tmpface[face_table[i]].v1 != UINT_MAX ) { + tmpface[face_table[i]].v1 = UINT_MAX; + ++face_count; + } + } + + /* delete faces which have a deleted edge */ + delete_faces( mesh, NULL, face_count ); + + /* clean up and return */ + MEM_freeN( face_table ); + Py_DECREF( args ); + mesh_update ( mesh ); + return EXPP_incr_ret( Py_None ); +} + static struct PyMethodDef BPy_MFaceSeq_methods[] = { {"extend", (PyCFunction)MFaceSeq_extend, METH_VARARGS, - "add faces and edges to mesh"}, + "add faces to mesh"}, + {"delete", (PyCFunction)MFaceSeq_delete, METH_VARARGS, + "delete faces to mesh"}, {NULL, NULL, 0, NULL} }; @@ -3316,15 +4131,350 @@ static PyObject *Mesh_Update( BPy_Mesh * self ) return EXPP_incr_ret( Py_None ); } -// #define MESH_TOOLS +/* + * search for a single edge in mesh's edge list + */ + +static PyObject *Mesh_findEdge( BPy_Mesh * self, PyObject *args ) +{ + int i; + unsigned int v1, v2; + PyObject *tmp; + MEdge *edge = self->mesh->medge; + + if( EXPP_check_sequence_consistency( args, &MVert_Type ) == 1 && + PySequence_Size( args ) == 2 ) { + tmp = PyTuple_GET_ITEM( args, 0 ); + v1 = ((BPy_MVert *)tmp)->index; + tmp = PyTuple_GET_ITEM( args, 1 ); + v2 = ((BPy_MVert *)tmp)->index; + } else if( PyArg_ParseTuple( args, "ii", &v1, &v2 ) ) { + if( (int)v1 >= self->mesh->totvert || (int)v2 >= self->mesh->totvert ) + return EXPP_ReturnPyObjError( PyExc_IndexError, + "index out of range" ); + } else + return EXPP_ReturnPyObjError( PyExc_RuntimeError, + "expected tuple of two ints or MVerts" ); + + for( i = 0; i < self->mesh->totedge; ++i ) { + if( ( edge->v1 == v1 && edge->v2 == v2 ) + || ( edge->v1 == v2 && edge->v2 == v1 ) ) { + tmp = PyInt_FromLong( i ); + if( tmp ) + return tmp; + return EXPP_ReturnPyObjError( PyExc_RuntimeError, + "PyInt_FromLong() failed" ); + } + ++edge; + } + return EXPP_incr_ret( Py_None ); +} + +/* + * search for a group of edges in mesh's edge list + */ + +static PyObject *Mesh_findEdges( PyObject * self, PyObject *args ) +{ + int len; + int i; + SrchEdges *oldpair, *tmppair, target, *result; + PyObject *list, *tmp; + BPy_MVert *v1, *v2; + unsigned int index1, index2; + MEdge *tmpedge; + Mesh *mesh = ((BPy_Mesh *)self)->mesh; + + /* if no edges, nothing to do */ + + if( !mesh->totedge ) + return EXPP_ReturnPyObjError( PyExc_ValueError, + "mesh has no edges" ); + + /* make sure we get a sequence of tuples of something */ + + tmp = PyTuple_GET_ITEM( args, 0 ); + switch( PySequence_Size ( args ) ) { + case 1: /* better be a list or a tuple */ + if( !PySequence_Check ( tmp ) ) + return EXPP_ReturnPyObjError( PyExc_TypeError, + "expected a sequence of tuple int or MVert pairs" ); + args = tmp; + Py_INCREF( args ); /* so we can safely DECREF later */ + break; + case 2: /* take any two args and put into a tuple */ + if( PyTuple_Check( tmp ) ) + Py_INCREF( args ); /* if first arg is a tuple, assume both are */ + else { + args = Py_BuildValue( "((OO))", tmp, PyTuple_GET_ITEM( args, 1 ) ); + if( !args ) + return EXPP_ReturnPyObjError( PyExc_RuntimeError, + "Py_BuildValue() failed" ); + } + break; + default: /* anything else is definitely wrong */ + return EXPP_ReturnPyObjError( PyExc_TypeError, + "expected a sequence of tuple pairs" ); + } + + len = PySequence_Size( args ); + if( len == 0 ) { + Py_DECREF( args ); + return EXPP_ReturnPyObjError( PyExc_ValueError, + "expected at least one tuple" ); + } + + /* if a single edge, handle the simpler way */ + if( len == 1 ) { + PyObject *result; + tmp = PySequence_GetItem( args, 0 ); + result = Mesh_findEdge( (BPy_Mesh *)self, tmp ); + Py_DECREF( tmp ); + Py_DECREF( args ); + return result; + } + + /* build a list of all edges so we can search */ + oldpair = (SrchEdges *)MEM_callocN( sizeof(SrchEdges)*mesh->totedge, + "MEdgePairs" ); + + tmppair = oldpair; + tmpedge = mesh->medge; + for( i = 0; i < mesh->totedge; ++i ) { + if( tmpedge->v1 < tmpedge->v2 ) { + tmppair->v[0] = tmpedge->v1; + tmppair->v[1] = tmpedge->v2; + } else { + tmppair->v[0] = tmpedge->v2; + tmppair->v[1] = tmpedge->v1; + } + tmppair->index = i; + ++tmpedge; + ++tmppair; + } + + /* sort the old edge pairs */ + qsort( oldpair, mesh->totedge, sizeof(SrchEdges), medge_comp ); + + list = PyList_New( len ); + if( !len ) + return EXPP_ReturnPyObjError( PyExc_RuntimeError, + "PyList_New() failed" ); + + /* scan the input list, find vert pairs, then search the edge list */ + + for( i = 0; i < len; ++i ) { + tmp = PySequence_GetItem( args, i ); + if( !PyTuple_Check( tmp ) || PyTuple_Size( tmp ) != 2 ) { + MEM_freeN( oldpair ); + Py_DECREF( tmp ); + Py_DECREF( args ); + Py_DECREF( list ); + return EXPP_ReturnPyObjError( PyExc_ValueError, + "expected tuple pair" ); + } + + /* get objects, check that they are both MVerts of this mesh */ + v1 = (BPy_MVert *)PyTuple_GET_ITEM( tmp, 0 ); + v2 = (BPy_MVert *)PyTuple_GET_ITEM( tmp, 1 ); + Py_DECREF ( tmp ); + if( BPy_MVert_Check( v1 ) && BPy_MVert_Check( v2 ) ) { + if( v1->data != (void *)mesh || v2->data != (void *)mesh ) { + MEM_freeN( oldpair ); + Py_DECREF( args ); + Py_DECREF( list ); + return EXPP_ReturnPyObjError( PyExc_ValueError, + "one or both MVerts do not belong to this mesh" ); + } + index1 = v1->index; + index2 = v2->index; + } else if( PyInt_CheckExact( v1 ) && PyInt_CheckExact( v2 ) ) { + index1 = PyInt_AsLong( (PyObject *)v1 ); + index2 = PyInt_AsLong( (PyObject *)v2 ); + if( (int)index1 >= mesh->totvert + || (int)index2 >= mesh->totvert ) { + MEM_freeN( oldpair ); + Py_DECREF( args ); + Py_DECREF( list ); + return EXPP_ReturnPyObjError( PyExc_IndexError, + "index out of range" ); + } + } else { + MEM_freeN( oldpair ); + Py_DECREF( args ); + Py_DECREF( list ); + return EXPP_ReturnPyObjError( PyExc_ValueError, + "expected tuple to contain MVerts" ); + } + + /* sort verts into order */ + if( index1 < index2 ) { + target.v[0] = index1; + target.v[1] = index2; + } else { + target.v[0] = index2; + target.v[1] = index1; + } + + /* search edge list for a match; result is index or None */ + result = bsearch( &target, oldpair, mesh->totedge, + sizeof(SrchEdges), medge_comp ); + if( result ) + tmp = PyInt_FromLong( result->index ); + else + tmp = EXPP_incr_ret( Py_None ); + if( !tmp ) { + MEM_freeN( oldpair ); + Py_DECREF( args ); + Py_DECREF( list ); + return EXPP_ReturnPyObjError( PyExc_RuntimeError, + "PyInt_FromLong() failed" ); + } + PyList_SET_ITEM( list, i, tmp ); + } + + MEM_freeN( oldpair ); + Py_DECREF ( args ); + return list; +} + +/* + * replace mesh data with mesh data from another object + */ + +static PyObject *Mesh_getFromObject( BPy_Mesh * self, PyObject * args ) +{ + Object *ob; + char *name; + ID tmpid; + Mesh *tmpmesh; + Object *tmpobj = NULL; + + if( !PyArg_ParseTuple( args, "s", &name ) ) + return EXPP_ReturnPyObjError( PyExc_TypeError, + "expected string argument" ); + + /* find the specified object */ + ob = ( Object * ) GetIdFromList( &( G.main->object ), name ); + if( !ob ) + return EXPP_ReturnPyObjError( PyExc_AttributeError, name ); + + /* perform the mesh extraction based on type */ + switch (ob->type) { + case OB_FONT: + case OB_CURVE: + case OB_SURF: + tmpobj = alloc_libblock( &( G.main->object ), ID_OB, "i_tmp" ); + tmpobj->id.us = 1; + tmpobj->flag = 0; + tmpobj->type = ob->type; + tmpobj->data = copy_curve( (Curve *) ob->data ); + makeDispListCurveTypes( tmpobj, 0 ); + nurbs_to_mesh( tmpobj ); + tmpmesh = tmpobj->data; + free_libblock_us( &G.main->object, tmpobj ); + break; + case OB_MBALL: + ob = find_basis_mball( ob ); + tmpmesh = add_mesh(); + mball_to_mesh( &ob->disp, tmpmesh ); + break; + case OB_MESH: + tmpmesh = copy_mesh( (Mesh *) ob->data ); + tmpmesh->id.us = 0; + break; + default: + return EXPP_ReturnPyObjError( PyExc_AttributeError, + "Object does not have geometry data" ); + } + + /* free mesh data in the original */ + free_mesh( self->mesh ); + /* save a copy of our ID, dup the temporary mesh, restore the ID */ + tmpid = self->mesh->id; + memcpy( self->mesh, tmpmesh, sizeof( Mesh ) ); + self->mesh->id= tmpid; + /* remove the temporary mesh */ + BLI_remlink( &G.main->mesh, tmpmesh ); + MEM_freeN( tmpmesh ); + + mesh_update( self->mesh ); + return EXPP_incr_ret( Py_None ); +} + +/* + * apply a transform to the mesh's vertices + * + * WARNING: unlike NMesh, this method ALWAYS changes the original mesh + */ + +static PyObject *Mesh_transform( BPy_Mesh *self, PyObject *args ) +{ + Mesh *mesh = self->mesh; + MVert *mv; + PyObject *ob1 = NULL; + MatrixObject *mat; + int i, recalc_normals = 0; + + if( !PyArg_ParseTuple( args, "O!|i", &matrix_Type, &ob1, &recalc_normals ) ) + return ( EXPP_ReturnPyObjError( PyExc_TypeError, + "expected matrix and optionally an int as arguments" ) ); + + mat = ( MatrixObject * ) ob1; + + if( mat->colSize != 4 || mat->rowSize != 4 ) + return EXPP_ReturnPyObjError( PyExc_AttributeError, + "matrix must be a 4x4 transformation matrix\n" + "for example as returned by object.getMatrix()" ); + + /* loop through all the verts and transform by the supplied matrix */ + mv = mesh->mvert; + for( i = 0; i < mesh->totvert; i++, mv++ ) + Mat4MulVecfl( (float(*)[4])*mat->matrix, mv->co ); + + if( recalc_normals ) { + /* loop through all the verts and transform normals by the inverse + * of the transpose of the supplied matrix */ + float invmat[4][4]; + + /* + * we only need to invert a 3x3 submatrix, because the 4th component of + * affine vectors is 0, but Mat4Invert reports non invertible matrices + */ + + if (!Mat4Invert((float(*)[4])*invmat, (float(*)[4])*mat->matrix)) + return EXPP_ReturnPyObjError (PyExc_AttributeError, + "given matrix is not invertible"); + + /* + * since normal is stored as shorts, convert to float + */ + + mv = mesh->mvert; + for( i = 0; i < mesh->totvert; i++, mv++ ) { + float vec[3]; + vec[0] = (float)mv->no[0] / 32767.0; + vec[1] = (float)mv->no[1] / 32767.0; + vec[2] = (float)mv->no[2] / 32767.0; + Mat4MulVecfl( (float(*)[4])*invmat, vec ); + Normalise( vec ); + mv->no[0] = (short)(vec[0] * 32767.0); + mv->no[1] = (short)(vec[1] * 32767.0); + mv->no[2] = (short)(vec[2] * 32767.0); + } + } + + return EXPP_incr_ret( Py_None ); +} #ifdef MESH_TOOLS -static PyObject *Mesh_Tools( BPy_Mesh * self, int type ) +static PyObject *Mesh_Tools( BPy_Mesh * self, int type, void **args ) { + Base *base; + int result; Object *object = NULL; - Base *basact = BASACT; - Base *base = FIRSTBASE; + PyObject *attr = NULL; /* if already in edit mode, exit */ @@ -3352,15 +4502,38 @@ static PyObject *Mesh_Tools( BPy_Mesh * self, int type ) G.obedit = object; enter_editmode( ); switch( type ) { - case B_SUBDIV: - esubdivideflag(1, 0.0, - G.scene->toolsettings->editbutflag & B_BEAUTY,1,0); + case MESH_TOOL_TOSPHERE: + vertices_to_sphere(); break; - case B_VERTEXSMOOTH: + case MESH_TOOL_VERTEXSMOOTH: vertexsmooth(); break; + case MESH_TOOL_FLIPNORM: + /* would be simple to rewrite this to not use edit mesh */ + /* see flipface() */ + flip_editnormals(); + break; + case MESH_TOOL_SUBDIV: + esubdivideflag( 1, 0.0, *((int *)args[0]), 1, 0 ); + break; + case MESH_TOOL_REMDOUB: + result = removedoublesflag( 1, *((float *)args[0]) ); + + attr = PyInt_FromLong( result ); + if( !attr ) + return EXPP_ReturnPyObjError( PyExc_RuntimeError, + "PyInt_FromLong() failed" ); + case MESH_TOOL_FILL: + fill_mesh(); + break; + case MESH_TOOL_RECALCNORM: + righthandfaces( *((int *)args[0]) ); + break; } exit_editmode( 1 ); + if( attr ) + return attr; + return EXPP_incr_ret( Py_None ); } @@ -3368,18 +4541,93 @@ static PyObject *Mesh_Tools( BPy_Mesh * self, int type ) * "Subdivide" function */ -static PyObject *Mesh_Subdivide( BPy_Mesh * self ) +static PyObject *Mesh_subdivide( BPy_Mesh * self, PyObject * args ) { - return Mesh_Tools( self, B_SUBDIV ); + int beauty = 0; + void *params = &beauty; + + if( !PyArg_ParseTuple( args, "|i", &beauty ) ) + return EXPP_ReturnPyObjError( PyExc_TypeError, + "expected nothing or an int argument" ); + + return Mesh_Tools( self, MESH_TOOL_SUBDIV, ¶ms ); } /* * "Smooth" function */ -static PyObject *Mesh_Smooth( BPy_Mesh * self ) +static PyObject *Mesh_smooth( BPy_Mesh * self ) +{ + return Mesh_Tools( self, MESH_TOOL_VERTEXSMOOTH, NULL ); +} + +/* + * "Remove doubles" function + */ + +static PyObject *Mesh_removeDoubles( BPy_Mesh * self, PyObject *args ) +{ + float limit; + void *params = &limit; + + if( !PyArg_ParseTuple( args, "f", &limit ) ) + return EXPP_ReturnPyObjError( PyExc_TypeError, + "expected float argument" ); + + limit = EXPP_ClampFloat( limit, 0.0f, 1.0f ); + + return Mesh_Tools( self, MESH_TOOL_REMDOUB, ¶ms ); +} + +/* + * "recalc normals" function + */ + +static PyObject *Mesh_recalcNormals( BPy_Mesh * self, PyObject *args ) +{ + int direction = 0; + void *params = &direction; + + if( !PyArg_ParseTuple( args, "|i", &direction ) ) + return EXPP_ReturnPyObjError( PyExc_TypeError, + "expected nothing or an int in range [0,1]" ); + + if( direction < 0 || direction > 1 ) + return EXPP_ReturnPyObjError( PyExc_ValueError, + "expected int in range [0,1]" ); + + /* righthandfaces(1) = outward, righthandfaces(2) = inward */ + ++direction; + + return Mesh_Tools( self, MESH_TOOL_RECALCNORM, ¶ms ); +} + +/* + * "Flip normals" function + */ + +static PyObject *Mesh_flipNormals( BPy_Mesh * self ) { - return Mesh_Tools( self, B_VERTEXSMOOTH ); + return Mesh_Tools( self, MESH_TOOL_FLIPNORM, NULL ); +} + +/* + * "To sphere" function + */ + +static PyObject *Mesh_toSphere( BPy_Mesh * self ) +{ + return Mesh_Tools( self, MESH_TOOL_TOSPHERE, NULL ); +} + +/* + * "Fill" (scan fill) function + */ + +static PyObject *Mesh_fill( BPy_Mesh * self ) +{ + return Mesh_Tools( self, MESH_TOOL_FILL, NULL ); } #endif @@ -3389,13 +4637,29 @@ static struct PyMethodDef BPy_Mesh_methods[] = { "all recalculate vertex normals"}, {"vertexShade", (PyCFunction)Mesh_vertexShade, METH_VARARGS, "color vertices based on the current lighting setup"}, + {"findEdges", (PyCFunction)Mesh_findEdges, METH_VARARGS, + "find indices of an multiple edges in the mesh"}, + {"getFromObject", (PyCFunction)Mesh_getFromObject, METH_VARARGS, + "Get a mesh by name"}, {"update", (PyCFunction)Mesh_Update, METH_NOARGS, "Update display lists after changes to mesh"}, + {"transform", (PyCFunction)Mesh_transform, METH_VARARGS, + "Applies a transformation matrix to mesh's vertices"}, #ifdef MESH_TOOLS - {"subdivide", (PyCFunction)Mesh_Subdivide, METH_NOARGS, - "Subdivide selected edges in a mesh (experimental)"}, - {"smooth", (PyCFunction)Mesh_Smooth, METH_NOARGS, + {"smooth", (PyCFunction)Mesh_smooth, METH_NOARGS, "Flattens angle of selected faces (experimental)"}, + {"flipNormals", (PyCFunction)Mesh_flipNormals, METH_NOARGS, + "Toggles the direction of selected face's normals (experimental)"}, + {"toSphere", (PyCFunction)Mesh_toSphere, METH_NOARGS, + "Moves selected vertices outward in a spherical shape (experimental)"}, + {"fill", (PyCFunction)Mesh_fill, METH_NOARGS, + "Scan fill a closed edge loop (experimental)"}, + {"subdivide", (PyCFunction)Mesh_subdivide, METH_VARARGS, + "Subdivide selected edges in a mesh (experimental)"}, + {"remDoubles", (PyCFunction)Mesh_removeDoubles, METH_VARARGS, + "Removes duplicates from selected vertices (experimental)"}, + {"recalcNormals", (PyCFunction)Mesh_recalcNormals, METH_VARARGS, + "Recalculates inside or outside normals (experimental)"}, #endif {NULL, NULL, 0, NULL} }; @@ -3413,6 +4677,56 @@ static PyObject *Mesh_getVerts( BPy_Mesh * self ) return (PyObject *)seq; } +static int Mesh_setVerts( BPy_Mesh * self, PyObject * args ) +{ + static int disabled = 0; + MVert *dst; + MVert *src; + char err[256]; + int i; + + if( disabled ) { + sprintf( err, "attribute 'verts' of '%s' objects is not writable", + self->ob_type->tp_name ); + return EXPP_ReturnIntError( PyExc_TypeError, err ); + } + + if( PyList_Check( args ) ) { + if( EXPP_check_sequence_consistency( args, &MVert_Type ) != 1 && + EXPP_check_sequence_consistency( args, &PVert_Type ) != 1 ) + return EXPP_ReturnIntError( PyExc_TypeError, + "expected a list of MVerts" ); + + if( PyList_Size( args ) != self->mesh->totvert ) + return EXPP_ReturnIntError( PyExc_TypeError, + "list must have the same number of vertices as the mesh" ); + + dst = self->mesh->mvert; + for( i = 0; i < PyList_Size( args ); ++i ) { + BPy_MVert *v = (BPy_MVert *)PyList_GET_ITEM( args, i ); + + if( BPy_MVert_Check( v ) ) + src = &((Mesh *)v->data)->mvert[v->index]; + else + src = (MVert *)v->data; + + memcpy( dst, src, sizeof(MVert) ); + ++dst; + } + } else if( args->ob_type == &MVertSeq_Type ) { + Mesh *mesh = ( (BPy_MVertSeq *) args)->mesh; + + if( mesh->totvert != self->mesh->totvert ) + return EXPP_ReturnIntError( PyExc_TypeError, + "vertex sequences must have the same number of vertices" ); + + memcpy( self->mesh->mvert, mesh->mvert, mesh->totvert*sizeof(MVert) ); + } else + return EXPP_ReturnIntError( PyExc_TypeError, + "expected a list or sequence of MVerts" ); + return 0; +} + static PyObject *Mesh_getEdges( BPy_Mesh * self ) { BPy_MEdgeSeq *seq = PyObject_NEW( BPy_MEdgeSeq, &MEdgeSeq_Type); @@ -3715,7 +5029,7 @@ static PyObject *Mesh_repr( BPy_Mesh * self ) /*****************************************************************************/ static PyGetSetDef BPy_Mesh_getseters[] = { {"verts", - (getter)Mesh_getVerts, (setter)NULL, + (getter)Mesh_getVerts, (setter)Mesh_setVerts, "The mesh's vertices (MVert)", NULL}, {"edges", @@ -3921,6 +5235,7 @@ static PyObject *M_Mesh_Get( PyObject * self, PyObject * args ) static PyObject *M_Mesh_New( PyObject * self, PyObject * args ) { char *name = "Mesh"; + PyObject *ret = NULL; Mesh *mesh; BPy_Mesh *obj; char buf[21]; @@ -3969,7 +5284,7 @@ static PyObject *M_Mesh_MVert( PyObject * self, PyObject * args ) */ if( PyTuple_Size ( args ) == 1 ) { - PyObject *tmp = PySequence_Fast_GET_ITEM( args, 0 ); + PyObject *tmp = PyTuple_GET_ITEM( args, 0 ); if( !VectorObject_Check( tmp ) || ((VectorObject *)tmp)->size != 3 ) return EXPP_ReturnPyObjError( PyExc_ValueError, "expected three floats or vector of size 3" ); diff --git a/source/blender/python/api2_2x/Mesh.h b/source/blender/python/api2_2x/Mesh.h index 714819036dd..31810a57d44 100644 --- a/source/blender/python/api2_2x/Mesh.h +++ b/source/blender/python/api2_2x/Mesh.h @@ -52,19 +52,19 @@ extern PyTypeObject Mesh_Type; extern PyTypeObject MVert_Type; extern PyTypeObject PVert_Type; extern PyTypeObject MVertSeq_Type; +extern PyTypeObject MEdge_Type; extern PyTypeObject MFace_Type; extern PyTypeObject MCol_Type; -extern PyTypeObject MEdge_Type; struct BPy_Object; /* Type checking for EXPP PyTypes */ #define BPy_Mesh_Check(v) ((v)->ob_type == &Mesh_Type) #define BPy_MFace_Check(v) ((v)->ob_type == &MFace_Type) +#define BPy_MEdge_Check(v) ((v)->ob_type == &MEdge_Type) #define BPy_MVert_Check(v) ((v)->ob_type == &MVert_Type) #define BPy_PVert_Check(v) ((v)->ob_type == &PVert_Type) #define BPy_MCol_Check(v) ((v)->ob_type == &MCol_Type) -#define BPy_MEdge_Check(v) ((v)->ob_type == &MEdge_Type) /* Typedefs for the new types */ @@ -87,7 +87,7 @@ typedef struct { typedef struct { PyObject_VAR_HEAD /* required python macro */ - Mesh * mesh; + Mesh *mesh; /* points to a Mesh */ int index; int iter; } BPy_MEdge; /* a Mesh edge */ diff --git a/source/blender/python/api2_2x/Types.c b/source/blender/python/api2_2x/Types.c index 1a770844e68..d41dd9b2c44 100644 --- a/source/blender/python/api2_2x/Types.c +++ b/source/blender/python/api2_2x/Types.c @@ -71,6 +71,7 @@ void types_InitAll( void ) Mesh_Type.ob_type = &PyType_Type; MFace_Type.ob_type = &PyType_Type; MVert_Type.ob_type = &PyType_Type; + PVert_Type.ob_type = &PyType_Type; MEdge_Type.ob_type = &PyType_Type; MCol_Type.ob_type = &PyType_Type; Mesh_Type.ob_type = &PyType_Type; @@ -130,6 +131,8 @@ PyObject *Types_Init( void ) ( PyObject * ) &MEdge_Type ); PyDict_SetItemString( dict, "MVertType", ( PyObject * ) &MVert_Type ); + PyDict_SetItemString( dict, "PVertType", + ( PyObject * ) &PVert_Type ); PyDict_SetItemString( dict, "MColType", ( PyObject * ) &MCol_Type ); PyDict_SetItemString( dict, "ArmatureType", diff --git a/source/blender/python/api2_2x/Types.h b/source/blender/python/api2_2x/Types.h index d4ac545dbb2..ea8a80a1a98 100644 --- a/source/blender/python/api2_2x/Types.h +++ b/source/blender/python/api2_2x/Types.h @@ -45,7 +45,8 @@ extern PyTypeObject Image_Type, Ipo_Type, IpoCurve_Type; extern PyTypeObject Lamp_Type, Lattice_Type; extern PyTypeObject Material_Type, Metaball_Type, MTex_Type; extern PyTypeObject NMFace_Type, NMVert_Type, NMCol_Type, NMesh_Type; -extern PyTypeObject MFace_Type, MVert_Type, MEdge_Type, MCol_Type, Mesh_Type; +extern PyTypeObject MFace_Type, MVert_Type, PVert_Type, MEdge_Type, MCol_Type, + Mesh_Type; extern PyTypeObject Object_Type; extern PyTypeObject Particle_Type; extern PyTypeObject Scene_Type, RenderData_Type; diff --git a/source/blender/python/api2_2x/doc/Mesh.py b/source/blender/python/api2_2x/doc/Mesh.py index 0c0ab3b547a..1a8518c9a40 100644 --- a/source/blender/python/api2_2x/doc/Mesh.py +++ b/source/blender/python/api2_2x/doc/Mesh.py @@ -3,6 +3,24 @@ """ The Blender.Mesh submodule. +B{New}: + - L{transform()<Mesh.transform>}: apply transform matrix to mesh vertices + - L{getFromObject()<Mesh.getFromObject>}: get mesh data from other + geometry objects + - L{findEdges()<Mesh.findEdges>}: determine if and where edges exist in the + mesh's edge list + - delete methods for L{verts<MVertSeq.delete>}, L{edges<MEdgeSeq.delete>} + and L{faces<MFaceSeq.delete>} + - new experimental mesh tools: + L{fill()<Mesh.Mesh.fill>}, + L{flipNormals()<Mesh.Mesh.flipNormals>}, + L{recalcNormals()<Mesh.Mesh.recalcNormals>}, + L{remDoubles()<Mesh.Mesh.remDoubles>}, + L{smooth()<Mesh.Mesh.smooth>}, + L{subdivide()<Mesh.Mesh.subdivide>} and + L{toSphere()<Mesh.Mesh.toSphere>} + - and if you're never used Mesh before, everything! + Mesh Data ========= @@ -82,14 +100,15 @@ class MVert: if vertex coordinates are changed, it may be necessary to use L{Mesh.calcNormals()} to update the vertex normals. @type no: vector - @ivar uvco: The vertex texture "sticky" coordinates (x, y), if present. + @ivar uvco: (MVerts only). The vertex texture "sticky" coordinates (x, y), + if present. Use L{Mesh.vertexUV} to test for presence before trying to access; otherwise an exception will may be thrown. (Sticky coordinates can be set when the object is in the Edit mode; from the Editing Panel (F9), look under the "Mesh" properties for the "Sticky" button). @type uvco: vector - @ivar index: The vertex's index within the mesh. Read-only. + @ivar index: (MVerts only). The vertex's index within the mesh. Read-only. @type index: int @ivar sel: The vertex's selection state (selected=1). B{Note}: a Mesh will return the selection state of the mesh when EditMode @@ -107,15 +126,29 @@ class MVert: def __init__(coord): """ - Create a new MVert object. + Create a new PVert object. + + @note: PVert-type objects are designed to be used for creating and + modifying a mesh's vertex list, but since they do not "wrap" any Blender + data there are some differences. The B{index} and B{uvco} attributes + are not defined for PVerts, and the B{no} attribute contains valid + data only if the PVert was created from an MVert (using a slice + operation on the mesh's vertex list.) PVerts also cannot be used as an + argument to any method which expects data wrapping a Blender mesh, such + as L{MVertSeq.delete()}. Example:: v = Blender.Mesh.MVert(1,0,0) v = Blender.Mesh.MVert(Blender.Mathutils.Vector([1,0,0])) + + m = Blender.Mesh.Get('Mesh') + vlist = m.verts[:] # slice operation also returns PVerts + @type coord: three floats or a Vector object @param coord: the coordinate values for the new vertex - @rtype: MVert - @return: a new MVert object + @rtype: PVert + @return: a new PVert object + """ class MVertSeq: @@ -128,7 +161,7 @@ class MVertSeq: a MVert object which "wraps" the actual vertex in the mesh; changing any of the vertex's attributes will immediately change the data in the mesh. When a slice of the vertex list is accessed, however, the operator[] - returns a list of MVert objects which are copies of the mesh's vertex + returns a list of PVert objects which are copies of the mesh's vertex data. Changes to these objects have no effect on the mesh; they must be assigned back to the mesh's vertex list. @@ -173,6 +206,20 @@ class MVertSeq: - a sequence (list or tuple) of either of the above. """ + def delete(verts): + """ + Deletes one or more vertices from the mesh. Any edge or face which + uses the specified vertices are also deleted. + + @type verts: multiple ints or MVerts + @param verts: can be + - a single MVert belonging to the mesh (B{note:} will not work with + PVerts) + - a single integer, specifying an index into the mesh's vertex list + - a sequence (list or tuple) containing two or more of either of + the above. + """ + class MEdge: """ The MEdge object @@ -228,6 +275,20 @@ class MEdgeSeq: of tuples each containing two to four MVerts. """ + def delete(edges): + """ + Deletes one or more edges from the mesh. In addition, also delete: + - any faces which uses the specified edge(s) + - any "orphan" vertices (belonging only to specified edge(s)) + + @type edges: multiple ints or MEdges + @param edges: can be + - a single MEdge belonging to the mesh + - a single integer, specifying an index into the mesh's edge list + - a sequence (list or tuple) containing two or more of either of + the above. + """ + class MFace: """ The MFace object @@ -383,6 +444,20 @@ class MFaceSeq: of tuples each containing two to four MVerts. """ + def delete(deledges, faces): + """ + Deletes one or more faces (and optionally the edges associated with + the face(s)) from the mesh. + + @type deledges: int + @param deledges: controls whether just the faces (deledges=0) + or the faces and edges (deledges=1) are deleted. These correspond to the + "Only Faces" and "Edges & Faces" options in the Edit Mode pop-up menu + @type faces: multiple ints or MFaces + @param faces: a sequence (list or tuple) containing one or more of: + - an MEdge belonging to the mesh + - a integer, specifying an index into the mesh's face list + """ class Mesh: """ @@ -433,11 +508,63 @@ class Mesh: @type activeFace: int """ + def getFromObject(name): + """ + Replace the mesh's existing data with the raw mesh data from a Blender + Object. This method support all the geometry based objects (mesh, text, + curve, surface, and meta). + @note: The mesh coordinates are in i{local space}, not the world space of + its object. For world space vertex coordinates, each vertex location must + be multiplied by the object's 4x4 transform matrix (see L{transform}). + @type name: string + @param name: name of the Blender object which contains the geometry data. + """ + def calcNormals(): """ Recalculates the vertex normals using face data. """ + def transform(matrix, recalc_normals = False): + """ + Transforms the mesh by the specified 4x4 matrix (such as returned by + L{Object.Object.getMatrix}). The matrix should be invertible. + Ideal usage for this is exporting to an external file where + global vertex locations are required for each object. + Sometimes external renderers or file formats do not use vertex normals. + In this case, you can skip transforming the vertex normals by leaving + the optional parameter recalc_normals as False or 0 (the default value). + + Example:: + # This script outputs deformed meshes worldspace vertex locations + # for a selected object without changing the object + import Blender + from Blender import Mesh, Object + + ob = Object.GetSelected()[0] # Get the first selected object + me = Mesh.New() # Create a new mesh + me.getFromObject(ob.name) # Get the object's mesh data + verts = me.verts[:] # Save a copy of the vertices + me.transform(ob.matrix) # Convert verts to world space + for v in me.verts: + print 'worldspace vert', v.co + me.verts = verts # Restore the original verts + + @type matrix: Py_Matrix + @param matrix: 4x4 Matrix which can contain location, scale and rotation. + @type recalc_normals: int + @param recalc_normals: if True or 1, also transform vertex normals. + @warn: unlike L{NMesh.transform()<NMesh.NMesh.transform>}, this method + I{will immediately modify the mesh data} when it is used. If you + transform the mesh using the object's matrix to get the vertices' + world positions, the result will be a "double transform". To avoid + this you either need to set the object's matrix to the identity + matrix, perform the inverse transform after outputting the transformed + vertices, or make a copy of the vertices prior to using this method + and restore them after outputting the transformed vertices (as shown + in the example). + """ + def vertexShade(object): """ Colors vertices based on the current lighting setup, like when there @@ -456,3 +583,69 @@ class Mesh: releases. """ + def findEdges(edges): + """ + Quickly search for the location of an edge. + @type edges: tuple(s) of ints or MVerts + @param edges: can be tuples of MVerts or integer indexes (B{note:} will + not work with PVerts) or a sequence (list or tuple) containing two or + tuples. + @rtype: int, None or list + @return: if an edge is found, its index is returned; otherwise None is + returned. If a sequence of edges is passed, a list is returned. + """ + + def smooth(): + """ + Flattens angle of selected faces. Experimental mesh tool. + An exception is thrown if called while in EditMode. + """ + + def flipNormals(): + """ + Toggles the direction of selected face's normals. Experimental mesh tool. + An exception is thrown if called while in EditMode. + """ + + def toSphere(): + """ + Moves selected vertices outward in a spherical shape. Experimental mesh + tool. + An exception is thrown if called while in EditMode. + """ + + def subdivide(beauty=0): + """ + Subdivide selected edges in a mesh. Experimental mesh tool. + An exception is thrown if called while in EditMode. + @type beauty: int + @param beauty: specifies whether a "beauty" subdivide should be + enabled (disabled is default). Value must be in the range [0,1]. + """ + + def remDoubles(limit): + """ + Removes duplicates from selected vertices. Experimental mesh tool. + An exception is thrown if called while in EditMode. + @type limit: float + @param limit: specifies the maximum distance considered for vertices + to be "doubles". Value is clamped to the range [0.0,1.0]. + @rtype: int + @return: the number of vertices deleted + """ + + def fill(): + """ + Scan fill a closed selected edge loop. Experimental mesh tool. + An exception is thrown if called while in EditMode. + """ + + def recalcNormals(direction=0): + """ + Recalculates inside or outside normals for selected faces. Experimental + mesh tool. + An exception is thrown if called while in EditMode. + @type direction: int + @param direction: specifies outward (0) or inward (1) normals. Outward + is the default. Value must be in the range [0,1]. + """ diff --git a/source/blender/python/api2_2x/doc/Types.py b/source/blender/python/api2_2x/doc/Types.py index 145a26b20ab..18e5352f142 100644 --- a/source/blender/python/api2_2x/doc/Types.py +++ b/source/blender/python/api2_2x/doc/Types.py @@ -36,7 +36,10 @@ Example:: @var MFaceType: Blender MFace. A mesh face, with three (a triangular face) or four (a quad face) vertices. @var MEdgeType: Blender MEdge. A mesh edge, with two vertices -@var MVertType: Blender MVert. A mesh vertex. +@var MVertType: Blender MVert. A mesh vertex which wraps a Blender mesh vertex + (typically an object returned from the mesh.verts sequence). +@var PVertType: Blender MVert. A mesh vertex which does not wrap a Blender + mesh vertex (returned from L{Blender.Mesh.MVert()<Mesh.MVert.__init__>}). @var MColType: Blender MCol. A mesh rgba color. @var ArmatureType: Blender Armature. The "skeleton", for animating and deforming objects. |