simview 3.2__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 (182) hide show
  1. simview-3.2/LICENSE +28 -0
  2. simview-3.2/MANIFEST.in +5 -0
  3. simview-3.2/PKG-INFO +552 -0
  4. simview-3.2/README.md +522 -0
  5. simview-3.2/pyproject.toml +89 -0
  6. simview-3.2/setup.cfg +4 -0
  7. simview-3.2/simview/__init__.py +69 -0
  8. simview-3.2/simview/__main__.py +174 -0
  9. simview-3.2/simview/launcher.py +109 -0
  10. simview-3.2/simview/live.py +204 -0
  11. simview-3.2/simview/merge.py +593 -0
  12. simview-3.2/simview/model.py +690 -0
  13. simview-3.2/simview/py.typed +0 -0
  14. simview-3.2/simview/scene.py +603 -0
  15. simview-3.2/simview/server.py +739 -0
  16. simview-3.2/simview/state.py +190 -0
  17. simview-3.2/simview/static/css/controls.css +36 -0
  18. simview-3.2/simview/static/js/SimView.js +601 -0
  19. simview-3.2/simview/static/js/components/AnimationController.js +654 -0
  20. simview-3.2/simview/static/js/components/BatchManager.js +198 -0
  21. simview-3.2/simview/static/js/components/InteractionController.js +446 -0
  22. simview-3.2/simview/static/js/components/InteractionControls.js +116 -0
  23. simview-3.2/simview/static/js/components/Scene.js +166 -0
  24. simview-3.2/simview/static/js/components/StateStore.js +188 -0
  25. simview-3.2/simview/static/js/config.js +233 -0
  26. simview-3.2/simview/static/js/main.js +3 -0
  27. simview-3.2/simview/static/js/objects/Body.js +616 -0
  28. simview-3.2/simview/static/js/objects/StaticObject.js +181 -0
  29. simview-3.2/simview/static/js/objects/Terrain.js +514 -0
  30. simview-3.2/simview/static/js/objects/utils.js +378 -0
  31. simview-3.2/simview/static/js/ui/AnalysisPanel.js +233 -0
  32. simview-3.2/simview/static/js/ui/BatchLegend.js +207 -0
  33. simview-3.2/simview/static/js/ui/BodyStateWindow.js +521 -0
  34. simview-3.2/simview/static/js/ui/Controls.js +586 -0
  35. simview-3.2/simview/static/js/ui/ErrorMetrics.js +664 -0
  36. simview-3.2/simview/static/js/ui/Legend.js +87 -0
  37. simview-3.2/simview/static/js/ui/PlaybackControls.js +314 -0
  38. simview-3.2/simview/static/js/ui/ScalarPlotter.js +575 -0
  39. simview-3.2/simview/static/js/utils/blobCodec.js +73 -0
  40. simview-3.2/simview/static/js/utils/bodyTransforms.js +151 -0
  41. simview-3.2/simview/static/js/utils/csv.js +46 -0
  42. simview-3.2/simview/static/js/utils/errorMath.js +69 -0
  43. simview-3.2/simview/static/js/utils/injectStyles.js +16 -0
  44. simview-3.2/simview/static/js/utils/interpolate.js +65 -0
  45. simview-3.2/simview/static/js/utils/liveFollow.js +17 -0
  46. simview-3.2/simview/static/js/utils/loadRecordingLibs.js +34 -0
  47. simview-3.2/simview/static/js/utils/viewState.js +226 -0
  48. simview-3.2/simview/static/lib/chroma-js-3.1.2/index.min.js +8 -0
  49. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/Color.js +49 -0
  50. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/chroma.js +10 -0
  51. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/colors/colorbrewer.js +81 -0
  52. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/colors/w3cx11.js +164 -0
  53. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/generator/average.js +88 -0
  54. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/generator/bezier.js +86 -0
  55. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/generator/blend.js +57 -0
  56. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/generator/cubehelix.js +87 -0
  57. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/generator/mix.js +19 -0
  58. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/generator/random.js +12 -0
  59. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/generator/scale.js +394 -0
  60. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/interpolator/_hsx.js +59 -0
  61. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/interpolator/hcg.js +12 -0
  62. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsi.js +12 -0
  63. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsl.js +12 -0
  64. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsv.js +12 -0
  65. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/interpolator/index.js +1 -0
  66. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/interpolator/lab.js +19 -0
  67. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/interpolator/lch.js +13 -0
  68. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/interpolator/lrgb.js +19 -0
  69. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/interpolator/num.js +15 -0
  70. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklab.js +19 -0
  71. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklch.js +12 -0
  72. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/interpolator/rgb.js +18 -0
  73. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/cmyk2rgb.js +16 -0
  74. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/index.js +27 -0
  75. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/rgb2cmyk.js +17 -0
  76. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/css/css2rgb.js +238 -0
  77. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/css/hsl2css.js +26 -0
  78. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/css/index.js +27 -0
  79. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/css/lab2css.js +24 -0
  80. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/css/lch2css.js +24 -0
  81. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/css/oklab2css.js +16 -0
  82. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/css/oklch2css.js +16 -0
  83. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/css/rgb2css.js +61 -0
  84. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/gl/index.js +22 -0
  85. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/hcg/hcg2rgb.js +55 -0
  86. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/hcg/index.js +27 -0
  87. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/hcg/rgb2hcg.js +23 -0
  88. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/hex/hex2rgb.js +56 -0
  89. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/hex/index.js +29 -0
  90. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/hex/rgb2hex.js +29 -0
  91. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/hsi/hsi2rgb.js +45 -0
  92. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/hsi/index.js +27 -0
  93. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/hsi/rgb2hsi.js +31 -0
  94. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/hsl/hsl2rgb.js +35 -0
  95. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/hsl/index.js +27 -0
  96. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/hsl/rgb2hsl.js +45 -0
  97. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/hsv/hsv2rgb.js +47 -0
  98. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/hsv/index.js +27 -0
  99. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/hsv/rgb2hsv.js +32 -0
  100. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/input.js +4 -0
  101. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/lab/index.js +28 -0
  102. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab-constants.js +120 -0
  103. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab2rgb.js +101 -0
  104. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/lab/rgb2lab.js +67 -0
  105. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/lch/hcl2rgb.js +9 -0
  106. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/lch/index.js +35 -0
  107. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/lch/lab2lch.js +12 -0
  108. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2lab.js +18 -0
  109. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2rgb.js +13 -0
  110. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/lch/rgb2lch.js +12 -0
  111. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/named/index.js +30 -0
  112. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/num/index.js +32 -0
  113. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/num/num2rgb.js +13 -0
  114. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/num/rgb2num.js +8 -0
  115. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/oklab/index.js +27 -0
  116. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/oklab/oklab2rgb.js +34 -0
  117. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/oklab/rgb2oklab.js +37 -0
  118. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/oklch/index.js +27 -0
  119. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/oklch/oklch2rgb.js +13 -0
  120. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/oklch/rgb2oklch.js +12 -0
  121. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/rgb/index.js +44 -0
  122. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/temp/index.js +22 -0
  123. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/temp/rgb2temperature.js +30 -0
  124. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/io/temp/temperature2rgb.js +39 -0
  125. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/ops/alpha.js +13 -0
  126. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/ops/clipped.js +5 -0
  127. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/ops/darken.js +17 -0
  128. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/ops/get.js +13 -0
  129. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/ops/luminance.js +54 -0
  130. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/ops/mix.js +10 -0
  131. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/ops/premultiply.js +12 -0
  132. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/ops/saturate.js +15 -0
  133. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/ops/set.js +43 -0
  134. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/ops/shade.js +11 -0
  135. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/utils/analyze.js +191 -0
  136. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/utils/clip_rgb.js +15 -0
  137. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/utils/contrast.js +12 -0
  138. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/utils/contrastAPCA.js +68 -0
  139. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/utils/delta-e.js +62 -0
  140. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/utils/distance.js +17 -0
  141. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/utils/index.js +27 -0
  142. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/utils/last.js +8 -0
  143. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/utils/limit.js +5 -0
  144. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/utils/multiply-matrices.js +36 -0
  145. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/utils/scales.js +15 -0
  146. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/utils/type.js +18 -0
  147. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/utils/unpack.js +17 -0
  148. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/utils/valid.js +11 -0
  149. simview-3.2/simview/static/lib/chroma-js-3.1.2/src/version.js +2 -0
  150. simview-3.2/simview/static/lib/download.js +132 -0
  151. simview-3.2/simview/static/lib/js-colormaps.js +19335 -0
  152. simview-3.2/simview/static/lib/tar.js +334 -0
  153. simview-3.2/simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +1556 -0
  154. simview-3.2/simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +8 -0
  155. simview-3.2/simview/static/lib/three-0.174.0/three.core.js +48830 -0
  156. simview-3.2/simview/static/lib/three-0.174.0/three.module.js +17313 -0
  157. simview-3.2/simview/static/lib/uPlot.esm.js +6140 -0
  158. simview-3.2/simview/static/lib/uPlot.min.css +2 -0
  159. simview-3.2/simview/static/textures/contacts/red-cross0.png +0 -0
  160. simview-3.2/simview/static/textures/points/ball0.png +0 -0
  161. simview-3.2/simview/static/textures/points/ball1.png +0 -0
  162. simview-3.2/simview/templates/index.html +81 -0
  163. simview-3.2/simview/utils.py +41 -0
  164. simview-3.2/simview.egg-info/PKG-INFO +552 -0
  165. simview-3.2/simview.egg-info/SOURCES.txt +180 -0
  166. simview-3.2/simview.egg-info/dependency_links.txt +1 -0
  167. simview-3.2/simview.egg-info/entry_points.txt +2 -0
  168. simview-3.2/simview.egg-info/requires.txt +13 -0
  169. simview-3.2/simview.egg-info/top_level.txt +1 -0
  170. simview-3.2/tests/test_cli.py +169 -0
  171. simview-3.2/tests/test_columnar_states.py +288 -0
  172. simview-3.2/tests/test_contacts.py +29 -0
  173. simview-3.2/tests/test_launcher.py +146 -0
  174. simview-3.2/tests/test_live.py +158 -0
  175. simview-3.2/tests/test_merge.py +535 -0
  176. simview-3.2/tests/test_roundtrip.py +412 -0
  177. simview-3.2/tests/test_scene.py +452 -0
  178. simview-3.2/tests/test_server.py +330 -0
  179. simview-3.2/tests/test_show.py +101 -0
  180. simview-3.2/tests/test_terrain.py +164 -0
  181. simview-3.2/tests/test_trajectory.py +168 -0
  182. simview-3.2/tests/test_utils.py +74 -0
