Add Mannequin Improvements

Issue #756 resolved
ViSlArT created an issue

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.

  1. Change the following:

    1. for vgrp in ob.vertex_groups:
      ____if vgrp.name in rig.data.bones:
      ________majors[vgrp.index] = []
      ____else:
      ________skip.append(vgrp.index)

    to:

    1. 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)
  2. At the start of the function (or before calling the function), put:

    1. pose = rig.data.pose_position
      rig.data.pose_position = 'REST'

    and at the end of the function or after calling the function, put:

    1. 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.

  3. Before rescanning the vertex groups, prior to creating the nobs, insert:

    1. mob = ob.evaluated_get(context.evaluated_depsgraph_get())

    and replace:

    1. verts = [obverts[vn].co-head for vn in nverts]

    with:

    1. 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

  4. Move the material creation to after each mannequin is created. Place the following after mob:

    1. 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:

    1. 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.

    1. 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 to 0.300300563).
    2. 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 utilizing bpy.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)

  1. Thomas Larsson 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.

  2. ViSlArT 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 a list 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.

  3. ViSlArT 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

  4. Thomas Larsson 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.

  5. ViSlArT 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 😀

  6. ViSlArT reporter

    In your commit, after if face_mats …. (when adding material to nobs), you put an else clause, to default to a mannequin material. line #1537-1539

    I 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.

  7. Log in to comment