@thatopen/services 0.3.10 → 0.3.12

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 (39) hide show
  1. package/dist/cli/commands/revit.d.ts +9 -0
  2. package/dist/cli/lib/revit-addin.d.ts +20 -0
  3. package/dist/cli.js +101 -0
  4. package/dist/cloud/LasToPotree.d.ts +59 -0
  5. package/dist/cloud/SplatToFq.d.ts +38 -0
  6. package/dist/cloud/index.d.ts +2 -0
  7. package/docs/builtin/paths.json +86 -86
  8. package/docs/cloud/GaussianSplatConverter.md +43 -0
  9. package/docs/cloud/PointCloudConverter.md +53 -0
  10. package/docs/revit-collab-quickstart.md +151 -0
  11. package/package.json +95 -95
  12. package/src/built-in/ClashesManager/example.ts +283 -283
  13. package/src/built-in/UIManager/src/clashes-panel/example.ts +160 -160
  14. package/src/built-in/UIManager/src/models-list/example.ts +122 -118
  15. package/src/cli/templates/app/README.md +217 -241
  16. package/src/cli/templates/app/package.json +33 -33
  17. package/src/cli/templates/app/src/setups/camera-tools.ts +0 -63
  18. package/src/cli/templates/app/src/setups/camera-views.ts +0 -201
  19. package/src/cli/templates/app/src/setups/card-header.ts +0 -35
  20. package/src/cli/templates/app/src/setups/clipper-tool.ts +0 -773
  21. package/src/cli/templates/app/src/setups/clipper.ts +0 -75
  22. package/src/cli/templates/app/src/setups/cloud-runner.ts +0 -13
  23. package/src/cli/templates/app/src/setups/exploded-view.ts +0 -246
  24. package/src/cli/templates/app/src/setups/helper-panel.ts +0 -88
  25. package/src/cli/templates/app/src/setups/hider.ts +0 -98
  26. package/src/cli/templates/app/src/setups/index.ts +0 -23
  27. package/src/cli/templates/app/src/setups/inspection.ts +0 -136
  28. package/src/cli/templates/app/src/setups/measurement-panel.ts +0 -188
  29. package/src/cli/templates/app/src/setups/measurement-tool.ts +0 -588
  30. package/src/cli/templates/app/src/setups/measurements.ts +0 -133
  31. package/src/cli/templates/app/src/setups/navigation-gizmo.ts +0 -316
  32. package/src/cli/templates/app/src/setups/plans-panel.ts +0 -619
  33. package/src/cli/templates/app/src/setups/right-sidebar.ts +0 -178
  34. package/src/cli/templates/app/src/setups/styles-panel.ts +0 -110
  35. package/src/cli/templates/app/src/setups/styles.ts +0 -375
  36. package/src/cli/templates/app/src/setups/tool-mode-manager.ts +0 -156
  37. package/src/cli/templates/app/src/setups/tool-mode.ts +0 -23
  38. package/src/cli/templates/app/src/setups/toolbar.ts +0 -229
  39. package/src/cli/templates/app/src/setups/ui-manager.ts +0 -27
