Wednesday, March 19, 2025

Scripting Improvements

I have spent a log of time developing the DAZ Importer plugin, but I rarely use its scripting capabilities. However, every button becomes an operator that can be called from a script; a list of operators defined by the DAZ Importer can be found here. These operators are defined automatically and usually work out of the box, but there are some caveats regarding operators that act on multiple files, morphs or shapekeys.

Assume that we want to import a figure with Easy Import. We select the duf file (or the corresponding .png or .tip.png file) and press Easy Import DAZ. To do the same thing programatically, we could expect that the following script would work: 

bpy.ops.daz.easy_import_daz(
    filepath = "D:/home/scenes/victoria-rocker.duf"
)

However, nothing is imported. The reason is that the Easy Import tool can import several files at once. If we select the following three files, three different figures will be imported at once.

And here are the figures in Blender.
Importing multiple character in one stroke may not seem very useful, but we may want to import thirty different props from a single directory, and then it is easier to ímport thirty files at once than to press the Easy Import button thirty times and only import a single prop each time.

The old way to import multiple files uses the api.set_selection function, which needs to be called before easy_import_daz is invoked. Functions defined by the DAZ Importer plugin are documented here.

import bpy
from bl_ext.user_default.import_daz import api

api.set_selection([
    "D:/home/scenes/aiko-basic-wear.duf",
    "D:/home/scenes/victoria-rocker.duf",
    "D:/home/scenes/michael-basic-wear.duf"])
bpy.ops.daz.easy_import_daz()


Unfortunately, this does not work very well. Under the hood the set_selection function sets a global variable, and this leads to complications for operators that invoke other operators, like easy_import_daz does. Recently I learned from Rakete that there is another way to access multiple files which does not involve any global variables.

When a file selector exists, it sets two keyword arguments: the StringProperty "directory" and the CollectionProperty "files". An alternative way to import multiple files is like this:

bpy.ops.daz.easy_import_daz(
    directory = "D:/home/scenes",
    files = [{"name" : "aiko-basic-wear.duf"},
             {"name" : "victoria-rocker.duf"},
             {"name" : "michael-basic-wear.duf"}])


Actually, importing files in this way has always worked, but I was not aware of it. The "files" argument is a bit complicated; it is a list of dicts with the "name" keyword, which is how we specify a CollectionProperty in scripts.

To import a single file we simply let "files" be a list with a single argument.

bpy.ops.daz.easy_import_daz(
    directory = "D:/home/scenes",
    files = [{"name" : "victoria-rocker.duf"}])

    
This way of importing multiple files is not limited to Easy Import. The following script imports three poses to the active object. The second line turns on automatic keying.

import bpy
bpy.context.scene.tool_settings.use_keyframe_insert_auto = True
folder = "/people/genesis 8 female/poses/base poses/"
files = ["base pose kneeling a.duf",
         "base pose kneeling b.duf",
         "base pose kneeling c.duf"]         
bpy.ops.daz.import_pose(
   directory = api.get_absolute_path(folder),
   files = [{"name" : file} for file in files],
)


This script imports three poses to consequtive frames. 

Many tools opens another type of selector, which lets us select which morphs to import, which shapekeys to transfer, which materials to modify, etc. Such selections used to be made with the api.set_selection function as well, but now the preferred way is to use the "selection" keyword. Again the argument is a CollectionProperty, which corresponds to a list of dicts with the "name" keyword in scripts.

The following script import three units morphs:

files = ["eCTRLEyesClosed.dsf",
         "eCTRLEyesClosedL.dsf",
         "eCTRLEyesClosedR.dsf"]
bpy.ops.daz.import_units(
    selection = [{"name" : file} for file in files]
)

It is thus equivalent to the following selection in the Import Units dialog.

If the "selection" keywprd is omitted, or equivalently if selection = [], all elements are selected. Thus the line

bpy.ops.daz.import_visemes()

imports all available viseme morphs. We can then set the corresponding rig properties.

rig = bpy.context.object
rig["eCTRLvAA"] = 1.0
rig["eCTRLvOW"] = 0.4