/* 
 * $Id: Mesh.c 17130 2008-10-20 12:33:31Z jesterking $
 *
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA	02111-1307, USA.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 *
 * This is a new part of Blender, partially based on NMesh.c API.
 *
 * Contributor(s): Ken Hughes
 *
 * ***** END GPL LICENSE BLOCK *****
 */

#include "Mesh.h" /*This must come first*/

#include "MEM_guardedalloc.h"

#include "DNA_key_types.h"
#include "DNA_armature_types.h"
#include "DNA_scene_types.h"
#include "DNA_oops_types.h"
#include "DNA_space_types.h"
#include "DNA_curve_types.h"
#include "DNA_meta_types.h"
#include "DNA_modifier_types.h"

#include "BDR_editface.h"	/* make_tfaces */
#include "BDR_vpaint.h"
#include "BDR_editobject.h"

#include "BIF_editdeform.h"
#include "BIF_editkey.h"	/* insert_meshkey */
#include "BIF_space.h"		/* REMAKEIPO - insert_meshkey */
#include "BIF_editview.h"
#include "BIF_editmesh.h"
#include "BIF_meshtools.h"

#include "BKE_customdata.h"
#include "BKE_deform.h"
#include "BKE_displist.h"
#include "BKE_mesh.h"
#include "BKE_material.h"
#include "BKE_main.h"
#include "BKE_multires.h"
#include "BKE_global.h"
#include "BKE_library.h"
#include "BKE_DerivedMesh.h"
#include "BKE_object.h"
#include "BKE_mball.h"
#include "BKE_utildefines.h"
#include "BKE_depsgraph.h"
#include "BSE_edit.h"		/* for countall(); */
#include "BKE_curve.h"		/* for copy_curve(); */
#include "BKE_modifier.h"	/* for modifier_new(), modifier_copyData(); */
#include "BKE_idprop.h"

#include "BLI_arithb.h"
#include "BLI_blenlib.h"
#include "BLI_memarena.h"

#include "blendef.h"
#include "mydevice.h"
#include "butspace.h"		/* for mesh tools */
#include "Object.h"
#include "Key.h"
#include "Image.h"
#include "Material.h"
#include "Mathutils.h"
#include "IDProp.h"
#include "meshPrimitive.h"
#include "constant.h"
#include "gen_utils.h"
#include "gen_library.h"
#include "multires.h"

/* EXPP Mesh defines */

#define MESH_SMOOTHRESH               30
#define MESH_SMOOTHRESH_MIN            1
#define MESH_SMOOTHRESH_MAX           80
#define MESH_SUBDIV                    1
#define MESH_SUBDIV_MIN                0
#define MESH_SUBDIV_MAX                6

#define MESH_HASFACEUV                 0
#define MESH_HASMCOL                   1
#define MESH_HASVERTUV                 2
#define MESH_HASMULTIRES               3

#define MESH_MULTIRES_LEVEL            0
#define MESH_MULTIRES_EDGE             1
#define MESH_MULTIRES_PIN              2
#define MESH_MULTIRES_RENDER           3

#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
#define MESH_TOOL_TRI2QUAD             7
#define MESH_TOOL_QUAD2TRI             8

static PyObject *MVertSeq_CreatePyObject( Mesh * mesh );
static PyObject *MFaceSeq_CreatePyObject( Mesh * mesh );
static PyObject *MEdgeSeq_CreatePyObject( Mesh * mesh );
static PyObject *MFace_CreatePyObject( Mesh * mesh, int i );
static PyObject *MEdge_CreatePyObject( Mesh * mesh, int i );

#define MFACE_VERT_BADRANGE_CHECK(me, face) ((int)face->v1 >= me->totvert || (int)face->v2 >= me->totvert || (int)face->v3 >= me->totvert || (int)face->v4 >= me->totvert)
#define MEDGE_VERT_BADRANGE_CHECK(me, edge) ((int)edge->v1 >= me->totvert || (int)edge->v2 >= me->totvert)

/************************************************************************
 *
 * internal utilities
 *
 ************************************************************************/

/*
 * internal structures used for sorting edges and faces
 */

typedef struct SrchEdges {
	unsigned int v[2];		/* indices for verts */
	unsigned char swap;		/* non-zero if verts swapped */
	unsigned int index;		/* index in original param list of this edge */
							/* (will be used by findEdges) */
} SrchEdges;

typedef struct SrchFaces {
	unsigned int v[4];		/* indices for verts */
	unsigned int index;		/* index in original param list of this edge */
	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
 */

static int medge_comp( const void *va, const void *vb )
{
	const unsigned int *a = ((SrchEdges *)va)->v;
	const unsigned int *b = ((SrchEdges *)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]);
}

/*
 * compare edges by insert list indices
 */

static int medge_index_comp( const void *va, const void *vb )
{
	const SrchEdges *a = (SrchEdges *)va;
	const SrchEdges *b = (SrchEdges *)vb;

	/* compare list indices for differences */

	if (a->index < b->index) return -1;
	else return (a->index > b->index);
}


/*
 * compare faces by vertex indices
 */

static int mface_comp( const void *va, const void *vb )
{
	const SrchFaces *a = va;
	const SrchFaces *b = vb;
	int i;

	/* compare indices, first to last, for differences */
	for( i = 0; i < 4; ++i ) {
		if( a->v[i] < b->v[i] )
			return -1;	
		if( a->v[i] > b->v[i] )
			return 1;
	}

	/*
	 * don't think this needs be done; if order is different then either
	 * (a) the face is good, just reversed or has a different starting
	 * vertex, or (b) face is bad (for 4 verts) and there's a "twist"
	 */

#if 0
	/* if all the same verts, compare their order */
	if( a->order < b->order )
		return -1;	
	if( a->order > b->order )
		return 1;	
#endif

	return 0;
}

/*
 * compare faces by insert list indices
 */

static int mface_index_comp( const void *va, const void *vb )
{
	const SrchFaces *a = va;
	const SrchFaces *b = vb;

	/* compare indices, first to last, for differences */
	if( a->index < b->index )
		return -1;	
	if( a->index > b->index )
		return 1;
	return 0;
}

/*
 * compare edges by vertex indices
 */

static int faceedge_comp( const void *va, const void *vb )
{
	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]);
}

/*
 * update the DAG for all objects linked to this mesh
 */

static void mesh_update( Mesh * mesh )
{
	Object_updateDag( (void *) mesh );
}

/*
 * 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;
	CustomData vdata;
	int i, count, state, dstindex, totvert;

	totvert = mesh->totvert - to_delete;
	CustomData_copy( &mesh->vdata, &vdata, CD_MASK_MESH, CD_CALLOC, totvert );

	/*
	 * do "smart compaction" of the table; find and copy groups of vertices
	 * which are not being deleted
	 */

	dstindex = 0;
	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 {
				count = 1;
				state = 1;
			}
			break;
		case 1:		/* gathering verts */
			if( *tmpvert != UINT_MAX ) {
				++count;
			} else {
				if( count ) {
					CustomData_copy_data( &mesh->vdata, &vdata, i-count,
						dstindex, count );
					dstindex += count;
				}
				count = 1;
				state = 0;
			}
		}
	}

	/* if we were gathering verts at the end of the loop, copy those */
	if( state && count )
		CustomData_copy_data( &mesh->vdata, &vdata, i-count, dstindex, count );

	/* delete old vertex list, install the new one, update vertex count */
	CustomData_free( &mesh->vdata, mesh->totvert );
	mesh->vdata = vdata;
	mesh->totvert = totvert;
	mesh_update_customdata_pointers( mesh );
}

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 ) {
		CustomData edata;
		int count, state, dstindex, totedge;
		
	/* allocate new edge list and populate */
		totedge = mesh->totedge - to_delete;
		CustomData_copy( &mesh->edata, &edata, CD_MASK_MESH, CD_CALLOC, totedge);

	/*
	 * do "smart compaction" of the edges; find and copy groups of edges
	 * which are not being deleted
	 */

		dstindex = 0;
		tmpedge = mesh->medge;
		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 {
					count = 1;
					state = 1;
				}
				break;
			case 1:		/* gathering edges */
				if( tmpedge->v1 != UINT_MAX ) {
					++count;
				} else {
					if( count ) {
						CustomData_copy_data( &mesh->edata, &edata, i-count,
							dstindex, count );
						dstindex += count;
					}
					count = 1;
					state = 0;
				}
			}
		/* if edge is good, update vertex indices */
		}

	/* copy any pending good edges */
		if( state && count )
			CustomData_copy_data( &mesh->edata, &edata, i-count, dstindex,
				count );

	/* delete old edge list, install the new one, update vertex count */
		CustomData_free( &mesh->edata, mesh->totedge );
		mesh->edata = edata;
		mesh->totedge = totedge;
		mesh_update_customdata_pointers( mesh );
	}

	/* 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, 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 );
		}
	} 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 );
	}
}

static void delete_faces( Mesh *mesh, unsigned int *vert_table, int to_delete )
{
	int i;
	MFace *tmpface;

		/* if there are faces to delete, handle it */
	if( to_delete ) {
		CustomData fdata;
		int count, state, dstindex, totface;
		
		totface = mesh->totface - to_delete;
		CustomData_copy( &mesh->fdata, &fdata, CD_MASK_MESH, CD_CALLOC, totface );

		/*
		 * do "smart compaction" of the faces; find and copy groups of faces
		 * which are not being deleted
		 */

		dstindex = 0;
		tmpface = mesh->mface;

		count = 0;
		state = 1;
		for( i = 0; i < mesh->totface; ++i ) {
			switch( state ) {
			case 0:		/* skipping faces */
				if( tmpface->v1 == UINT_MAX ) {
					++count;
				} else {
					count = 1;
					state = 1;
				}
				break;
			case 1:		/* gathering faces */
				if( tmpface->v1 != UINT_MAX ) {
					++count;
				} else {
					if( count ) {
						CustomData_copy_data( &mesh->fdata, &fdata, i-count,
							dstindex, count );
						dstindex += count;
					}
					count = 1;
					state = 0;
				}
			}
			++tmpface; 
		}

	/* if we were gathering faces at the end of the loop, copy those */
		if ( state && count )
			CustomData_copy_data( &mesh->fdata, &fdata, i-count, dstindex,
				count );

	/* delete old face list, install the new one, update face count */

		CustomData_free( &mesh->fdata, mesh->totface );
		mesh->fdata = fdata;
		mesh->totface = totface;
		mesh_update_customdata_pointers( mesh );
	}

	/* if vertices were deleted, update face's vertices */
	if( vert_table ) {
		tmpface = mesh->mface;

		for( i = 0; i<mesh->totface; ++i, ++tmpface ) {
			int len4 = tmpface->v4;
			tmpface->v1 = vert_table[tmpface->v1];
			tmpface->v2 = vert_table[tmpface->v2];
			tmpface->v3 = vert_table[tmpface->v3];
			if(len4)
				tmpface->v4 = vert_table[tmpface->v4];
			else
				tmpface->v4 = 0;

			test_index_face( tmpface, &mesh->fdata, i, len4? 4: 3);
		}
	}
}

/*
 * 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
 *
 ************************************************************************/

/*
 * get a color attribute
 */

static PyObject *MCol_getAttr( BPy_MCol * self, void *type )
{
	unsigned char param;

	switch( (long)type ) {
    case 'R':	/* these are backwards, but that how it works */
		param = self->color->b;
		break;
    case 'G':
		param = self->color->g;
		break;
    case 'B':	/* these are backwards, but that how it works */
		param = self->color->r;
		break;
    case 'A':
		param = self->color->a;
		break;
	default:
		{
			char errstr[1024];
			sprintf( errstr, "undefined type '%d' in MCol_getAttr",
					(int)((long)type & 0xff));
			return EXPP_ReturnPyObjError( PyExc_RuntimeError, errstr );
		}
	}

	return PyInt_FromLong( param ); 
}

/*
 * set a color attribute
 */

static int MCol_setAttr( BPy_MCol * self, PyObject * value, void * type )
{
	unsigned char *param;

	switch( (long)type ) {
    case 'R':	/* these are backwards, but that how it works */
		param = (unsigned char *)&self->color->b;
		break;
    case 'G':
		param = (unsigned char *)&self->color->g;
		break;
    case 'B':	/* these are backwards, but that how it works */
		param = (unsigned char *)&self->color->r;
		break;
    case 'A':
		param = (unsigned char *)&self->color->a;
		break;
	default:
		{
			char errstr[1024];
			sprintf( errstr, "undefined type '%d' in MCol_setAttr",
					(int)((long)type & 0xff));
			return EXPP_ReturnIntError( PyExc_RuntimeError, errstr );
		}
	}

	return EXPP_setIValueClamped( value, param, 0, 255, 'b' );
}

/************************************************************************
 *
 * Python MCol_Type attributes get/set structure
 *
 ************************************************************************/

static PyGetSetDef BPy_MCol_getseters[] = {
	{"r",
	 (getter)MCol_getAttr, (setter)MCol_setAttr,
	 "red component",
	 (void *)'R'},
	{"g",
	 (getter)MCol_getAttr, (setter)MCol_setAttr,
	 "green component",
	 (void *)'G'},
	{"b",
	 (getter)MCol_getAttr, (setter)MCol_setAttr,
	 "blue component",
	 (void *)'B'},
	{"a",
	 (getter)MCol_getAttr, (setter)MCol_setAttr,
	 "alpha component",
	 (void *)'A'},
	{NULL,NULL,NULL,NULL,NULL}  /* Sentinel */
};


/*----------------------------object[]---------------------------
  sequence accessor (get)*/
static PyObject *MCol_item(BPy_MCol * self, int i)
{
	unsigned char param;
	switch (i) {
	case 0:
		param = self->color->b;
		break;
	case 1:
		param = self->color->g;
		break;
	case 2:
		param = self->color->r;
		break;
	case 3:
		param = self->color->a;
		break;	
	default:
		return EXPP_ReturnPyObjError(PyExc_IndexError,
			"vector[index] = x: assignment index out of range\n");
	}
	
	return PyInt_FromLong( param );
}

/*----------------------------object[]-------------------------
  sequence accessor (set)*/
static int MCol_ass_item(BPy_MCol * self, int i, PyObject * value)
{
	unsigned char *param;
	
	switch (i) {
	case 0:
		param = (unsigned char *)&self->color->b; /* reversed? why */
		break;
	case 1:
		param = (unsigned char *)&self->color->g;
		break;
	case 2:
		param = (unsigned char *)&self->color->r; /* reversed? why */
		break;
	case 3:
		param = (unsigned char *)&self->color->a;
		break;	
	default:
		{
			return EXPP_ReturnIntError( PyExc_RuntimeError, "Index out of range" );
		}
	}
	return EXPP_setIValueClamped( value, param, 0, 255, 'b' );
}

/************************************************************************
 *
 * Python MCol_Type methods
 *
 ************************************************************************/

static PyObject *MCol_repr( BPy_MCol * self )
{
	return PyString_FromFormat( "[MCol %d %d %d %d]",
			(int)self->color->b, (int)self->color->g, 
			(int)self->color->r, (int)self->color->a ); 
}

/*-----------------PROTCOL DECLARATIONS--------------------------*/
static PySequenceMethods MCol_SeqMethods = {
	(inquiry) NULL,						/* sq_length */
	(binaryfunc) NULL,								/* sq_concat */
	(intargfunc) NULL,								/* sq_repeat */
	(intargfunc) MCol_item,					/* sq_item */
	(intintargfunc) NULL,				/* sq_slice */
	(intobjargproc) MCol_ass_item,			/* sq_ass_item */
	(intintobjargproc) NULL,		/* sq_ass_slice */
};

/************************************************************************
 *
 * Python MCol_Type structure definition
 *
 ************************************************************************/

PyTypeObject MCol_Type = {
	PyObject_HEAD_INIT( NULL )  /* required py macro */
	0,                          /* ob_size */
	/*  For printing, in format "<module>.<name>" */
	"Blender MCol",           /* char *tp_name; */
	sizeof( BPy_MCol ),       /* int tp_basicsize; */
	0,                          /* tp_itemsize;  For allocation */

	/* Methods to implement standard operations */

	NULL,						/* destructor tp_dealloc; */
	NULL,                       /* printfunc tp_print; */
	NULL,                       /* getattrfunc tp_getattr; */
	NULL,                       /* setattrfunc tp_setattr; */
	NULL,                       /* cmpfunc tp_compare; */
	( reprfunc ) MCol_repr,     /* reprfunc tp_repr; */

	/* Method suites for standard classes */

	NULL,                       /* PyNumberMethods *tp_as_number; */
	&MCol_SeqMethods,	        /* PySequenceMethods *tp_as_sequence; */
	NULL,                       /* PyMappingMethods *tp_as_mapping; */

	/* More standard operations (here for binary compatibility) */

	NULL,                       /* hashfunc tp_hash; */
	NULL,                       /* ternaryfunc tp_call; */
	NULL,                       /* reprfunc tp_str; */
	NULL,                       /* getattrofunc tp_getattro; */
	NULL,                       /* setattrofunc tp_setattro; */

	/* Functions to access object as input/output buffer */
	NULL,                       /* PyBufferProcs *tp_as_buffer; */

  /*** Flags to define presence of optional/expanded features ***/
	Py_TPFLAGS_DEFAULT,         /* long tp_flags; */

	NULL,                       /*  char *tp_doc;  Documentation string */
  /*** Assigned meaning in release 2.0 ***/
	/* call function for all accessible objects */
	NULL,                       /* traverseproc tp_traverse; */

	/* delete references to contained objects */
	NULL,                       /* inquiry tp_clear; */

  /***  Assigned meaning in release 2.1 ***/
  /*** rich comparisons ***/
	NULL,                       /* richcmpfunc tp_richcompare; */

  /***  weak reference enabler ***/
	0,                          /* long tp_weaklistoffset; */

  /*** Added in release 2.2 ***/
	/*   Iterators */
	NULL,                       /* getiterfunc tp_iter; */
	NULL,                       /* iternextfunc tp_iternext; */

  /*** Attribute descriptor and subclassing stuff ***/
	NULL,                       /* struct PyMethodDef *tp_methods; */
	NULL,                       /* struct PyMemberDef *tp_members; */
	BPy_MCol_getseters,       /* struct PyGetSetDef *tp_getset; */
	NULL,                       /* struct _typeobject *tp_base; */
	NULL,                       /* PyObject *tp_dict; */
	NULL,                       /* descrgetfunc tp_descr_get; */
	NULL,                       /* descrsetfunc tp_descr_set; */
	0,                          /* long tp_dictoffset; */
	NULL,                       /* initproc tp_init; */
	NULL,                       /* allocfunc tp_alloc; */
	NULL,                       /* newfunc tp_new; */
	/*  Low-level free-memory routine */
	NULL,                       /* freefunc tp_free;  */
	/* For PyObject_IS_GC */
	NULL,                       /* inquiry tp_is_gc;  */
	NULL,                       /* PyObject *tp_bases; */
	/* method resolution order */
	NULL,                       /* PyObject *tp_mro;  */
	NULL,                       /* PyObject *tp_cache; */
	NULL,                       /* PyObject *tp_subclasses; */
	NULL,                       /* PyObject *tp_weaklist; */
	NULL
};

static PyObject *MCol_CreatePyObject( MCol * color )
{
	BPy_MCol *obj = PyObject_NEW( BPy_MCol, &MCol_Type );

	if( !obj )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"PyObject_New() failed" );

	obj->color = color;
	return (PyObject *)obj;
}

/************************************************************************
 *
 * BPy_MVert attributes
 *
 ************************************************************************/

static MVert * MVert_get_pointer( BPy_MVert * self )
{
	if( BPy_MVert_Check( self ) ) {
		if( self->index >= ((Mesh *)self->data)->totvert )
			return (MVert *)EXPP_ReturnPyObjError( PyExc_RuntimeError,
					"MVert is no longer valid" );
		return &((Mesh *)self->data)->mvert[self->index];
	}
	else
		return (MVert *)self->data;
}

/*
 * get a vertex's coordinate
 */

static PyObject *MVert_getCoord( BPy_MVert * self )
{
	MVert *v;

	v = MVert_get_pointer( self );
	if( !v )
		return NULL;

	return newVectorObject( v->co, 3, Py_WRAP );
}

/*
 * set a vertex's coordinate
 */

static int MVert_setCoord( BPy_MVert * self, VectorObject * value )
{
	int i;
	MVert *v;

	v = MVert_get_pointer( self );
	if( !v )
		return -1;

	if( !VectorObject_Check( value ) || value->size != 3 )
		return EXPP_ReturnIntError( PyExc_TypeError,
				"expected vector argument of size 3" );

	for( i=0; i<3 ; ++i)
		v->co[i] = value->vec[i];

	return 0;
}

/*
 * get a vertex's index
 */

static PyObject *MVert_getIndex( BPy_MVert * self )
{
	if( self->index >= ((Mesh *)self->data)->totvert )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"MVert is no longer valid" );

	return PyInt_FromLong( self->index );
}


/*
 * get a verts's hidden state
 */

static PyObject *MVert_getMFlagBits( BPy_MVert * self, void * type )
{
	MVert *v;

	v = MVert_get_pointer( self );	
	if (!v)
		return NULL; /* error is set */

	return EXPP_getBitfield( &v->flag, (int)((long)type & 0xff), 'b' );
}


/*
 * set a verts's hidden state
 */

static int MVert_setMFlagBits( BPy_MVert * self, PyObject * value,
		void * type )
{
	MVert *v;

	v = MVert_get_pointer( self );

	if (!v)
		return -1; /* error is set */

	return EXPP_setBitfield( value, &v->flag, 
			(int)((long)type & 0xff), 'b' );
}


/*
 * get a vertex's normal
 */

static PyObject *MVert_getNormal( BPy_MVert * self )
{
	float no[3];
	int i;
	MVert *v;

	v = MVert_get_pointer( self );
	if( !v )
		return NULL; /* error set */

	for( i = 0; i < 3; ++i )
		no[i] = (float)(v->no[i] / 32767.0);
	return newVectorObject( no, 3, Py_NEW );
}

/*
 * set a vertex's normal
 */

static int MVert_setNormal( BPy_MVert * self, VectorObject * value )
{
	int i;
	MVert *v;
	float normal[3];
	
	v = MVert_get_pointer( self );
	if( !v )
		return -1; /* error set */

	if( !VectorObject_Check( value ) || value->size != 3 )
		return EXPP_ReturnIntError( PyExc_TypeError,
				"expected vector argument of size 3" );
	
	
	for( i=0; i<3 ; ++i)
		normal[i] = value->vec[i];
	
	Normalize(normal);
	
	for( i=0; i<3 ; ++i)
		v->no[i] = (short)(normal[i]*32767.0);
	
	return 0;
}


/*
 * get a vertex's select status
 */

static PyObject *MVert_getSel( BPy_MVert *self )
{
	MVert *v;

	v = MVert_get_pointer( self );
	if( !v )
		return NULL; /* error is set */

	return EXPP_getBitfield( &v->flag, SELECT, 'b' );
}

/*
 * set a vertex's select status
 */

static int MVert_setSel( BPy_MVert *self, PyObject *value )
{
	MVert *v = MVert_get_pointer( self );
	Mesh *me = (Mesh *)self->data;
	if (!v)
		return -1; /* error is set */

	/* 
	 * if vertex exists and setting status is OK, delete select storage
	 * of the edges and faces as well
	 */

	if( v && !EXPP_setBitfield( value, &v->flag, SELECT, 'b' ) ) {
		if( me && me->mselect ) {
			MEM_freeN( me->mselect );
			me->mselect = NULL;
		}
		return 0;
	}
	return -1;
}

/*
 * get a vertex's UV coordinates
 */

static PyObject *MVert_getUVco( BPy_MVert *self )
{
	Mesh *me = (Mesh *)self->data;

	if( !me->msticky )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
				"mesh has no 'sticky' coordinates" );

	if( self->index >= ((Mesh *)self->data)->totvert )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"MVert is no longer valid" );

	return newVectorObject( me->msticky[self->index].co, 2, Py_WRAP );
}

/*
 * set a vertex's UV coordinates
 */

static int MVert_setUVco( BPy_MVert *self, PyObject *value )
{
	float uvco[3] = {0.0, 0.0};
	Mesh *me = (Mesh *)self->data;
	struct MSticky *v;
	int i;

	/* 
	 * at least for now, don't allow creation of sticky coordinates if they
	 * don't already exist
	 */

	if( !me->msticky )
		return EXPP_ReturnIntError( PyExc_AttributeError,
				"mesh has no 'sticky' coordinates" );

	if( self->index >= ((Mesh *)self->data)->totvert )
		return EXPP_ReturnIntError( PyExc_RuntimeError,
				"MVert is no longer valid" );

	if( VectorObject_Check( value ) ) {
		VectorObject *vect = (VectorObject *)value;
		if( vect->size != 2 )
			return EXPP_ReturnIntError( PyExc_AttributeError,
					"expected 2D vector" );
		for( i = 0; i < vect->size; ++i )
			uvco[i] = vect->vec[i];
	} else if( !PyArg_ParseTuple( value, "ff",
				&uvco[0], &uvco[1] ) )
		return EXPP_ReturnIntError( PyExc_TypeError,
				"expected 2D vector" );

	v = &me->msticky[self->index];

	for( i = 0; i < 2; ++i )
		v->co[i] = uvco[i];

	return 0;
}

/************************************************************************
 *
 * Python MVert_Type attributes get/set structure
 *
 ************************************************************************/

static PyGetSetDef BPy_MVert_getseters[] = {
	{"co",
	 (getter)MVert_getCoord, (setter)MVert_setCoord,
	 "vertex's coordinate",
	 NULL},
	{"index",
	 (getter)MVert_getIndex, (setter)NULL,
	 "vertex's index",
	 NULL},
	{"no",
	 (getter)MVert_getNormal, (setter)MVert_setNormal,
	 "vertex's normal",
	 NULL},
	{"sel",
	 (getter)MVert_getSel, (setter)MVert_setSel,
	 "vertex's select status",
	 NULL},
    {"hide",
     (getter)MVert_getMFlagBits, (setter)MVert_setMFlagBits,
     "vert hidden in edit mode",
     (void *)ME_HIDE},
	{"uvco",
	 (getter)MVert_getUVco, (setter)MVert_setUVco,
	 "vertex's UV coordinates",
	 NULL},
	{NULL,NULL,NULL,NULL,NULL}  /* Sentinel */
};

static PyGetSetDef BPy_PVert_getseters[] = {
	{"co",
	 (getter)MVert_getCoord, (setter)MVert_setCoord,
	 "vertex's coordinate",
	 NULL},
	{"no",
	 (getter)MVert_getNormal, (setter)MVert_setNormal,
	 "vertex's normal",
	 NULL},
	{"sel",
	 (getter)MVert_getSel, (setter)MVert_setSel,
	 "vertex's select status",
	 NULL},
	{NULL,NULL,NULL,NULL,NULL}  /* Sentinel */
};

/************************************************************************
 *
 * Python MVert_Type standard operations
 *
 ************************************************************************/

static void MVert_dealloc( BPy_MVert * self )
{
	if( BPy_PVert_Check( self ) ) /* free memory of thick objects */
		MEM_freeN ( self->data );

	PyObject_DEL( self );
}

static int MVert_compare( BPy_MVert * a, BPy_MVert * b )
{
	return( a->data == b->data && a->index == b->index ) ? 0 : -1;
}

static PyObject *MVert_repr( BPy_MVert * self )
{
	char format[512];
	char index[24];
	MVert *v;

	v = MVert_get_pointer( self );
	if( !v )
		return NULL;

	if( BPy_MVert_Check( self ) )
		sprintf( index, "%d", self->index );
	else
		BLI_strncpy( index, "(None)", 24 );

	sprintf( format, "[MVert (%f %f %f) (%f %f %f) %s]",
			v->co[0], v->co[1], v->co[2], (float)(v->no[0] / 32767.0),
			(float)(v->no[1] / 32767.0), (float)(v->no[2] / 32767.0),
			index );

	return PyString_FromString( format );
}

static long MVert_hash( BPy_MVert *self )
{
	return (long)self->index;
}

static PyObject *Mesh_addPropLayer_internal(Mesh *mesh, CustomData *data, int tot, PyObject *args)
{
	char *name=NULL;
	int type = -1;
	
	if( !PyArg_ParseTuple( args, "si", &name, &type) )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
							"expected a string and an int" );
	if (strlen(name)>31)
		return EXPP_ReturnPyObjError( PyExc_ValueError,
							"error, maximum name length is 31");
	if((type != CD_PROP_FLT) && (type != CD_PROP_INT) && (type != CD_PROP_STR))
		return EXPP_ReturnPyObjError( PyExc_ValueError,
							"error, unknown layer type");
	if (name)
		CustomData_add_layer_named(data, type, CD_DEFAULT, NULL,tot,name);
	
	mesh_update_customdata_pointers(mesh);
	Py_RETURN_NONE;
}

static PyObject *Mesh_removePropLayer_internal(Mesh *mesh, CustomData *data, int tot,PyObject *value)
{
	CustomDataLayer *layer;
	char *name=PyString_AsString(value);
	int i;
	
	if( !name )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
					      "expected string argument" );
	
	if (strlen(name)>31)
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"error, maximum name length is 31" );
	
	i = CustomData_get_named_layer_index(data, CD_PROP_FLT, name);
	if(i == -1) i = CustomData_get_named_layer_index(data, CD_PROP_INT, name);
	if(i == -1) i = CustomData_get_named_layer_index(data, CD_PROP_STR, name);
	if (i==-1)
		return EXPP_ReturnPyObjError(PyExc_ValueError,
			"No matching layers to remove" );	
	layer = &data->layers[i];
	CustomData_free_layer(data, layer->type, tot, i);
	mesh_update_customdata_pointers(mesh);

	Py_RETURN_NONE;
}

static PyObject *Mesh_renamePropLayer_internal(Mesh *mesh, CustomData *data, PyObject *args)
{
	CustomDataLayer *layer;
	int i;
	char *name_from, *name_to;
	
	if( !PyArg_ParseTuple( args, "ss", &name_from, &name_to ) )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
				"expected 2 strings" );
	
	if (strlen(name_from)>31 || strlen(name_to)>31)
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"error, maximum name length is 31" );
	
	i = CustomData_get_named_layer_index(data, CD_PROP_FLT, name_from);
	if(i == -1) i = CustomData_get_named_layer_index(data, CD_PROP_INT, name_from);
	if(i == -1) i = CustomData_get_named_layer_index(data, CD_PROP_STR, name_from);
	if(i == -1)
		return EXPP_ReturnPyObjError(PyExc_ValueError,
			"No matching layers to rename" );	

	layer = &data->layers[i];
	
	strcpy(layer->name, name_to); /* we alredy know the string sizes are under 32 */
	CustomData_set_layer_unique_name(data, i);
	Py_RETURN_NONE;
}

static PyObject *Mesh_propList_internal(CustomData *data)
{
	CustomDataLayer *layer;
	PyObject *list = PyList_New( 0 ), *item;
	int i;
	for(i=0; i<data->totlayer; i++) {
		layer = &data->layers[i];
		if( (layer->type == CD_PROP_FLT) || (layer->type == CD_PROP_INT) || (layer->type == CD_PROP_STR)) {
			item = PyString_FromString(layer->name);
			PyList_Append( list, item );
			Py_DECREF(item);
		}
	}
	return list;
} 

static PyObject *Mesh_getProperty_internal(CustomData *data, int eindex, PyObject *value)
{
	CustomDataLayer *layer;
	char *name=PyString_AsString(value);
	int i;
	MFloatProperty *pf;
	MIntProperty *pi;
	MStringProperty *ps;

	if(!name)
		return EXPP_ReturnPyObjError( PyExc_TypeError,
			"expected an string argument" );
	
	if (strlen(name)>31)
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"error, maximum name length is 31" );
	
	i = CustomData_get_named_layer_index(data, CD_PROP_FLT, name);
	if(i == -1) i = CustomData_get_named_layer_index(data, CD_PROP_INT, name);
	if(i == -1) i = CustomData_get_named_layer_index(data, CD_PROP_STR, name);
	if(i == -1)
		return EXPP_ReturnPyObjError(PyExc_ValueError,
			"No matching layers" );	
	
	layer = &data->layers[i];

	if(layer->type == CD_PROP_FLT){ 
		pf = layer->data;
		return PyFloat_FromDouble(pf[eindex].f);
	}
	else if(layer->type == CD_PROP_INT){
		pi = layer->data;
		return PyInt_FromLong(pi[eindex].i);
	
	}
	else if(layer->type == CD_PROP_STR){
		ps = layer->data;
		return PyString_FromString(ps[eindex].s);
	}
	Py_RETURN_NONE;
}

