simview 3.3__tar.gz → 3.4__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 (190) hide show
  1. {simview-3.3/simview.egg-info → simview-3.4}/PKG-INFO +76 -3
  2. {simview-3.3 → simview-3.4}/README.md +75 -2
  3. {simview-3.3 → simview-3.4}/pyproject.toml +1 -1
  4. simview-3.4/simview/__main__.py +463 -0
  5. simview-3.4/simview/diff.py +360 -0
  6. simview-3.4/simview/info.py +561 -0
  7. simview-3.4/simview/terrain.py +558 -0
  8. {simview-3.3 → simview-3.4/simview.egg-info}/PKG-INFO +76 -3
  9. {simview-3.3 → simview-3.4}/simview.egg-info/SOURCES.txt +6 -0
  10. simview-3.4/tests/test_cli.py +621 -0
  11. simview-3.4/tests/test_diff.py +248 -0
  12. simview-3.4/tests/test_info.py +255 -0
  13. simview-3.4/tests/test_terrain_query.py +333 -0
  14. simview-3.3/simview/__main__.py +0 -172
  15. simview-3.3/tests/test_cli.py +0 -169
  16. {simview-3.3 → simview-3.4}/LICENSE +0 -0
  17. {simview-3.3 → simview-3.4}/MANIFEST.in +0 -0
  18. {simview-3.3 → simview-3.4}/setup.cfg +0 -0
  19. {simview-3.3 → simview-3.4}/simview/__init__.py +0 -0
  20. {simview-3.3 → simview-3.4}/simview/launcher.py +0 -0
  21. {simview-3.3 → simview-3.4}/simview/live.py +0 -0
  22. {simview-3.3 → simview-3.4}/simview/merge.py +0 -0
  23. {simview-3.3 → simview-3.4}/simview/model.py +0 -0
  24. {simview-3.3 → simview-3.4}/simview/py.typed +0 -0
  25. {simview-3.3 → simview-3.4}/simview/scene.py +0 -0
  26. {simview-3.3 → simview-3.4}/simview/server.py +0 -0
  27. {simview-3.3 → simview-3.4}/simview/state.py +0 -0
  28. {simview-3.3 → simview-3.4}/simview/static/css/controls.css +0 -0
  29. {simview-3.3 → simview-3.4}/simview/static/js/SimView.js +0 -0
  30. {simview-3.3 → simview-3.4}/simview/static/js/components/AnimationController.js +0 -0
  31. {simview-3.3 → simview-3.4}/simview/static/js/components/BatchManager.js +0 -0
  32. {simview-3.3 → simview-3.4}/simview/static/js/components/InteractionController.js +0 -0
  33. {simview-3.3 → simview-3.4}/simview/static/js/components/InteractionControls.js +0 -0
  34. {simview-3.3 → simview-3.4}/simview/static/js/components/Scene.js +0 -0
  35. {simview-3.3 → simview-3.4}/simview/static/js/components/StateStore.js +0 -0
  36. {simview-3.3 → simview-3.4}/simview/static/js/config.js +0 -0
  37. {simview-3.3 → simview-3.4}/simview/static/js/main.js +0 -0
  38. {simview-3.3 → simview-3.4}/simview/static/js/objects/Body.js +0 -0
  39. {simview-3.3 → simview-3.4}/simview/static/js/objects/StaticObject.js +0 -0
  40. {simview-3.3 → simview-3.4}/simview/static/js/objects/Terrain.js +0 -0
  41. {simview-3.3 → simview-3.4}/simview/static/js/objects/utils.js +0 -0
  42. {simview-3.3 → simview-3.4}/simview/static/js/ui/AnalysisPanel.js +0 -0
  43. {simview-3.3 → simview-3.4}/simview/static/js/ui/BatchLegend.js +0 -0
  44. {simview-3.3 → simview-3.4}/simview/static/js/ui/BodyStateWindow.js +0 -0
  45. {simview-3.3 → simview-3.4}/simview/static/js/ui/Controls.js +0 -0
  46. {simview-3.3 → simview-3.4}/simview/static/js/ui/ErrorMetrics.js +0 -0
  47. {simview-3.3 → simview-3.4}/simview/static/js/ui/Legend.js +0 -0
  48. {simview-3.3 → simview-3.4}/simview/static/js/ui/PlaybackControls.js +0 -0
  49. {simview-3.3 → simview-3.4}/simview/static/js/ui/ScalarPlotter.js +0 -0
  50. {simview-3.3 → simview-3.4}/simview/static/js/utils/blobCodec.js +0 -0
  51. {simview-3.3 → simview-3.4}/simview/static/js/utils/bodyTransforms.js +0 -0
  52. {simview-3.3 → simview-3.4}/simview/static/js/utils/csv.js +0 -0
  53. {simview-3.3 → simview-3.4}/simview/static/js/utils/errorMath.js +0 -0
  54. {simview-3.3 → simview-3.4}/simview/static/js/utils/injectStyles.js +0 -0
  55. {simview-3.3 → simview-3.4}/simview/static/js/utils/interpolate.js +0 -0
  56. {simview-3.3 → simview-3.4}/simview/static/js/utils/liveFollow.js +0 -0
  57. {simview-3.3 → simview-3.4}/simview/static/js/utils/loadRecordingLibs.js +0 -0
  58. {simview-3.3 → simview-3.4}/simview/static/js/utils/viewState.js +0 -0
  59. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/index.min.js +0 -0
  60. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/Color.js +0 -0
  61. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/chroma.js +0 -0
  62. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/colors/colorbrewer.js +0 -0
  63. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/colors/w3cx11.js +0 -0
  64. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/generator/average.js +0 -0
  65. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/generator/bezier.js +0 -0
  66. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/generator/blend.js +0 -0
  67. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/generator/cubehelix.js +0 -0
  68. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/generator/mix.js +0 -0
  69. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/generator/random.js +0 -0
  70. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/generator/scale.js +0 -0
  71. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/interpolator/_hsx.js +0 -0
  72. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hcg.js +0 -0
  73. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsi.js +0 -0
  74. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsl.js +0 -0
  75. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsv.js +0 -0
  76. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/interpolator/index.js +0 -0
  77. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lab.js +0 -0
  78. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lch.js +0 -0
  79. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lrgb.js +0 -0
  80. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/interpolator/num.js +0 -0
  81. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklab.js +0 -0
  82. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklch.js +0 -0
  83. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/interpolator/rgb.js +0 -0
  84. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/cmyk2rgb.js +0 -0
  85. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/index.js +0 -0
  86. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/rgb2cmyk.js +0 -0
  87. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/css/css2rgb.js +0 -0
  88. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/css/hsl2css.js +0 -0
  89. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/css/index.js +0 -0
  90. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/css/lab2css.js +0 -0
  91. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/css/lch2css.js +0 -0
  92. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/css/oklab2css.js +0 -0
  93. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/css/oklch2css.js +0 -0
  94. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/css/rgb2css.js +0 -0
  95. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/gl/index.js +0 -0
  96. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/hcg2rgb.js +0 -0
  97. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/index.js +0 -0
  98. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/rgb2hcg.js +0 -0
  99. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/hex/hex2rgb.js +0 -0
  100. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/hex/index.js +0 -0
  101. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/hex/rgb2hex.js +0 -0
  102. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/hsi2rgb.js +0 -0
  103. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/index.js +0 -0
  104. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/rgb2hsi.js +0 -0
  105. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/hsl2rgb.js +0 -0
  106. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/index.js +0 -0
  107. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/rgb2hsl.js +0 -0
  108. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/hsv2rgb.js +0 -0
  109. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/index.js +0 -0
  110. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/rgb2hsv.js +0 -0
  111. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/input.js +0 -0
  112. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/lab/index.js +0 -0
  113. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab-constants.js +0 -0
  114. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab2rgb.js +0 -0
  115. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/lab/rgb2lab.js +0 -0
  116. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/lch/hcl2rgb.js +0 -0
  117. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/lch/index.js +0 -0
  118. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lab2lch.js +0 -0
  119. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2lab.js +0 -0
  120. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2rgb.js +0 -0
  121. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/lch/rgb2lch.js +0 -0
  122. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/named/index.js +0 -0
  123. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/num/index.js +0 -0
  124. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/num/num2rgb.js +0 -0
  125. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/num/rgb2num.js +0 -0
  126. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/index.js +0 -0
  127. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/oklab2rgb.js +0 -0
  128. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/rgb2oklab.js +0 -0
  129. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/index.js +0 -0
  130. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/oklch2rgb.js +0 -0
  131. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/rgb2oklch.js +0 -0
  132. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/rgb/index.js +0 -0
  133. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/temp/index.js +0 -0
  134. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/temp/rgb2temperature.js +0 -0
  135. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/io/temp/temperature2rgb.js +0 -0
  136. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/ops/alpha.js +0 -0
  137. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/ops/clipped.js +0 -0
  138. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/ops/darken.js +0 -0
  139. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/ops/get.js +0 -0
  140. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/ops/luminance.js +0 -0
  141. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/ops/mix.js +0 -0
  142. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/ops/premultiply.js +0 -0
  143. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/ops/saturate.js +0 -0
  144. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/ops/set.js +0 -0
  145. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/ops/shade.js +0 -0
  146. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/utils/analyze.js +0 -0
  147. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/utils/clip_rgb.js +0 -0
  148. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/utils/contrast.js +0 -0
  149. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/utils/contrastAPCA.js +0 -0
  150. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/utils/delta-e.js +0 -0
  151. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/utils/distance.js +0 -0
  152. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/utils/index.js +0 -0
  153. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/utils/last.js +0 -0
  154. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/utils/limit.js +0 -0
  155. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/utils/multiply-matrices.js +0 -0
  156. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/utils/scales.js +0 -0
  157. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/utils/type.js +0 -0
  158. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/utils/unpack.js +0 -0
  159. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/utils/valid.js +0 -0
  160. {simview-3.3 → simview-3.4}/simview/static/lib/chroma-js-3.1.2/src/version.js +0 -0
  161. {simview-3.3 → simview-3.4}/simview/static/lib/download.js +0 -0
  162. {simview-3.3 → simview-3.4}/simview/static/lib/js-colormaps.js +0 -0
  163. {simview-3.3 → simview-3.4}/simview/static/lib/tar.js +0 -0
  164. {simview-3.3 → simview-3.4}/simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +0 -0
  165. {simview-3.3 → simview-3.4}/simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +0 -0
  166. {simview-3.3 → simview-3.4}/simview/static/lib/three-0.174.0/three.core.js +0 -0
  167. {simview-3.3 → simview-3.4}/simview/static/lib/three-0.174.0/three.module.js +0 -0
  168. {simview-3.3 → simview-3.4}/simview/static/lib/uPlot.esm.js +0 -0
  169. {simview-3.3 → simview-3.4}/simview/static/lib/uPlot.min.css +0 -0
  170. {simview-3.3 → simview-3.4}/simview/static/textures/contacts/red-cross0.png +0 -0
  171. {simview-3.3 → simview-3.4}/simview/static/textures/points/ball0.png +0 -0
  172. {simview-3.3 → simview-3.4}/simview/static/textures/points/ball1.png +0 -0
  173. {simview-3.3 → simview-3.4}/simview/templates/index.html +0 -0
  174. {simview-3.3 → simview-3.4}/simview/utils.py +0 -0
  175. {simview-3.3 → simview-3.4}/simview.egg-info/dependency_links.txt +0 -0
  176. {simview-3.3 → simview-3.4}/simview.egg-info/entry_points.txt +0 -0
  177. {simview-3.3 → simview-3.4}/simview.egg-info/requires.txt +0 -0
  178. {simview-3.3 → simview-3.4}/simview.egg-info/top_level.txt +0 -0
  179. {simview-3.3 → simview-3.4}/tests/test_columnar_states.py +0 -0
  180. {simview-3.3 → simview-3.4}/tests/test_contacts.py +0 -0
  181. {simview-3.3 → simview-3.4}/tests/test_launcher.py +0 -0
  182. {simview-3.3 → simview-3.4}/tests/test_live.py +0 -0
  183. {simview-3.3 → simview-3.4}/tests/test_merge.py +0 -0
  184. {simview-3.3 → simview-3.4}/tests/test_roundtrip.py +0 -0
  185. {simview-3.3 → simview-3.4}/tests/test_scene.py +0 -0
  186. {simview-3.3 → simview-3.4}/tests/test_server.py +0 -0
  187. {simview-3.3 → simview-3.4}/tests/test_show.py +0 -0
  188. {simview-3.3 → simview-3.4}/tests/test_terrain.py +0 -0
  189. {simview-3.3 → simview-3.4}/tests/test_trajectory.py +0 -0
  190. {simview-3.3 → simview-3.4}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simview
