Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,19 @@ detailed doc for whatever you're doing. Open only the doc you need — don't loa

## How to work

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

Expand Down
19 changes: 18 additions & 1 deletion docs/scaffolding.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,28 @@ Pass `--template <name>` to choose a template. The default is `bim`.

| Template | When to use |
|---|---|
| `bim` (default) | Standard BIM viewer app — Three.js viewport, BIM viewer, platform UI components. This is the right starting point for almost every app. |
| `bim` (default) | Standard BIM viewer app on the public engine libraries. |
| `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). |
| `default` | Minimal shell — just shows platform context. Use only when you explicitly want to start from scratch without any viewer. |

If no template is specified, use `bim`.

## Beta libraries (founding members) — use the `bim-beta` template

**Before scaffolding, ask the user whether they have beta access** and want the latest engine
libraries. If so, scaffold with the `bim-beta` template:

```bash
thatopen create <app-name> -t bim-beta
```

`bim-beta` is the `bim` viewer wired directly to the private `@thatopen-platform/*-beta` packages —
the **same library line the platform currently runs**. Prefer it whenever the user has beta access:
the public `bim` template uses older libraries that can error at runtime against a beta platform.

Founding members have **permanent** beta access. The beta packages are private, so the user must
have their beta access token configured in npm, or `npm install` will fail with a 401/403.

## What the scaffold produces

The `bim` template generates the full structure described in the **Project Structure** section of [./app-architecture.md](./app-architecture.md):
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"access": "public"
},
"private": false,
"version": "0.0.2",
"version": "0.0.3",
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",
Expand All @@ -37,7 +37,7 @@
"build:lib": "tsc && vite build",
"test": "vitest run",
"test:watch": "vitest",
"build:cli": "vite build --config vite.config.cli.mts && node scripts/copy-templates.mjs",
"build:cli": "vite build --config vite.config.cli.mts",
"preview": "vite preview",
"test:ui": "vite --config test/vite.config.mts",
"test:cli-build-app": "npm run build:cli && node test/setup-test-app.mjs",
Expand Down
12 changes: 11 additions & 1 deletion src/cli/commands/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, renam
import { basename, join, resolve } from 'node:path';
import { execSync } from 'node:child_process';

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

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

const isBeta = template === 'bim-beta';
if (isBeta) {
console.log('');
console.log('This template uses the private BETA engine libraries (@thatopen-platform/*-beta).');
console.log('If install fails with a 401/403, configure your beta access token in npm first.');
}

// Install dependencies automatically
console.log('');
console.log('Installing dependencies...');
try {
execSync('npm install', { cwd: targetDir, stdio: 'inherit' });
} catch {
console.error('Failed to install dependencies. Run `npm install` manually.');
if (isBeta) {
console.error('Beta packages are private — if this is an auth error, your beta token may not be configured.');
}
}

console.log('');
Expand Down
241 changes: 241 additions & 0 deletions src/cli/templates/bim-beta/CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
# ThatOpen BIM App

This is a BIM (Building Information Modeling) app built for the That Open Platform.
It runs in the browser inside the platform's iframe and has access to a 3D viewer,
UI components, and the platform API.

## How this app works

- **Entry point**: `src/main.ts` — runs as an IIFE when the platform loads the app.
- **Build output**: `dist/bundle.js` — a single IIFE file built by Vite.
- **Platform context**: The platform injects `window.__THATOPEN_CONTEXT__` with:
- `appId` — this app's unique ID
- `projectId` — the project this app belongs to
- `accessToken` — Auth0 JWT for API calls
- `apiUrl` — base URL for the That Open API

## Commands

```bash
npm run dev # Start dev server (esbuild watch + serve on :4000)
npm run build # Build dist/bundle.js (Vite/Rollup production build)
npm run login # Authenticate with the platform (saves token locally)
npm run publish # Publish to the platform
```

### Local development

Apps run inside the That Open Platform (platform.thatopen.com) within a project —
not as standalone websites. To develop locally:

1. Run `npm run dev` — this watches source files with esbuild and serves the bundle on port 4000.
2. Open your project on the platform and click the debug button.
3. Live reload is enabled — save a file to rebuild automatically.

The dev server (`thatopen serve`) uses esbuild for near-instant incremental rebuilds.
**Important**: Do NOT run `vite`, `vite build --watch`, or `npx vite` directly for development.
Always use `npm run dev` which runs `thatopen serve` under the hood.

## Key libraries

| Package | Import | Purpose |
|---------|--------|---------|
| `@thatopen-platform/components-beta` | `OBC` | BIM engine — components, fragments, worlds |
| `@thatopen-platform/components-front-beta` | `OBF` | Front-end BIM components (Highlighter, measurements, etc.) |
| `@thatopen-platform/fragments-beta` | `FRAGS` | Fragment geometry format |
| `@thatopen/ui` | `BUI` | UI web components (`<bim-panel>`, `<bim-grid>`, etc.) |
| `three` | `THREE` | 3D rendering engine |
| `@thatopen/services` | `EngineServicesClient` | Platform API client + built-in components |

## Architecture pattern

```
1. Create EngineServicesClient from platform context
2. Call client.setup(globals, ...builtIns) — creates OBC.Components,
inits BUI, loads built-in components, calls components.init()
3. Create viewport(s) and UI elements
4. Configure AppManager with elements + layouts
5. Call app.init()
```

## Built-in components

Built-in components are platform-hosted UI modules loaded at runtime via the API client.
They are fetched, evaluated, and registered with the OBC component system.

| Component | Purpose |
|-----------|---------|
| **AppManager** | App shell — CSS grid layout system with sidebar for switching layouts |
| **ViewportsManager** | Factory for 3D viewports with pre-configured world (scene, camera, renderer) |
| **LoadModelButton** | Button + dropdown for loading IFC and Fragments files |
| **ViewerToolbar** | Toolbar with Show/Hide/Focus/Isolate actions and color palette |
| **ModelsPanel** | Panel listing loaded models with search bar and load button |
| **ModelsDropdown** | Dropdown selector listing loaded models |
| **ClassificationsList** | Hierarchical table of IFC classification data |
| **ClashesList** | Interactive clash detection results with click-to-highlight |
| **ClippingsList** | Panel listing clipping planes with enable/delete controls |
| **LengthMeasuringsList** | Panel listing length measurements with cumulative total |
| **AreaMeasuringsList** | Panel listing area measurements with area/perimeter totals |
| **ColorsPalette** | Color picker grid with custom input and Highlighter styles |
| **HighlightersList** | Panel listing Highlighter styles with manage/apply actions |
| **QtoComparisonList** | Side-by-side quantity comparison for two selected elements |
| **QueriesHierarchy** | Recursive multi-level query browser for IFC data |
| **CustomViewLegend** | Color legend overlay with colored circles and labels |
| **ScreenshotAnnotator** | Modal for annotating screenshots (arrows, text, freehand) via MarkerJS |

**Full API reference**: Each component has detailed JSDoc with `@example` blocks in the
`@thatopen/services` package source (`src/built-in/index.ts`). Read that file for config
interfaces, method signatures, and code examples.

### Loading pattern

Use `setup` to create the component system and load built-in components in one call:

```ts
import { PlatformClient, AppManager, ViewportsManager } from "@thatopen/services";

const client = PlatformClient.fromPlatformContext();

// Creates OBC.Components, inits BUI, loads built-ins, calls components.init()
const { components } = await client.setup(
{ OBC, OBF, BUI, THREE, FRAGS },
AppManager, ViewportsManager,
);

const app = components.get(AppManager);
const viewports = components.get(ViewportsManager);
```

You can also load components individually if needed:

```ts
// Batch load (parallel)
await client.initBuiltInComponents(components, AppManager, ViewportsManager);

// Or one at a time
await client.initBuiltInComponent(AppManager, components);
```

### Required globals per component

| Component | Globals to pass | Extra npm packages needed |
|-----------|----------------|--------------------------|
| AppManager | `{ OBC, BUI }` | — |
| ViewportsManager | `{ OBC, BUI, THREE, FRAGS }` | — |
| LoadModelButton | `{ OBC, BUI }` | — |
| ModelsDropdown | `{ OBC, BUI }` | — |
| ViewerToolbar | `{ OBC, OBF, BUI, THREE }` | `@thatopen-platform/components-front-beta` |
| ColorsPalette | `{ OBC, OBF, BUI }` | `@thatopen-platform/components-front-beta` |
| ClashesList | `{ OBC, OBF, BUI, THREE }` | `@thatopen-platform/components-front-beta` |
| ClassificationsList | `{ OBC, OBF, BUI }` | `@thatopen-platform/components-front-beta` |
| ClippingsList | `{ OBC, BUI }` | — |
| HighlightersList | `{ OBC, OBF, BUI }` | `@thatopen-platform/components-front-beta` |
| LengthMeasuringsList | `{ OBC, OBF, BUI, THREE }` | `@thatopen-platform/components-front-beta` |
| AreaMeasuringsList | `{ OBC, OBF, BUI, THREE }` | `@thatopen-platform/components-front-beta` |
| QtoComparisonList | `{ OBC, OBF, BUI }` | `@thatopen-platform/components-front-beta` |
| QueriesHierarchy | `{ OBC, OBF, BUI }` | `@thatopen-platform/components-front-beta` |
| CustomViewLegend | `{ OBC, BUI }` | — |
| ScreenshotAnnotator | `{ OBC, BUI, MARKERJS }` | `@markerjs/markerjs3` |

### Global abbreviations

```ts
import * as OBC from "@thatopen-platform/components-beta";
import * as OBF from "@thatopen-platform/components-front-beta"; // needed for Toolbar, Highlighters, Clashes, etc.
import * as BUI from "@thatopen/ui";
import * as THREE from "three";
import * as FRAGS from "@thatopen-platform/fragments-beta";
import * as MARKERJS from "@markerjs/markerjs3"; // needed for ScreenshotAnnotator
```

### AppManager — app shell with CSS grid layouts

Creates a grid-based layout system. Define named element slots and named layouts.
A sidebar for switching layouts appears automatically when multiple layouts exist.

```ts
const Layouts = ["Viewer", "Split"];
const Elements = ["viewer", "panel"];

await app.init({
client,
icons: undefined, // pass Record<K, string> when using typed App interface with icon keys
grid: (grid: BUI.Grid<Layouts, Elements>) => {
grid.elements = {
viewer: viewportElement,
panel: panelFunction, // Can be HTMLElement, () => BUI.TemplateResult, or { template, initialState }
};
grid.layouts = {
Viewer: { template: `"viewer" 1fr / 1fr` },
Split: {
template: `"panel viewer" 1fr / 20rem 1fr`,
icon: "solar:settings-bold",
},
};
},
});
```

The `template` string uses CSS `grid-template` shorthand: `"areas" rows / columns`.

### ViewportsManager — 3D viewport factory

Creates viewports with pre-configured world (scene, camera, renderer) and auto-initialized fragments.

```ts
const viewports = components.get(ViewportsManager);
const { element, world } = await viewports.create();
// element is an HTMLElement to place in a layout slot
// world has world.scene, world.camera, world.renderer
```

## Loading a BIM model

```ts
const fragments = components.get(OBC.FragmentsManager);

// From URL
const response = await fetch("https://example.com/model.frag");
const buffer = await response.arrayBuffer();
await fragments.core.load(buffer, { modelId: "my-model" });

// From platform storage
const fileResponse = await client.downloadFile(fileId);
const fileBuffer = await fileResponse.arrayBuffer();
await fragments.core.load(fileBuffer, { modelId: "my-model" });
```

## EngineServicesClient API (commonly used in apps)

```ts
// Recommended: auto-reads window.__THATOPEN_CONTEXT__ and sets useBearer: true
const client = PlatformClient.fromPlatformContext();
console.log(client.context.projectId); // access the platform context

// Files
const files = await client.listFiles();
const file = await client.getFile(fileId);
const response = await client.downloadFile(fileId);
await client.createFile({ file: blob, name: "model.ifc", versionTag: "v1" });

// Folders
const folders = await client.listFolders();
await client.createFolder("My Folder");

// Execute cloud components
const { executionId } = await client.executeComponent(componentId, { param: "value" });
client.onExecutionProgress(executionId, (data) => {
// data.progressUpdate — progress percentage
// data.messageUpdate — status messages
});

// Test against a local cloud component (requires thatopen local-server running in the component project)
client.localServerUrl = "http://localhost:4001";
const local = await client.executeComponent("any-id", { param: "value" });
client.localServerUrl = null; // reset to use the cloud API
```

## Configuration

- `.thatopen` — local config (gitignored). Created by `npm run login`. Contains `accessToken`, `apiUrl`, and `appId` after first publish.
- `vite.config.js` — builds to IIFE format as `dist/bundle.js`. All dependencies are bundled.
25 changes: 25 additions & 0 deletions src/cli/templates/bim-beta/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "{{PROJECT_NAME}}",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "thatopen serve",
"build": "vite build",
"login": "thatopen login --local",
"publish": "thatopen publish"
},
"dependencies": {
"@markerjs/markerjs3": "^3.9.0",
"@thatopen-platform/components-beta": "latest",
"@thatopen-platform/components-front-beta": "latest",
"@thatopen-platform/fragments-beta": "latest",
"@thatopen/ui": "~3.4.0",
"@thatopen/services": "file:../../../..",
"three": "^0.182.0"
},
"devDependencies": {
"@types/three": "^0.182.0",
"typescript": "^5.2.0",
"vite": "^5.2.0"
}
}
16 changes: 16 additions & 0 deletions src/cli/templates/bim-beta/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as OBC from "@thatopen-platform/components-beta";
import * as BUI from "@thatopen/ui";
import { AppManager } from "@thatopen/services";
import { icons } from "./globals";
import { AppInfoSectionGridElement, CloudRunnerSectionGridElement } from "./ui-components";

export type App = {
icons: (keyof typeof icons)[];
grid: BUI.Grid<
["Viewer", "Split"],
["viewer", AppInfoSectionGridElement, CloudRunnerSectionGridElement]
>;
};

export const getAppManager = (components: OBC.Components) =>
components.get(AppManager<App>);
Loading
Loading