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

# Place furniture

> Put a block of furniture or a fixture on a storey, move it, turn it and mirror it.

A block is one piece of furniture or one fixture, such as a bathtub, a sofa or a desk. The building model keeps the definition of the block one time, as an asset. Each placed copy is an entry in the `instances` list of a storey.

[Live example](https://floorkit-examples.ploterr.com/floorkit/guides/place-furniture?embed=1)

## Before you start

You have a building model with a storey `ground`. See [Start a building](https://docs.ploterr.com/floorkit/guides/start-a-building.md). You also have the `.op` text of a block, for example `floorkit/bathtub/1.0.0.op` of the Floorkit block catalogue.

## Give the block source

An `external-op` asset names its `.op` file by a uri. Floorkit does not download the file. Give its text in `assetSources`, keyed by the uri, when you open the session.

```typescript theme={null}
import type * as THREE from 'three';
import { BuildingSession, type BmAssetInstance, type BuildingModel } from '@opengeometry/floorkit';

export const bathtubUri = 'floorkit/bathtub/1.0.0.op';

export function startFurnishedBuilding(
  scene: THREE.Scene,
  model: BuildingModel,
  bathtubText: string,
): BuildingSession {
  const session = new BuildingSession(model, { assetSources: { [bathtubUri]: bathtubText } });
  scene.add(session.root);
  return session;
}
```

An external asset with no source stops the build with an `asset-source-missing` error that names the uri.

## Add the asset and the block

`add-asset` adds the definition. `add-element` on the `instances` list places a copy. `assetRef` is the id of the asset. `position` is the center of the block in metres: `x` and `z` on the storey, `y` above the storey. Send both commands in one `batch`: one undo takes both back.

```typescript theme={null}
export const bathtub: BmAssetInstance = {
  id: 'bathtub',
  assetRef: 'bathtub',
  position: [0.925, 0, 0.45],
};

export function addBathtub(session: BuildingSession): void {
  session.apply(
    {
      kind: 'batch',
      commands: [
        { kind: 'add-asset', assetId: 'bathtub', asset: { kind: 'external-op', uri: bathtubUri } },
        {
          kind: 'add-element',
          buildingId: 'building',
          storeyId: 'ground',
          list: 'instances',
          element: bathtub,
        },
      ],
    },
    session.revision,
  );
}
```

One asset can have many blocks. Add the next copy with `add-element` only.

```typescript theme={null}
export function placeBlock(session: BuildingSession, block: BmAssetInstance): void {
  session.apply(
    {
      kind: 'add-element',
      buildingId: 'building',
      storeyId: 'ground',
      list: 'instances',
      element: block,
    },
    session.revision,
  );
}
```

## Move and turn a block

A block has no transform command. Send `replace-element` with a new `position` and, for a turn, a new `rotationYDeg`. A block turns about the vertical axis only.

```typescript theme={null}
export function moveBlock(
  session: BuildingSession,
  block: BmAssetInstance,
  x: number,
  z: number,
  rotationYDeg: number,
): void {
  session.apply(
    {
      kind: 'replace-element',
      buildingId: 'building',
      storeyId: 'ground',
      list: 'instances',
      elementId: block.id,
      element: { ...block, position: [x, block.position[1], z], rotationYDeg },
    },
    session.revision,
  );
}
```

In the plan editor and the 3D editor, you can move and turn a selected block. One drag is one step, and one undo puts the block back. The Delete key removes the block.

## Mirror a block

A negative X or Z scale mirrors the block. `[-1, 1, 1]` mirrors it from left to right in the same box.

```typescript theme={null}
export function mirrorBlock(session: BuildingSession, block: BmAssetInstance): void {
  const [x, y, z] = block.scale ?? [1, 1, 1];
  session.apply(
    {
      kind: 'replace-element',
      buildingId: 'building',
      storeyId: 'ground',
      list: 'instances',
      elementId: block.id,
      element: { ...block, scale: [-x, y, z] },
    },
    session.revision,
  );
}
```

* The scale has one size on all three axes, such as `[1.5, 1.5, 1.5]` or `[-1, 1, 1]`.
* Y stays positive, so the block stays upright. A zero is not a scale.
* The session refuses any other scale with a `bad-scale` issue at the path of the scale, for example `[-1, 2, 1]`.

## Save and open again

A `ProjectManager` saves the `.op` text of each external asset in `assetSources` of the project file, keyed by its uri. Thus the saved text opens again with no other input.

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

export function saveFurnishedProject(project: ProjectFile, bathtubText: string): string {
  const manager = new ProjectManager(project, {
    build: { assetSources: { [bathtubUri]: bathtubText } },
  });
  try {
    return manager.save();
  } finally {
    manager.dispose();
  }
}

export function openFurnishedProject(text: string): ProjectManager {
  return ProjectManager.open(text);
}
```

A source that you pass in `build.assetSources` wins over the saved one, and the next save keeps it.

## Draw the blocks

A plan draws the plan symbol of each block. A section or an elevation draws the 3D body of each block, also a mirrored or scaled one. `drawProjectView` and `exportProjectSheet` build the model again with the sources saved in the project file. Sources that you pass in `assetSources` replace the saved ones.

```typescript theme={null}
import { drawProjectView, openProject } from '@opengeometry/floorkit';

export function drawFurnishedView(savedText: string, viewId: string) {
  return drawProjectView(openProject(savedText), viewId);
}
```

* In a section, a block draws in projection with the projection pen. The section line does not cut it: there is no cut line and no poché. A block hides what is behind it.
* In a plan, a room label keeps off the blocks in the room where the room has space.

## Rules

* `position.y` is the height above the storey. The live block is at the storey elevation plus this height.
* `ifcClass` of the live block is the IFC class that the `.op` file names in `metadata['ifc:class']`, such as `IfcSanitaryTerminal`. It is undefined when the file names none.
* An `embedded-op` asset keeps the whole definition in the model, and needs no source. A definition that is not a valid block is refused as `bad-asset-definition`.
* `remove-asset` refuses an asset that a block uses.
* An inspector changes a block with `manager.replaceFields(liveId, patch)`. The keys are `labelName`, `placement.position`, `placement.rotation`, `placement.scale` and `propertySets`. `placement.position` is in world metres: its `y` saves as the height above the storey. A rotation with an X or Z turn is refused.

## Next

> **[Cut a section](https://docs.ploterr.com/floorkit/guides/cut-a-section.md)**
>
> Draw the blocks in a section and an elevation.

> **[OpenElement API](https://docs.ploterr.com/floorkit/api/architecture/open-element.md)**
>
> See the block definition and its fields.


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