openscvx 0.4.1.dev171__tar.gz → 0.4.1.dev172__tar.gz

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 (354) hide show
  1. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/.gitignore +3 -0
  2. {openscvx-0.4.1.dev171/openscvx.egg-info → openscvx-0.4.1.dev172}/PKG-INFO +1 -1
  3. openscvx-0.4.1.dev172/examples/animations/7_dof_arm.py +225 -0
  4. openscvx-0.4.1.dev172/examples/animations/_camera.py +142 -0
  5. openscvx-0.4.1.dev172/examples/animations/_render.py +261 -0
  6. openscvx-0.4.1.dev172/examples/animations/_sensor_view.py +306 -0
  7. openscvx-0.4.1.dev172/examples/animations/dr_vp_polytope.py +180 -0
  8. openscvx-0.4.1.dev172/examples/animations/logo.py +154 -0
  9. openscvx-0.4.1.dev172/examples/animations/obstacle_avoidance_vmap.py +151 -0
  10. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/plotting_viser.py +138 -68
  11. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/_version.py +3 -3
  12. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/plotting/viser/animated.py +37 -12
  13. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172/openscvx.egg-info}/PKG-INFO +1 -1
  14. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx.egg-info/SOURCES.txt +7 -0
  15. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/test_examples.py +4 -2
  16. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/.github/assets/logo.svg +0 -0
  17. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/.github/release-drafter.yml +0 -0
  18. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/.github/workflows/_docs.yml +0 -0
  19. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/.github/workflows/branch-name.yml +0 -0
  20. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/.github/workflows/docs.yml +0 -0
  21. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/.github/workflows/lint.yml +0 -0
  22. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/.github/workflows/nightly.yml +0 -0
  23. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/.github/workflows/release-drafter.yml +0 -0
  24. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/.github/workflows/release.yml +0 -0
  25. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/.github/workflows/tests-integration.yml +0 -0
  26. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/.github/workflows/tests-unit.yml +0 -0
  27. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/CONTRIBUTING.md +0 -0
  28. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/LICENSE +0 -0
  29. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/README.md +0 -0
  30. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/Foundations/constraint_reformulation.md +0 -0
  31. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/Foundations/control_parameterization.md +0 -0
  32. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/Foundations/discretization.md +0 -0
  33. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/Foundations/ocp.md +0 -0
  34. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/Foundations/scvx.md +0 -0
  35. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/Foundations/time_dilation.md +0 -0
  36. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/UnderTheHood/lowering_architecture.md +0 -0
  37. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/UnderTheHood/vectorization_and_vmapping.md +0 -0
  38. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/UsersGuide/00_introduction.md +0 -0
  39. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/UsersGuide/01_hello_world_brachistochrone.md +0 -0
  40. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/UsersGuide/02_drone_racing_constraints.md +0 -0
  41. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/UsersGuide/03_obstacle_avoidance_vmap.md +0 -0
  42. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/UsersGuide/04_viewpoint_constraints.md +0 -0
  43. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/UsersGuide/05_visualization.md +0 -0
  44. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/UsersGuide/06_logic.md +0 -0
  45. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/UsersGuide/07_lie.md +0 -0
  46. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/UsersGuide/08_mpcc.md +0 -0
  47. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/assets/favicon.png +0 -0
  48. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/assets/images/ct-scvx_dark.png +0 -0
  49. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/assets/images/ct-scvx_light.png +0 -0
  50. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/assets/images/ctcs_dark.png +0 -0
  51. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/assets/images/ctcs_light.png +0 -0
  52. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/assets/images/problem_class_dark.png +0 -0
  53. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/assets/images/problem_class_light.png +0 -0
  54. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/assets/logo.svg +0 -0
  55. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/citation.md +0 -0
  56. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/examples.md +0 -0
  57. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/getting-started.md +0 -0
  58. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/index.md +0 -0
  59. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/docs/javascripts/mathjax.js +0 -0
  60. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/abstract/brachistochrone.py +0 -0
  61. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/abstract/impulsive.py +0 -0
  62. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/abstract/stl_integer_variable.py +0 -0
  63. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/abstract/stl_or.py +0 -0
  64. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/arm/3_dof_arm.py +0 -0
  65. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/arm/7_dof_arm.py +0 -0
  66. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/arm/7_dof_arm_collision.py +0 -0
  67. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/arm/7_dof_arm_vp.py +0 -0
  68. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/car/dubins_car.py +0 -0
  69. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/car/dubins_car_disjoint.py +0 -0
  70. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/car/dubins_car_obstacle_conditional.py +0 -0
  71. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/car/dubins_car_obstacle_stl.py +0 -0
  72. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/car/dubins_car_stl_or.py +0 -0
  73. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/car/dubins_car_waypoint_stl.py +0 -0
  74. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/drone/cinema_vp.py +0 -0
  75. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/drone/dr_double_integrator.py +0 -0
  76. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/drone/dr_vp.py +0 -0
  77. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/drone/dr_vp_nodal.py +0 -0
  78. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/drone/dr_vp_polytope.py +0 -0
  79. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/drone/drone_racing.py +0 -0
  80. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/drone/logo.py +0 -0
  81. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/drone/logo_utils/acl_logo.svg +0 -0
  82. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/drone/logo_utils/svg_path_utils.py +0 -0
  83. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/drone/obstacle_avoidance.py +0 -0
  84. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/drone/obstacle_avoidance_nodal.py +0 -0
  85. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/drone/obstacle_avoidance_vmap.py +0 -0
  86. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/mpc/double_integrator_discrete.py +0 -0
  87. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/mpc/double_integrator_drone_racing.py +0 -0
  88. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/mpc/dubins_car_circle_analytical.py +0 -0
  89. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/mpc/dubins_car_circle_discrete.py +0 -0
  90. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/mpc/realtime_double_integrator_drone_racing.py +0 -0
  91. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/plotting.py +0 -0
  92. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/realtime/3DoF_pdg_realtime.py +0 -0
  93. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/realtime/6DoF_pdg_realtime.py +0 -0
  94. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/realtime/base_problems/3DoF_pdg_realtime_base.py +0 -0
  95. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/realtime/base_problems/6DoF_pdg_realtime_base.py +0 -0
  96. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/realtime/base_problems/cinema_vp_realtime_base.py +0 -0
  97. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/realtime/base_problems/drone_racing_realtime_base.py +0 -0
  98. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/realtime/base_problems/dubins_car_realtime_base.py +0 -0
  99. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/realtime/base_problems/obstacle_avoidance_realtime_base.py +0 -0
  100. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/realtime/cinema_vp_realtime.py +0 -0
  101. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/realtime/drone_racing_realtime.py +0 -0
  102. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/realtime/dubins_car_realtime.py +0 -0
  103. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/realtime/obstacle_avoidance_realtime.py +0 -0
  104. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/rocket/3DoF_pdg.py +0 -0
  105. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/rocket/6DoF_pdg.py +0 -0
  106. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/spacecraft/halo_orbit.py +0 -0
  107. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/spacecraft/hohmann_transfer.py +0 -0
  108. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/examples/spacecraft/proxops_cw.py +0 -0
  109. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/figures/ctlos_cine.gif +0 -0
  110. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/figures/ctlos_dr.gif +0 -0
  111. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/figures/dtlos_cine.gif +0 -0
  112. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/figures/dtlos_dr.gif +0 -0
  113. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/figures/openscvx_logo.svg +0 -0
  114. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/figures/openscvx_logo_square.png +0 -0
  115. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/figures/oscvx_structure_full_dark.svg +0 -0
  116. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/figures/video_preview.png +0 -0
  117. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/__init__.py +0 -0
  118. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/1-background.avif +0 -0
  119. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/1-background@1x.avif +0 -0
  120. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/1-background@2x.avif +0 -0
  121. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/1-background@3x.avif +0 -0
  122. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/1-background@4x.avif +0 -0
  123. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/2-mars.avif +0 -0
  124. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/2-mars@1x.avif +0 -0
  125. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/2-mars@2x.avif +0 -0
  126. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/2-mars@3x.avif +0 -0
  127. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/2-mars@4x.avif +0 -0
  128. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/3-moon.avif +0 -0
  129. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/3-moon@1x.avif +0 -0
  130. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/3-moon@2x.avif +0 -0
  131. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/3-moon@3x.avif +0 -0
  132. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/3-moon@4x.avif +0 -0
  133. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/4-sat1.avif +0 -0
  134. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/4-sat1@1x.avif +0 -0
  135. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/4-sat1@2x.avif +0 -0
  136. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/4-sat1@3x.avif +0 -0
  137. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/4-sat1@4x.avif +0 -0
  138. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/5-space.avif +0 -0
  139. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/5-space@1x.avif +0 -0
  140. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/5-space@2x.avif +0 -0
  141. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/5-space@3x.avif +0 -0
  142. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/5-space@4x.avif +0 -0
  143. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/6-earth.avif +0 -0
  144. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/6-earth@1x.avif +0 -0
  145. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/6-earth@2x.avif +0 -0
  146. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/6-earth@3x.avif +0 -0
  147. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/images/layers/6-earth@4x.avif +0 -0
  148. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/javascripts/parallax.js +0 -0
  149. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/logo.svg +0 -0
  150. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/stylesheets/custom.css +0 -0
  151. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/assets/stylesheets/parallax.css +0 -0
  152. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/home.html +0 -0
  153. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/main.html +0 -0
  154. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/partials/parallax/hero.html +0 -0
  155. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/material/overrides/partials/parallax.html +0 -0
  156. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/mkdocs.yml +0 -0
  157. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/__init__.py +0 -0
  158. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/__main__.py +0 -0
  159. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/algorithms/__init__.py +0 -0
  160. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/algorithms/augmented_lagrangian.py +0 -0
  161. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/algorithms/base.py +0 -0
  162. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/algorithms/constant_proximal_weight.py +0 -0
  163. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/algorithms/optimization_results.py +0 -0
  164. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/algorithms/penalized_trust_region.py +0 -0
  165. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/algorithms/ramp_proximal_weight.py +0 -0
  166. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/algorithms/weights.py +0 -0
  167. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/config.py +0 -0
  168. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/discretization/__init__.py +0 -0
  169. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/discretization/base.py +0 -0
  170. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/discretization/discretize_linearize.py +0 -0
  171. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/discretization/linearize_discretize.py +0 -0
  172. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/discretization/linearize_discretize_sparse.py +0 -0
  173. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/discretization/sparse_utils/__init__.py +0 -0
  174. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/discretization/sparse_utils/bcoo_helpers.py +0 -0
  175. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/discretization/sparse_utils/sparse_jacobian.py +0 -0
  176. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/expert/__init__.py +0 -0
  177. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/expert/byof.py +0 -0
  178. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/expert/lowering.py +0 -0
  179. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/expert/validation.py +0 -0
  180. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/init/__init__.py +0 -0
  181. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/init/interpolation.py +0 -0
  182. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/init/inverse_kinematics.py +0 -0
  183. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/integrators/__init__.py +0 -0
  184. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/integrators/diffrax.py +0 -0
  185. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/integrators/runge_kutta.py +0 -0
  186. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/loader.py +0 -0
  187. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/lowered/__init__.py +0 -0
  188. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/lowered/cvxpy_constraints.py +0 -0
  189. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/lowered/cvxpy_variables.py +0 -0
  190. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/lowered/dynamics.py +0 -0
  191. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/lowered/jax_constraints.py +0 -0
  192. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/lowered/parameters.py +0 -0
  193. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/lowered/problem.py +0 -0
  194. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/lowered/unified.py +0 -0
  195. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/plotting/__init__.py +0 -0
  196. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/plotting/plotting.py +0 -0
  197. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/plotting/scp_iteration.py +0 -0
  198. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/plotting/viser/__init__.py +0 -0
  199. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/plotting/viser/orbits.py +0 -0
  200. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/plotting/viser/plotly_integration.py +0 -0
  201. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/plotting/viser/primitives.py +0 -0
  202. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/plotting/viser/scp.py +0 -0
  203. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/plotting/viser/server.py +0 -0
  204. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/problem.py +0 -0
  205. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/propagation/__init__.py +0 -0
  206. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/propagation/post_processing.py +0 -0
  207. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/propagation/propagation.py +0 -0
  208. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/solvers/__init__.py +0 -0
  209. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/solvers/base.py +0 -0
  210. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/solvers/ptr_solver.py +0 -0
  211. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/__init__.py +0 -0
  212. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/augmentation.py +0 -0
  213. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/builder.py +0 -0
  214. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/constraint_set.py +0 -0
  215. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/__init__.py +0 -0
  216. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/arithmetic.py +0 -0
  217. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/array.py +0 -0
  218. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/constraint.py +0 -0
  219. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/control.py +0 -0
  220. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/expr.py +0 -0
  221. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/lie/__init__.py +0 -0
  222. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/lie/adjoint.py +0 -0
  223. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/lie/se3.py +0 -0
  224. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/lie/so3.py +0 -0
  225. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/linalg.py +0 -0
  226. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/logic.py +0 -0
  227. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/math.py +0 -0
  228. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/parameter.py +0 -0
  229. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/spatial.py +0 -0
  230. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/state.py +0 -0
  231. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/stl.py +0 -0
  232. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/stljax.py +0 -0
  233. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/time.py +0 -0
  234. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/variable.py +0 -0
  235. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/expr/vmap.py +0 -0
  236. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/hashing.py +0 -0
  237. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lower.py +0 -0
  238. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/__init__.py +0 -0
  239. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/cvxpy/__init__.py +0 -0
  240. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/cvxpy/_lowerer.py +0 -0
  241. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/cvxpy/_registry.py +0 -0
  242. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/cvxpy/arithmetic.py +0 -0
  243. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/cvxpy/array.py +0 -0
  244. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/cvxpy/constraint.py +0 -0
  245. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/cvxpy/control.py +0 -0
  246. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/cvxpy/expr.py +0 -0
  247. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/cvxpy/linalg.py +0 -0
  248. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/cvxpy/logic.py +0 -0
  249. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/cvxpy/math.py +0 -0
  250. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/cvxpy/state.py +0 -0
  251. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/__init__.py +0 -0
  252. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/_lowerer.py +0 -0
  253. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/_registry.py +0 -0
  254. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/arithmetic.py +0 -0
  255. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/array.py +0 -0
  256. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/constraint.py +0 -0
  257. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/control.py +0 -0
  258. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/expr.py +0 -0
  259. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/lie.py +0 -0
  260. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/linalg.py +0 -0
  261. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/logic.py +0 -0
  262. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/math.py +0 -0
  263. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/spatial.py +0 -0
  264. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/state.py +0 -0
  265. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/stl.py +0 -0
  266. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/stljax.py +0 -0
  267. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/lowerers/jax/vmap.py +0 -0
  268. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/parser/__init__.py +0 -0
  269. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/parser/_registry.py +0 -0
  270. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/parser/array.py +0 -0
  271. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/parser/constraint.py +0 -0
  272. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/parser/lie.py +0 -0
  273. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/parser/linalg.py +0 -0
  274. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/parser/logic.py +0 -0
  275. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/parser/math.py +0 -0
  276. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/parser/parser.py +0 -0
  277. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/parser/spatial.py +0 -0
  278. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/parser/stl.py +0 -0
  279. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/parser/stljax.py +0 -0
  280. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/parser/tokenizer.py +0 -0
  281. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/preprocessing.py +0 -0
  282. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/problem.py +0 -0
  283. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/sparsity.py +0 -0
  284. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/symbolic/unified.py +0 -0
  285. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/utils/__init__.py +0 -0
  286. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/utils/cache.py +0 -0
  287. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/utils/caching.py +0 -0
  288. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/utils/printing.py +0 -0
  289. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/utils/profiling.py +0 -0
  290. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx/utils/utils.py +0 -0
  291. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx.egg-info/dependency_links.txt +0 -0
  292. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx.egg-info/entry_points.txt +0 -0
  293. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx.egg-info/requires.txt +0 -0
  294. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/openscvx.egg-info/top_level.txt +0 -0
  295. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/pyproject.toml +0 -0
  296. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/scripts/gen_example_pages.py +0 -0
  297. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/scripts/gen_ref_pages.py +0 -0
  298. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/setup.cfg +0 -0
  299. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/__init__.py +0 -0
  300. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/brachistochrone_analytical.py +0 -0
  301. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/expr/__init__.py +0 -0
  302. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/expr/test_gmsr.py +0 -0
  303. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/fixtures/brachistochrone.json +0 -0
  304. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/fixtures/brachistochrone.yaml +0 -0
  305. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/hohmann_analytical.py +0 -0
  306. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/__init__.py +0 -0
  307. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/__init__.py +0 -0
  308. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/test_arithmetic.py +0 -0
  309. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/test_array.py +0 -0
  310. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/test_constraint.py +0 -0
  311. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/test_expr.py +0 -0
  312. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/test_lie.py +0 -0
  313. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/test_linalg.py +0 -0
  314. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/test_logic.py +0 -0
  315. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/test_math.py +0 -0
  316. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/test_node_reference.py +0 -0
  317. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/test_parameters.py +0 -0
  318. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/test_scaling.py +0 -0
  319. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/test_spatial.py +0 -0
  320. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/test_stl.py +0 -0
  321. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/test_variable.py +0 -0
  322. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/expr/test_vmap.py +0 -0
  323. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/parser/__init__.py +0 -0
  324. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/parser/test_array.py +0 -0
  325. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/parser/test_constraint.py +0 -0
  326. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/parser/test_lie.py +0 -0
  327. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/parser/test_linalg.py +0 -0
  328. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/parser/test_load.py +0 -0
  329. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/parser/test_logic.py +0 -0
  330. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/parser/test_math.py +0 -0
  331. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/parser/test_parser.py +0 -0
  332. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/parser/test_spatial.py +0 -0
  333. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/parser/test_stl.py +0 -0
  334. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/parser/test_tokenizer.py +0 -0
  335. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/parser/test_vmap.py +0 -0
  336. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/test_augmentation.py +0 -0
  337. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/test_hashing.py +0 -0
  338. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/test_lower_cvxpy.py +0 -0
  339. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/test_lower_jax.py +0 -0
  340. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/test_preprocessing.py +0 -0
  341. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/test_sparsity.py +0 -0
  342. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/symbolic/test_unified.py +0 -0
  343. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/test_autotuning.py +0 -0
  344. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/test_brachistochrone.py +0 -0
  345. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/test_cvxpygen_optional.py +0 -0
  346. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/test_discretization.py +0 -0
  347. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/test_expert.py +0 -0
  348. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/test_impulsive.py +0 -0
  349. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/test_init.py +0 -0
  350. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/test_integrators.py +0 -0
  351. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/test_loader.py +0 -0
  352. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/test_optimization_results.py +0 -0
  353. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/test_plotting.py +0 -0
  354. {openscvx-0.4.1.dev171 → openscvx-0.4.1.dev172}/tests/test_propagation.py +0 -0