3
- Version: 3.3
3
+ Version: 3.4
4
4
  Summary: THREE.js-based simulation visualization tool
5
5
  Author-email: Jan Vlk <vlkjan6@fel.cvut.cz>, Ales Kucera <kuceral4@fel.cvut.cz>, David Korcak <david.korcak@gmail.com>
6
6
  License-Expression: BSD-3-Clause
@@ -68,16 +68,28 @@ require `torch`.
68
68
 
69
69
  Requires **Python 3.12+**.
70
70
 
71
+ ### From PyPI
72
+
71
73
  To only view existing simulation JSON files:
72
74
 
73
75
  ```bash
74
- pip install -e .
76
+ pip install simview
75
77
  ```
76
78
 
77
79
  To also author simulations from Python (installs `torch` and `einops`):
78
80
 
79
81
  ```bash
80
- pip install -e ".[authoring]"
82
+ pip install "simview[authoring]"
83
+ ```
84
+
85
+ ### From source
86
+
87
+ For working on SimView itself (or to track an unreleased commit), install an
88
+ editable checkout instead:
89
+
90
+ ```bash
91
+ pip install -e . # viewing only
92
+ pip install -e ".[authoring]" # viewing + authoring
81
93
  ```
82
94
 
83
95
  For independent use of this repository, use `venv` or `uv`:
@@ -108,6 +120,67 @@ all of this using the following command:
108
120
  simview clear
109
121
  ```
110
122
 
123
+ ### Inspecting a scene file
124
+
125
+ To print a quick structural summary of a scene JSON file (body/terrain/state
126
+ breakdown, plus consistency warnings) without opening the viewer:
127
+
128
+ ```bash
129
+ simview info scene.json # human-readable text
130
+ simview info scene.json --json # machine-readable JSON (for scripts/agents)
131
+ ```
132
+
133
+ Works on gzip-compressed files automatically, and does not require the
134
+ `authoring` extra.
135
+
136
+ ### Querying terrain data
137
+
138
+ To read raw numeric terrain values (height, and friction/stiffness if present)
139
+ at a single point or over an area, without opening the viewer:
140
+
141
+ ```bash
142
+ simview terrain scene.json --point 1.5 -2.0 # bilinear-interpolated value(s) at (x, y)
143
+ simview terrain scene.json --area # whole terrain extent
144
+ simview terrain scene.json --area -5 5 -5 0 # xmin xmax ymin ymax sub-box
145
+ simview terrain scene.json --area --json # machine-readable JSON (for scripts/agents)
146
+ simview terrain scene.json --area --csv # CSV (for pandas/spreadsheets)
147
+ ```
148
+
149
+ Add `--layer height|friction|stiffness` to restrict to one layer, `--batch N`
150
+ to pick a batch (only matters when the terrain isn't a singleton), and
151
+ `--stride N` to subsample an `--area` query. Like `simview info`, this works
152
+ on gzip-compressed files and doesn't require the `authoring` extra.
153
+
154
+ Pass `--batches A B` instead of `--batch N` to compare two batches directly
155
+ (e.g. a ground-truth terrain vs. a recovered friction/stiffness estimate)
156
+ instead of querying a single one — each layer then reports `value_a`,
157
+ `value_b`, and their `delta`:
158
+
159
+ ```bash
160
+ simview terrain scene.json --area --batches 0 1 --json # ground truth vs. estimate, whole extent
161
+ ```
162
+
163
+ ### Comparing two batches' trajectories
164
+
165
+ To check how far apart two batches' trajectories are — e.g. ground truth vs.
166
+ a model's prediction, or baseline vs. post-adaptation — without opening the
167
+ viewer:
168
+
169
+ ```bash
170
+ simview diff scene.json --batches 0 1 # every body, human-readable text
171
+ simview diff scene.json --batches 0 1 --json # machine-readable JSON (for scripts/agents)
172
+ simview diff scene.json --batches 0 1 --csv # CSV, one row per (body, frame)
173
+ simview diff scene.json --batches 0 1 --body Box # restrict to one body
174
+ ```
175
+
176
+ For each body, this reports per-frame position error (meters) and
177
+ orientation error (degrees, quaternion angular distance) between the two
178
+ batches, plus mean/max/final summaries. Add `--every N` to subsample frames,
179
+ and `--pos-threshold METERS`/`--rot-threshold-deg DEGREES` to report the
180
+ first frame where a batch's trajectory diverges past a given tolerance. Like
181
+ `simview info`/`simview terrain`, this works on gzip-compressed files and
182
+ doesn't require the `authoring` extra.
183
+
111
184
  ### Visualization of exported simulations
112
185
 
113
186
  To visualize a simulation defined in a JSON file, run the following command, replacing `[path_to_json_file]` with the actual path to your JSON data:
@@ -37,16 +37,28 @@ require `torch`.
37
37
 
38
38
  Requires **Python 3.12+**.
39
39
 
40
+ ### From PyPI
41
+
40
42
  To only view existing simulation JSON files:
41
43
 
42
44
  ```bash