static PyObject *Mesh_setProperty_internal(CustomData *data, int eindex, PyObject *args)
{
	CustomDataLayer *layer;
	int i = 0, index, type = -1;
	float f = 0.0f;
	char *s=NULL, *name=NULL;
	MFloatProperty *pf;
	MIntProperty  *pi;
	MStringProperty *ps;
	PyObject *val;
	
	if(PyArg_ParseTuple(args, "sO", &name, &val)){
		if (strlen(name)>31)
			return EXPP_ReturnPyObjError( PyExc_ValueError,
					"error, maximum name length is 31" );
		
		if(PyInt_Check(val)){ 
			type = CD_PROP_INT;
			i = (int)PyInt_AS_LONG(val);
		}
		else if(PyFloat_Check(val)){
			type = CD_PROP_FLT;
			f = (float)PyFloat_AsDouble(val);
		}
		else if(PyString_Check(val)){
			type = CD_PROP_STR;
			s = PyString_AsString(val);
		}
		else
			return EXPP_ReturnPyObjError( PyExc_TypeError,
					"expected an name plus either float/int/string" );

	}

	index = CustomData_get_named_layer_index(data, type, name);
	if(index == -1)
		return EXPP_ReturnPyObjError(PyExc_ValueError,
			"No matching layers or type mismatch" );	

	layer = &data->layers[index];
	
	if(type==CD_PROP_STR){
		if (strlen(s)>255){
			return EXPP_ReturnPyObjError( PyExc_ValueError,
				"error, maximum string length is 255");
		}
		else{
			ps =  layer->data;
			strcpy(ps[eindex].s,s);
		}
	}
	else if(type==CD_PROP_FLT){
		pf = layer->data;
		pf[eindex].f = f;
	}
	else{
		pi = layer->data;
		pi[eindex].i = i;
	}
	Py_RETURN_NONE;
}

static PyObject *MVert_getProp( BPy_MVert *self, PyObject *args)
{
	if( BPy_MVert_Check( self ) ){
		Mesh *me = (Mesh *)self->data;
		if(self->index >= me->totvert)
			return EXPP_ReturnPyObjError( PyExc_ValueError,
				"error, MVert is no longer valid part of mesh!");
		else
			return Mesh_getProperty_internal(&(me->vdata), self->index, args);
	}
	return EXPP_ReturnPyObjError( PyExc_ValueError,
				"error, Vertex not part of a mesh!");
}

static PyObject *MVert_setProp( BPy_MVert *self,  PyObject *args)
{
	if( BPy_MVert_Check( self ) ){
		Mesh *me = (Mesh *)self->data;
		if(self->index >= me->totvert)
			return EXPP_ReturnPyObjError( PyExc_ValueError,
				"error, MVert is no longer valid part of mesh!");
		else
			return Mesh_setProperty_internal(&(me->vdata), self->index, args);
	}
	return EXPP_ReturnPyObjError( PyExc_ValueError,
				"error, Vertex not part of a mesh!");
}
	
static struct PyMethodDef BPy_MVert_methods[] = {
	{"getProperty", (PyCFunction)MVert_getProp, METH_O,
		"get property indicated by name"},
	{"setProperty", (PyCFunction)MVert_setProp, METH_VARARGS,
		"set property indicated by name"},
	{NULL, NULL, 0, NULL}
};


/************************************************************************
 *
 * Python MVert_Type structure definition
 *
 ************************************************************************/

PyTypeObject MVert_Type = {
	PyObject_HEAD_INIT( NULL )  /* required py macro */
	0,                          /* ob_size */
	/*  For printing, in format "<module>.<name>" */
	"Blender MVert",            /* char *tp_name; */
	sizeof( BPy_MVert ),        /* int tp_basicsize; */
	0,                          /* tp_itemsize;  For allocation */

	/* Methods to implement standard operations */

	( destructor ) MVert_dealloc,/* destructor tp_dealloc; */
	NULL,                       /* printfunc tp_print; */
	NULL,                       /* getattrfunc tp_getattr; */
	NULL,                       /* setattrfunc tp_setattr; */
	( cmpfunc ) MVert_compare,  /* cmpfunc tp_compare; */
	( reprfunc ) MVert_repr,    /* reprfunc tp_repr; */

	/* Method suites for standard classes */

	NULL,                       /* PyNumberMethods *tp_as_number; */
	NULL,	        			/* PySequenceMethods *tp_as_sequence; */
	NULL,                       /* PyMappingMethods *tp_as_mapping; */

	/* More standard operations (here for binary compatibility) */

	( hashfunc ) MVert_hash,    /* hashfunc tp_hash; */
	NULL,                       /* ternaryfunc tp_call; */
	NULL,                       /* reprfunc tp_str; */
	NULL,                       /* getattrofunc tp_getattro; */
	NULL,                       /* setattrofunc tp_setattro; */

	/* Functions to access object as input/output buffer */
	NULL,                       /* PyBufferProcs *tp_as_buffer; */

  /*** Flags to define presence of optional/expanded features ***/
	Py_TPFLAGS_DEFAULT,         /* long tp_flags; */

	NULL,                       /*  char *tp_doc;  Documentation string */
  /*** Assigned meaning in release 2.0 ***/
	/* call function for all accessible objects */
	NULL,                       /* traverseproc tp_traverse; */

	/* delete references to contained objects */
	NULL,                       /* inquiry tp_clear; */

  /***  Assigned meaning in release 2.1 ***/
  /*** rich comparisons ***/
	NULL,                       /* richcmpfunc tp_richcompare; */

  /***  weak reference enabler ***/
	0,                          /* long tp_weaklistoffset; */

  /*** Added in release 2.2 ***/
	/*   Iterators */
	NULL,                       /* getiterfunc tp_iter; */
	NULL,                       /* iternextfunc tp_iternext; */

  /*** Attribute descriptor and subclassing stuff ***/
	BPy_MVert_methods,          /* struct PyMethodDef *tp_methods; */
	NULL,                       /* struct PyMemberDef *tp_members; */
	BPy_MVert_getseters,        /* struct PyGetSetDef *tp_getset; */
	NULL,                       /* struct _typeobject *tp_base; */
	NULL,                       /* PyObject *tp_dict; */
	NULL,                       /* descrgetfunc tp_descr_get; */
	NULL,                       /* descrsetfunc tp_descr_set; */
	0,                          /* long tp_dictoffset; */
	NULL,                       /* initproc tp_init; */
	NULL,                       /* allocfunc tp_alloc; */
	NULL,                       /* newfunc tp_new; */
	/*  Low-level free-memory routine */
	NULL,                       /* freefunc tp_free;  */
	/* For PyObject_IS_GC */
	NULL,                       /* inquiry tp_is_gc;  */
	NULL,                       /* PyObject *tp_bases; */
	/* method resolution order */
	NULL,                       /* PyObject *tp_mro;  */
	NULL,                       /* PyObject *tp_cache; */
	NULL,                       /* PyObject *tp_subclasses; */
	NULL,                       /* PyObject *tp_weaklist; */
	NULL
};

/************************************************************************
 *
 * Python PVert_Type standard operations
 *
 ************************************************************************/

static int PVert_compare( BPy_MVert * a, BPy_MVert * b )
{
	return( a->data == b->data ) ? 0 : -1;
}

/************************************************************************
 *
 * Python PVert_Type structure definition
 *
 ************************************************************************/

PyTypeObject PVert_Type = {
	PyObject_HEAD_INIT( NULL )  /* required py macro */
	0,                          /* ob_size */
	/*  For printing, in format "<module>.<name>" */
	"Blender PVert",            /* char *tp_name; */
	sizeof( BPy_MVert ),        /* int tp_basicsize; */
	0,                          /* tp_itemsize;  For allocation */

	/* Methods to implement standard operations */

	( destructor ) MVert_dealloc,/* destructor tp_dealloc; */
	NULL,                       /* printfunc tp_print; */
	NULL,                       /* getattrfunc tp_getattr; */
	NULL,                       /* setattrfunc tp_setattr; */
	( cmpfunc ) PVert_compare,  /* cmpfunc tp_compare; */
	( reprfunc ) MVert_repr,    /* reprfunc tp_repr; */

	/* Method suites for standard classes */

	NULL,                       /* PyNumberMethods *tp_as_number; */
	NULL,	        			/* PySequenceMethods *tp_as_sequence; */
	NULL,                       /* PyMappingMethods *tp_as_mapping; */

	/* More standard operations (here for binary compatibility) */

	( hashfunc ) MVert_hash,    /* hashfunc tp_hash; */
	NULL,                       /* ternaryfunc tp_call; */
	NULL,                       /* reprfunc tp_str; */
	NULL,                       /* getattrofunc tp_getattro; */
	NULL,                       /* setattrofunc tp_setattro; */

	/* Functions to access object as input/output buffer */
	NULL,                       /* PyBufferProcs *tp_as_buffer; */

  /*** Flags to define presence of optional/expanded features ***/
	Py_TPFLAGS_DEFAULT,         /* long tp_flags; */

	NULL,                       /*  char *tp_doc;  Documentation string */
  /*** Assigned meaning in release 2.0 ***/
	/* call function for all accessible objects */
	NULL,                       /* traverseproc tp_traverse; */

	/* delete references to contained objects */
	NULL,                       /* inquiry tp_clear; */

  /***  Assigned meaning in release 2.1 ***/
  /*** rich comparisons ***/
	NULL,                       /* richcmpfunc tp_richcompare; */

  /***  weak reference enabler ***/
	0,                          /* long tp_weaklistoffset; */

  /*** Added in release 2.2 ***/
	/*   Iterators */
	NULL,                       /* getiterfunc tp_iter; */
	NULL,                       /* iternextfunc tp_iternext; */

  /*** Attribute descriptor and subclassing stuff ***/
	NULL,                       /* struct PyMethodDef *tp_methods; */
	NULL,                       /* struct PyMemberDef *tp_members; */
	BPy_PVert_getseters,        /* struct PyGetSetDef *tp_getset; */
	NULL,                       /* struct _typeobject *tp_base; */
	NULL,                       /* PyObject *tp_dict; */
	NULL,                       /* descrgetfunc tp_descr_get; */
	NULL,                       /* descrsetfunc tp_descr_set; */
	0,                          /* long tp_dictoffset; */
	NULL,                       /* initproc tp_init; */
	NULL,                       /* allocfunc tp_alloc; */
	NULL,                       /* newfunc tp_new; */
	/*  Low-level free-memory routine */
	NULL,                       /* freefunc tp_free;  */
	/* For PyObject_IS_GC */
	NULL,                       /* inquiry tp_is_gc;  */
	NULL,                       /* PyObject *tp_bases; */
	/* method resolution order */
	NULL,                       /* PyObject *tp_mro;  */
	NULL,                       /* PyObject *tp_cache; */
	NULL,                       /* PyObject *tp_subclasses; */
	NULL,                       /* PyObject *tp_weaklist; */
	NULL
};

/*
 * create 'thin' or 'thick' MVert objects
 *
 * there are two types of objects; thin (wrappers for mesh vertex) and thick
 * (not contains in mesh).  Thin objects are MVert_Type and thick are
 * PVert_Type.  For thin objects, data is a pointer to a Mesh and index
 * is the vertex's index in mesh->mvert.  For thick objects, data is a
 * pointer to an MVert; index is unused.
 */

/*
 * create a thin MVert object
 */

static PyObject *MVert_CreatePyObject( Mesh *mesh, int i )
{
	BPy_MVert *obj = (BPy_MVert *)PyObject_NEW( BPy_MVert, &MVert_Type );

	if( !obj )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"PyObject_New() failed" );

	obj->index = i;
	obj->data = mesh;
	return (PyObject *)obj;
}

/*
 * create a thick MVert object
 */

static PyObject *PVert_CreatePyObject( MVert *vert )
{
	MVert *newvert;
	BPy_MVert *obj = (BPy_MVert *)PyObject_NEW( BPy_MVert, &PVert_Type );

	if( !obj )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"PyObject_New() failed" );

	newvert = (MVert *)MEM_callocN( sizeof( MVert ), "MVert" );
	if( !newvert )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"MEM_callocN() failed" );

	memcpy( newvert, vert, sizeof( MVert ) );
	obj->data = newvert;
	return (PyObject *)obj;
}

/************************************************************************
 *
 * Vertex sequence 
 *
 ************************************************************************/

static int MVertSeq_len( BPy_MVertSeq * self )
{
	return self->mesh->totvert;
}

/*
 * retrive a single MVert from somewhere in the vertex list
 */

static PyObject *MVertSeq_item( BPy_MVertSeq * self, int i )
{
	if( i < 0 || i >= self->mesh->totvert )
		return EXPP_ReturnPyObjError( PyExc_IndexError,
					      "array index out of range" );

	return MVert_CreatePyObject( self->mesh, i );
}

/*
 * retrieve a slice of the vertex list (as a Python list)
 *
 * Python is nice enough to handle negative indices for us: if the user
 * specifies -1, Python will pass us len()-1.  So we can just check for
 * indices in the range 0:len()-1.  Of course, we should never actually
 * return the high index, but up to one less.
 */

static PyObject *MVertSeq_slice( BPy_MVertSeq *self, int low, int high )
{
	PyObject *list;
	int i;

	/*
	 * Python list slice operator returns empty list when asked for a slice
	 * outside the list, or if indices are reversed (low > high).  Clamp
	 * our input to do the same.
	 */

	if( low < 0 ) low = 0;
	if( high > self->mesh->totvert ) high = self->mesh->totvert;
	if( low > high ) low = high;

	list = PyList_New( high-low );
	if( !list )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
					      "PyList_New() failed" );

	/*
	 * return Py_NEW copies of requested vertices
	 */

	for( i = low; i < high; ++i )
		PyList_SET_ITEM( list, i-low,
				PVert_CreatePyObject( (void *)&self->mesh->mvert[i] ) );
	return list;
}

/*
 * assign a single MVert to somewhere in the vertex list
 */

static int MVertSeq_assign_item( BPy_MVertSeq * self, int i,
		BPy_MVert *v )
{
	MVert *dst = &self->mesh->mvert[i];
	MVert *src;

	if( !v )
		return EXPP_ReturnIntError( PyExc_IndexError,
					      "del() not supported" );

	if( i < 0 || i >= self->mesh->totvert )
		return EXPP_ReturnIntError( PyExc_IndexError,
					      "array index out of range" );

	if( BPy_MVert_Check( v ) )
		src = &((Mesh *)v->data)->mvert[v->index];
	else
		src = (MVert *)v->data;

	memcpy( dst, src, sizeof(MVert) );
	/* mesh_update( self->mesh );*/
	return 0;
}

static int MVertSeq_assign_slice( BPy_MVertSeq *self, int low, int high,
		   PyObject *args )
{
	int len, i;

	if( !PyList_Check( args ) )
		return EXPP_ReturnIntError( PyExc_IndexError,
					      "can only assign lists of MVerts" );

	len = PyList_Size( args );

	/*
	 * Python list slice assign operator allows for changing the size of the
	 * destination list, by replacement and appending....
	 *
	 * >>> l=[1,2,3,4]
	 * >>> m=[11,12,13,14]
	 * >>> l[5:7]=m
	 * >>> print l
	 * [1, 2, 3, 4, 11, 12, 13, 14]
	 * >>> l=[1,2,3,4]
	 * >>> l[2:3]=m
	 * >>> print l
	 * [1, 2, 11, 12, 13, 14, 4]
	 *
	 * We don't want the size of the list to change (at least not at time
	 * point in development) so we are a little more strict:
	 * - low and high indices must be in range [0:len()]
	 * - high-low == PyList_Size(v)
	 */

	if( low < 0 || high > self->mesh->totvert || low > high )
		return EXPP_ReturnIntError( PyExc_IndexError,
					      "invalid slice range" );

	if( high-low != len )
		return EXPP_ReturnIntError( PyExc_IndexError,
					      "slice range and input list sizes must be equal" );

	for( i = low; i < high; ++i )
	{
		BPy_MVert *v = (BPy_MVert *)PyList_GET_ITEM( args, i-low );
		MVert *dst = &self->mesh->mvert[i];
		MVert *src;

		if( BPy_MVert_Check( v ) )
			src = &((Mesh *)v->data)->mvert[v->index];
		else
			src = (MVert *)v->data;

		memcpy( dst, src, sizeof(MVert) );
	}
	/* mesh_update( self->mesh );*/
	return 0;
}

static PySequenceMethods MVertSeq_as_sequence = {
	( inquiry ) MVertSeq_len,	/* sq_length */
	( binaryfunc ) 0,	/* sq_concat */
	( intargfunc ) 0,	/* sq_repeat */
	( intargfunc ) MVertSeq_item,	/* sq_item */
	( intintargfunc ) MVertSeq_slice,	/* sq_slice */
	( intobjargproc ) MVertSeq_assign_item,	/* sq_ass_item */
	( intintobjargproc ) MVertSeq_assign_slice,	/* sq_ass_slice */
	0,0,0,
};

/************************************************************************
 *
 * Python MVertSeq_Type iterator (iterates over vertices)
 *
 ************************************************************************/

/*
 * Initialize the interator index
 */

static PyObject *MVertSeq_getIter( BPy_MVertSeq * self )
{
	if (self->iter==-1) { /* iteration for this pyobject is not yet used, just return self */
		self->iter = 0;
		return EXPP_incr_ret ( (PyObject *) self );
	} else {
		/* were alredy using this as an iterator, make a copy to loop on */
		BPy_MVertSeq *seq = (BPy_MVertSeq *)MVertSeq_CreatePyObject(self->mesh);
		seq->iter = 0;
		return (PyObject *)seq;
	}
}

/*
 * Return next MVert.
 */

static PyObject *MVertSeq_nextIter( BPy_MVertSeq * self )
{
	if( self->iter == self->mesh->totvert ) {
		self->iter= -1; /* allow it to be used as an iterator again without creating a new BPy_MVertSeq */
		return EXPP_ReturnPyObjError( PyExc_StopIteration,
				"iterator at end" );
	}

	return MVert_CreatePyObject( self->mesh, self->iter++ );
}

/************************************************************************
 *
 * Python MVertSeq_Type methods
 *
 ************************************************************************/

static PyObject *MVertSeq_extend( BPy_MVertSeq * self, PyObject *args )
{
	int len, newlen;
	int i,j;
	PyObject *tmp;
	MVert *newvert, *tmpvert;
	Mesh *mesh = self->mesh;
	CustomData vdata;
	/* make sure we get a sequence of tuples of something */

	switch( PySequence_Size( args ) ) {
	case 1:		/* better be a list or a tuple */
		tmp = PyTuple_GET_ITEM( args, 0 );
		if( !VectorObject_Check ( tmp ) ) {
			if( !PySequence_Check ( tmp ) )
				return EXPP_ReturnPyObjError( PyExc_TypeError,
						"expected a sequence of sequence triplets" );
			else if( !PySequence_Size ( tmp ) ) {
				Py_RETURN_NONE;
			}
			args = tmp;
		}
		Py_INCREF( args );		/* so we can safely DECREF later */
		break;
	case 3:
		tmp = PyTuple_GET_ITEM( args, 0 );
		/* if first item is not a number, it's wrong */
		if( !PyNumber_Check( tmp ) )
			return EXPP_ReturnPyObjError( PyExc_TypeError,
					"expected a sequence of sequence triplets" );

		/* otherwise, put into a new tuple */
		args = Py_BuildValue( "((OOO))", tmp,
				PyTuple_GET_ITEM( args, 1 ), PyTuple_GET_ITEM( args, 2 ) );
		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 sequence triplets" );
	}

	/* if no verts given, return quietly */
	len = PySequence_Size( args );
	if( len == 0 ) {
		Py_DECREF ( args );
		Py_RETURN_NONE;
	}

	/* create custom vertex data arrays and copy existing vertices into it */

	newlen = mesh->totvert + len;
	CustomData_copy( &mesh->vdata, &vdata, CD_MASK_MESH, CD_DEFAULT, newlen );
	CustomData_copy_data( &mesh->vdata, &vdata, 0, 0, mesh->totvert );

	if ( !CustomData_has_layer( &vdata, CD_MVERT ) )
		CustomData_add_layer( &vdata, CD_MVERT, CD_CALLOC, NULL, newlen );

	newvert = CustomData_get_layer( &vdata, CD_MVERT );

	/* scan the input list and insert the new vertices */

	tmpvert = &newvert[mesh->totvert];
	for( i = 0; i < len; ++i ) {
		float co[3];
		tmp = PySequence_GetItem( args, i );
		if( VectorObject_Check( tmp ) ) {
			if( ((VectorObject *)tmp)->size != 3 ) {
				CustomData_free( &vdata, newlen );
				Py_DECREF ( tmp );
				Py_DECREF ( args );
				return EXPP_ReturnPyObjError( PyExc_ValueError,
					"expected vector of size 3" );
			}
			for( j = 0; j < 3; ++j )
				co[j] = ((VectorObject *)tmp)->vec[j];
		} else if( PySequence_Check( tmp ) ) {
			int ok=1;
			PyObject *flt;
			if( PySequence_Size( tmp ) != 3 )
				ok = 0;
			else	
				for( j = 0; ok && j < 3; ++j ) {
					flt = PySequence_ITEM( tmp, j );
					if( !PyNumber_Check ( flt ) )
						ok = 0;
					else
						co[j] = (float)PyFloat_AsDouble( flt );
					Py_DECREF( flt );
				}

			if( !ok ) {
				CustomData_free( &vdata, newlen );
				Py_DECREF ( args );
				Py_DECREF ( tmp );
				return EXPP_ReturnPyObjError( PyExc_ValueError,
					"expected sequence triplet of floats" );
			}
		} else {
			CustomData_free( &vdata, newlen );
			Py_DECREF ( args );
			Py_DECREF ( tmp );
			return EXPP_ReturnPyObjError( PyExc_ValueError,
				"expected sequence triplet of floats" );
		}

		Py_DECREF ( tmp );

	/* add the coordinate to the new list */
		memcpy( tmpvert->co, co, sizeof(co) );
		
		tmpvert->flag |= SELECT;
	/* TODO: anything else which needs to be done when we add a vert? */
	/* probably not: NMesh's newvert() doesn't */
		++tmpvert;
	}

	CustomData_free( &mesh->vdata, mesh->totvert );
	mesh->vdata = vdata;
	mesh_update_customdata_pointers( mesh );

	/*
	 * if there are keys, have to fix those lists up
	 */

	if( mesh->key ) {
		KeyBlock *currkey = mesh->key->block.first;
		float *fp, *newkey;

		while( currkey ) {

			/* create key list, copy existing data if any */
			newkey = MEM_callocN(mesh->key->elemsize*newlen, "keydata");
			if( currkey->data ) {
				memcpy( newkey, currkey->data,
						mesh->totvert*mesh->key->elemsize );
				MEM_freeN( currkey->data );
				currkey->data = newkey;
			}

			/* add data for new vertices */
			fp = (float *)((char *)currkey->data +
					(mesh->key->elemsize*mesh->totvert));
			tmpvert = mesh->mvert + mesh->totvert;
			for( i = newlen - mesh->totvert; i > 0; --i ) {
				VECCOPY(fp, tmpvert->co);
				fp += 3;
				tmpvert++;
			}
			currkey->totelem = newlen;
			currkey = currkey->next;
		}
	}

	/* set final vertex list size */
	mesh->totvert = newlen;

	mesh_update( mesh );

	Py_DECREF ( args );
	Py_RETURN_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;

	/*
	 * if input tuple contains a single sequence, use it as input instead;
	 * otherwise use the sequence as-is and check later that it contains
	 * one or more integers or MVerts
	 */
	if( PySequence_Size( args ) == 1 ) {
		PyObject *tmp = PyTuple_GET_ITEM( args, 0 );
		if( PySequence_Check( tmp ) ) 
			args = tmp;
	}

	/* if sequence is empty, do nothing */
	if( PySequence_Size( args ) == 0 ) {
		Py_RETURN_NONE;
	}

	/* 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( tmp );
				return EXPP_ReturnPyObjError( PyExc_ValueError,
						"MVert belongs to a different mesh" );
			}
			index = ((BPy_MVert*)tmp)->index;
		} else if( PyInt_Check( tmp ) ) {
			index = PyInt_AsLong ( tmp );
		} else {
			MEM_freeN( vert_table );
			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 );
			return EXPP_ReturnPyObjError( PyExc_IndexError,
					"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 ||
				( tmpface->v4 && 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_RETURN_NONE;
}

static PyObject *MVertSeq_selected( BPy_MVertSeq * self )
{
	int i, count;
	Mesh *mesh = self->mesh;
	MVert *tmpvert;
	PyObject *list;

	/* first count selected edges (quicker than appending to PyList?) */
	count = 0;
	tmpvert = mesh->mvert;
	for( i = 0; i < mesh->totvert; ++i, ++tmpvert )
		if( tmpvert->flag & SELECT )
			++count;

	list = PyList_New( count );
	if( !list )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"PyList_New() failed" );

	/* next, insert selected edges into list */
	count = 0;
	tmpvert = mesh->mvert;
	for( i = 0; i < mesh->totvert; ++i, ++tmpvert ) {
		if( tmpvert->flag & SELECT ) {
			PyObject *tmp = PyInt_FromLong( i );
			if( !tmp ) {
				Py_DECREF( list );
				return EXPP_ReturnPyObjError( PyExc_RuntimeError,
						"PyInt_FromLong() failed" );
			}
			PyList_SET_ITEM( list, count, tmp );
			++count;
		}
	}
	return list;
}
static PyObject *MVertSeq_add_layertype(BPy_MVertSeq *self, PyObject *args)
{
	Mesh *me = (Mesh*)self->mesh;
	return Mesh_addPropLayer_internal(me, &(me->vdata), me->totvert, args);
}
static PyObject *MVertSeq_del_layertype(BPy_MVertSeq *self, PyObject *value)
{
	Mesh *me = (Mesh*)self->mesh;
	return Mesh_removePropLayer_internal(me, &(me->vdata), me->totvert, value);
}
static PyObject *MVertSeq_rename_layertype(BPy_MVertSeq *self, PyObject *args)
{
	Mesh *me = (Mesh*)self->mesh;
	return Mesh_renamePropLayer_internal(me,&(me->vdata),args);
}
static PyObject *MVertSeq_PropertyList(BPy_MVertSeq *self) 
{
	Mesh *me = (Mesh*)self->mesh;
	return Mesh_propList_internal(&(me->vdata));
}
static PyObject *M_Mesh_PropertiesTypeDict(void)
{
	PyObject *Types = PyConstant_New( );
	if(Types) {
		BPy_constant *d = (BPy_constant *) Types;
		PyConstant_Insert(d, "FLOAT", PyInt_FromLong(CD_PROP_FLT));
		PyConstant_Insert(d, "INT" , PyInt_FromLong(CD_PROP_INT));
		PyConstant_Insert(d, "STRING", PyInt_FromLong(CD_PROP_STR));
	}
	return Types;
}

static struct PyMethodDef BPy_MVertSeq_methods[] = {
	{"extend", (PyCFunction)MVertSeq_extend, METH_VARARGS,
		"add vertices to mesh"},
	{"delete", (PyCFunction)MVertSeq_delete, METH_VARARGS,
		"delete vertices from mesh"},
	{"selected", (PyCFunction)MVertSeq_selected, METH_NOARGS,
		"returns a list containing indices of selected vertices"},
	{"addPropertyLayer",(PyCFunction)MVertSeq_add_layertype, METH_VARARGS,
		"add a new property layer"},
	{"removePropertyLayer",(PyCFunction)MVertSeq_del_layertype, METH_O,
		"removes a property layer"},
	{"renamePropertyLayer",(PyCFunction)MVertSeq_rename_layertype, METH_VARARGS,
		"renames an existing property layer"},
	{NULL, NULL, 0, NULL}
};

static PyGetSetDef BPy_MVertSeq_getseters[] = {
	{"properties",
	(getter)MVertSeq_PropertyList, (setter)NULL,
	"vertex property layers, read only",
	NULL},
	{NULL,NULL,NULL,NULL,NULL}  /* Sentinel */
};



/************************************************************************
 *
 * Python MVertSeq_Type standard operations
 *
 ************************************************************************/

/*****************************************************************************/
/* Python MVertSeq_Type structure definition:                               */
/*****************************************************************************/
PyTypeObject MVertSeq_Type = {
	PyObject_HEAD_INIT( NULL )  /* required py macro */
	0,                          /* ob_size */
	/*  For printing, in format "<module>.<name>" */
	"Blender MVertSeq",           /* char *tp_name; */
	sizeof( BPy_MVertSeq ),       /* int tp_basicsize; */
	0,                          /* tp_itemsize;  For allocation */

	/* Methods to implement standard operations */

	NULL,						/* destructor tp_dealloc; */
	NULL,                       /* printfunc tp_print; */
	NULL,                       /* getattrfunc tp_getattr; */
	NULL,                       /* setattrfunc tp_setattr; */
	NULL,                       /* cmpfunc tp_compare; */
	NULL,                       /* reprfunc tp_repr; */

	/* Method suites for standard classes */

	NULL,                       /* PyNumberMethods *tp_as_number; */
	&MVertSeq_as_sequence,	    /* PySequenceMethods *tp_as_sequence; */
	NULL,                       /* PyMappingMethods *tp_as_mapping; */

	/* More standard operations (here for binary compatibility) */

	NULL,                       /* hashfunc tp_hash; */
	NULL,                       /* ternaryfunc tp_call; */
	NULL,                       /* reprfunc tp_str; */
	NULL,                       /* getattrofunc tp_getattro; */
	NULL,                       /* setattrofunc tp_setattro; */

	/* Functions to access object as input/output buffer */
	NULL,                       /* PyBufferProcs *tp_as_buffer; */

  /*** Flags to define presence of optional/expanded features ***/
	Py_TPFLAGS_DEFAULT,         /* long tp_flags; */

	NULL,                       /*  char *tp_doc;  Documentation string */
  /*** Assigned meaning in release 2.0 ***/
	/* call function for all accessible objects */
	NULL,                       /* traverseproc tp_traverse; */

	/* delete references to contained objects */
	NULL,                       /* inquiry tp_clear; */

  /***  Assigned meaning in release 2.1 ***/
  /*** rich comparisons ***/
	NULL,                       /* richcmpfunc tp_richcompare; */

  /***  weak reference enabler ***/
	0,                          /* long tp_weaklistoffset; */

  /*** Added in release 2.2 ***/
	/*   Iterators */
	( getiterfunc) MVertSeq_getIter, /* getiterfunc tp_iter; */
	( iternextfunc ) MVertSeq_nextIter, /* iternextfunc tp_iternext; */

  /*** Attribute descriptor and subclassing stuff ***/
	BPy_MVertSeq_methods,       /* struct PyMethodDef *tp_methods; */
	NULL,                       /* struct PyMemberDef *tp_members; */
	BPy_MVertSeq_getseters,     /* struct PyGetSetDef *tp_getset; */
	NULL,                       /* struct _typeobject *tp_base; */
	NULL,                       /* PyObject *tp_dict; */
	NULL,                       /* descrgetfunc tp_descr_get; */
	NULL,                       /* descrsetfunc tp_descr_set; */
	0,                          /* long tp_dictoffset; */
	NULL,                       /* initproc tp_init; */
	NULL,                       /* allocfunc tp_alloc; */
	NULL,                       /* newfunc tp_new; */
	/*  Low-level free-memory routine */
	NULL,                       /* freefunc tp_free;  */
	/* For PyObject_IS_GC */
	NULL,                       /* inquiry tp_is_gc;  */
	NULL,                       /* PyObject *tp_bases; */
	/* method resolution order */
	NULL,                       /* PyObject *tp_mro;  */
	NULL,                       /* PyObject *tp_cache; */
	NULL,                       /* PyObject *tp_subclasses; */
	NULL,                       /* PyObject *tp_weaklist; */
	NULL
};

