← Back to team overview

yade-dev team mailing list archive

[svn] r1814 - in trunk: . lib lib/py lib/py/pygts-0.3.1

 

Author: eudoxos
Date: 2009-06-25 00:06:24 +0200 (Thu, 25 Jun 2009)
New Revision: 1814

Added:
   trunk/lib/py/pygts-0.3.1/
   trunk/lib/py/pygts-0.3.1/__init__.py
   trunk/lib/py/pygts-0.3.1/cleanup.c
   trunk/lib/py/pygts-0.3.1/cleanup.h
   trunk/lib/py/pygts-0.3.1/edge.c
   trunk/lib/py/pygts-0.3.1/edge.h
   trunk/lib/py/pygts-0.3.1/face.c
   trunk/lib/py/pygts-0.3.1/face.h
   trunk/lib/py/pygts-0.3.1/object.c
   trunk/lib/py/pygts-0.3.1/object.h
   trunk/lib/py/pygts-0.3.1/point.c
   trunk/lib/py/pygts-0.3.1/point.h
   trunk/lib/py/pygts-0.3.1/pygts.c
   trunk/lib/py/pygts-0.3.1/pygts.h
   trunk/lib/py/pygts-0.3.1/pygts.py
   trunk/lib/py/pygts-0.3.1/segment.c
   trunk/lib/py/pygts-0.3.1/segment.h
   trunk/lib/py/pygts-0.3.1/surface.c
   trunk/lib/py/pygts-0.3.1/surface.h
   trunk/lib/py/pygts-0.3.1/triangle.c
   trunk/lib/py/pygts-0.3.1/triangle.h
   trunk/lib/py/pygts-0.3.1/vertex.c
   trunk/lib/py/pygts-0.3.1/vertex.h
Modified:
   trunk/SConstruct
   trunk/lib/SConscript
   trunk/lib/py/README
Log:
1. Add checks for GTS (optional)
2. Add pyGTS to our tree (built only if needed), since no packages are available.


Modified: trunk/SConstruct
===================================================================
--- trunk/SConstruct	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/SConstruct	2009-06-24 22:06:24 UTC (rev 1814)
@@ -129,7 +129,7 @@
 	ListVariable('exclude','Yade components that will not be built','none',names=['qt3','gui','extra','common','dem','fem','lattice','mass-spring','realtime-rigidbody','snow']),
 	EnumVariable('arcs','Whether to generate or use branch probabilities','',['','gen','use'],{'no':'','0':'','false':''},1),
 	# OK, dummy prevents bug in scons: if one selects all, it says all in scons.config, but without quotes, which generates error.
-	ListVariable('features','Optional features that are turned on','python,log4cxx,openGL',names=['openGL','python','log4cxx','binfmt','CGAL','dummy']),
+	ListVariable('features','Optional features that are turned on','python,log4cxx,openGL',names=['openGL','python','log4cxx','binfmt','CGAL','dummy','GTS']),
 	('jobs','Number of jobs to run at the same time (same as -j, but saved)',4,None,int),
 	('extraModules', 'Extra directories with their own SConscript files (must be in-tree) (whitespace separated)',None,None,Split),
 	('buildPrefix','Where to create build-[version][variant] directory for intermediary files','..'),
@@ -321,6 +321,11 @@
 		ok=conf.CheckLibWithHeader('glut','GL/glut.h','c','glutGetModifiers();',autoadd=1)
 		if not ok: featureNotOK('openGL')
 		env.Append(CPPDEFINES='YADE_OPENGL')
+	if 'GTS' in env['features']:
+		env.ParseConfig('pkg-config glib-2.0 --cflags --libs');
+		ok=conf.CheckLibWithHeader('gts','gts.h','c','gts_object_class();',autoadd=1)
+		if not ok: featureNotOK('GTS')
+		env.Append(CPPDEFINES='YADE_GTS')
 	if 'qt3' not in env['exclude']:
 		if 'openGL' not in env['features']:
 			print "\nQt3 interface can only be used if openGL is enabled.\nEither add openGL to 'features' or add qt3 to 'exclude'."

Modified: trunk/lib/SConscript
===================================================================
--- trunk/lib/SConscript	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/SConscript	2009-06-24 22:06:24 UTC (rev 1814)
@@ -73,8 +73,13 @@
 			LIBS=env['LIBS']+['glut','$QGLVIEWER_LIB']),
 	])
 
+if 'GTS' in env['features']:
+	env.Install('$PREFIX/lib/yade$SUFFIX/py/gts',[
+		env.SharedLibrary('_gts',['py/pygts-0.3.1/cleanup.c','py/pygts-0.3.1/edge.c','py/pygts-0.3.1/face.c','py/pygts-0.3.1/object.c','py/pygts-0.3.1/point.c','py/pygts-0.3.1/pygts.c','py/pygts-0.3.1/segment.c','py/pygts-0.3.1/surface.c','py/pygts-0.3.1/triangle.c','py/pygts-0.3.1/vertex.c'],SHLIBPREFIX='',CPPDEFINES=env['CPPDEFINES']+['PYGTS_HAS_NUMPY']),
+		env.File('py/pygts-0.3.1/__init__.py'),
+		env.File('py/pygts-0.3.1/pygts.py')
+	])
 
-
 env.Install('$PREFIX/lib/yade$SUFFIX/lib',[
 
 	env.SharedLibrary('yade-base',

Modified: trunk/lib/py/README
===================================================================
--- trunk/lib/py/README	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/README	2009-06-24 22:06:24 UTC (rev 1814)
@@ -2,3 +2,10 @@
 	homepage: http://partiallydisassembled.net/euclid.html
 	latest SVN version: http://pyeuclid.googlecode.com/svn/trunk/euclid.py
 	documentation: http://partiallydisassembled.net/euclid/
+
+pygts-0.3.1:
+	homepage: http://pygts.sourceforge.net/
+	documentation: http://pygts.svn.sourceforge.net/viewvc/pygts/doc/gts.html
+	license: GNU GPL v2
+	note: the local version is only the 'gts' directory of the source archive (doc, examples, test ommited). Distutils are not used for building either.
+

Added: trunk/lib/py/pygts-0.3.1/__init__.py
===================================================================
--- trunk/lib/py/pygts-0.3.1/__init__.py	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/__init__.py	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,104 @@
+# pygts - python package for the manipulation of triangulated surfaces
+#
+#   Copyright (C) 2009 Thomas J. Duck
+#   All rights reserved.
+#
+#   Thomas J. Duck <tom.duck@xxxxxx>
+#   Department of Physics and Atmospheric Science,
+#   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+#
+# NOTICE
+#
+#   This library is free software; you can redistribute it and/or
+#   modify it under the terms of the GNU Library General Public
+#   License as published by the Free Software Foundation; either
+#   version 2 of the License, or (at your option) any later version.
+#
+#   This library 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
+#   Library General Public License for more details.
+#
+#   You should have received a copy of the GNU Library General Public
+#   License along with this library; if not, write to the
+#   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+#   Boston, MA 02111-1307, USA.
+
+"""A package for constructing and manipulating triangulated surfaces.
+
+PyGTS is a python binding for the GNU Triangulated Surface (GTS) 
+Library, which may be used to build, manipulate, and perform
+computations on triangulated surfaces.
+
+The following geometric primitives are provided:
+
+  Point - a point in 3D space
+  Vertex - a Point in 3D space that may be used to define a Segment
+  Segment - a line defined by two Vertex end-points
+  Edge - a Segment that may be used to define the edge of a Triangle
+  Triangle - a triangle defined by three Edges
+  Face - a Triangle that may be used to define a face on a Surface
+  Surface - a surface composed of Faces
+
+A tetrahedron is assembled from these primitives as follows.  First,
+create Vertices for each of the tetrahedron's points:
+
+    import gts
+
+    v1 = gts.Vertex(1,1,1)
+    v2 = gts.Vertex(-1,-1,1)
+    v3 = gts.Vertex(-1,1,-1)
+    v4 = gts.Vertex(1,-1,-1)
+
+Next, connect the four vertices to create six unique Edges:
+
+    e1 = gts.Edge(v1,v2)
+    e2 = gts.Edge(v2,v3)
+    e3 = gts.Edge(v3,v1)
+    e4 = gts.Edge(v1,v4)
+    e5 = gts.Edge(v4,v2)
+    e6 = gts.Edge(v4,v3)
+
+The four triangular faces are composed using three edges each:
+
+    f1 = gts.Face(e1,e2,e3)
+    f2 = gts.Face(e1,e4,e5)
+    f3 = gts.Face(e2,e5,e6)
+    f4 = gts.Face(e3,e4,e6)
+
+Finally, the surface is assembled from the faces:
+
+    s = gts.Surface()
+    for face in [f1,f2,f3,f4]:
+        s.add(face)
+
+Some care must be taken in the orientation of the faces.  In the above
+example, the surface normals are pointing inward, and so the surface
+technically defines a void, rather than a solid.  To create a 
+tetrahedron with surface normals pointing outward, use the following
+instead:
+
+    f1.revert()
+    s = Surface()
+    for face in [f1,f2,f3,f4]:
+        if not face.is_compatible(s):
+            face.revert()
+        s.add(face)
+
+Once the Surface is constructed, there are many different operations that
+can be performed.  For example, the volume can be calculated using:
+
+    s.volume()
+
+The difference between two Surfaces s1 and s2 is given by:
+
+    s3 = s2.difference(s1)
+
+Etc.
+
+It is also possible to read in GTS data files and plot surfaces to
+the screen.  See the example programs packaged with PyGTS for
+more information.
+"""
+
+from pygts import *

Added: trunk/lib/py/pygts-0.3.1/cleanup.c
===================================================================
--- trunk/lib/py/pygts-0.3.1/cleanup.c	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/cleanup.c	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,491 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 1999 St�ane Popinet
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+
+/*
+ * Below are functions for cleaning up duplicated edges and faces on
+ * a surface.  This file contains modified functions from the GTS 
+ * distribution.
+ */
+
+#include "pygts.h"
+
+
+/**
+ * Original documentation from GTS's vertex.c:
+ *
+ * gts_vertices_merge:
+ * @vertices: a list of #GtsVertex.
+ * @epsilon: half the size of the bounding box to consider for each vertex.
+ * @check: function called for each pair of vertices about to be merged
+ * or %NULL.
+ *
+ * For each vertex v in @vertices look if there are any vertex of
+ * @vertices contained in a box centered on v of size 2*@epsilon. If
+ * there are and if @check is not %NULL and returns %TRUE, replace
+ * them with v (using gts_vertex_replace()), destroy them and remove
+ * them from list.  This is done efficiently using Kd-Trees.
+ *
+ * Returns: the updated list of vertices.  
+ */
+/* This function is modified from the original in GTS in order to avoid 
+ * deallocating any objects referenced by the live-objects table.  The 
+ * approach is similar to what is used for replace() in vertex.c.
+ */
+GList*
+pygts_vertices_merge(GList* vertices, gdouble epsilon,
+		     gboolean (* check) (GtsVertex *, GtsVertex *))
+{
+  GPtrArray *array;
+  GList *i, *next;
+  GNode *kdtree;
+  GtsVertex *v;
+  GtsBBox *bbox;
+  GSList *selected, *j;
+  GtsVertex *sv;
+  PygtsObject *obj;
+  PygtsVertex *vertex=NULL;
+  GSList *parents=NULL, *ii,*cur;
+
+  g_return_val_if_fail(vertices != NULL, 0);
+
+  array = g_ptr_array_new();
+  i = vertices;
+  while (i) {
+    g_ptr_array_add(array, i->data);
+    i = g_list_next(i);
+  }
+  kdtree = gts_kdtree_new(array, NULL);
+  g_ptr_array_free(array, TRUE);
+
+  i = vertices;
+  while(i) {
+    v = i->data;
+    if (!GTS_OBJECT(v)->reserved) { /* Do something only if v is active */
+
+      /* build bounding box */
+      bbox = gts_bbox_new(gts_bbox_class(), v, 
+			  GTS_POINT(v)->x - epsilon,
+			  GTS_POINT(v)->y - epsilon,
+			  GTS_POINT(v)->z - epsilon,
+			  GTS_POINT(v)->x + epsilon,
+			  GTS_POINT(v)->y + epsilon,
+			  GTS_POINT(v)->z + epsilon);
+
+      /* select vertices which are inside bbox using kdtree */
+      j = selected = gts_kdtree_range(kdtree, bbox, NULL);
+      while(j) {
+        sv = j->data;
+        if( sv!=v && !GTS_OBJECT(sv)->reserved && (!check||(*check)(sv, v)) ) {
+          /* sv is not v and is active */
+	  if( (obj = g_hash_table_lookup(obj_table,GTS_OBJECT(sv))) !=NULL ) {
+	    vertex = PYGTS_VERTEX(obj);
+	    /* Detach and save any parent segments */
+	    ii = sv->segments;
+	    while(ii!=NULL) {
+	      cur = ii;
+	      ii = g_slist_next(ii);
+	      if(PYGTS_IS_PARENT_SEGMENT(cur->data)) {
+		sv->segments = g_slist_remove_link(sv->segments, cur);
+		parents = g_slist_prepend(parents,cur->data);
+		g_slist_free_1(cur);
+	      }
+	    } 
+	  }
+
+          gts_vertex_replace(sv, v);
+          GTS_OBJECT(sv)->reserved = sv; /* mark sv as inactive */
+
+	  /* Reattach the parent segments */
+	  if( vertex != NULL ) {
+	    ii = parents;
+	    while(ii!=NULL) {
+	      sv->segments = g_slist_prepend(sv->segments, ii->data);
+	      ii = g_slist_next(ii);
+	    }
+	    g_slist_free(parents);
+	    parents = NULL;
+	  }
+	  vertex = NULL;
+        }
+        j = g_slist_next(j);
+      }
+      g_slist_free(selected);
+      gts_object_destroy(GTS_OBJECT(bbox));
+    }
+    i = g_list_next(i);
+  }
+  gts_kdtree_destroy(kdtree);
+
+
+  /* destroy inactive vertices and removes them from list */
+
+  /* we want to control vertex destruction */
+  gts_allow_floating_vertices = TRUE;
+
+  i = vertices;
+  while (i) {
+    v = i->data;
+    next = g_list_next(i);
+    if(GTS_OBJECT(v)->reserved) { /* v is inactive */
+      if( g_hash_table_lookup(obj_table,GTS_OBJECT(v))==NULL ) {
+	gts_object_destroy(GTS_OBJECT(v));
+      }
+      else {
+	GTS_OBJECT(v)->reserved = 0;
+      }
+      vertices = g_list_remove_link(vertices, i);
+      g_list_free_1(i);
+    }
+    i = next;
+  }
+  gts_allow_floating_vertices = FALSE; 
+
+  return vertices;
+}
+
+
+static void 
+build_list(gpointer data, GSList ** list)
+{
+  *list = g_slist_prepend(*list, data);
+}
+
+
+static void
+build_list1(gpointer data, GList ** list)
+{
+  *list = g_list_prepend(*list, data);
+}
+
+
+void 
+pygts_vertex_cleanup(GtsSurface *s, gdouble threshold)
+{
+  GList * vertices = NULL;
+
+  /* merge vertices which are close enough */
+  /* build list of vertices */
+  gts_surface_foreach_vertex(s, (GtsFunc) build_list1, &vertices);
+
+  /* merge vertices: we MUST update the variable vertices because this function
+     modifies the list (i.e. removes the merged vertices). */
+  vertices = pygts_vertices_merge(vertices, threshold, NULL);
+
+  /* free the list */
+  g_list_free(vertices);
+}
+
+
+void 
+pygts_edge_cleanup(GtsSurface *s)
+{
+  GSList *edges = NULL;
+  GSList *i, *ii, *cur, *parents=NULL;
+  PygtsEdge *edge;
+  GtsEdge *e, *duplicate;
+
+  g_return_if_fail(s != NULL);
+
+  /* build list of edges */
+  gts_surface_foreach_edge(s, (GtsFunc)build_list, &edges);
+
+  /* remove degenerate and duplicate edges.
+     Note: we could use gts_edges_merge() to remove the duplicates and then
+     remove the degenerate edges but it is more efficient to do everything 
+     at once (and it's more pedagogical too ...) */
+
+  /* We want to control manually the destruction of edges */
+  gts_allow_floating_edges = TRUE;
+
+  i = edges;
+  while(i) {
+    e = i->data;
+    if(GTS_SEGMENT(e)->v1 == GTS_SEGMENT(e)->v2) {
+      /* edge is degenerate */
+      if( !g_hash_table_lookup(obj_table,GTS_OBJECT(e)) ) {
+	/* destroy e */
+	gts_object_destroy(GTS_OBJECT(e));
+      }
+    }
+    else {
+      if((duplicate = gts_edge_is_duplicate(e))) {
+
+	/* Detach and save any parent triangles */
+	if( (edge = PYGTS_EDGE(g_hash_table_lookup(obj_table,GTS_OBJECT(e))))
+	    !=NULL ) {
+	  ii = e->triangles;
+	  while(ii!=NULL) {
+	    cur = ii;
+	    ii = g_slist_next(ii);
+	    if(PYGTS_IS_PARENT_TRIANGLE(cur->data)) {
+	      e->triangles = g_slist_remove_link(e->triangles, cur);
+	      parents = g_slist_prepend(parents,cur->data);
+	      g_slist_free_1(cur);
+	    }
+	  } 
+	}
+
+	/* replace e with its duplicate */
+	gts_edge_replace(e, duplicate);
+
+	/* Reattach the parent segments */
+	if( edge != NULL ) {
+	  ii = parents;
+	  while(ii!=NULL) {
+	    e->triangles = g_slist_prepend(e->triangles, ii->data);
+	    ii = g_slist_next(ii);
+	  }
+	  g_slist_free(parents);
+	  parents = NULL;
+	}
+
+	if( !g_hash_table_lookup(obj_table,GTS_OBJECT(e)) ) {
+	  /* destroy e */
+	  gts_object_destroy(GTS_OBJECT (e));
+	}
+      }
+    }
+    i = g_slist_next(i);
+  }
+  
+  /* don't forget to reset to default */
+  gts_allow_floating_edges = FALSE;
+
+  /* free list of edges */
+  g_slist_free (edges);
+}
+
+
+void 
+pygts_face_cleanup(GtsSurface * s)
+{
+  GSList *triangles = NULL;
+  GSList * i;
+
+  g_return_if_fail(s != NULL);
+
+  /* build list of triangles */
+  gts_surface_foreach_face(s, (GtsFunc) build_list, &triangles);
+  
+  /* remove duplicate and degenerate triangles */
+  i = triangles;
+  while(i) {
+    GtsTriangle * t = i->data;
+    if (!gts_triangle_is_ok(t)) {
+      /* destroy t, its edges (if not used by any other triangle)
+	 and its corners (if not used by any other edge) */
+      if( g_hash_table_lookup(obj_table,GTS_OBJECT(t))==NULL ) {
+	gts_object_destroy(GTS_OBJECT(t));
+      }
+      else {
+	gts_surface_remove_face(PYGTS_SURFACE_AS_GTS_SURFACE(s),GTS_FACE(t));
+      }
+    }
+    i = g_slist_next(i);
+  }
+  
+  /* free list of triangles */
+  g_slist_free(triangles);
+}
+
+
+/* old main program (below) - retained as an example of how to use the
+ * functions above.
+ */
+
+/* cleanup - using a given threshold merge vertices which are too close.
+   Eliminate degenerate and duplicate edges.
+   Eliminate duplicate triangles . */
+/* static int  */
+/* main (int argc, char * argv[]) */
+/* { */
+/*   GtsSurface * s, * m; */
+/*   GList * vertices = NULL; */
+/*   gboolean verbose = FALSE, sever = FALSE, boundary = FALSE; */
+/*   gdouble threshold; */
+/*   int c = 0; */
+/*   GtsFile * fp; */
+/*   gboolean (* check) (GtsVertex *, GtsVertex *) = NULL; */
+
+/*   if (!setlocale (LC_ALL, "POSIX")) */
+/*     g_warning ("cannot set locale to POSIX"); */
+
+/*   s = gts_surface_new (gts_surface_class (), */
+/* 		       gts_face_class (), */
+/* 		       gts_edge_class (), */
+/* 		       gts_vertex_class ()); */
+
+/*   /\* parse options using getopt *\/ */
+/*   while (c != EOF) { */
+/* #ifdef HAVE_GETOPT_LONG */
+/*     static struct option long_options[] = { */
+/*       {"2D", no_argument, NULL, 'c'}, */
+/*       {"boundary", no_argument, NULL, 'b'}, */
+/*       {"merge", required_argument, NULL, 'm'}, */
+/*       {"sever", no_argument, NULL, 's'}, */
+/*       {"help", no_argument, NULL, 'h'}, */
+/*       {"verbose", no_argument, NULL, 'v'}, */
+/*       { NULL } */
+/*     }; */
+/*     int option_index = 0; */
+/*     switch ((c = getopt_long (argc, argv, "hvsm:bc", */
+/* 			      long_options, &option_index))) { */
+/* #else /\* not HAVE_GETOPT_LONG *\/ */
+/*     switch ((c = getopt (argc, argv, "hvsm:bc"))) { */
+/* #endif /\* not HAVE_GETOPT_LONG *\/ */
+/*     case 'c': /\* 2D *\/ */
+/*       check = check_boundaries; */
+/*       break; */
+/*     case 'b': /\* boundary *\/ */
+/*       boundary = TRUE; */
+/*       break; */
+/*     case 's': /\* sever *\/ */
+/*       sever = TRUE; */
+/*       break; */
+/*     case 'm': { /\* merge *\/ */
+/*       FILE * fptr = fopen (optarg, "rt"); */
+/*       GtsFile * fp; */
+
+/*       if (fptr == NULL) { */
+/* 	fprintf (stderr, "cleanup: cannot open file `%s' for merging\n", */
+/* 		 optarg); */
+/* 	return 1; /\* failure *\/ */
+/*       } */
+/*       m = gts_surface_new (gts_surface_class (), */
+/* 			   gts_face_class (), */
+/* 			   gts_edge_class (), */
+/* 			   s->vertex_class); */
+/*       fp = gts_file_new (fptr); */
+/*       if (gts_surface_read (m, fp)) { */
+/* 	fprintf (stderr, "cleanup: file `%s' is not a valid GTS file\n", */
+/* 		 optarg); */
+/* 	fprintf (stderr, "%s:%d:%d: %s\n", */
+/* 		 optarg, fp->line, fp->pos, fp->error); */
+/* 	return 1; /\* failure *\/ */
+/*       } */
+/*       gts_file_destroy (fp); */
+/*       fclose (fptr); */
+/*       gts_surface_merge (s, m); */
+/*       gts_object_destroy (GTS_OBJECT (m)); */
+/*       break; */
+/*     } */
+/*     case 'v': /\* verbose *\/ */
+/*       verbose = TRUE; */
+/*       break; */
+/*     case 'h': /\* help *\/ */
+/*       fprintf (stderr, */
+/* 	       "Usage: cleanup [OPTION] THRESHOLD < FILE\n" */
+/* 	       "Merge vertices of the GTS surface FILE if they are closer than THRESHOLD,\n" */
+/* 	       "eliminate degenerate, duplicate edges and duplicate triangles.\n" */
+/* 	       "\n" */
+/* 	       "  -c      --2D        2D boundary merging\n" */
+/* 	       "  -b      --boundary  only consider boundary vertices for merging\n" */
+/* 	       "  -s      --sever     sever \"contact\" vertices\n" */
+/* 	       "  -m FILE --merge     merge surface FILE\n" */
+/* 	       "  -v      --verbose   print statistics about the surface\n" */
+/* 	       "  -h      --help      display this help and exit\n" */
+/* 	       "\n" */
+/* 	       "Report bugs to %s\n", */
+/* 	       GTS_MAINTAINER); */
+/*       return 0; /\* success *\/ */
+/*       break; */
+/*     case '?': /\* wrong options *\/ */
+/*       fprintf (stderr, "Try `cleanup --help' for more information.\n"); */
+/*       return 1; /\* failure *\/ */
+/*     } */
+/*   } */
+
+/*   if (optind >= argc) { /\* missing threshold *\/ */
+/*     fprintf (stderr, */
+/* 	     "cleanup: missing THRESHOLD\n" */
+/* 	     "Try `cleanup --help' for more information.\n"); */
+/*     return 1; /\* failure *\/ */
+/*   } */
+
+/*   threshold = atof (argv[optind]); */
+
+/*   if (threshold < 0.0) { /\* threshold must be positive *\/ */
+/*      fprintf (stderr, */
+/* 	     "cleanup: THRESHOLD must be >= 0.0\n" */
+/* 	     "Try `cleanup --help' for more information.\n"); */
+/*     return 1; /\* failure *\/ */
+/*   } */
+
+/*   /\* read surface in *\/ */
+/*   m = gts_surface_new (gts_surface_class (), */
+/* 		       gts_face_class (), */
+/* 		       gts_edge_class (), */
+/* 		       s->vertex_class); */
+/*   fp = gts_file_new (stdin); */
+/*   if (gts_surface_read (m, fp)) { */
+/*     fputs ("cleanup: file on standard input is not a valid GTS file\n", */
+/* 	   stderr); */
+/*     fprintf (stderr, "stdin:%d:%d: %s\n", fp->line, fp->pos, fp->error); */
+/*     return 1; /\* failure *\/ */
+/*   } */
+/*   gts_surface_merge (s, m); */
+/*   gts_object_destroy (GTS_OBJECT (m)); */
+
+/*   /\* if verbose on print stats *\/ */
+/*   if (verbose) */
+/*     gts_surface_print_stats (s, stderr); */
+ 
+/*   /\* merge vertices which are close enough *\/ */
+/*   /\* build list of vertices *\/ */
+/*   gts_surface_foreach_vertex (s, boundary ? (GtsFunc) build_list2 : (GtsFunc) build_list1, */
+/* 			      &vertices); */
+/*   /\* merge vertices: we MUST update the variable vertices because this function */
+/*      modifies the list (i.e. removes the merged vertices). *\/ */
+/*   vertices = gts_vertices_merge (vertices, threshold, check); */
+
+/*   /\* free the list *\/ */
+/*   g_list_free (vertices); */
+
+/*   /\* eliminate degenerate and duplicate edges *\/ */
+/*   edge_cleanup (s); */
+/*   /\* eliminate duplicate triangles *\/ */
+/*   triangle_cleanup (s); */
+
+/*   if (sever) */
+/*     gts_surface_foreach_vertex (s, (GtsFunc) vertex_cleanup, NULL); */
+
+/*   /\* if verbose on print stats *\/ */
+/*   if (verbose) { */
+/*     GtsBBox * bb = gts_bbox_surface (gts_bbox_class (), s); */
+/*     gts_surface_print_stats (s, stderr); */
+/*     fprintf (stderr, "# Bounding box: [%g,%g,%g] [%g,%g,%g]\n", */
+/* 	     bb->x1, bb->y1, bb->z1, */
+/* 	     bb->x2, bb->y2, bb->z2); */
+/*   } */
+
+/*   /\* write surface *\/ */
+/*   gts_surface_write (s, stdout); */
+
+/*   return 0; /\* success *\/ */
+/* } */

Added: trunk/lib/py/pygts-0.3.1/cleanup.h
===================================================================
--- trunk/lib/py/pygts-0.3.1/cleanup.h	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/cleanup.h	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,44 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+
+/*
+ *  Below are functions for cleaning up duplicated edges and faces on
+ *  a surface.  This file was adapted from the example file of the same
+ *  name in the GTS distribution.
+ */
+
+#ifndef __PYGTS_CLEANUP_H__
+#define __PYGTS_CLEANUP_H__
+
+GList* pygts_vertices_merge(GList* vertices, gdouble epsilon,
+			    gboolean (* check) (GtsVertex *, GtsVertex *));
+void pygts_vertex_cleanup(GtsSurface *s, gdouble threhold);
+void pygts_edge_cleanup(GtsSurface * s);
+void pygts_face_cleanup(GtsSurface * s);
+
+#endif /* __PYGTS_CLEANUP_H__ */

Added: trunk/lib/py/pygts-0.3.1/edge.c
===================================================================
--- trunk/lib/py/pygts-0.3.1/edge.c	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/edge.c	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,639 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#include "pygts.h"
+
+
+#if PYGTS_DEBUG
+  #define SELF_CHECK if(!pygts_edge_check((PyObject*)self)) {         \
+                       PyErr_SetString(PyExc_RuntimeError,            \
+                       "problem with self object (internal error)");  \
+		       return NULL;                                   \
+                     }
+#else
+  #define SELF_CHECK
+#endif
+
+
+/*-------------------------------------------------------------------------*/
+/* Methods exported to python */
+
+static PyObject*
+is_ok(PygtsEdge *self, PyObject *args)
+{
+  if(pygts_edge_is_ok(self)) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+is_unattached(PygtsEdge *self, PyObject *args)
+{
+  guint n;
+
+  SELF_CHECK
+
+  /* Check for attachments other than to the gtsobj_parent */
+  n = g_slist_length(PYGTS_EDGE_AS_GTS_EDGE(self)->triangles);
+  if( n > 1 ) {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+  else if( n == 1 ){
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    PyErr_SetString(PyExc_RuntimeError,"Edge lost parent (internal error)");
+    return NULL;
+  }
+}
+
+
+/* replace() works, but can break Triangles and so is disabled */
+
+/* static PyObject* */
+/* replace(PygtsEdge *self, PyObject *args) */
+/* { */
+/*   PyObject *e2_; */
+/*   PygtsEdge *e2; */
+/*   GSList *parents=NULL, *i, *cur; */
+
+/* #if PYGTS_DEBUG */
+/*   if(!pygts_edge_check((PyObject*)self)) { */
+/*     PyErr_SetString(PyExc_TypeError, */
+/* 		    "problem with self object (internal error)"); */
+/*     return NULL; */
+/*   } */
+/* #endif */
+
+/*   /\* Parse the args *\/   */
+/*   if(! PyArg_ParseTuple(args, "O", &e2_) ) { */
+/*     return NULL; */
+/*   } */
+
+/*   /\* Convert to PygtsObjects *\/ */
+/*   if(!pygts_edge_check(e2_)) { */
+/*     PyErr_SetString(PyExc_TypeError,"expected an Edge"); */
+/*     return NULL; */
+/*   } */
+/*   e2 = PYGTS_EDGE(e2_); */
+
+/*   if(PYGTS_OBJECT(self)->gtsobj!=PYGTS_OBJECT(e2)->gtsobj) { */
+/*     /\* (Ignore self-replacement) *\/ */
+
+/*     /\* Detach and save any parent triangles *\/ */
+/*     i = GTS_EDGE(PYGTS_OBJECT(self)->gtsobj)->triangles; */
+/*     while(i!=NULL) { */
+/*       cur = i; */
+/*       i = i->next; */
+/*       if(PYGTS_IS_PARENT_TRIANGLE(cur->data)) { */
+/* 	GTS_EDGE(PYGTS_OBJECT(self)->gtsobj)->triangles =  */
+/* 	  g_slist_remove_link(GTS_EDGE(PYGTS_OBJECT(self)->gtsobj)->triangles, */
+/* 			      cur); */
+/* 	parents = g_slist_prepend(parents,cur->data); */
+/* 	g_slist_free_1(cur); */
+/*       } */
+/*     } */
+
+/*     /\* Perform the replace operation *\/ */
+/*     gts_edge_replace(GTS_EDGE(PYGTS_OBJECT(self)->gtsobj), */
+/* 		     GTS_EDGE(PYGTS_OBJECT(e2)->gtsobj)); */
+
+/*     /\* Reattach the parent segments *\/ */
+/*     i = parents; */
+/*     while(i!=NULL) { */
+/*       GTS_EDGE(PYGTS_OBJECT(self)->gtsobj)->triangles =  */
+/* 	g_slist_prepend(GTS_EDGE(PYGTS_OBJECT(self)->gtsobj)->triangles, */
+/* 			i->data); */
+/*       i = i->next; */
+/*     } */
+/*     g_slist_free(parents); */
+/*   } */
+
+/* #if PYGTS_DEBUG */
+/*   if(!pygts_edge_check((PyObject*)self)) { */
+/*     PyErr_SetString(PyExc_TypeError, */
+/* 		    "problem with self object (internal error)"); */
+/*     return NULL; */
+/*   } */
+/* #endif */
+
+/*   Py_INCREF(Py_None); */
+/*   return Py_None; */
+/* } */
+
+
+static PyObject*
+face_number(PygtsEdge *self, PyObject *args)
+{
+  PyObject *s_;
+  GtsSurface *s;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "O", &s_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_surface_check(s_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Surface");
+    return NULL;
+  }
+  s = PYGTS_SURFACE_AS_GTS_SURFACE(s_);
+
+  return Py_BuildValue("i",
+		       gts_edge_face_number(PYGTS_EDGE_AS_GTS_EDGE(self),s));
+}
+
+
+static PyObject*
+belongs_to_tetrahedron(PygtsEdge *self, PyObject *args)
+{
+  SELF_CHECK
+
+  if(gts_edge_belongs_to_tetrahedron(PYGTS_EDGE_AS_GTS_EDGE(self))) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+is_boundary(PygtsEdge *self, PyObject *args)
+{
+  PyObject *s_;
+  GtsSurface *s;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "O", &s_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_surface_check(s_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Surface");
+    return NULL;
+  }
+  s = PYGTS_SURFACE_AS_GTS_SURFACE(s_);
+
+  /* Make the call and return */
+  if(gts_edge_is_boundary(PYGTS_EDGE_AS_GTS_EDGE(self),s)!=NULL) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+contacts(PygtsEdge *self, PyObject *args)
+{
+  SELF_CHECK
+
+  return Py_BuildValue("i",
+		       gts_edge_is_contact(PYGTS_EDGE_AS_GTS_EDGE(self)));
+}
+
+
+/* Methods table */
+static PyMethodDef methods[] = {
+  {"is_ok", (PyCFunction)is_ok,
+   METH_NOARGS,
+   "True if this Edge e is not degenerate or duplicate.\n"
+   "False otherwise.  Degeneracy implies e.v1.id == e.v2.id.\n"
+   "\n"
+   "Signature: e.is_ok()\n"
+  },  
+
+  {"is_unattached", (PyCFunction)is_unattached,
+   METH_NOARGS,
+   "True if this Edge e is not part of any Triangle.\n"
+   "\n"
+   "Signature: e.is_unattached()\n"
+  },
+
+/* Edge replace() method works but results in Triangles that are "not ok";
+ * i.e., they have edges that don't connect.  We don't want that problem
+ * and so the method has been disabled.
+ */
+/*
+  {"replace", (PyCFunction)replace,
+   METH_VARARGS,
+   "Replaces this Edge e1 with Edge e2 in all Triangles that have e1.\n"
+   "Edge e1 itself is left unchanged.\n"
+   "\n"
+   "Signature: e1.replace(e2).\n"
+  },
+*/
+
+  {"face_number", (PyCFunction)face_number,
+   METH_VARARGS,
+   "Returns number of faces using this Edge e on Surface s.\n"
+   "\n"
+   "Signature: e.face_number(s)\n"
+  },
+
+  {"is_boundary", (PyCFunction)is_boundary,
+   METH_VARARGS,
+   "Returns True if this Edge e is a boundary on Surface s.\n"
+   "Otherwise False.\n"
+   "\n"
+   "Signature: e.is_boundary(s)\n"
+  },
+
+  {"belongs_to_tetrahedron", (PyCFunction)belongs_to_tetrahedron,
+   METH_NOARGS,
+   "Returns True if this Edge e belongs to a tetrahedron.\n"
+   "Otherwise False.\n"
+   "\n"
+   "Signature: e.belongs_to_tetrahedron()\n"
+  },
+
+  {"contacts", (PyCFunction)contacts,
+   METH_NOARGS,
+   "Returns number of sets of connected triangles share this Edge e\n"
+   "as a contact Edge.\n"
+   "\n"
+   "Signature: e.contacts()\n"
+  },
+
+  {NULL}  /* Sentinel */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Python type methods */
+
+static GtsObject* parent(GtsEdge *e1);
+
+static PyObject *
+new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+  PyObject *o;
+  PygtsObject *obj;
+  GtsEdge *tmp;
+  GtsObject *edge=NULL;
+  PyObject *v1_,*v2_;
+  PygtsVertex *v1,*v2;
+  guint alloc_gtsobj = TRUE;
+  guint N;
+
+  /* Parse the args */
+  if(kwds) {
+    o = PyDict_GetItemString(kwds,"alloc_gtsobj");
+    if(o==Py_False) {
+      alloc_gtsobj = FALSE;
+    }
+    if(o!=NULL) {
+      PyDict_DelItemString(kwds, "alloc_gtsobj");
+    }
+  }
+  if(kwds) {
+    Py_INCREF(Py_False);
+    PyDict_SetItemString(kwds,"alloc_gtsobj", Py_False);
+  }
+
+  /* Allocate the gtsobj (if needed) */
+  if( alloc_gtsobj ) {
+
+    /* Parse the args */
+    if( (N = PyTuple_Size(args)) < 2 ) {
+      PyErr_SetString(PyExc_TypeError,"expected two Vertices");
+      return NULL;
+    }
+    v1_ = PyTuple_GET_ITEM(args,0);
+    v2_ = PyTuple_GET_ITEM(args,1);
+
+    /* Convert to PygtsObjects */
+    if(!pygts_vertex_check(v1_)) {
+      PyErr_SetString(PyExc_TypeError,"expected two Vertices");
+      return NULL;
+    }
+    if(!pygts_vertex_check(v2_)) {
+      PyErr_SetString(PyExc_TypeError,"expected two Vertices");
+      return NULL;
+    }
+    v1 = PYGTS_VERTEX(v1_);
+    v2 = PYGTS_VERTEX(v2_);
+
+    /* Error check */
+    if(PYGTS_OBJECT(v1)->gtsobj == PYGTS_OBJECT(v2)->gtsobj) {
+      PyErr_SetString(PyExc_ValueError,"Vertices given are the same");
+      return NULL;
+    }
+
+    /* Create the GtsEdge */
+    edge = GTS_OBJECT(gts_edge_new(gts_edge_class(),
+				   GTS_VERTEX(v1->gtsobj),
+				   GTS_VERTEX(v2->gtsobj)));
+    if( edge == NULL )  {
+      PyErr_SetString(PyExc_MemoryError, "could not create Edge");
+      return NULL;
+    }
+
+    /* Check for duplicate */
+    tmp = gts_edge_is_duplicate(GTS_EDGE(edge));
+    if( tmp != NULL ) {
+      gts_object_destroy(edge);
+      edge = GTS_OBJECT(tmp);
+    }
+
+    /* If corresponding PyObject found in object table, we are done */
+    if( (obj=g_hash_table_lookup(obj_table,edge)) != NULL ) {
+      Py_INCREF(obj);
+      return (PyObject*)obj;
+    }
+  }
+
+  /* Chain up */
+  obj = PYGTS_OBJECT(PygtsSegmentType.tp_new(type,args,kwds));
+
+  if( alloc_gtsobj ) {
+    obj->gtsobj = edge;
+
+    /* Create a parent GtsTriangle */
+    if( (obj->gtsobj_parent = parent(GTS_EDGE(obj->gtsobj))) == NULL ) {
+      gts_object_destroy(obj->gtsobj);
+      obj->gtsobj = NULL;
+      return NULL;
+    }
+
+    pygts_object_register(PYGTS_OBJECT(obj));
+  }
+
+  return (PyObject*)obj;
+}
+
+
+static int
+init(PygtsEdge *self, PyObject *args, PyObject *kwds)
+{
+  gint ret;
+
+  /* Chain up */
+  if( (ret=PygtsSegmentType.tp_init((PyObject*)self,args,kwds)) != 0 ){
+    return ret;
+  }
+
+  return 0;
+}
+
+
+/* Methods table */
+PyTypeObject PygtsEdgeType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                       /* ob_size */
+    "gts.Edge",              /* tp_name */
+    sizeof(PygtsEdge),       /* tp_basicsize */
+    0,                       /* tp_itemsize */
+    0,                       /* tp_dealloc */
+    0,                       /* tp_print */
+    0,                       /* tp_getattr */
+    0,                       /* tp_setattr */
+    0,                       /* tp_compare */
+    0,                       /* tp_repr */
+    0,                       /* tp_as_number */
+    0,                       /* tp_as_sequence */
+    0,                       /* tp_as_mapping */
+    0,                       /* tp_hash */
+    0,                       /* tp_call */
+    0,                       /* tp_str */
+    0,                       /* tp_getattro */
+    0,                       /* tp_setattro */
+    0,                       /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT |
+      Py_TPFLAGS_BASETYPE,   /* tp_flags */
+    "Edge object",           /* tp_doc */
+    0,                       /* tp_traverse */
+    0,                       /* tp_clear */
+    0,                       /* tp_richcompare */
+    0,                       /* tp_weaklistoffset */
+    0,                       /* tp_iter */
+    0,                       /* tp_iternext */
+    methods,                 /* tp_methods */
+    0,                       /* tp_members */
+    0,                       /* tp_getset */
+    0,                       /* tp_base */
+    0,                       /* tp_dict */
+    0,                       /* tp_descr_get */
+    0,                       /* tp_descr_set */
+    0,                       /* tp_dictoffset */
+    (initproc)init,          /* tp_init */
+    0,                       /* tp_alloc */
+    (newfunc)new             /* tp_new */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Pygts functions */
+
+gboolean 
+pygts_edge_check(PyObject* o)
+{
+  if(! PyObject_TypeCheck(o, &PygtsEdgeType)) {
+    return FALSE;
+  }
+  else {
+#if PYGTS_DEBUG
+    return pygts_edge_is_ok(PYGTS_EDGE(o));
+#else
+    return TRUE;
+#endif
+  }
+}
+
+gboolean
+pygts_edge_is_ok(PygtsEdge *e)
+{
+  GSList *parent;
+  PygtsObject *obj;
+
+  obj = PYGTS_OBJECT(e);
+
+  if(!pygts_segment_is_ok(PYGTS_SEGMENT(e))) return FALSE;
+
+  /* Check for a valid parent */
+  g_return_val_if_fail(obj->gtsobj_parent!=NULL,FALSE);
+  g_return_val_if_fail(PYGTS_IS_PARENT_TRIANGLE(obj->gtsobj_parent),FALSE);
+  parent = g_slist_find(GTS_EDGE(obj->gtsobj)->triangles,
+			obj->gtsobj_parent);
+  g_return_val_if_fail(parent!=NULL,FALSE);
+
+  return TRUE;
+}
+
+
+static GtsObject* 
+parent(GtsEdge *e1) {
+  GtsVertex *v1,*v2,*v3;
+  GtsPoint *p1,*p2;
+  GtsEdge *e2, *e3;
+  GtsTriangle *p;
+
+  /* Create a third vertex for the triangle */
+  v1 = GTS_SEGMENT(e1)->v1;
+  v2 = GTS_SEGMENT(e1)->v2;
+  p1 = GTS_POINT(v1);
+  p2 = GTS_POINT(v2);
+  v3 = gts_vertex_new(pygts_parent_vertex_class(),
+		      p1->x+p2->x,p1->y+p2->y,p1->z+p2->z);
+  if( v3 == NULL ) {
+    PyErr_SetString(PyExc_MemoryError, "could not create Vertex");
+    return NULL;
+  }
+
+  /* Create another two edges */
+  if( (e2 = gts_edge_new(pygts_parent_edge_class(),v2,v3)) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError, "could not create Edge");
+    return NULL;
+  }
+  if( (e3 = gts_edge_new(pygts_parent_edge_class(),v3,v1)) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError, "could not create Edge");
+    gts_object_destroy(GTS_OBJECT(e2));
+    return NULL;
+  }
+
+  /* Create and return the parent */
+  if( (p = gts_triangle_new(pygts_parent_triangle_class(),e1,e2,e3)) 
+      == NULL ) {
+    gts_object_destroy(GTS_OBJECT(e2));
+    gts_object_destroy(GTS_OBJECT(e3));
+    PyErr_SetString(PyExc_MemoryError, "could not create Triangle");
+    return NULL;
+  }
+
+  return GTS_OBJECT(p);
+}
+
+
+PygtsEdge *
+pygts_edge_new(GtsEdge *e)
+{
+  PyObject *args, *kwds;
+  PygtsObject *edge;
+
+  /* Check for Edge in the object table */
+  if( (edge = PYGTS_OBJECT(g_hash_table_lookup(obj_table,GTS_OBJECT(e)))) 
+      !=NULL ) {
+    Py_INCREF(edge);
+    return PYGTS_EDGE(edge);
+  }
+
+  /* Build a new Edge */
+  args = Py_BuildValue("OO",Py_None,Py_None);
+  kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_False);
+  edge = PYGTS_EDGE(PygtsEdgeType.tp_new(&PygtsEdgeType, args, kwds));
+  Py_DECREF(args);
+  Py_DECREF(kwds);
+  if( edge == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"could not create Edge");
+    return NULL;
+  }
+  edge->gtsobj = GTS_OBJECT(e);
+
+  /* Attach the parent */
+  if( (edge->gtsobj_parent = parent(e)) == NULL ) {
+    Py_DECREF(edge);
+    return NULL;
+  }
+
+  /* Register and return */
+  pygts_object_register(edge);
+  return PYGTS_EDGE(edge);
+}
+
+
+GtsTriangleClass*
+pygts_parent_triangle_class(void)
+{
+  static GtsTriangleClass *klass = NULL;
+  GtsObjectClass *super = NULL;
+
+  if (klass == NULL) {
+
+    super = GTS_OBJECT_CLASS(gts_triangle_class());
+
+    GtsObjectClassInfo pygts_parent_triangle_info = {
+      "PygtsParentTriangle",
+      sizeof(PygtsParentTriangle),
+      sizeof(GtsTriangleClass),
+      (GtsObjectClassInitFunc)(super->info.class_init_func),
+      (GtsObjectInitFunc)(super->info.object_init_func),
+      (GtsArgSetFunc) NULL,
+      (GtsArgGetFunc) NULL
+    };
+    klass = gts_object_class_new(gts_object_class(),
+				 &pygts_parent_triangle_info);
+  }
+
+  return klass;
+}
+
+
+GtsEdgeClass*
+pygts_parent_edge_class(void)
+{
+  static GtsEdgeClass *klass = NULL;
+  GtsObjectClass *super = NULL;
+
+  if (klass == NULL) {
+
+    super = GTS_OBJECT_CLASS(pygts_parent_segment_class());
+
+    GtsObjectClassInfo pygts_parent_edge_info = {
+      "PygtsParentEdge",
+      sizeof(PygtsParentEdge),
+      sizeof(GtsEdgeClass),
+      (GtsObjectClassInitFunc)(super->info.class_init_func),
+      (GtsObjectInitFunc)(super->info.object_init_func),
+      (GtsArgSetFunc) NULL,
+      (GtsArgGetFunc) NULL
+    };
+    klass = gts_object_class_new(gts_object_class(),
+				 &pygts_parent_edge_info);
+  }
+
+  return klass;
+}

Added: trunk/lib/py/pygts-0.3.1/edge.h
===================================================================
--- trunk/lib/py/pygts-0.3.1/edge.h	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/edge.h	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,78 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PYGTS_EDGE_H__
+#define __PYGTS_EDGE_H__
+
+typedef struct _PygtsObject PygtsEdge;
+
+#define PYGTS_EDGE(obj) ((PygtsEdge*)obj)
+
+#define PYGTS_EDGE_AS_GTS_EDGE(o) (GTS_EDGE(PYGTS_OBJECT(o)->gtsobj))
+
+extern PyTypeObject PygtsEdgeType;
+
+gboolean pygts_edge_check(PyObject* o);
+gboolean pygts_edge_is_ok(PygtsEdge *e);
+
+PygtsEdge* pygts_edge_new(GtsEdge *e);
+
+
+/*-------------------------------------------------------------------------*/
+/* Parent GTS triangle for GTS edges */
+
+/* Define a GtsTriangle subclass that can be readily identified as the parent 
+ * of an encapsulated GtsEdge.  The pygts_parent_triangle_class() function 
+ * is defined at the bottom, and is what ultimately allows the distinction 
+ * to be made.  This capability is used for edge replacement operations.
+ */
+typedef struct _GtsTriangle PygtsParentTriangle;
+
+#define PYGTS_PARENT_TRIANGLE(obj) GTS_OBJECT_CAST(obj,\
+					      GtsTriangle,\
+					      pygts_parent_triangle_class())
+
+#define PYGTS_IS_PARENT_TRIANGLE(obj)(gts_object_is_from_class(obj,\
+                                             pygts_parent_triangle_class()))
+
+GtsTriangleClass* pygts_parent_triangle_class(void);
+
+
+/* GTS edges in parent triangles */
+
+typedef struct _GtsEdge PygtsParentEdge;
+
+#define PYGTS_PARENT_EDGE(obj) GTS_OBJECT_CAST(obj,\
+						 GtsEdge,\
+						 pygts_parent_edge_class())
+
+#define PYGTS_IS_PARENT_EDGE(obj)(gts_object_is_from_class(obj,\
+                                             pygts_parent_edge_class()))
+
+GtsEdgeClass* pygts_parent_edge_class(void);
+
+#endif /* __PYGTS_EDGE_H__ */

