An update to "morphing armature" to support the new mesh morphing code from #749

Issue #754 resolved
Suttisak Denduangchai created an issue

I’ve updated the armature morphing algorithm to better support the new morphing code from #749.

Right now, the current code will produce the result like this.

The updated algorithm will produce the result like this.

Notice the difference of bone position in Futalicious mesh which is extremely important for Futalicious morphs to be working correctly. (Some scaling morphs such as Scale All 1, 2 are still not quite working but I can use alternative morph + pose mode). You may not notice this with the current code if you don’t use Full Body Morph that change Genesis 8.1 mesh a lot such as Brooke 8.1 or Beverly for Brooke 8.1

daz.py

Registration of new properties

#------------------------------------------------------------------
#   Geograft-scaling morph armature support
#------------------------------------------------------------------

class AffectedBone(bpy.types.PropertyGroup):
    name: bpy.props.StringProperty(name="Bone name",  default="Unknown")
    weight: bpy.props.FloatProperty(name="Average Rigidty Map Weight",  default=0)

class ShapekeyScaleFactor(bpy.types.PropertyGroup):
    name: bpy.props.StringProperty(name="Shapekey name",  default="Unknown")
    shapekey_center_coord: bpy.props.FloatVectorProperty(name="Center of shapekey shape Rigidity Reference vertices",default=Vector((0,0,0)),subtype="XYZ")
    scale: bpy.props.FloatVectorProperty(name="Scale Factor", description="Scale factor is calculated when transfer shapekey to the geograft that has defined Rigidity Group",subtype="MATRIX",size=9)

class RigidityGroupScaleFactor(bpy.types.PropertyGroup):
    name: bpy.props.StringProperty(name="Name of object (eg. Geograft) that Rigidity Group originaly came from",  default="Unknown")
    base_center_coord: bpy.props.FloatVectorProperty(name="Center of basis shape Rigidity Reference vertices",default=Vector((0,0,0)),subtype="XYZ")
    shapekeys: bpy.props.CollectionProperty(type=ShapekeyScaleFactor)
    affected_bones: bpy.props.CollectionProperty(type=AffectedBone)
def register():    
    # Code omitted for brevity
    bpy.utils.register_class(AffectedBone)
    bpy.utils.register_class(ShapekeyScaleFactor)
    bpy.utils.register_class(RigidityGroupScaleFactor)

    bpy.types.Armature.RigidityGroupScaleFactors = bpy.props.CollectionProperty(type=RigidityGroupScaleFactor)

transfer.py