simview-3.2/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Vision for Robotics and Autonomous Systems
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,5 @@
1
+ recursive-include simview/static *
2
+ recursive-include simview/templates *
3
+ include simview/py.typed
4
+ include LICENSE
5
+ include README.md
simview-3.2/PKG-INFO ADDED
@@ -0,0 +1,552 @@
1
+ Metadata-Version: 2.4
2
+ Name: simview
3
+ Version: 3.2
4
+ Summary: THREE.js-based simulation visualization tool
5
+ Author-email: Ales Kucera <kuceral4@fel.cvut.cz>, David Korcak <david.korcak@gmail.com>, Jan Vlk <vlkjan6@fel.cvut.cz>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/vlk-jan/simview
8
+ Project-URL: Repository, https://github.com/vlk-jan/simview
9
+ Project-URL: Issues, https://github.com/vlk-jan/simview/issues
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Scientific/Engineering :: Visualization
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: fastapi>=0.135.3
20
+ Requires-Dist: httptools>=0.7.1; sys_platform != "win32"
21
+ Requires-Dist: jinja2>=3.1.6
22
+ Requires-Dist: orjson>=3.11.8
23
+ Requires-Dist: uvicorn>=0.44.0
24
+ Requires-Dist: uvloop>=0.22.1; sys_platform != "win32"
25
+ Provides-Extra: authoring
26
+ Requires-Dist: torch; extra == "authoring"
27
+ Requires-Dist: einops; extra == "authoring"
28
+ Requires-Dist: numpy; extra == "authoring"
29
+ Dynamic: license-file
30
+
31
+ # SimView Visualizer
32
+
33
+ **SimView** is a powerful and interactive tool for visualizing 3D models and terrain data in simulations. It enables you to explore and analyze multiple simulation scenarios (batches) within a shared environment, all defined through an intuitive JSON format or a Python API.
34
+
35
+ Whether you're simulating physical objects or comparing different runs, SimView provides a flexible and efficient way to bring your data to life using a web-based interface powered by Three.js.
36
+
37
+ ---
38
+
39
+ ## Features
40
+
41
+ - **Batched Simulations**: Visualize multiple simulation instances side-by-side.
42
+ - **Shared Terrain**: Efficient rendering with shared terrain across all batches.
43
+ - **Interactive UI**: Web-based controls for playback, camera, and data inspection.
44
+ - **Python API**: Easy-to-use API for generating scenes and launching the visualizer directly from your code.
45
+ - **JSON Support**: Load and save simulation data using a portable JSON format.
46
+
47
+ ---
48
+
49
+ ## Quick Start
50
+
51
+ The easiest way to get started is to run the provided example script:
52
+
53
+ ```bash
54
+ python example.py
55
+ ```
56
+
57
+ This script demonstrates how to use the Python API to create a simulation with wavy terrain, dynamic bodies, and time-series data.
58
+
59
+ The Python authoring API (`simview.scene`, `simview.state`, `simview.model`) depends on
60
+ `torch` and `einops`. Install them with the optional `authoring` extra shown below.
61
+ Only these are needed to *build* simulations; *viewing* an existing JSON file does not
62
+ require `torch`.
63
+
64
+ ---
65
+
66
+ ## Installation
67
+
68
+ Requires **Python 3.12+**.
69
+
70
+ To only view existing simulation JSON files:
71
+
72
+ ```bash
73
+ pip install -e .
74
+ ```
75
+
76
+ To also author simulations from Python (installs `torch` and `einops`):
77
+
78
+ ```bash
79
+ pip install -e ".[authoring]"
80
+ ```
81
+
82
+ For independent use of this repository, use `venv` or `uv`:
83
+
84
+ ```bash
85
+ python -m venv .venv
86
+ source .venv/bin/activate
87
+ pip install -e ".[authoring]"
88
+ ```
89
+
90
+ ```bash
91
+ uv sync --extra authoring
92
+ source .venv/bin/activate
93
+ ```
94
+
95
+ ---
96
+
97
+ ## CLI Utilities
98
+
99
+ ### Cache Management
100
+
101
+ SimView caches some temporary files for visualization. It also cleans up any
102
+ `simview_viz_*.json` temp scene files left behind by older versions (a launched viewer
103
+ now serves an in-memory `SimulationScene` directly, without writing one). You can clear
104
+ all of this using the following command:
105
+
106
+ ```bash
107
+ simview clear
108
+ ```
109
+
110
+ ### Visualization of exported simulations
111
+
112
+ 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:
113
+
114
+ ```bash
115
+ simview [path_to_json_file]
116
+ ```
117
+
118
+ Gzip-compressed files (e.g. `scene.json.gz`) are detected automatically and decompressed
119
+ transparently — no separate flag needed.
120
+
121
+ Useful flags:
122
+
123
+ ```bash
124
+ simview scene.json --host 0.0.0.0 --port 8080 # bind to a specific host/port
125
+ simview scene.json --no-browser # don't auto-open a browser tab
126
+ simview --version # print the installed version
127
+ ```
128
+
129
+ ### Comparing multiple runs (e.g. real-world vs. simulated)
130
+
131
+ Pass multiple JSON files to merge them into a single scene, each file's batches appended
132
+ as extra batches in the viewer:
133
+
134
+ ```bash
135
+ simview real_world.json simulated.json
136
+ ```
137
+
138
+ The files must describe the same physical setup (identical bodies and terrain grid) —
139
+ that's what makes the batches comparable. They don't need to share a timeline: the
140
+ **first** file's timestamps become the merged timeline, and every other file is
141
+ resampled onto it by nearest timestamp (no interpolation), so put the recording you
142
+ care most about matching frame-for-frame first. See [Analysis Panel](#analysis-panel)
143
+ below for a way to quantify the difference between two merged batches.
144
+
145
+ Each merged file's batches are auto-named after its filename (e.g. `real_world`,
146
+ `simulated`), shown in the [Batch Legend](#batch-legend). You can rename them from
147
+ there — renames are saved next to the input file(s) and reloaded automatically the
148
+ next time you open the same file(s). You can also set initial batch names yourself by
149
+ including a `batchNames` array directly in the JSON's `model` object (see
150
+ [JSON Format Specification](#json-format-specification)); renames from the UI take
151
+ precedence over this once saved.
152
+
153
+ To merge files without launching the viewer, e.g. to inspect or re-share the merged
154
+ scene, pass `--save-merged`:
155
+
156
+ ```bash
157
+ simview real_world.json simulated.json --save-merged combined.json.gz
158
+ ```
159
+
160
+ The output is gzipped if the path ends in `.gz`.
161
+
162
+ ---
163
+
164
+ ## Live streaming
165
+
166
+ Instead of saving a scene and viewing it afterwards, `LiveViewer` starts the server
167
+ immediately and pushes each state to an already-open browser tab as your simulation
168
+ produces it, over a WebSocket:
169
+
170
+ ```python
171
+ from simview import LiveViewer
172
+
173
+ # `scene` needs its complete model (terrain, bodies, ...) up front; states are
174
+ # streamed in afterwards.
175
+ with LiveViewer(scene, open_browser=True) as live:
176
+ for t in range(num_steps):
177
+ ... # step the simulation
178
+ live.push_state(time=t * dt, body_states=[...], scalar_values=...)
179
+
180
+ # scene.states was appended to exactly like scene.add_state would, so it can
181
+ # still be saved once streaming is done:
182
+ scene.save("recording.json.gz", compress=True)
183
+ ```
184
+
185
+ `push_state` has the same signature and validation as `SimulationScene.add_state` --
186
+ it delegates to it directly, then broadcasts the new frame to every connected viewer. A
187
+ viewer opened after the stream has already started still gets the full history so far,
188
+ replayed as a single catch-up message before it starts receiving new frames live. If no
189
+ viewer is connected yet, pushed frames are simply buffered for the next one to connect.
190
+ Playback in the browser follows the live frames automatically as long as you haven't
191
+ scrubbed backward or started a loop; a small "LIVE" badge shows while the socket is open.
192
+
193
+ See `example_live.py` for a runnable end-to-end example.
194
+
195
+ ---
196
+
197
+ ## Jupyter / non-blocking viewing
198
+
199
+ `scene.show()` starts a viewer on a background thread and returns immediately,
200
+ instead of blocking like `SimViewLauncher`/`SimViewServer.run`. This is handy in a
201
+ notebook: evaluating the returned handle as a cell's result embeds the viewer inline
202
+ via an iframe.
203
+
204
+ ```python
205
+ handle = scene.show() # non-blocking; scene itself is left untouched
206
+ handle # in Jupyter, displays the viewer inline (uses _repr_html_)
207
+
208
+ # ... do other work, or just let the cell above stay interactive ...
209
+
210
+ handle.stop() # or: `with scene.show() as handle: ...` to stop automatically
211
+ ```
212
+
213
+ ---
214
+
215
+ ## Visualization Controls
216
+
217
+ Once the visualizer is running, you can interact with the simulation using the following controls:
218
+
219
+ ### Camera
220
+
221
+ - **Rotate**: Left-click + drag OR `Ctrl` (`CMD` on Mac) + Arrow keys
222
+ - **Pan**: Right-click + drag OR Arrow keys
223
+ - **Zoom**: Scroll wheel
224
+ - **Track Body**: Automatically follow a specific body (via the "Camera Options" menu)
225
+ - **Split Screen**: Compare two batches side-by-side (via the "Camera Options" menu, requires ≥2 batches)
226
+ - **Field of View**: Adjust camera FOV (via the "Camera Options" menu)
227
+ - **Copy View Link**: Click the "Copy view link" button (in the "Camera Options" menu) to copy a
228
+ URL that encodes the current camera, playback time, focused batch, and visualization toggles --
229
+ opening it restores that view.
230
+
231
+ ### Timeline
232
+
233
+ - **Step Forward/Backward**: `Alt` + Arrow Right / Arrow Left
234
+ - **Seek (and Pause)**: Click on the timeline bar
235
+ - **Play/Pause**: `Space` or Click the Play button
236
+ - **Record**: `R` or Click the Record button (select WEBM, MP4 -- if your browser supports
237
+ recording it -- or PNG sequence via the dropdown). Recording seeks to the start, plays
238
+ exactly one loop, then automatically stops and downloads the file.
239
+ - **Screenshot**: `S` or Click the camera button next to Record to save the current frame as a PNG.
240
+ - **Playback Speed**: Adjust speed (0.1x to 5x) via the dropdown next to the timeline
241
+
242
+ ### Batch Selection
243
+
244
+ - **Move Selection**: `Shift` + Arrow keys
245
+
246
+ ### Visualization Options
247
+
248
+ - **`B`**: Toggle Body Visualization Mode (Mesh / Wireframe / Points)
249
+ - **`A`**: Toggle Axes Visibility
250
+ - **`G`**: Toggle Trajectory Trails
251
+ - **`I`**: Toggle Smooth Interpolation (on by default; interpolates position/orientation between recorded states during playback and scrubbing instead of snapping to the nearest frame)
252
+ - **`C`**: Toggle Contact Points
253
+ - **`V`**: Toggle Linear Velocity
254
+ - **`W`**: Toggle Angular Velocity
255
+ - **`F`**: Toggle Linear Force
256
+ - **`T`**: Toggle Torque
257
+ - **`P`**: Toggle Terrain Data Probe (interactive tooltip on hover)
258
+
259
+ You can also customize terrain colors, colormaps, and toggle surface/wireframe/normals from the "Terrain Options" menu.
260
+
261
+ ### Trajectory Trails
262
+
263
+ Toggling trails (`G`, or "Show Trails" in the Body Options panel) draws each body's
264
+ path from the start of the simulation up to the current playback time, one line per
265
+ batch in that batch's color. Useful for comparing the overall shape of two
266
+ trajectories (e.g. real vs. simulated) at a glance instead of scrubbing frame by frame.
267
+
268
+ ### Analysis Panel
269
+
270
+ Scalars and Error Metrics share one collapsible panel at the top-center of the screen.
271
+ When both are available, a mode switcher lets you flip between them; if only one is
272
+ available (e.g. a single-batch scene has no Error Metrics), that one is shown directly
273
+ without the switcher.
274
+
275
+ - **Scalars**: one tab per scalar defined in the model, each plotting its value over
276
+ time for every batch (colored per batch, click a line to focus that batch). An
277
+ "Export CSV" button on the active tab downloads its full series as `time` plus one
278
+ column per batch, named after each batch's current display name.
279
+ - **Error Metrics**: shown once a scene has 2 or more batches. Pick a body and two
280
+ batches ("Batch A" / "Batch B") to compare — e.g. the real and simulated batches
281
+ produced by [merging multiple files](#comparing-multiple-runs-eg-real-world-vs-simulated)
282
+ — and it computes, over the full timeline, the Euclidean position error and the
283
+ quaternion angle (orientation) error between the two batches for that body. A live
284
+ readout shows the current-frame values, and the chart plots both error curves over
285
+ time with a marker at the current playback position. The "Per-axis" toggle swaps the
286
+ combined position error curve for the signed X/Y/Z error components (Batch A minus
287
+ Batch B), useful for spotting a directional bias instead of just overall magnitude.
288
+ Below the readout, a compact stats block summarizes the full timeline: position
289
+ RMSE, the max position error (and when it occurs), the final-frame drift, and the
290
+ orientation RMSE and max angle error. An "Export CSV" button downloads the current
291
+ selection's per-frame series (`time`, `pos_error`, `err_x`, `err_y`, `err_z`,
292
+ `angle_error_deg`).
293
+
294
+ ### Batch Legend
295
+
296
+ When a scene has 2 or more batches, a toggleable "Batches" legend appears in the
297
+ bottom-right corner, listing each batch's color, index, and name. Click a row to focus
298
+ that batch, or click a name to rename it in place — renames persist next to the input
299
+ file(s), so they survive a reload or server restart.
300
+
301
+ ---
302
+
303
+ ## JSON Format Specification
304
+
305
+ If you prefer to generate data files manually or from another language, SimView uses a
306
+ single JSON document with two top-level keys: `model` (static data, sent once) and
307
+ `states` (an array of time-ordered snapshots). This is exactly what `SimulationScene.save()`
308
+ produces.
309
+
310
+ ```json
311
+ { "model": { ... }, "states": [ { ... }, { ... } ] }
312
+ ```
313
+
314
+ ### Model (Static Data)
315
+
316
+ - **`simBatches`** *(integer)* — number of parallel simulation instances (batches).
317
+ - **`batchNames`** *(array[string], optional)* — display name for each batch, length
318
+ must equal `simBatches`. Shown in the [Batch Legend](#batch-legend); falls back to
319
+ `"Batch <index>"` per entry if omitted, empty, or the wrong length. Renames made from
320
+ the Batch Legend are persisted server-side (see below) and take precedence over this
321
+ field on subsequent loads.
322
+ - **`scalarNames`** *(array[string])* — names of per-batch scalar time-series (e.g. `"energy"`).
323
+ - **`dt`** *(float)* — simulation timestep in seconds. Used for playback timing; if omitted or invalid the viewer infers it from consecutive state times.
324
+ - **`collapse`** *(boolean)* — UI hint to start with the body-state window collapsed.
325
+ - **`bodies`** *(array)* — dynamic bodies. Each entry:
326
+ - **`name`** *(string)* — unique identifier, referenced from each state.
327
+ - **`shape`** *(object)* — geometry, keyed by a **string** `type`:
328
+ - `"box"` — requires `hx`, `hy`, `hz` (half-extents).
329
+ - `"sphere"` — requires `radius`.
330
+ - `"cylinder"` — requires `radius`, `height`.
331
+ - `"pointcloud"` — requires `points` *(array[array[3]])* in the body's local frame.
332
+ - `"mesh"` — requires `vertices` *(array[array[3]])* and `faces` *(array[array[3]])*.
333
+ - **`availableAttributes`** *(array[string], optional)* — which optional per-state
334
+ fields this body provides. Any of `"contacts"`, `"velocity"`, `"angularVelocity"`,
335
+ `"force"`, `"torque"`.
336
+ - **`parent`** *(string, optional)* — name of another `model.bodies[]` entry this
337
+ body is attached to. When set, this body's pose is no longer absolute world
338
+ space; see `localTransform` below and the `bodyTransform` note under
339
+ [States](#states-dynamic-data).
340
+ - **`localTransform`** *(array[7], optional)* — `[x, y, z, w, qx, qy, qz]` constant
341
+ offset from `parent`, for bodies **rigidly** attached (e.g. a wheel bolted to a
342
+ chassis). Set only together with `parent`. A body with `localTransform` never
343
+ appears in any state's `bodies[]` — its world pose is derived every frame from
344
+ its parent's current pose plus this fixed offset, saving the cost of repeating
345
+ an unchanging transform every frame. For an **articulated** attachment (e.g. an
346
+ arm joint) instead, set only `parent` and keep providing a per-frame
347
+ `bodyTransform` in `states[].bodies[]` as usual — it's then interpreted as local
348
+ to the parent's current-frame pose rather than world space.
349
+ - **`staticObjects`** *(array, optional)* — non-moving geometry. Each entry has `name`,
350
+ `isSingleton` *(boolean)*, and either `shape` (when singleton) or `shapes`
351
+ *(array, one per batch)* using the same shape objects as bodies.
352
+ - **`terrain`** *(object)* — heightfield shared or per-batch:
353
+ - **`dimensions`**: `sizeX`, `sizeY` *(float)* and `resolutionX`, `resolutionY` *(int)*.
354
+ - **`bounds`**: `minX`, `maxX`, `minY`, `maxY`, `minZ`, `maxZ`. When friction/stiffness
355
+ data is present, also `minFriction`/`maxFriction` and/or `minStiffness`/`maxStiffness`,
356
+ which the viewer uses to normalize the color map.
357
+ - **`isSingleton`** *(boolean)* — `true` when one terrain is shared by all batches;
358
+ `false` when each batch has its own.
359
+ - **`heightData`** *(array[array[float]])* — one flattened `resolutionX * resolutionY`
360
+ grid per batch (a single flat array is also accepted and treated as one batch).
361
+ - **`normals`** *(array[array[array[3]]])* — per-batch surface normals, one `[x, y, z]`
362
+ per grid point.
363
+ - **`frictionData`**, **`stiffnessData`** *(array[array[float]] | null, optional)* —
364
+ per-batch scalar fields over the grid, selectable as terrain color modes.
365
+
366
+ ### States (Dynamic Data)
367
+
368
+ `states` is an array; each element is one snapshot:
369
+
370
+ - **`time`** *(float)* — snapshot time in seconds.
371
+ - **`bodies`** *(array)* — per body:
372
+ - **`name`** *(string | array[string])* — matches a `model.bodies[].name`. May instead be
373
+ a list of names when several bodies move rigidly together (e.g. links welded to the same
374
+ parent): the single entry's `bodyTransform` and other fields below then apply identically
375
+ to every named body, instead of repeating identical data once per body. All named bodies
376
+ must exist in `model.bodies`.
377
+ - **`bodyTransform`** — pose. Batched: `array[array[7]]`, one `[x, y, z, w, qx, qy, qz]`
378
+ per batch; single: a flat `[x, y, z, w, qx, qy, qz]`. Absolute world-space, unless
379
+ the referenced body has a `parent` in `model.bodies` (see above), in which case
380
+ this is local to that parent's current-frame pose instead. A body with a constant
381
+ `localTransform` on the model never has a `bodyTransform` entry here at all.
382
+ - **`contacts`** *(array[array[int]], optional)* — per batch, indices of contacting
383
+ points (into the body's pointcloud `points`). Empty array means no contacts.
384
+ - **`velocity`**, **`angularVelocity`**, **`force`**, **`torque`**
385
+ *(array[array[3]], optional)* — per-batch 3-vectors.
386
+ - **`<scalarName>`** *(array[float])* — for each name in `model.scalarNames`, one value per batch.
387
+
388
+ > **Binary state fields.** The numeric per-body fields (`bodyTransform`, `velocity`,
389
+ > `angularVelocity`, `force`, `torque`) may alternatively be a string of the form
390
+ > `"__b64__<base64>"`, where the base64 payload is the little-endian float32 bytes of the
391
+ > batched array in row-major order (`bodyTransform` is width 7, the vectors width 3). Both
392
+ > `SimViewBodyState` (used by `add_state`) and
393
+ > [`SimulationScene.add_trajectory`](#authoring-whole-trajectories) emit this by default
394
+ > (typically ~3-4× smaller than the equivalent plain JSON floats); pass `binary=False` to
395
+ > either to emit plain JSON lists instead. The viewer and the file-merge decode binary
396
+ > fields transparently. `contacts`, scalars, and `time` are always plain JSON.
397
+ >
398
+ > **Server-side columnar repack.** This on-disk, per-frame layout never changes (and
399
+ > `simview merge` still reads/writes it as described above); but when serving a scene to
400
+ > the viewer, the server repacks `states` at load time into whole-trajectory columns --
401
+ > one binary blob per body per numeric field (and one per scalar), covering all `T`
402
+ > frames at once -- and serves a small JSON index (`{"version": 4, "times", "bodies",
403
+ > "scalars"}`) whose entries are `/blob/...` URLs, fetched in parallel and decoded into
404
+ > `Float32Array`s. This avoids materializing thousands of tiny per-frame JS objects
405
+ > just to play back a long trajectory. The repack requires the body set, per-body field
406
+ > set, and field widths to be identical across every frame (`contacts` is exempt and may
407
+ > come and go per frame); if a scene doesn't meet that, the server falls back to serving
408
+ > the legacy per-frame JSON array unchanged, which the viewer also still supports.
409
+
410
+ ### Authoring whole trajectories
411
+
412
+ Building states one frame at a time (`add_state`) is fine for short scenes, but for long,
413
+ dense trajectories prefer `SimulationScene.add_trajectory`, which appends an entire
414
+ time-series in one call, converting each body's tensors once instead of per frame —
415
+ noticeably faster save/load than the same data built frame-by-frame. Both paths pack the
416
+ numeric fields as the binary blobs described above by default, so file size is comparable
417
+ either way:
418
+
419
+ ```python
420
+ from simview import SimulationScene, BodyShapeType, BodyTrajectory
421
+
422
+ scene = SimulationScene(batch_size=B, scalar_names=[], dt=0.001)
423
+ scene.create_terrain(...)
424
+ scene.create_body(body_name="box", shape_type=BodyShapeType.BOX, hx=0.5, hy=0.3, hz=0.15)
425
+
426
+ # positions: (T, B, 3), orientations: (T, B, 4) as [w, x, y, z]
427
+ # (2-D (T, 3) / (T, 4) is accepted when batch_size == 1)
428
+ scene.add_trajectory(
429
+ times=times, # length-T sequence or tensor
430
+ trajectories=[BodyTrajectory("box", positions, orientations)],
431
+ )
432
+ scene.save("scene.json")
433
+ ```
434
+
435
+ Pass `binary=False` to emit plain JSON lists instead.
436
+
437
+ Both `BodyTrajectory.name` and `SimViewBodyState`'s `body_name` accept a list of body names
438
+ instead of a single string, for bodies that move rigidly together (e.g. `BodyTrajectory(["link_a", "link_b"], positions, orientations)`) — the same transform (and any optional
439
+ attributes) is applied to every named body, so it only needs to be written once per frame
440
+ instead of once per body.
441
+
442
+ For large simulations, pass `compress=True` to `save()` (or use a `.gz` filepath) to
443
+ gzip the output — `SimulationScene.load()`, the CLI, and the server all detect and
444
+ decompress it transparently regardless of extension.
445
+
446
+ ### Parent-relative bodies (rigid and articulated attachments)
447
+
448
+ `create_body` accepts `parent`/`local_transform` to attach a body to another body
449
+ already in the model, instead of it moving in world space:
450
+
451
+ ```python
452
+ scene.create_body(body_name="chassis", shape_type=BodyShapeType.BOX, hx=0.6, hy=0.4, hz=0.2)
453
+
454
+ # Rigid attachment (e.g. a wheel bolted to the chassis): a constant offset, defined
455
+ # once, never repeated per frame. Never call add_state/add_trajectory for "left_wheel".
456
+ scene.create_body(
457
+ body_name="left_wheel", shape_type=BodyShapeType.CYLINDER, radius=0.15, height=0.1,
458
+ parent="chassis", local_transform=[0.4, 0.52, 0.0, 1.0, 0.0, 0.0, 0.0],
459
+ )
460
+
461
+ # Articulated attachment (e.g. an arm joint): only `parent` is set, so this body's
462
+ # pose is still supplied every frame via add_state/add_trajectory as usual -- it's
463
+ # just interpreted as local to the chassis's current-frame pose instead of world.
464
+ scene.create_body(body_name="arm_joint", shape_type=BodyShapeType.BOX, hx=0.05, hy=0.05, hz=0.2, parent="chassis")
465
+ ```
466
+
467
+ A body's `parent` must already exist in the model (added before its children), which
468
+ also rules out cycles. Merging files containing rigid (constant-offset) bodies works
469
+ the same way — `merge_simulation_files` carries the `parent`/`localTransform` through
470
+ in `model.bodies` and doesn't require or emit per-frame data for them.
471
+
472
+ ### Example (2 batches, one box, flat terrain)
473
+
474
+ ```json
475
+ {
476
+ "model": {
477
+ "simBatches": 2,
478
+ "scalarNames": ["energy"],
479
+ "dt": 0.1,
480
+ "collapse": false,
481
+ "bodies": [
482
+ {
483
+ "name": "Box",
484
+ "shape": { "type": "box", "hx": 0.5, "hy": 0.5, "hz": 0.5 },
485
+ "availableAttributes": ["velocity"]
486
+ }
487
+ ],
488
+ "staticObjects": [],
489
+ "terrain": {
490
+ "dimensions": { "sizeX": 10.0, "sizeY": 10.0, "resolutionX": 2, "resolutionY": 2 },
491
+ "bounds": { "minX": -5.0, "maxX": 5.0, "minY": -5.0, "maxY": 5.0, "minZ": 0.0, "maxZ": 0.0 },
492
+ "isSingleton": true,
493
+ "heightData": [[0.0, 0.0, 0.0, 0.0]],
494
+ "normals": [[[0, 0, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1]]]
495
+ }
496
+ },
497
+ "states": [
498
+ {
499
+ "time": 0.0,
500
+ "bodies": [
501
+ {
502
+ "name": "Box",
503
+ "bodyTransform": [
504
+ [0, 0, 1, 1, 0, 0, 0],
505
+ [2, 0, 1, 1, 0, 0, 0]
506
+ ],
507
+ "velocity": [
508
+ [0, 0, -0.1],
509
+ [0, 0, 0]
510
+ ]
511
+ }
512
+ ],
513
+ "energy": [1.2, 0.1]
514
+ }
515
+ ]
516
+ }
517
+ ```
518
+
519
+ ---
520
+
521
+ ## Notes
522
+
523
+ - **Quaternion Convention**
524
+ Quaternions use `[w, x, y, z]` (scalar-first format), packed into `bodyTransform` after the position.
525
+
526
+ - **Terrain Consistency**
527
+ Each per-batch `heightData` grid and `normals` list must contain exactly
528
+ `resolutionX * resolutionY` elements.
529
+
530
+ - **Batch Synchronization**
531
+ Per-batch arrays (`bodyTransform`, `velocity`, scalar values, …) must have length `simBatches`.
532
+ When `terrain.isSingleton` is `true`, `heightData`/`normals` hold a single batch that is
533
+ reused for all instances.
534
+
535
+ - **Contact Points**
536
+ The `contacts` field lists point indices into a body's pointcloud `points` for each batch.
537
+ An empty array means no contacts.
538
+
539
+ ---
540
+
541
+ ## License and Third-Party Notices
542
+
543
+ SimView is distributed under the [BSD 3-Clause License](LICENSE).
544
+
545
+ The web interface uses [**uPlot**](https://github.com/leeoniya/uPlot) (MIT licensed) for
546
+ scalar and error-metric plotting; it is vendored under `simview/static/lib/`.
547
+ [**three.js**](https://github.com/mrdoob/three.js) (MIT licensed) and
548
+ [**chroma-js**](https://github.com/gka/chroma.js) (MIT licensed) are likewise vendored
549
+ under `simview/static/lib/` (in version-stamped directories, e.g. `lib/three-0.174.0/`)
550
+ rather than loaded from a CDN, so the viewer works fully offline. All third-party
551
+ libraries used by SimView are permissively licensed (MIT/BSD), so there are no licensing
552
+ restrictions on commercial use.