2
$\begingroup$

I have a existing mesh of a human body and I have created vertex groups that contain groups of vertices that are associated with different parts on the left side of the body mesh. I want to use python to create additional vertex groups that contain the vertices for the same parts except on the right side of the body. The intent of my code is to iterate through all of the vertex groups associated with the mesh, identify those that have "_left" in their names, create their respective "_right" counterpart vertex groups, and populate those new groups with the appropriate vertices, those that mirror the vertices of the "_left" counterpart vertex group.

To locate a mirrored vertex, I take the coordinates of the "_left" group vertex and multiply the x-coordinate by -1, since the body mesh is symmetrical on the y-axis plane. I do double check, though, in the case the mesh is not perfectly symmetrical (although I believe it is) to make sure a vertex with the new coordinates exists.

I've clearly done something wrong because, although it is identifying the correct "_left" vertex groups, and is creating the correct corresponding "_right" vertex groups, and appears to be identifying the vertices that are to be added to the new groups (see console output snippet below), either it is not actually identifying the desired mirrored vertices correctly and/or my code is not handling them properly as far as adding them to the new groups. What results is incorrect vertices in the new "_right" groups, changed vertices in the existing "_left" groups (which should only be used to identify the correct mirrored right vertices and not change at all), and newly created edges and faces connecting these new incorrect left and right vertices.

Here is my code:

import bpy

def get_vertex_index(obj_vertices, v_co):
    for ov in obj_vertices:
        if ov.co == v_co:
            return ov.index
    return -1


def find_and_group_mirrored_vertices(active_obj):
    ao_vertex_groups = active_obj.vertex_groups
    ao_data_vertices = active_obj.data.vertices
    # Iterate through existing vertex groups of the active object
    for vg in ao_vertex_groups:
        # Check if the current group name is one of the groups that need "_right" vertex group versions created
        if ("_left" in vg.name):
            # If so, create a new "_right" vertex group to mirror the current "_left" vertex group
            right_vg_name = vg.name.replace("_left", "_right")
            right_vg = active_obj.vertex_groups.new(name=right_vg_name)
            right_vg_indices = []
            # Iterate through vertices in the "_left" group
            for aov in ao_data_vertices:
                for aov_vg in aov.groups:
                    if aov_vg.group == vg.index:
                        # Start with coordinates of respective "_left" group vertex for establishing desired "_right" group vertex coordinates
                        right_v_co = aov.co
                        # Change x-coordinate so that the coordinates identify the desired "_right" group vertex
                        right_v_co[0] *= -1
                        # Get the index of the vertex at these new "_right" group coordinates, if it exists in the active object mesh
                        right_v_idx = get_vertex_index(ao_data_vertices, right_v_co)
                        # If the desired vertex exists, append its index to to the new "_right" vertex group list of vertex indices
                        if right_v_idx != -1:
                            right_vg_indices.append(right_v_idx)
            print("Right Vertex Group Name: " + right_vg_name)
            print("Right Vertex Group Vertices:")
            print(right_vg_indices)
            for right_vg_index in right_vg_indices:
                right_vg.add([right_vg_index], 1.0, 'ADD')

# Set the active object
obj = bpy.context.active_object
bpy.ops.object.mode_set(mode='OBJECT', toggle=True)

# Call the function to copy and flip vertices
find_and_group_mirrored_vertices(obj)

A snippet of output to the console:

Right Vertex Group Name: leg_right_knee_circumference
Right Vertex Group Vertices:
[6727, 6731, 6738, 7235, 7237, 7244, 7248, 23790, 23792, 23846, 23847, 23848, 24144, 24146, 24149, 24153, 24178, 24179, 24181, 24183, 24185, 24187, 24190, 24418, 24420, 24449, 24452, 24455, 24458, 24466, 24513, 24515, 24516, 24518, 24520, 24550, 24552, 24556, 24558, 24559]

It is selecting the correct number of vertices for the knee circumference: 40.

Body mesh - before:

enter image description here

Body mesh - after:

enter image description here

There shouldn't be any new geometry at all.

Example left side vertex group (left knee circumference) - before:

enter image description here

And then the same left side vertex group and the new right side vertex group - after:

enter image description here

enter image description here

which appear to be identical. And as I stated above, the left side vertex group shouldn't have changed at all. I'm not sure where in my code it goes off the rails. I had a brief look at Bmesh, but wasn't quite sure if it was something that was needed here and, if it was, how to implement it.