/************************************************************************
 *
 * Edge attributes
 *
 ************************************************************************/

static MEdge * MEdge_get_pointer( BPy_MEdge * self )
{
	if( self->index >= self->mesh->totedge )
		return (MEdge *)EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"MEdge is no longer valid" );
	return &self->mesh->medge[self->index];
}

/*
 * get an edge's crease value
 */

static PyObject *MEdge_getCrease( BPy_MEdge * self )
{
	MEdge *edge = MEdge_get_pointer( self );

	if( !edge )
		return NULL;

	return PyInt_FromLong( edge->crease );
}

/*
 * set an edge's crease value
 */

static int MEdge_setCrease( BPy_MEdge * self, PyObject * value )
{
	MEdge *edge = MEdge_get_pointer( self );

	if( !edge )
		return -1;

	return EXPP_setIValueClamped( value, &edge->crease, 0, 255, 'b' );
}

/*
 * get an edge's flag
 */

static PyObject *MEdge_getFlag( BPy_MEdge * self )
{
	MEdge *edge = MEdge_get_pointer( self );

	if( !edge )
		return NULL;

	return PyInt_FromLong( edge->flag );
}

/*
 * set an edge's flag
 */

static int MEdge_setFlag( BPy_MEdge * self, PyObject * value )
{
	short param;
	static short bitmask = SELECT
				| ME_EDGEDRAW
				| ME_SEAM
				| ME_FGON
				| ME_HIDE
				| ME_EDGERENDER
				| ME_LOOSEEDGE
				| ME_SEAM_LAST
				| ME_SHARP;
	MEdge *edge = MEdge_get_pointer( self );

	if( !edge )
		return -1;

	if( !PyInt_Check ( value ) ) {
		char errstr[128];
		sprintf ( errstr , "expected int bitmask of 0x%04x", bitmask );
		return EXPP_ReturnIntError( PyExc_TypeError, errstr );
	}
	param = (short)PyInt_AS_LONG ( value );

	if ( ( param & bitmask ) != param )
		return EXPP_ReturnIntError( PyExc_ValueError,
						"invalid bit(s) set in mask" );

	edge->flag = param;

	return 0;
}

/*
 * get an edge's first vertex
 */

static PyObject *MEdge_getV1( BPy_MEdge * self )
{
	MEdge *edge = MEdge_get_pointer( self );

	if( !edge )
		return NULL;

	return MVert_CreatePyObject( self->mesh, edge->v1 );
}

/*
 * set an edge's first vertex
 */

static int MEdge_setV1( BPy_MEdge * self, BPy_MVert * value )
{
	MEdge *edge = MEdge_get_pointer( self );

	if( !edge )
		return -1;
	if( !BPy_MVert_Check( value ) )
		return EXPP_ReturnIntError( PyExc_TypeError, "expected an MVert" );

	edge->v1 = value->index;
	return 0;
}

/*
 * get an edge's second vertex
 */

static PyObject *MEdge_getV2( BPy_MEdge * self )
{
	MEdge *edge = MEdge_get_pointer( self );

	if( !edge )
		return NULL; /* error is set */
	/* if v2 is out of range, the python mvert will complain, no need to check here  */
	return MVert_CreatePyObject( self->mesh, edge->v2 );
}

/*
 * set an edge's second vertex
 */

static int MEdge_setV2( BPy_MEdge * self, BPy_MVert * value )
{
	MEdge *edge = MEdge_get_pointer( self );

	if( !edge )
		return -1; /* error is set */
	if( !BPy_MVert_Check( value ) )
		return EXPP_ReturnIntError( PyExc_TypeError, "expected an MVert" );

	if ( edge->v1 == value->index )
		return EXPP_ReturnIntError( PyExc_ValueError, "an edge cant use the same vertex for each end" );
	
	edge->v2 = value->index;
	return 0;
}

/*
 * get an edge's index
 */

static PyObject *MEdge_getIndex( BPy_MEdge * self )
{
	if( !MEdge_get_pointer( self ) )
		return NULL; /* error is set */

	return PyInt_FromLong( self->index );
}

/*
 * get an edge's flag
 */

static PyObject *MEdge_getMFlagBits( BPy_MEdge * self, void * type )
{
	MEdge *edge = MEdge_get_pointer( self );

	if( !edge )
		return NULL; /* error is set */

	return EXPP_getBitfield( &edge->flag, (int)((long)type & 0xff), 'b' );
}

/*
 * get an edge's length
 */

static PyObject *MEdge_getLength( BPy_MEdge * self )
{
	MEdge *edge = MEdge_get_pointer( self );
	double dot = 0.0f;
	float tmpf;
	int i;
	float *v1, *v2;

	if (!edge)
		return NULL; /* error is set */	
	
	if MEDGE_VERT_BADRANGE_CHECK(self->mesh, edge)
		return EXPP_ReturnPyObjError( PyExc_RuntimeError, "This edge uses removed vert(s)" );
	
	/* get the 2 edges vert locations */
	v1= (&((Mesh *)self->mesh)->mvert[edge->v1])->co;
	v2= (&((Mesh *)self->mesh)->mvert[edge->v2])->co;

	if( !edge )
		return NULL;

	for( i = 0; i < 3; i++ ) {
		tmpf = v1[i] - v2[i];
		dot += tmpf*tmpf;
	}
	return PyFloat_FromDouble( sqrt( dot ) );
}

/*
 * get an key for using in a dictionary or set key
 */

static PyObject *MEdge_getKey( BPy_MEdge * self )
{
	PyObject *attr;
	MEdge *edge = MEdge_get_pointer( self );
	if (!edge)
		return NULL; /* error is set */	
	
	attr = PyTuple_New( 2 );
	if (edge->v1 > edge->v2) {
		PyTuple_SET_ITEM( attr, 0, PyInt_FromLong(edge->v2) );
		PyTuple_SET_ITEM( attr, 1, PyInt_FromLong(edge->v1) );
	} else {
		PyTuple_SET_ITEM( attr, 0, PyInt_FromLong(edge->v1) );
		PyTuple_SET_ITEM( attr, 1, PyInt_FromLong(edge->v2) );
	}
	return attr;
}

/*
 * set an edge's select state
 */

static int MEdge_setSel( BPy_MEdge * self,PyObject * value,
		void * type_unused )
{
	MEdge *edge = MEdge_get_pointer( self );
	int param = PyObject_IsTrue( value );
	Mesh *me = self->mesh;

	if( !edge )
		return -1;
	
	if MEDGE_VERT_BADRANGE_CHECK(me, edge)
		return EXPP_ReturnIntError( PyExc_RuntimeError, "This edge uses removed vert(s)" );
	
	if( param == -1 )
		return EXPP_ReturnIntError( PyExc_TypeError,
				"expected True/False or 0/1" );

	if( param ) {
		edge->flag |= SELECT;
		me->mvert[edge->v1].flag |= SELECT;
		me->mvert[edge->v2].flag |= SELECT;
	}
	else {
		edge->flag &= ~SELECT;
		me->mvert[edge->v1].flag &= ~SELECT;
		me->mvert[edge->v2].flag &= ~SELECT;
	}

	if( self->mesh->mselect ) {
		MEM_freeN( self->mesh->mselect );
		self->mesh->mselect = NULL;
	}

	return 0;
}

/************************************************************************
 *
 * Python MEdge_Type attributes get/set structure
 *
 ************************************************************************/

static PyGetSetDef BPy_MEdge_getseters[] = {
	{"crease",
	 (getter)MEdge_getCrease, (setter)MEdge_setCrease,
	 "edge's crease value",
	 NULL},
	{"flag",
	 (getter)MEdge_getFlag, (setter)MEdge_setFlag,
	 "edge's flags",
	 NULL},
	{"v1",
	 (getter)MEdge_getV1, (setter)MEdge_setV1,
	 "edge's first vertex",
	 NULL},
	{"v2",
	 (getter)MEdge_getV2, (setter)MEdge_setV2,
	 "edge's second vertex",
	 NULL},
	{"index",
	 (getter)MEdge_getIndex, (setter)NULL,
	 "edge's index",
	 NULL},
	{"sel",
	 (getter)MEdge_getMFlagBits, (setter)MEdge_setSel,
     "edge selected in edit mode",
     (void *)SELECT},
	{"length",
	 (getter)MEdge_getLength, (setter)NULL,
     "edge's length, read only",
     NULL},
	{"key",
	 (getter)MEdge_getKey, (setter)NULL,
     "edge's key for using with sets or dictionaries, read only",
     NULL},
	{NULL,NULL,NULL,NULL,NULL}  /* Sentinel */
};

/************************************************************************
 *
 * Python MEdge_Type iterator (iterates over vertices)
 *
 ************************************************************************/

/*
 * Initialize the interator index
 */

static PyObject *MEdge_getIter( BPy_MEdge * self )
{
	if (self->iter==-1) { /* not alredy used to iterator on, just use self */
		self->iter = 0;
		return EXPP_incr_ret ( (PyObject *) self );
	} else { /* alredy being iterated on, return a copy */
		BPy_MEdge *seq = (BPy_MEdge *)MEdge_CreatePyObject(self->mesh, self->index);
		seq->iter = 0;
		return (PyObject *)seq;
	}
}

/*
 * Return next MVert.  Throw an exception after the second vertex.
 */

static PyObject *MEdge_nextIter( BPy_MEdge * self )
{
	if( self->iter == 2 ) {
		self->iter = -1;
		return EXPP_ReturnPyObjError( PyExc_StopIteration,
				"iterator at end" );
	}
	
	self->iter++;
	if( self->iter == 1 )
		return MEdge_getV1( self );
	else
		return MEdge_getV2( self );
}

/************************************************************************
 *
 * Python MEdge_Type standard operations
 *
 ************************************************************************/

static int MEdge_compare( BPy_MEdge * a, BPy_MEdge * b )
{
	return( a->mesh == b->mesh && a->index == b->index ) ? 0 : -1;
}

static PyObject *MEdge_repr( BPy_MEdge * self )
{
	struct MEdge *edge = MEdge_get_pointer( self );

	if( !edge )
		return NULL;

	return PyString_FromFormat( "[MEdge (%d %d) %d %d]",
			(int)edge->v1, (int)edge->v2, (int)edge->crease,
			(int)self->index );
}

static long MEdge_hash( BPy_MEdge *self )
{
	return (long)self->index;
}
static PyObject *MEdge_getProp( BPy_MEdge *self, PyObject *args)
{
	Mesh *me = (Mesh *)self->mesh;
	return Mesh_getProperty_internal(&(me->edata), self->index, args);
}

static PyObject *MEdge_setProp( BPy_MEdge *self,  PyObject *args)
{
	Mesh *me = (Mesh *)self->mesh;
	return Mesh_setProperty_internal(&(me->edata), self->index, args);
}

static struct PyMethodDef BPy_MEdge_methods[] = {
	{"getProperty", (PyCFunction)MEdge_getProp, METH_O,
		"get property indicated by name"},
	{"setProperty", (PyCFunction)MEdge_setProp, METH_VARARGS,
		"set property indicated by name"},
	{NULL, NULL, 0, NULL}
};
/************************************************************************
 *
 * Python MEdge_Type structure definition
 *
 ************************************************************************/

PyTypeObject MEdge_Type = {
	PyObject_HEAD_INIT( NULL )  /* required py macro */
	0,                          /* ob_size */
	/*  For printing, in format "<module>.<name>" */
	"Blender MEdge",           /* char *tp_name; */
	sizeof( BPy_MEdge ),       /* int tp_basicsize; */
	0,                          /* tp_itemsize;  For allocation */

	/* Methods to implement standard operations */

	NULL,						/* destructor tp_dealloc; */
	NULL,                       /* printfunc tp_print; */
	NULL,                       /* getattrfunc tp_getattr; */
	NULL,                       /* setattrfunc tp_setattr; */
	( cmpfunc ) MEdge_compare,  /* cmpfunc tp_compare; */
	( reprfunc ) MEdge_repr,    /* reprfunc tp_repr; */

	/* Method suites for standard classes */

	NULL,                       /* PyNumberMethods *tp_as_number; */
    NULL,                       /* PySequenceMethods *tp_as_sequence; */
	NULL,                       /* PyMappingMethods *tp_as_mapping; */

	/* More standard operations (here for binary compatibility) */

	( hashfunc ) MEdge_hash,    /* hashfunc tp_hash; */
	NULL,                       /* ternaryfunc tp_call; */
	NULL,                       /* reprfunc tp_str; */
	NULL,                       /* getattrofunc tp_getattro; */
	NULL,                       /* setattrofunc tp_setattro; */

	/* Functions to access object as input/output buffer */
	NULL,                       /* PyBufferProcs *tp_as_buffer; */

  /*** Flags to define presence of optional/expanded features ***/
	Py_TPFLAGS_DEFAULT,         /* long tp_flags; */

	NULL,                       /*  char *tp_doc;  Documentation string */
  /*** Assigned meaning in release 2.0 ***/
	/* call function for all accessible objects */
	NULL,                       /* traverseproc tp_traverse; */

	/* delete references to contained objects */
	NULL,                       /* inquiry tp_clear; */

  /***  Assigned meaning in release 2.1 ***/
  /*** rich comparisons ***/
	NULL,                       /* richcmpfunc tp_richcompare; */

  /***  weak reference enabler ***/
	0,                          /* long tp_weaklistoffset; */

  /*** Added in release 2.2 ***/
	/*   Iterators */
	( getiterfunc) MEdge_getIter, /* getiterfunc tp_iter; */
	( iternextfunc ) MEdge_nextIter, /* iternextfunc tp_iternext; */

  /*** Attribute descriptor and subclassing stuff ***/
	BPy_MEdge_methods,          /* struct PyMethodDef *tp_methods; */
	NULL,                       /* struct PyMemberDef *tp_members; */
	BPy_MEdge_getseters,        /* struct PyGetSetDef *tp_getset; */
	NULL,                       /* struct _typeobject *tp_base; */
	NULL,                       /* PyObject *tp_dict; */
	NULL,                       /* descrgetfunc tp_descr_get; */
	NULL,                       /* descrsetfunc tp_descr_set; */
	0,                          /* long tp_dictoffset; */
	NULL,                       /* initproc tp_init; */
	NULL,                       /* allocfunc tp_alloc; */
	NULL,                       /* newfunc tp_new; */
	/*  Low-level free-memory routine */
	NULL,                       /* freefunc tp_free;  */
	/* For PyObject_IS_GC */
	NULL,                       /* inquiry tp_is_gc;  */
	NULL,                       /* PyObject *tp_bases; */
	/* method resolution order */
	NULL,                       /* PyObject *tp_mro;  */
	NULL,                       /* PyObject *tp_cache; */
	NULL,                       /* PyObject *tp_subclasses; */
	NULL,                       /* PyObject *tp_weaklist; */
	NULL
};

static PyObject *MEdge_CreatePyObject( Mesh * mesh, int i )
{
	BPy_MEdge *obj = PyObject_NEW( BPy_MEdge, &MEdge_Type );

	if( !obj )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"PyObject_New() failed" );

	obj->mesh = mesh;
	obj->index = i;
	obj->iter = -1;
	return (PyObject *)obj;
}

/************************************************************************
 *
 * Edge sequence 
 *
 ************************************************************************/

static int MEdgeSeq_len( BPy_MEdgeSeq * self )
{
	return self->mesh->totedge;
}

static PyObject *MEdgeSeq_item( BPy_MEdgeSeq * self, int i )
{
	if( i < 0 || i >= self->mesh->totedge )
		return EXPP_ReturnPyObjError( PyExc_IndexError,
					      "array index out of range" );

	return MEdge_CreatePyObject( self->mesh, i );
}


static PySequenceMethods MEdgeSeq_as_sequence = {
	( inquiry ) MEdgeSeq_len,	/* sq_length */
	( binaryfunc ) 0,	/* sq_concat */
	( intargfunc ) 0,	/* sq_repeat */
	( intargfunc ) MEdgeSeq_item,	/* sq_item */
	( intintargfunc ) 0,	/* sq_slice */
	( intobjargproc ) 0,	/* sq_ass_item */
	( intintobjargproc ) 0,	/* sq_ass_slice */
	0,0,0,
};

/************************************************************************
 *
 * Python MEdgeSeq_Type iterator (iterates over edges)
 *
 ************************************************************************/

/*
 * Initialize the interator index
 */

static PyObject *MEdgeSeq_getIter( BPy_MEdgeSeq * self )
{
	if (self->iter==-1) { /* iteration for this pyobject is not yet used, just return self */
		self->iter = 0;
		return EXPP_incr_ret ( (PyObject *) self );
	} else {
		BPy_MEdgeSeq *seq = (BPy_MEdgeSeq *)MEdgeSeq_CreatePyObject(self->mesh);
		seq->iter = 0;
		return (PyObject *)seq;
	}
}

/*
 * Return next MEdge.
 */

static PyObject *MEdgeSeq_nextIter( BPy_MEdgeSeq * self )
{
	if( self->iter == self->mesh->totedge ) {
		self->iter= -1;
		return EXPP_ReturnPyObjError( PyExc_StopIteration,
				"iterator at end" );
	}

	return MEdge_CreatePyObject( self->mesh, self->iter++ );
}

/************************************************************************
 *
 * Python MEdgeSeq_Type methods
 *
 ************************************************************************/

/*
 * Create edges from tuples of vertices.  Duplicate new edges, or
 * edges which already exist,
 */

static PyObject *MEdgeSeq_extend( BPy_MEdgeSeq * self, PyObject *args )
{
	int len, nverts;
	int i, j, ok;
	int new_edge_count, good_edges;
	SrchEdges *oldpair, *newpair, *tmppair, *tmppair2;
	PyObject *tmp;
	BPy_MVert *e[4];
	MEdge *tmpedge;
	Mesh *mesh = self->mesh;

	/* make sure we get a tuple of sequences of something */
	switch( PySequence_Size( args ) ) {
	case 1:
		/* if a sequence... */
		tmp = PyTuple_GET_ITEM( args, 0 );
		if( PySequence_Check( tmp ) ) {
			PyObject *tmp2;

			/* ignore empty sequences */
			if( !PySequence_Size( tmp ) ) {
				Py_RETURN_NONE;
			}

			/* if another sequence, use it */
			tmp2 = PySequence_ITEM( tmp, 0 );
			if( PySequence_Check( tmp2 ) )
				args = tmp;
			Py_INCREF( args );
			Py_DECREF( tmp2 );
		} else
			return EXPP_ReturnPyObjError( PyExc_TypeError,
					"expected a sequence of sequence pairs" );
		break;
	case 2:	
	case 3:
	case 4:		/* two to four args may be individual verts */
		tmp = PyTuple_GET_ITEM( args, 0 );
		/*
		 * if first item isn't a sequence, then assume it's a bunch of MVerts
		 * and wrap inside a tuple
		 */
		if( !PySequence_Check( tmp ) ) {
			args = Py_BuildValue( "(O)", args );
			if( !args )
				return EXPP_ReturnPyObjError( PyExc_RuntimeError,
						"Py_BuildValue() failed" );
		/*
		 * otherwise, assume it already a bunch of sequences so use as-is
		 */
		} else { 
			Py_INCREF( args );		/* so we can safely DECREF later */
		}
		break;
	default:	/* anything else is definitely wrong */
		return EXPP_ReturnPyObjError( PyExc_TypeError,
				"expected a sequence of sequence pairs" );
	}

	/* make sure there is something to add */
	len = PySequence_Size( args );
	if( len == 0 ) {
		Py_DECREF ( args );
		Py_RETURN_NONE;
	}

	/* 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_GetItem( args, i );

		/* not a tuple of MVerts... error */
		if( !PySequence_Check( tmp ) ) {
			Py_DECREF( tmp );
			Py_DECREF( args );
			return EXPP_ReturnPyObjError( PyExc_ValueError,
				"expected sequence of MVert sequences" );
		}

		/* not the right number of MVerts... error */
		nverts = PySequence_Size( tmp );
		if( nverts < 2 || nverts > 4 ) {
			Py_DECREF( tmp );
			Py_DECREF( args );
			return EXPP_ReturnPyObjError( PyExc_ValueError,
				"expected 2 to 4 MVerts per sequence" );
		}

		if( EXPP_check_sequence_consistency( tmp, &MVert_Type ) == 1 ) {

			/* get MVerts, check they're from this mesh */
			ok = 1;
			for( j = 0; ok && j < nverts; ++j ) {
				e[0] = (BPy_MVert *)PySequence_GetItem( tmp, j );
				if( (void *)e[0]->data != (void *)self->mesh )
					ok = 0;
				Py_DECREF( e[0] );
			}
			Py_DECREF( tmp );

			/* not MVerts from another mesh ... error */
			if( !ok ) {
				Py_DECREF( args );
				return EXPP_ReturnPyObjError( PyExc_ValueError,
					"vertices are from a different mesh" );
			}
		} else {
			ok = 0; 
			for( j = 0; ok == 0 && j < nverts; ++j ) {
				PyObject *item = PySequence_ITEM( tmp, j );
				if( !PyInt_Check( item ) )
					ok = 1;
				else {
					int index = PyInt_AsLong ( item );
					if( index < 0 || index >= self->mesh->totvert )
						ok = 2;
				}
				Py_DECREF( item );
			}
			Py_DECREF( tmp );

			/* not ints or outside of vertex list ... error */
			if( ok ) {
				Py_DECREF( args );
				if( ok == 1 )
					return EXPP_ReturnPyObjError( PyExc_TypeError,
						"expected an integer index" );
				else
					return EXPP_ReturnPyObjError( PyExc_KeyError,
						"index out of range" );
			}
		}

		if( nverts == 2 )
			++new_edge_count;	/* if only two vert, then add only edge */
		else
			new_edge_count += nverts;	/* otherwise, one edge per vert */
	}

	/* OK, commit to allocating the search structures */
	newpair = (SrchEdges *)MEM_callocN( sizeof(SrchEdges)*new_edge_count,
			"MEdgePairs" );

	/* scan the input list and build the new edge pair list */
	len = PySequence_Size( args );
	tmppair = newpair;
	new_edge_count = 0;
	for( i = 0; i < len; ++i ) {
		int edge_count;
		int eedges[4];
		tmp = PySequence_GetItem( args, i );
		nverts = PySequence_Size( tmp );

		/* get new references for the vertices */
		for(j = 0; j < nverts; ++j ) {
			PyObject *item = PySequence_ITEM( tmp, j );
			if( BPy_MVert_Check( item ) ) {
				eedges[j] = ((BPy_MVert *)item)->index;
			} else {
				eedges[j] = PyInt_AsLong ( item );
			}
			Py_DECREF( item );
		}
		Py_DECREF( tmp );

		if( nverts == 2 )
			edge_count = 1;	 /* again, two verts give just one edge */
		else
			edge_count = nverts;	

		/* now add the edges to the search list */
		for( j = 0; j < edge_count; ++j ) {
			int k = j+1;
			if( k == nverts )	/* final edge */ 
				k = 0;

			/* sort verts into search list, skip if two are the same */
			if( eedges[j] != eedges[k] ) {
				if( eedges[j] < eedges[k] ) {
					tmppair->v[0] = eedges[j];
					tmppair->v[1] = eedges[k];
					tmppair->swap = 0;
				} else {
					tmppair->v[0] = eedges[k];
					tmppair->v[1] = eedges[j];
					tmppair->swap = 1;
				} 
				tmppair->index = new_edge_count;
				++new_edge_count;
				tmppair++;
			}
		}

	}

	/* sort the new edge pairs */
	qsort( newpair, new_edge_count, sizeof(SrchEdges), medge_comp );

	/*
	 * find duplicates in the new list and mark.  if it's a duplicate,
	 * then mark by setting second vert index to 0 (a real edge won't have
	 * second vert index of 0 since verts are sorted)
	 */

	good_edges = new_edge_count;	/* all edges are good at this point */

	tmppair = newpair;		/* "last good edge" */
	tmppair2 = &tmppair[1];	/* "current candidate edge" */
	for( i = 0; i < new_edge_count; ++i ) {
		if( tmppair->v[0] != tmppair2->v[0] ||
				tmppair->v[1] != tmppair2->v[1] )
			tmppair = tmppair2;	/* last != current, so current == last */
		else {
			tmppair2->v[1] = 0; /* last == current, so mark as duplicate */
			--good_edges;		/* one less good edge */
		}
		tmppair2++;
	}

	/* if mesh has edges, see if any of the new edges are already in it */
	if( mesh->totedge ) {
		oldpair = (SrchEdges *)MEM_callocN( sizeof(SrchEdges)*mesh->totedge,
				"MEdgePairs" );

		/*
		 * build a search list of new edges (don't need to update "swap"
		 * field, since we're not creating edges here)
		 */
		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;
			}
			++tmpedge;
			++tmppair;
		}

	/* sort the old edge pairs */
		qsort( oldpair, mesh->totedge, sizeof(SrchEdges), medge_comp );

	/* eliminate new edges already in the mesh */
		tmppair = newpair;
		for( i = new_edge_count; i-- ; ) {
			if( tmppair->v[1] ) {
				if( bsearch( tmppair, oldpair, mesh->totedge,
							sizeof(SrchEdges), medge_comp ) ) {
					tmppair->v[1] = 0;	/* mark as duplicate */
					--good_edges;
				} 
			}
			tmppair++;
		}
		MEM_freeN( oldpair );
	}

	/* if any new edges are left, add to list */
	if( good_edges ) {
		CustomData edata;
		int totedge = mesh->totedge+good_edges;

	/* create custom edge data arrays and copy existing edges into it */
		CustomData_copy( &mesh->edata, &edata, CD_MASK_MESH, CD_DEFAULT, totedge );
		CustomData_copy_data( &mesh->edata, &edata, 0, 0, mesh->totedge );

		if ( !CustomData_has_layer( &edata, CD_MEDGE ) )
			CustomData_add_layer( &edata, CD_MEDGE, CD_CALLOC, NULL, totedge );

	/* replace old with new data */
		CustomData_free( &mesh->edata, mesh->totedge );
		mesh->edata = edata;
		mesh_update_customdata_pointers( mesh );

	/* resort edges into original order */
		qsort( newpair, new_edge_count, sizeof(SrchEdges), medge_index_comp );

	/* point to the first edge we're going to add */
		tmpedge = &mesh->medge[mesh->totedge];
		tmppair = newpair;

	/* as we find a good edge, add it */
		while( good_edges ) {
			if( tmppair->v[1] ) {	/* not marked as duplicate ! */
				if( !tmppair->swap ) {
					tmpedge->v1 = tmppair->v[0];
					tmpedge->v2 = tmppair->v[1];
				} else {
					tmpedge->v1 = tmppair->v[1];
					tmpedge->v2 = tmppair->v[0];
				}
				tmpedge->flag = ME_EDGEDRAW | ME_EDGERENDER | SELECT;
				mesh->totedge++;
				--good_edges;
				++tmpedge;
			}
			tmppair++;
		}
	}

	/* clean up and leave */
	mesh_update( mesh );
	MEM_freeN( newpair );
	Py_DECREF ( args );
	Py_RETURN_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;

	/*
	 * if input tuple contains a single sequence, use it as input instead;
	 * otherwise use the sequence as-is and check later that it contains
	 * one or more integers or MVerts
	 */
	if( PySequence_Size( args ) == 1 ) {
		PyObject *tmp = PyTuple_GET_ITEM( args, 0 );
		if( PySequence_Check( tmp ) ) 
			args = tmp;
	}

	/* if sequence is empty, do nothing */
	len = PySequence_Size( args );
	if( len == 0 ) {
		Py_RETURN_NONE;
	}

	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_Check( tmp ) )
			edge_table[i] = PyInt_AsLong ( tmp );
		else {
			MEM_freeN( edge_table );
			Py_DECREF( tmp );
			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 );
			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 );
	mesh_update ( mesh );
	Py_RETURN_NONE;
}

static PyObject *MEdgeSeq_collapse( BPy_MEdgeSeq * self, PyObject *args )
{
	MEdge *srcedge;
	unsigned int *edge_table;
	float (*vert_list)[3];
	int i, len;
	Base *base, *basact;
	Mesh *mesh = self->mesh;
	Object *object = NULL; 
	PyObject *tmp;

	/*
	 * when using removedoublesflag(), we need to switch to editmode, so
	 * nobody else can be using it
	 */

	if( G.obedit )
		return EXPP_ReturnPyObjError(PyExc_RuntimeError,
				"can't use collapse() while in edit mode" );

	/* make sure we get a tuple of sequences of something */
	switch( PySequence_Size( args ) ) {
	case 1:
		/* if a sequence... */
		tmp = PyTuple_GET_ITEM( args, 0 );
		if( PySequence_Check( tmp ) ) {
			PyObject *tmp2;

			/* ignore empty sequences */
			if( !PySequence_Size( tmp ) ) {
				Py_RETURN_NONE;
			}

			/* if another sequence, use it */
			tmp2 = PySequence_ITEM( tmp, 0 );
			if( PySequence_Check( tmp2 ) )
				args = tmp;
			Py_INCREF( args );
			Py_DECREF( tmp2 );
		} else
			return EXPP_ReturnPyObjError( PyExc_TypeError,
					"expected a sequence of sequence pairs" );
		break;
	case 2:	/* two args may be individual edges/verts */
		tmp = PyTuple_GET_ITEM( args, 0 );
		/*
		 * if first item isn't a sequence, then assume it's a bunch of MVerts
		 * and wrap inside a tuple
		 */
		if( !PySequence_Check( tmp ) ) {
			args = Py_BuildValue( "(O)", args );
			if( !args )
				return EXPP_ReturnPyObjError( PyExc_RuntimeError,
						"Py_BuildValue() failed" );
		/*
		 * otherwise, assume it already a bunch of sequences so use as-is
		 */
		} else { 
			Py_INCREF( args );		/* so we can safely DECREF later */
		}
		break;
	default:	/* anything else is definitely wrong */
		return EXPP_ReturnPyObjError( PyExc_TypeError,
				"expected a sequence of sequence pairs" );
	}

	/* if sequence is empty, do nothing */
	len = PySequence_Size( args );
	if( len == 0 ) {
		Py_RETURN_NONE;
	}

	/* allocate table of edge indices and new vertex values */

	edge_table = (unsigned int *)MEM_callocN( len*sizeof( unsigned int ),
			"edge_table" );
	vert_list = (float (*)[3])MEM_callocN( 3*len*sizeof( float ),
			"vert_list" );

	/* get the indices of edges to be collapsed and new vert locations */
	for( i = len; i--; ) {
		PyObject *tmp1;
		PyObject *tmp2;

		tmp = PySequence_GetItem( args, i );

		/* if item isn't sequence of size 2, error */
		if( !PySequence_Check( tmp ) || PySequence_Size( tmp ) != 2 ) {
			MEM_freeN( edge_table );
			MEM_freeN( vert_list );
			Py_DECREF( tmp );
			Py_DECREF( args );
			return EXPP_ReturnPyObjError( PyExc_TypeError,
					"expected a sequence of (MEdges, vector)" );
		}

		/* if items aren't a MEdge/int and vector, error */
		tmp1 = PySequence_GetItem( tmp, 0 );
		tmp2 = PySequence_GetItem( tmp, 1 );
		Py_DECREF( tmp );
		if( !(BPy_MEdge_Check( tmp1 ) || PyInt_Check( tmp1 )) ||
				!VectorObject_Check ( tmp2 ) ) {
			MEM_freeN( edge_table );
			MEM_freeN( vert_list );
			Py_DECREF( tmp1 );
			Py_DECREF( tmp2 );
			Py_DECREF( args );
			return EXPP_ReturnPyObjError( PyExc_TypeError,
					"expected a sequence of (MEdges, vector)" );
		}

		/* store edge index, new vertex location */
		if( PyInt_Check( tmp1 ) )
			edge_table[i] = PyInt_AsLong ( tmp1 );
		else
			edge_table[i] = ((BPy_MEdge *)tmp1)->index;
		memcpy( vert_list[i], ((VectorObject *)tmp2)->vec,
				3*sizeof( float ) );
		Py_DECREF( tmp1 );
		Py_DECREF( tmp2 );

		/* if index out-of-range, throw exception */
		if( edge_table[i] >= (unsigned int)mesh->totedge ) {
			MEM_freeN( edge_table );
			MEM_freeN( vert_list );
			return EXPP_ReturnPyObjError( PyExc_ValueError,
					"edge index out of range" );
		}
	}

	/*
	 * simple algorithm:
	 * (1) deselect all verts
	 * (2) for each edge
	 *   (2a) replace both verts with the new vert
	 *   (2b) select both verts
	 * (3) call removedoublesflag()
	 */

	/* (1) deselect all verts */
	for( i = mesh->totvert; i--; )
		mesh->mvert[i].flag &= ~SELECT;

	/* (2) replace edge's verts and select them */
	for( i = len; i--; ) {
		srcedge = &mesh->medge[edge_table[i]];
		memcpy( &mesh->mvert[srcedge->v1].co, vert_list[i], 3*sizeof( float ) );
		memcpy( &mesh->mvert[srcedge->v2].co, vert_list[i], 3*sizeof( float ) );
		mesh->mvert[srcedge->v1].flag |= SELECT;
		mesh->mvert[srcedge->v2].flag |= SELECT;
	}

	/* (3) call removedoublesflag() */
	for( base = FIRSTBASE; base; base = base->next ) {
		if( base->object->type == OB_MESH && 
				base->object->data == self->mesh ) {
			object = base->object;
			break;
		}
	}

	basact = BASACT;
	BASACT = base;
	
	removedoublesflag( 1, 0, 0.0 );
	/* make mesh's object active, enter mesh edit mode */
	G.obedit = object;
	
	/* exit edit mode, free edit mesh */
	load_editMesh();
	free_editMesh(G.editMesh);
	
	BASACT = basact;

	/* clean up and exit */
	Py_DECREF( args );
	MEM_freeN( vert_list );
	MEM_freeN( edge_table );
	mesh_update ( mesh );
	Py_RETURN_NONE;
}


