|
| 1 | +# ThatOpen BIM App |
| 2 | + |
| 3 | +This is a BIM (Building Information Modeling) app built for the That Open Platform. |
| 4 | +It runs in the browser inside the platform's iframe and has access to a 3D viewer, |
| 5 | +UI components, and the platform API. |
| 6 | + |
| 7 | +## How this app works |
| 8 | + |
| 9 | +- **Entry point**: `src/main.ts` — runs as an IIFE when the platform loads the app. |
| 10 | +- **Build output**: `dist/bundle.js` — a single IIFE file built by Vite. |
| 11 | +- **Platform context**: The platform injects `window.__THATOPEN_CONTEXT__` with: |
| 12 | + - `appId` — this app's unique ID |
| 13 | + - `projectId` — the project this app belongs to |
| 14 | + - `accessToken` — Auth0 JWT for API calls |
| 15 | + - `apiUrl` — base URL for the That Open API |
| 16 | + |
| 17 | +## Commands |
| 18 | + |
| 19 | +```bash |
| 20 | +npm run dev # Start dev server (esbuild watch + serve on :4000) |
| 21 | +npm run build # Build dist/bundle.js (Vite/Rollup production build) |
| 22 | +npm run login # Authenticate with the platform (saves token locally) |
| 23 | +npm run publish # Publish to the platform |
| 24 | +``` |
| 25 | + |
| 26 | +### Local development |
| 27 | + |
| 28 | +Apps run inside the That Open Platform (platform.thatopen.com) within a project — |
| 29 | +not as standalone websites. To develop locally: |
| 30 | + |
| 31 | +1. Run `npm run dev` — this watches source files with esbuild and serves the bundle on port 4000. |
| 32 | +2. Open your project on the platform and click the debug button. |
| 33 | +3. Live reload is enabled — save a file to rebuild automatically. |
| 34 | + |
| 35 | +The dev server (`thatopen serve`) uses esbuild for near-instant incremental rebuilds. |
| 36 | +**Important**: Do NOT run `vite`, `vite build --watch`, or `npx vite` directly for development. |
| 37 | +Always use `npm run dev` which runs `thatopen serve` under the hood. |
| 38 | + |
| 39 | +## Key libraries |
| 40 | + |
| 41 | +| Package | Import | Purpose | |
| 42 | +|---------|--------|---------| |
| 43 | +| `@thatopen-platform/components-beta` | `OBC` | BIM engine — components, fragments, worlds | |
| 44 | +| `@thatopen-platform/components-front-beta` | `OBF` | Front-end BIM components (Highlighter, measurements, etc.) | |
| 45 | +| `@thatopen-platform/fragments-beta` | `FRAGS` | Fragment geometry format | |
| 46 | +| `@thatopen/ui` | `BUI` | UI web components (`<bim-panel>`, `<bim-grid>`, etc.) | |
| 47 | +| `three` | `THREE` | 3D rendering engine | |
| 48 | +| `@thatopen/services` | `EngineServicesClient` | Platform API client + built-in components | |
| 49 | + |
| 50 | +## Architecture pattern |
| 51 | + |
| 52 | +``` |
| 53 | +1. Create EngineServicesClient from platform context |
| 54 | +2. Call client.setup(globals, ...builtIns) — creates OBC.Components, |
| 55 | + inits BUI, loads built-in components, calls components.init() |
| 56 | +3. Create viewport(s) and UI elements |
| 57 | +4. Configure AppManager with elements + layouts |
| 58 | +5. Call app.init() |
| 59 | +``` |
| 60 | + |
| 61 | +## Built-in components |
| 62 | + |
| 63 | +Built-in components are platform-hosted UI modules loaded at runtime via the API client. |
| 64 | +They are fetched, evaluated, and registered with the OBC component system. |
| 65 | + |
| 66 | +| Component | Purpose | |
| 67 | +|-----------|---------| |
| 68 | +| **AppManager** | App shell — CSS grid layout system with sidebar for switching layouts | |
| 69 | +| **ViewportsManager** | Factory for 3D viewports with pre-configured world (scene, camera, renderer) | |
| 70 | +| **LoadModelButton** | Button + dropdown for loading IFC and Fragments files | |
| 71 | +| **ViewerToolbar** | Toolbar with Show/Hide/Focus/Isolate actions and color palette | |
| 72 | +| **ModelsPanel** | Panel listing loaded models with search bar and load button | |
| 73 | +| **ModelsDropdown** | Dropdown selector listing loaded models | |
| 74 | +| **ClassificationsList** | Hierarchical table of IFC classification data | |
| 75 | +| **ClashesList** | Interactive clash detection results with click-to-highlight | |
| 76 | +| **ClippingsList** | Panel listing clipping planes with enable/delete controls | |
| 77 | +| **LengthMeasuringsList** | Panel listing length measurements with cumulative total | |
| 78 | +| **AreaMeasuringsList** | Panel listing area measurements with area/perimeter totals | |
| 79 | +| **ColorsPalette** | Color picker grid with custom input and Highlighter styles | |
| 80 | +| **HighlightersList** | Panel listing Highlighter styles with manage/apply actions | |
| 81 | +| **QtoComparisonList** | Side-by-side quantity comparison for two selected elements | |
| 82 | +| **QueriesHierarchy** | Recursive multi-level query browser for IFC data | |
| 83 | +| **CustomViewLegend** | Color legend overlay with colored circles and labels | |
| 84 | +| **ScreenshotAnnotator** | Modal for annotating screenshots (arrows, text, freehand) via MarkerJS | |
| 85 | + |
| 86 | +**Full API reference**: Each component has detailed JSDoc with `@example` blocks in the |
| 87 | +`@thatopen/services` package source (`src/built-in/index.ts`). Read that file for config |
| 88 | +interfaces, method signatures, and code examples. |
| 89 | + |
| 90 | +### Loading pattern |
| 91 | + |
| 92 | +Use `setup` to create the component system and load built-in components in one call: |
| 93 | + |
| 94 | +```ts |
| 95 | +import { PlatformClient, AppManager, ViewportsManager } from "@thatopen/services"; |
| 96 | + |
| 97 | +const client = PlatformClient.fromPlatformContext(); |
| 98 | + |
| 99 | +// Creates OBC.Components, inits BUI, loads built-ins, calls components.init() |
| 100 | +const { components } = await client.setup( |
| 101 | + { OBC, OBF, BUI, THREE, FRAGS }, |
| 102 | + AppManager, ViewportsManager, |
| 103 | +); |
| 104 | + |
| 105 | +const app = components.get(AppManager); |
| 106 | +const viewports = components.get(ViewportsManager); |
| 107 | +``` |
| 108 | + |
| 109 | +You can also load components individually if needed: |
| 110 | + |
| 111 | +```ts |
| 112 | +// Batch load (parallel) |
| 113 | +await client.initBuiltInComponents(components, AppManager, ViewportsManager); |
| 114 | + |
| 115 | +// Or one at a time |
| 116 | +await client.initBuiltInComponent(AppManager, components); |
| 117 | +``` |
| 118 | + |
| 119 | +### Required globals per component |
| 120 | + |
| 121 | +| Component | Globals to pass | Extra npm packages needed | |
| 122 | +|-----------|----------------|--------------------------| |
| 123 | +| AppManager | `{ OBC, BUI }` | — | |
| 124 | +| ViewportsManager | `{ OBC, BUI, THREE, FRAGS }` | — | |
| 125 | +| LoadModelButton | `{ OBC, BUI }` | — | |
| 126 | +| ModelsDropdown | `{ OBC, BUI }` | — | |
| 127 | +| ViewerToolbar | `{ OBC, OBF, BUI, THREE }` | `@thatopen-platform/components-front-beta` | |
| 128 | +| ColorsPalette | `{ OBC, OBF, BUI }` | `@thatopen-platform/components-front-beta` | |
| 129 | +| ClashesList | `{ OBC, OBF, BUI, THREE }` | `@thatopen-platform/components-front-beta` | |
| 130 | +| ClassificationsList | `{ OBC, OBF, BUI }` | `@thatopen-platform/components-front-beta` | |
| 131 | +| ClippingsList | `{ OBC, BUI }` | — | |
| 132 | +| HighlightersList | `{ OBC, OBF, BUI }` | `@thatopen-platform/components-front-beta` | |
| 133 | +| LengthMeasuringsList | `{ OBC, OBF, BUI, THREE }` | `@thatopen-platform/components-front-beta` | |
| 134 | +| AreaMeasuringsList | `{ OBC, OBF, BUI, THREE }` | `@thatopen-platform/components-front-beta` | |
| 135 | +| QtoComparisonList | `{ OBC, OBF, BUI }` | `@thatopen-platform/components-front-beta` | |
| 136 | +| QueriesHierarchy | `{ OBC, OBF, BUI }` | `@thatopen-platform/components-front-beta` | |
| 137 | +| CustomViewLegend | `{ OBC, BUI }` | — | |
| 138 | +| ScreenshotAnnotator | `{ OBC, BUI, MARKERJS }` | `@markerjs/markerjs3` | |
| 139 | + |
| 140 | +### Global abbreviations |
| 141 | + |
| 142 | +```ts |
| 143 | +import * as OBC from "@thatopen-platform/components-beta"; |
| 144 | +import * as OBF from "@thatopen-platform/components-front-beta"; // needed for Toolbar, Highlighters, Clashes, etc. |
| 145 | +import * as BUI from "@thatopen/ui"; |
| 146 | +import * as THREE from "three"; |
| 147 | +import * as FRAGS from "@thatopen-platform/fragments-beta"; |
| 148 | +import * as MARKERJS from "@markerjs/markerjs3"; // needed for ScreenshotAnnotator |
| 149 | +``` |
| 150 | + |
| 151 | +### AppManager — app shell with CSS grid layouts |
| 152 | + |
| 153 | +Creates a grid-based layout system. Define named element slots and named layouts. |
| 154 | +A sidebar for switching layouts appears automatically when multiple layouts exist. |
| 155 | + |
| 156 | +```ts |
| 157 | +const Layouts = ["Viewer", "Split"]; |
| 158 | +const Elements = ["viewer", "panel"]; |
| 159 | + |
| 160 | +await app.init({ |
| 161 | + client, |
| 162 | + icons: undefined, // pass Record<K, string> when using typed App interface with icon keys |
| 163 | + grid: (grid: BUI.Grid<Layouts, Elements>) => { |
| 164 | + grid.elements = { |
| 165 | + viewer: viewportElement, |
| 166 | + panel: panelFunction, // Can be HTMLElement, () => BUI.TemplateResult, or { template, initialState } |
| 167 | + }; |
| 168 | + grid.layouts = { |
| 169 | + Viewer: { template: `"viewer" 1fr / 1fr` }, |
| 170 | + Split: { |
| 171 | + template: `"panel viewer" 1fr / 20rem 1fr`, |
| 172 | + icon: "solar:settings-bold", |
| 173 | + }, |
| 174 | + }; |
| 175 | + }, |
| 176 | +}); |
| 177 | +``` |
| 178 | + |
| 179 | +The `template` string uses CSS `grid-template` shorthand: `"areas" rows / columns`. |
| 180 | + |
| 181 | +### ViewportsManager — 3D viewport factory |
| 182 | + |
| 183 | +Creates viewports with pre-configured world (scene, camera, renderer) and auto-initialized fragments. |
| 184 | + |
| 185 | +```ts |
| 186 | +const viewports = components.get(ViewportsManager); |
| 187 | +const { element, world } = await viewports.create(); |
| 188 | +// element is an HTMLElement to place in a layout slot |
| 189 | +// world has world.scene, world.camera, world.renderer |
| 190 | +``` |
| 191 | + |
| 192 | +## Loading a BIM model |
| 193 | + |
| 194 | +```ts |
| 195 | +const fragments = components.get(OBC.FragmentsManager); |
| 196 | + |
| 197 | +// From URL |
| 198 | +const response = await fetch("https://example.com/model.frag"); |
| 199 | +const buffer = await response.arrayBuffer(); |
| 200 | +await fragments.core.load(buffer, { modelId: "my-model" }); |
| 201 | + |
| 202 | +// From platform storage |
| 203 | +const fileResponse = await client.downloadFile(fileId); |
| 204 | +const fileBuffer = await fileResponse.arrayBuffer(); |
| 205 | +await fragments.core.load(fileBuffer, { modelId: "my-model" }); |
| 206 | +``` |
| 207 | + |
| 208 | +## EngineServicesClient API (commonly used in apps) |
| 209 | + |
| 210 | +```ts |
| 211 | +// Recommended: auto-reads window.__THATOPEN_CONTEXT__ and sets useBearer: true |
| 212 | +const client = PlatformClient.fromPlatformContext(); |
| 213 | +console.log(client.context.projectId); // access the platform context |
| 214 | + |
| 215 | +// Files |
| 216 | +const files = await client.listFiles(); |
| 217 | +const file = await client.getFile(fileId); |
| 218 | +const response = await client.downloadFile(fileId); |
| 219 | +await client.createFile({ file: blob, name: "model.ifc", versionTag: "v1" }); |
| 220 | + |
| 221 | +// Folders |
| 222 | +const folders = await client.listFolders(); |
| 223 | +await client.createFolder("My Folder"); |
| 224 | + |
| 225 | +// Execute cloud components |
| 226 | +const { executionId } = await client.executeComponent(componentId, { param: "value" }); |
| 227 | +client.onExecutionProgress(executionId, (data) => { |
| 228 | + // data.progressUpdate — progress percentage |
| 229 | + // data.messageUpdate — status messages |
| 230 | +}); |
| 231 | + |
| 232 | +// Test against a local cloud component (requires thatopen local-server running in the component project) |
| 233 | +client.localServerUrl = "http://localhost:4001"; |
| 234 | +const local = await client.executeComponent("any-id", { param: "value" }); |
| 235 | +client.localServerUrl = null; // reset to use the cloud API |
| 236 | +``` |
| 237 | + |
| 238 | +## Configuration |
| 239 | + |
| 240 | +- `.thatopen` — local config (gitignored). Created by `npm run login`. Contains `accessToken`, `apiUrl`, and `appId` after first publish. |
| 241 | +- `vite.config.js` — builds to IIFE format as `dist/bundle.js`. All dependencies are bundled. |
0 commit comments