Skip to main content
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

1

Check

The session checks the command and the model that results from it.
2

Build

It builds the new geometry apart from your scene. Walls join again and openings cut again.
3

Publish

If the build is good, the session puts the new geometry below session.root and adds the edit to the history.
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

The commands

See each field in the 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.
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 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. 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.

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

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

Plan editor

Edit walls and openings in a 2D plan.

3D editor

Edit the same building in your Three.js view.
Last modified on October 16, 2026