static PyObject *MEdgeSeq_selected( BPy_MEdgeSeq * self )
{
	int i, count;
	Mesh *mesh = self->mesh;
	MEdge *tmpedge;
	PyObject *list;

	/* first count selected edges (quicker than appending to PyList?) */
	count = 0;
	tmpedge = mesh->medge;
	for( i = 0; i < mesh->totedge; ++i, ++tmpedge )
		if( (mesh->mvert[tmpedge->v1].flag & SELECT) &&
				(mesh->mvert[tmpedge->v2].flag & SELECT) )
			++count;

	list = PyList_New( count );
	if( !list )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"PyList_New() failed" );

	/* next, insert selected edges into list */
	count = 0;
	tmpedge = mesh->medge;
	for( i = 0; i < mesh->totedge; ++i, ++tmpedge ) {
		if( (mesh->mvert[tmpedge->v1].flag & SELECT) &&
				(mesh->mvert[tmpedge->v2].flag & SELECT) ) {
			PyObject *tmp = PyInt_FromLong( i );
			if( !tmp ) {
				Py_DECREF( list );
				return EXPP_ReturnPyObjError( PyExc_RuntimeError,
						"PyInt_FromLong() failed" );
			}
			PyList_SET_ITEM( list, count, tmp );
			++count;
		}
	}
	return list;
}

static PyObject *MEdgeSeq_add_layertype(BPy_MEdgeSeq *self, PyObject *args)
{
	Mesh *me = (Mesh*)self->mesh;
	return Mesh_addPropLayer_internal(me, &(me->edata), me->totedge, args);
}
static PyObject *MEdgeSeq_del_layertype(BPy_MEdgeSeq *self, PyObject *value)
{
	Mesh *me = (Mesh*)self->mesh;
	return Mesh_removePropLayer_internal(me, &(me->edata), me->totedge, value);
}
static PyObject *MEdgeSeq_rename_layertype(BPy_MEdgeSeq *self, PyObject *args)
{
	Mesh *me = (Mesh*)self->mesh;
	return Mesh_renamePropLayer_internal(me,&(me->edata),args);
}
static PyObject *MEdgeSeq_PropertyList(BPy_MEdgeSeq *self) 
{
	Mesh *me = (Mesh*)self->mesh;
	return Mesh_propList_internal(&(me->edata));
}


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"},
	{"selected", (PyCFunction)MEdgeSeq_selected, METH_NOARGS,
		"returns a list containing indices of selected edges"},
	{"collapse", (PyCFunction)MEdgeSeq_collapse, METH_VARARGS,
		"collapse one or more edges to a vertex"},
	{"addPropertyLayer",(PyCFunction)MEdgeSeq_add_layertype, METH_VARARGS,
		"add a new property layer"},
	{"removePropertyLayer",(PyCFunction)MEdgeSeq_del_layertype, METH_O,
		"removes a property layer"},
	{"renamePropertyLayer",(PyCFunction)MEdgeSeq_rename_layertype, METH_VARARGS,
		"renames an existing property layer"},

	{NULL, NULL, 0, NULL}
};
static PyGetSetDef BPy_MEdgeSeq_getseters[] = {
	{"properties",
	(getter)MEdgeSeq_PropertyList, (setter)NULL,
	"edge property layers, read only",
	NULL},
	{NULL,NULL,NULL,NULL,NULL}  /* Sentinel */
};


/************************************************************************
 *
 * Python MEdgeSeq_Type standard operators
 *
 ************************************************************************/

/*****************************************************************************/
/* Python MEdgeSeq_Type structure definition:                               */
/*****************************************************************************/
PyTypeObject MEdgeSeq_Type = {
	PyObject_HEAD_INIT( NULL )  /* required py macro */
	0,                          /* ob_size */
	/*  For printing, in format "<module>.<name>" */
	"Blender MEdgeSeq",           /* char *tp_name; */
	sizeof( BPy_MEdgeSeq ),       /* int tp_basicsize; */
	0,                          /* tp_itemsize;  For allocation */

	/* Methods to implement standard operations */

	NULL,						/* destructor tp_dealloc; */
	NULL,                       /* printfunc tp_print; */
	NULL,                       /* getattrfunc tp_getattr; */
	NULL,                       /* setattrfunc tp_setattr; */
	NULL,                       /* cmpfunc tp_compare; */
	NULL,                       /* reprfunc tp_repr; */

	/* Method suites for standard classes */

	NULL,                       /* PyNumberMethods *tp_as_number; */
	&MEdgeSeq_as_sequence,	    /* PySequenceMethods *tp_as_sequence; */
	NULL,                       /* PyMappingMethods *tp_as_mapping; */

	/* More standard operations (here for binary compatibility) */

	NULL,                       /* hashfunc tp_hash; */
	NULL,                       /* ternaryfunc tp_call; */
	NULL,                       /* reprfunc tp_str; */
	NULL,                       /* getattrofunc tp_getattro; */
	NULL,                       /* setattrofunc tp_setattro; */

	/* Functions to access object as input/output buffer */
	NULL,                       /* PyBufferProcs *tp_as_buffer; */

  /*** Flags to define presence of optional/expanded features ***/
	Py_TPFLAGS_DEFAULT,         /* long tp_flags; */

	NULL,                       /*  char *tp_doc;  Documentation string */
  /*** Assigned meaning in release 2.0 ***/
	/* call function for all accessible objects */
	NULL,                       /* traverseproc tp_traverse; */

	/* delete references to contained objects */
	NULL,                       /* inquiry tp_clear; */

  /***  Assigned meaning in release 2.1 ***/
  /*** rich comparisons ***/
	NULL,                       /* richcmpfunc tp_richcompare; */

  /***  weak reference enabler ***/
	0,                          /* long tp_weaklistoffset; */

  /*** Added in release 2.2 ***/
	/*   Iterators */
	( getiterfunc) MEdgeSeq_getIter, /* getiterfunc tp_iter; */
	( iternextfunc ) MEdgeSeq_nextIter, /* iternextfunc tp_iternext; */

  /*** Attribute descriptor and subclassing stuff ***/
	BPy_MEdgeSeq_methods,       /* struct PyMethodDef *tp_methods; */
	NULL,                       /* struct PyMemberDef *tp_members; */
	BPy_MEdgeSeq_getseters,     /* struct PyGetSetDef *tp_getset; */
	NULL,                       /* struct _typeobject *tp_base; */
	NULL,                       /* PyObject *tp_dict; */
	NULL,                       /* descrgetfunc tp_descr_get; */
	NULL,                       /* descrsetfunc tp_descr_set; */
	0,                          /* long tp_dictoffset; */
	NULL,                       /* initproc tp_init; */
	NULL,                       /* allocfunc tp_alloc; */
	NULL,                       /* newfunc tp_new; */
	/*  Low-level free-memory routine */
	NULL,                       /* freefunc tp_free;  */
	/* For PyObject_IS_GC */
	NULL,                       /* inquiry tp_is_gc;  */
	NULL,                       /* PyObject *tp_bases; */
	/* method resolution order */
	NULL,                       /* PyObject *tp_mro;  */
	NULL,                       /* PyObject *tp_cache; */
	NULL,                       /* PyObject *tp_subclasses; */
	NULL,                       /* PyObject *tp_weaklist; */
	NULL
};

/************************************************************************
 *
 * Face attributes
 *
 ************************************************************************/

static MFace * MFace_get_pointer( BPy_MFace * self )
{
	if( self->index >= self->mesh->totface )
		return (MFace *)EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"MFace is no longer valid" );
	return &self->mesh->mface[self->index];
}

/*
 * get a face's vertices
 */

static PyObject *MFace_getVerts( BPy_MFace * self )
{
	PyObject *attr;
	MFace *face = MFace_get_pointer( self );

	if( !face )
		return NULL;

	attr = PyTuple_New( face->v4 ? 4 : 3 );

	if( !attr )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"PyTuple_New() failed" );

	PyTuple_SetItem( attr, 0, MVert_CreatePyObject( self->mesh, face->v1 ) );
	PyTuple_SetItem( attr, 1, MVert_CreatePyObject( self->mesh, face->v2 ) );
	PyTuple_SetItem( attr, 2, MVert_CreatePyObject( self->mesh, face->v3 ) );
	if( face->v4 )
		PyTuple_SetItem( attr, 3, MVert_CreatePyObject( self->mesh,
					face->v4 ) );

	return attr;
}

/*
 * set a face's vertices
 */

static int MFace_setVerts( BPy_MFace * self, PyObject * args )
{
	BPy_MVert *v1, *v2, *v3, *v4 = NULL;
	MFace *face = MFace_get_pointer( self );

	if( !face )
		return -1;

	if( !PyArg_ParseTuple ( args, "O!O!O!|O!", &MVert_Type, &v1,
				&MVert_Type, &v2, &MVert_Type, &v3, &MVert_Type, &v4 ) )
		return EXPP_ReturnIntError( PyExc_TypeError,
			"expected tuple of 3 or 4 MVerts" );
	
	if(	v1->index == v2->index || 
		v1->index == v3->index || 
		v2->index == v3->index  ) 
		return EXPP_ReturnIntError( PyExc_ValueError,
			"cannot assign 2 or move verts that are the same" );
	
	if(v4 && (	v1->index == v4->index ||
				v2->index == v4->index ||
				v3->index == v4->index ))
		return EXPP_ReturnIntError( PyExc_ValueError,
			"cannot assign 2 or move verts that are the same" );
	
	if(		v1->index >= self->mesh->totvert || 
			v2->index >= self->mesh->totvert || 
			v3->index >= self->mesh->totvert ||
	(v4 &&(	v4->index >= self->mesh->totvert)))
		return EXPP_ReturnIntError( PyExc_ValueError,
			"cannot assign verts that have been removed" );
	
	face->v1 = v1->index;
	face->v2 = v2->index;
	face->v3 = v3->index;
	if( v4 )
		face->v4 = v4->index;
	return 0;
}

/*
 * get face's material index
 */

static PyObject *MFace_getMat( BPy_MFace * self )
{
	MFace *face = MFace_get_pointer( self );

	if( !face )
		return NULL;

	return PyInt_FromLong( face->mat_nr );
}

/*
 * set face's material index
 */

static int MFace_setMat( BPy_MFace * self, PyObject * value )
{
	MFace *face = MFace_get_pointer( self );

	if( !face )
		return -1; /* error is set */

	return EXPP_setIValueRange( value, &face->mat_nr, 0, 15, 'b' );
}

/*
 * get a face's index
 */

static PyObject *MFace_getIndex( BPy_MFace * self )
{
	MFace *face = MFace_get_pointer( self );

	if( !face )
		return NULL; /* error is set */

	return PyInt_FromLong( self->index );
}

/*
 * get face's normal index
 */

static PyObject *MFace_getNormal( BPy_MFace * self )
{
	float *vert[4];
	float no[3];
	MFace *face = MFace_get_pointer( self );

	Mesh *me = self->mesh;
	
	if( !face )
	return NULL; /* error is set */
	
	if MFACE_VERT_BADRANGE_CHECK(me, face)
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"one or more MFace vertices are no longer valid" );

	vert[0] = me->mvert[face->v1].co;
	vert[1] = me->mvert[face->v2].co;
	vert[2] = me->mvert[face->v3].co;
	if( face->v4 ) {
		vert[3] = me->mvert[face->v4].co;
		CalcNormFloat4( vert[0], vert[1], vert[2], vert[3], no );
	} else
		CalcNormFloat( vert[0], vert[1], vert[2], no );

	return newVectorObject( no, 3, Py_NEW );
}

/*
 * get face's center location
 */

static PyObject *MFace_getCent( BPy_MFace * self )
{
	float *vert[4];
	float cent[3]= {0,0,0};
	int i=3, j, k;
	Mesh *me = self->mesh;
	MFace *face = MFace_get_pointer( self );
	
	if( !face )
		return NULL; /* error is set */
	

	if MFACE_VERT_BADRANGE_CHECK(me, face)
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"one or more MFace vertices are no longer valid" );

	vert[0] = me->mvert[face->v1].co;
	vert[1] = me->mvert[face->v2].co;
	vert[2] = me->mvert[face->v3].co;
	if( face->v4 ) {
		vert[3] = me->mvert[face->v4].co;
		i=4;
	} 
	
	for (j=0;j<i;j++) {
		for (k=0;k<3;k++) {
			cent[k]+=vert[j][k];
		}
	}
	
	for (j=0;j<3;j++) {
		cent[j]=cent[j]/i;
	}
	return newVectorObject( cent, 3, Py_NEW );
}

/*
 * get face's area
 */
static PyObject *MFace_getArea( BPy_MFace * self )
{
	float *v1,*v2,*v3,*v4;
	MFace *face = MFace_get_pointer( self );
	Mesh *me = self->mesh;
	
	if( !face )
		return NULL; /* error is set */

	if MFACE_VERT_BADRANGE_CHECK(me, face)
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"one or more MFace vertices are no longer valid" );

	v1 = me->mvert[face->v1].co;
	v2 = me->mvert[face->v2].co;
	v3 = me->mvert[face->v3].co;
	
	if( face->v4 ) {
		v4 = me->mvert[face->v4].co;
		return PyFloat_FromDouble( AreaQ3Dfl(v1, v2, v3, v4));
	} else
		return PyFloat_FromDouble( AreaT3Dfl(v1, v2, v3));
}

/*
 * get one of a face's mface flag bits
 */

static PyObject *MFace_getMFlagBits( BPy_MFace * self, void * type )
{
	MFace *face = MFace_get_pointer( self );

	if( !face )
		return NULL; /* error is set */

	return EXPP_getBitfield( &face->flag, (int)((long)type & 0xff), 'b' );
}

/*
 * set one of a face's mface flag bits
 */

static int MFace_setMFlagBits( BPy_MFace * self, PyObject * value,
		void * type )
{
	MFace *face = MFace_get_pointer( self );

	if( !face )
		return -1; /* error is set */

	return EXPP_setBitfield( value, &face->flag, 
			(int)((long)type & 0xff), 'b' );
}

static int MFace_setSelect( BPy_MFace * self, PyObject * value,
		void * type_unused )
{
	MFace *face = MFace_get_pointer( self );
	int param = PyObject_IsTrue( value );
	Mesh *me;

	if( !face )
		return -1; /* error is set */

	if( param == -1 )
		return EXPP_ReturnIntError( PyExc_TypeError,
				"expected True/False or 0/1" );

	me = self->mesh;
	if( param ) {
		face->flag |= ME_FACE_SEL;
		me->mvert[face->v1].flag |= SELECT;
		me->mvert[face->v2].flag |= SELECT;
		me->mvert[face->v3].flag |= SELECT;
		if( face->v4 )
			me->mvert[face->v4].flag |= SELECT;
	}
	else {
		face->flag &= ~ME_FACE_SEL;
		me->mvert[face->v1].flag &= ~SELECT;
		me->mvert[face->v2].flag &= ~SELECT;
		me->mvert[face->v3].flag &= ~SELECT;
		if( face->v4 )
			me->mvert[face->v4].flag &= ~SELECT;
	}

	if( self->mesh->mselect ) {
		MEM_freeN( self->mesh->mselect );
		self->mesh->mselect = NULL;
	}

	return 0;
}

/*
 * get face's texture image
 */

static PyObject *MFace_getImage( BPy_MFace *self )
{
	MTFace *face;
	if( !self->mesh->mtface )
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"face has no texture values" );

	if( !MFace_get_pointer( self ) )
		return NULL;

	face = &self->mesh->mtface[self->index];

	if( face->tpage )
		return Image_CreatePyObject( face->tpage );
	else
		Py_RETURN_NONE;
}

/*
 * change or clear face's texture image
 */

static int MFace_setImage( BPy_MFace *self, PyObject *value )
{
	MTFace *face;

	if( !MFace_get_pointer( self ) )
		return -1;

	if( value && value != Py_None && !BPy_Image_Check( value ) )
	    return EXPP_ReturnIntError( PyExc_TypeError,
		    "expected image object or None" );

	if( !self->mesh->mtface )
#if 0
		return EXPP_ReturnIntError( PyExc_ValueError,
				"face has no texture values" );
#else
		make_tfaces( self->mesh );
#endif

	face = &self->mesh->mtface[self->index];

	if( value == NULL || value == Py_None )
        	face->tpage = NULL;		/* should memory be freed? */
	else {
		face->tpage = ( ( BPy_Image * ) value )->image;
		face->mode |= TF_TEX;
	}

	return 0;
}

#define MFACE_FLAG_BITMASK ( TF_SELECT | TF_SEL1 | \
		TF_SEL2 | TF_SEL3 | TF_SEL4 | TF_HIDE )

/*
* get face's texture flag
*/

static PyObject *MFace_getFlag( BPy_MFace *self )
{
	int flag;
	if( !self->mesh->mtface )
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"face has no texture values" );

	if( !MFace_get_pointer( self ) )
		return NULL;

	flag = self->mesh->mtface[self->index].flag & MFACE_FLAG_BITMASK;
	
	/* so old scripts still work */
	if (self->index == self->mesh->act_face)
		flag |= TF_ACTIVE;
		
	return PyInt_FromLong( (long)( flag ) );
}

/*
 * set face's texture flag
 */

static int MFace_setFlag( BPy_MFace *self, PyObject *value )
{
	int param;

	if( !self->mesh->mtface )
		return EXPP_ReturnIntError( PyExc_ValueError,
				"face has no texture values" );

	if( !MFace_get_pointer( self ) )
		return -1;

	if( !PyInt_Check ( value ) ) {
		char errstr[128];
		sprintf ( errstr , "expected int bitmask of 0x%04x", MFACE_FLAG_BITMASK );
		return EXPP_ReturnIntError( PyExc_TypeError, errstr );
	}
	param = PyInt_AS_LONG ( value );

	/* only one face can be active, so don't allow that here */
	if( param & TF_ACTIVE )
		param &= ~TF_ACTIVE;
	
	if( ( param & MFACE_FLAG_BITMASK ) != param )
		return EXPP_ReturnIntError( PyExc_ValueError,
						"invalid bit(s) set in mask" );

	/* merge active setting with other new params */
	param |= (self->mesh->mtface[self->index].flag);
	self->mesh->mtface[self->index].flag = (char)param;

	return 0;
}

/*
 * get face's texture mode
 */

static PyObject *MFace_getMode( BPy_MFace *self )
{
	if( !self->mesh->mtface )
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"face has no texture values" );

	if( !MFace_get_pointer( self ) )
		return NULL;

	return PyInt_FromLong( self->mesh->mtface[self->index].mode );
}

/*
 * set face's texture mode
 */

static int MFace_setMode( BPy_MFace *self, PyObject *value )
{
	int param;
	static short bitmask = TF_DYNAMIC
				| TF_ALPHASORT
				| TF_TEX
				| TF_SHAREDVERT
				| TF_LIGHT
				| TF_SHAREDCOL
				| TF_TILES
				| TF_BILLBOARD
				| TF_TWOSIDE
				| TF_INVISIBLE
				| TF_OBCOL
				| TF_BILLBOARD2
				| TF_SHADOW
				| TF_BMFONT;

	if( !self->mesh->mtface )
		return EXPP_ReturnIntError( PyExc_ValueError,
				"face has no texture values" );

	if( !MFace_get_pointer( self ) )
		return -1;

	if( !PyInt_Check ( value ) ) {
		char errstr[128];
		sprintf ( errstr , "expected int bitmask of 0x%04x", bitmask );
		return EXPP_ReturnIntError( PyExc_TypeError, errstr );
	}
	param = PyInt_AS_LONG ( value );
	
	if( param == 0xffff )		/* if param is ALL, set everything but HALO */
		param = bitmask ^ TF_BILLBOARD;
	else if( ( param & bitmask ) != param )
		return EXPP_ReturnIntError( PyExc_ValueError,
						"invalid bit(s) set in mask" );

	/* Blender UI doesn't allow these on at the same time */

	if( ( param & (TF_BILLBOARD | TF_BILLBOARD2) ) == 
			(TF_BILLBOARD | TF_BILLBOARD2) )
		return EXPP_ReturnIntError( PyExc_ValueError,
						"HALO and BILLBOARD cannot be enabled simultaneously" );

	self->mesh->mtface[self->index].mode = (short)param;

	return 0;
}

/*
 * get face's texture transparency setting
 */

static PyObject *MFace_getTransp( BPy_MFace *self )
{
	if( !self->mesh->mtface )
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"face has no texture values" );

	if( !MFace_get_pointer( self ) )
		return NULL;

	return PyInt_FromLong( self->mesh->mtface[self->index].transp );
}

/*
 * set face's texture transparency setting
 */

static int MFace_setTransp( BPy_MFace *self, PyObject *value )
{
	if( !self->mesh->mtface )
		return EXPP_ReturnIntError( PyExc_ValueError,
				"face has no texture values" );

	if( !MFace_get_pointer( self ) )
		return -1;

	return EXPP_setIValueRange( value,
			&self->mesh->mtface[self->index].transp, TF_SOLID, TF_SUB, 'b' );
}

/*
 * get a face's texture UV coord values
 */

static PyObject *MFace_getUV( BPy_MFace * self )
{
	MTFace *face;
	PyObject *attr;
	int length, i;

	if( !self->mesh->mtface )
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"face has no texture values" );

	if( !MFace_get_pointer( self ) )
		return NULL;

	face = &self->mesh->mtface[self->index];
	length = self->mesh->mface[self->index].v4 ? 4 : 3;
	attr = PyTuple_New( length );

	if( !attr )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"PyTuple_New() failed" );

	for( i=0; i<length; ++i ) {
		PyObject *vector = newVectorObject( face->uv[i], 2, Py_WRAP );
		if( !vector )
			return NULL;
		PyTuple_SetItem( attr, i, vector );
	}

	return attr;
}

/*
 * set a face's texture UV coord values
 */

static int MFace_setUV( BPy_MFace * self, PyObject * value )
{
	MTFace *face;
	int length, i;

	if( !MFace_get_pointer( self ) )
		return -1;

	if( !PySequence_Check( value ) ||
			EXPP_check_sequence_consistency( value, &vector_Type ) != 1 )
		return EXPP_ReturnIntError( PyExc_TypeError,
					    "expected sequence of vectors" );

	length = self->mesh->mface[self->index].v4 ? 4 : 3;
	if( length != PySequence_Size( value ) )
		return EXPP_ReturnIntError( PyExc_TypeError,
					    "size of vertex and UV sequences differ" );

	if( !self->mesh->mtface )
#if 0
		return EXPP_ReturnIntError( PyExc_ValueError,
				"face has no texture values" );
#else
		make_tfaces( self->mesh );
#endif

	face = &self->mesh->mtface[self->index];
	for( i=0; i<length; ++i ) {
		VectorObject *vector = (VectorObject *)PySequence_ITEM( value, i );
		face->uv[i][0] = vector->vec[0];
		face->uv[i][1] = vector->vec[1];
		Py_DECREF( vector );
	}
	return 0;
}

/*
 * get a face's texture UV coord select state
 */

static PyObject *MFace_getUVSel( BPy_MFace * self )
{
	MTFace *face;
	PyObject *attr;
	int length, i, mask;

	if( !self->mesh->mtface )
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"face has no texture values" );

	if( !MFace_get_pointer( self ) )
		return NULL;

	face = &self->mesh->mtface[self->index];
	length = self->mesh->mface[self->index].v4 ? 4 : 3;
	attr = PyTuple_New( length );

	if( !attr )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"PyTuple_New() failed" );

	/* get coord select state, one bit at a time */
	mask = TF_SEL1;
	for( i=0; i<length; ++i, mask <<= 1 ) {
		PyObject *value = PyInt_FromLong( face->flag & mask ? 1 : 0 );
		if( !value ) {
			Py_DECREF( attr );
			return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"PyInt_FromLong() failed" );
		}
		PyTuple_SetItem( attr, i, value );
	}

	return attr;
}

/*
 * set a face's texture UV coord select state
 */

static int MFace_setUVSel( BPy_MFace * self, PyObject * value )
{
	MTFace *face;
	int length, i, mask;

	if( !MFace_get_pointer( self ) )
		return -1;

	if( !PySequence_Check( value ) )
		return EXPP_ReturnIntError( PyExc_TypeError,
				"expected a tuple of integers" );

	length = self->mesh->mface[self->index].v4 ? 4 : 3;
	if( length != PySequence_Size( value ) )
		return EXPP_ReturnIntError( PyExc_TypeError,
					    "size of vertex and UV lists differ" );

	if( !self->mesh->mtface )
#if 0
		return EXPP_ReturnIntError( PyExc_ValueError,
				"face has no texture values" );
#else
		make_tfaces( self->mesh );
#endif

	/* set coord select state, one bit at a time */
	face = &self->mesh->mtface[self->index];
	mask = TF_SEL1;
	for( i=0; i<length; ++i, mask <<= 1 ) {
		PyObject *tmp = PySequence_GetItem( value, i ); /* adds a reference, remove below */
		if( !PyInt_Check( tmp ) ) {
			Py_DECREF(tmp);
			return EXPP_ReturnIntError( PyExc_TypeError,
					"expected a tuple of integers" );
		}
		if( PyInt_AsLong( tmp ) )
			face->flag |= mask;
		else
			face->flag &= ~mask;
		Py_DECREF(tmp);
	}
	return 0;
}

/*
 * get a face's vertex colors. note that if mesh->mtfaces is defined, then 
 * it takes precedent over mesh->mcol
 */

static PyObject *MFace_getCol( BPy_MFace * self )
{
	PyObject *attr;
	int length, i;
	MCol * mcol;

	/* if there's no mesh color vectors or texture faces, nothing to do */

	if( !self->mesh->mcol )
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"face has no vertex colors" );

	if( !MFace_get_pointer( self ) )
		return NULL;

	mcol = &self->mesh->mcol[self->index*4];

	length = self->mesh->mface[self->index].v4 ? 4 : 3;
	attr = PyTuple_New( length );

	if( !attr )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"PyTuple_New() failed" );

	for( i=0; i<length; ++i ) {
		PyObject *color = MCol_CreatePyObject( &mcol[i] );
		if( !color )
			return NULL;
		PyTuple_SetItem( attr, i, color );
	}

	return attr;
}

/*
 * set a face's vertex colors
 */

static int MFace_setCol( BPy_MFace * self, PyObject *value )
{
	int length, i;
	MCol * mcol;

	/* if there's no mesh color vectors or texture faces, nothing to do */

	if( !self->mesh->mcol )
		return EXPP_ReturnIntError( PyExc_ValueError,
				"face has no vertex colors" );

	if( !MFace_get_pointer( self ) )
		return -1;

	mcol = &self->mesh->mcol[self->index*4];

	length = self->mesh->mface[self->index].v4 ? 4 : 3;

	if( !PyList_Check( value ) && !PyTuple_Check( value ) )
		return EXPP_ReturnIntError( PyExc_TypeError,
				"expected a sequence of MCols" );

	if( EXPP_check_sequence_consistency( value, &MCol_Type ) != 1 )
		return EXPP_ReturnIntError( PyExc_TypeError,
				"expected a sequence of MCols" );

	if( PySequence_Size( value ) != length )
		return EXPP_ReturnIntError( PyExc_ValueError,
				"incorrect number of colors for this face" );

	for( i=0; i<length; ++i ) {
		BPy_MCol *obj = (BPy_MCol *)PySequence_ITEM( value, i );
		mcol[i].r = obj->color->r;
		mcol[i].g = obj->color->g;
		mcol[i].b = obj->color->b;
		mcol[i].a = obj->color->a;
		Py_DECREF( obj );
	}
	return 0;
}


/*
 * get edge keys for using in a dictionary or set key
 */

static PyObject *MFace_getEdgeKeys( BPy_MFace * self )
{
	MFace *face = MFace_get_pointer( self );
	PyObject *attr, *edpair;
	
	if (!face)
		return NULL; /* error set */
	
	if (face->v4) {
		attr = PyTuple_New( 4 );
		edpair = PyTuple_New( 2 );
		if (face->v1 > face->v2) {
			PyTuple_SET_ITEM( edpair, 0, PyInt_FromLong(face->v2) );
			PyTuple_SET_ITEM( edpair, 1, PyInt_FromLong(face->v1) );
		} else {
			PyTuple_SET_ITEM( edpair, 0, PyInt_FromLong(face->v1) );
			PyTuple_SET_ITEM( edpair, 1, PyInt_FromLong(face->v2) );
		}
		PyTuple_SET_ITEM( attr, 0, edpair );
		
		edpair = PyTuple_New( 2 );
		if (face->v2 > face->v3) {
			PyTuple_SET_ITEM( edpair, 0, PyInt_FromLong(face->v3) );
			PyTuple_SET_ITEM( edpair, 1, PyInt_FromLong(face->v2) );
		} else {
			PyTuple_SET_ITEM( edpair, 0, PyInt_FromLong(face->v2) );
			PyTuple_SET_ITEM( edpair, 1, PyInt_FromLong(face->v3) );
		}
		PyTuple_SET_ITEM( attr, 1, edpair );

		edpair = PyTuple_New( 2 );
		if (face->v3 > face->v4) {
			PyTuple_SET_ITEM( edpair, 0, PyInt_FromLong(face->v4) );
			PyTuple_SET_ITEM( edpair, 1, PyInt_FromLong(face->v3) );
		} else {
			PyTuple_SET_ITEM( edpair, 0, PyInt_FromLong(face->v3) );
			PyTuple_SET_ITEM( edpair, 1, PyInt_FromLong(face->v4) );
		}
		PyTuple_SET_ITEM( attr, 2, edpair );
		
		edpair = PyTuple_New( 2 );
		if (face->v4 > face->v1) {
			PyTuple_SET_ITEM( edpair, 0, PyInt_FromLong(face->v1) );
			PyTuple_SET_ITEM( edpair, 1, PyInt_FromLong(face->v4) );
		} else {
			PyTuple_SET_ITEM( edpair, 0, PyInt_FromLong(face->v4) );
			PyTuple_SET_ITEM( edpair, 1, PyInt_FromLong(face->v1) );
		}
		PyTuple_SET_ITEM( attr, 3, edpair );
		
	} else {
		
		attr = PyTuple_New( 3 );
		edpair = PyTuple_New( 2 );
		if (face->v1 > face->v2) {
			PyTuple_SET_ITEM( edpair, 0, PyInt_FromLong(face->v2) );
			PyTuple_SET_ITEM( edpair, 1, PyInt_FromLong(face->v1) );
		} else {
			PyTuple_SET_ITEM( edpair, 0, PyInt_FromLong(face->v1) );
			PyTuple_SET_ITEM( edpair, 1, PyInt_FromLong(face->v2) );
		}
		PyTuple_SET_ITEM( attr, 0, edpair );
		
		edpair = PyTuple_New( 2 );
		if (face->v2 > face->v3) {
			PyTuple_SET_ITEM( edpair, 0, PyInt_FromLong(face->v3) );
			PyTuple_SET_ITEM( edpair, 1, PyInt_FromLong(face->v2) );
		} else {
			PyTuple_SET_ITEM( edpair, 0, PyInt_FromLong(face->v2) );
			PyTuple_SET_ITEM( edpair, 1, PyInt_FromLong(face->v3) );
		}
		PyTuple_SET_ITEM( attr, 1, edpair );

		edpair = PyTuple_New( 2 );
		if (face->v3 > face->v1) {
			PyTuple_SET_ITEM( edpair, 0, PyInt_FromLong(face->v1) );
			PyTuple_SET_ITEM( edpair, 1, PyInt_FromLong(face->v3) );
		} else {
			PyTuple_SET_ITEM( edpair, 0, PyInt_FromLong(face->v3) );
			PyTuple_SET_ITEM( edpair, 1, PyInt_FromLong(face->v1) );
		}
		PyTuple_SET_ITEM( attr, 2, edpair );
	}
	
	return attr;
}


