- edited description
An update to "morphing armature" to support the new mesh morphing code from #749
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)
-
reporter -
reporter - edited description
-
reporter - edited description
-
repo owner Your code has been incorporated in the latest commit.
-
reporter - changed status to resolved
- Log in to comment