Added: trunk/lib/py/pygts-0.3.1/face.c
===================================================================
--- trunk/lib/py/pygts-0.3.1/face.c	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/face.c	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,646 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#include "pygts.h"
+
+
+#if PYGTS_DEBUG
+  #define SELF_CHECK if(!pygts_face_check((PyObject*)self)) {         \
+                       PyErr_SetString(PyExc_RuntimeError,            \
+                       "problem with self object (internal error)");  \
+		       return NULL;                                   \
+                     }
+#else
+  #define SELF_CHECK
+#endif
+
+
+/*-------------------------------------------------------------------------*/
+/* Methods exported to python */
+
+static PyObject*
+is_ok(PygtsFace *self, PyObject *args)
+{
+  if(pygts_face_is_ok(self)) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+is_unattached(PygtsFace *self, PyObject *args)
+{
+  guint n;
+
+  /* Check for attachments other than to the gtsobj_parent */
+  n = g_slist_length(PYGTS_FACE_AS_GTS_FACE(self)->surfaces);
+  if( n > 1 ) {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+  else if( n == 1 ){
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    PyErr_SetString(PyExc_RuntimeError, "Face lost parent (internal error)");
+    return NULL;
+  }
+}
+
+
+static PyObject*
+neighbor_number(PygtsFace *self, PyObject *args)
+{
+  PyObject *s_=NULL;
+  PygtsSurface *s=NULL;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &s_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if( pygts_surface_check(s_) ) {
+    s = PYGTS_SURFACE(s_);
+  }
+  else {
+    PyErr_SetString(PyExc_TypeError, "expected a Surface");
+      return NULL;
+  }
+
+  return Py_BuildValue("i",
+      gts_face_neighbor_number(PYGTS_FACE_AS_GTS_FACE(self),
+			       PYGTS_SURFACE_AS_GTS_SURFACE(s)));
+}
+
+
+static PyObject*
+neighbors(PygtsFace *self, PyObject *args)
+{
+  PyObject *s_=NULL;
+  PygtsSurface *s=NULL;
+  guint i,N;
+  PyObject *tuple;
+  GSList *faces,*f;
+  PygtsFace *face;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &s_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if( pygts_surface_check(s_) ) {
+    s = PYGTS_SURFACE(s_);
+  }
+  else {
+    PyErr_SetString(PyExc_TypeError, "expected a Surface");
+      return NULL;
+  }
+
+  N = gts_face_neighbor_number(PYGTS_FACE_AS_GTS_FACE(self),
+			       PYGTS_SURFACE_AS_GTS_SURFACE(s));
+
+  if( (tuple=PyTuple_New(N)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError, "Could not create tuple");
+    return NULL;
+  }
+
+  /* Get the neighbors */
+  faces = gts_face_neighbors(PYGTS_FACE_AS_GTS_FACE(self),
+			     PYGTS_SURFACE_AS_GTS_SURFACE(s));
+  f = faces;
+			     
+  for(i=0;i<N;i++) {
+    if( (face = pygts_face_new(GTS_FACE(f->data))) == NULL ) {
+      Py_DECREF(tuple);
+      return NULL;
+    }
+    PyTuple_SET_ITEM(tuple, i, (PyObject*)face);
+    f = g_slist_next(f);
+  }  
+
+  return (PyObject*)tuple;
+}
+
+
+static PyObject*
+is_compatible(PygtsFace *self, PyObject *args)
+{
+  PyObject *o1_=NULL;
+  GtsEdge *e=NULL;
+  PygtsTriangle *t=NULL;
+  PygtsSurface *s=NULL;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &o1_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if( pygts_triangle_check(o1_) ) {
+    t = PYGTS_TRIANGLE(o1_);
+  }
+  else {
+    if( pygts_surface_check(o1_) ) {
+      s = PYGTS_SURFACE(o1_);
+    }
+    else {
+      PyErr_SetString(PyExc_TypeError, "expected a Triangle or Surface");
+      return NULL;
+    }
+  }
+
+  if(t!=NULL) {
+    if( (e = gts_triangles_common_edge(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self),
+				       PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t))) 
+	== NULL ) {
+      PyErr_SetString(PyExc_RuntimeError, "Faces do not share common edge");
+      return NULL;
+    }
+    if(gts_triangles_are_compatible(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self),
+				    PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t),
+				    e)==TRUE) {
+
+      Py_INCREF(Py_True);
+      return Py_True;
+    }
+  }
+  else {
+    if(gts_face_is_compatible(PYGTS_FACE_AS_GTS_FACE(self),
+			      PYGTS_SURFACE_AS_GTS_SURFACE(s))==TRUE) {
+      Py_INCREF(Py_True);
+      return Py_True;
+    }
+  }
+  Py_INCREF(Py_False);
+  return Py_False;
+}
+
+
+static PyObject*
+is_on(PygtsFace *self, PyObject *args)
+{
+  PyObject *s_=NULL;
+  PygtsSurface *s=NULL;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &s_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if( pygts_surface_check(s_) ) {
+    s = PYGTS_SURFACE(s_);
+  }
+  else {
+    PyErr_SetString(PyExc_TypeError, "expected a Surface");
+      return NULL;
+  }
+
+  if( gts_face_has_parent_surface(PYGTS_FACE_AS_GTS_FACE(self),
+				  PYGTS_SURFACE_AS_GTS_SURFACE(s)) ) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+/* Methods table */
+static PyMethodDef methods[] = {
+  {"is_ok", (PyCFunction)is_ok,
+   METH_NOARGS,
+   "True if this Face f is non-degenerate and non-duplicate.\n"
+   "False otherwise.\n"
+   "\n"
+   "Signature: f.is_ok()\n"
+  },  
+
+  {"is_unattached", (PyCFunction)is_unattached,
+   METH_NOARGS,
+   "True if this Face f is not part of any Surface.\n"
+   "\n"
+   "Signature: f.is_unattached().\n"
+  },
+
+  {"neighbor_number", (PyCFunction)neighbor_number,
+   METH_VARARGS,
+   "Returns the number of neighbors of Face f belonging to Surface s.\n"
+   "\n"
+   "Signature: f.neighbor_number(s).\n"
+  },
+
+  {"neighbors", (PyCFunction)neighbors,
+   METH_VARARGS,
+   "Returns a tuple of neighbors of this Face f belonging to Surface s.\n"
+   "\n"
+   "Signature: f.neighbors(s).\n"
+  },
+
+  {"is_compatible", (PyCFunction)is_compatible,
+   METH_VARARGS,
+   "True if Face f is compatible with all neighbors in Surface s.\n"
+   "False otherwise.\n"
+   "\n"
+   "Signature: f.is_compatible(s).\n"
+  },  
+
+  {"is_on", (PyCFunction)is_on,
+   METH_VARARGS,
+   "True if this Face f is on Surface s.  False otherwise.\n"
+   "\n"
+   "Signature: f.is_on(s).\n"
+  },  
+
+  {NULL}  /* Sentinel */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Python type methods */
+
+
+static GtsObject * parent(GtsFace *face);
+
+static PyObject *
+new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+  PyObject *o;
+  PygtsObject *obj;
+  guint alloc_gtsobj = TRUE;
+  PyObject *o1_,*o2_,*o3_;
+  GtsVertex *v1=NULL, *v2=NULL, *v3=NULL;
+  GtsEdge *e1=NULL,*e2=NULL,*e3=NULL,*e;
+  GtsSegment *s1,*s2,*s3;
+  gboolean flag=FALSE;  /* Flag when the args are gts.Point objects */
+  GtsFace *f;
+  GtsTriangle *t;
+  guint N;
+
+  /* Parse the args */
+  if(kwds) {
+    o = PyDict_GetItemString(kwds,"alloc_gtsobj");
+    if(o==Py_False) {
+      alloc_gtsobj = FALSE;
+    }
+    if(o!=NULL) {
+      PyDict_DelItemString(kwds, "alloc_gtsobj");
+    }
+  }
+  if(kwds) {
+    Py_INCREF(Py_False);
+    PyDict_SetItemString(kwds,"alloc_gtsobj", Py_False);
+  }
+
+  /* Allocate the gtsobj (if needed) */
+  if( alloc_gtsobj ) {
+
+    /* Parse the args */
+    if( (N = PyTuple_Size(args)) < 3 ) {
+      PyErr_SetString(PyExc_TypeError,"expected three Edges or three Vertices");
+      return NULL;
+    }
+    o1_ = PyTuple_GET_ITEM(args,0);
+    o2_ = PyTuple_GET_ITEM(args,1);
+    o3_ = PyTuple_GET_ITEM(args,2);
+
+    /* Convert to PygtsObjects */
+    if( pygts_edge_check(o1_) ) {
+      e1 = PYGTS_EDGE_AS_GTS_EDGE(o1_);
+    }
+    else {
+      if( pygts_vertex_check(o1_) ) {
+	v1 = PYGTS_VERTEX_AS_GTS_VERTEX(o1_);
+	flag = TRUE;
+      }
+    }
+
+    if( pygts_edge_check(o2_) ) {
+      e2 = PYGTS_EDGE_AS_GTS_EDGE(o2_);
+    }
+    else {
+      if( pygts_vertex_check(o2_) ) {
+	v2 = PYGTS_VERTEX_AS_GTS_VERTEX(o2_);
+	flag = TRUE;
+      }
+    }
+
+    if( pygts_edge_check(o3_) ) {
+      e3 = PYGTS_EDGE_AS_GTS_EDGE(o3_);
+    }
+    else {
+      if(pygts_vertex_check(o3_)) {
+	v3 = PYGTS_VERTEX_AS_GTS_VERTEX(o3_);
+	flag = TRUE;
+      }
+    }
+    
+    /* Check for three edges or three vertices */
+    if( !((e1!=NULL && e2!=NULL && e3!=NULL) ||
+	  (v1!=NULL && v2!=NULL && v3!=NULL)) ) {
+      PyErr_SetString(PyExc_TypeError,
+		      "three Edge or three Vertex objects expected");
+      return NULL;
+    }
+
+    if(flag) {
+
+      /* Create gts edges */
+      if( (e1 = gts_edge_new(gts_edge_class(),v1,v2)) == NULL ) {
+	PyErr_SetString(PyExc_MemoryError, "could not create Edge");
+	return NULL;
+      }
+      if( (e2 = gts_edge_new(gts_edge_class(),v2,v3)) == NULL ) {
+	PyErr_SetString(PyExc_MemoryError, "could not create Edge");
+	gts_object_destroy(GTS_OBJECT(e1));
+	return NULL;
+      }
+      if( (e3 = gts_edge_new(gts_edge_class(),v3,v1)) == NULL ) {
+	PyErr_SetString(PyExc_MemoryError, "could not create Edge");
+	gts_object_destroy(GTS_OBJECT(e1));
+	gts_object_destroy(GTS_OBJECT(e2));
+	return NULL;
+      }
+      
+      /* Check for duplicates */
+      if( (e = gts_edge_is_duplicate(e1)) != NULL ) {
+	gts_object_destroy(GTS_OBJECT(e1));
+	e1 = e;
+      }
+      if( (e = gts_edge_is_duplicate(e2)) != NULL ) {
+	gts_object_destroy(GTS_OBJECT(e2));
+	e2 = e;
+      }
+      if( (e = gts_edge_is_duplicate(e3)) != NULL ) {
+	gts_object_destroy(GTS_OBJECT(e3));
+	e3 = e;
+      }
+    }
+  
+    /* Check that edges connect */
+    s1 = GTS_SEGMENT(e1);
+    s2 = GTS_SEGMENT(e2);
+    s3 = GTS_SEGMENT(e3);
+    if( !((s1->v1==s3->v2 && s1->v2==s2->v1 && s2->v2==s3->v1) ||
+	  (s1->v1==s3->v2 && s1->v2==s2->v2 && s2->v1==s3->v1) ||
+	  (s1->v1==s3->v1 && s1->v2==s2->v1 && s2->v2==s3->v2) ||
+	  (s1->v2==s3->v2 && s1->v1==s2->v1 && s2->v2==s3->v1) ||
+	  (s1->v1==s3->v1 && s1->v2==s2->v2 && s2->v1==s3->v2) ||
+	  (s1->v2==s3->v2 && s1->v1==s2->v2 && s2->v1==s3->v1) ||
+	  (s1->v2==s3->v1 && s1->v1==s2->v1 && s2->v2==s3->v2) ||
+	  (s1->v2==s3->v1 && s1->v1==s2->v2 && s2->v1==s3->v2)) ) {
+      PyErr_SetString(PyExc_RuntimeError, "Edges in face must connect");
+      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
+	gts_object_destroy(GTS_OBJECT(e1));
+      }
+      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
+	gts_object_destroy(GTS_OBJECT(e2));
+      }
+      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
+	gts_object_destroy(GTS_OBJECT(e3));
+      }
+      return NULL;
+    }
+
+    /* Create the GtsFace */
+    if( (f = gts_face_new(gts_face_class(),e1,e2,e3)) == NULL )  {
+      PyErr_SetString(PyExc_MemoryError, "could not create Face");
+      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
+	gts_object_destroy(GTS_OBJECT(e1));
+      }
+      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
+	gts_object_destroy(GTS_OBJECT(e2));
+      }
+      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
+	gts_object_destroy(GTS_OBJECT(e3));
+      }
+      return NULL;
+    }
+
+    /* Check for duplicate */
+    t = gts_triangle_is_duplicate(GTS_TRIANGLE(f));
+    if( t != NULL ) {
+      gts_object_destroy(GTS_OBJECT(f));
+      if(!GTS_IS_FACE(t)) {
+	PyErr_SetString(PyExc_TypeError, "expected a Face (internal error)");
+      }
+      f = GTS_FACE(t);
+    }
+
+    /* If corresponding PyObject found in object table, we are done */
+    if( (obj=g_hash_table_lookup(obj_table,GTS_OBJECT(f))) != NULL ) {
+      Py_INCREF(obj);
+      return (PyObject*)obj;
+    }
+  }
+  
+  /* Chain up */
+  obj = PYGTS_OBJECT(PygtsTriangleType.tp_new(type,args,kwds));
+
+  if( alloc_gtsobj ) {
+
+    obj->gtsobj = GTS_OBJECT(f);
+
+    /* Create the parent GtsSurface */
+    if( (obj->gtsobj_parent = parent(GTS_FACE(obj->gtsobj))) == NULL ) {
+      gts_object_destroy(obj->gtsobj);
+      obj->gtsobj = NULL;
+      return NULL;
+    }
+
+    pygts_object_register(PYGTS_OBJECT(obj));
+  }
+
+  return (PyObject*)obj;
+}
+
+
+static int
+init(PygtsFace *self, PyObject *args, PyObject *kwds)
+{
+  gint ret;
+
+  /* Chain up */
+  if( (ret=PygtsTriangleType.tp_init((PyObject*)self,args,kwds)) != 0 ){
+    return ret;
+  }
+
+  return 0;
+}
+
+
+/* Methods table */
+PyTypeObject PygtsFaceType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                       /* ob_size */
+    "gts.Face",              /* tp_name */
+    sizeof(PygtsFace),       /* tp_basicsize */
+    0,                       /* tp_itemsize */
+    0,                       /* tp_dealloc */
+    0,                       /* tp_print */
+    0,                       /* tp_getattr */
+    0,                       /* tp_setattr */
+    0,                       /* tp_compare */
+    0,                       /* tp_repr */
+    0,                       /* tp_as_number */
+    0,                       /* tp_as_sequence */
+    0,                       /* tp_as_mapping */
+    0,                       /* tp_hash */
+    0,                       /* tp_call */
+    0,                       /* tp_str */
+    0,                       /* tp_getattro */
+    0,                       /* tp_setattro */
+    0,                       /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT |
+      Py_TPFLAGS_BASETYPE,   /* tp_flags */
+    "Face object",           /* tp_doc */
+    0,                       /* tp_traverse */
+    0,                       /* tp_clear */
+    0,                       /* tp_richcompare */
+    0,                       /* tp_weaklistoffset */
+    0,                       /* tp_iter */
+    0,                       /* tp_iternext */
+    methods,                 /* tp_methods */
+    0,                       /* tp_members */
+    0,                       /* tp_getset */
+    0,                       /* tp_base */
+    0,                       /* tp_dict */
+    0,                       /* tp_descr_get */
+    0,                       /* tp_descr_set */
+    0,                       /* tp_dictoffset */
+    (initproc)init,          /* tp_init */
+    0,                       /* tp_alloc */
+    (newfunc)new             /* tp_new */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Pygts functions */
+
+gboolean 
+pygts_face_check(PyObject* o)
+{
+  if(! PyObject_TypeCheck(o, &PygtsFaceType)) {
+    return FALSE;
+  }
+  else {
+#if PYGTS_DEBUG
+    return pygts_face_is_ok(PYGTS_FACE(o));
+#else
+    return TRUE;
+#endif
+  }
+}
+
+
+gboolean 
+pygts_face_is_ok(PygtsFace *f)
+{
+  GSList *parent;
+  PygtsObject *obj;
+
+  obj = PYGTS_OBJECT(f);
+
+  if(!pygts_triangle_is_ok(PYGTS_TRIANGLE(f))) return FALSE;
+
+  /* Check for a valid parent */
+  g_return_val_if_fail(obj->gtsobj_parent!=NULL,FALSE);
+  g_return_val_if_fail(GTS_IS_SURFACE(obj->gtsobj_parent),FALSE);
+  parent = g_slist_find(GTS_FACE(obj->gtsobj)->surfaces,
+			obj->gtsobj_parent);
+  g_return_val_if_fail(parent!=NULL,FALSE);
+
+  return TRUE;
+}
+
+
+static GtsObject *
+parent(GtsFace *face) {
+  GtsSurface *p;
+
+  p = gts_surface_new(gts_surface_class(), gts_face_class(),
+		      gts_edge_class(), gts_vertex_class());
+
+  if( p == NULL )  {
+    PyErr_SetString(PyExc_MemoryError, "could not create parent");
+    return NULL;
+  }
+  gts_surface_add_face(p,face);
+
+  return GTS_OBJECT(p);
+}
+
+
+PygtsFace *
+pygts_face_new(GtsFace *f)
+{
+  PyObject *args, *kwds;
+  PygtsObject *face;
+
+  /* Check for Face in the object table */
+  if( (face=PYGTS_OBJECT(g_hash_table_lookup(obj_table,GTS_OBJECT(f))))
+      != NULL ) {
+    Py_INCREF(face);
+    return PYGTS_FACE(face);
+  }
+
+  /* Build a new Face */
+  args = Py_BuildValue("OOO",Py_None,Py_None,Py_None);
+  kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_False);
+  face = PYGTS_OBJECT(PygtsFaceType.tp_new(&PygtsFaceType, args, kwds));
+  Py_DECREF(args);
+  Py_DECREF(kwds);
+  if( face == NULL ) {
+    PyErr_SetString(PyExc_MemoryError, "could not create Face");
+    return NULL;
+  }
+  face->gtsobj = GTS_OBJECT(f);
+
+  /* Attach the parent */
+  if( (face->gtsobj_parent = parent(f)) == NULL ) {
+    Py_DECREF(face);
+    return NULL;
+  }
+  
+  /* Register and return */
+  pygts_object_register(face);
+  return PYGTS_FACE(face);
+}

Added: trunk/lib/py/pygts-0.3.1/face.h
===================================================================
--- trunk/lib/py/pygts-0.3.1/face.h	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/face.h	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,48 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PYGTS_FACE_H__
+#define __PYGTS_FACE_H__
+
+#ifndef gts_face_is_unattached
+#define gts_face_is_unattached(f) ((f)->surfaces == NULL ? TRUE : FALSE)
+#endif
+
+typedef struct _PygtsObject PygtsFace;
+
+#define PYGTS_FACE(obj) ((PygtsFace*)obj)
+
+#define PYGTS_FACE_AS_GTS_FACE(o) (GTS_FACE(PYGTS_OBJECT(o)->gtsobj))
+
+extern PyTypeObject PygtsFaceType;
+
+gboolean pygts_face_check(PyObject* o);
+gboolean pygts_face_is_ok(PygtsFace *f);
+
+PygtsFace* pygts_face_new(GtsFace *f);
+
+#endif /* __PYGTS_FACE_H__ */

