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.
Files changed (163) hide show
  1. simview/__init__.py +69 -0
  2. simview/__main__.py +174 -0
  3. simview/launcher.py +109 -0
  4. simview/live.py +204 -0
  5. simview/merge.py +593 -0
  6. simview/model.py +690 -0
  7. simview/py.typed +0 -0
  8. simview/scene.py +603 -0
  9. simview/server.py +739 -0
  10. simview/state.py +190 -0
  11. simview/static/css/controls.css +36 -0
  12. simview/static/js/SimView.js +601 -0
  13. simview/static/js/components/AnimationController.js +654 -0
  14. simview/static/js/components/BatchManager.js +198 -0
  15. simview/static/js/components/InteractionController.js +446 -0
  16. simview/static/js/components/InteractionControls.js +116 -0
  17. simview/static/js/components/Scene.js +166 -0
  18. simview/static/js/components/StateStore.js +188 -0
  19. simview/static/js/config.js +233 -0
  20. simview/static/js/main.js +3 -0
  21. simview/static/js/objects/Body.js +616 -0
  22. simview/static/js/objects/StaticObject.js +181 -0
  23. simview/static/js/objects/Terrain.js +514 -0
  24. simview/static/js/objects/utils.js +378 -0
  25. simview/static/js/ui/AnalysisPanel.js +233 -0
  26. simview/static/js/ui/BatchLegend.js +207 -0
  27. simview/static/js/ui/BodyStateWindow.js +521 -0
  28. simview/static/js/ui/Controls.js +586 -0
  29. simview/static/js/ui/ErrorMetrics.js +664 -0
  30. simview/static/js/ui/Legend.js +87 -0
  31. simview/static/js/ui/PlaybackControls.js +314 -0
  32. simview/static/js/ui/ScalarPlotter.js +575 -0
  33. simview/static/js/utils/blobCodec.js +73 -0
  34. simview/static/js/utils/bodyTransforms.js +151 -0
  35. simview/static/js/utils/csv.js +46 -0
  36. simview/static/js/utils/errorMath.js +69 -0
  37. simview/static/js/utils/injectStyles.js +16 -0
  38. simview/static/js/utils/interpolate.js +65 -0
  39. simview/static/js/utils/liveFollow.js +17 -0
  40. simview/static/js/utils/loadRecordingLibs.js +34 -0
  41. simview/static/js/utils/viewState.js +226 -0
  42. simview/static/lib/chroma-js-3.1.2/index.min.js +8 -0
  43. simview/static/lib/chroma-js-3.1.2/src/Color.js +49 -0
  44. simview/static/lib/chroma-js-3.1.2/src/chroma.js +10 -0
  45. simview/static/lib/chroma-js-3.1.2/src/colors/colorbrewer.js +81 -0
  46. simview/static/lib/chroma-js-3.1.2/src/colors/w3cx11.js +164 -0
  47. simview/static/lib/chroma-js-3.1.2/src/generator/average.js +88 -0
  48. simview/static/lib/chroma-js-3.1.2/src/generator/bezier.js +86 -0
  49. simview/static/lib/chroma-js-3.1.2/src/generator/blend.js +57 -0
  50. simview/static/lib/chroma-js-3.1.2/src/generator/cubehelix.js +87 -0
  51. simview/static/lib/chroma-js-3.1.2/src/generator/mix.js +19 -0
  52. simview/static/lib/chroma-js-3.1.2/src/generator/random.js +12 -0
  53. simview/static/lib/chroma-js-3.1.2/src/generator/scale.js +394 -0
  54. simview/static/lib/chroma-js-3.1.2/src/interpolator/_hsx.js +59 -0
  55. simview/static/lib/chroma-js-3.1.2/src/interpolator/hcg.js +12 -0
  56. simview/static/lib/chroma-js-3.1.2/src/interpolator/hsi.js +12 -0
  57. simview/static/lib/chroma-js-3.1.2/src/interpolator/hsl.js +12 -0
  58. simview/static/lib/chroma-js-3.1.2/src/interpolator/hsv.js +12 -0
  59. simview/static/lib/chroma-js-3.1.2/src/interpolator/index.js +1 -0
  60. simview/static/lib/chroma-js-3.1.2/src/interpolator/lab.js +19 -0
  61. simview/static/lib/chroma-js-3.1.2/src/interpolator/lch.js +13 -0
  62. simview/static/lib/chroma-js-3.1.2/src/interpolator/lrgb.js +19 -0
  63. simview/static/lib/chroma-js-3.1.2/src/interpolator/num.js +15 -0
  64. simview/static/lib/chroma-js-3.1.2/src/interpolator/oklab.js +19 -0
  65. simview/static/lib/chroma-js-3.1.2/src/interpolator/oklch.js +12 -0
  66. simview/static/lib/chroma-js-3.1.2/src/interpolator/rgb.js +18 -0
  67. simview/static/lib/chroma-js-3.1.2/src/io/cmyk/cmyk2rgb.js +16 -0
  68. simview/static/lib/chroma-js-3.1.2/src/io/cmyk/index.js +27 -0
  69. simview/static/lib/chroma-js-3.1.2/src/io/cmyk/rgb2cmyk.js +17 -0
  70. simview/static/lib/chroma-js-3.1.2/src/io/css/css2rgb.js +238 -0
  71. simview/static/lib/chroma-js-3.1.2/src/io/css/hsl2css.js +26 -0
  72. simview/static/lib/chroma-js-3.1.2/src/io/css/index.js +27 -0
  73. simview/static/lib/chroma-js-3.1.2/src/io/css/lab2css.js +24 -0
  74. simview/static/lib/chroma-js-3.1.2/src/io/css/lch2css.js +24 -0
  75. simview/static/lib/chroma-js-3.1.2/src/io/css/oklab2css.js +16 -0
  76. simview/static/lib/chroma-js-3.1.2/src/io/css/oklch2css.js +16 -0
  77. simview/static/lib/chroma-js-3.1.2/src/io/css/rgb2css.js +61 -0
  78. simview/static/lib/chroma-js-3.1.2/src/io/gl/index.js +22 -0
  79. simview/static/lib/chroma-js-3.1.2/src/io/hcg/hcg2rgb.js +55 -0
  80. simview/static/lib/chroma-js-3.1.2/src/io/hcg/index.js +27 -0
  81. simview/static/lib/chroma-js-3.1.2/src/io/hcg/rgb2hcg.js +23 -0
  82. simview/static/lib/chroma-js-3.1.2/src/io/hex/hex2rgb.js +56 -0
  83. simview/static/lib/chroma-js-3.1.2/src/io/hex/index.js +29 -0
  84. simview/static/lib/chroma-js-3.1.2/src/io/hex/rgb2hex.js +29 -0
  85. simview/static/lib/chroma-js-3.1.2/src/io/hsi/hsi2rgb.js +45 -0
  86. simview/static/lib/chroma-js-3.1.2/src/io/hsi/index.js +27 -0
  87. simview/static/lib/chroma-js-3.1.2/src/io/hsi/rgb2hsi.js +31 -0
  88. simview/static/lib/chroma-js-3.1.2/src/io/hsl/hsl2rgb.js +35 -0
  89. simview/static/lib/chroma-js-3.1.2/src/io/hsl/index.js +27 -0
  90. simview/static/lib/chroma-js-3.1.2/src/io/hsl/rgb2hsl.js +45 -0
  91. simview/static/lib/chroma-js-3.1.2/src/io/hsv/hsv2rgb.js +47 -0
  92. simview/static/lib/chroma-js-3.1.2/src/io/hsv/index.js +27 -0
  93. simview/static/lib/chroma-js-3.1.2/src/io/hsv/rgb2hsv.js +32 -0
  94. simview/static/lib/chroma-js-3.1.2/src/io/input.js +4 -0
  95. simview/static/lib/chroma-js-3.1.2/src/io/lab/index.js +28 -0
  96. simview/static/lib/chroma-js-3.1.2/src/io/lab/lab-constants.js +120 -0
  97. simview/static/lib/chroma-js-3.1.2/src/io/lab/lab2rgb.js +101 -0
  98. simview/static/lib/chroma-js-3.1.2/src/io/lab/rgb2lab.js +67 -0
  99. simview/static/lib/chroma-js-3.1.2/src/io/lch/hcl2rgb.js +9 -0
  100. simview/static/lib/chroma-js-3.1.2/src/io/lch/index.js +35 -0
  101. simview/static/lib/chroma-js-3.1.2/src/io/lch/lab2lch.js +12 -0
  102. simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2lab.js +18 -0
  103. simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2rgb.js +13 -0
  104. simview/static/lib/chroma-js-3.1.2/src/io/lch/rgb2lch.js +12 -0
  105. simview/static/lib/chroma-js-3.1.2/src/io/named/index.js +30 -0
  106. simview/static/lib/chroma-js-3.1.2/src/io/num/index.js +32 -0
  107. simview/static/lib/chroma-js-3.1.2/src/io/num/num2rgb.js +13 -0
  108. simview/static/lib/chroma-js-3.1.2/src/io/num/rgb2num.js +8 -0
  109. simview/static/lib/chroma-js-3.1.2/src/io/oklab/index.js +27 -0
  110. simview/static/lib/chroma-js-3.1.2/src/io/oklab/oklab2rgb.js +34 -0
  111. simview/static/lib/chroma-js-3.1.2/src/io/oklab/rgb2oklab.js +37 -0
  112. simview/static/lib/chroma-js-3.1.2/src/io/oklch/index.js +27 -0
  113. simview/static/lib/chroma-js-3.1.2/src/io/oklch/oklch2rgb.js +13 -0
  114. simview/static/lib/chroma-js-3.1.2/src/io/oklch/rgb2oklch.js +12 -0
  115. simview/static/lib/chroma-js-3.1.2/src/io/rgb/index.js +44 -0
  116. simview/static/lib/chroma-js-3.1.2/src/io/temp/index.js +22 -0
  117. simview/static/lib/chroma-js-3.1.2/src/io/temp/rgb2temperature.js +30 -0
  118. simview/static/lib/chroma-js-3.1.2/src/io/temp/temperature2rgb.js +39 -0
  119. simview/static/lib/chroma-js-3.1.2/src/ops/alpha.js +13 -0
  120. simview/static/lib/chroma-js-3.1.2/src/ops/clipped.js +5 -0
  121. simview/static/lib/chroma-js-3.1.2/src/ops/darken.js +17 -0
  122. simview/static/lib/chroma-js-3.1.2/src/ops/get.js +13 -0
  123. simview/static/lib/chroma-js-3.1.2/src/ops/luminance.js +54 -0
  124. simview/static/lib/chroma-js-3.1.2/src/ops/mix.js +10 -0
  125. simview/static/lib/chroma-js-3.1.2/src/ops/premultiply.js +12 -0
  126. simview/static/lib/chroma-js-3.1.2/src/ops/saturate.js +15 -0
  127. simview/static/lib/chroma-js-3.1.2/src/ops/set.js +43 -0
  128. simview/static/lib/chroma-js-3.1.2/src/ops/shade.js +11 -0
  129. simview/static/lib/chroma-js-3.1.2/src/utils/analyze.js +191 -0
  130. simview/static/lib/chroma-js-3.1.2/src/utils/clip_rgb.js +15 -0
  131. simview/static/lib/chroma-js-3.1.2/src/utils/contrast.js +12 -0
  132. simview/static/lib/chroma-js-3.1.2/src/utils/contrastAPCA.js +68 -0
  133. simview/static/lib/chroma-js-3.1.2/src/utils/delta-e.js +62 -0
  134. simview/static/lib/chroma-js-3.1.2/src/utils/distance.js +17 -0
  135. simview/static/lib/chroma-js-3.1.2/src/utils/index.js +27 -0
  136. simview/static/lib/chroma-js-3.1.2/src/utils/last.js +8 -0
  137. simview/static/lib/chroma-js-3.1.2/src/utils/limit.js +5 -0
  138. simview/static/lib/chroma-js-3.1.2/src/utils/multiply-matrices.js +36 -0
  139. simview/static/lib/chroma-js-3.1.2/src/utils/scales.js +15 -0
  140. simview/static/lib/chroma-js-3.1.2/src/utils/type.js +18 -0
  141. simview/static/lib/chroma-js-3.1.2/src/utils/unpack.js +17 -0
  142. simview/static/lib/chroma-js-3.1.2/src/utils/valid.js +11 -0
  143. simview/static/lib/chroma-js-3.1.2/src/version.js +2 -0
  144. simview/static/lib/download.js +132 -0
  145. simview/static/lib/js-colormaps.js +19335 -0
  146. simview/static/lib/tar.js +334 -0
  147. simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +1556 -0
  148. simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +8 -0
  149. simview/static/lib/three-0.174.0/three.core.js +48830 -0
  150. simview/static/lib/three-0.174.0/three.module.js +17313 -0
  151. simview/static/lib/uPlot.esm.js +6140 -0
  152. simview/static/lib/uPlot.min.css +2 -0
  153. simview/static/textures/contacts/red-cross0.png +0 -0
  154. simview/static/textures/points/ball0.png +0 -0
  155. simview/static/textures/points/ball1.png +0 -0
  156. simview/templates/index.html +81 -0
  157. simview/utils.py +41 -0
  158. simview-3.2.dist-info/METADATA +552 -0
  159. simview-3.2.dist-info/RECORD +163 -0
  160. simview-3.2.dist-info/WHEEL +5 -0
  161. simview-3.2.dist-info/entry_points.txt +2 -0
  162. simview-3.2.dist-info/licenses/LICENSE +28 -0
  163. 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
+ }