43
- pip install -e .
45
+ pip install simview
44
46
  ```
45
47
 
46
48
  To also author simulations from Python (installs `torch` and `einops`):
47
49
 
48
50
  ```bash
49
- pip install -e ".[authoring]"
51
+ pip install "simview[authoring]"
52
+ ```
53
+
54
+ ### From source
55
+
56
+ For working on SimView itself (or to track an unreleased commit), install an
57
+ editable checkout instead:
58
+
59
+ ```bash
60
+ pip install -e . # viewing only
61
+ pip install -e ".[authoring]" # viewing + authoring
50
62
  ```
51
63
 
52
64
  For independent use of this repository, use `venv` or `uv`:
@@ -77,6 +89,67 @@ all of this using the following command:
77
89
  simview clear
78
90
  ```
79
91
 
92
+ ### Inspecting a scene file
93
+
94
+ To print a quick structural summary of a scene JSON file (body/terrain/state
95
+ breakdown, plus consistency warnings) without opening the viewer:
96
+
97
+ ```bash
98
+ simview info scene.json # human-readable text
99
+ simview info scene.json --json # machine-readable JSON (for scripts/agents)
100
+ ```
101
+
102
+ Works on gzip-compressed files automatically, and does not require the
103
+ `authoring` extra.
104
+
105
+ ### Querying terrain data
106
+
107
+ To read raw numeric terrain values (height, and friction/stiffness if present)
108
+ at a single point or over an area, without opening the viewer:
109
+
110
+ ```bash
111
+ simview terrain scene.json --point 1.5 -2.0 # bilinear-interpolated value(s) at (x, y)
112
+ simview terrain scene.json --area # whole terrain extent
113
+ simview terrain scene.json --area -5 5 -5 0 # xmin xmax ymin ymax sub-box
114
+ simview terrain scene.json --area --json # machine-readable JSON (for scripts/agents)
115
+ simview terrain scene.json --area --csv # CSV (for pandas/spreadsheets)
116
+ ```
117
+
118
+ Add `--layer height|friction|stiffness` to restrict to one layer, `--batch N`
119
+ to pick a batch (only matters when the terrain isn't a singleton), and
120
+ `--stride N` to subsample an `--area` query. Like `simview info`, this works
121
+ on gzip-compressed files and doesn't require the `authoring` extra.
122
+
123
+ Pass `--batches A B` instead of `--batch N` to compare two batches directly
124
+ (e.g. a ground-truth terrain vs. a recovered friction/stiffness estimate)
125
+ instead of querying a single one — each layer then reports `value_a`,
126
+ `value_b`, and their `delta`:
127
+
128
+ ```bash
129
+ simview terrain scene.json --area --batches 0 1 --json # ground truth vs. estimate, whole extent
130
+ ```
131
+
132
+ ### Comparing two batches' trajectories
133
+
134
+ To check how far apart two batches' trajectories are — e.g. ground truth vs.
135
+ a model's prediction, or baseline vs. post-adaptation — without opening the
136
+ viewer:
137
+
138
+ ```bash
139
+ simview diff scene.json --batches 0 1 # every body, human-readable text
140
+ simview diff scene.json --batches 0 1 --json # machine-readable JSON (for scripts/agents)
141
+ simview diff scene.json --batches 0 1 --csv # CSV, one row per (body, frame)
142
+ simview diff scene.json --batches 0 1 --body Box # restrict to one body
143
+ ```
144
+
145
+ For each body, this reports per-frame position error (meters) and
146
+ orientation error (degrees, quaternion angular distance) between the two
147
+ batches, plus mean/max/final summaries. Add `--every N` to subsample frames,
148
+ and `--pos-threshold METERS`/`--rot-threshold-deg DEGREES` to report the
149
+ first frame where a batch's trajectory diverges past a given tolerance. Like
150
+ `simview info`/`simview terrain`, this works on gzip-compressed files and
151
+ doesn't require the `authoring` extra.
152
+
80
153
  ### Visualization of exported simulations
81
154
 
82
155
  To visualize a simulation defined in a JSON file, run the following command, replacing `[path_to_json_file]` with the actual path to your JSON data:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simview"
7
- version = "3.3"
7
+ version = "3.4"
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.12"
10
10
  license = "BSD-3-Clause"
@@ -0,0 +1,463 @@
1
+ import argparse
2
+ import gzip
3
+ import json
4
+ import logging
5
+ import shutil
6
+ import sys
7
+ import tempfile
8
+ from pathlib import Path
9
+
10
+ from simview import CACHE_DIR, __version__
11
+ from simview.server import SimViewServer
12
+
13
+ logger = logging.getLogger("simview.cli")
14
+
15
+ _logging_configured = False
16
+
17
+
18
+ def _configure_logging() -> None:
19
+ """CLI entry point default: INFO-level, message-only output on stderr.
20
+
21
+ Kept minimal (no timestamps/level names) since this is user-facing CLI
22
+ chatter, not application log output meant for parsing. Idempotent, so
23
+ calling `main()` more than once in the same process (e.g. across tests)
24
+ doesn't stack up duplicate handlers.
25
+ """
26
+ global _logging_configured
27
+ if _logging_configured:
28
+ return
29
+ handler = logging.StreamHandler(sys.stderr)
30
+ handler.setFormatter(logging.Formatter("%(message)s"))
31
+ root = logging.getLogger("simview")
32
+ root.addHandler(handler)
33
+ root.setLevel(logging.INFO)
34
+ _logging_configured = True
35
+
36
+
37
+ def _package_version() -> str:
38
+ """The installed simview version, with a sensible fallback for editable
39
+ checkouts run without an installed distribution (e.g. `python -m simview`
40
+ from a source tree that was never `pip install -e`'d)."""
41
+ if __version__ == "0.0.0.dev0":
42
+ return "unknown (not installed)"
43
+ return __version__
44
+
45
+
46
+ def clear_cache():
47
+ # Legacy cache directories (kept for cleanup of older installs).
48
+ for cache_dir in (Path("/tmp") / CACHE_DIR, Path.home() / ".cache" / CACHE_DIR):
49
+ if cache_dir.exists():
50
+ logger.info("Removing %s", cache_dir)
51
+ shutil.rmtree(cache_dir, ignore_errors=True)
52
+
53
+ # Temp scenes written by SimViewLauncher (tempfile.mkstemp with this prefix);
54
+ # these leak if a launched viewer is killed before cleanup runs.
55
+ removed = 0
56
+ for leftover in Path(tempfile.gettempdir()).glob("simview_viz_*.json"):
57
+ try:
58
+ leftover.unlink()
59
+ removed += 1
60
+ except OSError as e:
61
+ logger.warning("Could not remove %s: %s", leftover, e)
62
+ if removed:
63
+ logger.info("Removed %d leftover temporary scene file(s).", removed)
64
+
65
+ logger.info("Cache cleared.")
66
+
67
+
68
+ def run_info(path: Path, as_json: bool) -> None:
69
+ """Print a structural summary of the scene JSON at `path` to stdout."""
70
+ from simview.info import format_text, summarize_scene
71
+
72
+ try:
73
+ summary = summarize_scene(path)
74
+ except (json.JSONDecodeError, ValueError, KeyError) as e:
75
+ logger.error("Error: Could not parse '%s' as a scene file: %s", path, e)
76
+ sys.exit(1)
77
+
78
+ if as_json:
79
+ print(json.dumps(summary, indent=2))
80
+ else:
81
+ print(format_text(summary))
82
+
83
+
84
+ def run_terrain(path: Path, args: argparse.Namespace) -> None:
85
+ """Print terrain value(s) at a point or over an area, from the scene JSON
86
+ at `path`, to stdout. With `--batches A B`, compares two batches instead
87
+ of querying a single one."""
88
+ from simview import terrain as terrain_mod
89
+
90
+ if (args.point is None) == (args.area is None):
91
+ logger.error(
92
+ "Error: 'simview terrain' requires exactly one of --point or --area."
93
+ )
94
+ sys.exit(1)
95
+ if args.area is not None and len(args.area) not in (0, 4):
96
+ logger.error(
97
+ "Error: --area requires 0 values (whole terrain extent) or 4 values "
98
+ "(xmin xmax ymin ymax); got %d.",
99
+ len(args.area),
100
+ )
101
+ sys.exit(1)
102
+ if args.json and args.csv:
103
+ logger.error("Error: --json and --csv are mutually exclusive.")
104
+ sys.exit(1)
105
+
106
+ diff_mode = args.batches is not None
107
+
108
+ try:
109
+ model_data = terrain_mod.load_scene_model(path)
110
+ if diff_mode:
111
+ batch_a, batch_b = args.batches
112
+ if args.point is not None:
113
+ result = terrain_mod.query_point_diff(
114
+ model_data,
115
+ args.point[0],
116
+ args.point[1],
117
+ batch_a,
118
+ batch_b,
119
+ args.layer,
120
+ )
121
+ text = terrain_mod.format_point_diff_text(result)
122
+ csv_text = terrain_mod.format_point_diff_csv(result)
123
+ else:
124
+ bounds = tuple(args.area) if args.area else None
125
+ result = terrain_mod.query_area_diff(
126
+ model_data, bounds, batch_a, batch_b, args.layer, args.stride
127
+ )
128
+ text = terrain_mod.format_area_diff_text(result)
129
+ csv_text = terrain_mod.format_area_diff_csv(result)
130
+ elif args.point is not None:
131
+ result = terrain_mod.query_point(
132
+ model_data, args.point[0], args.point[1], args.layer, args.batch
133
+ )
134
+ text = terrain_mod.format_point_text(result)
135
+ csv_text = terrain_mod.format_point_csv(result)
136
+ else:
137
+ bounds = tuple(args.area) if args.area else None
138
+ result = terrain_mod.query_area(
139
+ model_data, bounds, args.layer, args.batch, args.stride
140
+ )
141
+ text = terrain_mod.format_area_text(result)
142
+ csv_text = terrain_mod.format_area_csv(result)
143
+ except (json.JSONDecodeError, ValueError, KeyError) as e:
144
+ logger.error("Error: %s", e)
145
+ sys.exit(1)
146
+
147
+ if args.csv:
148
+ print(csv_text, end="")
149
+ elif args.json:
150
+ print(json.dumps(result, indent=2))
151
+ else:
152
+ print(text)
153
+
154
+
155
+ def run_diff(path: Path, args: argparse.Namespace) -> None:
156
+ """Print a trajectory divergence report between two batches of the scene
157
+ JSON at `path`, to stdout."""
158
+ from simview import diff as diff_mod
159
+
160
+ if args.batches is None:
161
+ logger.error(
162
+ "Error: 'simview diff' requires --batches A B, e.g. 'simview diff "
163
+ "scene.json --batches 0 1'."
164
+ )
165
+ sys.exit(1)
166
+ if args.json and args.csv:
167
+ logger.error("Error: --json and --csv are mutually exclusive.")
168
+ sys.exit(1)
169
+
170
+ try:
171
+ model_data, states_data = diff_mod.load_scene(path)
172
+ result = diff_mod.compute_trajectory_diff(
173
+ model_data,
174
+ states_data,
175
+ args.batches[0],
176
+ args.batches[1],
177
+ body=args.body,
178
+ every=args.every,
179
+ pos_threshold=args.pos_threshold,
180
+ rot_threshold_deg=args.rot_threshold_deg,
181
+ )
182
+ text = diff_mod.format_diff_text(result)
183
+ csv_text = diff_mod.format_diff_csv(result)
184
+ except (json.JSONDecodeError, ValueError, KeyError) as e:
185
+ logger.error("Error: %s", e)
186
+ sys.exit(1)
187
+
188
+ if args.csv:
189
+ print(csv_text, end="")
190
+ elif args.json:
191
+ print(json.dumps(result, indent=2))
192
+ else:
193
+ print(text)
194
+
195
+
196
+ def save_merged(paths: list[Path], out_path: Path) -> None:
197
+ """Merge `paths` (must be >= 2) and write the result to `out_path`, gzipped
198
+ if it ends in .gz, without starting the server."""
199
+ if len(paths) < 2:
200
+ logger.error("Error: --save-merged requires at least 2 input files to merge.")
201
+ sys.exit(1)
202
+
203
+ from simview.merge import merge_simulation_files
204
+
205
+ merged = merge_simulation_files(paths)
206
+ payload = json.dumps(merged).encode("utf-8")
207
+ if out_path.suffix == ".gz":
208
+ payload = gzip.compress(payload, compresslevel=1)
209
+ out_path.write_bytes(payload)
210
+ logger.info("Merged scene written to %s", out_path)
211
+
212
+
213
+ def build_parser() -> argparse.ArgumentParser:
214
+ parser = argparse.ArgumentParser(description="SimView CLI")
215
+ parser.add_argument(
216
+ "inputs",
217
+ nargs="*",
218
+ help=(
219
+ "Path(s) to simulation JSON file(s) to visualize, 'clear' to clear "
220
+ "cache, 'info <file>' to print a structural summary of one scene "
221
+ "file, 'terrain <file>' to query terrain values at a point or over "
222
+ "an area, or 'diff <file>' to compare two batches' trajectories. "
223
+ "Multiple visualize-mode files are merged into one scene, each "
224
+ "file's batches appended as extra batches (e.g. a real-world "
225
+ "recording plus a simulated rerun)."
226
+ ),
227
+ )
228
+ parser.add_argument(
229
+ "--version",
230
+ action="store_true",
231
+ help="Print the installed simview version and exit.",
232
+ )
233
+ parser.add_argument(
234
+ "--json",
235
+ action="store_true",
236
+ help=(
237
+ "With 'simview info <file>', 'simview terrain <file>', or "
238
+ "'simview diff <file>', print machine-readable JSON instead of "
239
+ "text."
240
+ ),
241
+ )
242
+ parser.add_argument(
243
+ "--csv",
244
+ action="store_true",
245
+ help=(
246
+ "With 'simview terrain <file>' or 'simview diff <file>', print "
247
+ "CSV instead of text. Mutually exclusive with --json."
248
+ ),
249
+ )
250
+ parser.add_argument(
251
+ "--point",
252
+ nargs=2,
253
+ type=float,
254
+ metavar=("X", "Y"),
255
+ help=(
256
+ "With 'simview terrain <file>', query terrain value(s) at a single "
257
+ "(x, y) point (bilinear-interpolated). Mutually exclusive with "
258
+ "--area."
259
+ ),
260
+ )
261
+ parser.add_argument(
262
+ "--area",
263
+ nargs="*",
264
+ type=float,
265
+ metavar="BOUND",
266
+ help=(
267
+ "With 'simview terrain <file>', query terrain values over a "
268
+ "rectangular area: pass no values for the whole terrain extent, "
269
+ "or exactly 4 values 'XMIN XMAX YMIN YMAX' for a sub-box. Mutually "
270
+ "exclusive with --point."
271
+ ),
272
+ )
273
+ parser.add_argument(
274
+ "--layer",
275
+ choices=["height", "friction", "stiffness", "all"],
276
+ default="all",
277
+ help=(
278
+ "With 'simview terrain <file>', which terrain layer(s) to query "
279
+ "(default: all present)."
280
+ ),
281
+ )
282
+ parser.add_argument(
283
+ "--batch",
284
+ type=int,
285
+ default=0,
286
+ help="With 'simview terrain <file>', batch index to query (default: 0).",
287
+ )
288
+ parser.add_argument(
289
+ "--stride",
290
+ type=int,
291
+ default=1,
292
+ help=(
293
+ "With 'simview terrain <file> --area', subsample every Nth grid "
294
+ "point in both directions (default: 1)."
295
+ ),
296
+ )
297
+ parser.add_argument(
298
+ "--batches",
299
+ nargs=2,
300
+ type=int,
301
+ metavar=("A", "B"),
302
+ help=(
303
+ "Two batch indices to compare. Required by 'simview diff <file>'. "
304
+ "With 'simview terrain <file> --point|--area', switches into "
305
+ "cross-batch diff mode (value_a/value_b/delta per layer) instead "
306
+ "of a single-batch query, and takes precedence over --batch if "
307
+ "both are given."
308
+ ),
309
+ )
310
+ parser.add_argument(
311
+ "--body",
312
+ type=str,
313
+ default=None,
314
+ metavar="NAME",
315
+ help=(
316
+ "With 'simview diff <file>', restrict the trajectory diff to one "
317
+ "body (matched by its full label, e.g. 'wheel_fl+wheel_fr', or by "
318
+ "any single name inside a rigidly-grouped body). Default: diff "
319
+ "every body present in states."
320
+ ),
321
+ )
322
+ parser.add_argument(
323
+ "--every",
324
+ type=int,
325
+ default=1,
326
+ help="With 'simview diff <file>', sample every Nth frame (default: 1).",
327
+ )
328
+ parser.add_argument(
329
+ "--pos-threshold",
330
+ type=float,
331
+ default=None,
332
+ metavar="METERS",
333
+ help=(
334
+ "With 'simview diff <file>', report the first sampled frame whose "
335
+ "position error exceeds this many meters."
336
+ ),
337
+ )
338
+ parser.add_argument(
339
+ "--rot-threshold-deg",
340
+ type=float,
341
+ default=None,
342
+ metavar="DEGREES",
343
+ help=(
344
+ "With 'simview diff <file>', report the first sampled frame whose "
345
+ "orientation error exceeds this many degrees."
346
+ ),
347
+ )
348
+ parser.add_argument(
349
+ "--host",
350
+ type=str,
351
+ default="127.0.0.1",
352
+ help="Host/interface for the server to bind to (default: 127.0.0.1).",
353
+ )
354
+ parser.add_argument(
355
+ "--port",
356
+ type=int,
357
+ default=5420,
358
+ help=(
359
+ "Port for the server to use (default: 5420). If it's already taken, "
360
+ "the next free port is used instead."
361
+ ),
362
+ )
363
+ parser.add_argument(
364
+ "--no-browser",
365
+ action="store_true",
366
+ help="Don't automatically open a browser tab once the server starts.",
367
+ )
368
+ parser.add_argument(
369
+ "--save-merged",
370
+ type=str,
371
+ default=None,
372
+ metavar="PATH",
373
+ help=(
374
+ "Merge the given input files and write the result to PATH instead of "
375
+ "launching the viewer. Requires at least 2 input files. Gzips the "
376
+ "output if PATH ends in .gz."
377
+ ),
378
+ )
379
+ return parser
380
+
381
+
382
+ def main():
383
+ _configure_logging()
384
+ parser = build_parser()
385
+ args = parser.parse_args()
386
+
387
+ if args.version:
388
+ print(_package_version())
389
+ return
390
+
391
+ if not args.inputs:
392
+ parser.print_help()
393
+ sys.exit(1)
394
+
395
+ if args.inputs and args.inputs[0] == "info":
396
+ info_args = args.inputs[1:]
397
+ if len(info_args) != 1:
398
+ logger.error(
399
+ "Error: 'simview info' requires exactly one file argument, e.g. "
400
+ "'simview info scene.json'."
401
+ )
402
+ sys.exit(1)
403
+ info_path = Path(info_args[0])
404
+ if not (info_path.exists() and info_path.is_file()):
405
+ logger.error("Error: File '%s' not found or is not a file.", info_path)
406
+ sys.exit(1)
407
+ run_info(info_path, as_json=args.json)
408
+ return
409
+
410
+ if args.inputs and args.inputs[0] == "terrain":
411
+ terrain_args = args.inputs[1:]
412
+ if len(terrain_args) != 1:
413
+ logger.error(
414
+ "Error: 'simview terrain' requires exactly one file argument, "
415
+ "e.g. 'simview terrain scene.json --point 0 0'."
416
+ )
417
+ sys.exit(1)
418
+ terrain_path = Path(terrain_args[0])
419
+ if not (terrain_path.exists() and terrain_path.is_file()):
420
+ logger.error("Error: File '%s' not found or is not a file.", terrain_path)
421
+ sys.exit(1)
422
+ run_terrain(terrain_path, args)
423
+ return
424
+
425
+ if args.inputs and args.inputs[0] == "diff":
426
+ diff_args = args.inputs[1:]
427
+ if len(diff_args) != 1:
428
+ logger.error(
429
+ "Error: 'simview diff' requires exactly one file argument, "
430
+ "e.g. 'simview diff scene.json --batches 0 1'."
431
+ )
432
+ sys.exit(1)
433
+ diff_path = Path(diff_args[0])
434
+ if not (diff_path.exists() and diff_path.is_file()):
435
+ logger.error("Error: File '%s' not found or is not a file.", diff_path)
436
+ sys.exit(1)
437
+ run_diff(diff_path, args)
438
+ return
439
+
440
+ if args.inputs == ["clear"]:
441
+ clear_cache()
442
+ return
443
+
444
+ paths = [Path(p) for p in args.inputs]
445
+ for path in paths:
446
+ if not (path.exists() and path.is_file()):
447
+ logger.error("Error: File '%s' not found or is not a file.", path)
448
+ sys.exit(1)
449
+
450
+ if args.save_merged:
451
+ save_merged(paths, Path(args.save_merged))
452
+ return
453
+
454
+ SimViewServer.start(
455
+ sim_path=paths if len(paths) > 1 else paths[0],
456
+ host=args.host,
457
+ preferred_port=args.port,
458
+ open_browser=not args.no_browser,
459
+ )
460
+
461
+
462
+ if __name__ == "__main__":
463
+ main()