simview 3.5__tar.gz → 4.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 (197) hide show
  1. {simview-3.5/simview.egg-info → simview-4.0.0}/PKG-INFO +4 -4
  2. {simview-3.5 → simview-4.0.0}/README.md +3 -3
  3. {simview-3.5 → simview-4.0.0}/pyproject.toml +1 -1
  4. {simview-3.5 → simview-4.0.0}/simview/__main__.py +135 -48
  5. {simview-3.5 → simview-4.0.0}/simview/info.py +22 -23
  6. {simview-3.5 → simview-4.0.0}/simview/merge.py +51 -79
  7. {simview-3.5 → simview-4.0.0}/simview/model.py +183 -83
  8. {simview-3.5 → simview-4.0.0}/simview/scene.py +68 -13
  9. {simview-3.5 → simview-4.0.0}/simview/server.py +4 -8
  10. simview-4.0.0/simview/static/css/controls.css +104 -0
  11. {simview-3.5 → simview-4.0.0}/simview/static/js/SimView.js +69 -3
  12. {simview-3.5 → simview-4.0.0}/simview/static/js/components/BatchManager.js +6 -6
  13. {simview-3.5 → simview-4.0.0}/simview/static/js/components/InteractionController.js +123 -32
  14. {simview-3.5 → simview-4.0.0}/simview/static/js/config.js +10 -1
  15. {simview-3.5 → simview-4.0.0}/simview/static/js/objects/Body.js +98 -1
  16. {simview-3.5 → simview-4.0.0}/simview/static/js/objects/Terrain.js +184 -101
  17. simview-4.0.0/simview/static/js/objects/colormap.js +63 -0
  18. simview-4.0.0/simview/static/js/objects/similarity.js +38 -0
  19. {simview-3.5 → simview-4.0.0}/simview/static/js/objects/utils.js +25 -4
  20. {simview-3.5 → simview-4.0.0}/simview/static/js/ui/AnalysisPanel.js +46 -25
  21. {simview-3.5 → simview-4.0.0}/simview/static/js/ui/Controls.js +116 -0
  22. simview-4.0.0/simview/static/js/ui/Legend.js +154 -0
  23. {simview-3.5 → simview-4.0.0}/simview/static/js/ui/ScalarPlotter.js +6 -2
  24. simview-4.0.0/simview/static/js/ui/TerrainProfile.js +541 -0
  25. simview-4.0.0/simview/static/js/utils/terrainSample.js +108 -0
  26. {simview-3.5 → simview-4.0.0}/simview/terrain.py +509 -21
  27. {simview-3.5 → simview-4.0.0/simview.egg-info}/PKG-INFO +4 -4
  28. {simview-3.5 → simview-4.0.0}/simview.egg-info/SOURCES.txt +5 -0
  29. {simview-3.5 → simview-4.0.0}/tests/test_cli.py +351 -2
  30. {simview-3.5 → simview-4.0.0}/tests/test_info.py +27 -2
  31. simview-4.0.0/tests/test_pointcloud_features.py +135 -0
  32. {simview-3.5 → simview-4.0.0}/tests/test_roundtrip.py +31 -15
  33. {simview-3.5 → simview-4.0.0}/tests/test_scene.py +22 -5
  34. {simview-3.5 → simview-4.0.0}/tests/test_server.py +0 -18
  35. {simview-3.5 → simview-4.0.0}/tests/test_terrain_query.py +302 -9
  36. simview-3.5/simview/static/css/controls.css +0 -36
  37. simview-3.5/simview/static/js/ui/Legend.js +0 -103
  38. {simview-3.5 → simview-4.0.0}/LICENSE +0 -0
  39. {simview-3.5 → simview-4.0.0}/MANIFEST.in +0 -0
  40. {simview-3.5 → simview-4.0.0}/setup.cfg +0 -0
  41. {simview-3.5 → simview-4.0.0}/simview/__init__.py +0 -0
  42. {simview-3.5 → simview-4.0.0}/simview/diff.py +0 -0
  43. {simview-3.5 → simview-4.0.0}/simview/launcher.py +0 -0
  44. {simview-3.5 → simview-4.0.0}/simview/live.py +0 -0
  45. {simview-3.5 → simview-4.0.0}/simview/py.typed +0 -0
  46. {simview-3.5 → simview-4.0.0}/simview/render.py +0 -0
  47. {simview-3.5 → simview-4.0.0}/simview/state.py +0 -0
  48. {simview-3.5 → simview-4.0.0}/simview/static/js/components/AnimationController.js +0 -0
  49. {simview-3.5 → simview-4.0.0}/simview/static/js/components/InteractionControls.js +0 -0
  50. {simview-3.5 → simview-4.0.0}/simview/static/js/components/Scene.js +0 -0
  51. {simview-3.5 → simview-4.0.0}/simview/static/js/components/StateStore.js +0 -0
  52. {simview-3.5 → simview-4.0.0}/simview/static/js/main.js +0 -0
  53. {simview-3.5 → simview-4.0.0}/simview/static/js/objects/StaticObject.js +0 -0
  54. {simview-3.5 → simview-4.0.0}/simview/static/js/ui/BatchLegend.js +0 -0
  55. {simview-3.5 → simview-4.0.0}/simview/static/js/ui/BodyStateWindow.js +0 -0
  56. {simview-3.5 → simview-4.0.0}/simview/static/js/ui/ErrorMetrics.js +0 -0
  57. {simview-3.5 → simview-4.0.0}/simview/static/js/ui/PlaybackControls.js +0 -0
  58. {simview-3.5 → simview-4.0.0}/simview/static/js/utils/batchPresets.js +0 -0
  59. {simview-3.5 → simview-4.0.0}/simview/static/js/utils/blobCodec.js +0 -0
  60. {simview-3.5 → simview-4.0.0}/simview/static/js/utils/bodyTransforms.js +0 -0
  61. {simview-3.5 → simview-4.0.0}/simview/static/js/utils/csv.js +0 -0
  62. {simview-3.5 → simview-4.0.0}/simview/static/js/utils/errorMath.js +0 -0
  63. {simview-3.5 → simview-4.0.0}/simview/static/js/utils/injectStyles.js +0 -0
  64. {simview-3.5 → simview-4.0.0}/simview/static/js/utils/interpolate.js +0 -0
  65. {simview-3.5 → simview-4.0.0}/simview/static/js/utils/liveFollow.js +0 -0
  66. {simview-3.5 → simview-4.0.0}/simview/static/js/utils/loadRecordingLibs.js +0 -0
  67. {simview-3.5 → simview-4.0.0}/simview/static/js/utils/viewState.js +0 -0
  68. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/index.min.js +0 -0
  69. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/Color.js +0 -0
  70. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/chroma.js +0 -0
  71. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/colors/colorbrewer.js +0 -0
  72. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/colors/w3cx11.js +0 -0
  73. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/average.js +0 -0
  74. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/bezier.js +0 -0
  75. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/blend.js +0 -0
  76. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/cubehelix.js +0 -0
  77. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/mix.js +0 -0
  78. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/random.js +0 -0
  79. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/scale.js +0 -0
  80. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/_hsx.js +0 -0
  81. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hcg.js +0 -0
  82. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsi.js +0 -0
  83. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsl.js +0 -0
  84. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsv.js +0 -0
  85. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/index.js +0 -0
  86. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lab.js +0 -0
  87. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lch.js +0 -0
  88. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lrgb.js +0 -0
  89. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/num.js +0 -0
  90. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklab.js +0 -0
  91. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklch.js +0 -0
  92. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/rgb.js +0 -0
  93. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/cmyk2rgb.js +0 -0
  94. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/index.js +0 -0
  95. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/rgb2cmyk.js +0 -0
  96. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/css2rgb.js +0 -0
  97. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/hsl2css.js +0 -0
  98. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/index.js +0 -0
  99. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/lab2css.js +0 -0
  100. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/lch2css.js +0 -0
  101. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/oklab2css.js +0 -0
  102. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/oklch2css.js +0 -0
  103. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/rgb2css.js +0 -0
  104. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/gl/index.js +0 -0
  105. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/hcg2rgb.js +0 -0
  106. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/index.js +0 -0
  107. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/rgb2hcg.js +0 -0
  108. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hex/hex2rgb.js +0 -0
  109. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hex/index.js +0 -0
  110. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hex/rgb2hex.js +0 -0
  111. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/hsi2rgb.js +0 -0
  112. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/index.js +0 -0
  113. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/rgb2hsi.js +0 -0
  114. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/hsl2rgb.js +0 -0
  115. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/index.js +0 -0
  116. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/rgb2hsl.js +0 -0
  117. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/hsv2rgb.js +0 -0
  118. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/index.js +0 -0
  119. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/rgb2hsv.js +0 -0
  120. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/input.js +0 -0
  121. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lab/index.js +0 -0
  122. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab-constants.js +0 -0
  123. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab2rgb.js +0 -0
  124. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lab/rgb2lab.js +0 -0
  125. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/hcl2rgb.js +0 -0
  126. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/index.js +0 -0
  127. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lab2lch.js +0 -0
  128. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2lab.js +0 -0
  129. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2rgb.js +0 -0
  130. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/rgb2lch.js +0 -0
  131. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/named/index.js +0 -0
  132. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/num/index.js +0 -0
  133. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/num/num2rgb.js +0 -0
  134. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/num/rgb2num.js +0 -0
  135. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/index.js +0 -0
  136. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/oklab2rgb.js +0 -0
  137. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/rgb2oklab.js +0 -0
  138. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/index.js +0 -0
  139. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/oklch2rgb.js +0 -0
  140. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/rgb2oklch.js +0 -0
  141. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/rgb/index.js +0 -0
  142. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/temp/index.js +0 -0
  143. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/temp/rgb2temperature.js +0 -0
  144. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/temp/temperature2rgb.js +0 -0
  145. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/alpha.js +0 -0
  146. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/clipped.js +0 -0
  147. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/darken.js +0 -0
  148. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/get.js +0 -0
  149. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/luminance.js +0 -0
  150. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/mix.js +0 -0
  151. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/premultiply.js +0 -0
  152. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/saturate.js +0 -0
  153. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/set.js +0 -0
  154. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/shade.js +0 -0
  155. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/analyze.js +0 -0
  156. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/clip_rgb.js +0 -0
  157. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/contrast.js +0 -0
  158. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/contrastAPCA.js +0 -0
  159. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/delta-e.js +0 -0
  160. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/distance.js +0 -0
  161. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/index.js +0 -0
  162. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/last.js +0 -0
  163. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/limit.js +0 -0
  164. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/multiply-matrices.js +0 -0
  165. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/scales.js +0 -0
  166. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/type.js +0 -0
  167. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/unpack.js +0 -0
  168. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/valid.js +0 -0
  169. {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/version.js +0 -0
  170. {simview-3.5 → simview-4.0.0}/simview/static/lib/download.js +0 -0
  171. {simview-3.5 → simview-4.0.0}/simview/static/lib/js-colormaps.js +0 -0
  172. {simview-3.5 → simview-4.0.0}/simview/static/lib/tar.js +0 -0
  173. {simview-3.5 → simview-4.0.0}/simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +0 -0
  174. {simview-3.5 → simview-4.0.0}/simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +0 -0
  175. {simview-3.5 → simview-4.0.0}/simview/static/lib/three-0.174.0/three.core.js +0 -0
  176. {simview-3.5 → simview-4.0.0}/simview/static/lib/three-0.174.0/three.module.js +0 -0
  177. {simview-3.5 → simview-4.0.0}/simview/static/lib/uPlot.esm.js +0 -0
  178. {simview-3.5 → simview-4.0.0}/simview/static/lib/uPlot.min.css +0 -0
  179. {simview-3.5 → simview-4.0.0}/simview/static/textures/contacts/red-cross0.png +0 -0
  180. {simview-3.5 → simview-4.0.0}/simview/static/textures/points/ball0.png +0 -0
  181. {simview-3.5 → simview-4.0.0}/simview/static/textures/points/ball1.png +0 -0
  182. {simview-3.5 → simview-4.0.0}/simview/templates/index.html +0 -0
  183. {simview-3.5 → simview-4.0.0}/simview/utils.py +0 -0
  184. {simview-3.5 → simview-4.0.0}/simview.egg-info/dependency_links.txt +0 -0
  185. {simview-3.5 → simview-4.0.0}/simview.egg-info/entry_points.txt +0 -0
  186. {simview-3.5 → simview-4.0.0}/simview.egg-info/requires.txt +0 -0
  187. {simview-3.5 → simview-4.0.0}/simview.egg-info/top_level.txt +0 -0
  188. {simview-3.5 → simview-4.0.0}/tests/test_columnar_states.py +0 -0
  189. {simview-3.5 → simview-4.0.0}/tests/test_contacts.py +0 -0
  190. {simview-3.5 → simview-4.0.0}/tests/test_diff.py +0 -0
  191. {simview-3.5 → simview-4.0.0}/tests/test_launcher.py +0 -0
  192. {simview-3.5 → simview-4.0.0}/tests/test_live.py +0 -0
  193. {simview-3.5 → simview-4.0.0}/tests/test_merge.py +0 -0
  194. {simview-3.5 → simview-4.0.0}/tests/test_show.py +0 -0
  195. {simview-3.5 → simview-4.0.0}/tests/test_terrain.py +0 -0
  196. {simview-3.5 → simview-4.0.0}/tests/test_trajectory.py +0 -0
  197. {simview-3.5 → simview-4.0.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simview
3
- Version: 3.5
3
+ Version: 4.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
@@ -34,7 +34,7 @@ Dynamic: license-file
34
34
  # SimView Visualizer
35
35
 
36
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/)
37
+ [![Live Demo](https://img.shields.io/badge/demo-live-brightgreen.svg)](https://vlk-jan.github.io/simview/demo/)
38
38
 
39
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
40
 
@@ -50,8 +50,8 @@ the API reference, and developer/architecture docs — is available at:
50
50
  **[https://vlk-jan.github.io/simview/](https://vlk-jan.github.io/simview/)**
51
51
 
52
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).
53
+ [live browser demo](https://vlk-jan.github.io/simview/demo/)
54
+ (always up-to-date with the current code, no backend needed).
55
55
 
56
56
  ---
57
57
 
@@ -1,7 +1,7 @@
1
1
  # SimView Visualizer
2
2
 
3
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/)
4
+ [![Live Demo](https://img.shields.io/badge/demo-live-brightgreen.svg)](https://vlk-jan.github.io/simview/demo/)
5
5
 
6
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
7
 
@@ -17,8 +17,8 @@ the API reference, and developer/architecture docs — is available at:
17
17
  **[https://vlk-jan.github.io/simview/](https://vlk-jan.github.io/simview/)**
18
18
 
19
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).
20
+ [live browser demo](https://vlk-jan.github.io/simview/demo/)
21
+ (always up-to-date with the current code, no backend needed).
22
22
 
23
23
  ---
24
24
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simview"
7
- version = "3.5"
7
+ version = "4.0.0"
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.12"
10
10
  license = "BSD-3-Clause"
@@ -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)
@@ -193,6 +237,15 @@ def run_diff(path: Path, args: argparse.Namespace) -> None:
193
237
  else:
194
238
  print(text)
195
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
+
196
249
 
197
250
  def run_render(path: Path, args: argparse.Namespace) -> None:
198
251
  """Headlessly render one PNG screenshot of the scene JSON at `path`, for
@@ -249,12 +302,13 @@ def build_parser() -> argparse.ArgumentParser:
249
302
  help=(
250
303
  "Path(s) to simulation JSON file(s) to visualize, 'clear' to clear "
251
304
  "cache, 'info <file>' to print a structural summary of one scene "
252
- "file, 'terrain <file>' to query terrain values at a point or over "
253
- "an area, 'diff <file>' to compare two batches' trajectories, or "
254
- "'render <file>' to headlessly save a PNG screenshot (needs the "
255
- "'render' extra). Multiple visualize-mode files are merged into "
256
- "one scene, each file's batches appended as extra batches (e.g. a "
257
- "real-world 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)."
258
312
  ),
259
313
  )
260
314
  parser.add_argument(
@@ -287,7 +341,7 @@ def build_parser() -> argparse.ArgumentParser:
287
341
  help=(
288
342
  "With 'simview terrain <file>', query terrain value(s) at a single "
289
343
  "(x, y) point (bilinear-interpolated). Mutually exclusive with "
290
- "--area."
344
+ "--area and --along-body."
291
345
  ),
292
346
  )
293
347
  parser.add_argument(
@@ -299,16 +353,33 @@ def build_parser() -> argparse.ArgumentParser:
299
353
  "With 'simview terrain <file>', query terrain values over a "
300
354
  "rectangular area: pass no values for the whole terrain extent, "
301
355
  "or exactly 4 values 'XMIN XMAX YMIN YMAX' for a sub-box. Mutually "
302
- "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."
303
373
  ),
304
374
  )
305
375
  parser.add_argument(
306
376
  "--layer",
307
- choices=["height", "friction", "stiffness", "all"],
308
377
  default="all",
309
378
  help=(
310
- "With 'simview terrain <file>', which terrain layer(s) to query "
311
- "(default: all present)."
379
+ "With 'simview terrain <file>', which terrain layer to query: "
380
+ "'height', 'all' (default: all present), or the name of any "
381
+ "arbitrary property the terrain was authored with (e.g. "
382
+ "'friction', 'stiffness', or a custom name)."
312
383
  ),
313
384
  )
314
385
  parser.add_argument(
@@ -333,10 +404,11 @@ def build_parser() -> argparse.ArgumentParser:
333
404
  metavar=("A", "B"),
334
405
  help=(
335
406
  "Two batch indices to compare. Required by 'simview diff <file>'. "
336
- "With 'simview terrain <file> --point|--area', switches into "
337
- "cross-batch diff mode (value_a/value_b/delta per layer) instead "
338
- "of a single-batch query, and takes precedence over --batch if "
339
- "both are given."
407
+ "With 'simview terrain <file> --point|--area|--along-body', "
408
+ "switches into cross-batch diff mode (value_a/value_b/delta per "
409
+ "layer) instead of a single-batch query, and takes precedence "
410
+ "over --batch if both are given. For --along-body, both batches' "
411
+ "terrains are sampled along batch A's trajectory."
340
412
  ),
341
413
  )
342
414
  parser.add_argument(
@@ -355,7 +427,10 @@ def build_parser() -> argparse.ArgumentParser:
355
427
  "--every",
356
428
  type=int,
357
429
  default=1,
358
- help="With 'simview diff <file>', sample every Nth frame (default: 1).",
430
+ help=(
431
+ "With 'simview diff <file>' or 'simview terrain <file> "
432
+ "--along-body', sample every Nth frame (default: 1)."
433
+ ),
359
434
  )
360
435
  parser.add_argument(
361
436
  "--pos-threshold",
@@ -386,6 +461,18 @@ def build_parser() -> argparse.ArgumentParser:
386
461
  "the browser Error Metrics panel's per-axis toggle."
387
462
  ),
388
463
  )
464
+ parser.add_argument(
465
+ "--fail-on-exceed",
466
+ action="store_true",
467
+ help=(
468
+ "With 'simview diff <file>', exit with code 2 (after printing the "
469
+ "normal output) if any diffed body's trajectory exceeds "
470
+ "--pos-threshold or --rot-threshold-deg; requires at least one of "
471
+ "them. Exit codes: 0 = within thresholds, 1 = usage/parse error, "
472
+ "2 = threshold exceeded -- lets scripts/CI tell divergence apart "
473
+ "from a broken invocation."
474
+ ),
475
+ )
389
476
  parser.add_argument(
390
477
  "--host",
391
478
  type=str,
@@ -69,8 +69,7 @@ def _summarize_terrain(terrain: dict) -> dict:
69
69
  bounds = terrain.get("bounds") or {}
70
70
  height = terrain.get("heightData")
71
71
  normals = terrain.get("normals")
72
- friction = terrain.get("frictionData")
73
- stiffness = terrain.get("stiffnessData")
72
+ properties = terrain.get("properties") or {}
74
73
  return {
75
74
  "extent": {"x": dims.get("sizeX"), "y": dims.get("sizeY")},
76
75
  "shape": {"x": dims.get("resolutionX"), "y": dims.get("resolutionY")},
@@ -83,28 +82,18 @@ def _summarize_terrain(terrain: dict) -> dict:
83
82
  "max_z": bounds.get("maxZ"),
84
83
  },
85
84
  "is_singleton": terrain.get("isSingleton"),
86
- "has_friction": friction is not None,
87
- "has_stiffness": stiffness is not None,
88
- "friction_bounds": (
89
- {"min": bounds.get("minFriction"), "max": bounds.get("maxFriction")}
90
- if friction is not None
91
- else None
92
- ),
93
- "stiffness_bounds": (
94
- {"min": bounds.get("minStiffness"), "max": bounds.get("maxStiffness")}
95
- if stiffness is not None
96
- else None
97
- ),
98
85
  "height_data_encoding": _encoding_of(height),
99
86
  "height_data_bytes": _blob_byte_length(height),
100
87
  "normals_encoding": _encoding_of(normals),
101
88
  "normals_bytes": _blob_byte_length(normals),
102
- "friction_data_encoding": _encoding_of(friction)
103
- if friction is not None
104
- else None,
105
- "stiffness_data_encoding": (
106
- _encoding_of(stiffness) if stiffness is not None else None
107
- ),
89
+ "properties": {
90
+ name: {
91
+ "bounds": {"min": prop.get("min"), "max": prop.get("max")},
92
+ "encoding": _encoding_of(prop.get("data")),
93
+ "bytes": _blob_byte_length(prop.get("data")),
94
+ }
95
+ for name, prop in properties.items()
96
+ },
108
97
  }
109
98
 
110
99
 
@@ -142,6 +131,7 @@ def _summarize_model(model: dict, warnings: list[str]) -> dict:
142
131
  "scalar_names": model.get("scalarNames") or [],
143
132
  "dt": model.get("dt"),
144
133
  "collapse": model.get("collapse"),
134
+ "metadata": model.get("metadata"),
145
135
  "terrain": _summarize_terrain(terrain) if terrain is not None else None,
146
136
  "bodies": {
147
137
  "count": len(bodies),
@@ -445,6 +435,12 @@ def format_text(summary: dict) -> str:
445
435
  lines.append(f" Scalars: {', '.join(model['scalar_names']) or '(none)'}")
446
436
  lines.append(f" dt: {model['dt']} Collapse: {model['collapse']}")
447
437
 
438
+ metadata = model["metadata"]
439
+ if metadata:
440
+ lines.append("\nMetadata")
441
+ for key, value in metadata.items():
442
+ lines.append(f" {key}: {value}")
443
+
448
444
  terrain = model["terrain"]
449
445
  if terrain is None:
450
446
  lines.append("\nTerrain: (none)")
@@ -459,9 +455,12 @@ def format_text(summary: dict) -> str:
459
455
  f" Bounds: x=[{b['min_x']},{b['max_x']}] "
460
456
  f"y=[{b['min_y']},{b['max_y']}] z=[{b['min_z']},{b['max_z']}]"
461
457
  )
462
- friction = "yes" if terrain["has_friction"] else "no"
463
- stiffness = "yes" if terrain["has_stiffness"] else "no"
464
- lines.append(f" Friction data: {friction} Stiffness data: {stiffness}")
458
+ props = terrain["properties"]
459
+ if props:
460
+ names = ", ".join(sorted(props))
461
+ lines.append(f" Properties: {names}")
462
+ else:
463
+ lines.append(" Properties: (none)")
465
464
  h_bytes = terrain["height_data_bytes"]
466
465
  n_bytes = terrain["normals_bytes"]
467
466
  lines.append(
@@ -64,13 +64,13 @@ def _decode_state_field(value, width: int):
64
64
 
65
65
 
66
66
  def _decode_per_batch(value: list | str, batch_size: int) -> list:
67
- """Normalize a terrain data field (heightData/normals/frictionData/
68
- stiffnessData) to a plain list of length `batch_size`, one entry per
69
- batch, regardless of whether it's a binary ``__b64__`` blob (one flat
70
- buffer covering all batches) or an already-batched plain list. Used so
71
- that inputs mixing binary and plain-list encoding can still be
72
- concatenated -- each file's field is decoded independently rather than
73
- branching on a single file's encoding."""
67
+ """Normalize a terrain data field (heightData/normals/a named property's
68
+ data) to a plain list of length `batch_size`, one entry per batch,
69
+ regardless of whether it's a binary ``__b64__`` blob (one flat buffer
70
+ covering all batches) or an already-batched plain list. Used so that
71
+ inputs mixing binary and plain-list encoding can still be concatenated --
72
+ each file's field is decoded independently rather than branching on a
73
+ single file's encoding."""
74
74
  flat = _decode_b64_floats(value)
75
75
  if flat is None:
76
76
  # `_decode_b64_floats` only returns None for non-`__b64__` input, and
@@ -276,26 +276,28 @@ def _merge_terrain(
276
276
  f"'{labels[0]}' dimensions {dims}."
277
277
  )
278
278
 
279
- has_friction = all(
280
- model["terrain"].get("frictionData") is not None for model in models
281
- )
282
- has_stiffness = all(
283
- model["terrain"].get("stiffnessData") is not None for model in models
284
- )
285
- if not has_friction and any(
286
- model["terrain"].get("frictionData") is not None for model in models
287
- ):
288
- logger.warning(
289
- "Not all files provide terrain friction data; dropping "
290
- "frictionData from the merged terrain."
291
- )
292
- if not has_stiffness and any(
293
- model["terrain"].get("stiffnessData") is not None for model in models
294
- ):
295
- logger.warning(
296
- "Not all files provide terrain stiffness data; dropping "
297
- "stiffnessData from the merged terrain."
279
+ # Union of property names across all files, in first-seen order. A property
280
+ # present in every file is kept (concatenated + bounds merged); one present
281
+ # in only some is dropped with a warning, same all-or-nothing rule as
282
+ # heightData/normals require unconditionally.
283
+ property_names: list[str] = []
284
+ for model in models:
285
+ for name in model["terrain"].get("properties") or {}:
286
+ if name not in property_names:
287
+ property_names.append(name)
288
+ kept_properties = []
289
+ for name in property_names:
290
+ present_in_all = all(
291
+ name in (model["terrain"].get("properties") or {}) for model in models
298
292
  )
293
+ if present_in_all:
294
+ kept_properties.append(name)
295
+ else:
296
+ logger.warning(
297
+ "Not all files provide terrain property '%s'; dropping it "
298
+ "from the merged terrain.",
299
+ name,
300
+ )
299
301
 
300
302
  def _concat_lists_or_b64(items: list[tuple]) -> list | None:
301
303
  # Each item is decoded independently, keyed by its own batch_size (not
@@ -309,9 +311,12 @@ def _merge_terrain(
309
311
  merged.extend(_decode_per_batch(value, batch_size))
310
312
  return merged
311
313
 
312
- height_data, normals, friction_data, stiffness_data = [], [], [], []
314
+ height_data, normals = [], []
315
+ property_data: dict[str, list] = {name: [] for name in kept_properties}
313
316
  min_z = max_z = None
314
- min_friction = max_friction = min_stiffness = max_stiffness = None
317
+ property_min_max: dict[str, tuple[float | None, float | None]] = {
318
+ name: (None, None) for name in kept_properties
319
+ }
315
320
  for model, batch_size, label in zip(models, batch_sizes, labels):
316
321
  terrain = model["terrain"]
317
322
  singleton = terrain.get("isSingleton", False)
@@ -335,51 +340,18 @@ def _merge_terrain(
335
340
  bounds = terrain["bounds"]
336
341
  min_z = bounds["minZ"] if min_z is None else min(min_z, bounds["minZ"])
337
342
  max_z = bounds["maxZ"] if max_z is None else max(max_z, bounds["maxZ"])
338
- if has_friction:
339
- friction_data.append(
343
+ for name in kept_properties:
344
+ prop = terrain["properties"][name]
345
+ property_data[name].append(
340
346
  (
341
- _expand_batched(
342
- terrain["frictionData"],
343
- singleton,
344
- batch_size,
345
- "frictionData",
346
- label,
347
- ),
347
+ _expand_batched(prop["data"], singleton, batch_size, name, label),
348
348
  batch_size,
349
349
  )
350
350
  )
351
- min_friction = (
352
- bounds["minFriction"]
353
- if min_friction is None
354
- else min(min_friction, bounds["minFriction"])
355
- )
356
- max_friction = (
357
- bounds["maxFriction"]
358
- if max_friction is None
359
- else max(max_friction, bounds["maxFriction"])
360
- )
361
- if has_stiffness:
362
- stiffness_data.append(
363
- (
364
- _expand_batched(
365
- terrain["stiffnessData"],
366
- singleton,
367
- batch_size,
368
- "stiffnessData",
369
- label,
370
- ),
371
- batch_size,
372
- )
373
- )
374
- min_stiffness = (
375
- bounds["minStiffness"]
376
- if min_stiffness is None
377
- else min(min_stiffness, bounds["minStiffness"])
378
- )
379
- max_stiffness = (
380
- bounds["maxStiffness"]
381
- if max_stiffness is None
382
- else max(max_stiffness, bounds["maxStiffness"])
351
+ cur_min, cur_max = property_min_max[name]
352
+ property_min_max[name] = (
353
+ prop["min"] if cur_min is None else min(cur_min, prop["min"]),
354
+ prop["max"] if cur_max is None else max(cur_max, prop["max"]),
383
355
  )
384
356
 
385
357
  merged_bounds = {
@@ -390,12 +362,15 @@ def _merge_terrain(
390
362
  "minZ": min_z,
391
363
  "maxZ": max_z,
392
364
  }
393
- if has_friction:
394
- merged_bounds["minFriction"] = min_friction
395
- merged_bounds["maxFriction"] = max_friction
396
- if has_stiffness:
397
- merged_bounds["minStiffness"] = min_stiffness
398
- merged_bounds["maxStiffness"] = max_stiffness
365
+
366
+ merged_properties = {
367
+ name: {
368
+ "data": _concat_lists_or_b64(property_data[name]),
369
+ "min": property_min_max[name][0],
370
+ "max": property_min_max[name][1],
371
+ }
372
+ for name in kept_properties
373
+ }
399
374
 
400
375
  return {
401
376
  "dimensions": dims,
@@ -403,10 +378,7 @@ def _merge_terrain(
403
378
  "isSingleton": False,
404
379
  "heightData": _concat_lists_or_b64(height_data),
405
380
  "normals": _concat_lists_or_b64(normals),
406
- "frictionData": _concat_lists_or_b64(friction_data) if has_friction else None,
407
- "stiffnessData": _concat_lists_or_b64(stiffness_data)
408
- if has_stiffness
409
- else None,
381
+ "properties": merged_properties,
410
382
  }
411
383
 
412
384