simview 5.1.0__tar.gz → 6.0.0__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 (208) hide show
  1. {simview-5.1.0/simview.egg-info → simview-6.0.0}/PKG-INFO +3 -3
  2. {simview-5.1.0 → simview-6.0.0}/README.md +2 -2
  3. {simview-5.1.0 → simview-6.0.0}/pyproject.toml +1 -1
  4. {simview-5.1.0 → simview-6.0.0}/simview/__init__.py +3 -2
  5. {simview-5.1.0 → simview-6.0.0}/simview/__main__.py +40 -81
  6. {simview-5.1.0 → simview-6.0.0}/simview/columnar.py +9 -4
  7. {simview-5.1.0 → simview-6.0.0}/simview/diff.py +42 -76
  8. {simview-5.1.0 → simview-6.0.0}/simview/info.py +4 -4
  9. {simview-5.1.0 → simview-6.0.0}/simview/launcher.py +3 -14
  10. {simview-5.1.0 → simview-6.0.0}/simview/live.py +4 -119
  11. {simview-5.1.0 → simview-6.0.0}/simview/merge.py +48 -124
  12. {simview-5.1.0 → simview-6.0.0}/simview/model.py +14 -213
  13. {simview-5.1.0 → simview-6.0.0}/simview/remote.py +1 -3
  14. {simview-5.1.0 → simview-6.0.0}/simview/render.py +13 -54
  15. {simview-5.1.0 → simview-6.0.0}/simview/scene.py +121 -132
  16. {simview-5.1.0 → simview-6.0.0}/simview/server.py +126 -87
  17. {simview-5.1.0 → simview-6.0.0}/simview/state.py +15 -33
  18. {simview-5.1.0 → simview-6.0.0}/simview/static/css/controls.css +99 -177
  19. {simview-5.1.0 → simview-6.0.0}/simview/static/js/SimView.js +9 -50
  20. {simview-5.1.0 → simview-6.0.0}/simview/static/js/components/AnimationController.js +24 -74
  21. {simview-5.1.0 → simview-6.0.0}/simview/static/js/components/BatchManager.js +3 -29
  22. {simview-5.1.0 → simview-6.0.0}/simview/static/js/components/Scene.js +13 -16
  23. {simview-5.1.0 → simview-6.0.0}/simview/static/js/components/WindowedField.js +1 -2
  24. {simview-5.1.0 → simview-6.0.0}/simview/static/js/config.js +0 -4
  25. {simview-5.1.0 → simview-6.0.0}/simview/static/js/objects/Body.js +1 -26
  26. {simview-5.1.0 → simview-6.0.0}/simview/static/js/objects/StaticObject.js +9 -19
  27. {simview-5.1.0 → simview-6.0.0}/simview/static/js/objects/Terrain.js +6 -14
  28. {simview-5.1.0 → simview-6.0.0}/simview/static/js/objects/colormap.js +0 -6
  29. {simview-5.1.0 → simview-6.0.0}/simview/static/js/objects/utils.js +68 -20
  30. simview-6.0.0/simview/static/js/ui/AnalysisPanel.js +88 -0
  31. {simview-5.1.0 → simview-6.0.0}/simview/static/js/ui/BatchLegend.js +7 -22
  32. {simview-5.1.0 → simview-6.0.0}/simview/static/js/ui/BodyStateWindow.js +37 -67
  33. {simview-5.1.0 → simview-6.0.0}/simview/static/js/ui/Controls.js +61 -69
  34. {simview-5.1.0 → simview-6.0.0}/simview/static/js/ui/ErrorMetrics.js +90 -184
  35. {simview-5.1.0 → simview-6.0.0}/simview/static/js/ui/Legend.js +3 -1
  36. {simview-5.1.0 → simview-6.0.0}/simview/static/js/ui/PlaybackControls.js +31 -58
  37. {simview-5.1.0 → simview-6.0.0}/simview/static/js/ui/ScalarPlotter.js +20 -130
  38. {simview-5.1.0 → simview-6.0.0}/simview/static/js/ui/TerrainProfile.js +19 -138
  39. simview-6.0.0/simview/static/js/ui/chartControls.js +72 -0
  40. simview-6.0.0/simview/static/js/utils/blobCodec.js +42 -0
  41. {simview-5.1.0 → simview-6.0.0}/simview/static/js/utils/csv.js +17 -13
  42. simview-6.0.0/simview/static/js/utils/uplot.js +118 -0
  43. {simview-5.1.0 → simview-6.0.0}/simview/static/js/utils/viewState.js +32 -93
  44. simview-6.0.0/simview/static/lib/js-colormaps.js +3226 -0
  45. {simview-5.1.0 → simview-6.0.0}/simview/templates/index.html +2 -3
  46. {simview-5.1.0 → simview-6.0.0}/simview/terrain.py +67 -135
  47. {simview-5.1.0 → simview-6.0.0}/simview/utils.py +84 -24
  48. {simview-5.1.0 → simview-6.0.0/simview.egg-info}/PKG-INFO +3 -3
  49. simview-6.0.0/simview.egg-info/SOURCES.txt +98 -0
  50. {simview-5.1.0 → simview-6.0.0}/tests/test_columnar_ondisk.py +1 -1
  51. {simview-5.1.0 → simview-6.0.0}/tests/test_roundtrip.py +4 -9
  52. {simview-5.1.0 → simview-6.0.0}/tests/test_scene.py +3 -3
  53. {simview-5.1.0 → simview-6.0.0}/tests/test_show.py +2 -2
  54. simview-5.1.0/simview/static/js/components/InteractionControls.js +0 -63
  55. simview-5.1.0/simview/static/js/ui/AnalysisPanel.js +0 -159
  56. simview-5.1.0/simview/static/js/utils/blobCodec.js +0 -75
  57. simview-5.1.0/simview/static/js/utils/uplot.js +0 -59
  58. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/index.min.js +0 -8
  59. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/Color.js +0 -49
  60. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/chroma.js +0 -10
  61. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/colors/colorbrewer.js +0 -81
  62. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/colors/w3cx11.js +0 -164
  63. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/generator/average.js +0 -88
  64. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/generator/bezier.js +0 -86
  65. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/generator/blend.js +0 -57
  66. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/generator/cubehelix.js +0 -87
  67. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/generator/mix.js +0 -19
  68. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/generator/random.js +0 -12
  69. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/generator/scale.js +0 -394
  70. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/interpolator/_hsx.js +0 -59
  71. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/interpolator/hcg.js +0 -12
  72. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsi.js +0 -12
  73. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsl.js +0 -12
  74. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsv.js +0 -12
  75. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/interpolator/index.js +0 -1
  76. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/interpolator/lab.js +0 -19
  77. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/interpolator/lch.js +0 -13
  78. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/interpolator/lrgb.js +0 -19
  79. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/interpolator/num.js +0 -15
  80. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklab.js +0 -19
  81. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklch.js +0 -12
  82. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/interpolator/rgb.js +0 -18
  83. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/cmyk2rgb.js +0 -16
  84. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/index.js +0 -27
  85. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/rgb2cmyk.js +0 -17
  86. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/css/css2rgb.js +0 -238
  87. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/css/hsl2css.js +0 -26
  88. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/css/index.js +0 -27
  89. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/css/lab2css.js +0 -24
  90. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/css/lch2css.js +0 -24
  91. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/css/oklab2css.js +0 -16
  92. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/css/oklch2css.js +0 -16
  93. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/css/rgb2css.js +0 -61
  94. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/gl/index.js +0 -22
  95. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/hcg/hcg2rgb.js +0 -55
  96. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/hcg/index.js +0 -27
  97. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/hcg/rgb2hcg.js +0 -23
  98. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/hex/hex2rgb.js +0 -56
  99. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/hex/index.js +0 -29
  100. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/hex/rgb2hex.js +0 -29
  101. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/hsi/hsi2rgb.js +0 -45
  102. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/hsi/index.js +0 -27
  103. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/hsi/rgb2hsi.js +0 -31
  104. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/hsl/hsl2rgb.js +0 -35
  105. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/hsl/index.js +0 -27
  106. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/hsl/rgb2hsl.js +0 -45
  107. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/hsv/hsv2rgb.js +0 -47
  108. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/hsv/index.js +0 -27
  109. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/hsv/rgb2hsv.js +0 -32
  110. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/input.js +0 -4
  111. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/lab/index.js +0 -28
  112. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab-constants.js +0 -120
  113. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab2rgb.js +0 -101
  114. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/lab/rgb2lab.js +0 -67
  115. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/lch/hcl2rgb.js +0 -9
  116. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/lch/index.js +0 -35
  117. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/lch/lab2lch.js +0 -12
  118. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2lab.js +0 -18
  119. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2rgb.js +0 -13
  120. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/lch/rgb2lch.js +0 -12
  121. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/named/index.js +0 -30
  122. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/num/index.js +0 -32
  123. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/num/num2rgb.js +0 -13
  124. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/num/rgb2num.js +0 -8
  125. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/oklab/index.js +0 -27
  126. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/oklab/oklab2rgb.js +0 -34
  127. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/oklab/rgb2oklab.js +0 -37
  128. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/oklch/index.js +0 -27
  129. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/oklch/oklch2rgb.js +0 -13
  130. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/oklch/rgb2oklch.js +0 -12
  131. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/rgb/index.js +0 -44
  132. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/temp/index.js +0 -22
  133. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/temp/rgb2temperature.js +0 -30
  134. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/io/temp/temperature2rgb.js +0 -39
  135. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/ops/alpha.js +0 -13
  136. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/ops/clipped.js +0 -5
  137. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/ops/darken.js +0 -17
  138. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/ops/get.js +0 -13
  139. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/ops/luminance.js +0 -54
  140. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/ops/mix.js +0 -10
  141. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/ops/premultiply.js +0 -12
  142. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/ops/saturate.js +0 -15
  143. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/ops/set.js +0 -43
  144. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/ops/shade.js +0 -11
  145. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/utils/analyze.js +0 -191
  146. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/utils/clip_rgb.js +0 -15
  147. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/utils/contrast.js +0 -12
  148. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/utils/contrastAPCA.js +0 -68
  149. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/utils/delta-e.js +0 -62
  150. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/utils/distance.js +0 -17
  151. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/utils/index.js +0 -27
  152. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/utils/last.js +0 -8
  153. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/utils/limit.js +0 -5
  154. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/utils/multiply-matrices.js +0 -36
  155. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/utils/scales.js +0 -15
  156. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/utils/type.js +0 -18
  157. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/utils/unpack.js +0 -17
  158. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/utils/valid.js +0 -11
  159. simview-5.1.0/simview/static/lib/chroma-js-3.1.2/src/version.js +0 -2
  160. simview-5.1.0/simview/static/lib/js-colormaps.js +0 -19335
  161. simview-5.1.0/simview.egg-info/SOURCES.txt +0 -200
  162. {simview-5.1.0 → simview-6.0.0}/LICENSE +0 -0
  163. {simview-5.1.0 → simview-6.0.0}/MANIFEST.in +0 -0
  164. {simview-5.1.0 → simview-6.0.0}/setup.cfg +0 -0
  165. {simview-5.1.0 → simview-6.0.0}/simview/py.typed +0 -0
  166. {simview-5.1.0 → simview-6.0.0}/simview/static/js/components/InteractionController.js +0 -0
  167. {simview-5.1.0 → simview-6.0.0}/simview/static/js/components/StateStore.js +0 -0
  168. {simview-5.1.0 → simview-6.0.0}/simview/static/js/main.js +0 -0
  169. {simview-5.1.0 → simview-6.0.0}/simview/static/js/objects/similarity.js +0 -0
  170. {simview-5.1.0 → simview-6.0.0}/simview/static/js/utils/batchPresets.js +0 -0
  171. {simview-5.1.0 → simview-6.0.0}/simview/static/js/utils/batchVisibility.js +0 -0
  172. {simview-5.1.0 → simview-6.0.0}/simview/static/js/utils/blobWindow.js +0 -0
  173. {simview-5.1.0 → simview-6.0.0}/simview/static/js/utils/bodyTransforms.js +0 -0
  174. {simview-5.1.0 → simview-6.0.0}/simview/static/js/utils/cameraRange.js +0 -0
  175. {simview-5.1.0 → simview-6.0.0}/simview/static/js/utils/episodes.js +0 -0
  176. {simview-5.1.0 → simview-6.0.0}/simview/static/js/utils/errorMath.js +0 -0
  177. {simview-5.1.0 → simview-6.0.0}/simview/static/js/utils/interpolate.js +0 -0
  178. {simview-5.1.0 → simview-6.0.0}/simview/static/js/utils/liveFollow.js +0 -0
  179. {simview-5.1.0 → simview-6.0.0}/simview/static/js/utils/terrainSample.js +0 -0
  180. {simview-5.1.0 → simview-6.0.0}/simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +0 -0
  181. {simview-5.1.0 → simview-6.0.0}/simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +0 -0
  182. {simview-5.1.0 → simview-6.0.0}/simview/static/lib/three-0.174.0/three.core.js +0 -0
  183. {simview-5.1.0 → simview-6.0.0}/simview/static/lib/three-0.174.0/three.module.js +0 -0
  184. {simview-5.1.0 → simview-6.0.0}/simview/static/lib/uPlot.esm.js +0 -0
  185. {simview-5.1.0 → simview-6.0.0}/simview/static/lib/uPlot.min.css +0 -0
  186. {simview-5.1.0 → simview-6.0.0}/simview/static/textures/contacts/red-cross0.png +0 -0
  187. {simview-5.1.0 → simview-6.0.0}/simview/static/textures/points/ball1.png +0 -0
  188. {simview-5.1.0 → simview-6.0.0}/simview.egg-info/dependency_links.txt +0 -0
  189. {simview-5.1.0 → simview-6.0.0}/simview.egg-info/entry_points.txt +0 -0
  190. {simview-5.1.0 → simview-6.0.0}/simview.egg-info/requires.txt +0 -0
  191. {simview-5.1.0 → simview-6.0.0}/simview.egg-info/top_level.txt +0 -0
  192. {simview-5.1.0 → simview-6.0.0}/tests/test_batch_selection.py +0 -0
  193. {simview-5.1.0 → simview-6.0.0}/tests/test_cli.py +0 -0
  194. {simview-5.1.0 → simview-6.0.0}/tests/test_columnar_states.py +0 -0
  195. {simview-5.1.0 → simview-6.0.0}/tests/test_contacts.py +0 -0
  196. {simview-5.1.0 → simview-6.0.0}/tests/test_diff.py +0 -0
  197. {simview-5.1.0 → simview-6.0.0}/tests/test_episodes.py +0 -0
  198. {simview-5.1.0 → simview-6.0.0}/tests/test_info.py +0 -0
  199. {simview-5.1.0 → simview-6.0.0}/tests/test_launcher.py +0 -0
  200. {simview-5.1.0 → simview-6.0.0}/tests/test_live.py +0 -0
  201. {simview-5.1.0 → simview-6.0.0}/tests/test_merge.py +0 -0
  202. {simview-5.1.0 → simview-6.0.0}/tests/test_pointcloud_features.py +0 -0
  203. {simview-5.1.0 → simview-6.0.0}/tests/test_remote.py +0 -0
  204. {simview-5.1.0 → simview-6.0.0}/tests/test_server.py +0 -0
  205. {simview-5.1.0 → simview-6.0.0}/tests/test_terrain.py +0 -0
  206. {simview-5.1.0 → simview-6.0.0}/tests/test_terrain_query.py +0 -0
  207. {simview-5.1.0 → simview-6.0.0}/tests/test_trajectory.py +0 -0
  208. {simview-5.1.0 → simview-6.0.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simview
3
- Version: 5.1.0
3
+ Version: 6.0.0
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
@@ -128,8 +128,8 @@ source .venv/bin/activate
128
128
  ## License
129
129
 
130
130
  SimView is distributed under the [BSD 3-Clause License](LICENSE). The web interface
131
- vendors [uPlot](https://github.com/leeoniya/uPlot), [three.js](https://github.com/mrdoob/three.js),
132
- and [chroma-js](https://github.com/gka/chroma.js) (all MIT licensed) under
131
+ vendors [uPlot](https://github.com/leeoniya/uPlot) and [three.js](https://github.com/mrdoob/three.js)
132
+ (both MIT licensed) under
133
133
  `simview/static/lib/` so the viewer works fully offline — see the
134
134
  [documentation](https://vlk-jan.github.io/simview/dev/architecture/#vendored-third-party-libraries)
135
135
  for details. All third-party libraries used by SimView are permissively licensed
@@ -97,8 +97,8 @@ source .venv/bin/activate
97
97
  ## License
98
98
 
99
99
  SimView is distributed under the [BSD 3-Clause License](LICENSE). The web interface
100
- vendors [uPlot](https://github.com/leeoniya/uPlot), [three.js](https://github.com/mrdoob/three.js),
101
- and [chroma-js](https://github.com/gka/chroma.js) (all MIT licensed) under
100
+ vendors [uPlot](https://github.com/leeoniya/uPlot) and [three.js](https://github.com/mrdoob/three.js)
101
+ (both MIT licensed) under
102
102
  `simview/static/lib/` so the viewer works fully offline — see the
103
103
  [documentation](https://vlk-jan.github.io/simview/dev/architecture/#vendored-third-party-libraries)
104
104
  for details. All third-party libraries used by SimView are permissively licensed
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simview"
7
- version = "5.1.0"
7
+ version = "6.0.0"
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.12"
10
10
  license = "BSD-3-Clause"
@@ -24,7 +24,7 @@ logging.getLogger(__name__).addHandler(logging.NullHandler())
24
24
  # the import cost (and dependency requirement) when an authoring symbol is used.
25
25
  _LAZY_EXPORTS = {
26
26
  "SimulationScene": "simview.scene",
27
- "ViewerHandle": "simview.scene",
27
+ "ViewerHandle": "simview.server",
28
28
  "SimViewBody": "simview.model",
29
29
  "SimViewStaticObject": "simview.model",
30
30
  "SimViewTerrain": "simview.model",
@@ -48,7 +48,8 @@ if TYPE_CHECKING:
48
48
  SimViewStaticObject,
49
49
  SimViewTerrain,
50
50
  )
51
- from simview.scene import SimulationScene, ViewerHandle
51
+ from simview.scene import SimulationScene
52
+ from simview.server import ViewerHandle
52
53
  from simview.state import BodyTrajectory, SimViewBodyState
53
54
 
54
55
  __all__ = [
@@ -4,11 +4,11 @@ import json
4
4
  import logging
5
5
  import shutil
6
6
  import sys
7
- import tempfile
8
7
  from pathlib import Path
9
8
 
10
9
  from simview import CACHE_DIR, __version__, remote
11
10
  from simview.server import SimViewServer
11
+ from simview.utils import human_bytes, load_scene, load_scene_model
12
12
 
13
13
  logger = logging.getLogger("simview.cli")
14
14
 
@@ -48,46 +48,25 @@ def _package_version() -> str:
48
48
 
49
49
 
50
50
  def _dir_size(path: Path) -> int:
51
- total = 0
52
- for entry in path.rglob("*"):
53
- try:
54
- if entry.is_file():
55
- total += entry.stat().st_size
56
- except OSError:
57
- pass
58
- return total
51
+ return sum(p.stat().st_size for p in path.rglob("*") if p.is_file())
59
52
 
60
53
 
61
54
  def clear_cache():
62
- """Remove simview's on-disk cache: scene files fetched from remote hosts
63
- (see `simview.remote`), plus locations left behind by older installs."""
55
+ """Remove simview's on-disk cache of scene files fetched from remote hosts
56
+ (see `simview.remote`), including the default location when
57
+ $XDG_CACHE_HOME points elsewhere."""
64
58
  freed = 0
65
- seen: set[Path] = set()
66
- for cache_dir in (
67
- remote.cache_dir(),
68
- Path("/tmp") / CACHE_DIR,
69
- Path.home() / ".cache" / CACHE_DIR,
59
+ for cache_dir in dict.fromkeys(
60
+ (remote.cache_dir(), Path.home() / ".cache" / CACHE_DIR)
70
61
  ):
71
- if cache_dir in seen or not cache_dir.is_dir():
62
+ if not cache_dir.is_dir():
72
63
  continue
73
- seen.add(cache_dir)
74
64
  freed += _dir_size(cache_dir)
75
65
  logger.info("Removing %s", cache_dir)
76
66
  shutil.rmtree(cache_dir, ignore_errors=True)
77
67
 
78
- # Temp scenes written by SimViewLauncher (tempfile.mkstemp with this prefix);
79
- # these leak if a launched viewer is killed before cleanup runs.
80
- removed = 0
81
- for leftover in Path(tempfile.gettempdir()).glob("simview_viz_*.json"):
82
- try:
83
- leftover.unlink()
84
- removed += 1
85
- except OSError as e:
86
- logger.warning("Could not remove %s: %s", leftover, e)
87
- if removed:
88
- logger.info("Removed %d leftover temporary scene file(s).", removed)
89
68
  if freed:
90
- logger.info("Freed %s.", remote.human_bytes(freed))
69
+ logger.info("Freed %s.", human_bytes(freed))
91
70
 
92
71
  logger.info("Cache cleared.")
93
72
 
@@ -128,7 +107,7 @@ def run_terrain(path: Path, args: argparse.Namespace) -> None:
128
107
 
129
108
  try:
130
109
  if args.along_body is not None:
131
- model_data, states_data = terrain_mod.load_scene(path)
110
+ model_data, states_data = load_scene(path)
132
111
  if diff_mode:
133
112
  batch_a, batch_b = args.batches
134
113
  result = terrain_mod.query_along_body_diff(
@@ -154,7 +133,7 @@ def run_terrain(path: Path, args: argparse.Namespace) -> None:
154
133
  text_fn = terrain_mod.format_along_text
155
134
  csv_fn = terrain_mod.format_along_csv
156
135
  else:
157
- model_data = terrain_mod.load_scene_model(path)
136
+ model_data = load_scene_model(path)
158
137
  if diff_mode:
159
138
  batch_a, batch_b = args.batches
160
139
  if args.point is not None:
@@ -226,7 +205,7 @@ def run_diff(path: Path, args: argparse.Namespace) -> None:
226
205
  sys.exit(1)
227
206
 
228
207
  try:
229
- model_data, states_data = diff_mod.load_scene(path)
208
+ model_data, states_data = load_scene(path)
230
209
  result = diff_mod.compute_trajectory_diff(
231
210
  model_data,
232
211
  states_data,
@@ -366,6 +345,24 @@ def _add_json_csv_flags(sp: argparse.ArgumentParser) -> None:
366
345
  fmt.add_argument("--csv", action="store_true", help="Print CSV instead of text.")
367
346
 
368
347
 
348
+ def _add_host_port_flags(sp: argparse.ArgumentParser) -> None:
349
+ sp.add_argument(
350
+ "--host",
351
+ type=str,
352
+ default="127.0.0.1",
353
+ help="Host/interface for the server to bind to (default: 127.0.0.1).",
354
+ )
355
+ sp.add_argument(
356
+ "--port",
357
+ type=int,
358
+ default=5420,
359
+ help=(
360
+ "Port for the server to use (default: 5420). If it's already taken, "
361
+ "the next free port is used instead."
362
+ ),
363
+ )
364
+
365
+
369
366
  def _add_file_arg(sp: argparse.ArgumentParser) -> None:
370
367
  sp.add_argument(
371
368
  "file",
@@ -406,21 +403,7 @@ def build_parser() -> argparse.ArgumentParser:
406
403
  nargs="*",
407
404
  help="Path(s) to simulation JSON file(s) to visualize.",
408
405
  )
409
- view.add_argument(
410
- "--host",
411
- type=str,
412
- default="127.0.0.1",
413
- help="Host/interface for the server to bind to (default: 127.0.0.1).",
414
- )
415
- view.add_argument(
416
- "--port",
417
- type=int,
418
- default=5420,
419
- help=(
420
- "Port for the server to use (default: 5420). If it's already taken, "
421
- "the next free port is used instead."
422
- ),
423
- )
406
+ _add_host_port_flags(view)
424
407
  view.add_argument(
425
408
  "--no-browser",
426
409
  action="store_true",
@@ -606,21 +589,7 @@ def build_parser() -> argparse.ArgumentParser:
606
589
  metavar="PATH",
607
590
  help="PNG file path to save the screenshot to. Required.",
608
591
  )
609
- render.add_argument(
610
- "--host",
611
- type=str,
612
- default="127.0.0.1",
613
- help="Host/interface for the server to bind to (default: 127.0.0.1).",
614
- )
615
- render.add_argument(
616
- "--port",
617
- type=int,
618
- default=5420,
619
- help=(
620
- "Port for the server to use (default: 5420). If it's already taken, "
621
- "the next free port is used instead."
622
- ),
623
- )
592
+ _add_host_port_flags(render)
624
593
  render.add_argument(
625
594
  "--view",
626
595
  type=str,
@@ -696,24 +665,14 @@ def main():
696
665
  clear_cache()
697
666
  return
698
667
 
699
- if args.command == "info":
700
- path = _resolve_input(args.file, args)
701
- run_info(path, as_json=args.json)
702
- return
703
-
704
- if args.command == "terrain":
705
- path = _resolve_input(args.file, args)
706
- run_terrain(path, args)
707
- return
708
-
709
- if args.command == "diff":
710
- path = _resolve_input(args.file, args)
711
- run_diff(path, args)
712
- return
713
-
714
- if args.command == "render":
715
- path = _resolve_input(args.file, args)
716
- run_render(path, args)
668
+ single_file_commands = {
669
+ "info": lambda path, args: run_info(path, as_json=args.json),
670
+ "terrain": run_terrain,
671
+ "diff": run_diff,
672
+ "render": run_render,
673
+ }
674
+ if args.command in single_file_commands:
675
+ single_file_commands[args.command](_resolve_input(args.file, args), args)
717
676
  return
718
677
 
719
678
  # view mode: launch the viewer (or --save-merged) over one or more inputs.
@@ -81,13 +81,18 @@ def inline_blob(raw: bytes) -> str:
81
81
  return BLOB_PREFIX + base64.b64encode(raw).decode()
82
82
 
83
83
 
84
+ def blob_bytes(value: str) -> bytes:
85
+ """Raw bytes of one inline ``__b64__`` blob string."""
86
+ return base64.b64decode(value[len(BLOB_PREFIX) :])
87
+
88
+
84
89
  def blob_floats(value: Any) -> list[float]:
85
90
  """Decode one ``__b64__`` blob (or pass through an already-plain nested
86
91
  list, flattened) into a flat list of floats, without numpy."""
87
92
  if isinstance(value, str):
88
93
  if not value.startswith(BLOB_PREFIX):
89
94
  raise ValueError(f"expected a {BLOB_PREFIX} blob, got {value[:32]!r}")
90
- raw = base64.b64decode(value[len(BLOB_PREFIX) :])
95
+ raw = blob_bytes(value)
91
96
  return list(struct.unpack(f"<{len(raw) // 4}f", raw))
92
97
 
93
98
  flat: list[float] = []
@@ -147,7 +152,7 @@ def _decode_state_field_rows(value, width: int, batch_size: int):
147
152
  if isinstance(value, str):
148
153
  if not value.startswith(BLOB_PREFIX):
149
154
  raise StatesShapeMismatch(f"unexpected string value for field: {value!r}")
150
- flat = np.frombuffer(base64.b64decode(value[len(BLOB_PREFIX) :]), dtype="<f4")
155
+ flat = np.frombuffer(blob_bytes(value), dtype="<f4")
151
156
  else:
152
157
  arr = np.asarray(value, dtype="<f4")
153
158
  if arr.ndim == 1:
@@ -233,7 +238,7 @@ def columnarize_states(states_data: list, model_data: dict | None, register_blob
233
238
 
234
239
  fields = sorted(k for k in body if k in STATE_FIELD_WIDTHS)
235
240
  if key not in body_fields:
236
- if state_idx != 0 and body_rows.get(key) is None:
241
+ if state_idx != 0:
237
242
  # A body appearing for the first time after frame 0
238
243
  # would leave earlier frames' rows undefined -- bail
239
244
  # rather than guess a fill value.
@@ -396,7 +401,7 @@ def expand_columnar_states(states_doc: Any, batch_size: int) -> list[dict]:
396
401
  for field, (flat, width) in fields.items():
397
402
  start = t * B * width
398
403
  chunk = flat[start : start + B * width]
399
- entry[field] = inline_blob(struct.pack(f"<{len(chunk)}f", *chunk))
404
+ entry[field] = encode_floats(chunk)
400
405
  if contacts is not None:
401
406
  value = contacts[t] if t < len(contacts) else None
402
407
  if value is not None:
@@ -16,13 +16,19 @@ truncation) for scripts/coding agents to consume directly, with
16
16
  same data.
17
17
  """
18
18
 
19
- import csv
20
- import io
21
19
  import math
22
20
 
23
21
  from simview.columnar import body_key, decode_transform_row
24
- from simview.utils import body_label, cap, iter_names, resolve_body
25
- from simview.utils import load_scene as load_scene # re-exported for __main__.py
22
+ from simview.utils import (
23
+ bodies_by_name,
24
+ body_label,
25
+ cap,
26
+ collect_body_names,
27
+ iter_names,
28
+ resolve_body,
29
+ series_stats,
30
+ write_csv,
31
+ )
26
32
 
27
33
  _MAX_SERIES_ROWS = 10
28
34
 
@@ -115,19 +121,6 @@ def _compose(parent_row: list[float], local_row: list[float]) -> list[float]:
115
121
  ]
116
122
 
117
123
 
118
- def _expand_raw_bodies(raw_bodies: list | None) -> dict:
119
- """`name -> entry` for one state's `bodies`, expanding grouped (list) name
120
- entries so each individual body name maps to the shared entry."""
121
- expanded: dict = {}
122
- for entry in raw_bodies or []:
123
- name = entry.get("name")
124
- if name is None:
125
- continue
126
- for single in iter_names(name):
127
- expanded[single] = entry
128
- return expanded
129
-
130
-
131
124
  def _resolve_frame(
132
125
  meta: dict[str, dict],
133
126
  topo_order: list[str],
@@ -192,12 +185,6 @@ def _resolve_batches(model_data: dict, batch_a: int, batch_b: int) -> int:
192
185
  return batch_size
193
186
 
194
187
 
195
- def _stats(values: list[float]) -> dict:
196
- if not values:
197
- return {"mean": None, "max": None, "final": None}
198
- return {"mean": sum(values) / len(values), "max": max(values), "final": values[-1]}
199
-
200
-
201
188
  def _first_exceeding(
202
189
  frame_indices: list[int], values: list[float], threshold: float | None
203
190
  ) -> int | None:
@@ -250,18 +237,8 @@ def compute_trajectory_diff(
250
237
  if every < 1:
251
238
  raise ValueError(f"every must be >= 1; got {every}")
252
239
 
253
- all_names: list = []
254
- seen_keys = set()
255
- for state in states_data:
256
- for entry in state.get("bodies") or []:
257
- name = entry.get("name")
258
- if name is None:
259
- continue
260
- key = body_key(name)
261
- if key not in seen_keys:
262
- seen_keys.add(key)
263
- all_names.append(name)
264
-
240
+ all_names = collect_body_names(states_data)
241
+ seen_keys = {body_key(name) for name in all_names}
265
242
  if not all_names:
266
243
  raise ValueError("no bodies found in the scene's states to diff")
267
244
 
@@ -296,7 +273,7 @@ def compute_trajectory_diff(
296
273
  for idx, state in enumerate(states_data):
297
274
  if idx % every != 0:
298
275
  continue
299
- raw_by_name = _expand_raw_bodies(state.get("bodies"))
276
+ raw_by_name = bodies_by_name(state.get("bodies"))
300
277
  # Resolved once per frame for the whole scene rather than per body:
301
278
  # a child's world pose needs its ancestors' poses anyway.
302
279
  rows_a = _resolve_frame(meta, topo_order, raw_by_name, batch_size, batch_a)
@@ -318,41 +295,36 @@ def compute_trajectory_diff(
318
295
  out["err_y"].append(row_a[1] - row_b[1])
319
296
  out["err_z"].append(row_a[2] - row_b[2])
320
297
 
298
+ axes = ("err_x", "err_y", "err_z") if per_axis else ()
321
299
  bodies_out = {}
322
300
  for name in target_names:
323
301
  out = series[body_key(name)]
324
- label = body_label(name)
325
302
  frame_indices = out["frame_indices"]
326
- times = out["times"]
327
- position_error = out["position_error"]
328
- orientation_error_deg = out["orientation_error_deg"]
329
- err_x, err_y, err_z = out["err_x"], out["err_y"], out["err_z"]
330
-
331
303
  summary = {
332
304
  "frame_count": len(frame_indices),
333
- "position_error": _stats(position_error),
334
- "orientation_error_deg": _stats(orientation_error_deg),
305
+ "position_error": series_stats(out["position_error"]),
306
+ "orientation_error_deg": series_stats(out["orientation_error_deg"]),
335
307
  "first_frame_exceeding_pos_threshold": _first_exceeding(
336
- frame_indices, position_error, pos_threshold
308
+ frame_indices, out["position_error"], pos_threshold
337
309
  ),
338
310
  "first_frame_exceeding_rot_threshold": _first_exceeding(
339
- frame_indices, orientation_error_deg, rot_threshold_deg
311
+ frame_indices, out["orientation_error_deg"], rot_threshold_deg
340
312
  ),
341
313
  }
342
- bodies_out[label] = {
343
- "frame_indices": frame_indices,
344
- "times": times,
345
- "position_error": position_error,
346
- "orientation_error_deg": orientation_error_deg,
347
- "summary": summary,
314
+ body_out = {
315
+ key: out[key]
316
+ for key in (
317
+ "frame_indices",
318
+ "times",
319
+ "position_error",
320
+ "orientation_error_deg",
321
+ )
348
322
  }
349
- if per_axis:
350
- bodies_out[label]["err_x"] = err_x
351
- bodies_out[label]["err_y"] = err_y
352
- bodies_out[label]["err_z"] = err_z
353
- summary["err_x"] = _stats(err_x)
354
- summary["err_y"] = _stats(err_y)
355
- summary["err_z"] = _stats(err_z)
323
+ body_out["summary"] = summary
324
+ for axis in axes:
325
+ body_out[axis] = out[axis]
326
+ summary[axis] = series_stats(out[axis])
327
+ bodies_out[body_label(name)] = body_out
356
328
 
357
329
  return {
358
330
  "batch_a": batch_a,
@@ -438,22 +410,16 @@ def format_diff_csv(result: dict) -> str:
438
410
  (body, frame), full series (no truncation) -- CSV output is for scripts,
439
411
  matching the "just the numbers, in full" philosophy `--json` already
440
412
  uses everywhere in this CLI."""
441
- per_axis = result.get("per_axis")
442
- buf = io.StringIO()
443
- writer = csv.writer(buf)
413
+ keys = ["frame_indices", "times", "position_error", "orientation_error_deg"]
444
414
  header = ["body", "frame", "time", "position_error", "orientation_error_deg"]
445
- if per_axis:
415
+ if result.get("per_axis"):
416
+ keys += ["err_x", "err_y", "err_z"]
446
417
  header += ["err_x", "err_y", "err_z"]
447
- writer.writerow(header)
448
- for label, body in result["bodies"].items():
449
- columns = [
450
- body["frame_indices"],
451
- body["times"],
452
- body["position_error"],
453
- body["orientation_error_deg"],
454
- ]
455
- if per_axis:
456
- columns += [body["err_x"], body["err_y"], body["err_z"]]
457
- for row in zip(*columns):
458
- writer.writerow([label, *row])
459
- return buf.getvalue()
418
+ return write_csv(
419
+ header,
420
+ (
421
+ [label, *row]
422
+ for label, body in result["bodies"].items()
423
+ for row in zip(*(body[k] for k in keys))
424
+ ),
425
+ )
@@ -12,14 +12,14 @@ inspection tool. Shares its blob-decoding/body-label helpers with
12
12
  which are stdlib-only on this read path.
13
13
  """
14
14
 
15
- import base64
16
15
  import json
17
16
  from pathlib import Path
18
17
  from typing import Any
19
18
 
20
19
  from simview.columnar import (
21
- BLOB_PREFIX,
22
20
  STATE_FIELD_WIDTHS,
21
+ blob_bytes,
22
+ body_key,
23
23
  expand_columnar_states,
24
24
  is_blob,
25
25
  is_columnar,
@@ -33,7 +33,7 @@ _LARGE_FILE_BYTES = 100_000_000
33
33
  def _blob_byte_length(value: Any) -> int | None:
34
34
  """Decoded byte length if `value` is a `__b64__` blob string, else None."""
35
35
  if is_blob(value):
36
- return len(base64.b64decode(value[len(BLOB_PREFIX) :]))
36
+ return len(blob_bytes(value))
37
37
  return None
38
38
 
39
39
 
@@ -230,7 +230,7 @@ def _summarize_states(states, model: dict | None, warnings: list[str]) -> dict:
230
230
  reasons.append(f"state {idx} has a body entry missing 'name'")
231
231
  continue
232
232
  name = body["name"]
233
- key = tuple(name) if isinstance(name, list) else name
233
+ key = body_key(name)
234
234
  if key in seen_keys:
235
235
  reasons.append(f"state {idx} lists body '{name}' more than once")
236
236
  seen_keys.add(key)
@@ -25,14 +25,10 @@ class SimViewLauncher:
25
25
  self._sim_file_path: Path | None = None
26
26
 
27
27
  if isinstance(source, SimulationScene):
28
- if (
29
- source.model is None
30
- or not source.model.is_complete
31
- or source.states is None
32
- ):
28
+ if not source.model.is_complete:
33
29
  raise ValueError(
34
30
  "Cannot initialize visualizer: The provided SimulationScene "
35
- "is incomplete, has no states, or has already been cleared."
31
+ "is incomplete (e.g. terrain might be missing)."
36
32
  )
37
33
  self._scene = source
38
34
  elif isinstance(source, (str, Path)):
@@ -64,14 +60,7 @@ class SimViewLauncher:
64
60
  "states": self._scene.states,
65
61
  }
66
62
  server = SimViewServer(data=data)
67
- port = find_free_port(host, preferred_port)
68
- if port != preferred_port:
69
- logger.warning(
70
- "Preferred port %s is not available. Using port %s instead.",
71
- preferred_port,
72
- port,
73
- )
74
- server.run(host=host, port=port)
63
+ server.run(host=host, port=find_free_port(host, preferred_port))
75
64
  else:
76
65
  # __init__ guarantees exactly one of self._scene / self._sim_file_path
77
66
  # is set (it raises otherwise), so if self._scene is None here,