Sunday, August 9, 2020

Automatic Root Path Extraction

 Unlike OBJ or FBX files, a file from DAZ Studio (.duf, .dsf) is not self-contained. Instead it contains pointers to other DAZ files, which may contain further pointers or the actual data. The pointers are given relative to the DAZ Studio root directories, which are listed at the top of the Content Library tab in DAZ Studio.

 

In Blender we find the root paths in the Global Settings dialog, which we open by pressing the Global Settings button just below the Import DAZ File button in the Setup panel.

 

The default root paths listed in Blender may work in Windows if you have installed DAZ Studio with the default settings. However, if you use non-standard locations for your assets, or access assets over the web, or use a Mac, these settings will not be right. 

It is absolutely mandatory that you have the same root paths as in DAZ Studio, because otherwise the Blender plug-in will not find your assets at all. The time-honored method to fix this has been to type in the root paths by hand. However, this is rather cumbersome, even if you only have to do it once if you save the settings afterwords. The manual method still works, but in the development version there is a more convenient way to do it.

 

In the to_daz_studio folder there is a new subfolder called Scripts. Copy or link this folder to one of you DAZ Studio base directories and start up DAZ Studio.

 

In the Content Library tab, the Scripts folder contains a new subfolder called Diffeomorphic, which contains a single DAZ script called save_root_paths. (Further scripts will be added later). Double-click on the save_root_paths icon.

 

The scripts starts and asks for a location where to save the file. Specify the location and press Save.

A message box informs us that the root paths have been saved.

Back in Blender, open the Global Settings dialog again, and press Load Root Paths.

 

Select the file that you saved in DAZ Studio, and press Load Root Paths

The root paths in Blender are now the same as in DAZ Studio.

Actually, if you compare with the first illustration, there is an additional directory for shaders. This is an MDL directory, which is necessary to find some textures, e.g. the DTHDR-RuinsB-500.hdr environment map.


Saturday, August 1, 2020

Global Settings Improved

The Daz Importer depends on a number of global settings, that can be changed in the Settings panel. The most important of those are the Daz root paths, which tell the importer where to look for Daz assets; if the root paths are not the same as the ones defined in Daz Studio, the importer will not find the assets.

Until now, the global settings have been implemented as Blender properties, more precisely as scene properties. This is necessary in order to be able to display and modify the properties in the Settings panel. However, there is a serious drawback. If you have saved a start-up file, it will be loaded after the Daz importer has been initialized, and if the start-up file contains the scene properties used by the Daz importer, those values will be overwritten. So it does not help if you press the Save Default Settings button. The next time Blender is started, the new default settings are loaded, but they are immediately overwritten by the values in Blender's default start-up file.
Saving a startup file
To address this problem, the upcoming version 1.5 treats the global settings differently. The global settings are now global variables, which Blender can not mess with. In order to inspect and change the settings, they are converted into scene properties only when necessary, and then immediately converted back to global variables again.
To change the settings, press the Global Settings button immediately below Import DAZ File. The Global Settings dialog now appears.
In this dialog you can now change the settings, in particular the root paths to the left. If you press OK or hit carriage return, the settings are updated, and if you press outside the dialog or hit Escape any changes are cancelled.

Note that there are no Load/Save Default Settings buttons. This because all changes are persistent, so the default settings file is automatically updated when the settings are changed. The next time you open Blender, the updated settings are loaded.

The new settings system should be compatible with the old one, but since quite a lot of code is affected, there might be some glitches that have to be worked out.



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.

Tuesday, June 30, 2020

Add-on scripting

Since the latest changes to the add-on names were made to allow scripting, it is time to present a little script that uses both the Daz importer and the BVH retargeter. The script imports a Daz character from a .duf file, merges the rigs, and retargets the first 200 frames of a BVH animation to the armature, all in one go. For performance reasons, the script also excludes all meshes from the scene before retargeting the BVH file, and finally puts them back again. This example shows why the Daz importer's addon mechanism is insufficient: a sub-addon can only access the Daz importer but not other add-ons like the BVH retargeter.

To run this script you need version 1.4.2 of the Daz importer and the development version (2.0.1) of the BVH retargeter. Not surprisingly, I encountered some problems when I first tried the script, which are fixed with the latest commit.

Here are lists of the operators defined by the add-ons:
https://diffeomorphic.blogspot.com/p/daz-operators.html
https://diffeomorphic.blogspot.com/p/mcp-operators.html
The add-ons also define some functions, but they have not yet been documented.

And here is the script:

import bpy
import os
import import_daz
import retarget_bvh

def check_import_error():
    # The error message is the empty string if everything ok
    msg = import_daz.getErrorMessage()
    if msg:
        print("Import error: \"%s\"" % msg)


def check_retarget_error():
    # The error message is the empty string if everything ok
    msg = retarget_bvh.getErrorMessage()
    if msg:
        print("Retarget error: \"%s\"" % msg)


def exclude_meshes_collection(flag, lcoll):
    if lcoll.name.endswith("Meshes"):
        lcoll.exclude = flag
    for lchild in lcoll.children:
        exclude_meshes_collection(flag, lchild)


def main():
    # Daz importer in silent mode
    import_daz.setSilentMode(True)

    # Import the file

    filepath = "~/Documents/DAZ 3D/Docs/anna.duf"
    filepath = os.path.expanduser(filepath)
    bpy.ops.daz.import_daz(

        filepath=filepath, 
        fitMeshes='UNIQUE')
    check_import_error()
    rig = bpy.context.object

    # Merge rigs

    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.daz.merge_rigs()
    check_import_error()


    # Exclude meshes from scene for performance
    lcoll = bpy.context.view_layer.layer_collection
    exclude_meshes_collection(True, lcoll)

    # Restore Daz importer non-silent mode
    import_daz.setSilentMode(False)

    # BVH retargeter in silent mode
    retarget_bvh.setSilentMode(False)

    # Ensure that the BVH retargeter is initialized
       
    bpy.context.view_layer.objects.active = rig     
    retarget_bvh.ensureInited(bpy.context.scene)

    # Retarget BVH file to the active rig
    filepath = "C:/home/bvh/accad/Female/Female1_B03_Walk1.bvh"
    bpy.ops.mcp.load_and_retarget(
        filepath=filepath,
        startFrame=1,
        endFrame=200
        )
    check_retarget_error()

    # Restore BVH retargeter non-silent mode
    retarget_bvh.setSilentMode(False)

    # Include meshes in scene again
    exclude_meshes_collection(False, lcoll)


main()

Friday, June 26, 2020

Repo move now completed

As announced a few days ago, the Daz Importer repository is moving in order to make it possible to invoke the code from external python scripts. The BVH Retargeter is also moving for the same reason.

The move is now complete. Here are the new locations:

Daz Importer:

Repository: https://bitbucket.org/Diffeomorphic/import_daz/
Development version as a zip file: https://bitbucket.org/Diffeomorphic/import_daz/downloads/
Bug tracker: https://bitbucket.org/Diffeomorphic/import_daz/issues?status=new&status=open

BVH Retargeter:

Repository: https://bitbucket.org/Diffeomorphic/retarget_bvh/
Development version as a zip file: https://bitbucket.org/Diffeomorphic/retarget_bvh/downloads/
Bug tracker: https://bitbucket.org/Diffeomorphic/retarget_bvh/issues?status=new&status=open