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,302 @@
|
|
|
1
|
+
"""Read-only checks: ``evaluate`` (dry-run numbers), ``inspect nodes`` (mesh measurements), ``inspect views``.
|
|
2
|
+
|
|
3
|
+
Nothing here edits the director document. ``inspect views`` writes render runs and records the
|
|
4
|
+
runtime evidence in ``.topview3d/bom.json``.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import copy
|
|
9
|
+
import itertools
|
|
10
|
+
import math
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from jsonschema import Draft7Validator
|
|
14
|
+
|
|
15
|
+
from topview_3d_cli.director_document import new_scene_operation_id, stored_evaluate_body, summarize_evaluate_frames
|
|
16
|
+
from topview_3d_cli.director_geometry import check_primitive_geometry, compact_geometry
|
|
17
|
+
from topview_3d_cli.director_operations import DirectorConflictError, DirectorOperationError
|
|
18
|
+
from topview_3d_cli.director_static import camera_view, node_in
|
|
19
|
+
from topview_3d_cli.local_assets import asset_catalog, check_render_assets, local_asset_map
|
|
20
|
+
from topview_3d_cli.local_bom import record_views
|
|
21
|
+
from topview_3d_cli.local_edit import check_node_spec, check_scene_sequence, load_spec, stage_node_changes
|
|
22
|
+
from topview_3d_cli.local_errors import LocalProjectError
|
|
23
|
+
from topview_3d_cli.local_project import base_result, open_project, validate_assembled
|
|
24
|
+
from topview_3d_cli.local_render import BENIGN_RENDER_WARNINGS, render_snapshot, require_camera
|
|
25
|
+
from topview_3d_cli.node_batch import obj
|
|
26
|
+
from topview_3d_cli.renderer import director_cli
|
|
27
|
+
|
|
28
|
+
MAX_FRAMES = 32
|
|
29
|
+
GROUND_TOLERANCE = 0.02
|
|
30
|
+
_IDENTIFIER = {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,128}$"}
|
|
31
|
+
VIEWS_SCHEMA = obj({
|
|
32
|
+
"expectedSceneSequence": {"type": "integer", "minimum": 0},
|
|
33
|
+
"views": {"type": "array", "minItems": 1, "maxItems": 8, "items": obj({
|
|
34
|
+
"cameraNodeId": _IDENTIFIER,
|
|
35
|
+
"frames": {"type": "array", "minItems": 1, "maxItems": 3, "uniqueItems": True,
|
|
36
|
+
"items": {"type": "integer", "minimum": 0}},
|
|
37
|
+
}, ("cameraNodeId", "frames"))},
|
|
38
|
+
"width": {"type": "integer", "minimum": 64, "maximum": 1024},
|
|
39
|
+
"height": {"type": "integer", "minimum": 64, "maximum": 1024},
|
|
40
|
+
"primaryCameraNodeId": _IDENTIFIER,
|
|
41
|
+
}, ("views",))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def parse_frames(text: str | None, default: list[int] | None = None) -> list[int]:
|
|
45
|
+
if text is None:
|
|
46
|
+
return list(default or [0])
|
|
47
|
+
try:
|
|
48
|
+
frames = [int(part) for part in text.split(",") if part.strip()]
|
|
49
|
+
except ValueError as exc:
|
|
50
|
+
raise LocalProjectError("USAGE_INVALID", f"--frames expects comma-separated integers, got {text!r}") from exc
|
|
51
|
+
if not 1 <= len(frames) <= MAX_FRAMES or any(frame < 0 for frame in frames) or len(set(frames)) != len(frames):
|
|
52
|
+
raise LocalProjectError("USAGE_INVALID", f"--frames needs 1-{MAX_FRAMES} distinct non-negative integers")
|
|
53
|
+
return frames
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _camera(body: dict[str, Any], camera_id: str | None) -> dict[str, Any]:
|
|
57
|
+
require_camera(body["document"], camera_id)
|
|
58
|
+
return camera_view(body, camera_id)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _staged_plan(project: Any, plan: dict[str, Any]) -> tuple[str, dict[str, Any]]:
|
|
62
|
+
kinds = [key for key in ("changes", "operations") if key in plan]
|
|
63
|
+
extra = set(plan) - {"changes", "operations", "expectedSceneSequence"}
|
|
64
|
+
if len(kinds) != 1 or extra:
|
|
65
|
+
raise LocalProjectError("PLAN_INVALID", "a plan has exactly one of changes or operations "
|
|
66
|
+
"(plus optional expectedSceneSequence)")
|
|
67
|
+
check_scene_sequence(plan, project.store.scene_sequence)
|
|
68
|
+
if kinds[0] == "changes":
|
|
69
|
+
check_node_spec(plan, "PLAN_INVALID")
|
|
70
|
+
return "changes", stage_node_changes(project, plan)[1]
|
|
71
|
+
operations = plan["operations"]
|
|
72
|
+
if not isinstance(operations, list):
|
|
73
|
+
raise LocalProjectError("PLAN_INVALID", "operations must be a list")
|
|
74
|
+
for operation in operations:
|
|
75
|
+
if isinstance(operation, dict) and "operationId" not in operation:
|
|
76
|
+
operation["operationId"] = new_scene_operation_id("plan")
|
|
77
|
+
store = copy.deepcopy(project.store)
|
|
78
|
+
try:
|
|
79
|
+
store.apply(operations, strict_versions=False)
|
|
80
|
+
except (DirectorOperationError, DirectorConflictError) as exc:
|
|
81
|
+
raise LocalProjectError(exc.code, str(exc)) from exc
|
|
82
|
+
return "operations", store.assemble()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def evaluate(directory: str, plan_path: str | None = None, *, frames: list[int], camera: str | None) -> dict[str, Any]:
|
|
86
|
+
"""Numbers for the stored scene, or for a plan staged in memory; nothing is written."""
|
|
87
|
+
project = open_project(directory)
|
|
88
|
+
result = base_result(project)
|
|
89
|
+
plan_kind, snapshot = None, project.store.assemble()
|
|
90
|
+
if plan_path:
|
|
91
|
+
plan_kind, snapshot = _staged_plan(project, load_spec(plan_path, "PLAN_INVALID"))
|
|
92
|
+
validate_assembled(snapshot)
|
|
93
|
+
body = _camera(stored_evaluate_body(snapshot, frames), camera)
|
|
94
|
+
evaluation = summarize_evaluate_frames(director_cli("evaluate", body))
|
|
95
|
+
geometry = compact_geometry(check_primitive_geometry(snapshot["document"]))
|
|
96
|
+
return {**result, "persisted": False, "plan": plan_kind, "frames": frames,
|
|
97
|
+
"cameraNodeId": body.get("cameraNodeId") or body["document"]["content"].get("activeShotCameraNodeId"),
|
|
98
|
+
"evaluation": evaluation, "geometry": geometry,
|
|
99
|
+
"ok": evaluation.get("ok") is True and not geometry.get("issues")}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _vector(point: dict[str, Any]) -> tuple[float, float, float]:
|
|
103
|
+
return point["x"], point["y"], point["z"]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _pair(a: dict[str, Any], b: dict[str, Any]) -> dict[str, Any]:
|
|
107
|
+
low_a, high_a = _vector(a["bounds"]["min"]), _vector(a["bounds"]["max"])
|
|
108
|
+
low_b, high_b = _vector(b["bounds"]["min"]), _vector(b["bounds"]["max"])
|
|
109
|
+
gaps = [max(low_b[i] - high_a[i], low_a[i] - high_b[i], 0.0) for i in range(3)]
|
|
110
|
+
overlap = [min(high_a[i], high_b[i]) - max(low_a[i], low_b[i]) for i in range(3)]
|
|
111
|
+
intersecting = all(value > 0 for value in overlap)
|
|
112
|
+
return {
|
|
113
|
+
"nodeIds": [a["nodeId"], b["nodeId"]],
|
|
114
|
+
"originDistance": round(math.dist(_vector(a["origin"]), _vector(b["origin"])), 4),
|
|
115
|
+
"horizontalOriginDistance": round(math.dist((a["origin"]["x"], a["origin"]["z"]),
|
|
116
|
+
(b["origin"]["x"], b["origin"]["z"])), 4),
|
|
117
|
+
"boundsGap": round(math.sqrt(sum(value * value for value in gaps)), 4),
|
|
118
|
+
"boundsIntersect": intersecting,
|
|
119
|
+
**({"overlapSize": dict(zip("xyz", (round(value, 4) for value in overlap)))} if intersecting else {}),
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def inspect_nodes(directory: str, node_ids: list[str], *, frame: int = 0) -> dict[str, Any]:
|
|
124
|
+
"""World-space mesh bounds plus derived grounding and pairwise distance / overlap."""
|
|
125
|
+
if not 1 <= len(node_ids) <= 16 or len(set(node_ids)) != len(node_ids):
|
|
126
|
+
raise LocalProjectError("USAGE_INVALID", "inspect nodes takes 1-16 distinct node ids")
|
|
127
|
+
project = open_project(directory)
|
|
128
|
+
snapshot = project.store.assemble()
|
|
129
|
+
document = snapshot["document"]
|
|
130
|
+
for node_id in node_ids:
|
|
131
|
+
try:
|
|
132
|
+
node_in(document, node_id)
|
|
133
|
+
except ValueError as exc:
|
|
134
|
+
raise LocalProjectError("DIRECTOR_NODE_NOT_FOUND", node_id) from exc
|
|
135
|
+
mapping = local_asset_map(asset_catalog(project.paths))
|
|
136
|
+
check_render_assets({"content": {"nodes": [node_in(document, node_id) for node_id in node_ids]}}, mapping)
|
|
137
|
+
measured = director_cli("inspect-nodes", {
|
|
138
|
+
"document": document, "fcurves": snapshot["fcurves"], "nodeIds": node_ids, "frames": [frame],
|
|
139
|
+
"localAssets": mapping, "publicAssetBase": ""})
|
|
140
|
+
ground = float(((document.get("content") or {}).get("environment") or {}).get("display", {}).get("groundHeight") or 0)
|
|
141
|
+
nodes = []
|
|
142
|
+
for row in measured.get("nodes") or []:
|
|
143
|
+
row = dict(row)
|
|
144
|
+
if row.get("status") == "measured" and row.get("type") != "camera":
|
|
145
|
+
bottom = row["bounds"]["min"]["y"] - ground
|
|
146
|
+
row["grounding"] = {"groundHeight": ground, "bottomOffset": round(bottom, 4),
|
|
147
|
+
"status": "grounded" if abs(bottom) <= GROUND_TOLERANCE
|
|
148
|
+
else ("floating" if bottom > 0 else "penetrating")}
|
|
149
|
+
nodes.append(row)
|
|
150
|
+
solid = [row for row in nodes if row.get("status") == "measured" and row.get("type") != "camera"]
|
|
151
|
+
return {**base_result(project), "frame": frame, "ok": measured.get("ok") is True,
|
|
152
|
+
"groundTolerance": GROUND_TOLERANCE, "nodes": nodes,
|
|
153
|
+
"pairs": [_pair(a, b) for a, b in itertools.combinations(solid, 2)],
|
|
154
|
+
"warnings": measured.get("warnings") or [], "blockedRequests": measured.get("blockedRequests") or []}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _aggregate_issues(issues: Any) -> list[dict[str, Any]]:
|
|
158
|
+
grouped: dict[tuple[str, str], dict[str, Any]] = {}
|
|
159
|
+
for issue in issues or []:
|
|
160
|
+
if not isinstance(issue, dict) or not issue.get("code"):
|
|
161
|
+
continue
|
|
162
|
+
key = (str(issue["code"]), str(issue.get("severity") or "warning"))
|
|
163
|
+
row = grouped.setdefault(key, {"code": key[0], "severity": key[1], "nodeIds": []})
|
|
164
|
+
if issue.get("nodeId") and issue["nodeId"] not in row["nodeIds"]:
|
|
165
|
+
row["nodeIds"].append(issue["nodeId"])
|
|
166
|
+
return list(grouped.values())
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _numerical(result: dict[str, Any]) -> dict[str, Any]:
|
|
170
|
+
value = summarize_evaluate_frames(result)
|
|
171
|
+
frames = []
|
|
172
|
+
for frame in value.get("frames") or []:
|
|
173
|
+
compact = {key: frame[key] for key in ("frame", "cameraTargetDistance", "targetInFrustum")
|
|
174
|
+
if frame.get(key) is not None}
|
|
175
|
+
if len(compact) > 1:
|
|
176
|
+
frames.append(compact)
|
|
177
|
+
return {"ok": value.get("ok") is True, "issues": _aggregate_issues(value.get("issues")),
|
|
178
|
+
**({"frames": frames} if frames else {})}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# Other nodes leaving a close shot is normal; only the camera's own target must stay in frame.
|
|
182
|
+
ADVISORY_ISSUES = frozenset({"MESH_OUTSIDE_FRUSTUM", "ORIGIN_OUTSIDE_FRUSTUM"})
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _view_blockers(row: dict[str, Any]) -> list[str]:
|
|
186
|
+
camera = row["cameraNodeId"]
|
|
187
|
+
numerical = row["numerical"]
|
|
188
|
+
blockers = [f"{camera}: {issue['code']} {','.join(issue['nodeIds'])}".rstrip()
|
|
189
|
+
for issue in numerical["issues"] if issue["code"] not in ADVISORY_ISSUES]
|
|
190
|
+
if not numerical["ok"] and not blockers:
|
|
191
|
+
blockers.append(f"{camera}: evaluation failed")
|
|
192
|
+
missed = [frame["frame"] for frame in numerical.get("frames") or [] if frame.get("targetInFrustum") is False]
|
|
193
|
+
if missed:
|
|
194
|
+
blockers.append(f"{camera}: TARGET_OUTSIDE_FRUSTUM frames {','.join(map(str, missed))}")
|
|
195
|
+
if row.get("renderStatus") != "complete":
|
|
196
|
+
blockers.append(f"{camera}: render {row.get('renderStatus')}"
|
|
197
|
+
+ (f" ({row['renderError']})" if row.get("renderError") else ""))
|
|
198
|
+
return blockers
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _file(entry: dict[str, Any]) -> dict[str, Any]:
|
|
202
|
+
return {key: entry[key] for key in ("path", "sha256", "sizeBytes") if key in entry}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _character_bounds(project: Any, snapshot: dict[str, Any], frames: list[int]) -> dict[str, dict[str, Any]]:
|
|
206
|
+
"""Posed mesh bounds of every visible character per frame, for grounding and framing checks."""
|
|
207
|
+
document = snapshot["document"]
|
|
208
|
+
ids = [node["id"] for node in document["content"]["nodes"]
|
|
209
|
+
if node.get("type") == "character" and node.get("visible") is not False]
|
|
210
|
+
if not ids:
|
|
211
|
+
return {}
|
|
212
|
+
mapping = local_asset_map(asset_catalog(project.paths))
|
|
213
|
+
check_render_assets({"content": {"nodes": [node_in(document, node_id) for node_id in ids]}}, mapping)
|
|
214
|
+
bounds: dict[str, dict[str, Any]] = {}
|
|
215
|
+
for frame in frames:
|
|
216
|
+
measured = director_cli("inspect-nodes", {
|
|
217
|
+
"document": document, "fcurves": snapshot["fcurves"], "nodeIds": ids, "frames": [frame],
|
|
218
|
+
"localAssets": mapping, "publicAssetBase": ""})
|
|
219
|
+
bounds[str(frame)] = {row["nodeId"]: row["bounds"] for row in measured.get("nodes") or []
|
|
220
|
+
if row.get("status") == "measured" and row.get("bounds")}
|
|
221
|
+
return bounds
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _default_views(document: dict[str, Any], cameras: list[str], frames: list[int]) -> list[dict[str, Any]]:
|
|
225
|
+
ids = cameras or [node["id"] for node in document["content"]["nodes"] if node.get("type") == "camera"]
|
|
226
|
+
return [{"cameraNodeId": camera_id, "frames": frames} for camera_id in ids]
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def inspect_views(directory: str, views_path: str | None = None, *, cameras: list[str] | None = None,
|
|
230
|
+
frames: list[int] | None = None, primary: str | None = None) -> dict[str, Any]:
|
|
231
|
+
"""Numbers plus one render run per camera on one scene sequence; evidence goes to the BOM."""
|
|
232
|
+
project = open_project(directory)
|
|
233
|
+
snapshot = project.store.assemble()
|
|
234
|
+
if views_path:
|
|
235
|
+
if cameras or frames or primary:
|
|
236
|
+
raise LocalProjectError("USAGE_INVALID", "use either a views file or --camera/--frames/--primary")
|
|
237
|
+
spec = load_spec(views_path, "VIEWS_INVALID")
|
|
238
|
+
else:
|
|
239
|
+
spec = {"views": _default_views(snapshot["document"], cameras or [], frames or [0]),
|
|
240
|
+
**({"primaryCameraNodeId": primary} if primary else {})}
|
|
241
|
+
error = next(Draft7Validator(VIEWS_SCHEMA).iter_errors(spec), None)
|
|
242
|
+
if error:
|
|
243
|
+
raise LocalProjectError("VIEWS_INVALID", f"{'.'.join(map(str, error.path))}: {error.message[:450]}")
|
|
244
|
+
check_scene_sequence(spec, snapshot["sceneSequence"])
|
|
245
|
+
for view in spec["views"]:
|
|
246
|
+
require_camera(snapshot["document"], view["cameraNodeId"])
|
|
247
|
+
primary = spec.get("primaryCameraNodeId")
|
|
248
|
+
if primary and primary not in {view["cameraNodeId"] for view in spec["views"]}:
|
|
249
|
+
raise LocalProjectError("VIEWS_INVALID", "primaryCameraNodeId must be one of the views")
|
|
250
|
+
size = {key: spec[key] for key in ("width", "height") if key in spec}
|
|
251
|
+
geometry = check_primitive_geometry(snapshot["document"])
|
|
252
|
+
bounds_error = None
|
|
253
|
+
try:
|
|
254
|
+
bounds = _character_bounds(project, snapshot,
|
|
255
|
+
sorted({frame for view in spec["views"] for frame in view["frames"]}))
|
|
256
|
+
except LocalProjectError as exc:
|
|
257
|
+
bounds, bounds_error = {}, f"{exc.code}: {str(exc)[:200]}"
|
|
258
|
+
rows = []
|
|
259
|
+
for view in spec["views"]:
|
|
260
|
+
row: dict[str, Any] = {**view, "sceneSequence": snapshot["sceneSequence"]}
|
|
261
|
+
body = _camera(stored_evaluate_body(snapshot, view["frames"]), view["cameraNodeId"])
|
|
262
|
+
if bounds:
|
|
263
|
+
body["nodeBounds"] = bounds
|
|
264
|
+
row["numerical"] = _numerical(director_cli("evaluate", body))
|
|
265
|
+
try:
|
|
266
|
+
rendered = render_snapshot(project, {**snapshot, "document": body["document"]},
|
|
267
|
+
{"frames": view["frames"], "cameraNodeId": view["cameraNodeId"], **size})
|
|
268
|
+
except LocalProjectError as exc:
|
|
269
|
+
row.update(renderStatus="failed", renderError=f"{exc.code}: {str(exc)[:200]}")
|
|
270
|
+
rows.append(row)
|
|
271
|
+
continue
|
|
272
|
+
output = rendered["result"]
|
|
273
|
+
warnings = [str(item) for item in output.get("warnings") or []
|
|
274
|
+
if not any(marker in str(item) for marker in BENIGN_RENDER_WARNINGS)][:20]
|
|
275
|
+
frame_files = [frame for frame in output.get("frames") or [] if isinstance(frame, dict)]
|
|
276
|
+
row["render"] = {"runId": rendered["runId"], "contactSheet": _file(output.get("contactSheet") or {}),
|
|
277
|
+
"frameSha256s": [frame["sha256"] for frame in frame_files if frame.get("sha256")],
|
|
278
|
+
"frames": [{"frame": frame.get("frame"), **_file(frame)} for frame in frame_files]}
|
|
279
|
+
if warnings:
|
|
280
|
+
row["renderWarnings"] = warnings
|
|
281
|
+
row["renderStatus"] = ("complete" if row["render"]["contactSheet"].get("path") and not output.get("blank")
|
|
282
|
+
and not warnings else "needs-review")
|
|
283
|
+
rows.append(row)
|
|
284
|
+
stale = open_project(directory).store.scene_sequence != snapshot["sceneSequence"]
|
|
285
|
+
selected = next((row for row in rows if row["cameraNodeId"] == primary
|
|
286
|
+
and row.get("render", {}).get("contactSheet", {}).get("path")), None)
|
|
287
|
+
blockers = [*(["the scene changed while inspecting; rerun"] if stale else []),
|
|
288
|
+
*(f"geometry: {issue['code']} {issue.get('nodeId', '')}".rstrip() for issue in geometry["issues"]),
|
|
289
|
+
*(blocker for row in rows for blocker in _view_blockers(row))]
|
|
290
|
+
result = {
|
|
291
|
+
**base_result(project), "stale": stale, "views": rows, "geometry": compact_geometry(geometry),
|
|
292
|
+
"measurement": "character meshes" if bounds else f"character origins ({bounds_error or 'no characters'})",
|
|
293
|
+
"checksComplete": not blockers, "checksBlockedBy": blockers[:20],
|
|
294
|
+
"visualStatus": "pending-model-review",
|
|
295
|
+
}
|
|
296
|
+
if selected:
|
|
297
|
+
result["primaryStoryPreview"] = {"cameraNodeId": primary, "frames": selected["frames"],
|
|
298
|
+
"sceneSequence": snapshot["sceneSequence"],
|
|
299
|
+
**selected["render"]["contactSheet"]}
|
|
300
|
+
record_views(project, result)
|
|
301
|
+
result["bomPath"] = str(project.paths.bom)
|
|
302
|
+
return result
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""Local, single-writer Scene3D project storage.
|
|
2
|
+
|
|
3
|
+
``.topview3d/entities.json`` is the authoritative entity store (see
|
|
4
|
+
``director_operations``). ``document.json`` and ``fcurves.json`` are derived views
|
|
5
|
+
rewritten on every commit for renderers and humans; editing them has no effect.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import copy
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import tempfile
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from topview_3d_cli import __version__
|
|
19
|
+
from topview_3d_cli.director_document import (
|
|
20
|
+
DIRECTOR_DOCUMENT_TYPE,
|
|
21
|
+
empty_director_document,
|
|
22
|
+
normalize_document,
|
|
23
|
+
summarize_director_snapshot,
|
|
24
|
+
validate_director_document,
|
|
25
|
+
validate_fcurves,
|
|
26
|
+
)
|
|
27
|
+
from topview_3d_cli.director_operations import (
|
|
28
|
+
DirectorConflictError,
|
|
29
|
+
DirectorOperationError,
|
|
30
|
+
DirectorStore,
|
|
31
|
+
genesis_store,
|
|
32
|
+
)
|
|
33
|
+
from topview_3d_cli.local_errors import LocalProjectError
|
|
34
|
+
from topview_3d_cli.runtime import builder_version
|
|
35
|
+
|
|
36
|
+
FORMAT_NAME = "scene3d-local-project"
|
|
37
|
+
SCHEMA_VERSION = 2
|
|
38
|
+
CLI_VERSION = __version__
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class ProjectPaths:
|
|
43
|
+
root: Path
|
|
44
|
+
state: Path
|
|
45
|
+
metadata: Path
|
|
46
|
+
entities: Path
|
|
47
|
+
document: Path
|
|
48
|
+
fcurves: Path
|
|
49
|
+
assets: Path
|
|
50
|
+
renders: Path
|
|
51
|
+
bom: Path
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class Project:
|
|
56
|
+
paths: ProjectPaths
|
|
57
|
+
metadata: dict[str, Any]
|
|
58
|
+
store: DirectorStore
|
|
59
|
+
migrated_from: int | None = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def project_paths(root: str | os.PathLike[str]) -> ProjectPaths:
|
|
63
|
+
directory = Path(root).expanduser().resolve()
|
|
64
|
+
state = directory / ".topview3d"
|
|
65
|
+
return ProjectPaths(
|
|
66
|
+
root=directory,
|
|
67
|
+
state=state,
|
|
68
|
+
metadata=state / "metadata.json",
|
|
69
|
+
entities=state / "entities.json",
|
|
70
|
+
document=state / "document.json",
|
|
71
|
+
fcurves=state / "fcurves.json",
|
|
72
|
+
assets=state / "assets",
|
|
73
|
+
renders=state / "renders",
|
|
74
|
+
bom=state / "bom.json",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def utc_timestamp() -> str:
|
|
79
|
+
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _read_json(path: Path) -> dict[str, Any]:
|
|
83
|
+
try:
|
|
84
|
+
value = json.loads(path.read_text(encoding="utf-8"))
|
|
85
|
+
except FileNotFoundError as exc:
|
|
86
|
+
raise LocalProjectError("PROJECT_NOT_INITIALIZED", f"missing {path}") from exc
|
|
87
|
+
except json.JSONDecodeError as exc:
|
|
88
|
+
raise LocalProjectError("PROJECT_JSON_INVALID", f"{path}: {exc.msg}") from exc
|
|
89
|
+
if not isinstance(value, dict):
|
|
90
|
+
raise LocalProjectError("PROJECT_JSON_INVALID", f"{path} must contain an object")
|
|
91
|
+
return value
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def atomic_write_json(path: Path, value: dict[str, Any]) -> None:
|
|
95
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
96
|
+
encoded = json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True, allow_nan=False) + "\n"
|
|
97
|
+
descriptor, temporary = tempfile.mkstemp(prefix=f".{path.name}.", suffix=".tmp", dir=path.parent)
|
|
98
|
+
try:
|
|
99
|
+
with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
|
|
100
|
+
stream.write(encoded)
|
|
101
|
+
stream.flush()
|
|
102
|
+
os.fsync(stream.fileno())
|
|
103
|
+
os.replace(temporary, path)
|
|
104
|
+
finally:
|
|
105
|
+
if os.path.exists(temporary):
|
|
106
|
+
os.unlink(temporary)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def validate_assembled(assembled: dict[str, Any]) -> None:
|
|
110
|
+
try:
|
|
111
|
+
validate_director_document(assembled["document"])
|
|
112
|
+
except ValueError as exc:
|
|
113
|
+
raise LocalProjectError("INVALID_DIRECTOR_DOCUMENT", str(exc)) from exc
|
|
114
|
+
try:
|
|
115
|
+
validate_fcurves(assembled["fcurves"])
|
|
116
|
+
except ValueError as exc:
|
|
117
|
+
raise LocalProjectError("INVALID_DIRECTOR_FCURVES", str(exc)) from exc
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def commit(project: Project) -> dict[str, Any]:
|
|
121
|
+
"""Validate the assembled state and persist it; the entity store is written first."""
|
|
122
|
+
assembled = project.store.assemble()
|
|
123
|
+
validate_assembled(assembled)
|
|
124
|
+
metadata = copy.deepcopy(project.metadata)
|
|
125
|
+
metadata.update({
|
|
126
|
+
"format": FORMAT_NAME,
|
|
127
|
+
"schemaVersion": SCHEMA_VERSION,
|
|
128
|
+
"cliVersion": CLI_VERSION,
|
|
129
|
+
"revision": int(project.metadata.get("revision", 0)) + 1,
|
|
130
|
+
"sceneSequence": project.store.scene_sequence,
|
|
131
|
+
"updatedAt": utc_timestamp(),
|
|
132
|
+
})
|
|
133
|
+
atomic_write_json(project.paths.entities, project.store.to_json())
|
|
134
|
+
atomic_write_json(project.paths.document, assembled["document"])
|
|
135
|
+
atomic_write_json(project.paths.fcurves, assembled["fcurves"])
|
|
136
|
+
atomic_write_json(project.paths.metadata, metadata)
|
|
137
|
+
project.metadata = metadata
|
|
138
|
+
project.migrated_from = None
|
|
139
|
+
return metadata
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def init_project(root: str | os.PathLike[str], *, force: bool = False) -> dict[str, Any]:
|
|
143
|
+
paths = project_paths(root)
|
|
144
|
+
if paths.state.exists() and not force:
|
|
145
|
+
raise LocalProjectError("PROJECT_EXISTS", f"project already exists at {paths.state}")
|
|
146
|
+
paths.assets.mkdir(parents=True, exist_ok=True)
|
|
147
|
+
paths.renders.mkdir(parents=True, exist_ok=True)
|
|
148
|
+
store = genesis_store(normalize_document(empty_director_document()))
|
|
149
|
+
project = Project(paths, {"builderVersion": builder_version(), "revision": -1}, store)
|
|
150
|
+
metadata = commit(project)
|
|
151
|
+
return {"ok": True, "project": str(paths.root), "metadata": metadata}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _migrate_v1(paths: ProjectPaths) -> DirectorStore:
|
|
155
|
+
document = _read_json(paths.document)
|
|
156
|
+
animation = ((document.get("content") or {}).get("timeline") or {}).get("animation") or {}
|
|
157
|
+
curves = animation.get("fcurves") if isinstance(animation.get("fcurves"), list) else []
|
|
158
|
+
try:
|
|
159
|
+
return genesis_store(normalize_document(document), {"fcurves": curves})
|
|
160
|
+
except (ValueError, DirectorConflictError) as exc:
|
|
161
|
+
raise LocalProjectError("PROJECT_JSON_INVALID", f"cannot migrate schemaVersion 1 project: {exc}") from exc
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def open_project(root: str | os.PathLike[str]) -> Project:
|
|
165
|
+
paths = project_paths(root)
|
|
166
|
+
if not paths.metadata.is_file():
|
|
167
|
+
raise LocalProjectError("PROJECT_NOT_INITIALIZED", f"run `topview-3d-cli project init {paths.root}`")
|
|
168
|
+
metadata = _read_json(paths.metadata)
|
|
169
|
+
version = metadata.get("schemaVersion")
|
|
170
|
+
if version == 1:
|
|
171
|
+
return Project(paths, metadata, _migrate_v1(paths), migrated_from=1)
|
|
172
|
+
if version != SCHEMA_VERSION:
|
|
173
|
+
raise LocalProjectError("PROJECT_FORMAT_UNSUPPORTED", f"schemaVersion {version!r}; this CLI reads {SCHEMA_VERSION}")
|
|
174
|
+
try:
|
|
175
|
+
store = DirectorStore.from_json(_read_json(paths.entities))
|
|
176
|
+
except (KeyError, TypeError, ValueError) as exc:
|
|
177
|
+
if isinstance(exc, LocalProjectError):
|
|
178
|
+
raise
|
|
179
|
+
raise LocalProjectError("PROJECT_JSON_INVALID", f"{paths.entities}: {exc}") from exc
|
|
180
|
+
return Project(paths, metadata, store)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def base_result(project: Project) -> dict[str, Any]:
|
|
184
|
+
result = {
|
|
185
|
+
"ok": True,
|
|
186
|
+
"project": str(project.paths.root),
|
|
187
|
+
"revision": project.metadata.get("revision", 0),
|
|
188
|
+
"sceneSequence": project.store.scene_sequence,
|
|
189
|
+
}
|
|
190
|
+
if project.migrated_from is not None:
|
|
191
|
+
result["migrationPending"] = {"fromSchemaVersion": project.migrated_from, "toSchemaVersion": SCHEMA_VERSION}
|
|
192
|
+
return result
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def project_status(root: str | os.PathLike[str]) -> dict[str, Any]:
|
|
196
|
+
project = open_project(root)
|
|
197
|
+
assembled = project.store.assemble()
|
|
198
|
+
return {
|
|
199
|
+
**base_result(project),
|
|
200
|
+
"stateDirectory": str(project.paths.state),
|
|
201
|
+
"metadata": project.metadata,
|
|
202
|
+
"document": summarize_director_snapshot(assembled),
|
|
203
|
+
"danglingReferences": project.store.dangling_references(),
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def document_get(root: str | os.PathLike[str]) -> dict[str, Any]:
|
|
208
|
+
project = open_project(root)
|
|
209
|
+
assembled = project.store.assemble()
|
|
210
|
+
return {
|
|
211
|
+
**base_result(project),
|
|
212
|
+
"metadata": project.metadata,
|
|
213
|
+
"entityVersions": assembled["entityVersions"],
|
|
214
|
+
"document": assembled["document"],
|
|
215
|
+
"fcurves": assembled["fcurves"],
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def validate_project_document(root: str | os.PathLike[str]) -> dict[str, Any]:
|
|
220
|
+
project = open_project(root)
|
|
221
|
+
assembled = project.store.assemble()
|
|
222
|
+
try:
|
|
223
|
+
validate_assembled(assembled)
|
|
224
|
+
except LocalProjectError as exc:
|
|
225
|
+
raise LocalProjectError("DOCUMENT_INVALID", str(exc)) from exc
|
|
226
|
+
dangling = project.store.dangling_references()
|
|
227
|
+
if dangling:
|
|
228
|
+
raise LocalProjectError("DOCUMENT_INVALID", f"{len(dangling)} reference(s) to missing nodes; delete or "
|
|
229
|
+
"repoint them", details={"danglingReferences": dangling[:50]})
|
|
230
|
+
return {
|
|
231
|
+
**base_result(project),
|
|
232
|
+
"documentType": assembled["document"].get("type", DIRECTOR_DOCUMENT_TYPE),
|
|
233
|
+
"danglingReferences": project.store.dangling_references(),
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _reference_key(issue: dict[str, str]) -> tuple[str, str, str]:
|
|
238
|
+
return issue["entityId"], issue["field"], issue["ref"]
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def apply_operations(
|
|
242
|
+
root: str | os.PathLike[str],
|
|
243
|
+
operations: list[Any],
|
|
244
|
+
*,
|
|
245
|
+
strict: bool = False,
|
|
246
|
+
batch_id: str | None = None,
|
|
247
|
+
dry_run: bool = False,
|
|
248
|
+
expected_sequence: int | None = None,
|
|
249
|
+
) -> dict[str, Any]:
|
|
250
|
+
project = open_project(root)
|
|
251
|
+
if expected_sequence is not None and expected_sequence != project.store.scene_sequence:
|
|
252
|
+
raise LocalProjectError("SCENE_SEQUENCE_CONFLICT",
|
|
253
|
+
f"expectedSceneSequence={expected_sequence} but the project is at "
|
|
254
|
+
f"{project.store.scene_sequence}; re-read and re-plan",
|
|
255
|
+
details={"expected": expected_sequence, "current": project.store.scene_sequence})
|
|
256
|
+
known = {_reference_key(issue) for issue in project.store.dangling_references()}
|
|
257
|
+
try:
|
|
258
|
+
applied = project.store.apply(operations, strict_versions=strict, batch_id=batch_id)
|
|
259
|
+
except (DirectorOperationError, DirectorConflictError) as exc:
|
|
260
|
+
raise LocalProjectError(exc.code, str(exc)) from exc
|
|
261
|
+
# The shared engine (like the Java service) accepts references to missing nodes; the CLI refuses
|
|
262
|
+
# to write new ones. References that were already dangling do not block unrelated edits.
|
|
263
|
+
created = [issue for issue in project.store.dangling_references() if _reference_key(issue) not in known]
|
|
264
|
+
if created:
|
|
265
|
+
refs = ", ".join(f"{issue['entityId']}.{issue['field']} -> {issue['ref']}" for issue in created[:5])
|
|
266
|
+
raise LocalProjectError("DANGLING_REFERENCE", f"the batch references missing nodes ({refs}); "
|
|
267
|
+
"nothing was written", details={"danglingReferences": created[:20]})
|
|
268
|
+
if dry_run:
|
|
269
|
+
validate_assembled(project.store.assemble())
|
|
270
|
+
else:
|
|
271
|
+
commit(project)
|
|
272
|
+
return {
|
|
273
|
+
**base_result(project),
|
|
274
|
+
"dryRun": dry_run,
|
|
275
|
+
"strictVersions": strict,
|
|
276
|
+
"operationCount": len(applied["accepted"]),
|
|
277
|
+
"accepted": [{key: value for key, value in item.items() if key != "payload"} for item in applied["accepted"]],
|
|
278
|
+
"warnings": applied["warnings"],
|
|
279
|
+
"entityVersions": project.store.entity_versions(),
|
|
280
|
+
}
|