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.
- {simview-3.5/simview.egg-info → simview-4.0.0}/PKG-INFO +4 -4
- {simview-3.5 → simview-4.0.0}/README.md +3 -3
- {simview-3.5 → simview-4.0.0}/pyproject.toml +1 -1
- {simview-3.5 → simview-4.0.0}/simview/__main__.py +135 -48
- {simview-3.5 → simview-4.0.0}/simview/info.py +22 -23
- {simview-3.5 → simview-4.0.0}/simview/merge.py +51 -79
- {simview-3.5 → simview-4.0.0}/simview/model.py +183 -83
- {simview-3.5 → simview-4.0.0}/simview/scene.py +68 -13
- {simview-3.5 → simview-4.0.0}/simview/server.py +4 -8
- simview-4.0.0/simview/static/css/controls.css +104 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/SimView.js +69 -3
- {simview-3.5 → simview-4.0.0}/simview/static/js/components/BatchManager.js +6 -6
- {simview-3.5 → simview-4.0.0}/simview/static/js/components/InteractionController.js +123 -32
- {simview-3.5 → simview-4.0.0}/simview/static/js/config.js +10 -1
- {simview-3.5 → simview-4.0.0}/simview/static/js/objects/Body.js +98 -1
- {simview-3.5 → simview-4.0.0}/simview/static/js/objects/Terrain.js +184 -101
- simview-4.0.0/simview/static/js/objects/colormap.js +63 -0
- simview-4.0.0/simview/static/js/objects/similarity.js +38 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/objects/utils.js +25 -4
- {simview-3.5 → simview-4.0.0}/simview/static/js/ui/AnalysisPanel.js +46 -25
- {simview-3.5 → simview-4.0.0}/simview/static/js/ui/Controls.js +116 -0
- simview-4.0.0/simview/static/js/ui/Legend.js +154 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/ui/ScalarPlotter.js +6 -2
- simview-4.0.0/simview/static/js/ui/TerrainProfile.js +541 -0
- simview-4.0.0/simview/static/js/utils/terrainSample.js +108 -0
- {simview-3.5 → simview-4.0.0}/simview/terrain.py +509 -21
- {simview-3.5 → simview-4.0.0/simview.egg-info}/PKG-INFO +4 -4
- {simview-3.5 → simview-4.0.0}/simview.egg-info/SOURCES.txt +5 -0
- {simview-3.5 → simview-4.0.0}/tests/test_cli.py +351 -2
- {simview-3.5 → simview-4.0.0}/tests/test_info.py +27 -2
- simview-4.0.0/tests/test_pointcloud_features.py +135 -0
- {simview-3.5 → simview-4.0.0}/tests/test_roundtrip.py +31 -15
- {simview-3.5 → simview-4.0.0}/tests/test_scene.py +22 -5
- {simview-3.5 → simview-4.0.0}/tests/test_server.py +0 -18
- {simview-3.5 → simview-4.0.0}/tests/test_terrain_query.py +302 -9
- simview-3.5/simview/static/css/controls.css +0 -36
- simview-3.5/simview/static/js/ui/Legend.js +0 -103
- {simview-3.5 → simview-4.0.0}/LICENSE +0 -0
- {simview-3.5 → simview-4.0.0}/MANIFEST.in +0 -0
- {simview-3.5 → simview-4.0.0}/setup.cfg +0 -0
- {simview-3.5 → simview-4.0.0}/simview/__init__.py +0 -0
- {simview-3.5 → simview-4.0.0}/simview/diff.py +0 -0
- {simview-3.5 → simview-4.0.0}/simview/launcher.py +0 -0
- {simview-3.5 → simview-4.0.0}/simview/live.py +0 -0
- {simview-3.5 → simview-4.0.0}/simview/py.typed +0 -0
- {simview-3.5 → simview-4.0.0}/simview/render.py +0 -0
- {simview-3.5 → simview-4.0.0}/simview/state.py +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/components/AnimationController.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/components/InteractionControls.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/components/Scene.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/components/StateStore.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/main.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/objects/StaticObject.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/ui/BatchLegend.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/ui/BodyStateWindow.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/ui/ErrorMetrics.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/ui/PlaybackControls.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/utils/batchPresets.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/utils/blobCodec.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/utils/bodyTransforms.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/utils/csv.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/utils/errorMath.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/utils/injectStyles.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/utils/interpolate.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/utils/liveFollow.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/utils/loadRecordingLibs.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/js/utils/viewState.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/index.min.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/Color.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/chroma.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/colors/colorbrewer.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/colors/w3cx11.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/average.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/bezier.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/blend.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/cubehelix.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/mix.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/random.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/scale.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/_hsx.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hcg.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsi.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsl.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsv.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lab.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lch.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lrgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/num.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklab.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklch.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/cmyk2rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/rgb2cmyk.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/css2rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/hsl2css.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/lab2css.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/lch2css.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/oklab2css.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/oklch2css.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/rgb2css.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/gl/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/hcg2rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/rgb2hcg.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hex/hex2rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hex/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hex/rgb2hex.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/hsi2rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/rgb2hsi.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/hsl2rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/rgb2hsl.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/hsv2rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/rgb2hsv.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/input.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lab/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab-constants.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab2rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lab/rgb2lab.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/hcl2rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lab2lch.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2lab.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/rgb2lch.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/named/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/num/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/num/num2rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/num/rgb2num.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/oklab2rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/rgb2oklab.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/oklch2rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/rgb2oklch.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/rgb/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/temp/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/temp/rgb2temperature.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/temp/temperature2rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/alpha.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/clipped.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/darken.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/get.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/luminance.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/mix.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/premultiply.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/saturate.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/set.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/shade.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/analyze.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/clip_rgb.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/contrast.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/contrastAPCA.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/delta-e.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/distance.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/index.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/last.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/limit.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/multiply-matrices.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/scales.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/type.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/unpack.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/valid.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/version.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/download.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/js-colormaps.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/tar.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/three-0.174.0/three.core.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/three-0.174.0/three.module.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/uPlot.esm.js +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/lib/uPlot.min.css +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/textures/contacts/red-cross0.png +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/textures/points/ball0.png +0 -0
- {simview-3.5 → simview-4.0.0}/simview/static/textures/points/ball1.png +0 -0
- {simview-3.5 → simview-4.0.0}/simview/templates/index.html +0 -0
- {simview-3.5 → simview-4.0.0}/simview/utils.py +0 -0
- {simview-3.5 → simview-4.0.0}/simview.egg-info/dependency_links.txt +0 -0
- {simview-3.5 → simview-4.0.0}/simview.egg-info/entry_points.txt +0 -0
- {simview-3.5 → simview-4.0.0}/simview.egg-info/requires.txt +0 -0
- {simview-3.5 → simview-4.0.0}/simview.egg-info/top_level.txt +0 -0
- {simview-3.5 → simview-4.0.0}/tests/test_columnar_states.py +0 -0
- {simview-3.5 → simview-4.0.0}/tests/test_contacts.py +0 -0
- {simview-3.5 → simview-4.0.0}/tests/test_diff.py +0 -0
- {simview-3.5 → simview-4.0.0}/tests/test_launcher.py +0 -0
- {simview-3.5 → simview-4.0.0}/tests/test_live.py +0 -0
- {simview-3.5 → simview-4.0.0}/tests/test_merge.py +0 -0
- {simview-3.5 → simview-4.0.0}/tests/test_show.py +0 -0
- {simview-3.5 → simview-4.0.0}/tests/test_terrain.py +0 -0
- {simview-3.5 → simview-4.0.0}/tests/test_trajectory.py +0 -0
- {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
|
+
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
|
[](https://vlk-jan.github.io/simview/)
|
|
37
|
-
[](https://vlk-jan.github.io/
|
|
37
|
+
[](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/
|
|
54
|
-
(
|
|
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
|
[](https://vlk-jan.github.io/simview/)
|
|
4
|
-
[](https://vlk-jan.github.io/
|
|
4
|
+
[](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/
|
|
21
|
-
(
|
|
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
|
|
|
@@ -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
|
|
86
|
-
at `path`, to stdout. With `--batches
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
result = terrain_mod.
|
|
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
|
-
|
|
116
|
-
args.
|
|
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.
|
|
122
|
-
csv_text = terrain_mod.
|
|
126
|
+
text = terrain_mod.format_along_diff_text(result)
|
|
127
|
+
csv_text = terrain_mod.format_along_diff_csv(result)
|
|
123
128
|
else:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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.
|
|
129
|
-
csv_text = terrain_mod.
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
253
|
-
"an area, 'diff <file>' to compare
|
|
254
|
-
"'render <file>' to headlessly save
|
|
255
|
-
"'render' extra). Multiple
|
|
256
|
-
"one scene, each file's
|
|
257
|
-
"real-world recording
|
|
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
|
|
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',
|
|
337
|
-
"cross-batch diff mode (value_a/value_b/delta per
|
|
338
|
-
"of a single-batch query, and takes precedence
|
|
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=
|
|
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
|
-
|
|
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
|
-
"
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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/
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
|
314
|
+
height_data, normals = [], []
|
|
315
|
+
property_data: dict[str, list] = {name: [] for name in kept_properties}
|
|
313
316
|
min_z = max_z = None
|
|
314
|
-
|
|
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
|
-
|
|
339
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
if
|
|
354
|
-
else
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
"
|
|
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
|
|