0
$\begingroup$

In edit mode If I manually select a vertex group and highlight one vertice, I can open this, manually fill out the values, and hit copy. All the vertices in the group will then receive the vertex weights shown. Can this be done with Python?

enter image description here

I do have an array of indices: verts_in_group = [551, 556, 662, 664, 698, 2220, 2230, 2236]

so I'm not concerned about having to find them. I assume you can reference the bones by name, since they are already named?, but I don't know how to say: “For this vertice, the weight for this bone should be this number

I think what I want to say is something like..

For each indice in “verts_in_group”, 
Set Vertex Weights To:
CC_Base_Pelvis = 0.622
CC_Base_L_ThighTwist01 = 0.036
CC_Base_R_ThighTwist01 = 0.240
CC_Base_Waist = 0.062
Iterate until each vertice in the array has been set

I know I'm missing stuff here. I've only just started to learn scripting and I don't know where the weight information shown in this panel is stored. I'm assuming it's "in" the vertice since I had to click on a vertice to see it. I am looking on the API website too but it doesn't seem like they have examples there so it's difficult for a beginner to use.

Thanks much for any help

UPDATE:

Thank you for the links – I did see those before I asked: The first one has some great examples but they are all for “getting” vertices in a group, I already have the vertices I need to work on (as an array), and since I'm importing/working on the same mesh every day those will not change.

I think the last one shows how to iterate through vertices and see if they are assigned to a group and if so multiply the weight by a factor. I want to set them to specific weights by bone (like in the image), not increase all of their weights by a factor.

The middle two both go to the same place. It's more than 8 years old, but it worked, with a slight edit, to get some data back about one of my vertex groups:

import bpy
obj = bpy.context.object

def get_weights(ob, vgroup):
    group_index = vgroup.index
    for i, v in enumerate(ob.data.vertices):
        for g in v.groups:
            if g.group == group_index:
                yield (i, g.weight)
                break

import bpy
obj = bpy.context.object
vgroup = obj.vertex_groups[16] #I changed 0 to 16 to target a specific vertex group 

# iterate over index, weights
for i, w in get_weights(obj, vgroup):
    print(i, w)

The first column returned is all the indices for my group, but the second column shows only one weight, not all the weights associated with each vertice per each bone that affects it (like you see in the image at the top). Since at least four deform bones are affecting each of these vertices I would expect to see more data here – Maybe I'm thinking about this in the wrong way - Are the weights stored in the bones instead of the vertices?

551 1.0
556 1.0
662 1.0
664 1.0
698 1.0
2220 1.0
2230 1.0
2236 1.0

Final Summary: Wow - thank you so much for that answer. Your code worked for me and I think I understand most of the logic due to your detailed explanation and code comments:

the object has a mesh: (obj.data) the object's mesh has vertices: (obj.data.vertices) the vertices have groups (obj.data.vertices[x].groups) the groups have “vertex group elements” like indexes and weights (vge.group) (vge.weight)

Seems like one of the takeaways here is not to see the Vertex Group as a container of vertices, but rather as a property of a vertice (that's why you said “vertex groups don't know their vertices”) and it makes some sense since an object in the physical world would not be able to occupy two containers at the same time.

For any other beginner like myself: Even with the great explanation from Blunder I was stuck on this still because I only just now realized that something automatically makes vertex groups for bones and gives them the same names as the bones. So I thought the panel in my attached screen shot was showing me “weights per bone” when it was actually showing me “weights per group”. Once I realized that, I was better able to follow along with the code.

$\endgroup$
3
  • $\begingroup$ Hi. Thanks for the update. The first two (or three) links above indeed show how to get the vertices of a group. I've posted them so you can see how to access the vertex group and the weights. It looks like it's still confusing you ;-), so I posted an answer and hope it makes it clearer. As far as the API is concerned, the weights are not stored in the bones but in the Vertex Group Elements that are attached to the vertices. From the user's point of view, the weights are stored in the vertex groups. The weights determine how much influence the bones have on the mesh (you know that, right?) $\endgroup$
    – Blunder
    Commented Apr 26 at 20:04
  • $\begingroup$ thank you for this - you code worked for me and I'm learning a lot from it - I updated my post with some final thoughts $\endgroup$
    – vertstyler
    Commented Apr 26 at 23:53

1 Answer 1

2
$\begingroup$

Vertex groups are not handled in Python the way you might think (at least not the way I thought when I first started playing around with them).

Let's have a look at the relationship:

  • you have an object like 'Suzanne'. The active object is in bpy.context.active_object
    • the object (obj) has the vertex groups in obj.vertex_groups
      • the vertex groups have the same order as in the list of Vertex Groups on the Data Properties tab

      • the index values of the vertex group go from 0 to n-1. If you change their position in the list, their index changes accordingly.

      • the vertex groups can change the weights with the add method, e.g. group.add(v_index, weight, 'REPLACE')

    • the object has a mesh (obj.data)
      • with vertices in obj.data.vertices
        • the vertices have their vertex group elements in obj.data.vertices[x].groups
        • the vertex group elements know the weight (vge.weight) and the group index (vge.group)

Conclusion

  • the vertex groups don't know their vertices
  • you have to check the vertex group elements of the vertices to see if the vertex is in a given vertex group. Yes, high-poly mesh = million of verts = poor performance
  • the vertex group can change the weight
  • the weight is in the vertex group element which is bound to the vertex

