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,26 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
5
|
+
|
|
6
|
+
// TOPVIEW3D_NODE_PREFIX is an npm prefix (it holds node_modules/playwright), used when the
|
|
7
|
+
// renderer ships inside the Python package; otherwise playwright resolves from here.
|
|
8
|
+
export function playwrightDir() {
|
|
9
|
+
const prefix = process.env.TOPVIEW3D_NODE_PREFIX
|
|
10
|
+
try {
|
|
11
|
+
if (prefix) {
|
|
12
|
+
const require = createRequire(path.join(path.resolve(prefix), 'package.json'))
|
|
13
|
+
return path.dirname(require.resolve('playwright/package.json'))
|
|
14
|
+
}
|
|
15
|
+
return path.dirname(fileURLToPath(import.meta.resolve('playwright/package.json')))
|
|
16
|
+
} catch {
|
|
17
|
+
throw new Error('PLAYWRIGHT_NOT_INSTALLED')
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function loadPlaywright() {
|
|
22
|
+
const dir = playwrightDir()
|
|
23
|
+
const manifest = JSON.parse(readFileSync(path.join(dir, 'package.json'), 'utf8'))
|
|
24
|
+
const entry = manifest.exports?.['.']?.import ?? manifest.main ?? 'index.js'
|
|
25
|
+
return import(pathToFileURL(path.join(dir, entry)).href)
|
|
26
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { createReadStream } from 'node:fs'
|
|
3
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
|
4
|
+
import http from 'node:http'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
7
|
+
import { loadPlaywright } from './playwright.mjs'
|
|
8
|
+
|
|
9
|
+
const ROOT = path.dirname(fileURLToPath(import.meta.url))
|
|
10
|
+
const RENDERS_DIR_NAME = 'renders'
|
|
11
|
+
const RUN_ID = /^[A-Za-z0-9_-]{1,128}$/
|
|
12
|
+
const THREE_DRACO = path.join(ROOT, 'node_modules/three/examples/jsm/libs/draco')
|
|
13
|
+
const MIME = {
|
|
14
|
+
'.html': 'text/html; charset=utf-8',
|
|
15
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
16
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
17
|
+
'.css': 'text/css; charset=utf-8',
|
|
18
|
+
'.json': 'application/json',
|
|
19
|
+
'.wasm': 'application/wasm',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveServedFile(relative, localFiles) {
|
|
23
|
+
const local = /^local-assets\/(\d+)\/[^/]+$/.exec(relative)
|
|
24
|
+
if (local) return localFiles[Number(local[1])] ?? null
|
|
25
|
+
if (relative === 'draco' || relative.startsWith('draco/')) {
|
|
26
|
+
const rest = relative === 'draco' ? '' : relative.slice('draco/'.length)
|
|
27
|
+
const file = path.normalize(path.join(THREE_DRACO, rest))
|
|
28
|
+
return file.startsWith(THREE_DRACO) ? file : null
|
|
29
|
+
}
|
|
30
|
+
const file = path.normalize(path.join(ROOT, relative))
|
|
31
|
+
return file.startsWith(ROOT) ? file : null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function serveRoot(localFiles = []) {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
const server = http.createServer((req, res) => {
|
|
37
|
+
const url = new URL(req.url || '/', 'http://127.0.0.1')
|
|
38
|
+
const relative = decodeURIComponent(url.pathname.replace(/^\/+/, '')) || 'static/headless.html'
|
|
39
|
+
const file = resolveServedFile(relative, localFiles)
|
|
40
|
+
if (!file) {
|
|
41
|
+
res.writeHead(403)
|
|
42
|
+
res.end()
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
const stream = createReadStream(file)
|
|
46
|
+
stream.on('error', () => {
|
|
47
|
+
res.writeHead(404)
|
|
48
|
+
res.end()
|
|
49
|
+
})
|
|
50
|
+
stream.on('open', () => {
|
|
51
|
+
res.writeHead(200, { 'Content-Type': MIME[path.extname(file)] || 'application/octet-stream' })
|
|
52
|
+
stream.pipe(res)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
server.listen(0, '127.0.0.1', () => {
|
|
56
|
+
const { port } = server.address()
|
|
57
|
+
resolve({ server, origin: `http://127.0.0.1:${port}` })
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
// Requests may only reach the local static server and explicitly configured asset
|
|
64
|
+
// origins; anything else is aborted and reported, so offline renders stay offline.
|
|
65
|
+
export function assetOrigins(body) {
|
|
66
|
+
const base = publicAssetBase(body)
|
|
67
|
+
if (!base) return []
|
|
68
|
+
try { return [new URL(base).origin] } catch { return [] }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// body.localAssets maps asset keys to absolute files; only those files are served.
|
|
72
|
+
export function localAssetFiles(body) {
|
|
73
|
+
const entries = Object.entries(isPlainObject(body?.localAssets) ? body.localAssets : {})
|
|
74
|
+
for (const [key, file] of entries) {
|
|
75
|
+
if (typeof file !== 'string' || !path.isAbsolute(file)) throw new Error(`LOCAL_ASSET_PATH_INVALID:${key}`)
|
|
76
|
+
}
|
|
77
|
+
return entries
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function localAssetUrls(entries, origin) {
|
|
81
|
+
return Object.fromEntries(entries.map(([key, file], index) =>
|
|
82
|
+
[key, `${origin}/local-assets/${index}/${encodeURIComponent(path.basename(file))}`]))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function withPage(work, { allowedOrigins = [], localAssets = [] } = {}) {
|
|
86
|
+
const playwright = await loadPlaywright()
|
|
87
|
+
const { server, origin } = await serveRoot(localAssets.map(([, file]) => path.resolve(file)))
|
|
88
|
+
const network = { blocked: [] }
|
|
89
|
+
let browser
|
|
90
|
+
try {
|
|
91
|
+
browser = await playwright.chromium.launch({
|
|
92
|
+
args: [
|
|
93
|
+
'--use-angle=swiftshader',
|
|
94
|
+
'--enable-unsafe-swiftshader',
|
|
95
|
+
'--ignore-gpu-blocklist',
|
|
96
|
+
'--disable-dev-shm-usage',
|
|
97
|
+
'--disable-web-security',
|
|
98
|
+
],
|
|
99
|
+
chromiumSandbox: false,
|
|
100
|
+
})
|
|
101
|
+
const page = await browser.newPage()
|
|
102
|
+
const allowed = new Set([origin, ...allowedOrigins])
|
|
103
|
+
await page.route('**/*', (route) => {
|
|
104
|
+
const url = route.request().url()
|
|
105
|
+
let requestOrigin = 'null'
|
|
106
|
+
try { requestOrigin = new URL(url).origin } catch { /* data:, blob: */ }
|
|
107
|
+
if (allowed.has(requestOrigin) || /^(data|blob):/i.test(url)) return route.continue()
|
|
108
|
+
if (network.blocked.length < 50) {
|
|
109
|
+
try { const parsed = new URL(url); network.blocked.push(parsed.origin + parsed.pathname) }
|
|
110
|
+
catch { network.blocked.push('[unparsable URL]') }
|
|
111
|
+
}
|
|
112
|
+
return route.abort('blockedbyclient')
|
|
113
|
+
})
|
|
114
|
+
const warnings = []
|
|
115
|
+
page.on('console', (message) => {
|
|
116
|
+
if (!['warning', 'error'].includes(message.type()) || warnings.length >= 20) return
|
|
117
|
+
// Preserve asset-load diagnostics without returning signed URL credentials.
|
|
118
|
+
warnings.push(message.text().replace(/https?:\/\/[^\s]+/g, (value) => {
|
|
119
|
+
try { const url = new URL(value); return url.origin + url.pathname }
|
|
120
|
+
catch { return '[asset URL]' }
|
|
121
|
+
}))
|
|
122
|
+
})
|
|
123
|
+
page.on('pageerror', (error) => {
|
|
124
|
+
process.stderr.write(`headless pageerror: ${error.message}\n${error.stack || ''}\n`)
|
|
125
|
+
})
|
|
126
|
+
await page.goto(`${origin}/static/headless.html`, { waitUntil: 'load' })
|
|
127
|
+
await page.waitForFunction(() => window.__directorReady === true, null, { timeout: 30_000 })
|
|
128
|
+
return await work(page, origin, warnings, network, localAssetUrls(localAssets, origin))
|
|
129
|
+
} finally {
|
|
130
|
+
if (browser) await browser.close()
|
|
131
|
+
await new Promise((resolve) => server.close(resolve))
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function digest(buffer) {
|
|
136
|
+
return createHash('sha256').update(buffer).digest('hex')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function publicAssetBase(body) {
|
|
140
|
+
return body.publicAssetBase
|
|
141
|
+
|| process.env.TOPVIEW3D_DIRECTOR_PUBLIC_ASSET_BASE
|
|
142
|
+
|| ''
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// outputDir is `<absolute>/renders/<runId>`: the caller chooses the root (a project's
|
|
146
|
+
// .topview3d/renders), the renderer writes exactly one run directory below it.
|
|
147
|
+
export function resolveOutputDir(outputDir, pathApi = path) {
|
|
148
|
+
if (typeof outputDir !== 'string' || !outputDir.trim()) {
|
|
149
|
+
throw new Error('RENDER_OUTPUT_DIR_REQUIRED')
|
|
150
|
+
}
|
|
151
|
+
if (!pathApi.isAbsolute(outputDir)) throw new Error('RENDER_OUTPUT_DIR_INVALID')
|
|
152
|
+
if (outputDir.split(/[\\/]+/).some((segment) => segment === '..' || segment === '.')) {
|
|
153
|
+
throw new Error('RENDER_OUTPUT_DIR_INVALID')
|
|
154
|
+
}
|
|
155
|
+
const resolved = pathApi.resolve(outputDir)
|
|
156
|
+
const runId = pathApi.basename(resolved)
|
|
157
|
+
const parent = pathApi.dirname(resolved)
|
|
158
|
+
if (!RUN_ID.test(runId) || pathApi.basename(parent) !== RENDERS_DIR_NAME || parent === resolved) {
|
|
159
|
+
throw new Error('RENDER_OUTPUT_DIR_INVALID')
|
|
160
|
+
}
|
|
161
|
+
return resolved
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function resolveRenderSize(body) {
|
|
165
|
+
const requestedWidth = body.width ?? 640
|
|
166
|
+
const requestedHeight = body.height ?? 360
|
|
167
|
+
if (![requestedWidth, requestedHeight].every((value) => Number.isInteger(value) && value >= 64)) {
|
|
168
|
+
throw new Error('render dimensions must be integers >=64')
|
|
169
|
+
}
|
|
170
|
+
const ratio = Math.min(1, 1024 / Math.max(requestedWidth, requestedHeight))
|
|
171
|
+
return {
|
|
172
|
+
width: Math.max(1, Math.round(requestedWidth * ratio)),
|
|
173
|
+
height: Math.max(1, Math.round(requestedHeight * ratio)),
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function renderFrames(body) {
|
|
178
|
+
const frames = body.frames ?? [0]
|
|
179
|
+
if (!frames.length || frames.length > 9) throw new Error('contact sheet requires 1–9 frames')
|
|
180
|
+
const outputDir = resolveOutputDir(body.outputDir)
|
|
181
|
+
const { width, height } = resolveRenderSize(body)
|
|
182
|
+
await mkdir(outputDir, { recursive: true })
|
|
183
|
+
const localAssets = localAssetFiles(body)
|
|
184
|
+
return withPage(async (page, origin, warnings, network, localAssetUrls) => {
|
|
185
|
+
const result = await page.evaluate(async ({
|
|
186
|
+
document, fcurves, frames, width, height, publicAssetBase, dracoDecoderPath, cameraNodeId, sceneSequence,
|
|
187
|
+
localAssetUrls,
|
|
188
|
+
}) => {
|
|
189
|
+
const renderer = await window.createDirectorRenderer({
|
|
190
|
+
document,
|
|
191
|
+
fcurves,
|
|
192
|
+
width,
|
|
193
|
+
height,
|
|
194
|
+
publicAssetBase,
|
|
195
|
+
dracoDecoderPath,
|
|
196
|
+
localAssetUrls,
|
|
197
|
+
})
|
|
198
|
+
try {
|
|
199
|
+
const pngs = []
|
|
200
|
+
for (const frame of frames) {
|
|
201
|
+
const blob = await renderer.renderFrame(frame, cameraNodeId ? { cameraId: cameraNodeId } : undefined)
|
|
202
|
+
const buffer = await blob.arrayBuffer()
|
|
203
|
+
pngs.push({
|
|
204
|
+
frame,
|
|
205
|
+
base64: window.bytesToBase64(buffer),
|
|
206
|
+
bytes: buffer.byteLength,
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
const sheet = await window.composeContactSheet(pngs, width, height,
|
|
210
|
+
cameraNodeId ? `${cameraNodeId} · scene ${sceneSequence ?? '?'}` : '')
|
|
211
|
+
return { pngs, sheet }
|
|
212
|
+
} finally {
|
|
213
|
+
renderer.dispose()
|
|
214
|
+
}
|
|
215
|
+
}, {
|
|
216
|
+
document: body.document,
|
|
217
|
+
fcurves: body.fcurves ?? null,
|
|
218
|
+
frames,
|
|
219
|
+
cameraNodeId: body.cameraNodeId,
|
|
220
|
+
sceneSequence: body.sceneSequence,
|
|
221
|
+
width,
|
|
222
|
+
height,
|
|
223
|
+
publicAssetBase: publicAssetBase(body),
|
|
224
|
+
dracoDecoderPath: `${origin}/draco/`,
|
|
225
|
+
localAssetUrls,
|
|
226
|
+
})
|
|
227
|
+
const blank = result.pngs.some((item) => item.bytes < 256)
|
|
228
|
+
const frameEntries = []
|
|
229
|
+
for (const item of result.pngs) {
|
|
230
|
+
const buffer = Buffer.from(item.base64, 'base64')
|
|
231
|
+
const file = path.join(outputDir, `frame-${item.frame}.png`)
|
|
232
|
+
await writeFile(file, buffer)
|
|
233
|
+
frameEntries.push({
|
|
234
|
+
frame: item.frame,
|
|
235
|
+
path: file,
|
|
236
|
+
mimeType: 'image/png',
|
|
237
|
+
sizeBytes: buffer.byteLength,
|
|
238
|
+
sha256: digest(buffer),
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
const sheetBuffer = Buffer.from(result.sheet, 'base64')
|
|
242
|
+
const sheetFile = path.join(outputDir, 'contact-sheet.png')
|
|
243
|
+
await writeFile(sheetFile, sheetBuffer)
|
|
244
|
+
const cameraNodeId = body.cameraNodeId ?? body.document?.content?.activeShotCameraNodeId
|
|
245
|
+
const contactSheet = {
|
|
246
|
+
path: sheetFile,
|
|
247
|
+
mimeType: 'image/png',
|
|
248
|
+
sizeBytes: sheetBuffer.byteLength,
|
|
249
|
+
sha256: digest(sheetBuffer),
|
|
250
|
+
}
|
|
251
|
+
const manifestFile = path.join(outputDir, 'render.json')
|
|
252
|
+
await writeFile(manifestFile, `${JSON.stringify({
|
|
253
|
+
version: 1,
|
|
254
|
+
runId: path.basename(outputDir),
|
|
255
|
+
createdAt: new Date().toISOString(),
|
|
256
|
+
width,
|
|
257
|
+
height,
|
|
258
|
+
cameraNodeId,
|
|
259
|
+
sceneSequence: body.sceneSequence,
|
|
260
|
+
frames: frameEntries.map(({ path: file, ...entry }) => ({ ...entry, file: path.basename(file) })),
|
|
261
|
+
contactSheet: { ...contactSheet, path: undefined, file: path.basename(sheetFile) },
|
|
262
|
+
blank,
|
|
263
|
+
blockedRequests: network.blocked,
|
|
264
|
+
...(isPlainObject(body.metadata) ? { metadata: body.metadata } : {}),
|
|
265
|
+
}, null, 2)}\n`)
|
|
266
|
+
return {
|
|
267
|
+
outputDir,
|
|
268
|
+
manifest: manifestFile,
|
|
269
|
+
cameraNodeId,
|
|
270
|
+
sceneSequence: body.sceneSequence,
|
|
271
|
+
frames: frameEntries,
|
|
272
|
+
contactSheet,
|
|
273
|
+
blank,
|
|
274
|
+
blockedRequests: network.blocked,
|
|
275
|
+
warnings,
|
|
276
|
+
}
|
|
277
|
+
}, { allowedOrigins: assetOrigins(body), localAssets })
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function isPlainObject(value) {
|
|
281
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export async function inspectNodes(body) {
|
|
285
|
+
if (!body || typeof body.document !== 'object' || !Array.isArray(body.nodeIds) || !body.nodeIds.length) {
|
|
286
|
+
throw new Error('document and nodeIds required')
|
|
287
|
+
}
|
|
288
|
+
const localAssets = localAssetFiles(body)
|
|
289
|
+
return withPage(async (page, origin, warnings, network, localAssetUrls) => {
|
|
290
|
+
const nodes = await page.evaluate(async (input) => window.inspectDirectorNodes(input), {
|
|
291
|
+
localAssetUrls,
|
|
292
|
+
document: body.document,
|
|
293
|
+
fcurves: body.fcurves ?? null,
|
|
294
|
+
nodeIds: body.nodeIds,
|
|
295
|
+
frame: body.frames?.[0],
|
|
296
|
+
publicAssetBase: publicAssetBase(body),
|
|
297
|
+
dracoDecoderPath: `${origin}/draco/`,
|
|
298
|
+
})
|
|
299
|
+
return { ok: nodes.every((node) => node.status === 'measured'), nodes, warnings, blockedRequests: network.blocked }
|
|
300
|
+
}, { allowedOrigins: assetOrigins(body), localAssets })
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export async function applyLibraryPose(body) {
|
|
304
|
+
if (!body || typeof body.document !== 'object') {
|
|
305
|
+
throw new Error('document required')
|
|
306
|
+
}
|
|
307
|
+
const localAssets = localAssetFiles(body)
|
|
308
|
+
return withPage(async (page, origin, warnings, network, localAssetUrls) => page.evaluate(
|
|
309
|
+
(input) => window.compileDirectorPose(input),
|
|
310
|
+
{ ...body, localAssets: undefined, localAssetUrls, publicAssetBase: publicAssetBase(body),
|
|
311
|
+
dracoDecoderPath: `${origin}/draco/` },
|
|
312
|
+
), { allowedOrigins: assetOrigins(body), localAssets })
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Compiles several library poses against one loaded page; the document is not modified.
|
|
316
|
+
export async function applyLibraryPoses(body) {
|
|
317
|
+
if (!body || typeof body.document !== 'object' || !Array.isArray(body.items) || !body.items.length) {
|
|
318
|
+
throw new Error('document and items required')
|
|
319
|
+
}
|
|
320
|
+
const localAssets = localAssetFiles(body)
|
|
321
|
+
return withPage(async (page, origin, warnings, network, localAssetUrls) => {
|
|
322
|
+
const results = []
|
|
323
|
+
for (const item of body.items) {
|
|
324
|
+
results.push(await page.evaluate((input) => window.compileDirectorPose(input), {
|
|
325
|
+
document: body.document,
|
|
326
|
+
nodeId: item.nodeId,
|
|
327
|
+
libraryId: item.libraryId,
|
|
328
|
+
pose: item.pose,
|
|
329
|
+
localAssetUrls,
|
|
330
|
+
publicAssetBase: publicAssetBase(body),
|
|
331
|
+
dracoDecoderPath: `${origin}/draco/`,
|
|
332
|
+
}))
|
|
333
|
+
}
|
|
334
|
+
return { results, warnings, blockedRequests: network.blocked }
|
|
335
|
+
}, { allowedOrigins: assetOrigins(body), localAssets })
|
|
336
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<script type="importmap">
|
|
6
|
+
{
|
|
7
|
+
"imports": {
|
|
8
|
+
"three": "/node_modules/three/build/three.module.js",
|
|
9
|
+
"three/addons/": "/node_modules/three/examples/jsm/",
|
|
10
|
+
"zod": "/node_modules/zod/index.js"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
</script>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<canvas id="stage" width="1280" height="720"></canvas>
|
|
17
|
+
<script type="module">
|
|
18
|
+
import { createHeadlessRenderer } from '/node_modules/@topview/3d-builder/dist/headless/index.mjs'
|
|
19
|
+
import { Stage } from '/node_modules/@topview/3d-builder/dist/engine/index.mjs'
|
|
20
|
+
import { Box3, Vector3 } from 'three'
|
|
21
|
+
import { FCurveSet } from '/node_modules/@topview/3d-builder/dist/evaluate/index.mjs'
|
|
22
|
+
import { resolveHeadlessMediaUrl } from '/mediaUrl.mjs'
|
|
23
|
+
import { compileLibraryPose } from './pose.mjs'
|
|
24
|
+
import { supportSurfaces, characterLandmarks } from './measure.mjs'
|
|
25
|
+
window.compileDirectorPose = compileLibraryPose
|
|
26
|
+
|
|
27
|
+
window.createDirectorRenderer = async ({
|
|
28
|
+
document, fcurves, width, height, publicAssetBase, dracoDecoderPath, localAssetUrls,
|
|
29
|
+
}) => {
|
|
30
|
+
const renderer = createHeadlessRenderer({
|
|
31
|
+
width,
|
|
32
|
+
height,
|
|
33
|
+
dracoDecoderPath,
|
|
34
|
+
resolveMediaUrl: (ref) => resolveHeadlessMediaUrl(ref, publicAssetBase || '', localAssetUrls),
|
|
35
|
+
})
|
|
36
|
+
// Evidence renders should not cover faces/contact with Editor name sprites.
|
|
37
|
+
// This copy keeps the saved draft's label settings unchanged.
|
|
38
|
+
const renderDocument = structuredClone(document)
|
|
39
|
+
for (const node of renderDocument.content.nodes) {
|
|
40
|
+
if (node.character) node.character.label = { ...node.character.label, showLabel: false }
|
|
41
|
+
}
|
|
42
|
+
await renderer.load(renderDocument, fcurves)
|
|
43
|
+
return renderer
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
window.inspectDirectorNodes = async ({
|
|
47
|
+
document, fcurves, nodeIds, frame, publicAssetBase, dracoDecoderPath, localAssetUrls,
|
|
48
|
+
}) => {
|
|
49
|
+
const stage = new Stage(ref => resolveHeadlessMediaUrl(ref, publicAssetBase || '', localAssetUrls), { dracoDecoderPath })
|
|
50
|
+
try {
|
|
51
|
+
await stage.load(document)
|
|
52
|
+
if (fcurves) stage.setFcurves(FCurveSet.parse(fcurves))
|
|
53
|
+
stage.seek(frame ?? document.content.timeline.frameStart ?? 0)
|
|
54
|
+
stage.scene.updateMatrixWorld(true)
|
|
55
|
+
return nodeIds.map(nodeId => {
|
|
56
|
+
const node = document.content.nodes.find(n => n.id === nodeId)
|
|
57
|
+
const object = stage.characters.get(nodeId)?.root || stage.props.get(nodeId) || stage.primitives.get(nodeId) || stage.groups.get(nodeId)
|
|
58
|
+
if (!object) return { nodeId, status: 'geometry-unavailable', type: node?.type }
|
|
59
|
+
const bounds = new Box3()
|
|
60
|
+
object.traverse(child => {
|
|
61
|
+
if (!child.isMesh || !child.geometry) return
|
|
62
|
+
// Measure meshes only: character label sprites must not inflate size.
|
|
63
|
+
const box = new Box3().setFromObject(child, true)
|
|
64
|
+
if (!box.isEmpty()) bounds.union(box)
|
|
65
|
+
})
|
|
66
|
+
if (bounds.isEmpty()) return { nodeId, status: 'geometry-unavailable' }
|
|
67
|
+
const point = value => ({ x: value.x, y: value.y, z: value.z })
|
|
68
|
+
return { nodeId, name: node.name, type: node.type, status: 'measured',
|
|
69
|
+
size: point(bounds.getSize(new Vector3())),
|
|
70
|
+
bounds: { min: point(bounds.min), max: point(bounds.max) },
|
|
71
|
+
origin: point(object.getWorldPosition(new Vector3())),
|
|
72
|
+
localForwardAxis: point(new Vector3(0, 0, 1).transformDirection(object.matrixWorld)),
|
|
73
|
+
...(node.type === 'character' ? { landmarks: characterLandmarks(object) }
|
|
74
|
+
: { supportSurfaces: supportSurfaces(object) }),
|
|
75
|
+
semanticFront: 'unknown; verify in preview', transform: node.transform }
|
|
76
|
+
})
|
|
77
|
+
} finally { stage.dispose() }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Chunked encoder: `String.fromCharCode(...bytes)` spreads every byte as a
|
|
81
|
+
// call argument and overflows the stack for PNGs above ~100 KB, which is
|
|
82
|
+
// any real-resolution frame. Encode in fixed slices instead.
|
|
83
|
+
window.bytesToBase64 = (buffer) => {
|
|
84
|
+
const bytes = new Uint8Array(buffer)
|
|
85
|
+
const chunk = 0x8000
|
|
86
|
+
let binary = ''
|
|
87
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
88
|
+
binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk))
|
|
89
|
+
}
|
|
90
|
+
return btoa(binary)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
window.composeContactSheet = async (pngs, width, height, label = '') => {
|
|
94
|
+
const cols = Math.min(3, pngs.length)
|
|
95
|
+
const rows = Math.ceil(pngs.length / cols)
|
|
96
|
+
const canvas = document.createElement('canvas')
|
|
97
|
+
// Cap the sheet at 1024px on the long edge so the file stays cheap to persist.
|
|
98
|
+
const scale = Math.min(1, 1024 / Math.max(cols * width, rows * height))
|
|
99
|
+
canvas.width = Math.max(1, Math.round(cols * width * scale))
|
|
100
|
+
canvas.height = Math.max(1, Math.round(rows * height * scale))
|
|
101
|
+
const ctx = canvas.getContext('2d')
|
|
102
|
+
ctx.scale(canvas.width / (cols * width), canvas.height / (rows * height))
|
|
103
|
+
for (const [index, item] of pngs.entries()) {
|
|
104
|
+
const image = new Image()
|
|
105
|
+
image.src = `data:image/png;base64,${item.base64}`
|
|
106
|
+
await image.decode()
|
|
107
|
+
const x = (index % cols) * width, y = Math.floor(index / cols) * height
|
|
108
|
+
ctx.drawImage(image, x, y, width, height)
|
|
109
|
+
const fontSize = Math.max(14, Math.round(14 / scale))
|
|
110
|
+
ctx.fillStyle = 'rgba(0,0,0,0.8)'
|
|
111
|
+
ctx.fillRect(x, y, width, fontSize + 10)
|
|
112
|
+
ctx.font = `${fontSize}px sans-serif`
|
|
113
|
+
ctx.fillStyle = '#ffffff'
|
|
114
|
+
ctx.fillText(`${label ? label + ' · ' : ''}frame ${item.frame}`, x + 6, y + fontSize + 2, width - 12)
|
|
115
|
+
}
|
|
116
|
+
const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/png'))
|
|
117
|
+
const buffer = await blob.arrayBuffer()
|
|
118
|
+
return window.bytesToBase64(buffer)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
window.__directorReady = true
|
|
122
|
+
</script>
|
|
123
|
+
</body>
|
|
124
|
+
</html>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Box3, Vector3 } from 'three'
|
|
2
|
+
|
|
3
|
+
const point = v => ({ x: v.x, y: v.y, z: v.z })
|
|
4
|
+
|
|
5
|
+
// Geometric candidates only: a chair's seat, armrest and back top can all be
|
|
6
|
+
// horizontal. Consumers must identify the seat from annotations and the image.
|
|
7
|
+
export function supportSurfaces(object) {
|
|
8
|
+
const groups = new Map()
|
|
9
|
+
const a = new Vector3(), b = new Vector3(), c = new Vector3()
|
|
10
|
+
const ab = new Vector3(), ac = new Vector3(), normal = new Vector3()
|
|
11
|
+
object.traverse(mesh => {
|
|
12
|
+
const geometry = mesh.geometry
|
|
13
|
+
if (!mesh.isMesh || mesh.isSkinnedMesh || !geometry?.attributes.position) return
|
|
14
|
+
const indices = geometry.index
|
|
15
|
+
const count = indices?.count ?? geometry.attributes.position.count
|
|
16
|
+
for (let i = 0; i + 2 < count; i += 3) {
|
|
17
|
+
for (const [offset, v] of [[0, a], [1, b], [2, c]]) {
|
|
18
|
+
mesh.getVertexPosition(indices ? indices.getX(i + offset) : i + offset, v)
|
|
19
|
+
v.applyMatrix4(mesh.matrixWorld)
|
|
20
|
+
}
|
|
21
|
+
normal.crossVectors(ab.subVectors(b, a), ac.subVectors(c, a))
|
|
22
|
+
const area = normal.length() / 2
|
|
23
|
+
if (area < 1e-8 || normal.y / (2 * area) < 0.97) continue
|
|
24
|
+
const height = (a.y + b.y + c.y) / 3
|
|
25
|
+
const bucket = Math.round(height / 0.02)
|
|
26
|
+
const group = groups.get(bucket) || { area: 0, weightedHeight: 0, bounds: new Box3() }
|
|
27
|
+
group.area += area
|
|
28
|
+
group.weightedHeight += area * height
|
|
29
|
+
group.bounds.expandByPoint(a).expandByPoint(b).expandByPoint(c)
|
|
30
|
+
groups.set(bucket, group)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
return [...groups.values()].filter(g => g.area > 0.005)
|
|
34
|
+
.sort((a, b) => b.area - a.area).slice(0, 6).map(g => ({
|
|
35
|
+
height: g.weightedHeight / g.area, area: g.area,
|
|
36
|
+
worldWidth: g.bounds.max.x - g.bounds.min.x,
|
|
37
|
+
worldDepth: g.bounds.max.z - g.bounds.min.z,
|
|
38
|
+
bounds: { min: point(g.bounds.min), max: point(g.bounds.max) },
|
|
39
|
+
}))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function characterLandmarks(root) {
|
|
43
|
+
const names = {
|
|
44
|
+
hips: ['mixamorighips', 'hips', 'pelvis'],
|
|
45
|
+
leftKnee: ['mixamorigleftleg', 'leftleg', 'calfl'],
|
|
46
|
+
rightKnee: ['mixamorigrightleg', 'rightleg', 'calfr'],
|
|
47
|
+
leftAnkle: ['mixamorigleftfoot', 'leftfoot', 'footl'],
|
|
48
|
+
rightAnkle: ['mixamorigrightfoot', 'rightfoot', 'footr'],
|
|
49
|
+
leftHip: ['mixamorigleftupleg', 'leftupleg', 'thighl'],
|
|
50
|
+
rightHip: ['mixamorigrightupleg', 'rightupleg', 'thighr'],
|
|
51
|
+
}
|
|
52
|
+
const result = {}
|
|
53
|
+
root.traverse(obj => {
|
|
54
|
+
if (!obj.isBone) return
|
|
55
|
+
const name = obj.name.toLowerCase().replace(/[^a-z0-9]/g, '')
|
|
56
|
+
for (const [key, aliases] of Object.entries(names)) {
|
|
57
|
+
if (aliases.includes(name)) result[key] = point(obj.getWorldPosition(new Vector3()))
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
return result
|
|
61
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Stage, poseRootOffsetScale } from '/node_modules/@topview/3d-builder/dist/engine/index.mjs'
|
|
2
|
+
import { Box3, Quaternion, Vector3 } from 'three'
|
|
3
|
+
import { resolveHeadlessMediaUrl } from '/mediaUrl.mjs'
|
|
4
|
+
|
|
5
|
+
const normalize = value => value.toLowerCase().replace(/[^a-z0-9]/g, '')
|
|
6
|
+
const ual = { Hips: 'pelvis', Spine: 'spine_01', Spine1: 'spine_02', Spine2: 'spine_03', Neck: 'neck_01', Head: 'Head' }
|
|
7
|
+
for (const [side, suffix] of [['Left', 'l'], ['Right', 'r']]) {
|
|
8
|
+
for (const [part, target] of Object.entries({ Shoulder: 'clavicle', Arm: 'upperarm', ForeArm: 'lowerarm', Hand: 'hand', UpLeg: 'thigh', Leg: 'calf', Foot: 'foot', ToeBase: 'ball' })) ual[side + part] = `${target}_${suffix}`
|
|
9
|
+
for (const finger of ['Index', 'Middle', 'Ring', 'Pinky', 'Thumb']) {
|
|
10
|
+
for (let i = 1; i <= 3; i++) ual[`${side}Hand${finger}${i}`] = `${finger.toLowerCase()}_0${i}_${suffix}`
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// The package's evaluate/headless entrypoints do not expose its dynamic pose
|
|
15
|
+
// bank. Materialize the same world-delta retargeting as supported joint controls,
|
|
16
|
+
// so both the Editor and headless renderer can replay the static pose unaided.
|
|
17
|
+
export async function compileLibraryPose({ document, nodeId, pose, libraryId, publicAssetBase, dracoDecoderPath, localAssetUrls }) {
|
|
18
|
+
const doc = structuredClone(document)
|
|
19
|
+
const node = doc.content.nodes.find(n => n.id === nodeId)
|
|
20
|
+
if (!node?.character) throw new Error('DIRECTOR_POSE_REQUIRES_CHARACTER')
|
|
21
|
+
if (node.locked) throw new Error('NODE_LOCKED')
|
|
22
|
+
node.character.animation = { mode: 'pose', controlValues: {} }
|
|
23
|
+
const stage = new Stage(ref => resolveHeadlessMediaUrl(ref, publicAssetBase || '', localAssetUrls), { dracoDecoderPath })
|
|
24
|
+
try {
|
|
25
|
+
await stage.load(doc)
|
|
26
|
+
const character = stage.characters.get(nodeId)
|
|
27
|
+
if (!character) throw new Error('DIRECTOR_POSE_CHARACTER_NOT_LOADED')
|
|
28
|
+
const { root, restPose } = character
|
|
29
|
+
const savedRoot = root.quaternion.clone()
|
|
30
|
+
root.quaternion.identity()
|
|
31
|
+
root.traverse(obj => { if (restPose.has(obj.name)) obj.quaternion.copy(restPose.get(obj.name)) })
|
|
32
|
+
root.updateMatrixWorld(true)
|
|
33
|
+
const byName = new Map(), restWorld = new Map(), assigned = new Map()
|
|
34
|
+
root.traverse(obj => {
|
|
35
|
+
if (obj.isBone) byName.set(normalize(obj.name), obj)
|
|
36
|
+
restWorld.set(obj, obj.getWorldQuaternion(new Quaternion()))
|
|
37
|
+
})
|
|
38
|
+
for (const [name, value] of Object.entries(pose.bones)) {
|
|
39
|
+
const mapped = character.rig === 'ual1' ? ual[name.replace(/^mixamorig:?/i, '')] : null
|
|
40
|
+
const bone = byName.get(normalize(mapped || name))
|
|
41
|
+
if (bone) assigned.set(bone, value)
|
|
42
|
+
}
|
|
43
|
+
if (assigned.size < 10) throw new Error('DIRECTOR_POSE_INCOMPATIBLE_SKELETON')
|
|
44
|
+
const controls = {}
|
|
45
|
+
const apply = obj => {
|
|
46
|
+
const values = assigned.get(obj)
|
|
47
|
+
if (values) {
|
|
48
|
+
const desired = new Quaternion(...values).normalize().multiply(restWorld.get(obj))
|
|
49
|
+
const parent = obj.parent?.getWorldQuaternion(new Quaternion()) || new Quaternion()
|
|
50
|
+
obj.quaternion.copy(parent.invert().multiply(desired))
|
|
51
|
+
obj.updateWorldMatrix(true, false)
|
|
52
|
+
}
|
|
53
|
+
if (obj.isBone) {
|
|
54
|
+
for (const axis of ['x', 'y', 'z']) controls[`joint:${obj.name}:${axis}`] = obj.rotation[axis] * 180 / Math.PI
|
|
55
|
+
}
|
|
56
|
+
for (const child of obj.children) apply(child)
|
|
57
|
+
}
|
|
58
|
+
apply(root)
|
|
59
|
+
const animation = { mode: 'pose', posePresetId: pose.pose_id || libraryId.replace(/^a3d_pose_/, ''),
|
|
60
|
+
controlValues: controls, rootPositionOffset: { x: pose.hips[0], y: pose.hips[1], z: pose.hips[2] } }
|
|
61
|
+
// Return only character pose fields: signed model URLs must not be persisted.
|
|
62
|
+
// Same root drop as applySnapshot: library hips scale with the character's leg length.
|
|
63
|
+
character.inner.position.set(...pose.hips.map(value => value * poseRootOffsetScale(character.legLength)))
|
|
64
|
+
root.quaternion.copy(savedRoot)
|
|
65
|
+
root.updateMatrixWorld(true)
|
|
66
|
+
const bounds = new Box3().setFromObject(character.inner, true)
|
|
67
|
+
const point = value => ({ x: value.x, y: value.y, z: value.z })
|
|
68
|
+
return { animation, pose: { libraryId, matchedBones: assigned.size,
|
|
69
|
+
size: point(bounds.getSize(new Vector3())), bounds: { min: point(bounds.min), max: point(bounds.max) } } }
|
|
70
|
+
} finally { stage.dispose() }
|
|
71
|
+
}
|