DAZ Director Plugin (WIP)

So as I mentioned in a different post, I've started tinkering with integrating DAZ Studio with AI-assist to define the art of the possible. What I want to be clear about up-front is that this is not about AI image generation, and is not related to the DAZ AI Studio product released by DAZ. 

What I am talking about is using AI to help Studio users accelerate their workflows where they need the accelerating. The AI in this case becomes your "gopher" (agent in AI parlance), to get you past tedious tasks so you can focus on what's most interesting to you. That being said, what's tedious to you might be the most enjoyable part to someone else. So this tool would not be intended to replace the existing Studio interface, but support it. 

Anyway, let's get down to brass tacks. What are we talking about exactly?
 

Here's the "marketing" literature response:

DAZ Director connects an AI — specifically Claude but we can also use other models or local and public models— directly to DAZ Studio. Not as a helper that writes scripts you then have to paste and run yourself. Not as a chatbot that gives you instructions to follow manually. As a director. You describe what you want. It happens.

Here's what that looks like in practice.

You open a conversation pane (just another tab pane just like any other in DAZ Studio) and start talking about a scene. You want a portrait setup — a female character, dramatic and elegant, in a minimal interior. The AI talks through the possibilities with you: what kind of lighting would suit the mood, what camera angles would work, what the scene needs. Then, when you're ready, it starts building.

It searches your DAZ asset library — your actual installed content — finds a suitable character and environment, loads them into the scene, and positions everything. Then you say: "Create a cinematic close-up camera for her face, slightly low angle." The camera appears, framed exactly as described. You say: "Add a three-point lighting setup — soft portrait lights, warm key, cool fill." The lights are created, positioned, and configured. You say: "Now give me a camera animation that starts far back in the darkness, rushes in from behind her, and sweeps around to show her face." The keyframes are set. Press play.

None of that required opening a menu.

But here's what's equally important: at any point in that process, you can put down the conversation and pick up the mouse. Drag a light to a slightly different position. Tweak a morph slider by hand. Adjust the camera framing by eye. DAZ Director doesn't take over your workspace — it works alongside it. The dials and sliders you already know are still there, still yours, still often the fastest way to get exactly what you want.

So right now the basics can be demonstrated. The plugin can take a command do some pretty interesting things -- those things the marketing literature mentions above like finding content, adding it to the scene, moving cameras around, creating lights, all using natural language commands -- but it's not foolproof and it has many limitations that need to be worked. 

What I am hoping to get out of this discussion are answers about how people use DAZ and how something like this could help them use it better, more enjoyably. 

Some questions that immediately come to mind include:

  • Is something like this really useful for DAZ Studio users? I'm doing this a proof of concept, but the concept keeps expanding. 
  • What kinds of natural language command would you love to see? Full scene descriptions? Help posing characters? Expressions? Animations? What parts of your DAZ workflow do you find the most tedious?
  • Keeping in mind that this doesn't come for free. Even if the plugin was low-cost or free, there is still the matter of paying for the AI charges. It would involve purchasing API credits from someone like Anthropic (Claude) or OpenAI (ChatGPT) or Google (Gemini) or downloading and using your own models -- with the understanding that that is more technically involved and works better when you have local resources to handle it (like a GPU with more 12GB+ VRAM for example). Is that tradeoff or cost something that you would consider?

Anyway, I'm in the process of putting together a demo video of the basic capabilities, and a more detailed post about the technical details, in other forums.  But I thought I'd put this out here to foster discussion. 

