1
$\begingroup$

The idea: a Python script that looks at the left side, then looks at the right side group with same root name but ending with ".R", then process all vertices and copy the weights to the vertex closest to the ideal mirrored position.

My attempt: a somewhat ugly loop that assumes the model axis of symmetry is at [0, ?, ?] and then just multiply x by -1. This is assumed to be the ideal mirrored position for that vertex. Then iterate all vertices in the group ending with ".R" and apply weights to the one closest to the ideal position.

The problem: when I finally try to get the weight using vertex_groups[GROUP_NAME].weight(VERTEX_INDEX) I get RuntimeError: Error: Vertex not in group

This should not happen, as VERTEX_INDEX is taken from an array collected from a selection of the source group. I know the group is not externally changed while the script is running. There is no way a wrong VERTEX_INDEX is passed to the weight() function.

I already checked for bad indentation, that may results in statements running outside the intended blocks of code.

More info: before the expected "apply your weights before applying the deformation to the right side of the model". I'm not the one doing the model and I cannot edit it in a destructive way. The mesh is already provided, I have to weight it. I know that the numbers of vertices in both sides are the same, it's just that some parts of the model aren't perfectly symmetrical (like the artist did everything without the mirror modifier).

The script, a lot of hardcoded stuff, look for # conflictive line:

import bpy
import mathutils

# ensure we are in edit mode
bpy.ops.object.mode_set(mode="EDIT")

# configurable parameters
# We take weights from vertices in groups ending with src_group_part
# and transfer them to vertices in the group with the same root name
# but ending with dst_group_part.
# The closest possible vertex will be the one receiving the weights
src_group_part = ".L"
dst_group_part = ".R"

obj = bpy.context.edit_object

mesh = obj.data

#bpy.ops.object.mode_set(mode="OBJECT")

print("Working")

for v_group in obj.vertex_groups:
    # TODO: .L may not be at end of string, write more robust code
    is_src = v_group.name.find(src_group_part)
    if is_src == -1:
        continue
    mirrored_name = v_group.name.split(src_group_part)[0] + dst_group_part
    if not mirrored_name in obj.vertex_groups:
        continue
    target_group = obj.vertex_groups[mirrored_name]
    bpy.ops.mesh.select_all(action="DESELECT")
    bpy.context.edit_object.vertex_groups.active_index = v_group.index
    bpy.ops.object.vertex_group_select()
    src_sel_verts_ig = [[v.index, [g.group for g in v.groups], v.co] for v in mesh.vertices if v.select]
    bpy.ops.mesh.select_all(action="DESELECT")
    bpy.context.edit_object.vertex_groups.active_index = target_group.index
    bpy.ops.object.vertex_group_select()
    target_sel_verts_ig = [[v.index, [g.group for g in v.groups], v.co] for v in mesh.vertices if v.select]
    for src_v in src_sel_verts_ig:
        min_dist_v_index = -1
        min_dist = 1000000
        for dst_v in target_sel_verts_ig:
            mirrored_v = dst_v[2].copy()
            mirrored_v.x *= -1
            dist = (mirrored_v - dst_v[2]).length
            if min_dist > dist:
                min_dist_v_index = dst_v[0]
                min_dist = dist
        if min_dist_v_index > -1:
            new_weight = obj.vertex_groups[v_group.name].weight(src_v[0]) # conflictive line
            obj.vertex_groups[mirrored_name].add([min_dist_v_index], new_weight, 'REPLACE')

print("Finished")

Any alternative to get this script to work is also appreciated. Like, has Blender a tool for these cases?

$\endgroup$

1 Answer 1

3
$\begingroup$

Object Mode Version

Here is a test script to run in object mode. It looks for all vertex groups on active object that end with ".L", finds a matching group ending with ".R" (or None).

Then using the list comprehension methods from Python - Finding Vertices in a Vertex Group check if there are the same number in each.

If so, for each left group vert, sort the verts in the right group by the distance from the x mirrored location left group vert. Pop off the closest and set to mirrored pos of left.

Requires no operators and no edit mode.

import bpy
import re

context = bpy.context
ob = context.object
# get the mirrored name
def get_mirror(g):
    return ob.vertex_groups.get(re.sub(r'(.*)L', r'\1R', g.name))

mirror_groups = [(vg, get_mirror(vg)) for vg in ob.vertex_groups
        if vg.name.endswith(".L")]

for lg, rg in mirror_groups:
    if rg is None:
        print(lg.name, "has no mirror group")
        continue

    print(lg.name, rg.name)

    lverts = [v for v in ob.data.vertices 
            if lg.index in [vg.group for vg in v.groups]]  
    rverts = [v for v in ob.data.vertices 
            if rg.index in [vg.group for vg in v.groups]] 

    if len(lverts) != len(rverts):
        print("mismatching vert counts", len(lverts), len(rverts))
        continue 

    for lv in lverts:
        vec = lv.co.copy()
        vec.x *= -1
        # sort rverts by distance from lvert
        rverts.sort(key=lambda v: (vec - v.co).length) 
        # pop and set the rvert
        rv = rverts.pop(0)
        rv.co = vec 
        rg.add([rv.index], lg.weight(lv.index), 'REPLACE')
$\endgroup$
1
  • 1
    $\begingroup$ Thank you. I removed the line rv.co = vec. Remember I don't want to make the model symmetric. The model is as the artist wants it. I just want to copy the weights. $\endgroup$ May 11, 2018 at 3:34

You must log in to answer this question.

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