simview 3.4__tar.gz → 3.6__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 (196) hide show
  1. simview-3.6/PKG-INFO +136 -0
  2. simview-3.6/README.md +103 -0
  3. {simview-3.4 → simview-3.6}/pyproject.toml +7 -2
  4. {simview-3.4 → simview-3.6}/simview/__main__.py +217 -44
  5. {simview-3.4 → simview-3.6}/simview/diff.py +58 -24
  6. {simview-3.4 → simview-3.6}/simview/info.py +7 -0
  7. {simview-3.4 → simview-3.6}/simview/model.py +47 -4
  8. simview-3.6/simview/render.py +141 -0
  9. {simview-3.4 → simview-3.6}/simview/scene.py +30 -7
  10. {simview-3.4 → simview-3.6}/simview/static/js/SimView.js +22 -1
  11. {simview-3.4 → simview-3.6}/simview/static/js/components/BatchManager.js +1 -0
  12. {simview-3.4 → simview-3.6}/simview/static/js/components/InteractionController.js +77 -6
  13. {simview-3.4 → simview-3.6}/simview/static/js/config.js +1 -0
  14. {simview-3.4 → simview-3.6}/simview/static/js/objects/Terrain.js +123 -11
  15. {simview-3.4 → simview-3.6}/simview/static/js/ui/AnalysisPanel.js +46 -25
  16. {simview-3.4 → simview-3.6}/simview/static/js/ui/Controls.js +91 -0
  17. {simview-3.4 → simview-3.6}/simview/static/js/ui/ErrorMetrics.js +8 -2
  18. {simview-3.4 → simview-3.6}/simview/static/js/ui/Legend.js +18 -2
  19. {simview-3.4 → simview-3.6}/simview/static/js/ui/ScalarPlotter.js +6 -2
  20. simview-3.6/simview/static/js/ui/TerrainProfile.js +544 -0
  21. simview-3.6/simview/static/js/utils/batchPresets.js +28 -0
  22. simview-3.6/simview/static/js/utils/terrainSample.js +92 -0
  23. {simview-3.4 → simview-3.6}/simview/terrain.py +505 -3
  24. simview-3.6/simview.egg-info/PKG-INFO +136 -0
  25. {simview-3.4 → simview-3.6}/simview.egg-info/SOURCES.txt +4 -0
  26. {simview-3.4 → simview-3.6}/simview.egg-info/requires.txt +3 -0
  27. {simview-3.4 → simview-3.6}/tests/test_cli.py +473 -2
  28. {simview-3.4 → simview-3.6}/tests/test_diff.py +55 -0
  29. {simview-3.4 → simview-3.6}/tests/test_info.py +26 -0
  30. {simview-3.4 → simview-3.6}/tests/test_roundtrip.py +13 -0
  31. {simview-3.4 → simview-3.6}/tests/test_scene.py +18 -0
  32. simview-3.6/tests/test_terrain_query.py +659 -0
  33. simview-3.4/PKG-INFO +0 -626
  34. simview-3.4/README.md +0 -595
  35. simview-3.4/simview.egg-info/PKG-INFO +0 -626
  36. simview-3.4/tests/test_terrain_query.py +0 -333
  37. {simview-3.4 → simview-3.6}/LICENSE +0 -0
  38. {simview-3.4 → simview-3.6}/MANIFEST.in +0 -0
  39. {simview-3.4 → simview-3.6}/setup.cfg +0 -0
  40. {simview-3.4 → simview-3.6}/simview/__init__.py +0 -0
  41. {simview-3.4 → simview-3.6}/simview/launcher.py +0 -0
  42. {simview-3.4 → simview-3.6}/simview/live.py +0 -0
  43. {simview-3.4 → simview-3.6}/simview/merge.py +0 -0
  44. {simview-3.4 → simview-3.6}/simview/py.typed +0 -0
  45. {simview-3.4 → simview-3.6}/simview/server.py +0 -0
  46. {simview-3.4 → simview-3.6}/simview/state.py +0 -0
  47. {simview-3.4 → simview-3.6}/simview/static/css/controls.css +0 -0
  48. {simview-3.4 → simview-3.6}/simview/static/js/components/AnimationController.js +0 -0
  49. {simview-3.4 → simview-3.6}/simview/static/js/components/InteractionControls.js +0 -0
  50. {simview-3.4 → simview-3.6}/simview/static/js/components/Scene.js +0 -0
  51. {simview-3.4 → simview-3.6}/simview/static/js/components/StateStore.js +0 -0
  52. {simview-3.4 → simview-3.6}/simview/static/js/main.js +0 -0
  53. {simview-3.4 → simview-3.6}/simview/static/js/objects/Body.js +0 -0
  54. {simview-3.4 → simview-3.6}/simview/static/js/objects/StaticObject.js +0 -0
  55. {simview-3.4 → simview-3.6}/simview/static/js/objects/utils.js +0 -0
  56. {simview-3.4 → simview-3.6}/simview/static/js/ui/BatchLegend.js +0 -0
  57. {simview-3.4 → simview-3.6}/simview/static/js/ui/BodyStateWindow.js +0 -0
  58. {simview-3.4 → simview-3.6}/simview/static/js/ui/PlaybackControls.js +0 -0
  59. {simview-3.4 → simview-3.6}/simview/static/js/utils/blobCodec.js +0 -0
  60. {simview-3.4 → simview-3.6}/simview/static/js/utils/bodyTransforms.js +0 -0
  61. {simview-3.4 → simview-3.6}/simview/static/js/utils/csv.js +0 -0
  62. {simview-3.4 → simview-3.6}/simview/static/js/utils/errorMath.js +0 -0
  63. {simview-3.4 → simview-3.6}/simview/static/js/utils/injectStyles.js +0 -0
  64. {simview-3.4 → simview-3.6}/simview/static/js/utils/interpolate.js +0 -0
  65. {simview-3.4 → simview-3.6}/simview/static/js/utils/liveFollow.js +0 -0
  66. {simview-3.4 → simview-3.6}/simview/static/js/utils/loadRecordingLibs.js +0 -0
  67. {simview-3.4 → simview-3.6}/simview/static/js/utils/viewState.js +0 -0
  68. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/index.min.js +0 -0
  69. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/Color.js +0 -0
  70. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/chroma.js +0 -0
  71. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/colors/colorbrewer.js +0 -0
  72. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/colors/w3cx11.js +0 -0
  73. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/generator/average.js +0 -0
  74. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/generator/bezier.js +0 -0
  75. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/generator/blend.js +0 -0
  76. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/generator/cubehelix.js +0 -0
  77. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/generator/mix.js +0 -0
  78. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/generator/random.js +0 -0
  79. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/generator/scale.js +0 -0
  80. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/_hsx.js +0 -0
  81. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hcg.js +0 -0
  82. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsi.js +0 -0
  83. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsl.js +0 -0
  84. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsv.js +0 -0
  85. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/index.js +0 -0
  86. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lab.js +0 -0
  87. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lch.js +0 -0
  88. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lrgb.js +0 -0
  89. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/num.js +0 -0
  90. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklab.js +0 -0
  91. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklch.js +0 -0
  92. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/rgb.js +0 -0
  93. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/cmyk2rgb.js +0 -0
  94. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/index.js +0 -0
  95. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/rgb2cmyk.js +0 -0
  96. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/css2rgb.js +0 -0
  97. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/hsl2css.js +0 -0
  98. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/index.js +0 -0
  99. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/lab2css.js +0 -0
  100. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/lch2css.js +0 -0
  101. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/oklab2css.js +0 -0
  102. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/oklch2css.js +0 -0
  103. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/rgb2css.js +0 -0
  104. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/gl/index.js +0 -0
  105. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/hcg2rgb.js +0 -0
  106. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/index.js +0 -0
  107. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/rgb2hcg.js +0 -0
  108. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hex/hex2rgb.js +0 -0
  109. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hex/index.js +0 -0
  110. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hex/rgb2hex.js +0 -0
  111. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/hsi2rgb.js +0 -0
  112. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/index.js +0 -0
  113. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/rgb2hsi.js +0 -0
  114. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/hsl2rgb.js +0 -0
  115. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/index.js +0 -0
  116. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/rgb2hsl.js +0 -0
  117. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/hsv2rgb.js +0 -0
  118. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/index.js +0 -0
  119. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/rgb2hsv.js +0 -0
  120. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/input.js +0 -0
  121. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lab/index.js +0 -0
  122. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab-constants.js +0 -0
  123. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab2rgb.js +0 -0
  124. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lab/rgb2lab.js +0 -0
  125. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lch/hcl2rgb.js +0 -0
  126. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lch/index.js +0 -0
  127. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lab2lch.js +0 -0
  128. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2lab.js +0 -0
  129. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2rgb.js +0 -0
  130. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lch/rgb2lch.js +0 -0
  131. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/named/index.js +0 -0
  132. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/num/index.js +0 -0
  133. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/num/num2rgb.js +0 -0
  134. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/num/rgb2num.js +0 -0
  135. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/index.js +0 -0
  136. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/oklab2rgb.js +0 -0
  137. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/rgb2oklab.js +0 -0
  138. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/index.js +0 -0
  139. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/oklch2rgb.js +0 -0
  140. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/rgb2oklch.js +0 -0
  141. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/rgb/index.js +0 -0
  142. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/temp/index.js +0 -0
  143. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/temp/rgb2temperature.js +0 -0
  144. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/temp/temperature2rgb.js +0 -0
  145. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/alpha.js +0 -0
  146. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/clipped.js +0 -0
  147. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/darken.js +0 -0
  148. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/get.js +0 -0
  149. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/luminance.js +0 -0
  150. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/mix.js +0 -0
  151. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/premultiply.js +0 -0
  152. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/saturate.js +0 -0
  153. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/set.js +0 -0
  154. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/shade.js +0 -0
  155. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/analyze.js +0 -0
  156. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/clip_rgb.js +0 -0
  157. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/contrast.js +0 -0
  158. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/contrastAPCA.js +0 -0
  159. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/delta-e.js +0 -0
  160. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/distance.js +0 -0
  161. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/index.js +0 -0
  162. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/last.js +0 -0
  163. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/limit.js +0 -0
  164. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/multiply-matrices.js +0 -0
  165. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/scales.js +0 -0
  166. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/type.js +0 -0
  167. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/unpack.js +0 -0
  168. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/valid.js +0 -0
  169. {simview-3.4 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/version.js +0 -0
  170. {simview-3.4 → simview-3.6}/simview/static/lib/download.js +0 -0
  171. {simview-3.4 → simview-3.6}/simview/static/lib/js-colormaps.js +0 -0
  172. {simview-3.4 → simview-3.6}/simview/static/lib/tar.js +0 -0
  173. {simview-3.4 → simview-3.6}/simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +0 -0
  174. {simview-3.4 → simview-3.6}/simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +0 -0
  175. {simview-3.4 → simview-3.6}/simview/static/lib/three-0.174.0/three.core.js +0 -0
  176. {simview-3.4 → simview-3.6}/simview/static/lib/three-0.174.0/three.module.js +0 -0
  177. {simview-3.4 → simview-3.6}/simview/static/lib/uPlot.esm.js +0 -0
  178. {simview-3.4 → simview-3.6}/simview/static/lib/uPlot.min.css +0 -0
  179. {simview-3.4 → simview-3.6}/simview/static/textures/contacts/red-cross0.png +0 -0
  180. {simview-3.4 → simview-3.6}/simview/static/textures/points/ball0.png +0 -0
  181. {simview-3.4 → simview-3.6}/simview/static/textures/points/ball1.png +0 -0
  182. {simview-3.4 → simview-3.6}/simview/templates/index.html +0 -0
  183. {simview-3.4 → simview-3.6}/simview/utils.py +0 -0
  184. {simview-3.4 → simview-3.6}/simview.egg-info/dependency_links.txt +0 -0
  185. {simview-3.4 → simview-3.6}/simview.egg-info/entry_points.txt +0 -0
  186. {simview-3.4 → simview-3.6}/simview.egg-info/top_level.txt +0 -0
  187. {simview-3.4 → simview-3.6}/tests/test_columnar_states.py +0 -0
  188. {simview-3.4 → simview-3.6}/tests/test_contacts.py +0 -0
  189. {simview-3.4 → simview-3.6}/tests/test_launcher.py +0 -0
  190. {simview-3.4 → simview-3.6}/tests/test_live.py +0 -0
  191. {simview-3.4 → simview-3.6}/tests/test_merge.py +0 -0
  192. {simview-3.4 → simview-3.6}/tests/test_server.py +0 -0
  193. {simview-3.4 → simview-3.6}/tests/test_show.py +0 -0
  194. {simview-3.4 → simview-3.6}/tests/test_terrain.py +0 -0
  195. {simview-3.4 → simview-3.6}/tests/test_trajectory.py +0 -0
  196. {simview-3.4 → simview-3.6}/tests/test_utils.py +0 -0
simview-3.6/PKG-INFO ADDED
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: simview
3
+ Version: 3.6
4
+ Summary: THREE.js-based simulation visualization tool
5
+ Author-email: Jan Vlk <vlkjan6@fel.cvut.cz>, Ales Kucera <kuceral4@fel.cvut.cz>, David Korcak <david.korcak@gmail.com>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://vlk-jan.github.io/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 :: 5 - Production/Stable
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: Programming Language :: Python :: 3.14
16
+ Classifier: Topic :: Scientific/Engineering :: Visualization
17
+ Requires-Python: >=3.12
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: fastapi>=0.135.3
21
+ Requires-Dist: httptools>=0.7.1; sys_platform != "win32"
22
+ Requires-Dist: jinja2>=3.1.6
23
+ Requires-Dist: orjson>=3.11.8
24
+ Requires-Dist: uvicorn>=0.44.0
25
+ Requires-Dist: uvloop>=0.22.1; sys_platform != "win32"
26
+ Provides-Extra: authoring
27
+ Requires-Dist: torch; extra == "authoring"
28
+ Requires-Dist: einops; extra == "authoring"
29
+ Requires-Dist: numpy; extra == "authoring"
30
+ Provides-Extra: render
31
+ Requires-Dist: playwright>=1.40; extra == "render"
32
+ Dynamic: license-file
33
+
34
+ # SimView Visualizer
35
+
36
+ [![Documentation](https://img.shields.io/badge/docs-GitHub%20Pages-blue.svg)](https://vlk-jan.github.io/simview/)
37
+ [![Live Demo](https://img.shields.io/badge/demo-live-brightgreen.svg)](https://vlk-jan.github.io/assets/demos/simview/)
38
+
39
+ **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.
40
+
41
+ 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.
42
+
43
+ ---
44
+
45
+ ## Documentation
46
+
47
+ Full documentation — usage guides, CLI reference, the JSON format specification,
48
+ the API reference, and developer/architecture docs — is available at:
49
+
50
+ **[https://vlk-jan.github.io/simview/](https://vlk-jan.github.io/simview/)**
51
+
52
+ Want to try it without installing anything? There's a
53
+ [live browser demo](https://vlk-jan.github.io/assets/demos/simview/)
54
+ (static snapshot, no backend needed).
55
+
56
+ ---
57
+
58
+ ## Features
59
+
60
+ - **Batched Simulations**: Visualize multiple simulation instances side-by-side.
61
+ - **Shared Terrain**: Efficient rendering with shared terrain across all batches.
62
+ - **Interactive UI**: Web-based controls for playback, camera, and data inspection.
63
+ - **Python API**: Easy-to-use API for generating scenes and launching the visualizer directly from your code.
64
+ - **JSON Support**: Load and save simulation data using a portable JSON format.
65
+
66
+ ---
67
+
68
+ ## Quick Start
69
+
70
+ The easiest way to get started is to run the provided example script:
71
+
72
+ ```bash
73
+ python example.py
74
+ ```
75
+
76
+ This script demonstrates how to use the Python API to create a simulation with wavy terrain, dynamic bodies, and time-series data.
77
+
78
+ The Python authoring API (`simview.scene`, `simview.state`, `simview.model`) depends on
79
+ `torch` and `einops`. Install them with the optional `authoring` extra shown below.
80
+ Only these are needed to *build* simulations; *viewing* an existing JSON file does not
81
+ require `torch`.
82
+
83
+ ---
84
+
85
+ ## Installation
86
+
87
+ Requires **Python 3.12+**.
88
+
89
+ ### From PyPI
90
+
91
+ To only view existing simulation JSON files:
92
+
93
+ ```bash
94
+ pip install simview
95
+ ```
96
+
97
+ To also author simulations from Python (installs `torch` and `einops`):
98
+
99
+ ```bash
100
+ pip install "simview[authoring]"
101
+ ```
102
+
103
+ ### From source
104
+
105
+ For working on SimView itself (or to track an unreleased commit), install an
106
+ editable checkout instead:
107
+
108
+ ```bash
109
+ pip install -e . # viewing only
110
+ pip install -e ".[authoring]" # viewing + authoring
111
+ ```
112
+
113
+ For independent use of this repository, use `venv` or `uv`:
114
+
115
+ ```bash
116
+ python -m venv .venv
117
+ source .venv/bin/activate
118
+ pip install -e ".[authoring]"
119
+ ```
120
+
121
+ ```bash
122
+ uv sync --extra authoring
123
+ source .venv/bin/activate
124
+ ```
125
+
126
+ ---
127
+
128
+ ## License
129
+
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
133
+ `simview/static/lib/` so the viewer works fully offline — see the
134
+ [documentation](https://vlk-jan.github.io/simview/dev/architecture/#vendored-third-party-libraries)
135
+ for details. All third-party libraries used by SimView are permissively licensed
136
+ (MIT/BSD), so there are no licensing restrictions on commercial use.
simview-3.6/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # SimView Visualizer
2
+
3
+ [![Documentation](https://img.shields.io/badge/docs-GitHub%20Pages-blue.svg)](https://vlk-jan.github.io/simview/)
4
+ [![Live Demo](https://img.shields.io/badge/demo-live-brightgreen.svg)](https://vlk-jan.github.io/assets/demos/simview/)
5
+
6
+ **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.
7
+
8
+ 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.
9
+
10
+ ---
11
+
12
+ ## Documentation
13
+
14
+ Full documentation — usage guides, CLI reference, the JSON format specification,
15
+ the API reference, and developer/architecture docs — is available at:
16
+
17
+ **[https://vlk-jan.github.io/simview/](https://vlk-jan.github.io/simview/)**
18
+
19
+ Want to try it without installing anything? There's a
20
+ [live browser demo](https://vlk-jan.github.io/assets/demos/simview/)
21
+ (static snapshot, no backend needed).
22
+
23
+ ---
24
+
25
+ ## Features
26
+
27
+ - **Batched Simulations**: Visualize multiple simulation instances side-by-side.
28
+ - **Shared Terrain**: Efficient rendering with shared terrain across all batches.
29
+ - **Interactive UI**: Web-based controls for playback, camera, and data inspection.
30
+ - **Python API**: Easy-to-use API for generating scenes and launching the visualizer directly from your code.
31
+ - **JSON Support**: Load and save simulation data using a portable JSON format.
32
+
33
+ ---
34
+
35
+ ## Quick Start
36
+
37
+ The easiest way to get started is to run the provided example script:
38
+
39
+ ```bash
40
+ python example.py
41
+ ```
42
+
43
+ This script demonstrates how to use the Python API to create a simulation with wavy terrain, dynamic bodies, and time-series data.
44
+
45
+ The Python authoring API (`simview.scene`, `simview.state`, `simview.model`) depends on
46
+ `torch` and `einops`. Install them with the optional `authoring` extra shown below.
47
+ Only these are needed to *build* simulations; *viewing* an existing JSON file does not
48
+ require `torch`.
49
+
50
+ ---
51
+
52
+ ## Installation
53
+
54
+ Requires **Python 3.12+**.
55
+
56
+ ### From PyPI
57
+
58
+ To only view existing simulation JSON files:
59
+
60
+ ```bash
61
+ pip install simview
62
+ ```
63
+
64
+ To also author simulations from Python (installs `torch` and `einops`):
65
+
66
+ ```bash
67
+ pip install "simview[authoring]"
68
+ ```
69
+
70
+ ### From source
71
+
72
+ For working on SimView itself (or to track an unreleased commit), install an
73
+ editable checkout instead:
74
+
75
+ ```bash
76
+ pip install -e . # viewing only
77
+ pip install -e ".[authoring]" # viewing + authoring
78
+ ```
79
+
80
+ For independent use of this repository, use `venv` or `uv`:
81
+
82
+ ```bash
83
+ python -m venv .venv
84
+ source .venv/bin/activate
85
+ pip install -e ".[authoring]"
86
+ ```
87
+
88
+ ```bash
89
+ uv sync --extra authoring
90
+ source .venv/bin/activate
91
+ ```
92
+
93
+ ---
94
+
95
+ ## License
96
+
97
+ SimView is distributed under the [BSD 3-Clause License](LICENSE). The web interface
98
+ vendors [uPlot](https://github.com/leeoniya/uPlot), [three.js](https://github.com/mrdoob/three.js),
99
+ and [chroma-js](https://github.com/gka/chroma.js) (all MIT licensed) under
100
+ `simview/static/lib/` so the viewer works fully offline — see the
101
+ [documentation](https://vlk-jan.github.io/simview/dev/architecture/#vendored-third-party-libraries)
102
+ for details. All third-party libraries used by SimView are permissively licensed
103
+ (MIT/BSD), so there are no licensing restrictions on commercial use.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simview"
7
- version = "3.4"
7
+ version = "3.6"
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.12"
10
10
  license = "BSD-3-Clause"
@@ -34,7 +34,7 @@ dependencies = [
34
34
  ]
35
35
 
36
36
  [project.urls]
37
- Homepage = "https://github.com/vlk-jan/simview"
37
+ Homepage = "https://vlk-jan.github.io/simview/"
38
38
  Repository = "https://github.com/vlk-jan/simview"
39
39
  Issues = "https://github.com/vlk-jan/simview/issues"
40
40
 
@@ -42,6 +42,10 @@ Issues = "https://github.com/vlk-jan/simview/issues"
42
42
  # Needed only to author simulations via the Python API (simview.scene / state / model).
43
43
  # Viewing an existing JSON file does not require these.
44
44
  authoring = ["torch", "einops", "numpy"]
45
+ # Needed only for `simview render` (headless PNG screenshots, e.g. from a
46
+ # SLURM job with no display). Also requires a one-time `playwright install
47
+ # chromium` to fetch the browser binary itself.
48
+ render = ["playwright>=1.40"]
45
49
 
46
50
  [project.scripts]
47
51
  simview = "simview.__main__:main"
@@ -61,6 +65,7 @@ dev = [
61
65
  "pyright>=1.1.411",
62
66
  "websockets>=16.1",
63
67
  ]
68
+ docs = ["mkdocs>=1.6", "mkdocs-material>=9.5", "mkdocstrings[python]>=0.27"]
64
69
 
65
70
  [tool.ruff]
66
71
  target-version = "py312"
@@ -82,14 +82,18 @@ def run_info(path: Path, as_json: bool) -> None:
82
82
 
83
83
 
84
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."""
85
+ """Print terrain value(s) at a point, over an area, or along a body's
86
+ trajectory, from the scene JSON at `path`, to stdout. With `--batches
87
+ A B`, compares two batches instead of querying a single one (for
88
+ `--along-body`, both batches' terrains are sampled along batch A's
89
+ trajectory -- see `query_along_body_diff`)."""
88
90
  from simview import terrain as terrain_mod
89
91
 
90
- if (args.point is None) == (args.area is None):
92
+ mode_count = sum(v is not None for v in (args.point, args.area, args.along_body))
93
+ if mode_count != 1:
91
94
  logger.error(
92
- "Error: 'simview terrain' requires exactly one of --point or --area."
95
+ "Error: 'simview terrain' requires exactly one of --point, --area, "
96
+ "or --along-body."
93
97
  )
94
98
  sys.exit(1)
95
99
  if args.area is not None and len(args.area) not in (0, 4):
@@ -106,40 +110,67 @@ def run_terrain(path: Path, args: argparse.Namespace) -> None:
106
110
  diff_mode = args.batches is not None
107
111
 
108
112
  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(
113
+ if args.along_body is not None:
114
+ model_data, states_data = terrain_mod.load_scene(path)
115
+ if diff_mode:
116
+ batch_a, batch_b = args.batches
117
+ result = terrain_mod.query_along_body_diff(
114
118
  model_data,
115
- args.point[0],
116
- args.point[1],
119
+ states_data,
120
+ args.along_body,
117
121
  batch_a,
118
122
  batch_b,
119
123
  args.layer,
124
+ args.every,
120
125
  )
121
- text = terrain_mod.format_point_diff_text(result)
122
- csv_text = terrain_mod.format_point_diff_csv(result)
126
+ text = terrain_mod.format_along_diff_text(result)
127
+ csv_text = terrain_mod.format_along_diff_csv(result)
123
128
  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
129
+ result = terrain_mod.query_along_body(
130
+ model_data,
131
+ states_data,
132
+ args.along_body,
133
+ args.layer,
134
+ args.batch,
135
+ args.every,
127
136
  )
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)
137
+ text = terrain_mod.format_along_text(result)
138
+ csv_text = terrain_mod.format_along_csv(result)
136
139
  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)
140
+ model_data = terrain_mod.load_scene_model(path)
141
+ if diff_mode:
142
+ batch_a, batch_b = args.batches
143
+ if args.point is not None:
144
+ result = terrain_mod.query_point_diff(
145
+ model_data,
146
+ args.point[0],
147
+ args.point[1],
148
+ batch_a,
149
+ batch_b,
150
+ args.layer,
151
+ )
152
+ text = terrain_mod.format_point_diff_text(result)
153
+ csv_text = terrain_mod.format_point_diff_csv(result)
154
+ else:
155
+ bounds = tuple(args.area) if args.area else None
156
+ result = terrain_mod.query_area_diff(
157
+ model_data, bounds, batch_a, batch_b, args.layer, args.stride
158
+ )
159
+ text = terrain_mod.format_area_diff_text(result)
160
+ csv_text = terrain_mod.format_area_diff_csv(result)
161
+ elif args.point is not None:
162
+ result = terrain_mod.query_point(
163
+ model_data, args.point[0], args.point[1], args.layer, args.batch
164
+ )
165
+ text = terrain_mod.format_point_text(result)
166
+ csv_text = terrain_mod.format_point_csv(result)
167
+ else:
168
+ bounds = tuple(args.area) if args.area else None
169
+ result = terrain_mod.query_area(
170
+ model_data, bounds, args.layer, args.batch, args.stride
171
+ )
172
+ text = terrain_mod.format_area_text(result)
173
+ csv_text = terrain_mod.format_area_csv(result)
143
174
  except (json.JSONDecodeError, ValueError, KeyError) as e:
144
175
  logger.error("Error: %s", e)
145
176
  sys.exit(1)
@@ -154,7 +185,10 @@ def run_terrain(path: Path, args: argparse.Namespace) -> None:
154
185
 
155
186
  def run_diff(path: Path, args: argparse.Namespace) -> None:
156
187
  """Print a trajectory divergence report between two batches of the scene
157
- JSON at `path`, to stdout."""
188
+ JSON at `path`, to stdout. With `--fail-on-exceed`, additionally exits
189
+ with a non-zero code once printing is done: 0 = within thresholds, 1 =
190
+ usage/parse error, 2 = a threshold was exceeded (see `--fail-on-exceed`'s
191
+ help text)."""
158
192
  from simview import diff as diff_mod
159
193
 
160
194
  if args.batches is None:
@@ -166,6 +200,16 @@ def run_diff(path: Path, args: argparse.Namespace) -> None:
166
200
  if args.json and args.csv:
167
201
  logger.error("Error: --json and --csv are mutually exclusive.")
168
202
  sys.exit(1)
203
+ if (
204
+ args.fail_on_exceed
205
+ and args.pos_threshold is None
206
+ and args.rot_threshold_deg is None
207
+ ):
208
+ logger.error(
209
+ "Error: --fail-on-exceed requires --pos-threshold and/or "
210
+ "--rot-threshold-deg."
211
+ )
212
+ sys.exit(1)
169
213
 
170
214
  try:
171
215
  model_data, states_data = diff_mod.load_scene(path)
@@ -178,6 +222,7 @@ def run_diff(path: Path, args: argparse.Namespace) -> None:
178
222
  every=args.every,
179
223
  pos_threshold=args.pos_threshold,
180
224
  rot_threshold_deg=args.rot_threshold_deg,
225
+ per_axis=args.per_axis,
181
226
  )
182
227
  text = diff_mod.format_diff_text(result)
183
228
  csv_text = diff_mod.format_diff_csv(result)
@@ -192,6 +237,45 @@ def run_diff(path: Path, args: argparse.Namespace) -> None:
192
237
  else:
193
238
  print(text)
194
239
 
240
+ if args.fail_on_exceed:
241
+ exceeded = any(
242
+ body["summary"]["first_frame_exceeding_pos_threshold"] is not None
243
+ or body["summary"]["first_frame_exceeding_rot_threshold"] is not None
244
+ for body in result["bodies"].values()
245
+ )
246
+ if exceeded:
247
+ sys.exit(2)
248
+
249
+
250
+ def run_render(path: Path, args: argparse.Namespace) -> None:
251
+ """Headlessly render one PNG screenshot of the scene JSON at `path`, for
252
+ generating figures from a display-less environment (e.g. a SLURM job)."""
253
+ from simview.render import render_screenshot
254
+
255
+ if args.output is None:
256
+ logger.error(
257
+ "Error: 'simview render' requires --output, e.g. 'simview render "
258
+ "scene.json --output frame.png'."
259
+ )
260
+ sys.exit(1)
261
+
262
+ try:
263
+ render_screenshot(
264
+ path,
265
+ args.output,
266
+ host=args.host,
267
+ port=args.port,
268
+ view=args.view,
269
+ width=args.width,
270
+ height=args.height,
271
+ )
272
+ except ImportError as e:
273
+ logger.error("Error: %s", e)
274
+ sys.exit(1)
275
+ except (RuntimeError, TimeoutError) as e:
276
+ logger.error("Error: 'simview render' failed: %s", e)
277
+ sys.exit(1)
278
+
195
279
 
196
280
  def save_merged(paths: list[Path], out_path: Path) -> None:
197
281
  """Merge `paths` (must be >= 2) and write the result to `out_path`, gzipped
@@ -218,11 +302,13 @@ def build_parser() -> argparse.ArgumentParser:
218
302
  help=(
219
303
  "Path(s) to simulation JSON file(s) to visualize, 'clear' to clear "
220
304
  "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)."
305
+ "file, 'terrain <file>' to query terrain values at a point, over "
306
+ "an area, or along a body's trajectory, 'diff <file>' to compare "
307
+ "two batches' trajectories, or 'render <file>' to headlessly save "
308
+ "a PNG screenshot (needs the 'render' extra). Multiple "
309
+ "visualize-mode files are merged into one scene, each file's "
310
+ "batches appended as extra batches (e.g. a real-world recording "
311
+ "plus a simulated rerun)."
226
312
  ),
227
313
  )
228
314
  parser.add_argument(
@@ -255,7 +341,7 @@ def build_parser() -> argparse.ArgumentParser:
255
341
  help=(
256
342
  "With 'simview terrain <file>', query terrain value(s) at a single "
257
343
  "(x, y) point (bilinear-interpolated). Mutually exclusive with "
258
- "--area."
344
+ "--area and --along-body."
259
345
  ),
260
346
  )
261
347
  parser.add_argument(
@@ -267,7 +353,23 @@ def build_parser() -> argparse.ArgumentParser:
267
353
  "With 'simview terrain <file>', query terrain values over a "
268
354
  "rectangular area: pass no values for the whole terrain extent, "
269
355
  "or exactly 4 values 'XMIN XMAX YMIN YMAX' for a sub-box. Mutually "
270
- "exclusive with --point."
356
+ "exclusive with --point and --along-body."
357
+ ),
358
+ )
359
+ parser.add_argument(
360
+ "--along-body",
361
+ type=str,
362
+ default=None,
363
+ metavar="BODY",
364
+ help=(
365
+ "With 'simview terrain <file>', sample terrain value(s) "
366
+ "bilinear-interpolated at BODY's (x, y) position in every "
367
+ "sampled state (e.g. friction/stiffness under a robot's driven "
368
+ "path). BODY is matched the same way as 'simview diff' --body "
369
+ "(full label, or any single name inside a rigidly-grouped body). "
370
+ "With --batches A B, samples both batches' terrains along batch "
371
+ "A's trajectory instead and reports the delta per layer. "
372
+ "Mutually exclusive with --point and --area."
271
373
  ),
272
374
  )
273
375
  parser.add_argument(
@@ -301,10 +403,11 @@ def build_parser() -> argparse.ArgumentParser:
301
403
  metavar=("A", "B"),
302
404
  help=(
303
405
  "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."
406
+ "With 'simview terrain <file> --point|--area|--along-body', "
407
+ "switches into cross-batch diff mode (value_a/value_b/delta per "
408
+ "layer) instead of a single-batch query, and takes precedence "
409
+ "over --batch if both are given. For --along-body, both batches' "
410
+ "terrains are sampled along batch A's trajectory."
308
411
  ),
309
412
  )
310
413
  parser.add_argument(
@@ -323,7 +426,10 @@ def build_parser() -> argparse.ArgumentParser:
323
426
  "--every",
324
427
  type=int,
325
428
  default=1,
326
- help="With 'simview diff <file>', sample every Nth frame (default: 1).",
429
+ help=(
430
+ "With 'simview diff <file>' or 'simview terrain <file> "
431
+ "--along-body', sample every Nth frame (default: 1)."
432
+ ),
327
433
  )
328
434
  parser.add_argument(
329
435
  "--pos-threshold",
@@ -345,6 +451,27 @@ def build_parser() -> argparse.ArgumentParser:
345
451
  "orientation error exceeds this many degrees."
346
452
  ),
347
453
  )
454
+ parser.add_argument(
455
+ "--per-axis",
456
+ action="store_true",
457
+ help=(
458
+ "With 'simview diff <file>', also report signed per-axis "
459
+ "(err_x/err_y/err_z = batch_a - batch_b) position error, matching "
460
+ "the browser Error Metrics panel's per-axis toggle."
461
+ ),
462
+ )
463
+ parser.add_argument(
464
+ "--fail-on-exceed",
465
+ action="store_true",
466
+ help=(
467
+ "With 'simview diff <file>', exit with code 2 (after printing the "
468
+ "normal output) if any diffed body's trajectory exceeds "
469
+ "--pos-threshold or --rot-threshold-deg; requires at least one of "
470
+ "them. Exit codes: 0 = within thresholds, 1 = usage/parse error, "
471
+ "2 = threshold exceeded -- lets scripts/CI tell divergence apart "
472
+ "from a broken invocation."
473
+ ),
474
+ )
348
475
  parser.add_argument(
349
476
  "--host",
350
477
  type=str,
@@ -365,6 +492,37 @@ def build_parser() -> argparse.ArgumentParser:
365
492
  action="store_true",
366
493
  help="Don't automatically open a browser tab once the server starts.",
367
494
  )
495
+ parser.add_argument(
496
+ "--output",
497
+ type=str,
498
+ default=None,
499
+ metavar="PATH",
500
+ help="With 'simview render <file>', PNG file path to save the screenshot to.",
501
+ )
502
+ parser.add_argument(
503
+ "--view",
504
+ type=str,
505
+ default=None,
506
+ metavar="HASH",
507
+ help=(
508
+ "With 'simview render <file>', a shareable view-link hash (from "
509
+ "the viewer's 'Copy view link' button, with or without the "
510
+ "leading '#') to set the camera/playback/terrain state before "
511
+ "capturing. Default: the viewer's startup state."
512
+ ),
513
+ )
514
+ parser.add_argument(
515
+ "--width",
516
+ type=int,
517
+ default=1280,
518
+ help="With 'simview render <file>', screenshot width in pixels (default: 1280).",
519
+ )
520
+ parser.add_argument(
521
+ "--height",
522
+ type=int,
523
+ default=720,
524
+ help="With 'simview render <file>', screenshot height in pixels (default: 720).",
525
+ )
368
526
  parser.add_argument(
369
527
  "--save-merged",
370
528
  type=str,
@@ -437,6 +595,21 @@ def main():
437
595
  run_diff(diff_path, args)
438
596
  return
439
597
 
598
+ if args.inputs and args.inputs[0] == "render":
599
+ render_args = args.inputs[1:]
600
+ if len(render_args) != 1:
601
+ logger.error(
602
+ "Error: 'simview render' requires exactly one file argument, "
603
+ "e.g. 'simview render scene.json --output frame.png'."
604
+ )
605
+ sys.exit(1)
606
+ render_path = Path(render_args[0])
607
+ if not (render_path.exists() and render_path.is_file()):
608
+ logger.error("Error: File '%s' not found or is not a file.", render_path)
609
+ sys.exit(1)
610
+ run_render(render_path, args)
611
+ return
612
+
440
613
  if args.inputs == ["clear"]:
441
614
  clear_cache()
442
615
  return