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.
Files changed (332) hide show
  1. topview_3d_cli/__init__.py +3 -0
  2. topview_3d_cli/__main__.py +5 -0
  3. topview_3d_cli/_runtime/builtin-assets/LICENSE +396 -0
  4. topview_3d_cli/_runtime/builtin-assets/NOTICE +24 -0
  5. topview_3d_cli/_runtime/builtin-assets/characters/a3d_char_30ec53c93eae4173946b38639691ebe0.glb +0 -0
  6. topview_3d_cli/_runtime/builtin-assets/characters/a3d_char_461b4f4b76ae4b198f439fa4f6f743b6.glb +0 -0
  7. topview_3d_cli/_runtime/builtin-assets/characters/a3d_char_7d220ac359844c80a8faaf0e25478477.glb +0 -0
  8. topview_3d_cli/_runtime/builtin-assets/characters/a3d_char_de37873a79244fbe8a0660ea08d1b4ab.glb +0 -0
  9. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-attack-jab.webp +0 -0
  10. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-attack-lunge.webp +0 -0
  11. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-attack-rear.webp +0 -0
  12. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-crawling-forward-reaching-with-le.webp +0 -0
  13. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-exaggerated-punch-with-right-hand.webp +0 -0
  14. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-examine-fwd.webp +0 -0
  15. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-holding-bow.webp +0 -0
  16. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-jab-with-right-hand.webp +0 -0
  17. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-on-hands-and-toes-right-leg-forwa.webp +0 -0
  18. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-on-right-leg.webp +0 -0
  19. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-push-up.webp +0 -0
  20. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-pushing-backwards.webp +0 -0
  21. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-pushing-with-both-hands.webp +0 -0
  22. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-pushing-with-left-side-of-body.webp +0 -0
  23. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_action-sliding.webp +0 -0
  24. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-arms-spread-looking-upwards.webp +0 -0
  25. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-examine-fwd.webp +0 -0
  26. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-lean-fwd-right-hand-supporting.webp +0 -0
  27. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-leaning-against-object-look-l.webp +0 -0
  28. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-look-back.webp +0 -0
  29. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-look-down-back.webp +0 -0
  30. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-look-fwd-f.webp +0 -0
  31. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-look-fwd-m.webp +0 -0
  32. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-on-knees-arms-up.webp +0 -0
  33. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_crouch-sneaking.webp +0 -0
  34. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-l-toe-top-of-right-foot.webp +0 -0
  35. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-legs-spread-lean-l.webp +0 -0
  36. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-legs-spread-lean-r.webp +0 -0
  37. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-legs-x.webp +0 -0
  38. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-r-foot-arms-extended-backwards.webp +0 -0
  39. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-r-toe-arms-up.webp +0 -0
  40. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-r-toe-l-leg-up.webp +0 -0
  41. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-toes-arch.webp +0 -0
  42. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dance-toes-reach.webp +0 -0
  43. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dyn-floating-bent-forwards.webp +0 -0
  44. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dyn-floating-left-leg-bent-hands-spread.webp +0 -0
  45. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dyn-floating-legs-bent-and-arms-spread.webp +0 -0
  46. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dyn-floating-legs-raised-arms-extended-u.webp +0 -0
  47. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dyn-l-toe-look-l.webp +0 -0
  48. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dyn-leaping-forward-arms-extended-backwa.webp +0 -0
  49. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dynamic-float-arch.webp +0 -0
  50. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_dynamic-float-dive.webp +0 -0
  51. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-back-arched-arms-supp.webp +0 -0
  52. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-legs-to-side-arms-up.webp +0 -0
  53. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-legs-x-arms-supp.webp +0 -0
  54. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-back-left-leg-bent-hands-head.webp +0 -0
  55. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-back-left-leg-bent.webp +0 -0
  56. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-back-legs-x-hands-head.webp +0 -0
  57. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-left-side-left-arm-supporting-hea.webp +0 -0
  58. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-left-side-left-arm-supporting.webp +0 -0
  59. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-right-side-right-arm-supporting.webp +0 -0
  60. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-right-side-right-hand-supporting.webp +0 -0
  61. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-on-stomach-elbows-supporting.webp +0 -0
  62. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-right-arm-supporting-l-arm-up.webp +0 -0
  63. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_lay-right-leg-forward-arms-supp.webp +0 -0
  64. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-run-fwd.webp +0 -0
  65. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-run-reach.webp +0 -0
  66. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-sneaking-forward.webp +0 -0
  67. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-sprinting.webp +0 -0
  68. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-stepping-forward-look-l.webp +0 -0
  69. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-walk-fwd-look-r.webp +0 -0
  70. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-walk-fwd.webp +0 -0
  71. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-walking-arms-on-hips.webp +0 -0
  72. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-walking-right-arm-on-hip.webp +0 -0
  73. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_loco-walking.webp +0 -0
  74. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-arms-holding-left-knee-reclining.webp +0 -0
  75. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-arms-holding-right-knee.webp +0 -0
  76. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-arms-resting-on-knees.webp +0 -0
  77. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-arms-supp.webp +0 -0
  78. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-arms-up.webp +0 -0
  79. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-elbows-on-legs-lean-fwd.webp +0 -0
  80. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-elbows-on-legs-lean-l.webp +0 -0
  81. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-knees-up-hold.webp +0 -0
  82. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-lean-back-arms-supp.webp +0 -0
  83. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-lean-fwd-elbows-on-knees.webp +0 -0
  84. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-lean-fwd-hands-in-front.webp +0 -0
  85. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-legs-x-arms-x.webp +0 -0
  86. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-legs-x-hands-head.webp +0 -0
  87. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-legs-x-hold.webp +0 -0
  88. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-legs-x-reclining.webp +0 -0
  89. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-legs-x-right-arm-over-knee.webp +0 -0
  90. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reaching-hands-towards-toes.webp +0 -0
  91. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reaching-left-hand-towards-right-toe.webp +0 -0
  92. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reclined-facepalm.webp +0 -0
  93. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reclined-hands-head.webp +0 -0
  94. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reclined-left-leg-crossed-hands-head.webp +0 -0
  95. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reclined-left-leg-crossed.webp +0 -0
  96. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reclined-look-down.webp +0 -0
  97. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-reclined-right-leg-crossed-hands-hea.webp +0 -0
  98. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-rev-sit-hands-grasping.webp +0 -0
  99. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_sit-rev-sit-hands-relaxed.webp +0 -0
  100. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-arms-behind-back.webp +0 -0
  101. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-arms-down.webp +0 -0
  102. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-arms-slightly-spread.webp +0 -0
  103. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-bending-forward-arms-x-over-knees.webp +0 -0
  104. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-hands-on-hips-look-back.webp +0 -0
  105. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-l-hip-f.webp +0 -0
  106. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-l-hip-m.webp +0 -0
  107. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-l-leg-r-hip-l-arm-up.webp +0 -0
  108. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-lean-back-arms-behind.webp +0 -0
  109. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-lean-back-l-hip.webp +0 -0
  110. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-left-elbow-resting-obj.webp +0 -0
  111. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-left-leg-obj-left-hand-on-knee-r-h.webp +0 -0
  112. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-left-leg-obj-left-hand-on-knee.webp +0 -0
  113. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-legs-x-arms-up.webp +0 -0
  114. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-legs-x-l-hip.webp +0 -0
  115. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-legs-x-leaning-obj.webp +0 -0
  116. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-legs-x-left-arm-obj-r-hip.webp +0 -0
  117. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-look-down-r.webp +0 -0
  118. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-look-fwd.webp +0 -0
  119. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-look-l.webp +0 -0
  120. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-look-r.webp +0 -0
  121. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-r-arm-up-l-hip.webp +0 -0
  122. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-r-foot-l-leg-up.webp +0 -0
  123. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-r-foot-up.webp +0 -0
  124. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-r-hip-l-arm-up.webp +0 -0
  125. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-r-hip.webp +0 -0
  126. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-right-hand-behind-head.webp +0 -0
  127. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-right-leg-obj-arms-x.webp +0 -0
  128. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_stand-right-leg-obj-l-hip-right-hand-on.webp +0 -0
  129. topview_3d_cli/_runtime/builtin-assets/covers/a3d_pose_t-pose.webp +0 -0
  130. topview_3d_cli/_runtime/builtin-assets/manifest.json +2372 -0
  131. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-attack-jab.json +1 -0
  132. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-attack-lunge.json +1 -0
  133. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-attack-rear.json +1 -0
  134. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-crawling-forward-reaching-with-le.json +1 -0
  135. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-exaggerated-punch-with-right-hand.json +1 -0
  136. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-examine-fwd.json +1 -0
  137. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-holding-bow.json +1 -0
  138. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-jab-with-right-hand.json +1 -0
  139. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-on-hands-and-toes-right-leg-forwa.json +1 -0
  140. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-on-right-leg.json +1 -0
  141. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-push-up.json +1 -0
  142. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-pushing-backwards.json +1 -0
  143. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-pushing-with-both-hands.json +1 -0
  144. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-pushing-with-left-side-of-body.json +1 -0
  145. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_action-sliding.json +1 -0
  146. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-arms-spread-looking-upwards.json +1 -0
  147. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-examine-fwd.json +1 -0
  148. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-lean-fwd-right-hand-supporting.json +1 -0
  149. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-leaning-against-object-look-l.json +1 -0
  150. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-look-back.json +1 -0
  151. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-look-down-back.json +1 -0
  152. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-look-fwd-f.json +1 -0
  153. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-look-fwd-m.json +1 -0
  154. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-on-knees-arms-up.json +1 -0
  155. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_crouch-sneaking.json +1 -0
  156. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-l-toe-top-of-right-foot.json +1 -0
  157. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-legs-spread-lean-l.json +1 -0
  158. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-legs-spread-lean-r.json +1 -0
  159. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-legs-x.json +1 -0
  160. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-r-foot-arms-extended-backwards.json +1 -0
  161. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-r-toe-arms-up.json +1 -0
  162. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-r-toe-l-leg-up.json +1 -0
  163. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-toes-arch.json +1 -0
  164. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dance-toes-reach.json +1 -0
  165. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dyn-floating-bent-forwards.json +1 -0
  166. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dyn-floating-left-leg-bent-hands-spread.json +1 -0
  167. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dyn-floating-legs-bent-and-arms-spread.json +1 -0
  168. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dyn-floating-legs-raised-arms-extended-u.json +1 -0
  169. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dyn-l-toe-look-l.json +1 -0
  170. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dyn-leaping-forward-arms-extended-backwa.json +1 -0
  171. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dynamic-float-arch.json +1 -0
  172. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_dynamic-float-dive.json +1 -0
  173. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-back-arched-arms-supp.json +1 -0
  174. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-legs-to-side-arms-up.json +1 -0
  175. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-legs-x-arms-supp.json +1 -0
  176. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-back-left-leg-bent-hands-head.json +1 -0
  177. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-back-left-leg-bent.json +1 -0
  178. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-back-legs-x-hands-head.json +1 -0
  179. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-left-side-left-arm-supporting-hea.json +1 -0
  180. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-left-side-left-arm-supporting.json +1 -0
  181. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-right-side-right-arm-supporting.json +1 -0
  182. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-right-side-right-hand-supporting.json +1 -0
  183. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-on-stomach-elbows-supporting.json +1 -0
  184. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-right-arm-supporting-l-arm-up.json +1 -0
  185. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_lay-right-leg-forward-arms-supp.json +1 -0
  186. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-run-fwd.json +1 -0
  187. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-run-reach.json +1 -0
  188. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-sneaking-forward.json +1 -0
  189. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-sprinting.json +1 -0
  190. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-stepping-forward-look-l.json +1 -0
  191. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-walk-fwd-look-r.json +1 -0
  192. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-walk-fwd.json +1 -0
  193. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-walking-arms-on-hips.json +1 -0
  194. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-walking-right-arm-on-hip.json +1 -0
  195. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_loco-walking.json +1 -0
  196. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-arms-holding-left-knee-reclining.json +1 -0
  197. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-arms-holding-right-knee.json +1 -0
  198. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-arms-resting-on-knees.json +1 -0
  199. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-arms-supp.json +1 -0
  200. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-arms-up.json +1 -0
  201. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-elbows-on-legs-lean-fwd.json +1 -0
  202. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-elbows-on-legs-lean-l.json +1 -0
  203. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-knees-up-hold.json +1 -0
  204. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-lean-back-arms-supp.json +1 -0
  205. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-lean-fwd-elbows-on-knees.json +1 -0
  206. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-lean-fwd-hands-in-front.json +1 -0
  207. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-legs-x-arms-x.json +1 -0
  208. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-legs-x-hands-head.json +1 -0
  209. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-legs-x-hold.json +1 -0
  210. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-legs-x-reclining.json +1 -0
  211. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-legs-x-right-arm-over-knee.json +1 -0
  212. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reaching-hands-towards-toes.json +1 -0
  213. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reaching-left-hand-towards-right-toe.json +1 -0
  214. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reclined-facepalm.json +1 -0
  215. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reclined-hands-head.json +1 -0
  216. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reclined-left-leg-crossed-hands-head.json +1 -0
  217. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reclined-left-leg-crossed.json +1 -0
  218. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reclined-look-down.json +1 -0
  219. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-reclined-right-leg-crossed-hands-hea.json +1 -0
  220. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-rev-sit-hands-grasping.json +1 -0
  221. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_sit-rev-sit-hands-relaxed.json +1 -0
  222. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-arms-behind-back.json +1 -0
  223. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-arms-down.json +1 -0
  224. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-arms-slightly-spread.json +1 -0
  225. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-bending-forward-arms-x-over-knees.json +1 -0
  226. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-hands-on-hips-look-back.json +1 -0
  227. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-l-hip-f.json +1 -0
  228. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-l-hip-m.json +1 -0
  229. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-l-leg-r-hip-l-arm-up.json +1 -0
  230. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-lean-back-arms-behind.json +1 -0
  231. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-lean-back-l-hip.json +1 -0
  232. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-left-elbow-resting-obj.json +1 -0
  233. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-left-leg-obj-left-hand-on-knee-r-h.json +1 -0
  234. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-left-leg-obj-left-hand-on-knee.json +1 -0
  235. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-legs-x-arms-up.json +1 -0
  236. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-legs-x-l-hip.json +1 -0
  237. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-legs-x-leaning-obj.json +1 -0
  238. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-legs-x-left-arm-obj-r-hip.json +1 -0
  239. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-look-down-r.json +1 -0
  240. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-look-fwd.json +1 -0
  241. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-look-l.json +1 -0
  242. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-look-r.json +1 -0
  243. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-r-arm-up-l-hip.json +1 -0
  244. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-r-foot-l-leg-up.json +1 -0
  245. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-r-foot-up.json +1 -0
  246. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-r-hip-l-arm-up.json +1 -0
  247. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-r-hip.json +1 -0
  248. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-right-hand-behind-head.json +1 -0
  249. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-right-leg-obj-arms-x.json +1 -0
  250. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_stand-right-leg-obj-l-hip-right-hand-on.json +1 -0
  251. topview_3d_cli/_runtime/builtin-assets/poses/a3d_pose_t-pose.json +1 -0
  252. topview_3d_cli/_runtime/director-cli/applyIntent.mjs +35 -0
  253. topview_3d_cli/_runtime/director-cli/browser.mjs +47 -0
  254. topview_3d_cli/_runtime/director-cli/cli.mjs +56 -0
  255. topview_3d_cli/_runtime/director-cli/editSequence.mjs +125 -0
  256. topview_3d_cli/_runtime/director-cli/evaluate.mjs +254 -0
  257. topview_3d_cli/_runtime/director-cli/mediaUrl.mjs +47 -0
  258. topview_3d_cli/_runtime/director-cli/node_modules/@topview/3d-builder/dist/draco/draco_decoder.js +34 -0
  259. topview_3d_cli/_runtime/director-cli/node_modules/@topview/3d-builder/dist/draco/draco_decoder.wasm +0 -0
  260. topview_3d_cli/_runtime/director-cli/node_modules/@topview/3d-builder/dist/draco/draco_wasm_wrapper.js +117 -0
  261. topview_3d_cli/_runtime/director-cli/node_modules/@topview/3d-builder/dist/engine/index.mjs +22219 -0
  262. topview_3d_cli/_runtime/director-cli/node_modules/@topview/3d-builder/dist/evaluate/index.mjs +4229 -0
  263. topview_3d_cli/_runtime/director-cli/node_modules/@topview/3d-builder/dist/headless/index.mjs +22832 -0
  264. topview_3d_cli/_runtime/director-cli/node_modules/@topview/3d-builder/package.json +75 -0
  265. topview_3d_cli/_runtime/director-cli/node_modules/three/LICENSE +21 -0
  266. topview_3d_cli/_runtime/director-cli/node_modules/three/build/three.core.js +59732 -0
  267. topview_3d_cli/_runtime/director-cli/node_modules/three/build/three.module.js +19552 -0
  268. topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/controls/OrbitControls.js +1963 -0
  269. topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/curves/NURBSCurve.js +155 -0
  270. topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/curves/NURBSUtils.js +532 -0
  271. topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/libs/draco/draco_decoder.js +34 -0
  272. topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/libs/draco/draco_decoder.wasm +0 -0
  273. topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/libs/draco/draco_wasm_wrapper.js +117 -0
  274. topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/libs/fflate.module.js +2672 -0
  275. topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/lines/LineMaterial.js +703 -0
  276. topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/lines/LineSegments2.js +426 -0
  277. topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/lines/LineSegmentsGeometry.js +298 -0
  278. topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/loaders/DRACOLoader.js +739 -0
  279. topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/loaders/FBXLoader.js +4580 -0
  280. topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/loaders/GLTFLoader.js +4860 -0
  281. topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/utils/BufferGeometryUtils.js +1434 -0
  282. topview_3d_cli/_runtime/director-cli/node_modules/three/examples/jsm/utils/SkeletonUtils.js +496 -0
  283. topview_3d_cli/_runtime/director-cli/node_modules/three/package.json +123 -0
  284. topview_3d_cli/_runtime/director-cli/node_modules/zod/LICENSE +21 -0
  285. topview_3d_cli/_runtime/director-cli/node_modules/zod/index.js +4 -0
  286. topview_3d_cli/_runtime/director-cli/node_modules/zod/package.json +118 -0
  287. topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/ZodError.js +133 -0
  288. topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/errors.js +9 -0
  289. topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/external.js +6 -0
  290. topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/helpers/errorUtil.js +6 -0
  291. topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/helpers/parseUtil.js +109 -0
  292. topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/helpers/typeAliases.js +1 -0
  293. topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/helpers/util.js +133 -0
  294. topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/locales/en.js +109 -0
  295. topview_3d_cli/_runtime/director-cli/node_modules/zod/v3/types.js +3693 -0
  296. topview_3d_cli/_runtime/director-cli/package.json +6 -0
  297. topview_3d_cli/_runtime/director-cli/playwright.mjs +26 -0
  298. topview_3d_cli/_runtime/director-cli/render.mjs +336 -0
  299. topview_3d_cli/_runtime/director-cli/static/headless.html +124 -0
  300. topview_3d_cli/_runtime/director-cli/static/measure.mjs +61 -0
  301. topview_3d_cli/_runtime/director-cli/static/pose.mjs +71 -0
  302. topview_3d_cli/_runtime/director-cli/staticIntent.mjs +135 -0
  303. topview_3d_cli/_runtime/runtime.json +6 -0
  304. topview_3d_cli/bom.py +326 -0
  305. topview_3d_cli/contracts/director-v1/schema/director-document.schema.json +1097 -0
  306. topview_3d_cli/contracts/director-v1/schema/fcurves-compact-v1.schema.json +105 -0
  307. topview_3d_cli/director_camera_view.py +54 -0
  308. topview_3d_cli/director_document.py +793 -0
  309. topview_3d_cli/director_geometry.py +66 -0
  310. topview_3d_cli/director_operations.py +679 -0
  311. topview_3d_cli/director_static.py +134 -0
  312. topview_3d_cli/local_assets.py +248 -0
  313. topview_3d_cli/local_bom.py +108 -0
  314. topview_3d_cli/local_catalog.py +156 -0
  315. topview_3d_cli/local_cli.py +493 -0
  316. topview_3d_cli/local_edit.py +350 -0
  317. topview_3d_cli/local_errors.py +104 -0
  318. topview_3d_cli/local_inspect.py +302 -0
  319. topview_3d_cli/local_project.py +280 -0
  320. topview_3d_cli/local_read.py +139 -0
  321. topview_3d_cli/local_render.py +97 -0
  322. topview_3d_cli/node_batch.py +299 -0
  323. topview_3d_cli/renderer.py +75 -0
  324. topview_3d_cli/runtime.py +107 -0
  325. topview_3d_cli-0.1.0.dist-info/METADATA +48 -0
  326. topview_3d_cli-0.1.0.dist-info/RECORD +332 -0
  327. topview_3d_cli-0.1.0.dist-info/WHEEL +5 -0
  328. topview_3d_cli-0.1.0.dist-info/entry_points.txt +2 -0
  329. topview_3d_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
  330. topview_3d_cli-0.1.0.dist-info/licenses/LICENSE-ASSETS +396 -0
  331. topview_3d_cli-0.1.0.dist-info/licenses/NOTICE +18 -0
  332. topview_3d_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,139 @@