/************************************************************************
 *
 * Python MFace_Type attributes get/set structure
 *
 ************************************************************************/

static PyGetSetDef BPy_MFace_getseters[] = {
    {"verts",
     (getter)MFace_getVerts, (setter)MFace_setVerts,
     "face's vertices",
     NULL},
    {"v",
     (getter)MFace_getVerts, (setter)MFace_setVerts,
     "deprecated: see 'verts'",
     NULL},
    {"mat",
     (getter)MFace_getMat, (setter)MFace_setMat,
     "face's material index",
     NULL},
    {"index",
     (getter)MFace_getIndex, (setter)NULL,
     "face's index",
     NULL},
    {"no",
     (getter)MFace_getNormal, (setter)NULL,
     "face's normal",
     NULL},
    {"cent",
     (getter)MFace_getCent, (setter)NULL,
     "face's center",
     NULL},
    {"area",
     (getter)MFace_getArea, (setter)NULL,
     "face's 3D area",
     NULL},

    {"hide",
     (getter)MFace_getMFlagBits, (setter)MFace_setMFlagBits,
     "face hidden in edit mode",
     (void *)ME_HIDE},
    {"sel",
     (getter)MFace_getMFlagBits, (setter)MFace_setSelect,
     "face selected in edit mode",
     (void *)ME_FACE_SEL},
    {"smooth",
     (getter)MFace_getMFlagBits, (setter)MFace_setMFlagBits,
     "face smooth enabled",
     (void *)ME_SMOOTH},

	/* attributes for texture faces (mostly, I think) */

    {"col",
     (getter)MFace_getCol, (setter)MFace_setCol,
     "face's vertex colors",
     NULL},
    {"flag",
     (getter)MFace_getFlag, (setter)MFace_setFlag,
     "flags associated with texture faces",
     NULL},
    {"image",
     (getter)MFace_getImage, (setter)MFace_setImage,
     "image associated with texture faces",
     NULL},
    {"mode",
     (getter)MFace_getMode, (setter)MFace_setMode,
     "modes associated with texture faces",
     NULL},
    {"transp",
     (getter)MFace_getTransp, (setter)MFace_setTransp,
     "transparency of texture faces",
     NULL},
    {"uv",
     (getter)MFace_getUV, (setter)MFace_setUV,
     "face's UV coordinates",
     NULL},
    {"uvSel",
     (getter)MFace_getUVSel, (setter)MFace_setUVSel,
     "face's UV coordinates select status",
     NULL},
    {"edge_keys",
     (getter)MFace_getEdgeKeys, (setter)NULL,
     "for each edge this face uses return an ordered tuple edge pair that can be used as a key in a dictionary or set",
     NULL},

	{NULL,NULL,NULL,NULL,NULL}  /* Sentinel */
};

/************************************************************************
 *
 * Python MFace_Type iterator (iterates over vertices)
 *
 ************************************************************************/

/*
 * Initialize the interator index
 */

static PyObject *MFace_getIter( BPy_MFace * self )
{
	if (self->iter==-1) {
		self->iter = 0;
		return EXPP_incr_ret ( (PyObject *) self );
	} else {
		BPy_MFace *seq= (BPy_MFace *)MFace_CreatePyObject(self->mesh, self->index);
		seq->iter = 0;
		return (PyObject *) seq;
	}
}

/*
 * Return next MVert.  Throw an exception after the final vertex.
 */

static PyObject *MFace_nextIter( BPy_MFace * self )
{
	struct MFace *face = &self->mesh->mface[self->index];
	int len = self->mesh->mface[self->index].v4 ? 4 : 3;

	if( self->iter == len ) {
		self->iter = -1;
		return EXPP_ReturnPyObjError( PyExc_StopIteration,
				"iterator at end" );
	}
	
	++self->iter;
	switch ( self->iter ) {
	case 1:
		return MVert_CreatePyObject( self->mesh, face->v1 );
	case 2:
		return MVert_CreatePyObject( self->mesh, face->v2 );
	case 3:
		return MVert_CreatePyObject( self->mesh, face->v3 );
	default :
		return MVert_CreatePyObject( self->mesh, face->v4 );
	}
}

/************************************************************************
 *
 * Python MFace_Type methods
 *
 ************************************************************************/

/************************************************************************
 *
 * Python MFace_Type standard operations
 *
 ************************************************************************/
static int MFace_compare( BPy_MFace * a, BPy_MFace * b )
{
	return( a->mesh == b->mesh && a->index == b->index ) ? 0 : -1;
}

static PyObject *MFace_repr( BPy_MFace* self )
{
	MFace *face = MFace_get_pointer( self );

	if( !face )
		return NULL;

	if( face->v4 )
		return PyString_FromFormat( "[MFace (%d %d %d %d) %d]",
				(int)face->v1, (int)face->v2, 
				(int)face->v3, (int)face->v4, (int)self->index ); 
	else
		return PyString_FromFormat( "[MFace (%d %d %d) %d]",
				(int)face->v1, (int)face->v2,
				(int)face->v3, (int)self->index ); 
}

static long MFace_hash( BPy_MFace *self )
{
	return (long)self->index;
}

static int MFace_len( BPy_MFace * self )
{
	if( self->index >= self->mesh->totface )
		return 0;
	return self->mesh->mface[self->index].v4 ? 4 : 3;
}

static PySequenceMethods MFace_as_sequence = {
	( inquiry ) MFace_len,         /* sq_length */
	( binaryfunc ) 0,	           /* sq_concat */
	( intargfunc ) 0,	           /* sq_repeat */
	( intargfunc ) 0,              /* sq_item */
	( intintargfunc ) 0,           /* sq_slice */
	( intobjargproc ) 0,           /* sq_ass_item */
	( intintobjargproc ) 0,        /* sq_ass_slice */
	0,0,0,
};

static PyObject *MFace_getProp( BPy_MFace *self, PyObject *args)
{
	Mesh *me = (Mesh *)self->mesh;
	MFace *face = MFace_get_pointer( self );
	if( !face )
		return NULL;
	mesh_update_customdata_pointers(me); //!
	return Mesh_getProperty_internal(&(me->fdata), self->index, args);
}

static PyObject *MFace_setProp( BPy_MFace *self,  PyObject *args)
{
	Mesh *me = (Mesh *)self->mesh;
	PyObject *obj;
	MFace *face = MFace_get_pointer( self );
	if( !face )
		return NULL; /* error set */
	
	obj = Mesh_setProperty_internal(&(me->fdata), self->index, args);
	mesh_update_customdata_pointers(me); //!
	return obj;
}

static struct PyMethodDef BPy_MFace_methods[] = {
	{"getProperty", (PyCFunction)MFace_getProp, METH_O,
		"get property indicated by name"},
	{"setProperty", (PyCFunction)MFace_setProp, METH_VARARGS,
		"set property indicated by name"},
	{NULL, NULL, 0, NULL}
};
/************************************************************************
 *
 * Python MFace_Type structure definition
 *
 ************************************************************************/

PyTypeObject MFace_Type = {
	PyObject_HEAD_INIT( NULL )  /* required py macro */
	0,                          /* ob_size */
	/*  For printing, in format "<module>.<name>" */
	"Blender MFace",            /* char *tp_name; */
	sizeof( BPy_MFace ),        /* int tp_basicsize; */
	0,                          /* tp_itemsize;  For allocation */

	/* Methods to implement standard operations */

	NULL,						/* destructor tp_dealloc; */
	NULL,                       /* printfunc tp_print; */
	NULL,                       /* getattrfunc tp_getattr; */
	NULL,                       /* setattrfunc tp_setattr; */
	( cmpfunc ) MFace_compare,  /* cmpfunc tp_compare; */
	( reprfunc ) MFace_repr,    /* reprfunc tp_repr; */

	/* Method suites for standard classes */

	NULL,                       /* PyNumberMethods *tp_as_number; */
	&MFace_as_sequence,         /* PySequenceMethods *tp_as_sequence; */
	NULL,                       /* PyMappingMethods *tp_as_mapping; */

	/* More standard operations (here for binary compatibility) */

	( hashfunc ) MFace_hash,    /* hashfunc tp_hash; */
	NULL,                       /* ternaryfunc tp_call; */
	NULL,                       /* reprfunc tp_str; */
	NULL,                       /* getattrofunc tp_getattro; */
	NULL,                       /* setattrofunc tp_setattro; */

	/* Functions to access object as input/output buffer */
	NULL,                       /* PyBufferProcs *tp_as_buffer; */

  /*** Flags to define presence of optional/expanded features ***/
	Py_TPFLAGS_DEFAULT,         /* long tp_flags; */

	NULL,                       /*  char *tp_doc;  Documentation string */
  /*** Assigned meaning in release 2.0 ***/
	/* call function for all accessible objects */
	NULL,                       /* traverseproc tp_traverse; */

	/* delete references to contained objects */
	NULL,                       /* inquiry tp_clear; */

  /***  Assigned meaning in release 2.1 ***/
  /*** rich comparisons ***/
	NULL,                       /* richcmpfunc tp_richcompare; */

  /***  weak reference enabler ***/
	0,                          /* long tp_weaklistoffset; */

  /*** Added in release 2.2 ***/
	/*   Iterators */
	( getiterfunc ) MFace_getIter, /* getiterfunc tp_iter; */
	( iternextfunc ) MFace_nextIter, /* iternextfunc tp_iternext; */

  /*** Attribute descriptor and subclassing stuff ***/
	BPy_MFace_methods,          /* struct PyMethodDef *tp_methods; */
	NULL,                       /* struct PyMemberDef *tp_members; */
	BPy_MFace_getseters,        /* struct PyGetSetDef *tp_getset; */
	NULL,                       /* struct _typeobject *tp_base; */
	NULL,                       /* PyObject *tp_dict; */
	NULL,                       /* descrgetfunc tp_descr_get; */
	NULL,                       /* descrsetfunc tp_descr_set; */
	0,                          /* long tp_dictoffset; */
	NULL,                       /* initproc tp_init; */
	NULL,                       /* allocfunc tp_alloc; */
	NULL,                       /* newfunc tp_new; */
	/*  Low-level free-memory routine */
	NULL,                       /* freefunc tp_free;  */
	/* For PyObject_IS_GC */
	NULL,                       /* inquiry tp_is_gc;  */
	NULL,                       /* PyObject *tp_bases; */
	/* method resolution order */
	NULL,                       /* PyObject *tp_mro;  */
	NULL,                       /* PyObject *tp_cache; */
	NULL,                       /* PyObject *tp_subclasses; */
	NULL,                       /* PyObject *tp_weaklist; */
	NULL
};

static PyObject *MFace_CreatePyObject( Mesh * mesh, int i )
{
	BPy_MFace *obj = PyObject_NEW( BPy_MFace, &MFace_Type );

	if( !obj )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"PyObject_New() failed" );

	obj->mesh = mesh;
	obj->index = i;
	obj->iter= -1;
	return (PyObject *)obj;
}

/************************************************************************
 *
 * Face sequence 
 *
 ************************************************************************/

static int MFaceSeq_len( BPy_MFaceSeq * self )
{
	return self->mesh->totface;
}

static PyObject *MFaceSeq_item( BPy_MFaceSeq * self, int i )
{
	if( i < 0 || i >= self->mesh->totface )
		return EXPP_ReturnPyObjError( PyExc_IndexError,
					      "array index out of range" );

	return MFace_CreatePyObject( self->mesh, i );
}

static PySequenceMethods MFaceSeq_as_sequence = {
	( inquiry ) MFaceSeq_len,      /* sq_length */
	( binaryfunc ) 0,	           /* sq_concat */
	( intargfunc ) 0,	           /* sq_repeat */
	( intargfunc ) MFaceSeq_item,  /* sq_item */
	( intintargfunc ) 0,           /* sq_slice */
	( intobjargproc ) 0,           /* sq_ass_item */
	( intintobjargproc ) 0,        /* sq_ass_slice */
	0,0,0,
};

/************************************************************************
 *
 * Python MFaceSeq_Type iterator (iterates over faces)
 *
 ************************************************************************/

/*
 * Initialize the interator index
 */

static PyObject *MFaceSeq_getIter( BPy_MFaceSeq * self )
{
	if (self->iter==-1) {
		self->iter = 0;
		return EXPP_incr_ret ( (PyObject *) self );
	} else {
		BPy_MFaceSeq *seq = (BPy_MFaceSeq *)MFaceSeq_CreatePyObject(self->mesh);
		seq->iter = 0;
		return (PyObject *)seq;
	}
}

/*
 * Return next MFace.
 */

static PyObject *MFaceSeq_nextIter( BPy_MFaceSeq * self )
{
	if( self->iter == self->mesh->totface ) {
		self->iter= -1; /* not being used in a seq */
		return EXPP_ReturnPyObjError( PyExc_StopIteration,
				"iterator at end" );
	}
	return MFace_CreatePyObject( self->mesh, self->iter++ );
}

/************************************************************************
 *
 * Python MFaceSeq_Type methods
 *
 ************************************************************************/

static PyObject *MFaceSeq_extend( BPy_MEdgeSeq * self, PyObject *args,
	  PyObject *keywds )
{
	/*
	 * (a) check input for valid edge objects, faces which consist of
	 *     only three or four edges
	 * (b) check input to be sure edges form a closed face (each edge
	 *     contains verts in two other different edges?)
	 *
	 * (1) build list of new faces; remove duplicates
	 *   * use existing "v4=0 rule" for 3-vert faces
	 * (2) build list of existing faces for searching
	 * (3) from new face list, remove existing faces:
	 */

	int len, nverts;
	int i, j, k, new_face_count;
	int good_faces;
	SrchFaces *oldpair, *newpair, *tmppair, *tmppair2;
	PyObject *tmp;
	MFace *tmpface;
	Mesh *mesh = self->mesh;
	int ignore_dups = 0;
	PyObject *return_list = NULL;
	char flag = ME_FACE_SEL;
	
	/* before we try to add faces, add edges; if it fails; exit */

	tmp = MEdgeSeq_extend( self, args );
	if( !tmp )
		return NULL;
	Py_DECREF( tmp );

	/* process any keyword arguments */
	if( keywds ) {
		PyObject *res = PyDict_GetItemString( keywds, "ignoreDups" );
		if( res ) {
			ignore_dups = PyObject_IsTrue( res );
			if (ignore_dups==-1) {
				return EXPP_ReturnPyObjError( PyExc_TypeError,
						"keyword argument \"ignoreDups\" expected True/False or 0/1" );
			}
		}
		res = PyDict_GetItemString( keywds, "indexList" );
		if (res) {
			switch( PyObject_IsTrue( res ) ) {
			case  0:
				break;
			case -1:
				return EXPP_ReturnPyObjError( PyExc_TypeError,
						"keyword argument \"indexList\" expected True/False or 0/1" );
			default:
				return_list = PyList_New( 0 );
			}
		}
		
		res = PyDict_GetItemString( keywds, "smooth" );
		if (res) {
			switch( PyObject_IsTrue( res ) ) {
				case  0:
					break;
				case -1:
					return EXPP_ReturnPyObjError( PyExc_TypeError,
							"keyword argument \"smooth\" expected True/False or 0/1" );
				default:
					flag |= ME_SMOOTH;
			
			}
		}
	}

	/* make sure we get a tuple of sequences of something */

	switch( PySequence_Size( args ) ) {
	case 1:		/* better be a sequence or a tuple */
		/* if a sequence... */
		tmp = PyTuple_GET_ITEM( args, 0 );
		if( PySequence_Check( tmp ) ) {
			PyObject *tmp2;

			/* ignore empty sequences */
			if( !PySequence_Size( tmp ) ) {
				Py_RETURN_NONE;
			}

			/* if another sequence, use it */
			tmp2 = PySequence_ITEM( tmp, 0 );
			if( PySequence_Check( tmp2 ) )
				args = tmp;
			Py_INCREF( args );
			Py_DECREF( tmp2 );
		} else
			return EXPP_ReturnPyObjError( PyExc_TypeError,
					"expected a sequence of sequence pairs" );
		break;
	case 2:	
	case 3:
	case 4:		/* two to four args may be individual verts */
		tmp = PyTuple_GET_ITEM( args, 0 );
		/*
		 * if first item isn't a sequence, then assume it's a bunch of MVerts
		 * and wrap inside a tuple
		 */
		if( !PySequence_Check( tmp ) ) {
			args = Py_BuildValue( "(O)", args );
			if( !args )
				return EXPP_ReturnPyObjError( PyExc_RuntimeError,
						"Py_BuildValue() failed" );
		/*
		 * otherwise, assume it already a bunch of sequences so use as-is
		 */
		} else { 
			Py_INCREF( args );		/* so we can safely DECREF later */
		}
		break;
	default:	/* anything else is definitely wrong */
		return EXPP_ReturnPyObjError( PyExc_TypeError,
				"expected a sequence of sequence pairs" );
	}

	/* if nothing to add, just exit */
	len = PySequence_Size( args );
	if( len == 0 ) {
		Py_DECREF( args );
		Py_RETURN_NONE;
	}

	/* 
	 * Since we call MEdgeSeq_extend first, we already know the input list
	 * is valid.  Here we just need to count the total number of faces.
	 */

	new_face_count = 0;
	for( i = 0; i < len; ++i ) {
		tmp = PySequence_ITEM( args, i );
		nverts = PySequence_Size( tmp );
		if( return_list || nverts != 2 )		
			++new_face_count; /* new faces must have 3 or 4 verts */
		Py_DECREF( tmp );
	}

	/* OK, commit to allocating the search structures */
	newpair = (SrchFaces *)MEM_callocN( sizeof(SrchFaces)*new_face_count,
			"MFacePairs" );

	/* scan the input list and build the new face pair list */
	len = PySequence_Size( args );
	tmppair = newpair;

	for( i = 0; i < len; ++i ) {
		MFace tmpface;
		unsigned int vert[4]={0,0,0,0};
		unsigned char order[4]={0,1,2,3};
		tmp = PySequence_GetItem( args, i );
		nverts = PySequence_Size( tmp );

		if( nverts == 2 ) {	/* again, ignore 2-vert tuples */
			if( return_list )	/* if returning indices, mark as deleted */
				tmppair->v[1] = 0;
			Py_DECREF( tmp );
			continue;
		}
	
		/*
		 * get the face's vertices' indexes
		 */
	
		for( j = 0; j < nverts; ++j ) {
			PyObject *item = PySequence_ITEM( tmp, j );
			if( BPy_MVert_Check( item ) )
				vert[j] = ((BPy_MVert *)item)->index;
			else
				vert[j] = PyInt_AsLong( item );
			Py_DECREF( item );
		}
		Py_DECREF( tmp );
		tmpface.v1 = vert[0];
		tmpface.v2 = vert[1];
		tmpface.v3 = vert[2];
		tmpface.v4 = vert[3];

		/*
		 * go through some contortions to guarantee the third and fourth
		 * vertices are not index 0
		 */
		eeek_fix( &tmpface, nverts == 4 );
		vert[0] = tmpface.v1;
		vert[1] = tmpface.v2;
		vert[2] = tmpface.v3;
		if( nverts == 3 )
			vert[3] = tmppair->v[3] = 0;
		else
			vert[3] = tmpface.v4;

		/*
		 * sort the verts before placing in pair list.  the order of
		 * vertices in the face is very important, so keep track of
		 * the original order
		 */

		for( j = nverts-1; j >= 0; --j ) {
			for( k = 0; k < j; ++k ) {
				if( vert[k] > vert[k+1] ) {
					SWAP( int, vert[k], vert[k+1] );
					SWAP( char, order[k], order[k+1] );
				} else if( vert[k] == vert[k+1] ) {
					break;
				}
			}
			if( k < j )
				break;
			tmppair->v[j] = vert[j];
		}
		if( j >= 0 ) {				/* a duplicate vertex found */
			if( return_list ) {		/* if returning index list */
				tmppair->v[1] = 0;	/*    mark as deleted */
			} else {
				--new_face_count;	/* otherwise skip */
				continue;
			}
		}
		tmppair->index = i;

		/* pack order into a byte */
		tmppair->order = order[0]|(order[1]<<2)|(order[2]<<4)|(order[3]<<6);
		++tmppair;
	}

	/*
	 * find duplicates in the new list and mark.  if it's a duplicate,
	 * then mark by setting second vert index to 0 (a real edge won't have
	 * second vert index of 0 since verts are sorted)
	 */

	good_faces = new_face_count;	/* assume all faces good to start */

	tmppair = newpair;	/* "last good edge" */
	tmppair2 = &tmppair[1];	/* "current candidate edge" */
	if( !ignore_dups ) {

	/* sort the new face pairs */
		qsort( newpair, new_face_count, sizeof(SrchFaces), mface_comp );

		for( i = 0; i < new_face_count; ++i ) {
			if( mface_comp( tmppair, tmppair2 ) )
				tmppair = tmppair2;	/* last != current, so current == last */
			else {
				tmppair2->v[1] = 0; /* last == current, so mark as duplicate */
				--good_faces;		/* one less good face */
			}
			tmppair2++;
		}
	}

	/* if mesh has faces, see if any of the new faces are already in it */
	if( mesh->totface && !ignore_dups ) {
		oldpair = (SrchFaces *)MEM_callocN( sizeof(SrchFaces)*mesh->totface,
				"MFacePairs" );

		tmppair = oldpair;
		tmpface = mesh->mface;
		for( i = 0; i < mesh->totface; ++i ) {
			unsigned char order[4]={0,1,2,3};
			int verts[4];
			verts[0]=tmpface->v1;
			verts[1]=tmpface->v2;
			verts[2]=tmpface->v3;
			verts[3]=tmpface->v4;
	
			len = ( tmpface->v4 ) ? 3 : 2;
			tmppair->v[3] = 0;	/* for triangular faces */

		/* sort the verts before placing in pair list here too */
			for( j = len; j >= 0; --j ) {
				for( k = 0; k < j; ++k )
					if( verts[k] > verts[k+1] ) {
						SWAP( int, verts[k], verts[k+1] );
						SWAP( unsigned char, order[k], order[k+1] );
					}
				tmppair->v[j] = verts[j];
			}

		/* pack order into a byte */
			tmppair->order = order[0]|(order[1]<<2)|(order[2]<<4)|(order[3]<<6);
			++tmppair;
			++tmpface;
		}

	/* sort the old face pairs */
		qsort( oldpair, mesh->totface, sizeof(SrchFaces), mface_comp );

	/* eliminate new faces already in the mesh */
		tmppair = newpair;
		for( i = good_faces; i ; ) {
			if( tmppair->v[1] ) {
				if( bsearch( tmppair, oldpair, mesh->totface, 
						sizeof(SrchFaces), mface_comp ) ) {
					tmppair->v[1] = 0;	/* mark as duplicate */
					--good_faces;
				}
				--i;
			}
			tmppair++;
		}
		MEM_freeN( oldpair );
	}

	/* if any new faces are left, add to list */
	if( good_faces || return_list ) {
		int totface = mesh->totface+good_faces;	/* new face count */
		CustomData fdata;

		CustomData_copy( &mesh->fdata, &fdata, CD_MASK_MESH, CD_DEFAULT, totface );
		CustomData_copy_data( &mesh->fdata, &fdata, 0, 0, mesh->totface );

		if ( !CustomData_has_layer( &fdata, CD_MFACE ) )
			CustomData_add_layer( &fdata, CD_MFACE, CD_CALLOC, NULL, totface );

		CustomData_free( &mesh->fdata, mesh->totface );
		mesh->fdata = fdata;
		mesh_update_customdata_pointers( mesh );

	/* sort the faces back into their original input list order */
		if( !ignore_dups )
			qsort( newpair, new_face_count, sizeof(SrchFaces),
					mface_index_comp );


	/* point to the first face we're going to add */
		tmpface = &mesh->mface[mesh->totface];
		tmppair = newpair;

		if( return_list )
			good_faces = new_face_count;	/* assume all faces good to start */
			
	/* as we find a good face, add it */
		while ( good_faces ) {
			if( tmppair->v[1] ) {
				int i;
				unsigned int index[4];
				unsigned char order = tmppair->order;

		/* unpack the order of the vertices */
				for( i = 0; i < 4; ++i ) {
					index[(order & 0x03)] = i;
					order >>= 2;
				}

		/* now place vertices in the proper order */
				tmpface->v1 = tmppair->v[index[0]];
				tmpface->v2 = tmppair->v[index[1]];
				tmpface->v3 = tmppair->v[index[2]];
				tmpface->v4 = tmppair->v[index[3]];

				tmpface->flag = flag;

				if( return_list ) {
					tmp = PyInt_FromLong( mesh->totface );
					PyList_Append( return_list, tmp );
					Py_DECREF(tmp);
				}
				mesh->totface++;
				++tmpface;
				--good_faces;
			} else if( return_list ) {
				PyList_Append( return_list, Py_None );
				--good_faces;
			}
			tmppair++;
		}
	}

	/* clean up and leave */
	mesh_update( mesh );
	Py_DECREF ( args );
	MEM_freeN( newpair );

	if( return_list )
		return return_list;
	else
		Py_RETURN_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 an int and a sequence of ints or MFaces" );

	if( !PyList_Check( args ) && !PyTuple_Check( args ) )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
				"expected an int and a sequence of ints or MFaces" );

	/* see how many args we need to parse */
	len = PySequence_Size( args );
	if( len < 1 )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
				"sequence must contain at least one int or MFace" );

	face_table = 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_MFace_Check( tmp ) )
			face_table[i] = ((BPy_MFace *)tmp)->index;
		else if( PyInt_Check( tmp ) )
			face_table[i] = PyInt_AsLong( tmp );
		else {
			MEM_freeN( face_table );
			Py_DECREF( tmp );
			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 );
			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 remaining face, select all edges */
		tmpface = mesh->mface;
		fface = (struct fourEdges *)face_edges;
		for( i = mesh->totface; i--; ++tmpface, ++fface ) {
			if( tmpface->v1 != UINT_MAX ) {
				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 );
	mesh_update ( mesh );
	Py_RETURN_NONE;
}

/* copied from meshtools.c - should make generic? */
static void permutate(void *list, int num, int size, int *index)
{
	void *buf;
	int len;
	int i;

	len = num * size;

	buf = MEM_mallocN(len, "permutate");
	memcpy(buf, list, len);
	
	for (i = 0; i < num; i++) {
		memcpy((char *)list + (i * size), (char *)buf + (index[i] * size), size);
	}
	MEM_freeN(buf);
}

/* this wrapps list sorting then applies back to the mesh */
static PyObject *MFaceSeq_sort( BPy_MEdgeSeq * self, PyObject *args,
	  PyObject *keywds )
{
	PyObject *ret, *sort_func, *newargs;
	
	Mesh *mesh = self->mesh;
	PyObject *sorting_list;
	CustomDataLayer *layer;
	int i, *index;
	
	/* get a list for internal use */
	sorting_list = PySequence_List( (PyObject *)self );
	if( !sorting_list )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"PyList_New() failed" );
	
	/* create index list */
	index = (int *) MEM_mallocN(sizeof(int) * mesh->totface, "sort faces");
	if (!index)
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"faces.sort(...) failed to allocate memory" );
	
	newargs = EXPP_PyTuple_New_Prepend(args, sorting_list);
	sort_func = PyObject_GetAttrString( ((PyObject *)&PyList_Type), "sort");
	
	ret = PyObject_Call(sort_func, newargs, keywds);
	
	Py_DECREF(newargs);
	Py_DECREF(sort_func);
	
	if (ret) {
		/* copy the faces indicies to index */
		for (i = 0; i < mesh->totface; i++)
			index[i] = ((BPy_MFace *)PyList_GET_ITEM(sorting_list, i))->index;
		
		for(i = 0; i < mesh->fdata.totlayer; i++) {
			layer = &mesh->fdata.layers[i];
			permutate(layer->data, mesh->totface, CustomData_sizeof(layer->type), index);
		}
	}
	Py_DECREF(sorting_list);
	MEM_freeN(index);
	return ret;
}

static PyObject *MFaceSeq_selected( BPy_MFaceSeq * self )
{
	int i, count;
	Mesh *mesh = self->mesh;
	MFace *tmpface;
	PyObject *list;

	/* first count selected faces (quicker than appending to PyList?) */
	count = 0;
	tmpface = mesh->mface;
	for( i = 0; i < mesh->totface; ++i, ++tmpface )
		if( tmpface->flag & ME_FACE_SEL )
			++count;

	list = PyList_New( count );
	if( !list )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"PyList_New() failed" );

	/* next, insert selected faces into list */
	count = 0;
	tmpface = mesh->mface;
	for( i = 0; i < mesh->totface; ++i, ++tmpface ) {
		if( tmpface->flag & ME_FACE_SEL ) {
			PyObject *tmp = PyInt_FromLong( i );
			if( !tmp ) {
				Py_DECREF( list );
				return EXPP_ReturnPyObjError( PyExc_RuntimeError,
						"PyInt_FromLong() failed" );
			}
			PyList_SET_ITEM( list, count, tmp );
			++count;
		}
	}
	return list;
}

static PyObject *MFaceSeq_add_layertype(BPy_MFaceSeq *self, PyObject *args)
{
	Mesh *me = (Mesh*)self->mesh;
	return Mesh_addPropLayer_internal(me, &(me->fdata), me->totface, args);
}
static PyObject *MFaceSeq_del_layertype(BPy_MFaceSeq *self, PyObject *value)
{
	Mesh *me = (Mesh*)self->mesh;
	return Mesh_removePropLayer_internal(me, &(me->fdata), me->totface, value);
}
static PyObject *MFaceSeq_rename_layertype(BPy_MFaceSeq *self, PyObject *args)
{
	Mesh *me = (Mesh*)self->mesh;
	return Mesh_renamePropLayer_internal(me,&(me->fdata),args);
}
static PyObject *MFaceSeq_PropertyList(BPy_MFaceSeq *self) 
{
	Mesh *me = (Mesh*)self->mesh;
	return Mesh_propList_internal(&(me->fdata));
}

static struct PyMethodDef BPy_MFaceSeq_methods[] = {
	{"extend", (PyCFunction)MFaceSeq_extend, METH_VARARGS|METH_KEYWORDS,
		"add faces to mesh"},
	{"delete", (PyCFunction)MFaceSeq_delete, METH_VARARGS,
		"delete faces from mesh"},
	{"sort", (PyCFunction)MFaceSeq_sort, METH_VARARGS|METH_KEYWORDS,
		"sort the faces using list sorts syntax"},
	{"selected", (PyCFunction)MFaceSeq_selected, METH_NOARGS,
		"returns a list containing indices of selected faces"},
	{"addPropertyLayer",(PyCFunction)MFaceSeq_add_layertype, METH_VARARGS,
		"add a new property layer"},
	{"removePropertyLayer",(PyCFunction)MFaceSeq_del_layertype, METH_O,
		"removes a property layer"},
	{"renamePropertyLayer",(PyCFunction)MFaceSeq_rename_layertype, METH_VARARGS,
		"renames an existing property layer"},		
	{NULL, NULL, 0, NULL}
};
static PyGetSetDef BPy_MFaceSeq_getseters[] = {
	{"properties",
	(getter)MFaceSeq_PropertyList, (setter)NULL,
	"vertex property layers, read only",
	NULL},
	{NULL,NULL,NULL,NULL,NULL}  /* Sentinel */
};


