Mesh Sub-Objects

Sub-Objects in Blender.

Types

Setting and Query Modes

To work on the sub-objects you need to jump between Edit und Object mode quite frequently:

# Jump into edit mode:
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_mode(type="VERT")
bpy.ops.mesh.select_mode(type="EDGE")
bpy.ops.mesh.select_mode(type="FACE")
# Jump into object mode
bpy.ops.object.mode_set(mode="OBJECT")
# Query
bpy.context.active_object.mode
# > 'EDIT'

Important:

  • Operators need Edit Mode
  • Mesh data changes need Object Mode

Don't ask me why. It's stupid and cost me quite some pain to find out, but that's the way it is.

Selection

To select and deselect all subobjects you need to set Edit mode:

# select
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_mode(type="VERT") # or "EDGE" or "FACE"
bpy.ops.mesh.select_all(action = 'SELECT')
bpy.ops.object.mode_set(mode="OBJECT") # not necessary

# deselect
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_mode(type="VERT") # or "EDGE" or "FACE"
bpy.ops.mesh.select_all(action = 'DESELECT')
bpy.ops.object.mode_set(mode="OBJECT") # not necessary

# invert selection
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_mode(type="VERT") # or "EDGE" or "FACE"
bpy.ops.mesh.select_all(action = 'INVERT')
bpy.ops.object.mode_set(mode="OBJECT") # not necessary

Notes: before using the

Refs: select_all

Checking Selection

Check for selected faces:

# How many selected?
print(f"Count of selected verts: {obj.data.total_vert_sel}")
print(f"Count of selected edges: {obj.data.total_edge_sel}")
print(f"Count of selected faces: {obj.data.total_face_sel}")

# Get all selected
selected_verts = [v for v in obj.data.vertices if v.select]
selected_edges = [e for e in obj.data.edges if e.select]
selected_faces = [f for f in obj.data.polygons if f.select]

# Get all selected - Alternative
mesh=bpy.context.object.data
selected_verts = list(filter(lambda v: v.select, obj.data.vertices))
selected_edges = list(filter(lambda e: e.select, obj.data.edges))
selected_faces = list(filter(lambda f: f.select, obj.data.polygons))

Setting Sub-Object Selection

Various issues when trying to select sub-objects:

  • Apparently vertices are only added to a selection. As such you need to clear the selection with a operator fist!
  • Changing data on vertices has to happen in Object Mode, operators only operate in Edit Mode! This makes changing the mode at least twice necessary. (SUX)

This function takes care of it:

def select_vertex(obj, *ids):
    bpy.ops.object.mode_set(mode="EDIT")
    bpy.ops.mesh.select_all(action='DESELECT')
    bpy.ops.object.mode_set(mode="OBJECT")
    for v in obj.data.vertices:
        v.select = (v.index in ids)

Editing/Changing subobject data

Numpy Tutorial for subobject editing

UV Editing

UV's are stored as a list bpy_prop_collection of Float2AttributeValue (containing only 2D vector) in a MeshUVLoopLayer (which is a UV Map in Blender). UV maps are stored as uv_layers in the object data. The only direct correlation between the mesh and the UV maps is via polygons/faces which store the indices.

This represents how vertices and uvs correlate.

def print_uvs(obj):
    bpy.ops.object.mode_set(mode="OBJECT") # doesn't work in edit mode.
    print(f"Object has {len(obj.data.uv_layers.active.uv)} uvs.")
    uv_layer = obj.data.uv_layers.active
    for face in obj.data.polygons:
        cV = len(face.vertices)
        cUV= len(face.loop_indices)
        print(f"Face {face.index} has {cV} vertices and {cUV} uvs.")
        for vert, loop in zip(face.vertices, face.loop_indices):
            print(f"Vert {vert}: {loop} at {uv_layer.uv[loop].vector}")

This is how you create a correlation:

def uv_to_vert_list():
    """Returns a list where every uv index represents a vertex index."""
    uvs = [0] * len(obj.data.uv_layers.active.uv)
    uv_layer = obj.data.uv_layers.active
    for face in obj.data.polygons:
        for vert, loop in zip(face.vertices, face.loop_indices):
            uvs[loop] = vert
    return uvs

def vert_to_uv_list():
    """Returns a list where every vertex index represents a list of uvs"""
    verts = [[] for x in obj.data.vertices]
    uv_layer = obj.data.uv_layers.active
    for face in obj.data.polygons:
        for vert, loop in zip(face.vertices, face.loop_indices):
            verts[vert].append(loop)
    return verts

There will always be more UVs than vertices, hence the lists in the vert_to_uv_list function.

The locations of UVs can be manipulated directly:

bpy.ops.object.mode_set(mode="OBJECT") # Only can adress UVs in object mode!
uvmap = obj.data.uv_layers.active
for i in range(len(uvmap.uv)):
    uvmap.uv[i].vector += Vector((0.1, 0.0))
bpy.ops.object.mode_set(mode="EDIT")

Vertex Groups

Vertex groups are stored in the object, not in the object data!

Object: VertexGroups Collection of Vertex Group

obj = bpy.context.active_object
obj.vertex_groups.new(name="Left Side")     # Create a new vertex group
myGroup = obj.vertex_groups.active          # active group
print(myGroup.name)
myGroup.add([1,2,3], 1.0, 'ADD')            # Add vertices to group with weight of 1
myGroup.weight(2)                           # Get weight for vertex #2
myGroup.remove(3)                           # Remove vertex 3

Vertex group data

Vertices store data about their relationship to vertex groups:

Vertex: Collection of VertexGroupElement

vert = obj.data.vertices[0]
vert.groups[0]                              # Groups are stored by number index
vert.groups[0].group                        # Group index in the list
vert.groups[0].weight                       # The weight of the vertex

Setting Vertex Groups

You can only set a vertex weight via the group function add:

obj.vertex_groups[0].add( [1], .3, 'REPLACE')

On the plus side, the "normalize weights"

# needed after changing bone weights
bpy.ops.object.vertex_group_normalize_all(group_select_mode='BONE_DEFORM')

Get Vertex Group vertices

This function gets all vertices in a certain group.

def get_vertex_group_vertices(obj, group_name):
    vg = obj.vertex_groups[group_name]
    verts = []
    for v in obj.data.vertices:
        for g in v.groups:
            if g.group == vg.index: # compare with index in VertexGroupElement
                verts.append(v.index)
    return verts