topview-3d-cli 0.1.0__py3-none-any.whl
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.
- topview_3d_cli/__init__.py +3 -0
- topview_3d_cli/__main__.py +5 -0
- topview_3d_cli/_runtime/builtin-assets/LICENSE +396 -0
- topview_3d_cli/_runtime/builtin-assets/NOTICE +24 -0
- topview_3d_cli/_runtime/builtin-assets/characters/a3d_char_30ec53c93eae4173946b38639691ebe0.glb +0 -0
- topview_3d_cli/_runtime/builtin-assets/characters/a3d_char_461b4f4b76ae4b198f439fa4f6f743b6.glb +0 -0
- topview_3d_cli/_runtime/builtin-assets/characters/a3d_char_7d220ac359844c80a8faaf0e25478477.glb +0 -0
- topview_3d_cli/_runtime/builtin-assets/characters/a3d_char_de37873a79244fbe8a0660ea08d1b4ab.glb +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-attack-jab.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-attack-lunge.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-attack-rear.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-crawling-forward-reaching-with-le.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-exaggerated-punch-with-right-hand.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-examine-fwd.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-holding-bow.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-jab-with-right-hand.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-on-hands-and-toes-right-leg-forwa.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-on-right-leg.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-push-up.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-pushing-backwards.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-pushing-with-both-hands.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-pushing-with-left-side-of-body.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-sliding.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-arms-spread-looking-upwards.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-examine-fwd.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-lean-fwd-right-hand-supporting.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-leaning-against-object-look-l.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-look-back.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-look-down-back.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-look-fwd-f.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-look-fwd-m.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-on-knees-arms-up.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-sneaking.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-l-toe-top-of-right-foot.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-legs-spread-lean-l.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-legs-spread-lean-r.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-legs-x.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-r-foot-arms-extended-backwards.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-r-toe-arms-up.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-r-toe-l-leg-up.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-toes-arch.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-toes-reach.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dyn-floating-bent-forwards.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dyn-floating-left-leg-bent-hands-spread.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dyn-floating-legs-bent-and-arms-spread.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dyn-floating-legs-raised-arms-extended-u.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dyn-l-toe-look-l.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dyn-leaping-forward-arms-extended-backwa.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dynamic-float-arch.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dynamic-float-dive.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-back-arched-arms-supp.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-legs-to-side-arms-up.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-legs-x-arms-supp.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-back-left-leg-bent-hands-head.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-back-left-leg-bent.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-back-legs-x-hands-head.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-left-side-left-arm-supporting-hea.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-left-side-left-arm-supporting.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-right-side-right-arm-supporting.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-right-side-right-hand-supporting.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-stomach-elbows-supporting.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-right-arm-supporting-l-arm-up.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-right-leg-forward-arms-supp.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-run-fwd.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-run-reach.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-sneaking-forward.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-sprinting.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-stepping-forward-look-l.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-walk-fwd-look-r.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-walk-fwd.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-walking-arms-on-hips.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-walking-right-arm-on-hip.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-walking.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-arms-holding-left-knee-reclining.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-arms-holding-right-knee.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-arms-resting-on-knees.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-arms-supp.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-arms-up.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-elbows-on-legs-lean-fwd.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-elbows-on-legs-lean-l.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-knees-up-hold.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-lean-back-arms-supp.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-lean-fwd-elbows-on-knees.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-lean-fwd-hands-in-front.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-legs-x-arms-x.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-legs-x-hands-head.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-legs-x-hold.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-legs-x-reclining.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-legs-x-right-arm-over-knee.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reaching-hands-towards-toes.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reaching-left-hand-towards-right-toe.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reclined-facepalm.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reclined-hands-head.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reclined-left-leg-crossed-hands-head.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reclined-left-leg-crossed.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reclined-look-down.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reclined-right-leg-crossed-hands-hea.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-rev-sit-hands-grasping.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-rev-sit-hands-relaxed.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-arms-behind-back.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-arms-down.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-arms-slightly-spread.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-bending-forward-arms-x-over-knees.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-hands-on-hips-look-back.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-l-hip-f.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-l-hip-m.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-l-leg-r-hip-l-arm-up.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-lean-back-arms-behind.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-lean-back-l-hip.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-left-elbow-resting-obj.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-left-leg-obj-left-hand-on-knee-r-h.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-left-leg-obj-left-hand-on-knee.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-legs-x-arms-up.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-legs-x-l-hip.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-legs-x-leaning-obj.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-legs-x-left-arm-obj-r-hip.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-look-down-r.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-look-fwd.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-look-l.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-look-r.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-r-arm-up-l-hip.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-r-foot-l-leg-up.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-r-foot-up.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-r-hip-l-arm-up.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-r-hip.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-right-hand-behind-head.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-right-leg-obj-arms-x.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-right-leg-obj-l-hip-right-hand-on.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_t-pose.webp +0 -0
- topview_3d_cli/_runtime/builtin-assets/manifest.json +2372 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-attack-jab.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-attack-lunge.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-attack-rear.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-crawling-forward-reaching-with-le.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-exaggerated-punch-with-right-hand.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-examine-fwd.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-holding-bow.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-jab-with-right-hand.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-on-hands-and-toes-right-leg-forwa.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-on-right-leg.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-push-up.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-pushing-backwards.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-pushing-with-both-hands.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-pushing-with-left-side-of-body.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-sliding.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-arms-spread-looking-upwards.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-examine-fwd.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-lean-fwd-right-hand-supporting.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-leaning-against-object-look-l.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-look-back.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-look-down-back.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-look-fwd-f.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-look-fwd-m.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-on-knees-arms-up.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-sneaking.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-l-toe-top-of-right-foot.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-legs-spread-lean-l.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-legs-spread-lean-r.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-legs-x.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-r-foot-arms-extended-backwards.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-r-toe-arms-up.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-r-toe-l-leg-up.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-toes-arch.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-toes-reach.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dyn-floating-bent-forwards.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dyn-floating-left-leg-bent-hands-spread.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dyn-floating-legs-bent-and-arms-spread.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dyn-floating-legs-raised-arms-extended-u.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dyn-l-toe-look-l.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dyn-leaping-forward-arms-extended-backwa.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dynamic-float-arch.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dynamic-float-dive.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-back-arched-arms-supp.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-legs-to-side-arms-up.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-legs-x-arms-supp.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-back-left-leg-bent-hands-head.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-back-left-leg-bent.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-back-legs-x-hands-head.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-left-side-left-arm-supporting-hea.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-left-side-left-arm-supporting.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-right-side-right-arm-supporting.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-right-side-right-hand-supporting.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-stomach-elbows-supporting.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-right-arm-supporting-l-arm-up.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-right-leg-forward-arms-supp.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-run-fwd.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-run-reach.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-sneaking-forward.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-sprinting.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-stepping-forward-look-l.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-walk-fwd-look-r.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-walk-fwd.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-walking-arms-on-hips.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-walking-right-arm-on-hip.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-walking.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-arms-holding-left-knee-reclining.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-arms-holding-right-knee.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-arms-resting-on-knees.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-arms-supp.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-arms-up.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-elbows-on-legs-lean-fwd.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-elbows-on-legs-lean-l.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-knees-up-hold.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-lean-back-arms-supp.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-lean-fwd-elbows-on-knees.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-lean-fwd-hands-in-front.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-legs-x-arms-x.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-legs-x-hands-head.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-legs-x-hold.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-legs-x-reclining.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-legs-x-right-arm-over-knee.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reaching-hands-towards-toes.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reaching-left-hand-towards-right-toe.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reclined-facepalm.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reclined-hands-head.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reclined-left-leg-crossed-hands-head.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reclined-left-leg-crossed.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reclined-look-down.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reclined-right-leg-crossed-hands-hea.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-rev-sit-hands-grasping.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-rev-sit-hands-relaxed.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-arms-behind-back.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-arms-down.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-arms-slightly-spread.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-bending-forward-arms-x-over-knees.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-hands-on-hips-look-back.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-l-hip-f.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-l-hip-m.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-l-leg-r-hip-l-arm-up.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-lean-back-arms-behind.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-lean-back-l-hip.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-left-elbow-resting-obj.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-left-leg-obj-left-hand-on-knee-r-h.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-left-leg-obj-left-hand-on-knee.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-legs-x-arms-up.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-legs-x-l-hip.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-legs-x-leaning-obj.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-legs-x-left-arm-obj-r-hip.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-look-down-r.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-look-fwd.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-look-l.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-look-r.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-r-arm-up-l-hip.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-r-foot-l-leg-up.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-r-foot-up.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-r-hip-l-arm-up.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-r-hip.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-right-hand-behind-head.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-right-leg-obj-arms-x.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-right-leg-obj-l-hip-right-hand-on.json +1 -0
- topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_t-pose.json +1 -0
- topview_3d_cli/_runtime/director-cli/applyIntent.mjs +35 -0
- topview_3d_cli/_runtime/director-cli/browser.mjs +47 -0
- topview_3d_cli/_runtime/director-cli/cli.mjs +56 -0
- topview_3d_cli/_runtime/director-cli/editSequence.mjs +125 -0
- topview_3d_cli/_runtime/director-cli/evaluate.mjs +254 -0
- topview_3d_cli/_runtime/director-cli/mediaUrl.mjs +47 -0
- topview_3d_cli/_runtime/director-cli/node_modules/@topview/3d-builder/dist/draco/draco_decoder.js +34 -0
- topview_3d_cli/_runtime/director-cli/node_modules/@topview/3d-builder/dist/draco/draco_decoder.wasm +0 -0
- topview_3d_cli/_runtime/director-cli/node_modules/@topview/3d-builder/dist/draco/draco_wasm_wrapper.js +117 -0
- topview_3d_cli/_runtime/director-cli/node_modules/@topview/3d-builder/dist/engine/index.mjs +22219 -0
- topview_3d_cli/_runtime/director-cli/node_modules/@topview/3d-builder/dist/evaluate/index.mjs +4229 -0
- topview_3d_cli/_runtime/director-cli/node_modules/@topview/3d-builder/dist/headless/index.mjs +22832 -0
- topview_3d_cli/_runtime/director-cli/node_modules/@topview/3d-builder/package.json +75 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/LICENSE +21 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/build/three.core.js +59732 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/build/three.module.js +19552 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/controls/OrbitControls.js +1963 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/curves/NURBSCurve.js +155 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/curves/NURBSUtils.js +532 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/libs/draco/draco_decoder.js +34 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/libs/draco/draco_decoder.wasm +0 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/libs/draco/draco_wasm_wrapper.js +117 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/libs/fflate.module.js +2672 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/lines/LineMaterial.js +703 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/lines/LineSegments2.js +426 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/lines/LineSegmentsGeometry.js +298 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/loaders/DRACOLoader.js +739 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/loaders/FBXLoader.js +4580 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/loaders/GLTFLoader.js +4860 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/utils/BufferGeometryUtils.js +1434 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/utils/SkeletonUtils.js +496 -0
- topview_3d_cli/_runtime/director-cli/node_modules/three/package.json +123 -0
- topview_3d_cli/_runtime/director-cli/node_modules/zod/LICENSE +21 -0
- topview_3d_cli/_runtime/director-cli/node_modules/zod/index.js +4 -0
- topview_3d_cli/_runtime/director-cli/node_modules/zod/package.json +118 -0
- topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/ZodError.js +133 -0
- topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/errors.js +9 -0
- topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/external.js +6 -0
- topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/helpers/errorUtil.js +6 -0
- topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/helpers/parseUtil.js +109 -0
- topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/helpers/typeAliases.js +1 -0
- topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/helpers/util.js +133 -0
- topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/locales/en.js +109 -0
- topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/types.js +3693 -0
- topview_3d_cli/_runtime/director-cli/package.json +6 -0
- topview_3d_cli/_runtime/director-cli/playwright.mjs +26 -0
- topview_3d_cli/_runtime/director-cli/render.mjs +336 -0
- topview_3d_cli/_runtime/director-cli/static/headless.html +124 -0
- topview_3d_cli/_runtime/director-cli/static/measure.mjs +61 -0
- topview_3d_cli/_runtime/director-cli/static/pose.mjs +71 -0
- topview_3d_cli/_runtime/director-cli/staticIntent.mjs +135 -0
- topview_3d_cli/_runtime/runtime.json +6 -0
- topview_3d_cli/bom.py +326 -0
- topview_3d_cli/contracts/director-v1/schema/director-document.schema.json +1097 -0
- topview_3d_cli/contracts/director-v1/schema/fcurves-compact-v1.schema.json +105 -0
- topview_3d_cli/director_camera_view.py +54 -0
- topview_3d_cli/director_document.py +793 -0
- topview_3d_cli/director_geometry.py +66 -0
- topview_3d_cli/director_operations.py +679 -0
- topview_3d_cli/director_static.py +134 -0
- topview_3d_cli/local_assets.py +248 -0
- topview_3d_cli/local_bom.py +108 -0
- topview_3d_cli/local_catalog.py +156 -0
- topview_3d_cli/local_cli.py +493 -0
- topview_3d_cli/local_edit.py +350 -0
- topview_3d_cli/local_errors.py +104 -0
- topview_3d_cli/local_inspect.py +302 -0
- topview_3d_cli/local_project.py +280 -0
- topview_3d_cli/local_read.py +139 -0
- topview_3d_cli/local_render.py +97 -0
- topview_3d_cli/node_batch.py +299 -0
- topview_3d_cli/renderer.py +75 -0
- topview_3d_cli/runtime.py +107 -0
- topview_3d_cli-0.1.0.dist-info/METADATA +48 -0
- topview_3d_cli-0.1.0.dist-info/RECORD +332 -0
- topview_3d_cli-0.1.0.dist-info/WHEEL +5 -0
- topview_3d_cli-0.1.0.dist-info/entry_points.txt +2 -0
- topview_3d_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
- topview_3d_cli-0.1.0.dist-info/licenses/LICENSE-ASSETS +396 -0
- topview_3d_cli-0.1.0.dist-info/licenses/NOTICE +18 -0
- topview_3d_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
"""Normalize and fail-closed-validate director-document-v1 JSON."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import copy
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
import uuid
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from jsonschema import Draft7Validator
|
|
12
|
+
|
|
13
|
+
DIRECTOR_DOCUMENT_TYPE = "biz/scene3d-director-document"
|
|
14
|
+
NODE_TYPES = ("camera", "character", "prop", "path", "group", "primitive")
|
|
15
|
+
SCENE3D_OP_ID = re.compile(r"^[A-Za-z0-9_-]{1,128}$")
|
|
16
|
+
_SCENE3D_OP_ID_UNSAFE = re.compile(r"[^A-Za-z0-9_-]")
|
|
17
|
+
SCHEMA_PATH = Path(__file__).resolve().parent / "contracts/director-v1/schema/director-document.schema.json"
|
|
18
|
+
FCURVES_SCHEMA_PATH = SCHEMA_PATH.with_name("fcurves-compact-v1.schema.json")
|
|
19
|
+
_VALIDATOR: Draft7Validator | None = None
|
|
20
|
+
_FCURVES_VALIDATOR: Draft7Validator | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _validator() -> Draft7Validator:
|
|
24
|
+
global _VALIDATOR
|
|
25
|
+
if _VALIDATOR is None:
|
|
26
|
+
if not SCHEMA_PATH.is_file():
|
|
27
|
+
raise FileNotFoundError(f"director-document schema missing: {SCHEMA_PATH}")
|
|
28
|
+
_VALIDATOR = Draft7Validator(json.loads(SCHEMA_PATH.read_text(encoding="utf-8")))
|
|
29
|
+
return _VALIDATOR
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def new_scene_operation_id(stem: str = "op") -> str:
|
|
33
|
+
suffix = uuid.uuid4().hex[:10]
|
|
34
|
+
cleaned = _SCENE3D_OP_ID_UNSAFE.sub("_", str(stem or "op")).strip("_") or "op"
|
|
35
|
+
cleaned = cleaned[: 128 - 1 - len(suffix)].rstrip("_") or "op"
|
|
36
|
+
return f"{cleaned}_{suffix}"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def fcurve_node_id(curve: Any) -> str | None:
|
|
40
|
+
"""Node id of one compact-v1 curve (``t: ["node", id]`` or ``t: id``; legacy ``nodeId``)."""
|
|
41
|
+
if not isinstance(curve, dict):
|
|
42
|
+
return None
|
|
43
|
+
target = curve.get("t")
|
|
44
|
+
if isinstance(target, list) and len(target) >= 2 and isinstance(target[1], str):
|
|
45
|
+
return target[1]
|
|
46
|
+
if isinstance(target, str):
|
|
47
|
+
return target
|
|
48
|
+
legacy = curve.get("nodeId")
|
|
49
|
+
return legacy if isinstance(legacy, str) and legacy else None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def xyz(x: float = 0, y: float = 0, z: float = 0) -> dict[str, float]:
|
|
53
|
+
return {"x": float(x), "y": float(y), "z": float(z)}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def empty_director_document() -> dict[str, Any]:
|
|
57
|
+
camera = default_camera_node("cam-main")
|
|
58
|
+
return {
|
|
59
|
+
"type": DIRECTOR_DOCUMENT_TYPE,
|
|
60
|
+
"pippitAssetId": "local_director",
|
|
61
|
+
"extra": {},
|
|
62
|
+
"content": {
|
|
63
|
+
"version": 1,
|
|
64
|
+
"aspectRatio": "16:9",
|
|
65
|
+
"activeShotCameraNodeId": camera["id"],
|
|
66
|
+
"environment": {
|
|
67
|
+
"background": {"mode": "color", "skyColor": "#87CEEB"},
|
|
68
|
+
"display": {
|
|
69
|
+
"characterLabelsVisible": True,
|
|
70
|
+
"groundVisible": True,
|
|
71
|
+
"groundHeight": 0,
|
|
72
|
+
"groundOpacity": 1,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
"asset": {"motionPath": []},
|
|
76
|
+
"nodes": [camera],
|
|
77
|
+
"physicalConstraints": [],
|
|
78
|
+
"timeline": {
|
|
79
|
+
"version": 1,
|
|
80
|
+
"fps": 24,
|
|
81
|
+
"frameStart": 0,
|
|
82
|
+
"frameEnd": 120,
|
|
83
|
+
"usePreviewRange": False,
|
|
84
|
+
"animation": {
|
|
85
|
+
"fcurves": [],
|
|
86
|
+
"cameraMotionClips": [],
|
|
87
|
+
"motionTransitions": [],
|
|
88
|
+
"motionClips": [],
|
|
89
|
+
"pathMotionClips": [],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def default_camera_node(node_id: str = "cam-main") -> dict[str, Any]:
|
|
97
|
+
return {
|
|
98
|
+
"id": node_id,
|
|
99
|
+
"type": "camera",
|
|
100
|
+
"name": "主相机",
|
|
101
|
+
"visible": True,
|
|
102
|
+
"locked": False,
|
|
103
|
+
"transform": {
|
|
104
|
+
"position": xyz(0, 1.8, 5),
|
|
105
|
+
"rotation": xyz(-8, 0, 0),
|
|
106
|
+
"scale": xyz(1, 1, 1),
|
|
107
|
+
},
|
|
108
|
+
"camera": {
|
|
109
|
+
"projection": "perspective",
|
|
110
|
+
"fov": 50,
|
|
111
|
+
"fovAxis": "vertical",
|
|
112
|
+
"near": 0.1,
|
|
113
|
+
"far": 2000,
|
|
114
|
+
"isPrimary": True,
|
|
115
|
+
"lookAt": xyz(0, 1.2, 0),
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _as_xyz(value: Any, field: str, default: dict[str, float] | None = None) -> dict[str, float]:
|
|
121
|
+
if isinstance(value, (list, tuple)) and len(value) == 3:
|
|
122
|
+
return xyz(value[0], value[1], value[2])
|
|
123
|
+
if isinstance(value, dict) and all(axis in value for axis in ("x", "y", "z")):
|
|
124
|
+
return xyz(value["x"], value["y"], value["z"])
|
|
125
|
+
if default is not None:
|
|
126
|
+
return dict(default)
|
|
127
|
+
raise ValueError(f"INVALID_DIRECTOR_TRANSFORM:{field}")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def normalize_transform(raw: Any) -> dict[str, dict[str, float]]:
|
|
131
|
+
source = raw if isinstance(raw, dict) else {}
|
|
132
|
+
return {
|
|
133
|
+
"position": _as_xyz(source.get("position"), "position", xyz()),
|
|
134
|
+
"rotation": _as_xyz(source.get("rotation"), "rotation", xyz()),
|
|
135
|
+
"scale": _as_xyz(source.get("scale"), "scale", xyz(1, 1, 1)),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _merge_metadata(node: dict[str, Any]) -> dict[str, Any]:
|
|
140
|
+
metadata = node.get("metadata") if isinstance(node.get("metadata"), dict) else {}
|
|
141
|
+
asset = node.get("asset") if isinstance(node.get("asset"), dict) else {}
|
|
142
|
+
if asset.get("file") and not metadata.get("modelUrl"):
|
|
143
|
+
metadata["modelUrl"] = asset["file"]
|
|
144
|
+
if asset.get("id") and not metadata.get("assetId"):
|
|
145
|
+
metadata["assetId"] = asset["id"]
|
|
146
|
+
return metadata
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _fill_camera(raw: Any) -> dict[str, Any]:
|
|
150
|
+
camera = copy.deepcopy(raw) if isinstance(raw, dict) else {}
|
|
151
|
+
camera["projection"] = camera.get("projection") or "perspective"
|
|
152
|
+
camera["fov"] = float(camera["fov"]) if isinstance(camera.get("fov"), (int, float)) else 50
|
|
153
|
+
camera["fovAxis"] = camera.get("fovAxis") or "vertical"
|
|
154
|
+
camera["near"] = float(camera["near"]) if isinstance(camera.get("near"), (int, float)) else 0.1
|
|
155
|
+
camera["far"] = float(camera["far"]) if isinstance(camera.get("far"), (int, float)) else 2000
|
|
156
|
+
camera["isPrimary"] = camera["isPrimary"] if isinstance(camera.get("isPrimary"), bool) else True
|
|
157
|
+
camera["lookAt"] = _as_xyz(camera.get("lookAt"), "lookAt", xyz(0, 1.2, 0))
|
|
158
|
+
return camera
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _fill_character(raw: Any) -> dict[str, Any]:
|
|
162
|
+
character = copy.deepcopy(raw) if isinstance(raw, dict) else {}
|
|
163
|
+
appearance = character.get("appearance") if isinstance(character.get("appearance"), dict) else {}
|
|
164
|
+
label = character.get("label") if isinstance(character.get("label"), dict) else {}
|
|
165
|
+
animation = character.get("animation") if isinstance(character.get("animation"), dict) else {}
|
|
166
|
+
character["placeholder"] = (
|
|
167
|
+
character["placeholder"] if isinstance(character.get("placeholder"), bool) else True
|
|
168
|
+
)
|
|
169
|
+
character["gender"] = character.get("gender") or "unknown"
|
|
170
|
+
if "motionId" not in character:
|
|
171
|
+
character["motionId"] = None
|
|
172
|
+
character["appearance"] = {**appearance, "color": appearance.get("color") or "#d7b5ee"}
|
|
173
|
+
character["label"] = {
|
|
174
|
+
**label,
|
|
175
|
+
"showLabel": label["showLabel"] if isinstance(label.get("showLabel"), bool) else True,
|
|
176
|
+
"scale": float(label["scale"]) if isinstance(label.get("scale"), (int, float)) else 1,
|
|
177
|
+
"yOffset": float(label["yOffset"]) if isinstance(label.get("yOffset"), (int, float)) else 0.04,
|
|
178
|
+
}
|
|
179
|
+
character["animation"] = {
|
|
180
|
+
**animation,
|
|
181
|
+
"mode": animation.get("mode") or "pose",
|
|
182
|
+
"controlValues": animation["controlValues"]
|
|
183
|
+
if isinstance(animation.get("controlValues"), dict) else {},
|
|
184
|
+
}
|
|
185
|
+
return character
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _fill_prop(raw: Any, category: str | None) -> dict[str, Any]:
|
|
189
|
+
prop = raw if isinstance(raw, dict) else {}
|
|
190
|
+
return {"category": prop.get("category") or category or "set"}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def normalize_node(raw: Any) -> dict[str, Any]:
|
|
194
|
+
if not isinstance(raw, dict):
|
|
195
|
+
raise ValueError("INVALID_DIRECTOR_NODE")
|
|
196
|
+
node = copy.deepcopy(raw)
|
|
197
|
+
node_id = str(node.get("id") or "").strip()
|
|
198
|
+
node_type = str(node.get("type") or "").strip()
|
|
199
|
+
if not node_id or node_type not in NODE_TYPES:
|
|
200
|
+
raise ValueError("INVALID_DIRECTOR_NODE")
|
|
201
|
+
node["id"] = node_id
|
|
202
|
+
node["type"] = node_type
|
|
203
|
+
node["name"] = str(node.get("name") or node_id)
|
|
204
|
+
node["visible"] = node["visible"] if isinstance(node.get("visible"), bool) else True
|
|
205
|
+
node["locked"] = node["locked"] if isinstance(node.get("locked"), bool) else False
|
|
206
|
+
node["transform"] = normalize_transform(node.get("transform"))
|
|
207
|
+
metadata = _merge_metadata(node)
|
|
208
|
+
if metadata:
|
|
209
|
+
node["metadata"] = metadata
|
|
210
|
+
node.pop("asset", None)
|
|
211
|
+
if node_type == "camera":
|
|
212
|
+
node["camera"] = _fill_camera(node.get("camera"))
|
|
213
|
+
elif node_type == "character":
|
|
214
|
+
node["character"] = _fill_character(node.get("character"))
|
|
215
|
+
elif node_type == "prop":
|
|
216
|
+
node["prop"] = _fill_prop(node.get("prop"), None)
|
|
217
|
+
elif node_type == "group":
|
|
218
|
+
group = node.get("group") if isinstance(node.get("group"), dict) else {}
|
|
219
|
+
appearance = group.get("appearance") if isinstance(group.get("appearance"), dict) else {}
|
|
220
|
+
node["group"] = {
|
|
221
|
+
"appearance": {"color": appearance.get("color") or "#888888"},
|
|
222
|
+
"kind": group.get("kind") or "group",
|
|
223
|
+
}
|
|
224
|
+
elif node_type == "primitive":
|
|
225
|
+
primitive = node.get("primitive") if isinstance(node.get("primitive"), dict) else {}
|
|
226
|
+
node["primitive"] = {
|
|
227
|
+
"kind": primitive.get("kind") or "cube",
|
|
228
|
+
"parameters": primitive["parameters"]
|
|
229
|
+
if isinstance(primitive.get("parameters"), dict) else {},
|
|
230
|
+
}
|
|
231
|
+
elif node_type == "path":
|
|
232
|
+
path = node.get("path") if isinstance(node.get("path"), dict) else {}
|
|
233
|
+
if not isinstance(path.get("points"), list) or not path["points"]:
|
|
234
|
+
raise ValueError("INVALID_DIRECTOR_PATH:points required")
|
|
235
|
+
node["path"] = {
|
|
236
|
+
"source": path.get("source") or "manual",
|
|
237
|
+
"curve": path.get("curve") or "catmullrom",
|
|
238
|
+
"closed": path["closed"] if isinstance(path.get("closed"), bool) else False,
|
|
239
|
+
"groundSnap": path["groundSnap"] if isinstance(path.get("groundSnap"), bool) else True,
|
|
240
|
+
"parameterization": path.get("parameterization") or "arcLength",
|
|
241
|
+
"smoothing": float(path["smoothing"]) if isinstance(path.get("smoothing"), (int, float)) else 0.5,
|
|
242
|
+
"points": path["points"],
|
|
243
|
+
}
|
|
244
|
+
return node
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def node_from_library(
|
|
248
|
+
kind: str,
|
|
249
|
+
entry: dict[str, Any],
|
|
250
|
+
*,
|
|
251
|
+
node_id: str | None = None,
|
|
252
|
+
transform: dict[str, Any] | None = None,
|
|
253
|
+
) -> dict[str, Any]:
|
|
254
|
+
library_id = str(entry.get("id") or "").strip()
|
|
255
|
+
if not library_id:
|
|
256
|
+
raise ValueError("DIRECTOR_LIBRARY_ENTRY_MISSING_ID")
|
|
257
|
+
resolved_id = str(node_id or library_id)
|
|
258
|
+
name = str(entry.get("name") or library_id)
|
|
259
|
+
file_key = entry.get("file")
|
|
260
|
+
if kind == "characters":
|
|
261
|
+
node = {
|
|
262
|
+
"id": resolved_id,
|
|
263
|
+
"type": "character",
|
|
264
|
+
"name": name,
|
|
265
|
+
"metadata": {"assetId": library_id, "modelUrl": file_key},
|
|
266
|
+
"character": _fill_character({}),
|
|
267
|
+
}
|
|
268
|
+
elif kind == "props":
|
|
269
|
+
node = {
|
|
270
|
+
"id": resolved_id,
|
|
271
|
+
"type": "prop",
|
|
272
|
+
"name": name,
|
|
273
|
+
"metadata": {"assetId": library_id, "modelUrl": file_key},
|
|
274
|
+
"prop": _fill_prop({}, entry.get("category") or "set"),
|
|
275
|
+
}
|
|
276
|
+
else:
|
|
277
|
+
raise ValueError("add_director_library_node only supports characters or props")
|
|
278
|
+
if transform:
|
|
279
|
+
node["transform"] = transform
|
|
280
|
+
return normalize_node(node)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _fill_environment(raw: Any) -> dict[str, Any]:
|
|
284
|
+
environment = raw if isinstance(raw, dict) else {}
|
|
285
|
+
background = environment.get("background") if isinstance(environment.get("background"), dict) else {}
|
|
286
|
+
display = environment.get("display") if isinstance(environment.get("display"), dict) else {}
|
|
287
|
+
filled = {
|
|
288
|
+
"background": {
|
|
289
|
+
"mode": background.get("mode") or "color",
|
|
290
|
+
"skyColor": background.get("skyColor") or "#87CEEB",
|
|
291
|
+
},
|
|
292
|
+
"display": {
|
|
293
|
+
"characterLabelsVisible": display["characterLabelsVisible"]
|
|
294
|
+
if isinstance(display.get("characterLabelsVisible"), bool) else True,
|
|
295
|
+
"groundVisible": display["groundVisible"]
|
|
296
|
+
if isinstance(display.get("groundVisible"), bool) else True,
|
|
297
|
+
"groundHeight": float(display["groundHeight"])
|
|
298
|
+
if isinstance(display.get("groundHeight"), (int, float)) else 0,
|
|
299
|
+
"groundOpacity": float(display["groundOpacity"])
|
|
300
|
+
if isinstance(display.get("groundOpacity"), (int, float)) else 1,
|
|
301
|
+
},
|
|
302
|
+
}
|
|
303
|
+
for key, value in environment.items():
|
|
304
|
+
if key not in filled:
|
|
305
|
+
filled[key] = copy.deepcopy(value)
|
|
306
|
+
return filled
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _fill_timeline(raw: Any) -> dict[str, Any]:
|
|
310
|
+
timeline = raw if isinstance(raw, dict) else {}
|
|
311
|
+
animation = timeline.get("animation") if isinstance(timeline.get("animation"), dict) else {}
|
|
312
|
+
filled = {
|
|
313
|
+
"version": timeline.get("version") if isinstance(timeline.get("version"), (int, float)) else 1,
|
|
314
|
+
"fps": timeline.get("fps") if isinstance(timeline.get("fps"), (int, float)) else 24,
|
|
315
|
+
"frameStart": timeline.get("frameStart") if isinstance(timeline.get("frameStart"), (int, float)) else 0,
|
|
316
|
+
"frameEnd": timeline.get("frameEnd") if isinstance(timeline.get("frameEnd"), (int, float)) else 120,
|
|
317
|
+
"usePreviewRange": timeline["usePreviewRange"]
|
|
318
|
+
if isinstance(timeline.get("usePreviewRange"), bool) else False,
|
|
319
|
+
"animation": {
|
|
320
|
+
"fcurves": animation.get("fcurves") if isinstance(animation.get("fcurves"), list) else [],
|
|
321
|
+
"cameraMotionClips": animation.get("cameraMotionClips")
|
|
322
|
+
if isinstance(animation.get("cameraMotionClips"), list) else [],
|
|
323
|
+
"motionTransitions": animation.get("motionTransitions")
|
|
324
|
+
if isinstance(animation.get("motionTransitions"), list) else [],
|
|
325
|
+
"motionClips": animation.get("motionClips")
|
|
326
|
+
if isinstance(animation.get("motionClips"), list) else [],
|
|
327
|
+
"pathMotionClips": animation.get("pathMotionClips")
|
|
328
|
+
if isinstance(animation.get("pathMotionClips"), list) else [],
|
|
329
|
+
},
|
|
330
|
+
}
|
|
331
|
+
for key, value in animation.items():
|
|
332
|
+
if key not in filled["animation"]:
|
|
333
|
+
filled["animation"][key] = copy.deepcopy(value)
|
|
334
|
+
return filled
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def normalize_document(raw: Any) -> dict[str, Any]:
|
|
338
|
+
if not isinstance(raw, dict):
|
|
339
|
+
raise ValueError("INVALID_DIRECTOR_DOCUMENT")
|
|
340
|
+
document = copy.deepcopy(raw)
|
|
341
|
+
document["type"] = document.get("type") or DIRECTOR_DOCUMENT_TYPE
|
|
342
|
+
if document["type"] != DIRECTOR_DOCUMENT_TYPE:
|
|
343
|
+
raise ValueError("INVALID_DIRECTOR_DOCUMENT")
|
|
344
|
+
document["pippitAssetId"] = str(document.get("pippitAssetId") or "director")
|
|
345
|
+
content = document.get("content") if isinstance(document.get("content"), dict) else {}
|
|
346
|
+
nodes = [normalize_node(node) for node in content.get("nodes") or [] if isinstance(node, dict)]
|
|
347
|
+
cameras = [node for node in nodes if node.get("type") == "camera"]
|
|
348
|
+
content["version"] = content.get("version") if isinstance(content.get("version"), (int, float)) else 1
|
|
349
|
+
content["aspectRatio"] = content.get("aspectRatio") or "16:9"
|
|
350
|
+
shot = content.get("activeShotCameraNodeId")
|
|
351
|
+
if isinstance(shot, str) and shot:
|
|
352
|
+
content["activeShotCameraNodeId"] = shot
|
|
353
|
+
elif cameras:
|
|
354
|
+
content["activeShotCameraNodeId"] = cameras[0]["id"]
|
|
355
|
+
else:
|
|
356
|
+
content.pop("activeShotCameraNodeId", None)
|
|
357
|
+
content["environment"] = _fill_environment(content.get("environment"))
|
|
358
|
+
content["asset"] = content.get("asset") if isinstance(content.get("asset"), dict) else {"motionPath": []}
|
|
359
|
+
content["asset"].setdefault("motionPath", [])
|
|
360
|
+
content["nodes"] = nodes
|
|
361
|
+
content["physicalConstraints"] = (
|
|
362
|
+
content.get("physicalConstraints")
|
|
363
|
+
if isinstance(content.get("physicalConstraints"), list) else []
|
|
364
|
+
)
|
|
365
|
+
content["timeline"] = _fill_timeline(content.get("timeline"))
|
|
366
|
+
document["content"] = content
|
|
367
|
+
return document
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def validate_director_node(node: dict[str, Any]) -> dict[str, Any]:
|
|
371
|
+
document = empty_director_document()
|
|
372
|
+
document["content"]["nodes"] = [node]
|
|
373
|
+
document["content"]["activeShotCameraNodeId"] = (
|
|
374
|
+
node["id"] if node.get("type") == "camera"
|
|
375
|
+
else document["content"]["activeShotCameraNodeId"]
|
|
376
|
+
)
|
|
377
|
+
validate_director_document(document)
|
|
378
|
+
return node
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def validate_director_document(document: dict[str, Any]) -> dict[str, Any]:
|
|
382
|
+
errors = sorted(_validator().iter_errors(document), key=lambda item: list(item.path))
|
|
383
|
+
if errors:
|
|
384
|
+
first = errors[0]
|
|
385
|
+
path = ".".join(str(part) for part in first.path) or "<root>"
|
|
386
|
+
raise ValueError(f"INVALID_DIRECTOR_DOCUMENT:{path}: {first.message}")
|
|
387
|
+
return document
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def validate_fcurves(fcurves: dict[str, Any]) -> dict[str, Any]:
|
|
391
|
+
global _FCURVES_VALIDATOR
|
|
392
|
+
if _FCURVES_VALIDATOR is None:
|
|
393
|
+
_FCURVES_VALIDATOR = Draft7Validator(json.loads(FCURVES_SCHEMA_PATH.read_text(encoding="utf-8")))
|
|
394
|
+
errors = sorted(_FCURVES_VALIDATOR.iter_errors(fcurves), key=lambda item: list(item.path))
|
|
395
|
+
if errors:
|
|
396
|
+
first = errors[0]
|
|
397
|
+
path = ".".join(str(part) for part in first.path) or "<root>"
|
|
398
|
+
raise ValueError(f"INVALID_DIRECTOR_FCURVES:{path}: {first.message}")
|
|
399
|
+
return fcurves
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
_SCENE_ROOT_KEYS = {"type", "pippitAssetId", "extra", "content", "nodes"}
|
|
403
|
+
_CLIP_ARRAYS = ("cameraMotionClips", "motionClips", "pathMotionClips")
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _as_document(document: Any) -> dict[str, Any] | None:
|
|
407
|
+
if not isinstance(document, dict):
|
|
408
|
+
return None
|
|
409
|
+
if "content" in document or document.get("type") == DIRECTOR_DOCUMENT_TYPE:
|
|
410
|
+
return document
|
|
411
|
+
inner = document.get("document")
|
|
412
|
+
return inner if isinstance(inner, dict) else document
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _index_nodes(document: Any) -> dict[str, dict[str, Any]]:
|
|
416
|
+
resolved = _as_document(document) or {}
|
|
417
|
+
content = resolved.get("content") if isinstance(resolved, dict) else {}
|
|
418
|
+
nodes = content.get("nodes") if isinstance(content, dict) else []
|
|
419
|
+
return {
|
|
420
|
+
str(node["id"]): node
|
|
421
|
+
for node in nodes or []
|
|
422
|
+
if isinstance(node, dict) and node.get("id")
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def preserve_node_metadata(node: dict[str, Any], existing: dict[str, Any] | None) -> dict[str, Any]:
|
|
427
|
+
if not existing:
|
|
428
|
+
return node
|
|
429
|
+
incoming_meta = node.get("metadata") if isinstance(node.get("metadata"), dict) else {}
|
|
430
|
+
existing_meta = existing.get("metadata") if isinstance(existing.get("metadata"), dict) else {}
|
|
431
|
+
merged = dict(existing_meta)
|
|
432
|
+
merged.update(incoming_meta)
|
|
433
|
+
if existing_meta.get("modelUrl") and not incoming_meta.get("modelUrl"):
|
|
434
|
+
merged["modelUrl"] = existing_meta["modelUrl"]
|
|
435
|
+
if existing_meta.get("assetId") and not incoming_meta.get("assetId"):
|
|
436
|
+
merged["assetId"] = existing_meta["assetId"]
|
|
437
|
+
if merged:
|
|
438
|
+
node["metadata"] = merged
|
|
439
|
+
return node
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def scene_content_from_payload(scene: Any) -> dict[str, Any]:
|
|
443
|
+
if not isinstance(scene, dict):
|
|
444
|
+
raise ValueError("INVALID_DIRECTOR_SCENE")
|
|
445
|
+
content = scene.get("content") if isinstance(scene.get("content"), dict) else None
|
|
446
|
+
if "nodes" in scene or (content is not None and "nodes" in content):
|
|
447
|
+
raise ValueError("INVALID_DIRECTOR_SCENE:content cannot include nodes")
|
|
448
|
+
animation = {}
|
|
449
|
+
if content is not None:
|
|
450
|
+
timeline = content.get("timeline") if isinstance(content.get("timeline"), dict) else {}
|
|
451
|
+
animation = timeline.get("animation") if isinstance(timeline.get("animation"), dict) else {}
|
|
452
|
+
if any(key in animation for key in _CLIP_ARRAYS):
|
|
453
|
+
raise ValueError("INVALID_DIRECTOR_SCENE:content cannot include clips")
|
|
454
|
+
if content is not None:
|
|
455
|
+
return copy.deepcopy(content)
|
|
456
|
+
extracted = {
|
|
457
|
+
key: copy.deepcopy(value)
|
|
458
|
+
for key, value in scene.items()
|
|
459
|
+
if key not in _SCENE_ROOT_KEYS
|
|
460
|
+
}
|
|
461
|
+
return extracted
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def apply_scene_set(document: dict[str, Any], scene: Any) -> dict[str, Any]:
|
|
465
|
+
incoming = scene_content_from_payload(scene)
|
|
466
|
+
current = copy.deepcopy(document) if isinstance(document, dict) else empty_director_document()
|
|
467
|
+
content = current.setdefault("content", {})
|
|
468
|
+
kept_nodes = copy.deepcopy(content.get("nodes") or [])
|
|
469
|
+
kept_timeline = copy.deepcopy(content.get("timeline") or {})
|
|
470
|
+
kept_animation = (kept_timeline.get("animation") if isinstance(kept_timeline, dict) else {}) or {}
|
|
471
|
+
for key, value in incoming.items():
|
|
472
|
+
if key == "timeline" and isinstance(value, dict):
|
|
473
|
+
merged_timeline = dict(kept_timeline)
|
|
474
|
+
incoming_animation = value.get("animation") if isinstance(value.get("animation"), dict) else {}
|
|
475
|
+
merged_animation = dict(kept_animation)
|
|
476
|
+
for anim_key, anim_value in incoming_animation.items():
|
|
477
|
+
if anim_key not in _CLIP_ARRAYS:
|
|
478
|
+
merged_animation[anim_key] = copy.deepcopy(anim_value)
|
|
479
|
+
merged_timeline.update({
|
|
480
|
+
item_key: copy.deepcopy(item_value)
|
|
481
|
+
for item_key, item_value in value.items()
|
|
482
|
+
if item_key != "animation"
|
|
483
|
+
})
|
|
484
|
+
merged_timeline["animation"] = merged_animation
|
|
485
|
+
content["timeline"] = merged_timeline
|
|
486
|
+
else:
|
|
487
|
+
content[key] = copy.deepcopy(value)
|
|
488
|
+
content["nodes"] = kept_nodes
|
|
489
|
+
if isinstance(scene, dict):
|
|
490
|
+
if scene.get("type"):
|
|
491
|
+
current["type"] = scene["type"]
|
|
492
|
+
if "pippitAssetId" in scene:
|
|
493
|
+
current["pippitAssetId"] = scene["pippitAssetId"]
|
|
494
|
+
if "extra" in scene:
|
|
495
|
+
current["extra"] = copy.deepcopy(scene["extra"])
|
|
496
|
+
current["content"] = content
|
|
497
|
+
return normalize_document(current)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def camera_clip_has_curves(clip: Any) -> bool:
|
|
501
|
+
if not isinstance(clip, dict):
|
|
502
|
+
return False
|
|
503
|
+
motion = clip.get("motion") if isinstance(clip.get("motion"), dict) else {}
|
|
504
|
+
curves = motion.get("curves")
|
|
505
|
+
return isinstance(curves, list) and bool(curves)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def stored_evaluate_body(
|
|
509
|
+
snapshot: Any,
|
|
510
|
+
frames: Any,
|
|
511
|
+
*,
|
|
512
|
+
width: int | None = None,
|
|
513
|
+
height: int | None = None,
|
|
514
|
+
) -> dict[str, Any]:
|
|
515
|
+
if not isinstance(snapshot, dict) or not isinstance(snapshot.get("document"), dict):
|
|
516
|
+
raise ValueError("DIRECTOR_DOCUMENT_MISSING")
|
|
517
|
+
if not isinstance(frames, list) or not frames:
|
|
518
|
+
raise ValueError("INVALID_DIRECTOR_FRAMES")
|
|
519
|
+
document = prepare_put_document(snapshot["document"], current=snapshot["document"])
|
|
520
|
+
body: dict[str, Any] = {"document": document, "frames": list(frames)}
|
|
521
|
+
fcurves = snapshot.get("fcurves")
|
|
522
|
+
if isinstance(fcurves, dict) and fcurves.get("encoding") == "compact-v1":
|
|
523
|
+
body["fcurves"] = copy.deepcopy(fcurves)
|
|
524
|
+
if width is not None:
|
|
525
|
+
body["width"] = width
|
|
526
|
+
if height is not None:
|
|
527
|
+
body["height"] = height
|
|
528
|
+
return body
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _index_camera_clips(document: Any) -> dict[str, dict[str, Any]]:
|
|
532
|
+
resolved = _as_document(document) or {}
|
|
533
|
+
content = resolved.get("content") if isinstance(resolved, dict) else {}
|
|
534
|
+
timeline = content.get("timeline") if isinstance(content, dict) else {}
|
|
535
|
+
animation = timeline.get("animation") if isinstance(timeline, dict) else {}
|
|
536
|
+
clips = animation.get("cameraMotionClips") if isinstance(animation, dict) else []
|
|
537
|
+
return {
|
|
538
|
+
str(clip["id"]): clip
|
|
539
|
+
for clip in clips or []
|
|
540
|
+
if isinstance(clip, dict) and clip.get("id")
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def _preserve_camera_motion_clips(document: dict[str, Any], current: Any) -> None:
|
|
545
|
+
existing = _index_camera_clips(current)
|
|
546
|
+
animation = document["content"].setdefault("timeline", {}).setdefault("animation", {})
|
|
547
|
+
kept: list[dict[str, Any]] = []
|
|
548
|
+
for clip in animation.get("cameraMotionClips") or []:
|
|
549
|
+
if not isinstance(clip, dict):
|
|
550
|
+
continue
|
|
551
|
+
if camera_clip_has_curves(clip):
|
|
552
|
+
kept.append(clip)
|
|
553
|
+
elif existing.get(str(clip.get("id") or "")) and camera_clip_has_curves(existing[str(clip["id"])]):
|
|
554
|
+
kept.append(copy.deepcopy(existing[str(clip["id"])]))
|
|
555
|
+
animation["cameraMotionClips"] = kept
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def prepare_put_document(document: Any, current: Any = None) -> dict[str, Any]:
|
|
559
|
+
normalized = normalize_document(document)
|
|
560
|
+
existing = _index_nodes(current)
|
|
561
|
+
incoming_ids = {node["id"] for node in normalized["content"]["nodes"]}
|
|
562
|
+
for node in normalized["content"]["nodes"]:
|
|
563
|
+
preserve_node_metadata(node, existing.get(node["id"]))
|
|
564
|
+
if not any(node.get("type") == "camera" for node in normalized["content"]["nodes"]):
|
|
565
|
+
for node_id, node in existing.items():
|
|
566
|
+
if node.get("type") == "camera" and node_id not in incoming_ids:
|
|
567
|
+
normalized["content"]["nodes"].insert(0, copy.deepcopy(node))
|
|
568
|
+
if not normalized["content"].get("activeShotCameraNodeId"):
|
|
569
|
+
current_doc = _as_document(current) or {}
|
|
570
|
+
current_shot = (current_doc.get("content") or {}).get("activeShotCameraNodeId")
|
|
571
|
+
cameras = [node for node in normalized["content"]["nodes"] if node.get("type") == "camera"]
|
|
572
|
+
normalized["content"]["activeShotCameraNodeId"] = (
|
|
573
|
+
current_shot if isinstance(current_shot, str) and current_shot
|
|
574
|
+
else (cameras[0]["id"] if cameras else "cam-main")
|
|
575
|
+
)
|
|
576
|
+
_preserve_camera_motion_clips(normalized, current)
|
|
577
|
+
return validate_director_document(normalized)
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def summarize_editorial(document: Any) -> dict[str, Any]:
|
|
581
|
+
resolved = _as_document(document) or {}
|
|
582
|
+
content = resolved.get("content") if isinstance(resolved, dict) else {}
|
|
583
|
+
editorial = content.get("editorial") if isinstance(content, dict) else None
|
|
584
|
+
if not isinstance(editorial, dict) or not isinstance(editorial.get("sequences"), list):
|
|
585
|
+
editorial = {
|
|
586
|
+
"version": 1,
|
|
587
|
+
"activeSequenceId": "sequence_1",
|
|
588
|
+
"sequences": [{"id": "sequence_1", "clips": []}],
|
|
589
|
+
}
|
|
590
|
+
sequences = []
|
|
591
|
+
for sequence in editorial.get("sequences") or []:
|
|
592
|
+
if not isinstance(sequence, dict):
|
|
593
|
+
continue
|
|
594
|
+
clips = []
|
|
595
|
+
duration = 0
|
|
596
|
+
for clip in sequence.get("clips") or []:
|
|
597
|
+
if not isinstance(clip, dict):
|
|
598
|
+
continue
|
|
599
|
+
start = clip.get("sourceFrameStart")
|
|
600
|
+
end = clip.get("sourceFrameEnd")
|
|
601
|
+
if isinstance(start, int) and isinstance(end, int) and end >= start:
|
|
602
|
+
duration += end - start + 1
|
|
603
|
+
clips.append({
|
|
604
|
+
"id": clip.get("id"),
|
|
605
|
+
"cameraNodeId": clip.get("cameraNodeId"),
|
|
606
|
+
"sourceFrameStart": start,
|
|
607
|
+
"sourceFrameEnd": end,
|
|
608
|
+
})
|
|
609
|
+
item = {"id": sequence.get("id"), "durationFrames": duration, "clips": clips}
|
|
610
|
+
if sequence.get("name"):
|
|
611
|
+
item["name"] = sequence["name"]
|
|
612
|
+
sequences.append(item)
|
|
613
|
+
return {
|
|
614
|
+
"version": editorial.get("version") or 1,
|
|
615
|
+
"activeSequenceId": editorial.get("activeSequenceId") or (
|
|
616
|
+
sequences[0]["id"] if sequences else "sequence_1"
|
|
617
|
+
),
|
|
618
|
+
"sequences": sequences,
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def summarize_director_snapshot(snapshot: Any) -> dict[str, Any]:
|
|
623
|
+
if not isinstance(snapshot, dict):
|
|
624
|
+
raise ValueError("DIRECTOR_DOCUMENT_MISSING")
|
|
625
|
+
document = snapshot.get("document") if isinstance(snapshot.get("document"), dict) else snapshot
|
|
626
|
+
content = document.get("content") if isinstance(document, dict) else {}
|
|
627
|
+
nodes = content.get("nodes") if isinstance(content, dict) else []
|
|
628
|
+
outlined = []
|
|
629
|
+
cameras = []
|
|
630
|
+
for node in nodes or []:
|
|
631
|
+
if not isinstance(node, dict):
|
|
632
|
+
continue
|
|
633
|
+
metadata = node.get("metadata") if isinstance(node.get("metadata"), dict) else {}
|
|
634
|
+
outlined.append({
|
|
635
|
+
"id": node.get("id"),
|
|
636
|
+
"type": node.get("type"),
|
|
637
|
+
"name": node.get("name"),
|
|
638
|
+
"visible": node.get("visible"),
|
|
639
|
+
"locked": node.get("locked"),
|
|
640
|
+
"assetId": metadata.get("assetId"),
|
|
641
|
+
})
|
|
642
|
+
if node.get("type") == "camera" and node.get("id"):
|
|
643
|
+
cameras.append(node["id"])
|
|
644
|
+
timeline = content.get("timeline") if isinstance(content.get("timeline"), dict) else {}
|
|
645
|
+
animation = timeline.get("animation") if isinstance(timeline.get("animation"), dict) else {}
|
|
646
|
+
clips = []
|
|
647
|
+
for clip_kind, key in (
|
|
648
|
+
("cameraMotion", "cameraMotionClips"),
|
|
649
|
+
("motion", "motionClips"),
|
|
650
|
+
("pathMotion", "pathMotionClips"),
|
|
651
|
+
):
|
|
652
|
+
for clip in animation.get(key) or []:
|
|
653
|
+
if not isinstance(clip, dict):
|
|
654
|
+
continue
|
|
655
|
+
target = clip.get("target") if isinstance(clip.get("target"), dict) else {}
|
|
656
|
+
clips.append({
|
|
657
|
+
"id": clip.get("id"),
|
|
658
|
+
"clipKind": clip_kind,
|
|
659
|
+
"nodeId": target.get("nodeId"),
|
|
660
|
+
"frameStart": clip.get("frameStart"),
|
|
661
|
+
"frameEnd": clip.get("frameEnd"),
|
|
662
|
+
})
|
|
663
|
+
fcurves = snapshot.get("fcurves") if isinstance(snapshot.get("fcurves"), dict) else {}
|
|
664
|
+
fcurve_ids = []
|
|
665
|
+
for item in fcurves.get("fcurves") or []:
|
|
666
|
+
node_id = fcurve_node_id(item)
|
|
667
|
+
if node_id:
|
|
668
|
+
entity_id = "fcurves__" + node_id
|
|
669
|
+
if entity_id not in fcurve_ids:
|
|
670
|
+
fcurve_ids.append(entity_id)
|
|
671
|
+
return {
|
|
672
|
+
"suggestedMode": "new" if len(cameras) == 1 and len(outlined) == 1 else "edit",
|
|
673
|
+
"activeCameraNodeId": content.get("activeShotCameraNodeId"),
|
|
674
|
+
"sceneSequence": snapshot.get("sceneSequence"),
|
|
675
|
+
"entityVersions": snapshot.get("entityVersions") or {},
|
|
676
|
+
"timeline": {
|
|
677
|
+
"fps": timeline.get("fps"),
|
|
678
|
+
"frameStart": timeline.get("frameStart"),
|
|
679
|
+
"frameEnd": timeline.get("frameEnd"),
|
|
680
|
+
},
|
|
681
|
+
"nodes": outlined,
|
|
682
|
+
"cameras": cameras,
|
|
683
|
+
"clips": clips,
|
|
684
|
+
"fcurvesNodeIds": fcurve_ids,
|
|
685
|
+
"editorial": summarize_editorial(document),
|
|
686
|
+
"message": (
|
|
687
|
+
"Outline only: curves and full nodes omitted. "
|
|
688
|
+
"Use get_director_entity for one node/clip. "
|
|
689
|
+
"Do not hand-edit this outline."
|
|
690
|
+
),
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def get_director_entity(snapshot: Any, entity_type: str, entity_id: str, *, include_curves: bool = False) -> dict[str, Any]:
|
|
695
|
+
if not isinstance(snapshot, dict):
|
|
696
|
+
raise ValueError("DIRECTOR_DOCUMENT_MISSING")
|
|
697
|
+
document = snapshot.get("document") if isinstance(snapshot.get("document"), dict) else {}
|
|
698
|
+
content = document.get("content") if isinstance(document, dict) else {}
|
|
699
|
+
if entity_type == "node":
|
|
700
|
+
for node in content.get("nodes") or []:
|
|
701
|
+
if isinstance(node, dict) and node.get("id") == entity_id:
|
|
702
|
+
return {"entityType": "node", "entity": copy.deepcopy(node)}
|
|
703
|
+
raise ValueError(f"DIRECTOR_ENTITY_NOT_FOUND:node:{entity_id}")
|
|
704
|
+
if entity_type == "clip":
|
|
705
|
+
animation = ((content.get("timeline") or {}).get("animation")
|
|
706
|
+
if isinstance(content.get("timeline"), dict) else {})
|
|
707
|
+
for key in ("cameraMotionClips", "motionClips", "pathMotionClips"):
|
|
708
|
+
for clip in (animation or {}).get(key) or []:
|
|
709
|
+
if not isinstance(clip, dict) or clip.get("id") != entity_id:
|
|
710
|
+
continue
|
|
711
|
+
body = copy.deepcopy(clip)
|
|
712
|
+
if not include_curves:
|
|
713
|
+
motion = body.get("motion") if isinstance(body.get("motion"), dict) else None
|
|
714
|
+
if motion and "curves" in motion:
|
|
715
|
+
motion = dict(motion)
|
|
716
|
+
motion.pop("curves", None)
|
|
717
|
+
motion["curvesOmitted"] = True
|
|
718
|
+
body["motion"] = motion
|
|
719
|
+
return {"entityType": "clip", "entity": body, "includeCurves": include_curves}
|
|
720
|
+
raise ValueError(f"DIRECTOR_ENTITY_NOT_FOUND:clip:{entity_id}")
|
|
721
|
+
if entity_type == "fcurves":
|
|
722
|
+
node_id = entity_id.removeprefix("fcurves__") if entity_id.startswith("fcurves__") else entity_id
|
|
723
|
+
matched = []
|
|
724
|
+
fcurves = snapshot.get("fcurves") if isinstance(snapshot.get("fcurves"), dict) else {}
|
|
725
|
+
for item in fcurves.get("fcurves") or []:
|
|
726
|
+
if fcurve_node_id(item) == node_id:
|
|
727
|
+
matched.append(copy.deepcopy(item) if include_curves else {
|
|
728
|
+
"nodeId": node_id,
|
|
729
|
+
"channel": item.get("p", item.get("channel")),
|
|
730
|
+
"index": item.get("i"),
|
|
731
|
+
"keyCount": len(item.get("k") or item.get("keys") or []),
|
|
732
|
+
})
|
|
733
|
+
if not matched:
|
|
734
|
+
raise ValueError(f"DIRECTOR_ENTITY_NOT_FOUND:fcurves:{entity_id}")
|
|
735
|
+
return {"entityType": "fcurves", "entityId": f"fcurves__{node_id}", "entity": matched}
|
|
736
|
+
raise ValueError("INVALID_DIRECTOR_ENTITY_TYPE")
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
_EVAL_POSE_KEYS = frozenset({
|
|
740
|
+
"camera", "lookAt", "position", "rotation", "scale", "transform",
|
|
741
|
+
})
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def summarize_evaluate_frames(result: Any) -> dict[str, Any]:
|
|
745
|
+
if not isinstance(result, dict):
|
|
746
|
+
raise ValueError("DIRECTOR_EVALUATE_RESULT_INVALID")
|
|
747
|
+
summary: dict[str, Any] = {}
|
|
748
|
+
if "ok" in result:
|
|
749
|
+
summary["ok"] = result["ok"]
|
|
750
|
+
if "issues" in result:
|
|
751
|
+
summary["issues"] = copy.deepcopy(result["issues"])
|
|
752
|
+
frames: list[dict[str, Any]] = []
|
|
753
|
+
for item in result.get("frames") or []:
|
|
754
|
+
if not isinstance(item, dict):
|
|
755
|
+
continue
|
|
756
|
+
frame: dict[str, Any] = {}
|
|
757
|
+
if "frame" in item:
|
|
758
|
+
frame["frame"] = item["frame"]
|
|
759
|
+
for key, value in item.items():
|
|
760
|
+
if key == "frame" or key in _EVAL_POSE_KEYS:
|
|
761
|
+
continue
|
|
762
|
+
if isinstance(value, bool) or value is None:
|
|
763
|
+
frame[key] = value
|
|
764
|
+
elif isinstance(value, str) and len(value) <= 120:
|
|
765
|
+
frame[key] = value
|
|
766
|
+
elif isinstance(value, (int, float)) and not isinstance(value, bool):
|
|
767
|
+
frame[key] = value
|
|
768
|
+
elif isinstance(value, dict):
|
|
769
|
+
flags = {
|
|
770
|
+
flag_key: flag_value
|
|
771
|
+
for flag_key, flag_value in value.items()
|
|
772
|
+
if flag_key not in _EVAL_POSE_KEYS
|
|
773
|
+
and (
|
|
774
|
+
isinstance(flag_value, bool)
|
|
775
|
+
or (isinstance(flag_value, str) and len(flag_value) <= 80)
|
|
776
|
+
)
|
|
777
|
+
}
|
|
778
|
+
if flags:
|
|
779
|
+
frame[key] = flags
|
|
780
|
+
if isinstance(item.get("nodes"), list):
|
|
781
|
+
frame["nodes"] = [{k: copy.deepcopy(v) for k, v in node.items()
|
|
782
|
+
if k in {"nodeId", "type", "inFrustum", "cameraDistance", "groundPenetration", "finite", "positiveScale", "visible", "animated"}}
|
|
783
|
+
for node in item["nodes"] if isinstance(node, dict)]
|
|
784
|
+
frames.append(frame)
|
|
785
|
+
summary["frames"] = frames
|
|
786
|
+
for key, value in result.items():
|
|
787
|
+
if key in summary or key == "frames":
|
|
788
|
+
continue
|
|
789
|
+
if isinstance(value, (bool, int, float, str)) or value is None:
|
|
790
|
+
summary[key] = value
|
|
791
|
+
return summary
|
|
792
|
+
|
|
793
|
+
|