/************************************************************************
 *
 * Python MFaceSeq_Type standard operations
 *
 ************************************************************************/

/*****************************************************************************/
/* Python MFaceSeq_Type structure definition:                               */
/*****************************************************************************/
PyTypeObject MFaceSeq_Type = {
	PyObject_HEAD_INIT( NULL )  /* required py macro */
	0,                          /* ob_size */
	/*  For printing, in format "<module>.<name>" */
	"Blender MFaceSeq",           /* char *tp_name; */
	sizeof( BPy_MFaceSeq ),       /* int tp_basicsize; */
	0,                          /* tp_itemsize;  For allocation */

	/* Methods to implement standard operations */

	NULL,						/* destructor tp_dealloc; */
	NULL,                       /* printfunc tp_print; */
	NULL,                       /* getattrfunc tp_getattr; */
	NULL,                       /* setattrfunc tp_setattr; */
	NULL,                       /* cmpfunc tp_compare; */
	NULL,                       /* reprfunc tp_repr; */

	/* Method suites for standard classes */

	NULL,                       /* PyNumberMethods *tp_as_number; */
	&MFaceSeq_as_sequence,	    /* PySequenceMethods *tp_as_sequence; */
	NULL,                       /* PyMappingMethods *tp_as_mapping; */

	/* More standard operations (here for binary compatibility) */

	NULL,                       /* hashfunc tp_hash; */
	NULL,                       /* ternaryfunc tp_call; */
	NULL,                       /* reprfunc tp_str; */
	NULL,                       /* getattrofunc tp_getattro; */
	NULL,                       /* setattrofunc tp_setattro; */

	/* Functions to access object as input/output buffer */
	NULL,                       /* PyBufferProcs *tp_as_buffer; */

  /*** Flags to define presence of optional/expanded features ***/
	Py_TPFLAGS_DEFAULT,         /* long tp_flags; */

	NULL,                       /*  char *tp_doc;  Documentation string */
  /*** Assigned meaning in release 2.0 ***/
	/* call function for all accessible objects */
	NULL,                       /* traverseproc tp_traverse; */

	/* delete references to contained objects */
	NULL,                       /* inquiry tp_clear; */

  /***  Assigned meaning in release 2.1 ***/
  /*** rich comparisons ***/
	NULL,                       /* richcmpfunc tp_richcompare; */

  /***  weak reference enabler ***/
	0,                          /* long tp_weaklistoffset; */

  /*** Added in release 2.2 ***/
	/*   Iterators */
	( getiterfunc )MFaceSeq_getIter, /* getiterfunc tp_iter; */
	( iternextfunc )MFaceSeq_nextIter, /* iternextfunc tp_iternext; */

  /*** Attribute descriptor and subclassing stuff ***/
	BPy_MFaceSeq_methods,       /* struct PyMethodDef *tp_methods; */
	NULL,                       /* struct PyMemberDef *tp_members; */
	BPy_MFaceSeq_getseters,     /* struct PyGetSetDef *tp_getset; */
	NULL,                       /* struct _typeobject *tp_base; */
	NULL,                       /* PyObject *tp_dict; */
	NULL,                       /* descrgetfunc tp_descr_get; */
	NULL,                       /* descrsetfunc tp_descr_set; */
	0,                          /* long tp_dictoffset; */
	NULL,                       /* initproc tp_init; */
	NULL,                       /* allocfunc tp_alloc; */
	NULL,                       /* newfunc tp_new; */
	/*  Low-level free-memory routine */
	NULL,                       /* freefunc tp_free;  */
	/* For PyObject_IS_GC */
	NULL,                       /* inquiry tp_is_gc;  */
	NULL,                       /* PyObject *tp_bases; */
	/* method resolution order */
	NULL,                       /* PyObject *tp_mro;  */
	NULL,                       /* PyObject *tp_cache; */
	NULL,                       /* PyObject *tp_subclasses; */
	NULL,                       /* PyObject *tp_weaklist; */
	NULL
};

/************************************************************************
 *
 * Python BPy_Mesh methods
 *
 ************************************************************************/

static PyObject *Mesh_calcNormals( BPy_Mesh * self )
{
	Mesh *mesh = self->mesh;

	mesh_calc_normals( mesh->mvert, mesh->totvert, mesh->mface,
			mesh->totface, NULL );
	Py_RETURN_NONE;
}

static PyObject *Mesh_vertexShade( BPy_Mesh * self )
{
	Base *base = FIRSTBASE;

	if( G.obedit )
		return EXPP_ReturnPyObjError(PyExc_RuntimeError,
				"can't shade vertices while in edit mode" );

	while( base ) {
		if( base->object->type == OB_MESH && 
				base->object->data == self->mesh ) {
			base->flag |= SELECT;
			set_active_base( base );
			make_vertexcol(1);
			countall();
			Py_RETURN_NONE;
		}
		base = base->next;
	}
	return EXPP_ReturnPyObjError(PyExc_RuntimeError,
			"object not found in baselist!" );
}

/*
 * force display list update
 */

static PyObject *Mesh_Update( BPy_Mesh * self, PyObject *args, PyObject *kwd )
{

	char *blockname= NULL;
	static char *kwlist[] = {"key", NULL};
	
	if( !PyArg_ParseTupleAndKeywords(args, kwd, "|s", kwlist, &blockname) )
	return EXPP_ReturnPyObjError( PyExc_TypeError,
			"Expected nothing or the name of a shapeKey");
	
	if (blockname) {
		Mesh *me = self->mesh;
		MVert *mv = me->mvert;  
		Key *key= me->key;
		KeyBlock *kb;
		float (*co)[3];
		int i;
		
		if (!key)
			return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"Cannot update the key for this mesh, it has no shape keys");
		
		for (kb = key->block.first; kb; kb=kb->next)
			if (strcmp(blockname, kb->name)==0)
				break;
		
		if (!kb)
			return EXPP_ReturnPyObjError( PyExc_ValueError,
				"This requested key to update does not exist");
		
		for(i=0, co= kb->data; i<me->totvert; i++, mv++, co++)
			VECCOPY(*co, mv->co);
	} else {
		/* Normal operation */
		mesh_update( self->mesh );
	}
	Py_RETURN_NONE;
}

/*
 * 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;
	}
	Py_RETURN_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_Check( v1 ) && PyInt_Check( 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 = NULL;
	PyObject *object_arg;
	ID tmpid;
	Mesh *tmpmesh;
	Curve *tmpcu = NULL;
	DerivedMesh *dm;
	Object *tmpobj = NULL;
	int cage = 0, render = 0, i;

	if( !PyArg_ParseTuple( args, "O|ii", &object_arg, &cage, &render ) )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
				"expected object or string and optional integer arguments" );
	
	if ( PyString_Check( object_arg ) ) {
		char *name;
		name = PyString_AsString ( object_arg );
		ob = ( Object * ) GetIdFromList( &( G.main->object ), name );
		if( !ob )
			return EXPP_ReturnPyObjError( PyExc_AttributeError, name );
	} else if ( BPy_Object_Check(object_arg) ) {
		ob = (( BPy_Object * ) object_arg)->object;
	} else {
		return EXPP_ReturnPyObjError( PyExc_TypeError,
				"expected object or string and optional integer arguments" );
	}
	
	if( cage != 0 && cage != 1 )
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"cage value must be 0 or 1" );

	

	/* perform the mesh extraction based on type */
 	switch (ob->type) {
 	case OB_FONT:
 	case OB_CURVE:
 	case OB_SURF:
		/* copies object and modifiers (but not the data) */
		tmpobj= copy_object( ob );
		tmpcu = (Curve *)tmpobj->data;
		tmpcu->id.us--;

		/* if getting the original caged mesh, delete object modifiers */
		if( cage )
			object_free_modifiers(tmpobj);

		/* copies the data */
		tmpobj->data = copy_curve( (Curve *) ob->data );

#if 0
		/* copy_curve() sets disp.first null, so currently not need */
		{
			Curve *cu;
			cu = (Curve *)tmpobj->data;
			if( cu->disp.first )
				MEM_freeN( cu->disp.first );
			cu->disp.first = NULL;
		}
	
#endif

		/* get updated display list, and convert to a mesh */
		makeDispListCurveTypes( tmpobj, 0 );
		nurbs_to_mesh( tmpobj );
		
		/* nurbs_to_mesh changes the type tp a mesh, check it worked */
		if (tmpobj->type != OB_MESH) {
			free_libblock_us( &G.main->object, tmpobj );
			return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"cant convert curve to mesh. Does the curve have any segments?" );
		}
		tmpmesh = tmpobj->data;
		free_libblock_us( &G.main->object, tmpobj );
		break;
 	case OB_MBALL:
		/* metaballs don't have modifiers, so just convert to mesh */
		ob = find_basis_mball( ob );
		tmpmesh = add_mesh("Mesh");
		mball_to_mesh( &ob->disp, tmpmesh );

 		break;
 	case OB_MESH:
		/* copies object and modifiers (but not the data) */
		if (cage) {
			/* copies the data */
			tmpmesh = copy_mesh( ob->data );
		/* if not getting the original caged mesh, get final derived mesh */
		} else {
			/* Make a dummy mesh, saves copying */
			
			/* Write the display mesh into the dummy mesh */
			if (render)
				dm = mesh_create_derived_render( ob, CD_MASK_MESH );
			else
				dm = mesh_create_derived_view( ob, CD_MASK_MESH );
			
			tmpmesh = add_mesh( "Mesh" );
			DM_to_mesh( dm, tmpmesh );
			dm->release( dm );
		}
		
		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;
	
	/* if mesh has keys, make sure they point back to this mesh */
	if( self->mesh->key )
		self->mesh->key->from = (ID *)self->mesh;
	
	
	
	/* Copy materials to new object */
	switch (ob->type) {
	case OB_SURF:
		self->mesh->totcol = tmpcu->totcol;		
		
		/* free old material list (if it exists) and adjust user counts */
		if( tmpcu->mat ) {
			for( i = tmpcu->totcol; i-- > 0; ) {
				
				/* are we an object material or data based? */
				if (ob->colbits & 1<<i) {
					self->mesh->mat[i] = ob->mat[i];
					ob->mat[i]->id.us++;
					tmpmesh->mat[i]->id.us--;
				} else {
					self->mesh->mat[i] = tmpcu->mat[i];
					if (self->mesh->mat[i]) {
						tmpmesh->mat[i]->id.us++;
					}
				}
			}
		}
		break;

#if 0
	/* Crashes when assigning the new material, not sure why */
	case OB_MBALL:
		tmpmb = (MetaBall *)ob->data;
		self->mesh->totcol = tmpmb->totcol;
		
		/* free old material list (if it exists) and adjust user counts */
		if( tmpmb->mat ) {
			for( i = tmpmb->totcol; i-- > 0; ) {
				self->mesh->mat[i] = tmpmb->mat[i]; /* CRASH HERE ??? */
				if (self->mesh->mat[i]) {
					tmpmb->mat[i]->id.us++;
				}
			}
		}
		break;
#endif

	case OB_MESH:
		if (!cage) {
			Mesh *origmesh= ob->data;
			self->mesh->flag= origmesh->flag;
			self->mesh->mat = MEM_dupallocN(origmesh->mat);
			self->mesh->totcol = origmesh->totcol;
			self->mesh->smoothresh= origmesh->smoothresh;
			if( origmesh->mat ) {
				for( i = origmesh->totcol; i-- > 0; ) {
					/* are we an object material or data based? */
					if (ob->colbits & 1<<i) {
						self->mesh->mat[i] = ob->mat[i];
						
						if (ob->mat[i])
							ob->mat[i]->id.us++;
						if (origmesh->mat[i])
							origmesh->mat[i]->id.us--;
					} else {
						self->mesh->mat[i] = origmesh->mat[i];
						
						if (origmesh->mat[i])
							origmesh->mat[i]->id.us++;
					}
				}
			}
		}
		break;
	} /* end copy materials */
	
	
	
	/* remove the temporary mesh */
	BLI_remlink( &G.main->mesh, tmpmesh );
	MEM_freeN( tmpmesh );

	/* make sure materials get updated in objects */
	test_object_materials( ( ID * ) self->mesh );

	mesh_update( self->mesh );
	Py_RETURN_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, PyObject *kwd )
{
	Mesh *mesh = self->mesh;
	MVert *mvert;
	/*PyObject *pymat = NULL;*/
	MatrixObject *bpymat=NULL;
	int i, recalc_normals = 0, selected_only = 0;

	static char *kwlist[] = {"matrix", "recalc_normals", "selected_only", NULL};
	
	if( !PyArg_ParseTupleAndKeywords(args, kwd, "|O!ii", kwlist,
				 &matrix_Type, &bpymat, &recalc_normals, &selected_only) ) {
		return EXPP_ReturnPyObjError( PyExc_TypeError,
				"matrix must be a 4x4 transformation matrix\n"
				"for example as returned by object.matrixWorld\n"
				"and optionaly keyword bools, recalc_normals and selected_only\n");
	}
	
	if (!bpymat)
		return EXPP_ReturnPyObjError( PyExc_TypeError,
				"the first argument must be a matrix or\n"
				"matrix passed as a keyword argument\n");
	
	
	/*bpymat = ( MatrixObject * ) pymat;*/

	if( bpymat->colSize != 4 || bpymat->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 */
	mvert = mesh->mvert;
	if (selected_only) {
		for( i = 0; i < mesh->totvert; i++, mvert++ ) {
			if (mvert->flag & SELECT) { 
				Mat4MulVecfl( (float(*)[4])*bpymat->matrix, mvert->co );
			}
		}
	} else {
		for( i = 0; i < mesh->totvert; i++, mvert++ ) {
			Mat4MulVecfl( (float(*)[4])*bpymat->matrix, mvert->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], vec[3], nx, ny, nz;
		
		/*
		 * 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])*bpymat->matrix))
			return EXPP_ReturnPyObjError (PyExc_AttributeError,
				"given matrix is not invertible");

		/*
		 * since normal is stored as shorts, convert to float 
		 */

		mvert = mesh->mvert;
		for( i = 0; i < mesh->totvert; i++, mvert++ ) {
			nx= vec[0] = (float)(mvert->no[0] / 32767.0);
			ny= vec[1] = (float)(mvert->no[1] / 32767.0);
			nz= vec[2] = (float)(mvert->no[2] / 32767.0);
			vec[0] = nx*invmat[0][0] + ny*invmat[0][1] + nz*invmat[0][2];
			vec[1] = nx*invmat[1][0] + ny*invmat[1][1] + nz*invmat[1][2]; 
			vec[2] = nx*invmat[2][0] + ny*invmat[2][1] + nz*invmat[2][2];
			Normalize( vec );
			mvert->no[0] = (short)(vec[0] * 32767.0);
			mvert->no[1] = (short)(vec[1] * 32767.0);
			mvert->no[2] = (short)(vec[2] * 32767.0);
		}
	}

	Py_RETURN_NONE;
}

static PyObject *Mesh_addVertGroup( PyObject * self, PyObject * value )
{
	char *groupStr = PyString_AsString(value);
	struct Object *object;

	if( !groupStr )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
					      "expected string argument" );

	object = ( ( BPy_Mesh * ) self )->object;
	
	if( object == NULL )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "mesh not linked to an object" );

	/* add_defgroup_name clamps the name to 32, make sure that dosnt change  */
	add_defgroup_name( object, groupStr );

	EXPP_allqueue( REDRAWBUTSALL, 1 );

	Py_RETURN_NONE;
}

static PyObject *Mesh_removeVertGroup( PyObject * self, PyObject * value )
{
	char *groupStr = PyString_AsString(value);
	struct Object *object;
	int nIndex;
	bDeformGroup *pGroup;

	if( G.obedit )
		return EXPP_ReturnPyObjError(PyExc_RuntimeError,
			"can't use removeVertGroup() while in edit mode" );
	
	if( !groupStr )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
					      "expected string argument" );

	if( ( ( BPy_Mesh * ) self )->object == NULL )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "mesh must be linked to an object first..." );

	object = ( ( BPy_Mesh * ) self )->object;

	pGroup = get_named_vertexgroup( object, groupStr );
	if( pGroup == NULL )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "group does not exist!" );

	nIndex = get_defgroup_num( object, pGroup );
	if( nIndex == -1 )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "no deform groups assigned to mesh" );
	nIndex++;
	object->actdef = (unsigned short)nIndex;

	del_defgroup_in_object_mode( object );
	
	EXPP_allqueue( REDRAWBUTSALL, 1 );

	Py_RETURN_NONE;
}

extern void add_vert_defnr( Object * ob, int def_nr, int vertnum, float weight,
		             int assignmode );
extern void remove_vert_def_nr (Object *ob, int def_nr, int vertnum);

static PyObject *Mesh_assignVertsToGroup( BPy_Mesh * self, PyObject * args )
{
	char *groupStr;
	int nIndex;
	bDeformGroup *pGroup;
	PyObject *listObject;
	int tempInt;
	int x;
	int assignmode = WEIGHT_REPLACE;
	float weight = 1.0;
	Object *object = self->object;
	Mesh *mesh = self->mesh;

	if( !object )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "mesh must be linked to an object first" );

	if( ((Mesh *)object->data) != mesh )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "object no longer linked to this mesh" );

	if( !PyArg_ParseTuple ( args, "sO!fi", &groupStr, &PyList_Type,
			&listObject, &weight, &assignmode) ) {
		return EXPP_ReturnPyObjError( PyExc_TypeError,
					      "expected string, list, float, int arguments" );
	}

	pGroup = get_named_vertexgroup( object, groupStr );
	if( pGroup == NULL )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "group does not exist!" );

	nIndex = get_defgroup_num( object, pGroup );
	if( nIndex == -1 )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "no deform groups assigned to mesh" );


	if( assignmode != WEIGHT_REPLACE && assignmode != WEIGHT_ADD &&
			assignmode != WEIGHT_SUBTRACT )
		return EXPP_ReturnPyObjError( PyExc_ValueError,
					      "bad assignment mode" );

	/* makes a set of dVerts corresponding to the mVerts */
	if( !mesh->dvert ) 
		create_dverts( &mesh->id );

	/* loop list adding verts to group */
	for( x = 0; x < PyList_Size( listObject ); x++ ) {
		if( !PyArg_Parse ( PyList_GetItem( listObject, x ), "i", &tempInt ) )
			return EXPP_ReturnPyObjError( PyExc_TypeError,
						      "python list integer not parseable" );

		if( tempInt < 0 || tempInt >= mesh->totvert )
			return EXPP_ReturnPyObjError( PyExc_ValueError,
						      "bad vertex index in list" );

		add_vert_defnr( object, nIndex, tempInt, weight, assignmode );
	}

	Py_RETURN_NONE;
}

static PyObject *Mesh_removeVertsFromGroup( BPy_Mesh * self, PyObject * args )
{
	/* not passing a list will remove all verts from group */

	char *groupStr;
	int nIndex;
	Object *object;
	Mesh *mesh;
	bDeformGroup *pGroup;
	PyObject *listObject = NULL;
	int tempInt;
	int i;

	object = self->object;
	mesh = self->mesh;
	if( !object )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "mesh must be linked to an object first" );

	if( ((Mesh *)object->data) != mesh )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "object no longer linked to this mesh" );

	if( !PyArg_ParseTuple
	    ( args, "s|O!", &groupStr, &PyList_Type, &listObject ) )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
					      "expected string and optional list argument" );

	if( !mesh->dvert )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
					      "this mesh contains no deform vertices" );

	pGroup = get_named_vertexgroup( object, groupStr );
	if( pGroup == NULL )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
					      "group does not exist!" );

	nIndex = get_defgroup_num( object, pGroup );
	if( nIndex == -1 )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
					      "no deform groups assigned to mesh" );

	/* get out of edit mode */

	if( G.obedit ) {
		load_editMesh();
		free_editMesh(G.editMesh);
		G.obedit = NULL;
	}

	if( !listObject ) /* no list given */
		for( i = 0; i < mesh->totvert; i++ )
			remove_vert_def_nr( object, nIndex, i );
	else		 /* loop list removing verts to group */
		for( i = 0; i < PyList_Size( listObject ); i++ ) {
			if( !PyArg_Parse( PyList_GetItem( listObject, i ), "i", &tempInt ) )
				return EXPP_ReturnPyObjError( PyExc_TypeError,
							      "python list integer not parseable" );

			if( tempInt < 0 || tempInt >= mesh->totvert )
				return EXPP_ReturnPyObjError( PyExc_ValueError,
							      "bad vertex index in list" );

			remove_vert_def_nr( object, nIndex, tempInt );
		}

	Py_RETURN_NONE;
}

static PyObject *Mesh_getVertsFromGroup( BPy_Mesh* self, PyObject * args )
{
	/*
	 * not passing a list will return all verts from group
	 * passing indecies not part of the group will not return data in pyList
	 * can be used as a index/group check for a vertex
	 */

	char *groupStr;
	int nIndex;
	bDeformGroup *pGroup;
	MDeformVert *dvert;
	int i, k, count;
	PyObject *vertexList;
	Object *object;
	Mesh *mesh;

	int num = 0;
	int weightRet = 0;
	PyObject *listObject = NULL;

	object = self->object;
	mesh = self->mesh;
	if( !object )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "mesh must be linked to an object first" );

	if( ((Mesh *)object->data) != mesh )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "object no longer linked to this mesh" );

	if( !PyArg_ParseTuple( args, "s|iO!", &groupStr, &weightRet,
			       &PyList_Type, &listObject ) )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
					      "expected string and optional int and list arguments" );

	if( weightRet < 0 || weightRet > 1 )
		return EXPP_ReturnPyObjError( PyExc_ValueError,
					      "return weights flag must be 0 or 1" );

	if( !mesh->dvert )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "this mesh contains no deform vertices" );

	pGroup = get_named_vertexgroup( object, groupStr );
	if( !pGroup )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "group does not exist!" );

	nIndex = get_defgroup_num( object, pGroup );
	if( nIndex == -1 )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "no deform groups assigned to mesh" );

	count = 0;

	if( !listObject ) {	/* do entire group */
		vertexList = PyList_New( mesh->totvert );
		if( !vertexList )
			return EXPP_ReturnPyObjError( PyExc_RuntimeError,
							  "getVertsFromGroup: can't create pylist!" );
		
		dvert = mesh->dvert;
		for( num = 0; num < mesh->totvert; num++, ++dvert ) {
			for( i = 0; i < dvert->totweight; i++ ) {
				if( dvert->dw[i].def_nr == nIndex ) {
					PyObject *attr;
					if( weightRet )
						attr = Py_BuildValue( "(i,f)", num,
								dvert->dw[i].weight );
					else
						attr = PyInt_FromLong ( num );
					PyList_SetItem( vertexList, count, attr );
					count++;
				}
			}
		}
		
		if (count < mesh->totvert)
			PyList_SetSlice(vertexList, count, mesh->totvert, NULL);
		
	} else {			/* do individual vertices */
		int listObjectLen = PyList_Size( listObject );
		
		vertexList = PyList_New( listObjectLen );
		for( i = 0; i < listObjectLen; i++ ) {
			PyObject *attr = NULL;

			num = PyInt_AsLong( PyList_GetItem( listObject, i ) );
			if (num == -1) {/* -1 is an error AND an invalid range, we dont care which */
				Py_DECREF(vertexList);
				return EXPP_ReturnPyObjError( PyExc_TypeError,
							      "python list integer not parseable" );
			}

			if( num < 0 || num >= mesh->totvert ) {
				Py_DECREF(vertexList);
				return EXPP_ReturnPyObjError( PyExc_ValueError,
							      "bad vertex index in list" );
			}
			dvert = mesh->dvert + num;
			for( k = 0; k < dvert->totweight; k++ ) {
				if( dvert->dw[k].def_nr == nIndex ) {
					if( weightRet )
						attr = Py_BuildValue( "(i,f)", num,
								dvert->dw[k].weight );
					else
						attr = PyInt_FromLong ( num );
					PyList_SetItem( vertexList, count, attr );
					count++;
				}
			}
		}
		if (count < listObjectLen)
			PyList_SetSlice(vertexList, count, listObjectLen, NULL);
	}
	
	return vertexList;
}

static PyObject *Mesh_renameVertGroup( BPy_Mesh * self, PyObject * args )
{
	char *oldGr = NULL;
	char *newGr = NULL;
	bDeformGroup *defGroup;
	Object *object;
	Mesh *mesh;

	object = self->object;
	mesh = self->mesh;
	if( !object )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "mesh must be linked to an object first" );

	if( ((Mesh *)object->data) != mesh )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "object no longer linked to this mesh" );

	if( !PyArg_ParseTuple( args, "ss", &oldGr, &newGr ) )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
					      "expected two string arguments" );

	defGroup = get_named_vertexgroup( object, oldGr );
	if( !defGroup )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
					      "couldn't find the vertex group" );

	PyOS_snprintf( defGroup->name, 32, newGr );
	unique_vertexgroup_name( defGroup, object );

	Py_RETURN_NONE;
}




static PyObject *Mesh_getVertGroupNames( BPy_Mesh * self )
{
	bDeformGroup *defGroup;
	PyObject *list;
	Object *obj = self->object;
	Mesh *mesh = self->mesh;
	int count;

	if( !obj )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "mesh must be linked to an object first" );

	if( ((Mesh *)obj->data) != mesh )
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "object no longer linked to this mesh" );

	if( !obj )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
					      "This mesh must be linked to an object" );

	count = 0;
	for( defGroup = obj->defbase.first; defGroup; defGroup = defGroup->next )
		++count;

	list = PyList_New( count );
	count = 0;
	for( defGroup = obj->defbase.first; defGroup; defGroup = defGroup->next )
		PyList_SET_ITEM( list, count++,
				PyString_FromString( defGroup->name ) );

	return list;
}

static PyObject *Mesh_getVertexInfluences( BPy_Mesh * self, PyObject * args )
{
	int index;
	PyObject *influence_list = NULL;
	Object *object = self->object;
	Mesh *me = self->mesh;

	/* Get a reference to the mesh object wrapped in here. */
	if( !object )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"This mesh must be linked to an object" ); 

	/* Parse the parameters: only on integer (vertex index) */
	if( !PyArg_ParseTuple( args, "i", &index ) )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
				"expected int argument (index of the vertex)" );

	/* check for valid index */
	if( index < 0 || index >= me->totvert )
		return EXPP_ReturnPyObjError( PyExc_IndexError,
				"vertex index out of range" );

	influence_list = PyList_New( 0 );

	/* Proceed only if we have vertex deformation information */
	if( me->dvert ) {
		int i;
		MDeformWeight *sweight = NULL;

		/* Number of bones influencing the vertex */
		int totinfluences = me->dvert[index].totweight;

		/* Get the reference of the first weight structure */
		sweight = me->dvert[index].dw;

		/* Build the list only with weights and names of the influent bones */
		for( i = 0; i < totinfluences; i++, sweight++ ) {
			bDeformGroup *defgroup = BLI_findlink( &object->defbase,
					sweight->def_nr );
			if( defgroup )
				PyList_Append( influence_list, Py_BuildValue( "[sf]",
						defgroup->name, sweight->weight ) ); 
		}
	}

	return influence_list;
}

static PyObject *Mesh_removeAllKeys( BPy_Mesh * self )
{
	Mesh *mesh = self->mesh;
	
	if( !mesh || !mesh->key )
		Py_RETURN_FALSE;

	mesh->key->id.us--;
	mesh->key = NULL;
	
	Py_RETURN_TRUE;
}


static PyObject *Mesh_insertKey( BPy_Mesh * self, PyObject * args )
{
	Mesh *mesh = self->mesh;
	int fra = -1, oldfra = -1;
	char *type = NULL;
	short typenum;
	
	if (mesh->mr)
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
					      "Shape Keys cannot be added to meshes with multires" );
	
	if( !PyArg_ParseTuple( args, "|is", &fra, &type ) )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
					      "expected nothing or an int and optionally a string as arguments" );
	
	if( !type || !strcmp( type, "relative" ) )
		typenum = 1;
	else if( !strcmp( type, "absolute" ) )
		typenum = 2;
	else
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "if given, type should be 'relative' or 'absolute'" );
	
	if( fra > 0 ) {
		fra = EXPP_ClampInt( fra, 1, MAXFRAME );
		oldfra = G.scene->r.cfra;
		G.scene->r.cfra = fra;
	}

	insert_meshkey( mesh, typenum );
	allspace(REMAKEIPO, 0);
	
	if( fra > 0 )
		G.scene->r.cfra = oldfra;
	
	Py_RETURN_NONE;
}




/* Custom Data Layers */

static PyObject * Mesh_addCustomLayer_internal(Mesh *me, PyObject * args, int type)
{
	char *name = NULL;
	CustomData *data = &me->fdata;
	if( !PyArg_ParseTuple( args, "|s", &name ) )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
					      "expected a string or nothing" );
	
	if (strlen(name)>31)
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"error, maximum name length is 31" );
	
	if (name)
		CustomData_add_layer_named(data, type, CD_DEFAULT,
											NULL, me->totface, name);
	else
		CustomData_add_layer(data, type, CD_DEFAULT,
											NULL, me->totface);
	mesh_update_customdata_pointers(me);
	Py_RETURN_NONE;
}

static PyObject *Mesh_addUVLayer( BPy_Mesh * self, PyObject * args )
{
	return Mesh_addCustomLayer_internal(self->mesh, args, CD_MTFACE);
}

static PyObject *Mesh_addColorLayer( BPy_Mesh * self, PyObject * args )
{
	return Mesh_addCustomLayer_internal(self->mesh, args, CD_MCOL);
}

static PyObject *Mesh_removeLayer_internal( BPy_Mesh * self, PyObject * value, int type )
{
	Mesh *me = self->mesh;
	CustomData *data = &me->fdata;
	char *name = PyString_AsString(value);
	int i;
	
	if( !name )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
					      "expected string argument" );
	
	if (strlen(name)>31)
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"error, maximum name length is 31" );
	
	i = CustomData_get_named_layer_index(data, type, name);
	
	if (i==-1)
		return EXPP_ReturnPyObjError(PyExc_ValueError,
			"No matching layers to remove" );	
				
	CustomData_free_layer(data, type, me->totface, i);
	mesh_update_customdata_pointers(me);
	
	/*	No more Color or UV layers left ?
		switch modes if this is the active object */	
	if (!CustomData_has_layer(data, type)) {
		if (me == get_mesh(OBACT)) {
			if(type == CD_MCOL && (G.f & G_VERTEXPAINT))
				G.f &= ~G_VERTEXPAINT; /* get out of vertexpaint mode */
			if(type == CD_MTFACE && (G.f & G_FACESELECT))
				G.f |= ~G_FACESELECT; /* get out of faceselect mode */
		}
	}
	
	Py_RETURN_NONE;
}


static PyObject *Mesh_removeUVLayer( BPy_Mesh * self, PyObject * value )
{
	return Mesh_removeLayer_internal(self, value, CD_MTFACE);
}

static PyObject *Mesh_removeColorLayer( BPy_Mesh * self, PyObject * value )
{
	return Mesh_removeLayer_internal(self, value, CD_MCOL);
}


static PyObject *Mesh_renameLayer_internal( BPy_Mesh * self, PyObject * args, int type )
{
	CustomData *data;
	CustomDataLayer *layer;
	Mesh *mesh = self->mesh;
	int i;
	char *name_from, *name_to;
	
	data = &mesh->fdata;
	
	if( !PyArg_ParseTuple( args, "ss", &name_from, &name_to ) )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
				"expected 2 strings" );
	
	if (strlen(name_from)>31 || strlen(name_to)>31)
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"error, maximum name length is 31" );
	
	i = CustomData_get_named_layer_index(data, type, name_from);
	
	if (i==-1)
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"layer name was not found" );	
	
	layer = &data->layers[i];
	strcpy(layer->name, name_to); /* we alredy know the string sizes are under 32 */
	CustomData_set_layer_unique_name(data, i);
	Py_RETURN_NONE;
}