Added: trunk/lib/py/pygts-0.3.1/object.c
===================================================================
--- trunk/lib/py/pygts-0.3.1/object.c	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/object.c	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,245 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#include "pygts.h"
+
+
+/*-------------------------------------------------------------------------*/
+/* Methods exported to python */
+
+static PyObject*
+is_unattached(PygtsObject *self, PyObject *args, PyObject *kwds)
+{
+  /* Objects are unattached by default */
+  Py_INCREF(Py_False);
+  return Py_False;
+}
+
+
+/* Methods table */
+static PyMethodDef methods[] = {
+  {"is_unattached", (PyCFunction)is_unattached,
+   METH_NOARGS,
+   "True if this Object o is not attached to another Object.\n"
+   "Otherwise False.\n"
+   "\n"
+   "Trace: o.is_unattached().\n"
+  }, 
+
+  {NULL}  /* Sentinel */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Attributes exported to python */
+
+static PyObject *
+id(PygtsObject *self, void *closure)
+{
+  if( self->gtsobj == NULL) {
+    PyErr_SetString(PyExc_RuntimeError, "GTS object does not exist!");
+    return NULL;
+  }
+  /* Use the pointer of the gtsobj */
+  return Py_BuildValue("i",(long)(self->gtsobj));
+}
+
+
+/* Methods table */
+static PyGetSetDef getset[] = {
+    {"id", (getter)id, NULL, "GTS object id", NULL},
+    {NULL}  /* Sentinel */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Python type methods */
+
+static void
+dealloc(PygtsObject* self)
+{
+  /* De-register entry from the object table */
+  pygts_object_deregister(self);
+
+  if(self->gtsobj_parent!=NULL) {
+    /* Free the parent; GTS will free the child unless it is attached
+     * to something else.
+     */
+    gts_object_destroy(self->gtsobj_parent);
+    self->gtsobj_parent=NULL;
+  }
+  else {
+    /* We have the only reference, and so it is safe to destroy the gtsobj 
+     * (unless it was never created in the first place).
+     */
+    if(self->gtsobj!=NULL) {
+      gts_object_destroy(self->gtsobj);
+      self->gtsobj=NULL;
+    }
+  }
+  self->ob_type->tp_free((PyObject*)self);
+}
+
+
+static PyObject *
+new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+  PygtsObject *self;
+
+  /* Chain up object allocation */
+  self = PYGTS_OBJECT(type->tp_alloc(type, 0));
+  if( self == NULL ) return NULL;
+
+  /* Object initialization */
+  self->gtsobj = NULL;
+  self->gtsobj_parent = NULL;
+
+  return (PyObject *)self;
+}
+
+
+static int
+init(PygtsObject *self, PyObject *args, PyObject *kwds)
+{
+  if( self->gtsobj == NULL ) {
+    PyErr_SetString(PyExc_RuntimeError, "Cannot create abstract Object");
+    return -1;
+  }
+  
+  return 0;
+}
+
+
+static int 
+compare(PygtsObject *o1, PygtsObject *o2)
+{
+  if(o1->gtsobj==o2->gtsobj) {
+    return 0;
+  }
+  else {
+    return -1;
+  }
+}
+
+
+/* Methods table */
+PyTypeObject PygtsObjectType = {
+  PyObject_HEAD_INIT(NULL)
+  0,                         /* ob_size */
+  "gts.Object"  ,            /* tp_name */
+  sizeof(PygtsObject),       /* tp_basicsize */
+  0,                         /* tp_itemsize */
+  (destructor)dealloc,       /* tp_dealloc */
+  0,                         /* tp_print */
+  0,                         /* tp_getattr */
+  0,                         /* tp_setattr */
+  (cmpfunc)compare,          /* tp_compare */
+  0,                         /* tp_repr */
+  0,                         /* tp_as_number */
+  0,                         /* tp_as_sequence */
+  0,                         /* tp_as_mapping */
+  0,                         /* tp_hash */
+  0,                         /* tp_call */
+  0,                         /* tp_str */
+  0,                         /* tp_getattro */
+  0,                         /* tp_setattro */
+  0,                         /* tp_as_buffer */
+  Py_TPFLAGS_DEFAULT |
+    Py_TPFLAGS_BASETYPE,     /* tp_flags */
+  "Base object",             /* tp_doc */
+  0,		             /* tp_traverse */
+  0,		             /* tp_clear */
+  0,		             /* tp_richcompare */
+  0,		             /* tp_weaklistoffset */
+  0,		             /* tp_iter */
+  0,		             /* tp_iternext */
+  methods,                   /* tp_methods */
+  0,                         /* tp_members */
+  getset,                    /* tp_getset */
+  0,                         /* tp_base: attached in pygts.c */
+  0,                         /* tp_dict */
+  0,                         /* tp_descr_get */
+  0,                         /* tp_descr_set */
+  0,                         /* tp_dictoffset */
+  (initproc)init,            /* tp_init */
+  0,                         /* tp_alloc */
+  (newfunc)new               /* tp_new */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Pygts functions */
+
+gboolean 
+pygts_object_check(PyObject* o)
+{
+  if(! PyObject_TypeCheck(o, &PygtsObjectType)) {
+    return FALSE;
+  }
+  else {
+#if PYGTS_DEBUG
+    return pygts_object_is_ok(PYGTS_OBJECT(o));
+#else
+    return TRUE;
+#endif
+  }
+}
+
+
+gboolean 
+pygts_object_is_ok(PygtsObject *o)
+{
+  g_return_val_if_fail(o->gtsobj!=NULL,FALSE);
+  g_return_val_if_fail(g_hash_table_lookup(obj_table,o->gtsobj)!=NULL,FALSE);
+  return TRUE;
+}
+
+
+/*-------------------------------------------------------------------------*/
+/* Object table functions */
+
+GHashTable *obj_table; /* GtsObject key, associated PyObject value */
+
+void
+pygts_object_register(PygtsObject *o)
+{
+  if( g_hash_table_lookup(obj_table,o->gtsobj) == NULL ) {
+    g_hash_table_insert(obj_table,o->gtsobj,o);
+  }
+}
+
+
+void
+pygts_object_deregister(PygtsObject *o)
+{
+  if(o->gtsobj!=NULL) {
+    if(g_hash_table_lookup(obj_table,o->gtsobj)==o) {
+      g_hash_table_remove(obj_table,o->gtsobj);
+    }
+  }
+}
+

Added: trunk/lib/py/pygts-0.3.1/object.h
===================================================================
--- trunk/lib/py/pygts-0.3.1/object.h	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/object.h	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,53 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PYGTS_OBJECT_H__
+#define __PYGTS_OBJECT_H__
+
+
+typedef struct _PygtsObject PygtsObject;
+typedef struct _PygtsMethods PygtsMethods;
+
+#define PYGTS_OBJECT(obj) ((PygtsObject*)obj)
+
+struct _PygtsObject {
+  PyObject_HEAD
+  GtsObject *gtsobj;         /* Encapsulated GtsObject */
+  GtsObject *gtsobj_parent;  /* A parent object to ensure persistence */
+};
+
+extern PyTypeObject PygtsObjectType;
+extern PygtsMethods PygtsObjectMethods;
+
+gboolean pygts_object_check(PyObject* o);
+gboolean pygts_object_is_ok(PygtsObject *o);
+
+extern GHashTable *obj_table; /* GtsObject key, associated PyObject value */
+void pygts_object_register(PygtsObject *o);
+void pygts_object_deregister(PygtsObject *o);
+
+#endif /* __PYGTS_OBJECT_H__ */

Added: trunk/lib/py/pygts-0.3.1/point.c
===================================================================
--- trunk/lib/py/pygts-0.3.1/point.c	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/point.c	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,1227 @@
+/* pygts - python point for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#include "pygts.h"
+
+
+#if PYGTS_DEBUG
+  #define SELF_CHECK if(!pygts_point_check((PyObject*)self)) {      \
+                       PyErr_SetString(PyExc_RuntimeError,            \
+                       "problem with self object (internal error)");  \
+		       return NULL;                                   \
+                     }
+#else
+  #define SELF_CHECK
+#endif
+
+
+/*-------------------------------------------------------------------------*/
+/* Methods exported to python */
+
+static PyObject*
+is_ok(PygtsPoint *self, PyObject *args)
+{
+  if(pygts_point_is_ok(self)) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+set(PygtsPoint *self, PyObject *args)
+{
+  gdouble x=0,y=0,z=0;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "|ddd",  &x,&y,&z)) {
+    return NULL;
+  }
+
+  gts_point_set(PYGTS_POINT_AS_GTS_POINT(self),x,y,z);
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+static PyObject*
+coords(PygtsPoint *self, PyObject *args)
+{
+  SELF_CHECK
+
+  return Py_BuildValue("ddd",PYGTS_POINT_AS_GTS_POINT(self)->x,
+		       PYGTS_POINT_AS_GTS_POINT(self)->y,
+		       PYGTS_POINT_AS_GTS_POINT(self)->z);
+}
+
+
+static PyObject*
+is_in_rectangle(PygtsPoint* self, PyObject *args)
+{
+  PyObject *o1_,*o2_;
+  PygtsPoint *p1, *p2;
+  gboolean flag = FALSE;
+  gdouble x,y;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "OO", &o1_, &o2_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!(pygts_point_check(o1_) && pygts_point_check(o2_))) {
+    PyErr_SetString(PyExc_TypeError,"expected two Points");
+    return NULL;
+  }
+
+  p1 = PYGTS_POINT(o1_);
+  p2 = PYGTS_POINT(o2_);
+
+  /* Test if point *may* be on rectangle perimeter */
+  x = PYGTS_POINT_AS_GTS_POINT(self)->x;
+  y = PYGTS_POINT_AS_GTS_POINT(self)->y;
+  if( PYGTS_POINT_AS_GTS_POINT(p1)->x == x ||
+      PYGTS_POINT_AS_GTS_POINT(p1)->y == y ||
+      PYGTS_POINT_AS_GTS_POINT(p2)->x == x ||
+      PYGTS_POINT_AS_GTS_POINT(p2)->y == y ) {
+    flag = TRUE;
+  }
+
+  if( gts_point_is_in_rectangle(PYGTS_POINT_AS_GTS_POINT(self), 
+				PYGTS_POINT_AS_GTS_POINT(p1), 
+				PYGTS_POINT_AS_GTS_POINT(p2)) ) {
+    if(flag) {
+      return Py_BuildValue("i",0);
+    }
+    else {
+      return Py_BuildValue("i",1);
+    }
+  }
+  else {
+    if( flag && 
+	gts_point_is_in_rectangle(PYGTS_POINT_AS_GTS_POINT(self), 
+				  PYGTS_POINT_AS_GTS_POINT(p2), 
+				  PYGTS_POINT_AS_GTS_POINT(p1))) {
+      return Py_BuildValue("i",0);
+    }
+    else {
+      return Py_BuildValue("i",-1);
+    }
+  }
+}
+
+
+static PyObject*
+distance(PygtsPoint* self, PyObject *args)
+{
+  PyObject *o_;
+  PygtsPoint *p=NULL;
+  PygtsSegment *s=NULL;
+  PygtsTriangle *t=NULL;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "O", &o_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(pygts_point_check(o_)) {
+    p = PYGTS_POINT(o_);
+  }
+  else {
+    if(pygts_segment_check(o_)) {
+      s = PYGTS_SEGMENT(o_);
+    }
+    else {
+      if(pygts_triangle_check(o_)) {
+	t = PYGTS_TRIANGLE(o_);
+      }
+      else {
+	PyErr_SetString(PyExc_TypeError,
+			"expected a Point, Segment or Triangle");
+	return NULL;
+      }
+    }
+  }
+
+  if(p!=NULL) {
+    return Py_BuildValue("d",
+        gts_point_distance(PYGTS_POINT_AS_GTS_POINT(self),
+			   PYGTS_POINT_AS_GTS_POINT(p)));
+  }
+  else {
+    if(s!=NULL) {
+      return Py_BuildValue("d",
+          gts_point_segment_distance(PYGTS_POINT_AS_GTS_POINT(self),
+				     PYGTS_SEGMENT_AS_GTS_SEGMENT(s) )
+			   );
+    }
+    else {
+      return Py_BuildValue("d",
+          gts_point_triangle_distance(PYGTS_POINT_AS_GTS_POINT(self),
+				      PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t) )
+			   );
+    }
+  }
+}
+
+
+static PyObject*
+distance2(PygtsPoint* self, PyObject *args)
+{
+  PyObject *o_;
+  PygtsPoint *p=NULL;
+  PygtsSegment *s=NULL;
+  PygtsTriangle *t=NULL;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "O", &o_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(pygts_point_check(o_)) {
+    p = PYGTS_POINT(o_);
+  }
+  else {
+    if(pygts_segment_check(o_)) {
+      s = PYGTS_SEGMENT(o_);
+    }
+    else {
+      if(pygts_triangle_check(o_)) {
+	t = PYGTS_TRIANGLE(o_);
+      }
+      else {
+	PyErr_SetString(PyExc_TypeError,
+			"expected a Point, Segment or Triangle");
+	return NULL;
+      }
+    }
+  }
+
+  if(p!=NULL) {
+    return Py_BuildValue("d",
+        gts_point_distance2(PYGTS_POINT_AS_GTS_POINT(self),
+			    PYGTS_POINT_AS_GTS_POINT(p)));
+  }
+  else {
+    if(s!=NULL) {
+      return Py_BuildValue("d",
+        gts_point_segment_distance2(PYGTS_POINT_AS_GTS_POINT(self),
+				    PYGTS_SEGMENT_AS_GTS_SEGMENT(s) )
+			   );
+    }
+    else {
+      return Py_BuildValue("d",
+          gts_point_triangle_distance2(PYGTS_POINT_AS_GTS_POINT(self),
+				       PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t) )
+			   );
+    }
+  }
+}
+
+
+static PyObject*
+orientation_3d(PygtsPoint* self, PyObject *args)
+{
+  PyObject *p1_,*p2_,*p3_;
+  PygtsPoint *p1,*p2,*p3;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "OOO", &p1_, &p2_, &p3_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_point_check(p1_)) {
+    PyErr_SetString(PyExc_TypeError,"expected three Points");
+    return NULL;
+  }
+  if(!pygts_point_check(p2_)) {
+    PyErr_SetString(PyExc_TypeError,"expected three Points");
+    return NULL;
+  }
+  if(!pygts_point_check(p3_)) {
+    PyErr_SetString(PyExc_TypeError,"expected three Points");
+    return NULL;
+  }
+  p1 = PYGTS_POINT(p1_);
+  p2 = PYGTS_POINT(p2_);
+  p3 = PYGTS_POINT(p3_);
+
+  return Py_BuildValue("d",
+             gts_point_orientation_3d(PYGTS_POINT_AS_GTS_POINT(p1),
+				      PYGTS_POINT_AS_GTS_POINT(p2),
+				      PYGTS_POINT_AS_GTS_POINT(p3),
+				      PYGTS_POINT_AS_GTS_POINT(self)));
+}
+
+
+static PyObject*
+orientation_3d_sos(PygtsPoint* self, PyObject *args)
+{
+  PyObject *p1_,*p2_,*p3_;
+  PygtsPoint *p1,*p2,*p3;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "OOO", &p1_, &p2_, &p3_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_point_check(p1_)) {
+    PyErr_SetString(PyExc_TypeError,"expected three Points");
+    return NULL;
+  }
+  if(!pygts_point_check(p2_)) {
+    PyErr_SetString(PyExc_TypeError,"expected three Points");
+    return NULL;
+  }
+  if(!pygts_point_check(p3_)) {
+    PyErr_SetString(PyExc_TypeError,"expected three Points");
+    return NULL;
+  }
+  p1 = PYGTS_POINT(p1_);
+  p2 = PYGTS_POINT(p2_);
+  p3 = PYGTS_POINT(p3_);
+
+  return Py_BuildValue("i",gts_point_orientation_3d_sos(
+                             PYGTS_POINT_AS_GTS_POINT(p1),
+			     PYGTS_POINT_AS_GTS_POINT(p2),
+			     PYGTS_POINT_AS_GTS_POINT(p3),
+			     PYGTS_POINT_AS_GTS_POINT(self)));
+}
+
+
+static PyObject*
+is_in_circle(PygtsPoint* self, PyObject *args)
+{
+  PyObject *o1_=NULL,*o2_=NULL,*o3_=NULL;
+  PygtsPoint *p1=NULL, *p2=NULL, *p3=NULL;
+  PygtsTriangle *t=NULL;
+  gdouble result;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "O|OO", &o1_, &o2_, &o3_) ) {
+    return NULL;
+  }
+  if( (o2_==NULL && o3_!=NULL) || (o2_!=NULL && o3_==NULL) ) {
+    PyErr_SetString(PyExc_TypeError,"expected three Points or one Triangle");
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(o2_==NULL && o3_==NULL) {
+    if(!pygts_triangle_check(o1_)) {
+      PyErr_SetString(PyExc_TypeError,"expected three Points or one Triangle");
+      return NULL;
+    }
+    t = PYGTS_TRIANGLE(o1_);
+  } 
+  else {
+    if(!pygts_point_check(o1_)) {
+      PyErr_SetString(PyExc_TypeError,"expected three Points or one Triangle");
+      return NULL;
+    }
+    if(!pygts_point_check(o2_)) {
+      PyErr_SetString(PyExc_TypeError,"expected three Points or one Triangle");
+      return NULL;
+    }
+    if(!pygts_point_check(o3_)) {
+      PyErr_SetString(PyExc_TypeError,"expected three Points or one Triangle");
+      return NULL;
+    }
+    p1 = PYGTS_POINT(o1_);
+    p2 = PYGTS_POINT(o2_);
+    p3 = PYGTS_POINT(o3_);
+  }
+
+
+  if(t!=NULL){
+    result=gts_point_in_triangle_circle(PYGTS_POINT_AS_GTS_POINT(self),
+					PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t));
+  }
+  else {
+    result = gts_point_in_circle(PYGTS_POINT_AS_GTS_POINT(self),
+				 PYGTS_POINT_AS_GTS_POINT(p1), 
+				 PYGTS_POINT_AS_GTS_POINT(p2), 
+				 PYGTS_POINT_AS_GTS_POINT(p3));
+  }
+  if(result>0) return Py_BuildValue("i",1);
+  if(result==0) return Py_BuildValue("i",0);
+  return Py_BuildValue("i",-1);
+}
+
+
+static PyObject*
+is_in(PygtsPoint* self, PyObject *args)
+{
+  PyObject *t_;
+  PygtsTriangle *t;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "O", &t_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_triangle_check(t_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Triangle");
+    return NULL;
+  }
+  t = PYGTS_TRIANGLE(t_);
+
+  return Py_BuildValue("i",
+      gts_point_is_in_triangle(PYGTS_POINT_AS_GTS_POINT(self),
+			       PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t)));
+}
+
+
+static PyObject*
+is_inside(PygtsPoint* self, PyObject *args)
+{
+  PyObject *s_;
+  PygtsSurface *s;
+  GNode *tree;
+  gboolean is_open=FALSE, ret;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "O", &s_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_surface_check(s_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Surface");
+    return NULL;
+  }
+  s = PYGTS_SURFACE(s_);
+
+  /* Error check */
+  if(!gts_surface_is_closed(PYGTS_SURFACE_AS_GTS_SURFACE(s))) {
+    PyErr_SetString(PyExc_RuntimeError,"Surface is not closed");
+    return NULL;
+  }
+
+  /* Determing is_open parameter; note the meaning is different from the 
+   * error check above.
+   */
+  if( gts_surface_volume(PYGTS_SURFACE_AS_GTS_SURFACE(s))<0. ) {
+    is_open = TRUE;
+  }
+
+  /* Construct the tree */
+  if((tree=gts_bb_tree_surface(PYGTS_SURFACE_AS_GTS_SURFACE(s))) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"could not create GTree");
+    return NULL;
+  }
+  
+  /* Make the call */
+  ret = gts_point_is_inside_surface(PYGTS_POINT_AS_GTS_POINT(self), tree,
+				    is_open);
+
+  g_node_destroy(tree);
+
+  if(ret) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+closest(PygtsPoint* self, PyObject *args)
+{
+  PyObject *o1_,*o2_;
+  PygtsPoint *p=NULL;
+  PygtsSegment *s=NULL;
+  PygtsTriangle *t=NULL;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "OO", &o1_, &o2_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(pygts_segment_check(o1_)) {
+    s = PYGTS_SEGMENT(o1_);
+  }
+  else {
+    if(pygts_triangle_check(o1_)) {
+      t = PYGTS_TRIANGLE(o1_);
+    }
+    else {
+      PyErr_SetString(PyExc_TypeError,
+		      "expected a Segment or Triangle, and a Point");
+      return NULL;
+    }
+  }
+  if(pygts_point_check(o2_)) {
+    p = PYGTS_POINT(o2_);
+  }
+  else {
+	PyErr_SetString(PyExc_TypeError,
+			"expected a Segment or Triangle, and a Point");
+	return NULL;
+  }
+
+  if(s!=NULL) {
+    gts_point_segment_closest(PYGTS_POINT_AS_GTS_POINT(p),
+			      PYGTS_SEGMENT_AS_GTS_SEGMENT(s),
+			      PYGTS_POINT_AS_GTS_POINT(self));
+  }
+  else {
+    gts_point_triangle_closest(PYGTS_POINT_AS_GTS_POINT(p),
+			       PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t),
+			       PYGTS_POINT_AS_GTS_POINT(self));
+  }
+
+  Py_INCREF(self);
+  return (PyObject*)self;
+}
+
+
+/* Helper for rotate() */
+gint
+pygts_point_rotate(GtsPoint* p, gdouble dx, gdouble dy, gdouble dz, gdouble a)
+{
+  GtsMatrix *m;
+  GtsVector v;
+
+  v[0] = dx; v[1] = dy; v[2] = dz;
+  if( (m = gts_matrix_rotate(NULL,v,a)) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"could not create matrix");
+    return -1;
+  }
+  gts_point_transform(p,m);
+  gts_matrix_destroy(m);
+
+  return 0;
+}
+
+
+static PyObject*
+rotate(PygtsPoint* self, PyObject *args, PyObject *keywds)
+{
+  static char *kwlist[] = {"dx", "dy", "dz", "a", NULL};
+  gdouble dx=0,dy=0,dz=0,a=0;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTupleAndKeywords(args, keywds,"|dddd", kwlist,
+				   &dx, &dy, &dz, &a) ) {
+    return NULL;
+  }
+
+  if(pygts_point_rotate(PYGTS_POINT_AS_GTS_POINT(self),dx,dy,dz,a)==-1)
+    return NULL;
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+/* Helper for scale() */
+gint
+pygts_point_scale(GtsPoint* p, gdouble dx, gdouble dy, gdouble dz)
+{
+  GtsMatrix *m;
+  GtsVector v;
+
+  v[0] = dx; v[1] = dy; v[2] = dz;
+  if( (m = gts_matrix_scale(NULL,v)) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"could not create matrix");
+    return -1;
+  }
+  gts_point_transform(p,m);
+  gts_matrix_destroy(m);
+
+  return 0;
+}
+
+
+static PyObject*
+scale(PygtsPoint* self, PyObject *args, PyObject *keywds)
+{
+  static char *kwlist[] = {"dx", "dy", "dz", NULL};
+  gdouble dx=1,dy=1,dz=1;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTupleAndKeywords(args, keywds,"|ddd", kwlist,
+				   &dx, &dy, &dz) ) {
+    return NULL;
+  }
+
+  if(pygts_point_scale(PYGTS_POINT_AS_GTS_POINT(self),dx,dy,dz)==-1)
+    return NULL;
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+/* Helper for translate() */
+gint
+pygts_point_translate(GtsPoint* p, gdouble dx, gdouble dy, gdouble dz)
+{
+  GtsMatrix *m;
+  GtsVector v;
+
+  v[0] = dx; v[1] = dy; v[2] = dz;
+  if( (m = gts_matrix_translate(NULL,v)) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"could not create matrix");
+    return -1;
+  }  
+  gts_point_transform(p,m);
+  gts_matrix_destroy(m);
+
+  return 0;
+}
+
+
+static PyObject*
+translate(PygtsPoint* self, PyObject *args, PyObject *keywds)
+{
+  static char *kwlist[] = {"dx", "dy", "dz", NULL};
+  gdouble dx=0,dy=0,dz=0;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTupleAndKeywords(args, keywds,"|ddd", kwlist,
+				   &dx, &dy, &dz) ) {
+    return NULL;
+  }
+
+  if(pygts_point_translate(PYGTS_POINT_AS_GTS_POINT(self),dx,dy,dz)==-1)
+    return NULL;
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+/* Methods table */
+static PyMethodDef methods[] = {
+
+  {"is_ok", (PyCFunction)is_ok,
+   METH_NOARGS,
+   "True if this Point p is OK.  False otherwise.\n"
+   "This method is useful for unit testing and debugging.\n"
+   "\n"
+   "Signature: p.is_ok().\n"
+  },  
+
+  {"set", (PyCFunction)set,
+   METH_VARARGS,
+   "Sets x, y, and z coordinates of this Point p.\n"
+   "\n"
+   "Signature: p.set(x,y,z)\n"
+  },  
+
+  {"coords", (PyCFunction)coords,
+   METH_VARARGS,
+   "Returns a tuple of the x, y, and z coordinates for this Point p.\n"
+   "\n"
+   "Signature: p.coords(x,y,z)\n"
+  },  
+
+  {"is_in_rectangle", (PyCFunction)is_in_rectangle,
+   METH_VARARGS,
+   "True if this Point p is in box with bottom-left and upper-right\n"
+   "Points p1 and p2.\n"
+   "\n"
+   "Signature: p.is_in_rectange(p1,p2)\n"
+  },
+
+  {"distance", (PyCFunction)distance,
+   METH_VARARGS,
+   "Returns Euclidean distance between this Point p and other Point p2,\n"
+   "Segment s, or Triangle t."
+   "\n"
+   "Signature: p.distance(p2), p.distance(s) or p.distance(t)\n"
+  },  
+
+  {"distance2", (PyCFunction)distance2,
+   METH_VARARGS,
+   "Returns squared Euclidean distance between Point p and Point p2,\n"
+   "Segment s, or Triangle t.\n"
+   "\n"
+   "Signature: p.distance2(p2), p.distance2(s), or p.distance2(t)\n"
+  },  
+
+  {"orientation_3d", (PyCFunction)orientation_3d,
+   METH_VARARGS,
+   "Determines if this Point p is above, below or on plane of 3 Points\n"
+   "p1, p2 and p3.\n"
+   "\n"
+   "Signature: p.orientation_3d(p1,p2,p3)\n"
+   "\n"
+   "Below is defined so that p1, p2 and p3 appear in counterclockwise\n"
+   "order when viewed from above the plane.\n"
+   "\n"
+   "The return value is positive if p4 lies below the plane, negative\n"
+   "if p4 lies above the plane, and zero if the four points are\n"
+   "coplanar.  The value is an approximation of six times the signed\n"
+   "volume of the tetrahedron defined by the four points.\n"
+  },  
+
+  {"orientation_3d_sos", (PyCFunction)orientation_3d_sos,
+   METH_VARARGS,
+   "Determines if this Point p is above, below or on plane of 3 Points\n"
+   "p1, p2 and p3.\n"
+   "\n"
+   "Signature: p.orientation_3d_sos(p1,p2,p3)\n"
+   "\n"
+   "Below is defined so that p1, p2 and p3 appear in counterclockwise\n"
+   "order when viewed from above the plane.\n"
+   "\n"
+   "The return value is +1 if p4 lies below the plane, and -1 if p4\n"
+   "lies above the plane.  Simulation of Simplicity (SoS) is used to\n"
+   "break ties when the orientation is degenerate (i.e. the point lies\n"
+   "on the plane definedby p1, p2 and p3)."
+  },  
+
+  {"is_in_circle", (PyCFunction)is_in_circle,
+   METH_VARARGS,
+   "Tests if this Point p is inside or outside circumcircle.\n"
+   "The planar projection (x,y) of Point p is tested against the\n"
+   "circumcircle defined by the planar projection of p1, p2 and p3,\n"
+   "or alternatively the Triangle t\n"
+   "\n"
+   "Signature: p.in_circle(p1,p2,p3) or p.in_circle(t) \n"
+   "\n"
+   "Returns +1 if p lies inside, -1 if p lies outside, and 0 if p lies\n"
+   "on the circle.  The Points p1, p2, and p3 must be in\n"
+   "counterclockwise order, or the sign of the result will be reversed.\n"
+  },
+
+  {"is_in", (PyCFunction)is_in,
+   METH_VARARGS,
+   "Tests if this Point p is inside or outside Triangle t.\n"
+   "The planar projection (x,y) of Point p is tested against the\n"
+   "planar projection of Triangle t.\n"
+   "\n"
+   "Signature: p.in_circle(p1,p2,p3) or p.in_circle(t) \n"
+   "\n"
+   "Returns a +1 if p lies inside, -1 if p lies outside, and 0\n"
+   "if p lies on the triangle.\n"
+  },
+
+  {"is_inside", (PyCFunction)is_inside,
+   METH_VARARGS,
+   "True if this Point p is inside or outside Surface s.\n"
+   "False otherwise.\n"
+   "\n"
+   "Signature: p.in_inside(s)\n"
+  },
+
+  {"closest", (PyCFunction)closest,
+   METH_VARARGS,
+   "Set the coordinates of Point p to the Point on Segment s\n"
+   "or Triangle t closest to the Point p2\n"
+   "\n"
+   "Signature: p.closest(s,p2) or p.closest(t,p2)\n"
+   "\n"
+   "Returns the (modified) Point p.\n"
+  },
+
+  {"rotate", (PyCFunction)rotate,
+   METH_VARARGS | METH_KEYWORDS,
+   "Rotates Point p around vector dx,dy,dz by angle a.\n"
+   "The sense of the rotation is given by the right-hand-rule.\n"
+   "\n"
+   "Signature: p.rotate(dx=0,dy=0,dz=0,a=0)\n"
+  },
+
+  {"scale", (PyCFunction)scale,
+   METH_VARARGS | METH_KEYWORDS,
+   "Scales Point p by vector dx,dy,dz.\n"
+   "\n"
+   "Signature: p.scale(dx=1,dy=1,dz=1)\n"
+  },
+
+  {"translate", (PyCFunction)translate,
+   METH_VARARGS | METH_KEYWORDS,
+   "Translates Point p by vector dx,dy,dz.\n"
+   "\n"
+   "Signature: p.translate(dx=0,dy=0,dz=0)\n"
+  },
+
+  {NULL}  /* Sentinel */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Attributes exported to python */
+
+static PyObject *
+getx(PygtsPoint *self, void *closure)
+{
+  SELF_CHECK
+
+  return Py_BuildValue("d",PYGTS_POINT_AS_GTS_POINT(self)->x);
+}
+
+
+static int
+setx(PygtsPoint *self, PyObject *value, void *closure)
+{
+  if(PyFloat_Check(value)) {
+    PYGTS_POINT_AS_GTS_POINT(self)->x = PyFloat_AsDouble(value);
+  }
+  else if(PyInt_Check(value)) {
+    PYGTS_POINT_AS_GTS_POINT(self)->x = (gdouble)PyInt_AsLong(value);
+  }
+  else {
+    PyErr_SetString(PyExc_TypeError,"expected a float");
+    return -1;
+  }
+  return 0;
+}
+
+
+static PyObject *
+gety(PygtsPoint *self, void *closure)
+{
+  SELF_CHECK
+
+  return Py_BuildValue("d",PYGTS_POINT_AS_GTS_POINT(self)->y);
+}
+
+
+static int
+sety(PygtsPoint *self, PyObject *value, void *closure)
+{
+  if(PyFloat_Check(value)) {
+    PYGTS_POINT_AS_GTS_POINT(self)->y = PyFloat_AsDouble(value);
+  }
+  else if(PyInt_Check(value)) {
+    PYGTS_POINT_AS_GTS_POINT(self)->y = (gdouble)PyInt_AsLong(value);
+  }
+  else {
+    PyErr_SetString(PyExc_TypeError,"expected a float");
+    return -1;
+  }
+  return 0;
+}
+
+
+static PyObject *
+getz(PygtsPoint *self, void *closure)
+{
+  SELF_CHECK
+
+  return Py_BuildValue("d",PYGTS_POINT_AS_GTS_POINT(self)->z);
+}
+
+
+static int
+setz(PygtsPoint *self, PyObject *value, void *closure)
+{
+  if(PyFloat_Check(value)) {
+    PYGTS_POINT_AS_GTS_POINT(self)->z = PyFloat_AsDouble(value);
+  }
+  else if(PyInt_Check(value)) {
+    PYGTS_POINT_AS_GTS_POINT(self)->z = (gdouble)PyInt_AsLong(value);
+  }
+  else {
+    PyErr_SetString(PyExc_TypeError,"expected a float");
+    return -1;
+  }
+  return 0;
+}
+
+
+/* Methods table */
+static PyGetSetDef getset[] = {
+    {"x", (getter)getx, (setter)setx, "x value", NULL},
+    {"y", (getter)gety, (setter)sety, "y value", NULL},
+    {"z", (getter)getz, (setter)setz, "z value", NULL},
+    {NULL}  /* Sentinel */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Python type methods */
+
+static PyObject *
+new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+  PyObject *o;
+  PygtsObject *obj;
+  guint alloc_gtsobj = TRUE;
+
+  /* Parse the args */
+  if(kwds) {
+    o = PyDict_GetItemString(kwds,"alloc_gtsobj");
+    if(o==Py_False) {
+      alloc_gtsobj = FALSE;
+    }
+    if(o!=NULL) {
+      PyDict_DelItemString(kwds, "alloc_gtsobj");
+    }
+  }
+  if(kwds) {
+    Py_INCREF(Py_False);
+    PyDict_SetItemString(kwds,"alloc_gtsobj", Py_False);
+  }
+
+  /* Chain up */
+  obj = PYGTS_OBJECT(PygtsObjectType.tp_new(type,args,kwds));
+
+  /* Allocate the gtsobj (if needed) */
+  if( alloc_gtsobj ) {
+    obj->gtsobj = GTS_OBJECT(gts_point_new(gts_point_class(),0,0,0));
+    if( obj->gtsobj == NULL )  {
+      PyErr_SetString(PyExc_MemoryError, "could not create Point");
+      return NULL;
+    }
+
+    pygts_object_register(obj);
+  }
+
+  return (PyObject*)obj;
+}
+
+
+static int
+init(PygtsPoint *self, PyObject *args, PyObject *kwds)
+{
+  PygtsObject *obj;
+  gdouble x=0,y=0,z=0;
+  guint a;
+  gint ret;
+  static char *kwlist[] = {"x", "y", "z", "alloc_gtsobj", NULL};
+
+  obj = PYGTS_OBJECT(self);
+
+  /* Parse the args */
+  if(! PyArg_ParseTupleAndKeywords(args, kwds, "|dddi", kwlist, &x,&y,&z,&a)) {
+    return -1;
+  }
+
+  /* Initialize */
+  gts_point_set(GTS_POINT(obj->gtsobj),x,y,z);
+
+  /* Chain up */
+  if( (ret=PygtsObjectType.tp_init((PyObject*)self,args,kwds)) != 0 ) {
+    return ret;
+  }
+
+  return 0;
+}
+
+
+static int 
+compare(PygtsPoint *p1_, PygtsPoint *p2_)
+{
+  GtsPoint *p1, *p2;
+
+#if PYGTS_DEBUG
+  pygts_point_check((PyObject*)p1_);
+  pygts_point_check((PyObject*)p2_);
+#endif
+
+  p1 = PYGTS_POINT_AS_GTS_POINT(p1_);
+  p2 = PYGTS_POINT_AS_GTS_POINT(p2_);
+
+  return pygts_point_compare(p1,p2);
+}
+
+
+/* Methods table */
+PyTypeObject PygtsPointType = {
+  PyObject_HEAD_INIT(NULL)
+  0,                         /* ob_size */
+  "gts.Point"  ,             /* tp_name */
+  sizeof(PygtsPoint),        /* tp_basicsize */
+  0,                         /* tp_itemsize */
+  0,                         /* tp_dealloc */
+  0,                         /* tp_print */
+  0,                         /* tp_getattr */
+  0,                         /* tp_setattr */
+  (cmpfunc)compare,          /* tp_compare */
+  0,                         /* tp_repr */
+  0,                         /* tp_as_number */
+  0,                         /* tp_as_sequence */
+  0,                         /* tp_as_mapping */
+  0,                         /* tp_hash */
+  0,                         /* tp_call */
+  0,                         /* tp_str */
+  0,                         /* tp_getattro */
+  0,                         /* tp_setattro */
+  0,                         /* tp_as_buffer */
+  Py_TPFLAGS_DEFAULT |
+    Py_TPFLAGS_BASETYPE,     /* tp_flags */
+  "Point object",            /* tp_doc */
+  0,		             /* tp_traverse */
+  0,		             /* tp_clear */
+  0,		             /* tp_richcompare */
+  0,		             /* tp_weaklistoffset */
+  0,		             /* tp_iter */
+  0,		             /* tp_iternext */
+  methods,                   /* tp_methods */
+  0,                         /* tp_members */
+  getset,                    /* tp_getset */
+  0,                         /* tp_base: attached in pygts.c */
+  0,                         /* tp_dict */
+  0,                         /* tp_descr_get */
+  0,                         /* tp_descr_set */
+  0,                         /* tp_dictoffset */
+  (initproc)init,            /* tp_init */
+  0,                         /* tp_alloc */
+  (newfunc)new               /* tp_new */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Pygts functions */
+
+gboolean 
+pygts_point_check(PyObject* o)
+{
+  gboolean check = FALSE;
+  guint i,N;
+  PyObject *obj;
+
+  /* Check for a Point */
+  if( PyObject_TypeCheck(o, &PygtsPointType) ) {
+    check = TRUE;
+  }
+
+  /* Convert list into tuple */
+  if(PyList_Check(o)) {
+    o = PyList_AsTuple(o);
+  }
+  else {
+    Py_INCREF(o);
+  }
+
+  /* Check for a tuple of floats */
+  if( PyTuple_Check(o) ) {
+    if( (N = PyTuple_Size(o)) <= 3 ) {
+      check = TRUE;
+      for(i=0;i<N;i++) {
+	obj = PyTuple_GET_ITEM(o,i);
+	if(!PyFloat_Check(obj) && !PyInt_Check(obj)) {
+	  check = FALSE;
+	}
+      }
+    }
+  }
+  Py_DECREF(o);
+
+  if( !check ) {
+    return FALSE;
+  }
+  else {
+#if PYGTS_DEBUG
+    if( PyObject_TypeCheck(o, &PygtsPointType) ) {
+      return pygts_point_is_ok(PYGTS_POINT(o));
+    }
+#endif
+    return TRUE;
+  }
+}
+
+
+gboolean 
+pygts_point_is_ok(PygtsPoint *p)
+{
+  return pygts_object_is_ok(PYGTS_OBJECT(p));
+}
+
+
+PygtsPoint *
+pygts_point_new(GtsPoint *p)
+{
+  PyObject *args, *kwds;
+  PygtsObject *point;
+
+  /* Check for Point in the object table */
+  if( (point = PYGTS_OBJECT(g_hash_table_lookup(obj_table,GTS_OBJECT(p)))) 
+      !=NULL ) {
+    Py_INCREF(point);
+    return PYGTS_POINT(point);
+  }
+
+  /* Build a new Point */
+  args = Py_BuildValue("ddd",0,0,0);
+  kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_False);
+  point = PYGTS_POINT(PygtsPointType.tp_new(&PygtsPointType, args, kwds));
+  Py_DECREF(args);
+  Py_DECREF(kwds);
+  if( point == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"could not create Point");
+    return NULL;
+  }
+  point->gtsobj = GTS_OBJECT(p);
+
+  /* Register and return */
+  pygts_object_register(point);
+  return PYGTS_POINT(point);
+}
+
+
+PygtsPoint *
+pygts_point_from_sequence(PyObject *tuple) {
+  guint i,N;
+  gdouble x=0,y=0,z=0;
+  PyObject *obj;
+  GtsPoint *p;
+  PygtsPoint *point;
+
+  /* Convert list into tuple */
+  if(PyList_Check(tuple)) {
+    tuple = PyList_AsTuple(tuple);
+  }
+  else {
+    Py_INCREF(tuple);
+  }
+  if(!PyTuple_Check(tuple)) {
+    Py_DECREF(tuple);
+    PyErr_SetString(PyExc_TypeError,"expected a list or tuple of vertices");
+    return NULL;
+  }
+
+  /* Get the tuple size */
+  if( (N = PyTuple_Size(tuple)) > 3 ) {
+    PyErr_SetString(PyExc_RuntimeError,
+		    "expected a list or tuple of up to three floats");
+    Py_DECREF(tuple);
+    return NULL;
+  }
+
+  /* Get the coordinates */
+  for(i=0;i<N;i++) {
+    obj = PyTuple_GET_ITEM(tuple,i);
+
+    if(!PyFloat_Check(obj) && !PyInt_Check(obj)) {
+      PyErr_SetString(PyExc_TypeError,"expected a list or tuple of floats");
+      Py_DECREF(tuple);
+      return NULL;
+    }
+    if(i==0) {
+      if(PyFloat_Check(obj)) x = PyFloat_AsDouble(obj);
+      else  x = (double)PyInt_AsLong(obj);
+    }
+    if(i==1) {
+      if(PyFloat_Check(obj)) y = PyFloat_AsDouble(obj);
+      else  y = (double)PyInt_AsLong(obj);
+    }
+    if(i==2) {
+      if(PyFloat_Check(obj)) z = PyFloat_AsDouble(obj);
+      else  z = (double)PyInt_AsLong(obj);
+    }
+  }
+  Py_DECREF(tuple);
+
+  /* Create the vertex */  
+  if( (p = gts_point_new(gts_point_class(),x,y,z)) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"could not create Point");
+  }
+  if( (point = pygts_point_new(p)) == NULL ) {
+    gts_object_destroy(GTS_OBJECT(p));
+    return NULL;
+  }
+
+  return point;
+}
+
+
+int 
+pygts_point_compare(GtsPoint* p1,GtsPoint* p2)
+{
+  double r1,r2;
+
+  if( (p1->x==p2->x) && (p1->y==p2->y) && (p1->z==p2->z) ) {
+    return 0;
+  }
+
+  /* Compare distances from origin */
+  r1 = sqrt(pow(p1->x,2) + pow(p1->y,2) + pow(p1->z,2));
+  r2 = sqrt(pow(p2->x,2) + pow(p2->y,2) + pow(p2->z,2));
+  if(r1<r2) return -1;
+  if(r1>r2) return 1;
+
+  /* Compare horizontal distances from origin */
+  r1 = sqrt(pow(p1->x,2) + pow(p1->y,2));
+  r2 = sqrt(pow(p2->x,2) + pow(p2->y,2));
+  if(r1<r2) return -1;
+  if(r1>r2) return 1;
+
+  /* Compare x */
+  r1 = p1->x;
+  r2 = p2->x;
+  if(r1<r2) return -1;
+  if(r1>r2) return 1;
+
+  /* Compare y */
+  r1 = p1->y;
+  r2 = p2->y;
+  if(r1<r2) return -1;
+  if(r1>r2) return 1;
+
+  /* Compare z */
+  r1 = p1->z;
+  r2 = p2->z;
+  if(r1<r2) return -1;
+  return 1;
+}

