@thatopen/services 0.3.0 → 0.3.2
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.
- package/README.md +3 -0
- package/dist/built-in/index.d.ts +22 -2
- package/dist/cli.js +3 -3
- package/docs/ai-quickstart.md +86 -0
- package/docs/cli/create.md +4 -3
- package/package.json +3 -3
- package/src/built-in/index.ts +669 -649
- package/src/cli/templates/app/package.json +31 -31
- package/src/cli/templates/app/src/app.ts +37 -37
- package/src/cli/templates/app/src/assets/tool-placeholder.ts +50 -50
- package/src/cli/templates/app/src/main.ts +223 -340
- package/src/cli/templates/app/src/setups/camera-tools.ts +63 -63
- package/src/cli/templates/app/src/setups/clipper-tool.ts +20 -1
- package/src/cli/templates/app/src/setups/clipper.ts +75 -75
- package/src/cli/templates/app/src/setups/index.ts +23 -33
- package/src/cli/templates/app/src/setups/inspection.ts +136 -136
- package/src/cli/templates/app/src/setups/measurement-tool.ts +19 -1
- package/src/cli/templates/app/src/setups/measurements.ts +133 -133
- package/src/cli/templates/app/src/setups/reality-capture/lib/hidden-tiles-plugin.ts +88 -88
- package/src/cli/templates/app/src/setups/reality-capture/lib/point-tile-plugin.ts +166 -166
- package/src/cli/templates/app/src/setups/reality-capture/lib/render-frame.ts +31 -31
- package/src/cli/templates/app/src/setups/reality-capture/lib/splat-tile-plugin.ts +51 -51
- package/src/cli/templates/app/src/setups/reality-capture-viewer.ts +1383 -1383
- package/src/cli/templates/app/src/setups/right-sidebar.ts +178 -178
- package/src/cli/templates/app/src/setups/styles-panel.ts +110 -110
- package/src/cli/templates/app/src/setups/tool-mode-manager.ts +15 -9
- package/src/cli/templates/app/src/setups/tool-mode.ts +23 -23
- package/src/cli/templates/app/src/setups/active-tool-hud.ts +0 -69
- package/src/cli/templates/app/src/setups/clipper-panel.ts +0 -167
- package/src/cli/templates/app/src/setups/commands-panel.ts +0 -239
- package/src/cli/templates/app/src/setups/data-table-panel.ts +0 -2224
- package/src/cli/templates/app/src/setups/file-format-icons.ts +0 -95
- package/src/cli/templates/app/src/setups/files-panel.ts +0 -1503
- package/src/cli/templates/app/src/setups/fps-indicator.ts +0 -63
- package/src/cli/templates/app/src/setups/graphics-panel.ts +0 -490
- package/src/cli/templates/app/src/setups/measurement-settings-panel.ts +0 -161
- package/src/cli/templates/app/src/setups/model-tree.ts +0 -797
- package/src/cli/templates/app/src/setups/objects-panel.ts +0 -127
- package/src/cli/templates/app/src/setups/properties-panel.ts +0 -1048
- package/src/cli/templates/app/src/setups/settings-panel.ts +0 -94
- package/src/cli/templates/app/src/setups/visibility-toolbar.ts +0 -498
- package/src/cli/templates/app/src/setups/walkthrough.ts +0 -528
|
@@ -1,340 +1,223 @@
|
|
|
1
|
-
import * as THREE from "three";
|
|
2
|
-
import * as OBC from "@thatopen/components";
|
|
3
|
-
import * as OBF from "@thatopen/components-front";
|
|
4
|
-
import * as FRAGS from "@thatopen/fragments";
|
|
5
|
-
// Inlines the fragments worker so it runs inside the platform's sandboxed iframe.
|
|
6
|
-
import "@thatopen/fragments/inline";
|
|
7
|
-
import * as BUI from "@thatopen/ui";
|
|
8
|
-
import * as MARKERJS from "@markerjs/markerjs3";
|
|
9
|
-
import { PlatformClient, UIManager } from "@thatopen/services";
|
|
10
|
-
import { setAppContext } from "./app";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
document.
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
console.warn("[a2] navigationGizmo failed to mount", e);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Auto-load one model (top-viewer's world wires fragments→scene itself).
|
|
228
|
-
void autoLoadFirstModel(components, client);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/** Resolves with the first world once it exists (top-viewer creates it async). */
|
|
232
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
233
|
-
function firstWorld(worlds: any): Promise<any> {
|
|
234
|
-
const existing = [...worlds.list.values()][0];
|
|
235
|
-
if (existing) return Promise.resolve(existing);
|
|
236
|
-
return new Promise((resolve) => {
|
|
237
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
238
|
-
const handler = ({ value }: any) => {
|
|
239
|
-
worlds.list.onItemSet.remove(handler);
|
|
240
|
-
resolve(value);
|
|
241
|
-
};
|
|
242
|
-
worlds.list.onItemSet.add(handler);
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* On-screen pivot dot for the dynamic anchor: an HTML element projected from the
|
|
248
|
-
* 3D anchor point each frame. Appended to the top-viewer host so it slots into
|
|
249
|
-
* the viewer overlay over the canvas. Revealed once a drag starts (so a click-to-
|
|
250
|
-
* select doesn't flash it) and kept glued to the pivot as the camera orbits.
|
|
251
|
-
*/
|
|
252
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
253
|
-
function setupAnchorDot(world: any, element: HTMLElement) {
|
|
254
|
-
const dot = document.createElement("div");
|
|
255
|
-
dot.style.cssText =
|
|
256
|
-
"position:absolute; width:13px; height:13px; border-radius:50%;" +
|
|
257
|
-
"background:#6528d7; transform:translate(-50%,-50%);" +
|
|
258
|
-
"pointer-events:none; display:none; z-index:5;" +
|
|
259
|
-
"box-shadow:0 0 0 2px rgba(255,255,255,0.35);";
|
|
260
|
-
element.appendChild(dot);
|
|
261
|
-
|
|
262
|
-
let anchor: THREE.Vector3 | null = null;
|
|
263
|
-
const place = () => {
|
|
264
|
-
if (!anchor) return;
|
|
265
|
-
const ndc = anchor.clone().project(world.camera.three);
|
|
266
|
-
dot.style.left = `${(ndc.x * 0.5 + 0.5) * element.clientWidth}px`;
|
|
267
|
-
dot.style.top = `${(-ndc.y * 0.5 + 0.5) * element.clientHeight}px`;
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
let shown = false;
|
|
271
|
-
world.onDynamicAnchorSet.add((p: THREE.Vector3) => {
|
|
272
|
-
anchor = p.clone();
|
|
273
|
-
shown = false;
|
|
274
|
-
place();
|
|
275
|
-
});
|
|
276
|
-
world.onDynamicAnchorClear.add(() => {
|
|
277
|
-
anchor = null;
|
|
278
|
-
shown = false;
|
|
279
|
-
dot.style.display = "none";
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// Reveal only once a real drag starts (DRAG px), so a click-to-select doesn't
|
|
283
|
-
// flash the dot.
|
|
284
|
-
const DRAG = 6;
|
|
285
|
-
let press: { x: number; y: number } | null = null;
|
|
286
|
-
element.addEventListener("pointerdown", (e) => {
|
|
287
|
-
if (e.button === 0) press = { x: e.clientX, y: e.clientY };
|
|
288
|
-
});
|
|
289
|
-
element.addEventListener("pointermove", (e) => {
|
|
290
|
-
if (!press || !anchor || shown) return;
|
|
291
|
-
const dx = e.clientX - press.x;
|
|
292
|
-
const dy = e.clientY - press.y;
|
|
293
|
-
if (dx * dx + dy * dy >= DRAG * DRAG) {
|
|
294
|
-
shown = true;
|
|
295
|
-
place();
|
|
296
|
-
dot.style.display = "block";
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
const clearPress = () => {
|
|
300
|
-
press = null;
|
|
301
|
-
};
|
|
302
|
-
element.addEventListener("pointerup", clearPress);
|
|
303
|
-
element.addEventListener("pointercancel", clearPress);
|
|
304
|
-
|
|
305
|
-
// Keep the dot glued to the 3D pivot as the camera orbits around it.
|
|
306
|
-
world.renderer?.onBeforeUpdate.add(place);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
310
|
-
async function autoLoadFirstModel(components: OBC.Components, client: any) {
|
|
311
|
-
const fragments = components.get(OBC.FragmentsManager);
|
|
312
|
-
const projectId: string | undefined = client?.context?.projectId;
|
|
313
|
-
if (!projectId) {
|
|
314
|
-
console.warn("[a2] no projectId — skipping auto-load");
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
try {
|
|
318
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
319
|
-
const items = (await client.listFiles({ projectId })) as any[];
|
|
320
|
-
const frags = items.filter((it) =>
|
|
321
|
-
(it.name ?? "").toLowerCase().endsWith(".frag"),
|
|
322
|
-
);
|
|
323
|
-
const frag =
|
|
324
|
-
frags.find((it) => (it.name ?? "").toLowerCase().includes("bloxhub")) ??
|
|
325
|
-
frags[0];
|
|
326
|
-
if (!frag) {
|
|
327
|
-
console.warn("[a2] no .frag in project to auto-load");
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
const resp = await client.downloadFile(String(frag._id));
|
|
331
|
-
const buffer = await resp.arrayBuffer();
|
|
332
|
-
await fragments.core.load(buffer, { modelId: String(frag._id) });
|
|
333
|
-
await fragments.core.update(true);
|
|
334
|
-
console.log("[a2] auto-loaded model:", frag.name);
|
|
335
|
-
} catch (error) {
|
|
336
|
-
console.warn("[a2] auto-load failed", error);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
main().catch(console.error);
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import * as OBC from "@thatopen/components";
|
|
3
|
+
import * as OBF from "@thatopen/components-front";
|
|
4
|
+
import * as FRAGS from "@thatopen/fragments";
|
|
5
|
+
// Inlines the fragments worker so it runs inside the platform's sandboxed iframe.
|
|
6
|
+
import "@thatopen/fragments/inline";
|
|
7
|
+
import * as BUI from "@thatopen/ui";
|
|
8
|
+
import * as MARKERJS from "@markerjs/markerjs3";
|
|
9
|
+
import { PlatformClient, UIManager } from "@thatopen/services";
|
|
10
|
+
import { setAppContext } from "./app";
|
|
11
|
+
|
|
12
|
+
// ─── A2 migration — PHASES 1+2: boot on UIManager + re-dock panels ───────────
|
|
13
|
+
// Juan consolidated the old AppManager (layout) + ViewportsManager (viewport)
|
|
14
|
+
// built-ins into the single UIManager built-in, which ships `top-app` (shell +
|
|
15
|
+
// layout) and `top-viewer` (deferred-PEN viewport). This boots the bim-viewer on
|
|
16
|
+
// that model.
|
|
17
|
+
// Phase 1 — top-app shell hosting one top-viewer + auto-load. [done]
|
|
18
|
+
// Phase 2 — re-dock the side panels (tree/properties/files/data/objects/
|
|
19
|
+
// settings) into top-app's layouts + sidebar. [this file]
|
|
20
|
+
// Phase 3 — the viewer-overlay tools (fps/HUD/gizmo/bottom toolbar/measure +
|
|
21
|
+
// clip handles) that mount over the canvas. [next]
|
|
22
|
+
// The pre-A2 rich main is preserved at `main.rich.ts.bak`.
|
|
23
|
+
async function main() {
|
|
24
|
+
const client = PlatformClient.fromPlatformContext();
|
|
25
|
+
|
|
26
|
+
// Brand accent (purple) — drives layout-selector active state, dividers, etc.
|
|
27
|
+
document.documentElement.style.setProperty("--bim-ui_accent-base", "#6528d7");
|
|
28
|
+
|
|
29
|
+
// The dev `thatopen serve` wrapper HTML doesn't zero the UA body margin (8px),
|
|
30
|
+
// which insets the whole app inside the platform iframe. Kill it here so it's
|
|
31
|
+
// fixed in both dev and production regardless of the host page.
|
|
32
|
+
document.body.style.margin = "0";
|
|
33
|
+
|
|
34
|
+
// UIManager must be in the setup call: it registers the platform web
|
|
35
|
+
// components (top-app, top-viewer, top-viewer-tools, …) before the DOM renders.
|
|
36
|
+
const { components } = await client.setup<OBC.Components>(
|
|
37
|
+
{ OBC, OBF, BUI, THREE, FRAGS, MARKERJS },
|
|
38
|
+
{ uuid: UIManager.uuid },
|
|
39
|
+
);
|
|
40
|
+
components.get(UIManager).init();
|
|
41
|
+
|
|
42
|
+
// One STABLE top-viewer node, returned by reference so re-rendering top-app
|
|
43
|
+
// (when we add the panels below) reuses it instead of disposing/recreating
|
|
44
|
+
// its world. No <top-viewer-tools>: the bim-viewer mounts its own tabbed
|
|
45
|
+
// visibility/inspect toolbar (see below), so the platform default would just
|
|
46
|
+
// duplicate it.
|
|
47
|
+
const viewerEl = document.createElement("top-viewer");
|
|
48
|
+
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
const app = document.createElement("top-app") as any;
|
|
51
|
+
|
|
52
|
+
app.setup = (waitUntil: (p: Promise<void>, label?: string) => void) => {
|
|
53
|
+
waitUntil(
|
|
54
|
+
(async () => {
|
|
55
|
+
const fragments = components.get(OBC.FragmentsManager);
|
|
56
|
+
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
57
|
+
fragments.init(await FRAGS.toClassicWorker(workerUrl), {
|
|
58
|
+
classicWorker: true,
|
|
59
|
+
});
|
|
60
|
+
})(),
|
|
61
|
+
"Fragments Core",
|
|
62
|
+
);
|
|
63
|
+
return { components, client };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Mount minimally first (viewer only) so top-viewer creates the world; the
|
|
67
|
+
// panels + their tools are built once that world exists (below).
|
|
68
|
+
app.elements = { viewer: () => BUI.html`${viewerEl}` };
|
|
69
|
+
app.layouts = {
|
|
70
|
+
Main: {
|
|
71
|
+
label: "Main",
|
|
72
|
+
icon: "solar:3d-square-bold",
|
|
73
|
+
template: `"viewer" 1fr / 1fr`,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
app.layout = "Main";
|
|
77
|
+
|
|
78
|
+
const container = document.getElementById("that-open-app") ?? document.body;
|
|
79
|
+
container.appendChild(app);
|
|
80
|
+
|
|
81
|
+
// Wait for top-viewer's world to exist before building world-dependent panels.
|
|
82
|
+
// The viewer tool suite + dynamic-anchor pivot dot are now baked into
|
|
83
|
+
// <top-viewer> (setupViewerTools), so the app no longer wires them.
|
|
84
|
+
await firstWorld(components.get(OBC.Worlds));
|
|
85
|
+
|
|
86
|
+
// Platform client + project data for the AppManager-shim consumers
|
|
87
|
+
// (CloudRunner, data-table-panel, app-info-section).
|
|
88
|
+
const projectId: string | undefined = client?.context?.projectId;
|
|
89
|
+
let projectData;
|
|
90
|
+
try {
|
|
91
|
+
if (projectId) projectData = await client.getProjectData(projectId);
|
|
92
|
+
} catch {
|
|
93
|
+
/* dev/no-project → consumers degrade gracefully */
|
|
94
|
+
}
|
|
95
|
+
setAppContext(client, projectData);
|
|
96
|
+
|
|
97
|
+
// Pluggable loaders for <top-models-list>. The built-in ships the lightweight
|
|
98
|
+
// defaults (.frag load, IFC→fragments convert); heavy/app-specific loaders are
|
|
99
|
+
// registered here so they stay OUT of the built-in's bundle. The reality-capture
|
|
100
|
+
// .3tz viewer pulls Spark + 3d-tiles-renderer, so it's lazy-imported app-side
|
|
101
|
+
// and plugged in via the loader registry. Alignment persists through the panel's
|
|
102
|
+
// app-data via the loader context (getAlignment/setAlignment).
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
|
+
let rcViewer: any = null;
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
106
|
+
const modelLoaders: Record<string, any> = {
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
108
|
+
"3tz": async (fileId: string, ctx: any) => {
|
|
109
|
+
if (!rcViewer) {
|
|
110
|
+
const { realityCaptureViewer } = await import(
|
|
111
|
+
"./setups/reality-capture-viewer"
|
|
112
|
+
);
|
|
113
|
+
rcViewer = realityCaptureViewer(ctx.components, client);
|
|
114
|
+
}
|
|
115
|
+
const saved = ctx.getAlignment(fileId) as number[] | undefined;
|
|
116
|
+
await rcViewer.loadIntoWorld(fileId, {
|
|
117
|
+
keepPostproduction: true,
|
|
118
|
+
transform: saved ? new THREE.Matrix4().fromArray(saved) : undefined,
|
|
119
|
+
onTransformChange: (m: THREE.Matrix4) =>
|
|
120
|
+
ctx.setAlignment(fileId, m.toArray()),
|
|
121
|
+
});
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Re-dock: the stable viewer + the panels, under the bim-viewer's named layouts
|
|
126
|
+
// with the activity-bar sidebar (Explorer · Assets · Data · Settings). All panels
|
|
127
|
+
// are now built-ins that self-wire from top-app's componentsContext/clientContext
|
|
128
|
+
// — no `components` plumbing in the app.
|
|
129
|
+
app.elements = {
|
|
130
|
+
viewer: () => BUI.html`${viewerEl}`,
|
|
131
|
+
tree: () => BUI.html`<top-model-tree></top-model-tree>`,
|
|
132
|
+
properties: () => BUI.html`<top-properties-panel></top-properties-panel>`,
|
|
133
|
+
files: () =>
|
|
134
|
+
BUI.html`<top-models-list .loaders=${modelLoaders}></top-models-list>`,
|
|
135
|
+
dataTable: () => BUI.html`<top-data-table-panel></top-data-table-panel>`,
|
|
136
|
+
objects: () => BUI.html`<top-objects-panel></top-objects-panel>`,
|
|
137
|
+
settings: () => BUI.html`<top-settings-panel></top-settings-panel>`,
|
|
138
|
+
};
|
|
139
|
+
// No `label` → the sidebar renders icon-only activity-bar buttons (matching
|
|
140
|
+
// the pre-A2 look), background only on the active one.
|
|
141
|
+
app.layouts = {
|
|
142
|
+
Explorer: {
|
|
143
|
+
icon: "mdi:file-tree",
|
|
144
|
+
template: `"tree viewer" 1fr "properties viewer" 1fr / 22rem 1fr`,
|
|
145
|
+
},
|
|
146
|
+
Assets: {
|
|
147
|
+
icon: "mdi:folder-multiple-outline",
|
|
148
|
+
template: `"files viewer" 1fr "objects viewer" 1fr / 22rem 1fr`,
|
|
149
|
+
},
|
|
150
|
+
Data: {
|
|
151
|
+
icon: "mdi:table",
|
|
152
|
+
template: `"dataTable viewer" 1fr / 22rem 1fr`,
|
|
153
|
+
},
|
|
154
|
+
Settings: {
|
|
155
|
+
icon: "mdi:cog",
|
|
156
|
+
template: `"settings viewer" 1fr / 22rem 1fr`,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
app.layout = "Explorer";
|
|
160
|
+
app.sidebar = true;
|
|
161
|
+
|
|
162
|
+
// ── Viewer toolbar — now a built-in slotted INTO <top-viewer> (it consumes the
|
|
163
|
+
// world + components contexts top-viewer provides). The rich bottom toolbar +
|
|
164
|
+
// active-tool HUD + clip/measure/walkthrough tools all live in
|
|
165
|
+
// <top-viewer-toolbar>; the side panels share the SAME tool singletons via
|
|
166
|
+
// components.get (OBC.Component-wrapped).
|
|
167
|
+
viewerEl.appendChild(document.createElement("top-viewer-toolbar"));
|
|
168
|
+
|
|
169
|
+
// Navigation gizmo is now baked into <top-viewer> (setupViewerTools), so the
|
|
170
|
+
// app no longer mounts it. (Cascade: the rest of the overlay tools follow.)
|
|
171
|
+
|
|
172
|
+
// Auto-load one model (top-viewer's world wires fragments→scene itself).
|
|
173
|
+
void autoLoadFirstModel(components, client);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Resolves with the first world once it exists (top-viewer creates it async). */
|
|
177
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
178
|
+
function firstWorld(worlds: any): Promise<any> {
|
|
179
|
+
const existing = [...worlds.list.values()][0];
|
|
180
|
+
if (existing) return Promise.resolve(existing);
|
|
181
|
+
return new Promise((resolve) => {
|
|
182
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
183
|
+
const handler = ({ value }: any) => {
|
|
184
|
+
worlds.list.onItemSet.remove(handler);
|
|
185
|
+
resolve(value);
|
|
186
|
+
};
|
|
187
|
+
worlds.list.onItemSet.add(handler);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
193
|
+
async function autoLoadFirstModel(components: OBC.Components, client: any) {
|
|
194
|
+
const fragments = components.get(OBC.FragmentsManager);
|
|
195
|
+
const projectId: string | undefined = client?.context?.projectId;
|
|
196
|
+
if (!projectId) {
|
|
197
|
+
console.warn("[a2] no projectId — skipping auto-load");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
202
|
+
const items = (await client.listFiles({ projectId })) as any[];
|
|
203
|
+
const frags = items.filter((it) =>
|
|
204
|
+
(it.name ?? "").toLowerCase().endsWith(".frag"),
|
|
205
|
+
);
|
|
206
|
+
const frag =
|
|
207
|
+
frags.find((it) => (it.name ?? "").toLowerCase().includes("bloxhub")) ??
|
|
208
|
+
frags[0];
|
|
209
|
+
if (!frag) {
|
|
210
|
+
console.warn("[a2] no .frag in project to auto-load");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const resp = await client.downloadFile(String(frag._id));
|
|
214
|
+
const buffer = await resp.arrayBuffer();
|
|
215
|
+
await fragments.core.load(buffer, { modelId: String(frag._id) });
|
|
216
|
+
await fragments.core.update(true);
|
|
217
|
+
console.log("[a2] auto-loaded model:", frag.name);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.warn("[a2] auto-load failed", error);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
main().catch(console.error);
|