1
+ """Read-only views: document outline and entities, render runs and their PNGs, camera presets."""
2
+ from __future__ import annotations
3
+
4
+ import hashlib
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from topview_3d_cli.director_document import get_director_entity, summarize_director_snapshot
10
+ from topview_3d_cli.local_errors import LocalProjectError
11
+ from topview_3d_cli.local_project import base_result, document_get, open_project
12
+ from topview_3d_cli.renderer import director_cli
13
+
14
+ ENTITY_TYPES = ("node", "clip", "fcurves")
15
+ PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
16
+
17
+
18
+ def document_summary(directory: str) -> dict[str, Any]:
19
+ project = open_project(directory)
20
+ summary = summarize_director_snapshot(project.store.assemble())
21
+ summary["message"] = ("Outline only: curves and full nodes omitted. "
22
+ "Use `document get <dir> --entity <id>` for one node, clip or fcurves shard.")
23
+ return {**base_result(project), "summary": summary}
24
+
25
+
26
+ def document_entity(directory: str, entity_id: str, *, entity_type: str | None = None,
27
+ include_curves: bool = False) -> dict[str, Any]:
28
+ """One node, clip or ``fcurves__<nodeId>`` shard; the type is inferred when omitted."""
29
+ project = open_project(directory)
30
+ snapshot = project.store.assemble()
31
+ if entity_type:
32
+ types = [entity_type]
33
+ elif entity_id.startswith("fcurves__"):
34
+ types = ["fcurves"]
35
+ else:
36
+ types = ["node", "clip", "fcurves"]
37
+ for candidate in types:
38
+ try:
39
+ found = get_director_entity(snapshot, candidate, entity_id, include_curves=include_curves)
40
+ except ValueError:
41
+ continue
42
+ key = found.get("entityId", entity_id)
43
+ return {**base_result(project), **found, "entityId": key,
44
+ "entityVersion": snapshot["entityVersions"].get(key)}
45
+ raise LocalProjectError("DOCUMENT_ENTITY_NOT_FOUND", f"no {' or '.join(types)} {entity_id!r} in the document")
46
+
47
+
48
+ def document_view(directory: str, *, summary: bool, entity: str | None, entity_type: str | None,
49
+ include_curves: bool) -> dict[str, Any]:
50
+ if summary and entity:
51
+ raise LocalProjectError("USAGE_INVALID", "use either --summary or --entity")
52
+ if entity_type and not entity:
53
+ raise LocalProjectError("USAGE_INVALID", "--type needs --entity")
54
+ if summary:
55
+ return document_summary(directory)
56
+ if entity:
57
+ return document_entity(directory, entity, entity_type=entity_type, include_curves=include_curves)
58
+ return document_get(directory)
59
+
60
+
61
+ def _read_run(run_dir: Path) -> dict[str, Any] | None:
62
+ try:
63
+ manifest = json.loads((run_dir / "render.json").read_text(encoding="utf-8"))
64
+ except (OSError, json.JSONDecodeError):
65
+ return None
66
+ return manifest if isinstance(manifest, dict) else None
67
+
68
+
69
+ def _runs(renders: Path) -> list[tuple[Path, dict[str, Any]]]:
70
+ rows = []
71
+ for run_dir in renders.iterdir() if renders.is_dir() else []:
72
+ manifest = _read_run(run_dir) if run_dir.is_dir() else None
73
+ if manifest:
74
+ rows.append((run_dir, manifest))
75
+ rows.sort(key=lambda row: (str(row[1].get("createdAt") or ""), row[0].name))
76
+ return rows
77
+
78
+
79
+ def renders_list(directory: str) -> dict[str, Any]:
80
+ project = open_project(directory)
81
+ sequence = project.store.scene_sequence
82
+ runs = [{
83
+ "runId": run_dir.name,
84
+ "createdAt": manifest.get("createdAt"),
85
+ "cameraNodeId": manifest.get("cameraNodeId"),
86
+ "sceneSequence": manifest.get("sceneSequence"),
87
+ "stale": manifest.get("sceneSequence") != sequence,
88
+ "frames": [frame.get("frame") for frame in manifest.get("frames") or []],
89
+ "width": manifest.get("width"),
90
+ "height": manifest.get("height"),
91
+ "blank": manifest.get("blank"),
92
+ "blockedRequests": len(manifest.get("blockedRequests") or []),
93
+ } for run_dir, manifest in _runs(project.paths.renders)]
94
+ return {**base_result(project), "count": len(runs), "runs": runs}
95
+
96
+
97
+ def renders_show(directory: str, run_id: str | None = None, *, frame: int | None = None) -> dict[str, Any]:
98
+ """Path and checksum of one PNG inside the project (the contact sheet unless --frame)."""
99
+ project = open_project(directory)
100
+ runs = _runs(project.paths.renders)
101
+ if run_id is None:
102
+ if not runs:
103
+ raise LocalProjectError("RENDER_NOT_FOUND", "the project has no renders yet; run `topview-3d-cli render`")
104
+ run_dir, manifest = runs[-1]
105
+ else:
106
+ run_dir, manifest = next(((path, data) for path, data in runs if path.name == run_id), (None, None))
107
+ if run_dir is None:
108
+ raise LocalProjectError("RENDER_NOT_FOUND", f"no render run {run_id!r}")
109
+ if frame is None:
110
+ entry = manifest.get("contactSheet") or {}
111
+ else:
112
+ entry = next((item for item in manifest.get("frames") or [] if item.get("frame") == frame), None)
113
+ if entry is None:
114
+ raise LocalProjectError("RENDER_NOT_FOUND", f"run {run_dir.name} has no frame {frame}")
115
+ path = (run_dir / str(entry.get("file") or "")).resolve()
116
+ if not entry.get("file") or path.parent != run_dir.resolve() or not path.is_file():
117
+ raise LocalProjectError("RENDER_NOT_FOUND", f"{run_dir.name}: image file missing")
118
+ data = path.read_bytes()
119
+ digest = hashlib.sha256(data).hexdigest()
120
+ if not data.startswith(PNG_SIGNATURE) or (entry.get("sha256") and entry["sha256"] != digest):
121
+ raise LocalProjectError("RENDER_IMAGE_INVALID", f"{path} is not the PNG recorded in render.json")
122
+ return {
123
+ **base_result(project),
124
+ "runId": run_dir.name,
125
+ "kind": "contactSheet" if frame is None else "frame",
126
+ "frame": frame,
127
+ "path": str(path),
128
+ "mimeType": "image/png",
129
+ "sizeBytes": len(data),
130
+ "sha256": digest,
131
+ "cameraNodeId": manifest.get("cameraNodeId"),
132
+ "sceneSequence": manifest.get("sceneSequence"),
133
+ "stale": manifest.get("sceneSequence") != project.store.scene_sequence,
134
+ }
135
+
136
+
137
+ def camera_presets() -> dict[str, Any]:
138
+ listed = director_cli("list-camera-presets")
139
+ return {"ok": True, "presets": listed.get("presets", [])}
@@ -0,0 +1,97 @@
1
+ """Render runs under ``.topview3d/renders/<runId>`` with the Node renderer and local model files."""
2
+ from __future__ import annotations
3
+
4
+ import hashlib
5
+ import json
6
+ import shutil
7
+ import uuid
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from topview_3d_cli.local_assets import asset_catalog, check_render_assets, local_asset_map
12
+ from topview_3d_cli.local_errors import LocalProjectError
13
+ from topview_3d_cli.local_project import CLI_VERSION, Project, open_project
14
+ from topview_3d_cli.renderer import director_cli
15
+ from topview_3d_cli.runtime import builder_version
16
+
17
+ # Console noise from the headless page, not scene problems: the builder's two bundles each import
18
+ # Mediabunny, and SwiftShader reports ReadPixels stalls.
19
+ BENIGN_RENDER_WARNINGS = ("Mediabunny was loaded twice", "GPU stall due to ReadPixels")
20
+ RENDER_PAYLOAD_KEYS = {"frames", "width", "height", "cameraNodeId", "publicAssetBase"}
21
+
22
+
23
+ def document_sha256(document: dict[str, Any]) -> str:
24
+ canonical = json.dumps(document, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
25
+ return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
26
+
27
+
28
+ def require_camera(document: dict[str, Any], camera_id: str | None) -> str:
29
+ """The camera to use: ``camera_id`` (which must exist and be a camera) or the document's default."""
30
+ nodes = document["content"]["nodes"]
31
+ if camera_id:
32
+ node = next((item for item in nodes if item["id"] == camera_id), None)
33
+ if node is None:
34
+ raise LocalProjectError("CAMERA_NOT_FOUND", f"no node {camera_id!r}; cameras: "
35
+ f"{[item['id'] for item in nodes if item.get('type') == 'camera']}",
36
+ details={"cameraNodeId": camera_id})
37
+ if node.get("type") != "camera":
38
+ raise LocalProjectError("CAMERA_REQUIRED", f"{camera_id} is a {node.get('type')}, not a camera",
39
+ details={"cameraNodeId": camera_id})
40
+ return camera_id
41
+ active = document["content"].get("activeShotCameraNodeId")
42
+ if any(item["id"] == active and item.get("type") == "camera" for item in nodes):
43
+ return active
44
+ fallback = next((item["id"] for item in nodes if item.get("type") == "camera"), None)
45
+ if fallback is None:
46
+ raise LocalProjectError("CAMERA_REQUIRED", "the scene has no camera; add one with `node batch` add_camera")
47
+ return fallback
48
+
49
+
50
+ def render_snapshot(project: Project, assembled: dict[str, Any], options: dict[str, Any]) -> dict[str, Any]:
51
+ """One render run of ``assembled``; the run directory is removed again when rendering fails."""
52
+ body: dict[str, Any] = {"frames": [0], **options}
53
+ mapping = local_asset_map(asset_catalog(project.paths))
54
+ warnings = check_render_assets(assembled["document"], mapping, str(body.get("publicAssetBase") or ""))
55
+ run_id = uuid.uuid4().hex
56
+ output_dir = project.paths.renders / run_id
57
+ body.update({
58
+ "document": assembled["document"],
59
+ "fcurves": assembled["fcurves"],
60
+ "sceneSequence": assembled["sceneSequence"],
61
+ "outputDir": str(output_dir),
62
+ "publicAssetBase": body.get("publicAssetBase") or "",
63
+ "localAssets": mapping,
64
+ "metadata": {
65
+ "revision": project.metadata.get("revision", 0),
66
+ "documentSha256": document_sha256(assembled["document"]),
67
+ "builderVersion": builder_version(),
68
+ "cliVersion": CLI_VERSION,
69
+ },
70
+ })
71
+ try:
72
+ result = director_cli("render-frames", body)
73
+ except LocalProjectError:
74
+ shutil.rmtree(output_dir, ignore_errors=True)
75
+ raise
76
+ if isinstance(result.get("warnings"), list):
77
+ result["warnings"] = [item for item in result["warnings"]
78
+ if not any(marker in str(item) for marker in BENIGN_RENDER_WARNINGS)]
79
+ return {"ok": True, "runId": run_id, **body["metadata"], "sceneSequence": assembled["sceneSequence"],
80
+ "warnings": warnings, "result": result}
81
+
82
+
83
+ def render(directory: str, payload_path: str | None = None) -> dict[str, Any]:
84
+ project = open_project(directory)
85
+ options: dict[str, Any] = {}
86
+ if payload_path:
87
+ try:
88
+ supplied = json.loads(Path(payload_path).read_text(encoding="utf-8"))
89
+ except (OSError, json.JSONDecodeError) as exc:
90
+ raise LocalProjectError("RENDER_PAYLOAD_INVALID", str(exc)) from exc
91
+ if not isinstance(supplied, dict) or set(supplied) - RENDER_PAYLOAD_KEYS:
92
+ raise LocalProjectError("RENDER_PAYLOAD_INVALID",
93
+ f"payload must be an object with keys from {sorted(RENDER_PAYLOAD_KEYS)}")
94
+ options.update(supplied)
95
+ assembled = project.store.assemble()
96
+ options["cameraNodeId"] = require_camera(assembled["document"], options.get("cameraNodeId"))
97
+ return render_snapshot(project, assembled, options)
@@ -0,0 +1,299 @@
1
+ """Typed, ordered node changes (``node batch``) and their compilation to one operation batch.
2
+
3
+ Staging runs on a copy of the stored snapshot, so a rejected change writes nothing.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import copy
8
+ from typing import Any
9
+
10
+ from jsonschema import Draft7Validator
11
+
12
+ from topview_3d_cli.director_document import apply_scene_set, fcurve_node_id, new_scene_operation_id
13
+ from topview_3d_cli.director_static import delete_node_operations
14
+
15
+ CLIP_ARRAYS = ("cameraMotionClips", "motionClips", "pathMotionClips")
16
+ BATCH_OPERATION_LIMIT = 64
17
+
18
+
19
+ def obj(properties, required=()):
20
+ return {"type": "object", "properties": properties, "required": list(required),
21
+ "additionalProperties": False}
22
+
23
+
24
+ def batch_schema(*, require_sequence: bool = True) -> dict[str, Any]:
25
+ identifier = {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,128}$"}
26
+ number = {"type": "number"}
27
+ positive = {"type": "number", "exclusiveMinimum": 0}
28
+ vector = {**obj({axis: number for axis in "xyz"}), "minProperties": 1}
29
+ scale = {**obj({axis: positive for axis in "xyz"}), "minProperties": 1}
30
+ transform = {"position": vector, "rotation": vector, "scale": scale}
31
+ point = obj({axis: number for axis in "xyz"}, "xyz")
32
+ view = {"oneOf": [
33
+ obj({"mode": {"const": "world"}, "position": point, "target": point},
34
+ ("mode", "position", "target")),
35
+ obj({"mode": {"const": "subject"}, "subjectNodeId": identifier,
36
+ "offset": point, "targetOffset": point},
37
+ ("mode", "subjectNodeId", "offset", "targetOffset")),
38
+ ]}
39
+ name = {"type": "string", "minLength": 1, "maxLength": 128, "pattern": "\\S"}
40
+ camera = {
41
+ "fov": {"type": "number", "minimum": 12, "maximum": 120},
42
+ "distance": {"type": "number", "minimum": 0.1, "maximum": 200},
43
+ "view": view,
44
+ }
45
+ geometry = []
46
+ for kind, dimensions in (
47
+ ("BoxGeometry", ("width", "height", "depth")),
48
+ ("SphereGeometry", ("radius",)),
49
+ ("CylinderGeometry", ("radiusTop", "radiusBottom", "height")),
50
+ ("ConeGeometry", ("radius", "height")),
51
+ ):
52
+ parameters = {key: positive for key in dimensions}
53
+ if kind == "CylinderGeometry":
54
+ parameters.update({key: {"type": "number", "minimum": 0}
55
+ for key in ("radiusTop", "radiusBottom")})
56
+ if kind in {"CylinderGeometry", "ConeGeometry"}:
57
+ parameters["radialSegments"] = {"type": "integer", "minimum": 3, "maximum": 128}
58
+ geometry.append(obj({"kind": {"const": kind}, "parameters": obj(parameters, dimensions)},
59
+ ("kind", "parameters")))
60
+
61
+ def action(kind, fields, required=(), description=""):
62
+ schema = obj({"action": {"const": kind}, "nodeId": identifier, **fields},
63
+ ("action", "nodeId", *required))
64
+ if description:
65
+ schema["description"] = description
66
+ return schema
67
+ return obj({
68
+ "expectedSceneSequence": {"type": "integer", "minimum": 0},
69
+ "changes": {"type": "array", "minItems": 1, "maxItems": 64, "items": {"oneOf": [
70
+ action("add_primitive", {"name": name, "primitive": {"oneOf": geometry}, **transform},
71
+ ("primitive",), "Create a new primitive. Requires primitive geometry."),
72
+ action("add_library", {"kind": {"enum": ["characters", "props"]},
73
+ "libraryId": identifier, "name": name, **transform}, ("kind", "libraryId"),
74
+ "Create a new character or prop from the asset library. Requires kind and libraryId."),
75
+ action("add_camera", {"presetId": name, "subjectNodeId": identifier,
76
+ "name": name, **transform, **camera}, ("presetId",),
77
+ "Create a new camera. Requires presetId. Do not use update to create cameras."),
78
+ {**action("update", {"name": name, **transform, **camera},
79
+ description="Change an existing node: name, partial transform, fov, distance. "
80
+ "Does not create nodes and does not accept presetId."),
81
+ "minProperties": 3},
82
+ action("delete", {}, description="Delete an existing node by nodeId."),
83
+ action("repeat_primitive", {
84
+ "name": name, "primitive": {"oneOf": geometry}, **transform,
85
+ "count": {"type": "integer", "minimum": 2, "maximum": 32},
86
+ "step": point,
87
+ }, ("primitive", "count", "step"),
88
+ "Create regularly spaced primitive copies nodeId_1 through nodeId_count."),
89
+ ]}},
90
+ }, ("expectedSceneSequence", "changes") if require_sequence else ("changes",))
91
+
92
+
93
+ BATCH_SCHEMA = batch_schema()
94
+
95
+
96
+ def schema_error(schema: dict[str, Any], arguments: Any) -> str | None:
97
+ """First schema violation as ``path: message``, preferring the change's own action schema."""
98
+ error = next(Draft7Validator(schema).iter_errors(arguments), None)
99
+ if error is None:
100
+ return None
101
+ for index, change in enumerate(arguments.get("changes", []) if isinstance(arguments, dict) else []):
102
+ if not isinstance(change, dict):
103
+ continue
104
+ selected = next((item for item in schema["properties"]["changes"]["items"]["oneOf"]
105
+ if item["properties"]["action"]["const"] == change.get("action")), None)
106
+ detail = next(Draft7Validator(selected).iter_errors(change), None) if selected else None
107
+ if detail and list(detail.path)[:1] == ["primitive"] and isinstance(change.get("primitive"), dict):
108
+ return f"changes.{index}.{_primitive_error(selected['properties']['primitive'], change['primitive'])}"
109
+ if detail:
110
+ return f"changes.{index}.{'.'.join(map(str, detail.path))}: {detail.message[:450]}"
111
+ return f"{'.'.join(map(str, error.path))}: {error.message[:450]}"
112
+
113
+
114
+ def _primitive_error(schema: dict[str, Any], primitive: dict[str, Any]) -> str:
115
+ """Validate against the branch for ``primitive.kind`` so the message names the kind and field."""
116
+ branches = {branch["properties"]["kind"]["const"]: branch for branch in schema["oneOf"]}
117
+ kind = primitive.get("kind")
118
+ if kind not in branches:
119
+ return f"primitive.kind: {kind!r} is not one of {sorted(branches)}"
120
+ detail = next(Draft7Validator(branches[kind]).iter_errors(primitive), None)
121
+ if detail is None:
122
+ return f"primitive: invalid {kind}"
123
+ return f"primitive.{'.'.join(map(str, detail.path))}: {detail.message[:400]} ({kind} takes " \
124
+ f"{sorted(branches[kind]['properties']['parameters']['properties'])})"
125
+
126
+
127
+ def expand_changes(changes: list[dict[str, Any]]) -> list[dict[str, Any]]:
128
+ """``repeat_primitive`` becomes ``add_primitive`` for nodeId_1..nodeId_count."""
129
+ expanded = []
130
+ for change in changes:
131
+ if change["action"] != "repeat_primitive":
132
+ expanded.append(change)
133
+ continue
134
+ for copy_index in range(change["count"]):
135
+ expanded.append({
136
+ **{key: value for key, value in change.items() if key not in {"count", "step"}},
137
+ "action": "add_primitive",
138
+ "nodeId": f"{change['nodeId']}_{copy_index + 1}",
139
+ "position": {
140
+ axis: change.get("position", {}).get(axis, 0) + copy_index * change["step"][axis]
141
+ for axis in "xyz"
142
+ },
143
+ })
144
+ return expanded
145
+
146
+
147
+ def stage_delete(snapshot: dict[str, Any], node_id: str) -> set[str]:
148
+ """Apply the cascading delete of ``node_id`` to a staged snapshot; return deleted fcurve ids."""
149
+ batch, _ = delete_node_operations(snapshot, node_id)
150
+ for operation in batch["operations"]:
151
+ kind, entity_id, payload = (operation[key] for key in ("kind", "entityId", "payload"))
152
+ content = snapshot["document"]["content"]
153
+ if kind == "director.node.delete":
154
+ content["nodes"] = [node for node in content["nodes"] if node["id"] != entity_id]
155
+ elif kind == "director.node.upsert":
156
+ content["nodes"] = [payload["node"] if node["id"] == entity_id else node for node in content["nodes"]]
157
+ elif kind == "director.clip.delete":
158
+ for key in CLIP_ARRAYS:
159
+ animation = content["timeline"]["animation"]
160
+ animation[key] = [clip for clip in animation.get(key, []) if clip["id"] != entity_id]
161
+ elif kind == "director.fcurves.delete":
162
+ curves = snapshot.get("fcurves") or {}
163
+ snapshot["fcurves"] = curves
164
+ curves.pop(entity_id, None)
165
+ if curves.get("encoding") == "compact-v1":
166
+ curves["fcurves"] = [c for c in curves.get("fcurves", []) if fcurve_node_id(c) != node_id]
167
+ elif kind == "director.scene.set":
168
+ snapshot["document"] = apply_scene_set(snapshot["document"], payload["scene"])
169
+ return {op["entityId"] for op in batch["operations"] if op["kind"] == "director.fcurves.delete"}
170
+
171
+
172
+ def scene_fields(document: dict[str, Any]) -> dict[str, Any]:
173
+ content = copy.deepcopy(document["content"])
174
+ content.pop("nodes", None)
175
+ for key in CLIP_ARRAYS:
176
+ content.get("timeline", {}).get("animation", {}).pop(key, None)
177
+ return content
178
+
179
+
180
+ def compile_operations(before: dict[str, Any], after: dict[str, Any], deleted_curves: set[str]):
181
+ """Diff two snapshots into director operations; returns (operations, old nodes, new nodes)."""
182
+ operations = []
183
+
184
+ def add(kind, entity_id, payload):
185
+ operations.append({"operationId": new_scene_operation_id("batch_node"),
186
+ "baseSequence": before["sceneSequence"], "entityId": entity_id,
187
+ "expectedEntityVersion": (before.get("entityVersions") or {}).get(entity_id),
188
+ "kind": kind, "payload": payload})
189
+ old = {node["id"]: node for node in before["document"]["content"]["nodes"]}
190
+ new = {node["id"]: node for node in after["document"]["content"]["nodes"]}
191
+ for node_id, node in new.items():
192
+ if old.get(node_id) != node:
193
+ add("director.node.upsert", node_id, {"node": node})
194
+ for node_id in old.keys() - new.keys():
195
+ add("director.node.delete", node_id, {})
196
+ for key in CLIP_ARRAYS:
197
+ old_clips = before["document"]["content"]["timeline"]["animation"].get(key, [])
198
+ kept = {clip["id"] for clip in after["document"]["content"]["timeline"]["animation"].get(key, [])}
199
+ for clip in old_clips:
200
+ if clip["id"] not in kept:
201
+ add("director.clip.delete", clip["id"], {})
202
+ for entity_id in sorted(deleted_curves):
203
+ add("director.fcurves.delete", entity_id, {})
204
+ old_scene, new_scene = scene_fields(before["document"]), scene_fields(after["document"])
205
+ delta = {key: value for key, value in new_scene.items() if old_scene.get(key) != value}
206
+ if delta:
207
+ add("director.scene.set", "director", {"scene": {"content": {"version": 1, **delta}}})
208
+ if len(operations) > BATCH_OPERATION_LIMIT:
209
+ raise ValueError("DIRECTOR_BATCH_OPERATION_LIMIT: split at dependency boundaries (including delete cleanup)")
210
+ return operations, old, new
211
+
212
+
213
+ def affected_nodes(snapshot: dict[str, Any], node_ids) -> list[str]:
214
+ """Include children and bound cameras whose world state follows edited nodes."""
215
+ nodes = snapshot["document"]["content"]["nodes"]
216
+ affected = set(node_ids)
217
+ while True:
218
+ previous = len(affected)
219
+ for node in nodes:
220
+ camera = node.get("camera") or {}
221
+ if (node.get("parentId") in affected
222
+ or (camera.get("subject") or {}).get("nodeId") in affected
223
+ or (camera.get("lookAtTarget") or {}).get("nodeId") in affected):
224
+ affected.add(node["id"])
225
+ if node["id"] in affected:
226
+ affected.update(node.get("children") or [])
227
+ if node.get("parentId"):
228
+ affected.add(node["parentId"])
229
+ if len(affected) == previous:
230
+ return [node["id"] for node in nodes if node["id"] in affected]
231
+
232
+
233
+ def compact_numbers(value, ndigits=3):
234
+ if isinstance(value, bool) or value is None:
235
+ return value
236
+ if isinstance(value, float):
237
+ return round(value, ndigits)
238
+ if isinstance(value, list):
239
+ return [compact_numbers(item, ndigits) for item in value]
240
+ if isinstance(value, dict):
241
+ return {key: compact_numbers(item, ndigits) for key, item in value.items()}
242
+ return value
243
+
244
+
245
+ def _near(value, expected) -> bool:
246
+ return isinstance(value, (int, float)) and abs(float(value) - expected) < 1e-6
247
+
248
+
249
+ def compact_node_state(row: dict[str, Any]) -> dict[str, Any]:
250
+ row = compact_numbers(row)
251
+ transform = row.get("transform")
252
+ if isinstance(transform, dict):
253
+ scale, rotation = transform.get("scale"), transform.get("rotation")
254
+ if isinstance(scale, dict) and all(_near(scale.get(axis, 1), 1) for axis in "xyz"):
255
+ transform.pop("scale", None)
256
+ if isinstance(rotation, dict) and all(_near(rotation.get(axis, 0), 0) for axis in "xyz"):
257
+ transform.pop("rotation", None)
258
+ if row.get("visible") is True:
259
+ row.pop("visible", None)
260
+ if row.get("locked") is False:
261
+ row.pop("locked", None)
262
+ measurement = row.get("measurement")
263
+ if isinstance(measurement, dict):
264
+ position = transform.get("position") if isinstance(transform, dict) else None
265
+ if measurement.get("origin") == position:
266
+ measurement.pop("origin", None)
267
+ axis = measurement.get("localForwardAxis")
268
+ if isinstance(axis, dict) and _near(axis.get("x", 0), 0) and _near(axis.get("y", 0), 0) \
269
+ and _near(axis.get("z", 0), 1):
270
+ measurement.pop("localForwardAxis", None)
271
+ surfaces = [{key: surface[key] for key in ("height", "worldWidth", "worldDepth") if key in surface}
272
+ for surface in measurement.get("supportSurfaces") or [] if isinstance(surface, dict)]
273
+ if surfaces:
274
+ measurement["supportSurfaces"] = surfaces
275
+ else:
276
+ measurement.pop("supportSurfaces", None)
277
+ for key in list(measurement):
278
+ if key not in {"status", "size", "bounds", "supportSurfaces", "landmarks", "origin", "localForwardAxis"}:
279
+ measurement.pop(key, None)
280
+ return row
281
+
282
+
283
+ def node_state(node: dict[str, Any], versions: dict[str, Any]) -> dict[str, Any]:
284
+ row = {"nodeId": node["id"], "type": node["type"], "name": node.get("name"),
285
+ "entityVersion": versions.get(node["id"]), "transform": copy.deepcopy(node["transform"])}
286
+ if "parentId" in node:
287
+ row["parentId"] = node["parentId"]
288
+ if (node.get("metadata") or {}).get("assetId"):
289
+ row["assetId"] = node["metadata"]["assetId"]
290
+ for key in ("visible", "locked"):
291
+ if key in node:
292
+ row[key] = node[key]
293
+ if node.get("camera"):
294
+ row["camera"] = {key: copy.deepcopy(node["camera"][key])
295
+ for key in ("fov", "lookAt", "subject", "lookAtTarget") if key in node["camera"]}
296
+ animation = (node.get("character") or {}).get("animation") or {}
297
+ if animation.get("posePresetId"):
298
+ row["posePresetId"] = animation["posePresetId"]
299
+ return row
@@ -0,0 +1,75 @@
1
+ """Calls into the Node renderer (``director-cli``): one process per command, JSON in and out."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import os
6
+ import re
7
+ import shutil
8
+ import subprocess
9
+ import tempfile
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from topview_3d_cli.local_errors import LocalProjectError
14
+ from topview_3d_cli.runtime import node_env, runtime
15
+
16
+ BROWSER_COMMANDS = frozenset({"render-frames", "inspect-nodes", "apply-library-pose", "apply-library-poses"})
17
+
18
+
19
+ def node_path() -> str:
20
+ node = shutil.which("node")
21
+ if not node:
22
+ raise LocalProjectError("NODE_UNAVAILABLE", "node is not on PATH; install Node.js 20.6 or newer")
23
+ return node
24
+
25
+
26
+ def director_cli(command: str, body: dict[str, Any] | None = None) -> dict[str, Any]:
27
+ """Run one director-cli command with a JSON payload file; return its last JSON line.
28
+
29
+ Failures raise ``RENDERER_FAILED`` (``RENDER_FAILED`` for ``render-frames``) with the thrown
30
+ error message as the message and the full stderr (stack included) in ``details.stderr``, or
31
+ ``BROWSER_NOT_INSTALLED`` when Chromium is missing.
32
+ """
33
+ current = runtime()
34
+ node = node_path()
35
+ args = [node, "cli.mjs", command]
36
+ payload_file = None
37
+ if body is not None:
38
+ descriptor, payload_file = tempfile.mkstemp(prefix="topview3d-", suffix=".json")
39
+ with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
40
+ json.dump(body, stream, ensure_ascii=False)
41
+ args.append(payload_file)
42
+ try:
43
+ process = subprocess.run(args, cwd=current.director_cli, env=node_env(current), check=False,
44
+ capture_output=True, text=True, encoding="utf-8")
45
+ finally:
46
+ if payload_file:
47
+ Path(payload_file).unlink(missing_ok=True)
48
+ if process.returncode:
49
+ message = (process.stderr or process.stdout).strip()[-2000:]
50
+ if "PLAYWRIGHT_NOT_INSTALLED" in message or "Executable doesn't exist" in message:
51
+ raise LocalProjectError("BROWSER_NOT_INSTALLED", "run `topview-3d-cli browser ensure` first")
52
+ failure = "RENDER_FAILED" if command == "render-frames" else "RENDERER_FAILED"
53
+ reason = renderer_reason(message)
54
+ raise LocalProjectError(failure, reason or f"director-cli {command} exited with {process.returncode}",
55
+ details={"command": command, "reason": reason, "stderr": message[-4000:]})
56
+ lines = [line for line in process.stdout.splitlines() if line.strip()]
57
+ if not lines:
58
+ raise LocalProjectError("RENDER_RESULT_MISSING")
59
+ try:
60
+ return json.loads(lines[-1])
61
+ except json.JSONDecodeError as exc:
62
+ raise LocalProjectError("RENDER_RESULT_INVALID", str(exc)) from exc
63
+
64
+
65
+ def renderer_reason(message: str) -> str:
66
+ """The thrown error message: an ``...Error:`` line, else the first line that is not a Node warning."""
67
+ for line in message.splitlines():
68
+ match = re.match(r"\s*(?:Uncaught )?[A-Za-z]*Error: (.+)", line)
69
+ if match:
70
+ return match.group(1).strip()
71
+ for line in message.splitlines():
72
+ text = line.strip()
73
+ if text and not text.startswith(("(node:", "(Use `node --trace")):
74
+ return text.removeprefix("Error: ")
75
+ return ""