Added: trunk/lib/py/pygts-0.3.1/point.h
===================================================================
--- trunk/lib/py/pygts-0.3.1/point.h	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/point.h	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,51 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PYGTS_POINT_H__
+#define __PYGTS_POINT_H__
+
+typedef struct _PygtsObject PygtsPoint;
+
+#define PYGTS_POINT(o) ( PyObject_TypeCheck((PyObject*)o, &PygtsPointType) ? \
+			 (PygtsPoint*)o :				\
+			 pygts_point_from_sequence((PyObject*)o) )
+
+#define PYGTS_POINT_AS_GTS_POINT(o) (GTS_POINT(PYGTS_OBJECT(o)->gtsobj))
+
+extern PyTypeObject PygtsPointType;
+
+gboolean pygts_point_check(PyObject* o);
+gboolean pygts_point_is_ok(PygtsPoint *o);
+
+PygtsPoint* pygts_point_from_sequence(PyObject *tuple);
+int pygts_point_compare(GtsPoint* p1,GtsPoint* p2);
+
+gint pygts_point_rotate(GtsPoint* p,gdouble dx,gdouble dy,gdouble dz,gdouble a);
+gint pygts_point_scale(GtsPoint* p, gdouble dx, gdouble dy, gdouble dz);
+gint pygts_point_translate(GtsPoint* p, gdouble dx, gdouble dy, gdouble dz);
+
+#endif /* __PYGTS_POINT_H__ */

Added: trunk/lib/py/pygts-0.3.1/pygts.c
===================================================================
--- trunk/lib/py/pygts-0.3.1/pygts.c	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/pygts.c	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,794 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#include "pygts.h"
+
+#if PYGTS_HAS_NUMPY
+  #include "numpy/arrayobject.h"
+#endif
+
+
+static PyObject*
+merge(PyObject *self, PyObject *args)
+{
+  PyObject *tuple, *obj;
+  guint i,N;
+  GList *vertices=NULL,*v;
+  gdouble epsilon;
+  PygtsVertex *vertex;
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "Od", &tuple, &epsilon) ) {
+    return NULL;
+  }
+  if(PyList_Check(tuple)) {
+    tuple = PyList_AsTuple(tuple);
+  }
+  else {
+    Py_INCREF(tuple);
+  }
+  if(!PyTuple_Check(tuple)) {
+    Py_DECREF(tuple);
+    PyErr_SetString(PyExc_TypeError,"expected a list or tuple of vertices");
+    return NULL;
+  }
+
+  /* Assemble the GList */
+  N = PyTuple_Size(tuple);
+  for(i=N-1;i>0;i--) {
+    obj = PyTuple_GET_ITEM(tuple,i);
+    if(!pygts_vertex_check(obj)) {
+      Py_DECREF(tuple);
+      g_list_free(vertices);
+      PyErr_SetString(PyExc_TypeError,"expected a list or tuple of vertices");
+      return NULL;
+    }
+    vertices = g_list_prepend(vertices,PYGTS_VERTEX_AS_GTS_VERTEX(obj));
+  }
+  Py_DECREF(tuple);
+
+  /* Make the call */
+  vertices = pygts_vertices_merge(vertices,epsilon,NULL);
+
+  /* Assemble the return tuple */
+  N = g_list_length(vertices);
+  if( (tuple=PyTuple_New(N)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"could not create tuple");
+    return NULL;
+  }
+  v = vertices;
+  for(i=0;i<N;i++) {
+    if( (vertex = PYGTS_VERTEX(g_hash_table_lookup(obj_table,
+						   GTS_OBJECT(v->data))
+			       )) ==NULL ) {
+      PyErr_SetString(PyExc_RuntimeError,
+		      "could not get object from table (internal error)");
+      g_list_free(vertices);
+      return NULL;
+    }
+    Py_INCREF(vertex);
+    PyTuple_SET_ITEM(tuple,i,(PyObject*)vertex);
+    v = g_list_next(v);
+  }
+
+  g_list_free(vertices);
+
+  return tuple;
+}
+
+
+static PyObject*
+vertices(PyObject *self, PyObject *args)
+{
+  PyObject *tuple, *obj;
+  guint i,N;
+  GSList *segments=NULL,*vertices=NULL,*v;
+  PygtsVertex *vertex;
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &tuple) ) {
+    return NULL;
+  }
+  if(PyList_Check(tuple)) {
+    tuple = PyList_AsTuple(tuple);
+  }
+  else {
+    Py_INCREF(tuple);
+  }
+  if(!PyTuple_Check(tuple)) {
+    Py_DECREF(tuple);
+    PyErr_SetString(PyExc_TypeError,"expected a list or tuple of Segments");
+    return NULL;
+  }
+
+  /* Assemble the GSList */
+  N = PyTuple_Size(tuple);
+  for(i=0;i<N;i++) {
+    obj = PyTuple_GET_ITEM(tuple,N-1-i);
+    if(!pygts_segment_check(obj)) {
+      Py_DECREF(tuple);
+      g_slist_free(segments);
+      PyErr_SetString(PyExc_TypeError,"expected a list or tuple of Segments");
+      return NULL;
+    }
+    segments = g_slist_prepend(segments,PYGTS_SEGMENT_AS_GTS_SEGMENT(obj));
+  }
+  Py_DECREF(tuple);
+
+  /* Make the call */
+  vertices = gts_vertices_from_segments(segments);
+  g_slist_free(segments);
+
+  /* Assemble the return tuple */
+  N = g_slist_length(vertices);
+  if( (tuple=PyTuple_New(N)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"could not create tuple");
+    return NULL;
+  }
+  v = vertices;
+  for(i=0;i<N;i++) {
+    if( (vertex = pygts_vertex_new(GTS_VERTEX(v->data))) == NULL ) {
+	Py_DECREF(tuple);
+	g_slist_free(vertices);
+	return NULL;
+    }
+    PyTuple_SET_ITEM(tuple,i,(PyObject*)vertex);
+    v = g_slist_next(v);
+  }
+
+  g_slist_free(vertices);
+
+  return tuple;
+}
+
+
+static PyObject*
+segments(PyObject *self, PyObject *args)
+{
+  PyObject *tuple, *obj;
+  guint i,n,N;
+  GSList *segments=NULL,*vertices=NULL,*s;
+  PygtsSegment *segment;
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &tuple) ) {
+    return NULL;
+  }
+  if(PyList_Check(tuple)) {
+    tuple = PyList_AsTuple(tuple);
+  }
+  else {
+    Py_INCREF(tuple);
+  }
+  if(!PyTuple_Check(tuple)) {
+    Py_DECREF(tuple);
+    PyErr_SetString(PyExc_TypeError,"expected a list or tuple of vertices");
+    return NULL;
+  }
+
+  /* Assemble the GSList */
+  N = PyTuple_Size(tuple);
+  for(i=0;i<N;i++) {
+    obj = PyTuple_GET_ITEM(tuple,i);
+    if(!pygts_vertex_check(obj)) {
+      Py_DECREF(tuple);
+      g_slist_free(vertices);
+      PyErr_SetString(PyExc_TypeError,"expected a list or tuple of vertices");
+      return NULL;
+    }
+    vertices = g_slist_prepend(vertices,PYGTS_VERTEX_AS_GTS_VERTEX(obj));
+  }
+  Py_DECREF(tuple);
+
+  /* Make the call */
+  if( (segments = gts_segments_from_vertices(vertices)) == NULL ) {
+    PyErr_SetString(PyExc_RuntimeError,"could not retrieve segments");
+    return NULL;
+  }
+  g_slist_free(vertices);
+
+  /* Assemble the return tuple */
+  N = g_slist_length(segments);
+  if( (tuple=PyTuple_New(N)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"could not create tuple");
+    return NULL;
+  }
+
+  s = segments;
+  n=0;
+  while(s!=NULL) {
+
+    /* Skip any parent segments */
+    if(PYGTS_IS_PARENT_SEGMENT(s->data) || PYGTS_IS_PARENT_EDGE(s->data)) {
+      s = g_slist_next(s);
+      segment = NULL;
+      continue;
+    }
+
+    /* Fill in the tuple */
+    if(GTS_IS_EDGE(s->data)) {
+      segment = PYGTS_SEGMENT(pygts_edge_new(GTS_EDGE(s->data)));
+    }
+    else {
+      segment = pygts_segment_new(GTS_SEGMENT(s->data));
+    }
+    if( segment == NULL ) {
+      Py_DECREF(tuple);
+      g_slist_free(segments);
+      return NULL;
+    }
+    PyTuple_SET_ITEM(tuple,n,(PyObject*)segment);
+    s = g_slist_next(s);
+    n += 1;
+  }
+
+  g_slist_free(segments);
+
+  if(_PyTuple_Resize(&tuple,n)!=0) {
+    Py_DECREF(tuple);
+    return NULL;
+  }
+
+  return tuple;
+}
+
+
+static PyObject*
+triangles(PyObject *self, PyObject *args)
+{
+  PyObject *tuple, *obj;
+  guint i,n,N;
+  GSList *edges=NULL,*triangles=NULL,*t;
+  PygtsTriangle *triangle;
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &tuple) ) {
+    return NULL;
+  }
+  if(PyList_Check(tuple)) {
+    tuple = PyList_AsTuple(tuple);
+  }
+  else {
+    Py_INCREF(tuple);
+  }
+  if(!PyTuple_Check(tuple)) {
+    Py_DECREF(tuple);
+    PyErr_SetString(PyExc_TypeError,"expected a list or tuple of edges");
+    return NULL;
+  }
+
+  /* Assemble the GSList */
+  N = PyTuple_Size(tuple);
+  for(i=0;i<N;i++) {
+    obj = PyTuple_GET_ITEM(tuple,i);
+    if(!pygts_edge_check(obj)) {
+      Py_DECREF(tuple);
+      g_slist_free(edges);
+      PyErr_SetString(PyExc_TypeError,"expected a list or tuple of edges");
+      return NULL;
+    }
+    edges = g_slist_prepend(edges,PYGTS_EDGE_AS_GTS_EDGE(obj));
+  }
+  Py_DECREF(tuple);
+
+  /* Make the call */
+  if( (triangles = gts_triangles_from_edges(edges)) == NULL ) {
+    PyErr_SetString(PyExc_RuntimeError,"could not retrieve triangles");
+    return NULL;
+  }
+  g_slist_free(edges);
+
+  /* Assemble the return tuple */
+  N = g_slist_length(triangles);
+  if( (tuple=PyTuple_New(N)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"could not create tuple");
+    return NULL;
+  }
+
+  t = triangles;
+  n=0;
+  while(t!=NULL) {
+
+    /* Skip any parent triangles */
+    if(PYGTS_IS_PARENT_TRIANGLE(t->data)) {
+      t = g_slist_next(t);
+      triangle = NULL;
+      continue;
+    }
+
+    /* Fill in the tuple */
+    if(GTS_IS_FACE(t->data)) {
+      triangle = PYGTS_TRIANGLE(pygts_face_new(GTS_FACE(t->data)));
+    }
+    else {
+      triangle = pygts_triangle_new(GTS_TRIANGLE(t->data));
+    }
+    if( triangle == NULL ) {
+      Py_DECREF(tuple);
+      g_slist_free(triangles);
+      return NULL;
+    }
+    PyTuple_SET_ITEM(tuple,n,(PyObject*)triangle);
+    t = g_slist_next(t);
+    n += 1;
+  }
+
+  g_slist_free(triangles);
+
+  if(_PyTuple_Resize(&tuple,n)!=0) {
+    Py_DECREF(tuple);
+    return NULL;
+  }
+
+  return tuple;
+}
+
+
+static PyObject*
+triangle_enclosing(PyObject *self, PyObject *args)
+{
+  PyObject *tuple, *obj;
+  guint i,N;
+  GSList *points=NULL;
+  GtsTriangle *t;
+  PygtsTriangle *triangle;
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &tuple) ) {
+    return NULL;
+  }
+  if(PyList_Check(tuple)) {
+    tuple = PyList_AsTuple(tuple);
+  }
+  else {
+    Py_INCREF(tuple);
+  }
+  if(!PyTuple_Check(tuple)) {
+    Py_DECREF(tuple);
+    PyErr_SetString(PyExc_TypeError,"expected a list or tuple of points");
+    return NULL;
+  }
+
+  /* Assemble the GSList */
+  N = PyTuple_Size(tuple);
+  for(i=0;i<N;i++) {
+    obj = PyTuple_GET_ITEM(tuple,i);
+    if(!pygts_point_check(obj)) {
+      Py_DECREF(tuple);
+      g_slist_free(points);
+      PyErr_SetString(PyExc_TypeError,"expected a list or tuple of points");
+      return NULL;
+    }
+    points = g_slist_prepend(points,PYGTS_POINT_AS_GTS_POINT(obj));
+  }
+  Py_DECREF(tuple);
+
+  /* Make the call */
+  t = gts_triangle_enclosing(gts_triangle_class(),points,1.0);
+  g_slist_free(points);
+
+  if(t==NULL) {
+    PyErr_SetString(PyExc_RuntimeError,"could not compute triangle");
+    return NULL;
+  }
+
+  if( (triangle = pygts_triangle_new(t)) == NULL ) {
+    return NULL;
+  }
+
+  return (PyObject*)triangle;
+}
+
+
+static PyObject*
+pygts_read(PygtsSurface *self, PyObject *args)
+{
+  PyObject *f_;
+  FILE *f;
+  GtsFile *fp;
+  guint lineno;
+  GtsSurface *s;
+  PygtsSurface *surface;
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &f_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if(!PyFile_Check(f_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a File");
+    return NULL;
+  }
+  f = PyFile_AsFile(f_);
+
+  if(feof(f)) {
+    PyErr_SetString(PyExc_EOFError,"End of File");
+    return NULL;
+  }
+
+  /* Create a temporary surface to read into */
+  if( (s = gts_surface_new(gts_surface_class(), gts_face_class(),
+			   gts_edge_class(), gts_vertex_class())) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"could not create Surface");
+    return NULL;
+  }
+
+  /* Read from the file */
+  fp = gts_file_new(f);
+  if( (lineno = gts_surface_read(s,fp)) != 0 ) {
+    PyErr_SetString(PyExc_RuntimeError,fp->error);
+    gts_file_destroy(fp);
+    return NULL;
+  }
+  gts_file_destroy(fp);
+  if( (surface = pygts_surface_new(s)) == NULL )  {
+    gts_object_destroy(GTS_OBJECT(s));
+    return NULL;
+  }
+
+  /* Clean up the surface */
+  pygts_edge_cleanup(PYGTS_SURFACE_AS_GTS_SURFACE(surface));
+  pygts_face_cleanup(PYGTS_SURFACE_AS_GTS_SURFACE(surface));
+
+  return (PyObject*)surface;
+}
+
+
+static PyObject*
+sphere(PyObject *self, PyObject *args)
+{
+  PyObject *kwds;
+  PygtsSurface *surface;
+  guint geodesation_order;
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "i", &geodesation_order) )
+    return NULL;
+
+  /* Chain up object allocation */
+  args = Py_BuildValue("()");
+  kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_True);
+  surface = PYGTS_SURFACE(PygtsSurfaceType.tp_new(&PygtsSurfaceType, 
+						  args, kwds));
+  Py_DECREF(args);
+  Py_DECREF(kwds);
+  if( surface == NULL ) {
+    PyErr_SetString(PyExc_MemoryError, "could not create Surface");
+    return NULL;
+  }
+
+  gts_surface_generate_sphere(PYGTS_SURFACE_AS_GTS_SURFACE(surface),
+			      geodesation_order);
+
+  pygts_object_register(PYGTS_OBJECT(surface));
+  return (PyObject*)surface;
+}
+
+
+#if PYGTS_HAS_NUMPY
+
+/* Helper for pygts_iso to fill f with a layer of data from scalar */
+static void isofunc(gdouble **f, GtsCartesianGrid g, guint k, gpointer data)
+{
+  PyArrayObject *scalars = (PyArrayObject *)data;
+  int i, j;
+
+  for (i = 0; i < scalars->dimensions[0]; i++) {
+    for (j = 0; j < scalars->dimensions[1]; j++) {
+      f[i][j] = *(gdouble *)(scalars->data + i*scalars->strides[0] + \
+			     j*scalars->strides[1] + k*scalars->strides[2]);
+    }
+  }
+}
+
+#define ISO_CLEANUP \
+  if (scalars) { Py_DECREF(scalars); } \
+  if (extents) { Py_DECREF(extents); }
+
+static PyObject*
+isosurface(PyObject *self, PyObject *args, PyObject *kwds)
+{
+  double isoval[1];
+  PyObject *Oscalars = NULL, *Oextents = NULL;
+  PyArrayObject *scalars = NULL, *extents = NULL;
+  GtsCartesianGrid g;
+  GtsSurface *s;
+  PygtsSurface *surface;
+  char *method = "cubes";
+  
+  static char *kwlist[] = {"scalars", "isoval", "method", "extents", NULL};
+
+  if(!PyArg_ParseTupleAndKeywords(args, kwds, "Od|sO", kwlist, 
+				  &Oscalars, isoval, &method, &Oextents)) {
+    return NULL;
+  }
+  
+  if(!(scalars = (PyArrayObject *) 
+       PyArray_ContiguousFromObject(Oscalars, PyArray_DOUBLE, 3, 3))) {
+    ISO_CLEANUP;
+    return NULL;
+  }
+
+  if(Oextents && 
+     (!(extents =  (PyArrayObject *)
+	PyArray_ContiguousFromObject(Oextents, PyArray_DOUBLE, 1, 1)))) {
+    ISO_CLEANUP;
+    return NULL;
+  }
+
+  if(extents && extents->dimensions[0] < 6) {
+    PyErr_SetString(PyExc_ValueError, "extents must have at least 6 elements");
+    ISO_CLEANUP;
+    return NULL;
+  }
+  
+  if(extents) {
+    int s = extents->strides[0];
+    g.x = *(gdouble*)(extents->data + 0*s);
+    g.nx = scalars->dimensions[0];
+    g.dx = (*(gdouble*)(extents->data + 1*s) - \
+	    *(gdouble*)(extents->data + 0* s))/(g.nx-1);
+
+    g.y = *(gdouble*)(extents->data + 2*s);
+    g.ny = scalars->dimensions[1];
+    g.dy = (*(gdouble*)(extents->data + 3*s) - \
+	    *(gdouble*)(extents->data + 2*s))/(g.ny-1);
+
+    g.z = *(gdouble*)(extents->data + 4*s);
+    g.nz = scalars->dimensions[2];
+    g.dz = (*(gdouble*)(extents->data + 5*s) - \
+	    *(gdouble*)(extents->data + 4*s))/(g.nz-1);
+  }
+  else {
+    g.x = -1.0;
+    g.nx = scalars->dimensions[0];
+    g.dx = 2.0/(scalars->dimensions[0]-1);
+    g.y = -1.0;
+    g.ny = scalars->dimensions[1];
+    g.dy = 2.0/(scalars->dimensions[1]-1);
+    g.z = -1.0;
+    g.nz = scalars->dimensions[2];
+    g.dz = 2.0/(scalars->dimensions[2]-1);
+  }
+
+  /* Create the surface */
+  if((s = gts_surface_new(gts_surface_class(), gts_face_class(),
+			  gts_edge_class(), gts_vertex_class())) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"could not create Surface");
+    return NULL;
+  }
+
+  /* Make the call */
+  switch(method[0]) {
+  case 'c': /* cubes */
+    gts_isosurface_cartesian(s, g, isofunc, scalars, isoval[0]);
+    break;
+  case 't': /* tetra */
+    gts_isosurface_tetra(s, g, isofunc, scalars, isoval[0]);
+    /* *** ATTENTION ***
+     * Isosurface produced is "inside-out", and so we must revert it.
+     * This is a bug in GTS.
+     */
+    gts_surface_foreach_face(s, (GtsFunc)gts_triangle_revert, NULL);
+    /* *** ATTENTION *** */
+    break;
+  case 'b': /* tetra bounded */
+    gts_isosurface_tetra_bounded(s, g, isofunc, scalars, isoval[0]);
+    /* *** ATTENTION ***
+     * Isosurface produced is "inside-out", and so we must revert it.
+     * This is a bug in GTS.
+     */
+    gts_surface_foreach_face(s, (GtsFunc)gts_triangle_revert, NULL);
+    /* *** ATTENTION *** */
+    break;
+  case 'd': /* tetra bcl*/
+    gts_isosurface_tetra_bcl(s, g, isofunc, scalars, isoval[0]);
+    /* *** ATTENTION ***
+     * Isosurface produced is "inside-out", and so we must revert it.
+     * This is a bug in GTS.
+     */
+    gts_surface_foreach_face(s, (GtsFunc)gts_triangle_revert, NULL);
+    /* *** ATTENTION *** */
+    break;
+  default:
+    PyErr_SetString(PyExc_ValueError, "unknown method");
+    ISO_CLEANUP;
+    return NULL;
+  }    
+
+  ISO_CLEANUP;
+
+  if( (surface = pygts_surface_new(s)) == NULL )  {
+    gts_object_destroy(GTS_OBJECT(s));
+    return NULL;
+  }
+
+  return (PyObject*)surface;
+}
+
+#endif /* PYGTS_HAS_NUMPY */
+
+
+static PyMethodDef gts_methods[] = {
+
+  {"read", (PyCFunction)pygts_read,
+   METH_VARARGS,
+   "Returns the data read from File f as a Surface.\n"
+   "The File data must be in GTS format (e.g., as written using\n"
+   "Surface.write())\n"
+   "\n"
+   "Signature: read(f)\n"
+  },
+
+  { "sphere", sphere, METH_VARARGS,
+    "Returns a unit sphere generated by recursive subdivision.\n"
+    "First approximation is an isocahedron; each level of refinement\n"
+    "(geodesation_order) increases the number of triangles by a factor\n"
+    "of 4.\n"
+    "\n"
+    "Signature: sphere(geodesation_order)\n"
+  },
+
+#if PYGTS_HAS_NUMPY
+  {"isosurface",  (PyCFunction)isosurface, 
+   METH_VARARGS|METH_KEYWORDS,
+   "Adds to surface new faces defining the isosurface data[x,y,z] = c\n"
+   "\n"
+   "Signature: isosurface(data, c, ...)\n"
+   "\n"
+   "data is a 3D numpy array.\n"
+   "c    is the isovalue defining the surface\n"
+   "\n"
+   "Keyword arguments:\n"
+   "extents= [xmin, xmax, ymin, ymax, zmin, zmax]\n"
+   "         A numpy array defining the extent of the data cube.\n" 
+   "         Default is the cube with corners at (-1,-1,-1) and (1,1,1)\n"
+   "         Data is assumed to be regularly sampled in the cube.\n"
+   "method=  ['cube'|'tetra'|'dual'|'bounded']\n"
+   "         String (only the first character counts) specifying the\n"
+   "         method.\n"
+   "         cube    -- marching cubes (default)\n"
+   "         tetra   -- marching tetrahedra\n"
+   "         dual    -- maching tetrahedra producing dual 'body-centred'\n"
+   "                    faces relative to 'tetra'\n"
+   "         bounded -- marching tetrahedra ensuring the surface is\n"
+   "                    bounded by adding a border of large negative\n"
+   "                    values around the domain.\n"
+   "\n"
+   "By convention, the normals to the surface are pointing towards\n"
+   "positive values of data[x,y,z] - c.\n"
+  },
+#endif /* PYGTS_HAS_NUMPY */
+
+  { "merge", merge, METH_VARARGS,
+    "Merges list of Vertices that are within a box of side-length\n"
+    "epsilon of each other.\n"
+    "\n"
+    "Signature: merge(list,epsilon)\n"
+  },
+
+  { "vertices", vertices, METH_VARARGS,
+    "Returns tuple of Vertices from a list or tuple of Segments.\n"
+    "\n"
+    "Signature: vertices(list)\n"
+  },
+
+  { "segments", segments, METH_VARARGS,
+    "Returns tuple of Segments from a list or tuple of Vertices.\n"
+    "\n"
+    "Signature: segments(list)\n"
+  },
+
+  { "triangles", triangles, METH_VARARGS,
+    "Returns tuple of Triangles from a list or tuple of Edges.\n"
+    "\n"
+    "Signature: triangles(list)\n"
+  },
+
+  { "triangle_enclosing", triangle_enclosing, METH_VARARGS,
+    "Returns a Triangle that encloses the plane projection of a list\n"
+    "or tuple of Points.  The Triangle is equilateral and encloses a\n"
+    "rectangle defined by the maximum and minimum x and y coordinates\n"
+    "of the points.\n"
+    "\n"
+    "Signature: triangles(list)\n"
+  },
+
+
+  {NULL}  /* Sentinel */
+};
+
+#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
+#define PyMODINIT_FUNC void
+#endif
+PyMODINIT_FUNC
+init_gts(void) 
+{
+  PyObject* m;
+
+  /* Allocate the object table */
+  if( (obj_table=g_hash_table_new(NULL,NULL)) == NULL ) return;
+
+  /* Set class base types and make ready (i.e., inherit methods) */
+  if (PyType_Ready(&PygtsObjectType) < 0) return;
+
+  PygtsPointType.tp_base = &PygtsObjectType;
+  if (PyType_Ready(&PygtsPointType) < 0) return;
+
+  PygtsVertexType.tp_base = &PygtsPointType;
+  if (PyType_Ready(&PygtsVertexType) < 0) return;
+
+  PygtsSegmentType.tp_base = &PygtsObjectType;
+  if (PyType_Ready(&PygtsSegmentType) < 0) return;
+
+  PygtsEdgeType.tp_base = &PygtsSegmentType;
+  if (PyType_Ready(&PygtsEdgeType) < 0) return;
+
+  PygtsTriangleType.tp_base = &PygtsObjectType;
+  if (PyType_Ready(&PygtsTriangleType) < 0) return;
+
+  PygtsFaceType.tp_base = &PygtsTriangleType;
+  if (PyType_Ready(&PygtsFaceType) < 0) return;
+
+  PygtsSurfaceType.tp_base = &PygtsObjectType;
+  if (PyType_Ready(&PygtsSurfaceType) < 0) return;
+
+
+  /* Initialize the module */
+  m = Py_InitModule3("_gts", gts_methods,"Gnu Triangulated Surface Library");
+  if (m == NULL) return;
+
+#if PYGTS_HAS_NUMPY
+  /* Make sure Surface.iso can work with numpy arrays */
+  import_array()
+#endif
+
+  /* Add new types to python */
+  Py_INCREF(&PygtsObjectType);
+  PyModule_AddObject(m, "Object", (PyObject *)&PygtsObjectType);
+
+  Py_INCREF(&PygtsPointType);
+  PyModule_AddObject(m, "Point", (PyObject *)&PygtsPointType);
+
+  Py_INCREF(&PygtsVertexType);
+  PyModule_AddObject(m, "Vertex", (PyObject *)&PygtsVertexType);
+
+  Py_INCREF(&PygtsSegmentType);
+  PyModule_AddObject(m, "Segment", (PyObject *)&PygtsSegmentType);
+
+  Py_INCREF(&PygtsEdgeType);
+  PyModule_AddObject(m, "Edge", (PyObject *)&PygtsEdgeType);
+
+  Py_INCREF(&PygtsTriangleType);
+  PyModule_AddObject(m, "Triangle", (PyObject *)&PygtsTriangleType);
+
+  Py_INCREF(&PygtsFaceType);
+  PyModule_AddObject(m, "Face", (PyObject *)&PygtsFaceType);
+
+  Py_INCREF(&PygtsSurfaceType);
+  PyModule_AddObject(m, "Surface", (PyObject *)&PygtsSurfaceType);
+}

Added: trunk/lib/py/pygts-0.3.1/pygts.h
===================================================================
--- trunk/lib/py/pygts-0.3.1/pygts.h	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/pygts.h	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,59 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PYGTS_H__
+#define __PYGTS_H__
+
+#ifndef PYGTS_DEBUG
+#define PYGTS_DEBUG 1
+#endif /* PYGTS_DEBUG */
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include <Python.h>
+#include <structmember.h>
+
+/* Defined for arrayobject.h which is only included where needed */
+#define PY_ARRAY_UNIQUE_SYMBOL PYGTS
+
+#include <glib.h>
+#include <gts.h>
+
+#include "object.h"
+#include "point.h"
+#include "vertex.h"
+#include "segment.h"
+#include "edge.h"
+#include "triangle.h"
+#include "face.h"
+#include "surface.h"
+
+#include "cleanup.h"
+
+#endif /* __PYGTS_H__ */

Added: trunk/lib/py/pygts-0.3.1/pygts.py
===================================================================
--- trunk/lib/py/pygts-0.3.1/pygts.py	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/pygts.py	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,144 @@
+# pygts - python package for the manipulation of triangulated surfaces
+#
+#   Copyright (C) 2009 Thomas J. Duck
+#   All rights reserved.
+#
+#   Thomas J. Duck <tom.duck@xxxxxx>
+#   Department of Physics and Atmospheric Science,
+#   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+#
+# NOTICE
+#
+#   This library is free software; you can redistribute it and/or
+#   modify it under the terms of the GNU Library General Public
+#   License as published by the Free Software Foundation; either
+#   version 2 of the License, or (at your option) any later version.
+#
+#   This library 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
+#   Library General Public License for more details.
+#
+#   You should have received a copy of the GNU Library General Public
+#   License along with this library; if not, write to the
+#   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+#   Boston, MA 02111-1307, USA.
+
+
+from _gts import *
+
+def get_coords_and_face_indices(s,unzip=False):
+    """Returns the coordinates and face indices of Surface s.
+
+    If unzip is True then four tuples are returned.  The first three 
+    are the x, y, and z coordinates for each Vertex on the Surface.  
+    The last is a list of tuples, one for each Face on the Surface, 
+    containing 3 indices linking the Face Vertices to the coordinate 
+    lists.
+
+    If unzip is False then the coordinates are given in a single list 
+    of 3-tuples.
+    """
+    vertices = s.vertices()
+    coords = [v.coords() for v in vertices]
+    face_indices = s.face_indices(vertices)
+
+    if unzip:
+        x,y,z = zip(*coords)
+        return x,y,z,face_indices
+    else:
+        return vertices, coords
+
+
+def cube():
+    """Returns a cube of side length 2 centered at the origin."""
+    
+    #             
+    #       v8 +------+ v5
+    #         /      /|
+    #        /    v1/ |
+    #    v4 +------+  |
+    #       |      |  + v6
+    #       |(v7)  | /
+    #       |      |/
+    #    v3 +------+ v2
+    #
+
+    v1,v2,v3,v4=Vertex(1,1,1),Vertex(1,1,-1),Vertex(1,-1,-1),Vertex(1,-1,1)
+    v5,v6,v7,v8=Vertex(-1,1,1),Vertex(-1,1,-1),Vertex(-1,-1,-1),Vertex(-1,-1,1)
+
+    e12,e23,e34,e14 = Edge(v1,v2), Edge(v2,v3), Edge(v3,v4), Edge(v4,v1)
+    e56,e67,e78,e58 = Edge(v5,v6), Edge(v6,v7), Edge(v7,v8), Edge(v8,v5)
+    e15,e26,e37,e48 = Edge(v1,v5), Edge(v2,v6), Edge(v3,v7), Edge(v4,v8)
+    e13,e16,e18 = Edge(v1,v3), Edge(v1,v6), Edge(v1,v8)
+    e27,e47,e57 = Edge(v7,v2), Edge(v7,v4), Edge(v7,v5)
+
+    faces = [ Face(e12,e23,e13), Face(e13,e34,e14),
+              Face(e12,e26,e16), Face(e15,e56,e16),
+              Face(e15,e58,e18), Face(e14,e48,e18),
+              Face(e58,e78,e57), Face(e56,e67,e57),
+              Face(e26,e67,e27), Face(e37,e23,e27),
+              Face(e37,e47,e34), Face(e78,e48,e47) ]
+
+    faces[0].revert()  # Set the orientation of the first face
+
+    s = Surface()
+
+    for face in faces:
+        if not face.is_compatible(s):
+            face.revert()
+        s.add(face)
+
+    return s
+
+
+def tetrahedron():
+    """Returns a tetrahedron of side length 2*sqrt(2) centered at origin.
+
+    The edges of the tetrahedron are perpendicular to the cardinal
+    directions.
+    """
+
+    #       v4
+    #        +
+    #        | \ e6
+    #  e5   '|e4 \ 
+    #   v1 . +-e3-+ v3
+    #       /   . 
+    #     ./e1. e2
+    #     / .
+    #    +
+    #   v2
+
+
+    # Create vertices
+    v1 = Vertex(1,1,1)
+    v2 = Vertex(-1,-1,1)
+    v3 = Vertex(-1,1,-1)
+    v4 = Vertex(1,-1,-1)
+
+    # Create edges
+    e1 = Edge(v1,v2)
+    e2 = Edge(v2,v3)
+    e3 = Edge(v3,v1)
+    e4 = Edge(v1,v4)
+    e5 = Edge(v4,v2)
+    e6 = Edge(v4,v3)
+
+    # Create faces
+    f1 = Face(e1,e2,e3) # Bottom face
+    f2 = Face(e1,e4,e5) # Left face
+    f3 = Face(e2,e5,e6) # Right face
+    f4 = Face(e3,e4,e6) # Back face
+
+    # Set orientation of first face
+    f1.revert()
+
+    # Assemble surface
+    s = Surface()
+    for face in [f1,f2,f3,f4]:
+        if not face.is_compatible(s):
+            face.revert()
+        s.add(face)
+
+    return s

