simview 6.2.1__tar.gz → 6.2.3__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.1/simview.egg-info → simview-6.2.3}/PKG-INFO +3 -3
- {simview-6.2.1 → simview-6.2.3}/README.md +2 -2
- {simview-6.2.1 → simview-6.2.3}/pyproject.toml +1 -1
- {simview-6.2.1 → simview-6.2.3}/simview/__main__.py +6 -5
- {simview-6.2.1 → simview-6.2.3}/simview/columnar.py +36 -20
- {simview-6.2.1 → simview-6.2.3}/simview/diff.py +10 -7
- {simview-6.2.1 → simview-6.2.3}/simview/info.py +6 -7
- {simview-6.2.1 → simview-6.2.3}/simview/launcher.py +14 -14
- {simview-6.2.1 → simview-6.2.3}/simview/live.py +3 -0
- {simview-6.2.1 → simview-6.2.3}/simview/merge.py +26 -24
- {simview-6.2.1 → simview-6.2.3}/simview/model.py +72 -35
- {simview-6.2.1 → simview-6.2.3}/simview/remote.py +1 -1
- {simview-6.2.1 → simview-6.2.3}/simview/render.py +6 -4
- {simview-6.2.1 → simview-6.2.3}/simview/scene.py +114 -112
- {simview-6.2.1 → simview-6.2.3}/simview/server.py +54 -60
- {simview-6.2.1 → simview-6.2.3}/simview/state.py +30 -24
- {simview-6.2.1 → simview-6.2.3}/simview/static/css/controls.css +84 -108
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/SimView.js +57 -83
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/components/AnimationController.js +8 -31
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/components/BatchManager.js +6 -8
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/components/InteractionController.js +3 -10
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/components/Scene.js +6 -6
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/components/StateStore.js +13 -27
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/components/WindowedField.js +2 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/config.js +2 -5
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/objects/Body.js +15 -21
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/objects/StaticObject.js +2 -8
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/objects/Terrain.js +11 -20
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/objects/colormap.js +12 -18
- simview-6.2.3/simview/static/js/objects/similarity.js +25 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/objects/utils.js +19 -60
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/ui/AnalysisPanel.js +0 -12
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/ui/BatchLegend.js +1 -3
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/ui/BodyStateWindow.js +41 -71
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/ui/Controls.js +65 -126
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/ui/ErrorMetrics.js +45 -61
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/ui/Legend.js +11 -12
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/ui/PlaybackControls.js +11 -10
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/ui/ScalarPlotter.js +18 -12
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/ui/TerrainProfile.js +7 -8
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/ui/chartControls.js +25 -12
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/batchVisibility.js +1 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/blobCodec.js +5 -5
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/blobWindow.js +9 -2
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/bodyTransforms.js +3 -4
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/cameraRange.js +6 -3
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/csv.js +3 -2
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/episodes.js +3 -3
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/errorMath.js +2 -5
- simview-6.2.3/simview/static/js/utils/html.js +5 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/keyboard.js +3 -2
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/scalarSeries.js +2 -2
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/terrainSample.js +9 -15
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/uplot.js +4 -5
- {simview-6.2.1/simview/static/lib → simview-6.2.3/simview/static/js/vendor}/js-colormaps.js +28 -33
- {simview-6.2.1 → simview-6.2.3}/simview/templates/index.html +16 -12
- {simview-6.2.1 → simview-6.2.3}/simview/terrain.py +13 -13
- {simview-6.2.1 → simview-6.2.3}/simview/view.py +2 -1
- {simview-6.2.1 → simview-6.2.3/simview.egg-info}/PKG-INFO +3 -3
- {simview-6.2.1 → simview-6.2.3}/simview.egg-info/SOURCES.txt +4 -3
- {simview-6.2.1 → simview-6.2.3}/tests/test_cli.py +2 -2
- {simview-6.2.1 → simview-6.2.3}/tests/test_columnar_states.py +4 -3
- {simview-6.2.1 → simview-6.2.3}/tests/test_diff.py +1 -1
- {simview-6.2.1 → simview-6.2.3}/tests/test_info.py +1 -1
- {simview-6.2.1 → simview-6.2.3}/tests/test_launcher.py +1 -1
- {simview-6.2.1 → simview-6.2.3}/tests/test_roundtrip.py +1 -1
- {simview-6.2.1 → simview-6.2.3}/tests/test_scene.py +4 -4
- {simview-6.2.1 → simview-6.2.3}/tests/test_server.py +3 -3
- {simview-6.2.1 → simview-6.2.3}/tests/test_terrain_query.py +1 -1
- {simview-6.2.1 → simview-6.2.3}/tests/test_utils.py +1 -1
- simview-6.2.1/simview/static/js/objects/similarity.js +0 -38
- {simview-6.2.1 → simview-6.2.3}/LICENSE +0 -0
- {simview-6.2.1 → simview-6.2.3}/MANIFEST.in +0 -0
- {simview-6.2.1 → simview-6.2.3}/setup.cfg +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/__init__.py +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/py.typed +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/main.js +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/objects/Polyline.js +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/batchPresets.js +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/interpolate.js +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/liveFollow.js +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/viewState.js +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/viewerDefaults.js +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/js/utils/visibleRanges.js +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/lib/three-0.174.0/three.core.js +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/lib/three-0.174.0/three.module.js +0 -0
- {simview-6.2.1/simview/static/lib → simview-6.2.3/simview/static/lib/uPlot-1.6.32}/uPlot.esm.js +0 -0
- {simview-6.2.1/simview/static/lib → simview-6.2.3/simview/static/lib/uPlot-1.6.32}/uPlot.min.css +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/textures/contacts/red-cross0.png +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/static/textures/points/ball1.png +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview/utils.py +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview.egg-info/dependency_links.txt +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview.egg-info/entry_points.txt +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview.egg-info/requires.txt +0 -0
- {simview-6.2.1 → simview-6.2.3}/simview.egg-info/top_level.txt +0 -0
- {simview-6.2.1 → simview-6.2.3}/tests/test_batch_selection.py +0 -0
- {simview-6.2.1 → simview-6.2.3}/tests/test_columnar_ondisk.py +0 -0
- {simview-6.2.1 → simview-6.2.3}/tests/test_contacts.py +0 -0
- {simview-6.2.1 → simview-6.2.3}/tests/test_embed_defaults.py +0 -0
- {simview-6.2.1 → simview-6.2.3}/tests/test_episodes.py +0 -0
- {simview-6.2.1 → simview-6.2.3}/tests/test_live.py +0 -0
- {simview-6.2.1 → simview-6.2.3}/tests/test_merge.py +0 -0
- {simview-6.2.1 → simview-6.2.3}/tests/test_pointcloud_features.py +0 -0
- {simview-6.2.1 → simview-6.2.3}/tests/test_remote.py +0 -0
- {simview-6.2.1 → simview-6.2.3}/tests/test_show.py +0 -0
- {simview-6.2.1 → simview-6.2.3}/tests/test_terrain.py +0 -0
- {simview-6.2.1 → simview-6.2.3}/tests/test_trajectory.py +0 -0
- {simview-6.2.1 → simview-6.2.3}/tests/test_view.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simview
|
|
3
|
-
Version: 6.2.
|
|
3
|
+
Version: 6.2.3
|
|
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
|
|
@@ -436,8 +438,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
436
438
|
metavar="PATH",
|
|
437
439
|
help=(
|
|
438
440
|
"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."
|
|
441
|
+
"launching the viewer. Requires at least 2 input files, or 1 with a "
|
|
442
|
+
"'#<batches>' selection. Gzips the output if PATH ends in .gz."
|
|
441
443
|
),
|
|
442
444
|
)
|
|
443
445
|
_add_remote_flags(view)
|
|
@@ -654,9 +656,8 @@ def main():
|
|
|
654
656
|
_configure_logging()
|
|
655
657
|
argv = sys.argv[1:]
|
|
656
658
|
|
|
657
|
-
# Checked
|
|
658
|
-
#
|
|
659
|
-
# before any other validation ran.
|
|
659
|
+
# Checked before parsing so --version works alongside any other
|
|
660
|
+
# arguments, without tripping their validation.
|
|
660
661
|
if "--version" in argv:
|
|
661
662
|
print(_package_version())
|
|
662
663
|
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
|
|
|
@@ -12,10 +12,10 @@ same document; only how a blob is referenced differs:
|
|
|
12
12
|
URL the browser fetches in parallel as a `Float32Array`.
|
|
13
13
|
|
|
14
14
|
That symmetry is the whole point: a columnar file needs no repacking at all
|
|
15
|
-
to be served, and `
|
|
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
|
|
@@ -122,6 +122,29 @@ def encode_floats(flat) -> str:
|
|
|
122
122
|
return inline_blob(struct.pack(f"<{len(flat)}f", *flat))
|
|
123
123
|
|
|
124
124
|
|
|
125
|
+
def batch_rows(flat: list[float], per_row: int, batch_size: int) -> list | None:
|
|
126
|
+
"""Split a decoded blob into `batch_size` rows of `per_row` floats: either
|
|
127
|
+
one row shared by every batch (replicated) or one row per batch. None if
|
|
128
|
+
`flat` is neither length -- callers raise their own error."""
|
|
129
|
+
if len(flat) == per_row:
|
|
130
|
+
return [flat] * batch_size
|
|
131
|
+
if len(flat) == batch_size * per_row:
|
|
132
|
+
return [flat[i : i + per_row] for i in range(0, len(flat), per_row)]
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def rewrite_blobs(obj: Any, register_blob) -> None:
|
|
137
|
+
"""Replace, in place, every inline ``__b64__`` blob string nested anywhere
|
|
138
|
+
in `obj` with `register_blob(raw_bytes)`."""
|
|
139
|
+
if not isinstance(obj, (dict, list)):
|
|
140
|
+
return
|
|
141
|
+
for k, v in obj.items() if isinstance(obj, dict) else enumerate(obj):
|
|
142
|
+
if is_blob(v):
|
|
143
|
+
obj[k] = register_blob(blob_bytes(v))
|
|
144
|
+
else:
|
|
145
|
+
rewrite_blobs(v, register_blob)
|
|
146
|
+
|
|
147
|
+
|
|
125
148
|
def decode_transform_row(value: Any, batch_size: int, batch_idx: int) -> list[float]:
|
|
126
149
|
"""Decode one state's `bodyTransform` field value for a single batch into
|
|
127
150
|
a flat 7-element `[x, y, z, w, qx, qy, qz]` row, without numpy.
|
|
@@ -134,8 +157,6 @@ def decode_transform_row(value: Any, batch_size: int, batch_idx: int) -> list[fl
|
|
|
134
157
|
if len(flat) == batch_size * width:
|
|
135
158
|
start = batch_idx * width
|
|
136
159
|
return flat[start : start + width]
|
|
137
|
-
if len(flat) == width and batch_size == 1:
|
|
138
|
-
return flat
|
|
139
160
|
raise ValueError(
|
|
140
161
|
f"bodyTransform has {len(flat)} floats; expected {width} "
|
|
141
162
|
f"(batch_size=1, flat) or {batch_size * width} "
|
|
@@ -150,7 +171,8 @@ def _decode_state_field_rows(value, width: int, batch_size: int):
|
|
|
150
171
|
Mirrors the shapes SimViewBodyState.to_json()/add_trajectory produce: a
|
|
151
172
|
``__b64__`` blob is always batch_size rows of `width` floats; a plain list
|
|
152
173
|
is either already nested (one row per batch) or, for a single-batch scene,
|
|
153
|
-
a flat list of `width` floats (see
|
|
174
|
+
a flat list of `width` floats (see docs/dev/json-format.md "Authoring whole
|
|
175
|
+
trajectories").
|
|
154
176
|
"""
|
|
155
177
|
assert np is not None
|
|
156
178
|
if isinstance(value, str):
|
|
@@ -200,7 +222,7 @@ def columnarize_states(states_data: list, model_data: dict | None, register_blob
|
|
|
200
222
|
Strict by design: this trades a bit of coverage (an inconsistent scene just
|
|
201
223
|
doesn't get the win) for never risking a subtly wrong repack. The one
|
|
202
224
|
deliberate exception is `contacts`, which may legitimately come and go per
|
|
203
|
-
frame (see
|
|
225
|
+
frame (see docs/dev/json-format.md) without disqualifying the rest of the scene.
|
|
204
226
|
"""
|
|
205
227
|
if np is None or not states_data:
|
|
206
228
|
return None
|
|
@@ -451,23 +473,17 @@ def write_static_bundle(data: dict, directory) -> None:
|
|
|
451
473
|
(out / "blob").mkdir(parents=True, exist_ok=True)
|
|
452
474
|
count = 0
|
|
453
475
|
|
|
454
|
-
def
|
|
476
|
+
def write_blob(raw: bytes) -> str:
|
|
455
477
|
nonlocal count
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
obj[k] = f"/blob/{count}"
|
|
462
|
-
count += 1
|
|
463
|
-
else:
|
|
464
|
-
externalize(v)
|
|
465
|
-
|
|
466
|
-
externalize(data["model"])
|
|
478
|
+
(out / "blob" / str(count)).write_bytes(raw)
|
|
479
|
+
count += 1
|
|
480
|
+
return f"/blob/{count - 1}"
|
|
481
|
+
|
|
482
|
+
rewrite_blobs(data["model"], write_blob)
|
|
467
483
|
# The viewer only resolves /blob/ URLs in the model and a columnar states
|
|
468
484
|
# object; a legacy per-frame array's inline __b64__ fields are decoded
|
|
469
485
|
# in place, so they must stay inline.
|
|
470
486
|
if is_columnar(data["states"]):
|
|
471
|
-
|
|
487
|
+
rewrite_blobs(data["states"], write_blob)
|
|
472
488
|
for name in ("model", "states"):
|
|
473
489
|
(out / f"{name}.json").write_text(json.dumps(data[name]))
|
|
@@ -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
|
]
|
|
@@ -3,11 +3,10 @@ agents inspecting simview-generated data files.
|
|
|
3
3
|
|
|
4
4
|
Deliberately dependency-free (stdlib only: json, gzip via
|
|
5
5
|
`simview.utils.read_maybe_gzipped_bytes`, base64) so it works on a base
|
|
6
|
-
install without the `authoring` extra (torch/numpy) -- see
|
|
7
|
-
|
|
8
|
-
pulls in torch at module scope, the latter fastapi/uvicorn
|
|
9
|
-
|
|
10
|
-
inspection tool. Shares its blob-decoding/body-label helpers with
|
|
6
|
+
install without the `authoring` extra (torch/numpy) -- see "Lazy imports" in
|
|
7
|
+
docs/dev/architecture.md. Does not import `simview.model`/`simview.server`:
|
|
8
|
+
the former pulls in torch at module scope, the latter fastapi/uvicorn --
|
|
9
|
+
both wrong layering for a lightweight inspection tool. Shares its blob-decoding/body-label helpers with
|
|
11
10
|
`simview/terrain.py`/`simview/diff.py` via `simview.columnar`/`simview.utils`,
|
|
12
11
|
which are stdlib-only on this read path.
|
|
13
12
|
"""
|
|
@@ -241,8 +240,8 @@ def _summarize_states(states, model: dict | None, warnings: list[str]) -> dict:
|
|
|
241
240
|
|
|
242
241
|
# Per-body walk, mirroring columnar.py's columnarize_states structural
|
|
243
242
|
# checks (field-set consistency, first-appearance-at-frame-0, no
|
|
244
|
-
# dropping out)
|
|
245
|
-
#
|
|
243
|
+
# dropping out) without numpy: blobs are decoded only to count their
|
|
244
|
+
# floats, never shaped into arrays.
|
|
246
245
|
body_order: list = []
|
|
247
246
|
body_first_frame: dict = {}
|
|
248
247
|
body_name_value: dict = {}
|
|
@@ -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:
|
|
@@ -27,6 +27,7 @@ import orjson
|
|
|
27
27
|
|
|
28
28
|
from .columnar import (
|
|
29
29
|
STATE_FIELD_WIDTHS,
|
|
30
|
+
batch_rows,
|
|
30
31
|
blob_floats,
|
|
31
32
|
encode_floats,
|
|
32
33
|
expand_columnar_states,
|
|
@@ -212,12 +213,8 @@ def _decode_per_batch(
|
|
|
212
213
|
return value
|
|
213
214
|
flat = blob_floats(value)
|
|
214
215
|
per_row = resolution * (vector_width or 1)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
chunks = [list(flat)] * batch_size
|
|
218
|
-
elif len(flat) == batch_size * per_row:
|
|
219
|
-
chunks = [list(flat[i : i + per_row]) for i in range(0, len(flat), per_row)]
|
|
220
|
-
else:
|
|
216
|
+
chunks = batch_rows(flat, per_row, batch_size)
|
|
217
|
+
if chunks is None:
|
|
221
218
|
raise ValueError(
|
|
222
219
|
f"terrain blob has {len(flat)} floats; expected {per_row} (one "
|
|
223
220
|
f"shared row) or {batch_size * per_row} ({batch_size} batches "
|
|
@@ -316,11 +313,14 @@ def _validate_doc(doc: dict, label: str) -> None:
|
|
|
316
313
|
def _expand_batched(
|
|
317
314
|
values: list | str, is_singleton: bool, batch_size: int, field: str, label: str
|
|
318
315
|
) -> list | str:
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
+
"""
|
|
324
324
|
if is_blob(values):
|
|
325
325
|
return values
|
|
326
326
|
|
|
@@ -636,8 +636,8 @@ def _merge_terrain(
|
|
|
636
636
|
)
|
|
637
637
|
|
|
638
638
|
def _bound(name: str, key: str, pick) -> float | None:
|
|
639
|
-
# A null bound means "unknown"
|
|
640
|
-
#
|
|
639
|
+
# A null bound means "unknown", and one unknown makes the merged
|
|
640
|
+
# range unknown too.
|
|
641
641
|
values = [t["properties"][name].get(key) for t in terrains]
|
|
642
642
|
return None if any(v is None for v in values) else pick(values)
|
|
643
643
|
|
|
@@ -679,15 +679,6 @@ def _nearest_index(sorted_times: list[float], t: float) -> int:
|
|
|
679
679
|
return i - 1 if (t - before) <= (after - t) else i
|
|
680
680
|
|
|
681
681
|
|
|
682
|
-
def _state_body_lookup(
|
|
683
|
-
states: list[dict], file_idx: int, state_idx: int, cache: dict
|
|
684
|
-
) -> dict[str, dict]:
|
|
685
|
-
key = (file_idx, state_idx)
|
|
686
|
-
if key not in cache:
|
|
687
|
-
cache[key] = bodies_by_name(states[state_idx].get("bodies"))
|
|
688
|
-
return cache[key]
|
|
689
|
-
|
|
690
|
-
|
|
691
682
|
def _merge_states(
|
|
692
683
|
states_list: list[list[dict]],
|
|
693
684
|
batch_sizes: list[int],
|
|
@@ -696,6 +687,13 @@ def _merge_states(
|
|
|
696
687
|
labels: list[str],
|
|
697
688
|
selections: list[list[int] | None],
|
|
698
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
|
+
"""
|
|
699
697
|
ref_times = [s["time"] for s in states_list[0]]
|
|
700
698
|
times_by_file = [[s["time"] for s in states] for states in states_list]
|
|
701
699
|
total_batches = sum(_output_sizes(batch_sizes, selections))
|
|
@@ -731,7 +729,11 @@ def _merge_states(
|
|
|
731
729
|
zip(states_list, batch_sizes, selections)
|
|
732
730
|
):
|
|
733
731
|
state_idx = state_idx_by_file[file_idx]
|
|
734
|
-
lookup =
|
|
732
|
+
lookup = lookup_cache.get((file_idx, state_idx))
|
|
733
|
+
if lookup is None:
|
|
734
|
+
lookup = lookup_cache[(file_idx, state_idx)] = bodies_by_name(
|
|
735
|
+
states[state_idx].get("bodies")
|
|
736
|
+
)
|
|
735
737
|
body_state = lookup.get(name)
|
|
736
738
|
if body_state is None:
|
|
737
739
|
raise ValueError(
|
|
@@ -934,7 +936,7 @@ def merge_simulation_files(
|
|
|
934
936
|
if dropped:
|
|
935
937
|
logger.warning(
|
|
936
938
|
"Dropping episode boundaries from %s: they index their own "
|
|
937
|
-
"timeline, and the merged scene uses '%s'
|
|
939
|
+
"timeline, and the merged scene uses '%s's timeline.",
|
|
938
940
|
", ".join(f"'{label}'" for label in dropped),
|
|
939
941
|
labels[0],
|
|
940
942
|
)
|