dazpy script-call batching + find_skeleton race fix - #39
Merged
Conversation
…zSceneState.apply() DazPose.apply_full() rewrites every property on a skeleton, including the internal FID_* property DAZ Studio uses to persist a conforming item's fit registration -- rewriting it desyncs the live getFollowTarget() pointer without raising. apply() now captures/restores each skeleton's conform/ fit-to relationship explicitly via getFollowTarget()/fit_to()/unfit(), with busy-retry, independent of the generic pose restore. Closes daz-script-server-jz0e. Also distinguish DzBackgroundProgress vs DzProgress in mainThreadBusyMessage() so busy responses during scene-load streaming report a more specific reason than the generic fallback. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Pins current client.execute() call counts for snapshot(), reset_transforms(), zero_figure() default mode, and Batch.add() so upcoming batching fixes (Tasks 2-4 of the script-call-batching plan) have a before/after to diff against. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
snapshot() previously called get_property() once per field, issuing one HTTP round-trip per field. It now reads all requested fields in one generated script. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
set_transform() writes any combination of local position/rotation/scale in one DazScript evaluation. reset_transforms() now calls it once instead of issuing three separate set_local_position/set_local_rotation/set_scale calls. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds DazSkeleton._zero_bones_and_morphs(), a single generated script that zeroes every bone rotation and non-zero DzMorph. zero_figure()'s default (include_props=False) path now calls it once instead of issuing separate bone_rotations()/morph_values()/set_bone_rotations()/set_morph_values() round-trips. The include_props=True path (DazPose.apply_full()) is unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Writes multiple properties by display label in a single DazScript
evaluation, returning {label: bool} indicating which labels resolved to a
real property.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ites Sets any combination of bone rotations, morph values, and node properties in one DazScript evaluation. bones/morphs use plain setValue(), matching set_bone_rotations()/set_morph_values() exactly; props also uses plain setValue() (not apply_full()'s ERC-avoidance raw-value path). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
add_operation() lets callers pass a result expression directly instead of guessing the internally generated _rN key name (fixing add()'s own stale docstring example). add_prelude() lets multiple operations share one setup block emitted once. max_operations/max_script_length guard against an oversized batch reaching Studio's main thread, raising BatchLimitExceededError client-side before any HTTP call. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
execute_batch_async() (DazClient and AsyncDazClient) composes a list of
{body_lines, result_expression} operations into one generated script via
the new build_operations_script() helper, then submits it through the
existing /execute/async route -- no C++ changes needed, since one script +
args was already all AsyncExecuteHandler accepts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds MetricsCollector::recordMainThreadWait()/getAvgMainThreadWaitMs()/ getMaxMainThreadWaitMs(), threads the HTTP-thread accepted-at timestamp into handleExecuteRequest(), and surfaces avg/max_main_thread_wait_ms as additive /metrics fields. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds add_operation()/add_prelude(), size limits, whole-batch failure/no-rollback semantics, and execute_batch_async() to docs/api/batch.rst, plus a before/after bulk-update example and sync-vs-async guidance in docs/quickstart.rst. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Scene.getSkeletonList() has been observed to momentarily omit a skeleton that is actually present, under load from a burst of other main-thread script calls (daz-script-server-xtkd) -- most likely a race against DAZ Studio's own deferred/background scene-graph work, in the same family as dpi-mxq's partial-main-thread-execution-under-contention. find_skeleton() now retries the lookup a few times with a short backoff before concluding the skeleton is really absent. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers the dazpy script-call batching epic and the find_skeleton retry-on-transient-miss fix landed on this branch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This was referenced Aug 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Batch.add_operation()/add_prelude()with size limits,DazElement.snapshot()/set_properties(),DazNode.set_transform(),DazSkeleton.set_state(), single-callzero_figure(), one-request async batch submission (execute_batch_async), and main-thread wait time exposed in/metrics.docs/api/batch.rstanddocs/quickstart.rst.DazScene.find_skeleton()now retriesScene.getSkeletonList()on a transient miss (observed under load from a burst of other main-thread script calls) instead of immediately raisingNodeNotFoundError.Test plan
python -m pytest tests/test_dazpy.py -q(710 passed)python -m pytest tests/test_dazpy_aio.py -q(26 passed)./build.sh build --clean/./build.sh build --sdk-version 6 --clean— both SDK4 and SDK6 built and installed cleanly (only pre-existing warnings)set_bone_rotations()calls → 20 HTTP requests (/metricstotal_requestsdelta confirms).Batch.add_operation()→ 1 HTTP request.avg_main_thread_wait_ms/max_main_thread_wait_mspopulate correctly under load, confirming the Task 6 metric works end-to-end.execute_batch_async()with 3 operations: onerequest_id, completed in 1ms, result correctly keyed_r0/_r1/_r2in submission order.find_skeleton()sanity check against the exact skeletons from the original bug report (Genesis9,Genesis9Eyelashes,Genesis9Tear) — all resolve correctly under normal load. (Did not attempt to reproduce the original race, which needs the same rapid restore() burst that previously froze Studio.)set_bone_rotations()/bone_rotations()round-trip evaluated values as raw values, so ERC-driven bones (auto-follow twist bones, foot chain) double-count the ERC contribution on restore — same class of issueDazPosealready works around by using raw values. Scene was fully restored to its exact original pose after testing (verified per-bone).🤖 Generated with Claude Code