Added: trunk/lib/py/pygts-0.3.1/segment.c
===================================================================
--- trunk/lib/py/pygts-0.3.1/segment.c	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/segment.c	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,570 @@
+/* pygts - python segment for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#include "pygts.h"
+
+
+#if PYGTS_DEBUG
+  #define SELF_CHECK if(!pygts_segment_check((PyObject*)self)) {      \
+                       PyErr_SetString(PyExc_RuntimeError,            \
+                       "problem with self object (internal error)");  \
+		       return NULL;                                   \
+                     }
+#else
+  #define SELF_CHECK
+#endif
+
+
+/*-------------------------------------------------------------------------*/
+/* Methods exported to python */
+
+static PyObject*
+is_ok(PygtsSegment *self, PyObject *args)
+{
+  if(pygts_segment_is_ok(self)) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+intersects(PygtsSegment *self, PyObject *args)
+{
+  PyObject *s_;
+  GtsSegment *s;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "O", &s_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_segment_check(s_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Segment");
+    return NULL;
+  }
+  s = PYGTS_SEGMENT_AS_GTS_SEGMENT(s_);
+
+  return Py_BuildValue("i",
+      gts_segments_are_intersecting(PYGTS_SEGMENT_AS_GTS_SEGMENT(self),s));
+}
+
+
+static PyObject*
+connects(PygtsSegment *self, PyObject *args)
+{
+  PyObject *v1_,*v2_;
+  GtsVertex *v1,*v2;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "OO", &v1_, &v2_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_vertex_check(v1_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Vertex");
+    return NULL;
+  }
+  v1 = PYGTS_VERTEX_AS_GTS_VERTEX(v1_);
+
+  if(!pygts_vertex_check(v2_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Vertex");
+    return NULL;
+  }
+  v2 = PYGTS_VERTEX_AS_GTS_VERTEX(v2_);
+
+  if(gts_segment_connect(PYGTS_SEGMENT_AS_GTS_SEGMENT(self),v1,v2)) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+touches(PygtsSegment *self, PyObject *args)
+{
+  PyObject *s_;
+  GtsSegment *s;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "O", &s_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_segment_check(s_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Segment");
+    return NULL;
+  }
+  s = PYGTS_SEGMENT_AS_GTS_SEGMENT(s_);
+
+  if(gts_segments_touch(PYGTS_SEGMENT_AS_GTS_SEGMENT(self),s)) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+midvertex(PygtsSegment *self, PyObject *args)
+{
+  PygtsVertex *vertex;
+  GtsVertex *v;
+
+  SELF_CHECK
+
+  v = gts_segment_midvertex(PYGTS_SEGMENT_AS_GTS_SEGMENT(self),
+			    gts_vertex_class());
+
+  if( (vertex = pygts_vertex_new(v)) == NULL ) {
+    return NULL;
+  }
+
+  return (PyObject*)vertex;
+}
+
+
+static PyObject*
+intersection(PygtsSegment *self, PyObject *args)
+{
+  PyObject *t_,*boundary_=NULL;
+  PygtsTriangle *t;
+  gboolean boundary=TRUE;
+  GtsVertex *v;
+  PygtsObject *vertex;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "O|O", &t_, &boundary_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_triangle_check(t_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Triangle and boolean");
+    return NULL;
+  }
+  t = PYGTS_TRIANGLE(t_);
+
+  if( boundary_ != NULL ) {
+    if(PyBool_Check(boundary_)==FALSE) {
+      PyErr_SetString(PyExc_TypeError,"expected a Triangle and boolean");
+      return NULL;
+    }
+    if( boundary_ == Py_False ){  /* Default TRUE */
+      boundary = FALSE;
+    }
+  }
+
+  v = GTS_VERTEX( gts_segment_triangle_intersection(
+		      PYGTS_SEGMENT_AS_GTS_SEGMENT(self),
+		      PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t),
+		      boundary,
+		      GTS_POINT_CLASS(gts_vertex_class())) );
+
+  if( v == NULL ) {
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+
+  if( (vertex = pygts_vertex_new(v)) == NULL ) {
+    return NULL;
+  }
+
+  return (PyObject *)vertex;
+}
+
+
+/* Methods table */
+static PyMethodDef methods[] = {
+  {"is_ok", (PyCFunction)is_ok,
+   METH_NOARGS,
+   "True if this Segment s is not degenerate or duplicate.\n"
+   "False otherwise.  Degeneracy implies s.v1.id == s.v2.id.\n"
+   "\n"
+   "Signature: s.is_ok().\n"
+  },  
+
+  {"intersects", (PyCFunction)intersects,
+   METH_VARARGS,
+   "Checks if this Segment s1 intersects with Segment s2.\n"
+   "Returns 1 if they intersect, 0 if an endpoint of one Segment lies\n"
+   "on the other Segment, -1 otherwise\n"
+   "\n"
+   "Signature: s1.intersects(s2).\n"
+  },  
+
+  {"connects", (PyCFunction)connects,
+   METH_VARARGS,
+   "Returns True if this Segment s1 connects Vertices v1 and v2.\n"
+   "False otherwise.\n"
+   "\n"
+   "Signature: s1.connects(v1,v2).\n"
+  },  
+
+  {"touches", (PyCFunction)touches,
+   METH_VARARGS,
+   "Returns True if this Segment s1 touches Segment s2\n"
+   "(i.e., they share a common Vertex).  False otherwise.\n"
+   "\n"
+   "Signature: s1.touches(s2).\n"
+  },  
+
+  {"midvertex", (PyCFunction)midvertex,
+   METH_NOARGS,
+   "Returns a new Vertex at the mid-point of this Segment s.\n"
+   "\n"
+   "Signature: s.midvertex().\n"
+  },  
+
+  {"intersection", (PyCFunction)intersection,
+   METH_VARARGS,
+   "Returns the intersection of Segment s with Triangle t\n"
+   "\n"
+   "This function is geometrically robust in the sense that it will\n"
+   "return None if s and t do not intersect and will return a\n"
+   "Vertex if they do. However, the point coordinates are subject\n"
+   "to round-off errors.  None will be returned if s is contained\n"
+   "in the plane defined by t.\n"
+   "\n"
+   "Signature: s.intersection(t) or s.intersection(t,boundary).\n"
+   "\n"
+   "If boundary is True (default), the boundary of s is taken into\n"
+   "account.\n"
+   "\n"
+   "Returns a summit of t (if boundary is True), one of the endpoints\n"
+   "of s, a new Vertex at the intersection of s with t, or None if\n"
+   "s and t don't intersect.\n"
+  },  
+
+  {NULL}  /* Sentinel */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Attributes exported to python */
+
+static PyObject *
+get_v1(PygtsSegment *self, void *closure)
+{
+  PygtsObject *v1;
+
+  SELF_CHECK
+
+  if( (v1=pygts_vertex_new(PYGTS_SEGMENT_AS_GTS_SEGMENT(self)->v1)) == NULL ) {
+    return NULL;
+  }
+
+  return (PyObject *)v1;
+}
+
+
+static PyObject *
+get_v2(PygtsSegment *self, void *closure)
+{
+  PygtsObject *v2;
+
+  SELF_CHECK
+
+  if( (v2=pygts_vertex_new(PYGTS_SEGMENT_AS_GTS_SEGMENT(self)->v2)) == NULL ) {
+    return NULL;
+  }
+
+  return (PyObject *)v2;
+}
+
+
+/* Methods table */
+static PyGetSetDef getset[] = {
+    {"v1", (getter)get_v1, NULL, "Vertex 1", NULL},
+    {"v2", (getter)get_v2, NULL, "Vertex 2", NULL},
+    {NULL}  /* Sentinel */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Python type methods */
+
+static PyObject *
+new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+  PyObject *o;
+  PygtsObject *obj;
+  GtsSegment *tmp;
+  GtsObject *segment=NULL;
+  PyObject *v1_=NULL,*v2_=NULL;
+  PygtsVertex *v1,*v2;
+  guint alloc_gtsobj = TRUE;
+  guint N;
+
+  /* Parse the args */
+  if(kwds) {
+    o = PyDict_GetItemString(kwds,"alloc_gtsobj");
+    if(o==Py_False) {
+      alloc_gtsobj = FALSE;
+    }
+    if(o!=NULL) {
+      PyDict_DelItemString(kwds, "alloc_gtsobj");
+    }
+  }
+  if(kwds) {
+    Py_INCREF(Py_False);
+    PyDict_SetItemString(kwds,"alloc_gtsobj", Py_False);
+  }
+
+  /* Allocate the gtsobj (if needed) */
+  if( alloc_gtsobj ) {
+
+    /* Parse the args */
+    if( (N = PyTuple_Size(args)) < 2 ) {
+      PyErr_SetString(PyExc_TypeError,"expected two Vertices");
+      return NULL;
+    }
+    v1_ = PyTuple_GET_ITEM(args,0);
+    v2_ = PyTuple_GET_ITEM(args,1);
+
+    /* Convert to PygtsObjects */
+    if(!pygts_vertex_check(v1_)) {
+      PyErr_SetString(PyExc_TypeError,"expected two Vertices");
+      return NULL;
+    }
+    if(!pygts_vertex_check(v2_)) {
+      PyErr_SetString(PyExc_TypeError,"expected two Vertices");
+      return NULL;
+    }
+    v1 = PYGTS_VERTEX(v1_);
+    v2 = PYGTS_VERTEX(v2_);
+
+    /* Error check */
+    if(PYGTS_OBJECT(v1)->gtsobj == PYGTS_OBJECT(v2)->gtsobj) {
+      PyErr_SetString(PyExc_ValueError,"Vertices are identical");
+      return NULL;
+    }
+
+    /* Create the GtsSegment */
+    segment = GTS_OBJECT(gts_segment_new(gts_segment_class(),
+					 GTS_VERTEX(v1->gtsobj),
+					 GTS_VERTEX(v2->gtsobj)));
+    if( segment == NULL )  {
+      PyErr_SetString(PyExc_MemoryError, "could not create Segment");
+      return NULL;
+    }
+
+    /* Check for duplicate */
+    tmp = gts_segment_is_duplicate(GTS_SEGMENT(segment));
+    if( tmp != NULL ) {
+      gts_object_destroy(segment);
+      segment = GTS_OBJECT(tmp);
+    }
+
+    /* If corresponding PyObject found in object table, we are done */
+    if( (obj=g_hash_table_lookup(obj_table,segment)) != NULL ) {
+      Py_INCREF(obj);
+      return (PyObject*)obj;
+    }
+  }  
+
+  /* Chain up */
+  obj = PYGTS_OBJECT(PygtsObjectType.tp_new(type,args,kwds));
+
+  if( alloc_gtsobj ) {
+    obj->gtsobj = segment;
+    pygts_object_register(PYGTS_OBJECT(obj));
+  }
+
+  return (PyObject*)obj;
+}
+
+
+static int
+init(PygtsSegment *self, PyObject *args, PyObject *kwds)
+{
+  gint ret;
+
+  /* Chain up */
+  if( (ret=PygtsObjectType.tp_init((PyObject*)self,args,kwds)) != 0 ){
+    return ret;
+  }
+
+  return 0;
+}
+
+
+static int 
+compare(PygtsSegment *s1_, PygtsSegment *s2_)
+{
+  GtsSegment *s1, *s2;
+
+#if PYGTS_DEBUG
+  pygts_segment_check((PyObject*)s1_);
+  pygts_segment_check((PyObject*)s2_);
+#endif
+
+  s1 = PYGTS_SEGMENT_AS_GTS_SEGMENT(s1_);
+  s2 = PYGTS_SEGMENT_AS_GTS_SEGMENT(s2_);
+  
+  return pygts_segment_compare(s1,s2);
+  
+}
+
+
+/* Methods table */
+PyTypeObject PygtsSegmentType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                       /* ob_size */
+    "gts.Segment",           /* tp_name */
+    sizeof(PygtsSegment),    /* tp_basicsize */
+    0,                       /* tp_itemsize */
+    0,                       /* tp_dealloc */
+    0,                       /* tp_print */
+    0,                       /* tp_getattr */
+    0,                       /* tp_setattr */
+    (cmpfunc)compare,        /* tp_compare */
+    0,                       /* tp_repr */
+    0,                       /* tp_as_number */
+    0,                       /* tp_as_sequence */
+    0,                       /* tp_as_mapping */
+    0,                       /* tp_hash */
+    0,                       /* tp_call */
+    0,                       /* tp_str */
+    0,                       /* tp_getattro */
+    0,                       /* tp_setattro */
+    0,                       /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT |
+      Py_TPFLAGS_BASETYPE,   /* tp_flags */
+    "Segment object",        /* tp_doc */
+    0,                       /* tp_traverse */
+    0,                       /* tp_clear */
+    0,                       /* tp_richcompare */
+    0,                       /* tp_weaklistoffset */
+    0,                       /* tp_iter */
+    0,                       /* tp_iternext */
+    methods,                 /* tp_methods */
+    0,                       /* tp_members */
+    getset,                  /* tp_getset */
+    0,                       /* tp_base */
+    0,                       /* tp_dict */
+    0,                       /* tp_descr_get */
+    0,                       /* tp_descr_set */
+    0,                       /* tp_dictoffset */
+    (initproc)init,          /* tp_init */
+    0,                       /* tp_alloc */
+    (newfunc)new             /* tp_new */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Pygts functions */
+
+gboolean 
+pygts_segment_check(PyObject* o)
+{
+  if(! PyObject_TypeCheck(o, &PygtsSegmentType)) {
+    return FALSE;
+  }
+  else {
+#if PYGTS_DEBUG
+    return pygts_segment_is_ok(PYGTS_SEGMENT(o));
+#else
+    return TRUE;
+#endif
+  }
+}
+
+
+gboolean 
+pygts_segment_is_ok(PygtsSegment *s)
+{
+  if(!pygts_object_is_ok(PYGTS_OBJECT(s))) return FALSE;
+  return gts_segment_is_ok(PYGTS_SEGMENT_AS_GTS_SEGMENT(s));
+}
+
+
+PygtsSegment * 
+pygts_segment_new(GtsSegment *s)
+{
+  PyObject *args, *kwds;
+  PygtsObject *segment;
+
+  /* Check for Segment in the object table */
+  if( (segment=PYGTS_OBJECT(g_hash_table_lookup(obj_table,GTS_OBJECT(s))))
+      != NULL ) {
+    Py_INCREF(segment);
+    return PYGTS_FACE(segment);
+  }
+
+  /* Build a new Segment */
+  args = Py_BuildValue("OO",Py_None,Py_None);
+  kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_False);
+  segment = PYGTS_SEGMENT(PygtsSegmentType.tp_new(&PygtsSegmentType, 
+						  args, kwds));
+  Py_DECREF(args);
+  Py_DECREF(kwds);
+  if( segment == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"could not create Segment");
+    return NULL;
+  }
+  segment->gtsobj = GTS_OBJECT(s);
+
+  /* Register and return */
+  pygts_object_register(segment);
+  return PYGTS_SEGMENT(segment);
+}
+
+
+int 
+pygts_segment_compare(GtsSegment* s1,GtsSegment* s2)
+{
+  if( (pygts_point_compare(GTS_POINT(s1->v1),GTS_POINT(s2->v1))==0 &&
+       pygts_point_compare(GTS_POINT(s1->v2),GTS_POINT(s2->v2))==0) || 
+      (pygts_point_compare(GTS_POINT(s1->v1),GTS_POINT(s2->v2))==0 &&
+       pygts_point_compare(GTS_POINT(s1->v2),GTS_POINT(s2->v1))==0) ) {
+    return 0;
+  }
+  return -1;
+}

Added: trunk/lib/py/pygts-0.3.1/segment.h
===================================================================
--- trunk/lib/py/pygts-0.3.1/segment.h	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/segment.h	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,46 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PYGTS_SEGMENT_H__
+#define __PYGTS_SEGMENT_H__
+
+typedef struct _PygtsObject PygtsSegment;
+
+#define PYGTS_SEGMENT(obj) ((PygtsSegment*)obj)
+
+#define PYGTS_SEGMENT_AS_GTS_SEGMENT(o) (GTS_SEGMENT(PYGTS_OBJECT(o)->gtsobj))
+
+extern PyTypeObject PygtsSegmentType;
+
+gboolean pygts_segment_check(PyObject* o);
+gboolean pygts_segment_is_ok(PygtsSegment *t);
+
+PygtsSegment* pygts_segment_new(GtsSegment *f);
+
+int pygts_segment_compare(GtsSegment* s1,GtsSegment* s2);
+
+#endif /* __PYGTS_SEGMENT_H__ */

Added: trunk/lib/py/pygts-0.3.1/surface.c
===================================================================
--- trunk/lib/py/pygts-0.3.1/surface.c	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/surface.c	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,2255 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#include "pygts.h"
+
+#if PYGTS_DEBUG
+  #define SELF_CHECK if(!pygts_surface_check((PyObject*)self)) {      \
+                       PyErr_SetString(PyExc_RuntimeError,            \
+                       "problem with self object (internal error)");  \
+		       return NULL;                                   \
+                     }
+#else
+  #define SELF_CHECK
+#endif
+
+
+/*-------------------------------------------------------------------------*/
+/* Methods exported to python */
+
+static PyObject*
+is_ok(PygtsSurface *self, PyObject *args)
+{
+  if(pygts_surface_is_ok(self)) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+add(PygtsSurface *self, PyObject *args)
+{
+  PyObject *o_;
+  PygtsFace *f;
+  PygtsSurface *s;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &o_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if(pygts_face_check(o_)) {
+    f = PYGTS_FACE(o_);
+    gts_surface_add_face(PYGTS_SURFACE_AS_GTS_SURFACE(self),
+		       PYGTS_FACE_AS_GTS_FACE(f));
+
+  }
+  else if(pygts_surface_check(o_)) {
+    s = PYGTS_SURFACE(o_);
+
+    /* Make the call */
+    gts_surface_merge(PYGTS_SURFACE_AS_GTS_SURFACE(self),
+		      PYGTS_SURFACE_AS_GTS_SURFACE(s));
+
+  }
+  else {
+    PyErr_SetString(PyExc_TypeError,"expected a Face or a Surface");
+    return NULL;
+  }
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+static PyObject*
+pygts_remove(PygtsSurface *self, PyObject *args)
+{
+  PyObject *f_;
+  PygtsFace *f;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &f_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if(!pygts_face_check(f_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Face");
+    return NULL;
+  }
+  f = PYGTS_FACE(f_);
+
+  /* Make the call */
+  gts_surface_remove_face(PYGTS_SURFACE_AS_GTS_SURFACE(self),
+			  PYGTS_FACE_AS_GTS_FACE(f));
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+static PyObject*
+copy(PygtsSurface *self, PyObject *args)
+{
+  PyObject *s_;
+  PygtsSurface *s;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &s_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if(!pygts_surface_check(s_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Surface");
+    return NULL;
+  }
+  s = PYGTS_SURFACE(s_);
+
+  /* Make the call */
+  gts_surface_copy(PYGTS_SURFACE_AS_GTS_SURFACE(self),
+		   PYGTS_SURFACE_AS_GTS_SURFACE(s));
+
+  Py_INCREF((PyObject*)self);
+  return (PyObject*)self;
+}
+
+
+static PyObject*
+is_manifold(PygtsSurface *self, PyObject *args)
+{
+
+  SELF_CHECK
+
+  if( gts_surface_is_manifold(PYGTS_SURFACE_AS_GTS_SURFACE(self)) ) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+manifold_faces(PygtsSurface *self, PyObject *args)
+{
+  PyObject *e_;
+  PygtsEdge *e;
+  GtsFace *f1,*f2;
+  PygtsFace *face1,*face2;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &e_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if(!pygts_edge_check(e_)) {
+    PyErr_SetString(PyExc_TypeError,"expected an Edge");
+    return NULL;
+  }
+  e = PYGTS_EDGE(e_);
+
+  /* Make the call */
+  if(!gts_edge_manifold_faces(PYGTS_EDGE_AS_GTS_EDGE(e),
+			      PYGTS_SURFACE_AS_GTS_SURFACE(self),
+			      &f1, &f2)) {
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+
+  if( (face1 = pygts_face_new(f1)) == NULL ) {
+    return NULL;
+  }
+
+  if( (face2 = pygts_face_new(f2)) == NULL ) {
+    Py_DECREF(face1);
+    return NULL;
+  }
+
+  return Py_BuildValue("OO",face1,face2);
+}
+
+
+static PyObject*
+is_orientable(PygtsSurface *self, PyObject *args)
+{
+  SELF_CHECK
+
+  if(gts_surface_is_orientable(PYGTS_SURFACE_AS_GTS_SURFACE(self))) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+is_closed(PygtsSurface *self, PyObject *args)
+{
+  SELF_CHECK
+
+  if(gts_surface_is_closed(PYGTS_SURFACE_AS_GTS_SURFACE(self))) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+boundary(PyObject *self, PyObject *args)
+{
+  PyObject *tuple;
+  guint i,N;
+  GSList *edges=NULL,*e;
+  PygtsEdge *edge;
+
+  SELF_CHECK
+
+  /* Make the call */
+    if( (edges = gts_surface_boundary(PYGTS_SURFACE_AS_GTS_SURFACE(self))) 
+      == NULL ) {
+    PyErr_SetString(PyExc_RuntimeError,"could not retrieve edges");
+    return NULL;
+  }
+
+  /* Assemble the return tuple */
+  N = g_slist_length(edges);
+  if( (tuple=PyTuple_New(N)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"could not create tuple");
+    return NULL;
+  }
+  e = edges;
+  for(i=0;i<N;i++) {
+    if( (edge = pygts_edge_new(GTS_EDGE(e->data))) == NULL ) {
+      Py_DECREF(tuple);
+      g_slist_free(edges);
+    }
+    PyTuple_SET_ITEM(tuple,i,(PyObject*)edge);
+    e = g_slist_next(e);
+  }
+
+  g_slist_free(edges);
+
+  return tuple;
+}
+
+
+static PyObject*
+area(PygtsSurface *self, PyObject *args)
+{
+  GtsSurface *s;
+
+  SELF_CHECK
+
+  s = PYGTS_SURFACE_AS_GTS_SURFACE(self);
+  return Py_BuildValue("d",gts_surface_area(s));
+}
+
+
+static PyObject*
+volume(PygtsSurface *self, PyObject *args)
+{
+  GtsSurface *s;
+
+  SELF_CHECK
+
+  s = PYGTS_SURFACE_AS_GTS_SURFACE(self);
+
+  if(!gts_surface_is_closed(s)) {
+    PyErr_SetString(PyExc_RuntimeError,"Surface is not closed");
+    return NULL;
+  }
+
+  if(!gts_surface_is_orientable(s)) {
+    PyErr_SetString(PyExc_RuntimeError,"Surface is not orientable");
+    return NULL;
+  }
+
+  return Py_BuildValue("d",gts_surface_volume(s));
+}
+
+
+static PyObject*
+center_of_mass(PygtsSurface *self, PyObject *args)
+{
+  GtsSurface *s;
+  GtsVector cm;
+
+  SELF_CHECK
+
+  s = PYGTS_SURFACE_AS_GTS_SURFACE(self);
+  gts_surface_center_of_mass(s,cm);
+  return Py_BuildValue("ddd",cm[0],cm[1],cm[2]);
+}
+
+
+static PyObject*
+center_of_area(PygtsSurface *self, PyObject *args)
+{
+  GtsSurface *s;
+  GtsVector cm;
+
+  SELF_CHECK
+
+  s = PYGTS_SURFACE_AS_GTS_SURFACE(self);
+  gts_surface_center_of_area(s,cm);
+  return Py_BuildValue("ddd",cm[0],cm[1],cm[2]);
+}
+
+
+static PyObject*
+pygts_write(PygtsSurface *self, PyObject *args)
+{
+  PyObject *f_;
+  FILE *f;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &f_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if(!PyFile_Check(f_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a File");
+    return NULL;
+  }
+  f = PyFile_AsFile(f_);
+
+  /* Write to the file */
+  gts_surface_write(PYGTS_SURFACE_AS_GTS_SURFACE(self),f);
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+static PyObject*
+pygts_write_oogl(PygtsSurface *self, PyObject *args)
+{
+  PyObject *f_;
+  FILE *f;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &f_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if(!PyFile_Check(f_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a File");
+    return NULL;
+  }
+  f = PyFile_AsFile(f_);
+
+  /* Write to the file */
+  gts_surface_write_oogl(PYGTS_SURFACE_AS_GTS_SURFACE(self),f);
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+static PyObject*
+pygts_write_oogl_boundary(PygtsSurface *self, PyObject *args)
+{
+  PyObject *f_;
+  FILE *f;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &f_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if(!PyFile_Check(f_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a File");
+    return NULL;
+  }
+  f = PyFile_AsFile(f_);
+
+  /* Write to the file */
+  gts_surface_write_oogl_boundary(PYGTS_SURFACE_AS_GTS_SURFACE(self),f);
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+static PyObject*
+pygts_write_vtk(PygtsSurface *self, PyObject *args)
+{
+  PyObject *f_;
+  FILE *f;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &f_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if(!PyFile_Check(f_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a File");
+    return NULL;
+  }
+  f = PyFile_AsFile(f_);
+
+  /* Write to the file */
+  gts_surface_write_vtk(PYGTS_SURFACE_AS_GTS_SURFACE(self),f);
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+static PyObject*
+fan_oriented(PygtsSurface *self, PyObject *args)
+{
+  PyObject *v_;
+  PygtsVertex *v;
+  GSList *edges=NULL, *e;
+  guint i,N;
+  PyObject *tuple;
+  PygtsEdge *edge;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &v_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if(!pygts_vertex_check(v_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Vertex");
+    return NULL;
+  }
+  v = PYGTS_VERTEX(v_);
+
+  /* Check that the Surface is orientable; the calculation will
+   * fail otherwise.
+   */
+  if(!gts_surface_is_orientable(PYGTS_SURFACE_AS_GTS_SURFACE(self))) {
+    PyErr_SetString(PyExc_RuntimeError,"Surface must be orientable");
+    return NULL;
+  }
+
+  /* Make the call */
+  edges = gts_vertex_fan_oriented(PYGTS_VERTEX_AS_GTS_VERTEX(v),
+				  PYGTS_SURFACE_AS_GTS_SURFACE(self));
+
+  /* Build the return tuple */
+  N = g_slist_length(edges);
+  if( (tuple=PyTuple_New(N)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"Could not create tuple");
+    return NULL;
+  }
+  e = edges;
+  for(i=0;i<N;i++) {
+    if( (edge = pygts_edge_new(GTS_EDGE(e->data))) == NULL ) {
+      Py_DECREF(tuple);
+      g_slist_free(edges);
+      return NULL;
+    }
+    PyTuple_SET_ITEM(tuple,i,(PyObject*)edge);
+    e = g_slist_next(e);
+  }
+
+  return tuple;
+}
+
+
+static PyObject*
+split(PygtsSurface *self, PyObject *args)
+{
+  GSList *surfaces, *s;
+  PyObject *tuple;
+  PygtsSurface *surface;
+  guint n,N;
+
+  SELF_CHECK
+
+  surfaces = gts_surface_split(PYGTS_SURFACE_AS_GTS_SURFACE(self));
+  
+  /* Create a tuple to put the Surfaces into */
+  N = g_slist_length(surfaces);
+  if( (tuple=PyTuple_New(N)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"could not create tuple");
+    return NULL;
+  }
+
+  /* Put PygtsSurface objects into the tuple */
+  s = surfaces;
+  for(n=0;n<N;n++) {
+    if( (surface = pygts_surface_new(GTS_SURFACE(s->data))) == NULL ) {
+      Py_DECREF(tuple);
+      return NULL;
+    }
+    surface->traverse = NULL;
+    PyTuple_SET_ITEM(tuple, n, (PyObject*)surface);
+    s = g_slist_next(s);
+  }
+
+  return tuple;
+}
+
+
+/* Helper function for vertices() */
+static void get_vertex(GtsVertex *vertex, GtsVertex ***v)
+{
+  **v = vertex;
+  *v += 1;
+}
+
+static PyObject*
+vertices(PygtsSurface *self, PyObject *args)
+{
+  PyObject *tuple;
+  PygtsVertex *vertex;
+  PygtsVertex **vertices,**v;
+  guint i,N=0;
+
+  SELF_CHECK
+
+  /* Get the number of vertices */
+  N = gts_surface_vertex_number(PYGTS_SURFACE_AS_GTS_SURFACE(self));
+
+  /* Retrieve all of the vertex pointers into a temporary array */
+  if( (vertices = (PygtsVertex**)malloc(N*sizeof(PygtsVertex*))) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"could not create array");
+    return NULL;
+  }
+  v = vertices;
+
+  gts_surface_foreach_vertex(PYGTS_SURFACE_AS_GTS_SURFACE(self),
+			     (GtsFunc)get_vertex,&v);
+
+  /* Create a tuple to put the vertices into */
+  if( (tuple=PyTuple_New(N)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"could not create tuple");
+    return NULL;
+  }
+
+  /* Put PygtsVertex objects into the tuple */
+  v = vertices;
+  for(i=0;i<N;i++) {
+    if( (vertex = pygts_vertex_new(GTS_VERTEX(*v))) == NULL ) {
+      free(vertices);
+      Py_DECREF(tuple);
+      return NULL;
+    }
+    PyTuple_SET_ITEM(tuple, i, (PyObject*)vertex);    
+    v += 1;
+  }
+
+  free(vertices);
+  return tuple;
+}
+
+
+/* Helper function for edges() */
+static void get_edge(GtsEdge *edge, GSList **edges)
+{
+  *edges = g_slist_prepend(*edges,edge);
+}
+
+
+static PyObject*
+parent(PygtsSurface *self, PyObject *args)
+{
+  PyObject *e_;
+  PygtsEdge *e;
+  GtsFace *f;
+  PygtsFace *face;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &e_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if(!pygts_edge_check(e_)) {
+    PyErr_SetString(PyExc_TypeError,"expected an Edge");
+    return NULL;
+  }
+  e = PYGTS_EDGE(e_);
+
+  /* Make the call */
+  if( (f=gts_edge_has_parent_surface(PYGTS_EDGE_AS_GTS_EDGE(e),
+				     PYGTS_SURFACE_AS_GTS_SURFACE(self)))
+      == NULL ) {
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+
+  if( (face = pygts_face_new(f)) == NULL ) {
+    return NULL;
+  }
+
+  return (PyObject*)face;
+}
+
+
+static PyObject*
+edges(PyObject *self, PyObject *args)
+{
+  PyObject *tuple=NULL, *obj;
+  guint i,N;
+  GSList *edges=NULL,*vertices=NULL,*e;
+  PygtsEdge *edge;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "|O", &tuple) ) {
+    return NULL;
+  }
+
+  if(tuple) {
+    if(PyList_Check(tuple)) {
+      tuple = PyList_AsTuple(tuple);
+    }
+    else {
+      Py_INCREF(tuple);
+    }
+    if(!PyTuple_Check(tuple)) {
+      Py_DECREF(tuple);
+      PyErr_SetString(PyExc_TypeError,"expected a list or tuple of vertices");
+      return NULL;
+    }
+
+    /* Assemble the GSList */
+    N = PyTuple_Size(tuple);
+    for(i=0;i<N;i++) {
+      obj = PyTuple_GET_ITEM(tuple,i);
+      if(!pygts_vertex_check(obj)) {
+	Py_DECREF(tuple);
+	g_slist_free(vertices);
+	PyErr_SetString(PyExc_TypeError,"expected a list or tuple of vertices");
+	return NULL;
+      }
+      vertices=g_slist_prepend(vertices,PYGTS_VERTEX_AS_GTS_VERTEX(obj));
+    }
+    Py_DECREF(tuple);
+
+    /* Make the call */
+    if( (edges = gts_edges_from_vertices(vertices,
+		     PYGTS_SURFACE_AS_GTS_SURFACE(self))) == NULL ) {
+      PyErr_SetString(PyExc_RuntimeError,"could not retrieve edges");
+      return NULL;
+    }
+    g_slist_free(vertices);
+  }
+  else {
+    /* Get all of the edges */
+    gts_surface_foreach_edge(PYGTS_SURFACE_AS_GTS_SURFACE(self),
+			     (GtsFunc)get_edge,&edges);
+  }
+
+  /* Assemble the return tuple */
+  N = g_slist_length(edges);
+  if( (tuple=PyTuple_New(N)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"could not create tuple");
+    return NULL;
+  }
+  e = edges;
+  for(i=0;i<N;i++) {
+    if( (edge = pygts_edge_new(GTS_EDGE(e->data))) == NULL ) {
+      Py_DECREF(tuple);
+      g_slist_free(edges);
+      return NULL;
+    }
+    PyTuple_SET_ITEM(tuple,i,(PyObject*)edge);
+    e = g_slist_next(e);
+  }
+
+  g_slist_free(edges);
+
+  return tuple;
+}
+
+
+/* Helper function for edges() */
+static void get_face(GtsFace *face, GSList **faces)
+{
+  *faces = g_slist_prepend(*faces,face);
+}
+
+static PyObject*
+faces(PyObject *self, PyObject *args)
+{
+  PyObject *tuple=NULL, *obj;
+  guint i,N;
+  GSList *edges=NULL,*faces=NULL,*f;
+  PygtsFace *face;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "|O", &tuple) ) {
+    return NULL;
+  }
+
+  if(tuple) {
+    if(PyList_Check(tuple)) {
+      tuple = PyList_AsTuple(tuple);
+    }
+    else {
+      Py_INCREF(tuple);
+    }
+    if(!PyTuple_Check(tuple)) {
+      Py_DECREF(tuple);
+      PyErr_SetString(PyExc_TypeError,"expected a list or tuple of edges");
+      return NULL;
+    }
+
+    /* Assemble the GSList */
+    N = PyTuple_Size(tuple);
+    for(i=0;i<N;i++) {
+      obj = PyTuple_GET_ITEM(tuple,i);
+      if(!pygts_edge_check(obj)) {
+	Py_DECREF(tuple);
+	g_slist_free(edges);
+	PyErr_SetString(PyExc_TypeError,"expected a list or tuple of edges");
+	return NULL;
+      }
+      edges = g_slist_prepend(edges,PYGTS_EDGE_AS_GTS_EDGE(obj));
+    }
+    Py_DECREF(tuple);
+
+  /* Make the call */
+    if( (faces = gts_faces_from_edges(edges,PYGTS_SURFACE_AS_GTS_SURFACE(self)))
+	== NULL ) {
+      PyErr_SetString(PyExc_RuntimeError,"could not retrieve faces");
+      return NULL;
+    }
+    g_slist_free(edges);
+  }
+  else {
+    /* Get all of the edges */
+    gts_surface_foreach_face(PYGTS_SURFACE_AS_GTS_SURFACE(self),
+			     (GtsFunc)get_face,&faces);
+  }
+
+  /* Assemble the return tuple */
+  N = g_slist_length(faces);
+  if( (tuple=PyTuple_New(N)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"could not create tuple");
+    return NULL;
+  }
+
+  f = faces;
+  for(i=0;i<N;i++) {
+    if( (face = pygts_face_new(GTS_FACE(f->data))) == NULL ) {
+      Py_DECREF(tuple);
+      g_slist_free(faces);
+      return NULL;
+    }
+    PyTuple_SET_ITEM(tuple,i,(PyObject*)face);
+    f = g_slist_next(f);
+  }
+
+  g_slist_free(faces);
+
+  return tuple;
+}
+
+
+/* Helper for face_indices() */
+typedef struct {
+  PyObject *vertices,*indices;  /* Vertex and indices tuples */
+  guint Nv,Ni;                  /* Number of vertices and indices */
+  guint n;                      /* Current face index */
+  gboolean errflag;
+} IndicesData;
+
+/* Helper for face_indices() */
+static void get_indices(GtsFace *face, IndicesData *data)
+{
+  PyObject *t;
+  GtsVertex *v[3];
+  guint i,j;
+  gboolean flag;
+
+  if(data->errflag) return;
+
+  /* Put the vertex pointers in an array */
+  gts_triangle_vertices( GTS_TRIANGLE(face), &(v[0]), &(v[1]), &(v[2]) );
+
+  /* Create a tuple to put the indices into */
+  if( (t=PyTuple_New(3)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"could not create tuple");
+    data->errflag = TRUE;
+    return;
+  }
+  PyTuple_SET_ITEM(data->indices, data->n, t);
+
+  /* Determine the indices */
+  for(i=0;i<3;i++) {
+    flag = FALSE;
+    for(j=0;j<data->Nv;j++) {
+      if( PYGTS_VERTEX_AS_GTS_VERTEX(PyTuple_GET_ITEM(data->vertices,j))
+	  ==v[i] ) {
+	PyTuple_SET_ITEM(t, i, PyInt_FromLong(j));
+	flag = TRUE;
+	break;
+      }
+    }
+    if(!flag) {
+      PyErr_SetString(PyExc_RuntimeError,
+		      "Could not initialize tuple (internal error)");
+      data->errflag = TRUE;
+      return;
+    }
+  }
+  data->n += 1;
+}
+
+
+static PyObject*
+face_indices(PygtsSurface *self, PyObject *args)
+{
+  PyObject *vertices,*indices;
+  IndicesData data;
+  guint Nv,Nf;
+  guint i;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &vertices) )
+    return NULL;
+
+  /* Make sure that the tuple contains only vertices */
+  Nv = PyTuple_Size(vertices);
+  for(i=0;i<Nv;i++) {
+    if(!pygts_vertex_check(PyTuple_GetItem(vertices,i))){
+      PyErr_SetString(PyExc_TypeError,"Tuple has objects other than Vertices");
+      return NULL;
+    }
+  }
+
+  /* Get the number of faces in this surface */
+  Nf = gts_surface_face_number(PYGTS_SURFACE_AS_GTS_SURFACE(self));
+
+  /* Create a tuple to put the index tuples into */
+  if( (indices=PyTuple_New(Nf)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"could not create tuple");
+    return NULL;
+  }
+
+  /* Initialize the IndicesData struct.  This is used to maintain state as each 
+   * face is processed.
+   */
+  data.vertices = vertices;
+  data.indices = indices;
+  data.Nv = Nv;
+  data.Ni = Nf;
+  data.n = 0;
+  data.errflag = FALSE;
+
+  /* Process each face */
+  gts_surface_foreach_face(PYGTS_SURFACE_AS_GTS_SURFACE(self),
+			   (GtsFunc)get_indices,&data);
+  if(data.errflag) {
+    Py_DECREF(data.indices);
+    return NULL;
+  }
+
+  return indices;
+}
+
+
+static PyObject*
+distance(PygtsSurface *self, PyObject *args)
+{
+  PyObject *s_;
+  PygtsSurface *s;
+  gdouble delta=0.1;
+  GtsRange face_range, boundary_range;
+  PyObject *fr, *br;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O|d", &s_,&delta) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if(!pygts_surface_check(s_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Surface");
+    return NULL;
+  }
+  s = PYGTS_SURFACE(s_);
+
+  gts_surface_distance(PYGTS_SURFACE_AS_GTS_SURFACE(self),
+		       PYGTS_SURFACE_AS_GTS_SURFACE(s),
+		       delta, &face_range, &boundary_range);
+
+  /* Populate the fr (face range) dict */
+  if( (fr = PyDict_New()) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"cannot create dict");
+    return NULL;
+  }
+  PyDict_SetItemString(fr, "min", Py_BuildValue("d",face_range.min));
+  PyDict_SetItemString(fr, "max", Py_BuildValue("d",face_range.max));
+  PyDict_SetItemString(fr, "sum", Py_BuildValue("d",face_range.sum));
+  PyDict_SetItemString(fr, "sum2", Py_BuildValue("d",face_range.sum2));
+  PyDict_SetItemString(fr, "mean", Py_BuildValue("d",face_range.mean));
+  PyDict_SetItemString(fr, "stddev", Py_BuildValue("d",face_range.stddev));
+  PyDict_SetItemString(fr, "n", Py_BuildValue("i",face_range.n));
+
+  /* Populate the br (boundary range) dict */
+  if(gts_surface_boundary(PYGTS_SURFACE_AS_GTS_SURFACE(self))!=NULL) {
+    if( (br = PyDict_New()) == NULL ) {
+      PyErr_SetString(PyExc_MemoryError,"cannot create dict");
+      Py_DECREF(fr);
+      return NULL;
+    }
+    PyDict_SetItemString(br,"min",Py_BuildValue("d",boundary_range.min));
+    PyDict_SetItemString(br,"max",Py_BuildValue("d",boundary_range.max));
+    PyDict_SetItemString(br,"sum",Py_BuildValue("d",boundary_range.sum));
+    PyDict_SetItemString(br,"sum2",Py_BuildValue("d",boundary_range.sum2));
+    PyDict_SetItemString(br,"mean",Py_BuildValue("d",boundary_range.mean));
+    PyDict_SetItemString(br,"stddev",Py_BuildValue("d",boundary_range.stddev));
+    PyDict_SetItemString(br, "n",Py_BuildValue("i",boundary_range.n));
+
+    return Py_BuildValue("OO",fr,br);
+  }
+  else {
+    return Py_BuildValue("O",fr);
+  }
+}
+
+
+static PyObject*
+strip(PygtsSurface *self, PyObject *args)
+{
+  GSList *strips, *s, *f;
+  PyObject *tuple, **tuples;
+  guint i,j,n,N;
+  PygtsFace *face=NULL;
+
+  SELF_CHECK
+
+  strips = gts_surface_strip(PYGTS_SURFACE_AS_GTS_SURFACE(self));
+  
+  /* Create tuples to put the Faces into */
+  N = g_slist_length(strips);
+
+  if( (tuple=PyTuple_New(N)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"could not create tuple");
+    return NULL;
+  }
+  if( (tuples = (PyObject**)malloc(N*sizeof(PyObject*))) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"could not create array");
+    Py_DECREF(tuple);
+    return NULL;
+  }
+  s = strips;
+  for(i=0;i<N;i++) {
+    f = (GSList*)(s->data);
+    n = g_slist_length(f);
+    if( (tuples[i]=PyTuple_New(n)) == NULL) {
+      PyErr_SetString(PyExc_MemoryError,"could not create tuple");
+      Py_DECREF(tuple);
+      free(tuples);
+      return NULL;
+    }
+    PyTuple_SET_ITEM(tuple, i, tuples[i]);
+    s = g_slist_next(s);
+  }
+
+  /* Put PygtsFace objects into the tuple */
+  s = strips;
+  for(i=0;i<N;i++) {
+    f = (GSList*)(s->data);
+    n = g_slist_length(f);
+    for(j=0;j<n;j++) {
+      if( (face = pygts_face_new(GTS_FACE(f->data))) == NULL ) {
+      }
+      PyTuple_SET_ITEM(tuples[i], j, (PyObject*)face);
+      f = g_slist_next(f);
+    }
+    s = g_slist_next(s);
+  }
+
+  free(tuples);
+
+  return tuple;
+}
+
+
+static PyObject*
+stats(PygtsSurface *self, PyObject *args)
+{
+  GtsSurfaceStats stats;
+  PyObject *dict, *edges_per_vertex, *faces_per_edge;
+
+  SELF_CHECK
+
+  /* Make the call */
+  gts_surface_stats(PYGTS_SURFACE_AS_GTS_SURFACE(self),&stats);
+
+  /* Create the dictionaries */
+  if( (dict = PyDict_New()) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"cannot create dict");
+    return NULL;
+  }
+  if( (edges_per_vertex = PyDict_New()) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"cannot create dict");
+    Py_DECREF(dict);
+    return NULL;
+  }
+  if( (faces_per_edge = PyDict_New()) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"cannot create dict");
+    Py_DECREF(dict);
+    Py_DECREF(edges_per_vertex);
+    return NULL;
+  }
+
+  /* Populate the edges_per_vertex dict */
+  PyDict_SetItemString(edges_per_vertex,"min", 
+		       Py_BuildValue("d",stats.edges_per_vertex.min));
+  PyDict_SetItemString(edges_per_vertex,"max", 
+		       Py_BuildValue("d",stats.edges_per_vertex.max));
+  PyDict_SetItemString(edges_per_vertex,"sum", 
+		       Py_BuildValue("d",stats.edges_per_vertex.sum));
+  PyDict_SetItemString(edges_per_vertex,"sum2", 
+		       Py_BuildValue("d",stats.edges_per_vertex.sum2));
+  PyDict_SetItemString(edges_per_vertex,"mean", 
+		       Py_BuildValue("d",stats.edges_per_vertex.mean));
+  PyDict_SetItemString(edges_per_vertex,"stddev", 
+		       Py_BuildValue("d",stats.edges_per_vertex.stddev));
+  PyDict_SetItemString(edges_per_vertex,"n", 
+		       Py_BuildValue("i",stats.edges_per_vertex.n));
+
+  /* Populate the faces_per_edge dict */
+  PyDict_SetItemString(faces_per_edge,"min", 
+		       Py_BuildValue("d",stats.faces_per_edge.min));
+  PyDict_SetItemString(faces_per_edge,"max", 
+		       Py_BuildValue("d",stats.faces_per_edge.max));
+  PyDict_SetItemString(faces_per_edge,"sum", 
+		       Py_BuildValue("d",stats.faces_per_edge.sum));
+  PyDict_SetItemString(faces_per_edge,"sum2", 
+		       Py_BuildValue("d",stats.faces_per_edge.sum2));
+  PyDict_SetItemString(faces_per_edge,"mean", 
+		       Py_BuildValue("d",stats.faces_per_edge.mean));
+  PyDict_SetItemString(faces_per_edge,"stddev", 
+		       Py_BuildValue("d",stats.faces_per_edge.stddev));
+  PyDict_SetItemString(faces_per_edge,"n", 
+		       Py_BuildValue("i",stats.faces_per_edge.n));
+
+  /* Populate the main dict */
+  PyDict_SetItemString(dict,"n_faces", Py_BuildValue("i",stats.n_faces));
+  PyDict_SetItemString(dict,"n_incompatible_faces", 
+		       Py_BuildValue("i",stats.n_incompatible_faces));
+  PyDict_SetItemString(dict,"n_boundary_edges", 
+		       Py_BuildValue("i",stats.n_boundary_edges));
+  PyDict_SetItemString(dict,"n_non_manifold_edges", 
+		       Py_BuildValue("i",stats.n_non_manifold_edges));
+  PyDict_SetItemString(dict,"edges_per_vertex", edges_per_vertex);
+  PyDict_SetItemString(dict,"faces_per_edge", faces_per_edge);
+
+  return dict;
+}
+
+
+static PyObject*
+quality_stats(PygtsSurface *self, PyObject *args)
+{
+  GtsSurfaceQualityStats stats;
+  PyObject *dict, *face_quality, *face_area, *edge_length, *edge_angle;
+
+  SELF_CHECK
+
+  /* Make the call */
+  gts_surface_quality_stats(PYGTS_SURFACE_AS_GTS_SURFACE(self),&stats);
+
+  /* Create the dictionaries */
+  if( (dict = PyDict_New()) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"cannot create dict");
+    return NULL;
+  }
+  if( (face_quality = PyDict_New()) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"cannot create dict");
+    Py_DECREF(dict);
+    return NULL;
+  }
+  if( (face_area = PyDict_New()) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"cannot create dict");
+    Py_DECREF(dict);
+    Py_DECREF(face_quality);
+    return NULL;
+  }
+  if( (edge_length = PyDict_New()) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"cannot create dict");
+    Py_DECREF(dict);
+    Py_DECREF(face_quality);
+    Py_DECREF(face_area);
+    return NULL;
+  }
+  if( (edge_angle = PyDict_New()) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"cannot create dict");
+    Py_DECREF(dict);
+    Py_DECREF(face_quality);
+    Py_DECREF(face_area);
+    Py_DECREF(edge_length);
+    return NULL;
+  }
+
+  /* Populate the face_quality dict */
+  PyDict_SetItemString(face_quality,"min", 
+		       Py_BuildValue("d",stats.face_quality.min));
+  PyDict_SetItemString(face_quality,"max", 
+		       Py_BuildValue("d",stats.face_quality.max));
+  PyDict_SetItemString(face_quality,"sum", 
+		       Py_BuildValue("d",stats.face_quality.sum));
+  PyDict_SetItemString(face_quality,"sum2", 
+		       Py_BuildValue("d",stats.face_quality.sum2));
+  PyDict_SetItemString(face_quality,"mean", 
+		       Py_BuildValue("d",stats.face_quality.mean));
+  PyDict_SetItemString(face_quality,"stddev", 
+		       Py_BuildValue("d",stats.face_quality.stddev));
+  PyDict_SetItemString(face_quality,"n", 
+		       Py_BuildValue("i",stats.face_quality.n));
+
+  /* Populate the face_area dict */
+  PyDict_SetItemString(face_area,"min", 
+		       Py_BuildValue("d",stats.face_area.min));
+  PyDict_SetItemString(face_area,"max", 
+		       Py_BuildValue("d",stats.face_area.max));
+  PyDict_SetItemString(face_area,"sum", 
+		       Py_BuildValue("d",stats.face_area.sum));
+  PyDict_SetItemString(face_area,"sum2", 
+		       Py_BuildValue("d",stats.face_area.sum2));
+  PyDict_SetItemString(face_area,"mean", 
+		       Py_BuildValue("d",stats.face_area.mean));
+  PyDict_SetItemString(face_area,"stddev", 
+		       Py_BuildValue("d",stats.face_area.stddev));
+  PyDict_SetItemString(face_area,"n", 
+		       Py_BuildValue("i",stats.face_area.n));
+
+  /* Populate the edge_length dict */
+  PyDict_SetItemString(edge_length,"min", 
+		       Py_BuildValue("d",stats.edge_length.min));
+  PyDict_SetItemString(edge_length,"max", 
+		       Py_BuildValue("d",stats.edge_length.max));
+  PyDict_SetItemString(edge_length,"sum", 
+		       Py_BuildValue("d",stats.edge_length.sum));
+  PyDict_SetItemString(edge_length,"sum2", 
+		       Py_BuildValue("d",stats.edge_length.sum2));
+  PyDict_SetItemString(edge_length,"mean", 
+		       Py_BuildValue("d",stats.edge_length.mean));
+  PyDict_SetItemString(edge_length,"stddev", 
+		       Py_BuildValue("d",stats.edge_length.stddev));
+  PyDict_SetItemString(edge_length,"n", 
+		       Py_BuildValue("i",stats.edge_length.n));
+
+  /* Populate the edge_angle dict */
+  PyDict_SetItemString(edge_angle,"min", 
+		       Py_BuildValue("d",stats.edge_angle.min));
+  PyDict_SetItemString(edge_angle,"max", 
+		       Py_BuildValue("d",stats.edge_angle.max));
+  PyDict_SetItemString(edge_angle,"sum", 
+		       Py_BuildValue("d",stats.edge_angle.sum));
+  PyDict_SetItemString(edge_angle,"sum2", 
+		       Py_BuildValue("d",stats.edge_angle.sum2));
+  PyDict_SetItemString(edge_angle,"mean", 
+		       Py_BuildValue("d",stats.edge_angle.mean));
+  PyDict_SetItemString(edge_angle,"stddev", 
+		       Py_BuildValue("d",stats.edge_angle.stddev));
+  PyDict_SetItemString(edge_angle,"n", 
+		       Py_BuildValue("i",stats.edge_angle.n));
+
+  /* Populate the main dict */
+  PyDict_SetItemString(dict,"face_quality", face_quality);
+  PyDict_SetItemString(dict,"face_area", face_area);
+  PyDict_SetItemString(dict,"edge_length", edge_length);
+  PyDict_SetItemString(dict,"edge_angle", edge_angle);
+
+  return dict;
+}
+
+
+static PyObject*
+tessellate(PygtsSurface *self, PyObject *args)
+{
+  SELF_CHECK
+
+  gts_surface_tessellate(PYGTS_SURFACE_AS_GTS_SURFACE(self),NULL,NULL);
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+/* Helper function for inter() */
+void
+get_largest_coord(GtsVertex *v,gdouble *val) {
+  if( fabs(GTS_POINT(v)->x) > *val ) *val = fabs(GTS_POINT(v)->x);
+  if( fabs(GTS_POINT(v)->y) > *val ) *val = fabs(GTS_POINT(v)->y);
+  if( fabs(GTS_POINT(v)->z) > *val ) *val = fabs(GTS_POINT(v)->z);
+}
+
+/* Helper function for intersection operations */
+static PyObject*
+inter(PygtsSurface *self, PyObject *args, GtsBooleanOperation op1,
+      GtsBooleanOperation op2)
+{
+  PyObject *obj;
+  PyObject *s_;
+  PygtsSurface *s;
+  GtsSurface *surface;
+  GtsVector cm1, cm2;
+  gdouble area1, area2;
+  GtsSurfaceInter *si;
+  GNode *tree1, *tree2;
+  gboolean is_open1, is_open2, closed;
+  gdouble eps=0.;
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &s_) )
+    return NULL;
+
+  /* Convert to PygtsObjects */
+  if(!pygts_surface_check(s_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Surface");
+    return NULL;
+  }
+  s = PYGTS_SURFACE(s_);
+
+  /* Make sure that we don't have two pointers to the same surface */
+  if( self == s ) {
+      PyErr_SetString(PyExc_RuntimeError,
+		      "can't determine intersection with self");
+      return NULL;
+  }
+
+
+  /* *** ATTENTION ***
+   * Eliminate any active gts traverse objects.  They appear to interfere
+   * with the intersection calculation.  I would guess that this is due
+   * to the use of the "reserved" field (i.e., it doesn't get properly
+   * reset until the traverse is destroyed).
+   *
+   * I don't expect this to cause problems here, but a bug report should be
+   * filed.
+   */
+  if(self->traverse!=NULL) {
+    gts_surface_traverse_destroy(self->traverse);
+    self->traverse = NULL;
+  }
+  if(s->traverse!=NULL) {
+    gts_surface_traverse_destroy(s->traverse);
+    s->traverse = NULL;
+  }
+  /* *** ATTENTION *** */
+
+  /* Check for self-intersections in either surface */
+  if( gts_surface_is_self_intersecting(PYGTS_SURFACE_AS_GTS_SURFACE(self))
+      != NULL ) {
+    PyErr_SetString(PyExc_RuntimeError,"Surface is self-intersecting");
+    return NULL;
+  }
+  if( gts_surface_is_self_intersecting(PYGTS_SURFACE_AS_GTS_SURFACE(s))
+      != NULL ) {
+    PyErr_SetString(PyExc_RuntimeError,"Surface is self-intersecting");
+    return NULL;
+  }
+
+  /* Avoid complete self-intersection of two surfaces*/
+  if( (gts_surface_face_number(PYGTS_SURFACE_AS_GTS_SURFACE(self)) ==
+      gts_surface_face_number(PYGTS_SURFACE_AS_GTS_SURFACE(s))) &&
+      (gts_surface_edge_number(PYGTS_SURFACE_AS_GTS_SURFACE(self)) ==
+       gts_surface_edge_number(PYGTS_SURFACE_AS_GTS_SURFACE(s))) &&
+      (gts_surface_vertex_number(PYGTS_SURFACE_AS_GTS_SURFACE(self)) ==
+       gts_surface_vertex_number(PYGTS_SURFACE_AS_GTS_SURFACE(s))) &&
+      (gts_surface_area(PYGTS_SURFACE_AS_GTS_SURFACE(self)) ==
+       gts_surface_area(PYGTS_SURFACE_AS_GTS_SURFACE(s))) ) {
+
+    area1 = \
+      gts_surface_center_of_area(PYGTS_SURFACE_AS_GTS_SURFACE(self),cm1);
+
+    area2 = \
+      gts_surface_center_of_area(PYGTS_SURFACE_AS_GTS_SURFACE(s),cm2);
+
+    if( (area1==area2) && (cm1[0]==cm2[0]) && (cm1[1]==cm2[1]) && 
+	(cm1[2]==cm2[2]) ) {
+      PyErr_SetString(PyExc_RuntimeError,"Surfaces mutually intersect");
+      return NULL;
+    }
+  }
+
+  /* Get bounding boxes */
+  if( (tree1=gts_bb_tree_surface(PYGTS_SURFACE_AS_GTS_SURFACE(self)))
+      ==NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"could not create tree");
+    return NULL;
+  }
+  is_open1 = !gts_surface_is_closed(PYGTS_SURFACE_AS_GTS_SURFACE(self));
+  if( (tree2=gts_bb_tree_surface(PYGTS_SURFACE_AS_GTS_SURFACE(s)))
+      ==NULL ) {
+    gts_bb_tree_destroy(tree1, TRUE);
+    PyErr_SetString(PyExc_MemoryError,"could not create tree");
+    return NULL;
+  }
+  is_open2 = !gts_surface_is_closed(PYGTS_SURFACE_AS_GTS_SURFACE(s));
+
+  /* Get the surface intersection object */
+  if( (si = gts_surface_inter_new(gts_surface_inter_class(),
+				  PYGTS_SURFACE_AS_GTS_SURFACE(self),
+				  PYGTS_SURFACE_AS_GTS_SURFACE(s),
+				  tree1, tree2, is_open1, is_open2))==NULL) {
+    gts_bb_tree_destroy(tree1, TRUE);
+    gts_bb_tree_destroy(tree2, TRUE);
+    PyErr_SetString(PyExc_RuntimeError,"could not create GtsSurfaceInter");
+    return NULL;
+  }
+  gts_bb_tree_destroy(tree1, TRUE);
+  gts_bb_tree_destroy(tree2, TRUE);
+
+  /* Check that the surface intersection object is closed  */
+  gts_surface_inter_check(si,&closed);
+  if( closed == FALSE ) {
+    gts_object_destroy(GTS_OBJECT(si));
+    PyErr_SetString(PyExc_RuntimeError,"result is not closed");
+    return NULL;
+  }
+
+  /* Create the surface */
+  if( (surface = gts_surface_new(gts_surface_class(), gts_face_class(),
+				 gts_edge_class(), gts_vertex_class()))
+      == NULL )  {
+    PyErr_SetString(PyExc_MemoryError, "could not create Surface");
+    return NULL;
+  }
+
+  /* Calculate the new surface */
+  gts_surface_inter_boolean(si, surface ,op1);
+  gts_surface_inter_boolean(si, surface ,op2);
+  gts_object_destroy(GTS_OBJECT(si));
+
+  /* Clean up the result */
+  gts_surface_foreach_vertex(surface, (GtsFunc)get_largest_coord, &eps);
+  eps *= pow(2.,-50);
+  pygts_vertex_cleanup(surface,1.e-9);
+  pygts_edge_cleanup(surface);
+  pygts_face_cleanup(surface);
+
+  /* Check for self-intersection */
+  if( gts_surface_is_self_intersecting(surface) != NULL ) {
+    gts_object_destroy(GTS_OBJECT(surface));
+    PyErr_SetString(PyExc_RuntimeError,"result is self-intersecting surface");
+    return NULL;
+  }
+
+  /* Create the return Surface */
+  if( (obj = (PyObject*)pygts_surface_new(surface)) == NULL ) {
+    gts_object_destroy(GTS_OBJECT(surface));
+    return NULL;
+  }
+
+  return obj;
+}
+
+
+static PyObject*
+intersection(PygtsSurface *self, PyObject *args, GtsBooleanOperation op1,
+	     GtsBooleanOperation op2)
+{
+  SELF_CHECK
+  return inter(self,args,GTS_1_IN_2,GTS_2_IN_1);
+}
+
+
+static PyObject*
+pygts_union(PygtsSurface *self, PyObject *args)
+{
+  SELF_CHECK
+  return inter(self,args,GTS_1_OUT_2,GTS_2_OUT_1);
+}
+
+
+static PyObject*
+difference(PygtsSurface *self, PyObject *args)
+{
+  SELF_CHECK
+  return inter(self,args,GTS_1_OUT_2,GTS_2_IN_1);
+}
+
+
+/* Helper for rotate(), scale() and translate() transforms */
+typedef struct {
+  double dx, dy, dz, a;
+  gboolean errflag;
+} TransformData;
+
+/* Helper for rotate() */
+static void
+rotate_point(GtsPoint *p, TransformData *data)
+{
+  if(data->errflag) return;
+
+  if(pygts_point_rotate(p,data->dx,data->dy,data->dz,data->a)==-1)
+    data->errflag=TRUE;
+}
+
+static PyObject*
+rotate(PygtsSurface* self, PyObject *args, PyObject *keywds)
+{
+  TransformData data;
+  static char *kwlist[] = {"dx", "dy", "dz", "a", NULL};
+
+  SELF_CHECK
+
+  data.dx=0; data.dy=0; data.dz=0; data.a=0;
+  data.errflag = FALSE;
+
+  /* Parse the args */
+  if(! PyArg_ParseTupleAndKeywords(args, keywds,"|dddd", kwlist,
+				   &(data.dx), &(data.dy), &(data.dz), 
+				   &(data.a)) ) {
+    return NULL;
+  }
+
+  gts_surface_foreach_vertex(PYGTS_SURFACE_AS_GTS_SURFACE(self),
+			     (GtsFunc)rotate_point,&data);
+
+  if(data.errflag) return NULL;
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+/* Helper for scale() */
+static void
+scale_point(GtsPoint *p, TransformData *data)
+{
+  if(data->errflag) return;
+
+  if(pygts_point_scale(p,data->dx,data->dy,data->dz)==-1)
+    data->errflag=TRUE;
+}
+
+static PyObject*
+scale(PygtsSurface* self, PyObject *args, PyObject *keywds)
+{
+  TransformData data;
+  static char *kwlist[] = {"dx", "dy", "dz", NULL};
+
+  SELF_CHECK
+
+  data.dx=1; data.dy=1; data.dz=1; data.a=0;
+  data.errflag = FALSE;
+
+  /* Parse the args */
+  if(! PyArg_ParseTupleAndKeywords(args, keywds,"|ddd", kwlist,
+				   &(data.dx), &(data.dy), &(data.dz)) ) {
+    return NULL;
+  }
+
+  gts_surface_foreach_vertex(PYGTS_SURFACE_AS_GTS_SURFACE(self),
+			     (GtsFunc)scale_point,&data);
+
+  if(data.errflag) return NULL;
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+/* Helper for translate() */
+static void
+translate_point(GtsPoint *p, TransformData *data)
+{
+  if(data->errflag) return;
+
+  if(pygts_point_translate(p,data->dx,data->dy,data->dz)==-1)
+    data->errflag=TRUE;
+}
+
+static PyObject*
+translate(PygtsSurface* self, PyObject *args, PyObject *keywds)
+{
+  TransformData data;
+  static char *kwlist[] = {"dx", "dy", "dz", NULL};
+
+  SELF_CHECK
+
+  data.dx=0; data.dy=0; data.dz=0; data.a=0;
+  data.errflag = FALSE;
+
+  /* Parse the args */
+  if(! PyArg_ParseTupleAndKeywords(args, keywds,"|ddd", kwlist,
+				   &(data.dx), &(data.dy), &(data.dz)) ) {
+    return NULL;
+  }
+
+  /* Make the call */
+  gts_surface_foreach_vertex(PYGTS_SURFACE_AS_GTS_SURFACE(self),
+			     (GtsFunc)translate_point,&data);
+
+  if(data.errflag) return NULL;
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+static PyObject*
+is_self_intersecting(PygtsSurface *self, PyObject *args)
+{
+  GtsSurface *s;
+  gboolean ret = FALSE;
+
+  SELF_CHECK
+
+  if( (s=gts_surface_is_self_intersecting(PYGTS_SURFACE_AS_GTS_SURFACE(self)))
+      != NULL) {
+    gts_object_destroy(GTS_OBJECT(s));
+    ret = TRUE;
+  }
+
+  if(ret) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }  
+}
+
+
+static PyObject*
+cleanup(PygtsSurface *self, PyObject *args)
+{
+  GtsSurface *s;
+  gdouble threshold = 0.;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args,"|d", &threshold) ) {
+    return NULL;
+  }
+
+  s = PYGTS_SURFACE_AS_GTS_SURFACE(self);
+
+  /* Do the cleanup */
+  if( threshold != 0. ) {
+    pygts_vertex_cleanup(s,threshold);
+  }
+  pygts_edge_cleanup(s);
+  pygts_face_cleanup(s);
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+static PyObject*
+coarsen(PygtsSurface *self, PyObject *args)
+{
+  guint n;
+  gdouble amin=0.;
+  GtsVolumeOptimizedParams params = {0.5,0.5,1.e-10};
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args,"i|d", &n, &amin) ) {
+    return NULL;
+  }
+
+  /* Make the call */
+  gts_surface_coarsen(PYGTS_SURFACE_AS_GTS_SURFACE(self),
+		      (GtsKeyFunc)gts_volume_optimized_cost, &params,
+		      (GtsCoarsenFunc)gts_volume_optimized_vertex, &params,
+		      (GtsStopFunc)gts_coarsen_stop_number, &n, amin);
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+/* Methods table */
+static PyMethodDef methods[] = {
+  {"is_ok", (PyCFunction)is_ok,
+   METH_NOARGS,
+   "True if this Surface s is OK.  False otherwise.\n"
+   "\n"
+   "Signature: s.is_ok()\n"
+  },
+
+  {"add", (PyCFunction)add,
+   METH_VARARGS,
+   "Adds a Face f or Surface s2 to Surface s1.\n"
+   "\n"
+   "Signature: s1.add(f) or s2.add(f)\n"
+  },
+
+  {"remove", (PyCFunction)pygts_remove,
+   METH_VARARGS,
+   "Removes Face f from this Surface s.\n"
+   "\n"
+   "Signature: s.remove(f)\n"
+  },
+
+  {"copy", (PyCFunction)copy,
+   METH_VARARGS,
+   "Copys all Faces, Edges and Vertices of Surface s2 to Surface s1.\n"
+   "\n"
+   "Signature: s1.copy(s2)\n"
+   "\n"
+   "Returns s1.\n"
+  },
+
+  {"is_manifold", (PyCFunction)is_manifold,
+   METH_NOARGS,
+   "True if Surface s is a manifold, False otherwise.\n"
+   "\n"
+   "Signature: s.is_manifold()\n"
+  },
+
+  {"manifold_faces", (PyCFunction)manifold_faces,
+   METH_VARARGS,
+   "Returns the 2 manifold Faces of Edge e on this Surface s\n"
+   "if they exist, or None.\n"
+   "\n"
+   "Signature: s.manifold_faces(e)\n"
+  },
+
+  {"is_orientable", (PyCFunction)is_orientable,
+   METH_NOARGS,
+   "True if Faces in Surface s have compatible orientation,\n"
+   "False otherwise.\n"
+   "Note that a closed surface is also a manifold.  Note that an\n"
+   "orientable surface is also a manifold.\n"
+   "\n"
+   "Signature: s.is_orientable()\n"
+  },
+
+  {"is_closed", (PyCFunction)is_closed,
+   METH_NOARGS,
+   "True if Surface s is closed, False otherwise.\n"
+   "Note that a closed Surface is also a manifold.\n"
+   "\n"
+   "Signature: s.is_closed()\n"
+  },
+
+  {"boundary", (PyCFunction)boundary,
+   METH_NOARGS,
+   "Returns a tuple of boundary Edges of Surface s.\n"
+   "\n"
+   "Signature: s.boundary()\n"
+  },
+
+  {"area", (PyCFunction)area,
+   METH_NOARGS,
+   "Returns the area of Surface s.\n"
+   "The area is taken as the sum of the signed areas of the Faces of s.\n"
+   "\n"
+   "Signature: s.area()\n"
+  },
+
+  {"volume", (PyCFunction)volume,
+   METH_NOARGS,
+   "Returns the signed volume of the domain bounded by the Surface s.\n"
+   "\n"
+   "Signature: s.volume()\n"
+  },
+
+  {"center_of_mass", (PyCFunction)center_of_mass,
+   METH_NOARGS,
+   "Returns the coordinates of the center of mass of Surface s.\n"
+   "\n"
+   "Signature: s.center_of_mass()\n"
+  },
+
+  {"center_of_area", (PyCFunction)center_of_area,
+   METH_NOARGS,
+   "Returns the coordinates of the center of area of Surface s.\n"
+   "\n"
+   "Signature: s.center_of_area()\n"
+  },
+
+  {"write", (PyCFunction)pygts_write,
+   METH_VARARGS,
+   "Saves Surface s to File f in GTS ascii format.\n"
+   "All the lines beginning with #! are ignored.\n"
+   "\n"
+   "Signature: s.write(f)\n"
+  },
+
+  {"write_oogl", (PyCFunction)pygts_write_oogl,
+   METH_VARARGS,
+   "Saves Surface s to File f in OOGL (Geomview) format.\n"
+   "\n"
+   "Signature: s.write_oogl(f)\n"
+  },
+
+  {"write_oogl_boundary", (PyCFunction)pygts_write_oogl_boundary,
+   METH_VARARGS,
+   "Saves boundary of Surface s to File f in OOGL (Geomview) format.\n"
+   "\n"
+   "Signature: s.write_oogl_boundary(f)\n"
+  },
+
+  {"write_vtk", (PyCFunction)pygts_write_vtk,
+   METH_VARARGS,
+   "Saves Surface s to File f in VTK format.\n"
+   "\n"
+   "Signature: s.write_vtk(f)\n"
+  },
+
+  {"fan_oriented", (PyCFunction)fan_oriented,
+   METH_VARARGS,
+   "Returns a tuple of outside Edges of the Faces fanning from\n"
+   "Vertex v on this Surface s.  The Edges are given in \n"
+   "counter-clockwise order.\n"
+   "\n"
+   "Signature: s.fan_oriented(v)\n"
+  },
+
+  {"split", (PyCFunction)split,
+   METH_NOARGS,
+   "Splits a surface into a tuple of connected and manifold components.\n"
+   "\n"
+   "Signature: s.split()\n"
+  },
+
+  {"distance", (PyCFunction)distance,
+   METH_VARARGS,
+   "Calculates the distance between the faces of this Surface s1 and\n"
+   "the nearest Faces of other s2, and (if applicable) the distance\n"
+   "between the boundary of this Surface s1 and the nearest boundary\n"
+   "Edges of other s2.\n"
+   "\n"
+   "One or two dictionaries are returned (where applicable), the first\n"
+   "for the face range and the second for the boundary range.  The\n"
+   "fields in each dictionary describe statistical results for each\n"
+   "population: {min,max,sum,sum2,mean,stddev,n}.\n"
+   "\n"
+   "Signature: s1.distance(s2) or s1.distance(s2,delta)\n"
+   "\n"
+   "The value delta is a spatial increment defined as the percentage\n"
+   "of the diagonal of the bounding box of s2 (default 0.1).\n"
+  },
+
+  {"strip", (PyCFunction)strip,
+   METH_NOARGS,
+   "Returns a tuple of strips, where each strip is a tuple of Faces\n"
+   "that are successive and have one edge in common.\n"
+   "\n"
+   "Signature: s.split()\n"
+  },
+
+  {"stats", (PyCFunction)stats,
+   METH_NOARGS,
+   "Returns statistics for this Surface f in a dict.\n"
+   "The stats include n_faces, n_incompatible_faces,, n_boundary_edges,\n"
+   "n_non_manifold_edges, and the statisics {min, max, sum, sum2, mean,\n"
+   "stddev, and n} for populations of edges_per_vertex and\n"
+   "faces_per_edge.  Each of these names are dictionary keys.\n"
+   "\n"
+   "Signature: s.stats()\n"
+  },
+
+  {"quality_stats", (PyCFunction)quality_stats,
+   METH_NOARGS,
+   "Returns quality statistics for this Surface f in a dict.\n"
+   "The statistics include the {min, max, sum, sum2, mean, stddev,\n"
+   "and n} for populations of face_quality, face_area, edge_length,\n"
+   "and edge_angle.  Each of these names are dictionary keys.\n"
+   "See Triangle.quality() for an explanation of the face_quality.\n"
+   "\n"
+   "Signature: s.quality_stats()\n"
+  },
+
+  {"tessellate", (PyCFunction)tessellate,
+   METH_NOARGS,
+   "Tessellate each face of this Surface s with 4 triangles.\n"
+   "The number of triangles is increased by a factor of 4.\n"
+   "\n"
+   "Signature: s.tessellate()\n"
+  },
+
+  {"vertices", (PyCFunction)vertices,
+   METH_NOARGS,
+   "Returns a tuple containing the vertices of Surface s.\n"
+   "\n"
+   "Signature: s.vertices()\n"
+  },
+
+  {"parent", (PyCFunction)parent,
+   METH_VARARGS,
+   "Returns Face on this Surface s that has Edge e, or None\n"
+   "if the Edge is not on this Surface.\n"
+   "\n"
+   "Signature: s.parent(e)\n"
+  },
+
+  {"edges", (PyCFunction)edges,
+   METH_VARARGS,
+   "Returns tuple of Edges on Surface s that have Vertex in list.\n"
+   "If a list is not given then all of the Edges are returned.\n"
+   "\n"
+   "Signature: s.edges(list) or s.edges()\n"
+  },
+
+  {"faces", (PyCFunction)faces,
+   METH_VARARGS,
+   "Returns tuple of Faces on Surface s that have Edge in list.\n"
+   "If a list is not given then all of the Faces are returned.\n"
+   "\n"
+   "Signature: s.faces(list) s.faces()\n"
+  },
+
+  {"face_indices", (PyCFunction)face_indices,
+   METH_VARARGS,
+   "Returns a tuple of 3-tuples containing Vertex indices for each Face\n"
+   "in Surface s.  The index for each Vertex in a face corresponds to\n"
+   "where it is found in the Vertex tuple vs.\n"
+   "\n"
+   "Signature: s.face_indices(vs)\n"
+  },
+
+  {"intersection", (PyCFunction)intersection,
+   METH_VARARGS,
+   "Returns the intersection of this Surface s1 with Surface s2.\n"
+   "\n"
+   "Signature: s1.intersection(s2)\n"
+  },
+
+  {"union", (PyCFunction)pygts_union,
+   METH_VARARGS,
+   "Returns the union of this Surface s1 with Surface s2.\n"
+   "\n"
+   "Signature: s1.union(s2)\n"
+  },
+
+  {"difference", (PyCFunction)difference,
+   METH_VARARGS,
+   "Returns the difference of this Surface s1 with Surface s2.\n"
+   "\n"
+   "Signature: s1.difference(s2)\n"
+  },
+
+  {"rotate", (PyCFunction)rotate,
+   METH_VARARGS | METH_KEYWORDS,
+   "Rotates Surface s about vector dx,dy,dz and angle a.\n"
+   "The sense of the rotation is given by the right-hand-rule.\n"
+   "\n"
+   "Signature: s.rotate(dx,dy,dz,a)\n"
+  },
+
+  {"scale", (PyCFunction)scale,
+   METH_VARARGS | METH_KEYWORDS,
+   "Scales Surface s by vector dx,dy,dz.\n"
+   "\n"
+   "Signature: s.scale(dx=1,dy=1,dz=1)\n"
+  },
+
+  {"translate", (PyCFunction)translate,
+   METH_VARARGS | METH_KEYWORDS,
+   "Translates Surface s by vector dx,dy,dz.\n"
+   "\n"
+   "Signature: s.translate(dx=0,dy=0,dz=0)\n"
+  },
+
+  {"is_self_intersecting", (PyCFunction)is_self_intersecting,
+   METH_NOARGS,
+   "Returns True if this Surface s is self-intersecting.\n"
+   "False otherwise.\n"
+   "\n"
+   "Signature: s.is_self_intersecting()\n"
+  },
+
+  {"cleanup", (PyCFunction)cleanup,
+   METH_VARARGS,
+   "Cleans up the Vertices, Edges, and Faces on a Surface s.\n"
+   "\n"
+   "Signature: s.cleanup() or s.cleanup(threhold)\n"
+   "\n"
+   "If threhold is given, then Vertices that are spaced less than\n"
+   "the threshold are merged.  Degenerate Edges and Faces are also\n"
+   "removed.\n"
+  },
+
+  {"coarsen", (PyCFunction)coarsen,
+   METH_VARARGS,
+   "Reduces the number of vertices on Surface s.\n"
+   "\n"
+   "Signature: s.coarsen(n) and s.coarsen(amin)\n"
+   "\n"
+   "n is the smallest number of desired edges (but you may get fewer).\n"
+   "amin is the smallest angle between Faces.\n"
+  },
+
+  {NULL}  /* Sentinel */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Attributes exported to python */
+
+static PyObject *
+get_Nvertices(PygtsSurface *self, void *closure)
+{
+  SELF_CHECK
+  return Py_BuildValue("i",
+      gts_surface_vertex_number(PYGTS_SURFACE_AS_GTS_SURFACE(self)));
+
+}
+
+
+static PyObject *
+get_Nedges(PygtsSurface *self, void *closure)
+{
+  SELF_CHECK
+  return Py_BuildValue("i",
+      gts_surface_edge_number(PYGTS_SURFACE_AS_GTS_SURFACE(self)));
+}
+
+
+static PyObject *
+get_Nfaces(PygtsSurface *self, void *closure)
+{
+  SELF_CHECK
+  return Py_BuildValue("i",
+      gts_surface_face_number(PYGTS_SURFACE_AS_GTS_SURFACE(self)));
+}
+
+/* Methods table */
+static PyGetSetDef getset[] = {
+  { "Nvertices", (getter)get_Nvertices, NULL, 
+    "The number of unique vertices", NULL
+  },
+
+  { "Nedges", (getter)get_Nedges, NULL, 
+    "The number of unique edges", NULL
+  },
+
+  { "Nfaces", (getter)get_Nfaces, NULL, 
+    "The number of unique faces", NULL
+  },
+
+  {NULL}  /* Sentinel */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Python type methods */
+
+static void
+dealloc(PygtsSurface* self)
+{
+  if(self->traverse!=NULL) {
+    gts_surface_traverse_destroy(self->traverse);
+  }
+  self->traverse = NULL;
+
+  /* Chain up */
+  PygtsObjectType.tp_dealloc((PyObject*)self);
+}
+
+
+static PyObject *
+new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+  PyObject *o;
+  PygtsObject *obj;
+  guint alloc_gtsobj = TRUE;
+
+  /* Parse the args */
+  if(kwds) {
+    o = PyDict_GetItemString(kwds,"alloc_gtsobj");
+    if(o==Py_False) {
+      alloc_gtsobj = FALSE;
+    }
+    if(o!=NULL) {
+      PyDict_DelItemString(kwds, "alloc_gtsobj");
+    }
+  }
+  if(kwds) {
+    Py_INCREF(Py_False);
+    PyDict_SetItemString(kwds,"alloc_gtsobj", Py_False);
+  }
+
+  /* Chain up */
+  obj = PYGTS_OBJECT(PygtsObjectType.tp_new(type,args,kwds));
+
+  PYGTS_SURFACE(obj)->traverse = NULL;
+
+  /* Allocate the gtsobj (if needed) */
+  if( alloc_gtsobj ) {
+    obj->gtsobj = GTS_OBJECT(gts_surface_new(gts_surface_class(),
+					     gts_face_class(),
+					     gts_edge_class(),
+					     gts_vertex_class()));
+
+    if( obj->gtsobj == NULL )  {
+      PyErr_SetString(PyExc_MemoryError, "could not create Surface");
+      return NULL;
+    }
+
+    pygts_object_register(obj);
+  }
+
+  return (PyObject*)obj;
+}
+
+
+static int
+init(PygtsSurface *self, PyObject *args, PyObject *kwds)
+{
+  gint ret;
+
+  if( (ret = PygtsObjectType.tp_init((PyObject*)self,args,kwds)) != 0 ) {
+    return ret;
+  }
+
+  return 0;
+}
+
+
+/* Helper function for iter */
+static void 
+get_f0(GtsFace *f,GtsFace **f0)
+{
+  if(*f0==NULL) *f0 = f;
+}
+
+PyObject* 
+iter(PygtsSurface *self)
+{
+  GtsFace* f0=NULL;
+
+  SELF_CHECK
+
+  if(self->traverse!=NULL) {
+    gts_surface_traverse_destroy(self->traverse);
+    self->traverse = NULL;
+  }
+
+  /* Assign a "first" face */
+  gts_surface_foreach_face(PYGTS_SURFACE_AS_GTS_SURFACE(self),
+			   (GtsFunc)get_f0,&f0);
+  if(f0==NULL) {
+    PyErr_SetString(PyExc_RuntimeError, "No faces to traverse");
+    return NULL;
+  }
+
+  if( (self->traverse=gts_surface_traverse_new(
+           PYGTS_SURFACE_AS_GTS_SURFACE(self),f0)) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError, "could not create Traverse");
+    return NULL;
+  }
+
+  Py_INCREF((PyObject*)self);
+  return (PyObject*)self;
+}
+
+
+PyObject* 
+iternext(PygtsSurface *self)
+{
+  PygtsFace *face;
+  GtsFace *f;
+
+  SELF_CHECK
+
+  if( self->traverse == NULL ) {
+    PyErr_SetString(PyExc_RuntimeError, "iterator not initialized");
+    return NULL;
+  }
+
+  /* Get the next face */
+  if( (f = gts_surface_traverse_next(self->traverse,NULL)) == NULL ) {
+    gts_surface_traverse_destroy(self->traverse);
+    self->traverse = NULL;
+    PyErr_SetString(PyExc_StopIteration, "No more faces");
+    return NULL;
+  }
+
+  if( (face = pygts_face_new(f)) == NULL ) {
+    return NULL;
+  }
+
+  return (PyObject*)face;
+}
+
+
+/* Methods table */
+PyTypeObject PygtsSurfaceType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                       /* ob_size */
+    "gts.Surface",           /* tp_name */
+    sizeof(PygtsSurface),    /* tp_basicsize */
+    0,                       /* tp_itemsize */
+    (destructor)dealloc,     /* tp_dealloc */
+    0,                       /* tp_print */
+    0,                       /* tp_getattr */
+    0,                       /* tp_setattr */
+    0,                       /* tp_compare */
+    0,                       /* tp_repr */
+    0,                       /* tp_as_number */
+    0,                       /* tp_as_sequence */
+    0,                       /* tp_as_mapping */
+    0,                       /* tp_hash */
+    0,                       /* tp_call */
+    0,                       /* tp_str */
+    0,                       /* tp_getattro */
+    0,                       /* tp_setattro */
+    0,                       /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT |
+      Py_TPFLAGS_BASETYPE |
+      Py_TPFLAGS_HAVE_ITER,  /* tp_flags */
+    "Surface object",        /* tp_doc */
+    0,                       /* tp_traverse */
+    0,                       /* tp_clear */
+    0,                       /* tp_richcompare */
+    0,                       /* tp_weaklistoffset */
+    (getiterfunc)iter,       /* tp_iter */
+    (iternextfunc)iternext,  /* tp_iternext */
+    methods,                 /* tp_methods */
+    0,                       /* tp_members */
+    getset,                  /* tp_getset */
+    0,                       /* tp_base */
+    0,                       /* tp_dict */
+    0,                       /* tp_descr_get */
+    0,                       /* tp_descr_set */
+    0,                       /* tp_dictoffset */
+    (initproc)init,          /* tp_init */
+    0,                       /* tp_alloc */
+    (newfunc)new             /* tp_new */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Pygts functions */
+
+gboolean 
+pygts_surface_check(PyObject* o)
+{
+  if(! PyObject_TypeCheck(o, &PygtsSurfaceType)) {
+    return FALSE;
+  }
+  else {
+#if PYGTS_DEBUG
+    return pygts_surface_is_ok(PYGTS_SURFACE(o));
+#else
+    return TRUE;
+#endif
+  }
+}
+
+
+/* Helper function */
+static void 
+face_is_ok(GtsFace *f,gboolean *ret)
+{
+  if( !pygts_gts_triangle_is_ok(GTS_TRIANGLE(f)) ) {
+    *ret = FALSE;
+  }
+}
+
+
+gboolean 
+pygts_surface_is_ok(PygtsSurface *s)
+{
+  PygtsObject *obj;
+  gboolean ret=TRUE;
+
+  obj = PYGTS_OBJECT(s);
+
+  if(!pygts_object_is_ok(PYGTS_OBJECT(s))) return FALSE;
+  g_return_val_if_fail(obj->gtsobj_parent==NULL,FALSE);
+
+  /* Check all of the faces this surface contains */
+  gts_surface_foreach_face(GTS_SURFACE(obj->gtsobj),(GtsFunc)face_is_ok,&ret);
+  if( ret == FALSE ) return FALSE;
+
+  return TRUE;
+}
+
+
+PygtsSurface *
+pygts_surface_new(GtsSurface *s) {
+  PyObject *args, *kwds;
+  PygtsObject *surface;
+
+  /* Check for Surface in the object table */
+  if( (surface = PYGTS_OBJECT(g_hash_table_lookup(obj_table,GTS_OBJECT(s)))) 
+      !=NULL ) {
+    Py_INCREF(surface);
+    return PYGTS_SURFACE(surface);
+  }
+
+  /* Build a new Surface */
+  args = Py_BuildValue("()");
+  kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_False);
+  surface = PYGTS_OBJECT(PygtsSurfaceType.tp_new(&PygtsSurfaceType,args,kwds));
+  Py_DECREF(args);
+  Py_DECREF(kwds);
+  if( surface == NULL ) {
+    PyErr_SetString(PyExc_MemoryError, "could not create Surface");
+    return NULL;
+  }
+  surface->gtsobj = GTS_OBJECT(s);
+
+  /* Register and return */
+  pygts_object_register(surface);
+  return PYGTS_SURFACE(surface);
+}

