The Blender function that makes a poly from a point cloud is
* Makes an NGon from an un-ordered set of verts
* - that verts are only once in the list.
* - that the verts have roughly planer bounds
* - that the verts are roughly circular
* there can be concave areas but overlapping folds from the center point will fail.
* a brief explanation of the method used
* - find the center point
* - find the normal of the vcloud
* - order the verts around the face based on their angle to the normal vector at the center point.
* \note Since this is a vcloud there is no direction.
(If you want to read it, here's some quick links to
So for verts in a plane, it looks like the idea is to simply sweep out a circle around the centroid, taking verts in the order you hit them.
Assuming the verts are roughly a circle (no verts at the centroid, no edges pointing radially away from the centroid) you can do this with something like the function below. (These assumptions are pretty easy to lift if you need to, they just simplify the code a bit.)
from mathutils import Vector
def sort_radial_sweep(vs, indices):
Given a list of vertex positions (vs) and indices
for verts making up a circular-ish planar polygon,
returns the vertex indices in order around that poly.
assert len(vs) >= 3
# Centroid of verts
cent = Vector()
for v in vs:
cent += (1/len(vs)) * v
# Normalized vector from centroid to first vertex
# ASSUMES: vs is not located at the centroid
r0 = (vs - cent).normalized()
# Normal to plane of poly
# ASSUMES: cent, vs, and vs are not colinear
nor = (vs - cent).cross(r0).normalized()
# Pairs of (vertex index, angle to centroid)
vpairs = 
for vi, vpos in zip(indices, vs):
r1 = (vpos - cent).normalized()
dot = r1.dot(r0)
angle = math.acos(max(min(dot, 1), -1))
angle *= 1 if nor.dot(r1.cross(r0)) >= 0 else -1
# Sort by angle and return indices
vpairs.sort(key=lambda v: v)
return [vi for vi, angle in vpairs]
vs = [
Vector(( 0, 1, 0)),
Vector(( -1, 0, 0)),
Vector((-0.5, -0.5, 0)),
Vector(( 1, 0, 0)),
Vector(( 0.5, -0.5, 0))
indices = [
# => [874, 873, 870, 871, 872]
Note that you might get CW order instead of CCW. I think you get CW order if the first two verts in the input list are in CW order (and similarly for CCW). Maybe you have another way to pick the facing direction though.