I’ve got a couple of things I want to do with Python scripts, and I’m not sure how best to go about looking to see if such scripts already exist somewhere. They don’t seem to be in the official release, anyway. If nobody’s written them, I will write them myself and post them, but if they have been written, I’d like to know.
Basically both of the scripts are to help with rigging and weight painting. One thing I want to do is identify and fix anomalously weighted single vertices. If a single vertex is higher or lower in weight than all of the surrounding connected vertices, I’d assume it’s wrongly weighted and reweight it to the average of the surrounding connected vertices.
The other thing I’d like to do is output a list of bones which have more than one patch of non-zero weights associated with them. I assume that each bone has an area of non-zero weights surrounded by zero weights. If a bone has two patches, which are separated by zero weighted vertices, there’s a good chance that one of the patches shouldn’t actually be there. Much better to be able to output a list of culprit bones than to have to check every single bone in your armature for some weird unwanted deformation.
If nobody’s written scripts that do these things I’ll get started.
Thanks very much for the pointers. I’ve seen Cambo’s website, and I don’t see quite what I have in mind. The normalizing script which ondrew pointed to (and, it seems, pildanovak wrote…) looks really useful and I’ve bookmarked the site. I’ll definitely be checking that out more closely, but it also seems (from the description) slightly different than either of the scripts I have in mind… I think it might work well in conjunction with them. I’ll let you all know if I make any progress.
I made a little script to average weights when Blender didn’t care to assign weights to new vertices when subdividing or cutting (2.34). It is old and maybe can be made simpler.
I still use it. Now that Blender allows you to edit your mesh when it is deformed by an armature, the effect is immediate.
I can tell you to consider the distance to the connected vertices, don’t just average. Also set a minimum value to actually add a vertex to a group. Adding a vertex with a weight of 0.01 is not useful at all.
I can tell you to consider the distance to the connected vertices, don’t just average.
Good point.
Also set a minimum value to actually add a vertex to a group. Adding a vertex with a weight of 0.01 is not useful at all.
I wrote another script sometime back which purges 0 weight verts from groups, and I had a threshold value so that I could include very small weights as well. I think I’d probably run a pass with that script to clear out such small weights, if they arose.
Here’s the first one. It seems to be doing what I want it too, but I haven’t tested it thoroughly so please be sure to back up your work before trying it, and if shows any unexpected behavior please let me know.
Also, comments on the Python are welcome. I don’t make any claims to being a good programmer, but I’d always like to improve.
#!BPY
"""
Name: 'Reweight anomalous verts'
Blender: 241
Group: 'Mesh'
Tooltip: 'Reweights anomalously weighted vertices.'
"""
##reweightanom.py - Goes through each vertex group, and finds
##single verts which are higher weighted or lower weighted than all
##their surrounding verts. If such verts are found, they are reweighted
##as the weighted average of weights/normalized distances of the surrounding verts.
##(.001 is added to all distances to eliminate division by zero in
##the case of doubled vertices)
##Open this script in your text window. Select the mesh
##object you wish to work on. Do alt-p to run the script.
##The script will output a list of reweighted verts along with their
##respective vertex group and the original and new weights.
import Blender
from Blender import Mesh
from math import sqrt
object = Blender.Object.GetSelected()
replace = Blender.Mesh.AssignModes.REPLACE
if (len(object) == 1) & (object[0].getType() == 'Mesh'):
mymesh = object[0]
mymeshdata = mymesh.getData(mesh=True)
groups = mymeshdata.getVertGroupNames()
connected = dict()
g_v_weights = dict()
#collect the neighbors for each vert
for v in mymeshdata.verts:
connected[v.index] = dict()
for e in mymeshdata.edges:
vector = e.v1.co - e.v2.co
edgelength = sqrt(vector[0]**2+vector[1]**2+vector[2]**2)
edgelength += .001
if not connected[e.v1.index].has_key(e.v2.index):
connected[e.v1.index][e.v2.index] = edgelength
if not connected[e.v2.index].has_key(e.v1.index):
connected[e.v2.index][e.v1.index] = edgelength
#collect the weights for each vert in each group
for vgroup in groups:
g_v_weights[vgroup] = dict()
gverts = mymeshdata.getVertsFromGroup(vgroup,1,)
for gv in gverts:
g_v_weights[vgroup][gv[0]] = gv[1]
#for each group, go through all the vertices and and fix
for vgroup in groups:
for v in mymeshdata.verts:
neighbors_totalweight = 0
neighbor_weights = dict()
over_or_under = 0
if g_v_weights[vgroup].has_key(v.index):
thisweight = g_v_weights[vgroup][v.index]
else:
thisweight = 0
for neighbor in connected[v.index].keys():
if g_v_weights[vgroup].has_key(neighbor):
divisor = connected[v.index][neighbor]/len(connected[v.index].keys())
neighbor_weights[neighbor] = g_v_weights[vgroup][neighbor]
neighbors_totalweight = neighbors_totalweight + neighbor_weights[neighbor]
else:
neighbor_weights[neighbor] = 0
if thisweight == neighbor_weights[neighbor]:
over_or_under = 0
break
elif thisweight < neighbor_weights[neighbor]:
if (over_or_under == 0 or over_or_under == -1):
over_or_under = -1
elif (over_or_under == 1):
over_or_under = 0
break
elif thisweight > neighbor_weights[neighbor]:
if (over_or_under == 0 or over_or_under == 1):
over_or_under = 1
elif (over_or_under == -1):
over_or_under = 0
break
if over_or_under == 0:
continue
else:
avg = neighbors_totalweight/len(neighbor_weights.keys())
mymeshdata.assignVertsToGroup(vgroup,[v.index],avg,replace)
print "Vertex index ", v.index ," reweighted from ", thisweight, " to ", avg, " in group ", vgroup
else:
print "You must select a single mesh object"
but the indention from after the line that says groups = … is wonky.
I’m not sure if it’s the forum display or not, actually. I had a lot of trouble with indentation writing in the Blender text editor. I would often make a small change, comment or uncomment a line, and find that the whole block of text became a syntax error because some weird invisible thing happened with the indentation. I’d look at the code in IDLE and it would be completely wack, then I’d fix it in IDLE and reload it into blender and it would run.
I’ve never had this problem before, but it did cause me to think that the indentation stuff in Python actually may not be such a great design idea, because it makes the code very sensitive to quirks in the specific editor you’re using. I always used to like the indentation-based control structures. Maybe Blender just needs a better python editor.
I like SPE, but the spe.blend file you’re supposed to use to start SPE from Blender only worked once for me… Go figure. Subsequent times I tried to start SPE from Blender did not succeed. Just got “starting application” and then no application…
The other one… All the same caveats apply. Same method of calling it too, must have a mesh object selected in object mode.
It occurred to me that what would be really cool with this one would be to have a graphical interface listing each bone in the list, with its associated patches and radio buttons to select the patch, and when you selected the patch it lit up, so you could see where the bone was having an effect. And then an option to completely remove the patch. I ran it on a model I’m working with and found lots of patches for many bones, for example three or four non-zero patches for a finger bone. Obviously these are very low weighted patches that I can’t see in weight paint mode, or they are concealed in some cranny, so hard to reach. But even very low weighted bad patches add up and deform the mesh in unwanted ways. It’d be nice to light them up and just remove them entirely.
I don’t really know how to do a nice python gui in Blender just yet, though, but I’ll put it on my list.
#!BPY
"""
Name: 'Find multiple weight patches'
Blender: 241
Group: 'Mesh'
Tooltip: 'Finds and lists bones which have more than one associated patch of
non-zero weight paint separated by zero weighted vertices.'
"""
##
import sys
import Blender
from Blender import Mesh
from Blender import Armature as A
def collect_non_zeros(array,vert,connecteddict,weightsdict,countverts):
if (vert in weightsdict.keys() and weightsdict[vert] > 0):
countverts.extend([vert])
array.extend([vert])
for neighbor in connected[vert].keys():
if neighbor not in countverts:
collect_non_zeros(array,neighbor,connecteddict,weightsdict,countverts)
bonenames = []
arms = A.Get()
for arm in arms.values():
bonenames.extend(arm.bones.keys())
object = Blender.Object.GetSelected()
replace = Blender.Mesh.AssignModes.REPLACE
if (len(object) == 1) & (object[0].getType() == 'Mesh'):
mymesh = object[0]
mymeshdata = mymesh.getData(mesh=True)
groups = mymeshdata.getVertGroupNames()
connected = dict()
bone_group_patches = dict()
g_v_weights = dict()
#collect the neighbors for each vert
for v in mymeshdata.verts:
connected[v.index] = dict()
for e in mymeshdata.edges:
if not connected[e.v1.index].has_key(e.v2.index):
connected[e.v1.index][e.v2.index] = 1
if not connected[e.v2.index].has_key(e.v1.index):
connected[e.v2.index][e.v1.index] = 1
#collect the weights for each vert in each group
for vgroup in groups:
if vgroup in bonenames:
g_v_weights[vgroup] = dict()
gverts = mymeshdata.getVertsFromGroup(vgroup,1,)
for gv in gverts:
g_v_weights[vgroup][gv[0]] = gv[1]
for vgroup in groups:
if vgroup in bonenames:
countedverts = []
bone_group_patches[vgroup] = []
gverts = mymeshdata.getVertsFromGroup(vgroup,1,)
for gv in gverts:
if gv[0] not in countedverts:
bone_group_patches[vgroup].append([])
collect_non_zeros(bone_group_patches[vgroup][-1], gv[0],connected, g_v_weights[vgroup],countedverts)
if bone_group_patches[vgroup][-1] == []:
bone_group_patches[vgroup].pop()
if len(bone_group_patches[vgroup]) > 1:
print vgroup, ": ", len(bone_group_patches[vgroup]) , "patches found"
else:
print "You must select a single mesh object"
I’ve found that often, the Blender text editor stuffs up indention (I suspect that it might be doing some strange tabs to spaces stuff). I would recommend finding an editor, using it exclusively to edit the file. Then load it into IDLE and do a check for bad indention. If things are wrong, fix in IDLE.
The different ways that different editors handle indention can be annoying at times.