Added: trunk/lib/py/pygts-0.3.1/surface.h
===================================================================
--- trunk/lib/py/pygts-0.3.1/surface.h	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/surface.h	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,48 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PYGTS_SURFACE_H__
+#define __PYGTS_SURFACE_H__
+
+typedef struct _PygtsSurface PygtsSurface;
+
+#define PYGTS_SURFACE(o) ((PygtsSurface*)o)
+
+#define PYGTS_SURFACE_AS_GTS_SURFACE(o) (GTS_SURFACE(PYGTS_OBJECT(o)->gtsobj))
+
+struct _PygtsSurface {
+  PygtsObject o;
+  GtsSurfaceTraverse* traverse;
+};
+
+extern PyTypeObject PygtsSurfaceType;
+
+gboolean pygts_surface_check(PyObject* o);
+gboolean pygts_surface_is_ok(PygtsSurface *s);
+PygtsSurface* pygts_surface_new(GtsSurface *s);
+
+#endif /* __PYGTS_SURFACE_H__ */

Added: trunk/lib/py/pygts-0.3.1/triangle.c
===================================================================
--- trunk/lib/py/pygts-0.3.1/triangle.c	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/triangle.c	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,1049 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#include "pygts.h"
+
+
+#if PYGTS_DEBUG
+  #define SELF_CHECK if(!pygts_triangle_check((PyObject*)self)) {         \
+                       PyErr_SetString(PyExc_RuntimeError,            \
+                       "problem with self object (internal error)");  \
+		       return NULL;                                   \
+                     }
+#else
+  #define SELF_CHECK
+#endif
+
+
+/*-------------------------------------------------------------------------*/
+/* Methods exported to python */
+
+static PyObject*
+is_ok(PygtsTriangle *self, PyObject *args)
+{
+  if(pygts_triangle_is_ok(self)) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+area(PygtsTriangle *self, PyObject *args)
+{
+  SELF_CHECK
+
+  return Py_BuildValue("d",
+      gts_triangle_area(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self)));
+}
+
+
+static PyObject*
+perimeter(PygtsTriangle *self, PyObject *args)
+{
+  SELF_CHECK
+
+  return Py_BuildValue("d",
+      gts_triangle_perimeter(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self)));
+}
+
+
+static PyObject*
+quality(PygtsTriangle *self, PyObject *args)
+{
+  SELF_CHECK
+
+  return Py_BuildValue("d",
+      gts_triangle_quality(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self)));
+}
+
+
+static PyObject*
+normal(PygtsTriangle *self, PyObject *args)
+{
+  gdouble x,y,z;
+
+  SELF_CHECK
+
+  gts_triangle_normal(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self),&x,&y,&z);
+  return Py_BuildValue("ddd",x,y,z);
+}
+
+
+static PyObject*
+revert(PygtsTriangle *self, PyObject *args)
+{
+  SELF_CHECK
+
+  gts_triangle_revert(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self));
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+static PyObject*
+orientation(PygtsTriangle *self, PyObject *args)
+{
+  SELF_CHECK
+
+  return Py_BuildValue("d",
+      gts_triangle_orientation(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self)));
+}
+
+
+static PyObject*
+angle(PygtsTriangle* self, PyObject *args)
+{
+  PyObject *t_;
+  PygtsTriangle *t;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "O", &t_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_triangle_check(t_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Triangle");
+    return NULL;
+  }
+  t = PYGTS_TRIANGLE(t_);
+
+  return Py_BuildValue("d",
+      gts_triangles_angle(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self),
+			  PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t)));
+}
+
+
+static PyObject*
+is_compatible(PygtsTriangle *self, PyObject *args)
+{
+  PyObject *t2_;
+  PygtsTriangle *t2;
+  GtsEdge *e;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &t2_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_triangle_check(t2_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Triangle");
+    return NULL;
+  }
+  t2 = PYGTS_TRIANGLE(t2_);
+
+  /* Get the common edge */
+  if( (e = gts_triangles_common_edge(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self),
+				     PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t2)))
+      == NULL ) {
+    PyErr_SetString(PyExc_RuntimeError,"Triangles do not share common edge");
+    return NULL;
+  }
+
+  if( gts_triangles_are_compatible(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self),
+				   PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t2),e) ) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+common_edge(PygtsTriangle *self, PyObject *args)
+{
+  PyObject *t2_;
+  PygtsTriangle *t2;
+  GtsEdge *e;
+  PygtsEdge *edge;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &t2_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_triangle_check(t2_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Triangle");
+    return NULL;
+  }
+  t2 = PYGTS_TRIANGLE(t2_);
+
+  /* Get the common edge */
+  if( (e = gts_triangles_common_edge(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self),
+				     PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t2))) 
+      == NULL ) {
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+
+  if( (edge = pygts_edge_new(GTS_EDGE(e))) == NULL ) {
+    return NULL;
+  }
+
+  return (PyObject*)edge;
+}
+
+
+static PyObject*
+opposite(PygtsTriangle *self, PyObject *args)
+{
+  PyObject *o_;
+  PygtsEdge *e=NULL;
+  PygtsVertex *v=NULL;
+  GtsVertex *vertex=NULL,*v1,*v2,*v3;
+  GtsEdge *edge=NULL;
+  GtsTriangle *triangle;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &o_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(pygts_edge_check(o_)) {
+    e = PYGTS_TRIANGLE(o_);
+  }
+  else {
+    if(pygts_vertex_check(o_)) {
+      v = PYGTS_TRIANGLE(o_);
+    }
+    else {
+      PyErr_SetString(PyExc_TypeError,"expected an Edge or a Vertex");
+      return NULL;
+    }
+  }
+
+  /* Error check */
+  triangle = PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self);
+  if( e!=NULL ) {
+    edge = PYGTS_EDGE_AS_GTS_EDGE(e);
+    if(! ((triangle->e1==edge)||(triangle->e2==edge)||(triangle->e3==edge)) ) {
+      PyErr_SetString(PyExc_RuntimeError,"Edge not in Triangle");
+      return NULL;
+    }
+  }
+  else {
+    vertex = PYGTS_VERTEX_AS_GTS_VERTEX(v);
+    gts_triangle_vertices(triangle,&v1,&v2,&v3);
+    if(! ((vertex==v1)||(vertex==v2)||(vertex==v3)) ) {
+      PyErr_SetString(PyExc_RuntimeError,"Vertex not in Triangle");
+      return NULL;
+    }
+  }
+
+  /* Get the opposite and return */
+  if( e!=NULL) {
+    vertex = gts_triangle_vertex_opposite(triangle, edge);
+    if( (v = pygts_vertex_new(vertex)) == NULL ) {
+      return NULL;
+    }
+    return (PyObject*)v;
+  }
+  else{
+    edge = gts_triangle_edge_opposite(triangle, vertex);
+    if( (e = pygts_edge_new(edge)) == NULL ) {
+      return NULL;
+    }
+    return (PyObject*)e;
+  }
+}
+
+
+static PyObject *
+vertices(PygtsTriangle *self,PyObject *args)
+{
+  GtsVertex *v1_,*v2_,*v3_;
+  PygtsObject *v1,*v2,*v3;
+
+  SELF_CHECK
+
+  /* Get the vertices */
+  gts_triangle_vertices(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self),
+			&v1_, &v2_, &v3_);
+
+  if( (v1 = pygts_vertex_new(v1_)) == NULL ) {
+    return NULL;
+  }
+
+  if( (v2 = pygts_vertex_new(v2_)) == NULL ) {
+    Py_DECREF(v1);
+    return NULL;
+  }
+
+  if( (v3 = pygts_vertex_new(v3_)) == NULL ) {
+    Py_DECREF(v1);
+    Py_DECREF(v2);
+    return NULL;
+  }
+
+  return Py_BuildValue("OOO",v1,v2,v3);
+}
+
+
+static PyObject *
+vertex(PygtsTriangle *self,PyObject *args)
+{
+  GtsVertex *v1_;
+  PygtsObject *v1;
+
+  SELF_CHECK
+
+  /* Get the vertices */
+  v1_ = gts_triangle_vertex(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self));
+
+  if( (v1 = pygts_vertex_new(v1_)) == NULL ) {
+    return NULL;
+  }
+
+  return (PyObject*)v1;
+}
+
+
+static PyObject *
+circumcenter(PygtsTriangle *self,PyObject *args)
+{
+  PygtsVertex *v;
+  GtsVertex *vertex;
+
+  SELF_CHECK
+
+  /* Get the Vertex */
+  vertex = GTS_VERTEX(
+	       gts_triangle_circumcircle_center(
+	           PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self),
+		   GTS_POINT_CLASS(gts_vertex_class())));
+
+  if( vertex == NULL ) {
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+
+  if( (v = pygts_vertex_new(vertex)) == NULL ) {
+    return NULL;
+  }
+
+  return (PyObject*)v;
+}
+
+
+static PyObject *
+is_stabbed(PygtsTriangle *self,PyObject *args)
+{
+  PyObject *p_;
+  PygtsVertex *p;
+  GtsObject *obj;
+  PygtsVertex *vertex;
+  PygtsEdge *edge;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &p_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_point_check(p_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Point");
+    return NULL;
+  }
+  p = PYGTS_POINT(p_);
+
+  obj = gts_triangle_is_stabbed(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self),
+				GTS_POINT(PYGTS_OBJECT(p)->gtsobj),
+				NULL);
+
+  if( obj == NULL ) {
+    Py_INCREF(Py_None);
+    return Py_None;
+  }
+
+  if(GTS_IS_VERTEX(obj)) {
+    if( (vertex = pygts_vertex_new(GTS_VERTEX(obj))) == NULL ) {
+      return NULL;
+    }
+    return (PyObject*)vertex;
+  }
+
+  if(GTS_IS_EDGE(obj)) {
+    if( (edge = pygts_edge_new(GTS_EDGE(obj))) == NULL ) {
+      return NULL;
+    }
+    return (PyObject*)edge;
+  }
+
+  Py_INCREF(self);
+  return (PyObject*)self;
+}
+
+
+static PyObject *
+interpolate_height(PygtsTriangle *self,PyObject *args)
+{
+  PyObject *p_;
+  PygtsPoint *p;
+  GtsPoint point;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &p_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_point_check(p_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Point");
+    return NULL;
+  }
+  p = PYGTS_POINT(p_);
+
+  point.x = PYGTS_POINT_AS_GTS_POINT(p)->x;
+  point.y = PYGTS_POINT_AS_GTS_POINT(p)->y;
+
+  gts_triangle_interpolate_height(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self),
+				  &point);
+
+  return Py_BuildValue("d",point.z);
+}
+
+
+/* Methods table */
+static PyMethodDef methods[] = {
+  {"is_ok", (PyCFunction)is_ok,
+   METH_NOARGS,
+   "True if this Triangle t is non-degenerate and non-duplicate.\n"
+   "False otherwise.\n"
+   "\n"
+   "Signature: t.is_ok()\n"
+  },  
+
+  {"area", (PyCFunction)area,
+   METH_NOARGS,
+   "Returns the area of Triangle t.\n"
+   "\n"
+   "Signature: t.area()\n"
+  },  
+
+  {"perimeter", (PyCFunction)perimeter,
+   METH_NOARGS,
+   "Returns the perimeter of Triangle t.\n"
+   "\n"
+   "Signature: t.perimeter()\n"
+  },  
+
+  {"quality", (PyCFunction)quality,
+   METH_NOARGS,
+   "Returns the quality of Triangle t.\n"
+   "\n"
+   "The quality of a triangle is defined as the ratio of the square\n"
+   "root of its surface area to its perimeter relative to this same\n"
+   "ratio for an equilateral triangle with the same area.  The quality\n"
+   "is then one for an equilateral triangle and tends to zero for a\n"
+   "very stretched triangle."
+   "\n"
+   "Signature: t.quality()\n"
+  },  
+
+  {"normal", (PyCFunction)normal,
+   METH_NOARGS,
+   "Returns a tuple of coordinates of the oriented normal of Triangle t\n"
+   "as the cross-product of two edges, using the left-hand rule.  The\n"
+   "normal is not normalized.  If this triangle is part of a closed and\n" 
+   "oriented surface, the normal points to the outside of the surface.\n"
+   "\n"
+   "Signature: t.normal()\n"
+  },  
+
+  {"revert", (PyCFunction)revert,
+   METH_NOARGS,
+   "Changes the orientation of triangle t, turning it inside out.\n"
+   "\n"
+   "Signature: t.revert()\n"
+  },  
+
+  {"orientation", (PyCFunction)orientation,
+   METH_NOARGS,
+   "Determines orientation of the plane (x,y) projection of Triangle t\n"
+   "\n"
+   "Signature: t.orientation()\n"
+   "\n"
+   "Returns a positive value if Points p1, p2 and p3 in Triangle t\n"
+   "appear in counterclockwise order, a negative value if they appear\n"
+   "in clockwise order and zero if they are colinear.\n"
+  },  
+
+  {"angle", (PyCFunction)angle,
+   METH_VARARGS,
+   "Returns the angle (radians) between Triangles t1 and t2\n"
+   "\n"
+   "Signature: t1.angle(t2)\n"
+  },  
+
+  {"is_compatible", (PyCFunction)is_compatible,
+   METH_VARARGS,
+   "True if this triangle t1 and other t2 are compatible;\n"
+   "otherwise False.\n"
+   "\n"
+   "Checks if this triangle t1 and other t2, which share a common\n"
+   "Edge, can be part of the same surface without conflict in the\n"
+   "surface normal orientation.\n"
+   "\n"
+   "Signature: t1.is_compatible(t2)\n"
+  },  
+
+  {"common_edge", (PyCFunction)common_edge,
+   METH_VARARGS,
+   "Returns Edge common to both this Triangle t1 and other t2.\n"
+   "Returns None if the triangles do not share an Edge.\n"
+   "\n"
+   "Signature: t1.common_edge(t2)\n"
+  },  
+
+  {"opposite", (PyCFunction)opposite,
+   METH_VARARGS,
+   "Returns Vertex opposite to Edge e or Edge opposite to Vertex v\n"
+   "for this Triangle t.\n"
+   "\n"
+   "Signature: t.opposite(e) or t.opposite(v)\n"
+  },  
+
+  {"vertices", (PyCFunction)vertices,
+   METH_NOARGS,
+   "Returns the three oriented set of vertices in Triangle t.\n"
+   "\n"
+   "Signature: t.vertices()\n"
+  },  
+
+  {"vertex", (PyCFunction)vertex,
+   METH_NOARGS,
+   "Returns the Vertex of this Triangle t not in t.e1.\n"
+   "\n"
+   "Signature: t.vertex()\n"
+  },  
+
+  {"circumcenter", (PyCFunction)circumcenter,
+   METH_NOARGS,
+   "Returns a Vertex at the center of the circumscribing circle of\n"
+   "this Triangle t, or None if the circumscribing circle is not\n"
+   "defined.\n"
+   "\n"
+   "Signature: t.circumcircle_center()\n"
+  },  
+
+  {"is_stabbed", (PyCFunction)is_stabbed,
+   METH_VARARGS,
+   "Returns the component of this Triangle t that is stabbed by a\n"
+   "ray projecting from Point p to z=infinity.  The result\n"
+   "can be this Triangle t, one of its Edges or Vertices, or None.\n"
+   "If the ray is contained in the plan of this Triangle then None is\n"
+   "also returned.\n"
+   "\n"
+   "Signature: t.is_stabbed(p)\n"
+  },  
+
+  {"interpolate_height", (PyCFunction)interpolate_height,
+   METH_VARARGS,
+   "Returns the height of the plane defined by Triangle t at Point p.\n"
+   "Only the x- and y-coordinates of p are considered.\n"
+   "\n"
+   "Signature: t.interpolate_height(p)\n"
+  },  
+
+  {NULL}  /* Sentinel */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Attributes exported to python */
+
+static PyObject *
+get_e1(PygtsTriangle *self, void *closure)
+{
+  PygtsEdge *e1;
+
+  SELF_CHECK
+
+  if( (e1=pygts_edge_new(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self)->e1)) == NULL ) {
+    return NULL;
+  }
+
+  return (PyObject *)e1;
+}
+
+
+static PyObject *
+get_e2(PygtsTriangle *self, void *closure)
+{
+  PygtsEdge *e2;
+
+  SELF_CHECK
+
+  if( (e2=pygts_edge_new(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self)->e2)) == NULL ) {
+    return NULL;
+  }
+
+  return (PyObject *)e2;
+}
+
+
+static PyObject *
+get_e3(PygtsTriangle *self, void *closure)
+{
+  PygtsEdge *e3;
+
+  SELF_CHECK
+
+  if( (e3=pygts_edge_new(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self)->e3)) == NULL ) {
+    return NULL;
+  }
+
+  return (PyObject *)e3;
+}
+
+
+/* Methods table */
+static PyGetSetDef getset[] = {
+    {"e1", (getter)get_e1, NULL, "Edge 1", NULL},
+
+    {"e2", (getter)get_e2, NULL, "Edge 2", NULL},
+
+    {"e3", (getter)get_e3, NULL, "Edge 3", NULL},
+
+    {NULL}  /* Sentinel */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Python type methods */
+
+static PyObject *
+new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+  PyObject *o;
+  PygtsObject *obj;
+  guint alloc_gtsobj = TRUE;
+  PyObject *o1_,*o2_,*o3_;
+  GtsVertex *v1=NULL, *v2=NULL, *v3=NULL;
+  GtsEdge *e1=NULL,*e2=NULL,*e3=NULL,*e;
+  GtsSegment *s1,*s2,*s3;
+  gboolean flag=FALSE;  /* Flag when the args are gts.Point objects */
+  GtsTriangle *t,*t_;
+  guint N;
+
+  /* Parse the args */
+  if(kwds) {
+    o = PyDict_GetItemString(kwds,"alloc_gtsobj");
+    if(o==Py_False) {
+      alloc_gtsobj = FALSE;
+    }
+    if(o!=NULL) {
+      PyDict_DelItemString(kwds, "alloc_gtsobj");
+    }
+  }
+  if(kwds) {
+    Py_INCREF(Py_False);
+    PyDict_SetItemString(kwds,"alloc_gtsobj", Py_False);
+  }
+
+  /* Allocate the gtsobj (if needed) */
+  if( alloc_gtsobj ) {
+
+    /* Parse the args */
+    if( (N = PyTuple_Size(args)) < 3 ) {
+      PyErr_SetString(PyExc_TypeError,"expected three Edges or three Vertices");
+      return NULL;
+    }
+    o1_ = PyTuple_GET_ITEM(args,0);
+    o2_ = PyTuple_GET_ITEM(args,1);
+    o3_ = PyTuple_GET_ITEM(args,2);
+
+    /* Convert to PygtsObjects */
+    if( pygts_edge_check(o1_) ) {
+      e1 = PYGTS_EDGE_AS_GTS_EDGE(o1_);
+    }
+    else {
+      if( pygts_vertex_check(o1_) ) {
+	v1 = PYGTS_VERTEX_AS_GTS_VERTEX(o1_);
+	flag = TRUE;
+      }
+    }
+
+    if( pygts_edge_check(o2_) ) {
+      e2 = PYGTS_EDGE_AS_GTS_EDGE(o2_);
+    }
+    else {
+      if( pygts_vertex_check(o2_) ) {
+	v2 = PYGTS_VERTEX_AS_GTS_VERTEX(o2_);
+	flag = TRUE;
+      }
+    }
+
+    if( pygts_edge_check(o3_) ) {
+      e3 = PYGTS_EDGE_AS_GTS_EDGE(o3_);
+    }
+    else {
+      if(pygts_vertex_check(o3_)) {
+	v3 = PYGTS_VERTEX_AS_GTS_VERTEX(o3_);
+	flag = TRUE;
+      }
+    }
+
+    /* Check for three edges or three vertices */
+    if( !((e1!=NULL && e2!=NULL && e3!=NULL) ||
+	  (v1!=NULL && v2!=NULL && v3!=NULL)) ) {
+      PyErr_SetString(PyExc_TypeError,"expected three Edges or three Vertices");
+      return NULL;
+    }
+    if( (v1==v2 || v2==v3 || v1==v3) && v1!=NULL ) {
+      PyErr_SetString(PyExc_ValueError,"three Vertices must be different");
+      return NULL;
+    }
+
+    /* Get gts edges */
+    if(flag) {
+
+      /* Create gts edges */
+      if( (e1 = gts_edge_new(gts_edge_class(),v1,v2)) == NULL ) {
+	PyErr_SetString(PyExc_MemoryError, "could not create Edge");
+	return NULL;
+      }
+      if( (e2 = gts_edge_new(gts_edge_class(),v2,v3)) == NULL ) {
+	PyErr_SetString(PyExc_MemoryError, "could not create Edge");
+	gts_object_destroy(GTS_OBJECT(e1));
+	return NULL;
+      }
+      if( (e3 = gts_edge_new(gts_edge_class(),v3,v1)) == NULL ) {
+	PyErr_SetString(PyExc_MemoryError, "could not create Edge");
+	gts_object_destroy(GTS_OBJECT(e1));
+	gts_object_destroy(GTS_OBJECT(e2));
+	return NULL;
+      }
+
+      /* Check for duplicates */
+      if( (e = gts_edge_is_duplicate(e1)) != NULL ) {
+	gts_object_destroy(GTS_OBJECT(e1));
+	e1 = e;
+      }
+      if( (e = gts_edge_is_duplicate(e2)) != NULL ) {
+	gts_object_destroy(GTS_OBJECT(e2));
+	e2 = e;
+      }
+      if( (e = gts_edge_is_duplicate(e3)) != NULL ) {
+	gts_object_destroy(GTS_OBJECT(e3));
+	e3 = e;
+      }
+    }
+
+    /* Check that edges connect with common vertices */
+    s1 = GTS_SEGMENT(e1);
+    s2 = GTS_SEGMENT(e2);
+    s3 = GTS_SEGMENT(e3);
+    if( !((s1->v1==s3->v2 && s1->v2==s2->v1 && s2->v2==s3->v1) ||
+	  (s1->v1==s3->v2 && s1->v2==s2->v2 && s2->v1==s3->v1) ||
+	  (s1->v1==s3->v1 && s1->v2==s2->v1 && s2->v2==s3->v2) ||
+	  (s1->v2==s3->v2 && s1->v1==s2->v1 && s2->v2==s3->v1) ||
+	  (s1->v1==s3->v1 && s1->v2==s2->v2 && s2->v1==s3->v2) ||
+	  (s1->v2==s3->v2 && s1->v1==s2->v2 && s2->v1==s3->v1) ||
+	  (s1->v2==s3->v1 && s1->v1==s2->v1 && s2->v2==s3->v2) ||
+	  (s1->v2==s3->v1 && s1->v1==s2->v2 && s2->v1==s3->v2)) ) {
+      PyErr_SetString(PyExc_RuntimeError,
+		      "Edges in triangle must connect");
+      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
+	gts_object_destroy(GTS_OBJECT(e1));
+      }
+      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
+	gts_object_destroy(GTS_OBJECT(e2));
+      }
+      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
+	gts_object_destroy(GTS_OBJECT(e3));
+      }
+      return NULL;
+    }
+
+    /* Create the GtsTriangle */
+    if( (t = gts_triangle_new(gts_triangle_class(),e1,e2,e3)) == NULL )  {
+      PyErr_SetString(PyExc_MemoryError, "could not create Face");
+      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
+	gts_object_destroy(GTS_OBJECT(e1));
+      }
+      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
+	gts_object_destroy(GTS_OBJECT(e2));
+      }
+      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
+	gts_object_destroy(GTS_OBJECT(e3));
+      }
+      return NULL;
+    }
+
+    /* Check for duplicate */
+    t_ = gts_triangle_is_duplicate(GTS_TRIANGLE(t));
+    if( t_ != NULL ) {
+      gts_object_destroy(GTS_OBJECT(t));
+      t = t_;
+    }
+
+    /* If corresponding PyObject found in object table, we are done */
+    if( (obj=g_hash_table_lookup(obj_table,GTS_OBJECT(t))) != NULL ) {
+      Py_INCREF(obj);
+      return (PyObject*)obj;
+    }
+  }
+  
+  /* Chain up */
+  obj = PYGTS_OBJECT(PygtsObjectType.tp_new(type,args,kwds));
+
+  if( alloc_gtsobj ) {
+    obj->gtsobj = GTS_OBJECT(t);
+    pygts_object_register(PYGTS_OBJECT(obj));
+  }
+
+  return (PyObject*)obj;
+}
+
+
+static int
+init(PygtsTriangle *self, PyObject *args, PyObject *kwds)
+{
+  gint ret;
+
+  /* Chain up */
+  if( (ret=PygtsObjectType.tp_init((PyObject*)self,args,kwds)) != 0 ){
+    return ret;
+  }
+
+#if PYGTS_DEBUG
+  if(!pygts_triangle_check((PyObject*)self)) {
+    PyErr_SetString(PyExc_RuntimeError,
+		    "problem with self object (internal error)");
+    return -1;
+  }
+#endif
+
+  return 0;
+}
+
+
+static int
+compare(PyObject *o1, PyObject *o2)
+{
+  GtsTriangle *t1, *t2;
+
+  if( !(pygts_triangle_check(o1) && pygts_triangle_check(o2)) ) {
+    return -1;
+  }
+  t1 = PYGTS_TRIANGLE_AS_GTS_TRIANGLE(o1);
+  t2 = PYGTS_TRIANGLE_AS_GTS_TRIANGLE(o2);
+  
+  return pygts_triangle_compare(t1,t2);  
+}
+
+
+/* Methods table */
+PyTypeObject PygtsTriangleType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                       /* ob_size */
+    "gts.Triangle",          /* tp_name */
+    sizeof(PygtsTriangle),   /* tp_basicsize */
+    0,                       /* tp_itemsize */
+    0,                       /* tp_dealloc */
+    0,                       /* tp_print */
+    0,                       /* tp_getattr */
+    0,                       /* tp_setattr */
+    (cmpfunc)compare,        /* tp_compare */
+    0,                       /* tp_repr */
+    0,                       /* tp_as_number */
+    0,                       /* tp_as_sequence */
+    0,                       /* tp_as_mapping */
+    0,                       /* tp_hash */
+    0,                       /* tp_call */
+    0,                       /* tp_str */
+    0,                       /* tp_getattro */
+    0,                       /* tp_setattro */
+    0,                       /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT |
+      Py_TPFLAGS_BASETYPE,   /* tp_flags */
+    "Triangle object",       /* tp_doc */
+    0,                       /* tp_traverse */
+    0,                       /* tp_clear */
+    0,                       /* tp_richcompare */
+    0,                       /* tp_weaklistoffset */
+    0,                       /* tp_iter */
+    0,                       /* tp_iternext */
+    methods,                 /* tp_methods */
+    0,                       /* tp_members */
+    getset,                  /* tp_getset */
+    0,                       /* tp_base */
+    0,                       /* tp_dict */
+    0,                       /* tp_descr_get */
+    0,                       /* tp_descr_set */
+    0,                       /* tp_dictoffset */
+    (initproc)init,          /* tp_init */
+    0,                       /* tp_alloc */
+    (newfunc)new             /* tp_new */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Pygts functions */
+
+gboolean 
+pygts_triangle_check(PyObject* o)
+{
+  if(! PyObject_TypeCheck(o, &PygtsTriangleType)) {
+    return FALSE;
+  }
+  else {
+#if PYGTS_DEBUG
+    return pygts_triangle_is_ok(PYGTS_TRIANGLE(o));
+#else
+    return TRUE;
+#endif
+  }
+}
+
+
+gboolean 
+pygts_triangle_is_ok(PygtsTriangle *t)
+{
+  if(!pygts_object_is_ok(PYGTS_OBJECT(t))) return FALSE;
+  return pygts_gts_triangle_is_ok(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t));
+}
+
+
+PygtsTriangle *
+pygts_triangle_new(GtsTriangle *t)
+{
+  PyObject *args, *kwds;
+  PygtsObject *triangle;
+
+  /* Check for Triangle in the object table */
+  if( (triangle = PYGTS_OBJECT(g_hash_table_lookup(obj_table,GTS_OBJECT(t)))) 
+      !=NULL ) {
+    Py_INCREF(triangle);
+    return PYGTS_TRIANGLE(triangle);
+  }
+
+  /* Build a new Triangle */
+  args = Py_BuildValue("OOO",Py_None,Py_None,Py_None);
+  kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_False);
+  triangle = PYGTS_OBJECT(PygtsTriangleType.tp_new(&PygtsTriangleType,
+						   args, kwds));
+  Py_DECREF(args);
+  Py_DECREF(kwds);
+  if( triangle == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"could not create Triangle");
+    return NULL;
+  }
+  triangle->gtsobj = GTS_OBJECT(t);
+
+  /* Register and return */
+  pygts_object_register(triangle);
+  return PYGTS_TRIANGLE(triangle);
+}
+
+
+int 
+pygts_triangle_compare(GtsTriangle* t1,GtsTriangle* t2)
+{
+  if( (pygts_segment_compare(GTS_SEGMENT(t1->e1),GTS_SEGMENT(t2->e1))==0 &&
+       pygts_segment_compare(GTS_SEGMENT(t1->e2),GTS_SEGMENT(t2->e2))==0 &&
+       pygts_segment_compare(GTS_SEGMENT(t1->e3),GTS_SEGMENT(t2->e3))==0) ||
+      (pygts_segment_compare(GTS_SEGMENT(t1->e1),GTS_SEGMENT(t2->e3))==0 &&
+       pygts_segment_compare(GTS_SEGMENT(t1->e2),GTS_SEGMENT(t2->e1))==0 &&
+       pygts_segment_compare(GTS_SEGMENT(t1->e3),GTS_SEGMENT(t2->e2))==0) ||
+      (pygts_segment_compare(GTS_SEGMENT(t1->e1),GTS_SEGMENT(t2->e2))==0 &&
+       pygts_segment_compare(GTS_SEGMENT(t1->e2),GTS_SEGMENT(t2->e3))==0 &&
+       pygts_segment_compare(GTS_SEGMENT(t1->e3),GTS_SEGMENT(t2->e1))==0) ||
+
+      (pygts_segment_compare(GTS_SEGMENT(t1->e1),GTS_SEGMENT(t2->e3))==0 &&
+       pygts_segment_compare(GTS_SEGMENT(t1->e2),GTS_SEGMENT(t2->e2))==0 &&
+       pygts_segment_compare(GTS_SEGMENT(t1->e3),GTS_SEGMENT(t2->e1))==0) ||
+      (pygts_segment_compare(GTS_SEGMENT(t1->e1),GTS_SEGMENT(t2->e2))==0 &&
+       pygts_segment_compare(GTS_SEGMENT(t1->e2),GTS_SEGMENT(t2->e1))==0 &&
+       pygts_segment_compare(GTS_SEGMENT(t1->e3),GTS_SEGMENT(t2->e3))==0) ||
+      (pygts_segment_compare(GTS_SEGMENT(t1->e1),GTS_SEGMENT(t2->e1))==0 &&
+       pygts_segment_compare(GTS_SEGMENT(t1->e2),GTS_SEGMENT(t2->e3))==0 &&
+       pygts_segment_compare(GTS_SEGMENT(t1->e3),GTS_SEGMENT(t2->e2))==0) ) {
+    return 0;
+  }
+  return -1;
+}
+
+
+/**
+ * gts_triangle_is_ok:
+ * @t: a #GtsTriangle.
+ *
+ * Returns: %TRUE if @t is a non-degenerate, non-duplicate triangle,
+ * %FALSE otherwise.
+ */
+gboolean pygts_gts_triangle_is_ok (GtsTriangle * t)
+{
+  g_return_val_if_fail (t != NULL, FALSE);
+  g_return_val_if_fail (t->e1 != NULL, FALSE);
+  g_return_val_if_fail (t->e2 != NULL, FALSE);
+  g_return_val_if_fail (t->e3 != NULL, FALSE);
+  g_return_val_if_fail (t->e1 != t->e2 && t->e1 != t->e3 && t->e2 != t->e3, 
+                        FALSE);
+  g_return_val_if_fail (gts_segments_touch (GTS_SEGMENT (t->e1), 
+                                            GTS_SEGMENT (t->e2)),
+                        FALSE);
+  g_return_val_if_fail (gts_segments_touch (GTS_SEGMENT (t->e1), 
+                                            GTS_SEGMENT (t->e3)), 
+                        FALSE);
+  g_return_val_if_fail (gts_segments_touch (GTS_SEGMENT (t->e2), 
+                                            GTS_SEGMENT (t->e3)), 
+                        FALSE);
+  g_return_val_if_fail (GTS_SEGMENT (t->e1)->v1 != GTS_SEGMENT (t->e1)->v2, 
+                        FALSE);
+  g_return_val_if_fail (GTS_SEGMENT (t->e2)->v1 != GTS_SEGMENT (t->e2)->v2, 
+                        FALSE);
+  g_return_val_if_fail (GTS_SEGMENT (t->e3)->v1 != GTS_SEGMENT (t->e3)->v2, 
+                        FALSE);
+  /*  g_return_val_if_fail (GTS_OBJECT (t)->reserved == NULL, FALSE); */
+  g_return_val_if_fail (!gts_triangle_is_duplicate (t), FALSE);
+  return TRUE;
+}

