@thatopen/services 0.3.8 → 0.3.9
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/docs/builtin/paths.json +86 -86
- package/package.json +95 -95
- package/src/built-in/ClashesManager/example.ts +285 -285
- package/src/built-in/UIManager/src/clashes-panel/example.ts +164 -164
package/docs/builtin/paths.json
CHANGED
|
@@ -1,86 +1,86 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"path": "src/built-in/CDEManager/example.ts",
|
|
4
|
-
"description": "CDEManager — CDEManager is the central state machine for a Common Data Environment. It holds files, folders, metadata schemas, viewer groups, and user permissions, and exposes a typed state machine that all file operations — selecting, editing, uploading, viewing — route through. It has no DOM or Three.js dependencies and can run in Node, a browser, or a server-side worker wherever `@thatopen/components` is available."
|
|
5
|
-
},
|
|
6
|
-
{
|
|
7
|
-
"path": "src/built-in/CDEManager/examples/FileEditing/example.ts",
|
|
8
|
-
"description": "Bulk file editing — stage, preview, revert, and commit — A project manager preparing a deliverable handoff needs to update discipline tags across fifteen IFC files after a scope change, rename each to match the new naming convention, and archive three superseded models — all before anything is sent to the backend. Doing the changes one file at a time risks leaving the file list in a half-updated state if the session is interrupted, and there is no way to review exactly what will change before committing."
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
"path": "src/built-in/CDEManager/examples/FileMoves/example.ts",
|
|
12
|
-
"description": "Drag-and-drop file and folder moves — A coordinator restructures the project after a scope change: structural files move from the root into a dedicated discipline folder, a MEP sub-folder needs to be promoted to the top level, and a few documents need to come back to root to appear in the general view. Drag-and-drop is the natural gesture for all of this, but each drag session has intermediate stages — the file is \"in the air\", hovering over a candidate target — and every panel watching the file table should see the right state without coordinating directly with the drag component."
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"path": "src/built-in/CDEManager/examples/FileOperations/example.ts",
|
|
16
|
-
"description": "File operations — inline edit, rename, view, download, archive — BIM coordinators reviewing a model drop repeat the same small actions throughout their session: correcting a discipline tag that changed after a scope revision, renaming a file to match the agreed convention, opening it alongside the structural model to check for clashes, handing it off to a sub-consultant, or removing a superseded version from the live set. If every panel in the platform handles these separately, two panels looking at the same file at the same time can show contradictory states — one says \"uploading\", another still shows the old name."
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"path": "src/built-in/CDEManager/examples/FileTransfers/example.ts",
|
|
20
|
-
"description": "File uploads, versioning, and the download cache — A site coordinator drops a batch of files onto the CDE every Monday after the weekend review: some are entirely new models, some are revised versions of files already in the project, and sometimes the metadata group they upload into should be stamped automatically onto the new files. If the platform makes the coordinator sort this out manually, it slows the handoff and introduces naming errors. And if an upload fails halfway through a batch, the file table should land in a safe state rather than freeze at a loading spinner."
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"path": "src/built-in/CDEManager/examples/FolderManagement/example.ts",
|
|
24
|
-
"description": "Folder management — create, rename, archive, and commit — A project reaches the construction phase and the coordination team needs to restructure the folder hierarchy: add a \"Facades\" discipline folder, rename \"Structural\" to its official abbreviation, and archive two superseded folders that have been merged. Doing these one at a time through separate API calls means the folder tree flickers between invalid states as each change lands — a freshly created folder appears before its sibling is renamed, and the archived folders briefly disappear before the new one is visible."
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"path": "src/built-in/CDEManager/examples/MetadataSchema/example.ts",
|
|
28
|
-
"description": "Metadata schema — define, stage, and publish field definitions — Every project has its own vocabulary for describing files: discipline codes, revision phases, area zones, review statuses. A metadata schema defines which fields exist, what values they accept, and whether they are mandatory. Without a structured schema, each user invents their own labels and the file list becomes impossible to filter or report on consistently."
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"path": "src/built-in/CDEManager/examples/MetadataViews/example.ts",
|
|
32
|
-
"description": "Metadata views — organize fields into named table layouts — A structural engineer and an MEP coordinator look at the same file list but care about completely different metadata. Showing every field to everyone produces a wide table that is hard to navigate. Metadata views let each role define which fields appear in which order — saving the layout so the table opens correctly next time without requiring a manual setup every session."
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"path": "src/built-in/CDEManager/examples/ProjectConfig/example.ts",
|
|
36
|
-
"description": "Project configuration — read, write, and reload persistent data — A CDE application needs to store two kinds of configuration: project-wide settings that all team members share (the metadata schema, custom naming rules) and personal settings that belong to one user (which columns are visible, which metadata views are pinned). Both live inside the project's file storage under a reserved system folder, so they travel with the project and don't rely on a separate configuration service."
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
"path": "src/built-in/ClashesManager/example.ts",
|
|
40
|
-
"description": "ClashesManager — Structural engineers and BIM coordinators routinely detect geometric clashes between disciplines — structural steel penetrating ductwork, pipes clashing with slabs — and need to track the status of each issue through resolution without leaving their BIM environment."
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
"path": "src/built-in/UIManager/src/app/example.ts",
|
|
44
|
-
"description": "top-app — Front-end developers building BIM web apps need to coordinate several async initialization steps before the UI can be shown — loading the engine, preparing fragment workers, registering loaders — and users need visible feedback on what is happening during that wait, rather than a blank screen."
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
"path": "src/built-in/UIManager/src/app/examples/AppState/example.ts",
|
|
48
|
-
"description": "Shared reactive state with AppState — Developers building interactive BIM panels often need sibling components — a filter selector and a results table, for example — to stay in sync without a shared parent. Lifting state to a common ancestor and threading it down as props works for shallow trees but becomes unwieldy as the component tree grows."
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
"path": "src/built-in/UIManager/src/app/examples/Banner/example.ts",
|
|
52
|
-
"description": "Persistent banners with top:banner — Some application states need a persistent, visible indicator that stays on screen until the state is explicitly cleared — an archived-files mode, a read-only permission warning, a degraded-connectivity notice. A toast auto-dismisses after a few seconds, which is wrong for these cases. A modal blocks interaction entirely, which is too heavy."
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
"path": "src/built-in/UIManager/src/app/examples/Contexts/example.ts",
|
|
56
|
-
"description": "Consuming platform contexts in child components — Developers building custom BIM panels need access to the 3D engine, the platform API, and the current project's metadata — but threading these objects down through props across multiple component layers forces every intermediate element to know about data it doesn't use, making trees brittle and hard to refactor."
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
"path": "src/built-in/UIManager/src/app/examples/FileDrop/example.ts",
|
|
60
|
-
"description": "File drag-and-drop with top:files-dropped — Desktop BIM workflows often rely on dragging files straight from the OS file manager onto the browser window — but wiring a document-level dragenter, dragover, dragleave, and drop handler that correctly tracks nested enter/leave counts, shows a visual overlay, and forwards the files to the right component is tedious boilerplate to repeat for every app."
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"path": "src/built-in/UIManager/src/app/examples/FloatingLayout/example.ts",
|
|
64
|
-
"description": "Floating layout — Developers building BIM coordination tools often want the 3D model to fill the entire window while control panels float above it — but standard grid layouts give every area equal standing, shrinking the viewer into one cell and leaving visible seams between it and the panels."
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
"path": "src/built-in/UIManager/src/app/examples/Modal/example.ts",
|
|
68
|
-
"description": "Confirmation modals with top:modal — Destructive or irreversible actions — deleting records, overwriting files, leaving unsaved changes — should ask the user to confirm before proceeding. Opening a custom dialog element from a deeply nested component means threading a reference to a controller up through every layer of the tree."
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
"path": "src/built-in/UIManager/src/app/examples/Notifications/example.ts",
|
|
72
|
-
"description": "Toasts and cross-component notifications — Developers building BIM processing UIs need to surface status updates, errors, and async feedback to users from different points in the component tree — but reaching back to the app root to trigger a notification from a deeply nested component requires threading a reference through every layer in between."
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
"path": "src/built-in/UIManager/src/app/examples/Sidebar/example.ts",
|
|
76
|
-
"description": "Sidebar navigation — Product teams building BIM apps often need multiple distinct views — a 3D model viewer, a data browser, a settings screen — but wiring a custom nav bar and managing view transitions from scratch adds complexity before any real feature work begins."
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
"path": "src/built-in/UIManager/src/clashes-panel/example.ts",
|
|
80
|
-
"description": "clashes-panel — BIM coordinators need a dedicated review environment where they can see all detected clashes, filter by status, navigate to each issue in the 3D viewer, and mark items as resolved — without writing any UI code themselves."
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
"path": "src/built-in/UIManager/src/models-list/example.ts",
|
|
84
|
-
"description": "top-models-list — 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."
|
|
85
|
-
}
|
|
86
|
-
]
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"path": "src/built-in/CDEManager/example.ts",
|
|
4
|
+
"description": "CDEManager — CDEManager is the central state machine for a Common Data Environment. It holds files, folders, metadata schemas, viewer groups, and user permissions, and exposes a typed state machine that all file operations — selecting, editing, uploading, viewing — route through. It has no DOM or Three.js dependencies and can run in Node, a browser, or a server-side worker wherever `@thatopen/components` is available."
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
"path": "src/built-in/CDEManager/examples/FileEditing/example.ts",
|
|
8
|
+
"description": "Bulk file editing — stage, preview, revert, and commit — A project manager preparing a deliverable handoff needs to update discipline tags across fifteen IFC files after a scope change, rename each to match the new naming convention, and archive three superseded models — all before anything is sent to the backend. Doing the changes one file at a time risks leaving the file list in a half-updated state if the session is interrupted, and there is no way to review exactly what will change before committing."
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"path": "src/built-in/CDEManager/examples/FileMoves/example.ts",
|
|
12
|
+
"description": "Drag-and-drop file and folder moves — A coordinator restructures the project after a scope change: structural files move from the root into a dedicated discipline folder, a MEP sub-folder needs to be promoted to the top level, and a few documents need to come back to root to appear in the general view. Drag-and-drop is the natural gesture for all of this, but each drag session has intermediate stages — the file is \"in the air\", hovering over a candidate target — and every panel watching the file table should see the right state without coordinating directly with the drag component."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"path": "src/built-in/CDEManager/examples/FileOperations/example.ts",
|
|
16
|
+
"description": "File operations — inline edit, rename, view, download, archive — BIM coordinators reviewing a model drop repeat the same small actions throughout their session: correcting a discipline tag that changed after a scope revision, renaming a file to match the agreed convention, opening it alongside the structural model to check for clashes, handing it off to a sub-consultant, or removing a superseded version from the live set. If every panel in the platform handles these separately, two panels looking at the same file at the same time can show contradictory states — one says \"uploading\", another still shows the old name."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"path": "src/built-in/CDEManager/examples/FileTransfers/example.ts",
|
|
20
|
+
"description": "File uploads, versioning, and the download cache — A site coordinator drops a batch of files onto the CDE every Monday after the weekend review: some are entirely new models, some are revised versions of files already in the project, and sometimes the metadata group they upload into should be stamped automatically onto the new files. If the platform makes the coordinator sort this out manually, it slows the handoff and introduces naming errors. And if an upload fails halfway through a batch, the file table should land in a safe state rather than freeze at a loading spinner."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": "src/built-in/CDEManager/examples/FolderManagement/example.ts",
|
|
24
|
+
"description": "Folder management — create, rename, archive, and commit — A project reaches the construction phase and the coordination team needs to restructure the folder hierarchy: add a \"Facades\" discipline folder, rename \"Structural\" to its official abbreviation, and archive two superseded folders that have been merged. Doing these one at a time through separate API calls means the folder tree flickers between invalid states as each change lands — a freshly created folder appears before its sibling is renamed, and the archived folders briefly disappear before the new one is visible."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"path": "src/built-in/CDEManager/examples/MetadataSchema/example.ts",
|
|
28
|
+
"description": "Metadata schema — define, stage, and publish field definitions — Every project has its own vocabulary for describing files: discipline codes, revision phases, area zones, review statuses. A metadata schema defines which fields exist, what values they accept, and whether they are mandatory. Without a structured schema, each user invents their own labels and the file list becomes impossible to filter or report on consistently."
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"path": "src/built-in/CDEManager/examples/MetadataViews/example.ts",
|
|
32
|
+
"description": "Metadata views — organize fields into named table layouts — A structural engineer and an MEP coordinator look at the same file list but care about completely different metadata. Showing every field to everyone produces a wide table that is hard to navigate. Metadata views let each role define which fields appear in which order — saving the layout so the table opens correctly next time without requiring a manual setup every session."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"path": "src/built-in/CDEManager/examples/ProjectConfig/example.ts",
|
|
36
|
+
"description": "Project configuration — read, write, and reload persistent data — A CDE application needs to store two kinds of configuration: project-wide settings that all team members share (the metadata schema, custom naming rules) and personal settings that belong to one user (which columns are visible, which metadata views are pinned). Both live inside the project's file storage under a reserved system folder, so they travel with the project and don't rely on a separate configuration service."
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"path": "src/built-in/ClashesManager/example.ts",
|
|
40
|
+
"description": "ClashesManager — Structural engineers and BIM coordinators routinely detect geometric clashes between disciplines — structural steel penetrating ductwork, pipes clashing with slabs — and need to track the status of each issue through resolution without leaving their BIM environment."
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"path": "src/built-in/UIManager/src/app/example.ts",
|
|
44
|
+
"description": "top-app — Front-end developers building BIM web apps need to coordinate several async initialization steps before the UI can be shown — loading the engine, preparing fragment workers, registering loaders — and users need visible feedback on what is happening during that wait, rather than a blank screen."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"path": "src/built-in/UIManager/src/app/examples/AppState/example.ts",
|
|
48
|
+
"description": "Shared reactive state with AppState — Developers building interactive BIM panels often need sibling components — a filter selector and a results table, for example — to stay in sync without a shared parent. Lifting state to a common ancestor and threading it down as props works for shallow trees but becomes unwieldy as the component tree grows."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"path": "src/built-in/UIManager/src/app/examples/Banner/example.ts",
|
|
52
|
+
"description": "Persistent banners with top:banner — Some application states need a persistent, visible indicator that stays on screen until the state is explicitly cleared — an archived-files mode, a read-only permission warning, a degraded-connectivity notice. A toast auto-dismisses after a few seconds, which is wrong for these cases. A modal blocks interaction entirely, which is too heavy."
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"path": "src/built-in/UIManager/src/app/examples/Contexts/example.ts",
|
|
56
|
+
"description": "Consuming platform contexts in child components — Developers building custom BIM panels need access to the 3D engine, the platform API, and the current project's metadata — but threading these objects down through props across multiple component layers forces every intermediate element to know about data it doesn't use, making trees brittle and hard to refactor."
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"path": "src/built-in/UIManager/src/app/examples/FileDrop/example.ts",
|
|
60
|
+
"description": "File drag-and-drop with top:files-dropped — Desktop BIM workflows often rely on dragging files straight from the OS file manager onto the browser window — but wiring a document-level dragenter, dragover, dragleave, and drop handler that correctly tracks nested enter/leave counts, shows a visual overlay, and forwards the files to the right component is tedious boilerplate to repeat for every app."
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"path": "src/built-in/UIManager/src/app/examples/FloatingLayout/example.ts",
|
|
64
|
+
"description": "Floating layout — Developers building BIM coordination tools often want the 3D model to fill the entire window while control panels float above it — but standard grid layouts give every area equal standing, shrinking the viewer into one cell and leaving visible seams between it and the panels."
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"path": "src/built-in/UIManager/src/app/examples/Modal/example.ts",
|
|
68
|
+
"description": "Confirmation modals with top:modal — Destructive or irreversible actions — deleting records, overwriting files, leaving unsaved changes — should ask the user to confirm before proceeding. Opening a custom dialog element from a deeply nested component means threading a reference to a controller up through every layer of the tree."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"path": "src/built-in/UIManager/src/app/examples/Notifications/example.ts",
|
|
72
|
+
"description": "Toasts and cross-component notifications — Developers building BIM processing UIs need to surface status updates, errors, and async feedback to users from different points in the component tree — but reaching back to the app root to trigger a notification from a deeply nested component requires threading a reference through every layer in between."
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"path": "src/built-in/UIManager/src/app/examples/Sidebar/example.ts",
|
|
76
|
+
"description": "Sidebar navigation — Product teams building BIM apps often need multiple distinct views — a 3D model viewer, a data browser, a settings screen — but wiring a custom nav bar and managing view transitions from scratch adds complexity before any real feature work begins."
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"path": "src/built-in/UIManager/src/clashes-panel/example.ts",
|
|
80
|
+
"description": "clashes-panel — BIM coordinators need a dedicated review environment where they can see all detected clashes, filter by status, navigate to each issue in the 3D viewer, and mark items as resolved — without writing any UI code themselves."
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"path": "src/built-in/UIManager/src/models-list/example.ts",
|
|
84
|
+
"description": "top-models-list — 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."
|
|
85
|
+
}
|
|
86
|
+
]
|
package/package.json
CHANGED
|
@@ -1,95 +1,95 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@thatopen/services",
|
|
3
|
-
"description": "Client library and CLI for building BIM apps and cloud components on the That Open Platform",
|
|
4
|
-
"author": "That Open Company",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"homepage": "https://github.com/ThatOpen/engine_services#readme",
|
|
7
|
-
"bugs": {
|
|
8
|
-
"url": "https://github.com/ThatOpen/engine_services/issues"
|
|
9
|
-
},
|
|
10
|
-
"repository": {
|
|
11
|
-
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/ThatOpen/engine_services.git"
|
|
13
|
-
},
|
|
14
|
-
"publishConfig": {
|
|
15
|
-
"access": "public"
|
|
16
|
-
},
|
|
17
|
-
"private": false,
|
|
18
|
-
"version": "0.3.
|
|
19
|
-
"main": "dist/index.cjs.js",
|
|
20
|
-
"module": "dist/index.es.js",
|
|
21
|
-
"types": "dist/index.d.ts",
|
|
22
|
-
"bin": {
|
|
23
|
-
"thatopen": "dist/cli.js"
|
|
24
|
-
},
|
|
25
|
-
"files": [
|
|
26
|
-
"dist",
|
|
27
|
-
"src/cli/templates",
|
|
28
|
-
"src/built-in",
|
|
29
|
-
"docs",
|
|
30
|
-
"resources"
|
|
31
|
-
],
|
|
32
|
-
"scripts": {
|
|
33
|
-
"dev": "vite",
|
|
34
|
-
"lint": "eslint src/",
|
|
35
|
-
"build": "eslint src/ && tsc && vite build && vite build --config vite.config.cli.mts && node scripts/generate-cli-docs-paths.mjs && node scripts/generate-client-examples-paths.mjs",
|
|
36
|
-
"build:lib": "tsc && vite build",
|
|
37
|
-
"test": "vitest run",
|
|
38
|
-
"test:watch": "vitest",
|
|
39
|
-
"build:cli": "vite build --config vite.config.cli.mts",
|
|
40
|
-
"preview": "vite preview",
|
|
41
|
-
"test:ui": "vite --config test/vite.config.mts",
|
|
42
|
-
"template:app": "cd src/cli/templates/app && npm install && cd ../../../.. && node scripts/generate-template-tsconfig.mjs",
|
|
43
|
-
"template:app:dev": "cd src/cli/templates/app && npm run dev",
|
|
44
|
-
"changeset": "changeset",
|
|
45
|
-
"version": "changeset version",
|
|
46
|
-
"prepublishOnly": "npm run build"
|
|
47
|
-
},
|
|
48
|
-
"devDependencies": {
|
|
49
|
-
"@changesets/cli": "^2.27.7",
|
|
50
|
-
"@thatopen/components": "~3.4.0",
|
|
51
|
-
"@thatopen/components-front": "~3.4.0",
|
|
52
|
-
"@thatopen/fragments": "~3.4.0",
|
|
53
|
-
"@thatopen/ui": "~3.4.0",
|
|
54
|
-
"@thatopen/ui-obc": "~3.4.0",
|
|
55
|
-
"@types/node": "^20.12.12",
|
|
56
|
-
"@types/three": "^0.182.0",
|
|
57
|
-
"commander": "^14.0.3",
|
|
58
|
-
"esbuild": "^0.27.3",
|
|
59
|
-
"eslint": "9.x",
|
|
60
|
-
"eslint-config-prettier": "^9.1.0",
|
|
61
|
-
"eslint-plugin-prettier": "^5.1.3",
|
|
62
|
-
"globals": "^15.3.0",
|
|
63
|
-
"jszip": "^3.10.1",
|
|
64
|
-
"prettier": "^3.2.5",
|
|
65
|
-
"three": ">=0.182.0",
|
|
66
|
-
"tsx": "^4.16.2",
|
|
67
|
-
"typescript": "^5.2.2",
|
|
68
|
-
"typescript-eslint": "^7.10.0",
|
|
69
|
-
"vite": "^5.2.0",
|
|
70
|
-
"vite-plugin-dts": "^3.9.1",
|
|
71
|
-
"vite-plugin-eslint": "^1.8.1",
|
|
72
|
-
"vitest": "^4.1.4"
|
|
73
|
-
},
|
|
74
|
-
"dependencies": {
|
|
75
|
-
"dotenv": "^16.4.5",
|
|
76
|
-
"socket.io": "^4.8.0",
|
|
77
|
-
"socket.io-client": "^4.8.0"
|
|
78
|
-
},
|
|
79
|
-
"peerDependencies": {
|
|
80
|
-
"@thatopen/components": "~3.4.0",
|
|
81
|
-
"@thatopen/ui": "~3.4.0",
|
|
82
|
-
"three": ">=0.182.0"
|
|
83
|
-
},
|
|
84
|
-
"peerDependenciesMeta": {
|
|
85
|
-
"@thatopen/components": {
|
|
86
|
-
"optional": true
|
|
87
|
-
},
|
|
88
|
-
"@thatopen/ui": {
|
|
89
|
-
"optional": true
|
|
90
|
-
},
|
|
91
|
-
"three": {
|
|
92
|
-
"optional": true
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@thatopen/services",
|
|
3
|
+
"description": "Client library and CLI for building BIM apps and cloud components on the That Open Platform",
|
|
4
|
+
"author": "That Open Company",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/ThatOpen/engine_services#readme",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/ThatOpen/engine_services/issues"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/ThatOpen/engine_services.git"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"private": false,
|
|
18
|
+
"version": "0.3.9",
|
|
19
|
+
"main": "dist/index.cjs.js",
|
|
20
|
+
"module": "dist/index.es.js",
|
|
21
|
+
"types": "dist/index.d.ts",
|
|
22
|
+
"bin": {
|
|
23
|
+
"thatopen": "dist/cli.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"src/cli/templates",
|
|
28
|
+
"src/built-in",
|
|
29
|
+
"docs",
|
|
30
|
+
"resources"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"dev": "vite",
|
|
34
|
+
"lint": "eslint src/",
|
|
35
|
+
"build": "eslint src/ && tsc && vite build && vite build --config vite.config.cli.mts && node scripts/generate-cli-docs-paths.mjs && node scripts/generate-client-examples-paths.mjs",
|
|
36
|
+
"build:lib": "tsc && vite build",
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"test:watch": "vitest",
|
|
39
|
+
"build:cli": "vite build --config vite.config.cli.mts",
|
|
40
|
+
"preview": "vite preview",
|
|
41
|
+
"test:ui": "vite --config test/vite.config.mts",
|
|
42
|
+
"template:app": "cd src/cli/templates/app && npm install && cd ../../../.. && node scripts/generate-template-tsconfig.mjs",
|
|
43
|
+
"template:app:dev": "cd src/cli/templates/app && npm run dev",
|
|
44
|
+
"changeset": "changeset",
|
|
45
|
+
"version": "changeset version",
|
|
46
|
+
"prepublishOnly": "npm run build"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@changesets/cli": "^2.27.7",
|
|
50
|
+
"@thatopen/components": "~3.4.0",
|
|
51
|
+
"@thatopen/components-front": "~3.4.0",
|
|
52
|
+
"@thatopen/fragments": "~3.4.0",
|
|
53
|
+
"@thatopen/ui": "~3.4.0",
|
|
54
|
+
"@thatopen/ui-obc": "~3.4.0",
|
|
55
|
+
"@types/node": "^20.12.12",
|
|
56
|
+
"@types/three": "^0.182.0",
|
|
57
|
+
"commander": "^14.0.3",
|
|
58
|
+
"esbuild": "^0.27.3",
|
|
59
|
+
"eslint": "9.x",
|
|
60
|
+
"eslint-config-prettier": "^9.1.0",
|
|
61
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
62
|
+
"globals": "^15.3.0",
|
|
63
|
+
"jszip": "^3.10.1",
|
|
64
|
+
"prettier": "^3.2.5",
|
|
65
|
+
"three": ">=0.182.0",
|
|
66
|
+
"tsx": "^4.16.2",
|
|
67
|
+
"typescript": "^5.2.2",
|
|
68
|
+
"typescript-eslint": "^7.10.0",
|
|
69
|
+
"vite": "^5.2.0",
|
|
70
|
+
"vite-plugin-dts": "^3.9.1",
|
|
71
|
+
"vite-plugin-eslint": "^1.8.1",
|
|
72
|
+
"vitest": "^4.1.4"
|
|
73
|
+
},
|
|
74
|
+
"dependencies": {
|
|
75
|
+
"dotenv": "^16.4.5",
|
|
76
|
+
"socket.io": "^4.8.0",
|
|
77
|
+
"socket.io-client": "^4.8.0"
|
|
78
|
+
},
|
|
79
|
+
"peerDependencies": {
|
|
80
|
+
"@thatopen/components": "~3.4.0",
|
|
81
|
+
"@thatopen/ui": "~3.4.0",
|
|
82
|
+
"three": ">=0.182.0"
|
|
83
|
+
},
|
|
84
|
+
"peerDependenciesMeta": {
|
|
85
|
+
"@thatopen/components": {
|
|
86
|
+
"optional": true
|
|
87
|
+
},
|
|
88
|
+
"@thatopen/ui": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
91
|
+
"three": {
|
|
92
|
+
"optional": true
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -1,285 +1,285 @@
|
|
|
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 } from "thatopen-services";
|
|
32
|
-
import clashesManagerDef from "./index";
|
|
33
|
-
|
|
34
|
-
const ClashesManager = clashesManagerDef.componentDefinition;
|
|
35
|
-
|
|
36
|
-
const client = PlatformClient.fromPlatformContext();
|
|
37
|
-
|
|
38
|
-
const { components } = (await client.setup(
|
|
39
|
-
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
40
|
-
{ uuid: ClashesManager.uuid },
|
|
41
|
-
)) as { components: OBC.Components };
|
|
42
|
-
|
|
43
|
-
/* MD
|
|
44
|
-
### 🚀 Initializing the manager
|
|
45
|
-
|
|
46
|
-
`init(client)` is the only call required before using the manager. It:
|
|
47
|
-
|
|
48
|
-
- Loads all persisted clash data (jobs, runs, clashes, queries, matrices) from
|
|
49
|
-
the project's cloud storage into memory.
|
|
50
|
-
- Registers `save` and `reload` callbacks so writes are debounced to storage
|
|
51
|
-
automatically and the manager can refresh its data after a run completes.
|
|
52
|
-
- Reads any in-flight cloud executions so that a page reload mid-run doesn't
|
|
53
|
-
lose track of the job.
|
|
54
|
-
- Resolves the engine's active `World` in the background — if no world exists
|
|
55
|
-
yet, it waits until one is created, so the order of initialization does not
|
|
56
|
-
matter.
|
|
57
|
-
|
|
58
|
-
After `init` resolves, `manager.ready` is `true` and all data properties
|
|
59
|
-
(`jobs`, `runs`, `clashes`, `queries`, `matrices`) are populated.
|
|
60
|
-
*/
|
|
61
|
-
|
|
62
|
-
const manager = components.get(ClashesManager);
|
|
63
|
-
await manager.init(client);
|
|
64
|
-
|
|
65
|
-
console.log(
|
|
66
|
-
`Loaded ${manager.jobs.length} jobs, ` +
|
|
67
|
-
`${manager.runs.length} runs, ` +
|
|
68
|
-
`${manager.clashes.length} clashes.`,
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
/* MD
|
|
72
|
-
### 🖥️ Adding the built-in panel (recommended)
|
|
73
|
-
|
|
74
|
-
Once `init` has been called, the `<clashes-panel>` web component is the
|
|
75
|
-
recommended way to interact with `ClashesManager`. It covers the full
|
|
76
|
-
review workflow — detection matrix, clash table, sphere markers, status
|
|
77
|
-
filters, run progress — with zero additional wiring.
|
|
78
|
-
|
|
79
|
-
`clashes-panel` is registered when `UIManager` is included in the platform
|
|
80
|
-
setup call. See the **clashes-panel** tutorial for the complete setup and a
|
|
81
|
-
detailed breakdown of everything the panel manages automatically.
|
|
82
|
-
|
|
83
|
-
The sections below are for cases where you need direct control: building a
|
|
84
|
-
custom UI, integrating clash data into your own panels, or automating status
|
|
85
|
-
updates from outside the panel.
|
|
86
|
-
*/
|
|
87
|
-
|
|
88
|
-
/* MD
|
|
89
|
-
### 📐 Queries and matrices
|
|
90
|
-
|
|
91
|
-
A `ClashQuery` defines a named set of model elements: which files to scan and,
|
|
92
|
-
optionally, which IFC categories or attribute conditions to match. Queries have
|
|
93
|
-
no detection logic of their own — they are inputs to a matrix.
|
|
94
|
-
|
|
95
|
-
A `ClashMatrix` pairs queries in an upper-triangle grid. Each active cell
|
|
96
|
-
corresponds to one `ClashJob` that the cloud worker will execute. The matrix
|
|
97
|
-
also carries the clash type (`hard` or `clearance`) and, for clearance checks,
|
|
98
|
-
the tolerance in metres.
|
|
99
|
-
|
|
100
|
-
Assigning to `manager.queries` or `manager.matrices` triggers a debounced
|
|
101
|
-
auto-save (2 s). Call `await manager.flush()` to persist immediately before
|
|
102
|
-
navigating away.
|
|
103
|
-
*/
|
|
104
|
-
|
|
105
|
-
const structuralFileId = "file-id-of-structural-model";
|
|
106
|
-
const mepFileId = "file-id-of-mep-model";
|
|
107
|
-
|
|
108
|
-
manager.queries = [
|
|
109
|
-
{
|
|
110
|
-
id: "q-structural",
|
|
111
|
-
name: "Structural",
|
|
112
|
-
modelFileIds: [structuralFileId],
|
|
113
|
-
query: { categories: ["IFCBEAM", "IFCCOLUMN", "IFCSLAB"] },
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
id: "q-mep",
|
|
117
|
-
name: "MEP",
|
|
118
|
-
modelFileIds: [mepFileId],
|
|
119
|
-
// Omitting `query` matches all elements with geometry in the listed files.
|
|
120
|
-
},
|
|
121
|
-
];
|
|
122
|
-
|
|
123
|
-
// The matrix below defines a single hard-clash job between Structural and MEP.
|
|
124
|
-
// `activePairs` lists the upper-triangle cell keys (queryAId:queryBId).
|
|
125
|
-
// `jobIds` is managed by the platform when the matrix is saved; leave it empty
|
|
126
|
-
// on first creation and the backend fills it after the first save.
|
|
127
|
-
manager.matrices = [
|
|
128
|
-
{
|
|
129
|
-
id: "matrix-main",
|
|
130
|
-
name: "Structural vs MEP",
|
|
131
|
-
queryIds: ["q-structural", "q-mep"],
|
|
132
|
-
activePairs: ["q-structural:q-mep"],
|
|
133
|
-
jobIds: [],
|
|
134
|
-
jobType: "hard",
|
|
135
|
-
},
|
|
136
|
-
];
|
|
137
|
-
|
|
138
|
-
await manager.flush();
|
|
139
|
-
|
|
140
|
-
/* MD
|
|
141
|
-
### ▶️ Running clash detection
|
|
142
|
-
|
|
143
|
-
`run(jobId)` launches a Web Worker that calls the cloud clash-detection
|
|
144
|
-
endpoint. It returns immediately — all feedback arrives through events.
|
|
145
|
-
|
|
146
|
-
| Event | Payload | When |
|
|
147
|
-
|-------|---------|------|
|
|
148
|
-
| `onRunProgress` | `{ message, progress }` | Periodically while the worker runs |
|
|
149
|
-
| `onRunComplete` | `{ runId, clashCount }` | Worker finished successfully |
|
|
150
|
-
| `onRunError` | `{ code?, message }` | Worker or cloud error |
|
|
151
|
-
| `onRunningJobsChanged` | `Set<string>` | A job starts or finishes |
|
|
152
|
-
| `onReloadingChanged` | `boolean` | Data reload after a successful run |
|
|
153
|
-
|
|
154
|
-
If the page is reloaded while a run is in progress, `init` re-reads the
|
|
155
|
-
in-flight execution from storage and populates `runningJobIds`. Call
|
|
156
|
-
`reconnectIfRunning(jobId)` to resume receiving progress events for that job.
|
|
157
|
-
*/
|
|
158
|
-
|
|
159
|
-
manager.onRunProgress.add(({ message, progress }) => {
|
|
160
|
-
console.log(`[${progress}%] ${message}`);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
manager.onRunComplete.add(({ runId, clashCount }) => {
|
|
164
|
-
console.log(`Run ${runId} complete — ${clashCount} clash(es) found.`);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
manager.onRunError.add(({ code, message }) => {
|
|
168
|
-
console.error(`Run failed (${code ?? "unknown"}): ${message}`);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
manager.onReloadingChanged.add((reloading) => {
|
|
172
|
-
// Use this flag to show a loading state in a custom UI while fresh data
|
|
173
|
-
// is fetched from storage after the run. clashes-panel handles this automatically.
|
|
174
|
-
console.log(reloading ? "Reloading clash data…" : "Clash data ready.");
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
// Guard: only run if the manager is ready and the job is not already running.
|
|
178
|
-
const jobToRun = manager.jobs[0];
|
|
179
|
-
if (jobToRun && manager.ready && !manager.runningJobIds.has(jobToRun.id)) {
|
|
180
|
-
manager.run(jobToRun.id);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// If a run was already in progress before this page loaded, reconnect to it.
|
|
184
|
-
for (const jobId of manager.runningJobIds) {
|
|
185
|
-
manager.reconnectIfRunning(jobId);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/* MD
|
|
189
|
-
### ✅ Updating clash status
|
|
190
|
-
|
|
191
|
-
Each clash starts with status `"new"`. Reviewers move clashes through a
|
|
192
|
-
workflow by calling `updateClash`. Valid statuses are:
|
|
193
|
-
|
|
194
|
-
- `"new"` — detected, not yet reviewed
|
|
195
|
-
- `"active"` — under investigation
|
|
196
|
-
- `"acknowledged"` — known issue, not blocking
|
|
197
|
-
- `"resolved"` — fixed and verified
|
|
198
|
-
|
|
199
|
-
Status changes and comments are persisted automatically via the same
|
|
200
|
-
auto-save mechanism as queries and matrices.
|
|
201
|
-
|
|
202
|
-
`runsForJob(jobId)` and `clashesForRun(runId)` let you navigate the history:
|
|
203
|
-
a clash's `firstSeenRunId` / `lastSeenRunId` span shows across which runs it
|
|
204
|
-
was present, so you can determine whether a resolution actually closed it.
|
|
205
|
-
*/
|
|
206
|
-
|
|
207
|
-
manager.onClashStatusChanged.add((allClashes) => {
|
|
208
|
-
const open = allClashes.filter(c => c.status === "new" || c.status === "active");
|
|
209
|
-
console.log(`Open clashes: ${open.length}`);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
const clashToAcknowledge = manager.clashes[0];
|
|
213
|
-
if (clashToAcknowledge) {
|
|
214
|
-
manager.updateClash(clashToAcknowledge.id, {
|
|
215
|
-
status: "acknowledged",
|
|
216
|
-
comments: "Coordination with MEP team scheduled for next sprint.",
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Navigate run history for the first job.
|
|
221
|
-
const firstJob = manager.jobs[0];
|
|
222
|
-
if (firstJob) {
|
|
223
|
-
const runs = manager.runsForJob(firstJob.id); // newest first
|
|
224
|
-
const latestRun = runs[0];
|
|
225
|
-
if (latestRun) {
|
|
226
|
-
const clashesInRun = manager.clashesForRun(latestRun.id);
|
|
227
|
-
console.log(`Latest run has ${clashesInRun.length} active clash(es).`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/* MD
|
|
232
|
-
### 📍 Visualization without the built-in panel
|
|
233
|
-
|
|
234
|
-
If you are building a custom UI and not using `clashes-panel`, you can drive
|
|
235
|
-
the visualization layer directly.
|
|
236
|
-
|
|
237
|
-
**Sphere markers** — `showMarkers` places colored 3D spheres at clash
|
|
238
|
-
locations in the viewer. Call `hideMarkers` to remove them and re-enable
|
|
239
|
-
hover highlighting. Points are automatically transformed from IFC world space
|
|
240
|
-
to Three.js viewer space using `FragmentsManager.baseCoordinationMatrix`.
|
|
241
|
-
|
|
242
|
-
**Element highlights** — `highlightAllClashes` colors element A (green) and
|
|
243
|
-
element B (red) for every new/active clash in the provided list.
|
|
244
|
-
`clearClashHighlight` removes both styles. For single-clash focus, use
|
|
245
|
-
`highlightClash` and pair it with `ghostClashes` to dim everything else.
|
|
246
|
-
|
|
247
|
-
**Clustering** — `cluster(clashes, threshold)` groups spatially close clashes
|
|
248
|
-
into a single marker. Pass `threshold = 0` to show one sphere per clash.
|
|
249
|
-
`toViewerPoint` converts a raw IFC point to viewer space if you need to
|
|
250
|
-
position custom objects.
|
|
251
|
-
*/
|
|
252
|
-
|
|
253
|
-
const jobClashes = manager.clashes.filter(c => c.jobId === firstJob?.id);
|
|
254
|
-
|
|
255
|
-
// Show one marker per clash, colored by status.
|
|
256
|
-
manager.showMarkers(
|
|
257
|
-
jobClashes.map(c => ({ id: c.id, point: c.point, count: 1, status: c.status })),
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
// Highlight all open clashes across both element sets.
|
|
261
|
-
manager.highlightAllClashes(jobClashes);
|
|
262
|
-
|
|
263
|
-
// --- Clustered view ---
|
|
264
|
-
// Group clashes within 2 m of each other into a single sphere.
|
|
265
|
-
const clusters = manager.cluster(jobClashes, 2);
|
|
266
|
-
manager.showMarkers(
|
|
267
|
-
clusters.map(group => ({
|
|
268
|
-
point: group[0].point,
|
|
269
|
-
count: group.length,
|
|
270
|
-
status: group[0].status,
|
|
271
|
-
})),
|
|
272
|
-
);
|
|
273
|
-
|
|
274
|
-
// Focus on a single clash: ghost everything else and zoom in.
|
|
275
|
-
const focusClash = jobClashes[0];
|
|
276
|
-
if (focusClash) {
|
|
277
|
-
manager.clearClashHighlight();
|
|
278
|
-
manager.highlightClash(focusClash);
|
|
279
|
-
await manager.ghostClash(focusClash);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Restore full scene when done.
|
|
283
|
-
manager.hideMarkers();
|
|
284
|
-
manager.clearClashHighlight();
|
|
285
|
-
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 } from "thatopen-services";
|
|
32
|
+
import clashesManagerDef from "./index";
|
|
33
|
+
|
|
34
|
+
const ClashesManager = clashesManagerDef.componentDefinition;
|
|
35
|
+
|
|
36
|
+
const client = PlatformClient.fromPlatformContext();
|
|
37
|
+
|
|
38
|
+
const { components } = (await client.setup(
|
|
39
|
+
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
40
|
+
{ uuid: ClashesManager.uuid },
|
|
41
|
+
)) as { components: OBC.Components };
|
|
42
|
+
|
|
43
|
+
/* MD
|
|
44
|
+
### 🚀 Initializing the manager
|
|
45
|
+
|
|
46
|
+
`init(client)` is the only call required before using the manager. It:
|
|
47
|
+
|
|
48
|
+
- Loads all persisted clash data (jobs, runs, clashes, queries, matrices) from
|
|
49
|
+
the project's cloud storage into memory.
|
|
50
|
+
- Registers `save` and `reload` callbacks so writes are debounced to storage
|
|
51
|
+
automatically and the manager can refresh its data after a run completes.
|
|
52
|
+
- Reads any in-flight cloud executions so that a page reload mid-run doesn't
|
|
53
|
+
lose track of the job.
|
|
54
|
+
- Resolves the engine's active `World` in the background — if no world exists
|
|
55
|
+
yet, it waits until one is created, so the order of initialization does not
|
|
56
|
+
matter.
|
|
57
|
+
|
|
58
|
+
After `init` resolves, `manager.ready` is `true` and all data properties
|
|
59
|
+
(`jobs`, `runs`, `clashes`, `queries`, `matrices`) are populated.
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
const manager = components.get(ClashesManager);
|
|
63
|
+
await manager.init(client);
|
|
64
|
+
|
|
65
|
+
console.log(
|
|
66
|
+
`Loaded ${manager.jobs.length} jobs, ` +
|
|
67
|
+
`${manager.runs.length} runs, ` +
|
|
68
|
+
`${manager.clashes.length} clashes.`,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
/* MD
|
|
72
|
+
### 🖥️ Adding the built-in panel (recommended)
|
|
73
|
+
|
|
74
|
+
Once `init` has been called, the `<top-clashes-panel>` web component is the
|
|
75
|
+
recommended way to interact with `ClashesManager`. It covers the full
|
|
76
|
+
review workflow — detection matrix, clash table, sphere markers, status
|
|
77
|
+
filters, run progress — with zero additional wiring.
|
|
78
|
+
|
|
79
|
+
`clashes-panel` is registered when `UIManager` is included in the platform
|
|
80
|
+
setup call. See the **clashes-panel** tutorial for the complete setup and a
|
|
81
|
+
detailed breakdown of everything the panel manages automatically.
|
|
82
|
+
|
|
83
|
+
The sections below are for cases where you need direct control: building a
|
|
84
|
+
custom UI, integrating clash data into your own panels, or automating status
|
|
85
|
+
updates from outside the panel.
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
/* MD
|
|
89
|
+
### 📐 Queries and matrices
|
|
90
|
+
|
|
91
|
+
A `ClashQuery` defines a named set of model elements: which files to scan and,
|
|
92
|
+
optionally, which IFC categories or attribute conditions to match. Queries have
|
|
93
|
+
no detection logic of their own — they are inputs to a matrix.
|
|
94
|
+
|
|
95
|
+
A `ClashMatrix` pairs queries in an upper-triangle grid. Each active cell
|
|
96
|
+
corresponds to one `ClashJob` that the cloud worker will execute. The matrix
|
|
97
|
+
also carries the clash type (`hard` or `clearance`) and, for clearance checks,
|
|
98
|
+
the tolerance in metres.
|
|
99
|
+
|
|
100
|
+
Assigning to `manager.queries` or `manager.matrices` triggers a debounced
|
|
101
|
+
auto-save (2 s). Call `await manager.flush()` to persist immediately before
|
|
102
|
+
navigating away.
|
|
103
|
+
*/
|
|
104
|
+
|
|
105
|
+
const structuralFileId = "file-id-of-structural-model";
|
|
106
|
+
const mepFileId = "file-id-of-mep-model";
|
|
107
|
+
|
|
108
|
+
manager.queries = [
|
|
109
|
+
{
|
|
110
|
+
id: "q-structural",
|
|
111
|
+
name: "Structural",
|
|
112
|
+
modelFileIds: [structuralFileId],
|
|
113
|
+
query: { categories: ["IFCBEAM", "IFCCOLUMN", "IFCSLAB"] },
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "q-mep",
|
|
117
|
+
name: "MEP",
|
|
118
|
+
modelFileIds: [mepFileId],
|
|
119
|
+
// Omitting `query` matches all elements with geometry in the listed files.
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
// The matrix below defines a single hard-clash job between Structural and MEP.
|
|
124
|
+
// `activePairs` lists the upper-triangle cell keys (queryAId:queryBId).
|
|
125
|
+
// `jobIds` is managed by the platform when the matrix is saved; leave it empty
|
|
126
|
+
// on first creation and the backend fills it after the first save.
|
|
127
|
+
manager.matrices = [
|
|
128
|
+
{
|
|
129
|
+
id: "matrix-main",
|
|
130
|
+
name: "Structural vs MEP",
|
|
131
|
+
queryIds: ["q-structural", "q-mep"],
|
|
132
|
+
activePairs: ["q-structural:q-mep"],
|
|
133
|
+
jobIds: [],
|
|
134
|
+
jobType: "hard",
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
await manager.flush();
|
|
139
|
+
|
|
140
|
+
/* MD
|
|
141
|
+
### ▶️ Running clash detection
|
|
142
|
+
|
|
143
|
+
`run(jobId)` launches a Web Worker that calls the cloud clash-detection
|
|
144
|
+
endpoint. It returns immediately — all feedback arrives through events.
|
|
145
|
+
|
|
146
|
+
| Event | Payload | When |
|
|
147
|
+
|-------|---------|------|
|
|
148
|
+
| `onRunProgress` | `{ message, progress }` | Periodically while the worker runs |
|
|
149
|
+
| `onRunComplete` | `{ runId, clashCount }` | Worker finished successfully |
|
|
150
|
+
| `onRunError` | `{ code?, message }` | Worker or cloud error |
|
|
151
|
+
| `onRunningJobsChanged` | `Set<string>` | A job starts or finishes |
|
|
152
|
+
| `onReloadingChanged` | `boolean` | Data reload after a successful run |
|
|
153
|
+
|
|
154
|
+
If the page is reloaded while a run is in progress, `init` re-reads the
|
|
155
|
+
in-flight execution from storage and populates `runningJobIds`. Call
|
|
156
|
+
`reconnectIfRunning(jobId)` to resume receiving progress events for that job.
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
manager.onRunProgress.add(({ message, progress }) => {
|
|
160
|
+
console.log(`[${progress}%] ${message}`);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
manager.onRunComplete.add(({ runId, clashCount }) => {
|
|
164
|
+
console.log(`Run ${runId} complete — ${clashCount} clash(es) found.`);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
manager.onRunError.add(({ code, message }) => {
|
|
168
|
+
console.error(`Run failed (${code ?? "unknown"}): ${message}`);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
manager.onReloadingChanged.add((reloading) => {
|
|
172
|
+
// Use this flag to show a loading state in a custom UI while fresh data
|
|
173
|
+
// is fetched from storage after the run. clashes-panel handles this automatically.
|
|
174
|
+
console.log(reloading ? "Reloading clash data…" : "Clash data ready.");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Guard: only run if the manager is ready and the job is not already running.
|
|
178
|
+
const jobToRun = manager.jobs[0];
|
|
179
|
+
if (jobToRun && manager.ready && !manager.runningJobIds.has(jobToRun.id)) {
|
|
180
|
+
manager.run(jobToRun.id);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// If a run was already in progress before this page loaded, reconnect to it.
|
|
184
|
+
for (const jobId of manager.runningJobIds) {
|
|
185
|
+
manager.reconnectIfRunning(jobId);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* MD
|
|
189
|
+
### ✅ Updating clash status
|
|
190
|
+
|
|
191
|
+
Each clash starts with status `"new"`. Reviewers move clashes through a
|
|
192
|
+
workflow by calling `updateClash`. Valid statuses are:
|
|
193
|
+
|
|
194
|
+
- `"new"` — detected, not yet reviewed
|
|
195
|
+
- `"active"` — under investigation
|
|
196
|
+
- `"acknowledged"` — known issue, not blocking
|
|
197
|
+
- `"resolved"` — fixed and verified
|
|
198
|
+
|
|
199
|
+
Status changes and comments are persisted automatically via the same
|
|
200
|
+
auto-save mechanism as queries and matrices.
|
|
201
|
+
|
|
202
|
+
`runsForJob(jobId)` and `clashesForRun(runId)` let you navigate the history:
|
|
203
|
+
a clash's `firstSeenRunId` / `lastSeenRunId` span shows across which runs it
|
|
204
|
+
was present, so you can determine whether a resolution actually closed it.
|
|
205
|
+
*/
|
|
206
|
+
|
|
207
|
+
manager.onClashStatusChanged.add((allClashes) => {
|
|
208
|
+
const open = allClashes.filter(c => c.status === "new" || c.status === "active");
|
|
209
|
+
console.log(`Open clashes: ${open.length}`);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const clashToAcknowledge = manager.clashes[0];
|
|
213
|
+
if (clashToAcknowledge) {
|
|
214
|
+
manager.updateClash(clashToAcknowledge.id, {
|
|
215
|
+
status: "acknowledged",
|
|
216
|
+
comments: "Coordination with MEP team scheduled for next sprint.",
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Navigate run history for the first job.
|
|
221
|
+
const firstJob = manager.jobs[0];
|
|
222
|
+
if (firstJob) {
|
|
223
|
+
const runs = manager.runsForJob(firstJob.id); // newest first
|
|
224
|
+
const latestRun = runs[0];
|
|
225
|
+
if (latestRun) {
|
|
226
|
+
const clashesInRun = manager.clashesForRun(latestRun.id);
|
|
227
|
+
console.log(`Latest run has ${clashesInRun.length} active clash(es).`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* MD
|
|
232
|
+
### 📍 Visualization without the built-in panel
|
|
233
|
+
|
|
234
|
+
If you are building a custom UI and not using `clashes-panel`, you can drive
|
|
235
|
+
the visualization layer directly.
|
|
236
|
+
|
|
237
|
+
**Sphere markers** — `showMarkers` places colored 3D spheres at clash
|
|
238
|
+
locations in the viewer. Call `hideMarkers` to remove them and re-enable
|
|
239
|
+
hover highlighting. Points are automatically transformed from IFC world space
|
|
240
|
+
to Three.js viewer space using `FragmentsManager.baseCoordinationMatrix`.
|
|
241
|
+
|
|
242
|
+
**Element highlights** — `highlightAllClashes` colors element A (green) and
|
|
243
|
+
element B (red) for every new/active clash in the provided list.
|
|
244
|
+
`clearClashHighlight` removes both styles. For single-clash focus, use
|
|
245
|
+
`highlightClash` and pair it with `ghostClashes` to dim everything else.
|
|
246
|
+
|
|
247
|
+
**Clustering** — `cluster(clashes, threshold)` groups spatially close clashes
|
|
248
|
+
into a single marker. Pass `threshold = 0` to show one sphere per clash.
|
|
249
|
+
`toViewerPoint` converts a raw IFC point to viewer space if you need to
|
|
250
|
+
position custom objects.
|
|
251
|
+
*/
|
|
252
|
+
|
|
253
|
+
const jobClashes = manager.clashes.filter(c => c.jobId === firstJob?.id);
|
|
254
|
+
|
|
255
|
+
// Show one marker per clash, colored by status.
|
|
256
|
+
manager.showMarkers(
|
|
257
|
+
jobClashes.map(c => ({ id: c.id, point: c.point, count: 1, status: c.status })),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// Highlight all open clashes across both element sets.
|
|
261
|
+
manager.highlightAllClashes(jobClashes);
|
|
262
|
+
|
|
263
|
+
// --- Clustered view ---
|
|
264
|
+
// Group clashes within 2 m of each other into a single sphere.
|
|
265
|
+
const clusters = manager.cluster(jobClashes, 2);
|
|
266
|
+
manager.showMarkers(
|
|
267
|
+
clusters.map(group => ({
|
|
268
|
+
point: group[0].point,
|
|
269
|
+
count: group.length,
|
|
270
|
+
status: group[0].status,
|
|
271
|
+
})),
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// Focus on a single clash: ghost everything else and zoom in.
|
|
275
|
+
const focusClash = jobClashes[0];
|
|
276
|
+
if (focusClash) {
|
|
277
|
+
manager.clearClashHighlight();
|
|
278
|
+
manager.highlightClash(focusClash);
|
|
279
|
+
await manager.ghostClash(focusClash);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Restore full scene when done.
|
|
283
|
+
manager.hideMarkers();
|
|
284
|
+
manager.clearClashHighlight();
|
|
285
|
+
await manager.clearGhostClash();
|
|
@@ -1,164 +1,164 @@
|
|
|
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 } from "thatopen-services";
|
|
30
|
-
import uiManagerDef from "../../../index";
|
|
31
|
-
import clashesManagerDef from "../../../ClashesManager/index";
|
|
32
|
-
import type { App } from "../app/index";
|
|
33
|
-
|
|
34
|
-
const UIManager = uiManagerDef.componentDefinition;
|
|
35
|
-
const ClashesManager = clashesManagerDef.componentDefinition;
|
|
36
|
-
|
|
37
|
-
/* MD
|
|
38
|
-
### ✅ Prerequisites
|
|
39
|
-
|
|
40
|
-
Two conditions must be met before the panel mounts:
|
|
41
|
-
|
|
42
|
-
1. **`UIManager` must be in the setup call.** It registers all platform web
|
|
43
|
-
components, including `<clashes-panel>` itself. Without it the element is
|
|
44
|
-
unknown to the browser and renders as an empty box.
|
|
45
|
-
|
|
46
|
-
2. **`ClashesManager.init(client)` must have resolved.** The panel reads all
|
|
47
|
-
its initial data from the manager in `connectedCallback`. If `init` has not
|
|
48
|
-
finished, the panel mounts into an empty state and never catches up because
|
|
49
|
-
it only subscribes to change events — it does not poll.
|
|
50
|
-
|
|
51
|
-
Both conditions are naturally satisfied when you call `init` before appending
|
|
52
|
-
`top-app` to the document, which is the pattern shown below.
|
|
53
|
-
*/
|
|
54
|
-
|
|
55
|
-
const client = PlatformClient.fromPlatformContext();
|
|
56
|
-
|
|
57
|
-
const { components } = (await client.setup(
|
|
58
|
-
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
59
|
-
{ uuid: UIManager.uuid },
|
|
60
|
-
{ uuid: ClashesManager.uuid },
|
|
61
|
-
)) as { components: OBC.Components };
|
|
62
|
-
|
|
63
|
-
components.get(UIManager).init();
|
|
64
|
-
|
|
65
|
-
// init must resolve before top-app (and therefore clashes-panel) mounts.
|
|
66
|
-
await components.get(ClashesManager).init(client);
|
|
67
|
-
|
|
68
|
-
/* MD
|
|
69
|
-
### 🖥️ Wiring the panel
|
|
70
|
-
|
|
71
|
-
Add `clashes-panel` as a named area in `app.elements` and reference it in your
|
|
72
|
-
layout template. A side-by-side layout with the 3D viewer is the most natural
|
|
73
|
-
arrangement — the panel controls markers and highlights directly in the scene.
|
|
74
|
-
*/
|
|
75
|
-
|
|
76
|
-
const app = document.createElement("top-app") as unknown as App;
|
|
77
|
-
|
|
78
|
-
app.setup = (waitUntil) => {
|
|
79
|
-
waitUntil(
|
|
80
|
-
(async () => {
|
|
81
|
-
const fragments = components.get(OBC.FragmentsManager);
|
|
82
|
-
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
83
|
-
fragments.init(await FRAGS.toClassicWorker(workerUrl), { classicWorker: true });
|
|
84
|
-
})(),
|
|
85
|
-
"Fragments Core",
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
waitUntil(
|
|
89
|
-
(async () => {
|
|
90
|
-
const loader = components.get(OBC.IfcLoader);
|
|
91
|
-
await loader.setup();
|
|
92
|
-
})(),
|
|
93
|
-
"IFC Loader",
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
return { components, client };
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
app.elements = {
|
|
100
|
-
viewer: () => html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
|
|
101
|
-
clashes: () => html`<clashes-panel></clashes-panel>`,
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
app.layouts = {
|
|
105
|
-
main: {
|
|
106
|
-
label: "Main",
|
|
107
|
-
icon: "solar:3d-square-bold",
|
|
108
|
-
template: `"viewer clashes" 1fr / 1fr 22rem`,
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
app.layout = "main";
|
|
113
|
-
|
|
114
|
-
const container = document.getElementById("that-open-app") ?? document.body;
|
|
115
|
-
container.appendChild(app);
|
|
116
|
-
document.body.style.margin = "0";
|
|
117
|
-
|
|
118
|
-
/* MD
|
|
119
|
-
### 🤖 What the panel manages automatically
|
|
120
|
-
|
|
121
|
-
Once mounted, `clashes-panel` takes ownership of the following without any
|
|
122
|
-
further code from you:
|
|
123
|
-
|
|
124
|
-
**Data synchronization** — the panel subscribes to every `ClashesManager`
|
|
125
|
-
event (`onJobsChanged`, `onRunsChanged`, `onClashesChanged`,
|
|
126
|
-
`onClashStatusChanged`, `onQueriesChanged`, `onRunningJobsChanged`) and keeps
|
|
127
|
-
its tables in sync. Changes made programmatically through the manager API are
|
|
128
|
-
reflected in the panel immediately.
|
|
129
|
-
|
|
130
|
-
**Run lifecycle** — a progress callout appears as soon as a run starts
|
|
131
|
-
(`onRunProgress`), updates its percentage and message in real time, and
|
|
132
|
-
disappears when the run completes or errors. If a run was already in progress
|
|
133
|
-
when the page loaded, the panel calls `reconnectIfRunning` automatically when
|
|
134
|
-
the user opens that job.
|
|
135
|
-
|
|
136
|
-
**Post-run reload skeleton** — after a successful run the manager reloads clash
|
|
137
|
-
data from storage. During that window (`onReloadingChanged = true`) the panel
|
|
138
|
-
replaces its entire content with an animated skeleton so the user is never left
|
|
139
|
-
looking at stale results that appear final.
|
|
140
|
-
|
|
141
|
-
**Sphere markers** — entering a job view places colored spheres in the 3D scene
|
|
142
|
-
at each clash location, transformed from IFC world space to viewer space.
|
|
143
|
-
Selecting a clash in the table zooms the camera, ghosts unrelated elements, and
|
|
144
|
-
re-focuses the markers on the selection. Leaving the job view removes all
|
|
145
|
-
markers and restores the full scene.
|
|
146
|
-
|
|
147
|
-
**Element highlights** — element A is highlighted green and element B red for
|
|
148
|
-
every new or active clash in the current job. Selecting a single clash refines
|
|
149
|
-
the highlight to that pair only. Clearing the selection or changing status
|
|
150
|
-
restores the full job highlight.
|
|
151
|
-
|
|
152
|
-
**Model awareness** — the panel tracks which fragment models are currently
|
|
153
|
-
loaded in the scene. Markers and highlights are activated only when the relevant
|
|
154
|
-
models are present, and are cleaned up automatically if a model is disposed
|
|
155
|
-
while the panel is open.
|
|
156
|
-
|
|
157
|
-
**Save indicator** — a subtle indicator appears while auto-save is in progress
|
|
158
|
-
(`onSaveStart`) and disappears once the write confirms (`onSaveComplete`), so
|
|
159
|
-
users know their status changes and query edits are persisted.
|
|
160
|
-
|
|
161
|
-
**Disconnection cleanup** — when the panel is removed from the DOM it calls
|
|
162
|
-
`hideMarkers` and `clearClashHighlight` on the manager, leaving the 3D scene
|
|
163
|
-
clean regardless of what was selected at the time.
|
|
164
|
-
*/
|
|
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 } from "thatopen-services";
|
|
30
|
+
import uiManagerDef from "../../../index";
|
|
31
|
+
import clashesManagerDef from "../../../ClashesManager/index";
|
|
32
|
+
import type { App } from "../app/index";
|
|
33
|
+
|
|
34
|
+
const UIManager = uiManagerDef.componentDefinition;
|
|
35
|
+
const ClashesManager = clashesManagerDef.componentDefinition;
|
|
36
|
+
|
|
37
|
+
/* MD
|
|
38
|
+
### ✅ Prerequisites
|
|
39
|
+
|
|
40
|
+
Two conditions must be met before the panel mounts:
|
|
41
|
+
|
|
42
|
+
1. **`UIManager` must be in the setup call.** It registers all platform web
|
|
43
|
+
components, including `<top-clashes-panel>` itself. Without it the element is
|
|
44
|
+
unknown to the browser and renders as an empty box.
|
|
45
|
+
|
|
46
|
+
2. **`ClashesManager.init(client)` must have resolved.** The panel reads all
|
|
47
|
+
its initial data from the manager in `connectedCallback`. If `init` has not
|
|
48
|
+
finished, the panel mounts into an empty state and never catches up because
|
|
49
|
+
it only subscribes to change events — it does not poll.
|
|
50
|
+
|
|
51
|
+
Both conditions are naturally satisfied when you call `init` before appending
|
|
52
|
+
`top-app` to the document, which is the pattern shown below.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
const client = PlatformClient.fromPlatformContext();
|
|
56
|
+
|
|
57
|
+
const { components } = (await client.setup(
|
|
58
|
+
{ OBC, OBF, BUI, THREE, FRAGS },
|
|
59
|
+
{ uuid: UIManager.uuid },
|
|
60
|
+
{ uuid: ClashesManager.uuid },
|
|
61
|
+
)) as { components: OBC.Components };
|
|
62
|
+
|
|
63
|
+
components.get(UIManager).init();
|
|
64
|
+
|
|
65
|
+
// init must resolve before top-app (and therefore clashes-panel) mounts.
|
|
66
|
+
await components.get(ClashesManager).init(client);
|
|
67
|
+
|
|
68
|
+
/* MD
|
|
69
|
+
### 🖥️ Wiring the panel
|
|
70
|
+
|
|
71
|
+
Add `clashes-panel` as a named area in `app.elements` and reference it in your
|
|
72
|
+
layout template. A side-by-side layout with the 3D viewer is the most natural
|
|
73
|
+
arrangement — the panel controls markers and highlights directly in the scene.
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
const app = document.createElement("top-app") as unknown as App;
|
|
77
|
+
|
|
78
|
+
app.setup = (waitUntil) => {
|
|
79
|
+
waitUntil(
|
|
80
|
+
(async () => {
|
|
81
|
+
const fragments = components.get(OBC.FragmentsManager);
|
|
82
|
+
const workerUrl = await FRAGS.FragmentsModels.getWorker();
|
|
83
|
+
fragments.init(await FRAGS.toClassicWorker(workerUrl), { classicWorker: true });
|
|
84
|
+
})(),
|
|
85
|
+
"Fragments Core",
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
waitUntil(
|
|
89
|
+
(async () => {
|
|
90
|
+
const loader = components.get(OBC.IfcLoader);
|
|
91
|
+
await loader.setup();
|
|
92
|
+
})(),
|
|
93
|
+
"IFC Loader",
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return { components, client };
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
app.elements = {
|
|
100
|
+
viewer: () => html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
|
|
101
|
+
clashes: () => html`<top-clashes-panel></top-clashes-panel>`,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
app.layouts = {
|
|
105
|
+
main: {
|
|
106
|
+
label: "Main",
|
|
107
|
+
icon: "solar:3d-square-bold",
|
|
108
|
+
template: `"viewer clashes" 1fr / 1fr 22rem`,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
app.layout = "main";
|
|
113
|
+
|
|
114
|
+
const container = document.getElementById("that-open-app") ?? document.body;
|
|
115
|
+
container.appendChild(app);
|
|
116
|
+
document.body.style.margin = "0";
|
|
117
|
+
|
|
118
|
+
/* MD
|
|
119
|
+
### 🤖 What the panel manages automatically
|
|
120
|
+
|
|
121
|
+
Once mounted, `clashes-panel` takes ownership of the following without any
|
|
122
|
+
further code from you:
|
|
123
|
+
|
|
124
|
+
**Data synchronization** — the panel subscribes to every `ClashesManager`
|
|
125
|
+
event (`onJobsChanged`, `onRunsChanged`, `onClashesChanged`,
|
|
126
|
+
`onClashStatusChanged`, `onQueriesChanged`, `onRunningJobsChanged`) and keeps
|
|
127
|
+
its tables in sync. Changes made programmatically through the manager API are
|
|
128
|
+
reflected in the panel immediately.
|
|
129
|
+
|
|
130
|
+
**Run lifecycle** — a progress callout appears as soon as a run starts
|
|
131
|
+
(`onRunProgress`), updates its percentage and message in real time, and
|
|
132
|
+
disappears when the run completes or errors. If a run was already in progress
|
|
133
|
+
when the page loaded, the panel calls `reconnectIfRunning` automatically when
|
|
134
|
+
the user opens that job.
|
|
135
|
+
|
|
136
|
+
**Post-run reload skeleton** — after a successful run the manager reloads clash
|
|
137
|
+
data from storage. During that window (`onReloadingChanged = true`) the panel
|
|
138
|
+
replaces its entire content with an animated skeleton so the user is never left
|
|
139
|
+
looking at stale results that appear final.
|
|
140
|
+
|
|
141
|
+
**Sphere markers** — entering a job view places colored spheres in the 3D scene
|
|
142
|
+
at each clash location, transformed from IFC world space to viewer space.
|
|
143
|
+
Selecting a clash in the table zooms the camera, ghosts unrelated elements, and
|
|
144
|
+
re-focuses the markers on the selection. Leaving the job view removes all
|
|
145
|
+
markers and restores the full scene.
|
|
146
|
+
|
|
147
|
+
**Element highlights** — element A is highlighted green and element B red for
|
|
148
|
+
every new or active clash in the current job. Selecting a single clash refines
|
|
149
|
+
the highlight to that pair only. Clearing the selection or changing status
|
|
150
|
+
restores the full job highlight.
|
|
151
|
+
|
|
152
|
+
**Model awareness** — the panel tracks which fragment models are currently
|
|
153
|
+
loaded in the scene. Markers and highlights are activated only when the relevant
|
|
154
|
+
models are present, and are cleaned up automatically if a model is disposed
|
|
155
|
+
while the panel is open.
|
|
156
|
+
|
|
157
|
+
**Save indicator** — a subtle indicator appears while auto-save is in progress
|
|
158
|
+
(`onSaveStart`) and disappears once the write confirms (`onSaveComplete`), so
|
|
159
|
+
users know their status changes and query edits are persisted.
|
|
160
|
+
|
|
161
|
+
**Disconnection cleanup** — when the panel is removed from the DOM it calls
|
|
162
|
+
`hideMarkers` and `clearClashHighlight` on the manager, leaving the 3D scene
|
|
163
|
+
clean regardless of what was selected at the time.
|
|
164
|
+
*/
|