Saturday, July 25, 2020

New Morph System

The naming convention for morphs has been changed in the development version. Previously the plugin stripped the standard Daz prefix from the morph name (eCTRL, pJCM, etc.) and replaced it by a different prefix, always with three letters (DzU, DzC, etc.). This made it easy to list all morphs of a certain type in a user-friendly format, by stripping off the first three letters from the morph name.

However, this system had a few drawbacks. Once one starts to delete and edit morphs, the plugin could become confused. In particular, it could add multiple prefixes, like DzUDzC to the morph names. It was also not obvious what the original morph name was, since the beginning of the Daz name had disappeared. To overcome such drawbacks, a new morph system has been introduced. In the new system, the original Daz names is kept. To keep track of the morph type (Units, Expressions, JCMs, etc.), all morph names are stored in different collection properties, together with the user-friendly print name.

The change should be almost invisible for the user, except that the new morph system has introduced some bugs that need to be fixed. However, there are a few places where you can actually see the difference.
Shapekeys keep their original Daz names. In previous versions, the two last shapekeys would be called DzMAiko8Afraid and DzMAiko8Angry.
If an old file with a Daz character is opened, the Morphs panel consists of a single button. Once we press Update Morphs For Version 1.5, all objects in the scene are updated and the Morphs panel look as it did before.
It is not necessary to do the update when a new scene is imported with the Import DAZ button.

To access the morphs of a certain type from python, use the function import_daz.getMorphs(object, type). The function returns a dict which describes the morph.

The follow script prints all custom morphs of the active object

import bpy
import import_daz

ob = bpy.context.object
morphs = import_daz.getMorphs(ob, "Custom")
print("Keys")
print(morphs.keys())
print("Values")
for item in morphs.values():
    print("    %20s %20s" % (item.name, item.text))


Here is the output
Keys
dict_keys(['FHMAiko8', 'eJCMAiko8Afraid', 'eCTRLAfraid', 'eJCMAiko8Angry', 'eCTRLAngry'])
Values
                FHMAiko8             FHMAiko8
         eJCMAiko8Afraid          Aiko8Afraid
             eCTRLAfraid               Afraid
          eJCMAiko8Angry           Aiko8Angry
              eCTRLAngry                Angry


If we load four expressions, we can print them by changing "Custom" to "Expression" in the script above, viz. import_daz.getMorphs(ob, "Expression").

Keys
dict_keys(['eCTRLAfraid', 'eCTRLAngry', 'eCTRLBereft', 'eCTRLBored'])
Values
             eCTRLAfraid               Afraid
              eCTRLAngry                Angry
             eCTRLBereft               Bereft
              eCTRLBored                Bored





Wednesday, July 15, 2020

More on scripting

Updated October 10, 2021: There are some changes in the scripting interface in version 1.6.0 of the DAZ Importer. See Scripting for version 1.6 for details.


Updated July 15, 2020: setFilePaths replaced by setSelection. Added code for transferring and removing morphs.


Recently we discussed how to import a Daz scene and merge rigs from Python, but we did not attempt to import morphs to the new character. Afterwards I realized that doing so does not work at all. The problem is that the operators which import multiple files use a collection property, and specifying collection properties from Python seems impossible. At least I did not figure out how to do it.

Instead the problem is solved in a different way in the development version: with a global variable. Whenever the UI creates a file selector that generates multiple inputs, we need to call the function

import_daz.setSelection(list_of_filepaths)

before calling the operator. This function sets the global variable from which the filepaths are taken.

So let us write a script that imports various things from Python. We start by importing the relevant modules and settings up the root path. 

import os
import bpy
import import_daz
rootpath = os.path.expanduser("~/Documents/DAZ 3D/Studio/My Library")


With the character rig or mesh active, we next import three morphs that close the eyes

headpath = "/data/DAZ 3D/Genesis 8/Female/Morphs/DAZ 3D/Base Pose Head/"
folder = rootpath + headpath
files = ["eCTRLEyesClosed.dsf", "eCTRLEyesClosedL.dsf", "eCTRLEyesClosedR.dsf"]
paths = [folder+file for file in files]
import_daz.
setSelection(paths)
bpy.ops.daz.import_units()

If we have invoked the operator with the 'INVOKE_DEFAULT' argument, it would instead have launced a morph selector in the same way as the Import Units button does.

bpy.ops.daz.import_units('INVOKE_DEFAULT')

Next we import two custom morph

yoyopath = "/data/DAZ 3D/Genesis 8/Female/Morphs/Hamster/YOYO/"
folder = rootpath + yoyopath
files = ["FBMYoyo.dsf", "FHMYoyo.dsf"]
paths = [folder+file for file in files]
import_daz.
setSelection(paths)
bpy.ops.daz.import_custom_morphs()


Note how the eyes have moved relative to the eyelashes. Loading morphs can be of limited use since Blender shapekeys only affect the mesh, whereas Daz morphs can affect the rig rest pose as well. To fix this we transfer the two Yoyo morphs to the eyelashes.

ob = bpy.data.objects["Genesis 8 Female Eyelashes"]
ob.select_set(True)
import_daz.setSelection(["DzMFBMYOYO", "DzMFHMYOYO"])
bpy.ops.daz.transfer_other_morphs(useDriver=True)

Then we import some standard JCMs

jcmpath = "/data/DAZ 3D/Genesis 8/Female/Morphs/DAZ 3D/Base Correctives/"
folder = rootpath + jcmpath
files = ["pJCMAbdomen2Fwd_40.dsf", "pJCMAbdomenFwd_35.dsf"]
paths = [folder+file for file in files]
import_daz.
setSelection(paths)
bpy.ops.daz.import_standard_jcms()

This technique is not limited to importing morphs. The next code snippet resizes all textures that involve the arms. At this point the active object must be the character mesh, the blend file must be saved, and we must have saved local textures. Otherwise the operator will fail with a polling error.

folder = "/home/myblends/characters/test/textures"
paths = []
for file in os.listdir(folder):
    if "Arms" in file:
        paths.append(os.path.join(folder, file))
import_daz.
setSelection(paths)
bpy.ops.daz.resize_textures(steps=3)


Finally we change the active object to the armature, and load the first three poses in the Base Poses directory.

posepath = "/People/Genesis 8 Female/Poses/Base Poses/"
folder = rootpath + posepath
paths = []
for file in os.listdir(folder):
    if os.path.splitext(file)[-1] == ".duf":
        paths.append(folder+file)
import_daz.
setSelection(paths[0:3])
bpy.ops.daz.import_action()

Finally we remove the eyes closed morphs again.

rig = bpy.context.object
keys = [key for key in rig.keys() if "Closed" in key]
import_daz.setSelection(keys)
bpy.ops.daz.remove_standard_morphs()

The eyes closed morphs are removed but the eyes squint morphs remain.

An updated list of operators can be found at https://diffeomorphic.blogspot.com/p/daz-operators.html. It specifies the operators that use import_daz.setSelection to get its arguments.