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

# Site massing

> Try a 600 m² site study. Change building size, check floor area and zoning limits, and compare design options with Floorkit.

export const LiveExample = ({path, title = 'Live example', height = 460}) => {
  return <Frame>
      <iframe src={`https://floorkit-examples.ploterr.com/floorkit/${path}?embed=1`} ref={frame => {
    if (!frame || !['localhost', '127.0.0.1'].includes(window.location.hostname)) return;
    const local = `http://localhost:5555/${path}?embed=1`;
    if (frame.src !== local) frame.src = local;
  }} title={title} loading="lazy" allow="fullscreen" style={{
    width: '100%',
    height: `${height}px`,
    border: 0,
    display: 'block'
  }} />
    </Frame>;
};

**Site massing** tests the size and placement of buildings before designing rooms and walls. Use simple volumes to explore floor area, height and the limits of a site.

Try this **600 m² parcel** with a shop, homes above it and a second building. No code is needed to use the example. Floorkit supplies the editable model and checks; your app supplies the site rules.

[Try the study](#try-the-worked-study) · [Build with an agent](#build-this-with-a-coding-agent) · [Developer recipes](#build-the-app)

## Try the worked study

Use **Controls** to edit buildings, review **Results**, or save and download **Options**.

<LiveExample path="playbooks/site-massing" title="Site massing study" height={680} />

### Compare two options

1. In **Edit**, choose **Add second building** once. Floor area becomes **384 m²** and FAR **0.64**.
2. In **Options**, name this **Baseline** and choose **Save option**.
3. Return to **Edit**, select the tower in **Building part**, enter **9 m** for **Part height**, and press Enter. Floor area becomes **432 m²**, FAR **0.72**, and the study reports one issue.
4. Save this as **Taller tower** in **Options**. Compare the cards or use **Reopen** to return to either design. To undo instead, close the Controls drawer with **×** if it is open, then choose **Undo**.

Switch between **Plan** and **3D** to edit the same design. Turn **Envelope on** in Edit to show the supplied setback and height limits.

## Understand the results

The example uses an illustrative FAR limit of **0.7**, coverage limit of **40%** and height limit of **15 m**. On this 600 m² parcel, FAR 0.7 allows **420 m² of counted floor area**. The baseline uses 384 m², leaving 36 m² below that limit. The taller option uses 432 m², exceeding it by 12 m² even though its height is below 15 m.

| State | Gross / counted floor area | FAR | Coverage | Maximum height | Supplied checks |
| - | - | - | - | - | - |
| Podium and tower only | 192 m² | 0.32 | 16% | 10 m | Pass |
| After adding the second building | 384 m² | 0.64 | 24% | 12 m | Pass |
| Tower part raised to 9 m | 432 m² | 0.72 | 24% | 13 m | `exceeds-far` |
| After undoing the height change | 384 m² | 0.64 | 24% | 12 m | Pass |

**Pass** means the design meets the checks encoded for this example. It does not establish planning approval, structural support, daylight, access or whether homes and parking spaces physically fit. Use the applicable site rules and record their source before applying the method to a real project.

To test another constraint, set **Front setback** to **5 m**. Both buildings are 4 m from the front edge, so both fail that setback check. **Undo** restores the original 3 m rule.

## Common questions

<AccordionGroup>
  <Accordion title="What should I change first?">
    Add the second building, then raise the tower part from 6 m to 9 m. Watch floor area and FAR
    change. Use Undo to return to the previous design.
  </Accordion>

  <Accordion title="What do FAR and coverage tell me?">
    FAR measures counted floor area across all floors relative to the parcel. Coverage measures land
    covered at ground level. The baseline uses 384 m² on a 600 m² parcel: FAR 0.64 and 24% coverage.
    Another floor can increase FAR without increasing coverage.
  </Accordion>

  <Accordion title="Why does the taller option fail?">
    The sample FAR limit allows 420 m². The taller option has 432 m², so it exceeds that limit by 12
    m². Its 13 m height is still below the 15 m height limit. Results shows the affected rule.
  </Accordion>

  <Accordion title="Are these the rules for my site?">
    No. They are sample inputs for learning. Use the applicable parcel measurements and planning
    controls for your site. A passing report covers the supplied checks; it is not planning
    approval. See the [calculation limits](#recheck-zoning-after-each-change) before relying on a
    result.
  </Accordion>

  <Accordion title="How do I compare and keep my designs?">
    In Options, save each design with a name. Reopen returns to an option saved in this session.
    Choose Saved options in Download to keep the designs and assumptions before closing the page.
    This example has no file-import control; a Floorkit app can reopen a downloaded project.
  </Accordion>

  <Accordion title="Does the home estimate prove how many homes fit?">
    No. The baseline has 288 m² of residential floor area. At 80% efficiency and 60 m² per home, the
    estimate gives three homes. It does not test room layouts, stairs, daylight or parking fit.
    Change those assumptions in Options.
  </Accordion>
</AccordionGroup>

## Understand the terms

| Term | Meaning in this study | Example |
| - | - | - |
| Parcel | The piece of land being studied. | 30 × 20 m = 600 m². |
| Footprint | The outline of a building part when seen from above. | The ground-level shop is 12 × 8 m = 96 m². |
| Storey | One floor or level of a building. | The smaller part above the shop has two storeys. |
| Podium and tower | A wider lower part with a smaller part stacked above it. | A shop below two residential floors. |
| Gross floor area | The sum of the modelled floor areas across all storeys. | The first building has 192 m² across three storeys. |
| FAR (floor area ratio) | The floor area counted by the rules divided by the parcel area. | 384 ÷ 600 = 0.64. |
| Site coverage | The share of the parcel covered by the building footprints at ground level. | 144 ÷ 600 = 24%. |
| Setback | A required distance between a building and a parcel edge. | A front setback of 3 m. |
| Buildable envelope | A volume showing the supplied setback and height constraints. | An outline in Plan and a volume in 3D. |

The study counts all gross floor area toward FAR. Actual area definitions and exclusions depend on the rules for your site.

## Build this with a coding agent

If you want your own site feasibility app, give a coding agent the brief below. For an AI agent that changes designs inside your app, also follow [Agent building workflow](/floorkit/agents/agent-building-workflow): your app provides the AI model and supported editing tools.

The recipes use massing APIs planned for **Floorkit 1.2.0**. Check the installed package and its `index.d.ts` against the [changelog](/floorkit/changelog) before using them. A docs page does not establish that the release is published.

Give the agent the [documentation index](https://docs.ploterr.com/llms.txt), [this playbook as Markdown](https://docs.ploterr.com/floorkit/playbooks/site-massing.md) and [documentation MCP setup](/floorkit/agents/use-a-coding-agent#connect-documentation-search-through-mcp). These resources help it read the APIs; the documentation MCP does not edit a design.

```text theme={null}
Build a browser-based site massing app with @opengeometry/floorkit.
Read https://docs.ploterr.com/llms.txt and
https://docs.ploterr.com/floorkit/playbooks/site-massing.md first.
Check the installed version and index.d.ts for the APIs used in this playbook.
If an API is missing, report it before implementing an alternative.

Reproduce the playbook's 30 × 20 m parcel at flat grade Y = 0:
a retail podium with a residential tower and a separate four-storey building.
Use the exact model, authored IDs, footprints and controls from the recipes.
Label the supplied FAR 0.7, coverage 40%, height 15 m, setbacks 3 m and
separation 4 m as illustrative. Do not invent rules for a real site.

Initialize the geometry kernel once. Keep one ProjectManager for the open
study and connect the plan and 3D editors to it. Apply supported commands
through the manager; preserve IDs and one undo history. Keep geometry in
metres, Y up, with footprint points [x, z]. Validate unknown JSON and check
stale proposals before applying agent edits, following Agent building workflow.

Show quantities, all named zoning violations and visible edit errors.
Verify the two-building baseline: 384 m², FAR 0.64, coverage 24%, height 12 m.
Set the tower part to 9 m: 432 m², FAR 0.72, total height 13 m, exceeds-far.
Undo must restore the baseline. Front setback 5 m must flag both buildings.

Save named options with their editable project, rules and assumptions.
Compare equivalent site/rule bases; flag different bases. Reopen an option,
restore its assumptions and state that saved files do not retain undo history.
Export editable JSON, a site plan PDF, envelope-free GLB and report CSV.
Treat estimated unit counts as arithmetic assumptions, not a physical fit test.

Run and verify locally. Report the package version, assumptions, unresolved
checks and verification results. Follow the hostname licensing requirements
before deploying. Do not claim the example establishes planning approval.
```

## Build the app

The following recipes are for developers. They reproduce the interactive example above. For a smaller first step, use [Model a mass](/floorkit/guides/model-a-mass).

### Prepare the app

Complete the [installation](/floorkit/installation) and initialize the geometry kernel before constructing a `ProjectManager`. You need a Three.js scene, a container for the plan editor, and a panel for quantities and violations. A deployed app also needs a [hostname license key](/floorkit/licensing).

Keep one `ProjectManager` per open option. Send changes through `manager.apply`, and connect both editors to that manager so the model, views, saving and undo share one history. Geometry uses metres, Y is up, and footprint points are `[x, z]`.

### Define the parcel and its controls

This worked study uses a 30 × 20 m parcel with a retail podium and residential tower. Its controls are illustrative inputs: FAR 0.7, coverage 40%, height 15 m, setbacks 3 m, and building separation 4 m. Supply the applicable values and keep their source and date in your app; Floorkit's presets contain example numbers.

The `lotLines` entries correspond to consecutive outer-ring edges, including the last edge back to the first point. Here the edge from `[0, 0]` to `[30, 0]` is the front. Explicit classifications let the zoning evaluator apply each class's setback directly.

The study assumes a surveyed parcel with no holes and **flat grade at world Y = 0**. FAR and coverage use the full net parcel area, 600 m², after subtracting any parcel holes. This is not the area inside the setbacks. If the applicable controls require deductions such as road reservations, establish that area basis in your app before comparing results. Record the rule source, effective date, grade datum and unresolved constraints alongside each study. The example labels its controls as illustrative; access, daylight, terrain and parking fit are not evaluated.

```typescript theme={null}
import {
  ProjectManager,
  basicZoningRules,
  evaluateZoning,
  irMassToMassLike,
  massBuildingCommand,
  massSegmentHeightCommand,
  type BuildingModel,
  type ManagedPlanEditor,
  type ProjectFile,
  type ZoningReport,
} from '@opengeometry/floorkit';

export const siteModel: BuildingModel = {
  schema: 'floorkit.building-model',
  version: 3,
  units: 'meters',
  upAxis: 'y',
  id: 'site-study',
  site: {
    id: 'parcel',
    boundary: {
      outerRing: [
        [0, 0],
        [30, 0],
        [30, 20],
        [0, 20],
      ],
    },
    lotLines: ['front', 'side', 'rear', 'side'],
    elevation: 0,
    trueNorthDeg: 0,
    zoning: basicZoningRules({
      id: 'study-controls',
      maxFAR: 0.7,
      maxCoverage: 0.4,
      maxHeight: 15,
      setbacks: { front: 3, side: 3, rear: 3 },
      minSeparation: 4,
      countsTowardFAR: ['gross'],
    }),
  },
  buildings: [
    {
      id: 'podium-tower',
      name: 'Podium and tower',
      storeys: [
        { id: 'ground', elevation: 0, height: 4, usage: 'retail' },
        { id: 'first', elevation: 4, height: 3, usage: 'residential' },
        { id: 'second', elevation: 7, height: 3, usage: 'residential' },
      ],
      mass: {
        id: 'podium-tower-mass',
        elevation: 0,
        segments: [
          {
            id: 'podium',
            name: 'Podium',
            footprint: {
              outerRing: [
                [4, 4],
                [16, 4],
                [16, 12],
                [4, 12],
              ],
            },
            height: 4,
            use: 'retail',
            storeyIds: ['ground'],
          },
          {
            id: 'tower',
            name: 'Tower',
            footprint: {
              outerRing: [
                [6, 5],
                [14, 5],
                [14, 11],
                [6, 11],
              ],
            },
            height: 6,
            use: 'residential',
            storeyIds: ['first', 'second'],
          },
        ],
      },
    },
  ],
};
```

A building holds one mass. Each mass is a stack of parts; each part names its storeys and has a height equal to their summed heights. Preserve these authored IDs when editing. Courtyards are `holes` in a segment footprint.

Two towers on a common podium are not one native branching mass. Use separate buildings and partition the shared podium so its floor area is counted once, or model one podium with separate towers starting at its top. Recheck collisions, separation and aggregate quantities. Never duplicate the full podium under each tower: that doubles its floor area. The worked study uses a single podium/tower stack and a separate building at grade.

### Open a project and show the site

Use a saved plan view with `showMassing: true`. It draws every building's mass, the parcel and the dashed setback boundary. The parcel and setbacks are display geometry and do not catch clicks.

```typescript theme={null}
export const siteProject: ProjectFile = {
  schema: 'floorkit.project',
  version: 3,
  buildingModel: siteModel,
  views: [
    {
      id: 'site-plan',
      name: 'Site massing',
      kind: 'plan',
      storeyId: 'ground',
      cutHeightAboveStorey: 1.2,
      showMassing: true,
    },
  ],
  paper: {
    version: 1,
    unitSystem: 'metric',
    sheets: [
      {
        id: 'site-sheet',
        name: 'Site study',
        number: 'S-01',
        page: {
          format: 'A3',
          orientation: 'landscape',
          size: { width: 420, height: 297 },
          margin: 10,
        },
        viewports: [
          {
            id: 'site-viewport',
            viewId: 'site-plan',
            drawingNumber: '1',
            frame: { x: 20, y: 20, width: 380, height: 245 },
            scale: 0.01,
            rotationDeg: 0,
            clip: true,
            title: { text: 'Site massing', showScale: true },
          },
        ],
        annotations: [],
        titleblock: { templateId: 'default', values: { sheetNumber: 'S-01' } },
      },
    ],
  },
};

export function openStudy(onError?: (error: unknown) => void): ProjectManager {
  const manager = new ProjectManager(siteProject, { onError });
  manager.setBuildableEnvelope(true);
  return manager;
}

export async function mountSitePlan(
  manager: ProjectManager,
  container: HTMLElement,
): Promise<ManagedPlanEditor> {
  return manager.mountPlanEditor(container, 'site-plan');
}
```

Add `manager.root` to your Three.js scene once. Bind a `GUIEditor` with `manager.bindSceneEditor` as in [Add a 3D editor](/floorkit/guides/add-a-3d-editor). The envelope switch shows the buildable volume in 3D and refreshes after a site edit. It is a display setting; enable it again after reopening. A dashed plan setback shows the boundary at grade, so also evaluate height and inclined envelope planes numerically.

Pass a visible error handler to `openStudy` so a refused editor action explains its reason beside the model. When both editors stay mounted, route history shortcuts through the app once and clear the inactive editor's selection; hiding its canvas alone does not disable its keyboard handlers.

### Add and edit buildings

For a known footprint, make a command from the current model. This second building has four residential storeys on a 6 × 8 m footprint, 4 m from the podium.

```typescript theme={null}
export function addSecondBuilding(manager: ProjectManager): void {
  const command = massBuildingCommand(
    manager.session.snapshot(),
    [
      [20, 4],
      [26, 4],
      [26, 12],
      [20, 12],
    ],
    { name: 'Second building', storeyHeight: 3, storeyCount: 4, use: 'residential' },
  );
  manager.apply(command);
}

export async function drawBuilding(plan: ManagedPlanEditor): Promise<string | undefined> {
  return plan.startMassDrawing({ storeyHeight: 3, storeyCount: 4, use: 'residential' });
}

export function raiseTower(manager: ProjectManager, height: number): void {
  const command = massSegmentHeightCommand(
    manager.session.snapshot(),
    'podium-tower',
    'tower',
    height,
  );
  if (command) manager.apply(command);
}
```

Call `addSecondBuilding` once for the worked study. For interactive creation, call `drawBuilding` instead: click to add corners, click the first corner or press Enter to finish, and Escape to cancel. Completion adds a new building as one undo step. `startOutlineDrawing` returns a polygon without saving it when your app needs its own creation flow.

| Action | Plan editor | 3D editor |
| - | - | - |
| Select a part | Click its outline or region | Click the part |
| Change the footprint | Drag a corner or edge anchor | Drag a footprint anchor |
| Move or turn | Use the selected part's controls | Use the selected part's controls |
| Change height | A number field calls `raiseTower` | Push/pull the height anchor |
| Delete | Delete removes the selected part | Delete removes the selected part |

Move, turn and Delete act on the selected part. Deleting the last part removes the mass; it does not remove the building. Use `remove-building` to remove a whole building. The manager refuses removal while a saved view references its storey; change or remove that view first and dispose any editor mounted on it. At least one building must remain.

A gesture saves one command and one undo step. Height changes snap to whole storeys; shrinking cannot drop a storey containing elements or referenced by a view. For a custom footprint or storey edit, send `replace-mass` with both the mass and the complete storey settings. Use `manager.undo()` and `manager.redo()` for the shared history.

Read the selected building and part before choosing the edit. Widen a ground-level footprint to increase floor area and coverage; widening only the tower increases floor area while this study's coverage stays unchanged. Add storeys to increase floor area and height without changing coverage. Inspect the affected quantities and named violations, then undo or retain the change. Geometry accepted by the editor does not establish structural support, circulation, daylight, core layout or parking fit.

### Recheck zoning after each change

Evaluate all masses together, so the result includes total ratios, collisions and separation. Model validation and zoning are separate: the SDK allows a design outside the parcel or over the limits so the user can inspect and repair it.

```typescript theme={null}
export function studyReport(manager: ProjectManager): ZoningReport {
  const { site, buildings } = manager.session.snapshot();
  if (!site?.boundary || !site.zoning) throw new Error('The study needs a parcel and controls.');
  const masses = buildings.flatMap((building) =>
    building.mass ? [irMassToMassLike(building)] : [],
  );
  return evaluateZoning(
    { profile: site.boundary, lotLines: site.lotLines, elevation: site.elevation },
    masses,
    site.zoning,
  );
}

export function watchStudy(
  manager: ProjectManager,
  render: (report: ZoningReport) => void,
): () => void {
  render(studyReport(manager));
  return manager.subscribe(() => render(studyReport(manager)));
}
```

Display `metrics`, `compliant`, and the full `violations` list. Each violation can name a `ruleId`, `massId`, pair of `masses`, or lot `edgeIndex`, plus `current` and `limit` when available. `failureClass` identifies one priority failure for a repair loop; it does not replace the list. An incomplete rule basis can produce `missing-zoning-basis`.

`watchStudy` refreshes after committed edits, undo and redo. It returns an unsubscribe function; call it when closing the study. Do not apply another command synchronously inside a subscriber. During a drag, the editor's `onDrag` exposes the live target before commit: read the live mass's `quantities()` for an area preview, then use `studyReport` for the saved design. The saved snapshot still describes the previous committed state during the gesture.

Compare the report with the [expected study results](#understand-the-results).

<Warning>
  Coverage currently counts a mass's lowest footprint only when its base elevation equals site
  grade. A mass starting above or below grade can understate coverage. The worked study keeps every
  mass base at grade; check coverage separately for basements, raised towers and split levels. The
  grade setback picture also does not establish compliance with an inclined envelope at the top of a
  building.
</Warning>

### Change the parcel or a rule

Build a site form in your app. Managed editors have no site-boundary anchors. `replace-site` replaces the whole site, so keep the fields you intend to retain.

```typescript theme={null}
export function setFrontSetback(manager: ProjectManager, metres: number): void {
  const site = manager.session.snapshot().site;
  if (!site?.zoning) throw new Error('The study needs zoning controls.');
  const rules = site.zoning.rules.map((rule) =>
    rule.kind === 'envelope-plane' && rule.edgeClass === 'front'
      ? { ...rule, offsetAtGrade: metres }
      : rule,
  );
  manager.apply({ kind: 'replace-site', site: { ...site, zoning: { ...site.zoning, rules } } });
}
```

Set the front setback to 5 m. Both buildings start at `z = 4`, so the report shows setback violations and the displayed boundary changes. Undo restores the 3 m control. Keep geographic coordinates in `geoAnchor` and local metre coordinates in the boundary; `trueNorthDeg` records north, but does not provide a sun or shadow analysis.

### Estimate feasibility and compare options

Use `report.metrics.perMass` for the area by storey, use and area kind. Gross floor area and counted floor area are different: `countsTowardFAR` defaults to `['gross']`. Mark parking, basement, core or mechanical storeys with `areaKind` through `replace-storey-settings`, supplying all their settings. Labels such as `usage: 'parking'` describe use; set the area kind explicitly when the counting assumption matters.

A storey's floor area is its segment footprint area after subtracting courtyard holes; gross floor area sums those storey areas. Programme use describes what the area is for, while `areaKind` controls its counting category. An explicit `areaKind` takes priority over inference. Automatic basement classification requires the whole storey's top to be at or below grade, not merely its base.

Keep efficiency, average unit size, parking ratios and targets in your app. For example, after adding the second building the residential area is 288 m² (96 m² in the tower and 192 m² in the second building). With an assumed 80% efficiency and 60 m² average unit size, it gives 230.4 m² estimated usable residential area and three whole units.

```typescript theme={null}
export interface StudyAssumptions {
  residentialEfficiency: number;
  averageUnitArea: number;
}

export const defaultAssumptions: StudyAssumptions = {
  residentialEfficiency: 0.8,
  averageUnitArea: 60,
};

export function residentialEstimate(report: ZoningReport, assumptions = defaultAssumptions) {
  const grossResidentialArea = report.metrics.perMass.reduce(
    (sum, mass) => sum + (mass.grossFloorAreaByUse.residential ?? 0),
    0,
  );
  if (
    !Number.isFinite(assumptions.residentialEfficiency) ||
    assumptions.residentialEfficiency <= 0 ||
    assumptions.residentialEfficiency > 1 ||
    !Number.isFinite(assumptions.averageUnitArea) ||
    assumptions.averageUnitArea <= 0
  )
    throw new Error(
      'Efficiency must be above 0 and at most 1; average unit area must be positive.',
    );
  const estimatedUsableArea = grossResidentialArea * assumptions.residentialEfficiency;
  return {
    grossResidentialArea,
    estimatedUsableArea,
    estimatedUnits: Math.floor(estimatedUsableArea / assumptions.averageUnitArea),
  };
}
```

Label these as estimates and show the assumptions beside the results. An efficiency multiplier does not measure NIA or prove that units, cores or parking spaces fit.

Save each option as a separate project with `manager.save()`. The project format holds one building model; store option names, comparison rows and assumptions in your app rather than adding unknown fields to Floorkit's JSON. Compare the reports under the same site and rule set. Use `ProjectManager.open(text)` to reopen an option, construct the replacement before disposing the old manager, and remount its editors. Undo history is not saved.

```typescript theme={null}
export interface StudyOption {
  name: string;
  projectText: string;
  assumptions: StudyAssumptions;
  siteBasis: string;
  report: ZoningReport;
}

export function captureOption(
  manager: ProjectManager,
  name: string,
  assumptions: StudyAssumptions,
): StudyOption {
  const report = studyReport(manager);
  residentialEstimate(report, assumptions);
  return {
    name,
    projectText: manager.save(),
    assumptions: { ...assumptions },
    siteBasis: JSON.stringify(manager.session.snapshot().site),
    report,
  };
}
```

For a concrete comparison, add the second building and save **Baseline**. Raise the tower to 9 m and save **Taller tower**. Keep efficiency at 80% and average unit area at 60 m² for both: Baseline has 384 m², FAR 0.64 and three estimated units; Taller tower has 432 m², FAR 0.72, four estimated units and an FAR violation. Both have 24% coverage. Show the height, counted area and complete violations beside these figures. If an option's `siteBasis` differs, mark it as a different study basis instead of ranking it as an equivalent alternative. Save the app's option records with their assumptions alongside each editable project.

### Deliver the study

Save the editable project as the source of the design. Use a sheet viewport referencing `site-plan` to deliver a site plan PDF; `showMassing` carries through to sheets and PDF. See [Compose a sheet](/floorkit/guides/compose-a-sheet) and [Export a PDF](/floorkit/guides/export-a-pdf).

The worked project includes an A3 site sheet at 1:100. `manager.exportSheet('site-sheet', 'pdf')` exports it. Deliver the option records, assumption and rule basis, and comparison report alongside the editable project and drawings.

For a GLB, follow [Export a GLB](/floorkit/guides/export-a-glb), passing `manager.root` to Three.js's exporter. Call `manager.setBuildableEnvelope(false)` before exporting the root: a raw GLB includes the visible envelope. GLB keeps geometry and colours; the project JSON keeps the editable design. Your app can export the report and comparison table as CSV.

When moving to detailed design, `massToShell` produces walls, slabs and flat roofs. It reuses storey IDs and creates a different building ID. Replace the mass building in a copied project, update views that explicitly name the old building, then open the new project. This starts a new history; keep the massing option separately. See [Turn the mass into walls, slabs and roofs](/floorkit/guides/model-a-mass#turn-the-mass-into-walls-slabs-and-roofs).

Sections and elevations exclude masses. Use the massing plan and 3D view for this stage, or create a shell for physical cuts. Terrain, neighbour context, map tiles and sun/shadow analysis require app-side work.

### Check the finished workflow

* Open the worked project, add the second building and confirm the pass row above.
* Raise the tower to 9 m, show `exceeds-far`, and undo to the previous quantities.
* Draw another building; Escape cancels without saving, while completion is one undo step.
* Select and reshape the tower in 2D, then change its height in 3D; confirm both views follow the same model.
* Change the front setback to 5 m, show the violations, and undo the rule change.
* Save and reopen; confirm the site, rules, masses and massing plan persist, then enable the envelope again.
* Export the site plan PDF and a GLB with the envelope hidden; retain the editable project alongside them.

## API references

* [Site](/floorkit/api/world/site) and [zoning rules](/floorkit/api/world/zoning-rules)
* [Zoning evaluation](/floorkit/api/world/zoning-evaluation) and [buildable envelope](/floorkit/api/world/zoning-envelope)
* [Massing helpers](/floorkit/api/architecture/massing-helpers) and [Building quantities](/floorkit/api/architecture/building)
* [ProjectManager](/floorkit/api/editor/project-manager) and [ManagedPlanEditor](/floorkit/api/editor/managed-plan-editor)


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