dazpy 2.7.2__tar.gz → 2.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. {dazpy-2.7.2/dazpy.egg-info → dazpy-2.9.0}/PKG-INFO +189 -9
  2. dazpy-2.7.2/PKG-INFO → dazpy-2.9.0/README.md +186 -23
  3. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/__init__.py +62 -3
  4. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_camera.py +46 -0
  5. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_client.py +189 -34
  6. dazpy-2.9.0/dazpy/_client_aio.py +411 -0
  7. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_light.py +2 -2
  8. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_node.py +99 -1
  9. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_property.py +26 -11
  10. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_render.py +375 -6
  11. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_render_api.py +23 -4
  12. dazpy-2.9.0/dazpy/_render_api_aio.py +150 -0
  13. dazpy-2.9.0/dazpy/_shot_geometry.py +81 -0
  14. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_skeleton.py +27 -0
  15. dazpy-2.9.0/dazpy/aio.py +20 -0
  16. dazpy-2.9.0/dazpy/cinematics.py +423 -0
  17. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/exceptions.py +39 -0
  18. dazpy-2.9.0/dazpy/lighting.py +227 -0
  19. dazpy-2.9.0/dazpy/materials.py +270 -0
  20. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/math3.py +167 -0
  21. dazpy-2.9.0/dazpy/poses.py +88 -0
  22. dazpy-2.7.2/README.md → dazpy-2.9.0/dazpy.egg-info/PKG-INFO +203 -8
  23. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy.egg-info/SOURCES.txt +20 -0
  24. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy.egg-info/requires.txt +3 -0
  25. {dazpy-2.7.2 → dazpy-2.9.0}/pyproject.toml +4 -1
  26. {dazpy-2.7.2 → dazpy-2.9.0}/tests/test_api.py +38 -0
  27. {dazpy-2.7.2 → dazpy-2.9.0}/tests/test_comfyui_client.py +38 -2
  28. {dazpy-2.7.2 → dazpy-2.9.0}/tests/test_comfyui_enhance_integration.py +7 -1
  29. {dazpy-2.7.2 → dazpy-2.9.0}/tests/test_dazpy.py +2012 -40
  30. dazpy-2.9.0/tests/test_dazpy_aio.py +338 -0
  31. {dazpy-2.7.2 → dazpy-2.9.0}/tests/test_file_watcher.py +2 -1
  32. dazpy-2.9.0/tests/test_math3.py +183 -0
  33. dazpy-2.9.0/tests/test_render_api.py +77 -0
  34. dazpy-2.9.0/tests/test_sprite_matrix_canvas_convert.py +110 -0
  35. dazpy-2.9.0/tests/test_sprite_matrix_integration.py +134 -0
  36. dazpy-2.9.0/tests/test_sprite_matrix_paths.py +86 -0
  37. dazpy-2.9.0/tests/test_sprite_matrix_presets.py +86 -0
  38. dazpy-2.9.0/tests/test_sprite_matrix_render_shot.py +80 -0
  39. dazpy-2.9.0/tests/test_sprite_matrix_render_util.py +73 -0
  40. dazpy-2.9.0/tests/test_sprite_matrix_schema.py +234 -0
  41. dazpy-2.9.0/tests/test_sprite_matrix_stylize_stage.py +121 -0
  42. dazpy-2.9.0/tests/test_sprite_matrix_workflow_builder.py +383 -0
  43. {dazpy-2.7.2 → dazpy-2.9.0}/tests/test_workflow_builder.py +7 -1
  44. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_animation.py +0 -0
  45. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_batch.py +0 -0
  46. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_bone.py +0 -0
  47. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_dforce.py +0 -0
  48. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_element.py +0 -0
  49. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_geometry.py +0 -0
  50. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_interaction.py +0 -0
  51. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_material.py +0 -0
  52. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_modifier.py +0 -0
  53. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_morph.py +0 -0
  54. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_polling.py +0 -0
  55. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_pose.py +0 -0
  56. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_result.py +0 -0
  57. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_scene.py +0 -0
  58. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_scene_events.py +0 -0
  59. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_scene_state.py +0 -0
  60. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_script_builder.py +0 -0
  61. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_timeline.py +0 -0
  62. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_undo.py +0 -0
  63. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/_viewport.py +0 -0
  64. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy/py.typed +0 -0
  65. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy.egg-info/dependency_links.txt +0 -0
  66. {dazpy-2.7.2 → dazpy-2.9.0}/dazpy.egg-info/top_level.txt +0 -0
  67. {dazpy-2.7.2 → dazpy-2.9.0}/setup.cfg +0 -0
  68. {dazpy-2.7.2 → dazpy-2.9.0}/tests/test_dazpy_integration.py +0 -0
  69. {dazpy-2.7.2 → dazpy-2.9.0}/tests/test_dsp_runner.py +0 -0
  70. {dazpy-2.7.2 → dazpy-2.9.0}/tests/test_performance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dazpy
3
- Version: 2.7.2
3
+ Version: 2.9.0
4
4
  Summary: Python SDK for the DAZ Studio Script Server
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/bluemoonfoundry/daz-script-server
@@ -12,10 +12,12 @@ Requires-Dist: requests>=2.28
12
12
  Provides-Extra: docs