Center coordinate Vector((x,y,z)) of vertices of the basis shape and shapekey of Geograft that is in Rigidity Group Reference, scale factor, name of bones of the geograft (I get these by checking the vertex group name of Geograft with bone names in the armature), affected weight of each bone (Calculated by weight-average of rigidity map weight of Geograft by weight of bone vertex group weight). All of this will be saved to above properties.

    def correctForRigidity(self, ob, skey):
        from mathutils import Matrix
        for rgroup in ob.data.DazRigidityGroups:
            rotmode = rgroup.rotation_mode
            maskverts = [elt.a for elt in rgroup.mask_vertices]
            refverts = [elt.a for elt in rgroup.reference_vertices]
            nrefverts = len(refverts)

            if nrefverts == 0:
                continue

            if rotmode != "none":
                raise RuntimeError("Not yet implemented: Rigidity rotmode = %s" % rotmode)

            scalemodes = rgroup.scale_modes.split(" ")

            base_coords = [ob.data.vertices[vn].co for vn in refverts]
            shapekey_coords = [skey.data[vn].co for vn in refverts]

            # I think Daz3d use Singular Value Decomposition to determine which X,Y,Z scaling between shapekey_coords and base_coords
            # https://www.daz3d.com/forums/discussion/comment/636426/
            # https://gregorygundersen.com/blog/2018/12/10/svd/

            base_center_coords = np.average(base_coords, axis=0)
            shapekey_center_coords = np.average(shapekey_coords, axis=0)

            # Transfrom Base Coordinate to be relative to its center
            base_coords_relative_to_base_center_coords = base_coords - base_center_coords
            # Singular value decomposition
            S1= np.linalg.svd(base_coords_relative_to_base_center_coords, compute_uv=False)
            # Transfrom Shapekey Coordinate to be relative to its center
            shapekey_coords_relative_to_shapekey_center_coords = shapekey_coords - shapekey_center_coords
            # Singular value decomposition
            S2= np.linalg.svd(shapekey_coords_relative_to_shapekey_center_coords, compute_uv=False)
            # U matrix is average coordinates of polygon, S is matrix is how coordinates dilate and reflex. The dilated and reflexed shape (without rotation) is U cross S
            scale_between_shapekey_and_base_averagecoords = S2/S1

            refverts_base_dimension = [["X",S1[0],scale_between_shapekey_and_base_averagecoords[0]],["Y",S1[1],scale_between_shapekey_and_base_averagecoords[1]],["Z",S1[2],scale_between_shapekey_and_base_averagecoords[2]]]
            # Sort from max dimension to min dimension to determine Primary (scaling of max dimension) to Tertiary (scaling of min dimension) scale mode
            # ex. [["Y",10,1.1],["X",5,1.2],["Z",2,1.05]]
            refverts_base_dimension.sort(key=lambda x: -x[1])

            # Determine First - Thrid axis by target object (eg. Geograft) dimensions
            target_dimension= [["X",ob.dimensions.x,1],["Y",ob.dimensions.y,1],["Z",ob.dimensions.z,1]]
            target_dimension.sort(key=lambda x: -x[1])

            for n,smode in enumerate(scalemodes): # Scale mode of First to Third axis which is defined in Rigidity group editor in Daz3d
                if smode == "primary":
                    target_dimension[n][2] = refverts_base_dimension[0][2]
                elif smode == "secondary":
                    target_dimension[n][2] = refverts_base_dimension[1][2]
                elif smode == "tertiary":
                    target_dimension[n][2] = refverts_base_dimension[2][2]
                # No-scale No need to reassign 1 again
            target_dimension.sort(key=lambda x: x[0])
            smat = Matrix.Identity(3)
            base_center_vector = Vector((0,0,0))
            shapekey_center_vector = Vector((0,0,0))
            for n in range(3):
                base_center_vector[n] = base_center_coords[n]
                shapekey_center_vector[n] = shapekey_center_coords[n]
            for n in range(3):
                smat[n][n]= target_dimension[n][2]
            if "Rigidity" in ob.vertex_groups.keys():
                rigidity_map_vertex_group_index = ob.vertex_groups["Rigidity"].index
                for n,vn in enumerate(maskverts): # Called Rigidity Participant Vertex in Daz3D
                    for v in ob.data.vertices:
                        if(v.index == vn):
                            for g in v.groups:
                                if g.group == rigidity_map_vertex_group_index:
                                    # Max Rigidity (Rigidity=1) coordinate
                                    max_rigidity_coordinate = (smat @ (ob.data.vertices[vn].co - base_center_vector)) + shapekey_center_vector
                                    # Min Rigidity (Rigidity=0) coordinate
                                    min_rididity_coordinate = skey.data[vn].co
                                    # Mix both coordinate using Rigidity Weight Map
                                    skey.data[vn].co = (max_rigidity_coordinate * g.weight) + ((1-g.weight)*min_rididity_coordinate)
                                    
                # Save RigidityGroupScaleFactor to Armature
                parent = ob.parent
                while parent:
                    if(parent.type == "ARMATURE"):
                        armature = parent
                        break
                    parent = parent.parent
                if armature:
                    if ob.name in armature.data.RigidityGroupScaleFactors:
                        rigidity_group = armature.data.RigidityGroupScaleFactors[ob.name]
                    else:
                        rigidity_group = armature.data.RigidityGroupScaleFactors.add()
                        rigidity_group.name = ob.name
                        rigidity_group.base_center_coord = base_center_vector
                    
                    rigidity_group.affected_bones.clear()
                    affectedbones = [vx for vx in ob.vertex_groups.keys() if vx in armature.data.bones];
                    for affectedbonename in affectedbones:
                        newbonename = rigidity_group.affected_bones.add()
                        newbonename.name = affectedbonename
                        affectedbone_vertex_group_index = ob.vertex_groups[affectedbonename].index
                        vertex_group_weight = 0
                        rigidity_map_weight_sum = 0
                        for v in ob.data.vertices:
                            vertex_enveloping_bone_map = None
                            rigidity_map = None
                            for g in v.groups:
                                if g.group == affectedbone_vertex_group_index:
                                    vertex_enveloping_bone_map = g
                                if g.group == rigidity_map_vertex_group_index:
                                    rigidity_map = g
                            if(vertex_enveloping_bone_map and rigidity_map):
                                rigidity_map_weight_sum = rigidity_map_weight_sum + vertex_enveloping_bone_map.weight*rigidity_map.weight
                                vertex_group_weight = vertex_group_weight + vertex_enveloping_bone_map.weight
                        if(vertex_group_weight>0):
                            newbonename.weight = rigidity_map_weight_sum/vertex_group_weight
                        
                    if skey.name in rigidity_group.shapekeys:
                        shapekey_scalefactor = rigidity_group.shapekeys[skey.name]
                    else:
                        shapekey_scalefactor = rigidity_group.shapekeys.add()
                        shapekey_scalefactor.name = skey.name

                    shapekey_scalefactor.shapekey_center_coord = shapekey_center_vector
                    shapekey_scalefactor.scale = [smat[j][i] for i in range(len(smat)) for j in range(len(smat))]

                    print("Save scale factor for"," ".join(affectedbones))

