simview 6.3.0__tar.gz → 6.3.1__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.3.0/simview.egg-info → simview-6.3.1}/PKG-INFO +1 -1
- {simview-6.3.0 → simview-6.3.1}/pyproject.toml +1 -1
- {simview-6.3.0 → simview-6.3.1}/simview/__main__.py +8 -2
- simview-6.3.1/simview/preload.py +69 -0
- {simview-6.3.0 → simview-6.3.1}/simview/server.py +14 -3
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/SimView.js +47 -23
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/components/BatchManager.js +9 -1
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/components/Scene.js +55 -1
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/components/StateStore.js +23 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/objects/Terrain.js +46 -17
- {simview-6.3.0 → simview-6.3.1}/simview/templates/index.html +11 -0
- {simview-6.3.0 → simview-6.3.1/simview.egg-info}/PKG-INFO +1 -1
- {simview-6.3.0 → simview-6.3.1}/simview.egg-info/SOURCES.txt +2 -0
- simview-6.3.1/tests/test_preload.py +25 -0
- {simview-6.3.0 → simview-6.3.1}/LICENSE +0 -0
- {simview-6.3.0 → simview-6.3.1}/MANIFEST.in +0 -0
- {simview-6.3.0 → simview-6.3.1}/README.md +0 -0
- {simview-6.3.0 → simview-6.3.1}/setup.cfg +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/__init__.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/columnar.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/diff.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/info.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/launcher.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/live.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/merge.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/model.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/py.typed +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/remote.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/render.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/scene.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/state.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/css/controls.css +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/components/AnimationController.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/components/InteractionController.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/components/WindowedField.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/config.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/main.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/objects/Body.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/objects/Polyline.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/objects/StaticObject.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/objects/colormap.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/objects/similarity.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/objects/utils.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/ui/AnalysisPanel.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/ui/BatchLegend.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/ui/BodyStateWindow.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/ui/Controls.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/ui/ErrorMetrics.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/ui/Legend.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/ui/PlaybackControls.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/ui/ScalarPlotter.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/ui/TerrainProfile.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/ui/chartControls.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/batchPresets.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/batchVisibility.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/blobCodec.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/blobWindow.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/bodyTransforms.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/cameraRange.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/csv.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/episodes.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/errorMath.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/html.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/interpolate.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/keyboard.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/liveFollow.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/scalarSeries.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/terrainSample.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/uplot.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/viewState.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/viewerDefaults.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/utils/visibleRanges.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/js/vendor/js-colormaps.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/lib/three-0.174.0/three.core.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/lib/three-0.174.0/three.module.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/lib/uPlot-1.6.32/uPlot.esm.js +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/lib/uPlot-1.6.32/uPlot.min.css +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/textures/contacts/red-cross0.png +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/static/textures/points/ball1.png +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/terrain.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/utils.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview/view.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview.egg-info/dependency_links.txt +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview.egg-info/entry_points.txt +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview.egg-info/requires.txt +0 -0
- {simview-6.3.0 → simview-6.3.1}/simview.egg-info/top_level.txt +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_batch_selection.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_cli.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_columnar_ondisk.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_columnar_states.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_contacts.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_diff.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_embed_defaults.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_episodes.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_info.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_launcher.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_live.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_merge.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_pointcloud_features.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_remote.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_roundtrip.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_scene.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_server.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_show.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_terrain.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_terrain_query.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_trajectory.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_utils.py +0 -0
- {simview-6.3.0 → simview-6.3.1}/tests/test_view.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simview
|
|
3
|
-
Version: 6.3.
|
|
3
|
+
Version: 6.3.1
|
|
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,8 +82,14 @@ def export_static_viewer(directory: Path) -> None:
|
|
|
82
82
|
|
|
83
83
|
package = files("simview")
|
|
84
84
|
directory.mkdir(parents=True, exist_ok=True)
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
from simview.preload import render_index
|
|
86
|
+
|
|
87
|
+
index = package / "templates" / "index.html"
|
|
88
|
+
static = package / "static"
|
|
89
|
+
(directory / "index.html").write_text(
|
|
90
|
+
render_index(str(index), str(package)), encoding="utf-8"
|
|
91
|
+
)
|
|
92
|
+
shutil.copytree(str(static), directory / "static", dirs_exist_ok=True)
|
|
87
93
|
logger.info("Static viewer written to %s", directory)
|
|
88
94
|
|
|
89
95
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""`<link rel="modulepreload">` tags for the viewer page.
|
|
2
|
+
|
|
3
|
+
The browser only discovers an ES module's imports after it has fetched and
|
|
4
|
+
parsed the importer, so the ~50-file module graph downloads one dependency
|
|
5
|
+
level at a time. Listing every module in the page head lets them all start at
|
|
6
|
+
once (a ~50 ms round trip each otherwise adds up level by level).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
from functools import cache
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
# `import ... from "x"`, `export ... from "x"` and side-effect `import "x"`.
|
|
15
|
+
# Dynamic import() is skipped: those modules are lazy on purpose.
|
|
16
|
+
_STATIC_IMPORT = re.compile(
|
|
17
|
+
r"""(?:^|[;\n])\s*(?:import|export)\b[^;'"]*?(?:\bfrom\s*)?["']([^"']+)["']\s*;?""",
|
|
18
|
+
)
|
|
19
|
+
_IMPORTMAP = re.compile(r'<script type="importmap">(.*?)</script>', re.S)
|
|
20
|
+
PLACEHOLDER = "<!-- @modulepreload -->"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _resolve(
|
|
24
|
+
spec: str, importer: Path, root: Path, imports: dict[str, str]
|
|
25
|
+
) -> Path | None:
|
|
26
|
+
"""Filesystem path `spec` points at, or None for anything outside `root`."""
|
|
27
|
+
if spec.startswith("."):
|
|
28
|
+
target = importer.parent / spec
|
|
29
|
+
else:
|
|
30
|
+
# Bare specifier -> import map; "prefix/" entries map whole directories.
|
|
31
|
+
for key, value in imports.items():
|
|
32
|
+
if spec == key or (key.endswith("/") and spec.startswith(key)):
|
|
33
|
+
target = root / (value + spec[len(key) :])
|
|
34
|
+
break
|
|
35
|
+
else:
|
|
36
|
+
return None
|
|
37
|
+
target = target.resolve()
|
|
38
|
+
return target if target.is_file() and root in target.parents else None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def module_graph(html: str, root: Path, entry: str = "static/js/main.js") -> list[str]:
|
|
42
|
+
"""Page-relative URLs (`./static/...`) of every module reachable from `entry`."""
|
|
43
|
+
match = _IMPORTMAP.search(html)
|
|
44
|
+
imports = json.loads(match.group(1))["imports"] if match else {}
|
|
45
|
+
imports = {k: v.removeprefix("./") for k, v in imports.items()}
|
|
46
|
+
root = root.resolve()
|
|
47
|
+
seen: dict[Path, None] = {}
|
|
48
|
+
todo = [(root / entry).resolve()]
|
|
49
|
+
while todo:
|
|
50
|
+
path = todo.pop()
|
|
51
|
+
if path in seen:
|
|
52
|
+
continue
|
|
53
|
+
seen[path] = None
|
|
54
|
+
for spec in _STATIC_IMPORT.findall(path.read_text(encoding="utf-8")):
|
|
55
|
+
dep = _resolve(spec, path, root, imports)
|
|
56
|
+
if dep is not None:
|
|
57
|
+
todo.append(dep)
|
|
58
|
+
return ["./" + p.relative_to(root).as_posix() for p in seen]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@cache
|
|
62
|
+
def render_index(index_path: str, root: str) -> str:
|
|
63
|
+
"""index.html with its modulepreload placeholder filled in."""
|
|
64
|
+
html = Path(index_path).read_text(encoding="utf-8")
|
|
65
|
+
links = "\n ".join(
|
|
66
|
+
f'<link rel="modulepreload" href="{url}" />'
|
|
67
|
+
for url in module_graph(html, Path(root))
|
|
68
|
+
)
|
|
69
|
+
return html.replace(PLACEHOLDER, links)
|
|
@@ -21,13 +21,15 @@ import orjson
|
|
|
21
21
|
import uvicorn
|
|
22
22
|
from fastapi import FastAPI, Request, Response, WebSocket, WebSocketDisconnect
|
|
23
23
|
from fastapi.middleware.cors import CORSMiddleware
|
|
24
|
-
from fastapi.responses import
|
|
24
|
+
from fastapi.responses import HTMLResponse, JSONResponse
|
|
25
25
|
from fastapi.staticfiles import StaticFiles
|
|
26
26
|
from pydantic import BaseModel
|
|
27
27
|
from starlette.datastructures import Headers
|
|
28
|
+
from starlette.middleware.gzip import GZipMiddleware
|
|
28
29
|
from starlette.types import Scope
|
|
29
30
|
|
|
30
31
|
from simview.columnar import columnarize_states, is_columnar, rewrite_blobs
|
|
32
|
+
from simview.preload import render_index
|
|
31
33
|
from simview.utils import dialable_host, find_free_port, read_maybe_gzipped_bytes
|
|
32
34
|
|
|
33
35
|
logger = logging.getLogger("simview.server")
|
|
@@ -348,8 +350,17 @@ class SimViewServer:
|
|
|
348
350
|
# libs as immutable and everything else short-lived (max-age=60), so
|
|
349
351
|
# index.html's fixed ./static/... paths never need a cache-busting
|
|
350
352
|
# query param.
|
|
353
|
+
# Gzipped on the fly (three.js alone is ~1.1 MB raw, ~230 KB gzipped);
|
|
354
|
+
# applied to /static only -- /blob is float data that barely
|
|
355
|
+
# compresses and is served with Range support.
|
|
351
356
|
self.app.mount(
|
|
352
|
-
"/static",
|
|
357
|
+
"/static",
|
|
358
|
+
GZipMiddleware(
|
|
359
|
+
CacheControlStaticFiles(directory=STATIC),
|
|
360
|
+
minimum_size=1024,
|
|
361
|
+
compresslevel=6,
|
|
362
|
+
),
|
|
363
|
+
name="static",
|
|
353
364
|
)
|
|
354
365
|
|
|
355
366
|
# Pre-serialized, gzipped payloads for HTTP serving. The parsed dicts are
|
|
@@ -521,7 +532,7 @@ class SimViewServer:
|
|
|
521
532
|
def setup_routes(self):
|
|
522
533
|
@self.app.get("/")
|
|
523
534
|
async def index():
|
|
524
|
-
return
|
|
535
|
+
return HTMLResponse(render_index(INDEX_HTML, str(Path(STATIC).parent)))
|
|
525
536
|
|
|
526
537
|
_gzip_headers = {"Content-Encoding": "gzip"}
|
|
527
538
|
|
|
@@ -6,7 +6,7 @@ import { AnimationController } from "./components/AnimationController.js";
|
|
|
6
6
|
import { UI_DEFAULT_CONFIG } from "./config.js";
|
|
7
7
|
import { Body } from "./objects/Body.js";
|
|
8
8
|
import { Terrain } from "./objects/Terrain.js";
|
|
9
|
-
import { BatchManager } from "./components/BatchManager.js";
|
|
9
|
+
import { BatchManager, batchCountOf } from "./components/BatchManager.js";
|
|
10
10
|
import { ScalarPlotter } from "./ui/ScalarPlotter.js";
|
|
11
11
|
import { StaticObject } from "./objects/StaticObject.js";
|
|
12
12
|
import { Polyline } from "./objects/Polyline.js";
|
|
@@ -109,10 +109,18 @@ export class SimView {
|
|
|
109
109
|
async loadData() {
|
|
110
110
|
const splash = document.getElementById("loading-splash");
|
|
111
111
|
const base = this.staticBase;
|
|
112
|
+
// Requests the page started before the modules loaded (see
|
|
113
|
+
// templates/index.html), each usable once. /states doesn't depend on
|
|
114
|
+
// the model, so it's requested alongside it instead of after
|
|
115
|
+
// initFromModel.
|
|
116
|
+
const prefetched = base ? null : window.__simviewPrefetch;
|
|
117
|
+
window.__simviewPrefetch = null;
|
|
118
|
+
const statesFetch = prefetched?.states ?? fetch(base ? `${base}/states.json` : "/states");
|
|
119
|
+
statesFetch.catch(() => {}); // surfaced where it's awaited below
|
|
112
120
|
try {
|
|
113
121
|
if (splash) splash.innerHTML = "<h1>Fetching model…</h1>";
|
|
114
122
|
console.time("fetch_model");
|
|
115
|
-
const modelResponse = await fetch(base ? `${base}/model.json` : "/model");
|
|
123
|
+
const modelResponse = await (prefetched?.model ?? fetch(base ? `${base}/model.json` : "/model"));
|
|
116
124
|
console.timeEnd("fetch_model");
|
|
117
125
|
if (!modelResponse.ok)
|
|
118
126
|
throw new Error(`Failed to fetch model: ${modelResponse.status} ${modelResponse.statusText}`);
|
|
@@ -122,6 +130,13 @@ export class SimView {
|
|
|
122
130
|
const model = await modelResponse.json();
|
|
123
131
|
console.timeEnd("parse_model");
|
|
124
132
|
|
|
133
|
+
// The states side needs only the batch count (not the built
|
|
134
|
+
// scene), so its parse + blob downloads overlap the model's blobs
|
|
135
|
+
// and initFromModel instead of queueing behind them.
|
|
136
|
+
const batchCount = batchCountOf(model);
|
|
137
|
+
const statesReady = this.fetchStatesPayload(statesFetch, batchCount);
|
|
138
|
+
statesReady.catch(() => {}); // surfaced where it's awaited below
|
|
139
|
+
|
|
125
140
|
console.time("fetch_blobs");
|
|
126
141
|
await this.fetchBlobs(model);
|
|
127
142
|
console.timeEnd("fetch_blobs");
|
|
@@ -130,15 +145,7 @@ export class SimView {
|
|
|
130
145
|
this.initFromModel(model);
|
|
131
146
|
|
|
132
147
|
if (splash) splash.innerHTML = "<h1>Fetching states…</h1>";
|
|
133
|
-
|
|
134
|
-
const statesResponse = await fetch(base ? `${base}/states.json` : "/states");
|
|
135
|
-
console.timeEnd("fetch_states");
|
|
136
|
-
if (!statesResponse.ok)
|
|
137
|
-
throw new Error(`Failed to fetch states: ${statesResponse.status} ${statesResponse.statusText}`);
|
|
138
|
-
|
|
139
|
-
console.time("parse_states");
|
|
140
|
-
const statesPayload = await statesResponse.json();
|
|
141
|
-
console.timeEnd("parse_states");
|
|
148
|
+
const statesPayload = await statesReady;
|
|
142
149
|
|
|
143
150
|
if (Array.isArray(statesPayload)) {
|
|
144
151
|
// Legacy wire shape: a plain per-frame array, possibly with
|
|
@@ -154,15 +161,9 @@ export class SimView {
|
|
|
154
161
|
} else if (statesPayload && statesPayload.version === 4) {
|
|
155
162
|
// Columnar wire shape (see columnar.py::columnarize_states):
|
|
156
163
|
// a lightweight index plus /blob/... URLs for the actual
|
|
157
|
-
// whole-trajectory float32 data, fetched
|
|
164
|
+
// whole-trajectory float32 data, already fetched by
|
|
165
|
+
// fetchStatesPayload.
|
|
158
166
|
console.debug(`Received ${statesPayload.times.length} states (columnar)`);
|
|
159
|
-
// Long trajectories: swap the biggest current-frame-only
|
|
160
|
-
// fields for windowed readers before the bulk fetch, so they
|
|
161
|
-
// are never materialized in full (see utils/blobWindow.js).
|
|
162
|
-
this.windowLargeStateFields(statesPayload);
|
|
163
|
-
console.time("fetch_state_blobs");
|
|
164
|
-
await this.fetchBlobs(statesPayload);
|
|
165
|
-
console.timeEnd("fetch_state_blobs");
|
|
166
167
|
this.store = new ColumnarStateStore(statesPayload, this.batchManager.simBatches);
|
|
167
168
|
this.onStoreReady();
|
|
168
169
|
} else {
|
|
@@ -182,6 +183,30 @@ export class SimView {
|
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
|
|
186
|
+
// Fetches and parses /states and, for the columnar shape, downloads its
|
|
187
|
+
// blobs (long trajectories' biggest current-frame-only fields are swapped
|
|
188
|
+
// for windowed readers first, so they are never materialized in full --
|
|
189
|
+
// see utils/blobWindow.js). Resolves to the payload.
|
|
190
|
+
async fetchStatesPayload(statesFetch, batchCount) {
|
|
191
|
+
console.time("fetch_states");
|
|
192
|
+
const statesResponse = await statesFetch;
|
|
193
|
+
console.timeEnd("fetch_states");
|
|
194
|
+
if (!statesResponse.ok)
|
|
195
|
+
throw new Error(`Failed to fetch states: ${statesResponse.status} ${statesResponse.statusText}`);
|
|
196
|
+
|
|
197
|
+
console.time("parse_states");
|
|
198
|
+
const payload = await statesResponse.json();
|
|
199
|
+
console.timeEnd("parse_states");
|
|
200
|
+
|
|
201
|
+
if (payload && !Array.isArray(payload) && payload.version === 4) {
|
|
202
|
+
this.windowLargeStateFields(payload, batchCount);
|
|
203
|
+
console.time("fetch_state_blobs");
|
|
204
|
+
await this.fetchBlobs(payload);
|
|
205
|
+
console.timeEnd("fetch_state_blobs");
|
|
206
|
+
}
|
|
207
|
+
return payload;
|
|
208
|
+
}
|
|
209
|
+
|
|
185
210
|
// Replaces the blob URL of every large, current-frame-only per-body field
|
|
186
211
|
// with a WindowedField, so fetchBlobs skips it and it's read in windows
|
|
187
212
|
// around the playhead instead. Static demo mode is excluded: it serves
|
|
@@ -190,10 +215,9 @@ export class SimView {
|
|
|
190
215
|
// bodyTransform, contacts and the scalars are deliberately untouched --
|
|
191
216
|
// trails, error metrics, the terrain profile and the scalar plots all walk
|
|
192
217
|
// the whole trajectory, so windowing those would just move the cost.
|
|
193
|
-
windowLargeStateFields(statesPayload) {
|
|
218
|
+
windowLargeStateFields(statesPayload, batchCount) {
|
|
194
219
|
if (this.staticBase) return;
|
|
195
220
|
const totalFrames = statesPayload.times.length;
|
|
196
|
-
const batchCount = this.batchManager.simBatches;
|
|
197
221
|
// Overridable so tests can exercise windowing without a fixture big
|
|
198
222
|
// enough to cross the real threshold (same spirit as
|
|
199
223
|
// window.__debugSimView); undefined in normal use.
|
|
@@ -435,12 +459,12 @@ export class SimView {
|
|
|
435
459
|
// Resolves and records position/orientation (and contacts, vectors, ...)
|
|
436
460
|
// history for every frame from `startIndex` to the end of the store, for
|
|
437
461
|
// trails and the error metrics panel. Each frame is materialized
|
|
438
|
-
// transiently via store.
|
|
462
|
+
// transiently via store.getTransformFrame(i) (not retained) so this works
|
|
439
463
|
// identically whether the store is legacy or columnar.
|
|
440
464
|
appendBodyHistories(startIndex) {
|
|
441
465
|
if (!this.bodies || this.bodies.size === 0) return;
|
|
442
466
|
for (let s = startIndex; s < this.store.length; s++) {
|
|
443
|
-
const bodyStates = this.store.
|
|
467
|
+
const bodyStates = this.store.getTransformFrame(s).bodies;
|
|
444
468
|
if (!bodyStates) continue;
|
|
445
469
|
// Resolve off this historical state's own data (not any Body's
|
|
446
470
|
// current/live pose, which reflects whatever frame is currently
|
|
@@ -8,6 +8,14 @@ import {
|
|
|
8
8
|
visibleBatchSet,
|
|
9
9
|
} from "../utils/batchVisibility.js";
|
|
10
10
|
|
|
11
|
+
// The batch count a model declares (1 when absent); also what loadData needs
|
|
12
|
+
// before any BatchManager exists.
|
|
13
|
+
export function batchCountOf(modelData) {
|
|
14
|
+
return modelData?.simBatches !== undefined
|
|
15
|
+
? Math.max(1, parseInt(modelData.simBatches))
|
|
16
|
+
: 1;
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
export class BatchManager {
|
|
12
20
|
constructor(app, modelData) {
|
|
13
21
|
this.app = app;
|
|
@@ -34,7 +42,7 @@ export class BatchManager {
|
|
|
34
42
|
|
|
35
43
|
// Set batch count from model data
|
|
36
44
|
if (modelData.simBatches !== undefined) {
|
|
37
|
-
this.simBatches =
|
|
45
|
+
this.simBatches = batchCountOf(modelData);
|
|
38
46
|
console.log(
|
|
39
47
|
`Initializing with ${this.simBatches} simulation batches`
|
|
40
48
|
);
|
|
@@ -16,6 +16,51 @@ export class Scene {
|
|
|
16
16
|
this.initScene();
|
|
17
17
|
this.minRenderDelay = 1000 / FREQ_CONFIG.scene;
|
|
18
18
|
this.lastRenderTime = Number.NEGATIVE_INFINITY;
|
|
19
|
+
this.setupInvalidation();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// The canvas only needs redrawing when something may have changed.
|
|
23
|
+
// Mutations are scattered (GUI toggles, panels, data landing), so rather
|
|
24
|
+
// than hooking each one, any user input or camera move marks the scene
|
|
25
|
+
// dirty, playback/recording/new frames always render, and an idle
|
|
26
|
+
// heartbeat repaints anything else (e.g. a Range window landing while
|
|
27
|
+
// paused) within IDLE_RENDER_MS.
|
|
28
|
+
static IDLE_RENDER_MS = 250;
|
|
29
|
+
invalidate() {
|
|
30
|
+
this.needsRender = true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setupInvalidation() {
|
|
34
|
+
this.needsRender = true;
|
|
35
|
+
this._lastFrameIndex = null;
|
|
36
|
+
const mark = () => this.invalidate();
|
|
37
|
+
// pointermove only while a button is held: drags orbit/pan the camera.
|
|
38
|
+
this._onInput = (e) => {
|
|
39
|
+
if (e.type !== "pointermove" || e.buttons) this.invalidate();
|
|
40
|
+
};
|
|
41
|
+
this._inputEvents = [
|
|
42
|
+
"pointerdown", "pointermove", "pointerup", "wheel",
|
|
43
|
+
"keydown", "keyup", "input", "change", "click",
|
|
44
|
+
];
|
|
45
|
+
this._inputEvents.forEach((t) =>
|
|
46
|
+
window.addEventListener(t, this._onInput, { capture: true, passive: true })
|
|
47
|
+
);
|
|
48
|
+
this.controls.addEventListener("change", mark);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#shouldRender(now) {
|
|
52
|
+
const ac = this.app.animationController;
|
|
53
|
+
if (
|
|
54
|
+
this.needsRender ||
|
|
55
|
+
ac?.isPlaying ||
|
|
56
|
+
ac?.isRecording ||
|
|
57
|
+
ac?.currentStateIndex !== this._lastFrameIndex ||
|
|
58
|
+
now - this.lastRenderTime >= Scene.IDLE_RENDER_MS
|
|
59
|
+
) {
|
|
60
|
+
this._lastFrameIndex = ac?.currentStateIndex;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
19
64
|
}
|
|
20
65
|
|
|
21
66
|
initScene() {
|
|
@@ -67,12 +112,17 @@ export class Scene {
|
|
|
67
112
|
window.removeEventListener("resize", this._onResize);
|
|
68
113
|
this._onResize = null;
|
|
69
114
|
}
|
|
115
|
+
this._inputEvents?.forEach((t) =>
|
|
116
|
+
window.removeEventListener(t, this._onInput, { capture: true })
|
|
117
|
+
);
|
|
118
|
+
this._inputEvents = null;
|
|
70
119
|
this.controls?.dispose();
|
|
71
120
|
this.renderer?.dispose();
|
|
72
121
|
this.renderer?.domElement?.remove();
|
|
73
122
|
}
|
|
74
123
|
|
|
75
124
|
#handleWindowResize() {
|
|
125
|
+
this.invalidate(); // resizing clears the canvas
|
|
76
126
|
this.camera.aspect = window.innerWidth / window.innerHeight;
|
|
77
127
|
this.camera.updateProjectionMatrix();
|
|
78
128
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
|
@@ -139,7 +189,11 @@ export class Scene {
|
|
|
139
189
|
// and B side by side, shifting the camera by each batch's offset from the
|
|
140
190
|
// active one for its half of the viewport.
|
|
141
191
|
animate(now) {
|
|
142
|
-
|
|
192
|
+
// The -1 absorbs rAF timestamp jitter: at 60 Hz the deltas hover
|
|
193
|
+
// around 16.67 ms and would otherwise drop every other frame.
|
|
194
|
+
if (now - this.lastRenderTime < this.minRenderDelay - 1) return;
|
|
195
|
+
if (!this.#shouldRender(now)) return;
|
|
196
|
+
this.needsRender = false;
|
|
143
197
|
|
|
144
198
|
if (this.app.uiState && this.app.uiState.splitScreen && this.app.batchManager && this.app.batchManager.simBatches >= 2) {
|
|
145
199
|
const batchA = this.app.uiState.splitBatchA !== undefined ? this.app.uiState.splitBatchA : 0;
|
|
@@ -167,6 +167,25 @@ export class ColumnarStateStore {
|
|
|
167
167
|
return state;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
// Frame `i` carrying only each body's bodyTransform. For whole-run walks
|
|
171
|
+
// (trail/error-metric histories) that never read the windowed fields:
|
|
172
|
+
// getFrame() would start a Range fetch for every window of every one.
|
|
173
|
+
getTransformFrame(i) {
|
|
174
|
+
const bodies = [];
|
|
175
|
+
for (const body of this._bodies) {
|
|
176
|
+
const value = body.fields.bodyTransform;
|
|
177
|
+
const entry = { name: body.name };
|
|
178
|
+
if (value && typeof value.rowsAt === "function") {
|
|
179
|
+
const rows = value.rowsAt(i);
|
|
180
|
+
if (rows) entry.bodyTransform = rows;
|
|
181
|
+
} else if (value) {
|
|
182
|
+
entry.bodyTransform = this._sliceField(value, COLUMNAR_FIELD_WIDTHS.bodyTransform, i);
|
|
183
|
+
}
|
|
184
|
+
bodies.push(entry);
|
|
185
|
+
}
|
|
186
|
+
return { time: this.times[i], bodies };
|
|
187
|
+
}
|
|
188
|
+
|
|
170
189
|
// Per-batch series for the scalar plotter: one per-frame value array per
|
|
171
190
|
// batch, indexed like `times`.
|
|
172
191
|
// `simBatches` is accepted (and ignored) for API parity with
|
|
@@ -217,6 +236,10 @@ export class LegacyStateStore {
|
|
|
217
236
|
return this._states[i];
|
|
218
237
|
}
|
|
219
238
|
|
|
239
|
+
getTransformFrame(i) {
|
|
240
|
+
return this._states[i];
|
|
241
|
+
}
|
|
242
|
+
|
|
220
243
|
// `simBatches` is passed explicitly: the legacy shape records no batch
|
|
221
244
|
// count of its own (callers use app.batchManager.simBatches).
|
|
222
245
|
getScalarSeries(name, simBatches) {
|
|
@@ -4,6 +4,12 @@ import { colormapLUT, getCallableFromColorMapName } from "./colormap.js";
|
|
|
4
4
|
import { toFlatFloat32Array } from "./utils.js";
|
|
5
5
|
|
|
6
6
|
export class Terrain {
|
|
7
|
+
// Per-batch normals field and the built-once arrow template of a
|
|
8
|
+
// singleton terrain; the arrows themselves are built lazily, see
|
|
9
|
+
// #ensureNormals.
|
|
10
|
+
#normals = null;
|
|
11
|
+
#singletonNormals = null;
|
|
12
|
+
|
|
7
13
|
constructor(terrainData, app) {
|
|
8
14
|
this.app = app; // Store reference to app for accessing batch manager
|
|
9
15
|
this.bounds = terrainData.bounds;
|
|
@@ -18,6 +24,7 @@ export class Terrain {
|
|
|
18
24
|
this.#initEmbeddingData(terrainData.embeddingData);
|
|
19
25
|
|
|
20
26
|
const normals = this.#normalizeVectorField(terrainData.normals, 3);
|
|
27
|
+
this.#normals = normals;
|
|
21
28
|
|
|
22
29
|
this.#createVisualRepresentations(this.heightData, normals);
|
|
23
30
|
}
|
|
@@ -197,7 +204,6 @@ export class Terrain {
|
|
|
197
204
|
|
|
198
205
|
let singletonSurfaceGeometry = null;
|
|
199
206
|
let singletonWireframeGeometry = null;
|
|
200
|
-
let singletonNormals = null;
|
|
201
207
|
|
|
202
208
|
if (this.isSingleton) {
|
|
203
209
|
singletonSurfaceGeometry = this.#createSurfaceGeometryFromHeightData(
|
|
@@ -206,10 +212,6 @@ export class Terrain {
|
|
|
206
212
|
normals?.[0]
|
|
207
213
|
);
|
|
208
214
|
singletonWireframeGeometry = this.#createWireframeGeometry(singletonSurfaceGeometry);
|
|
209
|
-
singletonNormals = this.#createNormalVectors(
|
|
210
|
-
heightData[0],
|
|
211
|
-
normals?.[0]
|
|
212
|
-
);
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
// Create geometry for each batch
|
|
@@ -240,11 +242,12 @@ export class Terrain {
|
|
|
240
242
|
wireframeMesh.raycast = () => {};
|
|
241
243
|
batchGroup.add(wireframeMesh);
|
|
242
244
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
// Filled on first use: 121 ArrowHelpers (3 objects each) per
|
|
246
|
+
// batch made up ~99% of a 512-env scene while hidden.
|
|
247
|
+
const surfaceNormals = new THREE.Group();
|
|
247
248
|
surfaceNormals.name = "normals";
|
|
249
|
+
surfaceNormals.visible =
|
|
250
|
+
this.app.uiState.terrainVisualizationModes.normals || false;
|
|
248
251
|
batchGroup.add(surfaceNormals);
|
|
249
252
|
|
|
250
253
|
for (const [key, value] of Object.entries(
|
|
@@ -270,12 +273,35 @@ export class Terrain {
|
|
|
270
273
|
refreshBatchVisibility() {
|
|
271
274
|
const batchManager = this.app.batchManager;
|
|
272
275
|
if (!batchManager?.isBatchVisible || !this.group) return;
|
|
276
|
+
const showNormals = this.app.uiState.terrainVisualizationModes?.normals;
|
|
273
277
|
for (let i = 0; i < batchManager.simBatches; i++) {
|
|
274
|
-
|
|
275
|
-
|
|
278
|
+
// group.children[i] is batch i (built in order); getObjectByName
|
|
279
|
+
// searched every earlier batch's whole subtree, O(n^2) overall.
|
|
280
|
+
const batchGroup = this.group.children[i];
|
|
281
|
+
if (!batchGroup) continue;
|
|
282
|
+
batchGroup.visible = batchManager.isBatchVisible(i);
|
|
283
|
+
if (showNormals && batchGroup.visible) this.#ensureNormals(i);
|
|
276
284
|
}
|
|
277
285
|
}
|
|
278
286
|
|
|
287
|
+
// Builds batch i's normal arrows the first time they're wanted.
|
|
288
|
+
#ensureNormals(i) {
|
|
289
|
+
const target = this.group.children[i]?.getObjectByName("normals");
|
|
290
|
+
if (!target || target.userData.built || !this.#normals) return;
|
|
291
|
+
target.userData.built = true;
|
|
292
|
+
let source;
|
|
293
|
+
if (this.isSingleton) {
|
|
294
|
+
this.#singletonNormals ??= this.#createNormalVectors(
|
|
295
|
+
this.heightData[0],
|
|
296
|
+
this.#normals[0]
|
|
297
|
+
);
|
|
298
|
+
source = this.#singletonNormals.clone();
|
|
299
|
+
} else {
|
|
300
|
+
source = this.#createNormalVectors(this.heightData[i], this.#normals[i]);
|
|
301
|
+
}
|
|
302
|
+
target.add(...source.children);
|
|
303
|
+
}
|
|
304
|
+
|
|
279
305
|
// Normalize a value into [0, 1] against an explicit [min, max] range.
|
|
280
306
|
// Falls back to [0, 1] clamping when the range is missing or degenerate.
|
|
281
307
|
#normalizeToRange(value, min, max) {
|
|
@@ -489,12 +515,15 @@ export class Terrain {
|
|
|
489
515
|
|
|
490
516
|
// Shows/hides a named sub-object (surface/wireframe/normals) in every batch group.
|
|
491
517
|
toggleVisualization(type, visible) {
|
|
492
|
-
|
|
518
|
+
const batchManager = this.app.batchManager;
|
|
519
|
+
this.group.children.forEach((batchGroup, i) => {
|
|
493
520
|
const object = batchGroup.getObjectByName(type);
|
|
494
|
-
if (object)
|
|
495
|
-
|
|
521
|
+
if (!object) return;
|
|
522
|
+
object.visible = visible;
|
|
523
|
+
if (type === "normals" && visible && (batchManager?.isBatchVisible?.(i) ?? true)) {
|
|
524
|
+
this.#ensureNormals(i);
|
|
496
525
|
}
|
|
497
|
-
}
|
|
526
|
+
});
|
|
498
527
|
}
|
|
499
528
|
|
|
500
529
|
// Returns the per-batch data array for a diff layer name ("height", or
|
|
@@ -671,7 +700,7 @@ export class Terrain {
|
|
|
671
700
|
const batchesToUpdate = this.isSingleton ? 1 : this.app.batchManager.simBatches;
|
|
672
701
|
|
|
673
702
|
for (let i = 0; i < batchesToUpdate; i++) {
|
|
674
|
-
const batchGroup = this.group.
|
|
703
|
+
const batchGroup = this.group.children[i];
|
|
675
704
|
const surfaceMesh = batchGroup.getObjectByName("surface");
|
|
676
705
|
const geometry = surfaceMesh.geometry;
|
|
677
706
|
this.#updateSurfaceColor(i, geometry, callableColormap);
|
|
@@ -684,7 +713,7 @@ export class Terrain {
|
|
|
684
713
|
const batchesToUpdate = this.isSingleton ? 1 : this.app.batchManager.simBatches;
|
|
685
714
|
|
|
686
715
|
for (let i = 0; i < batchesToUpdate; i++) {
|
|
687
|
-
const batchGroup = this.group.
|
|
716
|
+
const batchGroup = this.group.children[i];
|
|
688
717
|
const surfaceMesh = batchGroup.getObjectByName("surface");
|
|
689
718
|
const geometry = surfaceMesh.geometry;
|
|
690
719
|
this.#updateSurfaceColor(i, geometry, callableColormap);
|
|
@@ -41,6 +41,17 @@
|
|
|
41
41
|
}
|
|
42
42
|
</script>
|
|
43
43
|
|
|
44
|
+
<!-- Start the model and states requests now rather than once the ES-module
|
|
45
|
+
graph has loaded, so they overlap it (SimView.loadData consumes
|
|
46
|
+
these). Skipped for the serverless viewer (#data=...), which has no
|
|
47
|
+
/model to ask. -->
|
|
48
|
+
<script>
|
|
49
|
+
if (/^https?:/.test(location.protocol) && !/[#&]data=/.test(location.hash) && !window.__simviewStaticBase) {
|
|
50
|
+
const quiet = (p) => (p.catch(() => {}), p);
|
|
51
|
+
window.__simviewPrefetch = { model: quiet(fetch("/model")), states: quiet(fetch("/states")) };
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
<!-- @modulepreload -->
|
|
44
55
|
<script type="module" src="./static/js/main.js"></script>
|
|
45
56
|
<script>
|
|
46
57
|
window.onerror = function(msg, url, line, col, error) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simview
|
|
3
|
-
Version: 6.3.
|
|
3
|
+
Version: 6.3.1
|
|
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
|
|
@@ -11,6 +11,7 @@ simview/launcher.py
|
|
|
11
11
|
simview/live.py
|
|
12
12
|
simview/merge.py
|
|
13
13
|
simview/model.py
|
|
14
|
+
simview/preload.py
|
|
14
15
|
simview/py.typed
|
|
15
16
|
simview/remote.py
|
|
16
17
|
simview/render.py
|
|
@@ -95,6 +96,7 @@ tests/test_launcher.py
|
|
|
95
96
|
tests/test_live.py
|
|
96
97
|
tests/test_merge.py
|
|
97
98
|
tests/test_pointcloud_features.py
|
|
99
|
+
tests/test_preload.py
|
|
98
100
|
tests/test_remote.py
|
|
99
101
|
tests/test_roundtrip.py
|
|
100
102
|
tests/test_scene.py
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from simview.preload import PLACEHOLDER, module_graph, render_index
|
|
4
|
+
from simview.server import INDEX_HTML, STATIC
|
|
5
|
+
|
|
6
|
+
ROOT = Path(STATIC).parent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_module_graph_reaches_every_viewer_module():
|
|
10
|
+
graph = set(module_graph(Path(INDEX_HTML).read_text(encoding="utf-8"), ROOT))
|
|
11
|
+
assert "./static/js/main.js" in graph
|
|
12
|
+
assert "./static/lib/three-0.174.0/three.core.js" in graph # via the import map
|
|
13
|
+
on_disk = {
|
|
14
|
+
"./" + p.relative_to(ROOT).as_posix()
|
|
15
|
+
for p in (ROOT / "static" / "js").rglob("*.js")
|
|
16
|
+
}
|
|
17
|
+
# A module the preload walk misses just loads late, but one that's on disk
|
|
18
|
+
# and unreachable is probably an import form the walk can't read.
|
|
19
|
+
assert on_disk <= graph
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_render_index_fills_the_placeholder():
|
|
23
|
+
html = render_index(INDEX_HTML, str(ROOT))
|
|
24
|
+
assert PLACEHOLDER not in html
|
|
25
|
+
assert '<link rel="modulepreload" href="./static/js/SimView.js" />' in html
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simview-6.3.0 → simview-6.3.1}/simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js
RENAMED
|
File without changes
|
{simview-6.3.0 → simview-6.3.1}/simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|