Pose

DazPose

class dazpy.DazPose(figure, bones, morphs, props)[source]

Bases: object

A snapshot of a figure’s complete pose state.

Stores bone rotations (Euler XYZ degrees), geometry morph values, and node-level numeric properties. Sparse by default — zero values are omitted so the object stays compact for morph-heavy figures.

Typical workflow:

from dazpy import DazScene, DazPose

scene = DazScene()
figure = scene.find_skeleton_by_label("Genesis 9")

neutral = DazPose.capture(figure)
neutral.save("neutral.json")

smile = DazPose.load("smile.json")
neutral.lerp(smile, t=0.5).apply(figure)

JSON schema (same as character_state.py output):

{
  "figure": "Genesis 9",
  "bones":  {"hip": [0, 2.3, 0], "rForeArm": [0, 0, -45]},
  "morphs": {"PHMSmileFull": 0.8},
  "props":  {"facs_ctrl_SmileFullFace": 0.5}
}
Parameters:
  • figure (str) – The label of the figure this pose belongs to.

  • bones (dict[str, list[float]]) – Bone name → [x, y, z] Euler angles in degrees.

  • morphs (dict[str, float]) – Morph name → blend value (typically 0–1).

  • props (dict[str, float]) – Node property name → numeric value.

classmethod capture(skeleton)[source]

Capture the current pose of skeleton in a single HTTP call.

Records all non-zero bone rotations, morph values, and node-level numeric properties. Zero values are omitted (sparse storage); they are implied as 0.0 during lerp() and apply_full().

Parameters:

skeleton (DazSkeleton) – The figure to capture.

Returns:

A new DazPose.

Raises:

NodeNotFoundError – If the skeleton is not found.

Return type:

DazPose

classmethod load(path)[source]

Load a pose from a JSON file.

Accepts files produced by save() and by the character_state.py example script.

Parameters:

path (str | Path) – Path to the JSON file.

Returns:

A new DazPose.

Return type:

DazPose

save(path)[source]

Write this pose to a JSON file.

Parameters:

path (str | Path) – Destination path. Parent directories must exist.

to_dict()[source]

Return the pose as a plain dict (same schema as the JSON file).

lerp(other, t)[source]

Linearly interpolate between this pose and other.

Missing keys in either pose are treated as zero. The result uses the figure label from self.

This is a pure-Python operation — no HTTP round-trip.

Parameters:
  • other (DazPose) – The target pose (t=1.0 yields an exact copy of other).

  • t (float) – Blend factor. 0.0 = this pose, 1.0 = other. Values outside [0, 1] extrapolate.

Returns:

A new DazPose at the interpolated position.

Return type:

DazPose

apply(skeleton, *, retry_on_busy=True, max_wait=30.0)[source]

Apply this pose to skeleton in a single HTTP call.

Only channels present in the pose are changed. Bones and morphs not stored in the pose are left at their current values. Use apply_full() when you need a clean, authoritative reset to exactly this pose.

Parameters:
  • skeleton (DazSkeleton) – The figure to pose.

  • retry_on_busy (bool) – If True (the default), transparently retry with backoff when the server reports StudioBusyError for the HTTP call itself, up to max_wait seconds total. This does not protect against DAZ Studio’s main thread bailing partway through the script under contention (see dpi-mxq) – callers that need correctness guarantees on the result should verify with an independent read-back after applying.

  • max_wait (float) – Maximum total seconds to retry when retry_on_busy is True.

apply_full(skeleton, *, retry_on_busy=True, max_wait=30.0)[source]

Apply this pose and zero every channel not present in the pose.

Unlike apply(), every bone rotation, morph, and node property on the skeleton is explicitly set — channels absent from the pose are driven to zero. Use this to restore a known baseline cleanly.

Parameters:
  • skeleton (DazSkeleton) – The figure to pose.

  • retry_on_busy (bool) – If True (the default), transparently retry with backoff when the server reports StudioBusyError for the HTTP call itself, up to max_wait seconds total. This only covers a busy error raised for the whole call – it does not detect DAZ Studio’s main thread bailing partway through the script under contention and leaving some writes unapplied while the HTTP call still reports success (see dpi-mxq). Callers that need correctness guarantees (e.g. checkpoint restore) must independently verify the result with a fresh capture() read-back, such as apply() does.

  • max_wait (float) – Maximum total seconds to retry when retry_on_busy is True.

dazpy.poses

Common pose operations built on DazPose.

dazpy.apply_pose(skeleton, pose)[source]

Apply pose to skeleton in a single HTTP call.

Parameters:
dazpy.reset_transforms(node)[source]

Reset node’s local position and rotation to zero, and scale to 1.0.

Works on any DazNode — camera, prop, or figure root. Uses a single DazScript evaluation via set_transform().

Parameters:

node (DazNode) – The node to reset.

dazpy.zero_figure(skeleton, *, include_props=False)[source]

Drive every bone rotation and morph on skeleton to zero.

The default (include_props=False) is what guarantees this function never touches the figure’s root position/rotation/scale — use reset_transforms() for that instead.

Parameters:
  • skeleton (DazSkeleton) – The figure to zero.

  • include_props (bool) –

    When True, node-level numeric properties are also zeroed, via apply_full(). This is opt-in, not the default: apply_full writes 0 for every property returned by the figure’s node-property enumeration that is absent from the pose it’s given, and every other caller passes a captured pose whose props dict already contains those values. zero_figure instead passes an empty props={}, so with include_props=True that “absent → 0” fallback can drive built-in transform-adjacent dials — e.g. the figure’s general Scale property (see the DzERCLink comment in dazpy/_pose.py around line 117-125) — to 0, contradicting the “does not touch root transform” guarantee. Only pass True if you know your rig doesn’t route transforms through its node-property list.

    The two modes also differ in how they write ERC-driven channels: the True path goes through apply_full(), which prefers setRawValue() writes to avoid double-applying DzERCLink controller contributions (see the comment in dazpy/_pose.py around line 117-125). The False path uses set_bone_rotations() / set_morph_values(), which use plain setValue() writes. On ERC-driven channels the two modes can therefore leave the figure in different end states.