EDIT: I just wrote some quick code to capture the indices of selected vertices and it showed me that the indices in the console output snippet above are not those of the desired mirrored vertices intended for the "_right" vertex group, they are the indices of the "_left" vertex group vertices, those whose coordinates were supposed to be used to identify the proper "_right" vertex group vertices. So, part of my problem is in identifying the mirrored, right side vertices.

$\endgroup$

1 Answer 1

1
$\begingroup$

I think it's because of this line:

right_v_co[0] *= -1

as it modifies the left vertex.

You should take a copy of the vector before.


Below the modified code, using a KDTree instead of the loop. Modifications are marked by "<<<<<".

It works with it and does not (on a sample mesh) using the loop. Most likely due to float values approximations.

By the way, it is much faster.

import bpy
from mathutils import Vector, kdtree # <<<<

def get_vertex_index(obj_vertices, v_co):
    for ov in obj_vertices:
        if ov.co == v_co:
            return ov.index
    return -1

def find_and_group_mirrored_vertices(active_obj):
    
    # <<<<
    mesh = active_obj.data
    size = len(mesh.vertices)
    # feed the kd tree
    kd = kdtree.KDTree(size)
    for i, v in enumerate(mesh.vertices):
        kd.insert(v.co, i)

    # initialize it
    kd.balance()
    # >>>>
    
    ao_vertex_groups = active_obj.vertex_groups
    ao_data_vertices = active_obj.data.vertices
    # Iterate through existing vertex groups of the active object
    for vg in ao_vertex_groups:
        # Check if the current group name is one of the groups that need "_right" vertex group versions created
        if ("_left" in vg.name):
            # If so, create a new "_right" vertex group to mirror the current "_left" vertex group
            right_vg_name = vg.name.replace("_left", "_right")
            right_vg = active_obj.vertex_groups.new(name=right_vg_name)
            right_vg_indices = []
            # Iterate through vertices in the "_left" group
            for aov in ao_data_vertices:
                for aov_vg in aov.groups:
                    if aov_vg.group == vg.index:
                        # Start with coordinates of respective "_left" group vertex for establishing desired "_right" group vertex coordinates
                        right_v_co = Vector(aov.co)
                        # Change x-coordinate so that the coordinates identify the desired "_right" group vertex
                        right_v_co[0] *= -1
                        # <<<
                        # Get the closest point on the right
                        v, right_v_idx, dist = kd.find(right_v_co)
                        # >>>>
                        # Get the index of the vertex at these new "_right" group coordinates, if it exists in the active object mesh
                        #right_v_idx = get_vertex_index(ao_data_vertices, right_v_co)
                        # If the desired vertex exists, append its index to to the new "_right" vertex group list of vertex indices
                        if right_v_idx != -1:
                            right_vg_indices.append(right_v_idx)
            print("Right Vertex Group Name: " + right_vg_name)
            print("Right Vertex Group Vertices:")
            print(right_vg_indices)
            for right_vg_index in right_vg_indices:
                right_vg.add([right_vg_index], 1.0, 'ADD')

# Set the active object
obj = bpy.context.active_object
bpy.ops.object.mode_set(mode='OBJECT', toggle=True)

# Call the function to copy and flip vertices
find_and_group_mirrored_vertices(obj)
$\endgroup$
4
  • $\begingroup$ For performence, you should use a KDTree instead of the loop inside get_vertex_index. $\endgroup$
    – lemon
    Jan 21 at 11:26
  • $\begingroup$ Thanks. Am I doing that in this line: right_v_co = aov.co.copy()? By making that change, it is now now not changing the left side vertices, which is good, but it is not finding the correct right side, mirrored vertices. I've selected the left and right knee circumference vertices and output their coordinates and it appears to be symmetrical so I'm not sure why this change wouldn't identify them since the only difference is the x-coordinate multiplied by -1, which would be correct. $\endgroup$ Jan 21 at 11:52
  • $\begingroup$ Try to use a KDTree and find the nearest (in opposite X). docs.blender.org/api/current/… . Or share your file, so that some here could have a look. $\endgroup$
    – lemon
    Jan 21 at 12:06
  • $\begingroup$ Ok, I will try to use KDTree and see how far I get. $\endgroup$ Jan 21 at 12:33

You must log in to answer this question.

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