> ## 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.

# Editing and history

> Each edit is a command. A session checks it, builds it and keeps it for undo.

In Floorkit, the user does not change a mesh. The user sends a command, such as "move this door to 2.5 metres". A `BuildingSession` applies the command to the building model and builds the geometry again.

This gives you three things for free: a model that is always valid, undo and redo, and a 2D view and a 3D view that always agree.

## What a session does with a command

<Steps>
  <Step title="Check">The session checks the command and the model that results from it.</Step>

  <Step title="Build">
    It builds the new geometry apart from your scene. Walls join again and openings cut again.
  </Step>

  <Step title="Publish">
    If the build is good, the session puts the new geometry below `session.root` and adds the edit
    to the history.
  </Step>
</Steps>

If one step fails, the session throws an error and changes nothing. The old model, the old geometry and the history stay as they were.

## Send a command

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

export function moveDoor(session: BuildingSession): void {
  session.apply(
    {
      kind: 'move-hosted-opening',
      buildingId: 'building',
      storeyId: 'ground',
      wallId: 'room-walls',
      openingId: 'entry-door',
      offset: 2.5,
    },
    session.revision,
  );
}
```

## The commands

| Command `kind` | What it does |
| - | - |
| `create-wall`, `delete-wall` | Adds or removes a wall |
| `move-wall-point` | Moves one point of a wall |
| `move-network-wall-end` | Moves one end of one wall segment off its corner |
| `transform-wall`, `translate-network-wall` | Moves or turns a wall |
| `add-hosted-opening`, `remove-hosted-opening` | Adds or removes a door or a window in a wall |
| `move-hosted-opening` | Moves a door or a window along its wall |
| `rehost-hosted-opening` | Moves a door or a window to a different wall |
| `detach-hosted-opening`, `rehost-free-opening` | Takes an opening out of a wall, or puts it in |
| `add-free-opening`, `move-free-opening`, `remove-free-opening` | Works with a door or a window that is not in a wall |
| `transform-slab`, `transform-roof`, `transform-stair`, `transform-space` | Moves or turns one element |
| `reshape-slab`, `reshape-roof`, `reshape-stair`, `reshape-space` | Changes the shape of one element |
| `add-element`, `replace-element`, `remove-element` | Adds, replaces or removes one element of a storey |
| `add-asset`, `replace-asset`, `remove-asset` | Adds, replaces or removes one asset |
| `add-storey`, `remove-storey`, `replace-storey-settings` | Adds or removes a storey, or sets its settings |
| `replace-mass` | Sets the mass of a building and all its storeys |
| `add-building`, `remove-building` | Adds or removes a whole building |
| `replace-site` | Sets or removes the site and its zoning rules |
| `add-model-object`, `replace-model-object`, `remove-model-object` | Adds, replaces or removes one basic shape |
| `batch` | Applies several commands as one step |

See each field in the [`BuildingEditCommand`](/floorkit/api/editor/building-session#buildingeditcommand) type.

## Add, replace and remove

`add-element`, `replace-element` and `remove-element` name a `list` of the storey: `slabs`, `roofs`, `spaces`, `stairs`, `columns`, `beams`, `railings` or `instances`. An `add` gives the new `element`. A `replace` gives the `elementId` and the full new `element`. A `remove` gives only the `elementId`.

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

export function replaceColumn(session: BuildingSession, column: BmColumn): void {
  session.apply(
    {
      kind: 'replace-element',
      buildingId: 'building',
      storeyId: 'ground',
      list: 'columns',
      elementId: column.id,
      element: column,
    },
    session.revision,
  );
}
```

An asset is the definition that an instance shows. The asset commands have an `assetId`, and `add-asset` and `replace-asset` have the `asset`. `add-storey` has the full `storey`. `remove-storey` has its `storeyId`. `replace-storey-settings` has the `storeyId` and the `settings`: the `name`, the `elevation`, the `height` and the `usage` together. `replace-mass` has the `mass`, or no `mass` to remove it, and the settings of each storey of the building in the new order. A storey id that stays keeps its elements. `add-building` has the full `building`; `remove-building` has its `buildingId`. `replace-site` has the full `site`, or no `site` to remove it.

