Tuesday, July 1, 2025

Animation Layers

After we have imported a mocap animation we usually have to edit it. There are many reasons for this: the mocap data is faulty, it doesn't fit the target character, it doesn't quite do what we want, etc. The BVH and FBX retargeter had some tools for this, but they were quite clumsy and incomplete. The natural way to edit animations in a non-destructive way would be to use animation layers, but I didn't think Blender had those. However, Alessandro pointed out that Blender always have had animation layers, but they are hidden inside the NLA editor and a bit inconvenient to use. So I decided to replace the old edit tools with animation layers, taking inspiration from this video.

Here we have imported a walk animation from ACCAD. A common problem with retargeting such files is that the collar bones tend to slope. To fix this, we add a new animation layer by pressing the plus button in the Animation Layers panel.
This creates two animation layers: the base layer which is the original animation, and a second layer where we can modify the animation.
Behind the scenes we have converted the original animation to an NLA track, and added a new empy animation on top of that. The NLA tracks can be disabled, but the last layer is always the current action.
If we now add keyframes to the collar and shoulder bones at one frame, the animation is modified at all frames.
The total animation now consists of an action and a number of NLA tracks. If we need it to be a single action, e.g. in order to export it to games, we can use the Bake Animation Layers button.
After baking animation layers, the layers list is cleared, but the animation remains the same.
The tracks are gone from the NLA editor, and only a single action remains.

Wednesday, June 25, 2025

Scanning the DAZ database is now obsolete

 I'm a Windows user myself, but the DAZ Importer still runs on Linux. The problem with Linux is that its file system is case sensitive, so e.g. Runtime/Textures and runtime/textures are different directories on Linux, whereas they are the same on Windows. To deal with these problem we previously had to scan the entire DAZ database, cf the post on Linux Revisited. Alas, this led to several problems. Scanning the database could take considerable time if many DAZ assets have been installed. Moreover, scanning is error prone because if you add new assets, they will not be found unless the database is rescanned.

Fortunately, these are now problems of the past. In the last commit, the relevant paths are scanned on the fly, meaning that a separate scanning step is obsolete. Having to look at several locations for an asset does have a price, but it seems to be a modest one. My initial tests indicate that importing files with "Case-Sensitive File Paths" enabled increases the load time with about 10%.

And please note that this is only an issue under Linux. Under Windows, and I believe Mac as well, file paths are case insensitive so the issue never arises.

Tuesday, May 20, 2025

Importing DBZ Files Directly

Getting the DAZ Importer up and running is a bit more complicated then using a normal importer. We must Copy Scripts folder to DAZ Studio, Save Scene in DAZ Studio, and Export To Blender. Those are the steps that any export script would need to do. But the DAZ Importer also required two extra steps, Save the DAZ root paths in DAZ Studio, and also Setting up the DAZ root paths in Blender. Those steps are necessary because the DAZ assets are not stored in the duf file. Instead the duf file contains pointers to the actual assets relative to the root paths. So knowing the root paths is necessary to find the assets.

However, the extra steps have been automated in the development version, which means that the DAZ Importer behaves more like a normal importer: export the dbz file from DAZ Studio and import it into Blender. The root paths are stored inside the dbz file, provided that you copy the latest version of the Export To Blender script a the DAZ Studio scripts folder. Older dbz files can also be imported, but in that case the root paths must be set up correctly first.

When we now press Easy Import DAZ or Import DAZ Manually, we notice two differences:

  • The file list contains dbz files instead of duf files and pictures.
  • The Mesh Fitting option is gone. 
The default root paths are probably not correct. Previously we had to set up the root paths manually, or export the root paths from DAZ Studio and load them into Blender. This is no longer necessary if we use the latest version of the Export To Blender script, because the root paths are now stored inside the dbz file. If we import an older dbz file it is still necessary to set up the correct root paths.

After the dbz file has been imported, the root paths are updated.

There are two new global settings that control the behaviour.

  • Only DBZ Files. If disabled the file selector looks like it used to, showing duf and dsf files and images. The Mesh Fitting option is also shown.
  • Root Paths From DBZ. If enabled the plugin uses the root paths in the dbz file, otherwise the root paths in the global settings are used.

Here is the file selector if DBZ Files Only is disabled. The file selector contains duf files and pictures. The Mesh Fitting options are shown.

Sunday, April 6, 2025

Diffeomorphic Add-ons Version 4.4.0 Released

Version 4.4.0 of the DAZ Importer, MHX Runtime System, and FBX and BVH Retargeter have been released. They can be downloaded from

The previous stable version 4.3.0 does not work with Blender 4.4 due to an API change.

Apart from working with Blender 4.4, there are also many bugfixes and some other changes.

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



Thursday, March 6, 2025

Mathjax Test

The formulas in the header.

\[\begin{align}
[{\cal L}_\xi, {\cal L}_\eta] &= {\cal L}_{[\xi,\eta]}
 + {1\over{2\pi i}}\int dt\ \dot q^\rho(t)
 \Big( c_1\ \partial_\rho\partial_\nu\xi^\mu(q(t))\ \partial_\mu \eta^\nu(q(t))\ +\\
& \qquad\qquad\qquad +\ c_2\ \partial_\rho \partial_\mu \xi^\mu(q(t))\ \partial_\nu \eta^\nu(q(t)) \Big),\\
[{\cal L}_\xi, q^\mu(t)] &= \xi^\mu(q(t)), \\
[q^\mu(t), q^\nu(t')] &= 0. 
\end{align}\]

If you use Firefox with NoScript, as I do, you need to whitelist jsdelivr.net and polyfill.io, otherwise only the uncompiled LaTex code will show up.

If anybody wonders, this is the Virasoro-like extension of the diffeomorphism algebra in multiple dimensions. e.g. on the \(d\)-dimensional torus. When \(d=1\), both terms proportional to \(c_1\) and \(c_2\) reduce to the ordinary Virasoro algebra on the circle, a piece of mathematics that is popular e.g. in string theory and statistical physics. By the way, I discovered the \(c_2\) term myself a long time ago, which is something that I am still proud of (\(c_1\) was discovered by Rao and Moody).

When I started this blog almost a decade ago I had planned to write about this and how it in my opinion fits into physics, hence the name of the blog. I never got around to do that, but that might change in the future.