@thatopen/services 0.3.11 → 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.
- package/dist/cli/commands/revit.d.ts +9 -0
- package/dist/cli/lib/revit-addin.d.ts +20 -0
- package/dist/cli.js +101 -0
- package/dist/cloud/LasToPotree.d.ts +59 -0
- package/dist/cloud/SplatToFq.d.ts +38 -0
- package/dist/cloud/index.d.ts +2 -0
- package/docs/builtin/paths.json +86 -86
- package/docs/cloud/GaussianSplatConverter.md +43 -0
- package/docs/cloud/PointCloudConverter.md +53 -0
- package/docs/revit-collab-quickstart.md +151 -0
- package/package.json +95 -95
- package/src/built-in/ClashesManager/example.ts +283 -283
- package/src/built-in/UIManager/src/clashes-panel/example.ts +160 -160
- package/src/built-in/UIManager/src/models-list/example.ts +122 -118
- package/src/cli/templates/app/package.json +33 -33
- package/src/cli/templates/app/src/setups/camera-tools.ts +0 -63
- package/src/cli/templates/app/src/setups/camera-views.ts +0 -201
- package/src/cli/templates/app/src/setups/card-header.ts +0 -35
- package/src/cli/templates/app/src/setups/clipper-tool.ts +0 -773
- package/src/cli/templates/app/src/setups/clipper.ts +0 -75
- package/src/cli/templates/app/src/setups/cloud-runner.ts +0 -13
- package/src/cli/templates/app/src/setups/exploded-view.ts +0 -246
- package/src/cli/templates/app/src/setups/helper-panel.ts +0 -88
- package/src/cli/templates/app/src/setups/hider.ts +0 -98
- package/src/cli/templates/app/src/setups/index.ts +0 -23
- package/src/cli/templates/app/src/setups/inspection.ts +0 -136
- package/src/cli/templates/app/src/setups/measurement-panel.ts +0 -188
- package/src/cli/templates/app/src/setups/measurement-tool.ts +0 -588
- package/src/cli/templates/app/src/setups/measurements.ts +0 -133
- package/src/cli/templates/app/src/setups/navigation-gizmo.ts +0 -316
- package/src/cli/templates/app/src/setups/plans-panel.ts +0 -619
- package/src/cli/templates/app/src/setups/right-sidebar.ts +0 -178
- package/src/cli/templates/app/src/setups/styles-panel.ts +0 -110
- package/src/cli/templates/app/src/setups/styles.ts +0 -375
- package/src/cli/templates/app/src/setups/tool-mode-manager.ts +0 -156
- package/src/cli/templates/app/src/setups/tool-mode.ts +0 -23
- package/src/cli/templates/app/src/setups/toolbar.ts +0 -229
- package/src/cli/templates/app/src/setups/ui-manager.ts +0 -27
|
@@ -1,283 +1,283 @@
|
|
|
1
|
-
/* MD
|
|
2
|
-
## ClashesManager
|
|
3
|
-
---
|
|
4
|
-
Structural engineers and BIM coordinators routinely detect geometric clashes
|
|
5
|
-
between disciplines — structural steel penetrating ductwork, pipes clashing
|
|
6
|
-
with slabs — and need to track the status of each issue through resolution
|
|
7
|
-
without leaving their BIM environment.
|
|
8
|
-
|
|
9
|
-
ClashesManager is the platform-side component that owns clash data: it loads and
|
|
10
|
-
persists jobs, runs, and clashes; launches cloud-based clash detection in a Web
|
|
11
|
-
Worker; and exposes the events and methods needed to build review workflows on
|
|
12
|
-
top of it.
|
|
13
|
-
|
|
14
|
-
This tutorial covers initializing the manager from the platform client; the
|
|
15
|
-
recommended `clashes-panel` UI that works out of the box once `init` has been
|
|
16
|
-
called; defining queries and detection matrices for fine-grained control;
|
|
17
|
-
launching a detection run and tracking progress; updating clash status
|
|
18
|
-
programmatically; and rendering sphere markers and element highlights without
|
|
19
|
-
the built-in panel.
|
|
20
|
-
|
|
21
|
-
By the end, you'll understand every public surface of ClashesManager and know
|
|
22
|
-
which parts are handled automatically by `clashes-panel` so you can decide how
|
|
23
|
-
much of the stack you need for your use case.
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import * as THREE from "three";
|
|
27
|
-
import * as OBC from "@thatopen/components";
|
|
28
|
-
import * as OBF from "@thatopen/components-front";
|
|
29
|
-
import * as FRAGS from "@thatopen/fragments";
|
|
30
|
-
import * as BUI from "@thatopen/ui";
|
|
31
|
-
import { PlatformClient, ClashesManager } from "@thatopen/services";
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const client = PlatformClient.fromPlatformContext();
|
|
35
|
-
|
|
36
|
-
const { components } = (await client.setup(
|
|
37
|
-
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
38
|
-
{ uuid: ClashesManager.uuid },
|
|
39
|
-
)) as { components: OBC.Components };
|
|
40
|
-
|
|
41
|
-
/* MD
|
|
42
|
-
### 🚀 Initializing the manager
|
|
43
|
-
|
|
44
|
-
`init(client)` is the only call required before using the manager. It:
|
|
45
|
-
|
|
46
|
-
- Loads all persisted clash data (jobs, runs, clashes, queries, matrices) from
|
|
47
|
-
the project's cloud storage into memory.
|
|
48
|
-
- Registers `save` and `reload` callbacks so writes are debounced to storage
|
|
49
|
-
automatically and the manager can refresh its data after a run completes.
|
|
50
|
-
- Reads any in-flight cloud executions so that a page reload mid-run doesn't
|
|
51
|
-
lose track of the job.
|
|
52
|
-
- Resolves the engine's active `World` in the background — if no world exists
|
|
53
|
-
yet, it waits until one is created, so the order of initialization does not
|
|
54
|
-
matter.
|
|
55
|
-
|
|
56
|
-
After `init` resolves, `manager.ready` is `true` and all data properties
|
|
57
|
-
(`jobs`, `runs`, `clashes`, `queries`, `matrices`) are populated.
|
|
58
|
-
*/
|
|
59
|
-
|
|
60
|
-
const manager = components.get(ClashesManager);
|
|
61
|
-
await manager.init(client);
|
|
62
|
-
|
|
63
|
-
console.log(
|
|
64
|
-
`Loaded ${manager.jobs.length} jobs, ` +
|
|
65
|
-
`${manager.runs.length} runs, ` +
|
|
66
|
-
`${manager.clashes.length} clashes.`,
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
/* MD
|
|
70
|
-
### 🖥️ Adding the built-in panel (recommended)
|
|
71
|
-
|
|
72
|
-
Once `init` has been called, the `<top-clashes-panel>` web component is the
|
|
73
|
-
recommended way to interact with `ClashesManager`. It covers the full
|
|
74
|
-
review workflow — detection matrix, clash table, sphere markers, status
|
|
75
|
-
filters, run progress — with zero additional wiring.
|
|
76
|
-
|
|
77
|
-
`clashes-panel` is registered when `UIManager` is included in the platform
|
|
78
|
-
setup call. See the **clashes-panel** tutorial for the complete setup and a
|
|
79
|
-
detailed breakdown of everything the panel manages automatically.
|
|
80
|
-
|
|
81
|
-
The sections below are for cases where you need direct control: building a
|
|
82
|
-
custom UI, integrating clash data into your own panels, or automating status
|
|
83
|
-
updates from outside the panel.
|
|
84
|
-
*/
|
|
85
|
-
|
|
86
|
-
/* MD
|
|
87
|
-
### 📐 Queries and matrices
|
|
88
|
-
|
|
89
|
-
A `ClashQuery` defines a named set of model elements: which files to scan and,
|
|
90
|
-
optionally, which IFC categories or attribute conditions to match. Queries have
|
|
91
|
-
no detection logic of their own — they are inputs to a matrix.
|
|
92
|
-
|
|
93
|
-
A `ClashMatrix` pairs queries in an upper-triangle grid. Each active cell
|
|
94
|
-
corresponds to one `ClashJob` that the cloud worker will execute. The matrix
|
|
95
|
-
also carries the clash type (`hard` or `clearance`) and, for clearance checks,
|
|
96
|
-
the tolerance in metres.
|
|
97
|
-
|
|
98
|
-
Assigning to `manager.queries` or `manager.matrices` triggers a debounced
|
|
99
|
-
auto-save (2 s). Call `await manager.flush()` to persist immediately before
|
|
100
|
-
navigating away.
|
|
101
|
-
*/
|
|
102
|
-
|
|
103
|
-
const structuralFileId = "file-id-of-structural-model";
|
|
104
|
-
const mepFileId = "file-id-of-mep-model";
|
|
105
|
-
|
|
106
|
-
manager.queries = [
|
|
107
|
-
{
|
|
108
|
-
id: "q-structural",
|
|
109
|
-
name: "Structural",
|
|
110
|
-
modelFileIds: [structuralFileId],
|
|
111
|
-
query: { categories: ["IFCBEAM", "IFCCOLUMN", "IFCSLAB"] },
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
id: "q-mep",
|
|
115
|
-
name: "MEP",
|
|
116
|
-
modelFileIds: [mepFileId],
|
|
117
|
-
// Omitting `query` matches all elements with geometry in the listed files.
|
|
118
|
-
},
|
|
119
|
-
];
|
|
120
|
-
|
|
121
|
-
// The matrix below defines a single hard-clash job between Structural and MEP.
|
|
122
|
-
// `activePairs` lists the upper-triangle cell keys (queryAId:queryBId).
|
|
123
|
-
// `jobIds` is managed by the platform when the matrix is saved; leave it empty
|
|
124
|
-
// on first creation and the backend fills it after the first save.
|
|
125
|
-
manager.matrices = [
|
|
126
|
-
{
|
|
127
|
-
id: "matrix-main",
|
|
128
|
-
name: "Structural vs MEP",
|
|
129
|
-
queryIds: ["q-structural", "q-mep"],
|
|
130
|
-
activePairs: ["q-structural:q-mep"],
|
|
131
|
-
jobIds: [],
|
|
132
|
-
jobType: "hard",
|
|
133
|
-
},
|
|
134
|
-
];
|
|
135
|
-
|
|
136
|
-
await manager.flush();
|
|
137
|
-
|
|
138
|
-
/* MD
|
|
139
|
-
### ▶️ Running clash detection
|
|
140
|
-
|
|
141
|
-
`run(jobId)` launches a Web Worker that calls the cloud clash-detection
|
|
142
|
-
endpoint. It returns immediately — all feedback arrives through events.
|
|
143
|
-
|
|
144
|
-
| Event | Payload | When |
|
|
145
|
-
|-------|---------|------|
|
|
146
|
-
| `onRunProgress` | `{ message, progress }` | Periodically while the worker runs |
|
|
147
|
-
| `onRunComplete` | `{ runId, clashCount }` | Worker finished successfully |
|
|
148
|
-
| `onRunError` | `{ code?, message }` | Worker or cloud error |
|
|
149
|
-
| `onRunningJobsChanged` | `Set<string>` | A job starts or finishes |
|
|
150
|
-
| `onReloadingChanged` | `boolean` | Data reload after a successful run |
|
|
151
|
-
|
|
152
|
-
If the page is reloaded while a run is in progress, `init` re-reads the
|
|
153
|
-
in-flight execution from storage and populates `runningJobIds`. Call
|
|
154
|
-
`reconnectIfRunning(jobId)` to resume receiving progress events for that job.
|
|
155
|
-
*/
|
|
156
|
-
|
|
157
|
-
manager.onRunProgress.add(({ message, progress }) => {
|
|
158
|
-
console.log(`[${progress}%] ${message}`);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
manager.onRunComplete.add(({ runId, clashCount }) => {
|
|
162
|
-
console.log(`Run ${runId} complete — ${clashCount} clash(es) found.`);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
manager.onRunError.add(({ code, message }) => {
|
|
166
|
-
console.error(`Run failed (${code ?? "unknown"}): ${message}`);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
manager.onReloadingChanged.add((reloading) => {
|
|
170
|
-
// Use this flag to show a loading state in a custom UI while fresh data
|
|
171
|
-
// is fetched from storage after the run. clashes-panel handles this automatically.
|
|
172
|
-
console.log(reloading ? "Reloading clash data…" : "Clash data ready.");
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
// Guard: only run if the manager is ready and the job is not already running.
|
|
176
|
-
const jobToRun = manager.jobs[0];
|
|
177
|
-
if (jobToRun && manager.ready && !manager.runningJobIds.has(jobToRun.id)) {
|
|
178
|
-
manager.run(jobToRun.id);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// If a run was already in progress before this page loaded, reconnect to it.
|
|
182
|
-
for (const jobId of manager.runningJobIds) {
|
|
183
|
-
manager.reconnectIfRunning(jobId);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/* MD
|
|
187
|
-
### ✅ Updating clash status
|
|
188
|
-
|
|
189
|
-
Each clash starts with status `"new"`. Reviewers move clashes through a
|
|
190
|
-
workflow by calling `updateClash`. Valid statuses are:
|
|
191
|
-
|
|
192
|
-
- `"new"` — detected, not yet reviewed
|
|
193
|
-
- `"active"` — under investigation
|
|
194
|
-
- `"acknowledged"` — known issue, not blocking
|
|
195
|
-
- `"resolved"` — fixed and verified
|
|
196
|
-
|
|
197
|
-
Status changes and comments are persisted automatically via the same
|
|
198
|
-
auto-save mechanism as queries and matrices.
|
|
199
|
-
|
|
200
|
-
`runsForJob(jobId)` and `clashesForRun(runId)` let you navigate the history:
|
|
201
|
-
a clash's `firstSeenRunId` / `lastSeenRunId` span shows across which runs it
|
|
202
|
-
was present, so you can determine whether a resolution actually closed it.
|
|
203
|
-
*/
|
|
204
|
-
|
|
205
|
-
manager.onClashStatusChanged.add((allClashes) => {
|
|
206
|
-
const open = allClashes.filter(c => c.status === "new" || c.status === "active");
|
|
207
|
-
console.log(`Open clashes: ${open.length}`);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
const clashToAcknowledge = manager.clashes[0];
|
|
211
|
-
if (clashToAcknowledge) {
|
|
212
|
-
manager.updateClash(clashToAcknowledge.id, {
|
|
213
|
-
status: "acknowledged",
|
|
214
|
-
comments: "Coordination with MEP team scheduled for next sprint.",
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Navigate run history for the first job.
|
|
219
|
-
const firstJob = manager.jobs[0];
|
|
220
|
-
if (firstJob) {
|
|
221
|
-
const runs = manager.runsForJob(firstJob.id); // newest first
|
|
222
|
-
const latestRun = runs[0];
|
|
223
|
-
if (latestRun) {
|
|
224
|
-
const clashesInRun = manager.clashesForRun(latestRun.id);
|
|
225
|
-
console.log(`Latest run has ${clashesInRun.length} active clash(es).`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/* MD
|
|
230
|
-
### 📍 Visualization without the built-in panel
|
|
231
|
-
|
|
232
|
-
If you are building a custom UI and not using `clashes-panel`, you can drive
|
|
233
|
-
the visualization layer directly.
|
|
234
|
-
|
|
235
|
-
**Sphere markers** — `showMarkers` places colored 3D spheres at clash
|
|
236
|
-
locations in the viewer. Call `hideMarkers` to remove them and re-enable
|
|
237
|
-
hover highlighting. Points are automatically transformed from IFC world space
|
|
238
|
-
to Three.js viewer space using `FragmentsManager.baseCoordinationMatrix`.
|
|
239
|
-
|
|
240
|
-
**Element highlights** — `highlightAllClashes` colors element A (green) and
|
|
241
|
-
element B (red) for every new/active clash in the provided list.
|
|
242
|
-
`clearClashHighlight` removes both styles. For single-clash focus, use
|
|
243
|
-
`highlightClash` and pair it with `ghostClashes` to dim everything else.
|
|
244
|
-
|
|
245
|
-
**Clustering** — `cluster(clashes, threshold)` groups spatially close clashes
|
|
246
|
-
into a single marker. Pass `threshold = 0` to show one sphere per clash.
|
|
247
|
-
`toViewerPoint` converts a raw IFC point to viewer space if you need to
|
|
248
|
-
position custom objects.
|
|
249
|
-
*/
|
|
250
|
-
|
|
251
|
-
const jobClashes = manager.clashes.filter(c => c.jobId === firstJob?.id);
|
|
252
|
-
|
|
253
|
-
// Show one marker per clash, colored by status.
|
|
254
|
-
manager.showMarkers(
|
|
255
|
-
jobClashes.map(c => ({ id: c.id, point: c.point, count: 1, status: c.status })),
|
|
256
|
-
);
|
|
257
|
-
|
|
258
|
-
// Highlight all open clashes across both element sets.
|
|
259
|
-
manager.highlightAllClashes(jobClashes);
|
|
260
|
-
|
|
261
|
-
// --- Clustered view ---
|
|
262
|
-
// Group clashes within 2 m of each other into a single sphere.
|
|
263
|
-
const clusters = manager.cluster(jobClashes, 2);
|
|
264
|
-
manager.showMarkers(
|
|
265
|
-
clusters.map(group => ({
|
|
266
|
-
point: group[0].point,
|
|
267
|
-
count: group.length,
|
|
268
|
-
status: group[0].status,
|
|
269
|
-
})),
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
// Focus on a single clash: ghost everything else and zoom in.
|
|
273
|
-
const focusClash = jobClashes[0];
|
|
274
|
-
if (focusClash) {
|
|
275
|
-
manager.clearClashHighlight();
|
|
276
|
-
manager.highlightClash(focusClash);
|
|
277
|
-
await manager.ghostClash(focusClash);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Restore full scene when done.
|
|
281
|
-
manager.hideMarkers();
|
|
282
|
-
manager.clearClashHighlight();
|
|
283
|
-
await manager.clearGhostClash();
|
|
1
|
+
/* MD
|
|
2
|
+
## ClashesManager
|
|
3
|
+
---
|
|
4
|
+
Structural engineers and BIM coordinators routinely detect geometric clashes
|
|
5
|
+
between disciplines — structural steel penetrating ductwork, pipes clashing
|
|
6
|
+
with slabs — and need to track the status of each issue through resolution
|
|
7
|
+
without leaving their BIM environment.
|
|
8
|
+
|
|
9
|
+
ClashesManager is the platform-side component that owns clash data: it loads and
|
|
10
|
+
persists jobs, runs, and clashes; launches cloud-based clash detection in a Web
|
|
11
|
+
Worker; and exposes the events and methods needed to build review workflows on
|
|
12
|
+
top of it.
|
|
13
|
+
|
|
14
|
+
This tutorial covers initializing the manager from the platform client; the
|
|
15
|
+
recommended `clashes-panel` UI that works out of the box once `init` has been
|
|
16
|
+
called; defining queries and detection matrices for fine-grained control;
|
|
17
|
+
launching a detection run and tracking progress; updating clash status
|
|
18
|
+
programmatically; and rendering sphere markers and element highlights without
|
|
19
|
+
the built-in panel.
|
|
20
|
+
|
|
21
|
+
By the end, you'll understand every public surface of ClashesManager and know
|
|
22
|
+
which parts are handled automatically by `clashes-panel` so you can decide how
|
|
23
|
+
much of the stack you need for your use case.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import * as THREE from "three";
|
|
27
|
+
import * as OBC from "@thatopen/components";
|
|
28
|
+
import * as OBF from "@thatopen/components-front";
|
|
29
|
+
import * as FRAGS from "@thatopen/fragments";
|
|
30
|
+
import * as BUI from "@thatopen/ui";
|
|
31
|
+
import { PlatformClient, ClashesManager } from "@thatopen/services";
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
const client = PlatformClient.fromPlatformContext();
|
|
35
|
+
|
|
36
|
+
const { components } = (await client.setup(
|
|
37
|
+
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
38
|
+
{ uuid: ClashesManager.uuid },
|
|
39
|
+
)) as { components: OBC.Components };
|
|
40
|
+
|
|
41
|
+
/* MD
|
|
42
|
+
### 🚀 Initializing the manager
|
|
43
|
+
|
|
44
|
+
`init(client)` is the only call required before using the manager. It:
|
|
45
|
+
|
|
46
|
+
- Loads all persisted clash data (jobs, runs, clashes, queries, matrices) from
|
|
47
|
+
the project's cloud storage into memory.
|
|
48
|
+
- Registers `save` and `reload` callbacks so writes are debounced to storage
|
|
49
|
+
automatically and the manager can refresh its data after a run completes.
|
|
50
|
+
- Reads any in-flight cloud executions so that a page reload mid-run doesn't
|
|
51
|
+
lose track of the job.
|
|
52
|
+
- Resolves the engine's active `World` in the background — if no world exists
|
|
53
|
+
yet, it waits until one is created, so the order of initialization does not
|
|
54
|
+
matter.
|
|
55
|
+
|
|
56
|
+
After `init` resolves, `manager.ready` is `true` and all data properties
|
|
57
|
+
(`jobs`, `runs`, `clashes`, `queries`, `matrices`) are populated.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
const manager = components.get(ClashesManager);
|
|
61
|
+
await manager.init(client);
|
|
62
|
+
|
|
63
|
+
console.log(
|
|
64
|
+
`Loaded ${manager.jobs.length} jobs, ` +
|
|
65
|
+
`${manager.runs.length} runs, ` +
|
|
66
|
+
`${manager.clashes.length} clashes.`,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
/* MD
|
|
70
|
+
### 🖥️ Adding the built-in panel (recommended)
|
|
71
|
+
|
|
72
|
+
Once `init` has been called, the `<top-clashes-panel>` web component is the
|
|
73
|
+
recommended way to interact with `ClashesManager`. It covers the full
|
|
74
|
+
review workflow — detection matrix, clash table, sphere markers, status
|
|
75
|
+
filters, run progress — with zero additional wiring.
|
|
76
|
+
|
|
77
|
+
`clashes-panel` is registered when `UIManager` is included in the platform
|
|
78
|
+
setup call. See the **clashes-panel** tutorial for the complete setup and a
|
|
79
|
+
detailed breakdown of everything the panel manages automatically.
|
|
80
|
+
|
|
81
|
+
The sections below are for cases where you need direct control: building a
|
|
82
|
+
custom UI, integrating clash data into your own panels, or automating status
|
|
83
|
+
updates from outside the panel.
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
/* MD
|
|
87
|
+
### 📐 Queries and matrices
|
|
88
|
+
|
|
89
|
+
A `ClashQuery` defines a named set of model elements: which files to scan and,
|
|
90
|
+
optionally, which IFC categories or attribute conditions to match. Queries have
|
|
91
|
+
no detection logic of their own — they are inputs to a matrix.
|
|
92
|
+
|
|
93
|
+
A `ClashMatrix` pairs queries in an upper-triangle grid. Each active cell
|
|
94
|
+
corresponds to one `ClashJob` that the cloud worker will execute. The matrix
|
|
95
|
+
also carries the clash type (`hard` or `clearance`) and, for clearance checks,
|
|
96
|
+
the tolerance in metres.
|
|
97
|
+
|
|
98
|
+
Assigning to `manager.queries` or `manager.matrices` triggers a debounced
|
|
99
|
+
auto-save (2 s). Call `await manager.flush()` to persist immediately before
|
|
100
|
+
navigating away.
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
const structuralFileId = "file-id-of-structural-model";
|
|
104
|
+
const mepFileId = "file-id-of-mep-model";
|
|
105
|
+
|
|
106
|
+
manager.queries = [
|
|
107
|
+
{
|
|
108
|
+
id: "q-structural",
|
|
109
|
+
name: "Structural",
|
|
110
|
+
modelFileIds: [structuralFileId],
|
|
111
|
+
query: { categories: ["IFCBEAM", "IFCCOLUMN", "IFCSLAB"] },
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: "q-mep",
|
|
115
|
+
name: "MEP",
|
|
116
|
+
modelFileIds: [mepFileId],
|
|
117
|
+
// Omitting `query` matches all elements with geometry in the listed files.
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
// The matrix below defines a single hard-clash job between Structural and MEP.
|
|
122
|
+
// `activePairs` lists the upper-triangle cell keys (queryAId:queryBId).
|
|
123
|
+
// `jobIds` is managed by the platform when the matrix is saved; leave it empty
|
|
124
|
+
// on first creation and the backend fills it after the first save.
|
|
125
|
+
manager.matrices = [
|
|
126
|
+
{
|
|
127
|
+
id: "matrix-main",
|
|
128
|
+
name: "Structural vs MEP",
|
|
129
|
+
queryIds: ["q-structural", "q-mep"],
|
|
130
|
+
activePairs: ["q-structural:q-mep"],
|
|
131
|
+
jobIds: [],
|
|
132
|
+
jobType: "hard",
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
await manager.flush();
|
|
137
|
+
|
|
138
|
+
/* MD
|
|
139
|
+
### ▶️ Running clash detection
|
|
140
|
+
|
|
141
|
+
`run(jobId)` launches a Web Worker that calls the cloud clash-detection
|
|
142
|
+
endpoint. It returns immediately — all feedback arrives through events.
|
|
143
|
+
|
|
144
|
+
| Event | Payload | When |
|
|
145
|
+
|-------|---------|------|
|
|
146
|
+
| `onRunProgress` | `{ message, progress }` | Periodically while the worker runs |
|
|
147
|
+
| `onRunComplete` | `{ runId, clashCount }` | Worker finished successfully |
|
|
148
|
+
| `onRunError` | `{ code?, message }` | Worker or cloud error |
|
|
149
|
+
| `onRunningJobsChanged` | `Set<string>` | A job starts or finishes |
|
|
150
|
+
| `onReloadingChanged` | `boolean` | Data reload after a successful run |
|
|
151
|
+
|
|
152
|
+
If the page is reloaded while a run is in progress, `init` re-reads the
|
|
153
|
+
in-flight execution from storage and populates `runningJobIds`. Call
|
|
154
|
+
`reconnectIfRunning(jobId)` to resume receiving progress events for that job.
|
|
155
|
+
*/
|
|
156
|
+
|
|
157
|
+
manager.onRunProgress.add(({ message, progress }) => {
|
|
158
|
+
console.log(`[${progress}%] ${message}`);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
manager.onRunComplete.add(({ runId, clashCount }) => {
|
|
162
|
+
console.log(`Run ${runId} complete — ${clashCount} clash(es) found.`);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
manager.onRunError.add(({ code, message }) => {
|
|
166
|
+
console.error(`Run failed (${code ?? "unknown"}): ${message}`);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
manager.onReloadingChanged.add((reloading) => {
|
|
170
|
+
// Use this flag to show a loading state in a custom UI while fresh data
|
|
171
|
+
// is fetched from storage after the run. clashes-panel handles this automatically.
|
|
172
|
+
console.log(reloading ? "Reloading clash data…" : "Clash data ready.");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Guard: only run if the manager is ready and the job is not already running.
|
|
176
|
+
const jobToRun = manager.jobs[0];
|
|
177
|
+
if (jobToRun && manager.ready && !manager.runningJobIds.has(jobToRun.id)) {
|
|
178
|
+
manager.run(jobToRun.id);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// If a run was already in progress before this page loaded, reconnect to it.
|
|
182
|
+
for (const jobId of manager.runningJobIds) {
|
|
183
|
+
manager.reconnectIfRunning(jobId);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* MD
|
|
187
|
+
### ✅ Updating clash status
|
|
188
|
+
|
|
189
|
+
Each clash starts with status `"new"`. Reviewers move clashes through a
|
|
190
|
+
workflow by calling `updateClash`. Valid statuses are:
|
|
191
|
+
|
|
192
|
+
- `"new"` — detected, not yet reviewed
|
|
193
|
+
- `"active"` — under investigation
|
|
194
|
+
- `"acknowledged"` — known issue, not blocking
|
|
195
|
+
- `"resolved"` — fixed and verified
|
|
196
|
+
|
|
197
|
+
Status changes and comments are persisted automatically via the same
|
|
198
|
+
auto-save mechanism as queries and matrices.
|
|
199
|
+
|
|
200
|
+
`runsForJob(jobId)` and `clashesForRun(runId)` let you navigate the history:
|
|
201
|
+
a clash's `firstSeenRunId` / `lastSeenRunId` span shows across which runs it
|
|
202
|
+
was present, so you can determine whether a resolution actually closed it.
|
|
203
|
+
*/
|
|
204
|
+
|
|
205
|
+
manager.onClashStatusChanged.add((allClashes) => {
|
|
206
|
+
const open = allClashes.filter(c => c.status === "new" || c.status === "active");
|
|
207
|
+
console.log(`Open clashes: ${open.length}`);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const clashToAcknowledge = manager.clashes[0];
|
|
211
|
+
if (clashToAcknowledge) {
|
|
212
|
+
manager.updateClash(clashToAcknowledge.id, {
|
|
213
|
+
status: "acknowledged",
|
|
214
|
+
comments: "Coordination with MEP team scheduled for next sprint.",
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Navigate run history for the first job.
|
|
219
|
+
const firstJob = manager.jobs[0];
|
|
220
|
+
if (firstJob) {
|
|
221
|
+
const runs = manager.runsForJob(firstJob.id); // newest first
|
|
222
|
+
const latestRun = runs[0];
|
|
223
|
+
if (latestRun) {
|
|
224
|
+
const clashesInRun = manager.clashesForRun(latestRun.id);
|
|
225
|
+
console.log(`Latest run has ${clashesInRun.length} active clash(es).`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* MD
|
|
230
|
+
### 📍 Visualization without the built-in panel
|
|
231
|
+
|
|
232
|
+
If you are building a custom UI and not using `clashes-panel`, you can drive
|
|
233
|
+
the visualization layer directly.
|
|
234
|
+
|
|
235
|
+
**Sphere markers** — `showMarkers` places colored 3D spheres at clash
|
|
236
|
+
locations in the viewer. Call `hideMarkers` to remove them and re-enable
|
|
237
|
+
hover highlighting. Points are automatically transformed from IFC world space
|
|
238
|
+
to Three.js viewer space using `FragmentsManager.baseCoordinationMatrix`.
|
|
239
|
+
|
|
240
|
+
**Element highlights** — `highlightAllClashes` colors element A (green) and
|
|
241
|
+
element B (red) for every new/active clash in the provided list.
|
|
242
|
+
`clearClashHighlight` removes both styles. For single-clash focus, use
|
|
243
|
+
`highlightClash` and pair it with `ghostClashes` to dim everything else.
|
|
244
|
+
|
|
245
|
+
**Clustering** — `cluster(clashes, threshold)` groups spatially close clashes
|
|
246
|
+
into a single marker. Pass `threshold = 0` to show one sphere per clash.
|
|
247
|
+
`toViewerPoint` converts a raw IFC point to viewer space if you need to
|
|
248
|
+
position custom objects.
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
const jobClashes = manager.clashes.filter(c => c.jobId === firstJob?.id);
|
|
252
|
+
|
|
253
|
+
// Show one marker per clash, colored by status.
|
|
254
|
+
manager.showMarkers(
|
|
255
|
+
jobClashes.map(c => ({ id: c.id, point: c.point, count: 1, status: c.status })),
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// Highlight all open clashes across both element sets.
|
|
259
|
+
manager.highlightAllClashes(jobClashes);
|
|
260
|
+
|
|
261
|
+
// --- Clustered view ---
|
|
262
|
+
// Group clashes within 2 m of each other into a single sphere.
|
|
263
|
+
const clusters = manager.cluster(jobClashes, 2);
|
|
264
|
+
manager.showMarkers(
|
|
265
|
+
clusters.map(group => ({
|
|
266
|
+
point: group[0].point,
|
|
267
|
+
count: group.length,
|
|
268
|
+
status: group[0].status,
|
|
269
|
+
})),
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// Focus on a single clash: ghost everything else and zoom in.
|
|
273
|
+
const focusClash = jobClashes[0];
|
|
274
|
+
if (focusClash) {
|
|
275
|
+
manager.clearClashHighlight();
|
|
276
|
+
manager.highlightClash(focusClash);
|
|
277
|
+
await manager.ghostClash(focusClash);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Restore full scene when done.
|
|
281
|
+
manager.hideMarkers();
|
|
282
|
+
manager.clearClashHighlight();
|
|
283
|
+
await manager.clearGhostClash();
|