@@ -1,160 +1,160 @@
1
- /* MD
2
- ## clashes-panel
3
- ---
4
- BIM coordinators need a dedicated review environment where they can see all
5
- detected clashes, filter by status, navigate to each issue in the 3D viewer,
6
- and mark items as resolved — without writing any UI code themselves.
7
-
8
- `clashes-panel` is a ready-made web component that delivers exactly that. It
9
- connects to `ClashesManager` automatically and manages the entire review
10
- workflow: the detection matrix, the clash table with status filters, sphere
11
- markers and element highlights in the viewer, run controls with live progress,
12
- and a loading skeleton while fresh data reloads after a run completes.
13
-
14
- This tutorial covers the prerequisites before mounting the panel; dropping it
15
- into a `top-app` layout alongside a 3D viewer; and a detailed breakdown of
16
- everything the panel handles automatically so you know what you do not need to
17
- implement yourself.
18
-
19
- By the end, you'll have a fully working clash-review environment running in
20
- your application with a single line of markup.
21
- */
22
-
23
- import { html } from "lit";
24
- import * as THREE from "three";
25
- import * as OBC from "@thatopen/components";
26
- import * as OBF from "@thatopen/components-front";
27
- import * as FRAGS from "@thatopen/fragments";
28
- import * as BUI from "@thatopen/ui";
29
- import { PlatformClient, UIManager, ClashesManager } from "@thatopen/services";
30
- import type { App } from "../app/index";
31
-
32
-
33
- /* MD
34
- ### ✅ Prerequisites
35
-
36
- Two conditions must be met before the panel mounts:
37
-
38
- 1. **`UIManager` must be in the setup call.** It registers all platform web
39
- components, including `<top-clashes-panel>` itself. Without it the element is
40
- unknown to the browser and renders as an empty box.
41
-
42
- 2. **`ClashesManager.init(client)` must have resolved.** The panel reads all
43
- its initial data from the manager in `connectedCallback`. If `init` has not
44
- finished, the panel mounts into an empty state and never catches up because
45
- it only subscribes to change events — it does not poll.
46
-
47
- Both conditions are naturally satisfied when you call `init` before appending
48
- `top-app` to the document, which is the pattern shown below.
49
- */
50
-
51
- const client = PlatformClient.fromPlatformContext();
52
-
53
- const { components } = (await client.setup(
54
- { OBC, OBF, BUI, THREE, FRAGS },
55
- { uuid: UIManager.uuid },
56
- { uuid: ClashesManager.uuid },
57
- )) as { components: OBC.Components };
58
-
59
- components.get(UIManager).init();
60
-
61
- // init must resolve before top-app (and therefore clashes-panel) mounts.
62
- await components.get(ClashesManager).init(client);
63
-
64
- /* MD
65
- ### 🖥️ Wiring the panel
66
-
67
- Add `clashes-panel` as a named area in `app.elements` and reference it in your
68
- layout template. A side-by-side layout with the 3D viewer is the most natural
69
- arrangement — the panel controls markers and highlights directly in the scene.
70
- */
71
-
72
- const app = document.createElement("top-app") as unknown as App;
73
-
74
- app.setup = (waitUntil) => {
75
- waitUntil(
76
- (async () => {
77
- const fragments = components.get(OBC.FragmentsManager);
78
- const workerUrl = await FRAGS.FragmentsModels.getWorker();
79
- fragments.init(await FRAGS.toClassicWorker(workerUrl), { classicWorker: true });
80
- })(),
81
- "Fragments Core",
82
- );
83
-
84
- waitUntil(
85
- (async () => {
86
- const loader = components.get(OBC.IfcLoader);
87
- await loader.setup();
88
- })(),
89
- "IFC Loader",
90
- );
91
-
92
- return { components, client };
93
- };
94
-
95
- app.elements = {
96
- viewer: () => html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
97
- clashes: () => html`<top-clashes-panel></top-clashes-panel>`,
98
- };
99
-
100
- app.layouts = {
101
- main: {
102
- label: "Main",
103
- icon: "solar:3d-square-bold",
104
- template: `"viewer clashes" 1fr / 1fr 22rem`,
105
- },
106
- };
107
-
108
- app.layout = "main";
109
-
110
- const container = document.getElementById("that-open-app") ?? document.body;
111
- container.appendChild(app);
112
- document.body.style.margin = "0";
113
-
114
- /* MD
115
- ### 🤖 What the panel manages automatically
116
-
117
- Once mounted, `clashes-panel` takes ownership of the following without any
118
- further code from you:
119
-
120
- **Data synchronization** — the panel subscribes to every `ClashesManager`
121
- event (`onJobsChanged`, `onRunsChanged`, `onClashesChanged`,
122
- `onClashStatusChanged`, `onQueriesChanged`, `onRunningJobsChanged`) and keeps
123
- its tables in sync. Changes made programmatically through the manager API are
124
- reflected in the panel immediately.
125
-
126
- **Run lifecycle** — a progress callout appears as soon as a run starts
127
- (`onRunProgress`), updates its percentage and message in real time, and
128
- disappears when the run completes or errors. If a run was already in progress
129
- when the page loaded, the panel calls `reconnectIfRunning` automatically when
130
- the user opens that job.
131
-
132
- **Post-run reload skeleton** — after a successful run the manager reloads clash
133
- data from storage. During that window (`onReloadingChanged = true`) the panel
134
- replaces its entire content with an animated skeleton so the user is never left
135
- looking at stale results that appear final.
136
-
137
- **Sphere markers** — entering a job view places colored spheres in the 3D scene
138
- at each clash location, transformed from IFC world space to viewer space.
139
- Selecting a clash in the table zooms the camera, ghosts unrelated elements, and
140
- re-focuses the markers on the selection. Leaving the job view removes all
141
- markers and restores the full scene.
142
-
143
- **Element highlights** — element A is highlighted green and element B red for
144
- every new or active clash in the current job. Selecting a single clash refines
145
- the highlight to that pair only. Clearing the selection or changing status
146
- restores the full job highlight.
147
-
148
- **Model awareness** — the panel tracks which fragment models are currently
149
- loaded in the scene. Markers and highlights are activated only when the relevant
150
- models are present, and are cleaned up automatically if a model is disposed
151
- while the panel is open.
152
-
153
- **Save indicator** — a subtle indicator appears while auto-save is in progress
154
- (`onSaveStart`) and disappears once the write confirms (`onSaveComplete`), so
155
- users know their status changes and query edits are persisted.
156
-
157
- **Disconnection cleanup** — when the panel is removed from the DOM it calls
158
- `hideMarkers` and `clearClashHighlight` on the manager, leaving the 3D scene
159
- clean regardless of what was selected at the time.
160
- */
1
+ /* MD
2
+ ## clashes-panel
3
+ ---
4
+ BIM coordinators need a dedicated review environment where they can see all
5
+ detected clashes, filter by status, navigate to each issue in the 3D viewer,
6
+ and mark items as resolved — without writing any UI code themselves.
7
+
8
+ `clashes-panel` is a ready-made web component that delivers exactly that. It
9
+ connects to `ClashesManager` automatically and manages the entire review
10
+ workflow: the detection matrix, the clash table with status filters, sphere
11
+ markers and element highlights in the viewer, run controls with live progress,
12
+ and a loading skeleton while fresh data reloads after a run completes.
13
+
14
+ This tutorial covers the prerequisites before mounting the panel; dropping it
15
+ into a `top-app` layout alongside a 3D viewer; and a detailed breakdown of
16
+ everything the panel handles automatically so you know what you do not need to
17
+ implement yourself.
18
+
19
+ By the end, you'll have a fully working clash-review environment running in
20
+ your application with a single line of markup.
21
+ */
22
+
23
+ import { html } from "lit";
24
+ import * as THREE from "three";
25
+ import * as OBC from "@thatopen/components";
26
+ import * as OBF from "@thatopen/components-front";
27
+ import * as FRAGS from "@thatopen/fragments";
28
+ import * as BUI from "@thatopen/ui";
29
+ import { PlatformClient, UIManager, ClashesManager } from "@thatopen/services";
30
+ import type { App } from "../app/index";
31
+
32
+
33
+ /* MD
34
+ ### ✅ Prerequisites
35
+
36
+ Two conditions must be met before the panel mounts:
37
+
38
+ 1. **`UIManager` must be in the setup call.** It registers all platform web
39
+ components, including `<top-clashes-panel>` itself. Without it the element is
40
+ unknown to the browser and renders as an empty box.
41
+
42
+ 2. **`ClashesManager.init(client)` must have resolved.** The panel reads all
43
+ its initial data from the manager in `connectedCallback`. If `init` has not
44
+ finished, the panel mounts into an empty state and never catches up because
45
+ it only subscribes to change events — it does not poll.
46
+
47
+ Both conditions are naturally satisfied when you call `init` before appending
48
+ `top-app` to the document, which is the pattern shown below.
49
+ */
50
+
51
+ const client = PlatformClient.fromPlatformContext();
52
+
53
+ const { components } = (await client.setup(
54
+ { OBC, OBF, BUI, THREE, FRAGS },
55
+ { uuid: UIManager.uuid },
56
+ { uuid: ClashesManager.uuid },
57
+ )) as { components: OBC.Components };
58
+
59
+ components.get(UIManager).init();
60
+
61
+ // init must resolve before top-app (and therefore clashes-panel) mounts.
62
+ await components.get(ClashesManager).init(client);
63
+
64
+ /* MD
65
+ ### 🖥️ Wiring the panel
66
+
67
+ Add `clashes-panel` as a named area in `app.elements` and reference it in your
68
+ layout template. A side-by-side layout with the 3D viewer is the most natural
69
+ arrangement — the panel controls markers and highlights directly in the scene.
70
+ */
71
+
72
+ const app = document.createElement("top-app") as unknown as App;
73
+
74
+ app.setup = (waitUntil) => {
75
+ waitUntil(
76
+ (async () => {
77
+ const fragments = components.get(OBC.FragmentsManager);
78
+ const workerUrl = await FRAGS.FragmentsModels.getWorker();
79
+ fragments.init(await FRAGS.toClassicWorker(workerUrl), { classicWorker: true });
80
+ })(),
81
+ "Fragments Core",
82
+ );
83
+
84
+ waitUntil(
85
+ (async () => {
86
+ const loader = components.get(OBC.IfcLoader);
87
+ await loader.setup();
88
+ })(),
89
+ "IFC Loader",
90
+ );
91
+
92
+ return { components, client };
93
+ };
94
+
95
+ app.elements = {
96
+ viewer: () => html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
97
+ clashes: () => html`<top-clashes-panel></top-clashes-panel>`,
98
+ };
99
+
100
+ app.layouts = {
101
+ main: {
102
+ label: "Main",
103
+ icon: "solar:3d-square-bold",
104
+ template: `"viewer clashes" 1fr / 1fr 22rem`,
105
+ },
106
+ };
107
+
108
+ app.layout = "main";
109
+
110
+ const container = document.getElementById("that-open-app") ?? document.body;
111
+ container.appendChild(app);
112
+ document.body.style.margin = "0";
113
+
114
+ /* MD
115
+ ### 🤖 What the panel manages automatically
116
+
117
+ Once mounted, `clashes-panel` takes ownership of the following without any
118
+ further code from you:
119
+
120
+ **Data synchronization** — the panel subscribes to every `ClashesManager`
121
+ event (`onJobsChanged`, `onRunsChanged`, `onClashesChanged`,
122
+ `onClashStatusChanged`, `onQueriesChanged`, `onRunningJobsChanged`) and keeps
123
+ its tables in sync. Changes made programmatically through the manager API are
124
+ reflected in the panel immediately.
125
+
126
+ **Run lifecycle** — a progress callout appears as soon as a run starts
127
+ (`onRunProgress`), updates its percentage and message in real time, and
128
+ disappears when the run completes or errors. If a run was already in progress
129
+ when the page loaded, the panel calls `reconnectIfRunning` automatically when
130
+ the user opens that job.
131
+
132
+ **Post-run reload skeleton** — after a successful run the manager reloads clash
133
+ data from storage. During that window (`onReloadingChanged = true`) the panel
134
+ replaces its entire content with an animated skeleton so the user is never left
135
+ looking at stale results that appear final.
136
+
137
+ **Sphere markers** — entering a job view places colored spheres in the 3D scene
138
+ at each clash location, transformed from IFC world space to viewer space.
139
+ Selecting a clash in the table zooms the camera, ghosts unrelated elements, and
140
+ re-focuses the markers on the selection. Leaving the job view removes all
141
+ markers and restores the full scene.
142
+
143
+ **Element highlights** — element A is highlighted green and element B red for
144
+ every new or active clash in the current job. Selecting a single clash refines
145
+ the highlight to that pair only. Clearing the selection or changing status
146
+ restores the full job highlight.
147
+
148
+ **Model awareness** — the panel tracks which fragment models are currently
149
+ loaded in the scene. Markers and highlights are activated only when the relevant
150
+ models are present, and are cleaned up automatically if a model is disposed
151
+ while the panel is open.
152
+
153
+ **Save indicator** — a subtle indicator appears while auto-save is in progress
154
+ (`onSaveStart`) and disappears once the write confirms (`onSaveComplete`), so
155
+ users know their status changes and query edits are persisted.
156
+
157
+ **Disconnection cleanup** — when the panel is removed from the DOM it calls
158
+ `hideMarkers` and `clearClashHighlight` on the manager, leaving the 3D scene
159
+ clean regardless of what was selected at the time.
160
+ */
@@ -1,118 +1,122 @@
1
- /* MD
2
- ## top-models-list
3
- ---
4
- top-models-list is the project files panel: it lists the project's files, uploads new ones, converts IFC to fragments, and loads `.frag` models into the 3D scene. Its loaders are PLUGGABLE — the panel owns the UI and the orchestration (progress, refresh, association), but the HOST decides how each file format is loaded, so you can teach it new formats without forking the built-in.
5
-
6
- This tutorial covers the two extension points. First, registering a custom loader for a file extension — either imperatively with `registerLoader(ext, fn)` or up front via the `loaders` property; a loader receives `(fileId, ctx)` where `ctx` carries the engine (`components`), the platform `client`, and per-file alignment persistence. A format with no registered loader simply hides its load action. Second, overriding the IFC to fragments `converter` — omit it to use the built-in default (which drives the project's IfcFragmenter cloud component), or provide one to run conversion elsewhere.
7
-
8
- By the end you'll have a files panel that knows how to load a custom format and how to run a custom IFC conversion, with the panel's UI untouched.
9
- */
10
-
11
- import { html } from "lit";
12
- import * as THREE from "three";
13
- import * as OBC from "@thatopen/components";
14
- import * as OBF from "@thatopen/components-front";
15
- import * as FRAGS from "@thatopen/fragments";
16
- import * as BUI from "@thatopen/ui";
17
- import { PlatformClient, UIManager } from "@thatopen/services";
18
- import type { App } from "../app/index";
19
- import type { ModelsList } from "./index";
20
-
21
-
22
- const client = PlatformClient.fromPlatformContext();
23
-
24
- // UIManager registers every platform web component (top-app, top-viewer,
25
- // top-models-list, …) before the DOM renders.
26
- const { components } = (await client.setup(
27
- { OBC, OBF, BUI, THREE, FRAGS },
28
- { uuid: UIManager.uuid },
29
- )) as { components: OBC.Components };
30
-
31
- components.get(UIManager).init();
32
-
33
- const app = document.createElement("top-app") as unknown as App;
34
-
35
- app.setup = (waitUntil) => {
36
- waitUntil(
37
- (async () => {
38
- const fragments = components.get(OBC.FragmentsManager);
39
- const workerUrl = await FRAGS.FragmentsModels.getWorker();
40
- fragments.init(await FRAGS.toClassicWorker(workerUrl), {
41
- classicWorker: true,
42
- });
43
- })(),
44
- "Fragments Core",
45
- );
46
- return { components, client };
47
- };
48
-
49
- // One stable <top-models-list>, kept by reference so re-rendering top-app reuses
50
- // it. The loaders are read LIVE, so registration can happen at any time.
51
- const modelsList = document.createElement("top-models-list") as ModelsList;
52
-
53
- // ── Extension point 1: a custom loader, keyed by file extension ──────────────
54
- // A ModelLoader is `(fileId, ctx) => void | Promise<void>`. The panel calls it
55
- // when the user loads a file whose extension matches. `ctx` gives you everything
56
- // you need: { name, ext, components, client, getAlignment, setAlignment }. Do
57
- // whatever it takes to put the file in the scene — fetch it via the client,
58
- // parse it, and add geometry to a world from `components`.
59
- modelsList.registerLoader("xyz", async (fileId, ctx) => {
60
- // const buffer = await ctx.client
61
- // .downloadFile(fileId)
62
- // .then((r: Response) => r.arrayBuffer());
63
- // ...parse `buffer` and add it to the world here...
64
- // Persist/restore a placement matrix across reloads via the panel's app-data:
65
- // const saved = ctx.getAlignment(fileId);
66
- // ctx.setAlignment(fileId, matrix.toArray());
67
- console.log(`[xyz loader] would load "${ctx.name}" (${fileId})`, ctx.components);
68
- });
69
-
70
- // Equivalent declarative form — hand the whole registry over at once:
71
- // modelsList.loaders = { xyz: async (fileId, ctx) => { ... } };
72
- //
73
- // Registering a loader for an extension is also what makes that format's files
74
- // APPEAR in the list (recognition follows the loaders registry). For a
75
- // CONVERT-style action — e.g. a PLY that runs a PLY→3D-tiles cloud component
76
- // then views the result — register a RICH entry so the button reads correctly:
77
- // modelsList.registerLoader("ply", {
78
- // label: "Generate 3D tiles",
79
- // icon: "mdi:cube-scan",
80
- // fn: async (fileId, ctx) => {
81
- // // const { executionId } = await ctx.client.executeComponent(...);
82
- // // ...poll until done, then open the produced tileset...
83
- // },
84
- // });
85
-
86
- // ── Extension point 2: override the IFC → fragments converter ────────────────
87
- // OMIT this to keep the built-in default (drives the project's IfcFragmenter
88
- // cloud component via the platform client). Provide it to run conversion
89
- // elsewhere / differently — the panel keeps the progress bar, reload-survival,
90
- // association and refresh; your converter just `start`s and is `poll`ed.
91
- modelsList.converter = {
92
- async start(fileId) {
93
- // kick off your conversion; return an execution id to poll
94
- return `exec-${fileId}`;
95
- },
96
- async poll(executionId) {
97
- // report progress; when done, hand back the produced .frag id
98
- return { done: true, ok: true, fragId: "demo-frag", message: "Converted" };
99
- },
100
- };
101
-
102
- // Mount the panel into a one-area layout. (In a real app it sits beside a
103
- // top-viewer so loaded models have a world to render into.)
104
- app.elements = {
105
- files: () => html`${modelsList}`,
106
- };
107
- app.layouts = {
108
- main: {
109
- label: "Files",
110
- icon: "mdi:folder-multiple-outline",
111
- template: `"files" 1fr / 1fr`,
112
- },
113
- };
114
- app.layout = "main";
115
-
116
- const container = document.getElementById("that-open-app") ?? document.body;
117
- container.appendChild(app);
118
- document.body.style.margin = "0";
1
+ /* MD
2
+ ## top-models-list
3
+ ---
4
+ top-models-list is the project's 3D-viewable-files tree: it mirrors the real storage folder structure, converts IFC to fragments, and loads `.frag`/`.potree`/`.splat` models into the 3D scene. Its loaders are PLUGGABLE — the panel owns the UI and the orchestration (progress, refresh, association), but the HOST decides how each extra file format is loaded, so you can teach it new formats without forking the built-in.
5
+
6
+ This tutorial covers the two extension points. First, registering a custom loader for a file extension — either imperatively with `registerLoader(ext, fn)` or up front via the `loaders` property; a loader receives `(fileId, ctx)` where `ctx` carries the engine (`components`), the platform `client`, and per-file alignment persistence. Second, overriding the IFC to fragments `converter` — omit it to use the built-in default (which drives the project's IfcFragmenter cloud component, resolved by its stable name — see `adrs/resolve-ifc-fragmenter-by-stable-name.md`), or provide one to run conversion elsewhere.
7
+
8
+ A behaviour worth knowing: registering a loader does NOT control whether a file shows up in the tree — the tree always mirrors the project's real storage folders, so an unrecognized file is still listed as long as it's a source (IFC/LAS/PLY) or derivative (`.frag`/`.potree`/`.splat`) extension; the loaders registry only decides whether a file's row gets a load action or none.
9
+
10
+ Known gaps (intentionally deferred, not forgotten): file upload and the base-model/auto-coordinate settings aren't ported, so `allow-upload` is currently a no-op. Everything else per-file is here, including editable attach/detach via a picker — MINUS delete, dropped on purpose. See `models-list/legacy-example.ts` + `models-list/impl.ts` for the retired implementation those gaps would be ported from.
11
+
12
+ By the end you'll have a files tree that knows how to load a custom format and how to run a custom IFC conversion, with the panel's UI untouched.
13
+ */
14
+
15
+ import { html } from "lit";
16
+ import * as THREE from "three";
17
+ import * as OBC from "@thatopen/components";
18
+ import * as OBF from "@thatopen/components-front";
19
+ import * as FRAGS from "@thatopen/fragments";
20
+ import * as BUI from "@thatopen/ui";
21
+ import { PlatformClient, UIManager } from "@thatopen/services";
22
+ import type { App } from "../app/index";
23
+ import type { ModelsListV2 } from "./src/v2/models-list-panel";
24
+
25
+
26
+ const client = PlatformClient.fromPlatformContext();
27
+
28
+ // UIManager registers every platform web component (top-app, top-viewer,
29
+ // top-models-list, …) before the DOM renders.
30
+ const { components } = (await client.setup(
31
+ { OBC, OBF, BUI, THREE, FRAGS },
32
+ { uuid: UIManager.uuid },
33
+ )) as { components: OBC.Components };
34
+
35
+ components.get(UIManager).init();
36
+
37
+ const app = document.createElement("top-app") as unknown as App;
38
+
39
+ app.setup = (waitUntil) => {
40
+ waitUntil(
41
+ (async () => {
42
+ const fragments = components.get(OBC.FragmentsManager);
43
+ const workerUrl = await FRAGS.FragmentsModels.getWorker();
44
+ fragments.init(await FRAGS.toClassicWorker(workerUrl), {
45
+ classicWorker: true,
46
+ });
47
+ })(),
48
+ "Fragments Core",
49
+ );
50
+ return { components, client };
51
+ };
52
+
53
+ // One stable <top-models-list>, kept by reference so re-rendering top-app
54
+ // reuses it. The loaders are read LIVE, so registration can happen at any time.
55
+ const modelsList = document.createElement("top-models-list") as ModelsListV2;
56
+
57
+ // ── Extension point 1: a custom loader, keyed by file extension ──────────────
58
+ // A ModelLoader is `(fileId, ctx) => void | Promise<void>`. The panel calls it
59
+ // when the user loads a file whose extension matches. `ctx` gives you everything
60
+ // you need: { name, ext, components, client, getAlignment, setAlignment }. Do
61
+ // whatever it takes to put the file in the scene — fetch it via the client,
62
+ // parse it, and add geometry to a world from `components`.
63
+ modelsList.registerLoader("xyz", async (fileId, ctx) => {
64
+ // const buffer = await ctx.client
65
+ // .downloadFile(fileId)
66
+ // .then((r: Response) => r.arrayBuffer());
67
+ // ...parse `buffer` and add it to the world here...
68
+ // Persist/restore a placement matrix across reloads via the panel's app-data:
69
+ // const saved = ctx.getAlignment(fileId);
70
+ // ctx.setAlignment(fileId, matrix.toArray());
71
+ console.log(`[xyz loader] would load "${ctx.name}" (${fileId})`, ctx.components);
72
+ });
73
+
74
+ // Equivalent declarative form — hand the whole registry over at once:
75
+ // modelsList.loaders = { xyz: async (fileId, ctx) => { ... } };
76
+ //
77
+ // Registering a loader for an extension gives its files a load action here —
78
+ // but doesn't control visibility (see the note above). For a CONVERT-style
79
+ // action — e.g. a PLY that runs a PLY→3D-tiles cloud component then views the
80
+ // result — register a RICH entry so the button reads correctly:
81
+ // modelsList.registerLoader("ply", {
82
+ // label: "Generate 3D tiles",
83
+ // icon: "mdi:cube-scan",
84
+ // fn: async (fileId, ctx) => {
85
+ // // const { executionId } = await ctx.client.executeComponent(...);
86
+ // // ...poll until done, then open the produced tileset...
87
+ // },
88
+ // });
89
+
90
+ // ── Extension point 2: override the IFC → fragments converter ────────────────
91
+ // OMIT this to keep the built-in default (drives the project's IfcFragmenter
92
+ // cloud component via the platform client). Provide it to run conversion
93
+ // elsewhere / differently — the panel keeps the progress bar, reload-survival,
94
+ // association and refresh; your converter just `start`s and is `poll`ed.
95
+ modelsList.converter = {
96
+ async start(fileId) {
97
+ // kick off your conversion; return an execution id to poll
98
+ return `exec-${fileId}`;
99
+ },
100
+ async poll(executionId) {
101
+ // report progress; when done, hand back the produced .frag id
102
+ return { done: true, ok: true, fragId: "demo-frag", message: "Converted" };
103
+ },
104
+ };
105
+
106
+ // Mount the panel into a one-area layout. (In a real app it sits beside a
107
+ // top-viewer so loaded models have a world to render into.)
108
+ app.elements = {
109
+ files: () => html`${modelsList}`,
110
+ };
111
+ app.layouts = {
112
+ main: {
113
+ label: "Files",
114
+ icon: "mdi:folder-multiple-outline",
115
+ template: `"files" 1fr / 1fr`,
116
+ },
117
+ };
118
+ app.layout = "main";
119
+
120
+ const container = document.getElementById("that-open-app") ?? document.body;
121
+ container.appendChild(app);
122
+ document.body.style.margin = "0";