Added: trunk/lib/py/pygts-0.3.1/triangle.h
===================================================================
--- trunk/lib/py/pygts-0.3.1/triangle.h	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/triangle.h	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,58 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PYGTS_TRIANGLE_H__
+#define __PYGTS_TRIANGLE_H__
+
+typedef struct _PygtsObject PygtsTriangle;
+
+#define PYGTS_TRIANGLE(obj) ((PygtsTriangle*)obj)
+
+#define PYGTS_TRIANGLE_AS_GTS_TRIANGLE(o) \
+  (GTS_TRIANGLE(PYGTS_OBJECT(o)->gtsobj))
+
+extern PyTypeObject PygtsTriangleType;
+
+gboolean pygts_triangle_check(PyObject* o);
+gboolean pygts_triangle_is_ok(PygtsTriangle *t);
+
+PygtsTriangle* pygts_triangle_new(GtsTriangle *t);
+
+int pygts_triangle_compare(GtsTriangle* t1,GtsTriangle* t2);
+
+
+
+/* Replacement for gts_triangle_is_ok().  The problem is that sometimes the 
+ * "reserved" variable is set in a face by gts, and so this function fails.  
+ * e.g., The error occurs when gts_triangle_is_ok() is called during 
+ * iteration over faces in a surface.  This function ignores that check so
+ * that there is no failure when PYGTS_DEBUG is set.  A bug report should be 
+ * submitted.
+ */
+gboolean pygts_gts_triangle_is_ok(GtsTriangle *t);
+
+#endif /* __PYGTS_TRIANGLE_H__ */