The session rejects these commands in these conditions:

* An `add` with an id that exists: an element in its list, an asset in the model, a storey in its building.
* A `replace` or a `remove` with an id that does not exist.
* A `replace-element` whose new element has a different id.
* A `remove-asset` of an asset that an instance uses.
* A `remove-storey` of the last storey of a building.
* A `replace-mass` that removes a storey that holds an element.
* An `add-building` with an id that any object of the model has, and a `remove-building` of the last building.
* A `replace-site` with no `site` when the model has none.

A session does not know the views of your project. A [`ProjectManager`](/floorkit/api/editor/project-manager) does, and it also rejects a `remove-storey` or a `replace-mass` that removes a storey that a plan view names (`storey-in-use`), a `remove-building` of a building that a saved view shows (`building-in-use`), and a `replace-site` that removes a site that a view lists (`site-in-use`).

## Doors and windows in walls

A door, a window or a hole fits on its wall when its whole cut lies between the joints of that wall. Then the wall has a hole for it. Floorkit uses this one fit rule in the build, in the commands and in the drags.

`add-hosted-opening` puts a door or a window at an `offset` along a wall. `move-hosted-opening` and `rehost-hosted-opening` move a door, a window or a hole to an `offset`. The opening must fit at that offset, or the session rejects the command. The issue `opening-does-not-fit` of the error gives the offsets where the opening fits and the nearest one. `add-hosted-opening` and `rehost-free-opening` also reject an opening over the seam of a T stem; the offsets in the issue then keep clear of the seams. These commands need the geometry kernel: `applyBuildingEdit` rejects them with `resolved-edit-required`. It also rejects a `move-wall-point` of any wall in a storey with hosted openings, because the move can make a door of another wall stop fitting.

A saved model can have an opening that does not fit at its offset. Opening the model does not change it. The build draws the opening at its saved offset without a hole, and gives the warning `opening-does-not-fit` in `session.currentBuild.warnings`, with the same numbers. To repair it, move the opening to an offset where it fits.

An edit does not change an opening that it does not carry. The session rejects an edit that makes an opening that fits stop fitting, or that puts the seam of a T stem under it. An opening that does not fit never blocks an edit. An edit of the wall of an opening carries the opening at the same distance from the start of its segment.

A drag on a door in a drawing becomes a `move-hosted-opening` with [`resolveViewEditCandidate`](/floorkit/api/editor/resolve-view-edit-candidate). Give it the current build. The drag starts at the saved offset, and the door stops at the nearest place where it fits, as in the live editor. A drag that cannot move the door gives the error `invalid-drag`.

```typescript theme={null}
import {
  resolveViewEditCandidate,
  type DrawingSourceRef,
  type ViewDrag,
  type ViewFrame,
} from '@opengeometry/floorkit';

export function dragDoor(
  session: BuildingSession,
  frame: ViewFrame,
  hit: DrawingSourceRef,
  drag: ViewDrag,
): void {
  const revision = session.revision;
  const command = resolveViewEditCandidate(
    session.snapshot(),
    frame,
    hit,
    drag,
    { kind: 'along-host-wall' },
    session.currentBuild,
  );
  session.apply(command, revision);
}
```

## A batch of commands

One change of the user can need several commands, such as a paste of several elements. Put them in one `batch`. The session applies the commands in order to one model, builds one time and adds one step to the history. One undo takes all of them back.

```typescript theme={null}
export function addColumns(session: BuildingSession, columns: BmColumn[]): void {
  session.apply(
    {
      kind: 'batch',
      commands: columns.map((column) => ({
        kind: 'add-element',
        buildingId: 'building',
        storeyId: 'ground',
        list: 'columns',
        element: column,
      })),
    },
    session.revision,
  );
}
```

* A batch is all or nothing. If one command fails, `apply` throws the error of that command and the session changes nothing. The message starts with the place of the command, such as `command 3 of 6:`.
* If one command has a wrong field or an unknown `kind`, the code is `invalid-command`, and the message starts with the place of the command too.
* Each command works on the result of the commands before it. Thus a batch can add a storey and then add an element to that storey.
* The model must be valid after each command, not only after the last one. For example, in a building with a mass, two commands change the heights of two storeys, and the storeys fit the mass only after the second command. The session rejects this batch at the first command.
* The rules of each command stay the same in a batch. For example, a `remove-asset` of an asset that an instance uses fails.
* A batch must have one command or more, and it cannot hold a batch. In TypeScript, a batch in a batch does not compile. A list with the type `BuildingEditCommand[]` does not go into a batch, because that type permits a batch. Give the list the type of its commands, such as `ElementListCommand[]`, or the type `BatchCommand['commands']`.
* `session.lastEditCommand` gives the batch.

## The revision number

A session has a `revision`. It goes up by one after each edit, undo or redo.

`apply` takes the revision that the edit started from. If a different edit came first, the number does not agree and `apply` throws `stale-revision`. This stops an old drag from overwriting a newer change.

Read the revision when the user starts the action. Do not read it again at the end.

## Undo and redo

```typescript theme={null}
export function undoLast(session: BuildingSession): void {
  if (session.canUndo) session.undo(session.revision);
}

export function redoLast(session: BuildingSession): void {
  if (session.canRedo) session.redo(session.revision);
}
```

A new edit clears the redo list. The history lives in the session and is not saved in the project file.

## Keep an edit fast

A host app sends many edits. These rules keep each edit short.

* Send one command through `apply` for one change of the user. If the change needs several commands, send one [`batch`](#a-batch-of-commands). After a command for one slab, roof, room, stair, column, beam, railing, block instance, loose door, loose window or basic shape, Floorkit builds again only that element. The other elements of the storey keep their geometry. After a command for a wall, or for a door or window in a wall, Floorkit joins the walls of the storey again and builds again only the walls, doors and windows that changed. A command that adds a storey or changes its settings builds that storey. A command that changes an asset, or the settings of a storey in a building with a mass, builds the whole building again. A batch builds again what its commands build, one time.
* Do not call `session.snapshot()`, `manager.snapshot()`, `manager.save()`, `saveProject`, `new BuildingSession(model)`, `drawProjectView` or an export at each edit or at each frame of a drag. Each of them copies, writes or builds the whole model. Save on an action of the user or on a timer.
* Do not open a new session for a change that a command covers, as `replaceBuilding` in [Start a building](/floorkit/guides/start-a-building#change-the-document) does. A new session builds the whole building and has an empty undo history. Open one to load or replace a whole building.
* Read the new revision from what `apply` returns, and read the built element from `session.currentBuild.elementsById`. Do not keep an element object across a change: read the element again by its id after each change. A changed slab, roof, stair, column, beam, railing, space, basic shape, block or loose door or window is a new object, and Floorkit disposes the old object. A changed wall, and a door, a window or a hole on a wall, can stay the same object with new geometry, or can be a new object. Do not call `snapshot()` to see the result. `snapshot()` copies the whole model.
* After a change, refresh only what the event names. The event of `session.subscribe` and of `manager.subscribe` has `changed`: the ids of the built elements that the step built again, added or removed. An element that the list does not name keeps its object. Read each id from `session.currentBuild.elementsById`, or from `session.currentBuild.modelObjectsById` for a basic shape. An id that is in neither map was removed. If `changed` is absent, the whole building was built again: refresh all.
* When a step fails, the model and the revision stay as they were. If the failed step already changed built elements, Floorkit builds them again from the saved model and sends one event with the kind `restore` (the cause `restore` from the manager) and with `changed`. Refresh these elements as after a step.

## Doors and windows follow their wall

When a wall moves, its doors and windows move with it and the wall is cut again. If the new wall is too short for an opening, Floorkit rejects the edit. It does not remove the opening without telling you.

## Interactive editors

You do not have to write commands by hand for pointer actions. Floorkit has two editors that make the commands for you. Give the same session to both, and the plan and the 3D view show the same building.

<CardGroup cols={2}>
  <Card title="Plan editor" icon="pen-ruler" href="/floorkit/guides/add-a-plan-editor">
    Edit walls and openings in a 2D plan.
  </Card>

  <Card title="3D editor" icon="cube" href="/floorkit/guides/add-a-3d-editor">
    Edit the same building in your Three.js view.
  </Card>
</CardGroup>


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