simview 3.6__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.6/simview.egg-info → simview-4.0.0}/PKG-INFO +4 -4
- {simview-3.6 → simview-4.0.0}/README.md +3 -3
- {simview-3.6 → simview-4.0.0}/pyproject.toml +1 -1
- {simview-3.6 → simview-4.0.0}/simview/__main__.py +4 -3
- {simview-3.6 → simview-4.0.0}/simview/info.py +15 -23
- {simview-3.6 → simview-4.0.0}/simview/merge.py +51 -79
- {simview-3.6 → simview-4.0.0}/simview/model.py +138 -81
- {simview-3.6 → simview-4.0.0}/simview/scene.py +40 -8
- {simview-3.6 → simview-4.0.0}/simview/server.py +4 -8
- simview-4.0.0/simview/static/css/controls.css +104 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/SimView.js +56 -11
- {simview-3.6 → simview-4.0.0}/simview/static/js/components/BatchManager.js +5 -6
- {simview-3.6 → simview-4.0.0}/simview/static/js/components/InteractionController.js +123 -32
- {simview-3.6 → simview-4.0.0}/simview/static/js/config.js +9 -1
- {simview-3.6 → simview-4.0.0}/simview/static/js/objects/Body.js +98 -1
- {simview-3.6 → 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.6 → simview-4.0.0}/simview/static/js/objects/utils.js +25 -4
- {simview-3.6 → simview-4.0.0}/simview/static/js/ui/Controls.js +110 -9
- simview-4.0.0/simview/static/js/ui/Legend.js +154 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/ui/TerrainProfile.js +49 -52
- {simview-3.6 → simview-4.0.0}/simview/static/js/utils/terrainSample.js +16 -0
- {simview-3.6 → simview-4.0.0}/simview/terrain.py +20 -23
- {simview-3.6 → simview-4.0.0/simview.egg-info}/PKG-INFO +4 -4
- {simview-3.6 → simview-4.0.0}/simview.egg-info/SOURCES.txt +3 -0
- {simview-3.6 → simview-4.0.0}/tests/test_cli.py +0 -4
- {simview-3.6 → simview-4.0.0}/tests/test_info.py +1 -2
- simview-4.0.0/tests/test_pointcloud_features.py +135 -0
- {simview-3.6 → simview-4.0.0}/tests/test_roundtrip.py +18 -15
- {simview-3.6 → simview-4.0.0}/tests/test_scene.py +4 -5
- {simview-3.6 → simview-4.0.0}/tests/test_server.py +0 -18
- {simview-3.6 → simview-4.0.0}/tests/test_terrain_query.py +11 -11
- simview-3.6/simview/static/css/controls.css +0 -36
- simview-3.6/simview/static/js/ui/Legend.js +0 -103
- {simview-3.6 → simview-4.0.0}/LICENSE +0 -0
- {simview-3.6 → simview-4.0.0}/MANIFEST.in +0 -0
- {simview-3.6 → simview-4.0.0}/setup.cfg +0 -0
- {simview-3.6 → simview-4.0.0}/simview/__init__.py +0 -0
- {simview-3.6 → simview-4.0.0}/simview/diff.py +0 -0
- {simview-3.6 → simview-4.0.0}/simview/launcher.py +0 -0
- {simview-3.6 → simview-4.0.0}/simview/live.py +0 -0
- {simview-3.6 → simview-4.0.0}/simview/py.typed +0 -0
- {simview-3.6 → simview-4.0.0}/simview/render.py +0 -0
- {simview-3.6 → simview-4.0.0}/simview/state.py +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/components/AnimationController.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/components/InteractionControls.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/components/Scene.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/components/StateStore.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/main.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/objects/StaticObject.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/ui/AnalysisPanel.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/ui/BatchLegend.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/ui/BodyStateWindow.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/ui/ErrorMetrics.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/ui/PlaybackControls.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/ui/ScalarPlotter.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/utils/batchPresets.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/utils/blobCodec.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/utils/bodyTransforms.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/utils/csv.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/utils/errorMath.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/utils/injectStyles.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/utils/interpolate.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/utils/liveFollow.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/utils/loadRecordingLibs.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/js/utils/viewState.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/index.min.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/Color.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/chroma.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/colors/colorbrewer.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/colors/w3cx11.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/average.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/bezier.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/blend.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/cubehelix.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/mix.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/random.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/generator/scale.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/_hsx.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hcg.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsi.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsl.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsv.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lab.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lch.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lrgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/num.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklab.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklch.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/interpolator/rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/cmyk2rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/rgb2cmyk.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/css2rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/hsl2css.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/lab2css.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/lch2css.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/oklab2css.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/oklch2css.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/css/rgb2css.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/gl/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/hcg2rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/rgb2hcg.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hex/hex2rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hex/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hex/rgb2hex.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/hsi2rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/rgb2hsi.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/hsl2rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/rgb2hsl.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/hsv2rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/rgb2hsv.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/input.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lab/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab-constants.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab2rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lab/rgb2lab.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/hcl2rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lab2lch.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2lab.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/lch/rgb2lch.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/named/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/num/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/num/num2rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/num/rgb2num.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/oklab2rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/rgb2oklab.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/oklch2rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/rgb2oklch.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/rgb/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/temp/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/temp/rgb2temperature.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/io/temp/temperature2rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/alpha.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/clipped.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/darken.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/get.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/luminance.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/mix.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/premultiply.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/saturate.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/set.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/ops/shade.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/analyze.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/clip_rgb.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/contrast.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/contrastAPCA.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/delta-e.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/distance.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/index.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/last.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/limit.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/multiply-matrices.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/scales.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/type.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/unpack.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/utils/valid.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/chroma-js-3.1.2/src/version.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/download.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/js-colormaps.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/tar.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/three-0.174.0/three.core.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/three-0.174.0/three.module.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/uPlot.esm.js +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/lib/uPlot.min.css +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/textures/contacts/red-cross0.png +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/textures/points/ball0.png +0 -0
- {simview-3.6 → simview-4.0.0}/simview/static/textures/points/ball1.png +0 -0
- {simview-3.6 → simview-4.0.0}/simview/templates/index.html +0 -0
- {simview-3.6 → simview-4.0.0}/simview/utils.py +0 -0
- {simview-3.6 → simview-4.0.0}/simview.egg-info/dependency_links.txt +0 -0
- {simview-3.6 → simview-4.0.0}/simview.egg-info/entry_points.txt +0 -0
- {simview-3.6 → simview-4.0.0}/simview.egg-info/requires.txt +0 -0
- {simview-3.6 → simview-4.0.0}/simview.egg-info/top_level.txt +0 -0
- {simview-3.6 → simview-4.0.0}/tests/test_columnar_states.py +0 -0
- {simview-3.6 → simview-4.0.0}/tests/test_contacts.py +0 -0
- {simview-3.6 → simview-4.0.0}/tests/test_diff.py +0 -0
- {simview-3.6 → simview-4.0.0}/tests/test_launcher.py +0 -0
- {simview-3.6 → simview-4.0.0}/tests/test_live.py +0 -0
- {simview-3.6 → simview-4.0.0}/tests/test_merge.py +0 -0
- {simview-3.6 → simview-4.0.0}/tests/test_show.py +0 -0
- {simview-3.6 → simview-4.0.0}/tests/test_terrain.py +0 -0
- {simview-3.6 → simview-4.0.0}/tests/test_trajectory.py +0 -0
- {simview-3.6 → 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
|
|
|
@@ -374,11 +374,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
374
374
|
)
|
|
375
375
|
parser.add_argument(
|
|
376
376
|
"--layer",
|
|
377
|
-
choices=["height", "friction", "stiffness", "all"],
|
|
378
377
|
default="all",
|
|
379
378
|
help=(
|
|
380
|
-
"With 'simview terrain <file>', which terrain layer
|
|
381
|
-
"(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)."
|
|
382
383
|
),
|
|
383
384
|
)
|
|
384
385
|
parser.add_argument(
|
|
@@ -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
|
|
|
@@ -466,9 +455,12 @@ def format_text(summary: dict) -> str:
|
|
|
466
455
|
f" Bounds: x=[{b['min_x']},{b['max_x']}] "
|
|
467
456
|
f"y=[{b['min_y']},{b['max_y']}] z=[{b['min_z']},{b['max_z']}]"
|
|
468
457
|
)
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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)")
|
|
472
464
|
h_bytes = terrain["height_data_bytes"]
|
|
473
465
|
n_bytes = terrain["normals_bytes"]
|
|
474
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
|
|
|
@@ -49,6 +49,27 @@ class OptionalBodyStateAttribute(StrEnum):
|
|
|
49
49
|
TORQUE = "torque"
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
@dataclass
|
|
53
|
+
class TerrainProperty:
|
|
54
|
+
"""One arbitrary named per-cell scalar field over the terrain grid (e.g.
|
|
55
|
+
friction, stiffness, or any other user-defined property), stored the same
|
|
56
|
+
way as `SimViewTerrain.height_data` -- a plain nested list, or an opaque
|
|
57
|
+
`__b64__`-prefixed blob string for compactness."""
|
|
58
|
+
|
|
59
|
+
data: list[list[float]] | str
|
|
60
|
+
# Value range used by the viewer to normalize the color map (analogous to
|
|
61
|
+
# min_z/max_z for height). None when not computed.
|
|
62
|
+
min: float | None = None
|
|
63
|
+
max: float | None = None
|
|
64
|
+
|
|
65
|
+
def to_json(self):
|
|
66
|
+
return {"data": self.data, "min": self.min, "max": self.max}
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def from_dict(cls, d: dict) -> "TerrainProperty":
|
|
70
|
+
return cls(data=d["data"], min=d.get("min"), max=d.get("max"))
|
|
71
|
+
|
|
72
|
+
|
|
52
73
|
@dataclass
|
|
53
74
|
class SimViewTerrain:
|
|
54
75
|
extent_x: float
|
|
@@ -68,33 +89,22 @@ class SimViewTerrain:
|
|
|
68
89
|
height_data: list[list[float]] | str
|
|
69
90
|
normals: list[list[list[float]]] | str
|
|
70
91
|
is_singleton: bool
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
92
|
+
# Arbitrary named per-cell scalar fields (e.g. "friction", "stiffness", or
|
|
93
|
+
# any other user-defined property), keyed by name -- see `TerrainProperty`.
|
|
94
|
+
# Adding a new property never requires touching this class or the viewer:
|
|
95
|
+
# supply it by name in `create()`'s `properties` dict and it becomes
|
|
96
|
+
# selectable as a terrain color mode automatically.
|
|
97
|
+
properties: dict[str, TerrainProperty] = field(default_factory=dict)
|
|
98
|
+
# Per-cell K-wide feature vector (e.g. a reduced-dim PCA projection of a
|
|
99
|
+
# learned backbone's features), stored the same way as `normals` (width
|
|
100
|
+
# inferred client-side from flat length, not shipped explicitly). Used by
|
|
101
|
+
# the viewer's "features" color mode: cosine similarity to a clicked cell,
|
|
102
|
+
# computed entirely in-browser -- not a named scalar property, so it has
|
|
103
|
+
# no min/max bounds (similarity is always [-1, 1]) and isn't part of
|
|
104
|
+
# `properties`.
|
|
105
|
+
embedding_data: list[list[float]] | str | None = None
|
|
79
106
|
|
|
80
107
|
def to_json(self):
|
|
81
|
-
# min/max friction/stiffness are independent Optional fields (not tied to
|
|
82
|
-
# friction_data/stiffness_data by the type system, only by convention in
|
|
83
|
-
# create()/from_dict), so the dict's values are genuinely `float | None`.
|
|
84
|
-
bounds: dict[str, float | None] = {
|
|
85
|
-
"minX": self.min_x,
|
|
86
|
-
"minY": self.min_y,
|
|
87
|
-
"maxX": self.max_x,
|
|
88
|
-
"maxY": self.max_y,
|
|
89
|
-
"minZ": self.min_z,
|
|
90
|
-
"maxZ": self.max_z,
|
|
91
|
-
}
|
|
92
|
-
if self.friction_data is not None:
|
|
93
|
-
bounds["minFriction"] = self.min_friction
|
|
94
|
-
bounds["maxFriction"] = self.max_friction
|
|
95
|
-
if self.stiffness_data is not None:
|
|
96
|
-
bounds["minStiffness"] = self.min_stiffness
|
|
97
|
-
bounds["maxStiffness"] = self.max_stiffness
|
|
98
108
|
return {
|
|
99
109
|
"dimensions": {
|
|
100
110
|
"sizeX": self.extent_x,
|
|
@@ -102,22 +112,31 @@ class SimViewTerrain:
|
|
|
102
112
|
"resolutionX": self.shape_x,
|
|
103
113
|
"resolutionY": self.shape_y,
|
|
104
114
|
},
|
|
105
|
-
"bounds":
|
|
115
|
+
"bounds": {
|
|
116
|
+
"minX": self.min_x,
|
|
117
|
+
"minY": self.min_y,
|
|
118
|
+
"maxX": self.max_x,
|
|
119
|
+
"maxY": self.max_y,
|
|
120
|
+
"minZ": self.min_z,
|
|
121
|
+
"maxZ": self.max_z,
|
|
122
|
+
},
|
|
106
123
|
"heightData": self.height_data,
|
|
107
124
|
"normals": self.normals,
|
|
108
125
|
"isSingleton": self.is_singleton,
|
|
109
|
-
"
|
|
110
|
-
|
|
126
|
+
"properties": {
|
|
127
|
+
name: prop.to_json() for name, prop in self.properties.items()
|
|
128
|
+
},
|
|
129
|
+
"embeddingData": self.embedding_data,
|
|
111
130
|
}
|
|
112
131
|
|
|
113
132
|
@classmethod
|
|
114
133
|
def from_dict(cls, d: dict) -> "SimViewTerrain":
|
|
115
134
|
"""Reconstruct a SimViewTerrain from the dict produced by `to_json`.
|
|
116
135
|
|
|
117
|
-
`heightData`/`normals
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
136
|
+
`heightData`/`normals`/each property's `data` are kept in whatever
|
|
137
|
+
form they were serialized in (plain nested lists or a `__b64__` blob
|
|
138
|
+
string) -- decode with `simview.model._decode_blob` if you need the
|
|
139
|
+
flat float values back out.
|
|
121
140
|
"""
|
|
122
141
|
try:
|
|
123
142
|
dimensions = d["dimensions"]
|
|
@@ -128,6 +147,11 @@ class SimViewTerrain:
|
|
|
128
147
|
except KeyError as e:
|
|
129
148
|
raise ValueError(f"Terrain dict is missing required key: {e}") from e
|
|
130
149
|
|
|
150
|
+
properties = {
|
|
151
|
+
name: TerrainProperty.from_dict(p)
|
|
152
|
+
for name, p in (d.get("properties") or {}).items()
|
|
153
|
+
}
|
|
154
|
+
|
|
131
155
|
return cls(
|
|
132
156
|
extent_x=dimensions["sizeX"],
|
|
133
157
|
extent_y=dimensions["sizeY"],
|
|
@@ -142,12 +166,8 @@ class SimViewTerrain:
|
|
|
142
166
|
height_data=height_data,
|
|
143
167
|
normals=normals,
|
|
144
168
|
is_singleton=is_singleton,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
min_friction=bounds.get("minFriction"),
|
|
148
|
-
max_friction=bounds.get("maxFriction"),
|
|
149
|
-
min_stiffness=bounds.get("minStiffness"),
|
|
150
|
-
max_stiffness=bounds.get("maxStiffness"),
|
|
169
|
+
properties=properties,
|
|
170
|
+
embedding_data=d.get("embeddingData"),
|
|
151
171
|
)
|
|
152
172
|
|
|
153
173
|
@staticmethod
|
|
@@ -157,8 +177,8 @@ class SimViewTerrain:
|
|
|
157
177
|
x_lim: tuple[float, float],
|
|
158
178
|
y_lim: tuple[float, float],
|
|
159
179
|
is_singleton: bool,
|
|
160
|
-
|
|
161
|
-
|
|
180
|
+
properties: dict[str, torch.Tensor] | None = None,
|
|
181
|
+
embedding_map: torch.Tensor | None = None,
|
|
162
182
|
) -> "SimViewTerrain":
|
|
163
183
|
if heightmap.ndim != 3:
|
|
164
184
|
raise ValueError(
|
|
@@ -186,31 +206,30 @@ class SimViewTerrain:
|
|
|
186
206
|
rearrange(normals, "b c d1 d2 -> b (d1 d2) c").cpu().numpy()
|
|
187
207
|
)
|
|
188
208
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if friction_map.ndim != 3:
|
|
209
|
+
properties_out: dict[str, TerrainProperty] = {}
|
|
210
|
+
for name, prop_map in (properties or {}).items():
|
|
211
|
+
if prop_map.ndim != 3:
|
|
193
212
|
raise ValueError(
|
|
194
|
-
f"
|
|
213
|
+
f"Property '{name}' map must include a batch dimension "
|
|
214
|
+
f"(ndim=3); got ndim={prop_map.ndim}."
|
|
195
215
|
)
|
|
196
|
-
|
|
197
|
-
|
|
216
|
+
properties_out[name] = TerrainProperty(
|
|
217
|
+
data=_encode_blob(
|
|
218
|
+
rearrange(prop_map, "b d1 d2 -> b (d1 d2)").cpu().numpy()
|
|
219
|
+
),
|
|
220
|
+
min=prop_map.min().item(),
|
|
221
|
+
max=prop_map.max().item(),
|
|
198
222
|
)
|
|
199
|
-
min_friction = friction_map.min().item()
|
|
200
|
-
max_friction = friction_map.max().item()
|
|
201
223
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if stiffness_map.ndim != 3:
|
|
224
|
+
embedding_data_list = None
|
|
225
|
+
if embedding_map is not None:
|
|
226
|
+
if embedding_map.ndim != 4:
|
|
206
227
|
raise ValueError(
|
|
207
|
-
f"
|
|
228
|
+
f"Embedding map must include a batch dimension (ndim=4); got ndim={embedding_map.ndim}."
|
|
208
229
|
)
|
|
209
|
-
|
|
210
|
-
rearrange(
|
|
230
|
+
embedding_data_list = _encode_blob(
|
|
231
|
+
rearrange(embedding_map, "b k d1 d2 -> b (d1 d2) k").cpu().numpy()
|
|
211
232
|
)
|
|
212
|
-
min_stiffness = stiffness_map.min().item()
|
|
213
|
-
max_stiffness = stiffness_map.max().item()
|
|
214
233
|
|
|
215
234
|
return SimViewTerrain(
|
|
216
235
|
extent_x=extent_x,
|
|
@@ -226,12 +245,8 @@ class SimViewTerrain:
|
|
|
226
245
|
height_data=height_data_list,
|
|
227
246
|
normals=normals_list,
|
|
228
247
|
is_singleton=is_singleton,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
min_friction=min_friction,
|
|
232
|
-
max_friction=max_friction,
|
|
233
|
-
min_stiffness=min_stiffness,
|
|
234
|
-
max_stiffness=max_stiffness,
|
|
248
|
+
properties=properties_out,
|
|
249
|
+
embedding_data=embedding_data_list,
|
|
235
250
|
)
|
|
236
251
|
|
|
237
252
|
|
|
@@ -335,7 +350,37 @@ class SimViewBody:
|
|
|
335
350
|
)
|
|
336
351
|
|
|
337
352
|
@staticmethod
|
|
338
|
-
def create_pointcloud(
|
|
353
|
+
def create_pointcloud(
|
|
354
|
+
name: str,
|
|
355
|
+
points: torch.Tensor,
|
|
356
|
+
color: torch.Tensor | None = None,
|
|
357
|
+
embedding: torch.Tensor | None = None,
|
|
358
|
+
**kwargs,
|
|
359
|
+
) -> "SimViewBody":
|
|
360
|
+
"""`color` (N, 3) in [0, 1] is an optional static per-point RGB color,
|
|
361
|
+
used by the viewer for vertex-colored rendering. `embedding` (N, K) is
|
|
362
|
+
an optional per-point K-wide feature vector (e.g. a reduced-dim PCA
|
|
363
|
+
projection of a learned backbone's features) enabling the viewer's
|
|
364
|
+
click-to-similarity color mode: cosine similarity to a clicked point,
|
|
365
|
+
computed entirely in-browser from this data. Both round-trip through
|
|
366
|
+
the existing generic `__b64__` blob mechanism -- no new wire format."""
|
|
367
|
+
if points.ndim != 2 or points.shape[1] != 3:
|
|
368
|
+
raise ValueError(
|
|
369
|
+
f"points must have shape (N, 3); got {tuple(points.shape)}."
|
|
370
|
+
)
|
|
371
|
+
N = points.shape[0]
|
|
372
|
+
if color is not None:
|
|
373
|
+
if tuple(color.shape) != (N, 3):
|
|
374
|
+
raise ValueError(
|
|
375
|
+
f"color must have shape ({N}, 3) matching points; got {tuple(color.shape)}."
|
|
376
|
+
)
|
|
377
|
+
kwargs["color"] = color
|
|
378
|
+
if embedding is not None:
|
|
379
|
+
if embedding.ndim != 2 or embedding.shape[0] != N:
|
|
380
|
+
raise ValueError(
|
|
381
|
+
f"embedding must have shape ({N}, K) matching points; got {tuple(embedding.shape)}."
|
|
382
|
+
)
|
|
383
|
+
kwargs["embedding"] = embedding
|
|
339
384
|
return SimViewBody.create(
|
|
340
385
|
name, BodyShapeType.POINTCLOUD, points=points, **kwargs
|
|
341
386
|
)
|
|
@@ -532,8 +577,8 @@ class SimViewModel:
|
|
|
532
577
|
x_lim: tuple[float, float] | None = None,
|
|
533
578
|
y_lim: tuple[float, float] | None = None,
|
|
534
579
|
grid_res: float | None = None,
|
|
535
|
-
|
|
536
|
-
|
|
580
|
+
properties: dict[str, torch.Tensor] | None = None,
|
|
581
|
+
embedding_map: torch.Tensor | None = None,
|
|
537
582
|
) -> None:
|
|
538
583
|
"""Adds terrain to the internal simulation model.
|
|
539
584
|
|
|
@@ -545,8 +590,14 @@ class SimViewModel:
|
|
|
545
590
|
y_lim (tuple[float, float] | None): (min, max) coordinates for the Y axis.
|
|
546
591
|
grid_res (float | None): Grid resolution. If x_lim and y_lim are omitted,
|
|
547
592
|
they will be automatically inferred assuming the grid is centered at 0.
|
|
548
|
-
|
|
549
|
-
|
|
593
|
+
properties (dict[str, torch.Tensor] | None): Optional arbitrary named
|
|
594
|
+
per-cell scalar maps (2D or 3D, like `heightmap`), e.g.
|
|
595
|
+
`{"friction": friction_map, "stiffness": stiffness_map}`. Each becomes
|
|
596
|
+
selectable as a terrain color mode in the viewer automatically, with no
|
|
597
|
+
further code changes needed.
|
|
598
|
+
embedding_map (torch.Tensor | None): Optional per-cell K-wide feature map
|
|
599
|
+
(3D channels-first `(K, Dy, Dx)` or 4D `(B, K, Dy, Dx)`, like `normals`)
|
|
600
|
+
enabling the viewer's click-to-similarity "features" color mode.
|
|
550
601
|
"""
|
|
551
602
|
if heightmap.ndim == 2:
|
|
552
603
|
heightmap = heightmap.unsqueeze(0) # add batch dim
|
|
@@ -574,10 +625,12 @@ class SimViewModel:
|
|
|
574
625
|
|
|
575
626
|
if normals.ndim == 3: # channels first
|
|
576
627
|
normals = normals.unsqueeze(0) # add batch dim
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
628
|
+
properties = {
|
|
629
|
+
name: (prop.unsqueeze(0) if prop.ndim == 2 else prop)
|
|
630
|
+
for name, prop in (properties or {}).items()
|
|
631
|
+
}
|
|
632
|
+
if embedding_map is not None and embedding_map.ndim == 3: # channels first
|
|
633
|
+
embedding_map = embedding_map.unsqueeze(0)
|
|
581
634
|
|
|
582
635
|
# Each field's batch dim must be either 1 (shared across all batches) or
|
|
583
636
|
# exactly batch_size (per-batch). Anything else is a mistake, and the old
|
|
@@ -586,8 +639,8 @@ class SimViewModel:
|
|
|
586
639
|
provided = {
|
|
587
640
|
"heightmap": heightmap,
|
|
588
641
|
"normals": normals,
|
|
589
|
-
"
|
|
590
|
-
|
|
642
|
+
"embedding_map": embedding_map,
|
|
643
|
+
**properties,
|
|
591
644
|
}
|
|
592
645
|
for name, tensor in provided.items():
|
|
593
646
|
if tensor is not None and tensor.shape[0] not in (1, self.batch_size):
|
|
@@ -609,10 +662,14 @@ class SimViewModel:
|
|
|
609
662
|
heightmap = heightmap.repeat(self.batch_size, 1, 1)
|
|
610
663
|
if normals.shape[0] == 1:
|
|
611
664
|
normals = normals.repeat(self.batch_size, 1, 1, 1)
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
665
|
+
properties = {
|
|
666
|
+
name: (
|
|
667
|
+
prop.repeat(self.batch_size, 1, 1) if prop.shape[0] == 1 else prop
|
|
668
|
+
)
|
|
669
|
+
for name, prop in properties.items()
|
|
670
|
+
}
|
|
671
|
+
if embedding_map is not None and embedding_map.shape[0] == 1:
|
|
672
|
+
embedding_map = embedding_map.repeat(self.batch_size, 1, 1, 1)
|
|
616
673
|
|
|
617
674
|
self.terrain = SimViewTerrain.create(
|
|
618
675
|
heightmap=heightmap,
|
|
@@ -620,8 +677,8 @@ class SimViewModel:
|
|
|
620
677
|
x_lim=x_lim,
|
|
621
678
|
y_lim=y_lim,
|
|
622
679
|
is_singleton=is_singleton,
|
|
623
|
-
|
|
624
|
-
|
|
680
|
+
properties=properties,
|
|
681
|
+
embedding_map=embedding_map,
|
|
625
682
|
)
|
|
626
683
|
|
|
627
684
|
def create_body(
|