Added: trunk/lib/py/pygts-0.3.1/vertex.c
===================================================================
--- trunk/lib/py/pygts-0.3.1/vertex.c	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/vertex.c	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,874 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#include "pygts.h"
+
+
+#if PYGTS_DEBUG
+  #define SELF_CHECK if(!pygts_vertex_check((PyObject*)self)) {      \
+                       PyErr_SetString(PyExc_RuntimeError,            \
+                       "problem with self object (internal error)");  \
+		       return NULL;                                   \
+                     }
+#else
+  #define SELF_CHECK
+#endif
+
+
+/*-------------------------------------------------------------------------*/
+/* Methods exported to python */
+
+static PyObject*
+is_ok(PygtsVertex *self, PyObject *args)
+{
+  if(pygts_vertex_is_ok(self)) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+is_unattached(PygtsVertex *self, PyObject *args)
+{
+  guint n;
+
+  SELF_CHECK
+
+  /* Check for attachments other than to the gtsobj_parent */
+  n = g_slist_length(PYGTS_VERTEX_AS_GTS_VERTEX(self)->segments);
+  if( n > 1 ) {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+  else {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+}
+
+
+static PyObject*
+is_boundary(PygtsVertex* self, PyObject *args)
+{
+  PyObject *s_;
+  PygtsObject *s;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "O", &s_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_surface_check(s_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Surface");
+    return NULL;
+  }
+  s = PYGTS_OBJECT(s_);
+
+  if( gts_vertex_is_boundary(PYGTS_VERTEX_AS_GTS_VERTEX(self),
+			     PYGTS_SURFACE_AS_GTS_SURFACE(s)) ) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+contacts(PygtsVertex* self, PyObject *args)
+{
+  PyObject *sever_=NULL;
+  gboolean sever=FALSE;
+  guint n;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "|O", &sever_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if( sever_ != NULL ) {
+    if(!PyBool_Check(sever_)) {
+      PyErr_SetString(PyExc_TypeError,"expected a Boolean");
+      return NULL;
+    }
+    if( sever_ == Py_True ) {
+      sever = TRUE;
+    }
+  }
+
+  n = gts_vertex_is_contact(PYGTS_VERTEX_AS_GTS_VERTEX(self),sever);
+  return Py_BuildValue("i",n);
+}
+
+
+static PyObject*
+is_connected(PygtsVertex *self, PyObject *args)
+{
+  PyObject *v_;
+  PygtsVertex *v;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &v_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_vertex_check(v_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Vertex");
+    return NULL;
+  }
+  v = PYGTS_VERTEX(v_);
+
+  if( gts_vertices_are_connected(PYGTS_VERTEX_AS_GTS_VERTEX(self),
+				 PYGTS_VERTEX_AS_GTS_VERTEX(v)) != NULL ) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+replace(PygtsVertex *self, PyObject *args)
+{
+  PyObject *p2_;
+  PygtsVertex *p2;
+  GSList *parents=NULL, *i, *cur;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &p2_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_vertex_check(p2_)) {
+    PyErr_SetString(PyExc_TypeError,"expected a Vertex");
+    return NULL;
+  }
+  p2 = PYGTS_VERTEX(p2_);
+
+  if( self != p2 ) {
+    /* (Ignore self-replacement) */
+
+    /* Detach and save any parent segments */
+    i = PYGTS_VERTEX_AS_GTS_VERTEX(self)->segments;
+    while(i!=NULL) {
+      cur = i;
+      i = g_slist_next(i);
+      if(PYGTS_IS_PARENT_SEGMENT(cur->data)) {
+	PYGTS_VERTEX_AS_GTS_VERTEX(self)->segments = 
+	  g_slist_remove_link(PYGTS_VERTEX_AS_GTS_VERTEX(self)->segments,
+			      cur);
+	parents = g_slist_prepend(parents,cur->data);
+	g_slist_free_1(cur);
+      }
+    }
+
+    /* Perform the replace operation */
+    gts_vertex_replace(PYGTS_VERTEX_AS_GTS_VERTEX(self),
+		       PYGTS_VERTEX_AS_GTS_VERTEX(p2));
+
+    /* Reattach the parent segments */
+    i = parents;
+    while(i!=NULL) {
+      PYGTS_VERTEX_AS_GTS_VERTEX(self)->segments = 
+	g_slist_prepend(PYGTS_VERTEX_AS_GTS_VERTEX(self)->segments,i->data);
+      i = g_slist_next(i);
+    }
+    g_slist_free(parents);
+  }
+
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+
+static PyObject*
+neighbors(PygtsVertex* self, PyObject *args)
+{
+  PyObject *s_=NULL;
+  GtsSurface *s=NULL;
+  GSList *vertices,*v;
+  PygtsVertex *vertex;
+  PyObject *tuple;
+  guint n,N;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "|O", &s_) ) {
+    return NULL;
+  }
+
+  /* Convert */
+  if( s_ != NULL ) {
+    if(!pygts_surface_check(s_)) {
+      PyErr_SetString(PyExc_TypeError,"expected a Surface");
+      return NULL;
+    }
+    s = PYGTS_SURFACE_AS_GTS_SURFACE(s_);
+  }
+
+  /* Get the neighbors */
+  vertices = gts_vertex_neighbors(PYGTS_VERTEX_AS_GTS_VERTEX(self),
+				  NULL,s);
+  N = g_slist_length(vertices);
+
+  /* Create the tuple */
+  if( (tuple=PyTuple_New(N)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"could not create tuple");
+    return NULL;
+  }
+
+  /* Put PygtsVertex objects into the tuple */
+  v = vertices;
+  for(n=0;n<N;n++) {
+
+    /* Skip this vertex if it is a parent */
+    while( v!=NULL && PYGTS_IS_PARENT_VERTEX(GTS_VERTEX(v->data)) ) {
+      v = g_slist_next(v);      
+    }
+    if( v==NULL ) break;
+
+    if( (vertex = pygts_vertex_new(GTS_VERTEX(v->data))) == NULL ) {
+      Py_DECREF((PyObject*)tuple);
+      return NULL;
+    }
+
+    PyTuple_SET_ITEM(tuple, n, (PyObject*)vertex);
+    
+    v = g_slist_next(v);
+  }
+
+  if(_PyTuple_Resize(&tuple,n)!=0) {
+    Py_DECREF(tuple);
+    return NULL;
+  }
+
+  return tuple;
+}
+
+
+static PyObject*
+faces(PygtsVertex* self, PyObject *args)
+{
+  PyObject *s_=NULL;
+  GtsSurface *s=NULL;
+  GSList *faces,*f;
+  PygtsFace *face;
+  PyObject *tuple;
+  guint n,N;
+
+  SELF_CHECK
+
+  /* Parse the args */
+  if(! PyArg_ParseTuple(args, "|O", &s_) ) {
+    return NULL;
+  }
+
+  /* Convert */
+  if( s_ != NULL ) {
+    if(!pygts_surface_check(s_)) {
+      PyErr_SetString(PyExc_TypeError,"expected a Surface");
+      return NULL;
+    }
+    s = PYGTS_SURFACE_AS_GTS_SURFACE(s_);
+  }
+
+  /* Get the faces */
+  faces = gts_vertex_faces(PYGTS_VERTEX_AS_GTS_VERTEX(self),s,NULL);
+  N = g_slist_length(faces);
+
+  /* Create the tuple */
+  if( (tuple=PyTuple_New(N)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"expected a tuple");
+    return NULL;
+  }
+
+  /* Put PygtsVertex objects into the tuple */
+  f = faces;
+  for(n=0;n<N;n++) {
+
+    if( (face = pygts_face_new(GTS_FACE(f->data))) == NULL ) {
+      Py_DECREF(tuple);
+      return NULL;
+    }
+
+    PyTuple_SET_ITEM(tuple, n, (PyObject*)face);
+    
+    f = g_slist_next(f);
+  }
+
+  return tuple;
+}
+
+
+static PyObject*
+encroaches(PygtsVertex *self, PyObject *args)
+{
+  PyObject *e_;
+  PygtsEdge *e;
+
+  SELF_CHECK
+
+  /* Parse the args */  
+  if(! PyArg_ParseTuple(args, "O", &e_) ) {
+    return NULL;
+  }
+
+  /* Convert to PygtsObjects */
+  if(!pygts_edge_check(e_)) {
+    PyErr_SetString(PyExc_TypeError,"expected an Edge");
+    return NULL;
+  }
+  e = PYGTS_EDGE(e_);
+
+  if(gts_vertex_encroaches_edge(PYGTS_VERTEX_AS_GTS_VERTEX(self),
+				PYGTS_EDGE_AS_GTS_EDGE(e))) {
+    Py_INCREF(Py_True);
+    return Py_True;
+  }
+  else {
+    Py_INCREF(Py_False);
+    return Py_False;
+  }
+}
+
+
+static PyObject*
+triangles(PygtsVertex *self, PyObject *args)
+{
+  GSList *triangles, *t;
+  PygtsTriangle *triangle;
+  guint i,N;
+  PyObject *tuple;
+
+  SELF_CHECK
+
+  triangles = gts_vertex_triangles(PYGTS_VERTEX_AS_GTS_VERTEX(self),NULL);
+  N = g_slist_length(triangles);
+
+  /* Create the tuple */
+  if( (tuple=PyTuple_New(N)) == NULL) {
+    PyErr_SetString(PyExc_MemoryError,"could not create tuple");
+    return NULL;
+  }
+
+  /* Put PygtsVertex objects into the tuple */
+  t = triangles;
+  for(i=0;i<N;i++) {
+
+    if( (triangle = pygts_triangle_new(GTS_TRIANGLE(t->data))) == NULL ) {
+      Py_DECREF(tuple);
+      return NULL;
+    }
+
+    PyTuple_SET_ITEM(tuple, i, (PyObject*)triangle);
+    
+    t = g_slist_next(t);
+  }
+
+  return tuple;
+}
+
+
+/* Methods table */
+static PyMethodDef methods[] = {
+
+  {"is_ok", (PyCFunction)is_ok,
+   METH_NOARGS,
+   "True if this Vertex v is OK.  False otherwise.\n"
+   "This method is useful for unit testing and debugging.\n"
+   "\n"
+   "Signature: v.is_ok().\n"
+  },  
+
+  {"is_unattached", (PyCFunction)is_unattached,
+   METH_NOARGS,
+   "True if this Vertex v is not the endpoint of any Segment.\n"
+   "\n"
+   "Signature: v.is_unattached().\n"
+  },
+
+  {"is_boundary", (PyCFunction)is_boundary,
+   METH_VARARGS,
+   "True if this Vertex v is used by a boundary Edge of Surface s.\n"
+   "\n"
+   "Signature: v.is_boundary().\n"
+  },
+
+  {"contacts", (PyCFunction)contacts,
+   METH_VARARGS,
+   "Returns the number of sets of connected Triangles sharing this\n"
+   "Vertex v.\n"
+   "\n"
+   "Signature: v.contacts().\n"
+   "\n"
+   "If sever is True (default: False) and v is a contact vertex then\n"
+   "the vertex is replaced in each Triangle with clones.\n"
+  },
+
+  {"is_connected", (PyCFunction)is_connected,
+   METH_VARARGS,
+   "Return True if this Vertex v1 is connected to Vertex v2\n"
+   "by a Segment.\n"
+   "\n"
+   "Signature: v1.is_connected().\n"
+  },
+
+  {"replace", (PyCFunction)replace,
+   METH_VARARGS,
+   "Replaces this Vertex v1 with Vertex v2 in all Segments that have v1.\n"
+   "Vertex v1 itself is left unchanged.\n"
+   "\n"
+   "Signature: v1.replace(v2).\n"
+  },
+
+  {"neighbors", (PyCFunction)neighbors,
+   METH_VARARGS,
+   "Returns a tuple of Vertices attached to this Vertex v\n"
+   "by a Segment.\n"
+   "\n"
+   "If a Surface s is given, only Vertices on s are considered.\n"
+   "\n"
+   "Signature: v.neighbors() or v.neighbors(s).\n"
+  },
+
+  {"faces", (PyCFunction)faces,
+   METH_VARARGS,
+   "Returns a tuple of Faces that have this Vertex v.\n"
+   "\n"
+   "If a Surface s is given, only Vertices on s are considered.\n"
+   "\n"
+   "Signature: v.faces() or v.faces(s).\n"
+  },
+
+  {"encroaches", (PyCFunction)encroaches,
+   METH_VARARGS,
+   "Returns True if this Vertex v is strictly contained in the\n"
+   "diametral circle of Edge e.  False otherwise.\n"
+   "\n"
+   "Only the projection onto the x-y plane is considered.\n"
+   "\n"
+   "Signature: v.encroaches(e)\n"
+  },
+
+  {"triangles", (PyCFunction)triangles,
+   METH_NOARGS,
+   "Returns a list of Triangles that have this Vertex v.\n"
+   "\n"
+   "Signature: v.triangles()\n"
+  },
+
+  {NULL}  /* Sentinel */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Python type methods */
+
+static GtsObject * parent(GtsVertex *v1);
+
+static PyObject *
+new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+  PyObject *o;
+  PygtsObject *obj;
+  guint alloc_gtsobj = TRUE;
+
+  /* Parse the args */
+  if(kwds) {
+    o = PyDict_GetItemString(kwds,"alloc_gtsobj");
+    if(o==Py_False) {
+      alloc_gtsobj = FALSE;
+    }
+    if(o!=NULL) {
+      PyDict_DelItemString(kwds, "alloc_gtsobj");
+    }
+  }
+  if(kwds) {
+    Py_INCREF(Py_False);
+    PyDict_SetItemString(kwds,"alloc_gtsobj", Py_False);
+  }
+
+  /* Chain up */
+  obj = PYGTS_OBJECT(PygtsPointType.tp_new(type,args,kwds));
+
+  /* Allocate the gtsobj (if needed) */
+  if( alloc_gtsobj ) {
+    obj->gtsobj = GTS_OBJECT(gts_vertex_new(gts_vertex_class(),0,0,0));
+    if( obj->gtsobj == NULL )  {
+      PyErr_SetString(PyExc_MemoryError, "could not create Vertex");
+      return NULL;
+    }
+
+    /* Create the parent GtsSegment */
+    if( (obj->gtsobj_parent=parent(GTS_VERTEX(obj->gtsobj))) == NULL ) {
+      gts_object_destroy(obj->gtsobj);
+      obj->gtsobj = NULL;
+      return NULL;
+    }
+
+    pygts_object_register(obj);
+  }
+
+  return (PyObject*)obj;
+}
+
+
+static int
+init(PygtsVertex *self, PyObject *args, PyObject *kwds)
+{
+  gint ret;
+
+  /* Chain up */
+  if( (ret=PygtsPointType.tp_init((PyObject*)self,args,kwds)) != 0 ) {
+    return ret;
+  }
+
+  return 0;
+}
+
+
+/* Methods table */
+PyTypeObject PygtsVertexType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                       /* ob_size */
+    "gts.Vertex",            /* tp_name */
+    sizeof(PygtsVertex),     /* tp_basicsize */
+    0,                       /* tp_itemsize */
+    0,                       /* tp_dealloc */
+    0,                       /* tp_print */
+    0,                       /* tp_getattr */
+    0,                       /* tp_setattr */
+    0,                       /* tp_compare */
+    0,                       /* tp_repr */
+    0,                       /* tp_as_number */
+    0,                       /* tp_as_sequence */
+    0,                       /* tp_as_mapping */
+    0,                       /* tp_hash */
+    0,                       /* tp_call */
+    0,                       /* tp_str */
+    0,                       /* tp_getattro */
+    0,                       /* tp_setattro */
+    0,                       /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT |
+      Py_TPFLAGS_BASETYPE,   /* tp_flags */
+    "Vertex object",         /* tp_doc */
+    0,                       /* tp_traverse */
+    0,                       /* tp_clear */
+    0,                       /* tp_richcompare */
+    0,                       /* tp_weaklistoffset */
+    0,                       /* tp_iter */
+    0,                       /* tp_iternext */
+    methods,                 /* tp_methods */
+    0,                       /* tp_members */
+    0,                       /* tp_getset */
+    0,                       /* tp_base: attached in pygts.c */
+    0,                       /* tp_dict */
+    0,                       /* tp_descr_get */
+    0,                       /* tp_descr_set */
+    0,                       /* tp_dictoffset */
+    (initproc)init,          /* tp_init */
+    0,                       /* tp_alloc */
+    (newfunc)new             /* tp_new */
+};
+
+
+/*-------------------------------------------------------------------------*/
+/* Pygts functions */
+
+gboolean 
+pygts_vertex_check(PyObject* o)
+{
+  gboolean check = FALSE;
+  guint i,N;
+  PyObject *obj;
+
+  /* Check for a Vertex */
+  if( PyObject_TypeCheck(o, &PygtsVertexType) ) {
+    check = TRUE;
+  }
+
+  /* Convert list into tuple */
+  if(PyList_Check(o)) {
+    o = PyList_AsTuple(o);
+  }
+  else {
+    Py_INCREF(o);
+  }
+
+  /* Check for a tuple of floats */
+  if( PyTuple_Check(o) ) {
+    if( (N = PyTuple_Size(o)) <= 3 ) {
+      check = TRUE;
+      for(i=0;i<N;i++) {
+	obj = PyTuple_GET_ITEM(o,i);
+	if(!PyFloat_Check(obj) && !PyInt_Check(obj)) {
+	  check = FALSE;
+	}
+      }
+    }
+  }
+  Py_DECREF(o);
+
+  if( !check ) {
+    return FALSE;
+  }
+  else {
+#if PYGTS_DEBUG
+    if( PyObject_TypeCheck(o, &PygtsVertexType) ) {
+      return pygts_vertex_is_ok(PYGTS_VERTEX(o));
+    }
+#endif
+    return TRUE;
+  }
+}
+
+
+gboolean 
+pygts_vertex_is_ok(PygtsVertex *v)
+{
+  GSList *parent;
+  PygtsObject *obj;
+
+  obj = PYGTS_OBJECT(v);
+
+  if(!pygts_point_is_ok(PYGTS_POINT(v))) return FALSE;
+
+  /* Check for a valid parent */
+  g_return_val_if_fail(obj->gtsobj_parent!=NULL,FALSE);
+  g_return_val_if_fail(PYGTS_IS_PARENT_SEGMENT(obj->gtsobj_parent),FALSE);
+  parent = g_slist_find(GTS_VERTEX(obj->gtsobj)->segments,
+			obj->gtsobj_parent);
+  g_return_val_if_fail(parent!=NULL,FALSE);
+
+  return TRUE;
+}
+
+
+static GtsObject *
+parent(GtsVertex *v1) {
+  GtsPoint *p1;
+  GtsVertex *v2;
+  GtsSegment *p;
+
+  /* Create another Vertex */
+  p1 = GTS_POINT(v1);
+  if( (v2 = gts_vertex_new(pygts_parent_vertex_class(),p1->x,p1->y,p1->z+1)) 
+    == NULL ) {
+    PyErr_SetString(PyExc_MemoryError, "could not create parent");
+    return NULL;
+  }
+
+  /* Create and return the parent */
+  if( (p = gts_segment_new(pygts_parent_segment_class(),v1,v2))
+      == NULL ) {
+    PyErr_SetString(PyExc_MemoryError, "could not create parent");
+    gts_object_destroy(GTS_OBJECT(v2));
+    return NULL;
+  }
+
+  return GTS_OBJECT(p);
+}
+
+
+PygtsVertex *
+pygts_vertex_new(GtsVertex *v)
+{
+  PyObject *args, *kwds;
+  PygtsObject *vertex;
+
+  /* Check for Vertex in the object table */
+  if( (vertex = PYGTS_OBJECT(g_hash_table_lookup(obj_table,GTS_OBJECT(v)))) 
+      !=NULL ) {
+    Py_INCREF(vertex);
+    return PYGTS_VERTEX(vertex);
+  }
+
+  /* Build a new Vertex */
+  args = Py_BuildValue("ddd",0,0,0);
+  kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_False);
+  vertex = PYGTS_VERTEX(PygtsVertexType.tp_new(&PygtsVertexType, args, kwds));
+  Py_DECREF(args);
+  Py_DECREF(kwds);
+  if( vertex == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"could not create Vertex");
+    return NULL;
+  }
+  vertex->gtsobj = GTS_OBJECT(v);
+
+  /* Attach the parent */
+  if( (vertex->gtsobj_parent=parent(v)) == NULL ) {
+	Py_DECREF(vertex);
+	return NULL;
+  }
+
+  /* Register and return */
+  pygts_object_register(vertex);
+  return PYGTS_VERTEX(vertex);
+}
+
+
+PygtsVertex *
+pygts_vertex_from_sequence(PyObject *tuple) {
+  guint i,N;
+  gdouble x=0,y=0,z=0;
+  PyObject *obj;
+  GtsVertex *v;
+  PygtsVertex *vertex;
+
+  /* Convert list into tuple */
+  if(PyList_Check(tuple)) {
+    tuple = PyList_AsTuple(tuple);
+  }
+  else {
+    Py_INCREF(tuple);
+  }
+  if(!PyTuple_Check(tuple)) {
+    Py_DECREF(tuple);
+    PyErr_SetString(PyExc_TypeError,"expected a list or tuple of vertices");
+    return NULL;
+  }
+
+  /* Get the tuple size */
+  if( (N = PyTuple_Size(tuple)) > 3 ) {
+    PyErr_SetString(PyExc_RuntimeError,
+		    "expected a list or tuple of up to three floats");
+    Py_DECREF(tuple);
+    return NULL;
+  }
+
+  /* Get the coordinates */
+  for(i=0;i<N;i++) {
+    obj = PyTuple_GET_ITEM(tuple,i);
+
+    if(!PyFloat_Check(obj) && !PyInt_Check(obj)) {
+      PyErr_SetString(PyExc_TypeError,"expected a list or tuple of floats");
+      Py_DECREF(tuple);
+      return NULL;
+    }
+    if(i==0) {
+      if(PyFloat_Check(obj)) x = PyFloat_AsDouble(obj);
+      else  x = (double)PyInt_AsLong(obj);
+    }
+    if(i==1) {
+      if(PyFloat_Check(obj)) y = PyFloat_AsDouble(obj);
+      else  y = (double)PyInt_AsLong(obj);
+    }
+    if(i==2) {
+      if(PyFloat_Check(obj)) z = PyFloat_AsDouble(obj);
+      else  z = (double)PyInt_AsLong(obj);
+    }
+  }
+  Py_DECREF(tuple);
+
+  /* Create the vertex */  
+  if( (v = gts_vertex_new(gts_vertex_class(),x,y,z)) == NULL ) {
+    PyErr_SetString(PyExc_MemoryError,"could not create Vertex");
+  }
+  if( (vertex = pygts_vertex_new(v)) == NULL ) {
+    gts_object_destroy(GTS_OBJECT(v));
+    return NULL;
+  }
+
+  return vertex;
+}
+
+
+GtsSegmentClass*
+pygts_parent_segment_class(void)
+{
+  static GtsSegmentClass *klass = NULL;
+  GtsObjectClass *super = NULL;
+
+  if (klass == NULL) {
+
+    super = GTS_OBJECT_CLASS(gts_segment_class());
+
+    GtsObjectClassInfo pygts_parent_segment_info = {
+      "PygtsParentSegment",
+      sizeof(PygtsParentSegment),
+      sizeof(GtsSegmentClass),
+      (GtsObjectClassInitFunc)(super->info.class_init_func),
+      (GtsObjectInitFunc)(super->info.object_init_func),
+      (GtsArgSetFunc) NULL,
+      (GtsArgGetFunc) NULL
+    };
+    klass = gts_object_class_new(gts_object_class(),
+				 &pygts_parent_segment_info);
+  }
+
+  return klass;
+}
+
+
+GtsVertexClass*
+pygts_parent_vertex_class(void)
+{
+  static GtsVertexClass *klass = NULL;
+  GtsObjectClass *super = NULL;
+
+  if (klass == NULL) {
+
+    super = GTS_OBJECT_CLASS(gts_vertex_class());
+
+    GtsObjectClassInfo pygts_parent_vertex_info = {
+      "PygtsParentVertex",
+      sizeof(PygtsParentVertex),
+      sizeof(GtsVertexClass),
+      (GtsObjectClassInitFunc)(super->info.class_init_func),
+      (GtsObjectInitFunc)(super->info.object_init_func),
+      (GtsArgSetFunc) NULL,
+      (GtsArgGetFunc) NULL
+    };
+    klass = gts_object_class_new(gts_object_class(),
+				 &pygts_parent_vertex_info);
+  }
+
+  return klass;
+}

Added: trunk/lib/py/pygts-0.3.1/vertex.h
===================================================================
--- trunk/lib/py/pygts-0.3.1/vertex.h	2009-06-24 17:04:39 UTC (rev 1813)
+++ trunk/lib/py/pygts-0.3.1/vertex.h	2009-06-24 22:06:24 UTC (rev 1814)
@@ -0,0 +1,86 @@
+/* pygts - python package for the manipulation of triangulated surfaces
+ *
+ *   Copyright (C) 2009 Thomas J. Duck
+ *   All rights reserved.
+ *
+ *   Thomas J. Duck <tom.duck@xxxxxx>
+ *   Department of Physics and Atmospheric Science,
+ *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
+ *
+ * NOTICE
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Library General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Library General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this library; if not, write to the
+ *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PYGTS_VERTEX_H__
+#define __PYGTS_VERTEX_H__
+
+typedef struct _PygtsObject PygtsVertex;
+
+
+#define PYGTS_VERTEX(o)							\
+  ( PyObject_TypeCheck((PyObject*)o, &PygtsVertexType) ?		\
+    (PygtsVertex*)o :							\
+    pygts_vertex_from_sequence((PyObject*)o) )
+
+#define PYGTS_VERTEX_AS_GTS_VERTEX(o)					\
+  ( PyObject_TypeCheck((PyObject*)o, &PygtsVertexType) ?		\
+    GTS_VERTEX(PYGTS_OBJECT(o)->gtsobj) :				\
+    GTS_VERTEX(PYGTS_OBJECT(PYGTS_VERTEX(o))->gtsobj) )
+
+extern PyTypeObject PygtsVertexType;
+
+gboolean pygts_vertex_check(PyObject* o);
+gboolean pygts_vertex_is_ok(PygtsVertex *v);
+
+PygtsVertex* pygts_vertex_new(GtsVertex *f);
+PygtsVertex* pygts_vertex_from_sequence(PyObject *tuple);
+
+
+/*-------------------------------------------------------------------------*/
+/* Parent GTS segment for GTS vertices */
+
+/* Define a GtsSegment subclass that can be readily identified as the parent 
+ * of an encapsulated GtsVertex.  The pygts_parent_segment_class() function 
+ * is defined at the bottom, and is what ultimately allows the distinction 
+ * to be made.  This capability is used for vertex replacement operations.
+ */
+typedef struct _GtsSegment PygtsParentSegment;
+
+#define PYGTS_PARENT_SEGMENT(obj) GTS_OBJECT_CAST(obj,\
+					     GtsSegment,\
+					     pygts_parent_segment_class())
+
+#define PYGTS_IS_PARENT_SEGMENT(obj)(gts_object_is_from_class(obj,\
+                                             pygts_parent_segment_class()))
+
+GtsSegmentClass* pygts_parent_segment_class(void);
+
+
+/* GTS vertices in parent segments */
+
+typedef struct _GtsVertex PygtsParentVertex;
+
+#define PYGTS_PARENT_VERTEX(obj) GTS_OBJECT_CAST(obj,\
+						 GtsVertex,\
+						 pygts_parent_vertex_class())
+
+#define PYGTS_IS_PARENT_VERTEX(obj)(gts_object_is_from_class(obj,\
+                                             pygts_parent_vertex_class()))
+
+GtsVertexClass *pygts_parent_vertex_class(void);
+
+#endif /* __PYGTS_VERTEX_H__ */