My take on this.
Sometimes it's more trouble than it's worth to completely avoid operators. Given the nature of this method I can't imagine you will be running it in a loop hundreds of times. Adding a modifier I would always use the API method. Selecting and assigning vertex groups using the operator is often the go, but you are constricted by the need to be in a certain mode for context.
Here's a method to do same, but with no operators. Can get the object from the vertex group, so that's all I pass to the method with: the number of times to grow the vertex group; and the name of the mask modifier so we don't end up adding modifiers endlessly to the stack.
I'm using a bmesh, for the ease of finding the linked faces for each vertex.
Test code, run in object mode with a mesh object having a vertex group, as context object.
import bpy
import bmesh
def mask_and_grow(vg, num=1, modname="Mask"):
ob = vg.id_data
mod = ob.modifiers.get(modname) or ob.modifiers.new(modname, type='MASK')
mod.vertex_group = vg.name
me = ob.data
bm = bmesh.new()
bm.from_mesh(me)
bm.verts.ensure_lookup_table()
def ingroup(vg, v):
vert = me.vertices[v.index] # use either bm or me
return vg.index in [vg.group for vg in vert.groups]
def grow(v):
return (v for f in v.link_faces
for v in f.verts
if not ingroup(vg, v))
vg_verts = (bm.verts[v.index] for v in me.vertices
if ingroup(vg, v))
for i in range(num):
# only need to look at extended next time
vg_verts = list(set(v for g in [grow(vgv) for vgv in vg_verts] for v in g))
vg.add([v.index for v in vg_verts],
1.0,
'REPLACE')
#me.update()
bm.free()
# test call
ob = bpy.context.object
vg = ob.vertex_groups.active
mask_and_grow(vg, num=2)