13
13
  Requires-Dist: sphinx>=7.0; extra == "docs"
14
14
  Requires-Dist: sphinx-rtd-theme>=2.0; extra == "docs"
15
+ Provides-Extra: aio
16
+ Requires-Dist: httpx>=0.24; extra == "aio"
15
17
 
16
18
  # DazScript Server
17
19
 
18
- **Version 2.7.2** | DAZ Studio 4.5+ | DAZ Studio 6.25+ | Windows & macOS
20
+ **Version 2.9.0** | DAZ Studio 4.5+ | DAZ Studio 6.25+ | Windows & macOS
19
21
 
20
22
  [![Docs](https://img.shields.io/badge/docs-dazpy%20SDK-blue)](https://bluemoonfoundry.github.io/daz-script-server/)
21
23
  [![HTTP API](https://img.shields.io/badge/docs-HTTP%20API%20reference-blue)](https://bluemoonfoundry.github.io/daz-script-server/api-reference/)
@@ -83,6 +85,8 @@ print(response.json())
83
85
  ### Getting Started
84
86
  - [Quick Start](#-quick-start)
85
87
  - [Why This Exists](#why-this-exists)
88
+ - [What's New in v2.9.0](#whats-new-in-v290)
89
+ - [What's New in v2.8.0](#whats-new-in-v280)
86
90
  - [What's New in v2.7.2](#whats-new-in-v272)
87
91
  - [What's New in v2.7.1](#whats-new-in-v271)
88
92
  - [What's New in v2.7.0](#whats-new-in-v270)
@@ -169,6 +173,177 @@ DAZ Studio is powerful for 3D content creation, but automation is limited to man
169
173
 
170
174
  ---
171
175
 
176
+ ## What's New in v2.9.0
177
+
178
+ ### 🎬 dazpy.cinematics — camera shot builders
179
+
180
+ `dazpy.cinematics` packages common camera choreography on top of
181
+ `DazCamera`/`DazScene`: `apply_static_shot()` places/aims/configures a
182
+ camera in one call; `apply_orbit_camera()` sweeps a camera around a target
183
+ across a frame range; `apply_frame_subject()` frames a subject at a named
184
+ shot distance (`close_up` / `medium` / `full_body`); and
185
+ `apply_animated_shot()` writes real, interpolated DAZ Studio keyframes at a
186
+ sequence of waypoints instead of baking a value on every frame.
187
+
188
+ ```python
189
+ from dazpy.cinematics import OrbitCamera, apply_orbit_camera
190
+
191
+ apply_orbit_camera(scene, OrbitCamera(target=figure, radius=250, frame_start=0, frame_end=90))
192
+ ```
193
+
194
+ ### 💡 dazpy.lighting — three-point rigs and HDRI environments
195
+
196
+ `apply_three_point_light_setup()` creates and places a key/fill/rim light
197
+ rig around a target in one call, via angle/distance placement or explicit
198
+ world-space positions per light. `apply_hdri_environment()` applies
199
+ image-based dome lighting — map, intensity, rotation, dome/scene mode,
200
+ visibility, and IBL resolution — validating the image path up front and
201
+ verifying a post-apply readback so a silent failure never passes as success.
202
+
203
+ ### 🧍 dazpy.poses — apply_pose, reset_transforms, zero_figure
204
+
205
+ Common pose operations without hand-assembling a `DazPose`:
206
+ `apply_pose()` applies a pose object or a JSON file path directly to a
207
+ skeleton; `reset_transforms()` zeroes a node's local position/rotation and
208
+ resets scale to 1.0; `zero_figure()` drives every bone rotation and morph
209
+ to zero while leaving the figure's root transform untouched by default.
210
+
211
+ ### 📐 dazpy.math3.AxisRemap — coordinate-space conversion
212
+
213
+ A generic signed-axis-permutation converter for `Vec3`/`Quat`/
214
+ `BoundingBox` between coordinate conventions, with a ready-made
215
+ `Y_UP_TO_Z_UP` preset for moving data from DAZ Studio (Y-up) into
216
+ Blender/glTF-style Z-up pipelines.
217
+
218
+ ```python
219
+ from dazpy.math3 import Y_UP_TO_Z_UP
220
+
221
+ blender_pos = Y_UP_TO_Z_UP.apply_vec3(daz_pos)
222
+ ```
223
+
224
+ ### 🦴 DazSkeleton.bone_rotations_quat()
225
+
226
+ Returns local-space quaternion rotations for every bone in one HTTP
227
+ round-trip — the composable counterpart to `bone_rotations()` for callers
228
+ feeding the result into another rotation (e.g. `AxisRemap.apply_quat()`),
229
+ sidestepping the per-bone Euler rotation-order tracking that would
230
+ otherwise be required.
231
+
232
+ ### 🎞️ Frame-level progress streaming for animation renders
233
+
234
+ The `GET /render/:id/progress` SSE stream now emits a progress event at
235
+ each frame boundary during an animation render, instead of only start/
236
+ terminal events — the DAZ SDK exposes no per-percent render progress
237
+ signal, so this frame-level marker is the most granular progress available.
238
+
239
+ ### ⚡ Async client — `dazpy.aio.AsyncDazClient`
240
+
241
+ Async frameworks (FastAPI, FastMCP, ComfyUI custom nodes, asyncio/Temporal
242
+ workflows) previously had to wrap every `DazClient` call in
243
+ `asyncio.to_thread()` or hand-roll an `httpx.AsyncClient` wrapper.
244
+ `dazpy.aio.AsyncDazClient` mirrors `DazClient`'s full method surface —
245
+ `execute`, `execute_file`, async submit/status/result/list/cancel, render
246
+ submit/batch/animation, USD export, and server-health endpoints — as
247
+ native `async def` methods backed by `httpx.AsyncClient`, including the
248
+ same `retry_on_busy`/`max_wait` backoff semantics (via `asyncio.sleep`
249
+ instead of blocking `time.sleep`).
250
+
251
+ ```python
252
+ from dazpy.aio import AsyncDazClient
253
+
254
+ async def main():
255
+ async with AsyncDazClient() as client:
256
+ result = await client.execute("1 + 1;")
257
+ print(result.value)
258
+ ```
259
+
260
+ Requires the optional `httpx` dependency: `pip install dazpy[aio]`.
261
+
262
+ ### `DazClient` connection pooling + `close()`
263
+
264
+ `DazClient` now issues requests through a pooled `requests.Session` instead
265
+ of a fresh connection per call, and supports `close()` / use as a context
266
+ manager (`with DazClient() as client:`) for symmetry with the new async
267
+ client's connection lifecycle.
268
+
269
+ ### Truthful Iray/Viewport render-engine selector
270
+
271
+ `DazRenderSettings.render_engine_state()` now reads the effective
272
+ `DzRenderOptions.renderType` value and the active renderer class/name as separate
273
+ facts. Its schema-1 record includes the raw enum value/name, normalized
274
+ `verified_iray` / `verified_non_iray` / `unavailable` status, and field-level
275
+ live-readback provenance. A retained `NVIDIA Iray` active-renderer name never
276
+ overrides a Viewport/OpenGL `renderType`.
277
+
278
+ `DazRenderSettings.set_render_engine("iray" | "viewport")` is an explicit
279
+ persistent setter. It selects the registered `DzIrayRenderer` plus `Software` for
280
+ Iray, or `ScreenShot` for Viewport, calls `applyChanges()`, and returns only after
281
+ exact readback. Missing renderers, mutation/apply faults, malformed replies, and
282
+ readback disagreement raise `RenderError`; unknown engine names raise `ValueError`
283
+ before dispatch.
284
+
285
+ ```python
286
+ settings = DazRenderSettings(client)
287
+ before = settings.render_engine_state()
288
+ change = settings.set_render_engine("iray")
289
+ assert change["readback"]["status"] == "verified_iray"
290
+ ```
291
+
292
+ The `engine` field on `/render`, `/render/batch`, and `/render/animation` also
293
+ accepts `viewport` and uses the same fail-closed mutation/readback rules. These
294
+ operations intentionally persist the requested engine. DAZ documents `ScreenShot`
295
+ and `HardwareAssisted` as Viewport/OpenGL operations and `Software` as the active
296
+ software-renderer operation in the
297
+ [DzRenderOptions reference](https://docs.daz3d.com/public/software/dazstudio/4/referenceguide/scripting/api_reference/object_index/renderoptions_dz);
298
+ renderer lookup and activation are documented by
299
+ [DzRenderMgr](https://docs.daz3d.com/public/software/dazstudio/4/referenceguide/scripting/api_reference/object_index/rendermgr_dz).
300
+
301
+ ---
302
+
303
+ ## What's New in v2.8.0
304
+
305
+ ### 🚦 Fail Fast When DAZ Studio Is Busy — `503 STUDIO_BUSY`
306
+
307
+ DAZ Studio's Qt main thread is single-threaded: if a user loads a scene,
308
+ saves, clears the scene, or renders through the DAZ Studio UI while the
309
+ server is also being driven remotely, requests that need the main thread
310
+ used to block indefinitely with no signal to the caller. `/execute`,
311
+ `/scripts/:id/execute`, the async-enqueue endpoints, and the render-submit
312
+ endpoints now check DAZ Studio's busy state first and return `503` with
313
+ `error_code: "STUDIO_BUSY"` (plus a `Retry-After` header) immediately
314
+ instead of hanging.
315
+
316
+ `dazpy` surfaces this as a new exception hierarchy — `DazBusyError`, with
317
+ `StudioBusyError` (503) and `ConcurrencyLimitError` (429) subclasses, each
318
+ carrying `reason` and `retry_after`. This also fixes a pre-existing bug
319
+ where `429 CONCURRENT_LIMIT_EXCEEDED` responses were misreported as
320
+ `ScriptRuntimeError`, as if the script itself had failed.
321
+
322
+ ```python
323
+ from dazpy import DazClient
324
+ from dazpy.exceptions import DazBusyError
325
+
326
+ client = DazClient()
327
+ try:
328
+ result = client.execute("1+1;")
329
+ except DazBusyError as e:
330
+ print(f"DAZ Studio is busy: {e.reason} — retry after {e.retry_after}s")
331
+ ```
332
+
333
+ Opt in to automatic retry with backoff instead of handling the exception
334
+ yourself:
335
+
336
+ ```python
337
+ result = client.execute("1+1;", retry_on_busy=True, max_wait=30.0)
338
+ ```
339
+
340
+ `retry_on_busy`/`max_wait` are available on `execute`, `execute_file`,
341
+ `execute_async_submit`, `render_submit`, `render_batch_submit`, and
342
+ `render_animation_submit`. Retry is opt-in and off by default — callers
343
+ (and any LLM driving them) stay informed rather than silently blocked.
344
+
345
+ ---
346
+
172
347
  ## What's New in v2.7.2
173
348
 
174
349
  No functional changes to `dazpy`'s behavior. `2.7.1`'s version bump missed
@@ -273,7 +448,7 @@ change. For dirty scenes it serialises via `Scene.saveScene()` and restores
273
448
  the original filename immediately. The `method` field in the response tells
274
449
  you which strategy was used.
275
450
 
276
- See `docs/examples/fundamentals/scene_save_copy.py` for a complete example
451
+ See [`fundamentals/scene_save_copy`](https://github.com/bluemoonfoundry/daz-script-server-examples/tree/main/fundamentals/scene_save_copy) in the examples repo for a complete example
277
452
  with `--compare` and `--dry-run` options.
278
453
 
279
454
  ---
@@ -325,7 +500,7 @@ snapshot = scene.scene_snapshot()
325
500
  profiles = build_rig_profiles_from_snapshot(snapshot)
326
501
  ```
327
502
 
328
- **Body measurement accuracy improvements** in `docs/examples/body_measurements.py`:
503
+ **Body measurement accuracy improvements** in [`geometry/body_measurements`](https://github.com/bluemoonfoundry/daz-script-server-examples/tree/main/geometry/body_measurements):
329
504
 
330
505
  - Slices now use centroid-weighted torso loop selection to filter arm cross-sections, correcting a ~70 cm overestimate on A-pose figures.
331
506
  - The heuristic fallback for unlabelled figures (e.g. a character named
@@ -505,11 +680,14 @@ print(anim.frame_range()) # (start, end)
505
680
  anim.bake_pose_to_keyframes(start=0, end=60)
506
681
  ```
507
682
 
508
- ### 📐 math3 — Vec3, Quat, BoundingBox
683
+ ### 📐 math3 — Vec3, Quat, BoundingBox, AxisRemap
509
684
 
510
685
  New module **`dazpy.math3`** provides lightweight value types returned throughout
511
686
  the SDK: `Vec3` (positions, translations), `Quat` (rotations), and `BoundingBox`
512
687
  (geometry bounds). All support arithmetic operators and round-trip through JSON.
688
+ `AxisRemap` converts `Vec3`/`Quat`/`BoundingBox` values between axis conventions
689
+ via signed-axis permutations (e.g. the built-in `Y_UP_TO_Z_UP` preset for
690
+ converting DAZ Studio's Y-up scene data to Z-up tools like Blender).
513
691
 
514
692
  ### 🌐 Posed Vertex Export & USD Scene Export
515
693
 
@@ -517,7 +695,7 @@ the SDK: `Vec3` (positions, translations), `Quat` (rotations), and `BoundingBox`
517
695
  positions with skinning and morphs already applied — enabling downstream export
518
696
  pipelines to read the final mesh without re-solving the rig.
519
697
 
520
- New example **`docs/examples/scene_to_usd.py`** uses this to export a full live
698
+ New example **[`export/scene_to_usd`](https://github.com/bluemoonfoundry/daz-script-server-examples/tree/main/export/scene_to_usd)** uses this to export a full live
521
699
  DAZ Studio scene to Pixar USD: posed meshes, cameras, lights, PBR materials, and
522
700
  strand-based hair as `UsdGeom.BasisCurves`. Pass `--morphs` to write blend
523
701
  shapes as `UsdSkel` targets.
@@ -555,8 +733,8 @@ Studio instance) now ship in the repo root.
555
733
 
556
734
  ### 📖 Docs & Ergonomics
557
735
 
558
- - API reference pages for `DazPose`, `DazAnimation`, `Vec3`, `Quat`, and `BoundingBox`
559
- - Every script in `docs/examples/` has an `if __name__ == "__main__":` guard and
736
+ - API reference pages for `DazPose`, `DazAnimation`, `Vec3`, `Quat`, `BoundingBox`, and `AxisRemap`
737
+ - Every script in [daz-script-server-examples](https://github.com/bluemoonfoundry/daz-script-server-examples) has an `if __name__ == "__main__":` guard and
560
738
  argparse `--help`, making all examples safe to import and self-documenting
561
739
 
562
740
  ---
@@ -620,6 +798,8 @@ Long-running operations (renders, exports, batch jobs) no longer need to block t
620
798
 
621
799
  **TTL:** Completed, failed, and cancelled requests are automatically purged after 1 hour (cleanup timer fires every 5 minutes).
622
800
 
801
+ **Stale running requests:** If a request has been `running` for more than 30 minutes without completing — e.g. a render blocked behind a DAZ Studio modal dialog — it's automatically marked `failed` so polling clients aren't left waiting forever. The underlying DAZ Studio operation may still be stuck; check the DAZ Studio window directly if this happens.
802
+
623
803
  ---
624
804
 
625
805
  ## What's New in v1.2.0
@@ -667,7 +847,7 @@ automation code without authoring DazScript by hand.
667
847
  Download the `.whl` file from the [latest release](https://github.com/bluemoonfoundry/daz-script-server/releases/latest) and install it:
668
848
 
669
849
  ```bash
670
- pip install dazpy-2.7.2-py3-none-any.whl
850
+ pip install dazpy-2.9.0-py3-none-any.whl
671
851
  ```
672
852
 
673
853
  Or install directly from the repo for development:
@@ -1,21 +1,6 @@
1
- Metadata-Version: 2.4
2
- Name: dazpy
3
- Version: 2.7.2
4
- Summary: Python SDK for the DAZ Studio Script Server
5
- License: MIT
6
- Project-URL: Homepage, https://github.com/bluemoonfoundry/daz-script-server
7
- Project-URL: Repository, https://github.com/bluemoonfoundry/daz-script-server
8
- Project-URL: Bug Tracker, https://github.com/bluemoonfoundry/daz-script-server/issues
9
- Requires-Python: >=3.10
10
- Description-Content-Type: text/markdown
11
- Requires-Dist: requests>=2.28
12
- Provides-Extra: docs
13
- Requires-Dist: sphinx>=7.0; extra == "docs"
14
- Requires-Dist: sphinx-rtd-theme>=2.0; extra == "docs"
15
-
16
1
  # DazScript Server
17
2
 
18
- **Version 2.7.2** | DAZ Studio 4.5+ | DAZ Studio 6.25+ | Windows & macOS
3
+ **Version 2.9.0** | DAZ Studio 4.5+ | DAZ Studio 6.25+ | Windows & macOS
19
4
 
20
5
  [![Docs](https://img.shields.io/badge/docs-dazpy%20SDK-blue)](https://bluemoonfoundry.github.io/daz-script-server/)
21
6
  [![HTTP API](https://img.shields.io/badge/docs-HTTP%20API%20reference-blue)](https://bluemoonfoundry.github.io/daz-script-server/api-reference/)
@@ -83,6 +68,8 @@ print(response.json())
83
68
  ### Getting Started
84
69
  - [Quick Start](#-quick-start)
85
70
  - [Why This Exists](#why-this-exists)
71
+ - [What's New in v2.9.0](#whats-new-in-v290)
72
+ - [What's New in v2.8.0](#whats-new-in-v280)
86
73
  - [What's New in v2.7.2](#whats-new-in-v272)
87
74
  - [What's New in v2.7.1](#whats-new-in-v271)
88
75
  - [What's New in v2.7.0](#whats-new-in-v270)
@@ -169,6 +156,177 @@ DAZ Studio is powerful for 3D content creation, but automation is limited to man
169
156
 
170
157
  ---
171
158
 
159
+ ## What's New in v2.9.0
160
+
161
+ ### 🎬 dazpy.cinematics — camera shot builders
162
+
163
+ `dazpy.cinematics` packages common camera choreography on top of
164
+ `DazCamera`/`DazScene`: `apply_static_shot()` places/aims/configures a
165
+ camera in one call; `apply_orbit_camera()` sweeps a camera around a target
166
+ across a frame range; `apply_frame_subject()` frames a subject at a named
167
+ shot distance (`close_up` / `medium` / `full_body`); and
168
+ `apply_animated_shot()` writes real, interpolated DAZ Studio keyframes at a
169
+ sequence of waypoints instead of baking a value on every frame.
170
+
171
+ ```python
172
+ from dazpy.cinematics import OrbitCamera, apply_orbit_camera
173
+
174
+ apply_orbit_camera(scene, OrbitCamera(target=figure, radius=250, frame_start=0, frame_end=90))
175
+ ```
176
+
177
+ ### 💡 dazpy.lighting — three-point rigs and HDRI environments
178
+
179
+ `apply_three_point_light_setup()` creates and places a key/fill/rim light
180
+ rig around a target in one call, via angle/distance placement or explicit
181
+ world-space positions per light. `apply_hdri_environment()` applies
182
+ image-based dome lighting — map, intensity, rotation, dome/scene mode,
183
+ visibility, and IBL resolution — validating the image path up front and
184
+ verifying a post-apply readback so a silent failure never passes as success.
185
+
186
+ ### 🧍 dazpy.poses — apply_pose, reset_transforms, zero_figure
187
+
188
+ Common pose operations without hand-assembling a `DazPose`:
189
+ `apply_pose()` applies a pose object or a JSON file path directly to a
190
+ skeleton; `reset_transforms()` zeroes a node's local position/rotation and
191
+ resets scale to 1.0; `zero_figure()` drives every bone rotation and morph
192
+ to zero while leaving the figure's root transform untouched by default.
193
+
194
+ ### 📐 dazpy.math3.AxisRemap — coordinate-space conversion
195
+
196
+ A generic signed-axis-permutation converter for `Vec3`/`Quat`/
197
+ `BoundingBox` between coordinate conventions, with a ready-made
198
+ `Y_UP_TO_Z_UP` preset for moving data from DAZ Studio (Y-up) into
199
+ Blender/glTF-style Z-up pipelines.
200
+
201
+ ```python
202
+ from dazpy.math3 import Y_UP_TO_Z_UP
203
+
204
+ blender_pos = Y_UP_TO_Z_UP.apply_vec3(daz_pos)
205
+ ```
206
+
207
+ ### 🦴 DazSkeleton.bone_rotations_quat()
208
+
209
+ Returns local-space quaternion rotations for every bone in one HTTP
210
+ round-trip — the composable counterpart to `bone_rotations()` for callers
211
+ feeding the result into another rotation (e.g. `AxisRemap.apply_quat()`),
212
+ sidestepping the per-bone Euler rotation-order tracking that would
213
+ otherwise be required.
214
+
215
+ ### 🎞️ Frame-level progress streaming for animation renders
216
+
217
+ The `GET /render/:id/progress` SSE stream now emits a progress event at
218
+ each frame boundary during an animation render, instead of only start/
219
+ terminal events — the DAZ SDK exposes no per-percent render progress
220
+ signal, so this frame-level marker is the most granular progress available.
221
+
222
+ ### ⚡ Async client — `dazpy.aio.AsyncDazClient`
223
+
224
+ Async frameworks (FastAPI, FastMCP, ComfyUI custom nodes, asyncio/Temporal
225
+ workflows) previously had to wrap every `DazClient` call in
226
+ `asyncio.to_thread()` or hand-roll an `httpx.AsyncClient` wrapper.
227
+ `dazpy.aio.AsyncDazClient` mirrors `DazClient`'s full method surface —
228
+ `execute`, `execute_file`, async submit/status/result/list/cancel, render
229
+ submit/batch/animation, USD export, and server-health endpoints — as
230
+ native `async def` methods backed by `httpx.AsyncClient`, including the
231
+ same `retry_on_busy`/`max_wait` backoff semantics (via `asyncio.sleep`
232
+ instead of blocking `time.sleep`).
233
+
234
+ ```python
235
+ from dazpy.aio import AsyncDazClient
236
+
237
+ async def main():
238
+ async with AsyncDazClient() as client:
239
+ result = await client.execute("1 + 1;")
240
+ print(result.value)
241
+ ```
242
+
243
+ Requires the optional `httpx` dependency: `pip install dazpy[aio]`.
244
+
245
+ ### `DazClient` connection pooling + `close()`
246
+
247
+ `DazClient` now issues requests through a pooled `requests.Session` instead
248
+ of a fresh connection per call, and supports `close()` / use as a context
249
+ manager (`with DazClient() as client:`) for symmetry with the new async
250
+ client's connection lifecycle.
251
+
252
+ ### Truthful Iray/Viewport render-engine selector
253
+
254
+ `DazRenderSettings.render_engine_state()` now reads the effective
255
+ `DzRenderOptions.renderType` value and the active renderer class/name as separate
256
+ facts. Its schema-1 record includes the raw enum value/name, normalized
257
+ `verified_iray` / `verified_non_iray` / `unavailable` status, and field-level
258
+ live-readback provenance. A retained `NVIDIA Iray` active-renderer name never
259
+ overrides a Viewport/OpenGL `renderType`.
260
+
261
+ `DazRenderSettings.set_render_engine("iray" | "viewport")` is an explicit
262
+ persistent setter. It selects the registered `DzIrayRenderer` plus `Software` for
263
+ Iray, or `ScreenShot` for Viewport, calls `applyChanges()`, and returns only after
264
+ exact readback. Missing renderers, mutation/apply faults, malformed replies, and
265
+ readback disagreement raise `RenderError`; unknown engine names raise `ValueError`
266
+ before dispatch.
267
+
268
+ ```python
269
+ settings = DazRenderSettings(client)
270
+ before = settings.render_engine_state()
271
+ change = settings.set_render_engine("iray")
272
+ assert change["readback"]["status"] == "verified_iray"
273
+ ```
274
+
275
+ The `engine` field on `/render`, `/render/batch`, and `/render/animation` also
276
+ accepts `viewport` and uses the same fail-closed mutation/readback rules. These
277
+ operations intentionally persist the requested engine. DAZ documents `ScreenShot`
278
+ and `HardwareAssisted` as Viewport/OpenGL operations and `Software` as the active
279
+ software-renderer operation in the
280
+ [DzRenderOptions reference](https://docs.daz3d.com/public/software/dazstudio/4/referenceguide/scripting/api_reference/object_index/renderoptions_dz);
281
+ renderer lookup and activation are documented by
282
+ [DzRenderMgr](https://docs.daz3d.com/public/software/dazstudio/4/referenceguide/scripting/api_reference/object_index/rendermgr_dz).
283
+
284
+ ---
285
+
286
+ ## What's New in v2.8.0
287
+
288
+ ### 🚦 Fail Fast When DAZ Studio Is Busy — `503 STUDIO_BUSY`
289
+
290
+ DAZ Studio's Qt main thread is single-threaded: if a user loads a scene,
291
+ saves, clears the scene, or renders through the DAZ Studio UI while the
292
+ server is also being driven remotely, requests that need the main thread
293
+ used to block indefinitely with no signal to the caller. `/execute`,
294
+ `/scripts/:id/execute`, the async-enqueue endpoints, and the render-submit
295
+ endpoints now check DAZ Studio's busy state first and return `503` with
296
+ `error_code: "STUDIO_BUSY"` (plus a `Retry-After` header) immediately
297
+ instead of hanging.
298
+
299
+ `dazpy` surfaces this as a new exception hierarchy — `DazBusyError`, with
300
+ `StudioBusyError` (503) and `ConcurrencyLimitError` (429) subclasses, each
301
+ carrying `reason` and `retry_after`. This also fixes a pre-existing bug
302
+ where `429 CONCURRENT_LIMIT_EXCEEDED` responses were misreported as
303
+ `ScriptRuntimeError`, as if the script itself had failed.
304
+
305
+ ```python
306
+ from dazpy import DazClient
307
+ from dazpy.exceptions import DazBusyError
308
+
309
+ client = DazClient()
310
+ try:
311
+ result = client.execute("1+1;")
312
+ except DazBusyError as e:
313
+ print(f"DAZ Studio is busy: {e.reason} — retry after {e.retry_after}s")
314
+ ```
315
+
316
+ Opt in to automatic retry with backoff instead of handling the exception
317
+ yourself:
318
+
319
+ ```python
320
+ result = client.execute("1+1;", retry_on_busy=True, max_wait=30.0)
321
+ ```
322
+
323
+ `retry_on_busy`/`max_wait` are available on `execute`, `execute_file`,
324
+ `execute_async_submit`, `render_submit`, `render_batch_submit`, and
325
+ `render_animation_submit`. Retry is opt-in and off by default — callers
326
+ (and any LLM driving them) stay informed rather than silently blocked.
327
+
328
+ ---
329
+
172
330
  ## What's New in v2.7.2
173
331
 
174
332
  No functional changes to `dazpy`'s behavior. `2.7.1`'s version bump missed
@@ -273,7 +431,7 @@ change. For dirty scenes it serialises via `Scene.saveScene()` and restores
273
431
  the original filename immediately. The `method` field in the response tells
274
432
  you which strategy was used.
275
433
 
276
- See `docs/examples/fundamentals/scene_save_copy.py` for a complete example
434
+ See [`fundamentals/scene_save_copy`](https://github.com/bluemoonfoundry/daz-script-server-examples/tree/main/fundamentals/scene_save_copy) in the examples repo for a complete example
277
435
  with `--compare` and `--dry-run` options.
278
436
 
279
437
  ---
@@ -325,7 +483,7 @@ snapshot = scene.scene_snapshot()
325
483
  profiles = build_rig_profiles_from_snapshot(snapshot)
326
484
  ```
327
485
 
328
- **Body measurement accuracy improvements** in `docs/examples/body_measurements.py`:
486
+ **Body measurement accuracy improvements** in [`geometry/body_measurements`](https://github.com/bluemoonfoundry/daz-script-server-examples/tree/main/geometry/body_measurements):
329
487
 
330
488
  - Slices now use centroid-weighted torso loop selection to filter arm cross-sections, correcting a ~70 cm overestimate on A-pose figures.
331
489
  - The heuristic fallback for unlabelled figures (e.g. a character named
@@ -505,11 +663,14 @@ print(anim.frame_range()) # (start, end)
505
663
  anim.bake_pose_to_keyframes(start=0, end=60)
506
664
  ```
507
665
 
508
- ### 📐 math3 — Vec3, Quat, BoundingBox
666
+ ### 📐 math3 — Vec3, Quat, BoundingBox, AxisRemap
509
667
 
510
668
  New module **`dazpy.math3`** provides lightweight value types returned throughout
511
669
  the SDK: `Vec3` (positions, translations), `Quat` (rotations), and `BoundingBox`
512
670
  (geometry bounds). All support arithmetic operators and round-trip through JSON.
671
+ `AxisRemap` converts `Vec3`/`Quat`/`BoundingBox` values between axis conventions
672
+ via signed-axis permutations (e.g. the built-in `Y_UP_TO_Z_UP` preset for
673
+ converting DAZ Studio's Y-up scene data to Z-up tools like Blender).
513
674
 
514
675
  ### 🌐 Posed Vertex Export & USD Scene Export
515
676
 
@@ -517,7 +678,7 @@ the SDK: `Vec3` (positions, translations), `Quat` (rotations), and `BoundingBox`
517
678
  positions with skinning and morphs already applied — enabling downstream export
518
679
  pipelines to read the final mesh without re-solving the rig.
519
680
 
520
- New example **`docs/examples/scene_to_usd.py`** uses this to export a full live
681
+ New example **[`export/scene_to_usd`](https://github.com/bluemoonfoundry/daz-script-server-examples/tree/main/export/scene_to_usd)** uses this to export a full live
521
682
  DAZ Studio scene to Pixar USD: posed meshes, cameras, lights, PBR materials, and
522
683
  strand-based hair as `UsdGeom.BasisCurves`. Pass `--morphs` to write blend
523
684
  shapes as `UsdSkel` targets.
@@ -555,8 +716,8 @@ Studio instance) now ship in the repo root.
555
716
 
556
717
  ### 📖 Docs & Ergonomics
557
718
 
558
- - API reference pages for `DazPose`, `DazAnimation`, `Vec3`, `Quat`, and `BoundingBox`
559
- - Every script in `docs/examples/` has an `if __name__ == "__main__":` guard and
719
+ - API reference pages for `DazPose`, `DazAnimation`, `Vec3`, `Quat`, `BoundingBox`, and `AxisRemap`
720
+ - Every script in [daz-script-server-examples](https://github.com/bluemoonfoundry/daz-script-server-examples) has an `if __name__ == "__main__":` guard and
560
721
  argparse `--help`, making all examples safe to import and self-documenting
561
722
 
562
723
  ---
@@ -620,6 +781,8 @@ Long-running operations (renders, exports, batch jobs) no longer need to block t
620
781
 
621
782
  **TTL:** Completed, failed, and cancelled requests are automatically purged after 1 hour (cleanup timer fires every 5 minutes).
622
783
 
784
+ **Stale running requests:** If a request has been `running` for more than 30 minutes without completing — e.g. a render blocked behind a DAZ Studio modal dialog — it's automatically marked `failed` so polling clients aren't left waiting forever. The underlying DAZ Studio operation may still be stuck; check the DAZ Studio window directly if this happens.
785
+
623
786
  ---
624
787
 
625
788
  ## What's New in v1.2.0
@@ -667,7 +830,7 @@ automation code without authoring DazScript by hand.
667
830
  Download the `.whl` file from the [latest release](https://github.com/bluemoonfoundry/daz-script-server/releases/latest) and install it:
668
831
 
669
832
  ```bash
670
- pip install dazpy-2.7.2-py3-none-any.whl
833
+ pip install dazpy-2.9.0-py3-none-any.whl
671
834
  ```
672
835
 
673
836
  Or install directly from the repo for development:
@@ -13,7 +13,7 @@ Typical usage::
13
13
  figure.find_bone("r_forearm").set_local_rotation(0, 0, 45)
14
14
  """
15
15
 
16
- __version__ = "2.7.2"
16
+ __version__ = "2.9.0"
17
17
 
18
18
  from ._client import DazClient
19
19
  from ._scene import DazScene
@@ -76,7 +76,37 @@ from ._undo import UndoGroup
76
76
  from ._pose import DazPose
77
77
  from ._scene_state import DazSceneState
78
78
  from ._animation import DazAnimation
79
- from .math3 import Vec3, Quat, BoundingBox
79
+ from .math3 import Vec3, Quat, BoundingBox, AxisRemap, Y_UP_TO_Z_UP
80
+ from .lighting import (
81
+ LightSpec,
82
+ ThreePointLightSetup,
83
+ ThreePointLightRig,
84
+ apply_three_point_light_setup,
85
+ HDRIEnvironment,
86
+ apply_hdri_environment,
87
+ set_light_color,
88
+ )
89
+ from .poses import apply_pose, reset_transforms, zero_figure
90
+ from .materials import (
91
+ IrayMaterial,
92
+ TextureMap,
93
+ SurfaceProperty,
94
+ apply_iray_material,
95
+ apply_texture_map,
96
+ get_surface_property,
97
+ set_surface_property,
98
+ )
99
+ from .cinematics import (
100
+ CinematicStaticShot,
101
+ OrbitCamera,
102
+ FrameSubject,
103
+ CameraKeyframe,
104
+ CinematicAnimatedShot,
105
+ apply_static_shot,
106
+ apply_orbit_camera,
107
+ apply_frame_subject,
108
+ apply_animated_shot,
109
+ )
80
110
  from ._result import ExecutionResult
81
111
  from ._polling import execute_long
82
112
  from ._render_api import (
@@ -87,7 +117,7 @@ from ._render_api import (
87
117
  render,
88
118
  render_variants,
89
119
  )
90
- from .exceptions import RenderError
120
+ from .exceptions import RenderError, MaterialError
91
121
  from ._scene_events import SceneEvent, watch_scene_events, wait_for_scene_event
92
122
  from . import exceptions
93
123
 
@@ -158,6 +188,34 @@ __all__ = [
158
188
  "Vec3",
159
189
  "Quat",
160
190
  "BoundingBox",
191
+ "AxisRemap",
192
+ "Y_UP_TO_Z_UP",
193
+ "LightSpec",
194
+ "ThreePointLightSetup",
195
+ "ThreePointLightRig",
196
+ "apply_three_point_light_setup",
197
+ "HDRIEnvironment",
198
+ "apply_hdri_environment",
199
+ "set_light_color",
200
+ "apply_pose",
201
+ "reset_transforms",
202
+ "zero_figure",
203
+ "IrayMaterial",
204
+ "TextureMap",
205
+ "SurfaceProperty",
206
+ "apply_iray_material",
207
+ "apply_texture_map",
208
+ "get_surface_property",
209
+ "set_surface_property",
210
+ "CinematicStaticShot",
211
+ "OrbitCamera",
212
+ "FrameSubject",
213
+ "CameraKeyframe",
214
+ "CinematicAnimatedShot",
215
+ "apply_static_shot",
216
+ "apply_orbit_camera",
217
+ "apply_frame_subject",
218
+ "apply_animated_shot",
161
219
  "ExecutionResult",
162
220
  "execute_long",
163
221
  "FigureMorphs",
@@ -165,6 +223,7 @@ __all__ = [
165
223
  "RenderBase",
166
224
  "RenderResult",
167
225
  "RenderError",
226
+ "MaterialError",
168
227
  "render",
169
228
  "render_variants",
170
229
  "SceneEvent",