# How to count the number of faces of each vertex group?

Hi everyone I am making an addon for my degree thesis, I have a problem with the following: I have to count the number of faces of each vertex group present in my scene and i use this function

           for x in list(bpy.data.objects):
list_vertex = []
for vertex in vs:
for g in vertex.groups:
if (g.group == i):
list_vertex.append(vertex.index)
break
for face in list(bpy.data.objects[x.name].data.polygons):
isFace = False
count = 0
for vertex in face.vertices:
if vertex in list_vertex:
isFace = True
count+=1
else: isFace = False
if isFace == False:
break
if count == len(face.vertices):
count_face += 1


If I try to run the addon on a mesh with few vertices then everything works, if instead I use a bigger scene blender stops working immediately so I think my problem is related to the performance of my code, I'm new to addon programming, could anyone tell me if there is a more optimal method to do this?

• What is vs? What is i? They're defined outside of the quoted code. Jun 13 at 19:33
• IIUC you count a poly as being in a vertex group if all of its verts are in the group (no matter their weight) and you want the total number of polys in each group? Jun 13 at 19:35
• Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking.
– Community Bot
Jun 13 at 19:39
• You can probably accelerate it a lot by just using a set() instead of a list for list_vertices. Doing vertex in list_vertex will be very slow. Also don't use list() on the data.polygons. Jun 13 at 19:57

You are right, there is room for improvement here. Quite a lot of unnecessary loops, where you loop over groups, then over object, vertices, and then over groups again. All these nested loops multiply, so the overall runtime explodes pretty quickly. Also you are creating unnecessary intermediate lists, which can be very inefficient when you add a lot of vertices one by one.

The basic idea is sound: count for each face how many of its vertices are in a group, then check if that matches the total vertex count of the face. That way you only need a single loop over groups and vertices. So lets do that:

import bpy

# No need to convert this into a list(...)
for obj in bpy.data.objects:
# We get errors if we try to do the code below for anything other than meshes
if obj.type != 'MESH':
continue

num_groups = len(obj.vertex_groups)

# [x] * n is a more efficient way to create a large list all at once,

# Counts number of faces in each group
group_faces = [0] * len(obj.vertex_groups)

for face in obj.data.polygons:
# Counts vertices of this face in each group
group_counts = [0] * num_groups

for vertex_idx in face.vertices:
vertex = obj.data.vertices[vertex_idx]
for vgroup in vertex.groups:
group_counts[vgroup.group] += 1

# Count up faces where all vertices are in a group
# enumerate is a utility to loop with an index and element together
for group_idx, count in enumerate(group_counts):
if count == len(face.vertices):
group_faces[group_idx] += 1

print(obj.name + ": " + ", ".join(group.name + "=" + str(group_faces[group_idx]) for group_idx, group in enumerate(obj.vertex_groups)))

• Sorry for the long delay, your solution helped me a lot, however I still have some problems in terms of efficiency, I tested your solution on a mesh with 250,000 vertices and 500,000 faces, the script takes about 8 seconds to run, Isn't there an even faster solution? Jul 16 at 15:35
• There are a few things to try if you really need that kind of performance: 1. Use bmesh instead of the bpy.Mesh class. This may involve conversion to the BMesh type, but it has nicer iterators over things like face corners, faces adjacent to a vertex, etc. docs.blender.org/api/3.2/bmesh.html 2. Profile the code to find actual bottlenecks docs.python.org/3/library/profile.html 3. Use numpy to store large arrays and parallelize computations. Numpy is shipped with Blender's builtin python. Copying mesh arrays to numpy and then computing with that might be faster overall. Jul 17 at 16:31

Here's the fastest I found. It first collects a set of vertex indices for all groups, then iterates over all polys using issuperset to detect if the poly's verts are contained in a group.

import bpy

mesh = ob.data
ngroups = len(ob.vertex_groups)

# set of all vertex indices that belong to each group
group_verts = [set() for _ in range(ngroups)]

for vi, v in enumerate(mesh.vertices):
for g in v.groups:

# number of polys in each group
# (poly is in group if all its verts are)
poly_counts = [0] * ngroups

for poly in mesh.polygons:
poly_verts = list(poly.vertices)

# check whether poly belongs to each group
for gi in range(ngroups):
if group_verts[gi].issuperset(poly_verts):
poly_counts[gi] += 1

print(poly_counts)


This was a bit faster than lukas_t's answer, but uses a lot more memory, so I don't necessarily recommend it.

Test mesh was 125k verts, 125k quads, 32 groups randomly assigned, ~3k polys in each group.

## Any Corner

Consider a face belonging to a vertex group if it's affected by it - at least one of its corners belongs to the vertex group:

import bpy
from bpy import data as D
from collections import defaultdict

for ob in D.objects:
if ob.type != 'MESH':
continue
print(f"--\n{ob.name}")
me = ob.data
g2f = defaultdict(set)  # group to face mapping
v2f = defaultdict(set)  # vertex to face mapping
for f in me.polygons:
for v_index in f.vertices:
for v in me.vertices:
for g in v.groups:
g2f[g.group].update(v2f[v.index])
for g_index, faces_set in g2f.items():
g_name = ob.vertex_groups[g_index].name
print(f"{g_name}: {len(faces_set)} faces")


## All Corners

A face belongs to a vert group only if it's entirely built from vertices belonging to the vertex group:

import bpy
from bpy import data as D
from collections import Counter

for ob in D.objects:
if ob.type != 'MESH':
continue
print(f"--\n{ob.name}")
me = ob.data
group_counter = Counter()
for f in me.polygons:
size = len(f.vertices)
verts = (me.vertices[i] for i in f.vertices)
cnt = Counter(g.group for v in verts for g in v.groups)
group_counter.update(i for i, c in cnt.items() if c == size)
for g_index, num_faces in group_counter.items():
g_name = ob.vertex_groups[g_index].name
print(f"{g_name}: {num_faces} faces")


## Bmesh - Any Corner

This will display info also for unused meshes and won't display info for the same mesh multiple times. Also I don't know how to access a vertex name without an object, though you could create a temporary object just for that...

import bpy, bmesh
from bpy import data as D
from collections import Counter

bm = bmesh.new()
for me in D.meshes:
print(f"--\n{me.name}")
bm.from_mesh(me, face_normals=False, vertex_normals=False)
layer = bm.verts.layers.deform.active
if not layer:
continue
group_counter = Counter()
for f in bm.faces:
group_counter.update({g for v in f.verts for g in v[layer].keys()})
for g_index, num_faces in group_counter.items():
print(f"Group #{g_index}: {num_faces} faces")
bm.clear()


## Bmesh - All Corners

import bpy, bmesh
from bpy import data as D
from collections import Counter

bm = bmesh.new()
for me in D.meshes:
print(f"--\n{me.name}")
bm.from_mesh(me, face_normals=False, vertex_normals=False)
layer = bm.verts.layers.deform.active
if not layer:
continue
group_counter = Counter()
for f in bm.faces:
groups = (v[layer].keys() for v in f.verts)
groups_set = set(next(groups))
for g in groups:
groups_set.intersection_update(g)
group_counter.update(groups_set)
for g_index, num_faces in group_counter.items():
print(f"Group #{g_index}: {num_faces} faces")
bm.clear()

• Doesn't this count a poly as belonging to a group if at least one of its verts belongs to it (vs all of them belonging to it)? Jun 14 at 3:22
• @scurest yes, I thought that's the idea. Jun 14 at 7:30