Skip to content

Commit 317f15c

Browse files
agviegasclaude
andauthored
feat: add bim-beta template (beta engine libs) + drop ui-obc; v0.0.3 (#17)
- New `bim-beta` template: @thatopen-platform/components-beta / components-front-beta / fragments-beta + public @thatopen/ui, no ui-obc (matches the platform's library line) - Remove @thatopen/ui-obc from the `bim` template (deprecated) - Register `bim-beta` in `thatopen create` - AGENTS.md / docs/scaffolding.md / shared AGENTS: ask about beta access, use `-t bim-beta`; note the scaffold is already a working viewer - Fix `build:cli` (drop the missing copy-templates.mjs step) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 168e24e commit 317f15c

28 files changed

Lines changed: 681 additions & 11 deletions

File tree

AGENTS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,19 @@ detailed doc for whatever you're doing. Open only the doc you need — don't loa
1313

1414
## How to work
1515

16+
- 🚩 **BEFORE creating anything, ask the user about beta access — this is important.** Ask:
17+
*"Do you have That Open beta access? It gives you the latest libraries, features and progress —
18+
and it's what the platform currently runs. Do you want to use it?"* **If yes, scaffold with the
19+
`bim-beta` template** (`thatopen create <name> -t bim-beta`). Founding members have **permanent** beta
20+
access, so for them the answer is almost always yes. Using the matching library line avoids
21+
runtime errors against the platform. (Beta packages are private — the user needs their beta
22+
access token configured in npm, or `npm install` will fail.) See
23+
[docs/scaffolding.md](./docs/scaffolding.md).
1624
- **New app or component?** The **first step is always the CLI** (`thatopen create`) — never
1725
hand-write the scaffold. See [docs/cli-setup.md](./docs/cli-setup.md) then
1826
[docs/scaffolding.md](./docs/scaffolding.md).
27+
- **The scaffold is already a complete, working viewer** (model loading, spatial tree, properties).
28+
**Run it first** (`npm run dev`) to see it work, *then* extend it — don't rebuild a viewer from scratch.
1929
- **Propose a short plan and get the user's OK** before changing files. If scope is unclear, ask.
2030
- Prefer existing engine functionality over custom code (see rule 2).
2131

docs/scaffolding.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,28 @@ Pass `--template <name>` to choose a template. The default is `bim`.
2222

2323
| Template | When to use |
2424
|---|---|
25-
| `bim` (default) | Standard BIM viewer app — Three.js viewport, BIM viewer, platform UI components. This is the right starting point for almost every app. |
25+
| `bim` (default) | Standard BIM viewer app on the public engine libraries. |
26+
| `bim-beta` | Same as `bim` but on the private **beta** engine libraries (`@thatopen-platform/*-beta`) — the line the platform currently runs. Use this for founding members (see below). |
2627
| `default` | Minimal shell — just shows platform context. Use only when you explicitly want to start from scratch without any viewer. |
2728

2829
If no template is specified, use `bim`.
2930

31+
## Beta libraries (founding members) — use the `bim-beta` template
32+
33+
**Before scaffolding, ask the user whether they have beta access** and want the latest engine
34+
libraries. If so, scaffold with the `bim-beta` template:
35+
36+
```bash
37+
thatopen create <app-name> -t bim-beta
38+
```
39+
40+
`bim-beta` is the `bim` viewer wired directly to the private `@thatopen-platform/*-beta` packages —
41+
the **same library line the platform currently runs**. Prefer it whenever the user has beta access:
42+
the public `bim` template uses older libraries that can error at runtime against a beta platform.
43+
44+
Founding members have **permanent** beta access. The beta packages are private, so the user must
45+
have their beta access token configured in npm, or `npm install` will fail with a 401/403.
46+
3047
## What the scaffold produces
3148

3249
The `bim` template generates the full structure described in the **Project Structure** section of [./app-architecture.md](./app-architecture.md):

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"access": "public"
1616
},
1717
"private": false,
18-
"version": "0.0.2",
18+
"version": "0.0.3",
1919
"main": "dist/index.cjs.js",
2020
"module": "dist/index.es.js",
2121
"types": "dist/index.d.ts",
@@ -37,7 +37,7 @@
3737
"build:lib": "tsc && vite build",
3838
"test": "vitest run",
3939
"test:watch": "vitest",
40-
"build:cli": "vite build --config vite.config.cli.mts && node scripts/copy-templates.mjs",
40+
"build:cli": "vite build --config vite.config.cli.mts",
4141
"preview": "vite preview",
4242
"test:ui": "vite --config test/vite.config.mts",
4343
"test:cli-build-app": "npm run build:cli && node test/setup-test-app.mjs",

src/cli/commands/create.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, renam
33
import { basename, join, resolve } from 'node:path';
44
import { execSync } from 'node:child_process';
55

6-
const TEMPLATES = ['default', 'bim', 'cloud', 'test', 'cloud-test'] as const;
6+
const TEMPLATES = ['default', 'bim', 'bim-beta', 'cloud', 'test', 'cloud-test'] as const;
77
type Template = (typeof TEMPLATES)[number];
88

99
/** Read the library version from package.json so templates stay in sync. */
@@ -83,13 +83,23 @@ export const createCommand = new Command('create')
8383
.replace(/"@thatopen\/services": "file:[^"]*"/, `"@thatopen/services": "^${libVersion}"`);
8484
writeFileSync(pkgPath, pkg);
8585

86+
const isBeta = template === 'bim-beta';
87+
if (isBeta) {
88+
console.log('');
89+
console.log('This template uses the private BETA engine libraries (@thatopen-platform/*-beta).');
90+
console.log('If install fails with a 401/403, configure your beta access token in npm first.');
91+
}
92+
8693
// Install dependencies automatically
8794
console.log('');
8895
console.log('Installing dependencies...');
8996
try {
9097
execSync('npm install', { cwd: targetDir, stdio: 'inherit' });
9198
} catch {
9299
console.error('Failed to install dependencies. Run `npm install` manually.');
100+
if (isBeta) {
101+
console.error('Beta packages are private — if this is an auth error, your beta token may not be configured.');
102+
}
93103
}
94104

95105
console.log('');
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
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.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "{{PROJECT_NAME}}",
3+
"version": "1.0.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "thatopen serve",
7+
"build": "vite build",
8+
"login": "thatopen login --local",
9+
"publish": "thatopen publish"
10+
},
11+
"dependencies": {
12+
"@markerjs/markerjs3": "^3.9.0",
13+
"@thatopen-platform/components-beta": "latest",
14+
"@thatopen-platform/components-front-beta": "latest",
15+
"@thatopen-platform/fragments-beta": "latest",
16+
"@thatopen/ui": "~3.4.0",
17+
"@thatopen/services": "file:../../../..",
18+
"three": "^0.182.0"
19+
},
20+
"devDependencies": {
21+
"@types/three": "^0.182.0",
22+
"typescript": "^5.2.0",
23+
"vite": "^5.2.0"
24+
}
25+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as OBC from "@thatopen-platform/components-beta";
2+
import * as BUI from "@thatopen/ui";
3+
import { AppManager } from "@thatopen/services";
4+
import { icons } from "./globals";
5+
import { AppInfoSectionGridElement, CloudRunnerSectionGridElement } from "./ui-components";
6+
7+
export type App = {
8+
icons: (keyof typeof icons)[];
9+
grid: BUI.Grid<
10+
["Viewer", "Split"],
11+
["viewer", AppInfoSectionGridElement, CloudRunnerSectionGridElement]
12+
>;
13+
};
14+
15+
export const getAppManager = (components: OBC.Components) =>
16+
components.get(AppManager<App>);

0 commit comments

Comments
 (0)