simview 3.5__tar.gz → 3.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {simview-3.5/simview.egg-info → simview-3.6}/PKG-INFO +1 -1
- {simview-3.5 → simview-3.6}/pyproject.toml +1 -1
- {simview-3.5 → simview-3.6}/simview/__main__.py +131 -45
- {simview-3.5 → simview-3.6}/simview/info.py +7 -0
- {simview-3.5 → simview-3.6}/simview/model.py +47 -4
- {simview-3.5 → simview-3.6}/simview/scene.py +30 -7
- {simview-3.5 → simview-3.6}/simview/static/js/SimView.js +22 -1
- {simview-3.5 → simview-3.6}/simview/static/js/components/BatchManager.js +1 -0
- {simview-3.5 → simview-3.6}/simview/static/js/config.js +1 -0
- {simview-3.5 → simview-3.6}/simview/static/js/ui/AnalysisPanel.js +46 -25
- {simview-3.5 → simview-3.6}/simview/static/js/ui/Controls.js +15 -0
- {simview-3.5 → simview-3.6}/simview/static/js/ui/ScalarPlotter.js +6 -2
- simview-3.6/simview/static/js/ui/TerrainProfile.js +544 -0
- simview-3.6/simview/static/js/utils/terrainSample.js +92 -0
- {simview-3.5 → simview-3.6}/simview/terrain.py +492 -1
- {simview-3.5 → simview-3.6/simview.egg-info}/PKG-INFO +1 -1
- {simview-3.5 → simview-3.6}/simview.egg-info/SOURCES.txt +2 -0
- {simview-3.5 → simview-3.6}/tests/test_cli.py +355 -2
- {simview-3.5 → simview-3.6}/tests/test_info.py +26 -0
- {simview-3.5 → simview-3.6}/tests/test_roundtrip.py +13 -0
- {simview-3.5 → simview-3.6}/tests/test_scene.py +18 -0
- {simview-3.5 → simview-3.6}/tests/test_terrain_query.py +293 -0
- {simview-3.5 → simview-3.6}/LICENSE +0 -0
- {simview-3.5 → simview-3.6}/MANIFEST.in +0 -0
- {simview-3.5 → simview-3.6}/README.md +0 -0
- {simview-3.5 → simview-3.6}/setup.cfg +0 -0
- {simview-3.5 → simview-3.6}/simview/__init__.py +0 -0
- {simview-3.5 → simview-3.6}/simview/diff.py +0 -0
- {simview-3.5 → simview-3.6}/simview/launcher.py +0 -0
- {simview-3.5 → simview-3.6}/simview/live.py +0 -0
- {simview-3.5 → simview-3.6}/simview/merge.py +0 -0
- {simview-3.5 → simview-3.6}/simview/py.typed +0 -0
- {simview-3.5 → simview-3.6}/simview/render.py +0 -0
- {simview-3.5 → simview-3.6}/simview/server.py +0 -0
- {simview-3.5 → simview-3.6}/simview/state.py +0 -0
- {simview-3.5 → simview-3.6}/simview/static/css/controls.css +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/components/AnimationController.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/components/InteractionController.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/components/InteractionControls.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/components/Scene.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/components/StateStore.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/main.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/objects/Body.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/objects/StaticObject.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/objects/Terrain.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/objects/utils.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/ui/BatchLegend.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/ui/BodyStateWindow.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/ui/ErrorMetrics.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/ui/Legend.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/ui/PlaybackControls.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/utils/batchPresets.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/utils/blobCodec.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/utils/bodyTransforms.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/utils/csv.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/utils/errorMath.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/utils/injectStyles.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/utils/interpolate.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/utils/liveFollow.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/utils/loadRecordingLibs.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/js/utils/viewState.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/index.min.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/Color.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/chroma.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/colors/colorbrewer.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/colors/w3cx11.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/generator/average.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/generator/bezier.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/generator/blend.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/generator/cubehelix.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/generator/mix.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/generator/random.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/generator/scale.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/_hsx.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hcg.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsi.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsl.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/hsv.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lab.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lch.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/lrgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/num.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklab.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/oklch.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/interpolator/rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/cmyk2rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/cmyk/rgb2cmyk.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/css2rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/hsl2css.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/lab2css.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/lch2css.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/oklab2css.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/oklch2css.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/css/rgb2css.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/gl/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/hcg2rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hcg/rgb2hcg.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hex/hex2rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hex/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hex/rgb2hex.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/hsi2rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsi/rgb2hsi.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/hsl2rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsl/rgb2hsl.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/hsv2rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/hsv/rgb2hsv.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/input.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lab/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab-constants.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lab/lab2rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lab/rgb2lab.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lch/hcl2rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lch/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lab2lch.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2lab.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/lch/rgb2lch.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/named/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/num/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/num/num2rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/num/rgb2num.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/oklab2rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/oklab/rgb2oklab.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/oklch2rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/oklch/rgb2oklch.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/rgb/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/temp/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/temp/rgb2temperature.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/io/temp/temperature2rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/alpha.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/clipped.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/darken.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/get.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/luminance.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/mix.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/premultiply.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/saturate.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/set.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/ops/shade.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/analyze.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/clip_rgb.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/contrast.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/contrastAPCA.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/delta-e.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/distance.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/index.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/last.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/limit.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/multiply-matrices.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/scales.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/type.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/unpack.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/utils/valid.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/chroma-js-3.1.2/src/version.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/download.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/js-colormaps.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/tar.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/three-0.174.0/three.core.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/three-0.174.0/three.module.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/uPlot.esm.js +0 -0
- {simview-3.5 → simview-3.6}/simview/static/lib/uPlot.min.css +0 -0
- {simview-3.5 → simview-3.6}/simview/static/textures/contacts/red-cross0.png +0 -0
- {simview-3.5 → simview-3.6}/simview/static/textures/points/ball0.png +0 -0
- {simview-3.5 → simview-3.6}/simview/static/textures/points/ball1.png +0 -0
- {simview-3.5 → simview-3.6}/simview/templates/index.html +0 -0
- {simview-3.5 → simview-3.6}/simview/utils.py +0 -0
- {simview-3.5 → simview-3.6}/simview.egg-info/dependency_links.txt +0 -0
- {simview-3.5 → simview-3.6}/simview.egg-info/entry_points.txt +0 -0
- {simview-3.5 → simview-3.6}/simview.egg-info/requires.txt +0 -0
- {simview-3.5 → simview-3.6}/simview.egg-info/top_level.txt +0 -0
- {simview-3.5 → simview-3.6}/tests/test_columnar_states.py +0 -0
- {simview-3.5 → simview-3.6}/tests/test_contacts.py +0 -0
- {simview-3.5 → simview-3.6}/tests/test_diff.py +0 -0
- {simview-3.5 → simview-3.6}/tests/test_launcher.py +0 -0
- {simview-3.5 → simview-3.6}/tests/test_live.py +0 -0
- {simview-3.5 → simview-3.6}/tests/test_merge.py +0 -0
- {simview-3.5 → simview-3.6}/tests/test_server.py +0 -0
- {simview-3.5 → simview-3.6}/tests/test_show.py +0 -0
- {simview-3.5 → simview-3.6}/tests/test_terrain.py +0 -0
- {simview-3.5 → simview-3.6}/tests/test_trajectory.py +0 -0
- {simview-3.5 → simview-3.6}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simview
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.6
|
|
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
|
|
@@ -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,7 +353,23 @@ 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(
|
|
@@ -333,10 +403,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
333
403
|
metavar=("A", "B"),
|
|
334
404
|
help=(
|
|
335
405
|
"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."
|
|
406
|
+
"With 'simview terrain <file> --point|--area|--along-body', "
|
|
407
|
+
"switches into cross-batch diff mode (value_a/value_b/delta per "
|
|
408
|
+
"layer) instead of a single-batch query, and takes precedence "
|
|
409
|
+
"over --batch if both are given. For --along-body, both batches' "
|
|
410
|
+
"terrains are sampled along batch A's trajectory."
|
|
340
411
|
),
|
|
341
412
|
)
|
|
342
413
|
parser.add_argument(
|
|
@@ -355,7 +426,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
355
426
|
"--every",
|
|
356
427
|
type=int,
|
|
357
428
|
default=1,
|
|
358
|
-
help=
|
|
429
|
+
help=(
|
|
430
|
+
"With 'simview diff <file>' or 'simview terrain <file> "
|
|
431
|
+
"--along-body', sample every Nth frame (default: 1)."
|
|
432
|
+
),
|
|
359
433
|
)
|
|
360
434
|
parser.add_argument(
|
|
361
435
|
"--pos-threshold",
|
|
@@ -386,6 +460,18 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
386
460
|
"the browser Error Metrics panel's per-axis toggle."
|
|
387
461
|
),
|
|
388
462
|
)
|
|
463
|
+
parser.add_argument(
|
|
464
|
+
"--fail-on-exceed",
|
|
465
|
+
action="store_true",
|
|
466
|
+
help=(
|
|
467
|
+
"With 'simview diff <file>', exit with code 2 (after printing the "
|
|
468
|
+
"normal output) if any diffed body's trajectory exceeds "
|
|
469
|
+
"--pos-threshold or --rot-threshold-deg; requires at least one of "
|
|
470
|
+
"them. Exit codes: 0 = within thresholds, 1 = usage/parse error, "
|
|
471
|
+
"2 = threshold exceeded -- lets scripts/CI tell divergence apart "
|
|
472
|
+
"from a broken invocation."
|
|
473
|
+
),
|
|
474
|
+
)
|
|
389
475
|
parser.add_argument(
|
|
390
476
|
"--host",
|
|
391
477
|
type=str,
|
|
@@ -142,6 +142,7 @@ def _summarize_model(model: dict, warnings: list[str]) -> dict:
|
|
|
142
142
|
"scalar_names": model.get("scalarNames") or [],
|
|
143
143
|
"dt": model.get("dt"),
|
|
144
144
|
"collapse": model.get("collapse"),
|
|
145
|
+
"metadata": model.get("metadata"),
|
|
145
146
|
"terrain": _summarize_terrain(terrain) if terrain is not None else None,
|
|
146
147
|
"bodies": {
|
|
147
148
|
"count": len(bodies),
|
|
@@ -445,6 +446,12 @@ def format_text(summary: dict) -> str:
|
|
|
445
446
|
lines.append(f" Scalars: {', '.join(model['scalar_names']) or '(none)'}")
|
|
446
447
|
lines.append(f" dt: {model['dt']} Collapse: {model['collapse']}")
|
|
447
448
|
|
|
449
|
+
metadata = model["metadata"]
|
|
450
|
+
if metadata:
|
|
451
|
+
lines.append("\nMetadata")
|
|
452
|
+
for key, value in metadata.items():
|
|
453
|
+
lines.append(f" {key}: {value}")
|
|
454
|
+
|
|
448
455
|
terrain = model["terrain"]
|
|
449
456
|
if terrain is None:
|
|
450
457
|
lines.append("\nTerrain: (none)")
|
|
@@ -2,7 +2,7 @@ import base64
|
|
|
2
2
|
import logging
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
from enum import StrEnum
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any, cast
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
import torch
|
|
@@ -488,6 +488,10 @@ class SimViewModel:
|
|
|
488
488
|
bodies: dict[str, SimViewBody] = field(default_factory=dict)
|
|
489
489
|
static_objects: dict[str, SimViewStaticObject] = field(default_factory=dict)
|
|
490
490
|
batch_names: list[str] | None = None
|
|
491
|
+
# Free-form, JSON-serializable run provenance (engine name, checkpoint path,
|
|
492
|
+
# git commit, CLI args, ...) with no meaning to the viewer itself -- just
|
|
493
|
+
# carried through so a scene saved months ago is still self-describing.
|
|
494
|
+
metadata: dict[str, Any] | None = None
|
|
491
495
|
|
|
492
496
|
def __post_init__(self):
|
|
493
497
|
if self.batch_names is not None and len(self.batch_names) != self.batch_size:
|
|
@@ -524,14 +528,50 @@ class SimViewModel:
|
|
|
524
528
|
def create_terrain(
|
|
525
529
|
self,
|
|
526
530
|
heightmap: torch.Tensor,
|
|
527
|
-
normals: torch.Tensor,
|
|
528
|
-
x_lim: tuple[float, float],
|
|
529
|
-
y_lim: tuple[float, float],
|
|
531
|
+
normals: torch.Tensor | None = None,
|
|
532
|
+
x_lim: tuple[float, float] | None = None,
|
|
533
|
+
y_lim: tuple[float, float] | None = None,
|
|
534
|
+
grid_res: float | None = None,
|
|
530
535
|
friction_map: torch.Tensor | None = None,
|
|
531
536
|
stiffness_map: torch.Tensor | None = None,
|
|
532
537
|
) -> None:
|
|
538
|
+
"""Adds terrain to the internal simulation model.
|
|
539
|
+
|
|
540
|
+
Args:
|
|
541
|
+
heightmap (torch.Tensor): 2D or 3D tensor of terrain heights.
|
|
542
|
+
normals (torch.Tensor | None): 3D or 4D tensor of terrain normals. If None,
|
|
543
|
+
normals are automatically computed from the heightmap gradients.
|
|
544
|
+
x_lim (tuple[float, float] | None): (min, max) coordinates for the X axis.
|
|
545
|
+
y_lim (tuple[float, float] | None): (min, max) coordinates for the Y axis.
|
|
546
|
+
grid_res (float | None): Grid resolution. If x_lim and y_lim are omitted,
|
|
547
|
+
they will be automatically inferred assuming the grid is centered at 0.
|
|
548
|
+
friction_map (torch.Tensor | None): Optional friction coefficient map.
|
|
549
|
+
stiffness_map (torch.Tensor | None): Optional stiffness coefficient map.
|
|
550
|
+
"""
|
|
533
551
|
if heightmap.ndim == 2:
|
|
534
552
|
heightmap = heightmap.unsqueeze(0) # add batch dim
|
|
553
|
+
|
|
554
|
+
if x_lim is None or y_lim is None:
|
|
555
|
+
if grid_res is None:
|
|
556
|
+
raise ValueError("Must provide either (x_lim, y_lim) or grid_res")
|
|
557
|
+
H_dim, W_dim = heightmap.shape[-2:]
|
|
558
|
+
x_lim = x_lim or (-W_dim * grid_res / 2.0, W_dim * grid_res / 2.0)
|
|
559
|
+
y_lim = y_lim or (-H_dim * grid_res / 2.0, H_dim * grid_res / 2.0)
|
|
560
|
+
|
|
561
|
+
if normals is None:
|
|
562
|
+
H_dim, W_dim = heightmap.shape[-2:]
|
|
563
|
+
res_x = (x_lim[1] - x_lim[0]) / W_dim
|
|
564
|
+
res_y = (y_lim[1] - y_lim[0]) / H_dim
|
|
565
|
+
dzdy, dzdx = torch.gradient(heightmap, spacing=(res_y, res_x), dim=(-2, -1))
|
|
566
|
+
nx = -dzdx
|
|
567
|
+
ny = -dzdy
|
|
568
|
+
nz = torch.ones_like(nx)
|
|
569
|
+
computed_normals = torch.stack([nx, ny, nz], dim=-3)
|
|
570
|
+
computed_normals = computed_normals / torch.linalg.norm(
|
|
571
|
+
computed_normals, dim=-3, keepdim=True
|
|
572
|
+
)
|
|
573
|
+
normals = cast(torch.Tensor, computed_normals.to(dtype=heightmap.dtype))
|
|
574
|
+
|
|
535
575
|
if normals.ndim == 3: # channels first
|
|
536
576
|
normals = normals.unsqueeze(0) # add batch dim
|
|
537
577
|
if friction_map is not None and friction_map.ndim == 2:
|
|
@@ -640,6 +680,8 @@ class SimViewModel:
|
|
|
640
680
|
}
|
|
641
681
|
if self.batch_names is not None:
|
|
642
682
|
r["batchNames"] = self.batch_names
|
|
683
|
+
if self.metadata is not None:
|
|
684
|
+
r["metadata"] = self.metadata
|
|
643
685
|
return r
|
|
644
686
|
|
|
645
687
|
@classmethod
|
|
@@ -683,6 +725,7 @@ class SimViewModel:
|
|
|
683
725
|
bodies=bodies,
|
|
684
726
|
static_objects=static_objects,
|
|
685
727
|
batch_names=d.get("batchNames"),
|
|
728
|
+
metadata=d.get("metadata"),
|
|
686
729
|
)
|
|
687
730
|
|
|
688
731
|
@property
|
|
@@ -165,10 +165,17 @@ class SimulationScene:
|
|
|
165
165
|
bodies: dict[str, SimViewBody] | None = None,
|
|
166
166
|
static_objects: dict[str, SimViewStaticObject] | None = None,
|
|
167
167
|
batch_names: list[str] | None = None,
|
|
168
|
+
metadata: dict[str, Any] | None = None,
|
|
168
169
|
) -> None:
|
|
169
170
|
"""
|
|
170
171
|
Initializes the simulation data container.
|
|
171
172
|
Manages the SimViewModel and the time-series states.
|
|
173
|
+
|
|
174
|
+
`metadata` is free-form, JSON-serializable run provenance (e.g. engine
|
|
175
|
+
name, checkpoint path, git commit, CLI args) with no meaning to the
|
|
176
|
+
viewer -- it's carried through to `simview info` and the browser so a
|
|
177
|
+
saved scene stays self-describing. Can also be set/updated later via
|
|
178
|
+
`self.model.metadata`.
|
|
172
179
|
"""
|
|
173
180
|
self.model = SimViewModel(
|
|
174
181
|
batch_size=batch_size,
|
|
@@ -179,6 +186,7 @@ class SimulationScene:
|
|
|
179
186
|
bodies=bodies if bodies is not None else {},
|
|
180
187
|
static_objects=static_objects if static_objects is not None else {},
|
|
181
188
|
batch_names=batch_names,
|
|
189
|
+
metadata=metadata,
|
|
182
190
|
)
|
|
183
191
|
self.states: list[dict] = []
|
|
184
192
|
|
|
@@ -208,6 +216,7 @@ class SimulationScene:
|
|
|
208
216
|
bodies=model.bodies,
|
|
209
217
|
static_objects=model.static_objects,
|
|
210
218
|
batch_names=model.batch_names,
|
|
219
|
+
metadata=model.metadata,
|
|
211
220
|
)
|
|
212
221
|
scene.states = list(states)
|
|
213
222
|
return scene
|
|
@@ -515,18 +524,32 @@ class SimulationScene:
|
|
|
515
524
|
def create_terrain(
|
|
516
525
|
self,
|
|
517
526
|
heightmap: torch.Tensor,
|
|
518
|
-
normals: torch.Tensor,
|
|
519
|
-
x_lim: tuple[float, float],
|
|
520
|
-
y_lim: tuple[float, float],
|
|
527
|
+
normals: torch.Tensor | None = None,
|
|
528
|
+
x_lim: tuple[float, float] | None = None,
|
|
529
|
+
y_lim: tuple[float, float] | None = None,
|
|
530
|
+
grid_res: float | None = None,
|
|
521
531
|
friction_map: torch.Tensor | None = None,
|
|
522
532
|
stiffness_map: torch.Tensor | None = None,
|
|
523
533
|
) -> None:
|
|
524
|
-
"""Adds terrain to the simulation model.
|
|
534
|
+
"""Adds terrain to the simulation model.
|
|
535
|
+
|
|
536
|
+
Args:
|
|
537
|
+
heightmap (torch.Tensor): 2D or 3D tensor of terrain heights.
|
|
538
|
+
normals (torch.Tensor | None): 3D or 4D tensor of terrain normals. If None,
|
|
539
|
+
normals are automatically computed from the heightmap gradients.
|
|
540
|
+
x_lim (tuple[float, float] | None): (min, max) coordinates for the X axis.
|
|
541
|
+
y_lim (tuple[float, float] | None): (min, max) coordinates for the Y axis.
|
|
542
|
+
grid_res (float | None): Grid resolution. If x_lim and y_lim are omitted,
|
|
543
|
+
they will be automatically inferred assuming the grid is centered at 0.
|
|
544
|
+
friction_map (torch.Tensor | None): Optional friction coefficient map.
|
|
545
|
+
stiffness_map (torch.Tensor | None): Optional stiffness coefficient map.
|
|
546
|
+
"""
|
|
525
547
|
self.model.create_terrain(
|
|
526
548
|
heightmap,
|
|
527
|
-
normals,
|
|
528
|
-
x_lim,
|
|
529
|
-
y_lim,
|
|
549
|
+
normals=normals,
|
|
550
|
+
x_lim=x_lim,
|
|
551
|
+
y_lim=y_lim,
|
|
552
|
+
grid_res=grid_res,
|
|
530
553
|
friction_map=friction_map,
|
|
531
554
|
stiffness_map=stiffness_map,
|
|
532
555
|
)
|
|
@@ -12,6 +12,7 @@ import { StaticObject } from "./objects/StaticObject.js";
|
|
|
12
12
|
import { Legend } from "./ui/Legend.js";
|
|
13
13
|
import { BatchLegend } from "./ui/BatchLegend.js";
|
|
14
14
|
import { ErrorMetrics } from "./ui/ErrorMetrics.js";
|
|
15
|
+
import { TerrainProfile } from "./ui/TerrainProfile.js";
|
|
15
16
|
import { AnalysisPanel } from "./ui/AnalysisPanel.js";
|
|
16
17
|
import { InteractionController } from "./components/InteractionController.js";
|
|
17
18
|
import { buildBodyMeta, resolveStateBodies, topoSortBodies } from "./utils/bodyTransforms.js";
|
|
@@ -33,6 +34,7 @@ export class SimView {
|
|
|
33
34
|
this.animationController = null;
|
|
34
35
|
this.scalarPlotter = null;
|
|
35
36
|
this.errorMetrics = null;
|
|
37
|
+
this.terrainProfile = null;
|
|
36
38
|
this.analysisPanel = null;
|
|
37
39
|
this.legend = null;
|
|
38
40
|
this.batchLegend = null;
|
|
@@ -325,6 +327,11 @@ export class SimView {
|
|
|
325
327
|
try {
|
|
326
328
|
this.disposeOfAll();
|
|
327
329
|
|
|
330
|
+
// Free-form run provenance carried through from the Python side
|
|
331
|
+
// (see SimViewModel.metadata) -- no meaning to the viewer itself,
|
|
332
|
+
// just displayed read-only in the "Scene Info" GUI folder.
|
|
333
|
+
this.metadata = model.metadata ?? null;
|
|
334
|
+
|
|
328
335
|
this.batchManager = new BatchManager(this, model);
|
|
329
336
|
this.bodies = new Map();
|
|
330
337
|
|
|
@@ -367,7 +374,10 @@ export class SimView {
|
|
|
367
374
|
}
|
|
368
375
|
const hasScalars = model.scalarNames && model.scalarNames.length > 0;
|
|
369
376
|
const hasErrorMetrics = this.batchManager.simBatches >= 2;
|
|
370
|
-
|
|
377
|
+
// Terrain tab needs an actual body path to sample against, so it's
|
|
378
|
+
// skipped for a scene with terrain but no bodies.
|
|
379
|
+
const hasTerrainProfile = !!this.terrain && this.bodies && this.bodies.size > 0;
|
|
380
|
+
if (hasScalars || hasErrorMetrics || hasTerrainProfile) {
|
|
371
381
|
this.analysisPanel = new AnalysisPanel(this);
|
|
372
382
|
}
|
|
373
383
|
if (hasScalars) {
|
|
@@ -382,6 +392,10 @@ export class SimView {
|
|
|
382
392
|
this.errorMetrics = new ErrorMetrics(this);
|
|
383
393
|
this.analysisPanel.attachErrorMetrics(this.errorMetrics);
|
|
384
394
|
}
|
|
395
|
+
if (hasTerrainProfile) {
|
|
396
|
+
this.terrainProfile = new TerrainProfile(this);
|
|
397
|
+
this.analysisPanel.attachTerrainProfile(this.terrainProfile);
|
|
398
|
+
}
|
|
385
399
|
this.interactionController = new InteractionController(this);
|
|
386
400
|
this.uiControls = new UIControls(this);
|
|
387
401
|
this.bodyStateWindow = new BodyStateWindow(this);
|
|
@@ -509,6 +523,10 @@ export class SimView {
|
|
|
509
523
|
this.errorMetrics.dispose();
|
|
510
524
|
this.errorMetrics = null;
|
|
511
525
|
}
|
|
526
|
+
if (this.terrainProfile) {
|
|
527
|
+
this.terrainProfile.dispose();
|
|
528
|
+
this.terrainProfile = null;
|
|
529
|
+
}
|
|
512
530
|
if (this.analysisPanel) {
|
|
513
531
|
this.analysisPanel.dispose();
|
|
514
532
|
this.analysisPanel = null;
|
|
@@ -548,6 +566,9 @@ export class SimView {
|
|
|
548
566
|
if (this.errorMetrics) {
|
|
549
567
|
this.errorMetrics.animate(now);
|
|
550
568
|
}
|
|
569
|
+
if (this.terrainProfile) {
|
|
570
|
+
this.terrainProfile.animate(now);
|
|
571
|
+
}
|
|
551
572
|
|
|
552
573
|
// 3. Render the scene
|
|
553
574
|
if (this.scene) {
|
|
@@ -79,6 +79,7 @@ export class BatchManager {
|
|
|
79
79
|
this._persistBatchNames();
|
|
80
80
|
if (this.app.batchLegend) this.app.batchLegend.refresh();
|
|
81
81
|
if (this.app.errorMetrics) this.app.errorMetrics.refreshBatchLabels();
|
|
82
|
+
if (this.app.terrainProfile) this.app.terrainProfile.refreshBatchLabels();
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
async _persistBatchNames() {
|
|
@@ -11,6 +11,7 @@ export class AnalysisPanel {
|
|
|
11
11
|
this.mode = "scalars";
|
|
12
12
|
this.scalarPlotter = null;
|
|
13
13
|
this.errorMetrics = null;
|
|
14
|
+
this.terrainProfile = null;
|
|
14
15
|
this.modeTabElements = {};
|
|
15
16
|
|
|
16
17
|
this._injectStyles();
|
|
@@ -132,15 +133,19 @@ export class AnalysisPanel {
|
|
|
132
133
|
this.modeTabBar.className = "analysis-mode-tab-bar";
|
|
133
134
|
this.modeTabElements.scalars = this._addModeTab("Scalars", "scalars");
|
|
134
135
|
this.modeTabElements.errorMetrics = this._addModeTab("Error Metrics", "errorMetrics");
|
|
136
|
+
this.modeTabElements.terrain = this._addModeTab("Terrain", "terrain");
|
|
135
137
|
|
|
136
138
|
this.scalarsSection = document.createElement("div");
|
|
137
139
|
this.scalarsSection.className = "analysis-section";
|
|
138
140
|
this.errorMetricsSection = document.createElement("div");
|
|
139
141
|
this.errorMetricsSection.className = "analysis-section";
|
|
142
|
+
this.terrainSection = document.createElement("div");
|
|
143
|
+
this.terrainSection.className = "analysis-section";
|
|
140
144
|
|
|
141
145
|
this.content.appendChild(this.modeTabBar);
|
|
142
146
|
this.content.appendChild(this.scalarsSection);
|
|
143
147
|
this.content.appendChild(this.errorMetricsSection);
|
|
148
|
+
this.content.appendChild(this.terrainSection);
|
|
144
149
|
this.container.appendChild(this.header);
|
|
145
150
|
this.container.appendChild(this.content);
|
|
146
151
|
document.body.appendChild(this.container);
|
|
@@ -168,20 +173,45 @@ export class AnalysisPanel {
|
|
|
168
173
|
this._refreshLayout();
|
|
169
174
|
}
|
|
170
175
|
|
|
176
|
+
attachTerrainProfile(terrainProfile) {
|
|
177
|
+
this.terrainProfile = terrainProfile;
|
|
178
|
+
this.terrainSection.appendChild(terrainProfile.content);
|
|
179
|
+
this._refreshLayout();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Sections currently attached, in tab order -- drives the tab bar
|
|
183
|
+
// (shown whenever >= 2 are attached, with individual tabs for any
|
|
184
|
+
// sections not attached hidden), the title fallback (the lone
|
|
185
|
+
// section's own title when only one is attached), and which mode a
|
|
186
|
+
// stale `this.mode` falls back to if its section gets detached.
|
|
187
|
+
_sections() {
|
|
188
|
+
return [
|
|
189
|
+
{ key: "scalars", instance: this.scalarPlotter, title: "Scalars" },
|
|
190
|
+
{ key: "errorMetrics", instance: this.errorMetrics, title: "Error Metrics" },
|
|
191
|
+
{ key: "terrain", instance: this.terrainProfile, title: "Terrain" },
|
|
192
|
+
];
|
|
193
|
+
}
|
|
194
|
+
|
|
171
195
|
_refreshLayout() {
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
const showModeBar =
|
|
196
|
+
const sections = this._sections();
|
|
197
|
+
const attached = sections.filter((s) => !!s.instance);
|
|
198
|
+
const showModeBar = attached.length >= 2;
|
|
175
199
|
this.modeTabBar.classList.toggle("visible", showModeBar);
|
|
176
200
|
|
|
177
|
-
|
|
178
|
-
|
|
201
|
+
for (const section of sections) {
|
|
202
|
+
const tab = this.modeTabElements[section.key];
|
|
203
|
+
if (tab) tab.style.display = section.instance ? "" : "none";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!attached.some((s) => s.key === this.mode)) {
|
|
207
|
+
this.mode = attached[0] ? attached[0].key : this.mode;
|
|
208
|
+
}
|
|
179
209
|
|
|
180
210
|
this.titleEl.textContent = showModeBar
|
|
181
211
|
? "Analysis"
|
|
182
|
-
:
|
|
183
|
-
?
|
|
184
|
-
: "
|
|
212
|
+
: attached[0]
|
|
213
|
+
? attached[0].title
|
|
214
|
+
: "Analysis";
|
|
185
215
|
|
|
186
216
|
this._applyMode();
|
|
187
217
|
}
|
|
@@ -192,23 +222,14 @@ export class AnalysisPanel {
|
|
|
192
222
|
"visible",
|
|
193
223
|
this.mode === "errorMetrics"
|
|
194
224
|
);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
"active",
|
|
204
|
-
this.mode === "errorMetrics"
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
if (this.scalarPlotter) {
|
|
208
|
-
this.scalarPlotter.setVisible(this.isExpanded && this.mode === "scalars");
|
|
209
|
-
}
|
|
210
|
-
if (this.errorMetrics) {
|
|
211
|
-
this.errorMetrics.setVisible(this.isExpanded && this.mode === "errorMetrics");
|
|
225
|
+
this.terrainSection.classList.toggle("visible", this.mode === "terrain");
|
|
226
|
+
|
|
227
|
+
for (const section of this._sections()) {
|
|
228
|
+
const tab = this.modeTabElements[section.key];
|
|
229
|
+
if (tab) tab.classList.toggle("active", this.mode === section.key);
|
|
230
|
+
if (section.instance) {
|
|
231
|
+
section.instance.setVisible(this.isExpanded && this.mode === section.key);
|
|
232
|
+
}
|
|
212
233
|
}
|
|
213
234
|
}
|
|
214
235
|
|