@@ -27,6 +27,9 @@ solver/*
27
27
  # Images
28
28
  *.eps
29
29
 
30
+ # Animation renders
31
+ examples/animations/mp4/
32
+
30
33
  # Numpy files
31
34
  *.npy
32
35
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openscvx
3
- Version: 0.4.1.dev171
3
+ Version: 0.4.1.dev172
4
4
  Summary: A general Python-based successive convexification implementation which uses a JAX backend.
5
5
  Author-email: Chris Hayner and Griffin Norris <haynec@uw.edu>
6
6
  License: Apache Software License
@@ -0,0 +1,225 @@
1
+ """Cinematic offline render of the 7-DOF arm pick-and-place example.
2
+
3
+ The trajectory optimization problem itself lives in
4
+ ``examples/arm/7_dof_arm.py``; this file imports that ``problem`` and the
5
+ robot parameters, solves it, builds a viser scene with arm links / joint
6
+ frames / EE trail, and drives it frame-by-frame while piping raw RGB into
7
+ ffmpeg to produce an mp4.
8
+
9
+ Run it with::
10
+
11
+ python examples/animations/7_dof_arm.py
12
+
13
+ The script prints a viser URL and waits. Open the URL in a browser — as soon
14
+ as the client connects, the render begins. Requires ``ffmpeg`` on ``PATH``;
15
+ ``openscvx`` does not depend on it.
16
+
17
+ Only one camera mode is provided — ``"overview"`` — a static elevated camera
18
+ that frames the full pick-and-place workspace.
19
+
20
+ Tweak ``OUTPUT_PATH`` / ``WIDTH`` / ``HEIGHT`` / ``FPS`` below for different
21
+ output variants.
22
+ """
23
+
24
+ import importlib
25
+ import os
26
+ import sys
27
+
28
+ import numpy as np
29
+
30
+ # Add the project root so `examples.*` imports resolve.
31
+ current_dir = os.path.dirname(os.path.abspath(__file__))
32
+ grandparent_dir = os.path.dirname(os.path.dirname(current_dir))
33
+ sys.path.append(grandparent_dir)
34
+
35
+ from examples.animations._camera import overview_pose
36
+ from examples.animations._render import render_animation_to_video
37
+ from examples.plotting_viser import AnimatedServerHandle
38
+ from openscvx.plotting.viser import (
39
+ add_animated_trail,
40
+ add_ellipsoid_obstacles,
41
+ add_ghost_trajectory,
42
+ add_position_marker,
43
+ add_target_markers,
44
+ compute_velocity_colors,
45
+ create_server,
46
+ )
47
+
48
+ # Import the arm example by path (filename starts with a digit).
49
+ _arm_spec = importlib.util.spec_from_file_location(
50
+ "arm_7dof", os.path.join(grandparent_dir, "examples", "arm", "7_dof_arm_collision.py")
51
+ )
52
+ _arm_mod = importlib.util.module_from_spec(_arm_spec)
53
+ _arm_spec.loader.exec_module(_arm_mod)
54
+
55
+ problem = _arm_mod.problem
56
+ d1 = _arm_mod.d1
57
+ a2 = _arm_mod.a2
58
+ a3 = _arm_mod.a3
59
+ a4 = _arm_mod.a4
60
+ joint_names = _arm_mod.joint_names
61
+ waypoint_names = _arm_mod.waypoint_names
62
+ waypoint_positions = _arm_mod.waypoint_positions
63
+ obstacle_center = _arm_mod.obstacle_center
64
+ obstacle_radius = _arm_mod.obstacle_radius
65
+
66
+ # Keypoint home positions (derived from link lengths — defined in __main__ of
67
+ # the arm example, so we replicate them here).
68
+ keypoint_home_positions = {
69
+ "base": np.array([0.0, 0.0, 0.0]),
70
+ "shoulder": np.array([0.0, 0.0, d1]),
71
+ "elbow": np.array([a2, 0.0, d1]),
72
+ "wrist": np.array([a2 + a3, 0.0, d1]),
73
+ "ee": np.array([a2 + a3 + a4, 0.0, d1]),
74
+ }
75
+
76
+ # --- Render settings ---------------------------------------------------------
77
+ OUTPUT_PATH = os.path.join(current_dir, "mp4", "7_dof_arm_overview.mp4")
78
+ WIDTH = 1080
79
+ HEIGHT = 1080
80
+ FPS = 60
81
+ CRF = 16
82
+
83
+ # Oversampling for smooth trails.
84
+ STRIDE = 4
85
+ PROPAGATION_HZ = FPS * STRIDE
86
+
87
+ # --- Camera settings ---------------------------------------------------------
88
+ OVERVIEW_AZIMUTH = np.radians(60.0)
89
+ OVERVIEW_ELEVATION = np.radians(15.0)
90
+ OVERVIEW_RADIUS_MARGIN = 1.0
91
+ OVERVIEW_FOV_DEG = 60.0
92
+
93
+
94
+ if __name__ == "__main__":
95
+ import jaxlie
96
+
97
+ problem.settings.prp.dt = 1.0 / PROPAGATION_HZ
98
+ problem.initialize()
99
+ problem.solve()
100
+ results = problem.post_process()
101
+
102
+ ee_pos = np.asarray(results.trajectory["ee_position"], dtype=np.float64)
103
+ n_frames = len(ee_pos)
104
+
105
+ # -- Extract joint keypoint positions from propagated transforms -----------
106
+ keypoints = np.zeros((n_frames, 5, 3))
107
+ joint_quats = np.zeros((n_frames, 5, 4))
108
+ for k, name in enumerate(joint_names):
109
+ T_k = np.asarray(results.trajectory[f"T_{name}"]) # (T, 4, 4)
110
+ p_home = np.append(keypoint_home_positions[name], 1.0)
111
+ keypoints[:, k] = (T_k @ p_home)[:, :3]
112
+ joint_quats[:, k] = np.array(
113
+ [jaxlie.SO3.from_matrix(T_k[t, :3, :3]).wxyz for t in range(n_frames)]
114
+ )
115
+
116
+ # -- Build viser scene (mirrors examples/arm/7_dof_arm.py) -----------------
117
+ server = create_server(ee_pos, show_grid=False)
118
+ server.scene.add_grid("/grid", width=1.5, height=1.5, cell_size=0.25)
119
+ server.scene.add_frame("/origin", axes_length=0.1, axes_radius=0.003)
120
+
121
+ # Waypoint markers
122
+ marker_colors = {
123
+ "home": (100, 150, 255),
124
+ "pre_grasp": (255, 180, 50),
125
+ "grasp": (255, 50, 50),
126
+ "pre_place": (255, 180, 50),
127
+ "place": (255, 50, 50),
128
+ }
129
+ add_target_markers(
130
+ server,
131
+ waypoint_positions,
132
+ radius=0.015,
133
+ colors=[marker_colors[name] for name in waypoint_names],
134
+ )
135
+
136
+ add_ellipsoid_obstacles(
137
+ server,
138
+ centers=[obstacle_center],
139
+ radii=[np.full(3, 1.0 / obstacle_radius)],
140
+ )
141
+
142
+ # Ghost EE trajectory + animated trail
143
+ ee_colors = compute_velocity_colors(np.asarray(results.trajectory.get("velocity")))
144
+ add_ghost_trajectory(server, ee_pos, ee_colors, point_size=0.005)
145
+ _, update_trail = add_animated_trail(server, ee_pos, ee_colors, point_size=0.008)
146
+
147
+ # Animated EE position marker
148
+ _, update_marker = add_position_marker(server, ee_pos, radius=0.015)
149
+
150
+ # Animated arm links (line segments between consecutive keypoints)
151
+ link_rgb = np.array(
152
+ [
153
+ [180, 180, 180], # base -> shoulder
154
+ [100, 180, 255], # shoulder -> elbow
155
+ [100, 255, 150], # elbow -> wrist
156
+ [255, 200, 100], # wrist -> ee
157
+ ],
158
+ dtype=np.uint8,
159
+ )
160
+ link_colors = np.stack([link_rgb, link_rgb], axis=1) # (4, 2, 3)
161
+
162
+ init_points = np.stack(
163
+ [np.stack([keypoints[0, k], keypoints[0, k + 1]]) for k in range(4)]
164
+ ).astype(np.float32)
165
+
166
+ arm_handle = server.scene.add_line_segments(
167
+ "/arm_links",
168
+ points=init_points,
169
+ colors=link_colors,
170
+ line_width=5.0,
171
+ )
172
+
173
+ # Joint coordinate frames
174
+ joint_frame_handles = []
175
+ for k in range(5):
176
+ h = server.scene.add_frame(
177
+ f"/joint_{joint_names[k]}",
178
+ wxyz=joint_quats[0, k].astype(np.float32),
179
+ position=keypoints[0, k].astype(np.float32),
180
+ axes_length=0.06,
181
+ axes_radius=0.002,
182
+ )
183
+ joint_frame_handles.append(h)
184
+
185
+ def update_arm(frame_idx: int) -> None:
186
+ pts = np.stack(
187
+ [np.stack([keypoints[frame_idx, k], keypoints[frame_idx, k + 1]]) for k in range(4)]
188
+ ).astype(np.float32)
189
+ arm_handle.points = pts
190
+ for k, h in enumerate(joint_frame_handles):
191
+ h.position = keypoints[frame_idx, k].astype(np.float32)
192
+ h.wxyz = joint_quats[frame_idx, k].astype(np.float32)
193
+
194
+ # -- Manual-step handle for the renderer -----------------------------------
195
+ traj_time = np.asarray(results.trajectory["time"], dtype=np.float64).flatten()
196
+ handle = AnimatedServerHandle(
197
+ server=server,
198
+ traj_time=traj_time,
199
+ update_callbacks=[update_trail, update_marker, update_arm],
200
+ )
201
+
202
+ # -- Camera: overview framing the full workspace ---------------------------
203
+ # Frame on waypoint positions + EE path so the full motion is visible.
204
+ all_points = np.vstack([ee_pos, np.array(waypoint_positions)])
205
+ static_pose = overview_pose(
206
+ all_points,
207
+ azimuth=OVERVIEW_AZIMUTH,
208
+ elevation=OVERVIEW_ELEVATION,
209
+ radius_margin=OVERVIEW_RADIUS_MARGIN,
210
+ fov_deg=OVERVIEW_FOV_DEG,
211
+ )
212
+
213
+ def camera_pose_fn(frame_idx: int):
214
+ return static_pose
215
+
216
+ render_animation_to_video(
217
+ handle,
218
+ OUTPUT_PATH,
219
+ camera_pose_fn,
220
+ width=WIDTH,
221
+ height=HEIGHT,
222
+ fps=FPS,
223
+ crf=CRF,
224
+ stride=STRIDE,
225
+ )
@@ -0,0 +1,142 @@
1
+ """Reusable camera pose helpers for viser animation rendering.
2
+
3
+ All functions return ``(cam_position, cam_wxyz, look_at)`` tuples suitable for
4
+ passing to ``render_animation_to_video``'s ``camera_pose_fn`` argument. They
5
+ depend only on numpy and ``viser.transforms`` — no openscvx imports.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import numpy as np
11
+ import viser.transforms as vtf
12
+
13
+
14
+ def look_at_wxyz(pos: np.ndarray, target: np.ndarray, up: np.ndarray) -> np.ndarray:
15
+ """Quaternion (w,x,y,z) for a camera at ``pos`` looking at ``target``.
16
+
17
+ Uses the OpenCV camera convention that viser expects: +X right, +Y down,
18
+ +Z forward. See ``examples/animations/camera_control_notes.md``.
19
+ """
20
+ forward = target - pos
21
+ forward /= np.linalg.norm(forward)
22
+ right = np.cross(forward, up)
23
+ right_norm = np.linalg.norm(right)
24
+ if right_norm < 1e-6:
25
+ # Gimbal lock: forward is (nearly) parallel to up. Pick an arbitrary
26
+ # world axis that isn't, so the camera stays defined. The chosen axis
27
+ # determines the "roll" of the camera at the singularity — not great
28
+ # cinematically, but prevents a NaN crash.
29
+ fallback = np.array([1.0, 0.0, 0.0]) if abs(forward[0]) < 0.9 else np.array([0.0, 1.0, 0.0])
30
+ right = np.cross(forward, fallback)
31
+ right_norm = np.linalg.norm(right)
32
+ right /= right_norm
33
+ cam_down = np.cross(forward, right) # = -world_up projected perp to forward
34
+ R_world_cam = np.stack([right, cam_down, forward], axis=1)
35
+ return vtf.SO3.from_matrix(R_world_cam).wxyz
36
+
37
+
38
+ def chase_pose(
39
+ subject: np.ndarray,
40
+ focus: np.ndarray,
41
+ *,
42
+ chase_distance: float = 15.0,
43
+ vertical_offset: float = 2.0,
44
+ up=(0.0, 0.0, 1.0),
45
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
46
+ """Return ``(cam_pos, cam_wxyz, look_at)`` for a chase camera behind ``subject``.
47
+
48
+ The camera sits on the ray from ``focus`` through ``subject``, extended
49
+ ``chase_distance`` units past the subject, then lifted along world up by
50
+ ``vertical_offset``. It always looks at ``focus``.
51
+ """
52
+ subject = np.asarray(subject, dtype=np.float64)
53
+ focus = np.asarray(focus, dtype=np.float64)
54
+ up = np.asarray(up, dtype=np.float64)
55
+
56
+ ray = subject - focus
57
+ ray_norm = np.linalg.norm(ray)
58
+ if ray_norm < 1e-6:
59
+ cam_pos = subject + vertical_offset * up
60
+ else:
61
+ cam_pos = subject + chase_distance * (ray / ray_norm) + vertical_offset * up
62
+
63
+ wxyz = look_at_wxyz(cam_pos, focus, up)
64
+ return cam_pos, wxyz, focus
65
+
66
+
67
+ def onboard_pose(
68
+ position: np.ndarray,
69
+ attitude_wxyz: np.ndarray,
70
+ R_sb: np.ndarray,
71
+ *,
72
+ forward_offset: float = 0.0,
73
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
74
+ """Return ``(cam_pos, cam_wxyz, look_at)`` for a sensor-mounted FPV camera.
75
+
76
+ Places the camera at ``position`` (shifted slightly forward along the sensor
77
+ boresight by ``forward_offset``) with orientation matching the sensor frame —
78
+ i.e. looking along the sensor boresight (+Z in sensor frame, mapped through
79
+ ``R_sb`` and the body attitude to world coordinates).
80
+ """
81
+ # R_body_to_world from the attitude quaternion (w, x, y, z)
82
+ w, x, y, z = attitude_wxyz
83
+ R_bw = np.array(
84
+ [
85
+ [1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w)],
86
+ [2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w)],
87
+ [2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y)],
88
+ ],
89
+ dtype=np.float64,
90
+ )
91
+
92
+ # Sensor-to-world: columns are the sensor axes in world coords.
93
+ # R_sb is body-to-sensor, so R_sb.T is sensor-to-body.
94
+ R_sensor_to_world = R_bw @ R_sb.T
95
+
96
+ # Viser uses OpenCV convention (+X right, +Y DOWN, +Z forward).
97
+ # The sensor frame has +Y UP, so we rotate 180deg around the boresight (Z)
98
+ # to flip both X and Y, converting to the convention viser expects.
99
+ R_opencv_from_sensor = np.diag([-1.0, -1.0, 1.0])
100
+ R_cam_to_world = R_sensor_to_world @ R_opencv_from_sensor
101
+
102
+ wxyz = vtf.SO3.from_matrix(R_cam_to_world).wxyz
103
+ # Boresight is sensor +Z expressed in world frame (unchanged by the flip).
104
+ boresight_world = R_sensor_to_world[:, 2]
105
+ cam_pos = position + forward_offset * boresight_world
106
+ look_at = cam_pos + 10.0 * boresight_world
107
+ return cam_pos, wxyz, look_at
108
+
109
+
110
+ def overview_pose(
111
+ positions: np.ndarray,
112
+ *,
113
+ azimuth: float = np.radians(135.0),
114
+ elevation: float = np.radians(25.0),
115
+ radius_margin: float = 0.75,
116
+ fov_deg: float = 60.0,
117
+ up=(0.0, 0.0, 1.0),
118
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
119
+ """Return a static ``(cam_pos, cam_wxyz, look_at)`` that frames all ``positions``.
120
+
121
+ The camera is placed on a sphere around the centroid of ``positions``,
122
+ parameterized by ``azimuth`` (angle in XY from +X, CCW) and ``elevation``
123
+ (angle above the horizon). The radius is auto-computed so the full extent
124
+ fits within ``fov_deg``, then scaled by ``radius_margin``.
125
+ """
126
+ up = np.asarray(up, dtype=np.float64)
127
+ center = positions.mean(axis=0)
128
+ max_extent = np.max(np.linalg.norm(positions - center, axis=1))
129
+ half_fov_rad = np.radians(fov_deg / 2.0)
130
+ radius = max_extent / np.sin(half_fov_rad) * radius_margin
131
+
132
+ cos_el = np.cos(elevation)
133
+ cam_pos = center + radius * np.array(
134
+ [
135
+ cos_el * np.cos(azimuth),
136
+ cos_el * np.sin(azimuth),
137
+ np.sin(elevation),
138
+ ]
139
+ )
140
+ look_at = center.copy()
141
+ wxyz = look_at_wxyz(cam_pos, look_at, up)
142
+ return cam_pos, wxyz, look_at
@@ -0,0 +1,261 @@
1
+ """Offline video rendering for animated viser examples.
2
+
3
+ Pipes raw RGB frames from ``client.get_render()`` directly into ffmpeg over
4
+ stdin, so there are no intermediate PNG files and no Python image library
5
+ required. The only runtime dependency is ``ffmpeg`` on ``PATH``; ``openscvx``
6
+ itself gains nothing from this module.
7
+
8
+ Typical usage (from an animation example):
9
+
10
+ .. code-block:: python
11
+
12
+ handle = create_animated_plotting_server(results, ..., controls="manual")
13
+
14
+ def camera_pose_fn(frame_idx):
15
+ return chase_pose(positions[frame_idx], target_center)
16
+
17
+ render_animation_to_video(handle, "out.mp4", camera_pose_fn)
18
+
19
+ The render blocks on a viser client connection, so you run the script, wait
20
+ for the "[render] waiting for a viser client..." line, open the printed viser
21
+ URL in a browser, and the render starts automatically.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import shutil
27
+ import subprocess
28
+ import time
29
+ from pathlib import Path
30
+ from typing import Callable
31
+
32
+ import numpy as np
33
+ import viser
34
+
35
+ from examples.plotting_viser import AnimatedServerHandle
36
+
37
+ # frame_idx -> (camera_position, camera_wxyz, camera_look_at)
38
+ CameraPoseFn = Callable[[int], tuple[np.ndarray, np.ndarray, np.ndarray]]
39
+
40
+
41
+ def wait_for_client(
42
+ server: viser.ViserServer,
43
+ timeout_s: float = 300.0,
44
+ poll_interval_s: float = 0.25,
45
+ ) -> viser.ClientHandle:
46
+ """Block until at least one viser client connects; return the first one.
47
+
48
+ Prints a reminder so the user knows they need to open the printed viser
49
+ URL in a browser for the render to proceed.
50
+ """
51
+ print(
52
+ f"[render] waiting for a viser client to connect "
53
+ f"(open the viser URL above; timeout in {int(timeout_s)}s)..."
54
+ )
55
+ t0 = time.time()
56
+ while True:
57
+ clients = server.get_clients()
58
+ if clients:
59
+ client = next(iter(clients.values()))
60
+ print("[render] client connected.")
61
+ return client
62
+ if time.time() - t0 > timeout_s:
63
+ raise TimeoutError(
64
+ f"No viser client connected within {timeout_s:.0f}s. "
65
+ f"Open the viser URL in a browser to start the render."
66
+ )
67
+ time.sleep(poll_interval_s)
68
+
69
+
70
+ def render_animation_to_video(
71
+ handle: AnimatedServerHandle,
72
+ output_path: str | Path,
73
+ camera_pose_fn: CameraPoseFn,
74
+ *,
75
+ width: int = 1280,
76
+ height: int = 720,
77
+ fps: int = 30,
78
+ crf: int = 16,
79
+ preset: str = "slow",
80
+ background_color: tuple[int, int, int] = (16, 17, 19),
81
+ start_frame: int = 0,
82
+ end_frame: int | None = None,
83
+ stride: int = 1,
84
+ settle_s: float = 0.0,
85
+ client: viser.ClientHandle | None = None,
86
+ progress_every: int = 30,
87
+ fov_deg: float | None = None,
88
+ ) -> Path:
89
+ """Render frames of ``handle`` to an H.264 mp4 by piping raw RGB into ffmpeg.
90
+
91
+ Each frame is fetched with ``transport_format="png"`` so we get a *lossless*
92
+ RGBA array from the browser — no JPEG pre-compression stacked under the
93
+ final h.264 pass. The alpha channel is composited onto ``background_color``
94
+ in numpy before writing.
95
+
96
+ Why compositing is necessary: viser's dark mode puts the canvas over a
97
+ ``theme.colors.dark[9]`` (= ``#101113``) DOM element (see viser client
98
+ ``App.tsx:426``), and the browser compositor blends the scene over that
99
+ background at display time. But ``client.get_render`` returns only the
100
+ WebGL canvas pixels, which are transparent where nothing is drawn — so
101
+ we have to composite ourselves. The default ``background_color`` matches
102
+ Mantine's ``dark[9]`` exactly so the rendered frames are indistinguishable
103
+ from the live view.
104
+
105
+ The video's playback rate (``fps``) is independent of how many frames the
106
+ trajectory contains — ``stride`` controls the frame range from the trajectory
107
+ that gets written. For realtime playback, pick ``fps`` so that
108
+ ``len(range(start_frame, end_frame, stride)) / fps`` matches the trajectory
109
+ duration. For slow-motion, raise ``fps`` relative to that; for time-lapse,
110
+ lower it.
111
+
112
+ Args:
113
+ handle: Handle returned by
114
+ ``create_animated_plotting_server(..., controls="manual")``.
115
+ output_path: Destination mp4 file. Parent dirs are created.
116
+ camera_pose_fn: ``frame_idx -> (position, wxyz, look_at)``. Called once
117
+ per rendered frame to position the camera.
118
+ width: Output video width in pixels.
119
+ height: Output video height in pixels.
120
+ fps: Output video frame rate.
121
+ crf: H.264 constant-rate factor; lower is higher quality. 16 is
122
+ visually near-lossless, 18 is high quality, 23 is ffmpeg's default.
123
+ preset: ffmpeg x264 preset (``ultrafast``..``veryslow``). ``slow`` gives
124
+ noticeably better quality/size than ``medium`` for static-heavy 3D
125
+ scenes at modest encode-time cost.
126
+ background_color: RGB tuple (0..255) for the scene background. Applied
127
+ both to viser's scene (so the live view matches) and as the
128
+ composite color for alpha channel in rendered frames.
129
+ start_frame: First trajectory frame index to render (inclusive).
130
+ end_frame: One past the last trajectory frame to render. ``None`` means
131
+ up to ``handle.n_frames``.
132
+ stride: Trajectory frame stride. ``stride=2`` renders every other
133
+ frame, halving the output length at fixed ``fps``.
134
+ settle_s: Optional sleep between pushing scene state and calling
135
+ ``get_render``. Usually 0; bump if you see torn/partial frames.
136
+ client: Pre-connected client. If ``None``, waits for one.
137
+ progress_every: Print a progress line every N rendered frames.
138
+ fov_deg: If given, override the client camera's vertical field of view
139
+ (in degrees) before rendering. Useful for matching a sensor FOV.
140
+
141
+ Returns:
142
+ Absolute path to the written mp4.
143
+ """
144
+ ffmpeg = shutil.which("ffmpeg")
145
+ if ffmpeg is None:
146
+ raise RuntimeError(
147
+ "ffmpeg not found on PATH. Install it locally (e.g. "
148
+ "`brew install ffmpeg` on macOS) — openscvx does not include it "
149
+ "as a package dependency."
150
+ )
151
+
152
+ output_path = Path(output_path).expanduser().resolve()
153
+ output_path.parent.mkdir(parents=True, exist_ok=True)
154
+
155
+ # Push the background color to viser so the live browser view matches the
156
+ # rendered output. `configure_theme(dark_mode=True)` only styles GUI panels;
157
+ # the 3D canvas clear color is controlled separately by set_background_image.
158
+ bg_rgb = np.asarray(background_color, dtype=np.uint8).reshape(1, 1, 3)
159
+ bg_image = np.broadcast_to(bg_rgb, (2, 2, 3)).copy()
160
+ handle.server.scene.set_background_image(bg_image, format="png")
161
+ bg_float = np.asarray(background_color, dtype=np.float32).reshape(1, 1, 3)
162
+
163
+ if client is None:
164
+ client = wait_for_client(handle.server)
165
+
166
+ if fov_deg is not None:
167
+ client.camera.fov = np.radians(fov_deg)
168
+
169
+ n = handle.n_frames
170
+ if end_frame is None:
171
+ end_frame = n
172
+ frame_indices = list(range(start_frame, min(end_frame, n), max(stride, 1)))
173
+ if not frame_indices:
174
+ raise ValueError(
175
+ f"No frames to render: start_frame={start_frame}, "
176
+ f"end_frame={end_frame}, n_frames={n}, stride={stride}"
177
+ )
178
+
179
+ cmd = [
180
+ ffmpeg,
181
+ "-y",
182
+ "-f",
183
+ "rawvideo",
184
+ "-vcodec",
185
+ "rawvideo",
186
+ "-s",
187
+ f"{width}x{height}",
188
+ "-pix_fmt",
189
+ "rgb24",
190
+ "-r",
191
+ str(fps),
192
+ "-i",
193
+ "-",
194
+ "-c:v",
195
+ "libx264",
196
+ "-pix_fmt",
197
+ "yuv420p",
198
+ "-crf",
199
+ str(crf),
200
+ "-preset",
201
+ preset,
202
+ "-movflags",
203
+ "+faststart",
204
+ str(output_path),
205
+ ]
206
+
207
+ print(f"[render] {len(frame_indices)} frame(s) @ {fps} fps, {width}x{height} -> {output_path}")
208
+
209
+ proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
210
+ assert proc.stdin is not None
211
+
212
+ try:
213
+ t_render_start = time.time()
214
+ for out_i, frame_i in enumerate(frame_indices):
215
+ handle.step(frame_i)
216
+ pos, wxyz, look_at = camera_pose_fn(frame_i)
217
+ with client.atomic():
218
+ client.camera.position = pos
219
+ client.camera.wxyz = wxyz
220
+ client.camera.look_at = look_at
221
+ if settle_s > 0:
222
+ time.sleep(settle_s)
223
+ # PNG transport is lossless RGBA. JPEG would introduce a second
224
+ # compression pass on top of h.264 and visibly hurts quality.
225
+ img = client.get_render(height=height, width=width, transport_format="png")
226
+ if img.ndim != 3 or img.shape[0] != height or img.shape[1] != width:
227
+ raise RuntimeError(
228
+ f"Unexpected get_render shape {img.shape}; "
229
+ f"expected ({height}, {width}, 3 or 4)."
230
+ )
231
+ if img.dtype != np.uint8:
232
+ img = img.astype(np.uint8)
233
+ if img.shape[2] == 4:
234
+ # Composite RGBA onto the chosen background color. Empty scene
235
+ # pixels come back alpha=0 so they'd otherwise be undefined.
236
+ alpha = img[:, :, 3:4].astype(np.float32) * (1.0 / 255.0)
237
+ rgb = img[:, :, :3].astype(np.float32)
238
+ composed = rgb * alpha + bg_float * (1.0 - alpha)
239
+ img = composed.astype(np.uint8)
240
+ proc.stdin.write(img.tobytes())
241
+ if progress_every > 0 and (out_i + 1) % progress_every == 0:
242
+ elapsed = time.time() - t_render_start
243
+ done = out_i + 1
244
+ rate = done / max(elapsed, 1e-6)
245
+ eta = (len(frame_indices) - done) / max(rate, 1e-6)
246
+ print(
247
+ f"[render] frame {done}/{len(frame_indices)} ({rate:.1f} fps, eta {eta:.0f}s)"
248
+ )
249
+ proc.stdin.close()
250
+ except BrokenPipeError as e:
251
+ proc.wait()
252
+ raise RuntimeError(
253
+ "ffmpeg closed its input pipe unexpectedly. See ffmpeg stderr above."
254
+ ) from e
255
+
256
+ ret = proc.wait()
257
+ if ret != 0:
258
+ raise RuntimeError(f"ffmpeg exited with code {ret}")
259
+
260
+ print(f"[render] done: {output_path}")
261
+ return output_path