Morphed mesh is different than Daz Studio
First, salute to you Thomas and the team for the fantastic software. Your new “Morphing armature” feature help me move my workflow from Daz to Blender a lot easier.
Yet, I have some problem. I try to morph many charactors (Victoria 8.1, Brooke 8.1, Beverly who is based on Brook8.1, August 8.1). The result meshed where the geografts (Futalicious, Breatsacular, Full Monty BBQ) attaching to are distorted and different from Daz Studio.
I currently use import-daz 1.61.0654 commit 4ae4b59
, Blender 3.0 Alpha (20 October 2021 build), Daz 4.15.1.72 build
M workflow are
- Export save the duf file at Base resolution and export dbz file.
- Import the duf file using Easy Import
- Import morph and then transfer the shape keys.
Comments (21)
-
repo owner -
reporter Thanks, @Thomas. But, I’ve already done step 1-5 but the problem is still the same. I start to suspect is it somehow related to rigidity group when I tick ignore rigidity group the area where geograft is attaching to look better but the geograft mesh is then distorted.
-
You say you use blender 3.0 that’s not tested yet so you have to expect bugs there. Does it work with blender 2.93 ?
-
reporter I also test in blender 2.93. It’s also not working. I suspect the problem maybe in
def correctForRigidity(self, ob, skey):
in transfer.py -
reporter I’m trying to fix it. I notice a mistake in
correctForRigidity
method. I’ve check in Daz Studio, if the geograft has rigidity map (such as Breastacular) but not rigidity group. The rigidity effect will not apply in Daz studio. So I changecorrectForRigidity
to checking theDazRigidityGroups
first.def correctForRigidity(self, ob, skey): from mathutils import Matrix for rgroup in ob.data.DazRigidityGroups: rotmode = rgroup.rotation_mode scalemodes = rgroup.scale_modes.split(" ") 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) xcoords = [ob.data.vertices[vn].co for vn in refverts] ycoords = [skey.data[vn].co for vn in refverts] if "Rigidity" in ob.vertex_groups.keys(): idx = ob.vertex_groups["Rigidity"].index for v in ob.data.vertices: if(v.index in refverts and not(v.index in maskverts)): continue for g in v.groups: if g.group == idx: x = skey.data[v.index] x.co = v.co + (1-g.weight)*(x.co - v.co) xsum = Vector((0,0,0)) ysum = Vector((0,0,0)) for co in xcoords: xsum += co for co in ycoords: ysum += co xcenter = xsum/nrefverts ycenter = ysum/nrefverts xdim = ydim = 0 for n in range(3): xs = [abs(co[n]-xcenter[n]) for co in xcoords] ys = [abs(co[n]-ycenter[n]) for co in ycoords] xdim += sum(xs) ydim += sum(ys) if xdim == 0 or ydim == 0: print("Rigidity division by zero") continue scale = ydim/xdim smat = Matrix.Identity(3) for n,smode in enumerate(scalemodes): #if smode == "primary": smat[n][n] = scale for n,vn in enumerate(maskverts):#[maskvert for maskvert in maskverts if maskvert not in refverts]): skey.data[vn].co = smat @ (ob.data.vertices[vn].co - xcenter) + ycenter
The Breastacular shape after morphed with Beverly is now comparable to Daz. But there is still a problem with Futalicous. I’m still fixing it.
-
reporter I have change the
correctForRigidity
method to be like this. The result is much better now. I’ve separate the scaling factor to 3-dimension. I still try to figure the Axis scaling (in Rigidity Group Editor) in Daz3d. Right now, I just scaling x,y,z axis respectively but I think to get corrected morped mesh I need to understand what are “Primary” “Secondary” “Tertiary” scaling mode.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] xsum = Vector((0,0,0)) ysum = Vector((0,0,0)) for co in base_coords: xsum += co for co in shapekey_coords: ysum += co xcenter = xsum/nrefverts ycenter = ysum/nrefverts scale = Vector((1,1,1)) for n in range(3): xdim = ydim = 0 xs = [abs(co[n]-xcenter[n]) for co in base_coords] ys = [abs(co[n]-ycenter[n]) for co in shapekey_coords] xdim += sum(xs) ydim += sum(ys) if xdim == 0 or ydim == 0: print("Rigidity division by zero") continue scale[n] = ydim/xdim smat = Matrix.Identity(3) for n,smode in enumerate(scalemodes): #if smode == "primary": smat[n][n] = scale[n]-1 if "Rigidity" in ob.vertex_groups.keys(): idx = ob.vertex_groups["Rigidity"].index for n,vn in enumerate(maskverts): for v in ob.data.vertices: if(v.index == vn): for g in v.groups: if g.group == idx: skey.data[vn].co = (smat*(1-g.weight) @ (ob.data.vertices[vn].co - xcenter)) + (ob.data.vertices[vn].co - xcenter) + ycenter
-
reporter I’ve update the code to (maybe) handle the primary-tertiary axis scaling
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] xsum = Vector((0,0,0)) ysum = Vector((0,0,0)) for co in base_coords: xsum += co for co in shapekey_coords: ysum += co xcenter = xsum/nrefverts ycenter = ysum/nrefverts scale = Vector((1,1,1)) #(x,y,z) for n in range(3): xdim = ydim = 0 xs = [abs(co[n]-xcenter[n]) for co in base_coords] ys = [abs(co[n]-ycenter[n]) for co in shapekey_coords] xdim += sum(xs) ydim += sum(ys) if xdim == 0 or ydim == 0: print("Rigidity division by zero") continue scale[n] = ydim/xdim # Calculate dimension of reference group for determining primary secondary and tertiary axes transpose = np.array(base_coords).T xdimension = abs(max(transpose[0])-min(transpose[0])) ydimension = abs(max(transpose[1])-min(transpose[1])) zdimension = abs(max(transpose[2])-min(transpose[2])) refverts_base_dimension = [[xdimension,scale[0]],[ydimension,scale[1]],[zdimension,scale[2]]] refverts_base_dimension.sort(key=lambda x: -x[0]) smat = Matrix.Identity(3) for n,smode in enumerate(scalemodes): if smode == "primary": smat[n][n] = refverts_base_dimension[0][1]-1 elif smode == "secondary": smat[n][n] = refverts_base_dimension[1][1]-1 elif smode == "tertiary": smat[n][n] = refverts_base_dimension[2][1]-1 else: # No-scale smat[n][n] = 1-1 if "Rigidity" in ob.vertex_groups.keys(): idx = ob.vertex_groups["Rigidity"].index for n,vn in enumerate(maskverts): for v in ob.data.vertices: if(v.index == vn): for g in v.groups: if g.group == idx: skey.data[vn].co = (smat*(1-g.weight) @ (ob.data.vertices[vn].co - xcenter)) + (ob.data.vertices[vn].co - xcenter) + ycenter
-
Exactly which morphs are you trying to load to futalicious ? Does it work fine without morphs ? Please note that you don’t have to transfer Beverly to futalicious since it’s already baked in the dbz.
For us to look at the issue please upload a duf file for testing, possibly using free assets when possible. That is, G8F with the pear FBM instead of Beverly for example. Then the detailed steps you’re following, I mean including all the details, specifically in this case what futalicious morphs you’re trying to load. If they’re actor morphs you’ll have to use the “morphing armature“ tool.
-
reporter I try to load Victoria 8.1 FBM, Brooke 8.1 FBM and Beverly MCM (It will apply after Brooke 8.1) on Genesis 8.1 mesh. The morph is load successfully and correctly morph the Genesis 8.1 mesh. The problem start when I transfer the morph to Breastacular and Futalicous. When I dial Victoria 8.1 FBM to 100% I notice that Futalicous and Breastacular geocraft borders which should be correctly align with genesis 8 main mesh are slightly off. When I merge the geograft, The final mesh when dialing Victoria 8.1 is mostly fine except at the seam between main mesh and merged geoshell. The problem is extremely bad (My top post) when I use Brooke 8.1 FBM or Beverly MCM which extremely distort the main mesh. Then I notice that if I tick “Ignore rigidity group” the shape of Breastacular is better and comparable to Daz3d shape. The Futalicious, however, has a good seam (like Daz3d) but incorrect shape.
So, I decide to look at the source code and try to fix the problem. The result is almost the same as the final meshed in Daz3D.
-
As I understand it that is not the intended workflow and I don’t know if you can work that way. I mean, usually you export the baked dbz with the shape you want then import custom morphs for posing. That is also what Thomas was referring to in his answer I guess.
-
reporter @Alessandro I need to transfer the morph in the first place because I want to move a lot of my workflow out of Daz3D. My workflow is
- My duf file is just Genesis 8.1 attaching Breastacular, Fender Bender, Futalicious, Full Monty BBQ with no single morph applied.
- I import the file (with dbz) to Blender with Easy Import (I check all default morph such as FACs. JCM … but I uncheck merge toe, merge geograft, merge lashed)
- I load custom body morph to main mesh
- I transfer Genesis 8.1 shapekeys to 1st layer geograft (with Nearest Face method) (everything except Full Monty BBQ)
- I transfer all shapekeys in Futalicious to Full Monty BBQ (This help fixing the problem with some FBM incorrectly distort Full Monty BBQ if I transfer from Genesis 8.1 directly
- I merge all geografts
- The problem I describe in the 1st post appear.
I then fix it and redo step 1-6.
So, Now I can use a lot of FBM (such as morph 50% Victoria 8.1 50% August8.1 with working and correctly shape Geograft) without need to reexport from Daz3d again.
-
Again in my opinion that is an extremely cumbersome workflow. For example consider that most figures get their own custom jcms as well. The purpose of diffeomorphic is not to turn blender into daz studio. You create your custom figure in daz studio then export to blender.
We better wait the answer by Thomas if it is supposed that you can use diffeomorphic that way.
-
reporter For anyone who may be interest, My workflow is now create a master.blend file contain all morphs for geograft and base genesis 8.1 body. I then save this file “master.blend” then I create a copy and import character-specific morphs (such as JCM) like Brooke 8.1 and Beverly like the image. The fix I post earlier helps me fix the geograft mesh problem when merge with main mesh. Now, If I want to create a new character such as Victoria 8.1, I just copy master.blend then load morphs to it.
I also tweak morph_armature.py to support futalicious and full monty bbq armature like this. (My python skill is newbie; the code add may not be the best practice)
def getEditBones(rig): scale = rig.DazScale 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) 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] fbname = "%s(fin)" % bname heads[bname] = heads[fbname] = heads[pb.name] tails[bname] = tails[fbname] = tails[pb.name] offsets[bname] = offsets[fbname] = offsets[pb.name] for pb in rig.pose.bones: if "shaft" in pb.name or "Testicle" in pb.name or "scrotum" in pb.name or "Labium" in pb.name or "clitoris" in pb.name or "vagina" in pb.name: offsets[pb.name] = offsets[pb.name] + offsets["pelvis"] if pb.name[-5:] == "(drv)": bname = pb.name[:-5] fbname = "%s(fin)" % bname offsets[bname] = offsets[fbname] = offsets[pb.name] return heads, tails, offsets
-
Thank you Suttisak for your work. Though personally I don’t think I’ll go for that workflow, it sounds extremely interesting. It’s kinda having daz studio into blender for figures generation. Your “master.blend” file could be called “genesis_81.blend“ as well since that’s what it is intended for.
I can’t help with rigs so I’m curious myself to hear from Thomas what he thinks.
-
reporter I think this code produce good enough result for me
transfer.py
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 U, S1, V = np.linalg.svd(base_coords_relative_to_base_center_coords) # Transfrom Shapekey Coordinate to be relative to its center shapekey_coords_relative_to_shapekey_center_coords = shapekey_coords - shapekey_center_coords # Singular value decomposition U, S2, V = np.linalg.svd(shapekey_coords_relative_to_shapekey_center_coords) # 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] print(smat) if "Rigidity" in ob.vertex_groups.keys(): idx = 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 == idx: # 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)
-
repo owner It was a long time since I looked at the rigidity code, and I don’t quite remember what it does, so I copied you code in the last commits. Didn’t see your last suggestion though, will look at that later.
-
reporter Thanks, Thomas. if you have a time, I recommend you merge the code, which contains “
Singular Value Decomposition”
in my last comment. It produces the shape almost same as Daz3d. It's still had a slight difference especially at the junction between main mesh and geograft through.
-
repo owner Your new code is incorporated in latest commit. I don’t quite understand what it does, but it seems to work well.
-
@Thomas Rigidity maps are for excluding some parts of a mesh from being deformed by morphs. As for geografts this can be used to exclude the geograft itself from being affected from the deformations of the graft area. Below an example with futalicious setting a rigidity map and groups.
Then I can’t understand the code by Suttisak so I can’t tell how it’s good. I’d like to hear from @engetudouiti or @xin what they think. Also, in
#223we introduced a smooth group on the graft area that I guess may interfere with the code by Suttisak, if it’s good I mean. -
repo owner Well, Suttisak’s code is definitely better than mine in some respects, e.g. he covers secondary and tertiary scale axes and not only primary axes. Since it solves his problem, and the cases that I tested seem to work well, I will settle for Suttisak’s code unless new problems appear.
-
reporter - changed status to resolved
- Log in to comment
I think you merged geografts before loading the custom morphs, perhaps by ticking that checkbox in easy import. If you do that you can still load morphs afterwards, but they will only affect the original body verts, not the part of the body that originally belonged to the grafts.
So you need to use a slightly more cumbersome workflow:
Alternatively, you could create a file with favorite morphs and load that during easy import, cf. https://diffeomorphic.blogspot.com/2021/05/morph-presets-and-easy-import.html. Then the morphs will be loaded and transferred before the geografts are merged. Make sure that the body type is set to All so the shapekeys are transferred both to geografts and lashes.