In your example, you only get one weight because you have only one vertex group (#16) and you check all vertices if they are in that group. If yes, you print the vertex index and weight of the vertex group element.

That's not what you want. For your given vertex indices, you need to get the vertex object for each index. Then build another loop over its vertex group elements giving you all the indices of the vertex groups this vertex is in. Now you can check if the vertex group index belongs to a group you are interested in. If yes, you can finally change the weight.

Here is an example script that prints the weights of all vertex groups that the given vertices are in. Then it sets/replaces the weights of the given groups. (The vertices and groups are defined by the index values.)

import bpy

def print_assigned_vertex_groups(obj, my_vertex_indices):
    print("Vertex group info:")
    for vert_index in my_vertex_indices:
        vert = obj.data.vertices[vert_index]   # get the vertex
        print(f"Vert {vert.index} at ({vert.co}) is ") 
        if len(vert.groups) == 0:
            print("- not assigned to any vertex group") 
        else:
            for vge in vert.groups:     # vge is a VertexGroupElement
                group = obj.vertex_groups[vge.group]
                print(f"- assigned to vertex group index {vge.group} = '{group.name}' with a weight of {vge.weight}")
    print()        


def assign_vertices_to_vertex_groups(obj, my_vertex_indices, my_group_indices, new_weights):
    print("Assigning vertices to selected vertex groups:")
    for vert_index in my_vertex_indices:
        vert = obj.data.vertices[vert_index]   # get the vertex
        
        for i, group_index in enumerate(my_group_indices):
            group = obj.vertex_groups[group_index]             # get the group            
            group.add([vert.index], new_weights[i], 'REPLACE') # change the weight  
    
    print("done\n")
    

def main():
    # active object
    obj = bpy.context.active_object

    # chosen vertices, groups & new weights
    verts_in_group = [60, 61, 62, 166]            # <-- change the vertex indices
    my_group_indices = [4, 5, 2]                  # <-- change the group indices
    new_weights = [ 0.5, 1.0, 0.25]               # <-- change the weights you want

    # print some info
    print("---START-------------------------------------------")
    print(f"Object '{obj.name}' has the following vertex groups:")
    for g in obj.vertex_groups:
        print(f"group {g.index} = '{g.name}'")

    print()
    print(f"Chosen groups:   {my_group_indices}")
    print(f"Chosen vertices: {verts_in_group}")
    print()

    print_assigned_vertex_groups(obj, verts_in_group)

    assign_vertices_to_vertex_groups(obj, verts_in_group, my_group_indices, new_weights)
    
    print_assigned_vertex_groups(obj, verts_in_group)
    

if __name__ == "__main__":
    main()

Output of a test with Susanne:

---START-------------------------------------------
Object 'Suzanne' has the following vertex groups:
group 0 = 'head'
group 1 = 'eye.left'
group 2 = 'eye.right (gets changed)'
group 3 = 'eyebrows'
group 4 = 'new demo_group_1'
group 5 = 'new_bone_head'
group 6 = 'last group (unused)'

Chosen groups:   [4, 5, 2]
Chosen vertices: [60, 61, 62, 166]

Vertex group info:
Vert 60 at (<Vector (0.3516, -0.8281, 0.2422)>) is
- assigned to vertex group index 0 = 'head' with a weight of 0.8312757015228271
- assigned to vertex group index 1 = 'eye.left' with a weight of 0.5308641791343689
Vert 61 at (<Vector (-0.3516, -0.8281, 0.2422)>) is
- assigned to vertex group index 0 = 'head' with a weight of 0.8312757015228271
- assigned to vertex group index 2 = 'eye.right (gets changed)' with a weight of 0.25925925374031067
Vert 62 at (<Vector (0.3516, -0.8047, 0.1172)>) is
- assigned to vertex group index 0 = 'head' with a weight of 0.8312757015228271
- assigned to vertex group index 1 = 'eye.left' with a weight of 0.5308641791343689
Vert 166 at (<Vector (-0.2109, -0.7109, -0.4453)>) is
- not assigned to any vertex group

Assigning vertices to selected vertex groups:
done

Vertex group info:
Vert 60 at (<Vector (0.3516, -0.8281, 0.2422)>) is
- assigned to vertex group index 0 = 'head' with a weight of 0.8312757015228271
- assigned to vertex group index 1 = 'eye.left' with a weight of 0.5308641791343689
- assigned to vertex group index 4 = 'new demo_group_1' with a weight of 0.5
- assigned to vertex group index 5 = 'new_bone_head' with a weight of 1.0
- assigned to vertex group index 2 = 'eye.right (gets changed)' with a weight of 0.25
Vert 61 at (<Vector (-0.3516, -0.8281, 0.2422)>) is
- assigned to vertex group index 0 = 'head' with a weight of 0.8312757015228271
- assigned to vertex group index 2 = 'eye.right (gets changed)' with a weight of 0.25
- assigned to vertex group index 4 = 'new demo_group_1' with a weight of 0.5
- assigned to vertex group index 5 = 'new_bone_head' with a weight of 1.0
Vert 62 at (<Vector (0.3516, -0.8047, 0.1172)>) is
- assigned to vertex group index 0 = 'head' with a weight of 0.8312757015228271
- assigned to vertex group index 1 = 'eye.left' with a weight of 0.5308641791343689
- assigned to vertex group index 4 = 'new demo_group_1' with a weight of 0.5
- assigned to vertex group index 5 = 'new_bone_head' with a weight of 1.0
- assigned to vertex group index 2 = 'eye.right (gets changed)' with a weight of 0.25
Vert 166 at (<Vector (-0.2109, -0.7109, -0.4453)>) is
- assigned to vertex group index 4 = 'new demo_group_1' with a weight of 0.5
- assigned to vertex group index 5 = 'new_bone_head' with a weight of 1.0
- assigned to vertex group index 2 = 'eye.right (gets changed)' with a weight of 0.25

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .