> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ploterr.com/llms.txt
> Use this file to discover all available pages before exploring further.

# ProjectManager

> ProjectManager holds one open project.

It keeps the views, the sheets and the markers of the project, and it owns the one `BuildingSession` that builds the building. It mounts the plan editors, connects the 3D editor, saves the project and exports the sheets. Each change is one step in one history, so one undo works for the building and for the sheets.

## Import

```typescript theme={null}
import { ProjectManager } from '@opengeometry/floorkit';
```

## Example

```typescript theme={null}
import type * as THREE from 'three';
import {
  GUIEditor,
  ProjectManager,
  type CameraControlsLike,
  type ManagedPlanEditor,
  type Sheet,
} from '@opengeometry/floorkit';

export async function showProject(
  text: string,
  scene: THREE.Scene,
  planContainer: HTMLElement,
  sceneContainer: HTMLElement,
  camera: THREE.Camera,
  controls: CameraControlsLike,
): Promise<{ manager: ProjectManager; plan: ManagedPlanEditor; sceneEditor: GUIEditor }> {
  const manager = ProjectManager.open(text);
  scene.add(manager.root);
  const plan = await manager.mountPlanEditor(planContainer, 'ground-plan');
  const sceneEditor = new GUIEditor(sceneContainer, camera, controls);
  manager.bindSceneEditor(sceneEditor);
  return { manager, plan, sceneEditor };
}

export async function addSheetAndExport(
  manager: ProjectManager,
  newSheet: Sheet,
): Promise<{ saved: string; pdf: Blob }> {
  manager.apply({ kind: 'add-project-item', list: 'sheets', item: newSheet });
  manager.undo();
  manager.redo();
  const saved = manager.save();
  const pdf = await manager.exportPdf();
  return { saved, pdf };
}

export function closeProject(
  scene: THREE.Scene,
  manager: ProjectManager,
  sceneEditor: GUIEditor,
): void {
  sceneEditor.dispose();
  scene.remove(manager.root);
  manager.dispose();
}
```

## Notes

### Project commands

`apply` takes an edit command of the building, or one of these commands of the project lists. Each item has an `id`. An edit command of the building can be a `batch`. A batch cannot hold a command of the project lists.

| Command | Result |
| - | - |
| `add-project-item` | Adds one item to `views`, `sheets` or `markers`. The `id` must be new in that list. |
| `replace-project-item` | Replaces the item that has the given `id`. The new item must have the same `id`. |
| `remove-project-item` | Removes the item that has the given `id`. |
| `replace-paper-settings` | Replaces the settings of the paper. The sheets and the paper version stay. |

```typescript theme={null}
import type { ProjectCommand } from '@opengeometry/floorkit';

export function renameSheet(manager: ProjectManager, sheet: Sheet, name: string): number {
  const command: ProjectCommand = {
    kind: 'replace-project-item',
    list: 'sheets',
    id: sheet.id,
    item: { ...sheet, name },
  };
  return manager.apply(command);
}
```

### Checks of a command

* Floorkit checks a project command as `save()` checks the project. A command that fails throws `ProjectFileError` and changes nothing.
* The value `-0`, the value `undefined` and an object that is not plain give the code `invalid-json-value`.
* The other codes of a project command are `duplicate-item`, `unknown-item`, `item-id-mismatch` and `invalid-command`.
* You cannot remove a view that a mounted plan editor shows, and you cannot make it a view that is not a plan. The code is `view-in-use`.
* If a plan editor shows a view that you added in this session, an `undo` that reaches the "add view" step fails with `view-in-use`. Dispose that plan editor first.
* Through `manager.apply` you cannot remove a storey while a plan view names it. The manager throws `ProjectFileError` with the code `storey-in-use` and the path of the view. Remove the view first. A `remove-storey` in a `batch` has the same check, also when the batch adds the storey again.
* Through `manager.apply` you cannot remove a building while a saved view shows it: a plan view of one of its storeys, a section or elevation view with its `buildingId`, or a view whose `elementIds` lists anything the building holds. The code is `building-in-use`, with the path of the view. A bound 3D editor has the same check.
* Through `manager.apply` you cannot remove the site, or give it a new id, while the `elementIds` of a view lists it. The code is `site-in-use`.
* An undo or a redo of a step of the project lists is checked as `save()` checks the project. A step that fails throws `ProjectFileError` and changes nothing.

### History

* `apply`, `undo` and `redo` return the new revision. `undo` and `redo` return `null` when there is no step.
* A drag in an editor is a building step in the same history. The undo key in each editor undoes the last step of the manager.
* `subscribe` reports each step with its `revision`, its `part` (`building` or `project`) and its `cause` (`apply`, `undo`, `redo` or `restore`). A `restore` is no new step and the revision stays as it was: a step of the building failed after it changed built elements, and the session built them again from the saved model.
* Do not call `apply`, `undo` or `redo` in a listener. The manager throws an error with the code `reentrant-transition`. `dispose()` in a listener during a building step throws the same code.
* After a project step, the manager draws each mounted plan again. Thus one project step costs one plan draw for each mounted plan editor.
* With a `ProjectManager`, call `manager.apply`, `manager.undo` and `manager.redo`. A direct `manager.session.apply` is counted as a step. A direct `manager.session.undo` or `redo`, and a storey command sent to the session while a view names that storey, can leave a step that the manager cannot undo, or a project that `save()` refuses. While a view names a missing storey, the undo of each project step is refused. To repair it, undo the building step, or remove the view with `manager.apply`.

### Files and errors

* `ProjectManager.open` throws `ProjectFileError` for a bad file. A file that is valid, but whose building does not compile, throws `BmBuildError` or `Error`.
* A project file from an earlier version opens, and the project commands work on it. `save()` writes the current version.
* Add `root` to your scene one time. It stays the same object after each edit.
* You make the `GUIEditor` and you dispose it. `dispose()` of the manager removes the plan editors and the session.
* After `dispose()`, `exportPdf` gives a promise that rejects.
* A deployed site needs a valid hostname key for exports, including a non-commercial demo. See [Licensing](/floorkit/licensing).

## Constructor

```typescript theme={null}
constructor(project: ProjectFile, options?: ProjectManagerOptions);
```

<ParamField path="options.build" type="Pick<BuildOptions, &#x22;assetSources&#x22; | &#x22;massColors&#x22; | &#x22;stampMembership&#x22;>">
  Build options of the one building session, for example the sources of external assets.
</ParamField>

<ParamField path="options.hostedOpeningDetachTolerance" type="number">
  Metres from every wall beyond which a dragged door or window leaves its wall; default 0.35.
</ParamField>

<ParamField path="options.onError" type="((error: unknown) => void)">
  Called when an editor rejects an edit, or cannot draw a step of the project lists.
</ParamField>

## Properties

<ResponseField name="session" type="BuildingSession">
  Read-only. The one building session of this project.
</ResponseField>

<ResponseField name="root" type="Group">
  Read-only. The built building. Add it to the Three.js scene one time.
</ResponseField>

<ResponseField name="revision" type="number">
  Read-only. Goes up by one for each applied, undone or redone step.
</ResponseField>

## Methods

### open

```typescript theme={null}
static open(text: string, options?: ProjectManagerOptions): ProjectManager;
```

Read project text, check it and build the building.

### snapshot

```typescript theme={null}
snapshot(): ProjectFile;
```

The current views, sheets and markers, with the current building.

### save

```typescript theme={null}
save(): string;
```

The current project as text.

### apply

```typescript theme={null}
apply(command: ProjectCommand): number;
```

Apply one command.

### undo

```typescript theme={null}
undo(): number | null;
```

Undo the last step.

### redo

```typescript theme={null}
redo(): number | null;
```

Redo the last undone step.

### subscribe

```typescript theme={null}
subscribe(listener: (change: ProjectChange) => void): () => void;
```

Call `listener` after each completed step.

### mountPlanEditor

```typescript theme={null}
mountPlanEditor(container: HTMLElement, viewId: string): Promise<ManagedPlanEditor>;
```

Mount a 2D plan editor for one saved plan view.

### bindSceneEditor

```typescript theme={null}
bindSceneEditor(editor: GUIEditor): void;
```

Connect a 3D editor to this project; its edits pass the same storey checks as `apply`, and Delete of a part of a mass keeps a storey that a saved plan view shows.

### setBuildableEnvelope

```typescript theme={null}
setBuildableEnvelope(shown: boolean): void;
```

Show or hide the buildable envelope of the site in the 3D scene (`root`).

### exportPdf

```typescript theme={null}
exportPdf(sheetIds?: string[], options?: ProjectExportOptions): Promise<Blob>;
```

Export the given sheets, or each sheet, of the current project as one PDF.

### dispose

```typescript theme={null}
dispose(): void;
```

Remove the plan editors of this manager, then the session.

## Types

### ProjectManagerOptions

```typescript theme={null}
interface ProjectManagerOptions {
  build?: Pick<BuildOptions, 'assetSources' | 'stampMembership' | 'massColors'>;
  hostedOpeningDetachTolerance?: number;
  onError?: (error: unknown) => void;
}
```

### ProjectCommand

One command of a project manager: an edit of the building or a change of the project lists.

```typescript theme={null}
type ProjectCommand = BuildingEditCommand | ProjectListCommand;
```

### ProjectListCommand

One change of the list of views, sheets or markers.

```typescript theme={null}
type ProjectListCommand =
  | {
      kind: 'add-project-item';
      list: 'views';
      item: SavedDrawingView;
    }
  | {
      kind: 'add-project-item';
      list: 'sheets';
      item: Sheet;
    }
  | {
      kind: 'add-project-item';
      list: 'markers';
      item: ProjectViewMarker;
    }
  | {
      kind: 'replace-project-item';
      list: 'views';
      id: string;
      item: SavedDrawingView;
    }
  | {
      kind: 'replace-project-item';
      list: 'sheets';
      id: string;
      item: Sheet;
    }
  | {
      kind: 'replace-project-item';
      list: 'markers';
      id: string;
      item: ProjectViewMarker;
    }
  | {
      kind: 'remove-project-item';
      list: 'views' | 'sheets' | 'markers';
      id: string;
    }
  | {
      kind: 'replace-paper-settings';
      settings: Omit<PaperDocument, 'version' | 'sheets'>;
    };
```

### ProjectChange

One completed step of a project manager, as a subscriber gets it.

```typescript theme={null}
interface ProjectChange {
  revision: number;
  part: 'building' | 'project';
  cause: 'apply' | 'undo' | 'redo' | 'restore';
  changed?: readonly string[];
}
```

<ResponseField name="revision" type="number" required>
  The revision of the manager after the step. A `restore` leaves the revision as it was.
</ResponseField>

<ResponseField name="part" type="'building' | 'project'" required>
  `building` is an edit of the session; `project` is a change of the project lists.
</ResponseField>

<ResponseField name="cause" type="'apply' | 'undo' | 'redo' | 'restore'" required>
  What made the step. `restore` is no saved step: a step of the building failed after it changed
  built objects, and the session built them again from the saved model.
</ResponseField>

<ResponseField name="changed" type="readonly string[]">
  The live ids of the built elements that the step built again, added or removed, where a removed id
  is in the list and not in the build; empty for a step of the project lists, and absent when the
  whole building was built again. An element that was built again is a new object, or the same
  object with new geometry.
</ResponseField>

## Related

* [`BuildingSession`](/floorkit/api/editor/building-session)
* [`GUIEditor`](/floorkit/api/editor/gui-editor)
* [`ManagedPlanEditor`](/floorkit/api/editor/managed-plan-editor)
* [`BuildingEditCommand`](/floorkit/api/editor/building-session#buildingeditcommand)
* [`BuildOptions`](/floorkit/api/architecture/build-from-building-model#buildoptions)
* [`PaperDocument`](/floorkit/api/drawings/sheet-documents#paperdocument)
* [`ProjectExportOptions`](/floorkit/api/exports/export-project-pdf#projectexportoptions)
* [`ProjectFile`](/floorkit/api/exports/project-file#projectfile)
* [`ProjectViewMarker`](/floorkit/api/exports/project-file#projectviewmarker)
* [`SavedDrawingView`](/floorkit/api/drawings/sheet-documents#saveddrawingview)
* [`Sheet`](/floorkit/api/drawings/sheet-documents#sheet)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.