simview 6.2.2__tar.gz → 6.3.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-6.2.2/simview.egg-info → simview-6.3.0}/PKG-INFO +3 -3
- {simview-6.2.2 → simview-6.3.0}/README.md +2 -2
- {simview-6.2.2 → simview-6.3.0}/pyproject.toml +1 -1
- {simview-6.2.2 → simview-6.3.0}/simview/__main__.py +21 -6
- {simview-6.2.2 → simview-6.3.0}/simview/columnar.py +5 -4
- {simview-6.2.2 → simview-6.3.0}/simview/diff.py +10 -7
- {simview-6.2.2 → simview-6.3.0}/simview/info.py +17 -12
- {simview-6.2.2 → simview-6.3.0}/simview/launcher.py +14 -14
- {simview-6.2.2 → simview-6.3.0}/simview/live.py +3 -0
- {simview-6.2.2 → simview-6.3.0}/simview/merge.py +18 -8
- {simview-6.2.2 → simview-6.3.0}/simview/model.py +72 -35
- {simview-6.2.2 → simview-6.3.0}/simview/remote.py +1 -1
- {simview-6.2.2 → simview-6.3.0}/simview/render.py +6 -4
- {simview-6.2.2 → simview-6.3.0}/simview/scene.py +114 -112
- {simview-6.2.2 → simview-6.3.0}/simview/server.py +216 -34
- {simview-6.2.2 → simview-6.3.0}/simview/state.py +30 -24
- {simview-6.2.2 → simview-6.3.0}/simview/static/css/controls.css +84 -107
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/SimView.js +66 -74
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/components/AnimationController.js +8 -15
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/components/BatchManager.js +6 -8
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/components/InteractionController.js +3 -10
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/components/Scene.js +6 -6
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/components/StateStore.js +6 -11
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/components/WindowedField.js +2 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/config.js +2 -1
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/objects/Body.js +12 -12
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/objects/StaticObject.js +2 -8
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/objects/Terrain.js +11 -20
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/objects/colormap.js +9 -16
- simview-6.3.0/simview/static/js/objects/similarity.js +25 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/objects/utils.js +15 -45
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/ui/BodyStateWindow.js +20 -31
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/ui/Controls.js +27 -32
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/ui/ErrorMetrics.js +4 -3
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/ui/Legend.js +7 -5
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/ui/PlaybackControls.js +4 -3
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/ui/ScalarPlotter.js +13 -8
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/ui/TerrainProfile.js +14 -5
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/batchVisibility.js +1 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/blobCodec.js +5 -5
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/blobWindow.js +9 -2
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/bodyTransforms.js +3 -4
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/cameraRange.js +6 -3
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/csv.js +3 -2
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/errorMath.js +2 -5
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/keyboard.js +3 -2
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/terrainSample.js +3 -7
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/uplot.js +4 -5
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/viewState.js +21 -0
- {simview-6.2.2/simview/static/lib → simview-6.3.0/simview/static/js/vendor}/js-colormaps.js +25 -3
- {simview-6.2.2 → simview-6.3.0}/simview/templates/index.html +16 -12
- {simview-6.2.2 → simview-6.3.0}/simview/terrain.py +13 -13
- {simview-6.2.2 → simview-6.3.0}/simview/view.py +9 -6
- {simview-6.2.2 → simview-6.3.0/simview.egg-info}/PKG-INFO +3 -3
- {simview-6.2.2 → simview-6.3.0}/simview.egg-info/SOURCES.txt +3 -3
- {simview-6.2.2 → simview-6.3.0}/tests/test_cli.py +6 -2
- {simview-6.2.2 → simview-6.3.0}/tests/test_columnar_states.py +4 -3
- {simview-6.2.2 → simview-6.3.0}/tests/test_diff.py +1 -1
- {simview-6.2.2 → simview-6.3.0}/tests/test_info.py +1 -1
- {simview-6.2.2 → simview-6.3.0}/tests/test_launcher.py +1 -1
- {simview-6.2.2 → simview-6.3.0}/tests/test_live.py +61 -1
- {simview-6.2.2 → simview-6.3.0}/tests/test_roundtrip.py +1 -1
- {simview-6.2.2 → simview-6.3.0}/tests/test_scene.py +4 -4
- {simview-6.2.2 → simview-6.3.0}/tests/test_server.py +46 -4
- {simview-6.2.2 → simview-6.3.0}/tests/test_show.py +20 -0
- {simview-6.2.2 → simview-6.3.0}/tests/test_terrain_query.py +1 -1
- {simview-6.2.2 → simview-6.3.0}/tests/test_utils.py +1 -1
- simview-6.2.2/simview/static/js/objects/similarity.js +0 -38
- {simview-6.2.2 → simview-6.3.0}/LICENSE +0 -0
- {simview-6.2.2 → simview-6.3.0}/MANIFEST.in +0 -0
- {simview-6.2.2 → simview-6.3.0}/setup.cfg +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/__init__.py +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/py.typed +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/main.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/objects/Polyline.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/ui/AnalysisPanel.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/ui/BatchLegend.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/ui/chartControls.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/batchPresets.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/episodes.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/html.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/interpolate.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/liveFollow.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/scalarSeries.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/viewerDefaults.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/js/utils/visibleRanges.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/lib/three-0.174.0/three.core.js +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/lib/three-0.174.0/three.module.js +0 -0
- {simview-6.2.2/simview/static/lib → simview-6.3.0/simview/static/lib/uPlot-1.6.32}/uPlot.esm.js +0 -0
- {simview-6.2.2/simview/static/lib → simview-6.3.0/simview/static/lib/uPlot-1.6.32}/uPlot.min.css +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/textures/contacts/red-cross0.png +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/static/textures/points/ball1.png +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview/utils.py +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview.egg-info/dependency_links.txt +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview.egg-info/entry_points.txt +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview.egg-info/requires.txt +0 -0
- {simview-6.2.2 → simview-6.3.0}/simview.egg-info/top_level.txt +0 -0
- {simview-6.2.2 → simview-6.3.0}/tests/test_batch_selection.py +0 -0
- {simview-6.2.2 → simview-6.3.0}/tests/test_columnar_ondisk.py +0 -0
- {simview-6.2.2 → simview-6.3.0}/tests/test_contacts.py +0 -0
- {simview-6.2.2 → simview-6.3.0}/tests/test_embed_defaults.py +0 -0
- {simview-6.2.2 → simview-6.3.0}/tests/test_episodes.py +0 -0
- {simview-6.2.2 → simview-6.3.0}/tests/test_merge.py +0 -0
- {simview-6.2.2 → simview-6.3.0}/tests/test_pointcloud_features.py +0 -0
- {simview-6.2.2 → simview-6.3.0}/tests/test_remote.py +0 -0
- {simview-6.2.2 → simview-6.3.0}/tests/test_terrain.py +0 -0
- {simview-6.2.2 → simview-6.3.0}/tests/test_trajectory.py +0 -0
- {simview-6.2.2 → simview-6.3.0}/tests/test_view.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simview
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.3.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
|
|
@@ -130,8 +130,8 @@ source .venv/bin/activate
|
|
|
130
130
|
|
|
131
131
|
SimView is distributed under the [BSD 3-Clause License](LICENSE). The web interface
|
|
132
132
|
vendors [uPlot](https://github.com/leeoniya/uPlot) and [three.js](https://github.com/mrdoob/three.js)
|
|
133
|
-
(both MIT licensed) under
|
|
134
|
-
`simview/static/
|
|
133
|
+
(both MIT licensed) under `simview/static/lib/`, and the matplotlib colour-map tables
|
|
134
|
+
(`js-colormaps.js`) under `simview/static/js/vendor/`, so the viewer works fully offline — see the
|
|
135
135
|
[documentation](https://vlk-jan.github.io/simview/dev/architecture/#vendored-third-party-libraries)
|
|
136
136
|
for details. All third-party libraries used by SimView are permissively licensed
|
|
137
137
|
(MIT/BSD), so there are no licensing restrictions on commercial use.
|
|
@@ -99,8 +99,8 @@ source .venv/bin/activate
|
|
|
99
99
|
|
|
100
100
|
SimView is distributed under the [BSD 3-Clause License](LICENSE). The web interface
|
|
101
101
|
vendors [uPlot](https://github.com/leeoniya/uPlot) and [three.js](https://github.com/mrdoob/three.js)
|
|
102
|
-
(both MIT licensed) under
|
|
103
|
-
`simview/static/
|
|
102
|
+
(both MIT licensed) under `simview/static/lib/`, and the matplotlib colour-map tables
|
|
103
|
+
(`js-colormaps.js`) under `simview/static/js/vendor/`, so the viewer works fully offline — see the
|
|
104
104
|
[documentation](https://vlk-jan.github.io/simview/dev/architecture/#vendored-third-party-libraries)
|
|
105
105
|
for details. All third-party libraries used by SimView are permissively licensed
|
|
106
106
|
(MIT/BSD), so there are no licensing restrictions on commercial use.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""The `simview` command-line interface."""
|
|
2
|
+
|
|
1
3
|
import argparse
|
|
2
4
|
import gzip
|
|
3
5
|
import json
|
|
@@ -295,9 +297,18 @@ def save_merged(
|
|
|
295
297
|
logger.error("Error: --save-merged requires at least 2 input files to merge.")
|
|
296
298
|
sys.exit(1)
|
|
297
299
|
|
|
300
|
+
from simview.columnar import columnarize_states, inline_blob
|
|
298
301
|
from simview.merge import merge_simulation_files
|
|
299
302
|
|
|
300
303
|
merged = merge_simulation_files(paths, selections)
|
|
304
|
+
# Pack the merged states the same way SimulationScene.save does: merge
|
|
305
|
+
# decodes every blob to plain lists, so dumping it directly would write
|
|
306
|
+
# the fat legacy per-frame layout. Falls back silently when numpy is
|
|
307
|
+
# unavailable or the states are too irregular (columnarize_states
|
|
308
|
+
# returns None).
|
|
309
|
+
columnar_states = columnarize_states(merged["states"], merged["model"], inline_blob)
|
|
310
|
+
if columnar_states is not None:
|
|
311
|
+
merged["states"] = columnar_states
|
|
301
312
|
payload = json.dumps(merged).encode("utf-8")
|
|
302
313
|
if out_path.suffix == ".gz":
|
|
303
314
|
payload = gzip.compress(payload, compresslevel=1)
|
|
@@ -370,7 +381,12 @@ def _add_host_port_flags(sp: argparse.ArgumentParser) -> None:
|
|
|
370
381
|
"--host",
|
|
371
382
|
type=str,
|
|
372
383
|
default="127.0.0.1",
|
|
373
|
-
help=
|
|
384
|
+
help=(
|
|
385
|
+
"Host/interface for the server to bind to (default: 127.0.0.1). "
|
|
386
|
+
"Note: the server has no authentication, so binding to a "
|
|
387
|
+
"non-loopback address exposes the scene data to everyone who can "
|
|
388
|
+
"reach it."
|
|
389
|
+
),
|
|
374
390
|
)
|
|
375
391
|
sp.add_argument(
|
|
376
392
|
"--port",
|
|
@@ -436,8 +452,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
436
452
|
metavar="PATH",
|
|
437
453
|
help=(
|
|
438
454
|
"Merge the given input files and write the result to PATH instead of "
|
|
439
|
-
"launching the viewer. Requires at least 2 input files
|
|
440
|
-
"output if PATH ends in .gz."
|
|
455
|
+
"launching the viewer. Requires at least 2 input files, or 1 with a "
|
|
456
|
+
"'#<batches>' selection. Gzips the output if PATH ends in .gz."
|
|
441
457
|
),
|
|
442
458
|
)
|
|
443
459
|
_add_remote_flags(view)
|
|
@@ -654,9 +670,8 @@ def main():
|
|
|
654
670
|
_configure_logging()
|
|
655
671
|
argv = sys.argv[1:]
|
|
656
672
|
|
|
657
|
-
# Checked
|
|
658
|
-
#
|
|
659
|
-
# before any other validation ran.
|
|
673
|
+
# Checked before parsing so --version works alongside any other
|
|
674
|
+
# arguments, without tripping their validation.
|
|
660
675
|
if "--version" in argv:
|
|
661
676
|
print(_package_version())
|
|
662
677
|
return
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
covering a whole trajectory, instead of thousands of small per-frame objects.
|
|
3
3
|
|
|
4
4
|
This is both a **wire** format (served by `simview/server.py`, consumed by
|
|
5
|
-
`static/js/components/StateStore.js`) and
|
|
5
|
+
`static/js/components/StateStore.js`) and an
|
|
6
6
|
**on-disk** format (`SimulationScene.save(columnar=True)`). The two are the
|
|
7
7
|
same document; only how a blob is referenced differs:
|
|
8
8
|
|
|
@@ -15,7 +15,7 @@ That symmetry is the whole point: a columnar file needs no repacking at all
|
|
|
15
15
|
to be served, and `columnar.rewrite_blobs` (shared by `server.py`) does the
|
|
16
16
|
rewriting for free.
|
|
17
17
|
|
|
18
|
-
Shape (see
|
|
18
|
+
Shape (see docs/dev/json-format.md for the authoritative spec)::
|
|
19
19
|
|
|
20
20
|
{"version": 4,
|
|
21
21
|
"times": [t0, t1, ...], # length T
|
|
@@ -171,7 +171,8 @@ def _decode_state_field_rows(value, width: int, batch_size: int):
|
|
|
171
171
|
Mirrors the shapes SimViewBodyState.to_json()/add_trajectory produce: a
|
|
172
172
|
``__b64__`` blob is always batch_size rows of `width` floats; a plain list
|
|
173
173
|
is either already nested (one row per batch) or, for a single-batch scene,
|
|
174
|
-
a flat list of `width` floats (see
|
|
174
|
+
a flat list of `width` floats (see docs/dev/json-format.md "Authoring whole
|
|
175
|
+
trajectories").
|
|
175
176
|
"""
|
|
176
177
|
assert np is not None
|
|
177
178
|
if isinstance(value, str):
|
|
@@ -221,7 +222,7 @@ def columnarize_states(states_data: list, model_data: dict | None, register_blob
|
|
|
221
222
|
Strict by design: this trades a bit of coverage (an inconsistent scene just
|
|
222
223
|
doesn't get the win) for never risking a subtly wrong repack. The one
|
|
223
224
|
deliberate exception is `contacts`, which may legitimately come and go per
|
|
224
|
-
frame (see
|
|
225
|
+
frame (see docs/dev/json-format.md) without disqualifying the rest of the scene.
|
|
225
226
|
"""
|
|
226
227
|
if np is None or not states_data:
|
|
227
228
|
return None
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
ground truth vs. prediction, baseline vs. post-adaptation) actually track
|
|
4
4
|
each other.
|
|
5
5
|
|
|
6
|
-
Deliberately dependency-free (stdlib only
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
Deliberately dependency-free (stdlib only) so it works on a base install
|
|
7
|
+
without the `authoring` extra (torch/numpy) -- see "Lazy imports" in
|
|
8
|
+
docs/dev/architecture.md. Shares its blob-decoding and
|
|
9
9
|
body-resolution helpers with `simview/terrain.py`/`simview/info.py` via
|
|
10
10
|
`simview.columnar`/`simview.utils`, which are stdlib-only on this read path.
|
|
11
11
|
|
|
@@ -36,8 +36,8 @@ _MAX_SERIES_ROWS = 10
|
|
|
36
36
|
def _build_body_meta(model_data: dict, state_names: list) -> dict[str, dict]:
|
|
37
37
|
"""`name -> {"parent", "localTransform"}` for every body, from the model's
|
|
38
38
|
`bodies` section. Bodies that appear in the states but not in the model are
|
|
39
|
-
added as parentless roots, so a hand-edited or third-party file
|
|
40
|
-
|
|
39
|
+
added as parentless roots, so a hand-edited or third-party file whose
|
|
40
|
+
model omits them still diffs."""
|
|
41
41
|
meta: dict[str, dict] = {}
|
|
42
42
|
for entry in model_data.get("bodies") or []:
|
|
43
43
|
name = entry.get("name")
|
|
@@ -54,8 +54,8 @@ def _build_body_meta(model_data: dict, state_names: list) -> dict[str, dict]:
|
|
|
54
54
|
|
|
55
55
|
def _topo_sort_bodies(meta: dict[str, dict]) -> list[str]:
|
|
56
56
|
"""Order body names so every parent precedes its children. Independent
|
|
57
|
-
stdlib port of `static/js/utils/bodyTransforms.js`'s `topoSortBodies` (
|
|
58
|
-
|
|
57
|
+
stdlib port of `static/js/utils/bodyTransforms.js`'s `topoSortBodies` (the
|
|
58
|
+
viewer's JS can't be shared with the CLI); each body has at most one
|
|
59
59
|
parent, so a DFS post-order walk suffices."""
|
|
60
60
|
order: list[str] = []
|
|
61
61
|
status: dict[str, str] = {}
|
|
@@ -374,6 +374,9 @@ def compute_trajectory_diff(
|
|
|
374
374
|
|
|
375
375
|
|
|
376
376
|
def format_diff_text(result: dict) -> str:
|
|
377
|
+
"""Render `compute_trajectory_diff`'s output as a skimmable text report,
|
|
378
|
+
capping each body's per-frame table (use `--json`/`--csv` for the full
|
|
379
|
+
series)."""
|
|
377
380
|
lines = [
|
|
378
381
|
f"Batches {result['batch_a']} vs {result['batch_b']} every={result['every']}"
|
|
379
382
|
]
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
"""Structural summary of a scene JSON file, for `simview info` and for coding
|
|
2
2
|
agents inspecting simview-generated data files.
|
|
3
3
|
|
|
4
|
-
Deliberately dependency-free (stdlib only: json, gzip
|
|
5
|
-
`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
reach a numpy-gated helper -- both wrong layering for a lightweight
|
|
10
|
-
inspection tool. Shares its blob-decoding/body-label helpers with
|
|
4
|
+
Deliberately dependency-free (stdlib only: json, gzip, base64) so it works on a base
|
|
5
|
+
install without the `authoring` extra (torch/numpy) -- see "Lazy imports" in
|
|
6
|
+
docs/dev/architecture.md. Does not import `simview.model`/`simview.server`:
|
|
7
|
+
the former pulls in torch at module scope, the latter fastapi/uvicorn --
|
|
8
|
+
both wrong layering for a lightweight inspection tool. Shares its blob-decoding/body-label helpers with
|
|
11
9
|
`simview/terrain.py`/`simview/diff.py` via `simview.columnar`/`simview.utils`,
|
|
12
10
|
which are stdlib-only on this read path.
|
|
13
11
|
"""
|
|
14
12
|
|
|
13
|
+
import gzip
|
|
15
14
|
import json
|
|
16
15
|
from pathlib import Path
|
|
17
16
|
from typing import Any
|
|
@@ -24,7 +23,7 @@ from simview.columnar import (
|
|
|
24
23
|
is_blob,
|
|
25
24
|
is_columnar,
|
|
26
25
|
)
|
|
27
|
-
from simview.utils import body_label, cap, human_bytes
|
|
26
|
+
from simview.utils import body_label, cap, human_bytes
|
|
28
27
|
|
|
29
28
|
_MAX_DETAIL_ITEMS = 20
|
|
30
29
|
_LARGE_FILE_BYTES = 100_000_000
|
|
@@ -219,9 +218,12 @@ def _summarize_states(states, model: dict | None, warnings: list[str]) -> dict:
|
|
|
219
218
|
|
|
220
219
|
scalars_summary = {}
|
|
221
220
|
for name in scalar_names:
|
|
222
|
-
|
|
221
|
+
# One pass per scalar: collect missing-frame indices and check
|
|
222
|
+
# lengths in the same walk.
|
|
223
|
+
missing = []
|
|
223
224
|
for i, s in enumerate(states):
|
|
224
225
|
if name not in s:
|
|
226
|
+
missing.append(i)
|
|
225
227
|
continue
|
|
226
228
|
value = s[name]
|
|
227
229
|
length = len(value) if isinstance(value, list) else 1
|
|
@@ -241,8 +243,8 @@ def _summarize_states(states, model: dict | None, warnings: list[str]) -> dict:
|
|
|
241
243
|
|
|
242
244
|
# Per-body walk, mirroring columnar.py's columnarize_states structural
|
|
243
245
|
# checks (field-set consistency, first-appearance-at-frame-0, no
|
|
244
|
-
# dropping out)
|
|
245
|
-
#
|
|
246
|
+
# dropping out) without numpy: blobs are decoded only to count their
|
|
247
|
+
# floats, never shaped into arrays.
|
|
246
248
|
body_order: list = []
|
|
247
249
|
body_first_frame: dict = {}
|
|
248
250
|
body_name_value: dict = {}
|
|
@@ -396,7 +398,10 @@ def summarize_scene(path: str | Path) -> dict:
|
|
|
396
398
|
path = Path(path)
|
|
397
399
|
raw = path.read_bytes()
|
|
398
400
|
gzipped = raw[:2] == b"\x1f\x8b"
|
|
399
|
-
|
|
401
|
+
# Decompress from the bytes already in hand rather than calling
|
|
402
|
+
# read_maybe_gzipped_bytes(path), which would read the whole file a
|
|
403
|
+
# second time -- scene files are often hundreds of MB.
|
|
404
|
+
data = json.loads(gzip.decompress(raw) if gzipped else raw)
|
|
400
405
|
if not isinstance(data, dict):
|
|
401
406
|
raise ValueError(
|
|
402
407
|
"scene file must contain a JSON object with 'model'/'states' keys"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Blocking launch of the viewer for a scene object or a scene file."""
|
|
2
|
+
|
|
1
3
|
import gc
|
|
2
4
|
import logging
|
|
3
5
|
from pathlib import Path
|
|
@@ -11,15 +13,19 @@ logger = logging.getLogger("simview.launcher")
|
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
class SimViewLauncher:
|
|
16
|
+
"""Serve a scene in the foreground until interrupted, then free it.
|
|
17
|
+
|
|
18
|
+
The blocking counterpart to `SimulationScene.show()`'s background viewer.
|
|
19
|
+
"""
|
|
20
|
+
|
|
14
21
|
def __init__(
|
|
15
22
|
self,
|
|
16
23
|
source: SimulationScene | str | Path,
|
|
17
24
|
) -> None:
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- If source is a path: it is used directly.
|
|
25
|
+
"""Validate `source`, a `SimulationScene` or a path to a scene file.
|
|
26
|
+
|
|
27
|
+
A scene is handed to the server in memory (no temporary file) and its
|
|
28
|
+
internal data is cleared once the server stops; a path is served as is.
|
|
23
29
|
"""
|
|
24
30
|
self._scene: SimulationScene | None = None
|
|
25
31
|
self._sim_file_path: Path | None = None
|
|
@@ -47,9 +53,7 @@ class SimViewLauncher:
|
|
|
47
53
|
)
|
|
48
54
|
|
|
49
55
|
def launch(self, host: str = "127.0.0.1", preferred_port: int = 5420) -> None:
|
|
50
|
-
"""
|
|
51
|
-
Launches the SimViewServer, then clears the in-memory scene (if any).
|
|
52
|
-
"""
|
|
56
|
+
"""Run the server until it stops, then clear the in-memory scene (if any)."""
|
|
53
57
|
try:
|
|
54
58
|
if self._scene is not None:
|
|
55
59
|
# Serialize the scene once and hand it to the server directly,
|
|
@@ -62,9 +66,7 @@ class SimViewLauncher:
|
|
|
62
66
|
server = SimViewServer(data=data)
|
|
63
67
|
server.run(host=host, port=find_free_port(host, preferred_port))
|
|
64
68
|
else:
|
|
65
|
-
# __init__
|
|
66
|
-
# is set (it raises otherwise), so if self._scene is None here,
|
|
67
|
-
# self._sim_file_path must have been set.
|
|
69
|
+
# __init__ sets exactly one of _scene / _sim_file_path.
|
|
68
70
|
assert self._sim_file_path is not None
|
|
69
71
|
SimViewServer.start(
|
|
70
72
|
sim_path=self._sim_file_path,
|
|
@@ -80,9 +82,7 @@ class SimViewLauncher:
|
|
|
80
82
|
self.cleanup()
|
|
81
83
|
|
|
82
84
|
def cleanup(self) -> None:
|
|
83
|
-
"""
|
|
84
|
-
Clears the in-memory scene's data. Safe to call multiple times.
|
|
85
|
-
"""
|
|
85
|
+
"""Clear the in-memory scene's data. Safe to call multiple times."""
|
|
86
86
|
if self._scene is not None:
|
|
87
87
|
logger.info(
|
|
88
88
|
"SimViewLauncher: Clearing source SimulationScene internal data..."
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Stream a running simulation to the browser viewer over WebSocket."""
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import logging
|
|
3
5
|
import queue
|
|
@@ -89,6 +91,7 @@ class LiveViewer:
|
|
|
89
91
|
|
|
90
92
|
@property
|
|
91
93
|
def url(self) -> str:
|
|
94
|
+
"""Base URL of the live viewer."""
|
|
92
95
|
return self._threaded.url
|
|
93
96
|
|
|
94
97
|
def push_state(self, time, body_states, scalar_values=None) -> None:
|
|
@@ -313,11 +313,14 @@ def _validate_doc(doc: dict, label: str) -> None:
|
|
|
313
313
|
def _expand_batched(
|
|
314
314
|
values: list | str, is_singleton: bool, batch_size: int, field: str, label: str
|
|
315
315
|
) -> list | str:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
316
|
+
"""Broadcast a plain-list terrain field to `batch_size` rows.
|
|
317
|
+
|
|
318
|
+
``__b64__`` blobs pass through untouched: `_decode_per_batch` resolves
|
|
319
|
+
their row layout by length (one shared row for a deduplicated singleton
|
|
320
|
+
terrain, or `batch_size` rows for per-batch / legacy broadcast-singleton
|
|
321
|
+
data) and replicates the shared case itself. Only plain lists, where the
|
|
322
|
+
row count is directly observable, may need broadcasting here.
|
|
323
|
+
"""
|
|
321
324
|
if is_blob(values):
|
|
322
325
|
return values
|
|
323
326
|
|
|
@@ -633,8 +636,8 @@ def _merge_terrain(
|
|
|
633
636
|
)
|
|
634
637
|
|
|
635
638
|
def _bound(name: str, key: str, pick) -> float | None:
|
|
636
|
-
# A null bound means "unknown"
|
|
637
|
-
#
|
|
639
|
+
# A null bound means "unknown", and one unknown makes the merged
|
|
640
|
+
# range unknown too.
|
|
638
641
|
values = [t["properties"][name].get(key) for t in terrains]
|
|
639
642
|
return None if any(v is None for v in values) else pick(values)
|
|
640
643
|
|
|
@@ -684,6 +687,13 @@ def _merge_states(
|
|
|
684
687
|
labels: list[str],
|
|
685
688
|
selections: list[list[int] | None],
|
|
686
689
|
) -> list[dict]:
|
|
690
|
+
"""Build the merged per-frame states on the first file's timeline.
|
|
691
|
+
|
|
692
|
+
Every other file contributes its nearest-in-time frame (zero-order hold).
|
|
693
|
+
Per-body fields and scalars are concatenated in file order, keeping only
|
|
694
|
+
each file's selected batches; rigidly attached bodies are skipped, since
|
|
695
|
+
they never appear in states.
|
|
696
|
+
"""
|
|
687
697
|
ref_times = [s["time"] for s in states_list[0]]
|
|
688
698
|
times_by_file = [[s["time"] for s in states] for states in states_list]
|
|
689
699
|
total_batches = sum(_output_sizes(batch_sizes, selections))
|
|
@@ -926,7 +936,7 @@ def merge_simulation_files(
|
|
|
926
936
|
if dropped:
|
|
927
937
|
logger.warning(
|
|
928
938
|
"Dropping episode boundaries from %s: they index their own "
|
|
929
|
-
"timeline, and the merged scene uses '%s'
|
|
939
|
+
"timeline, and the merged scene uses '%s's timeline.",
|
|
930
940
|
", ".join(f"'{label}'" for label in dropped),
|
|
931
941
|
labels[0],
|
|
932
942
|
)
|