- edited description
Add Mannequin Improvements
I use a modified version of your mannequin for my own purposes, and one thing I like to do is retain data from the original mesh, such as materials and vertex groups (to re-add modifiers).
I was fixing a bug, that’s in your version as well, wherein a mesh has a vertex group and the rig has a bone with the name BUT the bone isn’t a deformation bone. (I use this kind of bone/weight for other modifiers, such as warp modifier)
I was then fiddling with something else, and went back to your code to compare and found you added code for shape keys.
I then remembered that was an issue that I just chose to workaround.
My changes for your mannequin code are 1) fix the deform bone thing mentioned above, 2) parent from the rig’s base pose, not current pose, 3) use the mesh’s final verts (including all shape keys and modifiers), 4) use the materials from the original faces.
-
Change the following:
for vgrp in ob.vertex_groups:
____if vgrp.name in rig.data.bones:
________majors[vgrp.index] = []
____else:
________skip.append(vgrp.index)
to:
for vgrp in ob.vertex_groups:
____bone = rig.data.bones.get(vgrp.name)
____if bone and bone.use_deform:
________majors[vgrp.index] = []
____else:
________skip.append(vgrp.index)
-
At the start of the function (or before calling the function), put:
pose = rig.data.pose_position
rig.data.pose_position = 'REST'
and at the end of the function or after calling the function, put:
rig.data.pose_position = pose
I believe it’s only important to put it before updating the scene but that was probably just my preference.
-
Before rescanning the vertex groups, prior to creating the nobs, insert:
mob = ob.evaluated_get(context.evaluated_depsgraph_get())
and replace:
verts = [obverts[vn].co-head for vn in nverts]
with:
verts = [mob.data.vertices[vn].co - head for vn in nverts]
This generates a temporary object, who’s mesh’s vertices are at their global value, rather than local combined with shape keys and modifier deformations.
Note that everything is applied, so if a Subdivision modifier is on, then the resulting visual transforms would be a smoother mesh -
Move the material creation to after each mannequin is created. Place the following after
mob
:mat = None
face_mats = dict()
if ob.data.materials:
____for rnd in range(3, 8):
________face_mats[rnd] = dict()
________for f in mob.data.polygons:
____________face_mats[rnd][tuple(round(x, rnd) for x in f.normal)] = ob.material_slots[f.material_index].material
After creating each mannequin mesh
me
add the following:if face_mats:
____for b in me.polygons:
________# mat_i = face_mats.get((b.area, b.center[:], b.normal[:]))
________for rnd in reversed(range(3, 8)):
____________fmat = face_mats[rnd].get(tuple(round(x, rnd) for x in b.normal))
____________if fmat:
________________break
________else:
____________"Insert code to generate the mannequin material"
____________fmat = mat
________if fmat.name not in me.materials:
____________me.materials.append(fmat)
________for (i, mat_i) in enumerate(nob.material_slots):
____________if mat_i.material == fmat:
________________b.material_index = i
________________break
What the first code does, is stores the original mesh’s faces using 5 simplified versions of their face normals (which here, functionally behaves as their position).
The second code, tries to find those stored positions to get the saved material, and if it fails to do so, then it defaults to the generated material.- This requires the multiple fallbacks (preferably level 7 or 6), because despite the final faces being the same position, their position datas are fractionally different (like
0.300300596
to0.300300563
). -
This retains the materials, and their viewport color but in order to view the textures, an additional step is required.
After all the meshes are generated, I run a second operator utilizingbpy.ops.object.data_transfer()
, to transfer datas from the original mesh to the mannequin, which includes UVs and etc. The relevant code from it is here:transfer = bpy.ops.object.data_transfer
mesh = context.object.data
for face in mesh.polygons:
____if face.use_smooth:
________bpy.ops.object.shade_smooth()
________break
if mesh.use_auto_smooth:
____transfer(data_type='CUSTOM_NORMAL')
____for o in context.selected_objects:
________if o.type == 'MESH':
____________o.data.use_auto_smooth = True
if context.object.vertex_groups:
____transfer(data_type='VGROUP_WEIGHTS', layers_select_src='ALL', layers_select_dst='NAME')
if mesh.vertex_colors:
____transfer(data_type='VCOL', layers_select_src='ALL', layers_select_dst='NAME')
if mesh.uv_layers:
____transfer(data_type='UV', layers_select_src='ALL', layers_select_dst='NAME')
Change _
into tabs/spaces, because Bitbucket keeps horribly reformatting my code.
Comments (13)
-
reporter -
reporter - edited description
-
reporter - edited description
-
reporter - edited description
-
reporter - edited description
-
repo owner Your code has been incorporated in the latest commit. The various transfer steps have been made optional, because I don’t think that everyone will want to transfer everything, especially not the vertex groups. Perhaps there should be an option to only transfer vertex groups with no associated bones.
-
reporter You could store the vertex groups to a separate container at the same time time as storing the materials. You’d just then need to re-assign them, instead of using the transfer operator.
The thing is at the time, I was mostly just setting up a macro, wouldn’t have considered it anyway. Now, I have a clear idea of how to do so, and just don’t feel like making it. The way you do it, is you create a
dict
of vertex groups, with alist
of 1) weight value and 2)list
of of vertex indices.You then do something like: 1)
for (vg_name, vg_data) in vg_dict.items(): vg = ob.vertex_groups(name=vg_name)
,for (weight, vg_indices) in vg_data: vg.add(vg_indices, weight, 'REPLACE')
You would have to run that add weight for each mesh separately, and also have the correct index from the original mesh, or do a similar position approximation that I do. (
vg_indices
are from the mannequin; weight is from the original mesh vertex)But it’s possible that repeating
.add
is slower than using the operator, I just don’t care enough to write the real code to find out, as honestly the only reason I’d want it is to also remove 0-weight vertex groups (and unused, if the operator really does transfer everything).
Of course you could also still run the op, then retroactively remove the vertex groups. I’d imagine that’d be far easier, and if manually creating them would be slower, then doing that would also be faster.
-
reporter Hey, I was looking at your commit, and you left my note in there.
"Insert code to generate the mannequin material”
was a note to you, to do that there.Because, I tweaked the string and don’t use the “Skin” part.
Mine is:
from random import random
mat = bpy.data.materials.new(ob.name + 'Mannequin')
mat.diffuse_color[0:3] = (random(), random(), random())
Yours is:
mat = bpy.data.materials.new("%sMannequin" % ob.name)
mat.diffuse_color[0:3] = (random(), random(), random())
for omat in ob.data.materials:
____mat.diffuse_color = omat.diffuse_color
____if getSkinMaterial(omat) == 'Skin':
________break
-
repo owner The last issues have now been addressed.
As you suspected, repeatedly adding vertex weights was much slower than using the operator, at least for me. But my code used a lot of loops and tests in python. Anyway, I kept the transfer operator and pruned the vertex groups afterwards. That was easy, because code for pruning vertex groups already existed, and I think it is rather fast since it uses numpy.
There is now an option to ignore vertex groups associated with bones. I don’t understand why anybody would want to transfer those vertex groups, since the whole point with the mannequins is to get rid of the slow armature modifier.
-
reporter Initially I did it for faces and the….pelvis….
The headsaver code you use, relies on having specific bones, those being Daz.
I think at some point, I had the idea of using isolated deforming mannequins. Like when working on around a particular body part, those mannequins deform, and the rest don’t to have both performance and organic deformation. Obviously I never did anything to make that a reality but it was a thought
-
reporter In your commit, after
if face_mats
…. (when adding material to nobs), you put anelse
clause, to default to a mannequin material. line#1537-1539I was doing that originally, then I realized that the only time the
else
will occur, is when the object doesn’t already use a material. In which case, I didn’t want to add one anyway. -
repo owner The code in lines 1537-1539 was removed some time ago (commit e2033a6).
-
reporter - changed status to resolved
- Log in to comment