static PyObject *Mesh_renameUVLayer( BPy_Mesh * self, PyObject * args )
{
	return Mesh_renameLayer_internal( self, args, CD_MTFACE );
}

static PyObject *Mesh_renameColorLayer( BPy_Mesh * self, PyObject * args )
{
	return Mesh_renameLayer_internal( self, args, CD_MCOL );
}


static PyObject *Mesh_getLayerNames_internal( BPy_Mesh * self, int type )
{
	CustomData *data;
	CustomDataLayer *layer;
	PyObject *str, *list = PyList_New( 0 );
	Mesh *mesh = self->mesh;
	int i;
	data = &mesh->fdata;
	
	/* see if there is a duplicate */
	for(i=0; i<data->totlayer; i++) {
		layer = &data->layers[i];
		if(layer->type == type) {
			str = PyString_FromString(layer->name);
			PyList_Append( list, str );
			Py_DECREF(str);
		}
	}
	return list;
}

static PyObject *Mesh_getUVLayerNames( BPy_Mesh * self )
{
	return Mesh_getLayerNames_internal(self, CD_MTFACE);
}

static PyObject *Mesh_getColorLayerNames( BPy_Mesh * self )
{
	return Mesh_getLayerNames_internal(self, CD_MCOL);
}
/* used by activeUVLayer and activeColorLayer attrs */
static PyObject *Mesh_getActiveLayer( BPy_Mesh * self, void *type )
{
	CustomData *data = &self->mesh->fdata;
	int layer_type = GET_INT_FROM_POINTER(type);
	int i;
	if (layer_type < 0) { /* hack, if negative, its the renderlayer.*/
		layer_type = -layer_type;
		i = CustomData_get_render_layer_index(data, layer_type);
	} else {
		i = CustomData_get_active_layer_index(data, layer_type);
	}
	if (i == -1) /* so -1 is for no active layer 0+ for an active layer */
		Py_RETURN_NONE;
	else {
		return PyString_FromString( data->layers[i].name);
	}
}

static int Mesh_setActiveLayer( BPy_Mesh * self, PyObject * value, void *type )
{
	CustomData *data = &self->mesh->fdata;
	char *name;
	int i,ok,n,layer_type = GET_INT_FROM_POINTER(type), render=0;
	
	if( !PyString_Check( value ) )
		return EXPP_ReturnIntError( PyExc_ValueError,
				"expected a string argument" );

	if (layer_type<0) {
		layer_type = -layer_type;
		render = 1;
	}

	name = PyString_AsString( value );
	ok = 0;
	n = 0;
	for(i=0; i < data->totlayer; ++i) {
		if(data->layers[i].type == layer_type) {
			if (strcmp(data->layers[i].name, name)==0) {
				ok = 1;
				break;
			}
			n++;
		}
	}
	
	if (!ok)
		return EXPP_ReturnIntError( PyExc_ValueError,
				"layer name does not exist" );
	if (render) {
		CustomData_set_layer_render(data, layer_type, n);
	} else {
		CustomData_set_layer_active(data, layer_type, n);
		mesh_update_customdata_pointers(self->mesh);
	}
	return 0;
}


/* multires */
static PyObject *Mesh_getMultiresLevelCount( BPy_Mesh * self )
{
	int i;
	if (!self->mesh->mr)
		i=0;
	else
		i= self->mesh->mr->level_count;
	
	return PyInt_FromLong(i);
}


static PyObject *Mesh_getMultires( BPy_Mesh * self, void *type )
{	
	int i=0;
	if (self->mesh->mr) {
		switch (GET_INT_FROM_POINTER(type)) {
		case MESH_MULTIRES_LEVEL:
			i = self->mesh->mr->newlvl;
			break;
		case MESH_MULTIRES_EDGE:
			i = self->mesh->mr->edgelvl;
			break;
		case MESH_MULTIRES_PIN:
			i = self->mesh->mr->pinlvl;
			break;
		case MESH_MULTIRES_RENDER:
			i = self->mesh->mr->renderlvl;
			break;
		}
	}
	
	return PyInt_FromLong(i);
}

static int Mesh_setMultires( BPy_Mesh * self, PyObject *value, void *type )
{
	int i;
	if( !PyInt_Check( value ) )
		return EXPP_ReturnIntError( PyExc_TypeError,
					"expected integer argument" );
	
	if (!self->object)
		return EXPP_ReturnIntError( PyExc_RuntimeError,
			"This mesh must be linked to an object" ); 
	
	if (!self->mesh->mr)
		return EXPP_ReturnIntError( PyExc_RuntimeError,
					"the mesh has no multires data" );
	
	if (!self->mesh->mr->level_count)
		return EXPP_ReturnIntError( PyExc_RuntimeError,
					"multires data has no levels added" );
	
	i = PyInt_AsLong(value);
	
	if (i<1||i>self->mesh->mr->level_count)
		return EXPP_ReturnIntError( PyExc_TypeError,
					"value out of range" );
	
	switch (GET_INT_FROM_POINTER(type)) {
	case MESH_MULTIRES_LEVEL:
		self->mesh->mr->newlvl = i;
		multires_set_level_cb(self->object, self->mesh);
		break;
	case MESH_MULTIRES_EDGE:
		self->mesh->mr->edgelvl = i;
		multires_edge_level_update(self->object, self->mesh);
		break;
	case MESH_MULTIRES_PIN:
		self->mesh->mr->pinlvl = i;
		break;
	case MESH_MULTIRES_RENDER:
		self->mesh->mr->renderlvl = i;
		break;
	}
	
	return 0;
}

static PyObject *Mesh_addMultiresLevel( BPy_Mesh * self, PyObject * args )
{
	char typenum;
	int i, levels = 1;
	char *type = NULL;
	if( G.obedit )
		return EXPP_ReturnPyObjError(PyExc_RuntimeError,
					"can't add multires level while in edit mode" );
	if( !PyArg_ParseTuple( args, "|is", &levels, &type ) )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
					      "expected nothing or an int and optionally a string as arguments" );
	if( !type || !strcmp( type, "catmull-clark" ) )
		typenum = 0;
	else if( !strcmp( type, "simple" ) )
		typenum = 1;
	else
		return EXPP_ReturnPyObjError( PyExc_AttributeError,
					      "if given, type should be 'catmull-clark' or 'simple'" );
	if (!self->mesh->mr)
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
					"the mesh has no multires data" );
	for( i = 0; i < levels; i++ ) {
		multires_add_level(self->object, self->mesh, typenum);
	};
	multires_update_levels(self->mesh, 0);
	multires_level_to_editmesh(self->object, self->mesh, 0);	
	multires_finish_mesh_update(self->object);
	Py_RETURN_NONE;
}

/* end multires */


static PyObject *Mesh_Tools( BPy_Mesh * self, int type, void **args )
{
	Base *base;
	int result;
	Object *object = NULL; 
	PyObject *attr = NULL;

	/* if already in edit mode, exit */

	if( G.obedit )
		return EXPP_ReturnPyObjError(PyExc_RuntimeError,
				"can't use mesh tools while in edit mode" );

	for( base = FIRSTBASE; base; base = base->next ) {
		if( base->object->type == OB_MESH && 
				base->object->data == self->mesh ) {
			object = base->object;
			break;
		}
	}
	if( !object )
		return EXPP_ReturnPyObjError(PyExc_RuntimeError,
				"can't find an object for the mesh" );

	if( object->type != OB_MESH )
		return EXPP_ReturnPyObjError( PyExc_ValueError,
						"Object specified is not a mesh." );

	/* make mesh's object active, enter mesh edit mode */
	G.obedit = object;
	make_editMesh();

	/* apply operation, then exit edit mode */
	switch( type ) {
	case MESH_TOOL_TOSPHERE:
		vertices_to_sphere();
		break;
	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, 0, *((float *)args[0]) );

		attr = PyInt_FromLong( result );
		if( !attr )
			return EXPP_ReturnPyObjError( PyExc_RuntimeError,
					"PyInt_FromLong() failed" );
		break;
	case MESH_TOOL_FILL:
		fill_mesh();
		break;
	case MESH_TOOL_RECALCNORM:
		righthandfaces( *((int *)args[0]) );
		break;
	case MESH_TOOL_TRI2QUAD:
		join_triangles();
		break;
	case MESH_TOOL_QUAD2TRI:
		convert_to_triface( *((int *)args[0]) );
		break;
	}

	/* exit edit mode, free edit mesh */
	load_editMesh();
	free_editMesh(G.editMesh);

	if(G.f & G_FACESELECT)
		EXPP_allqueue( REDRAWIMAGE, 0 );
	if(G.f & G_WEIGHTPAINT)
		mesh_octree_table(G.obedit, NULL, 'e');
	G.obedit = NULL;

	DAG_object_flush_update(G.scene, object, OB_RECALC_DATA);

	if( attr )
		return attr;

	Py_RETURN_NONE;
}

/*
 * "Subdivide" function
 */

static PyObject *Mesh_subdivide( BPy_Mesh * self, PyObject * args )
{
	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, &params );
}

/*
 * "Smooth" function
 */

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, &params );
}

/*
 * "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, &params );
}

/*
 * "Quads to Triangles"  function
 */

static PyObject *Mesh_quad2tri( BPy_Mesh * self, PyObject *args )
{
	int kind = 0;
	void *params = &kind;

	if( !PyArg_ParseTuple( args, "|i", &kind ) )
			return EXPP_ReturnPyObjError( PyExc_TypeError,
					"expected nothing or an int in range [0,1]" );

	if( kind < 0 || kind > 1 )
			return EXPP_ReturnPyObjError( PyExc_ValueError,
					"expected int in range [0,1]" );

	return Mesh_Tools( self, MESH_TOOL_QUAD2TRI, &params );
}

/*
 * "Triangles to Quads"  function
 */

static PyObject *Mesh_tri2quad( BPy_Mesh * self )
{
	return Mesh_Tools( self, MESH_TOOL_TRI2QUAD, NULL );
}

/*
 * "Flip normals" function
 */

static PyObject *Mesh_flipNormals( BPy_Mesh * self )
{
	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 );
}


/*
 * "pointInside" function
 */
/* Warning - this is ordered - need to test both orders to be sure */
#define SIDE_OF_LINE(pa,pb,pp)	((pa[0]-pp[0])*(pb[1]-pp[1]))-((pb[0]-pp[0])*(pa[1]-pp[1]))
#define POINT_IN_TRI(p0,p1,p2,p3)	((SIDE_OF_LINE(p1,p2,p0)>=0) && (SIDE_OF_LINE(p2,p3,p0)>=0) && (SIDE_OF_LINE(p3,p1,p0)>=0))
static short pointInside_internal(float *vec, float *v1, float *v2, float  *v3 )
{	
	float z,w1,w2,w3,wtot;
	
	
	if (vec[2] > MAX3(v1[2], v2[2], v3[2]))
		return 0;
	
	/* need to test both orders */
	if (!POINT_IN_TRI(vec, v1,v2,v3) && !POINT_IN_TRI(vec, v3,v2,v1))
		return 0;
	
	w1= AreaF2Dfl(vec, v2, v3);
	w2=	AreaF2Dfl(v1, vec, v3);
	w3=	AreaF2Dfl(v1, v2, vec);
	wtot = w1+w2+w3;
	w1/=wtot; w2/=wtot; w3/=wtot;
	z =((v1[2] * w1) +
		(v2[2] * w2) +
		(v3[2] * w3));
	
	/* only return true if the face is above vec*/
	if (vec[2] < z )
		return 1;

	return 0;
}

static PyObject *Mesh_pointInside( BPy_Mesh * self, PyObject * args, PyObject *kwd )
{
	Mesh *mesh = self->mesh;
	MFace *mf = mesh->mface;
	MVert *mvert = mesh->mvert;
	int i;
	int isect_count=0;
	int selected_only = 0;
	VectorObject *vec;
	static char *kwlist[] = {"point", "selected_only", NULL};
	
	if( !PyArg_ParseTupleAndKeywords(args, kwd, "|O!i", kwlist,
		 &vector_Type, &vec, &selected_only) ) {
			 return EXPP_ReturnPyObjError( PyExc_TypeError, "expected a vector and an optional bool argument");
	}
	
	if(vec->size < 3)
		return EXPP_ReturnPyObjError(PyExc_AttributeError, 
			"Mesh.pointInside(vec) expects a 3D vector object\n");
	
	for( i = 0; i < mesh->totface; mf++, i++ ) {
		if (!selected_only || mf->flag & ME_FACE_SEL) {
			if (pointInside_internal(vec->vec, mvert[mf->v1].co, mvert[mf->v2].co, mvert[mf->v3].co)) {
				isect_count++;
			} else if (mf->v4 && pointInside_internal(vec->vec,mvert[mf->v1].co, mvert[mf->v3].co, mvert[mf->v4].co)) {
				
				isect_count++;
			}
		}
	}
	
	if (isect_count % 2)
		Py_RETURN_TRUE;
	else
		Py_RETURN_FALSE;
}


/* This is a bit nasty, Blenders tangents are computed for rendering, and this isnt compatible with a normal Mesh
 * so we have to rewrite parts of it here, make sure these stay in sync */

static PyObject *Mesh_getTangents( BPy_Mesh * self )
{
	/* python stuff */
	PyObject *py_tanlist;
	PyObject *py_tuple;
	
	
	PyObject *py_vector;
#if 0	/* BI-TANGENT */
	PyObject *py_bivector;
	PyObject *py_pair;
	
	float no[3];
#endif
	/* mesh vars */
	Mesh *mesh = self->mesh;
	MTFace *tf = mesh->mtface;
	MFace *mf = mesh->mface;
	MVert *v1, *v2, *v3, *v4;
	int mf_vi[4];
	
	/* See convertblender.c */
	float *uv1, *uv2, *uv3, *uv4;
	float fno[3];
	float tang[3];
	float uv[4][2];
	float *vtang;
	
	float (*orco)[3] = NULL;
	
	MemArena *arena= NULL;
	VertexTangent **vtangents= NULL;
	int i, j, len;
	
	
	if(!mesh->mtface) {
		if (!self->object)
			return EXPP_ReturnPyObjError( PyExc_RuntimeError, "cannot get tangents when there are not UV's, or the mesh has no link to an object");
		
		orco = (float(*)[3])get_mesh_orco_verts(self->object);
		
		if (!orco)
			return EXPP_ReturnPyObjError( PyExc_RuntimeError, "cannot get orco's for this objects tangents");
	}
	
	/* vertex normals */
	arena= BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE);
	BLI_memarena_use_calloc(arena);
	vtangents= MEM_callocN(sizeof(VertexTangent*)*mesh->totvert, "VertexTangent");
	
	for( i = 0, tf = mesh->mtface, mf = mesh->mface; i < mesh->totface; mf++, tf++, i++ ) {
		v1 = &mesh->mvert[mf->v1];
		v2 = &mesh->mvert[mf->v2];
		v3 = &mesh->mvert[mf->v3];
		if (mf->v4) {
			v4 = &mesh->mvert[mf->v4];
			
			CalcNormFloat4( v1->co, v2->co, v3->co, v4->co, fno );
		} else {
			v4 = NULL;
			CalcNormFloat( v1->co, v2->co, v3->co, fno );
		}
		
		if(mesh->mtface) {
			uv1= tf->uv[0];
			uv2= tf->uv[1];
			uv3= tf->uv[2];
			uv4= tf->uv[3];
		} else {
			uv1= uv[0]; uv2= uv[1]; uv3= uv[2]; uv4= uv[3];
			spheremap(orco[mf->v1][0], orco[mf->v1][1], orco[mf->v1][2], &uv[0][0], &uv[0][1]);
			spheremap(orco[mf->v2][0], orco[mf->v2][1], orco[mf->v2][2], &uv[1][0], &uv[1][1]);
			spheremap(orco[mf->v3][0], orco[mf->v3][1], orco[mf->v3][2], &uv[2][0], &uv[2][1]);
			if(v4)
				spheremap(orco[mf->v4][0], orco[mf->v4][1], orco[mf->v4][2], &uv[3][0], &uv[3][1]);
		}
		
		tangent_from_uv(uv1, uv2, uv3, v1->co, v2->co, v3->co, fno, tang);
		sum_or_add_vertex_tangent(arena, &vtangents[mf->v1], tang, uv1);
		sum_or_add_vertex_tangent(arena, &vtangents[mf->v2], tang, uv2);
		sum_or_add_vertex_tangent(arena, &vtangents[mf->v3], tang, uv3);
		
		if (mf->v4) {
			v4 = &mesh->mvert[mf->v4];
			
			tangent_from_uv(uv1, uv3, uv4, v1->co, v3->co, v4->co, fno, tang);
			sum_or_add_vertex_tangent(arena, &vtangents[mf->v1], tang, uv1);
			sum_or_add_vertex_tangent(arena, &vtangents[mf->v3], tang, uv3);
			sum_or_add_vertex_tangent(arena, &vtangents[mf->v4], tang, uv4);
		}
	}
	
	
	py_tanlist = PyList_New(mesh->totface);
	
	for( i = 0, tf = mesh->mtface, mf = mesh->mface; i < mesh->totface; mf++, tf++, i++ ) {
		 
		len = mf->v4 ? 4 : 3; 
		
		if(mesh->mtface) {
			uv1= tf->uv[0];
			uv2= tf->uv[1];
			uv3= tf->uv[2];
			uv4= tf->uv[3];
		} else {
			uv1= uv[0]; uv2= uv[1]; uv3= uv[2]; uv4= uv[3];
			spheremap(orco[mf->v1][0], orco[mf->v1][1], orco[mf->v1][2], &uv[0][0], &uv[0][1]);
			spheremap(orco[mf->v2][0], orco[mf->v2][1], orco[mf->v2][2], &uv[1][0], &uv[1][1]);
			spheremap(orco[mf->v3][0], orco[mf->v3][1], orco[mf->v3][2], &uv[2][0], &uv[2][1]);
			if(len==4)
				spheremap(orco[mf->v4][0], orco[mf->v4][1], orco[mf->v4][2], &uv[3][0], &uv[3][1]);
		}
		
		mf_vi[0] = mf->v1;
		mf_vi[1] = mf->v2;
		mf_vi[2] = mf->v3;
		mf_vi[3] = mf->v4;
		
#if 0	/* BI-TANGENT */
		/* now calculate the bitangent */
		if (mf->flag & ME_SMOOTH) {
			no[0] = (float)(mesh->mvert[mf_vi[j]]->no[0] / 32767.0);
			no[1] = (float)(mesh->mvert[mf_vi[j]]->no[1] / 32767.0);
			no[2] = (float)(mesh->mvert[mf_vi[j]]->no[2] / 32767.0);
		} else {
			/* calc face normal */
			if (len==4)		CalcNormFloat4( mesh->mvert[0]->co, mesh->mvert[1]->co, mesh->mvert[2]->co, mesh->mvert[3]->co, no );
			else			CalcNormFloat4( mesh->mvert[0]->co, mesh->mvert[1]->co, mesh->mvert[2]->co, no );
		}
#endif
		
		py_tuple = PyTuple_New( len );
		
		for (j=0; j<len; j++) {
			vtang= find_vertex_tangent(vtangents[mf_vi[j]], mesh->mtface ? tf->uv[j] : uv[j]);	/* mf_vi[j] == mf->v1,   uv[j] == tf->uv[0] */
			
			py_vector = newVectorObject( vtang, 3, Py_NEW );
			Normalize(((VectorObject *)py_vector)->vec);
			
#if 0		/* BI-TANGENT */
			py_pair = PyTuple_New( 2 );
			PyTuple_SetItem( py_pair, 0, py_vector );
			PyTuple_SetItem( py_pair, 1, py_bivector );
			
			/* qdn: tangent space */
			/* copied from texture.c */
			float B[3], tv[3];
			Crossf(B, shi->vn, shi->nmaptang);	/* bitangent */
			/* transform norvec from tangent space to object surface in camera space */
			tv[0] = texres.nor[0]*shi->nmaptang[0] + texres.nor[1]*B[0] + texres.nor[2]*shi->vn[0];
			tv[1] = texres.nor[0]*shi->nmaptang[1] + texres.nor[1]*B[1] + texres.nor[2]*shi->vn[1];
			tv[2] = texres.nor[0]*shi->nmaptang[2] + texres.nor[1]*B[2] + texres.nor[2]*shi->vn[2];
			shi->vn[0]= facm*shi->vn[0] + fact*tv[0];
			shi->vn[1]= facm*shi->vn[1] + fact*tv[1];
			shi->vn[2]= facm*shi->vn[2] + fact*tv[2];				
			PyTuple_SetItem( py_tuple, j, py_pair );
#else
			PyTuple_SetItem( py_tuple, j, py_vector );
#endif
		}

		PyList_SetItem( py_tanlist, i, py_tuple );
	}
	
	BLI_memarena_free(arena);
	if (orco) MEM_freeN( orco );
	MEM_freeN( vtangents );
	
	return py_tanlist;
}

/*
 * "__copy__" return a copy of the mesh
 */

static PyObject *Mesh_copy( BPy_Mesh * self )
{
	BPy_Mesh *obj;

	obj = (BPy_Mesh *)PyObject_NEW( BPy_Mesh, &Mesh_Type );

	if( !obj )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				       "PyObject_New() failed" );
	
	obj->mesh = copy_mesh( self->mesh );
	obj->mesh->id.us= 0;
	obj->object = NULL;
	obj->new = 1;
	return (PyObject *)obj;
}


static struct PyMethodDef BPy_Mesh_methods[] = {
	{"calcNormals", (PyCFunction)Mesh_calcNormals, METH_NOARGS,
		"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_VARARGS | METH_KEYWORDS,
		"Update display lists after changes to mesh"},
	{"transform", (PyCFunction)Mesh_transform, METH_VARARGS | METH_KEYWORDS,
		"Applies a transformation matrix to mesh's vertices"},
	{"addVertGroup", (PyCFunction)Mesh_addVertGroup, METH_O,
		"Assign vertex group name to the object linked to the mesh"},
	{"removeVertGroup", (PyCFunction)Mesh_removeVertGroup, METH_O,
		"Delete vertex group name from the object linked to the mesh"},
	{"assignVertsToGroup", (PyCFunction)Mesh_assignVertsToGroup, METH_VARARGS,
		"Assigns vertices to a vertex group"},
	{"removeVertsFromGroup", (PyCFunction)Mesh_removeVertsFromGroup, METH_VARARGS,
		"Removes vertices from a vertex group"},
	{"getVertsFromGroup", (PyCFunction)Mesh_getVertsFromGroup, METH_VARARGS,
		"Get index and optional weight for vertices in vertex group"},
	{"renameVertGroup", (PyCFunction)Mesh_renameVertGroup, METH_VARARGS,
		"Rename an existing vertex group"},
	{"getVertGroupNames", (PyCFunction)Mesh_getVertGroupNames, METH_NOARGS,
		"Get names of vertex groups"},
	{"getVertexInfluences", (PyCFunction)Mesh_getVertexInfluences, METH_VARARGS,
		"Get list of the influences of bones for a given mesh vertex"},
	/* Shape Keys */
	{"removeAllKeys", (PyCFunction)Mesh_removeAllKeys, METH_NOARGS,
		"Remove all the shape keys from a mesh"},
	{"insertKey", (PyCFunction)Mesh_insertKey, METH_VARARGS,
		"(frame = None, type = 'relative') - inserts a Mesh key at the given frame"},
	/* Mesh tools */
	{"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)"},
	{"triangleToQuad", (PyCFunction)Mesh_tri2quad, METH_VARARGS,
		"Convert selected triangles to quads (experimental)"},
	{"quadToTriangle", (PyCFunction)Mesh_quad2tri, METH_VARARGS,
		"Convert selected quads to triangles (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)"},
	{"pointInside", (PyCFunction)Mesh_pointInside, METH_VARARGS|METH_KEYWORDS,
		"Recalculates inside or outside normals (experimental)"},
	{"getTangents", (PyCFunction)Mesh_getTangents, METH_VARARGS|METH_KEYWORDS,
		"Return a list of face tangents"},
		
	/* mesh custom data layers */
	{"addUVLayer", (PyCFunction)Mesh_addUVLayer, METH_VARARGS,
		"adds a UV layer to this mesh"},
	{"addColorLayer", (PyCFunction)Mesh_addColorLayer, METH_VARARGS,
		"adds a color layer to this mesh "},
	{"removeUVLayer", (PyCFunction)Mesh_removeUVLayer, METH_O,
		"removes a UV layer to this mesh"},
	{"removeColorLayer", (PyCFunction)Mesh_removeColorLayer, METH_O,
		"removes a color layer to this mesh"},
	{"getUVLayerNames", (PyCFunction)Mesh_getUVLayerNames, METH_NOARGS,
		"Get names of UV layers"},
	{"getColorLayerNames", (PyCFunction)Mesh_getColorLayerNames, METH_NOARGS,
		"Get names of Color layers"},
	{"renameUVLayer", (PyCFunction)Mesh_renameUVLayer, METH_VARARGS,
		"Rename a UV Layer"},
	{"renameColorLayer", (PyCFunction)Mesh_renameColorLayer, METH_VARARGS,
		"Rename a Color Layer"},
	/* mesh multires */
	{"addMultiresLevel", (PyCFunction)Mesh_addMultiresLevel, METH_VARARGS,
		"(levels=1, type='catmull-clark') - adds multires levels of given type"},
		
	/* python standard class functions */
	{"__copy__", (PyCFunction)Mesh_copy, METH_NOARGS,
		"Return a copy of the mesh"},
	{"copy", (PyCFunction)Mesh_copy, METH_NOARGS,
		"Return a copy of the mesh"},
	{NULL, NULL, 0, NULL}
};

/************************************************************************
 *
 * Python BPy_Mesh attributes
 *
 ************************************************************************/

static PyObject *MVertSeq_CreatePyObject( Mesh * mesh )
{
	
	BPy_MVertSeq *obj = PyObject_NEW( BPy_MVertSeq, &MVertSeq_Type);
	obj->mesh = mesh;
	
	/*
	an iter of -1 means this seq has not been used as an iterator yet
	once it is, then any other calls on getIter will return a new copy of BPy_MVertSeq
	This means you can loop do nested loops with the same iterator without worrying about
	the iter variable being used twice and messing up the loops.
	*/
	obj->iter = -1;
	return (PyObject *)obj;
}

static PyObject *Mesh_getVerts( BPy_Mesh * self )
{
	return MVertSeq_CreatePyObject(self->mesh);
}

static int Mesh_setVerts( BPy_Mesh * self, PyObject * args )
{
	MVert *dst;
	MVert *src;
	int i;
	
	/* special case if None: delete the mesh */
	if( args == NULL || args == Py_None ) {
		Mesh *me = self->mesh;
		free_mesh( me );
        me->mvert = NULL; me->medge = NULL; me->mface = NULL;
		me->mtface = NULL; me->dvert = NULL; me->mcol = NULL;
		me->msticky = NULL; me->mat = NULL; me->bb = NULL; me->mselect = NULL;
		me->totvert = me->totedge = me->totface = me->totcol = 0;
		mesh_update( me );
		return 0;
	}

	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 *MEdgeSeq_CreatePyObject( Mesh *mesh )
{
	BPy_MEdgeSeq *obj = PyObject_NEW( BPy_MEdgeSeq, &MEdgeSeq_Type);
	obj->mesh = mesh;
	obj->iter = -1; /* iterator not yet used */
	return (PyObject *)obj;
}

static PyObject *Mesh_getEdges( BPy_Mesh * self )
{
	return MEdgeSeq_CreatePyObject(self->mesh);
}

static PyObject *MFaceSeq_CreatePyObject( Mesh * mesh )
{
	BPy_MFaceSeq *obj= PyObject_NEW( BPy_MFaceSeq, &MFaceSeq_Type);
	obj->mesh = mesh;
	obj->iter = -1; /* iterator not yet used */
	return (PyObject *)obj;
}

static PyObject *Mesh_getFaces( BPy_Mesh * self )
{
	return MFaceSeq_CreatePyObject( self->mesh );
}

static PyObject *Mesh_getMaterials( BPy_Mesh *self )
{
	return EXPP_PyList_fromMaterialList( self->mesh->mat,
			self->mesh->totcol, 1 );
}

static int Mesh_setMaterials( BPy_Mesh *self, PyObject * value )
{
    Material **matlist;
	int len;

    if( !PySequence_Check( value ) ||
			!EXPP_check_sequence_consistency( value, &Material_Type ) )
        return EXPP_ReturnIntError( PyExc_TypeError,
                  "list should only contain materials or None)" );

    len = PyList_Size( value );
    if( len > 16 )
        return EXPP_ReturnIntError( PyExc_TypeError,
                          "list can't have more than 16 materials" );

	/* free old material list (if it exists) and adjust user counts */
	if( self->mesh->mat ) {
		Mesh *me = self->mesh;
		int i;
		for( i = me->totcol; i-- > 0; )
			if( me->mat[i] )
           		me->mat[i]->id.us--;
		MEM_freeN( me->mat );
	}

	/* build the new material list, increment user count, store it */

	matlist = EXPP_newMaterialList_fromPyList( value );
	EXPP_incr_mats_us( matlist, len );
	self->mesh->mat = matlist;
    	self->mesh->totcol = (short)len;

/**@ This is another ugly fix due to the weird material handling of blender.
    * it makes sure that object material lists get updated (by their length)
    * according to their data material lists, otherwise blender crashes.
    * It just stupidly runs through all objects...BAD BAD BAD.
    */

    	test_object_materials( ( ID * ) self->mesh );

	return 0;
}

static PyObject *Mesh_getMaxSmoothAngle( BPy_Mesh * self )
{
    return PyInt_FromLong( self->mesh->smoothresh );
}

static int Mesh_setMaxSmoothAngle( BPy_Mesh *self, PyObject *value )
{
    return EXPP_setIValueClamped( value, &self->mesh->smoothresh,
                            MESH_SMOOTHRESH_MIN,
                            MESH_SMOOTHRESH_MAX, 'h' );
}

static PyObject *Mesh_getSubDivLevels( BPy_Mesh * self )
{
	return  Py_BuildValue( "(h,h)",
			self->mesh->subdiv, self->mesh->subdivr );
}

static int Mesh_setSubDivLevels( BPy_Mesh *self, PyObject *value )
{
	int subdiv[2];
	int i;
	PyObject *tmp;

	if( !PyTuple_Check( value ) || PyTuple_Size( value ) != 2 )
		return EXPP_ReturnIntError( PyExc_TypeError,
						"expected (int, int) as argument" );

	for( i = 0; i < 2; i++ ) {
		tmp = PyTuple_GET_ITEM( value, i );
		if( !PyInt_Check( tmp ) )
			return EXPP_ReturnIntError ( PyExc_TypeError,
				  "expected a list [int, int] as argument" );
		subdiv[i] = EXPP_ClampInt( PyInt_AsLong( tmp ),
						 MESH_SUBDIV_MIN,
						 MESH_SUBDIV_MAX );
	}

	self->mesh->subdiv = (short)subdiv[0];
	self->mesh->subdivr = (short)subdiv[1];
	return 0;
}

static PyObject *Mesh_getFlag( BPy_Mesh * self, void *type )
{
	switch( (long)type ) {
	case MESH_HASFACEUV:
		return self->mesh->mtface ? EXPP_incr_ret_True() :
			EXPP_incr_ret_False();
	case MESH_HASMCOL:
		return self->mesh->mcol ? EXPP_incr_ret_True() :
			EXPP_incr_ret_False();
	case MESH_HASVERTUV:
		return self->mesh->msticky ? EXPP_incr_ret_True() :
			EXPP_incr_ret_False();
	case MESH_HASMULTIRES:
		return self->mesh->mr ? EXPP_incr_ret_True() :
			EXPP_incr_ret_False();
	default:
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
					"couldn't get attribute" );
	}
}