morph_armature.py

The bones that are not affected by rigidity group. (Ex. only the bones of Futalicious mesh are affected by its rigidity group) will be process like the current code. But the bones that are affected by rigidity group will be processed differently.

def getEditBones(rig):
    def d2b90(v):
        return scale*Vector((v[0], -v[2], v[1]))

    def isOutlier(vec):
        return (vec[0] == -1 and vec[1] == -1 and vec[2] == -1)

    scale = rig.DazScale
    heads = {}
    tails = {}
    offsets = {}
    for pb in rig.pose.bones:
        if isOutlier(pb.DazHeadLocal):
            pb.DazHeadLocal = pb.bone.head_local
        if isOutlier(pb.DazTailLocal):
            pb.DazTailLocal = pb.bone.tail_local
        heads[pb.name] = Vector(pb.DazHeadLocal)
        tails[pb.name] = Vector(pb.DazTailLocal)
        offsets[pb.name] = d2b90(pb.HdOffset)
    for pb in rig.pose.bones:
        if pb.name[-5:] == "(drv)":
            bname = pb.name[:-5]
            finname = "%s(fin)" % bname
            heads[bname] = heads[finname] = heads[pb.name]
            tails[bname] = tails[finname] = tails[pb.name]
            offsets[bname] = offsets[finname] = offsets[pb.name]

    processed_bonenames = []
    for ob in bpy.data.objects:
        if(ob.parent == rig and "Mesh" in ob.name):
            GenesisMainMesh = ob
            break
    if GenesisMainMesh:
        for rigidity_group in rig.data.RigidityGroupScaleFactors:
            base_center_coord = rigidity_group.base_center_coord
            combined_all_used_shapekeys_center_coord = Vector(base_center_coord) # Copy 
            combined_all_used_shapekeys_scale_difference_from_baseshape = Matrix(([0,0,0],[0,0,0],[0,0,0]))
            for shapekey_scale_factor in rigidity_group.shapekeys:
                if shapekey_scale_factor.name in GenesisMainMesh.data.shape_keys.key_blocks.keys():
                    shapekey = GenesisMainMesh.data.shape_keys.key_blocks[shapekey_scale_factor.name]
                    if shapekey.value != 0:
                        shapekey_center_coord = shapekey_scale_factor.shapekey_center_coord
                        scale = shapekey_scale_factor.scale
                        for n in range(3):
                           combined_all_used_shapekeys_scale_difference_from_baseshape[n][n] =((shapekey.value * scale[n][n] + (1-shapekey.value) * 1)-1) + combined_all_used_shapekeys_scale_difference_from_baseshape[n][n]
                           combined_all_used_shapekeys_center_coord[n] = (shapekey.value * (shapekey_center_coord[n] - base_center_coord[n])) + combined_all_used_shapekeys_center_coord[n]
            combined_all_used_shapekeys_scale_difference_from_baseshape = combined_all_used_shapekeys_scale_difference_from_baseshape + Matrix.Identity(3)
            
            for bone in rigidity_group.affected_bones:
                parent = rig.pose.bones[bone.name].parent
                while parent and parent.bone.DazExtraBone:
                    parent = parent.parent
                heads[bone.name] = (bone.weight * ((combined_all_used_shapekeys_scale_difference_from_baseshape @ (heads[bone.name]-base_center_coord))+combined_all_used_shapekeys_center_coord)) + ((1-bone.weight)*(heads[bone.name]+ offsets[parent.name]))
                tails[bone.name] = (bone.weight * ((combined_all_used_shapekeys_scale_difference_from_baseshape @ (tails[bone.name]-base_center_coord))+combined_all_used_shapekeys_center_coord)) + ((1-bone.weight)*(tails[bone.name]+ offsets[parent.name]))
                offsets[bone.name] = (bone.weight * combined_all_used_shapekeys_scale_difference_from_baseshape @ offsets[bone.name]) + ((1-bone.weight)*offsets[bone.name])
                finname = "%s(fin)" % bone.name
                drvname = "%s(drv)" % bone.name
                heads[drvname] = heads[finname] = heads[bone.name]
                tails[drvname] = tails[finname] = tails[bone.name]
                offsets[drvname] = offsets[finname] = offsets[bone.name]                
                processed_bonenames.append(bone.name)
        for pb in rig.pose.bones:
            if pb.bone.DazExtraBone and pb.name not in processed_bonenames :
                parent = pb.parent
                while parent and parent.bone.DazExtraBone:
                    parent = parent.parent
                if parent:
                    offsets[pb.name] = offsets[pb.name] + offsets[parent.name]
    return heads, tails, offsets

Comments (5)

  1. Log in to comment