Options-driven workflow: configurable paths, physics, richer FrameData JSONs, 0-based frames + GP/DK fixes - #2
Open
polynaut wants to merge 35 commits into
Open
Conversation
Morphs in FrameData JSONs can carry base (explicit frame-0 value) and autoBase (resolve the frame-0 value from the current scene state before reset). Electra gains the InvictaCouture pressure morphs and the combined SLGlutes frames; frame 6 switches to Booty Preset 04. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
options.gpArtDirectionPath / options.dkArtDirectionPath override the hardcoded GP9_ArtDirection.json / DK9_ArtDirection.json next to the scripts, falling back to those when unset. Generated per-character wrappers (DTH Character Studio) use this to ship their own art direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The optional groups array (from DTH Character Studio) marks frame ranges as additive or cumulative. Their keyframes sustain until the group end instead of sawtoothing back: for additive only the base (first) pose sustains while the rest sawtooth within; for cumulative every pose holds until the group end, and props repeating inside a group ramp through their values (AnusOpen 22/35/80 pattern) without re-anchoring to base. advancedAdditive shapes the timeline like additive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
options.jcmRomPath/mouthRomPath short-circuit getRomPaths, and options.gpRomPath/dkRomPath override the ROM_GP9/ROM_DK9 constants - all falling back to the DTH_POSES_PATH resolution when unset, so existing workflows are unaffected. Also turns the trailing comma after the DTH_POSES_PATH declaration into a semicolon (invalid JS that QtScript happened to tolerate). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ApplyPhysics loads the 43-frame G9 Physics Example ROM (offsets 0-42, determined from its DSON keyframes) onto the base figure after the GP/DK blocks, with options.physRomPath overriding the default location. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, refresh docs Bug fix: - ApplyInitialValues wrote options.FACsDetailStrength into body_ctrl_FlexionAutoStrength instead of options.FlexionStrength. Hygiene: - Gate the OUT*.csv debug dumps (frameDatas / success / failed / scanned) behind a new options.bWriteDebugCSV (default false) so normal runs no longer litter the scripts folder. The morph application itself is unchanged. - Add .gitignore (.claude/, OUT*.csv) and untrack per-user .claude/settings.local.json. Honesty: - Mark the DicktatorG9 (ApplyDK9) path EXPERIMENTAL/INCOMPLETE with its known gaps (frame count, unused fence offsets, stale linear base) rather than shipping it as if finished. Logic left as-is. Docs: - Rewrite README: GP9 now works and DQS is the validated path (were marked not-working / untested); document the FrameData JSON format and the full options surface (romPaths, art-direction paths, physics, preserve*, jcmMorphMods, generation-method groups). Refresh the CLAUDE.md options list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…DK status The DicktatorG9 ROM is 54 frames (DK9.csv / DK9_ArtDirection.json are 0-53), but dk9FrameCount was 55. setTotalFrames(startFrame + 55) reserved one frame past the last pose, and since the next section derives its start from the timeline end, that reserved frame surfaced as an empty gap once a section (e.g. Physics) was appended after DK. Correcting it to 54 makes the next section start immediately after DK's last pose. Also reframe the ApplyDK9 banner + README from "experimental/incomplete" to "functional, not yet hardened": DK has been run end-to-end with the morphs coming through in Unreal, just without byte-level validation. Internal fences stay off (tested fine without them); the uncircumcised foreskin path is the remaining gap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Daz timelines are 0-based: frame 0 is the first frame, and each section's frame 0 is its own first frame. Make the framework consistent with that instead of relying on two off-by-ones cancelling. - ApplyGP9: gpFrameCount = 104 (the GP ROM's full frame count, rel 0-103), so the next section starts at rel 104 - the frame after ClitorisErect - exactly like DK (dk9FrameCount = 54). A loadPreset ROM block (Physics) and a 0-based extraJSONs section then both land cleanly at rel 104, with no overlap. - Migrate the example FrameData JSONs (ElectraG9, MattG9) from 1-based to 0-based (first FBM frame 1 -> 0). The 1-based numbering only cancelled the older 103 handoff; under 104 it would push the FBM block one frame too far. Verified against a real ElectraG9 PoseAsset export: GP ClitorisErect at abs 431, FBM BodyTone at abs 432. With gpFrameCount 104 and the 0-based JSON, BodyTone lands at 432 + 0 = 432 - identical layout, now convention-consistent, and Physics-after-GP no longer collides. NOTE: any external generator of FrameData JSONs (the studio's toDazFbmJson) must also emit 0-based. The absolute PoseAsset CSV layout is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ApplyDTHCharacter(config) to DthWorkflow.dsa: one call that takes a single object carrying the full character config AND all ROM morph definitions inline, maps it onto the shared options, and runs the normal workflow — no extra .json/.csv files read from disk. Powers DTH Character Studio's one-file <Name>_<Genesis>.dsa output. To support inline data without changing the file-based behaviour: - DthUtils: split readFrameDataJSON into a file reader + processFrameDataJSON (takes parsed data + optional GP art-direction data for the resetGP reset); split applyGPArtDirection into a file reader + applyArtDirectionData. - DthWorkflow: CreateFrameDatas honours options.extraFrames (inline); ApplyGP9 and ApplyDK9 prefer options.gpArtDirection / dkArtDirection over file paths. Local only — for review before pushing/PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Inline the keyframe-scanning functions (scanNodesForFrameDatas + helpers) from ScanKeyFrames.dsa into DthWorkflow.dsa and drop the include, so the runtime ships as one fewer file. Only reached via the bWriteDebugCSV path. ScanKeyFrames.dsa stays as a standalone debug script. Local only — for review before pushing/PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- setTotalFrames now also calls Scene.setPlayRange so the timeline scrubber reaches the later frames (the FBM section after GP) instead of staying bounded to the previous play range — fixes "can only scrub to ~431". - applyKeyData disables clamping on each morph property (setIsClamped(false), guarded) before setting values, so DAZ Studio 6 doesn't prompt "keep limits?" for FBM/custom morphs that exceed their authored range. Local only — for review before pushing/PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ApplyDTHWorkflow already captures the initial selection (aNodes / oNode) at the start; on success (after setFrame(0), before acceptUndo) it now deselects all nodes and reselects the original selection + primary, since loading the ROM presets re-selects nodes along the way. Error paths return early and skip this, leaving the scene as-is for debugging. Lives in DthWorkflow.dsa (the shared runtime), so DthWorkflowElectraG9.dsa and the studio-generated character scripts both pick it up via their include. Local only — for review before pushing/PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
processFrameDataJSON now resets whichever genital ROM's morphs are present (Golden Palace and/or Dicktator) at the FBM block's first frame, reading the matching sibling GP9/DK9 art-direction JSON or the inline gpArtDirection / dkArtDirection; CreateFrameDatas passes options.dkArtDirection through. README documents both meta flags (resetGPBeforeApplying / resetDKBeforeApplying). Also clarify the include-path comment: getScriptFileName() resolves to the invoking script's folder, not this file's. DK path is functional but not byte-validated (see Status).
Five fixes root-caused from a generated ROM script's log (kept in sync
with the DTH Character Studio bundled runtime copy):
- setFencePoses: replace `for…in` over the fence-frame array (which in
Daz's engine also yields enumerable Array.prototype members, restoring
the figure at `function f(){…}`/empty frames) with an indexed loop.
- getValueChannel(0) -> getValueChannel(): the method takes no args; the
extra 0 logged "Too many arguments, ignoring 1" on every morph lookup.
- applyArtDirectionData: add a findProperty/findPropertyByLabel fallback
(mirroring applyKeyData) so geo-graft alias morphs like Golden Palace
GP_PR_* resolve instead of "Property not found".
- setPropertyByName: verify by reading the value back instead of trusting
setValue's return, so a no-op (value already set) stops logging a false
"Failed" (e.g. FACS Detail Strength / Flexion Automatic Strength).
- var-scope implicit globals (oProp/oMod/oMorph/oContentMgr), silencing
the "used before declaration" warning.
Co-authored-by: Polynaut <polynaut.git@proton.me>
Morphs in FrameData JSONs can carry base (explicit frame-0 value) and autoBase (resolve the frame-0 value from the current scene state before reset). Electra gains the InvictaCouture pressure morphs and the combined SLGlutes frames; frame 6 switches to Booty Preset 04. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
options.gpArtDirectionPath / options.dkArtDirectionPath override the hardcoded GP9_ArtDirection.json / DK9_ArtDirection.json next to the scripts, falling back to those when unset. Generated per-character wrappers (DTH Character Studio) use this to ship their own art direction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The optional groups array (from DTH Character Studio) marks frame ranges as additive or cumulative. Their keyframes sustain until the group end instead of sawtoothing back: for additive only the base (first) pose sustains while the rest sawtooth within; for cumulative every pose holds until the group end, and props repeating inside a group ramp through their values (AnusOpen 22/35/80 pattern) without re-anchoring to base. advancedAdditive shapes the timeline like additive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
options.jcmRomPath/mouthRomPath short-circuit getRomPaths, and options.gpRomPath/dkRomPath override the ROM_GP9/ROM_DK9 constants - all falling back to the DTH_POSES_PATH resolution when unset, so existing workflows are unaffected. Also turns the trailing comma after the DTH_POSES_PATH declaration into a semicolon (invalid JS that QtScript happened to tolerate). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ApplyPhysics loads the 43-frame G9 Physics Example ROM (offsets 0-42, determined from its DSON keyframes) onto the base figure after the GP/DK blocks, with options.physRomPath overriding the default location. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, refresh docs Bug fix: - ApplyInitialValues wrote options.FACsDetailStrength into body_ctrl_FlexionAutoStrength instead of options.FlexionStrength. Hygiene: - Gate the OUT*.csv debug dumps (frameDatas / success / failed / scanned) behind a new options.bWriteDebugCSV (default false) so normal runs no longer litter the scripts folder. The morph application itself is unchanged. - Add .gitignore (.claude/, OUT*.csv) and untrack per-user .claude/settings.local.json. Honesty: - Mark the DicktatorG9 (ApplyDK9) path EXPERIMENTAL/INCOMPLETE with its known gaps (frame count, unused fence offsets, stale linear base) rather than shipping it as if finished. Logic left as-is. Docs: - Rewrite README: GP9 now works and DQS is the validated path (were marked not-working / untested); document the FrameData JSON format and the full options surface (romPaths, art-direction paths, physics, preserve*, jcmMorphMods, generation-method groups). Refresh the CLAUDE.md options list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…DK status The DicktatorG9 ROM is 54 frames (DK9.csv / DK9_ArtDirection.json are 0-53), but dk9FrameCount was 55. setTotalFrames(startFrame + 55) reserved one frame past the last pose, and since the next section derives its start from the timeline end, that reserved frame surfaced as an empty gap once a section (e.g. Physics) was appended after DK. Correcting it to 54 makes the next section start immediately after DK's last pose. Also reframe the ApplyDK9 banner + README from "experimental/incomplete" to "functional, not yet hardened": DK has been run end-to-end with the morphs coming through in Unreal, just without byte-level validation. Internal fences stay off (tested fine without them); the uncircumcised foreskin path is the remaining gap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Daz timelines are 0-based: frame 0 is the first frame, and each section's frame 0 is its own first frame. Make the framework consistent with that instead of relying on two off-by-ones cancelling. - ApplyGP9: gpFrameCount = 104 (the GP ROM's full frame count, rel 0-103), so the next section starts at rel 104 - the frame after ClitorisErect - exactly like DK (dk9FrameCount = 54). A loadPreset ROM block (Physics) and a 0-based extraJSONs section then both land cleanly at rel 104, with no overlap. - Migrate the example FrameData JSONs (ElectraG9, MattG9) from 1-based to 0-based (first FBM frame 1 -> 0). The 1-based numbering only cancelled the older 103 handoff; under 104 it would push the FBM block one frame too far. Verified against a real ElectraG9 PoseAsset export: GP ClitorisErect at abs 431, FBM BodyTone at abs 432. With gpFrameCount 104 and the 0-based JSON, BodyTone lands at 432 + 0 = 432 - identical layout, now convention-consistent, and Physics-after-GP no longer collides. NOTE: any external generator of FrameData JSONs (the studio's toDazFbmJson) must also emit 0-based. The absolute PoseAsset CSV layout is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ApplyDTHCharacter(config) to DthWorkflow.dsa: one call that takes a single object carrying the full character config AND all ROM morph definitions inline, maps it onto the shared options, and runs the normal workflow — no extra .json/.csv files read from disk. Powers DTH Character Studio's one-file <Name>_<Genesis>.dsa output. To support inline data without changing the file-based behaviour: - DthUtils: split readFrameDataJSON into a file reader + processFrameDataJSON (takes parsed data + optional GP art-direction data for the resetGP reset); split applyGPArtDirection into a file reader + applyArtDirectionData. - DthWorkflow: CreateFrameDatas honours options.extraFrames (inline); ApplyGP9 and ApplyDK9 prefer options.gpArtDirection / dkArtDirection over file paths. Local only — for review before pushing/PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Inline the keyframe-scanning functions (scanNodesForFrameDatas + helpers) from ScanKeyFrames.dsa into DthWorkflow.dsa and drop the include, so the runtime ships as one fewer file. Only reached via the bWriteDebugCSV path. ScanKeyFrames.dsa stays as a standalone debug script. Local only — for review before pushing/PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- setTotalFrames now also calls Scene.setPlayRange so the timeline scrubber reaches the later frames (the FBM section after GP) instead of staying bounded to the previous play range — fixes "can only scrub to ~431". - applyKeyData disables clamping on each morph property (setIsClamped(false), guarded) before setting values, so DAZ Studio 6 doesn't prompt "keep limits?" for FBM/custom morphs that exceed their authored range. Local only — for review before pushing/PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ApplyDTHWorkflow already captures the initial selection (aNodes / oNode) at the start; on success (after setFrame(0), before acceptUndo) it now deselects all nodes and reselects the original selection + primary, since loading the ROM presets re-selects nodes along the way. Error paths return early and skip this, leaving the scene as-is for debugging. Lives in DthWorkflow.dsa (the shared runtime), so DthWorkflowElectraG9.dsa and the studio-generated character scripts both pick it up via their include. Local only — for review before pushing/PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
processFrameDataJSON now resets whichever genital ROM's morphs are present (Golden Palace and/or Dicktator) at the FBM block's first frame, reading the matching sibling GP9/DK9 art-direction JSON or the inline gpArtDirection / dkArtDirection; CreateFrameDatas passes options.dkArtDirection through. README documents both meta flags (resetGPBeforeApplying / resetDKBeforeApplying). Also clarify the include-path comment: getScriptFileName() resolves to the invoking script's folder, not this file's. DK path is functional but not byte-validated (see Status).
Five fixes root-caused from a generated ROM script's log (kept in sync
with the DTH Character Studio bundled runtime copy):
- setFencePoses: replace `for…in` over the fence-frame array (which in
Daz's engine also yields enumerable Array.prototype members, restoring
the figure at `function f(){…}`/empty frames) with an indexed loop.
- getValueChannel(0) -> getValueChannel(): the method takes no args; the
extra 0 logged "Too many arguments, ignoring 1" on every morph lookup.
- applyArtDirectionData: add a findProperty/findPropertyByLabel fallback
(mirroring applyKeyData) so geo-graft alias morphs like Golden Palace
GP_PR_* resolve instead of "Property not found".
- setPropertyByName: verify by reading the value back instead of trusting
setValue's return, so a no-op (value already set) stops logging a false
"Failed" (e.g. FACS Detail Strength / Flexion Automatic Strength).
- var-scope implicit globals (oProp/oMod/oMorph/oContentMgr), silencing
the "used before declaration" warning.
Co-authored-by: Polynaut <polynaut.git@proton.me>
Character generation now lives in polynaut/dth-character-studio, so the per-character payloads and one-off debug tools no longer belong here. Delete: - DK9_Workflow.dsa (superseded; ApplyDK9 lives in DthWorkflow.dsa) - DthApplyFrameDatas.dsa (orphan; read a non-existent CSV) - DthScanDK9Props.dsa, DthVerifyDK9ArtDirection.dsa (one-off DK9 debug tools) - ScannedFrames.CSV, ElectraG9_FBMs.csv (stale export artifacts) - DK9.json, MattG9_FBMs.json, DthWorkflowMattG9.dsa, GP9.md, GP9_prep.dsa (character/product data now owned by dth-character-studio) Keep GP9/DK9_ArtDirection.json as runtime fallback defaults, and DK9.csv. Trim ElectraG9_FBMs.json from 49 frames to a 4-frame minimal example, and repoint DthWorkflowExample/FromCSV at it (both referenced deleted or never-existent files). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XFpiyAjBvNqVjT7R7L2NDi
The previous cleanup overreached and deleted four files authored by soltude (the upstream maintainer) rather than only the Polynaut/Claude additions. Restore them, since this PR targets soltude:main: - DK9_Workflow.dsa (initial commit) - DthApplyFrameDatas.dsa - GP9_prep.dsa (initial commit) - ScannedFrames.CSV Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XFpiyAjBvNqVjT7R7L2NDi
Its ApplyDK9 logic was merged into DthWorkflow.dsa; the standalone copy is no longer included by anything. Confirmed for removal with the maintainer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XFpiyAjBvNqVjT7R7L2NDi
polynaut
force-pushed
the
feature/small-enhancements
branch
from
June 27, 2026 16:50
15e5801 to
092c1eb
Compare
…ToHue-Scripts into feature/small-enhancements # Conflicts: # ElectraG9_FBMs.json # MattG9_FBMs.json
processFrameDataJSON: an entry with an invalid (NaN/negative) frame number is reported and skipped instead of silently corrupting the frame array and shortening the timeline - every valid frame keeps its slot, so the Daz timeline stays aligned with the Houdini PoseAsset frames. createMorphFrames: a morph that can't be resolved no longer `break`s out of the frame's config loop (which dropped all remaining morphs of that frame) - it's reported and skipped, costing exactly that one morph. Failures are printed to the Daz Studio log as before. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoMAkdAadM4fmMbQZ13htD
Author
|
Added one more fix (4eb1742): a morph that can't be resolved (or a frame entry with an invalid frame number) no longer shifts/shortens the ROM timeline — it's reported to the log and skipped, so only that one morph is lost and the Daz timeline stays frame-aligned with the Houdini PoseAsset import. Previously createMorphFrames dropped ALL remaining morphs of a frame on the first miss (break instead of continue), and a NaN frame number could silently corrupt the frame array. |
Two runtime fixes, mirrored from DTH Character Studio's copy (kept in that repo's print()-only flavor): - ApplyDefaultROMs only returned true from inside the bIncludeFAC branch, so running JCM without FAC (e.g. a custom base ROM with FAC off) loaded the base ROM and then silently aborted the whole workflow. Restructured to early-returns; the base ROM alone is a successful load, FAC stays optional. - Preset block lengths (base/GP/DK/Physics) are no longer inlined at each call site. getPresetFrameCount() uses options.presetFrames when provided (e.g. by DTH Character Studio, which measures the actual .duf lengths, or by a wrapper using a custom-length base ROM) and falls back to the stock DTH G9 asset lengths otherwise - so existing setups behave identically. New documented `presetFrames: null` option in DthOptions.dsa. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoMAkdAadM4fmMbQZ13htD
DS6's animation engine drifts LINEAR-interpolated ROM keys across the timeline (mrpdean, June 2026: the G9 DQS JCM FAC cheek poses creeping); converting every key to Constant after applying is his validated workaround. - dthDetectDaz6() (JoxiMoon's version-API detection, DS4-safe default). - setLinearInterp stamps CONSTANT_INTERP on DS6, LINEAR_INTERP on DS4 (unchanged validated behavior); the session default interpolation matches. - The final pass now also covers the FAC mouth node - its keys are morph modifiers on the MOUTH object, which a root-only pass never touched. Mirrored from DTH Character Studio's runtime copy (v17). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoMAkdAadM4fmMbQZ13htD
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.
Grows
feature/small-enhancementsbeyond the original autoBase change (all done with claude.ai). Every newoptions.*falls back to the existing path/constant when unset — current workflows behave identically.Options-driven — exact ROM paths (
jcmRomPath/gpRomPath/…), per-character art-direction JSONs, andbIncludePhysicsto append the G9 Physics ROM after GP/DK.Richer FrameData JSONs —⚠️ external JSON generators must now emit 0-based — absolute PoseAsset layout unchanged (verified against a real ElectraG9 export).
base/autoBaseper morph; generation-methodgroups(additive/cumulative) that sustain instead of sawtooth; and a 0-based frame convention (gpFrameCount = 104, matching DK's 54) so post-GP sections land cleanly.Packaging —
ApplyDTHCharacter(config): one call with config + all morph data inline, no extra files read from disk.ScanKeyFramesmerged intoDthWorkflow(one fewer runtime file).Fixes — FlexionStrength wrote to the wrong property; DK trailing empty frame (
dk9FrameCount55→54); DS6 play-range/clamping prompts; restore initial selection after a run; reset Dicktator morphs alongside GP; debug CSVs now gated behindbWriteDebugCSV(default off).Status — GP9/DQS is the validated path. DK is functional but not byte-hardened (runs end-to-end into Unreal; uncircumcised-foreskin path still a gap).