Comments

  • TugpsxTugpsx Posts: 878
    edited March 20

    Interesting, I have had a few projects on the back burner including a Pose Executor that uses phrases to pose characters in a scene even for animation.

    PosePrompter.png
    374 x 521 - 40K
    Post edited by Tugpsx on
  • Tugpsx said:

    Interesting, I have had a few projects on the back burner including a Pose Executor that uses phrases to pose characters in a scene even for animation.

    So did this incorporate an LLM or straight up NLP or something else? Honestly I've left posing as a special case that requires very specific attention. One of the hardest things to get right. 

  • cain-xcain-x Posts: 214

    Very interested in this PromptPose tool - does it work with non-Genesis characters or can it at least work with any character with the properly named bones your tool expects?

  • cain-xcain-x Posts: 214

    With regards to the "how AI can help or expand current DAZ workflows"... definitely would help if we could prompt for a pose and/or animation. More tighter AI mocap or openpose integration would be amazing. However, I forsee quite a bit of friction as it may cause many assets in the DAZ store to be unsellable, like pose/animation assets. Maybe this could be part of the Premiere package and royalties can be paid to the pose/animation creators if it was used to train some AI model for DAZ. 

    If you've seen Cascadeur's inbetweening tool: https://cascadeur.com/help/category/278. Coupled with the pose/animation AI assisted editing tools and auto-physics, it is amazing what they have built there. I feel DAZ could use a tighter integration with this tool or similar AI assist, since they advertise being able to do animations but its own tools have not seen much significant improvement in years, other than the small changes to the timeline.

  • GranvilleGranville Posts: 701

    A few key areas where it could really help is setting up a scene, if it understands placement etc. If it can see a camera view, have it set up composition based typical composition commands. Lighting can also be time consuming, so it would be great if it could block out some basic lighting. And then finally help with the histogram - how to fix lighting problems - especially when the hdri is blown out but your scene is dark.

  • Granville said:

    A few key areas where it could really help is setting up a scene, if it understands placement etc. If it can see a camera view, have it set up composition based typical composition commands. Lighting can also be time consuming, so it would be great if it could block out some basic lighting. And then finally help with the histogram - how to fix lighting problems - especially when the hdri is blown out but your scene is dark.

    So can you think of an example composition instruction that would be useful? Seeing through the camera view, placement, and blocking out lights I've seen it do, but as with anything AI the instructions have to be precise. Composition would seem to be something a bit more subjective, but maybe not?

     

     

  • TugpsxTugpsx Posts: 878
    edited March 22

    Actually I do have another project that creates a light rig for a selected character. I made it for a car render animation i was working on. It is not completed and the HDRI section is not showing in this version.
    That part was a bit more involved, whereas to find the brightest spot in any given HDRI and add a ghostlight as your source.

    Changing the environment light meant that the system had to analyze the new light for the correct relocation of the source light.

    This one is geared to showrooms so it was easier to move forward. It also creats a director camera where the lights are linked.

    Key Light, Rim Light, Fill Light Top wash light are all created in a single click. 

     

     

    Screenshot 2026-03-22 114012.png
    415 x 409 - 35K
    Post edited by Tugpsx on
  • TugpsxTugpsx Posts: 878

    cain-x said:

    Very interested in this PromptPose tool - does it work with non-Genesis characters or can it at least work with any character with the properly named bones your tool expects?

    This currently works with any character in your library. The beta focus is Daz G1-G9 characters since most users have a ton of them along with their poses.

    You can even ask for a supported character by name and Gx (the selected figure) will morph into a supported character. This can happen on frame 0 or over time if you want transformation. By specifying for example "Bishop over 30 frames" for a transformation from G9 to Bishop the G9 character will morph into Bishop. You can concat the request to then have Bishop transform to Desmond over 30 more frames with a gap of holding bishop for x amount of frames. G9 -> Bishop, wait x time -> Desmond. 
    There is a playback option to review your transformation so you can adjust it at any given time. Stop a transform in the middle and ask for different character target and it will do so. 

  • TugpsxTugpsx Posts: 878

    @sidcarton1587 please check your PM. A few questions about addition to your plugin.

  • sidcarton1587sidcarton1587 Posts: 96
    edited March 30

    Tugpsx said:

    @sidcarton1587 please check your PM. A few questions about addition to your plugin.

    I replied back to you with some details, but I thought I would repost here for people who are also interested.

    I've been adding features and improvements to the DAZ Script Server plugin that might be of interest. You can find the plugin code at https://github.com/bluemoonfoundry/daz-script-server. The README.md file there describes how to build, install, and use the plugin, including what APIs are available.

    I am also developing an MCP server that goes with the the script server that is intended to be used to make DAZ Studio a tool that LLMs can integrate into workflows. It's currently very much beta as I'm adding a bunch of upgrades, so it's not exactly stable yet, but in case anyone wants to play with it, it's located at: https://github.com/bluemoonfoundry/daz-mcp-server

     

    Post edited by sidcarton1587 on
  • TugpsxTugpsx Posts: 878

    sidcarton1587 said:

    Tugpsx said:

    @sidcarton1587 please check your PM. A few questions about addition to your plugin.

    I replied back to you with some details, but I thought I would repost here for people who are also interested.

    I've been adding features and improvements to the DAZ Script Server plugin that might be of interest. You can find the plugin code at https://github.com/bluemoonfoundry/daz-script-server. The README.md file there describes how to build, install, and use the plugin, including what APIs are available.

    I am also developing an MCP server that goes with the the script server that is intended to be used to make DAZ Studio a tool that LLMs can integrate into workflows. It's currently very much beta as I'm adding a bunch of upgrades, so it's not exactly stable yet, but in case anyone wants to play with it, it's located at: https://github.com/bluemoonfoundry/daz-mcp-server

     

    Thanks for the update.  

  • sidcarton1587sidcarton1587 Posts: 96
    edited April 9

    Just in case anyone is following, I've added substantial updates to the Script Server and the MCP Server. 

    See https://github.com/bluemoonfoundry/daz-script-server and https://github.com/bluemoonfoundry/daz-mcp-server

    Main updates are tighter integration between the two to work together, async requests to support long-running scripts like batch renders, and probably most important 70 tools/skills/resources exposed through the MCP server that can be leveraged by LLMs. The next big update will expose the DAZ content library to natural language search, which will also be exposed as a tool. 
     

    MCP Server Tools                                                                                                                                                                     
      Documentation Tools                                                                                                                                                               
      
      ┌─────────────────┬────────────────────────────────────────────────────────────────────┐
      │      Tool       │                            Description                             │
      ├─────────────────┼────────────────────────────────────────────────────────────────────┤
      │ daz_script_help │ Get DazScript documentation, examples, and best practices by topic │
      └─────────────────┴────────────────────────────────────────────────────────────────────┘
    
      Low-Level Tools
    
      ┌──────────────────┬───────────────────────────────────────────────────────────┐
      │       Tool       │                        Description                        │
      ├──────────────────┼───────────────────────────────────────────────────────────┤
      │ daz_status       │ Check DAZ Studio connectivity and DazScriptServer version │
      ├──────────────────┼───────────────────────────────────────────────────────────┤
      │ daz_execute      │ Execute inline DazScript code with optional args          │
      ├──────────────────┼───────────────────────────────────────────────────────────┤
      │ daz_execute_file │ Execute a .dsa/.ds script file from disk                  │
      └──────────────────┴───────────────────────────────────────────────────────────┘
    
      High-Level Tools
    
      ┌──────────────────┬────────────────────────────────────────────────────────────┐
      │       Tool       │                        Description                         │
      ├──────────────────┼────────────────────────────────────────────────────────────┤
      │ daz_scene_info   │ Get scene snapshot (figures, cameras, lights, selection)   │
      ├──────────────────┼────────────────────────────────────────────────────────────┤
      │ daz_get_node     │ Read all numeric properties of a node by label/name        │
      ├──────────────────┼────────────────────────────────────────────────────────────┤
      │ daz_set_property │ Set a numeric property (transform, morph, etc.) on a node  │
      ├──────────────────┼────────────────────────────────────────────────────────────┤
      │ daz_render       │ Trigger render with current settings, optional output path │
      ├──────────────────┼────────────────────────────────────────────────────────────┤
      │ daz_load_file    │ Load a content file into the scene                         │
      └──────────────────┴────────────────────────────────────────────────────────────┘
    
      Morph Discovery Tools
    
      ┌───────────────────┬──────────────────────────────────────────────────────────────────────┐
      │       Tool        │                             Description                              │
      ├───────────────────┼──────────────────────────────────────────────────────────────────────┤
      │ daz_list_morphs   │ List all morphs on a node with current values; filter to active only │
      ├───────────────────┼──────────────────────────────────────────────────────────────────────┤
      │ daz_search_morphs │ Search morphs by name pattern (case-insensitive substring match)     │
      └───────────────────┴──────────────────────────────────────────────────────────────────────┘
    
      Scene Hierarchy Tools
    
      ┌────────────────────────┬──────────────────────────────────────────────────────────────────────┐
      │          Tool          │                             Description                              │
      ├────────────────────────┼──────────────────────────────────────────────────────────────────────┤
      │ daz_get_node_hierarchy │ Get complete hierarchy tree for a node with all descendants          │
      ├────────────────────────┼──────────────────────────────────────────────────────────────────────┤
      │ daz_list_children      │ List direct children of a node                                       │
      ├────────────────────────┼──────────────────────────────────────────────────────────────────────┤
      │ daz_get_parent         │ Get parent node of a node                                            │
      ├────────────────────────┼──────────────────────────────────────────────────────────────────────┤
      │ daz_set_parent         │ Set parent of a node with optional world-space transform maintenance │
      └────────────────────────┴──────────────────────────────────────────────────────────────────────┘
    
      Multi-Character Interaction Tools
    
      ┌───────────────────────┬─────────────────────────────────────────────────────────────────────────────┐
      │         Tool          │                                 Description                                 │
      ├───────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
      │ daz_look_at_point     │ Make character look at world-space point with configurable body involvement │
      ├───────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
      │ daz_look_at_character │ Make one character look at another character's face                         │
      ├───────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
      │ daz_reach_toward      │ Position arm to reach toward world-space point using pseudo-IK              │
      ├───────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
      │ daz_interactive_pose  │ Coordinate two characters for interaction (hug, handshake, etc.)            │
      └───────────────────────┴─────────────────────────────────────────────────────────────────────────────┘
    
      Batch Operations Tools
    
      ┌──────────────────────────┬─────────────────────────────────────────────────────────────────────────────┐
      │           Tool           │                                 Description                                 │
      ├──────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
      │ daz_batch_set_properties │ Set multiple properties on one or more nodes with individual error handling │
      ├──────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
      │ daz_batch_transform      │ Apply same transform properties to multiple nodes                           │
      ├──────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
      │ daz_batch_visibility     │ Show or hide multiple nodes                                                 │
      ├──────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
      │ daz_batch_select         │ Select multiple nodes (replace or add to current selection)                 │
      └──────────────────────────┴─────────────────────────────────────────────────────────────────────────────┘
    
      Viewport and Camera Control Tools
    
      ┌──────────────────────────┬──────────────────────────────────────────────────────────────────────────┐
      │           Tool           │                               Description                                │
      ├──────────────────────────┼──────────────────────────────────────────────────────────────────────────┤
      │ daz_set_active_camera    │ Set which camera is active in the DAZ Studio viewport                    │
      ├──────────────────────────┼──────────────────────────────────────────────────────────────────────────┤
      │ daz_orbit_camera_around  │ Position camera orbiting around target at specified angle/distance       │
      ├──────────────────────────┼──────────────────────────────────────────────────────────────────────────┤
      │ daz_frame_camera_to_node │ Frame camera to show a node (auto-calculates distance from bounding box) │
      ├──────────────────────────┼──────────────────────────────────────────────────────────────────────────┤
      │ daz_save_camera_preset   │ Save camera position/rotation as JSON-serializable preset data           │
      ├──────────────────────────┼──────────────────────────────────────────────────────────────────────────┤
      │ daz_load_camera_preset   │ Restore camera from saved preset data                                    │
      └──────────────────────────┴──────────────────────────────────────────────────────────────────────────┘
    
      Animation System Tools
    
      ┌────────────────────────┬─────────────────────────────────────────────────────────┐
      │          Tool          │                       Description                       │
      ├────────────────────────┼─────────────────────────────────────────────────────────┤
      │ daz_set_keyframe       │ Set a keyframe on a property at specified frame         │
      ├────────────────────────┼─────────────────────────────────────────────────────────┤
      │ daz_get_keyframes      │ Get all keyframes for a property                        │
      ├────────────────────────┼─────────────────────────────────────────────────────────┤
      │ daz_remove_keyframe    │ Remove a keyframe at specified frame                    │
      ├────────────────────────┼─────────────────────────────────────────────────────────┤
      │ daz_clear_animation    │ Remove all keyframes from a property                    │
      ├────────────────────────┼─────────────────────────────────────────────────────────┤
      │ daz_set_frame          │ Set current animation frame                             │
      ├────────────────────────┼─────────────────────────────────────────────────────────┤
      │ daz_set_frame_range    │ Set animation frame range (start and end)               │
      ├────────────────────────┼─────────────────────────────────────────────────────────┤
      │ daz_get_animation_info │ Get animation timeline info (current frame, range, fps) │
      └────────────────────────┴─────────────────────────────────────────────────────────┘
    
      Advanced Rendering Control Tools
    
      ┌──────────────────────────┬───────────────────────────────────────────────────────┐
      │           Tool           │                      Description                      │
      ├──────────────────────────┼───────────────────────────────────────────────────────┤
      │ daz_render_with_camera   │ Render from specific camera without changing viewport │
      ├──────────────────────────┼───────────────────────────────────────────────────────┤
      │ daz_get_render_settings  │ Get current render settings and configuration         │
      ├──────────────────────────┼───────────────────────────────────────────────────────┤
      │ daz_batch_render_cameras │ Render from multiple cameras in sequence              │
      ├──────────────────────────┼───────────────────────────────────────────────────────┤
      │ daz_render_animation     │ Render animation frame range as image sequence        │
      └──────────────────────────┴───────────────────────────────────────────────────────┘
    
      Spatial Query Tools
    
      ┌──────────────────────────────┬─────────────────────────────────────────────────────────────────────────┐
      │             Tool             │                               Description                               │
      ├──────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
      │ daz_get_world_position       │ Get world-space position, local position, rotation, and scale of a node │
      ├──────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
      │ daz_get_bounding_box         │ Get bounding box (min/max corners, center, dimensions) of a node        │
      ├──────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
      │ daz_calculate_distance       │ Calculate distance and direction vector between two nodes (cm)          │
      ├──────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
      │ daz_get_spatial_relationship │ Natural language spatial relationship between two nodes                 │
      ├──────────────────────────────┼─────────────────────────────────────────────────────────────────────────┤
      │ daz_check_overlap            │ Check if two nodes have overlapping bounding boxes                      │
      └──────────────────────────────┴─────────────────────────────────────────────────────────────────────────┘
    
      Property Introspection Tools
    
      ┌───────────────────────────┬──────────────────────────────────────────────────────┐
      │           Tool            │                     Description                      │
      ├───────────────────────────┼──────────────────────────────────────────────────────┤
      │ daz_inspect_properties    │ List all properties on a node; filter by type        │
      ├───────────────────────────┼──────────────────────────────────────────────────────┤
      │ daz_get_property_metadata │ Get detailed metadata for a single named property    │
      ├───────────────────────────┼──────────────────────────────────────────────────────┤
      │ daz_validate_script       │ Static analysis of DazScript for known anti-patterns │
      └───────────────────────────┴──────────────────────────────────────────────────────┘
    
      Lighting Preset Tools
    
      ┌───────────────────────────┬──────────────────────────────────────────────────────────────────────────────┐
      │           Tool            │                                 Description                                  │
      ├───────────────────────────┼──────────────────────────────────────────────────────────────────────────────┤
      │ daz_apply_lighting_preset │ Create a professional lighting setup in one command                          │
      ├───────────────────────────┼──────────────────────────────────────────────────────────────────────────────┤
      │ daz_validate_scene        │ Validate scene for collisions, lighting, cameras, and figures; returns score │
      └───────────────────────────┴──────────────────────────────────────────────────────────────────────────────┘
    
      Emotional Direction Tools
    
      ┌─────────────────┬───────────────────────────────────────────────────────────────────────┐
      │      Tool       │                              Description                              │
      ├─────────────────┼───────────────────────────────────────────────────────────────────────┤
      │ daz_set_emotion │ Apply an emotional expression to a character (morphs + body language) │
      └─────────────────┴───────────────────────────────────────────────────────────────────────┘
    
      Content Library Navigation Tools
    
      ┌──────────────────────┬────────────────────────────────────────────────────────────┐
      │         Tool         │                        Description                         │
      ├──────────────────────┼────────────────────────────────────────────────────────────┤
      │ daz_list_categories  │ List subdirectories in content library under a parent path │
      ├──────────────────────┼────────────────────────────────────────────────────────────┤
      │ daz_browse_category  │ List .duf files in a content library category path         │
      ├──────────────────────┼────────────────────────────────────────────────────────────┤
      │ daz_get_content_info │ Read metadata from a .duf file without loading it          │
      └──────────────────────┴────────────────────────────────────────────────────────────┘
    
      Scene Composition / Cinematography Tools
    
      ┌────────────────────────────┬──────────────────────────────────────────────────────────────┐
      │            Tool            │                         Description                          │
      ├────────────────────────────┼──────────────────────────────────────────────────────────────┤
      │ daz_apply_composition_rule │ Position camera using a photography composition rule         │
      ├────────────────────────────┼──────────────────────────────────────────────────────────────┤
      │ daz_frame_shot             │ Frame camera to subject using a standard cinematic shot type │
      ├────────────────────────────┼──────────────────────────────────────────────────────────────┤
      │ daz_apply_camera_angle     │ Apply a standard camera angle preset relative to a subject   │
      └────────────────────────────┴──────────────────────────────────────────────────────────────┘
    
      Scene Checkpoint Tools
    
      ┌─────────────────────────┬─────────────────────────────────────────────────────────────────────────────┐
      │          Tool           │                                 Description                                 │
      ├─────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
      │ daz_save_scene_state    │ Save current transforms, morphs, and light properties as a named checkpoint │
      ├─────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
      │ daz_restore_scene_state │ Restore scene state from a named checkpoint                                 │
      ├─────────────────────────┼─────────────────────────────────────────────────────────────────────────────┤
      │ daz_list_checkpoints    │ List all saved checkpoints in the current session                           │
      └─────────────────────────┴─────────────────────────────────────────────────────────────────────────────┘
    
      Scene Layout & Proximity Tools
    
      ┌───────────────────────┬───────────────────────────────────────────────────────────────────────┐
      │         Tool          │                              Description                              │
      ├───────────────────────┼───────────────────────────────────────────────────────────────────────┤
      │ daz_get_scene_layout  │ Full spatial map of all scene nodes with positions and bounding boxes │
      ├───────────────────────┼───────────────────────────────────────────────────────────────────────┤
      │ daz_find_nearby_nodes │ Find all nodes within a radius of a target node                       │
      └───────────────────────┴───────────────────────────────────────────────────────────────────────┘
    
      Total: 70 tools across 17 categories
    Post edited by sidcarton1587 on
  • TugpsxTugpsx Posts: 878

    Wow quite an improvement, will update shortly.

  • sidcarton1587sidcarton1587 Posts: 96
    edited May 6

    For those following progress, I've implemented the natural language search for DAZ content here: bluemoonfoundry/daz-content-browser: Semantic search and content browser for DAZ 3D asset libraries

    The main part of the code is a server with an API web frontend that will be incorporated as a tool to the MCP server. The intent is that an LLM can now be given access to search a user's assets using natural langauge. 

    I've also included a reference web UI so that you can use this app as a generic content browser using natural language queries over your asset library. If you also install the DAZ script server, the UI will allow you to click a button on a search result and have it open up in Studio's content manager pane. 

    More to come!

     

    Screenshot 2026-05-05 080942.png
    2387 x 1298 - 3M
    Post edited by sidcarton1587 on
  • sidcarton1587sidcarton1587 Posts: 96
    edited May 17

    Hi all, 

      I  did a major revamp of the DAZ Script Server plugin to clean up some lingering bugs as well as adding a Python package to drive the plugin. Now you can write code in Python, which translates to DAZ Script and submits requests through the script server. So stuff like this is now possible:

    import timefrom dazpy import DazClient, DazSceneclient = DazClient()          # auto-loads token from ~/.daz3d/dazscriptserver_token.txtscene  = DazScene(client)print(scene.num_nodes(), "nodes in scene")figure = scene.find_skeleton_by_label("Genesis 9")bones = figure.bones()print([b._identifier.value for b in bones if "neck" in b._identifier.value.lower()])rots=[0,4,10,15,20]for rot in rots:    print (f"Set rotation {rot}")    figure.find_bone("neck1").set_local_rotation(0, rot, 0)    time.sleep(1)

    The Python scripting is new in v2.1.0, and there is a lot of testing to be done, but I thought I'd put it out there for people to kick the tires on. See what you think. If there are features you'd like to see, feel free to raise a GitHub issue and request 'em.

    Links:

     The main GitHub repo for the project: bluemoonfoundry/daz-script-server: DAZ Studio plugin: embedded HTTP server for executing DazScript remotely and returning results as JSON

     The GitHub release page for v2.0.0, which contains all this stuff: Release v2.0.0 — dazpy Python SDK + plugin rewrite · bluemoonfoundry/daz-script-server

     The Script Server API docs: DazScript Server — HTTP API Reference

     The dazpy Python package API docs: dazpy — DAZ Studio Python SDK — dazpy 0.1.0 documentation

    --sid 

     

     

    Post edited by sidcarton1587 on
  • TugpsxTugpsx Posts: 878

    sidcarton1587 said:

    Hi all, 

      I  did a major revamp of the DAZ Script Server plugin to clean up some lingering bugs as well as adding a Python package to drive the plugin. Now you can write code in Python, which translates to DAZ Script and submits requests through the script server. So stuff like this is now possible:

    import timefrom dazpy import DazClient, DazSceneclient = DazClient()          # auto-loads token from ~/.daz3d/dazscriptserver_token.txtscene  = DazScene(client)print(scene.num_nodes(), "nodes in scene")figure = scene.find_skeleton_by_label("Genesis 9")bones = figure.bones()print([b._identifier.value for b in bones if "neck" in b._identifier.value.lower()])rots=[0,4,10,15,20]for rot in rots:    print (f"Set rotation {rot}")    figure.find_bone("neck1").set_local_rotation(0, rot, 0)    time.sleep(1)

    The Python scripting is still in v0.1.0, and there is a lot of testing to be done, but I thought I'd put it out there for people to kick the tires on. See what you think. If there are features you'd like to see, feel free to raise a GitHub issue and request 'em.

    Links:

     The main GitHub repo for the project: bluemoonfoundry/daz-script-server: DAZ Studio plugin: embedded HTTP server for executing DazScript remotely and returning results as JSON

     The GitHub release page for v2.0.0, which contains all this stuff: Release v2.0.0 — dazpy Python SDK + plugin rewrite · bluemoonfoundry/daz-script-server

     The Script Server API docs: DazScript Server — HTTP API Reference

     The dazpy Python package API docs: dazpy — DAZ Studio Python SDK — dazpy 0.1.0 documentation

    --sid 

     

     

    I'll have to check into this agin once I repair my dead data drive. Warning make sure your AIO pump is not connected to the same power header as any hard drive, when the pump goes so does you hard drive. 

  • sidcarton1587sidcarton1587 Posts: 96

    Tugpsx said:

    sidcarton1587 said:

    Hi all, 

      I  did a major revamp of the DAZ Script Server plugin to clean up some lingering bugs as well as adding a Python package to drive the plugin. Now you can write code in Python, which translates to DAZ Script and submits requests through the script server. So stuff like this is now possible:

    import timefrom dazpy import DazClient, DazSceneclient = DazClient()          # auto-loads token from ~/.daz3d/dazscriptserver_token.txtscene  = DazScene(client)print(scene.num_nodes(), "nodes in scene")figure = scene.find_skeleton_by_label("Genesis 9")bones = figure.bones()print([b._identifier.value for b in bones if "neck" in b._identifier.value.lower()])rots=[0,4,10,15,20]for rot in rots:    print (f"Set rotation {rot}")    figure.find_bone("neck1").set_local_rotation(0, rot, 0)    time.sleep(1)

    The Python scripting is still in v0.1.0, and there is a lot of testing to be done, but I thought I'd put it out there for people to kick the tires on. See what you think. If there are features you'd like to see, feel free to raise a GitHub issue and request 'em.

    Links:

     The main GitHub repo for the project: bluemoonfoundry/daz-script-server: DAZ Studio plugin: embedded HTTP server for executing DazScript remotely and returning results as JSON

     The GitHub release page for v2.0.0, which contains all this stuff: Release v2.0.0 — dazpy Python SDK + plugin rewrite · bluemoonfoundry/daz-script-server

     The Script Server API docs: DazScript Server — HTTP API Reference

     The dazpy Python package API docs: dazpy — DAZ Studio Python SDK — dazpy 0.1.0 documentation

    --sid 

     

     

    I'll have to check into this agin once I repair my dead data drive. Warning make sure your AIO pump is not connected to the same power header as any hard drive, when the pump goes so does you hard drive. 

     

    Ouch!

     

     

  • sidcarton1587sidcarton1587 Posts: 96

    Latest release updated to v2.1.0 with a bunch of examples of using the Python API. 

    Release v2.1.0 · bluemoonfoundry/daz-script-server

  • TugpsxTugpsx Posts: 878
    edited May 17

    sidcarton1587 said:

    Latest release updated to v2.1.0 with a bunch of examples of using the Python API. 

    Release v2.1.0 · bluemoonfoundry/daz-script-server

    Thanks for the update.  Correct me if i'm wrong what is the proper process for installing the too. Please check your PM.

     

    Post edited by Tugpsx on
  • TugpsxTugpsx Posts: 878
    edited May 17

    Created a Daz lanucher to test your sample python scripts. The list is dynamic and is created when the user selects the folder containing python scripts. Adding or removing a script will adjust the list.

    Screenshot 2026-05-17 035957.png
    199 x 488 - 19K
    Post edited by Tugpsx on
  • TugpsxTugpsx Posts: 878
    edited May 17

    Here is the code for the test launcher. It can use some refining but it makes it easier for users to test your Daz Script Server functions.

    // DAZ Studio version 4.24.0.4 filetype DAZ Scriptvar PYTHON_EXE     = "python.exe";var SCRIPTS_FOLDER = "C:/daz-script-server-2.1.0/docs/examples";function getTempBatPath() {    return folderEdit.text + "/run_python_script.bat";}// ------------------------------------------------------------// Build dialog// ------------------------------------------------------------var dlg = new DzDialog();dlg.caption = "Python Script Launcher";var layout = new DzVBoxLayout(dlg);layout.autoAdd = true;// ------------------------------------------------------------// Folder label// ------------------------------------------------------------var folderLabel = new DzLabel(dlg);folderLabel.text = "Scripts Folder:";// ------------------------------------------------------------// Folder text field// ------------------------------------------------------------var folderEdit = new DzLineEdit(dlg);folderEdit.text = SCRIPTS_FOLDER;// ------------------------------------------------------------// Browse button// ------------------------------------------------------------var browseBtn = new DzPushButton(dlg);browseBtn.text = "Browse...";// ------------------------------------------------------------// Radio button group// ------------------------------------------------------------var group = new DzVButtonGroup(dlg);group.columns = 1;var radioButtons = [];var files = [];// ------------------------------------------------------------// Load scripts function// ------------------------------------------------------------function loadScripts() {    // Clear old buttons    for (var i = 0; i < radioButtons.length; i++) {        radioButtons[i].deleteLater();    }    radioButtons = [];    var dir = new DzDir(folderEdit.text);    files = dir.entryList("*.py", DzDir.Files);    if (files.length === 0) {        var rb = new DzRadioButton(group);        rb.text = "(No Python scripts found)";        radioButtons.push(rb);        return;    }    for (var i = 0; i < files.length; i++) {        var rb = new DzRadioButton(group);        rb.text = files[i].replace(".py", "");        radioButtons.push(rb);    }    if (radioButtons.length > 0)        radioButtons[0].checked = true;}loadScripts();// ------------------------------------------------------------// Browse button logic (OEM-safe folder picker)// ------------------------------------------------------------browseBtn.clicked.connect(function() {    var chosenFile = FileDialog.doFileDialog(        true,        "Select ANY file inside the folder",        folderEdit.text,        "*.*"    );    if (chosenFile) {        var folder = chosenFile.replace(/\\/g, "/");        folder = folder.substring(0, folder.lastIndexOf("/"));        folderEdit.text = folder;        loadScripts();    }});// ------------------------------------------------------------// Run button// ------------------------------------------------------------var runBtn = new DzPushButton(dlg);runBtn.text = "Run Selected Script";runBtn.clicked.connect(function() {    if (files.length === 0) {        print("No scripts to run.");        return;    }    var selected = null;    for (var i = 0; i < radioButtons.length; i++) {        if (radioButtons[i].checked) {            selected = files[i];            break;        }    }    if (!selected) {        print("No script selected.");        return;    }    var scriptPath = folderEdit.text + "/" + selected;    var batPath = getTempBatPath();    print("Launching Python script:");    print(scriptPath);    // Create .BAT file inside the selected folder    var bat = new DzFile(batPath);    if (bat.open(DzFile.WriteOnly)) {        bat.writeLine('"' + PYTHON_EXE + '" "' + scriptPath + '"');        bat.close();    }    // Launch via Windows Shell    App.showURL("file:///" + batPath.replace(/\\/g, "/"));});// ------------------------------------------------------------// Close button// ------------------------------------------------------------var closeBtn = new DzPushButton(dlg);closeBtn.text = "Close";closeBtn.clicked.connect(function() {    dlg.close();});// ------------------------------------------------------------// Show dialog// ------------------------------------------------------------dlg.exec();

     

    Post edited by Tugpsx on
  • TugpsxTugpsx Posts: 878

    Thanks for the update and added examples. Wasn't there an update on the server side to V2.2.0, the banner still shows as " DAZ Script Server v2.1.0"

  • sidcarton1587sidcarton1587 Posts: 96

    Tugpsx said:

    Thanks for the update and added examples. Wasn't there an update on the server side to V2.2.0, the banner still shows as " DAZ Script Server v2.1.0"

    Yep, but was having some trouble with the GitHub CI files keeping things in sync. Went ahead and fixed it for the v2.2.0 release, but I just published v2.3.0 so it's kind of moot now. 

  • sidcarton1587sidcarton1587 Posts: 96
    edited May 21

    So for those who are following, I just published v2.3.0 of the Script Server (and the dazpy Python package that's bundled with it) at Release v2.3.0 — Scene Change Events (SSE) · bluemoonfoundry/daz-script-server

    The major updates include:

    - Added animation and Geometry support. 

    *  DazPose (snapshot/restore/interpolate full skeleton poses)

    * DazAnimation (keyframe read/write)

    * Vec3/Quat/BoundingBox math types to the dazpy SDK.


    * New posed vertex positions API (vertex_positions_posed)

    * A new batch of example scripts covering:

        - USD export

       - BVH motion-capture import (though this one is still very flakey

       - Keyframe baking

       - geometry analysis, and animation blending.

       - Basic body measurements analyzer similar to Measure Metrics for G8,G8.1, and G9 

    * Full Sphinx API docs and an integration test suite.

    * A Server-Sent Events (using a /scene/events endpoint) feature that pushes live DAZ Studio scene changes — nodes, skeletons, lights, cameras,
      selection, time, and render events — to any connected client.

    Post edited by sidcarton1587 on
  • TugpsxTugpsx Posts: 878
    edited May 21

    Wow! you have been busy. I have updated the Launcher to include an argument option as well as a help button so the user knows the parameters needed.

    Screenshot 2026-05-21 163405.png
    202 x 790 - 29K
    Post edited by Tugpsx on
  • TugpsxTugpsx Posts: 878
    // DAZ Studio version 4.24.0.4 filetype DAZ Script// Python Script Launcher for DAZ Studio (final .dsa)// - Auto-discover and prefer existing config file// - Atomic saves with .tmp and .bak// - Defensive reads and self-healing restore// - Uses DAZ getSetting/saveSetting to persist last folder/selection// - Adds per-script args field and "Show Help" support// - Loads config before building UI so settings persist//// Paste this entire file into your .dsa and run it.var PYTHON_EXE = "python.exe";var folderEdit;var argsEdit;var helpBtn;var radioButtons = [];var files = [];var lastSelected = "";var CONFIG_FILE = null;var filesArgs = {}; // mapping scriptName -> args string// ---------------- helpers ----------------function normalizePath(p) {    if (!p) return null;    p = p.replace(/\\/g, "/");    if (p.charAt(p.length - 1) !== "/") p += "/";    return p;}function tryCreateDir(dirPath) {    try {        if (!dirPath) return false;        var d = new DzDir(dirPath);        if (!d.exists()) {            var tf = new DzFile(dirPath.replace(/\\/g, "/") + "/.mkdir_test");            if (tf.open(DzFile.WriteOnly)) { tf.writeLine("test"); try { tf.flush(); } catch(e) {} tf.close(); tf.remove(); }        }        return true;    } catch(e) { return false; }}function existsAndWritable(dirPath) {    try {        if (!dirPath) return false;        var testFile = new DzFile(dirPath.replace(/\\/g, "/") + "/.write_test");        if (testFile.open(DzFile.WriteOnly)) { testFile.writeLine("test"); try { testFile.flush(); } catch(e) {} testFile.close(); testFile.remove(); return true; }    } catch(e) {}    return false;}function stripBOM(s) {    if (!s) return s;    if (s.charCodeAt && s.charCodeAt(0) === 0xFEFF) return s.substring(1);    return s;}// ---------------- discover config dir ----------------function findConfigDir() {    try {        if (typeof App !== "undefined" && typeof App.getPreference === "function") {            var keys = ["TemporaryFilesPath","TempFilesPath","TempPath","DsonCachePath","DSONCachePath","CachePath","UserScriptsPath","ScriptsPath"];            for (var i=0;i<keys.length;i++) {                try {                    var v = App.getPreference(keys[i]);                    if (v && v.length>0) { v = normalizePath(v); tryCreateDir(v); if (existsAndWritable(v)) { print("Using DAZ preference path: " + v); return v; } }                } catch(e) {}            }        }    } catch(e) {}    try {        var envAppData = null;        try { envAppData = System.getenv("APPDATA"); } catch(e) {}        if (envAppData && envAppData.length>0) { var c = normalizePath(envAppData + "/PythonLauncher"); tryCreateDir(c); if (existsAndWritable(c)) { print("Using APPDATA path: " + c); return c; } }        var home = null;        try { home = System.getenv("HOME"); } catch(e) {}        if (home && home.length>0) {            var mac = normalizePath(home + "/Library/Application Support/PythonLauncher"); tryCreateDir(mac); if (existsAndWritable(mac)) { print("Using macOS Application Support path: " + mac); return mac; }            var linux = normalizePath(home + "/.config/pythonlauncher"); tryCreateDir(linux); if (existsAndWritable(linux)) { print("Using XDG config path: " + linux); return linux; }        }    } catch(e) {}    try {        var userHome = null;        try { userHome = System.getenv("HOME"); } catch(e) {}        if (!userHome) try { userHome = System.getenv("USERPROFILE"); } catch(e) {}        if (userHome) {            var d1 = normalizePath(userHome + "/AppData/Local/DAZ 3D/Studio4/Cache"); tryCreateDir(d1); if (existsAndWritable(d1)) { print("Using DAZ cache candidate: " + d1); return d1; }            var d2 = normalizePath(userHome + "/Library/Application Support/DAZ 3D/Studio4/Cache"); tryCreateDir(d2); if (existsAndWritable(d2)) { print("Using DAZ cache candidate (mac): " + d2); return d2; }        }    } catch(e) {}    try {        var scriptFileName = "";        try { if (typeof getScriptFileName === "function") scriptFileName = getScriptFileName(); } catch(e) { scriptFileName = ""; }        if (scriptFileName && scriptFileName.length>0) {            var sf = new DzFile(scriptFileName);            var sd = normalizePath(sf.path());            if (sd) { tryCreateDir(sd); if (existsAndWritable(sd)) { print("Using script folder: " + sd); return sd; } }        }    } catch(e) {}    try {        var home2 = System.getenv("HOME") || System.getenv("USERPROFILE");        if (home2) {            var docs = normalizePath(home2 + "/Documents/DAZ 3D/Studio/Scripts");            tryCreateDir(docs); if (existsAndWritable(docs)) { print("Using Documents Scripts folder: " + docs); return docs; }        }    } catch(e) {}    print("No writable config directory found via discovery.");    return null;}// ---------------- read/write helpers ----------------function readFileAsString(path) {    try {        var f = new DzFile(path);        if (!f.exists()) return null;        if (!f.open(DzFile.ReadOnly)) return null;        var raw = f.readAll();        f.close();        if (typeof raw === "undefined" || raw === null) return "";        if (typeof raw !== "string") {            try { raw = JSON.stringify(raw); } catch(e) { try { raw = String(raw); } catch(e) { raw = ""; } }        }        raw = stripBOM(raw);        return raw;    } catch(e) {        return null;    }}function loadConfigFromPath(path) {    try {        var text = readFileAsString(path);        if (text === null) return false;        try {            var obj = JSON.parse(text);            if (obj.lastFolder) folderEdit.text = obj.lastFolder;            if (obj.scriptList) files = obj.scriptList;            if (obj.lastSelected) lastSelected = obj.lastSelected;            filesArgs = obj.filesArgs || {};            print("Config loaded from: " + path);            return true;        } catch(e) {            print("Config JSON parse error for: " + path);            print("Contents preview: " + (text ? (text.length>400 ? text.substring(0,400) + "..." : text) : "<empty>"));            // Try .bak restore            var bakPath = path + ".bak";            var bakText = readFileAsString(bakPath);            if (bakText !== null && bakText.length > 0) {                try {                    var bakObj = JSON.parse(bakText);                    // atomic restore: write bakText to tmp then rename                    var tmp = new DzFile(path + ".tmp");                    if (tmp.open(DzFile.WriteOnly | DzFile.Truncate)) {                        tmp.writeLine(bakText);                        try { tmp.flush(); } catch(e) {}                        tmp.close();                        try {                            if (typeof tmp.rename === "function") tmp.rename(path);                            else {                                var final = new DzFile(path);                                if (final.open(DzFile.WriteOnly | DzFile.Truncate)) { final.writeLine(bakText); try { final.flush(); } catch(e) {} final.close(); }                            }                        } catch(e) {}                        folderEdit.text = bakObj.lastFolder || folderEdit.text;                        files = bakObj.scriptList || files;                        lastSelected = bakObj.lastSelected || lastSelected;                        filesArgs = bakObj.filesArgs || filesArgs;                        print("Restored config from backup: " + bakPath);                        return true;                    } else {                        print("Failed to open temp file for restore: " + path + ".tmp");                    }                } catch(e) {                    print("Backup JSON parse failed or backup invalid: " + bakPath);                }            } else {                print("No valid backup found at: " + bakPath);            }            // No valid backup: remove corrupted file so we can start fresh            try {                var rem = new DzFile(path);                if (rem.exists()) rem.remove();                print("Removed corrupted config file: " + path);            } catch(e) {                print("Failed to remove corrupted config file: " + e);            }            return false;        }    } catch(e) {        print("Error reading config: " + e);        return false;    }}function atomicSaveWithBackup(targetPath, text) {    tryCreateDir(targetPath.substring(0, targetPath.lastIndexOf("/")));    var tmpPath = targetPath + ".tmp";    var tmp = new DzFile(tmpPath);    if (!tmp.open(DzFile.WriteOnly | DzFile.Truncate)) { print("Failed to open temp file: " + tmpPath); return false; }    tmp.writeLine(text);    try { tmp.flush(); } catch(e) {}    tmp.close();    // rotate backup    var orig = new DzFile(targetPath);    if (orig.exists()) {        var bakPath = targetPath + ".bak";        try {            if (typeof orig.rename === "function") orig.rename(bakPath);            else {                var r = new DzFile(bakPath);                if (r.open(DzFile.WriteOnly | DzFile.Truncate)) {                    if (orig.open(DzFile.ReadOnly)) { r.writeLine(orig.readAll()); orig.close(); }                    r.close();                    orig.remove();                }            }        } catch(e) { print("Backup rotation failed: " + e); }    }    // rename tmp -> target    var mover = new DzFile(tmpPath);    try {        if (typeof mover.rename === "function") { mover.rename(targetPath); print("Config atomically renamed to: " + targetPath); return true; }    } catch(e) {}    // fallback copy    try {        if (mover.open(DzFile.ReadOnly)) {            var t = mover.readAll();            mover.close();            var final = new DzFile(targetPath);            if (final.open(DzFile.WriteOnly | DzFile.Truncate)) { final.writeLine(t); try { final.flush(); } catch(e) {} final.close(); mover.remove(); print("Config written to target via copy: " + targetPath); return true; }        }    } catch(e) {}    print("Atomic replace failed for: " + targetPath);    return false;}function saveConfig() {    var obj = { lastFolder: folderEdit.text, scriptList: files, lastSelected: lastSelected, filesArgs: filesArgs || {} };    var text = JSON.stringify(obj); // always stringify to avoid [object Object]    var target = CONFIG_FILE;    if (!target && folderEdit && folderEdit.text) target = folderEdit.text.replace(/\\/g,"/") + "/python_launcher_config.json";    if (!target) { print("No target path available for saving config."); return false; }    if (atomicSaveWithBackup(target, text)) { CONFIG_FILE = target; print("Config saved to: " + target);        // Also persist into DAZ settings for quick startup        try { saveSetting("pythonLauncher.lastFolder", folderEdit.text); } catch(e) {}        try { saveSetting("pythonLauncher.lastSelected", lastSelected); } catch(e) {}        // persist per-script args into DAZ settings (optional)        try {            for (var k in filesArgs) {                if (filesArgs.hasOwnProperty(k)) {                    try { saveSetting("pythonLauncher.args." + k, filesArgs[k]); } catch(e) {}                }            }        } catch(e) {}        return true;    }    print("All attempts to save config failed.");    return false;}// ---------------- per-script args helpers ----------------function getSavedArgsForScript(scriptName) {    try {        if (filesArgs && filesArgs[scriptName]) return filesArgs[scriptName];        // fallback to DAZ setting        try {            var s = getSetting ? getSetting("pythonLauncher.args." + scriptName, "") : "";            if (s && s.length > 0) return s;        } catch(e) {}    } catch(e) {}    return "";}function saveArgsForScript(scriptName, argsString) {    try {        if (!filesArgs) filesArgs = {};        filesArgs[scriptName] = argsString;        // persist        saveConfig();        try { saveSetting("pythonLauncher.args." + scriptName, argsString); } catch(e) {}    } catch(e) {}}// ---------------- scanning & UI helpers ----------------function scanScripts() {    try { var dir = new DzDir(folderEdit.text); files = dir.entryList("*.py", DzDir.Files); } catch(e) { files = []; }}function buildScriptButtons(group) {    for (var i=0;i<radioButtons.length;i++) radioButtons[i].deleteLater();    radioButtons = [];    if (files.length === 0) { var rb0 = new DzRadioButton(group); rb0.text = "(No Python scripts found)"; radioButtons.push(rb0); return; }    for (var j=0;j<files.length;j++) {        var rb = new DzRadioButton(group);        rb.text = files[j].replace(".py","");        radioButtons.push(rb);        if (files[j] === lastSelected) rb.checked = true;    }    if (!radioButtons.some(function(r){ return r.checked; })) radioButtons[0].checked = true;}// ---------------- build dialog and load config (correct order) ----------------var dlg = new DzDialog(); dlg.caption = "Python Script Launcher";var layout = new DzVBoxLayout(dlg); layout.autoAdd = true;var folderLabel = new DzLabel(dlg); folderLabel.text = "Scripts Folder:";folderEdit = new DzLineEdit(dlg);// Args UIvar argsLabel = new DzLabel(dlg); argsLabel.text = "Args (command line)";argsEdit = new DzLineEdit(dlg);argsEdit.placeholderText = "--flag value --option other";helpBtn = new DzPushButton(dlg); helpBtn.text = "Show Help";// --- Initialize folderEdit from DAZ settings (fallback) ---try {    var defaultPath = "";    try { defaultPath = new DzFile(getScriptFileName()).path(); } catch(e) { defaultPath = ""; }    try {        var saved = getSetting("pythonLauncher.lastFolder", defaultPath);        if (saved && saved.length > 0) {            folderEdit.text = saved;            print("folderEdit initialized from DAZ setting: " + folderEdit.text);        } else if (defaultPath && defaultPath.length > 0) {            folderEdit.text = defaultPath;            print("folderEdit initialized to default script path: " + folderEdit.text);        }    } catch(e) {        print("getSetting not available or failed: " + e);    }} catch(e) {    print("Error initializing folderEdit from settings: " + e);}// Discover config dir and prefer existing config filevar discovered = findConfigDir();if (discovered) {    var candidate = discovered + "python_launcher_config.json";    var candFile = new DzFile(candidate);    if (candFile.exists()) {        CONFIG_FILE = candidate;        print("Using existing config in discovered folder: " + CONFIG_FILE);    } else {        // explicit G: fallback (optional; keeps compatibility with your examples)        var gCandidate = "G:/DazServer/daz-script-server/docs/examples/python_launcher_config.json";        var gFile = new DzFile(gCandidate);        if (gFile.exists()) {            CONFIG_FILE = gCandidate;            print("Using existing config on G: drive: " + CONFIG_FILE);        } else {            CONFIG_FILE = candidate; // will create here on save            print("No existing config found; will use: " + CONFIG_FILE);        }    }} else {    print("No discovered config dir; will use fallback next to scripts when saving.");}// Load config now that folderEdit existsvar loaded = false;if (CONFIG_FILE) {    loaded = loadConfigFromPath(CONFIG_FILE);}if (!loaded) {    // try fallback next to script folder if folderEdit already has a path    var fallback = null;    if (folderEdit && folderEdit.text && folderEdit.text.length>0) fallback = folderEdit.text.replace(/\\/g,"/") + "/python_launcher_config.json";    if (!fallback) {        // try explicit G: fallback        var gCandidate2 = "G:/DazServer/daz-script-server/docs/examples/python_launcher_config.json";        var gFile2 = new DzFile(gCandidate2);        if (gFile2.exists()) fallback = gCandidate2;    }    if (fallback) {        if (loadConfigFromPath(fallback)) { CONFIG_FILE = fallback; loaded = true; }    }}// If loaded, ensure DAZ settings reflect it and apply lastFolder/lastSelected to UItry {    if (loaded) {        try { saveSetting("pythonLauncher.lastFolder", folderEdit.text); } catch(e) {}        try { saveSetting("pythonLauncher.lastSelected", lastSelected); } catch(e) {}        // If folderEdit is empty but config had lastFolder, ensure it's set        if ((!folderEdit.text || folderEdit.text.length === 0) && CONFIG_FILE) {            var txt = readFileAsString(CONFIG_FILE);            if (txt && txt.length > 0) {                try {                    var obj = JSON.parse(txt);                    if (obj.lastFolder && obj.lastFolder.length > 0) folderEdit.text = obj.lastFolder;                    if (obj.lastSelected && obj.lastSelected.length > 0) lastSelected = obj.lastSelected;                    filesArgs = obj.filesArgs || filesArgs;                } catch(e) {}            }        }    } else {        // If not loaded, try to initialize lastSelected from DAZ settings        try {            var savedSel = getSetting ? getSetting("pythonLauncher.lastSelected", "") : "";            if (savedSel && savedSel.length > 0) lastSelected = savedSel;        } catch(e) {}    }} catch(e) {}// Build UI controls after config loadvar browseBtn = new DzPushButton(dlg); browseBtn.text = "Browse...";var group = new DzVButtonGroup(dlg); group.columns = 1;// If no file list from config but folderEdit has a path, scan it nowif ((!files || files.length === 0) && folderEdit && folderEdit.text && folderEdit.text.length > 0) {    scanScripts();}buildScriptButtons(group);// Attach radio button click handlers to populate argsEditfor (var i=0;i<radioButtons.length;i++) {    (function(idx){        try {            radioButtons[idx].clicked.connect(function() {                var name = files[idx];                var saved = getSavedArgsForScript(name);                argsEdit.text = saved || "";            });        } catch(e) {}    })(i);}// Prepopulate argsEdit if lastSelected existsif (lastSelected && lastSelected.length > 0) {    argsEdit.text = getSavedArgsForScript(lastSelected) || "";}// Browse buttonbrowseBtn.clicked.connect(function() {    var chosenFile = FileDialog.doFileDialog(true, "Select ANY file inside the folder", folderEdit.text, "*.*");    if (chosenFile) {        var folder = chosenFile.replace(/\\/g, "/");        folder = folder.substring(0, folder.lastIndexOf("/"));        folderEdit.text = folder;        scanScripts();        buildScriptButtons(group);        // Save to config and DAZ settings        saveConfig();        try { saveSetting("pythonLauncher.lastFolder", folder); } catch(e) { print("saveSetting failed in Browse handler: " + e); }        // update argsEdit for new selection if any        if (lastSelected && lastSelected.length > 0) argsEdit.text = getSavedArgsForScript(lastSelected) || "";    }});// Run buttonvar runBtn = new DzPushButton(dlg); runBtn.text = "Run Selected Script";runBtn.clicked.connect(function() {    if (files.length === 0) { print("No scripts to run."); return; }    var selected = null;    for (var i=0;i<radioButtons.length;i++) if (radioButtons[i].checked) { selected = files[i]; break; }    if (!selected) { print("No script selected."); return; }    lastSelected = selected;    // Read args from argsEdit and save per-script    var argsString = "";    try { argsString = argsEdit.text ? argsEdit.text.trim() : ""; } catch(e) { argsString = ""; }    saveArgsForScript(selected, argsString);    // Save config (includes filesArgs)    saveConfig();    // Build command line    var scriptPath = folderEdit.text + "/" + selected;    var batPath = folderEdit.text + "/run_python_script.bat";    print("Launching Python script:");    print(scriptPath);    if (argsString && argsString.length > 0) print("With args: " + argsString);    var bat = new DzFile(batPath);    if (bat.open(DzFile.WriteOnly | DzFile.Truncate)) {        // Quote script path; append args as-is (user responsible for quoting complex args)        var cmd = '"' + PYTHON_EXE + '" "' + scriptPath + '"';        if (argsString && argsString.length > 0) cmd += " " + argsString;        bat.writeLine(cmd);        bat.close();    } else {        print("Failed to write run .bat to: " + batPath);        return;    }    try { App.showURL("file:///" + batPath.replace(/\\/g,"/")); } catch(e) { print("Failed to launch .bat via App.showURL."); }});// Help button: run script --help and capture outputhelpBtn.clicked.connect(function() {    var selected = null;    for (var i=0;i<radioButtons.length;i++) if (radioButtons[i].checked) { selected = files[i]; break; }    if (!selected) { print("No script selected for help."); return; }    var scriptPath = folderEdit.text + "/" + selected;    print("Requesting --help for: " + scriptPath);    var helpOut = folderEdit.text + "/__help_output.txt";    var batPath = folderEdit.text + "/__help_cmd.bat";    var bat = new DzFile(batPath);    if (!bat.open(DzFile.WriteOnly | DzFile.Truncate)) { print("Failed to write help command file: " + batPath); return; }    // Run python --help and then open the output in Notepad so it stays open    bat.writeLine('"' + PYTHON_EXE + '" "' + scriptPath + '" --help > "' + helpOut + '" 2>&1');    bat.writeLine('start "" notepad.exe "' + helpOut + '"');    bat.close();    try { App.showURL("file:///" + batPath.replace(/\\/g,"/")); } catch(e) { print("Failed to launch help .bat."); }});// Diagnostics button (safe preview)var diagBtn = new DzPushButton(dlg); diagBtn.text = "Diagnostics";diagBtn.clicked.connect(function() {    print("=== Diagnostics ===");    print("CONFIG_FILE = " + CONFIG_FILE);    if (CONFIG_FILE) {        var raw = readFileAsString(CONFIG_FILE);        if (raw === null) {            print("Config file not present at CONFIG_FILE.");        } else {            try {                var parsed = JSON.parse(raw);                var pretty = JSON.stringify(parsed, null, 2);                var preview = pretty.length > 400 ? pretty.substring(0,400) + "..." : pretty;                print("Config preview (first 400 chars): " + preview);                print("Config size (chars): " + pretty.length);            } catch(e) {                var safePreview = (typeof raw === "string") ? (raw.length > 400 ? raw.substring(0,400) + "..." : raw) : String(raw);                print("Config preview (raw, first 400 chars): " + safePreview);            }        }    } else {        print("CONFIG_FILE not set.");    }    print("folderEdit.text = " + folderEdit.text);    print("files count = " + files.length);    print("lastSelected = " + lastSelected);    print("filesArgs keys = " + (filesArgs ? Object.keys(filesArgs).join(",") : "<none>"));    print("===================");});// Close buttonvar closeBtn = new DzPushButton(dlg); closeBtn.text = "Close"; closeBtn.clicked.connect(function(){ dlg.close(); });dlg.exec();

    Wow! you have been busy. I have updated the Launcher to include an argument option as well as a help button so the user knows the parameters needed.

     

  • sidcarton1587sidcarton1587 Posts: 96

    Hi gang, 

     Here is the next update of the Script Server, v2.4.0. Release v2.4.0 — Render Endpoints, Scene Camera/Light Lookup, Undo/Redo · bluemoonfoundry/daz-script-server

     

    What's New

    Render Endpoints

    • POST /render — submit a render job and get a request_id
    • GET /render/:id/progress — SSE stream of real-time render progress
    • POST /render/batch — submit multiple render variants in one request
    • POST /render/:id/cancel — cancel a queued or running render job

    dazpy Python SDK

    • DazScene.find_camera_by_label(label) / find_light_by_label(label) — look up cameras and lights by Scene-panel label
    • DazScene.undo_last() / redo_last() — step the DAZ Studio undo stack programmatically
    • DazClient.cancel_render(request_id) — cancel a render job by ID
    • RenderResult.request_id — populated on render(..., wait=False) for deferred cancellation
    • docs/examples/vn_render_workflow.py — four visual-novel render patterns with inline docs

    Plugin Route Registration

    Companion plugins can now register their own HTTP routes into the running server. For example, I'll be using this to integrate the USD export plugin that I've been working on as an endpoint in Script Server. 

    Bug Fixes

    • Fixed CRT heap mismatch crash when the catch-all dispatcher routed to a companion plugin (mixed MSVC runtime)
    • Fixed companion plugin routes silently dropped when registered after server start
    • Fixed data race in the catch-all route dispatcher under concurrent requests
  • TugpsxTugpsx Posts: 878

    I see you rolled off USD support into its own project. USD Load and USD Export for Daz studio, very nice.

  • sidcarton1587sidcarton1587 Posts: 96

    Tugpsx said:

    I see you rolled off USD support into its own project. USD Load and USD Export for Daz studio, very nice.

    I started adding to the script server but then it occurred to me that I don't want to stuff everything into it, in the interest of keeping it lean. So instead added a mechanism for other DAZ plugins to register an endpoint with the script server so that they too can be exposed via REST endpoints (and the Python package). The USD support is basically an example plugin that works that way. Now comes the hard part, actually make USD import/export work. :-)

  • TugpsxTugpsx Posts: 878

    sidcarton1587 said:

    Tugpsx said:

    I see you rolled off USD support into its own project. USD Load and USD Export for Daz studio, very nice.

    I started adding to the script server but then it occurred to me that I don't want to stuff everything into it, in the interest of keeping it lean. So instead added a mechanism for other DAZ plugins to register an endpoint with the script server so that they too can be exposed via REST endpoints (and the Python package). The USD support is basically an example plugin that works that way. Now comes the hard part, actually make USD import/export work. :-)

    So true, this link forms the basis for exporting importing  in Studio . https://github.com/daz3d. It has the source for the various requirements but i'm sure you already have access to this information. 

Sign In or Register to comment.