static int Mesh_setFlag( BPy_Mesh * self, PyObject *value, void *type )
{
	int param;
	Mesh *mesh = self->mesh;

	param = PyObject_IsTrue( value );

	if( param == -1 )
		return EXPP_ReturnIntError( PyExc_TypeError,
				"expected True/False or 0/1" );

	/* sticky is independent of faceUV and vertUV */

	switch( (long)type ) {
	case MESH_HASFACEUV:
		if( !param ) {
			if( mesh->mtface ) {
				CustomData_free_layers( &mesh->fdata, CD_MTFACE, mesh->totface );
				mesh->mtface = NULL;
			}
		} else if( !mesh->mtface ) {
			if( !mesh->totface )
				return EXPP_ReturnIntError( PyExc_RuntimeError,
					"mesh has no faces" );
			make_tfaces( mesh );
		}
		return 0;
	case MESH_HASMCOL:
		if( !param ) {
			if( mesh->mcol ) {
				CustomData_free_layers( &mesh->fdata, CD_MCOL, mesh->totface );
				mesh->mcol = NULL;
			}
		} else if( !mesh->mcol ) {
				/* TODO: mesh_create_shadedColors */
			mesh->mcol = CustomData_add_layer( &mesh->fdata, CD_MCOL,
				CD_DEFAULT, NULL, mesh->totface );
		}
		return 0;
	case MESH_HASVERTUV:
		if( !param ) {
			if( mesh->msticky ) {
				CustomData_free_layer_active( &mesh->vdata, CD_MSTICKY, mesh->totvert );
				mesh->msticky = NULL;
			}
		} else {
			if( !mesh->msticky ) {
				mesh->msticky = CustomData_add_layer( &mesh->vdata, CD_MSTICKY,
					CD_CALLOC, NULL, mesh->totvert );
				memset( mesh->msticky, 255, mesh->totvert*sizeof( MSticky ) );
				/* TODO: rework RE_make_sticky() so we can calculate */
			}
		}
		return 0;
	case MESH_HASMULTIRES:
		if (!self->object)
			return EXPP_ReturnIntError( PyExc_RuntimeError,
				"This mesh must be linked to an object" ); 
		
		if( !param ) {
			if ( mesh->mr ) {
				multires_delete(self->object, mesh);
			}
		} else {
			if ( !mesh->mr ) {
				if (mesh->key)
					return EXPP_ReturnIntError( PyExc_RuntimeError,
						"Cannot enable multires for a mesh with shape keys" ); 
				multires_make(self->object, mesh);
			}
		}
		return 0;
	default:
		return EXPP_ReturnIntError( PyExc_RuntimeError,
					"couldn't get attribute" );
	}
}

static PyObject *Mesh_getMode( BPy_Mesh * self )
{
	return PyInt_FromLong( self->mesh->flag );
}

static int Mesh_setMode( BPy_Mesh *self, PyObject *value )
{
	short param;
	static short bitmask = ME_ISDONE | ME_NOPUNOFLIP | ME_TWOSIDED |
		ME_UVEFFECT | ME_VCOLEFFECT | ME_AUTOSMOOTH | ME_SMESH |
		ME_SUBSURF | ME_OPT_EDGES;

	if( !PyInt_Check ( value ) ) {
		char errstr[128];
		sprintf ( errstr , "expected int bitmask of 0x%04x", bitmask );
		return EXPP_ReturnIntError( PyExc_TypeError, errstr );
	}
	param = (short)PyInt_AS_LONG ( value );

	if( ( param & bitmask ) != param )
		return EXPP_ReturnIntError( PyExc_ValueError,
						"invalid bit(s) set in mask" );

	self->mesh->flag = param; 

	return 0;
}

static PyObject *Mesh_getKey( BPy_Mesh * self )
{
	if( self->mesh->key )
		return Key_CreatePyObject(self->mesh->key);
	else
		Py_RETURN_NONE;
}


static PyObject *Mesh_getActiveFace( BPy_Mesh * self )
{
	/* not needed but keep incase exceptions make use of it */
	if( !self->mesh->mtface )
		return EXPP_ReturnPyObjError( PyExc_ValueError,
				"face has no texture values" );

	if (self->mesh->act_face != -1 && self->mesh->act_face <= self->mesh->totface)
		return PyInt_FromLong( self->mesh->act_face );
	
	Py_RETURN_NONE;
}

static int Mesh_setActiveFace( BPy_Mesh * self, PyObject * value )
{
	int param;

	/* if no texture faces, error */

	if( !self->mesh->mtface )
		return EXPP_ReturnIntError( PyExc_ValueError,
				"face has no texture values" );

	/* if param isn't an int, error */

	if( !PyInt_Check( value ) )
		return EXPP_ReturnIntError( PyExc_TypeError,
				"expected an int argument" );

	/* check for a valid index */

	param = PyInt_AsLong( value );
	if( param < 0 || param > self->mesh->totface )
		return EXPP_ReturnIntError( PyExc_TypeError,
				"face index out of range" );
	
	self->mesh->act_face = param;
	return 0;
}

static PyObject *Mesh_getActiveGroup( BPy_Mesh * self )
{
	bDeformGroup *defGroup;
	Object *object = self->object;

	if( !object )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				"This mesh must be linked to an object" ); 

	if( object->actdef ) {
		defGroup = BLI_findlink( &object->defbase, object->actdef-1 );
		return PyString_FromString( defGroup->name );
	}

	Py_RETURN_NONE;
}

static int Mesh_setActiveGroup( BPy_Mesh * self, PyObject * arg )
{
	char *name;
	int tmp;
	Object *object = self->object;

	if( !object )
		return EXPP_ReturnIntError( PyExc_RuntimeError,
				"This mesh must be linked to an object" ); 

	if( !PyString_Check( arg ) )
		return EXPP_ReturnIntError( PyExc_AttributeError,
				"expected a string argument" );

	name = PyString_AsString( arg );
	tmp = object->actdef;
	vertexgroup_select_by_name( object, name );
	if( !object->actdef ) {
		object->actdef = tmp;
		return EXPP_ReturnIntError( PyExc_ValueError,
				"vertex group not found" );
	}

	return 0;
}

static PyObject *Mesh_getTexMesh( BPy_Mesh * self )
{
	Mesh *texme= self->mesh->texcomesh;
	
	if (texme)
		return Mesh_CreatePyObject( texme, NULL );
	else
		Py_RETURN_NONE;
}

static int Mesh_setTexMesh( BPy_Mesh * self, PyObject * value )
{	
	int ret = GenericLib_assignData(value, (void **) &self->mesh->texcomesh, 0, 1, ID_ME, 0);
	
	if (ret==0 && value!=Py_None) /*This must be a mesh type*/
		(( BPy_Mesh * ) value)->new= 0;
	
	return ret;
}

static int Mesh_setSel( BPy_Mesh * self, PyObject * value )
{
	int i, param = PyObject_IsTrue( value );
	Mesh *me = self->mesh;
	MVert *mvert = me->mvert;
	MEdge *medge = me->medge;
	MFace *mface = me->mface;

	if( param == -1 )
		return EXPP_ReturnIntError( PyExc_TypeError,
				"expected True/False or 0/1" );
	
	if( param ) {
		for( i = 0; i < me->totvert; ++mvert, ++i )
			mvert->flag |= SELECT;
		for( i = 0; i < me->totedge; ++medge, ++i )
			medge->flag |= SELECT;
		for( i = 0; i < me->totface; ++mface, ++i )
			mface->flag |= ME_FACE_SEL;
	} else {
		for( i = 0; i < me->totvert; ++mvert, ++i )
			mvert->flag &= ~SELECT;
		for( i = 0; i < me->totedge; ++medge, ++i )
			medge->flag &= ~SELECT;
		for( i = 0; i < me->totface; ++mface, ++i )
			mface->flag &= ~ME_FACE_SEL;
	}

	return 0;
}

static int Mesh_setHide( BPy_Mesh * self, PyObject * value )
{
	int i, param = PyObject_IsTrue( value );
	Mesh *me = self->mesh;
	MVert *mvert = me->mvert;
	MEdge *medge = me->medge;
	MFace *mface = me->mface;

	if( param == -1 )
		return EXPP_ReturnIntError( PyExc_TypeError,
				"expected True/False or 0/1" );
	
	if( param ) {
		for( i = 0; i < me->totvert; ++mvert, ++i )
			mvert->flag |= ME_HIDE;
		for( i = 0; i < me->totedge; ++medge, ++i )
			medge->flag |= ME_HIDE;
		for( i = 0; i < me->totface; ++mface, ++i )
			mface->flag |= ME_HIDE;
	} else {
		for( i = 0; i < me->totvert; ++mvert, ++i )
			mvert->flag &= ~ME_HIDE;
		for( i = 0; i < me->totedge; ++medge, ++i )
			medge->flag &= ~ME_HIDE;
		for( i = 0; i < me->totface; ++mface, ++i )
			mface->flag &= ~ME_HIDE;
	}

	return 0;
}

/************************************************************************
 *
 * Python Mesh_Type standard operations
 *
 ************************************************************************/

static void Mesh_dealloc( BPy_Mesh * self )
{
	Mesh *mesh = self->mesh;

	/* if the mesh is new and has no users, delete it */
	if( self->new && !mesh->id.us )
	    free_libblock( &G.main->mesh, mesh );

	PyObject_DEL( self );
}

static int Mesh_compare( BPy_Mesh * a, BPy_Mesh * b )
{
	return ( a->mesh == b->mesh ) ? 0 : -1;
}

static PyObject *Mesh_repr( BPy_Mesh * self )
{
	return PyString_FromFormat( "[Mesh \"%s\"]",
				    self->mesh->id.name + 2 );
}

/*****************************************************************************/
/* Python Mesh_Type attributes get/set structure:                           */
/*****************************************************************************/
static PyGetSetDef BPy_Mesh_getseters[] = {
	GENERIC_LIB_GETSETATTR,
	{"verts",
	 (getter)Mesh_getVerts, (setter)Mesh_setVerts,
	 "The mesh's vertices (MVert)",
	 NULL},
	{"edges",
	 (getter)Mesh_getEdges, (setter)NULL,
	 "The mesh's edge data (MEdge)",
	 NULL},
	{"faces",
	 (getter)Mesh_getFaces, (setter)NULL,
	 "The mesh's face data (MFace)",
	 NULL},
	{"materials",
	 (getter)Mesh_getMaterials, (setter)Mesh_setMaterials,
	 "List of the mesh's materials",
	 NULL},
	{"degr",
	 (getter)Mesh_getMaxSmoothAngle, (setter)Mesh_setMaxSmoothAngle,
	 "The max angle for auto smoothing",
	 NULL},
	{"maxSmoothAngle",
	 (getter)Mesh_getMaxSmoothAngle, (setter)Mesh_setMaxSmoothAngle,
	 "deprecated: see 'degr'",
	 NULL},
	{"subDivLevels",
	 (getter)Mesh_getSubDivLevels, (setter)Mesh_setSubDivLevels,
	 "The display and rendering subdivision levels",
	 NULL},
	{"mode",
	 (getter)Mesh_getMode, (setter)Mesh_setMode,
	 "The mesh's mode bitfield",
	 NULL},
	{"key",
	 (getter)Mesh_getKey, (setter)NULL,
	 "The mesh's key",
	 NULL},
	{"faceUV",
	 (getter)Mesh_getFlag, (setter)Mesh_setFlag,
	 "UV-mapped textured faces enabled",
 	 (void *)MESH_HASFACEUV},
	{"vertexColors",
	 (getter)Mesh_getFlag, (setter)Mesh_setFlag,
	 "Vertex colors for the mesh enabled",
	 (void *)MESH_HASMCOL},
	{"vertexUV",
	 (getter)Mesh_getFlag, (setter)Mesh_setFlag,
	 "'Sticky' flag for per vertex UV coordinates enabled",
	 (void *)MESH_HASVERTUV},
	{"multires",
	 (getter)Mesh_getFlag, (setter)Mesh_setFlag,
	 "'Sticky' flag for per vertex UV coordinates enabled",
	 (void *)MESH_HASMULTIRES},
	{"activeFace",
	 (getter)Mesh_getActiveFace, (setter)Mesh_setActiveFace,
	 "Index of the mesh's active texture face (in UV editor)",
	 NULL},
	{"activeGroup",
	 (getter)Mesh_getActiveGroup, (setter)Mesh_setActiveGroup,
	 "Active group for the mesh",
	 NULL},

	/* uv layers */
	{"activeColorLayer",
	 (getter)Mesh_getActiveLayer, (setter)Mesh_setActiveLayer,
	 "Name of the active UV layer",
	 (void *)CD_MCOL},
	{"activeUVLayer",
	 (getter)Mesh_getActiveLayer, (setter)Mesh_setActiveLayer,
	 "Name of the active vertex color layer",
	 (void *)CD_MTFACE},
	/* hack flip CD_MCOL so it uses the render setting */
	{"renderColorLayer",
	 (getter)Mesh_getActiveLayer, (setter)Mesh_setActiveLayer,
	 "Name of the render UV layer",
	 (void *)-CD_MCOL},
	{"renderUVLayer",
	 (getter)Mesh_getActiveLayer, (setter)Mesh_setActiveLayer,
	 "Name of the render vertex color layer",
	 (void *)-CD_MTFACE},
	 
	 

	/* Multires */
	{"multiresLevelCount",
	 (getter)Mesh_getMultiresLevelCount, (setter)NULL,
	 "The total number of multires levels",
	 NULL},
	{"multiresDrawLevel",
	 (getter)Mesh_getMultires, (setter)Mesh_setMultires,
	 "The current multires display level",
	 (void *)MESH_MULTIRES_LEVEL},
	{"multiresEdgeLevel",
	 (getter)Mesh_getMultires, (setter)Mesh_setMultires,
	 "The current multires edge level",
	 (void *)MESH_MULTIRES_EDGE},
	{"multiresPinLevel",
	 (getter)Mesh_getMultires, (setter)Mesh_setMultires,
	 "The current multires pin level",
	 (void *)MESH_MULTIRES_PIN},
	{"multiresRenderLevel",
	 (getter)Mesh_getMultires, (setter)Mesh_setMultires,
	 "The current multires render level",
	 (void *)MESH_MULTIRES_RENDER},

	{"texMesh",
	 (getter)Mesh_getTexMesh, (setter)Mesh_setTexMesh,
	 "The meshes tex mesh proxy texture coord mesh",
	 NULL},
	{"sel",
	 (getter)NULL, (setter)Mesh_setSel,
	 "Select/deselect all verts, edges, faces in the mesh",
	 NULL},
	{"hide",
	 (getter)NULL, (setter)Mesh_setHide,
	 "Hide/unhide all verts, edges, faces in the mesh",
	 NULL},

	{NULL,NULL,NULL,NULL,NULL}  /* Sentinel */
};

/*****************************************************************************/
/* Python Mesh_Type structure definition:                                   */
/*****************************************************************************/
PyTypeObject Mesh_Type = {
	PyObject_HEAD_INIT( NULL )  /* required py macro */
	0,                          /* ob_size */
	/*  For printing, in format "<module>.<name>" */
	"Blender Mesh",             /* char *tp_name; */
	sizeof( BPy_Mesh ),         /* int tp_basicsize; */
	0,                          /* tp_itemsize;  For allocation */

	/* Methods to implement standard operations */

	( destructor ) Mesh_dealloc,/* destructor tp_dealloc; */
	NULL,                       /* printfunc tp_print; */
	NULL,                       /* getattrfunc tp_getattr; */
	NULL,                       /* setattrfunc tp_setattr; */
	( cmpfunc ) Mesh_compare,   /* cmpfunc tp_compare; */
	( reprfunc ) Mesh_repr,     /* reprfunc tp_repr; */

	/* Method suites for standard classes */

	NULL,                       /* PyNumberMethods *tp_as_number; */
	NULL,                       /* PySequenceMethods *tp_as_sequence; */
	NULL,                       /* PyMappingMethods *tp_as_mapping; */

	/* More standard operations (here for binary compatibility) */

	( hashfunc ) GenericLib_hash,	/* hashfunc tp_hash; */
	NULL,                       /* ternaryfunc tp_call; */
	NULL,                       /* reprfunc tp_str; */
	NULL,                       /* getattrofunc tp_getattro; */
	NULL,                       /* setattrofunc tp_setattro; */

	/* Functions to access object as input/output buffer */
	NULL,                       /* PyBufferProcs *tp_as_buffer; */

  /*** Flags to define presence of optional/expanded features ***/
	Py_TPFLAGS_DEFAULT,         /* long tp_flags; */

	NULL,                       /*  char *tp_doc;  Documentation string */
  /*** Assigned meaning in release 2.0 ***/
	/* call function for all accessible objects */
	NULL,                       /* traverseproc tp_traverse; */

	/* delete references to contained objects */
	NULL,                       /* inquiry tp_clear; */

  /***  Assigned meaning in release 2.1 ***/
  /*** rich comparisons ***/
	NULL,                       /* richcmpfunc tp_richcompare; */

  /***  weak reference enabler ***/
	0,                          /* long tp_weaklistoffset; */

  /*** Added in release 2.2 ***/
	/*   Iterators */
	NULL,                       /* getiterfunc tp_iter; */
	NULL,                       /* iternextfunc tp_iternext; */

  /*** Attribute descriptor and subclassing stuff ***/
	BPy_Mesh_methods,          /* struct PyMethodDef *tp_methods; */
	NULL,                       /* struct PyMemberDef *tp_members; */
	BPy_Mesh_getseters,        /* struct PyGetSetDef *tp_getset; */
	NULL,                       /* struct _typeobject *tp_base; */
	NULL,                       /* PyObject *tp_dict; */
	NULL,                       /* descrgetfunc tp_descr_get; */
	NULL,                       /* descrsetfunc tp_descr_set; */
	0,                          /* long tp_dictoffset; */
	NULL,                       /* initproc tp_init; */
	NULL,                       /* allocfunc tp_alloc; */
	NULL,                       /* newfunc tp_new; */
	/*  Low-level free-memory routine */
	NULL,                       /* freefunc tp_free;  */
	/* For PyObject_IS_GC */
	NULL,                       /* inquiry tp_is_gc;  */
	NULL,                       /* PyObject *tp_bases; */
	/* method resolution order */
	NULL,                       /* PyObject *tp_mro;  */
	NULL,                       /* PyObject *tp_cache; */
	NULL,                       /* PyObject *tp_subclasses; */
	NULL,                       /* PyObject *tp_weaklist; */
	NULL
};

/*
 * get one or all mesh data objects
 */

static PyObject *M_Mesh_Get( PyObject * self_unused, PyObject * args )
{
	char *name = NULL;
	Mesh *mesh = NULL;
	BPy_Mesh* obj;

	if( !PyArg_ParseTuple( args, "|s", &name ) )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
				"expected zero or one string arguments" );

	if( name ) {
		mesh = ( Mesh * ) GetIdFromList( &( G.main->mesh ), name );

		if( !mesh ) {
			char error_msg[64];
			PyOS_snprintf( error_msg, sizeof( error_msg ),
				       "Mesh \"%s\" not found", name );
			return ( EXPP_ReturnPyObjError
				 ( PyExc_NameError, error_msg ) );
		}
		return Mesh_CreatePyObject( mesh, NULL );
	} else {			/* () - return a list with all meshes in the scene */
		PyObject *meshlist;
		Link *link;
		int index = 0;

		meshlist = PyList_New( BLI_countlist( &( G.main->mesh ) ) );

		if( !meshlist )
			return EXPP_ReturnPyObjError( PyExc_MemoryError,
					"couldn't create PyList" );

		link = G.main->mesh.first;
		index = 0;
		while( link ) {
			obj = ( BPy_Mesh * ) Mesh_CreatePyObject( ( Mesh * )link, NULL );
			PyList_SetItem( meshlist, index, ( PyObject * ) obj );
			index++;
			link = link->next;
		}
		return meshlist;
	}
}

/*
 * create a new mesh data object
 */

static PyObject *M_Mesh_New( PyObject * self_unused, PyObject * args )
{
	char *name = "Mesh";
	Mesh *mesh;
	BPy_Mesh *obj;

	if( !PyArg_ParseTuple( args, "|s", &name ) )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
					      "expected nothing or a string as argument" );

	obj = (BPy_Mesh *)PyObject_NEW( BPy_Mesh, &Mesh_Type );

	if( !obj )
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				       "PyObject_New() failed" );

	mesh = add_mesh(name); /* doesn't return NULL now, but might someday */
	
	if( !mesh ) {
		Py_DECREF ( obj );
		return EXPP_ReturnPyObjError( PyExc_RuntimeError,
				       "FATAL: could not create mesh object" );
	}
	
	/* Bound box set to null needed because a new mesh is initialized
	with a bounding box of -1 -1 -1 -1 -1 -1
	if its not set to null the bounding box is not re-calculated
	when ob.getBoundBox() is called.*/
	MEM_freeN(mesh->bb);
	mesh->bb= NULL;
	
	mesh->id.us = 0;

	obj->mesh = mesh;
	obj->object = NULL;
	obj->new = 1;
	return (PyObject *)obj;
}

/*
 * creates a new MVert for users to manipulate
 */

static PyObject *M_Mesh_MVert( PyObject * self_unused, PyObject * args )
{
	int i;
	MVert vert;

	/* initialize the new vert's data */
	memset( &vert, 0, sizeof( MVert ) );

	/*
	 * accept either a 3D vector or tuple of three floats
	 */

	if( PyTuple_Size ( args ) == 1 ) {
		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" );
		for( i = 0; i < 3; ++i )
			vert.co[i] = ((VectorObject *)tmp)->vec[i];
	} else if( !PyArg_ParseTuple ( args, "fff",
				&vert.co[0], &vert.co[1], &vert.co[2] ) )
		return EXPP_ReturnPyObjError( PyExc_ValueError,
			"expected three floats or vector of size 3" );

	/* make a new MVert from the data */
	return PVert_CreatePyObject( &vert );
}

static PyObject *M_Mesh_Modes( PyObject * self_unused, PyObject * args )
{
	int modes = 0;

	if( !G.scene ) {
		Py_RETURN_NONE;
	}

	if( !PyArg_ParseTuple( args, "|i", &modes ) )
		return EXPP_ReturnPyObjError( PyExc_TypeError,
					      "expected optional int as argument" );

	if( modes > ( SCE_SELECT_VERTEX | SCE_SELECT_EDGE | SCE_SELECT_FACE ) )
		return EXPP_ReturnPyObjError( PyExc_ValueError,
					      "value out of range" );

	if( modes > 0 )
		G.scene->selectmode = (short)modes;
	
	return PyInt_FromLong( G.scene->selectmode );
}

static struct PyMethodDef M_Mesh_methods[] = {
	{"New", (PyCFunction)M_Mesh_New, METH_VARARGS,
		"Create a new mesh"},
	{"Get", (PyCFunction)M_Mesh_Get, METH_VARARGS,
		"Get a mesh by name"},
	{"MVert", (PyCFunction)M_Mesh_MVert, METH_VARARGS,
		"Create a new MVert"},
	{"Mode", (PyCFunction)M_Mesh_Modes, METH_VARARGS,
		"Get/set edit selection mode(s)"},
	{NULL, NULL, 0, NULL},
};

static PyObject *M_Mesh_ModesDict( void )
{
	PyObject *Modes = PyConstant_New(  );

	if( Modes ) {
		BPy_constant *d = ( BPy_constant * ) Modes;

		PyConstant_Insert( d, "NOVNORMALSFLIP",
				PyInt_FromLong( ME_NOPUNOFLIP ) );
		PyConstant_Insert( d, "TWOSIDED", PyInt_FromLong( ME_TWOSIDED ) );
		PyConstant_Insert( d, "AUTOSMOOTH", 
				PyInt_FromLong( ME_AUTOSMOOTH ) );
	}

	return Modes;
}

/* Set constants for face drawing mode -- see drawmesh.c */

static PyObject *M_Mesh_FaceModesDict( void )
{
	PyObject *FM = PyConstant_New(  );

	if( FM ) {
		BPy_constant *d = ( BPy_constant * ) FM;

		PyConstant_Insert( d, "BILLBOARD", PyInt_FromLong( TF_BILLBOARD2 ) );
		PyConstant_Insert( d, "ALL", PyInt_FromLong( 0xffff ) );
		PyConstant_Insert( d, "HALO", PyInt_FromLong( TF_BILLBOARD ) );
		PyConstant_Insert( d, "DYNAMIC", PyInt_FromLong( TF_DYNAMIC ) );
		PyConstant_Insert( d, "ALPHASORT", PyInt_FromLong( TF_ALPHASORT ) );
		PyConstant_Insert( d, "INVISIBLE", PyInt_FromLong( TF_INVISIBLE ) );
		PyConstant_Insert( d, "LIGHT", PyInt_FromLong( TF_LIGHT ) );
		PyConstant_Insert( d, "OBCOL", PyInt_FromLong( TF_OBCOL ) );
		PyConstant_Insert( d, "SHADOW", PyInt_FromLong( TF_SHADOW ) );
		PyConstant_Insert( d, "TEXT", PyInt_FromLong( TF_BMFONT ) );
		PyConstant_Insert( d, "SHAREDVERT", PyInt_FromLong( TF_SHAREDVERT ) );
		PyConstant_Insert( d, "SHAREDCOL", PyInt_FromLong( TF_SHAREDCOL ) );
		PyConstant_Insert( d, "TEX", PyInt_FromLong( TF_TEX ) );
		PyConstant_Insert( d, "TILES", PyInt_FromLong( TF_TILES ) );
		PyConstant_Insert( d, "TWOSIDE", PyInt_FromLong( TF_TWOSIDE ) );
	}

	return FM;
}

static PyObject *M_Mesh_FaceFlagsDict( void )
{
	PyObject *FF = PyConstant_New(  );

	if( FF ) {
		BPy_constant *d = ( BPy_constant * ) FF;

		PyConstant_Insert( d, "SELECT", PyInt_FromLong( TF_SELECT ) );
		PyConstant_Insert( d, "HIDE", PyInt_FromLong( TF_HIDE ) );
		PyConstant_Insert( d, "ACTIVE", PyInt_FromLong( TF_ACTIVE ) ); /* deprecated */
	}

	return FF;
}

static PyObject *M_Mesh_FaceTranspModesDict( void )
{
	PyObject *FTM = PyConstant_New(  );

	if( FTM ) {
		BPy_constant *d = ( BPy_constant * ) FTM;

		PyConstant_Insert( d, "SOLID", PyInt_FromLong( TF_SOLID ) );
		PyConstant_Insert( d, "ADD", PyInt_FromLong( TF_ADD ) );
		PyConstant_Insert( d, "ALPHA", PyInt_FromLong( TF_ALPHA ) );
		PyConstant_Insert( d, "SUB", PyInt_FromLong( TF_SUB ) );
	}

	return FTM;
}

static PyObject *M_Mesh_EdgeFlagsDict( void )
{
	PyObject *EF = PyConstant_New(  );

	if( EF ) {
		BPy_constant *d = ( BPy_constant * ) EF;

		PyConstant_Insert(d, "SELECT", PyInt_FromLong( SELECT ) );
		PyConstant_Insert(d, "EDGEDRAW", PyInt_FromLong( ME_EDGEDRAW ) );
		PyConstant_Insert(d, "EDGERENDER", PyInt_FromLong( ME_EDGERENDER ) );
		PyConstant_Insert(d, "SEAM", PyInt_FromLong( ME_SEAM ) );
		PyConstant_Insert(d, "FGON", PyInt_FromLong( ME_FGON ) );
		PyConstant_Insert(d, "LOOSE", PyInt_FromLong( ME_LOOSEEDGE ) );
		PyConstant_Insert(d, "SHARP", PyInt_FromLong( ME_SHARP ) );
	}

	return EF;
}

static PyObject *M_Mesh_VertAssignDict( void )
{
	PyObject *Vert = PyConstant_New(  );
	if( Vert ) {
		BPy_constant *d = ( BPy_constant * ) Vert;
		PyConstant_Insert(d, "ADD", PyInt_FromLong(WEIGHT_ADD));
		PyConstant_Insert(d, "REPLACE", PyInt_FromLong(WEIGHT_REPLACE));
		PyConstant_Insert(d, "SUBTRACT", PyInt_FromLong(WEIGHT_SUBTRACT));
	}
	return Vert;
}


static PyObject *M_Mesh_SelectModeDict( void )
{
	PyObject *Mode = PyConstant_New(  );
	if( Mode ) {
		BPy_constant *d = ( BPy_constant * ) Mode;
		PyConstant_Insert(d, "VERTEX", PyInt_FromLong(SCE_SELECT_VERTEX));
		PyConstant_Insert(d, "EDGE", PyInt_FromLong(SCE_SELECT_EDGE));
		PyConstant_Insert(d, "FACE", PyInt_FromLong(SCE_SELECT_FACE));
	}
	return Mode;
}

static char M_Mesh_doc[] = "The Blender.Mesh submodule";

PyObject *Mesh_Init( void )
{
	PyObject *submodule;

	PyObject *Modes = M_Mesh_ModesDict( );
	PyObject *FaceFlags = M_Mesh_FaceFlagsDict( );
	PyObject *FaceModes = M_Mesh_FaceModesDict( );
	PyObject *FaceTranspModes = M_Mesh_FaceTranspModesDict( );
	PyObject *EdgeFlags = M_Mesh_EdgeFlagsDict(  );
	PyObject *AssignModes = M_Mesh_VertAssignDict( );
	PyObject *SelectModes = M_Mesh_SelectModeDict( );
	PyObject *PropertyTypes = M_Mesh_PropertiesTypeDict( );
	
	if( PyType_Ready( &MCol_Type ) < 0 )
		return NULL;
	if( PyType_Ready( &MVert_Type ) < 0 )
		return NULL;
	if( PyType_Ready( &PVert_Type ) < 0 )
		return NULL;
	if( PyType_Ready( &MVertSeq_Type ) < 0 )
		return NULL;
	if( PyType_Ready( &MEdge_Type ) < 0 )
		return NULL;
	if( PyType_Ready( &MEdgeSeq_Type ) < 0 )
		return NULL;
	if( PyType_Ready( &MFace_Type ) < 0 )
		return NULL;
	if( PyType_Ready( &MFaceSeq_Type ) < 0 )
		return NULL;
	if( PyType_Ready( &Mesh_Type ) < 0 )
		return NULL;

	submodule =
		Py_InitModule3( "Blender.Mesh", M_Mesh_methods, M_Mesh_doc );
	PyDict_SetItemString( PyModule_GetDict( submodule ),
			"Primitives", MeshPrimitives_Init( ) );

	if( Modes )
		PyModule_AddObject( submodule, "Modes", Modes );
	if( FaceFlags )
		PyModule_AddObject( submodule, "FaceFlags", FaceFlags );
	if( FaceModes )
		PyModule_AddObject( submodule, "FaceModes", FaceModes );
	if( FaceTranspModes )
		PyModule_AddObject( submodule, "FaceTranspModes",
				    FaceTranspModes );
	if( EdgeFlags )
		PyModule_AddObject( submodule, "EdgeFlags", EdgeFlags );
	if( AssignModes )
		PyModule_AddObject( submodule, "AssignModes", AssignModes );
	if( SelectModes )
		PyModule_AddObject( submodule, "SelectModes", SelectModes );
	if( PropertyTypes )
		PyModule_AddObject( submodule, "PropertyTypes", PropertyTypes );



	return submodule;
}

/* These are needed by Object.c */

PyObject *Mesh_CreatePyObject( Mesh * me, Object *obj )
{
	BPy_Mesh *nmesh = PyObject_NEW( BPy_Mesh, &Mesh_Type );

	if( !nmesh )
		return EXPP_ReturnPyObjError( PyExc_MemoryError,
				"couldn't create BPy_Mesh object" );

	nmesh->mesh = me;
	nmesh->object = obj;
	nmesh->new = 0;
	G.totmesh++;

	return ( PyObject * ) nmesh;
}


Mesh *Mesh_FromPyObject( PyObject * pyobj, Object *obj )
{
	BPy_Mesh *blen_obj;

	blen_obj = ( BPy_Mesh * ) pyobj;
	if (obj)
		blen_obj->object = obj;

	return blen_obj->mesh;

}
