simview 3.2__py3-none-any.whl
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/__init__.py +69 -0
- simview/__main__.py +174 -0
- simview/launcher.py +109 -0
- simview/live.py +204 -0
- simview/merge.py +593 -0
- simview/model.py +690 -0
- simview/py.typed +0 -0
- simview/scene.py +603 -0
- simview/server.py +739 -0
- simview/state.py +190 -0
- simview/static/css/controls.css +36 -0
- simview/static/js/SimView.js +601 -0
- simview/static/js/components/AnimationController.js +654 -0
- simview/static/js/components/BatchManager.js +198 -0
- simview/static/js/components/InteractionController.js +446 -0
- simview/static/js/components/InteractionControls.js +116 -0
- simview/static/js/components/Scene.js +166 -0
- simview/static/js/components/StateStore.js +188 -0
- simview/static/js/config.js +233 -0
- simview/static/js/main.js +3 -0
- simview/static/js/objects/Body.js +616 -0
- simview/static/js/objects/StaticObject.js +181 -0
- simview/static/js/objects/Terrain.js +514 -0
- simview/static/js/objects/utils.js +378 -0
- simview/static/js/ui/AnalysisPanel.js +233 -0
- simview/static/js/ui/BatchLegend.js +207 -0
- simview/static/js/ui/BodyStateWindow.js +521 -0
- simview/static/js/ui/Controls.js +586 -0
- simview/static/js/ui/ErrorMetrics.js +664 -0
- simview/static/js/ui/Legend.js +87 -0
- simview/static/js/ui/PlaybackControls.js +314 -0
- simview/static/js/ui/ScalarPlotter.js +575 -0
- simview/static/js/utils/blobCodec.js +73 -0
- simview/static/js/utils/bodyTransforms.js +151 -0
- simview/static/js/utils/csv.js +46 -0
- simview/static/js/utils/errorMath.js +69 -0
- simview/static/js/utils/injectStyles.js +16 -0
- simview/static/js/utils/interpolate.js +65 -0
- simview/static/js/utils/liveFollow.js +17 -0
- simview/static/js/utils/loadRecordingLibs.js +34 -0
- simview/static/js/utils/viewState.js +226 -0
- simview/static/lib/chroma-js-3.1.2/index.min.js +8 -0
- simview/static/lib/chroma-js-3.1.2/src/Color.js +49 -0
- simview/static/lib/chroma-js-3.1.2/src/chroma.js +10 -0
- simview/static/lib/chroma-js-3.1.2/src/colors/colorbrewer.js +81 -0
- simview/static/lib/chroma-js-3.1.2/src/colors/w3cx11.js +164 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/average.js +88 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/bezier.js +86 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/blend.js +57 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/cubehelix.js +87 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/mix.js +19 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/random.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/scale.js +394 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/_hsx.js +59 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/hcg.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/hsi.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/hsl.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/hsv.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/index.js +1 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/lab.js +19 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/lch.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/lrgb.js +19 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/num.js +15 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/oklab.js +19 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/oklch.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/rgb.js +18 -0
- simview/static/lib/chroma-js-3.1.2/src/io/cmyk/cmyk2rgb.js +16 -0
- simview/static/lib/chroma-js-3.1.2/src/io/cmyk/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/cmyk/rgb2cmyk.js +17 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/css2rgb.js +238 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/hsl2css.js +26 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/lab2css.js +24 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/lch2css.js +24 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/oklab2css.js +16 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/oklch2css.js +16 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/rgb2css.js +61 -0
- simview/static/lib/chroma-js-3.1.2/src/io/gl/index.js +22 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hcg/hcg2rgb.js +55 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hcg/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hcg/rgb2hcg.js +23 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hex/hex2rgb.js +56 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hex/index.js +29 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hex/rgb2hex.js +29 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsi/hsi2rgb.js +45 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsi/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsi/rgb2hsi.js +31 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsl/hsl2rgb.js +35 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsl/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsl/rgb2hsl.js +45 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsv/hsv2rgb.js +47 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsv/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsv/rgb2hsv.js +32 -0
- simview/static/lib/chroma-js-3.1.2/src/io/input.js +4 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lab/index.js +28 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lab/lab-constants.js +120 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lab/lab2rgb.js +101 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lab/rgb2lab.js +67 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/hcl2rgb.js +9 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/index.js +35 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/lab2lch.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2lab.js +18 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2rgb.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/rgb2lch.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/io/named/index.js +30 -0
- simview/static/lib/chroma-js-3.1.2/src/io/num/index.js +32 -0
- simview/static/lib/chroma-js-3.1.2/src/io/num/num2rgb.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/io/num/rgb2num.js +8 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklab/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklab/oklab2rgb.js +34 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklab/rgb2oklab.js +37 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklch/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklch/oklch2rgb.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklch/rgb2oklch.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/io/rgb/index.js +44 -0
- simview/static/lib/chroma-js-3.1.2/src/io/temp/index.js +22 -0
- simview/static/lib/chroma-js-3.1.2/src/io/temp/rgb2temperature.js +30 -0
- simview/static/lib/chroma-js-3.1.2/src/io/temp/temperature2rgb.js +39 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/alpha.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/clipped.js +5 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/darken.js +17 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/get.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/luminance.js +54 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/mix.js +10 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/premultiply.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/saturate.js +15 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/set.js +43 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/shade.js +11 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/analyze.js +191 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/clip_rgb.js +15 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/contrast.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/contrastAPCA.js +68 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/delta-e.js +62 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/distance.js +17 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/last.js +8 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/limit.js +5 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/multiply-matrices.js +36 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/scales.js +15 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/type.js +18 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/unpack.js +17 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/valid.js +11 -0
- simview/static/lib/chroma-js-3.1.2/src/version.js +2 -0
- simview/static/lib/download.js +132 -0
- simview/static/lib/js-colormaps.js +19335 -0
- simview/static/lib/tar.js +334 -0
- simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +1556 -0
- simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +8 -0
- simview/static/lib/three-0.174.0/three.core.js +48830 -0
- simview/static/lib/three-0.174.0/three.module.js +17313 -0
- simview/static/lib/uPlot.esm.js +6140 -0
- simview/static/lib/uPlot.min.css +2 -0
- simview/static/textures/contacts/red-cross0.png +0 -0
- simview/static/textures/points/ball0.png +0 -0
- simview/static/textures/points/ball1.png +0 -0
- simview/templates/index.html +81 -0
- simview/utils.py +41 -0
- simview-3.2.dist-info/METADATA +552 -0
- simview-3.2.dist-info/RECORD +163 -0
- simview-3.2.dist-info/WHEEL +5 -0
- simview-3.2.dist-info/entry_points.txt +2 -0
- simview-3.2.dist-info/licenses/LICENSE +28 -0
- simview-3.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import { FREQ_CONFIG } from "../config.js";
|
|
2
|
+
import { injectStyles } from "../utils/injectStyles.js";
|
|
3
|
+
|
|
4
|
+
export class BodyStateWindow {
|
|
5
|
+
static styleId = "body-state-window-styles";
|
|
6
|
+
constructor(app) {
|
|
7
|
+
this.app = app;
|
|
8
|
+
this.selectedBodies = new Set();
|
|
9
|
+
this.window = null;
|
|
10
|
+
this.batchSelector = null;
|
|
11
|
+
this.header = null; // Keep track of header element
|
|
12
|
+
this.content = null;
|
|
13
|
+
this.bodyListContainer = null;
|
|
14
|
+
this.detailsContainer = null;
|
|
15
|
+
this.bodyListItems = new Map(); // Map body name -> list item element
|
|
16
|
+
this.detailContainers = new Map(); // Map body name -> detail container element
|
|
17
|
+
this.minRenderDelay = 1000 / FREQ_CONFIG.bodyStateWindow; // 60 FPS
|
|
18
|
+
this.lastRenderTime = Number.NEGATIVE_INFINITY;
|
|
19
|
+
|
|
20
|
+
this.injectCSS(); // Inject CSS styles
|
|
21
|
+
this.initWindow();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
injectCSS() {
|
|
25
|
+
const css = `
|
|
26
|
+
/* Base styles for the body state window */
|
|
27
|
+
.body-state-window {
|
|
28
|
+
position: fixed;
|
|
29
|
+
top: 10px;
|
|
30
|
+
left: 1rem;
|
|
31
|
+
background-color: rgba(0, 0, 0, 0.75); /* Slightly adjusted alpha */
|
|
32
|
+
color: white;
|
|
33
|
+
border-radius: 8px; /* Slightly larger radius */
|
|
34
|
+
font-family: Arial, sans-serif;
|
|
35
|
+
z-index: 1000;
|
|
36
|
+
display: flex; /* Use flexbox for layout */
|
|
37
|
+
flex-direction: column; /* Stack header and content vertically */
|
|
38
|
+
width: 320px;
|
|
39
|
+
max-height: clamp(300px, 85vh, 90vh);
|
|
40
|
+
font-size: clamp(0.8rem, 0.9vw + 0.5rem, 1rem); /* Responsive base font */
|
|
41
|
+
padding: 1.0rem; /* ~20px if 1rem=16px */
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Allow content area to scroll */
|
|
45
|
+
.body-state-window-content {
|
|
46
|
+
overflow-y: auto; /* Enable vertical scrolling ONLY for content */
|
|
47
|
+
overflow-x: hidden; /* Prevent horizontal scroll */
|
|
48
|
+
flex-grow: 1; /* Allow content to fill available space */
|
|
49
|
+
padding-top: 0.5rem;
|
|
50
|
+
margin-top: 0.5rem;
|
|
51
|
+
border-top: 1px solid rgba(255, 255, 255, 0.5); /* Separator line */
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Header styles */
|
|
55
|
+
.body-state-window-header {
|
|
56
|
+
font-weight: bold;
|
|
57
|
+
font-size: 1.1em; /* Relative to parent window font size */
|
|
58
|
+
display: flex;
|
|
59
|
+
justify-content: space-between;
|
|
60
|
+
align-items: center;
|
|
61
|
+
flex-shrink: 0; /* Prevent header from shrinking */
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Batch selector container */
|
|
65
|
+
.batch-selector-container {
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
font-size: 0.8em; /* Smaller font size relative to header */
|
|
69
|
+
font-weight: normal; /* Normal weight for selector part */
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.batch-selector-label {
|
|
73
|
+
margin-right: 0.5em; /* Relative spacing */
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.batch-selector {
|
|
77
|
+
background-color: rgba(50, 50, 50, 0.8);
|
|
78
|
+
color: white;
|
|
79
|
+
border: 1px solid white;
|
|
80
|
+
padding: 0.25em 0.5em; /* Relative padding */
|
|
81
|
+
border-radius: 3px;
|
|
82
|
+
font-size: 1em; /* Inherit from container */
|
|
83
|
+
}
|
|
84
|
+
.batch-selector:focus {
|
|
85
|
+
outline: 1px solid skyblue; /* Add focus indicator */
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.body-list-title {
|
|
89
|
+
font-weight: bold;
|
|
90
|
+
margin-bottom: 0.5rem; /* Use rem */
|
|
91
|
+
font-size: 1.0em; /* Slightly larger */
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.body-list {
|
|
95
|
+
margin: 0 0 1rem 0; /* Bottom margin */
|
|
96
|
+
padding: 0 0 0 1.25rem; /* ~20px indent */
|
|
97
|
+
list-style-type: "→ "; /* Remove default bullets */
|
|
98
|
+
font-weight: bold;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.body-list-item {
|
|
102
|
+
margin-bottom: 0.3rem; /* Use rem */
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
padding: 0.25rem 0.5rem; /* Use rem */
|
|
105
|
+
border-radius: 3px;
|
|
106
|
+
transition: background-color 0.2s;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Hover effect for non-selected items */
|
|
110
|
+
.body-list-item:not(.selected):hover {
|
|
111
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Style for selected items */
|
|
115
|
+
.body-list-item.selected {
|
|
116
|
+
background-color: rgba(100, 150, 255, 0.2); /* Use a selection color */
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Details container */
|
|
120
|
+
.details-container { } /* Container for all detail blocks */
|
|
121
|
+
|
|
122
|
+
/* Individual body detail block */
|
|
123
|
+
.body-detail-container {
|
|
124
|
+
margin-top: 0.75rem; /* Use rem */
|
|
125
|
+
padding: 0.75rem; /* Use rem */
|
|
126
|
+
padding-top: 1.5rem; /* Extra top padding for close button */
|
|
127
|
+
background-color: rgba(50, 50, 50, 0.6); /* Slightly more contrast */
|
|
128
|
+
border-radius: 5px;
|
|
129
|
+
position: relative;
|
|
130
|
+
border: 1px solid rgba(255, 255, 255, 0.1); /* Subtle border */
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Close button */
|
|
134
|
+
.body-detail-close-button {
|
|
135
|
+
position: absolute;
|
|
136
|
+
top: 0.3rem; /* Use rem */
|
|
137
|
+
right: 0.5rem; /* Use rem */
|
|
138
|
+
cursor: pointer;
|
|
139
|
+
font-size: 1.1em; /* Relative size */
|
|
140
|
+
color: #aaa;
|
|
141
|
+
width: 1.5em; /* Relative size */
|
|
142
|
+
height: 1.5em; /* Relative size */
|
|
143
|
+
text-align: center;
|
|
144
|
+
line-height: 1.5em; /* Center vertically */
|
|
145
|
+
border-radius: 50%;
|
|
146
|
+
transition: background-color 0.2s, color 0.2s;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.body-detail-close-button:hover {
|
|
150
|
+
background-color: rgba(255, 255, 255, 0.2);
|
|
151
|
+
color: white;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* Header within the detail block */
|
|
155
|
+
.body-detail-header {
|
|
156
|
+
font-weight: bold;
|
|
157
|
+
margin-bottom: 0.75rem; /* Use rem */
|
|
158
|
+
font-size: 1.1em; /* Slightly larger */
|
|
159
|
+
color: #eee;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* Table within the detail block */
|
|
163
|
+
.body-detail-table {
|
|
164
|
+
width: 100%;
|
|
165
|
+
font-size: 0.9em; /* Slightly smaller relative font */
|
|
166
|
+
border-collapse: collapse;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.body-detail-table td {
|
|
170
|
+
padding: 0.25rem 0; /* Use rem for padding */
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.body-detail-table td:first-child { /* Label cell */
|
|
174
|
+
font-weight: bold;
|
|
175
|
+
color: #ccc; /* Lighter gray */
|
|
176
|
+
white-space: nowrap; /* Prevent labels wrapping */
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.body-detail-table td:last-child { /* Value cell */
|
|
180
|
+
font-family: monospace;
|
|
181
|
+
color: #f0f0f0; /* Brighter value text */
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* Collapsed state */
|
|
185
|
+
.body-state-window.collapsed {
|
|
186
|
+
height: auto !important;
|
|
187
|
+
max-height: none !important;
|
|
188
|
+
padding-bottom: 0.5rem;
|
|
189
|
+
}
|
|
190
|
+
.body-state-window.collapsed .body-state-window-content {
|
|
191
|
+
display: none;
|
|
192
|
+
}
|
|
193
|
+
.body-state-window-toggle-icon {
|
|
194
|
+
display: inline-block;
|
|
195
|
+
user-select: none;
|
|
196
|
+
padding-right: 0.4rem;
|
|
197
|
+
color: #ccc;
|
|
198
|
+
}
|
|
199
|
+
`;
|
|
200
|
+
injectStyles(BodyStateWindow.styleId, css);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
initWindow() {
|
|
204
|
+
// --- Window setup ---
|
|
205
|
+
this.window = document.createElement("div");
|
|
206
|
+
this.window.classList.add("body-state-window"); // Use CSS class
|
|
207
|
+
|
|
208
|
+
// --- Header setup ---
|
|
209
|
+
this.header = document.createElement("div");
|
|
210
|
+
this.header.classList.add("body-state-window-header"); // Use CSS class
|
|
211
|
+
this.header.style.cursor = "pointer";
|
|
212
|
+
this.header.addEventListener("click", () => this.toggleCollapse());
|
|
213
|
+
this.window.appendChild(this.header);
|
|
214
|
+
|
|
215
|
+
const titleGroup = document.createElement("div"); // Group title and toggle
|
|
216
|
+
titleGroup.style.display = "flex";
|
|
217
|
+
titleGroup.style.alignItems = "center";
|
|
218
|
+
this.header.appendChild(titleGroup);
|
|
219
|
+
|
|
220
|
+
this.toggleIcon = document.createElement("span");
|
|
221
|
+
this.toggleIcon.classList.add("body-state-window-toggle-icon");
|
|
222
|
+
this.toggleIcon.textContent = "▾"; // Default state is expanded
|
|
223
|
+
titleGroup.appendChild(this.toggleIcon);
|
|
224
|
+
|
|
225
|
+
const title = document.createElement("span");
|
|
226
|
+
title.textContent = "Body states";
|
|
227
|
+
titleGroup.appendChild(title);
|
|
228
|
+
|
|
229
|
+
// --- Batch Selector (if needed) ---
|
|
230
|
+
if (this.app.batchManager && this.app.batchManager.getSimBatches) {
|
|
231
|
+
const batchSize = this.app.batchManager.getSimBatches();
|
|
232
|
+
if (batchSize > 1) {
|
|
233
|
+
const selectorContainer = document.createElement("div");
|
|
234
|
+
selectorContainer.classList.add("batch-selector-container"); // Use CSS class
|
|
235
|
+
|
|
236
|
+
const label = document.createElement("span");
|
|
237
|
+
label.textContent = "Batch: ";
|
|
238
|
+
label.classList.add("batch-selector-label"); // Use CSS class
|
|
239
|
+
selectorContainer.appendChild(label);
|
|
240
|
+
|
|
241
|
+
const batchSelector = document.createElement("select");
|
|
242
|
+
batchSelector.classList.add("batch-selector"); // Use CSS class
|
|
243
|
+
|
|
244
|
+
for (let i = 0; i < batchSize; i++) {
|
|
245
|
+
const option = document.createElement("option");
|
|
246
|
+
option.value = i;
|
|
247
|
+
option.textContent = `${i}`;
|
|
248
|
+
batchSelector.appendChild(option);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
batchSelector.addEventListener("change", (e) => {
|
|
252
|
+
const batchIndex = parseInt(e.target.value);
|
|
253
|
+
this.app.batchManager.setActiveBatch(batchIndex);
|
|
254
|
+
e.target.blur(); // Remove focus from the selector
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
selectorContainer.addEventListener("click", (e) => e.stopPropagation());
|
|
258
|
+
selectorContainer.appendChild(batchSelector);
|
|
259
|
+
this.header.appendChild(selectorContainer); // Append to header
|
|
260
|
+
this.batchSelector = batchSelector;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// --- Content Area (for scrolling) ---
|
|
265
|
+
this.content = document.createElement("div");
|
|
266
|
+
this.content.classList.add("body-state-window-content"); // Use CSS class for scrolling
|
|
267
|
+
this.window.appendChild(this.content);
|
|
268
|
+
|
|
269
|
+
// --- Body List and Details Containers (inside the content area) ---
|
|
270
|
+
this.bodyListContainer = document.createElement("div");
|
|
271
|
+
this.bodyListContainer.classList.add("body-list-container"); // Use CSS class
|
|
272
|
+
this.content.appendChild(this.bodyListContainer);
|
|
273
|
+
|
|
274
|
+
this.detailsContainer = document.createElement("div");
|
|
275
|
+
this.detailsContainer.classList.add("details-container"); // Use CSS class
|
|
276
|
+
this.content.appendChild(this.detailsContainer);
|
|
277
|
+
|
|
278
|
+
document.body.appendChild(this.window);
|
|
279
|
+
|
|
280
|
+
// --- Event Listener (unchanged) ---
|
|
281
|
+
window.addEventListener("keydown", (event) => {
|
|
282
|
+
if (event.code === "Space") {
|
|
283
|
+
const activeElement = document.activeElement;
|
|
284
|
+
if (activeElement === this.batchSelector) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
event.preventDefault();
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Initialize body list once
|
|
292
|
+
this.updateBodyList();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
toggleCollapse() {
|
|
296
|
+
if (!this.window) return;
|
|
297
|
+
const isCollapsed = this.window.classList.toggle("collapsed");
|
|
298
|
+
this.toggleIcon.textContent = isCollapsed ? "▸" : "▾";
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
updateBodyList() {
|
|
302
|
+
this.bodyListContainer.innerHTML = ""; // Clear previous content
|
|
303
|
+
this.bodyListItems.clear();
|
|
304
|
+
|
|
305
|
+
const title = document.createElement("div");
|
|
306
|
+
title.textContent = "Bodies:";
|
|
307
|
+
title.classList.add("body-list-title"); // Use CSS class
|
|
308
|
+
this.bodyListContainer.appendChild(title);
|
|
309
|
+
|
|
310
|
+
const list = document.createElement("ul");
|
|
311
|
+
list.classList.add("body-list"); // Use CSS class
|
|
312
|
+
|
|
313
|
+
for (const name of this.app.bodies.keys()) {
|
|
314
|
+
const item = document.createElement("li");
|
|
315
|
+
item.classList.add("body-list-item"); // Use CSS class
|
|
316
|
+
item.textContent = name;
|
|
317
|
+
item.dataset.bodyName = name; // Store name for easier access if needed
|
|
318
|
+
|
|
319
|
+
// Add click handler for selection toggle
|
|
320
|
+
item.addEventListener("click", () => {
|
|
321
|
+
// Use arrow function for concise 'this'
|
|
322
|
+
if (this.selectedBodies.has(name)) {
|
|
323
|
+
this.deselectBody(name);
|
|
324
|
+
} else {
|
|
325
|
+
this.selectBody(name);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
list.appendChild(item);
|
|
330
|
+
this.bodyListItems.set(name, item);
|
|
331
|
+
|
|
332
|
+
// Initial style update (if it might be pre-selected somehow)
|
|
333
|
+
this.updateBodyListItemStyle(name);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
this.bodyListContainer.appendChild(list);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
selectBody(name) {
|
|
340
|
+
if (!this.selectedBodies.has(name)) {
|
|
341
|
+
this.selectedBodies.add(name);
|
|
342
|
+
const body = this.app.bodies.get(name);
|
|
343
|
+
if (body) {
|
|
344
|
+
const container = this.createBodyDetailContainer(body);
|
|
345
|
+
this.detailsContainer.appendChild(container);
|
|
346
|
+
this.detailContainers.set(name, container);
|
|
347
|
+
this.updateBodyListItemStyle(name); // Update style via class
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
deselectBody(name) {
|
|
353
|
+
if (this.selectedBodies.has(name)) {
|
|
354
|
+
this.selectedBodies.delete(name);
|
|
355
|
+
const container = this.detailContainers.get(name);
|
|
356
|
+
if (container) {
|
|
357
|
+
this.detailsContainer.removeChild(container);
|
|
358
|
+
this.detailContainers.delete(name);
|
|
359
|
+
}
|
|
360
|
+
this.updateBodyListItemStyle(name); // Update style via class
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
updateBodyListItemStyle(name) {
|
|
365
|
+
const item = this.bodyListItems.get(name);
|
|
366
|
+
if (item) {
|
|
367
|
+
// Toggle 'selected' class based on the set
|
|
368
|
+
if (this.selectedBodies.has(name)) {
|
|
369
|
+
item.classList.add("selected");
|
|
370
|
+
} else {
|
|
371
|
+
item.classList.remove("selected");
|
|
372
|
+
}
|
|
373
|
+
// Hover styles are now handled purely by CSS :hover pseudo-class
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
createBodyDetailContainer(body) {
|
|
378
|
+
const container = document.createElement("div");
|
|
379
|
+
container.classList.add("body-detail-container"); // Use CSS class
|
|
380
|
+
|
|
381
|
+
// Close button
|
|
382
|
+
const closeButton = document.createElement("div");
|
|
383
|
+
closeButton.innerHTML = "✕"; // Keep content
|
|
384
|
+
closeButton.classList.add("body-detail-close-button"); // Use CSS class
|
|
385
|
+
// Hover effect is handled by CSS
|
|
386
|
+
|
|
387
|
+
closeButton.addEventListener("click", (e) => {
|
|
388
|
+
e.stopPropagation(); // Prevent triggering other clicks
|
|
389
|
+
this.deselectBody(body.name);
|
|
390
|
+
});
|
|
391
|
+
container.appendChild(closeButton);
|
|
392
|
+
|
|
393
|
+
// Details header
|
|
394
|
+
const detailsHeader = document.createElement("div");
|
|
395
|
+
detailsHeader.classList.add("body-detail-header"); // Use CSS class
|
|
396
|
+
container.appendChild(detailsHeader);
|
|
397
|
+
container.header = detailsHeader; // Keep reference for updates
|
|
398
|
+
|
|
399
|
+
// Details table
|
|
400
|
+
const table = document.createElement("table");
|
|
401
|
+
table.classList.add("body-detail-table"); // Use CSS class
|
|
402
|
+
|
|
403
|
+
const properties = [
|
|
404
|
+
{ key: "positions", label: "Position" },
|
|
405
|
+
{ key: "rotations", label: "Rotation" },
|
|
406
|
+
// { key: "linearVelocity", label: "Velocity" }, // Abbreviate slightly if needed
|
|
407
|
+
// { key: "angularVelocity", label: "Ang. Vel." },
|
|
408
|
+
// { key: "linearForce", label: "Force" },
|
|
409
|
+
// { key: "torque", label: "Torque" },
|
|
410
|
+
];
|
|
411
|
+
|
|
412
|
+
const valueCells = {}; // Keep reference for updates
|
|
413
|
+
for (const prop of properties) {
|
|
414
|
+
if (body.availableAttributes.has(prop.key) || body[prop.key]) {
|
|
415
|
+
const row = table.insertRow();
|
|
416
|
+
const labelCell = row.insertCell(0);
|
|
417
|
+
labelCell.textContent = prop.label;
|
|
418
|
+
// Styling handled by CSS '.body-detail-table td:first-child'
|
|
419
|
+
const valueCell = row.insertCell(1);
|
|
420
|
+
// Styling handled by CSS '.body-detail-table td:last-child'
|
|
421
|
+
valueCells[prop.key] = valueCell; // Store reference to the cell
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
container.appendChild(table);
|
|
426
|
+
container.valueCells = valueCells; // Attach value cell references to the container
|
|
427
|
+
|
|
428
|
+
// Initial update of content
|
|
429
|
+
this.updateBodyDetailContainer(container, body);
|
|
430
|
+
return container;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// --- update, updateBodyDetailContainer, setSelectedBatch, show, hide, dispose, forceRedraw, animate ---
|
|
434
|
+
// These methods should remain largely unchanged as they deal with data logic,
|
|
435
|
+
// not the initial setup and styling which we've refactored.
|
|
436
|
+
|
|
437
|
+
update() {
|
|
438
|
+
// Only update existing detail containers
|
|
439
|
+
for (const [name, container] of this.detailContainers) {
|
|
440
|
+
const body = this.app.bodies.get(name);
|
|
441
|
+
if (body) {
|
|
442
|
+
this.updateBodyDetailContainer(container, body);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
updateBodyDetailContainer(container, body) {
|
|
448
|
+
// Check if batchManager exists and has the required properties/methods
|
|
449
|
+
const batchManager = this.app.batchManager;
|
|
450
|
+
let batchText = "";
|
|
451
|
+
const batchIndex = batchManager.currentlyActiveBatch;
|
|
452
|
+
const batchSize = batchManager.getSimBatches();
|
|
453
|
+
if (batchSize > 1) {
|
|
454
|
+
batchText = ` (Batch ${batchIndex})`;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
container.header.textContent = `${body.name}${batchText}`;
|
|
458
|
+
|
|
459
|
+
for (const [key, cell] of Object.entries(container.valueCells)) {
|
|
460
|
+
// Ensure the property and the specific batch index exist before accessing
|
|
461
|
+
var vector = null;
|
|
462
|
+
if (body[key] && body[key][batchIndex]) {
|
|
463
|
+
vector = body[key][batchIndex];
|
|
464
|
+
} else if (body.availableAttributes.has(key)) {
|
|
465
|
+
vector = body.attributeStorage.get(key)[batchIndex];
|
|
466
|
+
}
|
|
467
|
+
// Check if vector has x, y, z properties before calling toFixed
|
|
468
|
+
if (
|
|
469
|
+
vector &&
|
|
470
|
+
typeof vector.x === "number" &&
|
|
471
|
+
typeof vector.y === "number" &&
|
|
472
|
+
typeof vector.z === "number"
|
|
473
|
+
) {
|
|
474
|
+
const text = `(${vector.x.toFixed(3)}, ${vector.y.toFixed(3)}, ${vector.z.toFixed(3)})`;
|
|
475
|
+
if (cell.textContent !== text) cell.textContent = text;
|
|
476
|
+
} else {
|
|
477
|
+
if (cell.textContent !== "N/A") cell.textContent = "N/A";
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
setSelectedBatch(batchIndex) {
|
|
483
|
+
if (this.batchSelector) {
|
|
484
|
+
this.batchSelector.value = batchIndex;
|
|
485
|
+
}
|
|
486
|
+
this.update(); // Update displayed details for the new batch
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
show() {
|
|
490
|
+
if (this.window) {
|
|
491
|
+
this.window.style.display = "flex"; // Use flex since the class uses it
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
hide() {
|
|
496
|
+
if (this.window) {
|
|
497
|
+
this.window.style.display = "none";
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
dispose() {
|
|
502
|
+
if (this.window && this.window.parentNode) {
|
|
503
|
+
this.window.parentNode.removeChild(this.window);
|
|
504
|
+
this.window = null; // Clear reference
|
|
505
|
+
// Could also remove the keydown listener here if necessary
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
forceRedraw() {
|
|
510
|
+
this.lastRenderTime = Number.NEGATIVE_INFINITY;
|
|
511
|
+
this.update();
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
animate(now) {
|
|
515
|
+
if (!this.window || this.window.style.display === "none") return; // Don't update if hidden
|
|
516
|
+
|
|
517
|
+
if (now - this.lastRenderTime < this.minRenderDelay) return;
|
|
518
|
+
this.lastRenderTime = now;
|
|
519
|
+
this.update();
|
|
520
|
+
}
|
|
521
|
+
}
|