How do you load a subset of the standard JCMs from script in 1.4.2?

Issue #125 closed
Alec Vallintine created an issue

Is it possible to load a subset of the standard JCMs from a script while bypassing the selection dialogue?

I’ve tried calling bpy.ops.daz.import_standard_jcms but I can’t get that to work. The problem appears to be that certain properties must be set which only get set by invoke() and invoke() isn’t called when you call the operator from a script, unless you do import_standard_jcms("INVOKE_DEFAULT"), but that just opens the selection dialogue.

Something like this seems to work:

from import_daz.morphing import LoadMorph

loader = LoadMorph(mesh=mesh, rig=rig)
loader.type = "Correctives"
loader.prefix = "DzC"
loader.useShapekeysOnly = True
loader.useSoftLimits = False
loader.usePropDrivers = False
loader.useBoneDrivers = True
loader.useStages = True

paths = {'Abdomen2Fwd_40': 'path\\to\\pJCMAbdomen2Fwd_40.dsf'}

loader.getAllMorphs(paths, bpy.context)

But is there a better way?

This same issue might also apply to loading other types of “morphs” but I haven’t tested that yet. In a previous version of the addon I was able to select which morphs I wanted to load by setting properties on the scene. For instance, for facial units:

FACIAL_UNITS = [
    "DazMouthOpen",
    "DazEyesClosedL",
    "DazEyesClosedR"
]

# Deselect all units
bpy.ops.daz.select_all_morphs(type="Units", value=False)

# Select just the units we want
for u in FACIAL_UNITS:
    setattr(bpy.context.scene, u, True)

# Load selected units
bpy.ops.daz.load_all_units()

But that doesn’t appear to work anymore. What’s the best way to do this sort of thing?

Comments (12)

  1. Thomas Larsson repo owner

    I don’t have a good answer to this yet. It should be something like this

    from import_daz.utils import B

    selection = []
    for file in files:
    item = B.DazSelectGroup
    item.name = file
    item.text = file
    item.select = True
    selection.append(item)

    bpy.ops.daz.import_units(selection = selection, filter = "")

    where DazSelectGroup is defined in buttons28.py. But it doesn’t work because I get the following error message.

    TypeError: Converting py args to operator properties: DAZ_OT_import_units.selection expected a each sequence member to be a dict for an RNA collection, not RNAMetaPropGroup

  2. Thomas Larsson repo owner

    Now I have at least been able to import custom morphs. However, it was necessary to make a change to the code so you need to use the development version. Also, it only seems possible to import one morph at a time, which means that some morphs will not load correctly (if they are defined in terms of other morphs, in the other morph file).

    So you can do something like this:

    bpy.ops.daz.import_custom_morphs(filepath = folder + "eCTRLEyesClosed.dsf", catname = "Unit")
    bpy.ops.daz.import_custom_morphs(filepath = folder + "eCTRLEyesClosedL.dsf", catname = "Unit")
    bpy.ops.daz.import_custom_morphs(filepath = folder + "eCTRLEyesClosedR.dsf", catname = "Unit")

  3. Alec Vallintine reporter

    Ah, thanks for the clarification. At least I now understand how it was intended to work.

    You were getting that error in your first example because “selection” is a CollectionProperty of PropertyGroups and must be passed as a list of dicts. Theoretically, something like this should work:

    selection = []
    
    item = {}
    item["name"] = "path\\to\\eCTRLMouthOpen.dsf"
    item["text"] = "MouthOpen"
    item["category"] = ""
    item["index"] = 0
    item["select"] = True
    
    selection.append(item)
    
    bpy.ops.daz.import_units(selection=selection, filter="")
    

    Except that doesn’t actually work. Instead you get this:

    AttributeError: 'DAZ_OT_ImportUnits' object has no attribute 'char'
    

    Which is pretty much the same problem you had to fix to make import_custom_morphs() work. The problem in this case is that “char” gets set only when StandardMorphSelector.invoke() is called, but invoke() doesn’t get called when you call import_units() from a script.

    Anyway, thanks again for the help. I’m glad to know that I wasn’t overlooking something obvious when trying to get this to work.

  4. Thomas Larsson repo owner

    I don’t understand how to change collection properties from python, but I think I found a nice way to load standard morphs (face units, visemes, standard jcms, etc). Use the new function import_daz.setFilePaths to specify the list of file paths before calling the operator. Here is an example:

    import os
    import bpy
    import import_daz

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

    # Import some face units

    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.setFilePaths(paths)
    bpy.ops.daz.import_units()

    # Visemes in same folder

    bpy.ops.daz.import_visemes()
    files = ["eCTRLvAA.dsf", "eCTRLvEE.dsf", "eCTRLvF.dsf"]
    paths = [folder+file for file in files]
    import_daz.setFilePaths(paths)
    bpy.ops.daz.import_visemes()

    # Import some 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.setFilePaths(paths)
    bpy.ops.daz.import_standard_jcms()

  5. Alec Vallintine reporter

    I don’t understand how to change collection properties from python…

    Are you talking about doing something like this?

    import bpy
    from import_daz.utils import B
    
    bpy.types.Scene.my_collection = bpy.props.CollectionProperty(type=B.DazSelectGroup)
    
    item = bpy.context.scene.my_collection.add()
    item.name = "path\\to\\eCTRLMouthOpen.dsf"
    item.text = "MouthOpen"
    item.category = ""
    item.index = 0
    item.select = True
    
    # This is incorrect!
    bpy.ops.daz.import_units(selection=bpy.context.scene.my_collection, filter="")
    

    You’d think that would work, but apparently it doesn’t. Instead, you need to pass a list of dicts for “selection” like in my earlier example. That list will get converted into a collection of DazSelectGroup items behind-the-scenes.

    I’m really glad to see you’re making changes to make this possible/easier. I’ll check them out.

    Anyway, thanks for your attention to this matter, and thanks for your continued work on this add-on! It really has made my life much easier. It’s also the first thing I recommend to people wanting to move from Daz to Blender.

    If the reasons aren’t already obvious, it’s really important to me that all the functionality be accessible from script, so I can automate my pipeline as much as possible. So, for instance, if I need to add a morph, I just add it to the script and rebuild the figure without having to do anything by hand, which can be quite tedious and error-prone.

  6. Thomas Larsson repo owner

    Here is something which works on with the latest version

    import bpy
    import import_daz

    FACIAL_UNITS = [
    "eCTRLMouthOpen.dsf",
    "eCTRLEyesClosedL.dsf",
    "eCTRLEyesClosedR.dsf"
    ]

    # Absolute root path on my computer.
    root = "D:/DAZ 3D/Studio/My Library/"

    # Relative path to the folder where the poses are located
    folder = "/data/DAZ 3D/Genesis 8/Female/Morphs/DAZ 3D/Base Pose Head/"

    # Absolute paths to the files
    paths = [root+folder+unit for unit in FACIAL_UNITS]

    # Set selection
    import_daz.setSelection(paths)

    # Load selected units
    bpy.ops.daz.import_units()

  7. Log in to comment