Skip to content

v2.7.2

Choose a tag to compare

@github-actions github-actions released this 19 Jul 16:48

[2.7.2] - 2026-07-19

No functional changes to dazpy's behavior. 2.7.1's version bump missed
three places still reading 2.7.0: dazpy.__version__ itself (already
baked into the published 2.7.1 wheel, which is why this needed a new
version rather than a doc-only fix), openapi.yaml's info.version plus
two response examples, and the plugin's own DZSRV_VERSION_STR
(common_version.h — what /status//health actually return at
runtime). All three now correctly say 2.7.2.

Actual functional changes added for the 2.7.x release:

  • DazRenderSettings Iray Canvas support (list_canvases(),
    add_canvas(), remove_canvas(), canvases_enabled,
    canvas_output_paths()) — enumerate, create, and remove Iray Canvases
    (normal/depth/material-ID/etc. passes) and resolve their output file paths
    for a given render, closing GitHub issue #19's Canvas gap. Backed by
    App.getRenderMgr().getRenderElementObjects()[1], confirmed against a live
    DAZ Studio instance — no server-side changes needed.
  • DazScene.create_camera() / DazScene.create_light() — factory
    methods to create new camera and light nodes and add them to the scene,
    returning DazCamera/DazLight wrappers usable with the rest of the
    existing API (set_position, aim_at, set_color, etc). create_light()
    accepts "spot", "point", or "distant".
  • DazNode.delete() / DazNode.reparent() — remove a node from the
    scene entirely, or move it to a new parent in the hierarchy via the
    removeNodeChild/addNodeChild pattern, optionally preserving its
    world-space transform (preserve_world_transform=True by default).
  • DazProperty.raw_value — read/write a property's own dial value,
    excluding any DzERCLink controller contributions. Use this instead of
    .value whenever a save/restore round trip needs to be exact (see Fixed,
    below, for why .value is unsafe for that).
  • DazScene.overview() — one-call top-level scene snapshot (root
    figures, cameras, lights, open scene file, primary selection, node count).
  • DazScene.node_hierarchy(root, max_depth=None) — one-call descendant
    tree rooted at a single named node (typically a figure), with a type on
    every entry and an optional recursion-depth limit. Complements the
    existing node_tree(), which always starts from every scene root.
  • DazSceneState — full scene checkpoint: every skeleton's complete
    pose (via DazPose, so bone-level, not just the root transform) plus
    camera and light transforms/key properties, captured and restored via
    raw_value throughout so repeated capture/apply cycles are idempotent.
  • DazCamera.lens_shift_x / .lens_shift_y — read/write properties
    backed by DzCamera's "Lens Shift X/Y (mm)" controls, the same values the
    render engine uses — needed for depth-based effects (fog, DOF-driven line
    weight) to match the actual render.
  • DazScene.export_fbx() / .export_obj() — synchronous export via DAZ
    Studio's native DzFbxExporter/DzObjExporter, unlike the async job
    pattern export_usd_submit() uses — there's no custom pipeline behind
    these to poll on. RunSilent is forced on to avoid the native exporter's
    modal options dialog hanging the HTTP handler's thread.
  • DazViewport.draw_style() / .set_draw_style() — controls the
    viewport's preview quality (Wireframe .. NVIDIA Iray) via
    getUserDrawStyle()/setUserDrawStyle(), distinct from
    DazRenderSettings.active_engine() below. set_draw_style() reads the
    style back after setting it and raises ValueError if it didn't take,
    since setUserDrawStyle() silently no-ops on an unrecognized label.
  • DazRenderSettings.active_engine() / .set_active_engine() — query
    and switch the Render Settings "Engine" dropdown. Correctly distinguishes
    DzRenderOptions.renderType (which governs "viewport"/
    "multi_pass_opengl") from renderMgr.getActiveRenderer() (which only
    applies in Software mode) — the two are easy to conflate since the UI
    presents them as one dropdown. set_active_engine() raises RenderError
    for a pluggable renderer name that isn't registered (e.g. the Filament
    plugin not installed).
  • DazRenderSettings Iray quality/samples control
    max_samples/max_time_secs/quality properties and
    set_quality_preset() (draft/preview/good/final), backed by the active
    Iray renderer's property holder since these aren't exposed on
    DzRenderOptions. Plus DazClient.list_requests(), wrapping the existing
    GET /requests endpoint to list all tracked async requests by status.
  • Per-frame animation render submissionPOST /render/animation and
    DazClient.render_animation_submit(), mirroring the existing
    single-render pattern as one trackable async request that loops a
    start/end frame range, writing each frame to output_path with its
    {frame} token substituted (zero-padded).
  • DazNode.fit_to() / .unfit() / .fitted_items() — clothing/prop
    fit-to-figure control, matching what vangard-daz-mcp's hand-rolled
    fit/unfit/list-fitted scripts previously did outside dazpy.
  • DazProperty.get_keys() / .remove_key() / .clear_keys()
    keyframe curve introspection and single-key removal on an animated
    property, without hand-rolled DazScript.
  • dForce simulation control — a DazDForce modifier proxy
    (freeze_simulation/freeze()/unfreeze(), backed by
    DzDForceModifier's "Freeze Simulation" property), wired into DazNode's
    modifier discrimination plus a new dforce_modifiers() filter, and
    DazScene.run_dforce_simulation() / .is_simulating() /
    .clear_dforce_simulation() driven by DzSimulationMgr. Long-running
    simulations use the existing async execute-and-poll path.
  • General scene-change event stream clientDazClient. stream_scene_events() plus dazpy/_scene_events.py
    (SceneEvent/watch_scene_events()/wait_for_scene_event()), mirroring
    the existing render-progress stream so callers can watch or block on
    node/light/camera/selection/scene/time/render events over the server's
    existing GET /scene/events SSE endpoint without hand-rolling SSE
    parsing.
  • DazElement.numeric_properties() / .class_name
    numeric_properties() returns {label: value} for every numeric property
    on an element in one round trip, instead of needing one round trip per
    property after list_properties(). class_name exposes the DazScript
    class name (e.g. "DzFigure") for any element.

Fixed

  • ERC-inflation fix in DazPose didn't cover bone rotation channels
    the earlier fix that switched morphs and node properties to
    getRawValue()/setRawValue() left bone XRotControl/YRotControl/
    ZRotControl channels on plain getValue()/setValue(). For rigs where
    one bone's rotation is ERC-driven from another (e.g. auto-follow
    bend/twist ratios common on Genesis figures), repeated capture/apply
    cycles kept inflating that bone's rotation the same way the earlier fix
    addressed for morphs/props. capture()/apply()/apply_full() now
    feature-detect raw value support on bone rotation controls too.

  • DazViewport.capture() left state unrestored if the viewport
    disappeared mid-wait
    — the finish script's if (!vp) return null; guard
    ran before any of the overlay/selection/Tonemapper restore lines, so if
    the active viewport became unavailable (closed/changed) during the real
    wall-clock convergence_wait sleep between the prepare and finish scripts,
    restoration was skipped entirely -- a state the old atomic single-script
    capture could never leave the viewport in. Scene-level restoration
    (primary selection, Tonemapper/Environment node visibility) no longer
    depends on vp and always runs; only the viewport-specific properties and
    the capture itself are skipped (returning no image) if vp is gone.

  • DazViewport.capture() couldn't restore a bone selection — the
    two-pass capture's primary-selection restore serialises only the selected
    node's name across the prepare/finish round trip and re-resolves it via
    Scene.findNode(), which resolves top-level scene nodes but not bones or
    other non-node selectable items (e.g. a bone selected via the Joint
    Editor), so the original selection was silently lost after capture().
    Now also captures the owning skeleton's name for bone selections
    (isBoneSelectingNode()/getSkeleton()) and falls back to
    skeleton.findBone(name) when the direct findNode() lookup misses.

  • DazPose.apply_full() didn't zero absent node properties — the
    node-property loop only called setValue/setRawValue if (v !== undefined), with no else-zero branch, unlike the bones loop (explicit
    else setValue(0)) and morphs loop (_v = v !== undefined ? v : 0) right
    above it in the same function. This broke the documented contract that
    apply_full() drives every channel absent from the pose to zero, and also
    affected DazSceneState.apply()'s "clean baseline" restore, which relies
    on apply_full(). Now matches the bones/morphs pattern.

  • DazSceneState.apply() didn't isolate apply_full() errors per
    skeleton
    — the per-skeleton try/except only wrapped
    scene.find_skeleton(); a following pose.apply_full(skel) failure
    (transient HTTP/DazScript error, stale skeleton reference) propagated
    uncaught and aborted restoration of all remaining skeletons, cameras, and
    lights, contradicting the docstring's promise that failures are reported
    in errors rather than raising. Now apply_full() is wrapped in its own
    try/except so a single skeleton's failure is recorded and the loop
    continues.

  • build_hug_recipe() swapped far-shoulder targets between actors — with
    mismatched anchors (e.g. a_anchor="r_hand", b_anchor="l_hand"),
    actor_a's hand target used the far shoulder derived from actor_b's anchor
    side instead of actor_a's own, producing an anatomically wrong or
    self-intersecting embrace. Invisible with the default r_hand/r_hand
    anchors (both sides happen to compute the same far shoulder), which is all
    the prior test covered. Now each actor's far shoulder is derived from its
    own anchor's side.

  • DazRenderSettings.render() / .render_and_wait() broke truthiness
    callers
    — these switched from returning bool to a RenderOutcome
    dataclass with no __bool__, so existing code written against the old
    contract (if rs.render():, including downstream projects like
    vangard-daz-mcp) always evaluated truthy and silently treated failed
    renders as successful. RenderOutcome.__bool__ now reflects success, so
    if rs.render(): behaves the same as before; use .success/.output_path
    directly if you need the full outcome.

  • DazViewport.capture(..., backdrop_color=...) raised ReferenceError
    restoring the background
    — the two-pass capture's finish script
    referenced the JS variable prevBg, but prevBg was only declared in the
    separate prepare-script execute() call (a different HTTP round trip with
    no shared JS scope), so restoring the background always threw. prevBg is
    now round-tripped through Python (returned from the prepare script as
    plain JSON, alongside axesOn/selectionName/etc.) and substituted
    directly into the finish script.

  • DazPose.capture() / .apply() / .apply_full() inflate ERC-driven
    properties on every round trip
    — node-level properties captured via
    getValue() (e.g. a "Scale" dial fed by dozens of DzERCLink-linked
    morphs) return the post-ERC computed total, but apply()/apply_full()
    wrote that total back via setValue(), which sets the property's raw
    slot — so the ERC links add their contribution again on top. Each
    capture/apply cycle compounded the drift (observed inflating a custom
    character's Scale dial from 100% to 270% over a handful of cycles in
    daz-mcp-server's test suite, via its use of daz_save_pose/
    daz_load_pose). Now uses getRawValue()/setRawValue() (see
    DazProperty.raw_value above) for props and morphs, which round-trip
    correctly regardless of ERC links.

  • /render camera selection — the native render endpoint called
    App.getViewportMgr() when a cameraName was supplied, which is undefined
    in this DAZ Studio version and made every camera-targeted render fail
    immediately with a TypeError. Switched to MainWindow.getViewportMgr(),
    matching the pattern already used by dazpy's own render/viewport helpers.

  • /render black output with cameraName — after the above fix, renders
    submitted with an explicit camera completed without error but produced a
    black image, because the render camera actually read by doRender() comes
    from opts.camera, not the viewport's active camera. The endpoint only
    updated the viewport (setActiveCamera) and never set opts.camera, so
    the renderer used a stale/null camera. Now sets both, matching
    dazpy/_render.py and this repo's own camera-preset scripts. Affects both
    /render and /render/batch (they share buildRenderScript()).

  • DazClient.status() / .health() / .metrics() misreported auth
    failures
    — these called the internal _get() helper directly without
    the 401/403 check execute()/render_submit()/etc. already had, so a 401
    with no response body surfaced as a raw JSON-decode error instead of
    AuthenticationError.

  • ScriptError dropped captured output on failure — callers building
    diagnostics from a failed script (e.g. daz-mcp-server's daz_execute)
    lost the print() output that led up to the error. ScriptError/
    ScriptRuntimeError/ScriptSyntaxError now accept and surface an
    optional output list via .diagnostic.

  • Dropped the unsupported 3delight render engine option — DAZ Studio
    no longer ships 3Delight; removed it from the engine allow-list and docs
    so a render request naming it fails fast with a clear error instead of
    silently mapping to a renderer that isn't there.


Built Artifacts

DAZ Studio 4.5+

  • Windows: DazScriptServer-ds4-windows.dll
  • macOS Intel: DazScriptServer-ds4-macos-Intel.dylib
  • macOS Apple Silicon: DazScriptServer-ds4-macos-AppleSilicon.dylib

Daz Studio 6.25+

  • Windows: dsp_DazScriptServer-ds6-windows.dll
  • macOS Intel: dsp_DazScriptServer-ds6-macos-Intel.dylib
  • macOS Apple Silicon: dsp_DazScriptServer-ds6-macos-AppleSilicon.dylib

Python

  • Python Package: dazpy-*.whl